Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
878c983
fix: incorporate cross space transaction API
Gozala Jun 23, 2025
f67cc95
chore: update naming to match sigil spec
Gozala Jun 23, 2025
e2b5bd4
feat: first draft of the transactor implementation
Gozala Jun 25, 2025
ec3bc6c
Merge remote-tracking branch 'origin/main' into feat/memory-transaction
Gozala Jun 26, 2025
a84f1ec
chore: revert unintended changes
Gozala Jun 26, 2025
ae6e80c
fix: type errors
Gozala Jun 26, 2025
2319790
fix: potential race condition
Gozala Jun 26, 2025
f693277
chore: implement transactions that capture invariants
Gozala Jul 1, 2025
2d9f2bb
chore: add some invariant tests
Gozala Jul 1, 2025
6331515
fix: cleanup tests
Gozala Jul 1, 2025
f2b6646
chore: add more write invariant tests
Gozala Jul 1, 2025
449132c
chore: add read invariant tests
Gozala Jul 1, 2025
14ffd8f
chore: add tests for failures and deletes
Gozala Jul 1, 2025
e01319b
feat: prune redundant read invariants
Gozala Jul 1, 2025
3385db7
fix: add more tests and fix edge cases
Gozala Jul 1, 2025
bee5e36
chore: add some tests for transaction journal
Gozala Jul 1, 2025
5209be4
chore: add bunch more tests
Gozala Jul 1, 2025
46a1ea7
fix: refactor chronicle code into separate module
Gozala Jul 1, 2025
ca8d24f
chore: improve not found errors
Gozala Jul 2, 2025
0601bf0
chore: refactor more code
Gozala Jul 2, 2025
69d65a3
chore: more refactor & cleanup
Gozala Jul 2, 2025
c8d5ce7
fix: detect inconsistencies on reads
Gozala Jul 2, 2025
63224fc
fix: writes detect inconsistencies with previous writes
Gozala Jul 2, 2025
6421882
fix: refactor rest of the code
Gozala Jul 3, 2025
983e60e
chore: remove redundant imports
Gozala Jul 3, 2025
34b58b6
fix: add activity tracking
Gozala Jul 3, 2025
15905d6
fix: write method of the transaction
Gozala Jul 3, 2025
c8703b3
chore: add test to ensure inconsistency is detected on commit
Gozala Jul 3, 2025
82767c6
Merge remote-tracking branch 'origin/main' into feat/memory-transaction
Gozala Jul 3, 2025
cb851bf
chore: remove redundunt comments
Gozala Jul 3, 2025
ff98043
chore: revert unintended changes
Gozala Jul 3, 2025
0efd152
chore: delete notes
Gozala Jul 3, 2025
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
10 changes: 6 additions & 4 deletions packages/runner/src/builder/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ declare module "@commontools/api" {
connect(node: NodeRef): void;
export(): {
cell: OpaqueRef<any>;
path: PropertyKey[];
path: readonly PropertyKey[];
value?: Opaque<T>;
defaultValue?: Opaque<T>;
nodes: Set<NodeRef>;
Expand All @@ -96,7 +96,7 @@ declare module "@commontools/api" {
};
unsafe_bindToRecipeAndPath(
recipe: Recipe,
path: PropertyKey[],
path: readonly PropertyKey[],
): void;
unsafe_getExternal(): OpaqueRef<T>;
map<S>(
Expand Down Expand Up @@ -181,7 +181,9 @@ declare module "@commontools/api" {
nodes: Node[];
[unsafe_originalRecipe]?: Recipe;
[unsafe_parentRecipe]?: Recipe;
[unsafe_materializeFactory]?: (log: any) => (path: PropertyKey[]) => any;
[unsafe_materializeFactory]?: (
log: any,
) => (path: readonly PropertyKey[]) => any;
}
}

Expand Down Expand Up @@ -228,7 +230,7 @@ export function isShadowRef(value: unknown): value is ShadowRef {

export type UnsafeBinding = {
recipe: Recipe;
materialize: (path: PropertyKey[]) => any;
materialize: (path: readonly PropertyKey[]) => any;
parent?: UnsafeBinding;
};

Expand Down
26 changes: 19 additions & 7 deletions packages/runner/src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
type NormalizedFullLink,
parseNormalizedFullLinktoLegacyDocCellLink,
} from "./link-utils.ts";
import { type IStorageTransaction } from "./storage/interface.ts";
import type { IExtendedStorageTransaction as IStorageTransaction } from "./storage/interface.ts";

/**
* This is the regular Cell interface, generated by DocImpl.asCell().
Expand Down Expand Up @@ -174,7 +174,7 @@ declare module "@commontools/api" {
): Cell<T>;
withLog(log: ReactivityLog): Cell<T>;
sink(callback: (value: T) => Cancel | undefined | void): Cancel;
getAsQueryResult<Path extends PropertyKey[]>(
getAsQueryResult<Path extends readonly PropertyKey[]>(
path?: Path,
log?: ReactivityLog,
): QueryResult<DeepKeyLookup<T, Path>>;
Expand Down Expand Up @@ -231,7 +231,7 @@ declare module "@commontools/api" {
space: MemorySpace;
entityId: EntityId;
sourceURI: URI;
path: PropertyKey[];
path: readonly PropertyKey[];
[isCellMarker]: true;
copyTrap: boolean;
}
Expand Down Expand Up @@ -520,7 +520,13 @@ function createRegularCell<T>(
getAsQueryResult: (subPath: PropertyKey[] = [], newLog?: ReactivityLog) =>
createQueryResultProxy(doc, [...path, ...subPath], newLog ?? log),
getAsLegacyCellLink: (): LegacyDocCellLink => {
return { space: doc.space, cell: doc, path, schema, rootSchema };
return {
space: doc.space,
cell: doc,
path: path as string[],
schema,
rootSchema,
};
},
getAsNormalizedFullLink: () => link,
getAsLink: (
Expand Down Expand Up @@ -572,7 +578,13 @@ function createRegularCell<T>(
return self.get();
},
get cellLink(): LegacyDocCellLink {
return { space: doc.space, cell: doc, path, schema, rootSchema };
return {
space: doc.space,
cell: doc,
path: path as string[],
schema,
rootSchema,
};
},
get space(): MemorySpace {
return doc.space;
Expand All @@ -583,7 +595,7 @@ function createRegularCell<T>(
get sourceURI(): URI {
return toURI(doc.entityId);
},
get path(): PropertyKey[] {
get path(): readonly PropertyKey[] {
return path;
},
[isCellMarker]: true,
Expand Down Expand Up @@ -664,7 +676,7 @@ function subscribeToReferencedDocs<T>(
*/
function createSigilLink(
doc: DocImpl<any>,
path: PropertyKey[],
path: readonly PropertyKey[],
schema?: JSONSchema,
rootSchema?: JSONSchema,
options: {
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/data-updating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
// if there was no change.
export function setNestedValue<T>(
doc: DocImpl<T>,
path: PropertyKey[],
path: readonly PropertyKey[],
value: unknown,
log?: ReactivityLog,
): boolean {
Expand Down
26 changes: 17 additions & 9 deletions packages/runner/src/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export type DocImpl<T> = {
* @param path - Path to follow.
* @returns Value.
*/
getAtPath<Path extends PropertyKey[]>(path: Path): DeepKeyLookup<T, Path>;
getAtPath<Path extends readonly PropertyKey[]>(
path: Path,
): DeepKeyLookup<T, Path>;

/**
* Get as value proxy, following query (i.e. aliases) and cell references.
Expand Down Expand Up @@ -75,7 +77,7 @@ export type DocImpl<T> = {
* @param log - Reactivity log.
* @returns Simple cell.
*/
asCell<Q = T, Path extends PropertyKey[] = []>(
asCell<Q = T, Path extends readonly PropertyKey[] = []>(
path?: Path,
log?: ReactivityLog,
schema?: JSONSchema,
Expand Down Expand Up @@ -117,7 +119,7 @@ export type DocImpl<T> = {
* @returns Whether the value changed.
*/
setAtPath(
path: PropertyKey[],
path: readonly PropertyKey[],
newValue: any,
log?: ReactivityLog,
schema?: JSONSchema,
Expand All @@ -130,7 +132,7 @@ export type DocImpl<T> = {
* @returns Cancel function.
*/
updates(
callback: (value: T, path: PropertyKey[], labels?: Labels) => void,
callback: (value: T, path: readonly PropertyKey[], labels?: Labels) => void,
): Cancel;

/**
Expand Down Expand Up @@ -233,7 +235,8 @@ export type DocImpl<T> = {
copyTrap: boolean;
};

export type DeepKeyLookup<T, Path extends PropertyKey[]> = Path extends [] ? T
export type DeepKeyLookup<T, Path extends readonly PropertyKey[]> = Path extends
[] ? T
: Path extends [infer First, ...infer Rest]
? First extends keyof T
? Rest extends PropertyKey[] ? DeepKeyLookup<T[First], Rest>
Expand Down Expand Up @@ -293,7 +296,7 @@ export function createDoc<T>(
callbacks.add(callback);
return () => callbacks.delete(callback);
},
getAtPath: (path: PropertyKey[]) => getValueAtPath(value, path),
getAtPath: (path: readonly PropertyKey[]) => getValueAtPath(value, path),
setAtPath: (
path: PropertyKey[],
newValue: any,
Expand Down Expand Up @@ -346,7 +349,9 @@ export function createDoc<T>(
return value as T;
},
get "/"(): string {
return typeof entityId.toJSON === "function" ? entityId.toJSON()["/"] : (entityId["/"] as string);
return typeof entityId.toJSON === "function"
? entityId.toJSON()["/"]
: (entityId["/"] as string);
},
get entityId(): EntityId {
return entityId;
Expand Down Expand Up @@ -399,15 +404,18 @@ export function createDoc<T>(

const docLinkToOpaqueRef = new WeakMap<
Frame,
WeakMap<DocImpl<any>, { path: PropertyKey[]; opaqueRef: OpaqueRef<any> }[]>
WeakMap<
DocImpl<any>,
{ path: readonly PropertyKey[]; opaqueRef: OpaqueRef<any> }[]
>
>();

// Creates aliases to value, used in recipes to refer to this specific cell. We
// have to memoize these, as conversion happens at multiple places when
// creaeting the recipe.
export function makeOpaqueRef(
doc: DocImpl<any>,
path: PropertyKey[],
path: readonly PropertyKey[],
): OpaqueRef<any> {
const frame = getTopFrame();
if (!frame) throw new Error("No frame");
Expand Down
8 changes: 4 additions & 4 deletions packages/runner/src/link-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ export function createVisits(): Visits {
*/
function createPathCacheKey<T>(
doc: DocImpl<T>,
path: PropertyKey[],
path: readonly PropertyKey[],
aliases: boolean = false,
): string {
return JSON.stringify([doc.space, doc.toJSON(), path, aliases]);
}

export function resolveLinkToValue<T>(
doc: DocImpl<T>,
path: PropertyKey[],
path: readonly PropertyKey[],
log?: ReactivityLog,
schema?: JSONSchema,
rootSchema?: JSONSchema,
Expand All @@ -65,7 +65,7 @@ export function resolveLinkToValue<T>(

export function resolveLinkToWriteRedirect<T>(
doc: DocImpl<T>,
path: PropertyKey[],
path: readonly PropertyKey[],
log?: ReactivityLog,
schema?: JSONSchema,
rootSchema?: JSONSchema,
Expand All @@ -85,7 +85,7 @@ export function resolveLinks(

function resolvePath<T>(
doc: DocImpl<T>,
path: PropertyKey[],
path: readonly PropertyKey[],
log?: ReactivityLog,
schema?: JSONSchema,
rootSchema?: JSONSchema,
Expand Down
15 changes: 9 additions & 6 deletions packages/runner/src/link-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ import {
isQueryResultForDereferencing,
QueryResultInternals,
} from "./query-result-proxy.ts";
import { type IMemoryAddress } from "./storage/interface.ts";
import type {
IMemorySpaceAddress,
MemoryAddressPathComponent,
} from "./storage/interface.ts";

/**
* Normalized link structure returned by parsers
*/
export type NormalizedLink = {
id?: URI; // URI format with "of:" prefix
path: string[];
path: readonly MemoryAddressPathComponent[];
space?: MemorySpace;
type?: string; // Default is "application/json"
schema?: JSONSchema;
Expand All @@ -41,7 +44,7 @@ export type NormalizedLink = {
*
* Any such link can be used as a memory address.
*/
export type NormalizedFullLink = NormalizedLink & IMemoryAddress;
export type NormalizedFullLink = NormalizedLink & IMemorySpaceAddress;

/**
* A type reflecting all possible link formats, including cells themselves.
Expand Down Expand Up @@ -449,7 +452,7 @@ function parseToLegacyCellLinkWithMaybeACell(

return {
cell: cellValue,
path: link.path ?? [],
path: link.path as string[] ?? [],
space: link.space,
schema: link.schema,
rootSchema: link.rootSchema,
Expand All @@ -471,7 +474,7 @@ export function parseNormalizedFullLinktoLegacyDocCellLink(
): LegacyDocCellLink {
return {
cell: runtime.getCellFromLink(link).getDoc(),
path: link.path,
path: link.path as string[],
} satisfies LegacyDocCellLink;
}

Expand Down Expand Up @@ -519,7 +522,7 @@ export function createSigilLinkFromParsedLink(
const sigilLink: SigilLink = {
"/": {
[LINK_V1_TAG]: {
path: link.path,
path: link.path as string[],
schema: link.schema,
rootSchema: link.rootSchema,
},
Expand Down
7 changes: 5 additions & 2 deletions packages/runner/src/path-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function setValueAtPath(
return true;
}

export function getValueAtPath(obj: any, path: PropertyKey[]): any {
export function getValueAtPath(obj: any, path: readonly PropertyKey[]): any {
let current = obj;
for (const key of path) {
if (current === undefined || current === null) return undefined;
Expand Down Expand Up @@ -65,7 +65,10 @@ export const deepEqual = (a: any, b: any): boolean => {
return a !== a && b !== b; // NaN check
};

export function arrayEqual(a?: PropertyKey[], b?: PropertyKey[]): boolean {
export function arrayEqual(
a?: readonly PropertyKey[],
b?: readonly PropertyKey[],
): boolean {
if (!a || !b) return a === b;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
Expand Down
9 changes: 5 additions & 4 deletions packages/runner/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,9 @@ export class Runner implements IRunner {

// [unsafe closures:] For recipes from closures, add a materialize factory
if (recipe[unsafe_originalRecipe]) {
recipe[unsafe_materializeFactory] = (log: any) => (path: PropertyKey[]) =>
processCell.getAsQueryResult(path, log);
recipe[unsafe_materializeFactory] =
(log: any) => (path: readonly PropertyKey[]) =>
processCell.getAsQueryResult(path, log);
}

for (const node of recipe.nodes) {
Expand Down Expand Up @@ -551,7 +552,7 @@ export class Runner implements IRunner {

const frame = pushFrameFromCause(cause, {
recipe,
materialize: (path: PropertyKey[]) =>
materialize: (path: readonly PropertyKey[]) =>
processCell.getAsQueryResult(path),
});

Expand Down Expand Up @@ -618,7 +619,7 @@ export class Runner implements IRunner {
{ inputs, outputs, fn: fn.toString() },
{
recipe,
materialize: (path: PropertyKey[]) =>
materialize: (path: readonly PropertyKey[]) =>
processCell.getAsQueryResult(path, log),
} satisfies UnsafeBinding,
);
Expand Down
8 changes: 4 additions & 4 deletions packages/runner/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import type { RecipeEnvironment } from "./builder/env.ts";
import { ContextualFlowControl } from "./cfc.ts";
import { setRecipeEnvironment } from "./builder/env.ts";
import type {
IExtendedStorageTransaction,
IStorageManager,
IStorageProvider,
IStorageTransaction,
MemorySpace,
} from "./storage/interface.ts";
import { type Cell } from "./cell.ts";
Expand All @@ -36,9 +36,9 @@ import {
} from "./link-utils.ts";

export type {
IExtendedStorageTransaction,
IStorageManager,
IStorageProvider,
IStorageTransaction,
MemorySpace,
};

Expand Down Expand Up @@ -92,7 +92,7 @@ export interface IRuntime {
dispose(): Promise<void>;

// Storage transaction method
edit(): IStorageTransaction;
edit(): IExtendedStorageTransaction;

// Cell factory methods
getCell<T>(
Expand Down Expand Up @@ -416,7 +416,7 @@ export class Runtime implements IRuntime {
* locally replicated memory spaces. Transaction allows reading from many
* multiple spaces but writing only to one space.
*/
edit(): IStorageTransaction {
edit(): IExtendedStorageTransaction {
return new StorageTransaction(this);
}

Expand Down
Loading