@async/framework 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +81 -14
- package/{framework.d.ts → browser.d.ts} +68 -4
- package/{framework.js → browser.js} +587 -127
- package/browser.min.js +1 -0
- package/{framework.ts → browser.ts} +588 -128
- package/{framework.umd.js → browser.umd.js} +587 -127
- package/browser.umd.min.js +1 -0
- package/package.json +45 -30
- package/server.d.ts +640 -0
- package/src/app.js +110 -11
- package/src/async-signal.js +32 -4
- package/src/browser.js +15 -0
- package/src/component.js +42 -7
- package/src/index.js +5 -2
- package/src/loader.js +42 -10
- package/src/request-context.js +40 -0
- package/src/router.js +15 -2
- package/src/scheduler.js +296 -0
- package/src/server-entry.js +20 -0
- package/src/server-registry.js +97 -0
- package/src/server.js +5 -88
- package/src/signals.js +38 -6
- package/framework.min.js +0 -3820
- package/framework.umd.min.js +0 -3843
package/src/router.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Loader } from "./loader.js";
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
|
+
import { createScheduler } from "./scheduler.js";
|
|
3
4
|
import { createSignalRegistry } from "./signals.js";
|
|
4
5
|
import { applyServerResult } from "./server.js";
|
|
5
6
|
import { createRegistryStore } from "./registry-store.js";
|
|
@@ -121,12 +122,15 @@ export function createRouter({
|
|
|
121
122
|
partials,
|
|
122
123
|
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
123
124
|
routeEndpoint = "/__async/route",
|
|
124
|
-
attributes
|
|
125
|
+
attributes,
|
|
126
|
+
scheduler
|
|
125
127
|
} = {}) {
|
|
126
128
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
127
129
|
const rootNode = root ?? documentRef;
|
|
128
130
|
const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
|
|
129
131
|
const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
|
|
132
|
+
const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
|
|
133
|
+
const ownsScheduler = !scheduler && !loader?.scheduler;
|
|
130
134
|
const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
|
|
131
135
|
const loaderInstance =
|
|
132
136
|
loader ??
|
|
@@ -136,6 +140,7 @@ export function createRouter({
|
|
|
136
140
|
handlers: handlerRegistry,
|
|
137
141
|
server,
|
|
138
142
|
cache,
|
|
143
|
+
scheduler: schedulerInstance,
|
|
139
144
|
attributes: attributeConfig
|
|
140
145
|
});
|
|
141
146
|
const ownsLoader = !loader;
|
|
@@ -155,12 +160,13 @@ export function createRouter({
|
|
|
155
160
|
server,
|
|
156
161
|
cache,
|
|
157
162
|
partials,
|
|
163
|
+
scheduler: schedulerInstance,
|
|
158
164
|
attributes: attributeConfig,
|
|
159
165
|
|
|
160
166
|
start() {
|
|
161
167
|
assertActive();
|
|
162
168
|
loaderInstance.router = api;
|
|
163
|
-
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
|
|
169
|
+
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
|
|
164
170
|
if (ownsLoader) {
|
|
165
171
|
loaderInstance.start();
|
|
166
172
|
}
|
|
@@ -224,6 +230,9 @@ export function createRouter({
|
|
|
224
230
|
cleanup();
|
|
225
231
|
}
|
|
226
232
|
cleanups.clear();
|
|
233
|
+
if (ownsScheduler) {
|
|
234
|
+
schedulerInstance.destroy();
|
|
235
|
+
}
|
|
227
236
|
}
|
|
228
237
|
};
|
|
229
238
|
|
|
@@ -329,13 +338,16 @@ export function createRouter({
|
|
|
329
338
|
loader: loaderInstance,
|
|
330
339
|
router: api,
|
|
331
340
|
cache,
|
|
341
|
+
scheduler: schedulerInstance,
|
|
332
342
|
abort: navigation?.abort
|
|
333
343
|
});
|
|
344
|
+
await schedulerInstance.flush();
|
|
334
345
|
if (!isActiveNavigation(navigation)) {
|
|
335
346
|
return;
|
|
336
347
|
}
|
|
337
348
|
if (result?.html != null && !result.boundary && !result.redirect) {
|
|
338
349
|
loaderInstance.swap(boundary, result.html);
|
|
350
|
+
await schedulerInstance.flush();
|
|
339
351
|
}
|
|
340
352
|
if (result?.redirect || options.history === false) {
|
|
341
353
|
return;
|
|
@@ -380,6 +392,7 @@ export function createRouter({
|
|
|
380
392
|
loader: loaderInstance,
|
|
381
393
|
server,
|
|
382
394
|
cache,
|
|
395
|
+
scheduler: schedulerInstance,
|
|
383
396
|
abort: navigation?.abort
|
|
384
397
|
};
|
|
385
398
|
}
|
package/src/scheduler.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
|
|
2
|
+
|
|
3
|
+
export function createScheduler(options = {}) {
|
|
4
|
+
const phases = [...(options.phases ?? defaultPhases)];
|
|
5
|
+
const queues = new Map(phases.map((phase) => [phase, []]));
|
|
6
|
+
const keyedJobs = new Map();
|
|
7
|
+
const destroyedScopes = new Set();
|
|
8
|
+
const objectScopeIds = new WeakMap();
|
|
9
|
+
const onError = typeof options.onError === "function" ? options.onError : undefined;
|
|
10
|
+
const maxDepth = options.maxDepth ?? 100;
|
|
11
|
+
const strategy = options.strategy ?? "microtask";
|
|
12
|
+
let destroyed = false;
|
|
13
|
+
let flushing = false;
|
|
14
|
+
let scheduled = false;
|
|
15
|
+
let batchDepth = 0;
|
|
16
|
+
let jobCounter = 0;
|
|
17
|
+
let scopeCounter = 0;
|
|
18
|
+
|
|
19
|
+
const api = {
|
|
20
|
+
strategy,
|
|
21
|
+
phases,
|
|
22
|
+
|
|
23
|
+
batch(fn) {
|
|
24
|
+
if (typeof fn !== "function") {
|
|
25
|
+
throw new TypeError("scheduler.batch(fn) requires a function.");
|
|
26
|
+
}
|
|
27
|
+
assertActive();
|
|
28
|
+
batchDepth += 1;
|
|
29
|
+
let asyncBatch = false;
|
|
30
|
+
try {
|
|
31
|
+
const value = fn();
|
|
32
|
+
if (value && typeof value.then === "function") {
|
|
33
|
+
asyncBatch = true;
|
|
34
|
+
return value.finally(() => {
|
|
35
|
+
batchDepth -= 1;
|
|
36
|
+
requestFlush();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
} finally {
|
|
41
|
+
if (!asyncBatch && batchDepth > 0) {
|
|
42
|
+
batchDepth -= 1;
|
|
43
|
+
requestFlush();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
enqueue(phase, fn, options = {}) {
|
|
49
|
+
assertActive();
|
|
50
|
+
assertPhase(phase);
|
|
51
|
+
if (typeof fn !== "function") {
|
|
52
|
+
throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
|
|
53
|
+
}
|
|
54
|
+
const scope = options.scope;
|
|
55
|
+
if (scope !== undefined && destroyedScopes.has(scope)) {
|
|
56
|
+
return noop;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
|
|
60
|
+
if (dedupeKey && keyedJobs.has(dedupeKey)) {
|
|
61
|
+
return keyedJobs.get(dedupeKey).cancel;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const job = {
|
|
65
|
+
id: ++jobCounter,
|
|
66
|
+
phase,
|
|
67
|
+
fn,
|
|
68
|
+
scope,
|
|
69
|
+
boundary: options.boundary,
|
|
70
|
+
key: dedupeKey,
|
|
71
|
+
canceled: false,
|
|
72
|
+
cancel() {
|
|
73
|
+
job.canceled = true;
|
|
74
|
+
if (job.key) {
|
|
75
|
+
keyedJobs.delete(job.key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
queues.get(phase).push(job);
|
|
80
|
+
if (job.key) {
|
|
81
|
+
keyedJobs.set(job.key, job);
|
|
82
|
+
}
|
|
83
|
+
requestFlush();
|
|
84
|
+
return job.cancel;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
afterFlush(fn, options = {}) {
|
|
88
|
+
return api.enqueue("post", fn, options);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async flush() {
|
|
92
|
+
assertActive();
|
|
93
|
+
if (flushing) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
scheduled = false;
|
|
97
|
+
flushing = true;
|
|
98
|
+
let depth = 0;
|
|
99
|
+
try {
|
|
100
|
+
while (hasJobs()) {
|
|
101
|
+
depth += 1;
|
|
102
|
+
if (depth > maxDepth) {
|
|
103
|
+
throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
|
|
104
|
+
}
|
|
105
|
+
for (const phase of phases) {
|
|
106
|
+
await flushPhase(phase);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} finally {
|
|
110
|
+
flushing = false;
|
|
111
|
+
if (hasJobs()) {
|
|
112
|
+
requestFlush();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
async flushScope(scope) {
|
|
118
|
+
assertActive();
|
|
119
|
+
if (flushing) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
scheduled = false;
|
|
123
|
+
flushing = true;
|
|
124
|
+
let depth = 0;
|
|
125
|
+
try {
|
|
126
|
+
while (hasJobsForScope(scope)) {
|
|
127
|
+
depth += 1;
|
|
128
|
+
if (depth > maxDepth) {
|
|
129
|
+
throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
|
|
130
|
+
}
|
|
131
|
+
for (const phase of phases) {
|
|
132
|
+
await flushPhase(phase, scope);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
flushing = false;
|
|
137
|
+
if (hasJobs()) {
|
|
138
|
+
requestFlush();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
cancelScope(scope) {
|
|
144
|
+
if (scope === undefined) {
|
|
145
|
+
return api;
|
|
146
|
+
}
|
|
147
|
+
for (const queue of queues.values()) {
|
|
148
|
+
for (const job of queue) {
|
|
149
|
+
if (job.scope === scope) {
|
|
150
|
+
job.cancel();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return api;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
markScopeDestroyed(scope) {
|
|
158
|
+
if (scope !== undefined) {
|
|
159
|
+
destroyedScopes.add(scope);
|
|
160
|
+
api.cancelScope(scope);
|
|
161
|
+
}
|
|
162
|
+
return api;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
inspect() {
|
|
166
|
+
const counts = {};
|
|
167
|
+
for (const [phase, queue] of queues) {
|
|
168
|
+
counts[phase] = queue.filter((job) => !job.canceled).length;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
strategy,
|
|
172
|
+
phases: [...phases],
|
|
173
|
+
pending: counts,
|
|
174
|
+
scopesDestroyed: destroyedScopes.size,
|
|
175
|
+
flushing,
|
|
176
|
+
scheduled
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
destroy() {
|
|
181
|
+
destroyed = true;
|
|
182
|
+
for (const queue of queues.values()) {
|
|
183
|
+
for (const job of queue) {
|
|
184
|
+
job.cancel();
|
|
185
|
+
}
|
|
186
|
+
queue.length = 0;
|
|
187
|
+
}
|
|
188
|
+
keyedJobs.clear();
|
|
189
|
+
destroyedScopes.clear();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return api;
|
|
194
|
+
|
|
195
|
+
function requestFlush() {
|
|
196
|
+
if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
scheduled = true;
|
|
200
|
+
scheduleMicrotask(() => {
|
|
201
|
+
if (!destroyed) {
|
|
202
|
+
void api.flush();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function flushPhase(phase, scope) {
|
|
208
|
+
const queue = queues.get(phase);
|
|
209
|
+
const remaining = [];
|
|
210
|
+
const runnable = [];
|
|
211
|
+
|
|
212
|
+
for (const job of queue.splice(0)) {
|
|
213
|
+
if (job.canceled) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (scope !== undefined && job.scope !== scope) {
|
|
217
|
+
remaining.push(job);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
runnable.push(job);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
queue.push(...remaining);
|
|
224
|
+
|
|
225
|
+
for (const job of runnable) {
|
|
226
|
+
if (job.key) {
|
|
227
|
+
keyedJobs.delete(job.key);
|
|
228
|
+
}
|
|
229
|
+
if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
await job.fn();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (onError) {
|
|
236
|
+
onError(error, job);
|
|
237
|
+
} else {
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function hasJobs() {
|
|
245
|
+
for (const queue of queues.values()) {
|
|
246
|
+
if (queue.some((job) => !job.canceled)) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function hasJobsForScope(scope) {
|
|
254
|
+
for (const queue of queues.values()) {
|
|
255
|
+
if (queue.some((job) => !job.canceled && job.scope === scope)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function assertActive() {
|
|
263
|
+
if (destroyed) {
|
|
264
|
+
throw new Error("Scheduler has been destroyed.");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assertPhase(phase) {
|
|
269
|
+
if (!queues.has(phase)) {
|
|
270
|
+
throw new Error(`Unknown scheduler phase "${phase}".`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function scopeKey(scope) {
|
|
275
|
+
if (scope === undefined) {
|
|
276
|
+
return "global";
|
|
277
|
+
}
|
|
278
|
+
if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
|
|
279
|
+
if (!objectScopeIds.has(scope)) {
|
|
280
|
+
objectScopeIds.set(scope, `scope:${++scopeCounter}`);
|
|
281
|
+
}
|
|
282
|
+
return objectScopeIds.get(scope);
|
|
283
|
+
}
|
|
284
|
+
return String(scope);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function scheduleMicrotask(fn) {
|
|
289
|
+
if (typeof queueMicrotask === "function") {
|
|
290
|
+
queueMicrotask(fn);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
Promise.resolve().then(fn);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function noop() {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createApp as createBaseApp,
|
|
3
|
+
defineApp as defineBaseApp,
|
|
4
|
+
readSnapshot
|
|
5
|
+
} from "./app.js";
|
|
6
|
+
import { createServerRegistry } from "./server-registry.js";
|
|
7
|
+
|
|
8
|
+
export function createApp(appOrDefinition = Async, options = {}) {
|
|
9
|
+
return createBaseApp(appOrDefinition, {
|
|
10
|
+
serverFactory: createServerRegistry,
|
|
11
|
+
...options
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function defineApp(initial) {
|
|
16
|
+
return defineBaseApp(initial, { createRuntime: createApp });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Async = defineApp();
|
|
20
|
+
export { readSnapshot };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readRequestContext } from "./request-context.js";
|
|
2
|
+
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
3
|
+
import { assertServerId, createServerNamespace, createSignalReader } from "./server.js";
|
|
4
|
+
|
|
5
|
+
export function createServerRegistry(initialMap = {}, options = {}) {
|
|
6
|
+
const registryStore = options.registry ?? createRegistryStore();
|
|
7
|
+
const type = options.type ?? "server";
|
|
8
|
+
const entries = registryStore._map(type);
|
|
9
|
+
const defaults = {};
|
|
10
|
+
|
|
11
|
+
const registry = attachRegistryInspection({
|
|
12
|
+
register(id, fn) {
|
|
13
|
+
assertServerId(id);
|
|
14
|
+
if (typeof fn !== "function") {
|
|
15
|
+
throw new TypeError(`Server function "${id}" must be a function.`);
|
|
16
|
+
}
|
|
17
|
+
if (entries.has(id)) {
|
|
18
|
+
throw new Error(`Server function "${id}" is already registered.`);
|
|
19
|
+
}
|
|
20
|
+
entries.set(id, fn);
|
|
21
|
+
return id;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
registerMany(map) {
|
|
25
|
+
for (const [id, fn] of Object.entries(map ?? {})) {
|
|
26
|
+
registry.register(id, fn);
|
|
27
|
+
}
|
|
28
|
+
return registry;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
unregister(id) {
|
|
32
|
+
assertServerId(id);
|
|
33
|
+
return entries.delete(id);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
resolve(id) {
|
|
37
|
+
assertServerId(id);
|
|
38
|
+
return entries.get(id);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async run(id, args = [], context = {}) {
|
|
42
|
+
assertServerId(id);
|
|
43
|
+
const fn = registry.resolve(id);
|
|
44
|
+
if (!fn) {
|
|
45
|
+
throw new Error(`Server function "${id}" is not registered.`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let runContext;
|
|
49
|
+
const server = createServerNamespace((childId, childArgs, childContext = {}) => {
|
|
50
|
+
return registry.run(childId, childArgs, { ...runContext, ...childContext });
|
|
51
|
+
}, {}, () => runContext);
|
|
52
|
+
|
|
53
|
+
const mergedContext = mergeRequestContext({
|
|
54
|
+
...defaults,
|
|
55
|
+
...context,
|
|
56
|
+
cache: defaults.cache ?? context.cache
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
runContext = {
|
|
60
|
+
...mergedContext,
|
|
61
|
+
id,
|
|
62
|
+
args,
|
|
63
|
+
input: mergedContext.input,
|
|
64
|
+
signals: createSignalReader(mergedContext.signals),
|
|
65
|
+
abort: mergedContext.abort,
|
|
66
|
+
cache: mergedContext.cache,
|
|
67
|
+
server
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return fn.call(runContext, ...args);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
_setContext(context = {}) {
|
|
74
|
+
Object.assign(defaults, context);
|
|
75
|
+
return registry;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
_adoptMany() {
|
|
79
|
+
return registry;
|
|
80
|
+
}
|
|
81
|
+
}, registryStore, type);
|
|
82
|
+
|
|
83
|
+
registry.registerMany(initialMap);
|
|
84
|
+
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function mergeRequestContext(context) {
|
|
88
|
+
const requestContext = readRequestContext(context.requestContext);
|
|
89
|
+
return {
|
|
90
|
+
...context,
|
|
91
|
+
requestContext,
|
|
92
|
+
request: requestContext.request ?? context.request,
|
|
93
|
+
headers: requestContext.headers ?? context.headers,
|
|
94
|
+
cookies: requestContext.cookies ?? context.cookies,
|
|
95
|
+
locals: requestContext.locals ?? context.locals
|
|
96
|
+
};
|
|
97
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,91 +1,7 @@
|
|
|
1
|
-
import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
|
|
2
|
-
|
|
3
1
|
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
|
|
4
2
|
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
|
|
5
3
|
const appliedServerValues = new WeakSet();
|
|
6
4
|
|
|
7
|
-
export function createServerRegistry(initialMap = {}, options = {}) {
|
|
8
|
-
const registryStore = options.registry ?? createRegistryStore();
|
|
9
|
-
const type = options.type ?? "server";
|
|
10
|
-
const entries = registryStore._map(type);
|
|
11
|
-
const defaults = {};
|
|
12
|
-
|
|
13
|
-
const registry = attachRegistryInspection({
|
|
14
|
-
register(id, fn) {
|
|
15
|
-
assertServerId(id);
|
|
16
|
-
if (typeof fn !== "function") {
|
|
17
|
-
throw new TypeError(`Server function "${id}" must be a function.`);
|
|
18
|
-
}
|
|
19
|
-
if (entries.has(id)) {
|
|
20
|
-
throw new Error(`Server function "${id}" is already registered.`);
|
|
21
|
-
}
|
|
22
|
-
entries.set(id, fn);
|
|
23
|
-
return id;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
registerMany(map) {
|
|
27
|
-
for (const [id, fn] of Object.entries(map ?? {})) {
|
|
28
|
-
registry.register(id, fn);
|
|
29
|
-
}
|
|
30
|
-
return registry;
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
unregister(id) {
|
|
34
|
-
assertServerId(id);
|
|
35
|
-
return entries.delete(id);
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
resolve(id) {
|
|
39
|
-
assertServerId(id);
|
|
40
|
-
return entries.get(id);
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
async run(id, args = [], context = {}) {
|
|
44
|
-
assertServerId(id);
|
|
45
|
-
const fn = registry.resolve(id);
|
|
46
|
-
if (!fn) {
|
|
47
|
-
throw new Error(`Server function "${id}" is not registered.`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let runContext;
|
|
51
|
-
const server = createServerNamespace((childId, childArgs, childContext = {}) => {
|
|
52
|
-
return registry.run(childId, childArgs, { ...runContext, ...childContext });
|
|
53
|
-
}, {}, () => runContext);
|
|
54
|
-
|
|
55
|
-
const mergedContext = {
|
|
56
|
-
...defaults,
|
|
57
|
-
...context,
|
|
58
|
-
cache: defaults.cache ?? context.cache
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
runContext = {
|
|
62
|
-
...mergedContext,
|
|
63
|
-
id,
|
|
64
|
-
args,
|
|
65
|
-
input: mergedContext.input,
|
|
66
|
-
signals: createSignalReader(mergedContext.signals),
|
|
67
|
-
abort: mergedContext.abort,
|
|
68
|
-
cache: mergedContext.cache,
|
|
69
|
-
server
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return fn.call(runContext, ...args);
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
_setContext(context = {}) {
|
|
76
|
-
Object.assign(defaults, context);
|
|
77
|
-
return registry;
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
_adoptMany() {
|
|
81
|
-
return registry;
|
|
82
|
-
}
|
|
83
|
-
}, registryStore, type);
|
|
84
|
-
|
|
85
|
-
registry.registerMany(initialMap);
|
|
86
|
-
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
5
|
export function createServerProxy({
|
|
90
6
|
endpoint = "/__async/server",
|
|
91
7
|
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
@@ -93,13 +9,14 @@ export function createServerProxy({
|
|
|
93
9
|
loader,
|
|
94
10
|
router,
|
|
95
11
|
cache,
|
|
12
|
+
scheduler,
|
|
96
13
|
headers = {}
|
|
97
14
|
} = {}) {
|
|
98
15
|
if (typeof fetchImpl !== "function") {
|
|
99
16
|
throw new TypeError("createServerProxy(...) requires fetch to be available.");
|
|
100
17
|
}
|
|
101
18
|
|
|
102
|
-
const defaults = { signals, loader, router, cache };
|
|
19
|
+
const defaults = { signals, loader, router, cache, scheduler };
|
|
103
20
|
|
|
104
21
|
async function run(id, args = [], context = {}) {
|
|
105
22
|
assertServerId(id);
|
|
@@ -229,7 +146,7 @@ export function defaultInput(context = {}) {
|
|
|
229
146
|
};
|
|
230
147
|
}
|
|
231
148
|
|
|
232
|
-
function createServerNamespace(run, root = {}, contextProvider = () => ({})) {
|
|
149
|
+
export function createServerNamespace(run, root = {}, contextProvider = () => ({})) {
|
|
233
150
|
const cache = new Map();
|
|
234
151
|
|
|
235
152
|
function namespace(parts) {
|
|
@@ -311,7 +228,7 @@ function readSignal(signals, path) {
|
|
|
311
228
|
return signals.get(path);
|
|
312
229
|
}
|
|
313
230
|
|
|
314
|
-
function createSignalReader(signals) {
|
|
231
|
+
export function createSignalReader(signals) {
|
|
315
232
|
if (!signals || typeof signals.get === "function") {
|
|
316
233
|
return signals;
|
|
317
234
|
}
|
|
@@ -433,7 +350,7 @@ function toError(value) {
|
|
|
433
350
|
return new Error(String(value));
|
|
434
351
|
}
|
|
435
352
|
|
|
436
|
-
function assertServerId(id) {
|
|
353
|
+
export function assertServerId(id) {
|
|
437
354
|
if (typeof id !== "string" || id.length === 0) {
|
|
438
355
|
throw new TypeError("Server function id must be a non-empty string.");
|
|
439
356
|
}
|