Skip to content

Allow Trace to accept a Future as its data source #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
22 changes: 22 additions & 0 deletions examples/futures-as-data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/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();
36 changes: 23 additions & 13 deletions src/Future.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ abstract class Directive {

export class Trace extends Directive {
items: TraceProp[];
originNode: Node;
target: Node | Future<any>;

constructor(items: TraceProp[], originNode: Node) {
constructor(items: TraceProp[], target: Node | Future<any>) {
super();
this.items = items;
this.originNode = originNode;
this.target = target;
}

static Operation = {
Expand All @@ -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<any> {
// @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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -300,11 +312,9 @@ export abstract class FutureObject extends Future<Object> {

export class FutureAnyObject extends Future<Object> {
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) {
Expand Down
22 changes: 15 additions & 7 deletions src/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Expand Down
113 changes: 69 additions & 44 deletions tests/Future.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,58 +53,83 @@
});

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({

Check failure on line 91 in tests/Future.test.ts

View workflow job for this annotation

GitHub Actions / build (16.18.1)

tests/Future.test.ts > Future > Trace (Directive) > when target is a Node > .toJSON

AssertionError: expected { type: 'trace', …(3) } to deeply equal { type: 'trace', …(2) } - Expected + Received Object { "op_stack": Array [ Object { "accessor": "attr", "future_id": null, "key": "a", }, Object { "accessor": "item", "future_id": null, "key": 1, }, Object { "accessor": "attr", "future_id": "123", "key": null, }, Object { "accessor": "item", "future_id": "456", "key": null, }, ], + "origin_future_id": null, "origin_node_id": "NodeId", "type": "trace", } ❯ tests/Future.test.ts:91:28

Check failure on line 91 in tests/Future.test.ts

View workflow job for this annotation

GitHub Actions / build (18.17.1)

tests/Future.test.ts > Future > Trace (Directive) > when target is a Node > .toJSON

AssertionError: expected { type: 'trace', …(3) } to deeply equal { type: 'trace', …(2) } - Expected + Received Object { "op_stack": Array [ Object { "accessor": "attr", "future_id": null, "key": "a", }, Object { "accessor": "item", "future_id": null, "key": 1, }, Object { "accessor": "attr", "future_id": "123", "key": null, }, Object { "accessor": "item", "future_id": "456", "key": null, }, ], + "origin_future_id": null, "origin_node_id": "NodeId", "type": "trace", } ❯ tests/Future.test.ts:91:28
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]);
});
});
});

Expand Down
Loading