Skip to content

Commit 08c297d

Browse files
committed
Implement Module node for running subgraphs
1 parent 2a7b20b commit 08c297d

File tree

9 files changed

+233
-2
lines changed

9 files changed

+233
-2
lines changed

examples/module.ts

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env -S npx ts-node --transpileOnly
2+
3+
import { Substrate, Box, Module, sb } from "substrate";
4+
5+
async function main() {
6+
const SUBSTRATE_API_KEY = process.env["SUBSTRATE_API_KEY"];
7+
8+
const substrate = new Substrate({ apiKey: SUBSTRATE_API_KEY });
9+
10+
const x = sb.input({ type: "string", default: "hello" });
11+
const y = sb.input({ type: "string" });
12+
const z = sb.input({ type: "object", properties: {} });
13+
14+
const a = new Box({ value: { a: x, z: z, array: [x, x, x] } }, { id: "A" });
15+
const b = new Box(
16+
{ value: { b: sb.interpolate`x=${a.future.value.get("a")}, y=${y}` } },
17+
{ id: "B" },
18+
);
19+
20+
// publish the module on substrate.run
21+
// const publication = await substrate.module.publish({
22+
// name: "my reusable graph",
23+
// nodes: [a, b],
24+
// inputs: { x, y, z },
25+
// });
26+
27+
// update the module on substrate.run
28+
// const updated = await substrate.module.publish({
29+
// id: publication.id,
30+
// name: "my reusable graph (edited)",
31+
// nodes: [a, b],
32+
// inputs: { x, y, z },
33+
// });
34+
35+
// using the module from JSON
36+
const mod = new Module({
37+
module_json: substrate.module.serialize({
38+
nodes: [a, b],
39+
inputs: { x, y, z },
40+
}),
41+
inputs: {
42+
// when commented will use "hello" because it is defined as the default above
43+
// x: 123,
44+
y: "yyy",
45+
z: {
46+
arr: ["123"],
47+
},
48+
},
49+
});
50+
51+
// using the module from publication/module id
52+
// const mod = new Module({
53+
// module_id: publication.id,
54+
// inputs: { y: "yyy", z: { arr: ["123"] } },
55+
// });
56+
57+
// using the module from publication/module uri
58+
// const mod = new Module({
59+
// module_uri: publication.uri,
60+
// inputs: { y: "yyy", z: { arr: ["123"] } },
61+
// });
62+
63+
const c = new Box(
64+
{
65+
value: {
66+
"1": mod.future.get("A.value.z.arr[0]"),
67+
"2": mod.future.get("B.value.b"),
68+
},
69+
},
70+
{ id: "C" },
71+
);
72+
73+
const res = await substrate.run(mod, c);
74+
console.log(JSON.stringify(res.json, null, 2));
75+
}
76+
main();

examples/types.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env -S npx ts-node --transpileOnly
2+
3+
4+
class Foo<F extends (...args: any[]) => any> {
5+
f: F;
6+
7+
constructor(f: F) {
8+
this.f = f;
9+
}
10+
11+
run(...args: Parameters<F>): ReturnType<F> {
12+
return this.f(...args);
13+
}
14+
}
15+
16+
// (a: number, b: number) => number
17+
const add = (a: number, b: number) => a + b;
18+
19+
const foo = new Foo(add);
20+
// run(a: number, b: number): ReturnType<(a: number, b: number) => number>
21+
const res = foo.run(1, 2);

package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"vitest": "^1.0.4"
4444
},
4545
"dependencies": {
46+
"@types/json-schema": "^7.0.15",
4647
"@types/node-fetch": "^2.6.11",
4748
"node-fetch": "2.7.0",
4849
"pako": "^2.1.0"

src/Future.ts

+53-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { idGenerator } from "substrate/idGenerator";
22
import { Node } from "substrate/Node";
3+
import { type JSONSchema7 } from "json-schema";
34

45
type Accessor = "item" | "attr";
56
type TraceOperation = {
@@ -26,6 +27,7 @@ const parsePath = (path: string): TraceProp[] => {
2627
};
2728

2829
const newFutureId = idGenerator("future");
30+
const newInputId = idGenerator("input");
2931

3032
abstract class Directive {
3133
abstract items: any[];
@@ -122,7 +124,7 @@ export class JQ extends Directive {
122124
rawValue: (val: JQCompatible) => ({ future_id: null, val }),
123125
};
124126

125-
override next(...items: TraceProp[]) {
127+
override next(..._items: TraceProp[]) {
126128
return new JQ(this.query, this.target);
127129
}
128130

@@ -315,3 +317,53 @@ export class FutureAnyObject extends Future<Object> {
315317
return super._result();
316318
}
317319
}
320+
321+
export class Input extends Directive {
322+
items: any[]; // NOTE: unused field (will remove this from direcitve in a later refactor)
323+
name: string | null;
324+
325+
constructor(items: any[]) {
326+
super();
327+
this.items = items;
328+
}
329+
330+
override next(...args: any[]) {
331+
return new Input(args);
332+
}
333+
334+
override async result(): Promise<any> {
335+
return;
336+
}
337+
338+
override toJSON() {
339+
return {
340+
type: "input",
341+
name: this.name,
342+
};
343+
}
344+
}
345+
346+
export class FutureInput extends Future<any> {
347+
declare _directive: Input;
348+
schema: JSONSchema7;
349+
350+
constructor(schema?: JSONSchema7, id: string = newInputId()) {
351+
super(new Input([]), id);
352+
this.schema = schema ?? {};
353+
}
354+
}
355+
356+
/**
357+
* Specify an `input` future that can be assigned a name when create a new module.
358+
* Input types and validation paramters may optionally be described using a JSON Schema object.
359+
*
360+
* Default values may also be specified here and will be used if user input is not provided for this input.
361+
*/
362+
export function input(schema?: FutureInput["schema"]) {
363+
// NOTE: using `any` as the return type here for now to ease using
364+
// this in general node input args or helper functions.
365+
//
366+
// Once we ship our Future type reorganization work, we can just
367+
// use this as-is (Future<any>)
368+
return new FutureInput(schema) as any;
369+
}

src/Module.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Node, Options } from "substrate/Node";
2+
import { FutureInput } from "substrate/Future";
3+
4+
export type ModuleInputs = Record<string, FutureInput>;
5+
6+
type ModuleIn =
7+
| {
8+
module_json: any;
9+
inputs: Record<string, any>;
10+
}
11+
| {
12+
module_id: any;
13+
inputs: Record<string, any>;
14+
}
15+
| {
16+
module_uri: any;
17+
inputs: Record<string, any>;
18+
};
19+
20+
export class Module extends Node {
21+
constructor(args: ModuleIn, options?: Options) {
22+
super(args, options);
23+
this.node = "Module";
24+
}
25+
}

src/Substrate.ts

+47
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Future } from "substrate/Future";
88
import { getPlatformProperties } from "substrate/Platform";
99
import { deflate } from "pako";
1010
import { randomString } from "substrate/idGenerator";
11+
import { ModuleInputs } from "./Module";
1112

1213
type Configuration = {
1314
/**
@@ -291,4 +292,50 @@ export class Substrate {
291292

292293
return headers;
293294
}
295+
296+
module = {
297+
/**
298+
* Returns an object that represents a publishable "module" or code that can be used to construct
299+
* a `Module` node.
300+
*/
301+
serialize: ({ nodes, inputs }: { nodes: Node[]; inputs: ModuleInputs }) => {
302+
const inputIdToName = {};
303+
const inputNameToSchema = {};
304+
305+
for (let name in inputs) {
306+
let input = inputs[name];
307+
// @ts-ignore
308+
inputIdToName[input._id] = name;
309+
// @ts-ignore
310+
inputNameToSchema[name] = input.schema;
311+
}
312+
313+
const dag = Substrate.serialize(...nodes);
314+
315+
// update variable name bindings in dag using inputs
316+
dag.futures = dag.futures.map((future: any) => {
317+
if (future.directive.type === "input" && !future.directive.name) {
318+
// @ts-ignore
319+
future.directive.name = inputIdToName[future.id];
320+
}
321+
return future;
322+
});
323+
324+
return {
325+
dag,
326+
inputs: inputNameToSchema,
327+
api_version: this.apiVersion,
328+
};
329+
},
330+
331+
/**
332+
* Publishes a module on substrate.run
333+
* A successful response will contain the module id and web uri
334+
*/
335+
publish: async (_publishable: any) => {
336+
console.log("not implemented yet");
337+
let publication;
338+
return publication;
339+
},
340+
};
294341
}

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export {
5151
DeleteVectors,
5252
} from "substrate/Nodes";
5353

54+
export { Module } from "substrate/Module";
55+
5456
export { sb } from "substrate/sb";
5557
export { Substrate };
5658
import { Substrate } from "substrate/Substrate";

src/sb.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { FutureAnyObject, FutureString } from "substrate/Future";
1+
import { FutureAnyObject, FutureString, input } from "substrate/Future";
22
import { StreamingResponse } from "substrate/SubstrateStreamingResponse";
33

44
export const sb = {
55
concat: FutureString.concat,
66
jq: FutureAnyObject.jq,
77
interpolate: FutureString.interpolate,
8+
input,
89
streaming: {
910
fromSSEResponse: StreamingResponse.fromReponse,
1011
},

0 commit comments

Comments
 (0)