Skip to content
Merged
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
6 changes: 3 additions & 3 deletions docs/specs/recipe-construction/rollout-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- [x] Disable ShadowRef/unsafe_ and see what breaks, ideally remove it (will
merge later as it'll break a few patterns)
- [ ] Update Cell API types to already unify them
- [x] Update Cell API types to already unify them
- [x] Create an `BrandedCell<>` type with a symbol based brand, with the value
be `string`
- [x] Factor out parts of the cell interfaces along reading, writing, .send
Expand All @@ -19,9 +19,9 @@
- [ ] Simplify most wrap/unwrap types to use `CellLike`. We need
- [x] "Accept any T where any sub part of T can be wrapped in one or more
`BrandedCell`" (for inputs to node factories)
- [ ] "Strip any `BrandedCell` from T and then wrap it in OpaqueRef<>" (for
- [x] "Strip any `BrandedCell` from T and then wrap it in OpaqueRef<>" (for
outputs of node factories, where T is the output of the inner function)
- [ ] Make passing the output of the second into the first work. Tricky
- [x] Make passing the output of the second into the first work. Tricky
because we're doing almost opposite expansions on the type.
- [ ] Add ability to create a cell without a link yet.
- [x] Merge StreamCell into RegularCell and rename RegularCell to CellImpl
Expand Down
49 changes: 26 additions & 23 deletions packages/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,15 @@ type MaybeCellWrapped<T> =
: never);
export declare const CELL_LIKE: unique symbol;

/**
* Helper type to transform Cell<T> to Opaque<T> in pattern/lift/handler inputs
*/
export type StripCell<T> = T extends AnyBrandedCell<infer U> ? StripCell<U>
: T extends ArrayBuffer | ArrayBufferView | URL | Date ? T
: T extends Array<infer U> ? StripCell<U>[]
: T extends object ? { [K in keyof T]: StripCell<T[K]> }
: T;

/**
* Opaque accepts T or any cell wrapping T, recursively at any nesting level.
* Used in APIs that accept inputs from developers - can be static values
Expand Down Expand Up @@ -969,41 +978,41 @@ export interface BuiltInCompileAndRunState<T> {
export type PatternFunction = {
<T, R>(
fn: (input: OpaqueRef<Required<T>>) => Opaque<R>,
): RecipeFactory<T, R>;
): RecipeFactory<StripCell<T>, StripCell<R>>;

<T>(
fn: (input: OpaqueRef<Required<T>>) => unknown,
): RecipeFactory<T, ReturnType<typeof fn>>;
): RecipeFactory<StripCell<T>, StripCell<ReturnType<typeof fn>>>;

<IS extends JSONSchema = JSONSchema, OS extends JSONSchema = JSONSchema>(
fn: (
input: OpaqueRef<Required<Schema<IS>>>,
) => Opaque<Schema<OS>>,
argumentSchema: IS,
resultSchema: OS,
): RecipeFactory<Schema<IS>, Schema<OS>>;
): RecipeFactory<SchemaWithoutCell<IS>, SchemaWithoutCell<OS>>;
};

/** @deprecated Use pattern() instead */
export type RecipeFunction = {
// Function-only overload
<T, R>(
fn: (input: OpaqueRef<Required<T>>) => Opaque<R>,
): RecipeFactory<T, R>;
): RecipeFactory<StripCell<T>, StripCell<R>>;

<T>(
fn: (input: OpaqueRef<Required<T>>) => any,
): RecipeFactory<T, ReturnType<typeof fn>>;
): RecipeFactory<StripCell<T>, StripCell<ReturnType<typeof fn>>>;

<S extends JSONSchema>(
argumentSchema: S,
fn: (input: OpaqueRef<Required<SchemaWithoutCell<S>>>) => any,
): RecipeFactory<SchemaWithoutCell<S>, ReturnType<typeof fn>>;
): RecipeFactory<SchemaWithoutCell<S>, StripCell<ReturnType<typeof fn>>>;

<S extends JSONSchema, R>(
argumentSchema: S,
fn: (input: OpaqueRef<Required<SchemaWithoutCell<S>>>) => Opaque<R>,
): RecipeFactory<SchemaWithoutCell<S>, R>;
): RecipeFactory<SchemaWithoutCell<S>, StripCell<R>>;

<S extends JSONSchema, RS extends JSONSchema>(
argumentSchema: S,
Expand All @@ -1016,18 +1025,18 @@ export type RecipeFunction = {
<T>(
argumentSchema: string | JSONSchema,
fn: (input: OpaqueRef<Required<T>>) => any,
): RecipeFactory<T, ReturnType<typeof fn>>;
): RecipeFactory<StripCell<T>, StripCell<ReturnType<typeof fn>>>;

<T, R>(
argumentSchema: string | JSONSchema,
fn: (input: OpaqueRef<Required<T>>) => Opaque<R>,
): RecipeFactory<T, R>;
): RecipeFactory<StripCell<T>, StripCell<R>>;

<T, R>(
argumentSchema: string | JSONSchema,
resultSchema: JSONSchema,
fn: (input: OpaqueRef<Required<T>>) => Opaque<R>,
): RecipeFactory<T, R>;
): RecipeFactory<StripCell<T>, StripCell<R>>;
};

export type PatternToolFunction = <
Expand All @@ -1047,21 +1056,21 @@ export type LiftFunction = {

<T, R>(
implementation: (input: T) => R,
): ModuleFactory<T, R>;
): ModuleFactory<StripCell<T>, StripCell<R>>;

<T>(
implementation: (input: T) => any,
): ModuleFactory<T, ReturnType<typeof implementation>>;
): ModuleFactory<StripCell<T>, StripCell<ReturnType<typeof implementation>>>;

<T extends (...args: any[]) => any>(
implementation: T,
): ModuleFactory<Parameters<T>[0], ReturnType<T>>;
): ModuleFactory<StripCell<Parameters<T>[0]>, StripCell<ReturnType<T>>>;

<T, R>(
argumentSchema?: JSONSchema,
resultSchema?: JSONSchema,
implementation?: (input: T) => R,
): ModuleFactory<T, R>;
): ModuleFactory<StripCell<T>, StripCell<R>>;
};

// Helper type to make non-Cell and non-Stream properties readonly in handler state
Expand All @@ -1085,17 +1094,17 @@ export type HandlerFunction = {
eventSchema: JSONSchema,
stateSchema: JSONSchema,
handler: (event: E, props: HandlerState<T>) => any,
): ModuleFactory<StripCell<T>, E>;
): ModuleFactory<StripCell<T>, StripCell<E>>;

// Without schemas
<E, T>(
handler: (event: E, props: T) => any,
options: { proxy: true },
): ModuleFactory<StripCell<T>, E>;
): ModuleFactory<StripCell<T>, StripCell<E>>;

<E, T>(
handler: (event: E, props: HandlerState<T>) => any,
): ModuleFactory<StripCell<T>, E>;
): ModuleFactory<StripCell<T>, StripCell<E>>;
};

/**
Expand Down Expand Up @@ -1308,12 +1317,6 @@ export type Mutable<T> = T extends ReadonlyArray<infer U> ? Mutable<U>[]
: T extends object ? ({ -readonly [P in keyof T]: Mutable<T[P]> })
: T;

// Helper type to transform Cell<T> to Opaque<T> in handler inputs
export type StripCell<T> = T extends Cell<infer U> ? StripCell<U>
: T extends Array<infer U> ? StripCell<U>[]
: T extends object ? { [K in keyof T]: StripCell<T[K]> }
: T;

export type WishKey = `/${string}` | `#${string}`;

// ===== JSON Pointer Path Resolution Utilities =====
Expand Down
3 changes: 1 addition & 2 deletions packages/html/test/html-recipes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type Cell,
createBuilder,
type IExtendedStorageTransaction,
type OpaqueRef,
Runtime,
} from "@commontools/runner";
import { StorageManager } from "@commontools/runner/storage/cache.deno";
Expand Down Expand Up @@ -215,7 +214,7 @@ describe("recipes with HTML", () => {
h(
"ul",
null,
entries(row).map((input: OpaqueRef<[string, unknown]>) =>
entries(row).map((input) =>
h("li", null, [input[0], ": ", str`${input[1]}`])
) as VNode[],
)
Expand Down
6 changes: 3 additions & 3 deletions packages/patterns/chatbot-list-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ type Input = {
selectedCharm: Default<{ charm: any }, { charm: undefined }>;
charmsList: Default<CharmEntry[], []>;
theme?: {
accentColor: Default<string, "#3b82f6">;
fontFace: Default<string, "system-ui, -apple-system, sans-serif">;
borderRadius: Default<string, "0.5rem">;
accentColor: Cell<Default<string, "#3b82f6">>;
fontFace: Cell<Default<string, "system-ui, -apple-system, sans-serif">>;
borderRadius: Cell<Default<string, "0.5rem">>;
};
};

Expand Down
8 changes: 4 additions & 4 deletions packages/patterns/chatbot-note-composed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ function schemaifyWish<T>(path: string, def: T) {
}

type ChatbotNoteInput = {
title: Default<string, "LLM Test">;
messages: Default<Array<BuiltInLLMMessage>, []>;
title?: Cell<Default<string, "LLM Test">>;
messages?: Cell<Default<Array<BuiltInLLMMessage>, []>>;
};

type ChatbotNoteResult = {
Expand All @@ -57,11 +57,11 @@ const newNote = handler<
try {
const n = Note({
title: args.title,
content: args.content || "",
content: args.content ?? "",
});

args.result.set(
`Created note ${args.title}!`,
`Created note ${args.title}`,
);

// TODO(bf): we have to navigate here until DX1 lands
Expand Down
8 changes: 4 additions & 4 deletions packages/patterns/chatbot-outliner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ export const Page = recipe<PageInput>(
);

type LLMTestInput = {
title: Default<string, "LLM Test">;
messages: Default<Array<BuiltInLLMMessage>, []>;
expandChat: Default<boolean, false>;
outline: Default<
title?: Cell<Default<string, "LLM Test">>;
messages?: Cell<Default<Array<BuiltInLLMMessage>, []>>;
expandChat?: Cell<Default<boolean, false>>;
outline?: Default<
Outliner,
{ root: { body: "Untitled Page"; children: []; attachments: [] } }
>;
Expand Down
4 changes: 2 additions & 2 deletions packages/patterns/chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ const clearChat = handler(
);

type ChatInput = {
messages: Default<Array<BuiltInLLMMessage>, []>;
tools: any;
messages?: Cell<Default<Array<BuiltInLLMMessage>, []>>;
tools?: any;
theme?: any;
system?: string;
};
Expand Down
4 changes: 2 additions & 2 deletions packages/patterns/ct-checkbox-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { Cell, Default, handler, ifElse, NAME, recipe, UI } from "commontools";

interface CheckboxDemoInput {
simpleEnabled: Default<boolean, false>;
trackedEnabled: Default<boolean, false>;
simpleEnabled: Cell<Default<boolean, false>>;
trackedEnabled: Cell<Default<boolean, false>>;
}

interface CheckboxDemoOutput extends CheckboxDemoInput {}
Expand Down
4 changes: 2 additions & 2 deletions packages/patterns/ct-checkbox-handler.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// <cts-enable />
import { Default, ifElse, NAME, recipe, UI } from "commontools";
import { Cell, Default, ifElse, NAME, recipe, UI } from "commontools";

interface CheckboxSimpleInput {
enabled: Default<boolean, false>;
enabled: Cell<Default<boolean, false>>;
}

interface CheckboxSimpleOutput extends CheckboxSimpleInput {}
Expand Down
6 changes: 3 additions & 3 deletions packages/patterns/ct-list.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/// <cts-enable />
import { Default, NAME, recipe, UI } from "commontools";
import { Cell, Default, NAME, recipe, UI } from "commontools";

interface Item {
title: string;
}

interface ListInput {
title: Default<string, "My List">;
items: Default<Item[], []>;
title: Cell<Default<string, "My List">>;
items: Cell<Default<Item[], []>>;
}

interface ListOutput extends ListInput {}
Expand Down
8 changes: 4 additions & 4 deletions packages/patterns/ct-select.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/// <cts-enable />

import { Default, NAME, recipe, UI } from "commontools";
import { Cell, Default, NAME, recipe, UI } from "commontools";

type Input = {
selected: Default<string, "opt_1">;
numericChoice: Default<number, 1>;
category: Default<string, "Other">;
selected: Cell<Default<string, "opt_1">>;
numericChoice: Cell<Default<number, 1>>;
category: Cell<Default<string, "Other">>;
};

type Result = {
Expand Down
3 changes: 2 additions & 1 deletion packages/patterns/fetch-data.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <cts-enable />
import {
Cell,
Default,
derive,
fetchData,
Expand Down Expand Up @@ -151,7 +152,7 @@ function parseUrl(url: string): { org: string; user: string } {
}

export default recipe<
{ repoUrl: Default<string, "https://github.com/vercel/next.js"> }
{ repoUrl: Cell<Default<string, "https://github.com/vercel/next.js">> }
>(
"Github Fetcher Demo",
(state) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/patterns/note.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <cts-enable />
import {
type Cell,
Cell,
cell,
type Default,
derive,
Expand All @@ -17,8 +17,8 @@ import {
} from "commontools";
import { type MentionableCharm } from "./backlinks-index.tsx";
type Input = {
title: Default<string, "Untitled Note">;
content: Default<string, "">;
title?: Cell<Default<string, "Untitled Note">>;
content?: Cell<Default<string, "">>;
};

type Output = {
Expand Down
1 change: 0 additions & 1 deletion packages/patterns/omnibox-fab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export default recipe<OmniboxFABInput>(
const omnibot = Chatbot({
system:
"You are a polite but efficient assistant. Think Star Trek computer - helpful and professional without unnecessary conversation. Let your actions speak for themselves.\n\nTool usage priority:\n- Search this space first: listMentionable → addAttachment to access items\n- Search externally only when clearly needed: searchWeb for current events, external information, or when nothing relevant exists in the space\n\nBe matter-of-fact. Prefer action to explanation.",
messages: [],
tools: {
searchWeb: {
pattern: searchWeb,
Expand Down
6 changes: 4 additions & 2 deletions packages/shell/integration/iframe-counter-recipe.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <cts-enable />
import { type JSONSchema, NAME, recipe, UI } from "commontools";
import { CellLike, type JSONSchema, NAME, recipe, UI } from "commontools";

type IFrameRecipe = {
src: string;
Expand Down Expand Up @@ -58,7 +58,9 @@ const runIframeRecipe = (
) =>
recipe(argumentSchema, resultSchema, (data) => ({
[NAME]: name,
[UI]: <common-iframe src={src} $context={data}></common-iframe>,
[UI]: (
<common-iframe src={src} $context={data as CellLike<any>}></common-iframe>
),
count: data.count,
}));

Expand Down
2 changes: 1 addition & 1 deletion recipes/bgCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const updateError = handler<
);

export default recipe<
{ error: Default<string, "">; counter: Default<number, 0> }
{ error: Cell<Default<string, "">>; counter: Cell<Default<number, 0>> }
>(
"bgCounter",
({ counter, error }) => {
Expand Down
Loading