@async/framework 0.11.1 → 0.11.3

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/browser.umd.js CHANGED
@@ -196,6 +196,28 @@
196
196
  };
197
197
  },
198
198
 
199
+ _cloneSignalDeclaration() {
200
+ return asyncSignal(id, fn);
201
+ },
202
+
203
+ _restore(snapshot = {}) {
204
+ if (!isAsyncSignalSnapshot(snapshot)) {
205
+ return state.set(snapshot);
206
+ }
207
+ if (activeAbort && !activeAbort.aborted) {
208
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
209
+ }
210
+ value = snapshot.value;
211
+ loading = Boolean(snapshot.loading);
212
+ error = snapshot.error ?? null;
213
+ status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
214
+ if (Number.isFinite(snapshot.version)) {
215
+ version = snapshot.version;
216
+ }
217
+ notify();
218
+ return state;
219
+ },
220
+
199
221
  _bindRegistry(nextRegistry, nextId) {
200
222
  registry = nextRegistry;
201
223
  registeredId = nextId;
@@ -281,6 +303,27 @@
281
303
  return Boolean(value?.[asyncSignalKind]);
282
304
  }
283
305
 
306
+ function isAsyncSignalSnapshot(value) {
307
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
308
+ return false;
309
+ }
310
+ return Object.hasOwn(value, "value")
311
+ && (Object.hasOwn(value, "loading")
312
+ || Object.hasOwn(value, "error")
313
+ || Object.hasOwn(value, "status")
314
+ || Object.hasOwn(value, "version"));
315
+ }
316
+
317
+ function inferStatus({ value, loading, error }) {
318
+ if (loading) {
319
+ return "loading";
320
+ }
321
+ if (error) {
322
+ return "error";
323
+ }
324
+ return value === undefined ? "idle" : "ready";
325
+ }
326
+
284
327
  function attachCancel(signal, controller) {
285
328
  Object.defineProperty(signal, "cancel", {
286
329
  configurable: true,
@@ -928,7 +971,12 @@
928
971
  return registryStore.entries(`${type}.entries`);
929
972
  },
930
973
 
931
- _adoptMany() {
974
+ _adoptMany(map = {}) {
975
+ for (const [id, definition] of Object.entries(map ?? {})) {
976
+ if (!definitions.has(id)) {
977
+ registryApi.register(id, definition);
978
+ }
979
+ }
932
980
  return registryApi;
933
981
  }
934
982
  }, registryStore, type);
@@ -1086,6 +1134,10 @@
1086
1134
 
1087
1135
  snapshot() {
1088
1136
  return value;
1137
+ },
1138
+
1139
+ _cloneSignalDeclaration() {
1140
+ return createSignal(value);
1089
1141
  }
1090
1142
  };
1091
1143
 
@@ -1128,6 +1180,10 @@
1128
1180
  return backing.snapshot();
1129
1181
  },
1130
1182
 
1183
+ _cloneSignalDeclaration() {
1184
+ return computed(fn);
1185
+ },
1186
+
1131
1187
  _bindRegistry(registry, id) {
1132
1188
  return registry.effect(() => {
1133
1189
  backing.set(fn.call({
@@ -1152,6 +1208,9 @@
1152
1208
  [effectKind]: true,
1153
1209
  kind: "effect",
1154
1210
  fn,
1211
+ _cloneSignalDeclaration() {
1212
+ return effect(fn);
1213
+ },
1155
1214
  _bindRegistry(registry) {
1156
1215
  return registry.effect(fn);
1157
1216
  }
@@ -1370,10 +1429,14 @@
1370
1429
  },
1371
1430
 
1372
1431
  _adoptMany(map = {}) {
1373
- for (const id of Object.keys(map ?? {})) {
1374
- if (entries.has(id)) {
1375
- bindEntry(id, entries.get(id));
1432
+ for (const [id, signalLike] of Object.entries(map ?? {})) {
1433
+ if (!entries.has(id)) {
1434
+ const entry = cloneSignalDeclaration(signalLike);
1435
+ entries.set(id, entry);
1436
+ bindEntry(id, entry);
1437
+ continue;
1376
1438
  }
1439
+ bindEntry(id, entries.get(id));
1377
1440
  }
1378
1441
  return registry;
1379
1442
  }
@@ -1451,6 +1514,16 @@
1451
1514
  return createSignal(signalLike);
1452
1515
  }
1453
1516
 
1517
+ function cloneSignalDeclaration(signalLike) {
1518
+ if (typeof signalLike?._cloneSignalDeclaration === "function") {
1519
+ return signalLike._cloneSignalDeclaration();
1520
+ }
1521
+ if (isSignalLike(signalLike)) {
1522
+ return createSignal(typeof signalLike.snapshot === "function" ? signalLike.snapshot() : signalLike.value);
1523
+ }
1524
+ return createSignal(signalLike);
1525
+ }
1526
+
1454
1527
  function isSignalLike(value) {
1455
1528
  return Boolean(value && typeof value === "object" && typeof value.subscribe === "function");
1456
1529
  }
@@ -1629,7 +1702,7 @@
1629
1702
  frame.add(path);
1630
1703
  }
1631
1704
  }
1632
- return { createSignal, computed, effect, createSignalRegistry, isSignalRef, signal };
1705
+ return { createSignal, computed, effect, createSignalRegistry, cloneSignalDeclaration, isSignalRef, signal };
1633
1706
  })();
1634
1707
 
1635
1708
  const __htmlModule = (() => {
@@ -1871,7 +1944,12 @@
1871
1944
  return lazyComponents.get(id);
1872
1945
  },
1873
1946
 
1874
- _adoptMany() {
1947
+ _adoptMany(map = {}) {
1948
+ for (const [id, Component] of Object.entries(map ?? {})) {
1949
+ if (!entries.has(id)) {
1950
+ registry.register(id, Component);
1951
+ }
1952
+ }
1875
1953
  return registry;
1876
1954
  }
1877
1955
  }, registryStore, type);
@@ -1919,12 +1997,16 @@
1919
1997
  });
1920
1998
 
1921
1999
  const output = Component.call(context, props);
2000
+ if (output && typeof output.then === "function") {
2001
+ throw new TypeError(`Component "${componentName(Component)}" returned a Promise. Async components are not supported by synchronous renderComponent(). Use an async partial or handler instead.`);
2002
+ }
1922
2003
  const html = renderScopedTemplate(output);
1923
2004
 
1924
2005
  return {
1925
2006
  html,
1926
2007
  attach(target) {
1927
- for (const hook of attachHooks) {
2008
+ for (let index = 0; index < attachHooks.length; index += 1) {
2009
+ const hook = attachHooks[index];
1928
2010
  runtime.scheduler?.enqueue("lifecycle", () => {
1929
2011
  const cleanup = hook(target);
1930
2012
  if (typeof cleanup === "function") {
@@ -1932,7 +2014,7 @@
1932
2014
  }
1933
2015
  }, {
1934
2016
  scope,
1935
- key: `attach:${attachHooks.indexOf(hook)}`
2017
+ key: `attach:${index}`
1936
2018
  }) ?? runAttachHook(hook, target);
1937
2019
  }
1938
2020
  },
@@ -1940,8 +2022,12 @@
1940
2022
  this.attach(target);
1941
2023
  },
1942
2024
  visible(target, observeVisible) {
1943
- for (const hook of visibleHooks) {
1944
- const cleanup = observeVisible(target, () => {
2025
+ if (visibleHooks.length === 0) {
2026
+ return;
2027
+ }
2028
+ const cleanup = observeVisible(target, () => {
2029
+ for (let index = 0; index < visibleHooks.length; index += 1) {
2030
+ const hook = visibleHooks[index];
1945
2031
  runtime.scheduler?.enqueue("lifecycle", () => {
1946
2032
  const hookCleanup = hook(target);
1947
2033
  if (typeof hookCleanup === "function") {
@@ -1949,12 +2035,12 @@
1949
2035
  }
1950
2036
  }, {
1951
2037
  scope,
1952
- key: `visible:${visibleHooks.indexOf(hook)}`
2038
+ key: `visible:${index}`
1953
2039
  }) ?? runVisibleHook(hook, target);
1954
- });
1955
- if (typeof cleanup === "function") {
1956
- cleanups.push(cleanup);
1957
2040
  }
2041
+ });
2042
+ if (typeof cleanup === "function") {
2043
+ cleanups.push(cleanup);
1958
2044
  }
1959
2045
  },
1960
2046
  cleanup() {
@@ -2210,11 +2296,12 @@
2210
2296
  signal: context.abort
2211
2297
  });
2212
2298
 
2299
+ assertTransportResponse(id, response);
2213
2300
  if (!response.ok) {
2214
2301
  throw new Error(`Server function "${id}" failed with ${response.status}.`);
2215
2302
  }
2216
2303
 
2217
- const result = await readServerResponse(response);
2304
+ const result = await readServerResponse(id, response);
2218
2305
  await applyServerResult(result, runContext);
2219
2306
  return markAppliedServerValue(unwrapServerResult(result));
2220
2307
  }
@@ -2255,6 +2342,11 @@
2255
2342
  return result;
2256
2343
  }
2257
2344
 
2345
+ if (result.error) {
2346
+ markAppliedServerResult(result);
2347
+ throw toError(result.error);
2348
+ }
2349
+
2258
2350
  if (result.signals && context.signals) {
2259
2351
  for (const [path, value] of Object.entries(result.signals)) {
2260
2352
  context.signals.set?.(path, value);
@@ -2273,15 +2365,7 @@
2273
2365
  await context.router?.navigate?.(result.redirect);
2274
2366
  }
2275
2367
 
2276
- if (result.error) {
2277
- throw toError(result.error);
2278
- }
2279
-
2280
- Object.defineProperty(result, appliedServerResult, {
2281
- configurable: true,
2282
- enumerable: false,
2283
- value: true
2284
- });
2368
+ markAppliedServerResult(result);
2285
2369
 
2286
2370
  return result;
2287
2371
  }
@@ -2300,6 +2384,15 @@
2300
2384
  return value;
2301
2385
  }
2302
2386
 
2387
+ function markAppliedServerResult(result) {
2388
+ Object.defineProperty(result, appliedServerResult, {
2389
+ configurable: true,
2390
+ enumerable: false,
2391
+ value: true
2392
+ });
2393
+ return result;
2394
+ }
2395
+
2303
2396
  function defaultInput(context = {}) {
2304
2397
  const form = findForm(context);
2305
2398
  if (form) {
@@ -2377,10 +2470,37 @@
2377
2470
  return namespace([]);
2378
2471
  }
2379
2472
 
2380
- async function readServerResponse(response) {
2473
+ function assertTransportResponse(id, response) {
2474
+ if (!response || typeof response !== "object") {
2475
+ throw new Error(`Server function "${id}" transport returned an invalid response: expected a fetch Response-like object.`);
2476
+ }
2477
+ if (typeof response.ok !== "boolean") {
2478
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing boolean ok.`);
2479
+ }
2480
+ if (!response.headers || typeof response.headers.get !== "function") {
2481
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing headers.get(name).`);
2482
+ }
2483
+ }
2484
+
2485
+ async function readServerResponse(id, response) {
2486
+ if (response.status === 204) {
2487
+ return { value: undefined };
2488
+ }
2381
2489
  const type = response.headers.get("content-type") ?? "";
2382
2490
  if (type.includes("application/json")) {
2383
- return response.json();
2491
+ if (typeof response.json !== "function") {
2492
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing json().`);
2493
+ }
2494
+ try {
2495
+ return await response.json();
2496
+ } catch (cause) {
2497
+ throw new Error(`Server function "${id}" returned invalid JSON: ${errorMessage(cause)}`, {
2498
+ cause
2499
+ });
2500
+ }
2501
+ }
2502
+ if (typeof response.text !== "function") {
2503
+ throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
2384
2504
  }
2385
2505
  return { value: await response.text() };
2386
2506
  }
@@ -2493,6 +2613,9 @@
2493
2613
  if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
2494
2614
  throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
2495
2615
  }
2616
+ if (isUnsupportedJsonTransportObject(value, tag)) {
2617
+ throw new Error("Server proxy JSON transport does not support URLSearchParams, Headers, Request, Response, ReadableStream, ArrayBuffer, or typed array values yet.");
2618
+ }
2496
2619
  if (Array.isArray(value)) {
2497
2620
  for (const item of value) {
2498
2621
  assertJsonTransportable(item, stack);
@@ -2506,6 +2629,16 @@
2506
2629
  stack.delete(value);
2507
2630
  }
2508
2631
 
2632
+ function isUnsupportedJsonTransportObject(value, tag = Object.prototype.toString.call(value)) {
2633
+ return tag === "[object URLSearchParams]"
2634
+ || tag === "[object Headers]"
2635
+ || tag === "[object Request]"
2636
+ || tag === "[object Response]"
2637
+ || tag === "[object ReadableStream]"
2638
+ || tag === "[object ArrayBuffer]"
2639
+ || ArrayBuffer.isView(value);
2640
+ }
2641
+
2509
2642
  function joinEndpoint(endpoint, id) {
2510
2643
  return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
2511
2644
  }
@@ -2527,6 +2660,10 @@
2527
2660
  return new Error(String(value));
2528
2661
  }
2529
2662
 
2663
+ function errorMessage(error) {
2664
+ return error instanceof Error ? error.message : String(error);
2665
+ }
2666
+
2530
2667
  function assertServerId(id) {
2531
2668
  if (typeof id !== "string" || id.length === 0) {
2532
2669
  throw new TypeError("Server function id must be a non-empty string.");
@@ -2652,7 +2789,12 @@
2652
2789
  return results;
2653
2790
  },
2654
2791
 
2655
- _adoptMany() {
2792
+ _adoptMany(map = {}) {
2793
+ for (const [id, fn] of Object.entries(map ?? {})) {
2794
+ if (!handlers.has(id)) {
2795
+ registry.register(id, fn);
2796
+ }
2797
+ }
2656
2798
  return registry;
2657
2799
  }
2658
2800
  }, registryStore, type);
@@ -2759,7 +2901,8 @@
2759
2901
  const phases = [...(options.phases ?? defaultPhases)];
2760
2902
  const queues = new Map(phases.map((phase) => [phase, []]));
2761
2903
  const keyedJobs = new Map();
2762
- const destroyedScopes = new Set();
2904
+ const destroyedObjectScopes = new WeakSet();
2905
+ const destroyedPrimitiveScopes = new Set();
2763
2906
  const objectScopeIds = new WeakMap();
2764
2907
  const onError = typeof options.onError === "function" ? options.onError : undefined;
2765
2908
  const maxDepth = options.maxDepth ?? 100;
@@ -2807,7 +2950,7 @@
2807
2950
  throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
2808
2951
  }
2809
2952
  const scope = options.scope;
2810
- if (scope !== undefined && destroyedScopes.has(scope)) {
2953
+ if (isScopeDestroyed(scope)) {
2811
2954
  return noop;
2812
2955
  }
2813
2956
 
@@ -2911,14 +3054,29 @@
2911
3054
 
2912
3055
  markScopeDestroyed(scope) {
2913
3056
  if (scope !== undefined) {
2914
- destroyedScopes.add(scope);
3057
+ if (isObjectScope(scope)) {
3058
+ destroyedObjectScopes.add(scope);
3059
+ } else {
3060
+ destroyedPrimitiveScopes.add(scope);
3061
+ }
2915
3062
  api.cancelScope(scope);
2916
3063
  }
2917
3064
  return api;
2918
3065
  },
2919
3066
 
3067
+ reviveScope(scope) {
3068
+ if (scope !== undefined) {
3069
+ if (isObjectScope(scope)) {
3070
+ destroyedObjectScopes.delete(scope);
3071
+ } else {
3072
+ destroyedPrimitiveScopes.delete(scope);
3073
+ }
3074
+ }
3075
+ return api;
3076
+ },
3077
+
2920
3078
  isScopeDestroyed(scope) {
2921
- return scope !== undefined && destroyedScopes.has(scope);
3079
+ return isScopeDestroyed(scope);
2922
3080
  },
2923
3081
 
2924
3082
  inspect() {
@@ -2930,7 +3088,7 @@
2930
3088
  strategy,
2931
3089
  phases: [...phases],
2932
3090
  pending: counts,
2933
- scopesDestroyed: destroyedScopes.size,
3091
+ scopesDestroyed: destroyedPrimitiveScopes.size,
2934
3092
  flushing,
2935
3093
  scheduled
2936
3094
  };
@@ -2945,7 +3103,7 @@
2945
3103
  queue.length = 0;
2946
3104
  }
2947
3105
  keyedJobs.clear();
2948
- destroyedScopes.clear();
3106
+ destroyedPrimitiveScopes.clear();
2949
3107
  }
2950
3108
  };
2951
3109
 
@@ -2985,7 +3143,7 @@
2985
3143
  if (job.key) {
2986
3144
  keyedJobs.delete(job.key);
2987
3145
  }
2988
- if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
3146
+ if (job.canceled || isScopeDestroyed(job.scope)) {
2989
3147
  continue;
2990
3148
  }
2991
3149
  try {
@@ -3042,6 +3200,20 @@
3042
3200
  }
3043
3201
  return String(scope);
3044
3202
  }
3203
+
3204
+ function isScopeDestroyed(scope) {
3205
+ if (scope === undefined) {
3206
+ return false;
3207
+ }
3208
+ if (isObjectScope(scope)) {
3209
+ return destroyedObjectScopes.has(scope);
3210
+ }
3211
+ return destroyedPrimitiveScopes.has(scope);
3212
+ }
3213
+ }
3214
+
3215
+ function isObjectScope(scope) {
3216
+ return (typeof scope === "object" && scope !== null) || typeof scope === "function";
3045
3217
  }
3046
3218
 
3047
3219
  function scheduleMicrotask(fn) {
@@ -3102,6 +3274,7 @@
3102
3274
 
3103
3275
  scan(rootOrFragment = rootNode) {
3104
3276
  assertActive();
3277
+ reviveScopes(rootOrFragment);
3105
3278
  bindSignalAttributes(rootOrFragment);
3106
3279
  bindClassAttributes(rootOrFragment);
3107
3280
  bindEventAttributes(rootOrFragment);
@@ -3605,6 +3778,12 @@
3605
3778
  }
3606
3779
  }
3607
3780
 
3781
+ function reviveScopes(scope) {
3782
+ for (const element of elementsIn(scope)) {
3783
+ schedulerInstance.reviveScope?.(element);
3784
+ }
3785
+ }
3786
+
3608
3787
  return api;
3609
3788
  }
3610
3789
 
@@ -3904,7 +4083,12 @@
3904
4083
  return normalizePartialResult(result, partialContext);
3905
4084
  },
3906
4085
 
3907
- _adoptMany() {
4086
+ _adoptMany(map = {}) {
4087
+ for (const [id, fn] of Object.entries(map ?? {})) {
4088
+ if (!entries.has(id)) {
4089
+ registry.register(id, fn);
4090
+ }
4091
+ }
3908
4092
  return registry;
3909
4093
  }
3910
4094
  }, registryStore, type);
@@ -4056,7 +4240,11 @@
4056
4240
  },
4057
4241
 
4058
4242
  _adoptMany(map = {}) {
4059
- for (const pattern of Object.keys(map ?? {})) {
4243
+ for (const [pattern, definition] of Object.entries(map ?? {})) {
4244
+ if (!entries.has(pattern)) {
4245
+ registry.register(pattern, definition);
4246
+ continue;
4247
+ }
4060
4248
  adoptRoute(pattern, entries.get(pattern));
4061
4249
  }
4062
4250
  return registry;
@@ -4168,7 +4356,10 @@
4168
4356
  }
4169
4357
  const matched = api.match(url);
4170
4358
  if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
4171
- return partials.render(matched.route.partial, matched.params, contextFor(matched));
4359
+ return partials.render(matched.route.partial, matched.params, {
4360
+ ...contextFor(matched),
4361
+ prefetch: true
4362
+ });
4172
4363
  }
4173
4364
  return Promise.resolve(null);
4174
4365
  },
@@ -4549,7 +4740,7 @@
4549
4740
  const { createRouteRegistry, createRouter } = __routerModule;
4550
4741
  const { createScheduler } = __schedulerModule;
4551
4742
  const { createServerNamespace } = __serverModule;
4552
- const { createSignal, createSignalRegistry } = __signalsModule;
4743
+ const { cloneSignalDeclaration, createSignal, createSignalRegistry } = __signalsModule;
4553
4744
  const { createRegistryStore } = __registryStoreModule;
4554
4745
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
4555
4746
  const { createLazyRegistry, defineRegistrySnapshot, sameRegistryValue } = __lazyRegistryModule;
@@ -4632,7 +4823,7 @@
4632
4823
  registryAssets: options.registryAssets,
4633
4824
  importModule: options.importModule
4634
4825
  });
4635
- const registry = options.registry ?? app.registry.view({ target });
4826
+ const registry = options.registry ?? createRuntimeRegistry(app.registry, { target });
4636
4827
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal", lazyRegistry });
4637
4828
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler", lazyRegistry });
4638
4829
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
@@ -5013,6 +5204,22 @@
5013
5204
  registry?.registerMany?.(entries);
5014
5205
  }
5015
5206
 
5207
+ function createRuntimeRegistry(appRegistry, { target } = {}) {
5208
+ const declarations = appRegistry.rawSnapshot();
5209
+ return createRegistryStore({
5210
+ ...declarations,
5211
+ signal: cloneSignalDeclarations(declarations.signal)
5212
+ }, { target });
5213
+ }
5214
+
5215
+ function cloneSignalDeclarations(signals = {}) {
5216
+ const cloned = {};
5217
+ for (const [id, signalLike] of Object.entries(signals ?? {})) {
5218
+ cloned[id] = cloneSignalDeclaration(signalLike);
5219
+ }
5220
+ return cloned;
5221
+ }
5222
+
5016
5223
  function emptyDeclarations() {
5017
5224
  return {
5018
5225
  signal: {},
@@ -5204,6 +5411,13 @@
5204
5411
  function setOrRegisterSignal(signals, path, value) {
5205
5412
  const id = String(path).split(".")[0];
5206
5413
  if (signals.has?.(id)) {
5414
+ if (path === id) {
5415
+ const entry = signals._entry?.(id);
5416
+ if (typeof entry?._restore === "function" && isAsyncSignalSnapshot(value)) {
5417
+ entry._restore(value);
5418
+ return;
5419
+ }
5420
+ }
5207
5421
  signals.set(path, value);
5208
5422
  return;
5209
5423
  }
@@ -5213,6 +5427,17 @@
5213
5427
  }
5214
5428
  }
5215
5429
 
5430
+ function isAsyncSignalSnapshot(value) {
5431
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5432
+ return false;
5433
+ }
5434
+ return Object.hasOwn(value, "value")
5435
+ && (Object.hasOwn(value, "loading")
5436
+ || Object.hasOwn(value, "error")
5437
+ || Object.hasOwn(value, "status")
5438
+ || Object.hasOwn(value, "version"));
5439
+ }
5440
+
5216
5441
  function attachServerCache(server, cache) {
5217
5442
  try {
5218
5443
  server.cache = cache;