substarte 120240617.1.9

Sign up to get free protection for your applications and to get access to all the features.
package/src/Future.ts ADDED
@@ -0,0 +1,317 @@
1
+ import { idGenerator } from "substrate/idGenerator";
2
+ import { Node } from "substrate/Node";
3
+
4
+ type Accessor = "item" | "attr";
5
+ type TraceOperation = {
6
+ future_id: string | null;
7
+ key: string | number | null;
8
+ accessor: Accessor;
9
+ };
10
+
11
+ type TraceProp = string | FutureString | number | FutureNumber;
12
+ type Concatable = string | FutureString;
13
+ type JQCompatible = Record<string, unknown> | any[] | string | number;
14
+ type JQDirectiveTarget = Future<any> | JQCompatible;
15
+ type FutureTypeMap = {
16
+ string: FutureString;
17
+ object: FutureAnyObject;
18
+ number: FutureNumber;
19
+ boolean: FutureBoolean;
20
+ };
21
+ const parsePath = (path: string): TraceProp[] => {
22
+ // Split the path by dots or brackets, and filter out empty strings
23
+ const parts = path.split(/\.|\[|\]\[?/).filter(Boolean);
24
+ // Convert numeric parts to numbers and keep others as strings
25
+ return parts.map((part) => (isNaN(Number(part)) ? part : Number(part)));
26
+ };
27
+
28
+ const newFutureId = idGenerator("future");
29
+
30
+ abstract class Directive {
31
+ abstract items: any[];
32
+ abstract next(...args: any[]): Directive;
33
+ abstract toJSON(): any;
34
+
35
+ abstract result(): Promise<any>;
36
+
37
+ referencedFutures() {
38
+ return (
39
+ this.items
40
+ .filter((p) => p instanceof Future)
41
+ // @ts-ignore
42
+ .flatMap((p) => [p, ...p.referencedFutures()])
43
+ );
44
+ }
45
+ }
46
+
47
+ export class Trace extends Directive {
48
+ items: TraceProp[];
49
+ originNode: Node;
50
+
51
+ constructor(items: TraceProp[], originNode: Node) {
52
+ super();
53
+ this.items = items;
54
+ this.originNode = originNode;
55
+ }
56
+
57
+ static Operation = {
58
+ future: (accessor: Accessor, id: Future<any>["_id"]) => ({
59
+ future_id: id,
60
+ key: null,
61
+ accessor,
62
+ }),
63
+ key: (accessor: Accessor, key: string | number) => ({
64
+ future_id: null,
65
+ key,
66
+ accessor,
67
+ }),
68
+ };
69
+
70
+ override next(...items: TraceProp[]) {
71
+ return new Trace([...this.items, ...items], this.originNode);
72
+ }
73
+
74
+ override async result(): Promise<any> {
75
+ // @ts-expect-error (protected result())
76
+ let result: any = await this.originNode.result();
77
+
78
+ for (let item of this.items) {
79
+ if (item instanceof Future) {
80
+ // @ts-expect-error (protected result())
81
+ item = await item._result();
82
+ }
83
+ result = result[item as string | number];
84
+ }
85
+ return result;
86
+ }
87
+
88
+ override toJSON() {
89
+ return {
90
+ type: "trace",
91
+ origin_node_id: this.originNode.id,
92
+ op_stack: this.items.map((item) => {
93
+ if (item instanceof FutureString) {
94
+ // @ts-expect-error (accessing protected prop: _id)
95
+ return Trace.Operation.future("attr", item._id);
96
+ } else if (item instanceof FutureNumber) {
97
+ // @ts-expect-error (accessing protected prop: _id)
98
+ return Trace.Operation.future("item", item._id);
99
+ } else if (typeof item === "string") {
100
+ return Trace.Operation.key("attr", item);
101
+ }
102
+ return Trace.Operation.key("item", item);
103
+ }) as TraceOperation[],
104
+ };
105
+ }
106
+ }
107
+
108
+ export class JQ extends Directive {
109
+ items: any[];
110
+ target: JQDirectiveTarget;
111
+ query: string;
112
+
113
+ constructor(query: string, target: JQDirectiveTarget) {
114
+ super();
115
+ this.items = [target];
116
+ this.target = target;
117
+ this.query = query;
118
+ }
119
+
120
+ static JQDirectiveTarget = {
121
+ future: (id: Future<any>["_id"]) => ({ future_id: id, val: null }),
122
+ rawValue: (val: JQCompatible) => ({ future_id: null, val }),
123
+ };
124
+
125
+ override next(...items: TraceProp[]) {
126
+ return new JQ(this.query, this.target);
127
+ }
128
+
129
+ override async result(): Promise<JQCompatible> {
130
+ return this.target instanceof Future
131
+ ? // @ts-expect-error (accessing protected prop: id)
132
+ await this.target._result()
133
+ : this.target;
134
+ }
135
+
136
+ override toJSON(): any {
137
+ return {
138
+ type: "jq",
139
+ query: this.query,
140
+ target:
141
+ this.target instanceof Future
142
+ ? // @ts-expect-error (accessing protected prop: id)
143
+ JQ.JQDirectiveTarget.future(this.target._id)
144
+ : JQ.JQDirectiveTarget.rawValue(this.target),
145
+ };
146
+ }
147
+ }
148
+
149
+ export class StringConcat extends Directive {
150
+ items: Concatable[];
151
+
152
+ constructor(items: Concatable[] = []) {
153
+ super();
154
+ this.items = items;
155
+ }
156
+
157
+ static Concatable = {
158
+ string: (val: string) => ({ future_id: null, val }),
159
+ future: (id: Future<string>["_id"]) => ({ future_id: id, val: null }),
160
+ };
161
+
162
+ override next(...items: Concatable[]) {
163
+ return new StringConcat([...this.items, ...items]);
164
+ }
165
+
166
+ override async result(): Promise<string> {
167
+ let result = "";
168
+ for (let item of this.items) {
169
+ if (item instanceof Future) {
170
+ // @ts-expect-error (protected result())
171
+ item = await item._result();
172
+ }
173
+ result = result.concat(item);
174
+ }
175
+ return result;
176
+ }
177
+
178
+ override toJSON(): any {
179
+ return {
180
+ type: "string-concat",
181
+ items: this.items.map((item) => {
182
+ if (item instanceof Future) {
183
+ // @ts-expect-error (accessing protected prop: _id)
184
+ return StringConcat.Concatable.future(item._id);
185
+ }
186
+ return StringConcat.Concatable.string(item);
187
+ }),
188
+ };
189
+ }
190
+ }
191
+
192
+ export abstract class Future<T> {
193
+ protected _directive: Directive;
194
+ protected _id: string = "";
195
+
196
+ constructor(directive: Directive, id: string = newFutureId()) {
197
+ this._directive = directive;
198
+ this._id = id;
199
+ }
200
+
201
+ protected referencedFutures(): Future<any>[] {
202
+ return this._directive.referencedFutures();
203
+ }
204
+
205
+ protected toPlaceholder() {
206
+ return { __$$SB_GRAPH_OP_ID$$__: this._id };
207
+ }
208
+
209
+ protected async _result(): Promise<T> {
210
+ return this._directive.result();
211
+ }
212
+
213
+ static jq<T extends keyof FutureTypeMap>(
214
+ future: JQDirectiveTarget,
215
+ query: string,
216
+ futureType: keyof FutureTypeMap = "string",
217
+ ): FutureTypeMap[T] {
218
+ const directive = new JQ(query, future);
219
+ switch (futureType) {
220
+ case "string":
221
+ return new FutureString(directive) as FutureTypeMap[T];
222
+ case "number":
223
+ return new FutureNumber(directive) as FutureTypeMap[T];
224
+ case "object":
225
+ return new FutureAnyObject(directive) as FutureTypeMap[T];
226
+ case "boolean":
227
+ return new FutureBoolean(directive) as FutureTypeMap[T];
228
+ default:
229
+ throw new Error(`Unknown future type: ${futureType}`);
230
+ }
231
+ }
232
+
233
+ toJSON() {
234
+ return {
235
+ id: this._id,
236
+ directive: this._directive.toJSON(),
237
+ };
238
+ }
239
+ }
240
+
241
+ export class FutureBoolean extends Future<boolean> {}
242
+
243
+ export class FutureString extends Future<string> {
244
+ static concat(...items: (string | FutureString)[]) {
245
+ return new FutureString(new StringConcat(items));
246
+ }
247
+
248
+ static interpolate(
249
+ strings: TemplateStringsArray,
250
+ ...exprs: ({ toString(): string } | FutureString)[]
251
+ ): FutureString {
252
+ return FutureString.concat(
253
+ ...strings.flatMap((s: string, i: number) => {
254
+ const expr = exprs[i];
255
+ return expr
256
+ ? [s, expr instanceof Future ? expr : expr.toString()]
257
+ : [s];
258
+ }),
259
+ );
260
+ }
261
+
262
+ concat(...items: (string | FutureString)[]) {
263
+ return FutureString.concat(...[this, ...items]);
264
+ }
265
+
266
+ protected override async _result(): Promise<string> {
267
+ return super._result();
268
+ }
269
+ }
270
+
271
+ export class FutureNumber extends Future<number> {}
272
+
273
+ export abstract class FutureArray extends Future<any[] | FutureArray> {
274
+ abstract at(index: number): Future<any>;
275
+
276
+ protected override async _result(): Promise<any[] | FutureArray> {
277
+ return super._result();
278
+ }
279
+ }
280
+
281
+ export abstract class FutureObject extends Future<Object> {
282
+ get(path: string): Future<any> {
283
+ const props = parsePath(path);
284
+ return props.reduce((future, prop) => {
285
+ if (future instanceof FutureAnyObject) {
286
+ return typeof prop === "string"
287
+ ? future.get(prop as string)
288
+ : future.at(prop as number);
289
+ } else {
290
+ // @ts-ignore
291
+ return typeof prop === "string" ? future[prop] : future.at(prop);
292
+ }
293
+ }, this) as Future<any>;
294
+ }
295
+
296
+ protected override async _result(): Promise<Object> {
297
+ return super._result();
298
+ }
299
+ }
300
+
301
+ export class FutureAnyObject extends Future<Object> {
302
+ get(path: string | FutureString) {
303
+ const d =
304
+ typeof path === "string"
305
+ ? this._directive.next(...parsePath(path))
306
+ : this._directive.next(path);
307
+ return new FutureAnyObject(d);
308
+ }
309
+
310
+ at(index: number | FutureNumber) {
311
+ return new FutureAnyObject(this._directive.next(index));
312
+ }
313
+
314
+ protected override async _result(): Promise<Object> {
315
+ return super._result();
316
+ }
317
+ }
@@ -0,0 +1 @@
1
+ 20240617.20240806
package/src/Node.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { idGenerator } from "substrate/idGenerator";
2
+ import { Future, FutureAnyObject, Trace } from "substrate/Future";
3
+ import { SubstrateResponse } from "substrate/SubstrateResponse";
4
+ import { NodeError, SubstrateError } from "substrate/Error";
5
+ import { AnyNode } from "substrate/Nodes";
6
+
7
+ const generator = idGenerator("node");
8
+
9
+ export type Options = {
10
+ /** The id of the node. Default: random id */
11
+ id?: Node["id"];
12
+ /** When true the server will omit this node's output. Default: false */
13
+ hide?: boolean;
14
+ /** Number of seconds to cache an output for this node's unique inputs. Default: null */
15
+ cache_age?: number;
16
+ /** Applies if cache_age > 0. Optionally specify a subset of keys to use when computing a cache key.
17
+ * Default: all node arguments
18
+ */
19
+ cache_keys?: string[];
20
+ /** Max number of times to retry this node if it fails. Default: null means no retries */
21
+ max_retries?: number;
22
+ /** Specify nodes that this node depends on. */
23
+ depends?: Node[];
24
+ };
25
+
26
+ export abstract class Node {
27
+ /** The id of the node. Default: random id */
28
+ id: string;
29
+ /** The type of the node. */
30
+ node: string;
31
+ /** Node inputs */
32
+ args: Object;
33
+ /** When true the server will omit this node's output. Default: false */
34
+ hide: boolean;
35
+ /** Number of seconds to cache an output for this node's unique inputs. Default: null */
36
+ cache_age?: number;
37
+ /** Applies if cache_age > 0. Optionally specify a subset of keys to use when computing a cache key.
38
+ * Default: all node arguments
39
+ */
40
+ cache_keys?: string[];
41
+ /** Max number of times to retry this node if it fails. Default: null means no retries */
42
+ max_retries?: number;
43
+ /** Specify nodes that this node depends on. */
44
+ depends: Node[];
45
+
46
+ /** TODO this field stores the last response, but it's just temporary until the internals are refactored */
47
+ protected _response: SubstrateResponse | undefined;
48
+
49
+ constructor(args: Object = {}, opts?: Options) {
50
+ this.node = this.constructor.name;
51
+ this.args = args;
52
+ this.id = opts?.id || generator(this.node);
53
+ this.hide = opts?.hide || false;
54
+ this.cache_age = opts?.cache_age;
55
+ this.cache_keys = opts?.cache_keys;
56
+ this.max_retries = opts?.max_retries;
57
+ this.depends = opts?.depends ?? [];
58
+ }
59
+
60
+ /**
61
+ * Reference the future output of this node.
62
+ */
63
+ get future(): any {
64
+ return new FutureAnyObject(new Trace([], this as Node));
65
+ }
66
+
67
+ protected set response(res: SubstrateResponse) {
68
+ this._response = res;
69
+ }
70
+
71
+ protected output() {
72
+ const data = this._response?.json?.data?.[this.id];
73
+
74
+ // Errors from the server have these two fields
75
+ if (data?.type && data?.message) {
76
+ // NOTE: we only return these errors on client errors.
77
+ // Server errors are typically 5xx replies.
78
+ return new NodeError(data.type, data.message, data?.request_id);
79
+ } else if (data) {
80
+ return data;
81
+ }
82
+
83
+ return new NodeError("no_data", `Missing data for "${this.id}"`);
84
+ }
85
+
86
+ /**
87
+ * Return the resolved result for this node.
88
+ */
89
+ protected async result(): Promise<any> {
90
+ if (!this._response) {
91
+ return Promise.reject(
92
+ new SubstrateError(
93
+ `${this.node} (id=${this.id}) has not been run yet!`,
94
+ ),
95
+ );
96
+ }
97
+ return Promise.resolve(
98
+ this._response
99
+ ? this._response.get(this as unknown as AnyNode)
100
+ : undefined,
101
+ );
102
+ }
103
+
104
+ toJSON() {
105
+ const withPlaceholders = (obj: any): any => {
106
+ if (Array.isArray(obj)) {
107
+ return obj.map((item) => withPlaceholders(item));
108
+ }
109
+
110
+ if (obj instanceof Future) {
111
+ // @ts-expect-error (accessing protected method toPlaceholder)
112
+ return obj.toPlaceholder();
113
+ }
114
+
115
+ if (obj && typeof obj === "object") {
116
+ return Object.keys(obj).reduce((acc: any, k: any) => {
117
+ acc[k] = withPlaceholders(obj[k]);
118
+ return acc;
119
+ }, {});
120
+ }
121
+
122
+ return obj;
123
+ };
124
+
125
+ return {
126
+ id: this.id,
127
+ node: this.node,
128
+ args: withPlaceholders(this.args),
129
+ _should_output_globally: !this.hide,
130
+ ...(this.cache_age && { _cache_age: this.cache_age }),
131
+ ...(this.cache_keys && { _cache_keys: this.cache_keys }),
132
+ ...(this.max_retries && { _max_retries: this.max_retries }),
133
+ };
134
+ }
135
+
136
+ /**
137
+ * @private
138
+ * For this node, return all the Futures and other Nodes it has a reference to.
139
+ */
140
+ protected references() {
141
+ const nodes = new Set<Node>();
142
+ const futures = new Set<Future<any>>();
143
+
144
+ nodes.add(this);
145
+
146
+ for (let node of this.depends) {
147
+ const references = node.references();
148
+ for (let node of references.nodes) {
149
+ nodes.add(node);
150
+ }
151
+ for (let future of references.futures) {
152
+ futures.add(future);
153
+ }
154
+ }
155
+
156
+ const collectFutures = (obj: any) => {
157
+ if (Array.isArray(obj)) {
158
+ for (let item of obj) {
159
+ collectFutures(item);
160
+ }
161
+ }
162
+
163
+ if (obj instanceof Future) {
164
+ futures.add(obj);
165
+
166
+ // @ts-expect-error (accessing protected method referencedFutures)
167
+ for (let future of obj.referencedFutures()) {
168
+ futures.add(future);
169
+ }
170
+ return;
171
+ }
172
+
173
+ if (obj && typeof obj === "object") {
174
+ for (let key of Object.keys(obj)) {
175
+ collectFutures(obj[key]);
176
+ }
177
+ }
178
+ };
179
+ collectFutures(this.args);
180
+
181
+ for (let future of futures) {
182
+ // @ts-ignore protected access
183
+ let directive = future._directive;
184
+ if (directive instanceof Trace) {
185
+ // @ts-ignore protected access
186
+ const references = directive.originNode.references();
187
+ for (let node of references.nodes) {
188
+ nodes.add(node);
189
+ }
190
+ for (let future of references.futures) {
191
+ futures.add(future);
192
+ }
193
+ }
194
+ }
195
+
196
+ return { nodes, futures };
197
+ }
198
+ }