Skip to content

Commit

Permalink
Implements sb.jinja helper
Browse files Browse the repository at this point in the history
  • Loading branch information
liamgriffiths committed Jul 22, 2024
1 parent 6042726 commit 22f793e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 24 deletions.
27 changes: 27 additions & 0 deletions examples/jinja.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/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,
baseUrl: "https://api-staging.substrate.run",
});

const a = new Box({ value: ["a", "b"] });
const b = new Box({ value: { x: "x" } });

const f = sb.jinja('as=[{% for a in as %}{{a}},{% endfor%}], b={{b["x"]}}, c={{c}}', {
as: a.future.value,
b: b.future.value,
c: "1234",
});

const c = new Box({ value: f });

const res = await substrate.run(a, b, c);
console.log(JSON.stringify(res.json, null, 2));
}
main();
111 changes: 110 additions & 1 deletion src/Future.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,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 @@ -189,6 +189,84 @@ export class StringConcat extends Directive {
}
}

type JinjaTemplate =
| {
future_id: string;
val: null;
}
| { val: string; future_id: null };

export type JinjaVariables = {
[key: string]:
| string
| number
| boolean
| (string | number | boolean)[]
| JinjaVariables
| Future<any>;
};

export class Jinja extends Directive {
template: string | Future<string>;
variables: JinjaVariables;
items: Future<any>[];

static templateJSON(template: string | Future<string>): JinjaTemplate {
return template instanceof Future
? // @ts-ignore
{ val: null, future_id: template._id }
: { val: template, future_id: null };
}

constructor(template: string | Future<string>, variables: JinjaVariables) {
super();
this.template = template;
this.variables = variables;

// use items to contain all of the futures from the inputs
const futures = new Set<Future<any>>();
const collectFutures = (obj: any) => {
if (Array.isArray(obj)) {
for (let item of obj) {
collectFutures(item);
}
}

if (obj instanceof Future) {
futures.add(obj);
return;
}

if (obj && typeof obj === "object") {
for (let key of Object.keys(obj)) {
collectFutures(obj[key]);
}
}
};
collectFutures([template, variables]);
this.items = Array.from(futures)
}

override next(..._items: any[]) {
return new Jinja(this.template, this.variables);
}

override async result(): Promise<string> {
return this.template instanceof Future
? // @ts-ignore
await this.template._result()
: this.template;
}

override toJSON(): any {
return {
type: "jinja",
template: Jinja.templateJSON(this.template),
variables: replaceWithPlaceholders(this.variables),
};
}
}

export abstract class Future<T> {
protected _directive: Directive;
protected _id: string = "";
Expand Down Expand Up @@ -263,6 +341,13 @@ export class FutureString extends Future<string> {
return FutureString.concat(...[this, ...items]);
}

static jinja(
template: string | FutureString,
variables: JinjaVariables,
): FutureString {
return new FutureString(new Jinja(template, variables));
}

protected override async _result(): Promise<string> {
return super._result();
}
Expand Down Expand Up @@ -315,3 +400,27 @@ export class FutureAnyObject extends Future<Object> {
return super._result();
}
}

/**
* @internal
* Given some value, recursively replace `Future` instances with SB Placeholder
*/
export const replaceWithPlaceholders = (val: any): any => {
if (Array.isArray(val)) {
return val.map((item) => replaceWithPlaceholders(item));
}

if (val instanceof Future) {
// @ts-expect-error (accessing protected method toPlaceholder)
return val.toPlaceholder();
}

if (val && typeof val === "object") {
return Object.keys(val).reduce((acc: any, k: any) => {
acc[k] = replaceWithPlaceholders(val[k]);
return acc;
}, {});
}

return val;
};
24 changes: 2 additions & 22 deletions src/Node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { idGenerator } from "substrate/idGenerator";
import { Future, FutureAnyObject, Trace } from "substrate/Future";
import { Future, FutureAnyObject, Trace, replaceWithPlaceholders } from "substrate/Future";
import { SubstrateResponse } from "substrate/SubstrateResponse";
import { NodeError, SubstrateError } from "substrate/Error";
import { AnyNode } from "substrate/Nodes";
Expand Down Expand Up @@ -102,30 +102,10 @@ export abstract class Node {
}

toJSON() {
const withPlaceholders = (obj: any): any => {
if (Array.isArray(obj)) {
return obj.map((item) => withPlaceholders(item));
}

if (obj instanceof Future) {
// @ts-expect-error (accessing protected method toPlaceholder)
return obj.toPlaceholder();
}

if (obj && typeof obj === "object") {
return Object.keys(obj).reduce((acc: any, k: any) => {
acc[k] = withPlaceholders(obj[k]);
return acc;
}, {});
}

return obj;
};

return {
id: this.id,
node: this.node,
args: withPlaceholders(this.args),
args: replaceWithPlaceholders(this.args),
_should_output_globally: !this.hide,
...(this.cache_age && { _cache_age: this.cache_age }),
...(this.cache_keys && { _cache_keys: this.cache_keys }),
Expand Down
2 changes: 1 addition & 1 deletion src/Substrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Substrate {
* @throws {Error} when the client encounters an error making the request.
*/
async runSerialized(
nodes: Node[],
nodes: Node[] = [],
endpoint: string = "/compose",
): Promise<SubstrateResponse> {
const serialized = Substrate.serialize(...nodes);
Expand Down
1 change: 1 addition & 0 deletions src/sb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const sb = {
concat: FutureString.concat,
jq: FutureAnyObject.jq,
interpolate: FutureString.interpolate,
jinja: FutureString.jinja,
streaming: {
fromSSEResponse: StreamingResponse.fromReponse,
},
Expand Down
26 changes: 26 additions & 0 deletions tests/Future.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,31 @@ describe("Future", () => {
// @ts-expect-error
expect(i2._result()).resolves.toEqual("hello12");
});

describe(".jinja", () => {
test(".toJSON", () => {
const x = FutureString.concat("1", "2", "3");
const f = FutureString.jinja("template: x={{x}} y={{y}}", {
x,
y: "abc",
});

const json = f.toJSON();

expect(json).toEqual({
// @ts-ignore
id: f._id,
directive: {
type: "jinja",
template: { future_id: null, val: "template: x={{x}} y={{y}}" },
variables: {
// @ts-ignore (_id)
x: { __$$SB_GRAPH_OP_ID$$__: x._id },
y: "abc",
},
},
});
});
});
});
});

0 comments on commit 22f793e

Please sign in to comment.