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
56 changes: 43 additions & 13 deletions packages/runner/src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type {
IReadOptions,
} from "./storage/interface.ts";
import { fromURI } from "./uri-utils.ts";
import { getEntityId } from "./doc-map.ts";

/**
* This is the regular Cell interface, generated by DocImpl.asCell().
Expand Down Expand Up @@ -355,7 +356,9 @@ class StreamCell<T> implements Stream<T> {
}

getRaw(options?: IReadOptions): any {
return (this.tx?.status().status === "ready" ? this.tx : this.runtime.edit())
return (this.tx?.status().status === "ready"
? this.tx
: this.runtime.edit())
.readValueOrThrow(this.link, options);
}

Expand All @@ -364,7 +367,10 @@ class StreamCell<T> implements Stream<T> {
}

getDoc(): DocImpl<any> {
return this.runtime.documentMap.getDocByEntityId(this.link.space, this.link.id);
return this.runtime.documentMap.getDocByEntityId(
this.link.space,
this.link.id,
);
}

withTx(_tx?: IExtendedStorageTransaction): Stream<T> {
Expand All @@ -374,7 +380,7 @@ class StreamCell<T> implements Stream<T> {

class RegularCell<T> implements Cell<T> {
private readOnlyReason: string | undefined;

constructor(
public readonly runtime: IRuntime,
private readonly link: NormalizedFullLink,
Expand Down Expand Up @@ -433,7 +439,8 @@ class RegularCell<T> implements Cell<T> {
if (this.schema) {
// Check if schema allows objects
const allowsObject = this.schema.type === "object" ||
(Array.isArray(this.schema.type) && this.schema.type.includes("object")) ||
(Array.isArray(this.schema.type) &&
this.schema.type.includes("object")) ||
(this.schema.anyOf &&
this.schema.anyOf.some((s) =>
typeof s === "object" && s.type === "object"
Expand Down Expand Up @@ -571,7 +578,10 @@ class RegularCell<T> implements Cell<T> {
return createQueryResultProxy(
this.runtime,
tx ?? this.tx ?? this.runtime.edit(),
{ ...this.link, path: [...this.path, ...subPath.map((p) => p.toString())] as string[] },
{
...this.link,
path: [...this.path, ...subPath.map((p) => p.toString())] as string[],
},
);
}

Expand Down Expand Up @@ -606,11 +616,16 @@ class RegularCell<T> implements Cell<T> {
}

getDoc(): DocImpl<any> {
return this.runtime.documentMap.getDocByEntityId(this.link.space, this.link.id);
return this.runtime.documentMap.getDocByEntityId(
this.link.space,
this.link.id,
);
}

getRaw(options?: IReadOptions): any {
return (this.tx?.status().status === "ready" ? this.tx : this.runtime.edit())
return (this.tx?.status().status === "ready"
? this.tx
: this.runtime.edit())
.readValueOrThrow(this.link, options);
}

Expand Down Expand Up @@ -643,10 +658,18 @@ class RegularCell<T> implements Cell<T> {
>
| undefined;
getSourceCell(schema?: JSONSchema): Cell<any> | undefined {
const sourceCellId =
let sourceCellId =
(this.tx?.status().status === "ready" ? this.tx : this.runtime.edit())
.readOrThrow({ ...this.link, path: ["source"] });
if (!sourceCellId) return undefined;
.readOrThrow({ ...this.link, path: ["source"] }) as string | undefined;
if (!sourceCellId || typeof sourceCellId !== "string") {
return undefined;
}
if (sourceCellId.startsWith('{"/":')) {
sourceCellId = toURI(JSON.parse(sourceCellId));
}
if (!sourceCellId.startsWith("of:")) {
throw new Error("Source cell ID must start with 'of:'");
}
return createCell(this.runtime, {
space: this.link.space,
path: [],
Expand All @@ -662,12 +685,16 @@ class RegularCell<T> implements Cell<T> {
if (sourceLink.path.length > 0) {
throw new Error("Source cell must have empty path for now");
}
this.tx.writeOrThrow({ ...this.link, path: ["source"] }, sourceLink.id);
this.tx.writeOrThrow(
{ ...this.link, path: ["source"] },
JSON.stringify(getEntityId(sourceLink.id)),
);
}

freeze(reason: string): void {
this.readOnlyReason = reason;
this.runtime.documentMap.getDocByEntityId(this.link.space, this.link.id)?.freeze(reason);
this.runtime.documentMap.getDocByEntityId(this.link.space, this.link.id)
?.freeze(reason);
}

isFrozen(): boolean {
Expand All @@ -678,7 +705,10 @@ class RegularCell<T> implements Cell<T> {
// Keep old format for backward compatibility
return {
cell: {
"/": (this.link.id.startsWith("data:") ? this.link.id : fromURI(this.link.id)),
"/":
(this.link.id.startsWith("data:")
? this.link.id
: fromURI(this.link.id)),
},
path: this.path as (string | number)[],
};
Expand Down
15 changes: 11 additions & 4 deletions packages/runner/src/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,21 @@ export function createDoc<T>(

// Send notification if shim storage manager is available
if (runtime.storage.shim) {
let before = previousValue;
let after = newValue;
for (const key of ["value", ...path.map(String)].reverse()) {
before = { [key]: before };
after = { [key]: after };
}

const change: IMemoryChange = {
address: {
id: toURI(entityId),
type: "application/json",
path: ["value", ...path.map(String)],
},
before: previousValue,
after: newValue,
before: before,
after: after,
};

const notification: ICommitNotification = {
Expand Down Expand Up @@ -413,8 +420,8 @@ export function createDoc<T>(
type: "application/json",
path: ["source"],
},
before: JSON.stringify(sourceCell?.entityId),
after: JSON.stringify(cell?.entityId),
before: { source: JSON.stringify(sourceCell?.entityId) },
after: { source: JSON.stringify(cell?.entityId) },
};

const notification: ICommitNotification = {
Expand Down
8 changes: 1 addition & 7 deletions packages/runner/src/reactive-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,11 @@ export function determineTriggeredActions(

if (startPath.length > 0) {
// If we're starting from a specific path, filter the subscribers to only
// include those that start with that path.
// include those that can be affected by that path.
subscribers = subscribers.map(({ action, paths }) => ({
action,
paths: paths.filter((path) => arraysOverlap(path, startPath)),
})).filter(({ paths }) => paths.length > 0);

// And prepend path to data, so we don't have to special case this.
for (const key of startPath.toReversed()) {
before = { [key]: before } as JSONValue;
after = { [key]: after } as JSONValue;
}
}

// Sort subscribers by last/longest path first.
Expand Down
5 changes: 4 additions & 1 deletion packages/runner/src/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isDeno } from "@commontools/utils/env";
import type {
JSONSchema,
Module,
Expand Down Expand Up @@ -45,7 +46,9 @@ import { Runner } from "./runner.ts";
import { registerBuiltins } from "./builtins/index.ts";
import { StaticCache } from "@commontools/static";

const DEFAULT_USE_REAL_TRANSACTIONS = false;
const DEFAULT_USE_REAL_TRANSACTIONS = isDeno()
? ["1", "true", "on", "yes"].includes(Deno.env.get("USE_REAL_TRANSACTIONS")!)
: false;

export type { IExtendedStorageTransaction, IStorageProvider, MemorySpace };

Expand Down
54 changes: 34 additions & 20 deletions packages/runner/src/storage/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,13 +559,13 @@ export interface ITransactionReader {
* })
* assert(w.ok)
*
* assert(tx.read({ type, id, path:: ['author'] }).ok === undefined)
* assert(tx.read({ type, id, path:: ['author', 'address'] }).error.name === 'NotFoundError')
* assert(tx.read({ type, id, path: ['author'] }).ok === undefined)
* assert(tx.read({ type, id, path: ['author', 'address'] }).error.name === 'NotFoundError')
* // JS specific getters are not supported
* assert(tx.read({ the, of, at: ['content', 'length'] }).ok.is === undefined)
* assert(tx.read({ the, of, at: ['title'] }).ok.is === "Hello world")
* assert(tx.read({ type, id, path: ['content', 'length'] }).ok?.value === undefined)
* assert(tx.read({ type, id, path: ['title'] }).ok?.value === "Hello world")
* // Referencing non-existing facts produces errors
* assert(tx.read({ the: 'bad/mime' , of, at: ['author'] }).error.name === 'NotFoundError')
* assert(tx.read({ type: 'bad/mime', id, path: ['author'] }).error.name === 'NotFoundError')
* ```
*
* @param address - Memory address to read from
Expand Down Expand Up @@ -656,7 +656,10 @@ export type CommitError =

export interface INotFoundError extends Error {
name: "NotFoundError";
source: IAttestation;
address: IMemoryAddress;
path?: MemoryAddressPathComponent[];
from(space: MemorySpace): INotFoundError;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we lost the source: IAttestation and address: IMemoryAddress fields here.
It's not clear if that was intentional or accidental, since the interface was double defined with half here, and half later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, and the implementation in NotFound actually sets those. Added.

}

/**
Expand All @@ -682,13 +685,15 @@ export type ReadError =
| INotFoundError
| InactiveTransactionError
| IInvalidDataURIError
| IUnsupportedMediaTypeError;
| IUnsupportedMediaTypeError
| ITypeMismatchError;

export type WriteError =
| INotFoundError
| IUnsupportedMediaTypeError
| InactiveTransactionError
| IReadOnlyAddressError;
| IReadOnlyAddressError
| ITypeMismatchError;

export type ReaderError = InactiveTransactionError;

Expand All @@ -700,19 +705,6 @@ export type WriterError =
export interface IStorageTransactionComplete extends Error {
name: "StorageTransactionCompleteError";
}
export interface INotFoundError extends Error {
name: "NotFoundError";

/**
* Source in which address could not be resolved.
*/
source: IAttestation;

/**
* Address that we could not resolve.
*/
address: IMemoryAddress;
}

/**
* Represents adddress within the memory space which is like pointer inside the
Expand Down Expand Up @@ -859,6 +851,28 @@ export interface IReadOnlyAddressError extends Error {
from(space: MemorySpace): IReadOnlyAddressError;
}

/**
* Error returned when attempting to access a property on a non-object value.
* This is different from NotFound (document doesn't exist) and Inconsistency
* (state changed). This error indicates a type mismatch that would persist
* even if the transaction were retried.
*/
export interface ITypeMismatchError extends Error {
name: "TypeMismatchError";

/**
* The address being accessed.
*/
address: IMemoryAddress;

/**
* The actual type encountered.
*/
actualType: string;

from(space: MemorySpace): ITypeMismatchError;
}

/**
* Describes either observed or desired state of the memory at a specific
* address.
Expand Down
Loading