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