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
23 changes: 20 additions & 3 deletions packages/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,8 +503,10 @@ type OpaqueRefInner<T> = [T] extends
: [T] extends [Array<infer U>] ? Array<OpaqueRef<U>>
: [T] extends [AnyBrandedCell<any>] ? T
: [T] extends [object] ? { [K in keyof T]: OpaqueRef<T[K]> }
// For nullable types (T | null | undefined), extract and map the non-null object/array part
// For nullable types (T | null | undefined), extract and map the non-null part
: [NonNullable<T>] extends [never] ? T
// Handle nullable branded cells (e.g., (OpaqueCell<X> & X) | undefined) - don't wrap
: [NonNullable<T>] extends [AnyBrandedCell<any>] ? T
: [NonNullable<T>] extends [Array<infer U>] ? Array<OpaqueRef<U>>
: [NonNullable<T>] extends [object]
? { [K in keyof NonNullable<T>]: OpaqueRef<NonNullable<T>[K]> }
Expand Down Expand Up @@ -655,7 +657,7 @@ export type toJSON = {
};

export type Handler<T = any, R = any> = Module & {
with: (inputs: Opaque<StripCell<T>>) => OpaqueRef<R>;
with: (inputs: Opaque<StripCell<T>>) => Stream<R>;
};

export type NodeFactory<T, R> =
Expand All @@ -674,7 +676,7 @@ export type ModuleFactory<T, R> =
& toJSON;

export type HandlerFactory<T, R> =
& ((inputs: Opaque<StripCell<T>>) => OpaqueRef<R>)
& ((inputs: Opaque<StripCell<T>>) => Stream<R>)
& Handler<T, R>
& toJSON;

Expand Down Expand Up @@ -1139,6 +1141,20 @@ export type HandlerFunction = {
): ModuleFactory<StripCell<T>, Stream<StripCell<E>>>;
};

/**
* ActionFunction creates a handler that doesn't use the state parameter.
*
* This is to handler as computed is to lift/derive:
* - User writes: action((e) => count.set(e.data))
* - Transformer rewrites to: handler((e, { count }) => count.set(e.data))({ count })
*
* The transformer extracts closures and makes them explicit, just like how
* computed(() => expr) becomes derive({}, () => expr) with closure extraction.
*/
export type ActionFunction = {
<T>(fn: (event: T) => void): HandlerFactory<T, void>;
};

/**
* DeriveFunction creates a reactive computation that transforms input values.
*
Expand Down Expand Up @@ -1354,6 +1370,7 @@ export declare const recipe: RecipeFunction;
export declare const patternTool: PatternToolFunction;
export declare const lift: LiftFunction;
export declare const handler: HandlerFunction;
export declare const action: ActionFunction;
/** @deprecated Use compute() instead */
export declare const derive: DeriveFunction;
export declare const computed: ComputedFunction;
Expand Down
29 changes: 29 additions & 0 deletions packages/memory/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,35 @@ export const querySchema = async (session: Session, query: SchemaQuery) => {
});
};

/**
* Internal variant of querySchema that also returns the schemaTracker.
* Used by provider.ts for incremental subscription updates.
*/
export const querySchemaWithTracker = async (
session: Session,
query: SchemaQuery,
) => {
return await traceAsync("memory.querySchemaWithTracker", async (span) => {
addMemoryAttributes(span, {
operation: "querySchemaWithTracker",
space: query.sub,
});

const { ok: space, error } = await mount(session, query.sub);
if (error) {
span.setAttribute("mount.status", "error");
return { error };
}

span.setAttribute("mount.status", "success");
// Cast is safe: the Space class implements both SpaceSession and Session<Subject>
return Space.querySchemaWithTracker(
space as unknown as Space.Session<typeof query.sub>,
query,
);
});
};

export const transact = async (session: Session, transaction: Transaction) => {
return await traceAsync("memory.transact", async (span) => {
addMemoryAttributes(span, {
Expand Down
Loading
Loading