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/trust-graph": "0.4.7",
"ts-pattern": "5.0.5",
"vitest": "0.34.6"
"vitest": "0.34.6",
"zod": "3.22.4"
},
"peerDependencies": {
"@fluencelabs/js-client": "workspace:^"

View File

@ -27,17 +27,14 @@ interface TsOutput {
sources: string;
}
type LanguageOutput = {
js: JsOutput;
ts: TsOutput;
};
type LanguageOutput = JsOutput | TsOutput;
type NothingToGenerate = null;
export default async function aquaToJs<T extends OutputType>(
export default async function aquaToJs(
res: CompilationResult,
outputType: T,
): Promise<LanguageOutput[T] | NothingToGenerate> {
outputType: OutputType,
): Promise<LanguageOutput | NothingToGenerate> {
if (
Object.keys(res.services).length === 0 &&
Object.keys(res.functions).length === 0
@ -49,12 +46,10 @@ export default async function aquaToJs<T extends OutputType>(
return outputType === "js"
? {
sources: generateSources(res, "js", packageJson),
sources: generateSources(res, outputType, 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, "ts", packageJson),
} as LanguageOutput[T]);
: {
sources: generateSources(res, outputType, packageJson),
};
}

View File

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

View File

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

View File

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

View File

@ -18,21 +18,14 @@ import { readFile } from "fs/promises";
import { createRequire } from "module";
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 assetPath path of required asset in given package
* @param root CDN domain in browser or js-client itself in node
*/
export async function fetchResource(
pkg: FetchedPackages,
assetPath: string,
root: string,
) {
export const fetchResource: FetchResourceFn = async (pkg, assetPath) => {
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 packagePathIndex = require.resolve(name);
@ -47,7 +40,7 @@ export async function fetchResource(
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);
@ -60,4 +53,4 @@ export async function fetchResource(
: "application/text",
},
});
}
};

View File

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

View File

@ -20,10 +20,10 @@ import { fileURLToPath } from "url";
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";
export const getWorker: GetWorker = (pkg: FetchedPackages) => {
export const getWorker: GetWorkerFn = (pkg: FetchedPackages) => {
const require = createRequire(import.meta.url);
const pathToThisFile = dirname(fileURLToPath(import.meta.url));

View File

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

View File

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

View File

@ -61,7 +61,6 @@ export const callAquaFunction = async ({
peer,
args,
}: 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 });
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')
// 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);
});
};

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 { getWorker } from "@fluencelabs/js-client-isomorphic/worker-resolver";
import { ZodError } from "zod";
import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js";
import {
ClientConfig,
configSchema,
ConnectionState,
RelayOptions,
relaySchema,
} from "./clientPeer/types.js";
import { callAquaFunction } from "./compilerSupport/callFunction.js";
import { registerService } from "./compilerSupport/registerService.js";
@ -33,6 +36,15 @@ const createClient = async (
relay: RelayOptions,
config: ClientConfig = {},
): 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 fetchMarineJsWasm = async () => {

View File

@ -14,8 +14,6 @@
* limitations under the License.
*/
import assert from "assert";
import { JSONArray } from "@fluencelabs/interfaces";
import { toUint8Array } from "js-base64";
import { it, describe, expect, test } from "vitest";
@ -28,6 +26,7 @@ import { KeyPair } from "../../keypair/index.js";
import { builtInServices } from "../builtins.js";
import { allowServiceFn } from "../securityGuard.js";
import { Sig, defaultSigGuard } from "../Sig.js";
import assert from "assert";
const a10b20 = `{
"a": 10,
@ -54,32 +53,32 @@ describe("Tests for default handler", () => {
serviceId | fnName | args | retCode | result
${"op"} | ${"identity"} | ${[]} | ${0} | ${{}}
${"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"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 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]]} | ${0} | ${[1, 2]}
${"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", 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", 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], 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", 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!", true]} | ${1} | ${"sha256_string accepts 1 argument, found: 2"}
${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"sha256_string accepts 1 argument, found: 0"}
${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"Expected 1 argument(s). Got 2"}
${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"}
${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""}
${"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"} | ${[]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"}
${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${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} | ${"Expected 2 argument(s). Got 3"}
${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'"<empty argument list>"'}
${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20}
${"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 { registerService } from "../../compilerSupport/registerService.js";
import { ServiceImpl } from "../../compilerSupport/types.js";
import { KeyPair } from "../../keypair/index.js";
import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js";
import { allowServiceFn } from "../securityGuard.js";
@ -48,12 +47,12 @@ describe("Sig service test suite", () => {
const customSig = new Sig(customKeyPair);
const data = [1, 2, 3, 4, 5];
const anyService: Record<never, unknown> = customSig;
registerService({
peer,
serviceId: "CustomSig",
// TODO: fix this after changing registerService signature
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: customSig as unknown as ServiceImpl,
service: anyService,
});
registerService({
@ -89,12 +88,12 @@ describe("Sig service test suite", () => {
const customSig = new Sig(customKeyPair);
const data = [1, 2, 3, 4, 5];
const anyService: Record<never, unknown> = customSig;
registerService({
peer,
serviceId: "CustomSig",
// TODO: fix this after changing registerService signature
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
service: customSig as unknown as ServiceImpl,
service: anyService,
});
registerService({

View File

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

View File

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

View File

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

View File

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

View File

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

55
pnpm-lock.yaml generated
View File

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