Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
more renaming of cell to doc
  • Loading branch information
seefeldb committed Mar 4, 2025
commit 66a9baff979c895f88bb7b1b213127f2e920e46a
4 changes: 2 additions & 2 deletions typescript/packages/common-runner/src/builtins/fetch-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { refer } from "merkle-reference";
*
* Returns the fetched result as `result`. `pending` is true while a request is pending.
*
* @param url - A cell containing the URL to fetch data from.
* @param url - A doc containing the URL to fetch data from.
* @param mode - The mode to use for fetching data. Either `text` or `json`
* default to `json` results.
* @returns { pending: boolean, result: any, error: any } - As individual cells, representing `pending` state, final `result`, and any `error`.
* @returns { pending: boolean, result: any, error: any } - As individual docs, representing `pending` state, final `result`, and any `error`.
*/
export function fetchData(
inputsCell: DocImpl<{
Expand Down
14 changes: 7 additions & 7 deletions typescript/packages/common-runner/src/builtins/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import { type ReactivityLog } from "../scheduler.ts";
* Returns the complete result as `result` and the incremental result as
* `partial`. `pending` is true while a request is pending.
*
* @param prompt - A cell to store the prompt message - if you only have a single message
* @param prompt - A doc to store the prompt message - if you only have a single message
* @param messages - list of messages to send to the LLM. - alternating user and assistant messages.
* - if you end with an assistant message, the LLM will continue from there.
* - if both prompt and messages are empty, no LLM call will be made,
* result and partial will be undefined.
* @param model - A cell to store the model to use.
* @param system - A cell to store the system message.
* @param stop - A cell to store (optional) stop sequence.
* @param max_tokens - A cell to store the maximum number of tokens to generate.
* @param model - A doc to store the model to use.
* @param system - A doc to store the system message.
* @param stop - A doc to store (optional) stop sequence.
* @param max_tokens - A doc to store the maximum number of tokens to generate.
*
* @returns { pending: boolean, result?: string, partial?: string } - As individual
* cells, representing `pending` state, final `result` and incrementally
* docs, representing `pending` state, final `result` and incrementally
* updating `partial` result.
*/
export function llm(
Expand Down Expand Up @@ -114,7 +114,7 @@ export function llm(

// Return if the same request is being made again, either concurrently (same
// as previousCallHash) or when rehydrated from storage (same as the
// contents of the requestHash cell).
// contents of the requestHash doc).
if (hash === previousCallHash || hash === requestHash.get()) return;
previousCallHash = hash;

Expand Down
18 changes: 9 additions & 9 deletions typescript/packages/common-runner/src/builtins/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ import { type AddCancel } from "../cancel.ts";
* The goal is to keep the output array current without recomputing too much.
*
* Approach:
* 1. Create a cell to store the result.
* 2. Create a handler to update the result cell when the input cell changes.
* 3. Create a handler to update the result cell when the op cell changes.
* 4. For each value in the input cell, create a handler to update the result
* cell when the value changes.
* 1. Create a doc to store the result.
* 2. Create a handler to update the result doc when the input doc changes.
* 3. Create a handler to update the result doc when the op doc changes.
* 4. For each value in the input doc, create a handler to update the result
* doc when the value changes.
*
* TODO: Optimization depends on javascript objects and not lookslike objects.
* We should make sure updates to arrays don't unnecessarily re-ify objects
* and/or change the comparision here.
*
* @param list - A cell containing an array of values to map over.
* @param list - A doc containing an array of values to map over.
* @param op - A recipe to apply to each value.
* @returns A cell containing the mapped values.
* @returns A doc containing the mapped values.
*/
export function map(
inputsCell: DocImpl<{
Expand Down Expand Up @@ -72,7 +72,7 @@ export function map(
throw new Error("map currently only supports arrays");
}

// // Hack to get to underlying array that lists cell references, etc.
// Hack to get to underlying array that lists doc links, etc.
const listRef = getDocLinkOrThrow(list);

// Same for op, but here it's so that the proxy doesn't follow the aliases
Expand Down Expand Up @@ -106,7 +106,7 @@ export function map(
// TODO(seefeld): Have `run` return cancel, once we make resultCell required
addCancel(cancels.get(resultCell));

// Send the result value to the result cell
// Send the result value to the result doc
result.setAtPath([initializedUpTo], { cell: resultCell, path: [] }, log);

initializedUpTo++;
Expand Down
6 changes: 3 additions & 3 deletions typescript/packages/common-runner/src/builtins/stream-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { type ReactivityLog } from "../scheduler.ts";
*
* Returns the streamed result as `result`. `pending` is true while a request is pending.
*
* @param url - A cell containing the URL to stream data from.
* @returns { pending: boolean, result: any, error: any } - As individual cells, representing `pending` state, streamed `result`, and any `error`.
* @param url - A doc containing the URL to stream data from.
* @returns { pending: boolean, result: any, error: any } - As individual docs, representing `pending` state, streamed `result`, and any `error`.
*/
export function streamData(
inputsCell: DocImpl<{
Expand Down Expand Up @@ -53,7 +53,7 @@ export function streamData(
result.sourceCell = parentDoc;
error.sourceCell = parentDoc;

// Since we'll only write into the cells above, we only have to call this once
// Since we'll only write into the docs above, we only have to call this once
// here, instead of in the action.
sendResult({ pending, result, error });

Expand Down
190 changes: 190 additions & 0 deletions typescript/packages/common-runner/src/cell-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { isOpaqueRef } from "@commontools/builder";
import { type DocImpl, type DocLink, getDoc, isDoc, isDocLink } from "./doc.ts";
import {
getDocLinkOrThrow,
isQueryResultForDereferencing,
} from "./query-result-proxy.ts";
import { isCell } from "./cell.ts";
import { refer } from "merkle-reference";
import { type Space } from "./space.ts";

export type EntityId = {
"/": string | Uint8Array;
toJSON?: () => { "/": string };
};

/**
* Generates an entity ID.
*
* @param source - The source object.
* @param cause - Optional causal source. Otherwise a random n is used.
*/
export const createRef = (
source: any = {},
cause: any = crypto.randomUUID(),
): EntityId => {
const seen = new Set<any>();

// Unwrap query result proxies, replace cells with their ids and remove
// functions and undefined values, since `merkle-reference` doesn't support
// them.
function traverse(obj: any): any {
// Avoid cycles
if (seen.has(obj)) return null;
seen.add(obj);

// Don't traverse into ids.
if (typeof obj === "object" && obj !== null && "/" in obj) return obj;

// If there is a .toJSON method, replace obj with it, then descend.
if (
typeof obj === "object" && obj !== null &&
typeof obj.toJSON === "function"
) {
obj = obj.toJSON() ?? obj;
}

if (isOpaqueRef(obj)) return obj.export().value ?? crypto.randomUUID();

if (isQueryResultForDereferencing(obj)) {
// It'll traverse this and call .toJSON on the cell in the reference.
obj = getDocLinkOrThrow(obj);
}

// If referencing other cells, return their ids (or random as fallback).
if (isDoc(obj) || isCell(obj)) return obj.entityId ?? crypto.randomUUID();
else if (Array.isArray(obj)) return obj.map(traverse);
else if (typeof obj === "object" && obj !== null) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, traverse(value)]),
);
} else if (typeof obj === "function") return obj.toString();
else if (obj === undefined) return null;
else return obj;
}

return refer(traverse({ ...source, causal: cause }));
};

/**
* Extracts an entity ID from a cell or cell representation. Creates a stable
* derivative entity ID for path references.
*
* @param value - The value to extract the entity ID from.
* @returns The entity ID, or undefined if the value is not a cell.
*/
export const getEntityId = (value: any): { "/": string } | undefined => {
if (typeof value === "string") {
return value.startsWith("{") ? JSON.parse(value) : { "/": value };
}
if (typeof value === "object" && value !== null && "/" in value) {
return JSON.parse(JSON.stringify(value));
}

let ref: DocLink | undefined = undefined;

if (isQueryResultForDereferencing(value)) ref = getDocLinkOrThrow(value);
else if (isDocLink(value)) ref = value;
else if (isCell(value)) ref = value.getAsDocLink();
else if (isDoc(value)) ref = { cell: value, path: [] };

if (!ref?.cell.entityId) return undefined;

if (ref.path.length > 0) {
return JSON.parse(
JSON.stringify(createRef({ path: ref.path }, ref.cell.entityId)),
);
} else return JSON.parse(JSON.stringify(ref.cell.entityId));
};

/**
* A map that holds weak references to its values per space.
*/
class SpaceAwareCleanableMap<T extends object> {
private maps = new Map<Space, CleanableMap<T>>();

set(space: Space, key: string, value: T) {
let map = this.maps.get(space);
if (!map) {
map = new CleanableMap<T>();
this.maps.set(space, map);
}
map.set(key, value);
}

get(space: Space, key: string): T | undefined {
return this.maps.get(space)?.get(key);
}
}

const entityIdToDocMap = new SpaceAwareCleanableMap<DocImpl<any>>();

/**
* A map that holds weak references to its values. Triggers a cleanup of the map
* when any item was garbage collected, so that the weak references themselves
* can be garbage collected.
*/
class CleanableMap<T extends object> {
private map = new Map<string, WeakRef<T>>();
private cleanupScheduled = false;

set(key: string, value: T) {
this.map.set(key, new WeakRef(value));
}

get(key: string): T | undefined {
const ref = this.map.get(key);
if (ref) {
const value = ref.deref();
if (value === undefined) {
this.scheduleCleanup();
}
return value;
}
return undefined;
}

private scheduleCleanup() {
if (!this.cleanupScheduled) {
this.cleanupScheduled = true;
queueMicrotask(() => {
this.cleanup();
this.cleanupScheduled = false;
});
}
}

private cleanup() {
for (const [key, ref] of this.map) {
if (ref.deref() === undefined) {
this.map.delete(key);
}
}
}
}

export function getDocByEntityId<T = any>(
space: Space,
entityId: EntityId | string,
createIfNotFound = true,
): DocImpl<T> | undefined {
const id = typeof entityId === "string" ? entityId : JSON.stringify(entityId);
let doc = entityIdToDocMap.get(space, id);
if (doc) return doc;
if (!createIfNotFound) return undefined;

doc = getDoc<T>();
if (typeof entityId === "string") entityId = JSON.parse(entityId) as EntityId;
doc.entityId = entityId;
doc.space = space;
setDocByEntityId(space, entityId, doc);
return doc;
}

export const setDocByEntityId = (
space: Space,
entityId: EntityId,
cell: DocImpl<any>,
) => {
entityIdToDocMap.set(space, JSON.stringify(entityId), cell);
};
14 changes: 7 additions & 7 deletions typescript/packages/common-runner/src/query-result-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ function isProxyForArrayValue(value: any): value is ProxyForArrayValue {
}

/**
* Get cell reference or return values as is if not a cell value proxy.
* Get doc link or return values as is if not a cell value proxy.
*
* @param {any} value - The value to get the cell reference or value from.
* @param {any} value - The value to get the doc link or value from.
* @returns {DocLink | any}
*/
export function getDocLinkOrValue(value: any): DocLink {
Expand All @@ -282,9 +282,9 @@ export function getDocLinkOrValue(value: any): DocLink {
}

/**
* Get cell reference or throw if not a cell value proxy.
* Get doc link or throw if not a cell value proxy.
*
* @param {any} value - The value to get the cell reference from.
* @param {any} value - The value to get the doc link from.
* @returns {DocLink}
* @throws {Error} If the value is not a cell value proxy.
*/
Expand All @@ -294,7 +294,7 @@ export function getDocLinkOrThrow(value: any): DocLink {
}

/**
* Check if value is a cell proxy.
* Check if value is a cell value proxy.
*
* @param {any} value - The value to check.
* @returns {boolean}
Expand All @@ -307,8 +307,8 @@ export function isQueryResult(value: any): value is QueryResult<any> {
const getDocLink = Symbol("isQueryResultProxy");

/**
* Check if value is a cell proxy. Return as type that allows dereferencing, but
* not using the proxy.
* Check if value is a cell value proxy. Return as type that allows
* dereferencing, but not using the proxy.
*
* @param {any} value - The value to check.
* @returns {boolean}
Expand Down
Loading