Skip to content

Commit aa3e003

Browse files
committed
Type checking: Allow inputs to writing cell methods to have a different Cell<> structure than the destination cell
1 parent 15e7df5 commit aa3e003

File tree

3 files changed

+55
-37
lines changed

3 files changed

+55
-37
lines changed

builder/src/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isObj } from "@commontools/utils";
22

3-
export const ID: symbol = Symbol("ID, unique to the context");
4-
export const ID_FIELD: symbol = Symbol(
3+
export const ID: unique symbol = Symbol("ID, unique to the context");
4+
export const ID_FIELD: unique symbol = Symbol(
55
"ID_FIELD, name of sibling that contains id",
66
);
77

@@ -108,9 +108,11 @@ export type JSONValue =
108108
| boolean
109109
| null
110110
| JSONValue[]
111-
| { [key: string]: JSONValue };
111+
| { [key: string]: JSONValue } & { [ID]?: any; [ID_FIELD]?: any };
112112

113113
export type JSONSchema = {
114+
readonly [ID]?: any;
115+
readonly [ID_FIELD]?: any;
114116
readonly type?:
115117
| "object"
116118
| "array"

runner/src/cell.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { isStreamAlias, TYPE } from "@commontools/builder";
2-
import { getTopFrame, ID, type JSONSchema } from "@commontools/builder";
2+
import {
3+
getTopFrame,
4+
ID,
5+
ID_FIELD,
6+
type JSONSchema,
7+
} from "@commontools/builder";
38
import { type DeepKeyLookup, type DocImpl, getDoc, isDoc } from "./doc.ts";
49
import {
510
createQueryResultProxy,
@@ -103,15 +108,13 @@ import { type Schema } from "@commontools/builder";
103108
*/
104109
export interface Cell<T> {
105110
get(): T;
106-
set(value: T): void;
107-
send(value: T): void;
108-
update(values: Partial<T>): void;
111+
set(value: Cellify<T> | T): void;
112+
send(value: Cellify<T> | T): void;
113+
update(values: Cellify<Partial<T> | T>): void;
109114
push(
110115
...value: Array<
111-
| (T extends Array<infer U> ? U : any)
112-
| DocImpl<T extends Array<infer U> ? U : any>
116+
| (T extends Array<infer U> ? (Cellify<U> | U | DocImpl<U>) : any)
113117
| CellLink
114-
| Cell<T extends Array<infer U> ? U : any>
115118
>
116119
): void;
117120
equals(other: Cell<any>): boolean;
@@ -164,6 +167,26 @@ export interface Cell<T> {
164167
copyTrap: boolean;
165168
}
166169

170+
/**
171+
* Cellify is a type utility that allows any part of type T to be wrapped in
172+
* Cell<>, and allow any part of T that is currently wrapped in Cell<> to be
173+
* used unwrapped. This is designed for use with Cell<T> method parameters,
174+
* allowing flexibility in how values are passed.
175+
*/
176+
export type Cellify<T> =
177+
// Handle existing Cell<> types, allowing unwrapping
178+
T extends Cell<infer U> ? Cellify<U> | Cell<Cellify<U>>
179+
// Handle arrays
180+
: T extends Array<infer U> ? Array<Cellify<U>> | Cell<Array<Cellify<U>>>
181+
// Handle objects (excluding null), adding optional ID fields
182+
: T extends object ?
183+
| ({ [K in keyof T]: Cellify<T[K]> } & { [ID]?: any; [ID_FIELD]?: any })
184+
| Cell<
185+
{ [K in keyof T]: Cellify<T[K]> } & { [ID]?: any; [ID_FIELD]?: any }
186+
>
187+
// Handle primitives
188+
: T | Cell<T>;
189+
167190
export interface Stream<T> {
168191
send(event: T): void;
169192
sink(callback: (event: T) => Cancel | undefined | void): Cancel;
@@ -359,15 +382,15 @@ function createRegularCell<T>(
359382

360383
const self = {
361384
get: () => validateAndTransform(doc, path, schema, log, rootSchema),
362-
set: (newValue: T) =>
385+
set: (newValue: Cellify<T>) =>
363386
diffAndUpdate(
364387
resolveLinkToAlias(doc, path, log),
365388
newValue,
366389
log,
367390
getTopFrame()?.cause,
368391
),
369-
send: (newValue: T) => self.set(newValue),
370-
update: (values: Partial<T>) => {
392+
send: (newValue: Cellify<T>) => self.set(newValue),
393+
update: (values: Cellify<Partial<T>>) => {
371394
if (typeof values !== "object" || values === null) {
372395
throw new Error("Can't update with non-object value");
373396
}
@@ -378,10 +401,9 @@ function createRegularCell<T>(
378401
},
379402
push: (
380403
...values: Array<
381-
| (T extends Array<infer U> ? U : any)
382-
| DocImpl<T extends Array<infer U> ? U : any>
404+
| (T extends Array<infer U> ? Cellify<U> : any)
405+
| DocImpl<T extends Array<infer U> ? Cellify<U> : any>
383406
| CellLink
384-
| Cell<T extends Array<infer U> ? U : any>
385407
>
386408
) => {
387409
// Follow aliases and references, since we want to get to an assumed

runner/test/cell.test.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import { type DocImpl, getDoc, isDoc } from "../src/doc.ts";
44
import { isCell, isCellLink } from "../src/cell.ts";
55
import { isQueryResult } from "../src/query-result-proxy.ts";
66
import { type ReactivityLog } from "../src/scheduler.ts";
7-
import {
8-
getTopFrame,
9-
ID,
10-
JSONSchema,
11-
popFrame,
12-
pushFrame,
13-
} from "@commontools/builder";
7+
import { ID, JSONSchema, popFrame, pushFrame } from "@commontools/builder";
148
import { addEventHandler, idle } from "../src/scheduler.ts";
159
import { addCommonIDfromObjectID } from "../src/utils.ts";
1610

@@ -562,7 +556,7 @@ describe("asCell with schema", () => {
562556
},
563557
},
564558
required: ["name", "age", "tags", "nested"],
565-
} satisfies JSONSchema;
559+
} as const satisfies JSONSchema;
566560

567561
const cell = c.asCell([], undefined, schema);
568562
const value = cell.get();
@@ -596,7 +590,7 @@ describe("asCell with schema", () => {
596590
},
597591
},
598592
required: ["id", "metadata"],
599-
} satisfies JSONSchema;
593+
} as const satisfies JSONSchema;
600594

601595
const value = c.asCell([], undefined, schema).get();
602596

@@ -643,7 +637,7 @@ describe("asCell with schema", () => {
643637
},
644638
},
645639
required: ["name", "children"],
646-
} satisfies JSONSchema;
640+
} as const satisfies JSONSchema;
647641

648642
const value = c.asCell([], undefined, schema).get();
649643

@@ -700,7 +694,7 @@ describe("asCell with schema", () => {
700694
},
701695
},
702696
required: ["user"],
703-
} satisfies JSONSchema;
697+
} as const satisfies JSONSchema;
704698

705699
const cell = c.asCell([], undefined, schema);
706700
const userCell = cell.key("user");
@@ -831,7 +825,7 @@ describe("asCell with schema", () => {
831825
},
832826
},
833827
required: ["id", "context"],
834-
} satisfies JSONSchema;
828+
} as const satisfies JSONSchema;
835829

836830
const cell = c.asCell([], undefined, schema);
837831
const value = cell.get();
@@ -873,7 +867,7 @@ describe("asCell with schema", () => {
873867
},
874868
},
875869
required: ["context"],
876-
} satisfies JSONSchema;
870+
} as const satisfies JSONSchema;
877871

878872
const cell = c.asCell([], undefined, schema);
879873
const value = cell.get();
@@ -919,7 +913,7 @@ describe("asCell with schema", () => {
919913
},
920914
},
921915
required: ["context"],
922-
} satisfies JSONSchema;
916+
} as const satisfies JSONSchema;
923917

924918
const cell = c.asCell([], undefined, schema);
925919
const value = cell.get();
@@ -965,7 +959,7 @@ describe("asCell with schema", () => {
965959
},
966960
},
967961
required: ["context"],
968-
} satisfies JSONSchema;
962+
} as const satisfies JSONSchema;
969963

970964
const cell = c.asCell([], undefined, schema);
971965
const value = cell.get();
@@ -1032,7 +1026,7 @@ describe("asCell with schema", () => {
10321026
},
10331027
},
10341028
required: ["context"],
1035-
} satisfies JSONSchema;
1029+
} as const satisfies JSONSchema;
10361030

10371031
const log = { reads: [], writes: [] } as ReactivityLog;
10381032
const cell = c.asCell([], log, schema);
@@ -1082,7 +1076,7 @@ describe("asCell with schema", () => {
10821076
},
10831077
},
10841078
required: ["items"],
1085-
} satisfies JSONSchema;
1079+
} as const satisfies JSONSchema;
10861080

10871081
const cell = c.asCell([], undefined, schema);
10881082
const itemsCell = cell.key("items");
@@ -1115,7 +1109,7 @@ describe("asCell with schema", () => {
11151109
value: { type: "number" },
11161110
},
11171111
},
1118-
} satisfies JSONSchema;
1112+
} as const satisfies JSONSchema;
11191113

11201114
const cell = c.asCell([], undefined, schema);
11211115

@@ -1149,7 +1143,7 @@ describe("asCell with schema", () => {
11491143
type: "object",
11501144
properties: { anything: { asCell: true } },
11511145
},
1152-
} satisfies JSONSchema;
1146+
} as const satisfies JSONSchema;
11531147

11541148
const cell = c.asCell([], undefined, schema);
11551149

@@ -1226,8 +1220,8 @@ describe("asCell with schema", () => {
12261220
const schema = {
12271221
type: "array",
12281222
items: { type: "object", properties: { value: { type: "number" } } },
1229-
default: [{ [ID]: "test", "value": 10 }, { [ID]: "test2", "value": 20 }],
1230-
} as JSONSchema;
1223+
default: [{ [ID]: "test", value: 10 }, { [ID]: "test2", value: 20 }],
1224+
} as const satisfies JSONSchema;
12311225

12321226
const c = getDoc({}, "push-to-undefined-schema-stable-id", "test");
12331227
const arrayCell = c.asCell(["items"], undefined, schema);

0 commit comments

Comments
 (0)