Skip to content

Commit 1bc9956

Browse files
authored
refactor(runner): create cells using memory addresses instead of direct references (#1347)
refactor(runner): create cells using memory addresses instead of direct references - Updated cell creation logic to use memory addresses (URIs) for referencing cells, rather than direct DocImpl references. - Refactored related modules (cell, doc, link-resolution, link-utils, query-result-proxy, runner, runtime, scheduler, schema) to support address-based cell instantiation and linking. - Updated tests to reflect new cell creation and linking approach.
1 parent 9c6f0fe commit 1bc9956

File tree

13 files changed

+182
-114
lines changed

13 files changed

+182
-114
lines changed

packages/runner/src/cell.ts

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ import {
3434
type URI,
3535
} from "./sigil-types.ts";
3636
import { areLinksSame, isLink } from "./link-utils.ts";
37+
import { type IRuntime } from "./runtime.ts";
38+
import {
39+
type NormalizedFullLink,
40+
parseNormalizedFullLinktoLegacyDocCellLink,
41+
} from "./link-utils.ts";
3742

3843
/**
3944
* This is the regular Cell interface, generated by DocImpl.asCell().
@@ -90,6 +95,9 @@ import { areLinksSame, isLink } from "./link-utils.ts";
9095
* @method getAsLegacyCellLink Returns a cell link for the cell (legacy format).
9196
* @returns {LegacyDocCellLink}
9297
*
98+
* @method getAsNormalizedFullLink Returns a normalized full link for the cell.
99+
* @returns {NormalizedFullLink}
100+
*
93101
* @method getAsLink Returns a cell link for the cell (new sigil format).
94102
* @returns {SigilLink}
95103
*
@@ -170,6 +178,7 @@ declare module "@commontools/api" {
170178
log?: ReactivityLog,
171179
): QueryResult<DeepKeyLookup<T, Path>>;
172180
getAsLegacyCellLink(): LegacyDocCellLink;
181+
getAsNormalizedFullLink(): NormalizedFullLink;
173182
getAsLink(
174183
options?: {
175184
base?: Cell<any>;
@@ -255,20 +264,22 @@ export interface Stream<T> {
255264
send(event: T): void;
256265
sink(callback: (event: T) => Cancel | undefined | void): Cancel;
257266
getRaw(): any;
267+
getAsNormalizedFullLink(): NormalizedFullLink;
258268
getDoc(): DocImpl<any>;
259269
schema?: JSONSchema;
260270
rootSchema?: JSONSchema;
261271
[isStreamMarker]: true;
262272
}
263273

264274
export function createCell<T>(
265-
doc: DocImpl<any>,
266-
path: PropertyKey[] = [],
275+
runtime: IRuntime,
276+
link: NormalizedFullLink,
267277
log?: ReactivityLog,
268-
schema?: JSONSchema,
269-
rootSchema: JSONSchema | undefined = schema,
270278
noResolve = false,
271279
): Cell<T> {
280+
const doc = runtime.documentMap.getDocByEntityId(link.space, link.id, true);
281+
let { path, schema, rootSchema } = link;
282+
272283
// Resolve the path to check whether it's a stream. We're not logging this right now.
273284
// The corner case where during it's lifetime this changes from non-stream to stream
274285
// or vice versa will not be detected.
@@ -283,23 +294,26 @@ export function createCell<T>(
283294

284295
if (isStreamValue(ref.cell.getAtPath(ref.path))) {
285296
return createStreamCell(
286-
ref.cell,
287-
ref.path,
297+
runtime,
298+
{
299+
...link,
300+
space: ref.cell.space,
301+
id: toURI(ref.cell.entityId),
302+
path: ref.path as string[],
303+
schema,
304+
rootSchema,
305+
},
288306
log,
289-
schema,
290-
rootSchema,
291307
) as unknown as Cell<T>;
292308
} else {
293-
return createRegularCell(doc, path, log, schema, rootSchema);
309+
return createRegularCell(runtime, { ...link, schema, rootSchema }, log);
294310
}
295311
}
296312

297313
function createStreamCell<T>(
298-
doc: DocImpl<any>,
299-
path: PropertyKey[],
314+
runtime: IRuntime,
315+
link: NormalizedFullLink,
300316
_log?: ReactivityLog,
301-
schema?: JSONSchema,
302-
rootSchema?: JSONSchema,
303317
): Stream<T> {
304318
const listeners = new Set<(event: T) => Cancel | undefined>();
305319

@@ -309,11 +323,7 @@ function createStreamCell<T>(
309323
// Implementing just the subset of Cell<T> that is needed for streams.
310324
send: (event: T) => {
311325
// Use runtime from doc if available
312-
if (doc.runtime) {
313-
doc.runtime.scheduler.queueEvent({ cell: doc, path }, event);
314-
} else {
315-
throw new Error("No runtime available for queueEvent");
316-
}
326+
runtime.scheduler.queueEvent(link, event);
317327

318328
cleanup?.();
319329
const [cancel, addCancel] = useCancelGroup();
@@ -325,23 +335,25 @@ function createStreamCell<T>(
325335
listeners.add(callback);
326336
return () => listeners.delete(callback);
327337
},
328-
getRaw: () => doc.getAtPath(path),
329-
getDoc: () => doc,
330-
schema,
331-
rootSchema,
338+
getRaw: () => self.getDoc().getAtPath(link.path),
339+
getAsNormalizedFullLink: () => link,
340+
getDoc: () => runtime.documentMap.getDocByEntityId(link.space, link.id),
341+
schema: link.schema,
342+
rootSchema: link.rootSchema,
332343
[isStreamMarker]: true,
333344
} satisfies Stream<T>;
334345

335346
return self;
336347
}
337348

338349
function createRegularCell<T>(
339-
doc: DocImpl<any>,
340-
path: PropertyKey[],
350+
runtime: IRuntime,
351+
link: NormalizedFullLink,
341352
log?: ReactivityLog,
342-
schema?: JSONSchema,
343-
rootSchema?: JSONSchema,
344353
): Cell<T> {
354+
const doc = runtime.documentMap.getDocByEntityId(link.space, link.id);
355+
const { path, schema, rootSchema } = link;
356+
345357
const self = {
346358
get: () => validateAndTransform(doc, path, schema, log, rootSchema),
347359
set: (newValue: Cellify<T>) =>
@@ -453,25 +465,31 @@ function createRegularCell<T>(
453465
rootSchema,
454466
);
455467
return createCell(
456-
doc,
457-
[...path, valueKey],
468+
runtime,
469+
{
470+
...link,
471+
path: [...path, valueKey.toString()],
472+
schema: childSchema,
473+
},
458474
log,
459-
childSchema,
460-
rootSchema,
461475
) as T extends Cell<infer S> ? Cell<S[K & keyof S]> : Cell<T[K]>;
462476
},
463477

464478
asSchema: (newSchema?: JSONSchema) =>
465-
createCell(doc, path, log, newSchema, newSchema),
466-
withLog: (newLog: ReactivityLog) =>
467-
createCell(doc, path, newLog, schema, rootSchema),
479+
createCell(
480+
runtime,
481+
{ ...link, schema: newSchema, rootSchema: newSchema },
482+
log,
483+
),
484+
withLog: (newLog: ReactivityLog) => createCell(runtime, link, newLog),
468485
sink: (callback: (value: T) => Cancel | undefined) =>
469486
subscribeToReferencedDocs(callback, doc, path, schema, rootSchema),
470487
getAsQueryResult: (subPath: PropertyKey[] = [], newLog?: ReactivityLog) =>
471488
createQueryResultProxy(doc, [...path, ...subPath], newLog ?? log),
472489
getAsLegacyCellLink: (): LegacyDocCellLink => {
473490
return { space: doc.space, cell: doc, path, schema, rootSchema };
474491
},
492+
getAsNormalizedFullLink: () => link,
475493
getAsLink: (
476494
options?: {
477495
base?: Cell<any>;

packages/runner/src/doc.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { type ReactivityLog } from "./scheduler.ts";
2020
import { type Cancel } from "./cancel.ts";
2121
import { Labels, MemorySpace } from "./storage.ts";
2222
import { arrayEqual } from "./path-utils.ts";
23+
import { toURI } from "./uri-utils.ts";
2324

2425
/**
2526
* Lowest level cell implementation.
@@ -274,7 +275,15 @@ export function createDoc<T>(
274275
log?: ReactivityLog,
275276
schema?: JSONSchema,
276277
rootSchema?: JSONSchema,
277-
) => createCell<Q>(self, path || [], log, schema, rootSchema),
278+
) =>
279+
createCell(runtime, {
280+
space,
281+
id: toURI(entityId),
282+
path: path?.map(String) ?? [],
283+
type: "application/json",
284+
schema,
285+
rootSchema,
286+
}, log),
278287
send: (newValue: T, log?: ReactivityLog) =>
279288
self.setAtPath([], newValue, log),
280289
updates: (

packages/runner/src/link-resolution.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
parseLink,
1515
parseToLegacyCellLink,
1616
} from "./link-utils.ts";
17+
import { toURI } from "./uri-utils.ts";
1718

1819
/**
1920
* Track visited cell links and memoize results during path resolution
@@ -210,10 +211,13 @@ export function followLinks(
210211
? parseToLegacyCellLink(
211212
target,
212213
createCell(
213-
result.cell,
214-
[], // Use empty path to reference the document itself
215-
undefined,
216-
undefined,
214+
result.cell.runtime,
215+
{
216+
space: result.cell.space,
217+
id: toURI(result.cell.entityId),
218+
path: [], // Use empty path to reference to document itself
219+
type: "application/json",
220+
},
217221
undefined,
218222
true,
219223
),

packages/runner/src/link-utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type IRuntime } from "./runtime.ts";
66
import {
77
type JSONCellLink,
88
type LegacyAlias,
9-
type LegacyDocCellLink as LegacyDocCellLink,
9+
type LegacyDocCellLink,
1010
LINK_V1_TAG,
1111
type SigilLink,
1212
type SigilValue,
@@ -219,7 +219,8 @@ export function isLegacyAlias(value: any): value is LegacyAlias {
219219
export function parseLink(
220220
value:
221221
| Cell<any>
222-
| DocImpl<any>,
222+
| DocImpl<any>
223+
| LegacyDocCellLink,
223224
base?: Cell | NormalizedLink,
224225
): NormalizedFullLink;
225226
export function parseLink(

packages/runner/src/query-result-proxy.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type LegacyDocCellLink } from "./sigil-types.ts";
66
import { type ReactivityLog } from "./scheduler.ts";
77
import { diffAndUpdate, setNestedValue } from "./data-updating.ts";
88
import { resolveLinkToValue } from "./link-resolution.ts";
9+
import { parseLink } from "./link-utils.ts";
910

1011
// Maximum recursion depth to prevent infinite loops
1112
const MAX_RECURSION_DEPTH = 100;
@@ -243,10 +244,15 @@ export function createQueryResultProxy<T>(
243244
) {
244245
log?.writes.push({ cell: valueCell, path: [...valuePath, i] });
245246
if (valueCell.runtime) {
246-
valueCell.runtime.scheduler.queueEvent({
247-
cell: valueCell,
248-
path: [...valuePath, i],
249-
}, undefined);
247+
valueCell.runtime.scheduler.queueEvent(
248+
parseLink(
249+
{
250+
cell: valueCell,
251+
path: [...valuePath, i],
252+
} as LegacyDocCellLink,
253+
),
254+
undefined,
255+
);
250256
}
251257
}
252258
}

packages/runner/src/runner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,9 @@ export class Runner implements IRunner {
590590
}
591591
};
592592

593-
addCancel(this.runtime.scheduler.addEventHandler(handler, stream));
593+
addCancel(
594+
this.runtime.scheduler.addEventHandler(handler, parseLink(streamRef)),
595+
);
594596
} else {
595597
if (isRecord(inputs) && "$event" in inputs) {
596598
throw new Error(

packages/runner/src/runtime.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
isLegacyCellLink,
3131
isLink,
3232
isNormalizedFullLink,
33+
type NormalizedFullLink,
3334
type NormalizedLink,
3435
parseLink,
3536
} from "./link-utils.ts";
@@ -180,8 +181,8 @@ export interface IScheduler {
180181
unschedule(action: Action): void;
181182
onConsole(fn: ConsoleHandler): void;
182183
onError(fn: ErrorHandler): void;
183-
queueEvent(eventRef: LegacyDocCellLink, event: any): void;
184-
addEventHandler(handler: EventHandler, ref: LegacyDocCellLink): Cancel;
184+
queueEvent(eventRef: NormalizedFullLink, event: any): void;
185+
addEventHandler(handler: EventHandler, ref: NormalizedFullLink): Cancel;
185186
runningPromise: Promise<unknown> | undefined;
186187
}
187188

packages/runner/src/scheduler.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import type {
1616
IScheduler,
1717
MemorySpace,
1818
} from "./runtime.ts";
19+
import {
20+
areNormalizedLinksSame,
21+
type NormalizedFullLink,
22+
} from "./link-utils.ts";
1923

2024
// Re-export types that tests expect from scheduler
2125
export type { ErrorWithContext };
@@ -39,7 +43,7 @@ const MAX_ITERATIONS_PER_RUN = 100;
3943
export class Scheduler implements IScheduler {
4044
private pending = new Set<Action>();
4145
private eventQueue: (() => any)[] = [];
42-
private eventHandlers: [LegacyDocCellLink, EventHandler][] = [];
46+
private eventHandlers: [NormalizedFullLink, EventHandler][] = [];
4347
private dirty = new Set<DocImpl<any>>();
4448
private dependencies = new WeakMap<Action, ReactivityLog>();
4549
private cancels = new WeakMap<Action, Cancel[]>();
@@ -179,20 +183,16 @@ export class Scheduler implements IScheduler {
179183
});
180184
}
181185

182-
queueEvent(eventRef: LegacyDocCellLink, event: any): void {
183-
for (const [ref, handler] of this.eventHandlers) {
184-
if (
185-
ref.cell === eventRef.cell &&
186-
ref.path.length === eventRef.path.length &&
187-
ref.path.every((p, i) => p === eventRef.path[i])
188-
) {
186+
queueEvent(eventLink: NormalizedFullLink, event: any): void {
187+
for (const [link, handler] of this.eventHandlers) {
188+
if (areNormalizedLinksSame(link, eventLink)) {
189189
this.queueExecution();
190190
this.eventQueue.push(() => handler(event));
191191
}
192192
}
193193
}
194194

195-
addEventHandler(handler: EventHandler, ref: LegacyDocCellLink): Cancel {
195+
addEventHandler(handler: EventHandler, ref: NormalizedFullLink): Cancel {
196196
this.eventHandlers.push([ref, handler]);
197197
return () => {
198198
const index = this.eventHandlers.findIndex(([r, h]) =>

0 commit comments

Comments
 (0)