@async/framework 0.11.0 → 0.11.2

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/server.js CHANGED
@@ -187,6 +187,24 @@ const __asyncSignalModule = (() => {
187
187
  };
188
188
  },
189
189
 
190
+ _restore(snapshot = {}) {
191
+ if (!isAsyncSignalSnapshot(snapshot)) {
192
+ return state.set(snapshot);
193
+ }
194
+ if (activeAbort && !activeAbort.aborted) {
195
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
196
+ }
197
+ value = snapshot.value;
198
+ loading = Boolean(snapshot.loading);
199
+ error = snapshot.error ?? null;
200
+ status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
201
+ if (Number.isFinite(snapshot.version)) {
202
+ version = snapshot.version;
203
+ }
204
+ notify();
205
+ return state;
206
+ },
207
+
190
208
  _bindRegistry(nextRegistry, nextId) {
191
209
  registry = nextRegistry;
192
210
  registeredId = nextId;
@@ -272,6 +290,27 @@ const __asyncSignalModule = (() => {
272
290
  return Boolean(value?.[asyncSignalKind]);
273
291
  }
274
292
 
293
+ function isAsyncSignalSnapshot(value) {
294
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
295
+ return false;
296
+ }
297
+ return Object.hasOwn(value, "value")
298
+ && (Object.hasOwn(value, "loading")
299
+ || Object.hasOwn(value, "error")
300
+ || Object.hasOwn(value, "status")
301
+ || Object.hasOwn(value, "version"));
302
+ }
303
+
304
+ function inferStatus({ value, loading, error }) {
305
+ if (loading) {
306
+ return "loading";
307
+ }
308
+ if (error) {
309
+ return "error";
310
+ }
311
+ return value === undefined ? "idle" : "ready";
312
+ }
313
+
275
314
  function attachCancel(signal, controller) {
276
315
  Object.defineProperty(signal, "cancel", {
277
316
  configurable: true,
@@ -1910,12 +1949,16 @@ const __componentModule = (() => {
1910
1949
  });
1911
1950
 
1912
1951
  const output = Component.call(context, props);
1952
+ if (output && typeof output.then === "function") {
1953
+ throw new TypeError(`Component "${componentName(Component)}" returned a Promise. Async components are not supported by synchronous renderComponent(). Use an async partial or handler instead.`);
1954
+ }
1913
1955
  const html = renderScopedTemplate(output);
1914
1956
 
1915
1957
  return {
1916
1958
  html,
1917
1959
  attach(target) {
1918
- for (const hook of attachHooks) {
1960
+ for (let index = 0; index < attachHooks.length; index += 1) {
1961
+ const hook = attachHooks[index];
1919
1962
  runtime.scheduler?.enqueue("lifecycle", () => {
1920
1963
  const cleanup = hook(target);
1921
1964
  if (typeof cleanup === "function") {
@@ -1923,7 +1966,7 @@ const __componentModule = (() => {
1923
1966
  }
1924
1967
  }, {
1925
1968
  scope,
1926
- key: `attach:${attachHooks.indexOf(hook)}`
1969
+ key: `attach:${index}`
1927
1970
  }) ?? runAttachHook(hook, target);
1928
1971
  }
1929
1972
  },
@@ -1931,8 +1974,12 @@ const __componentModule = (() => {
1931
1974
  this.attach(target);
1932
1975
  },
1933
1976
  visible(target, observeVisible) {
1934
- for (const hook of visibleHooks) {
1935
- const cleanup = observeVisible(target, () => {
1977
+ if (visibleHooks.length === 0) {
1978
+ return;
1979
+ }
1980
+ const cleanup = observeVisible(target, () => {
1981
+ for (let index = 0; index < visibleHooks.length; index += 1) {
1982
+ const hook = visibleHooks[index];
1936
1983
  runtime.scheduler?.enqueue("lifecycle", () => {
1937
1984
  const hookCleanup = hook(target);
1938
1985
  if (typeof hookCleanup === "function") {
@@ -1940,12 +1987,12 @@ const __componentModule = (() => {
1940
1987
  }
1941
1988
  }, {
1942
1989
  scope,
1943
- key: `visible:${visibleHooks.indexOf(hook)}`
1990
+ key: `visible:${index}`
1944
1991
  }) ?? runVisibleHook(hook, target);
1945
- });
1946
- if (typeof cleanup === "function") {
1947
- cleanups.push(cleanup);
1948
1992
  }
1993
+ });
1994
+ if (typeof cleanup === "function") {
1995
+ cleanups.push(cleanup);
1949
1996
  }
1950
1997
  },
1951
1998
  cleanup() {
@@ -2201,11 +2248,12 @@ const __serverModule = (() => {
2201
2248
  signal: context.abort
2202
2249
  });
2203
2250
 
2251
+ assertTransportResponse(id, response);
2204
2252
  if (!response.ok) {
2205
2253
  throw new Error(`Server function "${id}" failed with ${response.status}.`);
2206
2254
  }
2207
2255
 
2208
- const result = await readServerResponse(response);
2256
+ const result = await readServerResponse(id, response);
2209
2257
  await applyServerResult(result, runContext);
2210
2258
  return markAppliedServerValue(unwrapServerResult(result));
2211
2259
  }
@@ -2246,6 +2294,11 @@ const __serverModule = (() => {
2246
2294
  return result;
2247
2295
  }
2248
2296
 
2297
+ if (result.error) {
2298
+ markAppliedServerResult(result);
2299
+ throw toError(result.error);
2300
+ }
2301
+
2249
2302
  if (result.signals && context.signals) {
2250
2303
  for (const [path, value] of Object.entries(result.signals)) {
2251
2304
  context.signals.set?.(path, value);
@@ -2264,15 +2317,7 @@ const __serverModule = (() => {
2264
2317
  await context.router?.navigate?.(result.redirect);
2265
2318
  }
2266
2319
 
2267
- if (result.error) {
2268
- throw toError(result.error);
2269
- }
2270
-
2271
- Object.defineProperty(result, appliedServerResult, {
2272
- configurable: true,
2273
- enumerable: false,
2274
- value: true
2275
- });
2320
+ markAppliedServerResult(result);
2276
2321
 
2277
2322
  return result;
2278
2323
  }
@@ -2291,6 +2336,15 @@ const __serverModule = (() => {
2291
2336
  return value;
2292
2337
  }
2293
2338
 
2339
+ function markAppliedServerResult(result) {
2340
+ Object.defineProperty(result, appliedServerResult, {
2341
+ configurable: true,
2342
+ enumerable: false,
2343
+ value: true
2344
+ });
2345
+ return result;
2346
+ }
2347
+
2294
2348
  function defaultInput(context = {}) {
2295
2349
  const form = findForm(context);
2296
2350
  if (form) {
@@ -2368,10 +2422,37 @@ const __serverModule = (() => {
2368
2422
  return namespace([]);
2369
2423
  }
2370
2424
 
2371
- async function readServerResponse(response) {
2425
+ function assertTransportResponse(id, response) {
2426
+ if (!response || typeof response !== "object") {
2427
+ throw new Error(`Server function "${id}" transport returned an invalid response: expected a fetch Response-like object.`);
2428
+ }
2429
+ if (typeof response.ok !== "boolean") {
2430
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing boolean ok.`);
2431
+ }
2432
+ if (!response.headers || typeof response.headers.get !== "function") {
2433
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing headers.get(name).`);
2434
+ }
2435
+ }
2436
+
2437
+ async function readServerResponse(id, response) {
2438
+ if (response.status === 204) {
2439
+ return { value: undefined };
2440
+ }
2372
2441
  const type = response.headers.get("content-type") ?? "";
2373
2442
  if (type.includes("application/json")) {
2374
- return response.json();
2443
+ if (typeof response.json !== "function") {
2444
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing json().`);
2445
+ }
2446
+ try {
2447
+ return await response.json();
2448
+ } catch (cause) {
2449
+ throw new Error(`Server function "${id}" returned invalid JSON: ${errorMessage(cause)}`, {
2450
+ cause
2451
+ });
2452
+ }
2453
+ }
2454
+ if (typeof response.text !== "function") {
2455
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
2375
2456
  }
2376
2457
  return { value: await response.text() };
2377
2458
  }
@@ -2484,6 +2565,9 @@ const __serverModule = (() => {
2484
2565
  if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
2485
2566
  throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
2486
2567
  }
2568
+ if (isUnsupportedJsonTransportObject(value, tag)) {
2569
+ throw new Error("Server proxy JSON transport does not support URLSearchParams, Headers, Request, Response, ReadableStream, ArrayBuffer, or typed array values yet.");
2570
+ }
2487
2571
  if (Array.isArray(value)) {
2488
2572
  for (const item of value) {
2489
2573
  assertJsonTransportable(item, stack);
@@ -2497,6 +2581,16 @@ const __serverModule = (() => {
2497
2581
  stack.delete(value);
2498
2582
  }
2499
2583
 
2584
+ function isUnsupportedJsonTransportObject(value, tag = Object.prototype.toString.call(value)) {
2585
+ return tag === "[object URLSearchParams]"
2586
+ || tag === "[object Headers]"
2587
+ || tag === "[object Request]"
2588
+ || tag === "[object Response]"
2589
+ || tag === "[object ReadableStream]"
2590
+ || tag === "[object ArrayBuffer]"
2591
+ || ArrayBuffer.isView(value);
2592
+ }
2593
+
2500
2594
  function joinEndpoint(endpoint, id) {
2501
2595
  return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
2502
2596
  }
@@ -2518,6 +2612,10 @@ const __serverModule = (() => {
2518
2612
  return new Error(String(value));
2519
2613
  }
2520
2614
 
2615
+ function errorMessage(error) {
2616
+ return error instanceof Error ? error.message : String(error);
2617
+ }
2618
+
2521
2619
  function assertServerId(id) {
2522
2620
  if (typeof id !== "string" || id.length === 0) {
2523
2621
  throw new TypeError("Server function id must be a non-empty string.");
@@ -2750,7 +2848,8 @@ const __schedulerModule = (() => {
2750
2848
  const phases = [...(options.phases ?? defaultPhases)];
2751
2849
  const queues = new Map(phases.map((phase) => [phase, []]));
2752
2850
  const keyedJobs = new Map();
2753
- const destroyedScopes = new Set();
2851
+ const destroyedObjectScopes = new WeakSet();
2852
+ const destroyedPrimitiveScopes = new Set();
2754
2853
  const objectScopeIds = new WeakMap();
2755
2854
  const onError = typeof options.onError === "function" ? options.onError : undefined;
2756
2855
  const maxDepth = options.maxDepth ?? 100;
@@ -2798,7 +2897,7 @@ const __schedulerModule = (() => {
2798
2897
  throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
2799
2898
  }
2800
2899
  const scope = options.scope;
2801
- if (scope !== undefined && destroyedScopes.has(scope)) {
2900
+ if (isScopeDestroyed(scope)) {
2802
2901
  return noop;
2803
2902
  }
2804
2903
 
@@ -2902,14 +3001,29 @@ const __schedulerModule = (() => {
2902
3001
 
2903
3002
  markScopeDestroyed(scope) {
2904
3003
  if (scope !== undefined) {
2905
- destroyedScopes.add(scope);
3004
+ if (isObjectScope(scope)) {
3005
+ destroyedObjectScopes.add(scope);
3006
+ } else {
3007
+ destroyedPrimitiveScopes.add(scope);
3008
+ }
2906
3009
  api.cancelScope(scope);
2907
3010
  }
2908
3011
  return api;
2909
3012
  },
2910
3013
 
3014
+ reviveScope(scope) {
3015
+ if (scope !== undefined) {
3016
+ if (isObjectScope(scope)) {
3017
+ destroyedObjectScopes.delete(scope);
3018
+ } else {
3019
+ destroyedPrimitiveScopes.delete(scope);
3020
+ }
3021
+ }
3022
+ return api;
3023
+ },
3024
+
2911
3025
  isScopeDestroyed(scope) {
2912
- return scope !== undefined && destroyedScopes.has(scope);
3026
+ return isScopeDestroyed(scope);
2913
3027
  },
2914
3028
 
2915
3029
  inspect() {
@@ -2921,7 +3035,7 @@ const __schedulerModule = (() => {
2921
3035
  strategy,
2922
3036
  phases: [...phases],
2923
3037
  pending: counts,
2924
- scopesDestroyed: destroyedScopes.size,
3038
+ scopesDestroyed: destroyedPrimitiveScopes.size,
2925
3039
  flushing,
2926
3040
  scheduled
2927
3041
  };
@@ -2936,7 +3050,7 @@ const __schedulerModule = (() => {
2936
3050
  queue.length = 0;
2937
3051
  }
2938
3052
  keyedJobs.clear();
2939
- destroyedScopes.clear();
3053
+ destroyedPrimitiveScopes.clear();
2940
3054
  }
2941
3055
  };
2942
3056
 
@@ -2976,7 +3090,7 @@ const __schedulerModule = (() => {
2976
3090
  if (job.key) {
2977
3091
  keyedJobs.delete(job.key);
2978
3092
  }
2979
- if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
3093
+ if (job.canceled || isScopeDestroyed(job.scope)) {
2980
3094
  continue;
2981
3095
  }
2982
3096
  try {
@@ -3033,6 +3147,20 @@ const __schedulerModule = (() => {
3033
3147
  }
3034
3148
  return String(scope);
3035
3149
  }
3150
+
3151
+ function isScopeDestroyed(scope) {
3152
+ if (scope === undefined) {
3153
+ return false;
3154
+ }
3155
+ if (isObjectScope(scope)) {
3156
+ return destroyedObjectScopes.has(scope);
3157
+ }
3158
+ return destroyedPrimitiveScopes.has(scope);
3159
+ }
3160
+ }
3161
+
3162
+ function isObjectScope(scope) {
3163
+ return (typeof scope === "object" && scope !== null) || typeof scope === "function";
3036
3164
  }
3037
3165
 
3038
3166
  function scheduleMicrotask(fn) {
@@ -3093,6 +3221,7 @@ const __loaderModule = (() => {
3093
3221
 
3094
3222
  scan(rootOrFragment = rootNode) {
3095
3223
  assertActive();
3224
+ reviveScopes(rootOrFragment);
3096
3225
  bindSignalAttributes(rootOrFragment);
3097
3226
  bindClassAttributes(rootOrFragment);
3098
3227
  bindEventAttributes(rootOrFragment);
@@ -3596,6 +3725,12 @@ const __loaderModule = (() => {
3596
3725
  }
3597
3726
  }
3598
3727
 
3728
+ function reviveScopes(scope) {
3729
+ for (const element of elementsIn(scope)) {
3730
+ schedulerInstance.reviveScope?.(element);
3731
+ }
3732
+ }
3733
+
3599
3734
  return api;
3600
3735
  }
3601
3736
 
@@ -4159,7 +4294,10 @@ const __routerModule = (() => {
4159
4294
  }
4160
4295
  const matched = api.match(url);
4161
4296
  if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
4162
- return partials.render(matched.route.partial, matched.params, contextFor(matched));
4297
+ return partials.render(matched.route.partial, matched.params, {
4298
+ ...contextFor(matched),
4299
+ prefetch: true
4300
+ });
4163
4301
  }
4164
4302
  return Promise.resolve(null);
4165
4303
  },
@@ -5195,6 +5333,13 @@ const __appModule = (() => {
5195
5333
  function setOrRegisterSignal(signals, path, value) {
5196
5334
  const id = String(path).split(".")[0];
5197
5335
  if (signals.has?.(id)) {
5336
+ if (path === id) {
5337
+ const entry = signals._entry?.(id);
5338
+ if (typeof entry?._restore === "function" && isAsyncSignalSnapshot(value)) {
5339
+ entry._restore(value);
5340
+ return;
5341
+ }
5342
+ }
5198
5343
  signals.set(path, value);
5199
5344
  return;
5200
5345
  }
@@ -5204,6 +5349,17 @@ const __appModule = (() => {
5204
5349
  }
5205
5350
  }
5206
5351
 
5352
+ function isAsyncSignalSnapshot(value) {
5353
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5354
+ return false;
5355
+ }
5356
+ return Object.hasOwn(value, "value")
5357
+ && (Object.hasOwn(value, "loading")
5358
+ || Object.hasOwn(value, "error")
5359
+ || Object.hasOwn(value, "status")
5360
+ || Object.hasOwn(value, "version"));
5361
+ }
5362
+
5207
5363
  function attachServerCache(server, cache) {
5208
5364
  try {
5209
5365
  server.cache = cache;