feat(npm-aqua-compiler): create package (#401)

* Add npm-aqua-compiler package

* Release new package

* Remove noUncheckedIndexedAccess from tsconfig.json

* Fix a test script

* Fix length checks

* Fix

* Update error description

* Try to choose a nicer err message

* New import format and API

* Fix error message

* Improve test

* Don't add empty string key when globalImports prop is empty

* Fix exports
This commit is contained in:
Akim 2023-12-15 23:14:07 +07:00 committed by GitHub
parent ac407c204d
commit d6008110cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1653 additions and 154 deletions

View File

@ -20,13 +20,7 @@
], ],
"ignorePatterns": ["**/node_modules/", "**/dist/", "**/build/", "**/public/"], "ignorePatterns": ["**/node_modules/", "**/dist/", "**/build/", "**/public/"],
"rules": { "rules": {
"eqeqeq": [ "eqeqeq": ["error", "always"],
"error",
"always",
{
"null": "ignore"
}
],
"no-console": ["error"], "no-console": ["error"],
"arrow-body-style": ["error", "always"], "arrow-body-style": ["error", "always"],
"no-empty": [ "no-empty": [

View File

@ -12,6 +12,7 @@
"packages/core/js-client-isomorphic": {}, "packages/core/js-client-isomorphic": {},
"packages/core/marine-worker": {}, "packages/core/marine-worker": {},
"packages/core/aqua-to-js": {}, "packages/core/aqua-to-js": {},
"packages/core/interfaces": {} "packages/core/interfaces": {},
"packages/core/npm-aqua-compiler": {}
} }
} }

View File

@ -3,5 +3,6 @@
"packages/core/marine-worker": "0.5.0", "packages/core/marine-worker": "0.5.0",
"packages/core/aqua-to-js": "0.3.4", "packages/core/aqua-to-js": "0.3.4",
"packages/core/js-client-isomorphic": "0.3.0", "packages/core/js-client-isomorphic": "0.3.0",
"packages/core/interfaces": "0.9.0" "packages/core/interfaces": "0.9.0",
"packages/core/npm-aqua-compiler": "0.0.0"
} }

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from "node:assert";
import { dirname, join } from "path"; import { dirname, join } from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
@ -45,6 +46,8 @@ const test = async () => {
const browser = await puppeteer.launch(); const browser = await puppeteer.launch();
const page = (await browser.pages())[0]; const page = (await browser.pages())[0];
assert(page);
page.on("console", (message) => { page.on("console", (message) => {
console.log(`${message.type().toUpperCase()}: ${message.text()}`); console.log(`${message.type().toUpperCase()}: ${message.text()}`);
}); });
@ -77,7 +80,7 @@ const test = async () => {
await browser.close(); await browser.close();
await stopServer(localServer); await stopServer(localServer);
if (content == null) { if (content === null || content === undefined) {
throw new Error("smoke test failed!"); throw new Error("smoke test failed!");
} }
}; };

View File

@ -35,7 +35,7 @@ export function genTypeName(
const args = const args =
item.tag === "labeledProduct" ? Object.values(item.fields) : item.items; item.tag === "labeledProduct" ? Object.values(item.fields) : item.items;
if (args.length === 1) { if (args.length === 1 && "0" in args) {
return genTypeName(args[0], name); return genTypeName(args[0], name);
} }
@ -112,7 +112,7 @@ export function typeToTs(t: NonArrowType | ArrowType): string {
const retType = const retType =
codomain.tag === "nil" codomain.tag === "nil"
? "void" ? "void"
: codomain.items.length === 1 : codomain.items.length === 1 && "0" in codomain.items
? typeToTs(codomain.items[0]) ? typeToTs(codomain.items[0])
: typeToTs(codomain); : typeToTs(codomain);

View File

@ -132,7 +132,7 @@ export class TSTypeGenerator implements TypeGenerator {
]; ];
const registerServiceArgs = const registerServiceArgs =
srvDef.defaultServiceId == null srvDef.defaultServiceId === undefined
? functionOverloadsWithoutDefaultServiceId ? functionOverloadsWithoutDefaultServiceId
: functionOverloadsWithDefaultServiceId; : functionOverloadsWithDefaultServiceId;

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from "assert";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
@ -86,7 +85,7 @@ export function recursiveRenameLaquaProps(obj: JSONValue): unknown {
// Last part of the property separated by "_" is a correct name // Last part of the property separated by "_" is a correct name
const refinedProperty = prop.split("_").pop(); const refinedProperty = prop.split("_").pop();
if (refinedProperty == null) { if (refinedProperty === undefined) {
throw new Error(`Bad property name: ${prop}.`); throw new Error(`Bad property name: ${prop}.`);
} }
@ -95,11 +94,15 @@ export function recursiveRenameLaquaProps(obj: JSONValue): unknown {
} }
} }
assert(accessProp in obj); const laquaProp = obj[accessProp];
if (laquaProp === undefined) {
return acc;
}
return { return {
...acc, ...acc,
[accessProp]: recursiveRenameLaquaProps(obj[accessProp]), [accessProp]: recursiveRenameLaquaProps(laquaProp),
}; };
}, {}); }, {});
} }

View File

@ -1,7 +1,6 @@
{ {
"extends": "../../../tsconfig.json", "extends": "../../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"outDir": "./dist" "outDir": "./dist"
}, },

View File

@ -36,7 +36,7 @@ export const fetchResource: FetchResourceFn = async (pkg, assetPath) => {
const packagePath = matches?.[0]; const packagePath = matches?.[0];
if (packagePath == null) { if (packagePath === undefined) {
throw new Error(`Cannot find dependency ${name} in path ${posixPath}`); throw new Error(`Cannot find dependency ${name} in path ${posixPath}`);
} }

View File

@ -18,7 +18,7 @@ import { fileURLToPath } from "url";
import { compileFromPath } from "@fluencelabs/aqua-api"; import { compileFromPath } from "@fluencelabs/aqua-api";
import { ServiceDef } from "@fluencelabs/interfaces"; import { ServiceDef } from "@fluencelabs/interfaces";
import { describe, expect, it } from "vitest"; import { assert, describe, expect, it } from "vitest";
import { v5_registerService } from "./api.js"; import { v5_registerService } from "./api.js";
import { callAquaFunction } from "./compilerSupport/callFunction.js"; import { callAquaFunction } from "./compilerSupport/callFunction.js";
@ -65,8 +65,12 @@ describe("User API methods", () => {
const typedServices: Record<string, ServiceDef> = services; const typedServices: Record<string, ServiceDef> = services;
assert("demoCalc" in functions);
const { script } = functions["demoCalc"]; const { script } = functions["demoCalc"];
assert("Calc" in typedServices);
v5_registerService([peer, "calc", calcService], { v5_registerService([peer, "calc", calcService], {
defaultServiceId: "calc", defaultServiceId: "calc",
functions: typedServices["Calc"].functions, functions: typedServices["Calc"].functions,

View File

@ -26,12 +26,13 @@ import { z } from "zod";
import { CallAquaFunctionConfig } from "./compilerSupport/callFunction.js"; import { CallAquaFunctionConfig } from "./compilerSupport/callFunction.js";
import { import {
aqua2js, aqua2js,
SchemaValidationError,
js2aqua, js2aqua,
SchemaValidationError,
wrapJsFunction, wrapJsFunction,
} from "./compilerSupport/conversions.js"; } from "./compilerSupport/conversions.js";
import { ServiceImpl, UserServiceImpl } from "./compilerSupport/types.js"; import { ServiceImpl, UserServiceImpl } from "./compilerSupport/types.js";
import { FluencePeer } from "./jsPeer/FluencePeer.js"; import { FluencePeer } from "./jsPeer/FluencePeer.js";
import { zip } from "./util/utils.js";
import { callAquaFunction, Fluence, registerService } from "./index.js"; import { callAquaFunction, Fluence, registerService } from "./index.js";
@ -74,51 +75,48 @@ export const v5_callFunction = async (
); );
} }
const argNames = Object.keys(
def.arrow.domain.tag === "nil" ? [] : def.arrow.domain.fields,
);
const schemaArgCount = argNames.length;
type FunctionArg = SimpleTypes | ArrowWithoutCallbacks;
const schemaFunctionArgs: Record<string, FunctionArg> = const schemaFunctionArgs: Record<string, FunctionArg> =
def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields; def.arrow.domain.tag === "nil" ? {} : def.arrow.domain.fields;
const schemaFunctionArgEntries = Object.entries(schemaFunctionArgs);
const schemaArgCount = schemaFunctionArgEntries.length;
type FunctionArg = SimpleTypes | ArrowWithoutCallbacks;
// if there are more args than expected in schema (schemaArgCount) then last arg is config // if there are more args than expected in schema (schemaArgCount) then last arg is config
const config = schemaArgCount < rest.length ? rest.pop() : undefined; const config = schemaArgCount < rest.length ? rest.pop() : undefined;
validateAquaConfig(config); validateAquaConfig(config);
const callArgs = Object.fromEntries<JSONValue | ServiceImpl[string]>( const callArgs = Object.fromEntries<JSONValue | ServiceImpl[string]>(
rest.slice(0, schemaArgCount).map((arg, i) => { zip(rest.slice(0, schemaArgCount), schemaFunctionArgEntries).map(
const argName = argNames[i]; ([arg, [argName, argType]]) => {
const argSchema = schemaFunctionArgs[argName]; if (argType.tag === "arrow") {
if (typeof arg !== "function") {
throw new SchemaValidationError(
[argName],
argType,
"function",
arg,
);
}
if (argSchema.tag === "arrow") { return [argName, wrapJsFunction(arg, argType)];
if (typeof arg !== "function") { }
if (typeof arg === "function") {
throw new SchemaValidationError( throw new SchemaValidationError(
[argName], [argName],
argSchema, argType,
"function", "non-function value",
arg, arg,
); );
} }
return [argName, wrapJsFunction(arg, argSchema)]; return [argName, js2aqua(arg, argType, { path: [def.functionName] })];
} },
),
if (typeof arg === "function") {
throw new SchemaValidationError(
[argName],
argSchema,
"non-function value",
arg,
);
}
return [argName, js2aqua(arg, argSchema, { path: [def.functionName] })];
}),
); );
const returnTypeVoid = const returnTypeVoid =
@ -126,7 +124,8 @@ export const v5_callFunction = async (
const returnSchema = const returnSchema =
def.arrow.codomain.tag === "unlabeledProduct" && def.arrow.codomain.tag === "unlabeledProduct" &&
def.arrow.codomain.items.length === 1 def.arrow.codomain.items.length === 1 &&
"0" in def.arrow.codomain.items
? def.arrow.codomain.items[0] ? def.arrow.codomain.items[0]
: def.arrow.codomain; : def.arrow.codomain;
@ -145,7 +144,7 @@ export const v5_callFunction = async (
}; };
const getDefaultPeer = (): FluencePeer => { const getDefaultPeer = (): FluencePeer => {
if (Fluence.defaultClient == null) { if (Fluence.defaultClient === undefined) {
throw new Error( throw new Error(
"Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?", "Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?",
); );
@ -155,7 +154,7 @@ const getDefaultPeer = (): FluencePeer => {
}; };
const getDefaultServiceId = (def: ServiceDef) => { const getDefaultServiceId = (def: ServiceDef) => {
if (def.defaultServiceId == null) { if (def.defaultServiceId === undefined) {
throw new Error("Service ID is not provided"); throw new Error("Service ID is not provided");
} }
@ -204,13 +203,18 @@ export const v5_registerService = (
// Wrapping service functions, selecting only those listed in schema, to convert their args js -> aqua and backwards // Wrapping service functions, selecting only those listed in schema, to convert their args js -> aqua and backwards
const wrappedServiceImpl = Object.fromEntries( const wrappedServiceImpl = Object.fromEntries(
Object.keys(serviceSchema).map((schemaKey) => { Object.entries(serviceSchema).map(([schemaKey, schemaValue]) => {
const serviceImplValue = serviceImpl[schemaKey];
if (serviceImplValue === undefined) {
throw new Error(
`Service function ${schemaKey} listed in Aqua schema but wasn't provided in schema implementation object or class instance. Check that your Aqua service definition matches passed service implementation`,
);
}
return [ return [
schemaKey, schemaKey,
wrapJsFunction( wrapJsFunction(serviceImplValue.bind(serviceImpl), schemaValue),
serviceImpl[schemaKey].bind(serviceImpl),
serviceSchema[schemaKey],
),
] as const; ] as const;
}), }),
); );

View File

@ -60,7 +60,7 @@ export const makeClientPeerConfig = async (
relayConfig: { relayConfig: {
peerId: keyPair.getLibp2pPeerId(), peerId: keyPair.getLibp2pPeerId(),
relayAddress: relayAddress, relayAddress: relayAddress,
...(config.connectionOptions?.dialTimeoutMs != null ...(config.connectionOptions?.dialTimeoutMs !== undefined
? { ? {
dialTimeout: config.connectionOptions.dialTimeoutMs, dialTimeout: config.connectionOptions.dialTimeoutMs,
} }

View File

@ -15,7 +15,7 @@
*/ */
import { JSONValue } from "@fluencelabs/interfaces"; import { JSONValue } from "@fluencelabs/interfaces";
import { it, describe, expect } from "vitest"; import { it, describe, expect, assert } from "vitest";
import { ExpirationError } from "../../jsPeer/errors.js"; import { ExpirationError } from "../../jsPeer/errors.js";
import { CallServiceData } from "../../jsServiceHost/interfaces.js"; import { CallServiceData } from "../../jsServiceHost/interfaces.js";
@ -103,6 +103,7 @@ describe("FluenceClient usage test suite", () => {
callback: { callback: {
callback: (args): undefined => { callback: (args): undefined => {
const [val] = args; const [val] = args;
assert(val);
resolve(val); resolve(val);
}, },
error: (args): undefined => { error: (args): undefined => {

View File

@ -20,6 +20,6 @@ export const nodes = [
"/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", "/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR",
}, },
]; ] as const;
export const RELAY = nodes[0].multiaddr; export const RELAY = nodes[0].multiaddr;

View File

@ -86,7 +86,7 @@ export const checkConnection = async (
const [val] = args; const [val] = args;
setTimeout(() => { setTimeout(() => {
resolve(val); resolve(val ?? null);
}, 0); }, 0);
return {}; return {};

View File

@ -76,7 +76,7 @@ const structs = [
c: [null, 2], c: [null, 2],
}, },
}, },
]; ] as const;
const labeledProduct2 = { const labeledProduct2 = {
tag: "labeledProduct", tag: "labeledProduct",
@ -167,7 +167,7 @@ const nestedStructs = [
c: [], c: [],
}, },
}, },
]; ] as const;
interface ConversionTestArgs { interface ConversionTestArgs {
aqua: JSONValue; aqua: JSONValue;

View File

@ -25,6 +25,8 @@ import {
UnlabeledProductType, UnlabeledProductType,
} from "@fluencelabs/interfaces"; } from "@fluencelabs/interfaces";
import { zip } from "../util/utils.js";
import { ServiceImpl, UserServiceImpl } from "./types.js"; import { ServiceImpl, UserServiceImpl } from "./types.js";
export class SchemaValidationError extends Error { export class SchemaValidationError extends Error {
@ -103,10 +105,10 @@ export function aqua2js(
throw new SchemaValidationError([], schema, "array", value); throw new SchemaValidationError([], schema, "array", value);
} }
if (value.length === 0) { if ("0" in value) {
return null;
} else {
return aqua2js(value[0], schema.type); return aqua2js(value[0], schema.type);
} else {
return null;
} }
} else if ( } else if (
schema.tag === "scalar" || schema.tag === "scalar" ||
@ -127,17 +129,23 @@ export function aqua2js(
throw new SchemaValidationError([], schema, "array", value); throw new SchemaValidationError([], schema, "array", value);
} }
return value.map((y, i) => { return zip(value, schema.items).map(([v, s]) => {
return aqua2js(y, schema.items[i]); return aqua2js(v, s);
}); });
} else if (["labeledProduct", "struct"].includes(schema.tag)) { } else if (["labeledProduct", "struct"].includes(schema.tag)) {
if (typeof value !== "object" || value == null || Array.isArray(value)) { if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new SchemaValidationError([], schema, "object", value); throw new SchemaValidationError([], schema, "object", value);
} }
return Object.fromEntries( return Object.fromEntries(
Object.entries(schema.fields).map(([key, type]) => { Object.entries(schema.fields).map(([key, type]) => {
const val = aqua2js(value[key], type); let v = value[key];
if (v === undefined) {
v = null;
}
const val = aqua2js(v, type);
return [key, val]; return [key, val];
}), }),
); );
@ -159,7 +167,7 @@ export function js2aqua(
return value; return value;
} else if (schema.tag === "option") { } else if (schema.tag === "option") {
// option means 'type | null' // option means 'type | null'
return value == null ? [] : [js2aqua(value, schema.type, { path })]; return value === null ? [] : [js2aqua(value, schema.type, { path })];
} else if (schema.tag === "topType") { } else if (schema.tag === "topType") {
// topType equals to 'any' // topType equals to 'any'
return value; return value;
@ -181,8 +189,8 @@ export function js2aqua(
throw new SchemaValidationError(path, schema, "array", value); throw new SchemaValidationError(path, schema, "array", value);
} }
return value.map((y, i) => { return zip(value, schema.items).map(([v, s], i) => {
return js2aqua(y, schema.items[i], { path: [...path, `[${i}]`] }); return js2aqua(v, s, { path: [...path, `[${i}]`] });
}); });
} else if (["labeledProduct", "struct"].includes(schema.tag)) { } else if (["labeledProduct", "struct"].includes(schema.tag)) {
if (typeof value !== "object" || value === null || Array.isArray(value)) { if (typeof value !== "object" || value === null || Array.isArray(value)) {
@ -191,7 +199,13 @@ export function js2aqua(
return Object.fromEntries( return Object.fromEntries(
Object.entries(schema.fields).map(([key, type]) => { Object.entries(schema.fields).map(([key, type]) => {
const val = js2aqua(value[key], type, { path: [...path, key] }); let v = value[key];
if (v === undefined) {
v = null;
}
const val = js2aqua(v, type, { path: [...path, key] });
return [key, val]; return [key, val];
}), }),
); );
@ -222,8 +236,8 @@ export const wrapJsFunction = (
); );
} }
const jsArgs = args.map((arg, i) => { const jsArgs = zip(args, schemaArgs).map(([arg, schemaArg]) => {
return aqua2js(arg, schemaArgs[i]); return aqua2js(arg, schemaArg);
}); });
const returnTypeVoid = const returnTypeVoid =
@ -231,7 +245,8 @@ export const wrapJsFunction = (
const resultSchema = const resultSchema =
schema.codomain.tag === "unlabeledProduct" && schema.codomain.tag === "unlabeledProduct" &&
schema.codomain.items.length === 1 schema.codomain.items.length === 1 &&
"0" in schema.codomain.items
? schema.codomain.items[0] ? schema.codomain.items[0]
: schema.codomain; : schema.codomain;

View File

@ -28,10 +28,12 @@ interface RegisterServiceArgs {
service: ServiceImpl; service: ServiceImpl;
} }
type ServiceFunctionPair = [key: string, value: ServiceImpl[string]];
// This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain. // This function iterates on plain object or class instance functions ignoring inherited functions and prototype chain.
const findAllPossibleRegisteredServiceFunctions = ( const findAllPossibleRegisteredServiceFunctions = (
service: ServiceImpl, service: ServiceImpl,
): Array<string> => { ): Array<ServiceFunctionPair> => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const prototype = Object.getPrototypeOf(service) as ServiceImpl; const prototype = Object.getPrototypeOf(service) as ServiceImpl;
@ -41,9 +43,14 @@ const findAllPossibleRegisteredServiceFunctions = (
service = prototype; service = prototype;
} }
return Object.getOwnPropertyNames(service).filter((prop) => { return Object.getOwnPropertyNames(service)
return typeof service[prop] === "function" && prop !== "constructor"; .map((prop) => {
}); return [prop, service[prop]];
})
.filter((entry): entry is ServiceFunctionPair => {
const [prop, value] = entry;
return typeof value === "function" && prop !== "constructor";
});
}; };
export const registerService = ({ export const registerService = ({
@ -55,8 +62,7 @@ export const registerService = ({
const serviceFunctions = findAllPossibleRegisteredServiceFunctions(service); const serviceFunctions = findAllPossibleRegisteredServiceFunctions(service);
for (const serviceFunction of serviceFunctions) { for (const [serviceFunction, handler] of serviceFunctions) {
const handler = service[serviceFunction];
const userDefinedHandler = handler.bind(service); const userDefinedHandler = handler.bind(service);
const serviceDescription = userHandlerService( const serviceDescription = userHandlerService(

View File

@ -79,7 +79,7 @@ export const responseService = (resolveCallback: (val: JSONValue) => void) => {
const userFunctionReturn = const userFunctionReturn =
req.args.length === 0 req.args.length === 0
? null ? null
: req.args.length === 1 : req.args.length === 1 && "0" in req.args
? req.args[0] ? req.args[0]
: req.args; : req.args;
@ -108,7 +108,10 @@ export const errorHandlingService = (
const [err] = req.args; const [err] = req.args;
setTimeout(() => { setTimeout(() => {
rejectCallback(err); rejectCallback(
err ??
"Unknown error happened when executing aqua code. No error text was passed to 'errorHandlingSrv.error' function, probably because AIR code was modified or aqua compiler didn't produce the correct call",
);
}, 0); }, 0);
return { return {

View File

@ -92,7 +92,7 @@ export class RelayConnection implements IConnection {
this.relayAddress = multiaddr(this.config.relayAddress); this.relayAddress = multiaddr(this.config.relayAddress);
const peerId = this.relayAddress.getPeerId(); const peerId = this.relayAddress.getPeerId();
if (peerId == null) { if (peerId === null) {
throwHasNoPeerId(this.relayAddress); throwHasNoPeerId(this.relayAddress);
} }
@ -125,7 +125,7 @@ export class RelayConnection implements IConnection {
streamMuxers: [yamux()], streamMuxers: [yamux()],
connectionEncryption: [noise()], connectionEncryption: [noise()],
connectionManager: { connectionManager: {
...(this.config.dialTimeoutMs != null ...(this.config.dialTimeoutMs !== undefined
? { ? {
dialTimeout: this.config.dialTimeoutMs, dialTimeout: this.config.dialTimeoutMs,
} }

View File

@ -30,10 +30,12 @@ import { logger } from "../util/logger.js";
const log = logger("ephemeral"); const log = logger("ephemeral");
interface EphemeralConfig { interface EphemeralConfig {
peers: Array<{ peers: Readonly<
peerId: PeerIdB58; Array<{
sk: string; peerId: PeerIdB58;
}>; sk: string;
}>
>;
} }
export const defaultConfig = { export const defaultConfig = {
@ -119,7 +121,7 @@ export const defaultConfig = {
sk: "RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=", sk: "RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=",
}, },
], ],
}; } as const;
export interface IEphemeralConnection extends IConnection { export interface IEphemeralConnection extends IConnection {
readonly selfPeerId: PeerIdB58; readonly selfPeerId: PeerIdB58;
@ -248,17 +250,17 @@ export class EphemeralNetwork {
const peers = await Promise.all(promises); const peers = await Promise.all(promises);
for (let i = 0; i < peers.length; i++) { peers.forEach((peer, i) => {
for (let j = 0; j < i; j++) { const otherPeers = peers.slice(0, i);
otherPeers.forEach((otherPeer, j) => {
if (i === j) { if (i === j) {
continue; return;
} }
peers[i].ephemeralConnection.connectToOther( peer.ephemeralConnection.connectToOther(otherPeer.ephemeralConnection);
peers[j].ephemeralConnection, });
); });
}
}
const startPromises = peers.map((x) => { const startPromises = peers.map((x) => {
return x.start(); return x.start();

View File

@ -111,7 +111,7 @@ export const Fluence: FluencePublicApi = {
* @returns IFluenceClient instance * @returns IFluenceClient instance
*/ */
getClient: function () { getClient: function () {
if (this.defaultClient == null) { if (this.defaultClient === undefined) {
throw new Error( throw new Error(
"Fluence client is not initialized. Call Fluence.connect() first", "Fluence client is not initialized. Call Fluence.connect() first",
); );

View File

@ -15,7 +15,7 @@
*/ */
import { JSONValue } from "@fluencelabs/interfaces"; import { JSONValue } from "@fluencelabs/interfaces";
import { it, describe, expect } from "vitest"; import { it, describe, expect, assert } from "vitest";
import { handleTimeout } from "../../particle/Particle.js"; import { handleTimeout } from "../../particle/Particle.js";
import { registerHandlersHelper, withPeer } from "../../util/testUtils.js"; import { registerHandlersHelper, withPeer } from "../../util/testUtils.js";
@ -39,6 +39,7 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
print: { print: {
print: (args): undefined => { print: (args): undefined => {
const [res] = args; const [res] = args;
assert(res);
resolve(res); resolve(res);
}, },
}, },
@ -80,6 +81,7 @@ describe("Basic AVM functionality in Fluence Peer tests", () => {
registerHandlersHelper(peer, particle, { registerHandlersHelper(peer, particle, {
print: { print: {
print: (args): undefined => { print: (args): undefined => {
assert(args[0]);
res.push(args[0]); res.push(args[0]);
if (res.length === 2) { if (res.length === 2) {

View File

@ -14,10 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from "assert";
import { JSONValue } from "@fluencelabs/interfaces"; import { JSONValue } from "@fluencelabs/interfaces";
import { describe, expect, it } from "vitest"; import { describe, expect, it, assert } from "vitest";
import { import {
CallServiceData, CallServiceData,
@ -52,6 +50,7 @@ describe("FluencePeer flow tests", () => {
(req: CallServiceData) => { (req: CallServiceData) => {
const [timeout, message] = req.args; const [timeout, message] = req.args;
assert(typeof timeout === "number"); assert(typeof timeout === "number");
assert(message);
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
@ -77,6 +76,7 @@ describe("FluencePeer flow tests", () => {
callback: { callback: {
callback1: (args): undefined => { callback1: (args): undefined => {
const [val] = args; const [val] = args;
assert(val);
values.push(val); values.push(val);
if (values.length === 2) { if (values.length === 2) {
@ -85,6 +85,7 @@ describe("FluencePeer flow tests", () => {
}, },
callback2: (args): undefined => { callback2: (args): undefined => {
const [val] = args; const [val] = args;
assert(val);
values.push(val); values.push(val);
if (values.length === 2) { if (values.length === 2) {

View File

@ -18,7 +18,7 @@ import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import * as url from "url"; import * as url from "url";
import { it, describe, expect, beforeAll } from "vitest"; import { it, describe, expect, beforeAll, assert } from "vitest";
import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js";
@ -46,6 +46,7 @@ describe("Marine js tests", () => {
await peer.registerMarineService(wasm, "greeting"); await peer.registerMarineService(wasm, "greeting");
// act // act
assert(aqua["call"]);
const res = await aqua["call"](peer, { arg: "test" }); const res = await aqua["call"](peer, { arg: "test" });
// assert // assert

View File

@ -43,7 +43,7 @@ export class MarineBackgroundRunner implements IMarineHost {
) {} ) {}
async hasService(serviceId: string) { async hasService(serviceId: string) {
if (this.workerThread == null) { if (this.workerThread === undefined) {
throw new Error("Worker is not initialized"); throw new Error("Worker is not initialized");
} }
@ -51,7 +51,7 @@ export class MarineBackgroundRunner implements IMarineHost {
} }
async removeService(serviceId: string) { async removeService(serviceId: string) {
if (this.workerThread == null) { if (this.workerThread === undefined) {
throw new Error("Worker is not initialized"); throw new Error("Worker is not initialized");
} }
@ -59,7 +59,7 @@ export class MarineBackgroundRunner implements IMarineHost {
} }
async start(): Promise<void> { async start(): Promise<void> {
if (this.workerThread != null) { if (this.workerThread !== undefined) {
throw new Error("Worker thread already initialized"); throw new Error("Worker thread already initialized");
} }
@ -69,7 +69,7 @@ export class MarineBackgroundRunner implements IMarineHost {
const logfn: LogFunction = (message) => { const logfn: LogFunction = (message) => {
const serviceLogger = this.loggers.get(message.service); const serviceLogger = this.loggers.get(message.service);
if (serviceLogger == null) { if (serviceLogger === undefined) {
return; return;
} }
@ -86,7 +86,7 @@ export class MarineBackgroundRunner implements IMarineHost {
serviceModule: ArrayBuffer | SharedArrayBuffer, serviceModule: ArrayBuffer | SharedArrayBuffer,
serviceId: string, serviceId: string,
): Promise<void> { ): Promise<void> {
if (this.workerThread == null) { if (this.workerThread === undefined) {
throw new Error("Worker is not initialized"); throw new Error("Worker is not initialized");
} }
@ -100,7 +100,7 @@ export class MarineBackgroundRunner implements IMarineHost {
args: Array<JSONValueNonNullable> | Record<string, JSONValueNonNullable>, args: Array<JSONValueNonNullable> | Record<string, JSONValueNonNullable>,
callParams?: CallParameters, callParams?: CallParameters,
): Promise<JSONValue> { ): Promise<JSONValue> {
if (this.workerThread == null) { if (this.workerThread === undefined) {
throw new Error("Worker is not initialized"); throw new Error("Worker is not initialized");
} }
@ -113,7 +113,7 @@ export class MarineBackgroundRunner implements IMarineHost {
} }
async stop(): Promise<void> { async stop(): Promise<void> {
if (this.workerThread == null) { if (this.workerThread === undefined) {
return; return;
} }

View File

@ -14,11 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from "assert";
import { JSONArray } from "@fluencelabs/interfaces"; import { JSONArray } from "@fluencelabs/interfaces";
import { toUint8Array } from "js-base64"; import { toUint8Array } from "js-base64";
import { describe, expect, it, test } from "vitest"; import { describe, expect, it, test, assert } from "vitest";
import { CallServiceData } from "../../jsServiceHost/interfaces.js"; import { CallServiceData } from "../../jsServiceHost/interfaces.js";
import { KeyPair } from "../../keypair/index.js"; import { KeyPair } from "../../keypair/index.js";
@ -155,7 +153,8 @@ describe("Tests for default handler", () => {
}; };
// act // act
const fn = builtInServices[req.serviceId][req.fnName]; const fn = builtInServices[req.serviceId]?.[req.fnName];
assert(fn);
const res = await fn(req); const res = await fn(req);
// Our test cases above depend on node error message. In node 20 error message for JSON.parse has changed. // Our test cases above depend on node error message. In node 20 error message for JSON.parse has changed.
@ -192,7 +191,8 @@ describe("Tests for default handler", () => {
}; };
// act // act
const fn = builtInServices[req.serviceId][req.fnName]; const fn = builtInServices[req.serviceId]?.[req.fnName];
assert(fn);
const res = await fn(req); const res = await fn(req);
// assert // assert

View File

@ -17,7 +17,7 @@
import * as path from "path"; import * as path from "path";
import * as url from "url"; import * as url from "url";
import { it, describe, expect, beforeAll } from "vitest"; import { it, describe, expect, beforeAll, assert } from "vitest";
import { registerService } from "../../compilerSupport/registerService.js"; import { registerService } from "../../compilerSupport/registerService.js";
import { KeyPair } from "../../keypair/index.js"; import { KeyPair } from "../../keypair/index.js";
@ -72,6 +72,7 @@ describe("Sig service test suite", () => {
customSig.securityGuard = allowServiceFn("data", "provide_data"); customSig.securityGuard = allowServiceFn("data", "provide_data");
assert(aqua["callSig"]);
const result = await aqua["callSig"](peer, { sigId: "CustomSig" }); const result = await aqua["callSig"](peer, { sigId: "CustomSig" });
expect(result).toHaveProperty("success", true); expect(result).toHaveProperty("success", true);
@ -116,6 +117,7 @@ describe("Sig service test suite", () => {
customSig.securityGuard = allowServiceFn("wrong", "wrong"); customSig.securityGuard = allowServiceFn("wrong", "wrong");
assert(aqua["callSig"]);
const result = await aqua["callSig"](peer, { sigId: "CustomSig" }); const result = await aqua["callSig"](peer, { sigId: "CustomSig" });
expect(result).toHaveProperty("success", false); expect(result).toHaveProperty("success", false);
}); });
@ -137,6 +139,7 @@ describe("Sig service test suite", () => {
}, },
}); });
assert(aqua["callSig"]);
const callAsSigRes = await aqua["callSig"](peer, { sigId: "sig" }); const callAsSigRes = await aqua["callSig"](peer, { sigId: "sig" });
const callAsPeerIdRes = await aqua["callSig"](peer, { const callAsPeerIdRes = await aqua["callSig"](peer, {

View File

@ -17,7 +17,7 @@
import * as path from "path"; import * as path from "path";
import * as url from "url"; import * as url from "url";
import { it, describe, expect, beforeAll } from "vitest"; import { it, describe, expect, beforeAll, assert } from "vitest";
import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js";
import { registerNodeUtils } from "../_aqua/node-utils.js"; import { registerNodeUtils } from "../_aqua/node-utils.js";
@ -42,6 +42,7 @@ describe("Srv service test suite", () => {
const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm");
// act // act
assert(aqua["happy_path"]);
const res = await aqua["happy_path"](peer, { file_path: wasm }); const res = await aqua["happy_path"](peer, { file_path: wasm });
// assert // assert
@ -57,6 +58,7 @@ describe("Srv service test suite", () => {
const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm");
// act // act
assert(aqua["list_services"]);
const res = await aqua["list_services"](peer, { file_path: wasm }); const res = await aqua["list_services"](peer, { file_path: wasm });
// assert // assert
@ -72,6 +74,8 @@ describe("Srv service test suite", () => {
const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm");
// act // act
assert(aqua["service_removed"]);
const res = await aqua["service_removed"](peer, { const res = await aqua["service_removed"](peer, {
file_path: wasm, file_path: wasm,
}); });
@ -87,6 +91,7 @@ describe("Srv service test suite", () => {
registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); registerNodeUtils(peer, "node_utils", new NodeUtils(peer));
// act // act
assert(aqua["file_not_found"]);
const res = await aqua["file_not_found"](peer, {}); const res = await aqua["file_not_found"](peer, {});
// assert // assert
@ -102,6 +107,7 @@ describe("Srv service test suite", () => {
registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); registerNodeUtils(peer, "node_utils", new NodeUtils(peer));
// act // act
assert(aqua["removing_non_exiting"]);
const res = await aqua["removing_non_exiting"](peer, {}); const res = await aqua["removing_non_exiting"](peer, {});
// assert // assert

View File

@ -109,7 +109,7 @@ const parseWithSchema = <T extends z.ZodTypeAny>(
if (result.success) { if (result.success) {
return [result.data, null]; return [result.data, null];
} else { } else {
return [null, result.error.errors[0].message]; return [null, result.error.errors[0]?.message ?? "Unknown error"];
} }
}; };
@ -141,7 +141,7 @@ const withSchema: withSchema = <T extends z.ZodTypeAny>(schema: T) => {
return (req) => { return (req) => {
const [value, message] = parseWithSchema(schema, req); const [value, message] = parseWithSchema(schema, req);
if (message != null) { if (message !== null) {
return error(message); return error(message);
} }
@ -306,7 +306,7 @@ export const builtInServices: Record<
}), }),
identity: withSchema(z.array(jsonSchema).max(1))((args) => { identity: withSchema(z.array(jsonSchema).max(1))((args) => {
return success(args.length === 0 ? {} : args[0]); return success("0" in args ? args[0] : {});
}), }),
concat: withSchema(z.array(z.array(z.any())))((args) => { concat: withSchema(z.array(z.array(z.any())))((args) => {

View File

@ -33,8 +33,8 @@ export const allowTetraplet = (
pred: (tetraplet: SecurityTetraplet) => boolean, pred: (tetraplet: SecurityTetraplet) => boolean,
): SecurityGuard => { ): SecurityGuard => {
return (params) => { return (params) => {
const t = params.tetraplets[0][0]; const t = params.tetraplets[0]?.[0];
return pred(t); return t !== undefined && pred(t);
}; };
}; };

View File

@ -26,7 +26,7 @@ export function relayOptionToMultiaddr(relay: RelayOptions): Multiaddr {
const peerId = ma.getPeerId(); const peerId = ma.getPeerId();
if (peerId == null) { if (peerId === null) {
throwHasNoPeerId(ma); throwHasNoPeerId(ma);
} }

View File

@ -33,3 +33,19 @@ export const getErrorMessage = (error: unknown) => {
return String(error); return String(error);
}; };
export function zip<A, B>(arr1: Array<A>, arr2: Array<B>): Array<[A, B]> {
if (arr1.length !== arr2.length) {
throw new Error(`Array length doesn't match`);
}
const arr = new Array<[A, B]>(arr1.length);
for (let i = 0; i < arr1.length; i++) {
// Length has been checked above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
arr[i] = [arr1[i]!, arr2[i]!];
}
return arr;
}

View File

@ -74,7 +74,7 @@ const toExpose = {
serviceId: string, serviceId: string,
envs?: Env, envs?: Env,
): Promise<void> => { ): Promise<void> => {
if (controlModule == null) { if (controlModule === undefined) {
throw new Error( throw new Error(
"MarineJS is not initialized. To initialize call `init` function", "MarineJS is not initialized. To initialize call `init` function",
); );
@ -135,7 +135,7 @@ const toExpose = {
) => { ) => {
const srv = marineServices.get(serviceId); const srv = marineServices.get(serviceId);
if (srv == null) { if (srv === undefined) {
throw new Error(`service with id=${serviceId} not found`); throw new Error(`service with id=${serviceId} not found`);
} }

View File

@ -0,0 +1,29 @@
{
"type": "module",
"name": "@fluencelabs/npm-aqua-compiler",
"version": "0.0.0",
"description": "Tool for converting npm imports to aqua compiler input",
"types": "./dist/imports.d.ts",
"exports": {
".": "./dist/imports.js"
},
"scripts": {
"test": "npm i --prefix ./test/transitive-deps/project && vitest run",
"build": "tsc"
},
"files": [
"dist"
],
"keywords": [],
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"@npmcli/arborist": "^7.2.1",
"treeverse": "3.0.0"
},
"devDependencies": {
"@types/npmcli__arborist": "5.6.5",
"@types/treeverse": "3.0.4",
"vitest": "0.34.6"
}
}

View File

@ -0,0 +1,115 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { join } from "path";
import { fileURLToPath } from "url";
import { assert, describe, expect, it } from "vitest";
import { gatherImportsFromNpm } from "./imports.js";
describe("imports", () => {
/**
* NOTE: This test expects that `npm i` is run
* inside `./__test__/data/transitive-deps/project` folder
*/
it("should resolve transitive dependencies", async () => {
const npmProjectDirPath = "./test/transitive-deps/project";
const aquaToCompileDirPath = "./test/transitive-deps/aqua-project";
const globalImports = ["./.fluence/aqua"];
const expectedResolution: Record<
string,
Record<string, string[] | string>
> = {
[aquaToCompileDirPath]: {
"": globalImports,
A: "./A",
B: "./B",
},
"./A": {
C: "./C",
D: "./D",
},
"./B": {
C: "./B/C",
D: "./B/D",
},
"./C": {
D: "./C/D",
},
"./B/C": {
D: "./B/C/D",
},
};
const prefix = join(
fileURLToPath(new URL("./", import.meta.url)),
"..",
"test",
"transitive-deps",
"project",
);
const buildResolutionKey = (str: string) => {
return (
"./" +
str
.slice(prefix.length)
.split("/node_modules/")
.filter(Boolean)
.join("/")
);
};
const imports = await gatherImportsFromNpm({
npmProjectDirPath,
aquaToCompileDirPath,
globalImports,
});
expect(Object.keys(imports).length).toBe(
Object.keys(expectedResolution).length,
);
Object.entries(imports).forEach(([key, value]) => {
const resolutionKey =
key === aquaToCompileDirPath ? key : buildResolutionKey(key);
const resolutionValues = expectedResolution[resolutionKey];
assert(resolutionValues);
expect(Object.keys(value).length).toBe(
Object.keys(resolutionValues).length,
);
for (const [dep, path] of Object.entries(value)) {
if (Array.isArray(path)) {
expect(dep).toBe("");
expect(expectedResolution[resolutionKey]).toHaveProperty(dep, path);
continue;
}
expect(expectedResolution[resolutionKey]).toHaveProperty(
dep,
buildResolutionKey(path),
);
}
});
});
});

View File

@ -0,0 +1,92 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Arborist from "@npmcli/arborist";
import { breadth } from "treeverse";
export interface GatherImportsArg {
npmProjectDirPath: string;
aquaToCompileDirPath?: string; // Default: npmProjectDirPath
globalImports?: string[];
}
export type GatherImportsResult = Record<
string,
Record<string, string[] | string>
>;
export async function gatherImportsFromNpm({
npmProjectDirPath,
aquaToCompileDirPath,
globalImports = [],
}: GatherImportsArg): Promise<GatherImportsResult> {
const arborist = new Arborist({ path: npmProjectDirPath });
const tree = await arborist.loadActual();
/**
* Traverse dependency tree to construct map
* (real path of a package) -> (real paths of its immediate dependencies)
*/
const result: GatherImportsResult = {};
breadth({
tree,
getChildren(node) {
const deps: Arborist.Node[] = [];
for (const edge of node.edgesOut.values()) {
// Skip dependencies that are not installed.
// Looks like Arborist type is incorrect here, so it's possible to have null here
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (edge.to === null) {
continue;
}
// NOTE: Any errors in edge are ignored.
const dep = edge.to;
// Gather dependencies to traverse them.
deps.push(dep);
// Root node should have top-level property pointed to aqua dependency folder
if (node.isRoot) {
const aquaDepPath = aquaToCompileDirPath ?? npmProjectDirPath;
result[aquaDepPath] = {
...(result[aquaDepPath] ??
(globalImports.length > 0
? {
"": globalImports,
}
: {})),
[dep.name]: dep.realpath,
};
} else {
// Gather dependencies real paths.
result[node.realpath] = {
...(result[node.realpath] ?? {}),
[dep.name]: dep.realpath,
};
}
}
return deps;
},
});
return result;
}

View File

@ -0,0 +1,8 @@
use "B.aqua"
func versionAC() -> string:
<- A.versionC()
func versionBC() -> string:
<- B.versionC()

View File

@ -0,0 +1,70 @@
{
"name": "project",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "project",
"version": "0.1.0",
"dependencies": {
"A": "file:../A-0.1.0.tgz",
"B": "file:../B-0.1.0.tgz"
}
},
"node_modules/A": {
"version": "0.1.0",
"resolved": "file:../A-0.1.0.tgz",
"integrity": "sha512-H0nhbQVQxPm3VwXYiePLJ0oyHa2FxPNNPjOcTdz3YWvIoE0/dZFJ1yrqig7fkrETYEYfLuVJaN0yg1BX/HAScg==",
"dependencies": {
"C": "file:./C-0.2.0.tgz",
"D": "file:./D-0.1.0.tgz"
}
},
"node_modules/B": {
"version": "0.1.0",
"resolved": "file:../B-0.1.0.tgz",
"integrity": "sha512-u6n6V5KlxIN/GwRQt82gZQAPwYi0OzqQ2wr8ufmygreLK3fPIfO49f13qagbGXaYiRxN9effXaPqZlMIyTygng==",
"dependencies": {
"C": "file:./C-0.1.0.tgz",
"D": "file:./D-0.2.0.tgz"
}
},
"node_modules/B/node_modules/C": {
"version": "0.1.0",
"resolved": "file:../C-0.1.0.tgz",
"integrity": "sha512-zvzWgHLm+ptWwysP+dJItnogVSca/jvHegWmwi6NmmHFO/wTqlGrMPnC2dEkpXDJBU4X1bUjevFh0q3Xe9e0MA==",
"dependencies": {
"D": "file:./D-0.1.0.tgz"
}
},
"node_modules/B/node_modules/C/node_modules/D": {
"version": "0.1.0",
"resolved": "file:../D-0.1.0.tgz",
"integrity": "sha512-1rlKmuyzHSGTt9tBhEBY3j7gZvMBg0LnZMogZSucmX4gww4l0+HPQwBIPjJpqOspE2ND8PcLymQoiw06xWXn0g=="
},
"node_modules/B/node_modules/D": {
"version": "0.2.0",
"resolved": "file:../D-0.2.0.tgz",
"integrity": "sha512-7h1TUU8j60q6BZ0Wq/xDZOUf6iS0S4SgL/lgXOaiyxN76q7ld8Rx/qIxtGKmrWh65v5cjvAG5asbMEkXb6DuYQ=="
},
"node_modules/C": {
"version": "0.2.0",
"resolved": "file:../C-0.2.0.tgz",
"integrity": "sha512-uNqb8p69kuombZsb3xI/ygeL94WHpwkGR9/GRWgdg+01iKGsRMaZgL5up0UG7D/9DW7NQBozZG8ITPQ8DLgwSQ==",
"dependencies": {
"D": "file:./D-0.2.0.tgz"
}
},
"node_modules/C/node_modules/D": {
"version": "0.2.0",
"resolved": "file:../D-0.2.0.tgz",
"integrity": "sha512-7h1TUU8j60q6BZ0Wq/xDZOUf6iS0S4SgL/lgXOaiyxN76q7ld8Rx/qIxtGKmrWh65v5cjvAG5asbMEkXb6DuYQ=="
},
"node_modules/D": {
"version": "0.1.0",
"resolved": "file:../D-0.1.0.tgz",
"integrity": "sha512-1rlKmuyzHSGTt9tBhEBY3j7gZvMBg0LnZMogZSucmX4gww4l0+HPQwBIPjJpqOspE2ND8PcLymQoiw06xWXn0g=="
}
}
}

View File

@ -0,0 +1,8 @@
{
"name": "project",
"version": "0.1.0",
"dependencies": {
"A": "file:../A-0.1.0.tgz",
"B": "file:../B-0.1.0.tgz"
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/**/__test__"]
}

1150
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,7 @@
"module": "ESNext", "module": "ESNext",
"esModuleInterop": true, "esModuleInterop": true,
"declaration": true, "declaration": true,
"moduleResolution": "nodenext", "moduleResolution": "nodenext"
"noUncheckedIndexedAccess": false
}, },
"files": ["reset.d.ts"] "files": ["reset.d.ts"]
} }