Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,9 @@
"lerna": "4.0.0",
"prettier": "2.2.1",
"rimraf": "3.0.2"
},
"resolutions": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security patch or?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was removed here: #1452

And it turns out it's needed within the top-level package.json because of a problem w/ package resolution within the monorepo. React is still used within the template project, and when you install the react template's deps directly (cd packages/templates/app/typescript-react && yarn) it works, but at the root monorepo level it fails due to mismatched dependencies.

"@types/react": "16.9.0",
"@types/react-dom": "16.9.0"
}
}
109 changes: 1 addition & 108 deletions packages/js/client/src/__tests__/core/wasm-wrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { buildWrapper } from "@polywrap/test-env-js";
import { msgpackDecode } from "@polywrap/msgpack-js";
import { GetPathToTestWrappers } from "@polywrap/test-cases";
import fs from "fs";
import { Uri, Subscription, PolywrapClient, IWrapPackage } from "../..";
import { Uri, PolywrapClient, IWrapPackage } from "../..";
import { WrapManifest } from "@polywrap/wrap-manifest-types-js";
import { makeMemoryStoragePlugin } from "../e2e/memory-storage";
import { PluginModule, PluginPackage } from "@polywrap/plugin-js";
import { UriResolver } from "@polywrap/uri-resolvers-js";
import { ErrResult } from "../utils/resultTypes";
Expand All @@ -16,15 +15,9 @@ jest.setTimeout(200000);
const simpleWrapperPath = `${GetPathToTestWrappers()}/wasm-as/simple`;
const simpleWrapperUri = new Uri(`fs/${simpleWrapperPath}/build`);

const memoryStoragePluginUri = "wrap://ens/memory-storage.polywrap.eth";

const simpleMemoryWrapperPath = `${GetPathToTestWrappers()}/wasm-as/simple-memory`;
const simpleMemoryWrapperUri = new Uri(`fs/${simpleMemoryWrapperPath}/build`);

describe("wasm-wrapper", () => {
beforeAll(async () => {
await buildWrapper(simpleWrapperPath);
await buildWrapper(simpleMemoryWrapperPath);
});

const mockPlugin = (): IWrapPackage => {
Expand Down Expand Up @@ -229,104 +222,4 @@ describe("wasm-wrapper", () => {
"client.getFile(...) is not implemented for Plugins."
);
});

test("subscribe", async () => {
const client = new PolywrapClient({
resolvers: [
{
uri: memoryStoragePluginUri,
package: makeMemoryStoragePlugin({}),
},
],
});

// test subscription
let expectedResults: number[] = [];
let results: number[] = [];
let value = 0;

const setter = setInterval(async () => {
expectedResults.push(value);

await client.invoke({
uri: simpleMemoryWrapperUri.uri,
method: "setData",
args: {
value: value++,
},
});
}, 500);

const getSubscription: Subscription<number> = client.subscribe<number>({
uri: simpleMemoryWrapperUri.uri,
method: "getData",
frequency: { ms: 650 },
});

for await (let result of getSubscription) {
if (!result.ok) fail(result.error);
const val = result.value;

if (val !== undefined) {
results.push(val);
if (results.length >= 2) {
break;
}
}
}
clearInterval(setter);

expect(results).toStrictEqual(expectedResults);
});

test("subscription early stop", async () => {
const client = new PolywrapClient({
resolvers: [
{
uri: memoryStoragePluginUri,
package: makeMemoryStoragePlugin({}),
},
],
});

// test subscription
let results: number[] = [];
let value = 0;

const setter = setInterval(async () => {
await client.invoke({
uri: simpleMemoryWrapperUri.uri,
method: "setData",
args: {
value: value++,
},
});
}, 500);

const getSubscription: Subscription<number> = client.subscribe<number>({
uri: simpleMemoryWrapperUri.uri,
method: "getData",
frequency: { ms: 550 },
});

new Promise(async () => {
for await (let result of getSubscription) {
if (!result.ok) fail(result.error);
const val = result.value;

if (val !== undefined) {
results.push(val);
if (val >= 2) {
break;
}
}
}
});
await new Promise((r) => setTimeout(r, 1000));
getSubscription.stop();
clearInterval(setter);

expect(results).toContain(0);
expect(results).not.toContain(2);
});
});
217 changes: 0 additions & 217 deletions packages/js/core-client/src/PolywrapCoreClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,14 @@ import {
InterfaceImplementations,
InvokeOptions,
InvokerOptions,
QueryOptions,
SubscribeOptions,
Subscription,
Uri,
createQueryDocument,
getImplementations,
parseQuery,
TryResolveUriOptions,
IUriResolver,
IUriResolutionContext,
UriPackageOrWrapper,
UriResolutionContext,
getEnvFromUriHistory,
QueryResult,
InvokeResult,
ValidateOptions,
buildCleanUriHistory,
Expand Down Expand Up @@ -226,106 +220,6 @@ export class PolywrapCoreClient implements CoreClient {
return ResultOk(uris);
}

/**
* Invoke a wrapper using GraphQL query syntax
*
* @remarks
* This method behaves similar to the invoke method and allows parallel requests,
* but the syntax is more verbose. If the query is successful, data will be returned
* and the `error` value of the returned object will be undefined. If the query fails,
* the data property will be undefined and the error property will be populated.
*
* @param options - {
* // The Wrapper's URI
* uri: TUri;
*
* // The GraphQL query to parse and execute, leading to one or more Wrapper invocations.
* query: string | QueryDocument;
*
* // Variables referenced within the query string via GraphQL's '$variable' syntax.
* variables?: TVariables;
* }
*
* @returns A Promise containing an object with either the data or an error
*/
@Tracer.traceMethod("PolywrapClient: query", TracingLevel.High)
public async query<
TData extends Record<string, unknown> = Record<string, unknown>,
TVariables extends Record<string, unknown> = Record<string, unknown>,
TUri extends Uri | string = string
>(options: QueryOptions<TVariables, TUri>): Promise<QueryResult<TData>> {
let result: QueryResult<TData>;

err: try {
const typedOptions: QueryOptions<TVariables, Uri> = {
...options,
uri: Uri.from(options.uri),
};

const { uri, query, variables } = typedOptions;

// Convert the query string into a query document
const queryDocument =
typeof query === "string" ? createQueryDocument(query) : query;

// Parse the query to understand what's being invoked
const parseResult = parseQuery(uri, queryDocument, variables);
if (!parseResult.ok) {
result = { errors: [parseResult.error as Error] };
break err;
}
const queryInvocations = parseResult.value;

// Execute all invocations in parallel
const parallelInvocations: Promise<{
name: string;
result: InvokeResult<unknown>;
}>[] = [];

for (const invocationName of Object.keys(queryInvocations)) {
parallelInvocations.push(
this.invoke({
...queryInvocations[invocationName],
uri: queryInvocations[invocationName].uri,
}).then((result) => ({
name: invocationName,
result,
}))
);
}

// Await the invocations
const invocationResults = await Promise.all(parallelInvocations);

Tracer.addEvent("invocationResults", invocationResults);

// Aggregate all invocation results
const data: Record<string, unknown> = {};
const errors: Error[] = [];

for (const invocation of invocationResults) {
if (invocation.result.ok) {
data[invocation.name] = invocation.result.value;
} else {
errors.push(invocation.result.error as Error);
}
}

result = {
data: data as TData,
errors: errors.length === 0 ? undefined : errors,
};
} catch (error: unknown) {
if (Array.isArray(error)) {
result = { errors: error };
} else {
result = { errors: [error as Error] };
}
}

return result;
}

/**
* Invoke a wrapper using standard syntax and an instance of the wrapper
*
Expand Down Expand Up @@ -456,117 +350,6 @@ export class PolywrapCoreClient implements CoreClient {
}
}

/**
* Invoke a wrapper at a regular frequency (within ~16ms)
*
* @param options - {
* // The Wrapper's URI
* uri: TUri;
*
* // Method to be executed.
* method: string;
*
* //Arguments for the method, structured as a map, removing the chance of incorrectly ordering arguments.
* args?: Record<string, unknown> | Uint8Array;
*
* // Env variables for the wrapper invocation.
* env?: Record<string, unknown>;
*
* resolutionContext?: IUriResolutionContext;
*
* // if true, return value is a msgpack-encoded byte array
* encodeResult?: boolean;
*
* // the frequency at which to perform the invocation
* frequency?: {
* ms?: number;
* sec?: number;
* min?: number;
* hours?: number;
* }
* }
*
* @returns A Promise with a Result containing the return value or an error
*/
@Tracer.traceMethod("PolywrapClient: subscribe")
public subscribe<TData = unknown, TUri extends Uri | string = string>(
options: SubscribeOptions<TUri>
): Subscription<TData> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const thisClient: PolywrapCoreClient = this;

const typedOptions: SubscribeOptions<Uri> = {
...options,
uri: Uri.from(options.uri),
};
const { uri, method, args, frequency: freq } = typedOptions;

// calculate interval between invokes, in milliseconds, 1 min default value
/* eslint-disable prettier/prettier */
let frequency: number;
if (freq && (freq.ms || freq.sec || freq.min || freq.hours)) {
frequency =
(freq.ms ?? 0) +
((freq.hours ?? 0) * 3600 + (freq.min ?? 0) * 60 + (freq.sec ?? 0)) *
1000;
} else {
frequency = 60000;
}
/* eslint-enable prettier/prettier */

const subscription: Subscription<TData> = {
frequency: frequency,
isActive: false,
stop(): void {
subscription.isActive = false;
},
async *[Symbol.asyncIterator](): AsyncGenerator<InvokeResult<TData>> {
let timeout: NodeJS.Timeout | undefined = undefined;
subscription.isActive = true;

try {
let readyVals = 0;
let sleep: ((value?: unknown) => void) | undefined;

timeout = setInterval(() => {
readyVals++;
if (sleep) {
sleep();
sleep = undefined;
}
}, frequency);

while (subscription.isActive) {
if (readyVals === 0) {
await new Promise((r) => (sleep = r));
}

for (; readyVals > 0; readyVals--) {
if (!subscription.isActive) {
break;
}

const result = await thisClient.invoke<TData, Uri>({
uri: uri,
method: method,
args: args,
});

yield result;
}
}
} finally {
if (timeout) {
clearInterval(timeout);
}
subscription.isActive = false;
}
},
};

return subscription;
}

/**
* Resolve a URI to a wrap package, a wrapper, or a uri
*
Expand Down
4 changes: 1 addition & 3 deletions packages/js/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
"dependencies": {
"@polywrap/result": "0.10.0-pre.6",
"@polywrap/tracing-js": "0.10.0-pre.6",
"@polywrap/wrap-manifest-types-js": "0.10.0-pre.6",
"graphql": "15.5.0",
"graphql-tag": "2.10.4"
"@polywrap/wrap-manifest-types-js": "0.10.0-pre.6"
},
"devDependencies": {
"@types/jest": "26.0.8",
Expand Down
Loading