From 16f3cfb51d8f7f475917457d9d0d6038734eca27 Mon Sep 17 00:00:00 2001 From: Liam Griffiths Date: Mon, 22 Jul 2024 16:41:18 -0400 Subject: [PATCH 1/4] Allow `Trace` to accept a `Future` as its data source (in addition to a origin `Node`) --- src/Future.ts | 36 +++++++++----- src/Node.ts | 22 ++++++--- tests/Future.test.ts | 115 ++++++++++++++++++++++++++----------------- 3 files changed, 109 insertions(+), 64 deletions(-) diff --git a/src/Future.ts b/src/Future.ts index 75559a5..1f4ddc9 100644 --- a/src/Future.ts +++ b/src/Future.ts @@ -46,12 +46,12 @@ abstract class Directive { export class Trace extends Directive { items: TraceProp[]; - originNode: Node; + target: Node | Future; - constructor(items: TraceProp[], originNode: Node) { + constructor(items: TraceProp[], target: Node | Future) { super(); this.items = items; - this.originNode = originNode; + this.target = target; } static Operation = { @@ -68,12 +68,15 @@ export class Trace extends Directive { }; override next(...items: TraceProp[]) { - return new Trace([...this.items, ...items], this.originNode); + return new Trace([...this.items, ...items], this.target); } override async result(): Promise { - // @ts-expect-error (protected result()) - let result: any = await this.originNode.result(); + let result: any = await (this.target instanceof Future + ? // @ts-expect-error (protected _result()) + this.target._result() + : // @ts-expect-error (protected result()) + this.target.result()); for (let item of this.items) { if (item instanceof Future) { @@ -85,10 +88,19 @@ export class Trace extends Directive { return result; } + override referencedFutures() { + if (this.target instanceof Future) { + return [...super.referencedFutures(), this.target]; + } + return super.referencedFutures(); + } + override toJSON() { return { type: "trace", - origin_node_id: this.originNode.id, + origin_node_id: this.target instanceof Node ? this.target.id : null, + // @ts-ignore (protected _id) + origin_future_id: this.target instanceof Node ? null : this.target._id, op_stack: this.items.map((item) => { if (item instanceof FutureString) { // @ts-expect-error (accessing protected prop: _id) @@ -122,7 +134,7 @@ export class JQ extends Directive { rawValue: (val: JQCompatible) => ({ future_id: null, val }), }; - override next(...items: TraceProp[]) { + override next(..._items: TraceProp[]) { return new JQ(this.query, this.target); } @@ -300,11 +312,9 @@ export abstract class FutureObject extends Future { export class FutureAnyObject extends Future { get(path: string | FutureString) { - const d = - typeof path === "string" - ? this._directive.next(...parsePath(path)) - : this._directive.next(path); - return new FutureAnyObject(d); + const traceProps = typeof path === "string" ? parsePath(path) : [path]; + const trace = new Trace(traceProps, this); + return new FutureAnyObject(trace); } at(index: number | FutureNumber) { diff --git a/src/Node.ts b/src/Node.ts index e642348..48fbb2a 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -182,13 +182,21 @@ export abstract class Node { // @ts-ignore protected access let directive = future._directive; if (directive instanceof Trace) { - // @ts-ignore protected access - const references = directive.originNode.references(); - for (let node of references.nodes) { - nodes.add(node); - } - for (let future of references.futures) { - futures.add(future); + if (directive.target instanceof Node) { + // @ts-ignore protected access + const references = directive.target.references(); + for (let node of references.nodes) { + nodes.add(node); + } + for (let future of references.futures) { + futures.add(future); + } + } else if (directive.target instanceof Future) { + // @ts-ignore protected access + const referencedFutures = directive.target.referencedFutures(); + for (let future of referencedFutures) { + futures.add(future); + } } } } diff --git a/tests/Future.test.ts b/tests/Future.test.ts index 1eda437..7561397 100644 --- a/tests/Future.test.ts +++ b/tests/Future.test.ts @@ -53,58 +53,85 @@ describe("Future", () => { }); describe("Trace (Directive)", () => { - test(".next", () => { - const s = new FutureString(new Trace([], node("123"))); - const n = new FutureNumber(new Trace([], node("456"))); - const d = new Trace(["a", 1, s, n], node("NodeId")); - const d2 = d.next("b", 2); - - expect(d2.items).toEqual(["a", 1, s, n, "b", 2]); - expect(d2.originNode.id).toEqual("NodeId"); - }); - - test(".result", async () => { - // when the trace is empty, it resovles to the node's output - const n0 = staticNode("hello"); - const t0 = new Trace([], n0); - expect(t0.result()).resolves.toEqual("hello"); + describe("when target is a Node", () => { + test(".next", () => { + const s = new FutureString(new Trace([], node("123"))); + const n = new FutureNumber(new Trace([], node("456"))); + const d = new Trace(["a", 1, s, n], node("NodeId")); + const d2 = d.next("b", 2); + + expect(d2.items).toEqual(["a", 1, s, n, "b", 2]); + expect((d2.target as Node).id).toEqual("NodeId"); + }); - // when the trace only includes primitive values - const n1 = staticNode({ a: ["result1"] }); - const t1 = new Trace(["a", 0], n1); - expect(t1.result()).resolves.toEqual("result1"); + test(".result", async () => { + // when the trace is empty, it resovles to the node's output + const n0 = staticNode("hello"); + const t0 = new Trace([], n0); + expect(t0.result()).resolves.toEqual("hello"); + + // when the trace only includes primitive values + const n1 = staticNode({ a: ["result1"] }); + const t1 = new Trace(["a", 0], n1); + expect(t1.result()).resolves.toEqual("result1"); + + // when the trace contains futures, they get resolved + const fs = new FutureString(new Trace([], staticNode("b"))); + const fn = new FutureNumber(new Trace([], staticNode(1))); + const n2 = staticNode({ a: [{ b: [undefined, "result2"] }] }); + const t2 = new Trace(["a", 0, fs, fn], n2); + expect(t2.result()).resolves.toEqual("result2"); + }); - // when the trace contains futures, they get resolved - const fs = new FutureString(new Trace([], staticNode("b"))); - const fn = new FutureNumber(new Trace([], staticNode(1))); - const n2 = staticNode({ a: [{ b: [undefined, "result2"] }] }); - const t2 = new Trace(["a", 0, fs, fn], n2); - expect(t2.result()).resolves.toEqual("result2"); - }); + test(".toJSON", () => { + const s = new FutureString(new Trace([], node()), "123"); + const n = new FutureNumber(new Trace([], node()), "456"); + const d = new Trace(["a", 1, s, n], node("NodeId")); + + expect(d.toJSON()).toEqual({ + type: "trace", + origin_node_id: "NodeId", + op_stack: [ + Trace.Operation.key("attr", "a"), + Trace.Operation.key("item", 1), + Trace.Operation.future("attr", "123"), + Trace.Operation.future("item", "456"), + ], + }); + }); - test(".toJSON", () => { - const s = new FutureString(new Trace([], node()), "123"); - const n = new FutureNumber(new Trace([], node()), "456"); - const d = new Trace(["a", 1, s, n], node("NodeId")); + test(".referencedFutures", () => { + const s = new FutureString(new Trace([], node())); + const n = new FutureNumber(new Trace([], node())); + const d = new Trace(["a", 1, s, n], node("NodeId")); - expect(d.toJSON()).toEqual({ - type: "trace", - origin_node_id: "NodeId", - op_stack: [ - Trace.Operation.key("attr", "a"), - Trace.Operation.key("item", 1), - Trace.Operation.future("attr", "123"), - Trace.Operation.future("item", "456"), - ], + expect(d.referencedFutures()).toEqual([s, n]); }); }); - test(".referencedFutures", () => { - const s = new FutureString(new Trace([], node())); - const n = new FutureNumber(new Trace([], node())); - const d = new Trace(["a", 1, s, n], node("NodeId")); + describe("when target is a Future", () => { + test(".toJSON", () => { + const n = staticNode({ x: 123 }); + const f = Future.jq<"object">(n.future, ".", "object"); // => { x: 123 } + const t = new Trace(["x"], f); + + expect(t.toJSON()).toEqual({ + type: "trace", + origin_node_id: null, + // @ts-ignore + origin_future_id: f._id, + op_stack: [ + Trace.Operation.key("attr", "x"), + ], + }); + }); - expect(d.referencedFutures()).toEqual([s, n]); + test(".referencedFutures", () => { + const n = staticNode({ x: 123 }); + const f = Future.jq<"object">(n.future, ".", "object"); // => { x: 123 } + const t = new Trace(["x"], f); + expect(t.referencedFutures()).toEqual([f]); + }); }); }); From cb2498c51f8de3dd7710d2f507b5bbac64d85b10 Mon Sep 17 00:00:00 2001 From: Liam Griffiths Date: Thu, 25 Jul 2024 12:44:46 -0400 Subject: [PATCH 2/4] Formatting --- tests/Future.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Future.test.ts b/tests/Future.test.ts index 7561397..b591af8 100644 --- a/tests/Future.test.ts +++ b/tests/Future.test.ts @@ -120,9 +120,7 @@ describe("Future", () => { origin_node_id: null, // @ts-ignore origin_future_id: f._id, - op_stack: [ - Trace.Operation.key("attr", "x"), - ], + op_stack: [Trace.Operation.key("attr", "x")], }); }); From 4834fc6a7a9d48422c147508e75fe99b2bc53fc3 Mon Sep 17 00:00:00 2001 From: Liam Griffiths Date: Thu, 25 Jul 2024 12:51:42 -0400 Subject: [PATCH 3/4] Add example usage --- examples/futures-as-data-source.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 examples/futures-as-data-source.ts diff --git a/examples/futures-as-data-source.ts b/examples/futures-as-data-source.ts new file mode 100755 index 0000000..2529fde --- /dev/null +++ b/examples/futures-as-data-source.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env -S npx ts-node --transpileOnly + +import { Substrate, Box, sb } from "substrate"; + +async function main() { + const SUBSTRATE_API_KEY = process.env["SUBSTRATE_API_KEY"]; + + const substrate = new Substrate({ apiKey: SUBSTRATE_API_KEY }); + + const f = sb.jq<"object">({ foo: { bar: "baz" } }, ".", "object"); + + const a = new Box({ value: { + x: f, + y: f.get("foo.bar"), + }}); + + const res = await substrate.run(a); + console.log(JSON.stringify(res.json.data, null, 2)); +} +main(); From 8ec97e2b86784ee41d353d226dc74fa0c9159b06 Mon Sep 17 00:00:00 2001 From: Liam Griffiths Date: Thu, 25 Jul 2024 13:16:17 -0400 Subject: [PATCH 4/4] Formatting --- examples/futures-as-data-source.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/futures-as-data-source.ts b/examples/futures-as-data-source.ts index 2529fde..73cf32b 100755 --- a/examples/futures-as-data-source.ts +++ b/examples/futures-as-data-source.ts @@ -9,10 +9,12 @@ async function main() { const f = sb.jq<"object">({ foo: { bar: "baz" } }, ".", "object"); - const a = new Box({ value: { - x: f, - y: f.get("foo.bar"), - }}); + const a = new Box({ + value: { + x: f, + y: f.get("foo.bar"), + }, + }); const res = await substrate.run(a); console.log(JSON.stringify(res.json.data, null, 2));