From 515b4397caaee41be1d78f9e2d1e118c167acc1b Mon Sep 17 00:00:00 2001 From: Bernhard Seefeld Date: Mon, 3 Mar 2025 16:38:06 -0800 Subject: [PATCH 1/2] document cell interface for system vs spells (+ minor fix for update) --- typescript/packages/common-runner/src/cell.ts | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/typescript/packages/common-runner/src/cell.ts b/typescript/packages/common-runner/src/cell.ts index 72217dedc..e7f0429d3 100644 --- a/typescript/packages/common-runner/src/cell.ts +++ b/typescript/packages/common-runner/src/cell.ts @@ -27,6 +27,8 @@ import { Space } from "./space.ts"; * This abstracts away the paths behind an interface that e.g. the UX code or * modules that prefer cell interfaces can use. * + * These methods are available in the system and in spell code: + * * @method get Returns the current value of the cell. * @returns {T} * @@ -35,35 +37,69 @@ import { Space } from "./space.ts"; * @param {T} value - The new value to set. * @returns {void} * + * @method update Updates multiple properties of an object cell at once. + * @param {Partial} values - The properties to update. + * @returns {void} + * + * @method push Adds an item to the end of an array cell. + * @param {U | DocImpl | DocLink} value - The value to add, where U is the + * array element type. + * @returns {void} + * + * @method equals Compares two cells for equality. + * @param {Cell} other - The cell to compare with. + * @returns {boolean} + * * @method key Returns a new cell for the specified key path. * @param {K} valueKey - The key to access in the cell's value. * @returns {Cell} * + * Everything below is only available in the system, not in spell code: + * + * @method asSchema Creates a new cell with a specific schema. + * @param {JSONSchema} schema - The schema to apply. + * @returns {Cell} - A cell with the specified schema. + * + * @method withLog Creates a new cell with a specific reactivity log. + * @param {ReactivityLog} log - The log to use. + * @returns {Cell} + * * @method sink Adds a callback that is called immediately and on cell changes. * @param {function} callback - The callback to be called when the cell changes. * @returns {function} - A function to Cleanup the callback. * - * @method getAsProxy Returns a value proxy for the cell. - * @param {Path} path - The path to follow. + * @method getAsQueryResult Returns a query result for the cell. + * @param {Path} path - The optional path to follow. + * @param {ReactivityLog} log - Optional reactivity log. * @returns {QueryResult>} * - * @method getAsCellReference Returns a cell reference for the cell. + * @method getAsDocLink Returns a document link for the cell. * @returns {DocLink} * - * @method toJSON Returns a JSON pointer to the cell (not the contents!). - * @returns {{"/": string}} + * @method getSourceCell Returns the source cell with optional schema. + * @param {JSONSchema} schema - Optional schema to apply. + * @returns {Cell} + * + * @method toJSON Returns a serializable doclink (not the contents) to the cell. + * @returns {{cell: {"/": string} | undefined, path: PropertyKey[]}} * * @method value Returns the current value of the cell. * @returns {T} * - * @method entityId Returns the current entity ID of the cell. + * @property docLink The document link representing this cell. + * @returns {DocLink} + * + * @property entityId Returns the current entity ID of the cell. * @returns {EntityId | undefined} + * + * @property schema Optional schema for the cell. + * @returns {JSONSchema | undefined} */ export interface Cell { get(): T; set(value: T): void; send(value: T): void; - update(value: Partial): void; + update(values: Partial): void; push( value: | (T extends Array ? U : any) @@ -267,18 +303,13 @@ function createRegularCell( } }, send: (newValue: T) => self.set(newValue), - update: (value: Partial) => { - // TODO(seefeld): This doesn't respect aliases on write. Should it? - const ref = resolvePath(doc, path, log); - const previousValue = ref.cell.getAtPath(ref.path); - if (typeof previousValue !== "object" || previousValue === null) { - throw new Error("Can't update non-object value"); + update: (values: Partial) => { + if (typeof values !== "object" || values === null) { + throw new Error("Can't update with non-object value"); + } + for (const [key, value] of Object.entries(values)) { + self.key(key as keyof T).set(value as T[keyof T]); } - const newValue = { - ...previousValue, - ...value, - }; - ref.cell.setAtPath(ref.path, newValue, log); }, push: (value: any) => { const ref = resolvePath(doc, path, log); From 31631c4ee4c7bcda597c87b6ea982010f6255009 Mon Sep 17 00:00:00 2001 From: Bernhard Seefeld Date: Mon, 3 Mar 2025 16:40:56 -0800 Subject: [PATCH 2/2] minor fix: for push it's ok to follow references as well, since we treat the array as value --- typescript/packages/common-runner/src/cell.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/typescript/packages/common-runner/src/cell.ts b/typescript/packages/common-runner/src/cell.ts index e7f0429d3..9153d0568 100644 --- a/typescript/packages/common-runner/src/cell.ts +++ b/typescript/packages/common-runner/src/cell.ts @@ -284,7 +284,6 @@ function createRegularCell( const self = { get: () => validateAndTransform(doc, path, schema, log, rootSchema), set: (newValue: T) => { - // TODO(seefeld): This doesn't respect aliases on write. Should it? const ref = resolvePath(doc, path, log); if ( prepareForSaving( @@ -312,7 +311,9 @@ function createRegularCell( } }, push: (value: any) => { - const ref = resolvePath(doc, path, log); + // Follow aliases and references, since we want to get to an assumed + // existing array. + const ref = resolveLinkToValue(doc, path, log); const array = ref.cell.getAtPath(ref.path) ?? []; if (!Array.isArray(array)) { throw new Error("Can't push into non-array value");