Fix all tests

This commit is contained in:
Akim Mamedov 2023-11-14 20:48:24 +07:00
parent 3a039cd355
commit d6eb91b76f
24 changed files with 498 additions and 955 deletions

View File

@ -22,7 +22,8 @@
"@fluencelabs/spell": "0.5.20", "@fluencelabs/spell": "0.5.20",
"@fluencelabs/trust-graph": "0.4.7", "@fluencelabs/trust-graph": "0.4.7",
"ts-pattern": "5.0.5", "ts-pattern": "5.0.5",
"vitest": "0.34.6" "vitest": "0.34.6",
"zod": "3.22.4"
}, },
"peerDependencies": { "peerDependencies": {
"@fluencelabs/js-client": "workspace:^" "@fluencelabs/js-client": "workspace:^"

View File

@ -27,17 +27,14 @@ interface TsOutput {
sources: string; sources: string;
} }
type LanguageOutput = { type LanguageOutput = JsOutput | TsOutput;
js: JsOutput;
ts: TsOutput;
};
type NothingToGenerate = null; type NothingToGenerate = null;
export default async function aquaToJs<T extends OutputType>( export default async function aquaToJs(
res: CompilationResult, res: CompilationResult,
outputType: T, outputType: OutputType,
): Promise<LanguageOutput[T] | NothingToGenerate> { ): Promise<LanguageOutput | NothingToGenerate> {
if ( if (
Object.keys(res.services).length === 0 && Object.keys(res.services).length === 0 &&
Object.keys(res.functions).length === 0 Object.keys(res.functions).length === 0
@ -49,12 +46,10 @@ export default async function aquaToJs<T extends OutputType>(
return outputType === "js" return outputType === "js"
? { ? {
sources: generateSources(res, "js", packageJson), sources: generateSources(res, outputType, packageJson),
types: generateTypes(res, packageJson), types: generateTypes(res, packageJson),
} }
: // TODO: probably there is a way to remove this type assert : {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions sources: generateSources(res, outputType, packageJson),
({ };
sources: generateSources(res, "ts", packageJson),
} as LanguageOutput[T]);
} }

View File

@ -28,14 +28,17 @@ import {
SimpleTypes, SimpleTypes,
UnlabeledProductType, UnlabeledProductType,
} from "@fluencelabs/interfaces"; } from "@fluencelabs/interfaces";
import { z } from "zod";
export interface PackageJson { const packageJsonSchema = z.object({
name: string; name: z.string(),
version: string; version: z.string(),
devDependencies: { devDependencies: z.object({
["@fluencelabs/aqua-api"]: string; ["@fluencelabs/aqua-api"]: z.string(),
}; }),
} });
export type PackageJson = z.infer<typeof packageJsonSchema>;
export async function getPackageJsonContent(): Promise<PackageJson> { export async function getPackageJsonContent(): Promise<PackageJson> {
const content = await readFile( const content = await readFile(
@ -43,9 +46,7 @@ export async function getPackageJsonContent(): Promise<PackageJson> {
"utf-8", "utf-8",
); );
// TODO: Add validation here return packageJsonSchema.parse(JSON.parse(content));
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return JSON.parse(content) as PackageJson;
} }
export function getFuncArgs( export function getFuncArgs(

View File

@ -24,7 +24,7 @@
}, },
"dependencies": { "dependencies": {
"@fluencelabs/avm": "0.54.0", "@fluencelabs/avm": "0.54.0",
"@fluencelabs/marine-js": "0.7.2", "@fluencelabs/marine-js": "0.8.0",
"@fluencelabs/marine-worker": "0.4.2", "@fluencelabs/marine-worker": "0.4.2",
"@fluencelabs/threads": "^2.0.0" "@fluencelabs/threads": "^2.0.0"
}, },

View File

@ -14,18 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { FetchedPackages, getVersionedPackage } from "../types.js"; import { FetchResourceFn, getVersionedPackage } from "../types.js";
/** /**
* @param pkg name of package with version * @param pkg name of package with version
* @param assetPath path of required asset in given package * @param assetPath path of required asset in given package
* @param root CDN domain in browser or file system root in node * @param root CDN domain in browser or file system root in node
*/ */
export async function fetchResource( export const fetchResource: FetchResourceFn = async (pkg, assetPath, root) => {
pkg: FetchedPackages,
assetPath: string,
root: string,
) {
const refinedAssetPath = assetPath.startsWith("/") const refinedAssetPath = assetPath.startsWith("/")
? assetPath.slice(1) ? assetPath.slice(1)
: assetPath; : assetPath;
@ -36,4 +32,4 @@ export async function fetchResource(
return fetch(url).catch(() => { return fetch(url).catch(() => {
throw new Error(`Cannot fetch from ${url.toString()}`); throw new Error(`Cannot fetch from ${url.toString()}`);
}); });
} };

View File

@ -18,21 +18,14 @@ import { readFile } from "fs/promises";
import { createRequire } from "module"; import { createRequire } from "module";
import { sep, posix, join } from "path"; import { sep, posix, join } from "path";
import { FetchedPackages, getVersionedPackage } from "../types.js"; import { FetchResourceFn, getVersionedPackage } from "../types.js";
/** /**
* @param pkg name of package with version * @param pkg name of package with version
* @param assetPath path of required asset in given package * @param assetPath path of required asset in given package
* @param root CDN domain in browser or js-client itself in node
*/ */
export async function fetchResource( export const fetchResource: FetchResourceFn = async (pkg, assetPath) => {
pkg: FetchedPackages,
assetPath: string,
root: string,
) {
const { name } = getVersionedPackage(pkg); const { name } = getVersionedPackage(pkg);
// TODO: `root` will be handled somehow in the future. For now, we use filesystem root where js-client is running;
root = "/";
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const packagePathIndex = require.resolve(name); const packagePathIndex = require.resolve(name);
@ -47,7 +40,7 @@ export async function fetchResource(
throw new Error(`Cannot find dependency ${name} in path ${posixPath}`); throw new Error(`Cannot find dependency ${name} in path ${posixPath}`);
} }
const pathToResource = join(root, packagePath, assetPath); const pathToResource = join(packagePath, assetPath);
const file = await readFile(pathToResource); const file = await readFile(pathToResource);
@ -60,4 +53,4 @@ export async function fetchResource(
: "application/text", : "application/text",
}, },
}); });
} };

View File

@ -20,7 +20,7 @@ import versions from "./versions.js";
export type FetchedPackages = keyof typeof versions; export type FetchedPackages = keyof typeof versions;
type VersionedPackage = { name: string; version: string }; type VersionedPackage = { name: string; version: string };
export type GetWorker = ( export type GetWorkerFn = (
pkg: FetchedPackages, pkg: FetchedPackages,
CDNUrl: string, CDNUrl: string,
) => Promise<Worker>; ) => Promise<Worker>;
@ -31,3 +31,9 @@ export const getVersionedPackage = (pkg: FetchedPackages): VersionedPackage => {
version: versions[pkg], version: versions[pkg],
}; };
}; };
export type FetchResourceFn = (
pkg: FetchedPackages,
assetPath: string,
root: string,
) => Promise<Response>;

View File

@ -17,9 +17,9 @@
import { BlobWorker } from "@fluencelabs/threads/master"; import { BlobWorker } from "@fluencelabs/threads/master";
import { fetchResource } from "../fetchers/browser.js"; import { fetchResource } from "../fetchers/browser.js";
import type { FetchedPackages, GetWorker } from "../types.js"; import type { FetchedPackages, GetWorkerFn } from "../types.js";
export const getWorker: GetWorker = async ( export const getWorker: GetWorkerFn = async (
pkg: FetchedPackages, pkg: FetchedPackages,
CDNUrl: string, CDNUrl: string,
) => { ) => {

View File

@ -20,10 +20,10 @@ import { fileURLToPath } from "url";
import { Worker } from "@fluencelabs/threads/master"; import { Worker } from "@fluencelabs/threads/master";
import type { FetchedPackages, GetWorker } from "../types.js"; import type { FetchedPackages, GetWorkerFn } from "../types.js";
import { getVersionedPackage } from "../types.js"; import { getVersionedPackage } from "../types.js";
export const getWorker: GetWorker = (pkg: FetchedPackages) => { export const getWorker: GetWorkerFn = (pkg: FetchedPackages) => {
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const pathToThisFile = dirname(fileURLToPath(import.meta.url)); const pathToThisFile = dirname(fileURLToPath(import.meta.url));

View File

@ -43,10 +43,10 @@
"@libp2p/peer-id-factory": "3.0.3", "@libp2p/peer-id-factory": "3.0.3",
"@libp2p/websockets": "7.0.4", "@libp2p/websockets": "7.0.4",
"@multiformats/multiaddr": "11.3.0", "@multiformats/multiaddr": "11.3.0",
"assert": "2.1.0",
"async": "3.2.4", "async": "3.2.4",
"bs58": "5.0.0", "bs58": "5.0.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"class-transformer": "0.5.1",
"debug": "4.3.4", "debug": "4.3.4",
"it-length-prefixed": "8.0.4", "it-length-prefixed": "8.0.4",
"it-map": "2.0.0", "it-map": "2.0.0",
@ -57,11 +57,12 @@
"rxjs": "7.5.5", "rxjs": "7.5.5",
"uint8arrays": "4.0.3", "uint8arrays": "4.0.3",
"uuid": "8.3.2", "uuid": "8.3.2",
"zod": "3.22.4" "zod": "3.22.4",
"zod-validation-error": "2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@fluencelabs/aqua-api": "0.9.3", "@fluencelabs/aqua-api": "0.9.3",
"@fluencelabs/marine-js": "0.7.2", "@fluencelabs/marine-js": "0.8.0",
"@rollup/plugin-inject": "5.0.3", "@rollup/plugin-inject": "5.0.3",
"@types/bs58": "4.0.1", "@types/bs58": "4.0.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { z } from "zod";
/** /**
* Peer ID's id as a base58 string (multihash/CIDv0). * Peer ID's id as a base58 string (multihash/CIDv0).
*/ */
@ -33,20 +35,30 @@ export type Node = {
* - string: multiaddr in string format * - string: multiaddr in string format
* - Node: node structure, @see Node * - Node: node structure, @see Node
*/ */
export type RelayOptions = string | Node; export const relaySchema = z.union([
z.string(),
z.object({
peerId: z.string(),
multiaddr: z.string(),
}),
]);
export type RelayOptions = z.infer<typeof relaySchema>;
/** /**
* Fluence Peer's key pair types * Fluence Peer's key pair types
*/ */
export type KeyTypes = "RSA" | "Ed25519" | "secp256k1"; export type KeyTypes = "RSA" | "Ed25519" | "secp256k1";
const keyPairOptionsSchema = z.object({
type: z.literal("Ed25519"),
source: z.union([z.literal("random"), z.instanceof(Uint8Array)]),
});
/** /**
* Options to specify key pair used in Fluence Peer * Options to specify key pair used in Fluence Peer
*/ */
export type KeyPairOptions = { export type KeyPairOptions = z.infer<typeof keyPairOptionsSchema>;
type: "Ed25519";
source: "random" | Uint8Array;
};
/** /**
* Fluence JS Client connection states as string literals * Fluence JS Client connection states as string literals
@ -63,17 +75,10 @@ export const ConnectionStates = [
*/ */
export type ConnectionState = (typeof ConnectionStates)[number]; export type ConnectionState = (typeof ConnectionStates)[number];
export interface IFluenceInternalApi {
/**
* Internal API
*/
internals: unknown;
}
/** /**
* Public API of Fluence JS Client * Public API of Fluence JS Client
*/ */
export interface IFluenceClient extends IFluenceInternalApi { export interface IFluenceClient {
/** /**
* Connect to the Fluence network * Connect to the Fluence network
*/ */
@ -107,65 +112,66 @@ export interface IFluenceClient extends IFluenceInternalApi {
getRelayPeerId(): string; getRelayPeerId(): string;
} }
/** export const configSchema = z
* Configuration used when initiating Fluence Client .object({
*/
export interface ClientConfig {
/** /**
* Specify the KeyPair to be used to identify the Fluence Peer. * Specify the KeyPair to be used to identify the Fluence Peer.
* Will be generated randomly if not specified * Will be generated randomly if not specified
*/ */
keyPair?: KeyPairOptions; keyPair: keyPairOptionsSchema,
/** /**
* Options to configure the connection to the Fluence network * Options to configure the connection to the Fluence network
*/ */
connectionOptions?: { connectionOptions: z
.object({
/** /**
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly. * When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
* The options allows to specify the timeout for that message in milliseconds. * The options allows to specify the timeout for that message in milliseconds.
* If not specified the default timeout will be used * If not specified the default timeout will be used
*/ */
skipCheckConnection?: boolean; skipCheckConnection: z.boolean(),
/** /**
* The dialing timeout in milliseconds * The dialing timeout in milliseconds
*/ */
dialTimeoutMs?: number; dialTimeoutMs: z.number(),
/** /**
* The maximum number of inbound streams for the libp2p node. * The maximum number of inbound streams for the libp2p node.
* Default: 1024 * Default: 1024
*/ */
maxInboundStreams?: number; maxInboundStreams: z.number(),
/** /**
* The maximum number of outbound streams for the libp2p node. * The maximum number of outbound streams for the libp2p node.
* Default: 1024 * Default: 1024
*/ */
maxOutboundStreams?: number; maxOutboundStreams: z.number(),
}; })
.partial(),
/** /**
* Sets the default TTL for all particles originating from the peer with no TTL specified. * Sets the default TTL for all particles originating from the peer with no TTL specified.
* If the originating particle's TTL is defined then that value will be used * If the originating particle's TTL is defined then that value will be used
* If the option is not set default TTL will be 7000 * If the option is not set default TTL will be 7000
*/ */
defaultTtlMs?: number; defaultTtlMs: z.number(),
/** /**
* Property for passing custom CDN Url to load dependencies from browser. https://unpkg.com used by default * Property for passing custom CDN Url to load dependencies from browser. https://unpkg.com used by default
*/ */
CDNUrl?: string; CDNUrl: z.string(),
/** /**
* Enables\disabled various debugging features * Enables\disabled various debugging features
*/ */
debug?: { debug: z
.object({
/** /**
* If set to true, newly initiated particle ids will be printed to console. * If set to true, newly initiated particle ids will be printed to console.
* Useful to see what particle id is responsible for aqua function * Useful to see what particle id is responsible for aqua function
*/ */
printParticleId?: boolean; printParticleId: z.boolean(),
}; })
} .partial(),
})
.partial();
/**
* Configuration used when initiating Fluence Client
*/
export type ClientConfig = z.infer<typeof configSchema>;

View File

@ -61,7 +61,6 @@ export const callAquaFunction = async ({
peer, peer,
args, args,
}: CallAquaFunctionArgs) => { }: CallAquaFunctionArgs) => {
// TODO: this function should be rewritten. We can remove asserts if we wont check definition there
log.trace("calling aqua function %j", { script, config, args }); log.trace("calling aqua function %j", { script, config, args });
const particle = await peer.internals.createNewParticle(script, config.ttl); const particle = await peer.internals.createNewParticle(script, config.ttl);
@ -88,15 +87,6 @@ export const callAquaFunction = async ({
// 1. The particle is sent to the network (state 'sent') // 1. The particle is sent to the network (state 'sent')
// 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone') // 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone')
// TODO: make test
// if (
// isReturnTypeVoid(def) &&
// (stage.stage === "sent" || stage.stage === "localWorkDone")
// ) {
// resolve(undefined);
// }
// },
peer.internals.initiateParticle(particle, resolve, reject); peer.internals.initiateParticle(particle, resolve, reject);
}); });
}; };

View File

@ -1,238 +0,0 @@
/**
* 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.
*/
// TODO: This file is a mess. Need to refactor it later
/* eslint-disable */
// @ts-nocheck
import assert from "assert";
import type {
ArrowType,
ArrowWithoutCallbacks,
JSONArray,
JSONValue,
LabeledProductType,
NonArrowType,
SimpleTypes,
} from "@fluencelabs/interfaces";
import { match } from "ts-pattern";
import { CallServiceData } from "../jsServiceHost/interfaces.js";
import { jsonify } from "../util/utils.js";
/**
* Convert value from its representation in aqua language to representation in typescript
* @param value - value as represented in aqua
* @param type - definition of the aqua type
* @returns value represented in typescript
*/
export const aqua2ts = (value: JSONValue, type: NonArrowType): JSONValue => {
const res = match(type)
.with({ tag: "nil" }, () => {
return null;
})
.with({ tag: "option" }, (opt) => {
assert(Array.isArray(value), "Should not be possible, bad types");
if (value.length === 0) {
return null;
} else {
return aqua2ts(value[0], opt.type);
}
})
.with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => {
return value;
})
.with({ tag: "array" }, (arr) => {
assert(Array.isArray(value), "Should not be possible, bad types");
return value.map((y) => {
return aqua2ts(y, arr.type);
});
})
.with({ tag: "struct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = aqua2ts(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "labeledProduct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = aqua2ts(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items.map((type, index) => {
return aqua2ts(value[index], type);
});
})
// uncomment to check that every pattern in matched
// .exhaustive();
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(type));
});
return res;
};
/**
* Convert call service arguments list from their aqua representation to representation in typescript
* @param req - call service data
* @param arrow - aqua type definition
* @returns arguments in typescript representation
*/
export const aquaArgs2Ts = (
req: CallServiceData,
arrow: ArrowType<LabeledProductType<SimpleTypes>>,
): JSONArray => {
const argTypes = match(arrow.domain)
.with({ tag: "labeledProduct" }, (x) => {
return Object.values(x.fields);
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items;
})
.with({ tag: "nil" }, (x) => {
return [];
})
// uncomment to check that every pattern in matched
// .exhaustive()
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(arrow.domain));
});
if (req.args.length !== argTypes.length) {
throw new Error(
`incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`,
);
}
return req.args.map((arg, index) => {
return aqua2ts(arg, argTypes[index]);
});
};
/**
* Convert value from its typescript representation to representation in aqua
* @param value - the value as represented in typescript
* @param type - definition of the aqua type
* @returns value represented in aqua
*/
export const ts2aqua = (value: JSONValue, type: NonArrowType): JSONValue => {
const res = match(type)
.with({ tag: "nil" }, () => {
return null;
})
.with({ tag: "option" }, (opt) => {
if (value === null || value === undefined) {
return [];
} else {
return [ts2aqua(value, opt.type)];
}
})
.with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => {
return value;
})
.with({ tag: "array" }, (arr) => {
assert(Array.isArray(value), "Should not be possible, bad types");
return value.map((y) => {
return ts2aqua(y, arr.type);
});
})
.with({ tag: "struct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = ts2aqua(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "labeledProduct" }, (x) => {
return Object.entries(x.fields).reduce((agg, [key, type]) => {
const val = ts2aqua(value[key], type);
return { ...agg, [key]: val };
}, {});
})
.with({ tag: "unlabeledProduct" }, (x) => {
return x.items.map((type, index) => {
return ts2aqua(value[index], type);
});
})
// uncomment to check that every pattern in matched
// .exhaustive()
.otherwise(() => {
throw new Error("Unexpected tag: " + jsonify(type));
});
return res;
};
/**
* Convert return type of the service from it's typescript representation to representation in aqua
* @param returnValue - the value as represented in typescript
* @param arrowType - the arrow type which describes the service
* @returns - value represented in aqua
*/
export const returnType2Aqua = (
returnValue: any,
arrowType: ArrowType<NonArrowType>,
) => {
// TODO: cover with tests
if (arrowType.codomain.tag === "nil") {
return {};
}
if (arrowType.codomain.items.length === 0) {
return {};
}
if (arrowType.codomain.items.length === 1) {
return ts2aqua(returnValue, arrowType.codomain.items[0]);
}
return arrowType.codomain.items.map((type, index) => {
return ts2aqua(returnValue[index], type);
});
};
/**
* Converts response value from aqua its representation to representation in typescript
* @param req - call service data
* @param arrow - aqua type definition
* @returns response value in typescript representation
*/
export const responseServiceValue2ts = (
req: CallServiceData,
arrow: ArrowType<any>,
) => {
return match(arrow.codomain)
.with({ tag: "nil" }, () => {
return null;
})
.with({ tag: "unlabeledProduct" }, (x) => {
if (x.items.length === 0) {
return null;
}
if (x.items.length === 1) {
return aqua2ts(req.args[0], x.items[0]);
}
return req.args.map((y, index) => {
return aqua2ts(y, x.items[index]);
});
})
.exhaustive();
};

View File

@ -16,12 +16,15 @@
import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher"; import { fetchResource } from "@fluencelabs/js-client-isomorphic/fetcher";
import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver"; import { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
import { ZodError } from "zod";
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js"; import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
import { import {
ClientConfig, ClientConfig,
configSchema,
ConnectionState, ConnectionState,
RelayOptions, RelayOptions,
relaySchema,
} from "./clientPeer/types.js"; } from "./clientPeer/types.js";
import { callAquaFunction } from "./compilerSupport/callFunction.js"; import { callAquaFunction } from "./compilerSupport/callFunction.js";
import { registerService } from "./compilerSupport/registerService.js"; import { registerService } from "./compilerSupport/registerService.js";
@ -33,6 +36,15 @@ const createClient = async (
relay: RelayOptions, relay: RelayOptions,
config: ClientConfig = {}, config: ClientConfig = {},
): Promise<ClientPeer> => { ): Promise<ClientPeer> => {
try {
relay = relaySchema.parse(relay);
config = configSchema.parse(config);
} catch (e) {
if (e instanceof ZodError) {
throw new Error(JSON.stringify(e.format()));
}
}
const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL; const CDNUrl = config.CDNUrl ?? DEFAULT_CDN_URL;
const fetchMarineJsWasm = async () => { const fetchMarineJsWasm = async () => {

View File

@ -14,8 +14,6 @@
* 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 { it, describe, expect, test } from "vitest"; import { it, describe, expect, test } from "vitest";
@ -28,6 +26,7 @@ import { KeyPair } from "../../keypair/index.js";
import { builtInServices } from "../builtins.js"; import { builtInServices } from "../builtins.js";
import { allowServiceFn } from "../securityGuard.js"; import { allowServiceFn } from "../securityGuard.js";
import { Sig, defaultSigGuard } from "../Sig.js"; import { Sig, defaultSigGuard } from "../Sig.js";
import assert from "assert";
const a10b20 = `{ const a10b20 = `{
"a": 10, "a": 10,
@ -54,32 +53,32 @@ describe("Tests for default handler", () => {
serviceId | fnName | args | retCode | result serviceId | fnName | args | retCode | result
${"op"} | ${"identity"} | ${[]} | ${0} | ${{}} ${"op"} | ${"identity"} | ${[]} | ${0} | ${{}}
${"op"} | ${"identity"} | ${[1]} | ${0} | ${1} ${"op"} | ${"identity"} | ${[1]} | ${0} | ${1}
${"op"} | ${"identity"} | ${[1, 2]} | ${1} | ${"identity accepts up to 1 arguments, received 2 arguments"} ${"op"} | ${"identity"} | ${[1, 2]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}} ${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}}
${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]} ${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3} ${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3}
${"op"} | ${"array_length"} | ${[]} | ${1} | ${"array_length accepts exactly one argument, found: 0"} ${"op"} | ${"array_length"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"}
${"op"} | ${"concat"} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]} ${"op"} | ${"concat"} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
${"op"} | ${"concat"} | ${[[1, 2]]} | ${0} | ${[1, 2]} ${"op"} | ${"concat"} | ${[[1, 2]]} | ${0} | ${[1, 2]}
${"op"} | ${"concat"} | ${[]} | ${0} | ${[]} ${"op"} | ${"concat"} | ${[]} | ${0} | ${[]}
${"op"} | ${"concat"} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"} ${"op"} | ${"concat"} | ${[1, [1, 2], 1]} | ${1} | ${"Argument 0 expected to be of type array, Got number"}
${"op"} | ${"string_to_b58"} | ${["test"]} | ${0} | ${"3yZe7d"} ${"op"} | ${"string_to_b58"} | ${["test"]} | ${0} | ${"3yZe7d"}
${"op"} | ${"string_to_b58"} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"} ${"op"} | ${"string_to_b58"} | ${["test", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"string_from_b58"} | ${["3yZe7d"]} | ${0} | ${"test"} ${"op"} | ${"string_from_b58"} | ${["3yZe7d"]} | ${0} | ${"test"}
${"op"} | ${"string_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"} ${"op"} | ${"string_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"} ${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"} ${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]} ${"op"} | ${"bytes_from_b58"} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
${"op"} | ${"bytes_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"} ${"op"} | ${"bytes_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"sha256_string"} | ${["hello, world!"]} | ${0} | ${"QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u"} ${"op"} | ${"sha256_string"} | ${["hello, world!"]} | ${0} | ${"QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u"}
${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"sha256_string accepts 1 argument, found: 2"} ${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"sha256_string accepts 1 argument, found: 0"} ${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"}
${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""} ${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""}
${"op"} | ${"concat_strings"} | ${["a", "b", "c"]} | ${0} | ${"abc"} ${"op"} | ${"concat_strings"} | ${["a", "b", "c"]} | ${0} | ${"abc"}
${"peer"} | ${"timeout"} | ${[200, []]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} ${"peer"} | ${"timeout"} | ${[200, []]} | ${1} | ${"Argument 1 expected to be of type string, Got array"}
${"peer"} | ${"timeout"} | ${[200, "test"]} | ${0} | ${"test"} ${"peer"} | ${"timeout"} | ${[200, "test"]} | ${0} | ${"test"}
${"peer"} | ${"timeout"} | ${[]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} ${"peer"} | ${"timeout"} | ${[]} | ${1} | ${"Expected 2 argument(s). Got 0"}
${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} ${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${1} | ${"Expected 2 argument(s). Got 3"}
${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'"<empty argument list>"'} ${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'"<empty argument list>"'}
${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20} ${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20}
${"debug"} | ${"stringify"} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour} ${"debug"} | ${"stringify"} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour}

View File

@ -20,7 +20,6 @@ import * as url from "url";
import { it, describe, expect, beforeAll } from "vitest"; import { it, describe, expect, beforeAll } from "vitest";
import { registerService } from "../../compilerSupport/registerService.js"; import { registerService } from "../../compilerSupport/registerService.js";
import { ServiceImpl } from "../../compilerSupport/types.js";
import { KeyPair } from "../../keypair/index.js"; import { KeyPair } from "../../keypair/index.js";
import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js";
import { allowServiceFn } from "../securityGuard.js"; import { allowServiceFn } from "../securityGuard.js";
@ -48,12 +47,12 @@ describe("Sig service test suite", () => {
const customSig = new Sig(customKeyPair); const customSig = new Sig(customKeyPair);
const data = [1, 2, 3, 4, 5]; const data = [1, 2, 3, 4, 5];
const anyService: Record<never, unknown> = customSig;
registerService({ registerService({
peer, peer,
serviceId: "CustomSig", serviceId: "CustomSig",
// TODO: fix this after changing registerService signature service: anyService,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: customSig as unknown as ServiceImpl,
}); });
registerService({ registerService({
@ -89,12 +88,12 @@ describe("Sig service test suite", () => {
const customSig = new Sig(customKeyPair); const customSig = new Sig(customKeyPair);
const data = [1, 2, 3, 4, 5]; const data = [1, 2, 3, 4, 5];
const anyService: Record<never, unknown> = customSig;
registerService({ registerService({
peer, peer,
serviceId: "CustomSig", serviceId: "CustomSig",
// TODO: fix this after changing registerService signature service: anyService,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: customSig as unknown as ServiceImpl,
}); });
registerService({ registerService({

View File

@ -15,7 +15,6 @@
*/ */
import { registerService } from "../../compilerSupport/registerService.js"; import { registerService } from "../../compilerSupport/registerService.js";
import { ServiceImpl } from "../../compilerSupport/types.js";
import { FluencePeer } from "../../jsPeer/FluencePeer.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js";
import { NodeUtils } from "../NodeUtils.js"; import { NodeUtils } from "../NodeUtils.js";
@ -24,11 +23,11 @@ export function registerNodeUtils(
serviceId: string, serviceId: string,
service: NodeUtils, service: NodeUtils,
) { ) {
const anyService: Record<never, unknown> = service;
registerService({ registerService({
peer, peer,
// TODO: fix this after changing registerService signature service: anyService,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: service as unknown as ServiceImpl,
serviceId, serviceId,
}); });
} }

View File

@ -15,7 +15,6 @@
*/ */
import { registerService } from "../../compilerSupport/registerService.js"; import { registerService } from "../../compilerSupport/registerService.js";
import { ServiceImpl } from "../../compilerSupport/types.js";
import { FluencePeer } from "../../jsPeer/FluencePeer.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js";
import { ParticleContext } from "../../jsServiceHost/interfaces.js"; import { ParticleContext } from "../../jsServiceHost/interfaces.js";
import { Sig } from "../Sig.js"; import { Sig } from "../Sig.js";
@ -46,11 +45,11 @@ export function registerSig(
serviceId: string, serviceId: string,
service: Sig, service: Sig,
) { ) {
const anyService: Record<never, unknown> = service;
registerService({ registerService({
peer, peer,
// TODO: fix this after changing registerService signature service: anyService,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: service as unknown as ServiceImpl,
serviceId, serviceId,
}); });
} }

View File

@ -15,7 +15,6 @@
*/ */
import { registerService } from "../../compilerSupport/registerService.js"; import { registerService } from "../../compilerSupport/registerService.js";
import { ServiceImpl } from "../../compilerSupport/types.js";
import { FluencePeer } from "../../jsPeer/FluencePeer.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js";
import { Srv } from "../SingleModuleSrv.js"; import { Srv } from "../SingleModuleSrv.js";
@ -24,12 +23,12 @@ export function registerSrv(
serviceId: string, serviceId: string,
service: Srv, service: Srv,
) { ) {
const anyService: Record<never, unknown> = service;
registerService({ registerService({
peer, peer,
serviceId, serviceId,
// TODO: fix this after changing registerService signature service: anyService,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: service as unknown as ServiceImpl,
}); });
} }

View File

@ -14,74 +14,142 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from "assert";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import { JSONValue } from "@fluencelabs/interfaces"; import { JSONValue } from "@fluencelabs/interfaces";
import bs58 from "bs58"; import bs58 from "bs58";
import { sha256 } from "multiformats/hashes/sha2"; import { sha256 } from "multiformats/hashes/sha2";
import { z } from "zod";
import { import {
CallServiceData,
CallServiceResult, CallServiceResult,
CallServiceResultType, CallServiceResultType,
GenericCallServiceHandler, GenericCallServiceHandler,
ResultCodes, ResultCodes,
} from "../jsServiceHost/interfaces.js"; } from "../jsServiceHost/interfaces.js";
import { getErrorMessage, isString, jsonify } from "../util/utils.js"; import { getErrorMessage, jsonify } from "../util/utils.js";
const success = ( const success = (result: CallServiceResultType): CallServiceResult => {
// TODO: Remove unknown after adding validation to builtin inputs
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
result: CallServiceResultType | unknown,
): CallServiceResult => {
return { return {
// TODO: Remove type assertion after adding validation to builtin inputs result,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
result: result as CallServiceResultType,
retCode: ResultCodes.success, retCode: ResultCodes.success,
}; };
}; };
const error = ( const error = (error: CallServiceResultType): CallServiceResult => {
// TODO: Remove unknown after adding validation to builtin inputs
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
error: CallServiceResultType | unknown,
): CallServiceResult => {
return { return {
// TODO: Remove type assertion after adding validation to builtin inputs result: error,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
result: error as CallServiceResultType,
retCode: ResultCodes.error, retCode: ResultCodes.error,
}; };
}; };
const chunk = <T>(arr: T[]): T[][] => {
const res: T[][] = [];
const chunkSize = 2;
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize);
res.push(chunk);
}
return res;
};
const errorNotImpl = (methodName: string) => { const errorNotImpl = (methodName: string) => {
return error( return error(
`The JS implementation of Peer does not support "${methodName}"`, `The JS implementation of Peer does not support "${methodName}"`,
); );
}; };
const makeJsonImpl = (args: [Record<string, JSONValue>, ...JSONValue[]]) => { const parseWithSchema = <T extends z.ZodTypeAny>(
const [obj, ...kvs] = args; schema: T,
req: CallServiceData,
const toMerge: Record<string, JSONValue> = {}; ): [z.infer<T>, null] | [null, string] => {
const result = schema.safeParse(req.args, {
for (let i = 0; i < kvs.length / 2; i++) { errorMap: (issue, ctx) => {
const k = kvs[i * 2]; if (issue.code === z.ZodIssueCode.invalid_type) {
if (issue.path.length === 1 && typeof issue.path[0] === "number") {
if (!isString(k)) { const [arg] = issue.path;
return error(`Argument ${i * 2 + 1} is expected to be string`); return {
message: `Argument ${arg} expected to be of type ${issue.expected}, Got ${issue.received}`,
};
}
} }
const v = kvs[i * 2 + 1]; if (issue.code === z.ZodIssueCode.too_big) {
toMerge[k] = v; return {
message: `Expected ${
issue.maximum
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
} argument(s). Got ${ctx.data.length}`,
};
} }
const res = { ...obj, ...toMerge }; if (issue.code === z.ZodIssueCode.too_small) {
return success(res); return {
message: `Expected ${
issue.minimum
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
} argument(s). Got ${ctx.data.length}`,
};
}
if (issue.code === z.ZodIssueCode.invalid_union) {
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
message: `Expected argument(s). Got ${ctx.data.length}`,
};
}
return { message: ctx.defaultError };
},
});
if (result.success) {
return [result.data, null];
} else {
return [null, result.error.errors[0].message];
}
};
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() => {
return z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]);
});
const jsonImplSchema = z
.tuple([z.record(jsonSchema)])
.rest(z.tuple([z.string(), jsonSchema]));
const makeJsonImpl = (args: z.infer<typeof jsonImplSchema>) => {
const [obj, ...kvs] = args;
return success({ ...obj, ...Object.fromEntries(kvs) });
};
type withSchema = <T extends z.ZodTypeAny>(
arg: T,
) => (
arg1: (value: z.infer<T>) => CallServiceResult | Promise<CallServiceResult>,
) => (req: CallServiceData) => CallServiceResult | Promise<CallServiceResult>;
const withSchema: withSchema = <T extends z.ZodTypeAny>(schema: T) => {
return (bound) => {
return (req) => {
const [value, message] = parseWithSchema(schema, req);
if (message != null) {
return error(message);
}
return bound(value);
};
};
}; };
// TODO: These assert made for silencing more stricter ts rules. Will be fixed in DXJ-493
export const builtInServices: Record< export const builtInServices: Record<
string, string,
Record<string, GenericCallServiceHandler> Record<string, GenericCallServiceHandler>
@ -116,29 +184,16 @@ export const builtInServices: Record<
return errorNotImpl("peer.get_contact"); return errorNotImpl("peer.get_contact");
}, },
timeout: (req) => { timeout: withSchema(z.tuple([z.number(), z.string()]))(
if (req.args.length !== 2) { ([durationMs, msg]) => {
return error(
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
);
}
const durationMs = req.args[0];
const message = req.args[1];
if (typeof durationMs !== "number" || typeof message !== "string") {
return error(
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
);
}
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
const res = success(message); const res = success(msg);
resolve(res); resolve(res);
}, durationMs); }, durationMs);
}); });
}, },
),
}, },
kad: { kad: {
@ -246,120 +301,48 @@ export const builtInServices: Record<
return success(req.args); return success(req.args);
}, },
array_length: (req) => { array_length: withSchema(z.tuple([z.array(z.unknown())]))(([arr]) => {
if (req.args.length !== 1) { return success(arr.length);
return error( }),
"array_length accepts exactly one argument, found: " +
req.args.length,
);
} else {
assert(Array.isArray(req.args[0]));
return success(req.args[0].length);
}
},
identity: (req) => { identity: withSchema(z.array(jsonSchema).max(1))((args) => {
if (req.args.length > 1) { return success(args.length === 0 ? {} : args[0]);
return error( }),
`identity accepts up to 1 arguments, received ${req.args.length} arguments`,
);
} else {
return success(req.args.length === 0 ? {} : req.args[0]);
}
},
concat: (req) => { concat: withSchema(z.array(z.array(z.unknown())))((args) => {
const incorrectArgIndices = req.args // // concat accepts only 'never' type
.map((x, i): [boolean, number] => {
return [Array.isArray(x), i];
})
.filter(([isArray]) => {
return !isArray;
})
.map(([, index]) => {
return index;
});
if (incorrectArgIndices.length > 0) {
const str = incorrectArgIndices.join(", ");
return error(
`All arguments of 'concat' must be arrays: arguments ${str} are not`,
);
} else {
// TODO: remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return success([].concat(...(req.args as never[][]))); const arr = args as never[][];
} return success([].concat(...arr));
}, }),
string_to_b58: (req) => { string_to_b58: withSchema(z.tuple([z.string()]))(([input]) => {
if (req.args.length !== 1) {
return error("string_to_b58 accepts only one string argument");
} else {
const [input] = req.args;
// TODO: remove after adding validation
assert(typeof input === "string");
return success(bs58.encode(new TextEncoder().encode(input))); return success(bs58.encode(new TextEncoder().encode(input)));
} }),
},
string_from_b58: (req) => { string_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
if (req.args.length !== 1) {
return error("string_from_b58 accepts only one string argument");
} else {
const [input] = req.args;
// TODO: remove after adding validation
assert(typeof input === "string");
return success(new TextDecoder().decode(bs58.decode(input))); return success(new TextDecoder().decode(bs58.decode(input)));
} }),
},
bytes_to_b58: (req) => { bytes_to_b58: withSchema(z.tuple([z.array(z.number())]))(([input]) => {
if (req.args.length !== 1 || !Array.isArray(req.args[0])) { return success(bs58.encode(new Uint8Array(input)));
return error( }),
"bytes_to_b58 accepts only single argument: array of numbers",
);
} else {
// TODO: remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const argumentArray = req.args[0] as number[];
return success(bs58.encode(new Uint8Array(argumentArray)));
}
},
bytes_from_b58: (req) => { bytes_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
if (req.args.length !== 1) {
return error("bytes_from_b58 accepts only one string argument");
} else {
const [input] = req.args;
// TODO: remove after adding validation
assert(typeof input === "string");
return success(Array.from(bs58.decode(input))); return success(Array.from(bs58.decode(input)));
} }),
},
sha256_string: async (req) => { sha256_string: withSchema(z.tuple([z.string()]))(async ([input]) => {
if (req.args.length !== 1) {
return error(
`sha256_string accepts 1 argument, found: ${req.args.length}`,
);
} else {
const [input] = req.args;
// TODO: remove after adding validation
assert(typeof input === "string");
const inBuffer = Buffer.from(input); const inBuffer = Buffer.from(input);
const multihash = await sha256.digest(inBuffer); const multihash = await sha256.digest(inBuffer);
return success(bs58.encode(multihash.bytes)); return success(bs58.encode(multihash.bytes));
} }),
},
concat_strings: (req) => { concat_strings: withSchema(z.array(z.string()))((args) => {
// TODO: remove after adding validation const res = "".concat(...args);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const res = "".concat(...(req.args as string[]));
return success(res); return success(res);
}, }),
}, },
debug: { debug: {
@ -379,365 +362,187 @@ export const builtInServices: Record<
}, },
math: { math: {
add: (req) => { add: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x + y); return success(x + y);
}, }),
sub: (req) => { sub: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x - y); return success(x - y);
}, }),
mul: (req) => { mul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x * y); return success(x * y);
}, }),
fmul: (req) => { fmul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(Math.floor(x * y)); return success(Math.floor(x * y));
}, }),
div: (req) => { div: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(Math.floor(x / y)); return success(Math.floor(x / y));
}, }),
rem: (req) => { rem: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x % y); return success(x % y);
}, }),
pow: (req) => { pow: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(Math.pow(x, y)); return success(Math.pow(x, y));
}, }),
log: (req) => { log: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(Math.log(y) / Math.log(x)); return success(Math.log(y) / Math.log(x));
}, }),
}, },
cmp: { cmp: {
gt: (req) => { gt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x > y); return success(x > y);
}, }),
gte: (req) => { gte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x >= y); return success(x >= y);
}, }),
lt: (req) => { lt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x < y); return success(x < y);
}, }),
lte: (req) => { lte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x <= y); return success(x <= y);
}, }),
cmp: (req) => { cmp: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [x, y] = req.args;
// TODO: Remove after adding validation
assert(typeof x === "number" && typeof y === "number");
return success(x === y ? 0 : x > y ? 1 : -1); return success(x === y ? 0 : x > y ? 1 : -1);
}, }),
}, },
array: { array: {
sum: (req) => { sum: withSchema(z.tuple([z.array(z.number())]))(([xs]) => {
let err;
if ((err = checkForArgumentsCount(req, 1)) != null) {
return err;
}
// TODO: Remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const [xs] = req.args as [number[]];
return success( return success(
xs.reduce((agg, cur) => { xs.reduce((agg, cur) => {
return agg + cur; return agg + cur;
}, 0), }, 0),
); );
}, }),
dedup: (req) => { dedup: withSchema(z.tuple([z.array(z.any())]))(([xs]) => {
let err;
if ((err = checkForArgumentsCount(req, 1)) != null) {
return err;
}
const [xs] = req.args;
// TODO: Remove after adding validation
assert(Array.isArray(xs));
const set = new Set(xs); const set = new Set(xs);
return success(Array.from(set)); return success(Array.from(set));
}, }),
intersect: (req) => {
let err;
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [xs, ys] = req.args;
// TODO: Remove after adding validation
assert(Array.isArray(xs) && Array.isArray(ys));
intersect: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
([xs, ys]) => {
const intersection = xs.filter((x) => { const intersection = xs.filter((x) => {
return ys.includes(x); return ys.includes(x);
}); });
return success(intersection); return success(intersection);
}, },
),
diff: (req) => { diff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
let err; ([xs, ys]) => {
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [xs, ys] = req.args;
// TODO: Remove after adding validation
assert(Array.isArray(xs) && Array.isArray(ys));
const diff = xs.filter((x) => { const diff = xs.filter((x) => {
return !ys.includes(x); return !ys.includes(x);
}); });
return success(diff); return success(diff);
}, },
),
sdiff: (req) => { sdiff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
let err; ([xs, ys]) => {
if ((err = checkForArgumentsCount(req, 2)) != null) {
return err;
}
const [xs, ys] = req.args;
// TODO: Remove after adding validation
assert(Array.isArray(xs) && Array.isArray(ys));
const sdiff = [ const sdiff = [
// force new line xs.filter((y) => {
...xs.filter((y) => {
return !ys.includes(y); return !ys.includes(y);
}), }),
...ys.filter((x) => { ys.filter((x) => {
return !xs.includes(x); return !xs.includes(x);
}), }),
]; ].flat();
return success(sdiff); return success(sdiff);
}, },
),
}, },
json: { json: {
obj: (req) => { obj: withSchema(
let err; z
.array(z.unknown())
if ((err = checkForArgumentsCountEven(req)) != null) { .refine(
return err; (arr) => {
} return arr.length % 2 === 0;
// TODO: remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return makeJsonImpl([{}, ...req.args] as [
Record<string, JSONValue>,
...JSONValue[],
]);
}, },
(arr) => {
put: (req) => { return {
let err; message: "Expected even number of argument(s). Got " + arr.length,
};
if ((err = checkForArgumentsCount(req, 3)) != null) {
return err;
}
if ((err = checkForArgumentType(req, 0, "object")) != null) {
return err;
}
return makeJsonImpl(
// TODO: remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
req.args as [Record<string, JSONValue>, ...JSONValue[]],
);
}, },
)
.transform((args) => {
return chunk(args);
})
.pipe(z.array(z.tuple([z.string(), jsonSchema]))),
)((args) => {
return makeJsonImpl([{}, ...args]);
}),
puts: (req) => { put: withSchema(
let err; z
.tuple([z.record(jsonSchema), z.string(), jsonSchema])
if ((err = checkForArgumentsCountOdd(req)) != null) { .transform(
return err; ([obj, name, value]): [{ [key: string]: Json }, [string, Json]] => {
} return [obj, [name, value]];
if ((err = checkForArgumentsCountMoreThan(req, 3)) != null) {
return err;
}
if ((err = checkForArgumentType(req, 0, "object")) != null) {
return err;
}
return makeJsonImpl(
// TODO: remove after adding validation
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
req.args as [Record<string, JSONValue>, ...JSONValue[]],
);
}, },
),
)(makeJsonImpl),
stringify: (req) => { puts: withSchema(
let err; z
.array(z.unknown())
.refine(
(arr) => {
return arr.length >= 3;
},
(value) => {
return {
message: `Expected more than 3 argument(s). Got ${value.length}`,
};
},
)
.refine(
(arr) => {
return arr.length % 2 === 1;
},
{
message: "Argument count must be odd.",
},
)
.transform((args) => {
return [args[0], ...chunk(args.slice(1))];
})
.pipe(jsonImplSchema),
)(makeJsonImpl),
if ((err = checkForArgumentsCount(req, 1)) != null) { stringify: withSchema(z.tuple([z.record(z.string(), jsonSchema)]))(
return err; ([json]) => {
}
if ((err = checkForArgumentType(req, 0, "object")) != null) {
return err;
}
const [json] = req.args;
const res = JSON.stringify(json); const res = JSON.stringify(json);
return success(res); return success(res);
}, },
),
parse: (req) => { parse: withSchema(z.tuple([z.string()]))(([raw]) => {
let err;
if ((err = checkForArgumentsCount(req, 1)) != null) {
return err;
}
if ((err = checkForArgumentType(req, 0, "string")) != null) {
return err;
}
const [raw] = req.args;
try { try {
// TODO: Remove after adding validation // Parsing any argument here yields JSONValue
assert(typeof raw === "string"); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const json = JSON.parse(raw); const json = JSON.parse(raw) as JSONValue;
return success(json); return success(json);
} catch (err: unknown) { } catch (err: unknown) {
return error(getErrorMessage(err)); return error(getErrorMessage(err));
} }
}, }),
}, },
"run-console": { "run-console": {
@ -749,59 +554,3 @@ export const builtInServices: Record<
}, },
}, },
} as const; } as const;
const checkForArgumentsCount = (
req: { args: Array<unknown> },
count: number,
) => {
if (req.args.length !== count) {
return error(`Expected ${count} argument(s). Got ${req.args.length}`);
}
return null;
};
const checkForArgumentsCountMoreThan = (
req: { args: Array<unknown> },
count: number,
) => {
if (req.args.length < count) {
return error(
`Expected more than ${count} argument(s). Got ${req.args.length}`,
);
}
return null;
};
const checkForArgumentsCountEven = (req: { args: Array<unknown> }) => {
if (req.args.length % 2 === 1) {
return error(`Expected even number of argument(s). Got ${req.args.length}`);
}
return null;
};
const checkForArgumentsCountOdd = (req: { args: Array<unknown> }) => {
if (req.args.length % 2 === 0) {
return error(`Expected odd number of argument(s). Got ${req.args.length}`);
}
return null;
};
const checkForArgumentType = (
req: { args: Array<unknown> },
index: number,
type: string,
) => {
const actual = typeof req.args[index];
if (actual !== type) {
return error(
`Argument ${index} expected to be of type ${type}, Got ${actual}`,
);
}
return null;
};

View File

@ -0,0 +1,28 @@
/**
* 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 { classToPlain } from "class-transformer";
import { NodeUtils } from "./services/NodeUtils.js";
import { Fluence } from "./index.js";
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
console.log(classToPlain(new NodeUtils(Fluence.defaultClient!)));
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
console.log(
Object.getPrototypeOf(classToPlain(new NodeUtils(Fluence.defaultClient!))),
);

View File

@ -24,7 +24,7 @@
"vitest": "0.34.6" "vitest": "0.34.6"
}, },
"dependencies": { "dependencies": {
"@fluencelabs/marine-js": "0.7.2", "@fluencelabs/marine-js": "0.8.0",
"observable-fns": "0.6.1", "observable-fns": "0.6.1",
"@fluencelabs/threads": "^2.0.0" "@fluencelabs/threads": "^2.0.0"
} }

View File

@ -28,7 +28,6 @@ import type {
} from "@fluencelabs/marine-js/dist/types"; } from "@fluencelabs/marine-js/dist/types";
import { import {
defaultCallParameters, defaultCallParameters,
JSONValue,
logLevelToEnv, logLevelToEnv,
} from "@fluencelabs/marine-js/dist/types"; } from "@fluencelabs/marine-js/dist/types";
import { expose } from "@fluencelabs/threads/worker"; import { expose } from "@fluencelabs/threads/worker";
@ -140,9 +139,7 @@ const toExpose = {
throw new Error(`service with id=${serviceId} not found`); throw new Error(`service with id=${serviceId} not found`);
} }
// TODO: Make MarineService return JSONValue type return srv.call(functionName, args, callParams);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return srv.call(functionName, args, callParams) as JSONValue;
}, },
onLogMessage() { onLogMessage() {

55
pnpm-lock.yaml generated
View File

@ -190,6 +190,9 @@ importers:
vitest: vitest:
specifier: 0.34.6 specifier: 0.34.6
version: 0.34.6 version: 0.34.6
zod:
specifier: 3.22.4
version: 3.22.4
packages/core/interfaces: packages/core/interfaces:
devDependencies: devDependencies:
@ -244,9 +247,6 @@ importers:
'@multiformats/multiaddr': '@multiformats/multiaddr':
specifier: 11.3.0 specifier: 11.3.0
version: 11.3.0 version: 11.3.0
assert:
specifier: 2.1.0
version: 2.1.0
async: async:
specifier: 3.2.4 specifier: 3.2.4
version: 3.2.4 version: 3.2.4
@ -256,6 +256,9 @@ importers:
buffer: buffer:
specifier: 6.0.3 specifier: 6.0.3
version: 6.0.3 version: 6.0.3
class-transformer:
specifier: 0.5.1
version: 0.5.1
debug: debug:
specifier: 4.3.4 specifier: 4.3.4
version: 4.3.4 version: 4.3.4
@ -289,13 +292,16 @@ importers:
zod: zod:
specifier: 3.22.4 specifier: 3.22.4
version: 3.22.4 version: 3.22.4
zod-validation-error:
specifier: 2.1.0
version: 2.1.0(zod@3.22.4)
devDependencies: devDependencies:
'@fluencelabs/aqua-api': '@fluencelabs/aqua-api':
specifier: 0.9.3 specifier: 0.9.3
version: 0.9.3 version: 0.9.3
'@fluencelabs/marine-js': '@fluencelabs/marine-js':
specifier: 0.7.2 specifier: 0.8.0
version: 0.7.2 version: 0.8.0
'@rollup/plugin-inject': '@rollup/plugin-inject':
specifier: 5.0.3 specifier: 5.0.3
version: 5.0.3 version: 5.0.3
@ -333,8 +339,8 @@ importers:
specifier: 0.54.0 specifier: 0.54.0
version: 0.54.0 version: 0.54.0
'@fluencelabs/marine-js': '@fluencelabs/marine-js':
specifier: 0.7.2 specifier: 0.8.0
version: 0.7.2 version: 0.8.0
'@fluencelabs/marine-worker': '@fluencelabs/marine-worker':
specifier: 0.4.2 specifier: 0.4.2
version: link:../marine-worker version: link:../marine-worker
@ -345,8 +351,8 @@ importers:
packages/core/marine-worker: packages/core/marine-worker:
dependencies: dependencies:
'@fluencelabs/marine-js': '@fluencelabs/marine-js':
specifier: 0.7.2 specifier: 0.8.0
version: 0.7.2 version: 0.8.0
'@fluencelabs/threads': '@fluencelabs/threads':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0 version: 2.0.0
@ -3714,8 +3720,8 @@ packages:
/@fluencelabs/avm@0.54.0: /@fluencelabs/avm@0.54.0:
resolution: {integrity: sha512-5GgROVly/vC7gasltr6/3TIY8vfV6b+SPfWUAGWnyXdbWt4jJANLO2YtXdaUsdNk9PiwOep7TMjLnypljdyMjQ==} resolution: {integrity: sha512-5GgROVly/vC7gasltr6/3TIY8vfV6b+SPfWUAGWnyXdbWt4jJANLO2YtXdaUsdNk9PiwOep7TMjLnypljdyMjQ==}
/@fluencelabs/marine-js@0.7.2: /@fluencelabs/marine-js@0.8.0:
resolution: {integrity: sha512-etjbXDgzyZkK82UZvtuIU3bfy5f52siDUy1m+T5Y5r70k82xYdZZ8vgWVgB6ivi2f3aDyQjgNTfzWQjKFpAReQ==} resolution: {integrity: sha512-exxp0T0Dk69dxnbpAiVc/qp66s8Jq/P71TRB9aeQZLZy3EQtVAMCBJvwQY8LzVVlYEyVjmqQkFG/N0rAeYU1vg==}
dependencies: dependencies:
'@wasmer/wasi': 0.12.0 '@wasmer/wasi': 0.12.0
'@wasmer/wasmfs': 0.12.0 '@wasmer/wasmfs': 0.12.0
@ -5910,16 +5916,6 @@ packages:
util: 0.12.5 util: 0.12.5
dev: true dev: true
/assert@2.1.0:
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
dependencies:
call-bind: 1.0.2
is-nan: 1.3.2
object-is: 1.1.5
object.assign: 4.1.4
util: 0.12.5
dev: false
/assertion-error@1.1.0: /assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true dev: true
@ -6675,6 +6671,10 @@ packages:
resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==}
dev: false dev: false
/class-transformer@0.5.1:
resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
dev: false
/clean-css@5.3.2: /clean-css@5.3.2:
resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==} resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==}
engines: {node: '>= 10.0'} engines: {node: '>= 10.0'}
@ -9351,6 +9351,7 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
dev: true
/is-glob@4.0.3: /is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
@ -9376,6 +9377,7 @@ packages:
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
define-properties: 1.2.0 define-properties: 1.2.0
dev: true
/is-negative-zero@2.0.2: /is-negative-zero@2.0.2:
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
@ -14375,6 +14377,7 @@ packages:
is-generator-function: 1.0.10 is-generator-function: 1.0.10
is-typed-array: 1.1.12 is-typed-array: 1.1.12
which-typed-array: 1.1.11 which-typed-array: 1.1.11
dev: true
/utila@0.4.0: /utila@0.4.0:
resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==}
@ -15218,6 +15221,14 @@ packages:
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
dev: true dev: true
/zod-validation-error@2.1.0(zod@3.22.4):
resolution: {integrity: sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.18.0
dependencies:
zod: 3.22.4
dev: false
/zod@3.22.4: /zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false