@@ -35,17 +35,25 @@ import { type AddCancel, type Cancel, useCancelGroup } from "./cancel.js";
3535import "./builtins/index.ts" ;
3636import { addRecipe , getRecipe , getRecipeId } from "./recipe-map.js" ;
3737import { isCell } from "./cell.js" ;
38+ import { isQueryResultForDereferencing } from "./query-result-proxy.js" ;
39+ import { getDocLinkOrThrow } from "./query-result-proxy.js" ;
3840
3941export const cancels = new WeakMap < DocImpl < any > , Cancel > ( ) ;
4042
4143/**
4244 * Run a recipe.
4345 *
44- * When called with a result cell, it'll read from the cell and update the
45- * instance if appropriate. This can be used to rehydrate an instance .
46+ * resultCell is required and should have an id. processCell is created if not
47+ * already set .
4648 *
47- * When called without a result cell, or the result cell is empty, a new
48- * instance is created.
49+ * If no recipe is provided, the previous one is used, and the recipe is started
50+ * if it isn't already started.
51+ *
52+ * If no argument is provided, the previous one is used, and the recipe is
53+ * started if it isn't already running.
54+ *
55+ * If a new recipe or any argument value is provided, a currently running recipe
56+ * is stopped, the recipe and argument replaced and the recipe restarted.
4957 *
5058 * @param recipeFactory - Function that takes the argument and returns a recipe.
5159 * @param argument - The argument to pass to the recipe. Can be static data
@@ -69,22 +77,6 @@ export function run<T, R = any>(
6977 argument : T ,
7078 resultCell : DocImpl < R > ,
7179) : DocImpl < R > {
72- if ( cancels . has ( resultCell ) ) {
73- // If it's already running and no new recipe or argument are given,
74- // we are just returning the result cell
75- if ( recipeOrModule === undefined && argument === undefined ) {
76- return resultCell ;
77- }
78-
79- // Otherwise stop execution of the old recipe. TODO: Await, but this will
80- // make all this async.
81- stop ( resultCell ) ;
82- }
83-
84- // Keep track of subscriptions to cancel them later
85- const [ cancel , addCancel ] = useCancelGroup ( ) ;
86- cancels . set ( resultCell , cancel ) ;
87-
8880 let processCell : DocImpl < {
8981 [ TYPE ] : string ;
9082 argument ?: T ;
@@ -97,7 +89,7 @@ export function run<T, R = any>(
9789 // TODO: Allow keeping of previous argument but still supply defaults
9890 argument = argument ?? ( processCell . get ( ) ?. argument as T ) ;
9991 } else {
100- processCell = getDoc ( ) ;
92+ processCell = getDoc ( undefined , { cell : resultCell , path : [ ] } , resultCell . space ) as any ;
10193 resultCell . sourceCell = processCell ;
10294 }
10395
@@ -136,19 +128,45 @@ export function run<T, R = any>(
136128 recipe = recipeOrModule as Recipe ;
137129 }
138130
131+ recipeId ??= addRecipe ( recipe ) ;
132+
133+ if ( cancels . has ( resultCell ) ) {
134+ // If it's already running and no new recipe or argument are given,
135+ // we are just returning the result cell
136+ if ( argument === undefined && recipeId === processCell . get ( ) ?. [ TYPE ] ) return resultCell ;
137+
138+ // TODO: If recipe is the same, but argument is different, just update the argument without stopping
139+
140+ // Otherwise stop execution of the old recipe. TODO: Await, but this will
141+ // make all this async.
142+ stop ( resultCell ) ;
143+ }
144+
145+ // Keep track of subscriptions to cancel them later
146+ const [ cancel , addCancel ] = useCancelGroup ( ) ;
147+ cancels . set ( resultCell , cancel ) ;
148+
139149 // Walk the recipe's schema and extract all default values
140150 const defaults = extractDefaultValues ( recipe . argumentSchema ) ;
141151
142152 // If the bindings are a cell or cell reference, convert them to an object
143153 // where each property is a cell reference.
144154 // TODO: If new keys are added after first load, this won't work.
145- if ( isDoc ( argument ) || isDocLink ( argument ) || isCell ( argument ) ) {
155+
156+ if (
157+ isDoc ( argument ) ||
158+ isDocLink ( argument ) ||
159+ isCell ( argument ) ||
160+ isQueryResultForDereferencing ( argument )
161+ ) {
146162 // If it's a cell, turn it into a cell reference
147163 const ref = isDocLink ( argument )
148164 ? argument
149165 : isCell ( argument )
150166 ? argument . getAsDocLink ( )
151- : ( { cell : argument , path : [ ] } satisfies DocLink ) ;
167+ : isQueryResultForDereferencing ( argument )
168+ ? getDocLinkOrThrow ( argument )
169+ : ( { cell : argument , path : [ ] } satisfies DocLink ) ;
152170
153171 // Get value, but just to get the keys. Throw if it isn't an object.
154172 const value = ref . cell . getAsQueryResult ( ref . path ) ;
@@ -166,15 +184,6 @@ export function run<T, R = any>(
166184 }
167185 }
168186
169- // TODO: Add a causal relationship. For example a recipe that only transforms
170- // data from a fixed source could have an idea that depends on that source
171- // alone, as any instance of the recipe will do the same thing. The trouble is
172- // though that we support the recipe to change over time, and such a change
173- // might change this condition and we'd need distinct ideas for different
174- // instances of this recipe again.
175- if ( ! processCell . entityId && resultCell . entityId )
176- processCell . generateEntityId ( { cell : resultCell , path : [ ] } , resultCell . space ) ;
177-
178187 const internal = processCell . get ( ) ?. internal ?? ( recipe . initial as { internal : any } ) ?. internal ;
179188
180189 // Ensure static data is converted to cell references, e.g. for arrays
@@ -184,7 +193,7 @@ export function run<T, R = any>(
184193 if ( defaults ) argument = mergeObjects ( argument , deepCopy ( defaults ) ) ;
185194
186195 processCell . send ( {
187- [ TYPE ] : recipeId ?? addRecipe ( recipe ) ,
196+ [ TYPE ] : recipeId ,
188197 argument,
189198 ...( internal ? { internal : deepCopy ( internal ) } : { } ) ,
190199 resultRef : { cell : resultCell , path : [ ] } ,
0 commit comments