Skip to content

Commit 0c65d67

Browse files
authored
Implement wish<>() built-in (#1898)
* Implement `wish<>()` built-in Also removed the need to inject `allCharms` into `default-app.tsx` as an example usage * Format and lint * Add doc comment * Fix wish tests * Move all charms ID constant to runner package Resolves circular import * Use `derive` to attach correct schema to wish result * Action Berni's feedback * Fix failing test * Format -_- * Update commontools.d.ts
1 parent 824da8c commit 0c65d67

File tree

14 files changed

+277
-28
lines changed

14 files changed

+277
-28
lines changed

packages/api/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,13 @@ export type CompileAndRunFunction = <T = any, S = any>(
524524
) => OpaqueRef<BuiltInCompileAndRunState<S>>;
525525

526526
export type NavigateToFunction = (cell: OpaqueRef<any>) => OpaqueRef<string>;
527+
export type WishFunction = {
528+
<T = unknown>(target: Opaque<string>): OpaqueRef<T | undefined>;
529+
<T = unknown>(
530+
target: Opaque<string>,
531+
defaultValue: Opaque<T>,
532+
): OpaqueRef<T>;
533+
};
527534

528535
export type CreateNodeFactoryFunction = <T = any, R = any>(
529536
moduleSpec: Module,
@@ -575,6 +582,7 @@ export declare const fetchData: FetchDataFunction;
575582
export declare const streamData: StreamDataFunction;
576583
export declare const compileAndRun: CompileAndRunFunction;
577584
export declare const navigateTo: NavigateToFunction;
585+
export declare const wish: WishFunction;
578586
export declare const createNodeFactory: CreateNodeFactoryFunction;
579587
export declare const createCell: CreateCellFunction;
580588
export declare const cell: CellFunction;

packages/charm/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,3 @@ export {
6565
type WorkflowType,
6666
} from "./workflow.ts";
6767
export * from "./spellbook.ts";
68-
export * from "./well-known.ts";

packages/charm/src/well-known.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

packages/patterns/default-app.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/// <cts-enable />
22
import {
33
Cell,
4-
Default,
54
derive,
65
handler,
76
NAME,
@@ -10,6 +9,7 @@ import {
109
recipe,
1110
str,
1211
UI,
12+
wish,
1313
} from "commontools";
1414

1515
// Import recipes we want to be launchable from the default app.
@@ -28,9 +28,7 @@ export type Charm = {
2828
[key: string]: any;
2929
};
3030

31-
type CharmsListInput = {
32-
allCharms: Default<MentionableCharm[], []>;
33-
};
31+
type CharmsListInput = void;
3432

3533
// Recipe returns only UI, no data outputs (only symbol properties)
3634
interface CharmsListOutput {
@@ -113,10 +111,18 @@ const spawnNote = handler<
113111
}));
114112
});
115113

114+
function getAllCharms() {
115+
const allCharms = wish<MentionableCharm[]>("#/allCharms", []);
116+
// ensure schema is added to return type
117+
return derive<MentionableCharm[], MentionableCharm[]>(allCharms, (c) => c);
118+
}
119+
116120
export default recipe<CharmsListInput, CharmsListOutput>(
117121
"DefaultCharmList",
118-
({ allCharms }) => {
122+
(_) => {
123+
const allCharms = getAllCharms();
119124
const index = BacklinksIndex({ allCharms });
125+
120126
return {
121127
[NAME]: str`DefaultCharmList (${allCharms.length})`,
122128
[UI]: (

packages/runner/src/builder/built-in.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,26 @@ export const navigateTo = createNodeFactory({
8888
implementation: "navigateTo",
8989
}) as (cell: OpaqueRef<unknown>) => OpaqueRef<string>;
9090

91+
let wishFactory: NodeFactory<[unknown, unknown], any> | undefined;
92+
93+
export function wish<T = unknown>(
94+
target: Opaque<string>,
95+
): OpaqueRef<T | undefined>;
96+
export function wish<T = unknown>(
97+
target: Opaque<string>,
98+
defaultValue: Opaque<T> | T,
99+
): OpaqueRef<T>;
100+
export function wish<T = unknown>(
101+
target: Opaque<string>,
102+
defaultValue?: Opaque<T> | T,
103+
): OpaqueRef<T | undefined> {
104+
wishFactory ||= createNodeFactory({
105+
type: "ref",
106+
implementation: "wish",
107+
});
108+
return wishFactory([target, defaultValue]) as OpaqueRef<T | undefined>;
109+
}
110+
91111
// Example:
92112
// str`Hello, ${name}!`
93113
//

packages/runner/src/builder/factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
navigateTo,
3232
str,
3333
streamData,
34+
wish,
3435
} from "./built-in.ts";
3536
import { getRecipeEnvironment } from "./env.ts";
3637
import type { RuntimeProgram } from "../harness/types.ts";
@@ -117,6 +118,7 @@ export const createBuilder = (
117118
streamData,
118119
compileAndRun,
119120
navigateTo,
121+
wish,
120122

121123
// Cell creation
122124
createCell,

packages/runner/src/builder/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {
3232
StreamDataFunction,
3333
StreamFunction,
3434
StrFunction,
35+
WishFunction,
3536
} from "@commontools/api";
3637
import {
3738
h,
@@ -287,6 +288,7 @@ export interface BuilderFunctionsAndConstants {
287288
streamData: StreamDataFunction;
288289
compileAndRun: CompileAndRunFunction;
289290
navigateTo: NavigateToFunction;
291+
wish: WishFunction;
290292

291293
// Cell creation
292294
createCell: CreateCellFunction;

packages/runner/src/builtins/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ifElse } from "./if-else.ts";
77
import type { IRuntime } from "../runtime.ts";
88
import { compileAndRun } from "./compile-and-run.ts";
99
import { navigateTo } from "./navigate-to.ts";
10+
import { wish } from "./wish.ts";
1011
import type { Cell } from "../cell.ts";
1112
import type { BuiltInGenerateObjectParams } from "@commontools/api";
1213
import { llmDialog } from "./llm-dialog.ts";
@@ -34,4 +35,5 @@ export function registerBuiltins(runtime: IRuntime) {
3435
}>(generateObject),
3536
);
3637
moduleRegistry.addModuleByRef("navigateTo", raw(navigateTo));
38+
moduleRegistry.addModuleByRef("wish", raw(wish));
3739
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Well-known entity IDs used by built-in functions.
3+
*/
4+
5+
/**
6+
* Well-known entity ID for the all charms list.
7+
*/
8+
export const ALL_CHARMS_ID =
9+
"baedreiahv63wxwgaem4hzjkizl4qncfgvca7pj5cvdon7cukumfon3ioye";
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { type Cell } from "../cell.ts";
2+
import { type Action } from "../scheduler.ts";
3+
import type { IRuntime } from "../runtime.ts";
4+
import type {
5+
IExtendedStorageTransaction,
6+
MemorySpace,
7+
} from "../storage/interface.ts";
8+
import type { EntityId } from "../create-ref.ts";
9+
import { ALL_CHARMS_ID } from "./well-known.ts";
10+
import type { JSONSchema } from "../builder/types.ts";
11+
12+
type WishResolution = {
13+
entityId: EntityId;
14+
path?: readonly string[];
15+
};
16+
17+
const WISH_TARGETS: Record<string, WishResolution> = {
18+
"#/allCharms": { entityId: { "/": ALL_CHARMS_ID } },
19+
};
20+
21+
function resolveWishTarget(
22+
wish: string,
23+
runtime: IRuntime,
24+
space: MemorySpace,
25+
tx: IExtendedStorageTransaction,
26+
): Cell<unknown> | undefined {
27+
const target = WISH_TARGETS[wish];
28+
if (!target) return undefined;
29+
30+
const path = target.path ? [...target.path] : [];
31+
return runtime.getCellFromEntityId(
32+
space,
33+
target.entityId,
34+
path,
35+
undefined,
36+
tx,
37+
);
38+
}
39+
40+
const TARGET_SCHEMA = {
41+
type: "string",
42+
default: "",
43+
} as const satisfies JSONSchema;
44+
45+
export function wish(
46+
inputsCell: Cell<[unknown, unknown]>,
47+
sendResult: (tx: IExtendedStorageTransaction, result: unknown) => void,
48+
_addCancel: (cancel: () => void) => void,
49+
_cause: Cell<any>[],
50+
parentCell: Cell<any>,
51+
runtime: IRuntime,
52+
): Action {
53+
return (tx: IExtendedStorageTransaction) => {
54+
const inputsWithTx = inputsCell.withTx(tx);
55+
const values = inputsWithTx.get() ?? [];
56+
const target = inputsWithTx.key(0).asSchema(TARGET_SCHEMA).get();
57+
const defaultValue = values.length >= 2 ? values[1] : undefined;
58+
const hasDefault = defaultValue !== undefined && defaultValue !== null;
59+
const defaultCell = hasDefault ? inputsWithTx.key(1) : undefined;
60+
61+
const wishTarget = typeof target === "string" ? target.trim() : "";
62+
63+
if (wishTarget === "") {
64+
sendResult(tx, hasDefault ? defaultCell : undefined);
65+
return;
66+
}
67+
68+
const resolvedCell = resolveWishTarget(
69+
wishTarget,
70+
runtime,
71+
parentCell.space,
72+
tx,
73+
);
74+
75+
if (!resolvedCell) {
76+
console.error(`Wish target "${wishTarget}" is not recognized.`);
77+
sendResult(tx, hasDefault ? defaultCell : undefined);
78+
return;
79+
}
80+
81+
sendResult(tx, resolvedCell.withTx(tx));
82+
};
83+
}

0 commit comments

Comments
 (0)