@async/framework 0.11.10 → 0.11.12

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/framework.ts CHANGED
@@ -134,9 +134,9 @@ const __asyncSignalModule = (() => {
134
134
 
135
135
  snapshot() {
136
136
  return {
137
- value,
137
+ value: value === undefined && error !== null ? null : value,
138
138
  loading,
139
- error,
139
+ error: serializeAsyncError(error),
140
140
  status,
141
141
  version
142
142
  };
@@ -153,7 +153,7 @@ const __asyncSignalModule = (() => {
153
153
  cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
154
154
  value = snapshot.value;
155
155
  loading = Boolean(snapshot.loading);
156
- error = snapshot.error ?? null;
156
+ error = restoreAsyncError(snapshot.error);
157
157
  status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
158
158
  if (Number.isFinite(snapshot.version)) {
159
159
  version = snapshot.version;
@@ -405,6 +405,51 @@ const __asyncSignalModule = (() => {
405
405
  return value === undefined ? "idle" : "ready";
406
406
  }
407
407
 
408
+ function serializeAsyncError(value) {
409
+ if (value == null) {
410
+ return null;
411
+ }
412
+
413
+ const record = {
414
+ name: readErrorName(value),
415
+ message: readErrorMessage(value)
416
+ };
417
+ const code = readErrorCode(value);
418
+ if (code !== undefined) {
419
+ record.code = code;
420
+ }
421
+ return record;
422
+ }
423
+
424
+ function restoreAsyncError(value) {
425
+ return serializeAsyncError(value);
426
+ }
427
+
428
+ function readErrorName(value) {
429
+ if (value && typeof value === "object" && typeof value.name === "string" && value.name.length > 0) {
430
+ return value.name;
431
+ }
432
+ return "Error";
433
+ }
434
+
435
+ function readErrorMessage(value) {
436
+ if (value instanceof Error) {
437
+ return value.message;
438
+ }
439
+ if (value && typeof value === "object" && typeof value.message === "string") {
440
+ return value.message;
441
+ }
442
+ return String(value);
443
+ }
444
+
445
+ function readErrorCode(value) {
446
+ if (!value || typeof value !== "object" || !Object.hasOwn(value, "code")) {
447
+ return undefined;
448
+ }
449
+ const code = value.code;
450
+ return typeof code === "string" || typeof code === "number" ? code : undefined;
451
+ }
452
+
408
453
  function attachCancel(signal, controller, onCancel) {
409
454
  Object.defineProperty(signal, "cancel", {
410
455
  configurable: true,
@@ -5939,101 +5984,25 @@ const __boundaryReceiverModule = (() => {
5939
5984
 
5940
5985
  const normalized = validatePatch(patch);
5941
5986
  const record = boundaryRecord(normalized.boundary);
5942
- if (normalized.seq <= record.lastSeq) {
5943
- const result = {
5944
- status: "ignored-stale",
5945
- boundary: normalized.boundary,
5946
- seq: normalized.seq,
5947
- lastSeq: record.lastSeq
5948
- };
5949
- record.ignored += 1;
5950
- record.lastStatus = result.status;
5951
- remember(result);
5952
- onIgnore?.(result, patch);
5953
- return result;
5954
- }
5955
-
5956
- if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
5957
- const result = {
5958
- status: "ignored-destroyed",
5959
- boundary: normalized.boundary,
5960
- seq: normalized.seq,
5961
- parentScope: normalized.parentScope
5962
- };
5963
- record.ignored += 1;
5964
- record.lastStatus = result.status;
5965
- remember(result);
5966
- onIgnore?.(result, patch);
5967
- return result;
5968
- }
5969
-
5970
- record.lastSeq = normalized.seq;
5971
-
5972
- if (Object.hasOwn(normalized, "error")) {
5973
- const error = toStableError(normalized.error);
5974
- const result = {
5975
- status: "errored",
5976
- boundary: normalized.boundary,
5977
- seq: normalized.seq,
5978
- error
5979
- };
5980
- record.errored += 1;
5981
- record.lastStatus = result.status;
5982
- remember(result);
5983
- onError?.(error, result, patch);
5984
- if (throwOnError) {
5985
- throw error;
5986
- }
5987
- return result;
5988
- }
5987
+ let releasePending;
5988
+ const previousPending = record.pending ?? Promise.resolve();
5989
+ const pending = new Promise((resolve) => {
5990
+ releasePending = resolve;
5991
+ });
5992
+ record.pending = pending;
5989
5993
 
5990
- if (normalized.signals) {
5991
- if (!signals || typeof signals.set !== "function") {
5992
- throw new Error("Boundary patch includes signals, but no signal registry is available.");
5993
- }
5994
- for (const [path, value] of Object.entries(normalized.signals)) {
5995
- signals.set(path, value);
5994
+ try {
5995
+ await previousPending;
5996
+ if (destroyed) {
5997
+ throw new Error("Boundary receiver has been destroyed.");
5996
5998
  }
5997
- }
5998
-
5999
- if (normalized.cache?.browser) {
6000
- if (!cache || typeof cache.restore !== "function") {
6001
- throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
5999
+ return await applyBoundaryPatch(record, normalized, patch);
6000
+ } finally {
6001
+ releasePending();
6002
+ if (record.pending === pending) {
6003
+ record.pending = undefined;
6002
6004
  }
6003
- cache.restore(normalized.cache.browser);
6004
- }
6005
-
6006
- if (normalized.html != null) {
6007
- loader.swap(normalized.boundary, normalized.html);
6008
6005
  }
6009
-
6010
- await flushScheduler(scheduler, normalized.scope);
6011
-
6012
- if (normalized.redirect) {
6013
- await followRedirect(normalized.redirect, router, loader);
6014
- const result = {
6015
- status: "redirected",
6016
- boundary: normalized.boundary,
6017
- seq: normalized.seq,
6018
- redirect: normalized.redirect
6019
- };
6020
- record.applied += 1;
6021
- record.lastStatus = result.status;
6022
- remember(result);
6023
- onApply?.(result, patch);
6024
- return result;
6025
- }
6026
-
6027
- const result = {
6028
- status: "applied",
6029
- boundary: normalized.boundary,
6030
- seq: normalized.seq
6031
- };
6032
- record.applied += 1;
6033
- record.lastStatus = result.status;
6034
- remember(result);
6035
- onApply?.(result, patch);
6036
- return result;
6037
6006
  },
6038
6007
 
6039
6008
  inspect() {
@@ -6081,6 +6050,105 @@ const __boundaryReceiverModule = (() => {
6081
6050
 
6082
6051
  return receiver;
6083
6052
 
6053
+ async function applyBoundaryPatch(record, normalized, patch) {
6054
+ if (normalized.seq <= record.lastSeq) {
6055
+ const result = {
6056
+ status: "ignored-stale",
6057
+ boundary: normalized.boundary,
6058
+ seq: normalized.seq,
6059
+ lastSeq: record.lastSeq
6060
+ };
6061
+ record.ignored += 1;
6062
+ record.lastStatus = result.status;
6063
+ remember(result);
6064
+ onIgnore?.(result, patch);
6065
+ return result;
6066
+ }
6067
+
6068
+ if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
6069
+ const result = {
6070
+ status: "ignored-destroyed",
6071
+ boundary: normalized.boundary,
6072
+ seq: normalized.seq,
6073
+ parentScope: normalized.parentScope
6074
+ };
6075
+ record.ignored += 1;
6076
+ record.lastStatus = result.status;
6077
+ remember(result);
6078
+ onIgnore?.(result, patch);
6079
+ return result;
6080
+ }
6081
+
6082
+ if (Object.hasOwn(normalized, "error")) {
6083
+ const error = toStableError(normalized.error);
6084
+ const result = {
6085
+ status: "errored",
6086
+ boundary: normalized.boundary,
6087
+ seq: normalized.seq,
6088
+ error
6089
+ };
6090
+ record.lastSeq = normalized.seq;
6091
+ record.errored += 1;
6092
+ record.lastStatus = result.status;
6093
+ remember(result);
6094
+ onError?.(error, result, patch);
6095
+ if (throwOnError) {
6096
+ throw error;
6097
+ }
6098
+ return result;
6099
+ }
6100
+
6101
+ if (normalized.signals) {
6102
+ if (!signals || typeof signals.set !== "function") {
6103
+ throw new Error("Boundary patch includes signals, but no signal registry is available.");
6104
+ }
6105
+ for (const [path, value] of Object.entries(normalized.signals)) {
6106
+ signals.set(path, value);
6107
+ }
6108
+ }
6109
+
6110
+ if (normalized.cache?.browser) {
6111
+ if (!cache || typeof cache.restore !== "function") {
6112
+ throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
6113
+ }
6114
+ cache.restore(normalized.cache.browser);
6115
+ }
6116
+
6117
+ if (normalized.html != null) {
6118
+ loader.swap(normalized.boundary, normalized.html);
6119
+ }
6120
+
6121
+ await flushScheduler(scheduler, normalized.scope);
6122
+
6123
+ if (normalized.redirect) {
6124
+ const result = {
6125
+ status: "redirected",
6126
+ boundary: normalized.boundary,
6127
+ seq: normalized.seq,
6128
+ redirect: normalized.redirect
6129
+ };
6130
+ await followRedirect(normalized.redirect, router, loader);
6131
+ record.applied += 1;
6132
+ record.lastSeq = normalized.seq;
6133
+ record.lastStatus = result.status;
6134
+ remember(result);
6135
+ onApply?.(result, patch);
6136
+ return result;
6137
+ }
6138
+
6139
+ const result = {
6140
+ status: "applied",
6141
+ boundary: normalized.boundary,
6142
+ seq: normalized.seq
6143
+ };
6144
+ record.applied += 1;
6145
+ record.lastSeq = normalized.seq;
6146
+ record.lastStatus = result.status;
6147
+ remember(result);
6148
+ onApply?.(result, patch);
6149
+ return result;
6150
+ }
6151
+
6084
6152
  function boundaryRecord(boundary) {
6085
6153
  if (!boundaries.has(boundary)) {
6086
6154
  boundaries.set(boundary, {
@@ -6088,7 +6156,8 @@ const __boundaryReceiverModule = (() => {
6088
6156
  applied: 0,
6089
6157
  ignored: 0,
6090
6158
  errored: 0,
6091
- lastStatus: undefined
6159
+ lastStatus: undefined,
6160
+ pending: undefined
6092
6161
  });
6093
6162
  }
6094
6163
  return boundaries.get(boundary);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@async/framework",
3
- "version": "0.11.10",
3
+ "version": "0.11.12",
4
4
  "description": "No-build Loader app runtime with browser and server entrypoints, signals, command events, route partials, cache split, SSR activation, and streaming boundaries.",
5
5
  "type": "module",
6
6
  "main": "./server.js",
package/server.js CHANGED
@@ -133,9 +133,9 @@ const __asyncSignalModule = (() => {
133
133
 
134
134
  snapshot() {
135
135
  return {
136
- value,
136
+ value: value === undefined && error !== null ? null : value,
137
137
  loading,
138
- error,
138
+ error: serializeAsyncError(error),
139
139
  status,
140
140
  version
141
141
  };
@@ -152,7 +152,7 @@ const __asyncSignalModule = (() => {
152
152
  cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
153
153
  value = snapshot.value;
154
154
  loading = Boolean(snapshot.loading);
155
- error = snapshot.error ?? null;
155
+ error = restoreAsyncError(snapshot.error);
156
156
  status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
157
157
  if (Number.isFinite(snapshot.version)) {
158
158
  version = snapshot.version;
@@ -404,6 +404,51 @@ const __asyncSignalModule = (() => {
404
404
  return value === undefined ? "idle" : "ready";
405
405
  }
406
406
 
407
+ function serializeAsyncError(value) {
408
+ if (value == null) {
409
+ return null;
410
+ }
411
+
412
+ const record = {
413
+ name: readErrorName(value),
414
+ message: readErrorMessage(value)
415
+ };
416
+ const code = readErrorCode(value);
417
+ if (code !== undefined) {
418
+ record.code = code;
419
+ }
420
+ return record;
421
+ }
422
+
423
+ function restoreAsyncError(value) {
424
+ return serializeAsyncError(value);
425
+ }
426
+
427
+ function readErrorName(value) {
428
+ if (value && typeof value === "object" && typeof value.name === "string" && value.name.length > 0) {
429
+ return value.name;
430
+ }
431
+ return "Error";
432
+ }
433
+
434
+ function readErrorMessage(value) {
435
+ if (value instanceof Error) {
436
+ return value.message;
437
+ }
438
+ if (value && typeof value === "object" && typeof value.message === "string") {
439
+ return value.message;
440
+ }
441
+ return String(value);
442
+ }
443
+
444
+ function readErrorCode(value) {
445
+ if (!value || typeof value !== "object" || !Object.hasOwn(value, "code")) {
446
+ return undefined;
447
+ }
448
+ const code = value.code;
449
+ return typeof code === "string" || typeof code === "number" ? code : undefined;
450
+ }
451
+
407
452
  function attachCancel(signal, controller, onCancel) {
408
453
  Object.defineProperty(signal, "cancel", {
409
454
  configurable: true,
@@ -5938,101 +5983,25 @@ const __boundaryReceiverModule = (() => {
5938
5983
 
5939
5984
  const normalized = validatePatch(patch);
5940
5985
  const record = boundaryRecord(normalized.boundary);
5941
- if (normalized.seq <= record.lastSeq) {
5942
- const result = {
5943
- status: "ignored-stale",
5944
- boundary: normalized.boundary,
5945
- seq: normalized.seq,
5946
- lastSeq: record.lastSeq
5947
- };
5948
- record.ignored += 1;
5949
- record.lastStatus = result.status;
5950
- remember(result);
5951
- onIgnore?.(result, patch);
5952
- return result;
5953
- }
5954
-
5955
- if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
5956
- const result = {
5957
- status: "ignored-destroyed",
5958
- boundary: normalized.boundary,
5959
- seq: normalized.seq,
5960
- parentScope: normalized.parentScope
5961
- };
5962
- record.ignored += 1;
5963
- record.lastStatus = result.status;
5964
- remember(result);
5965
- onIgnore?.(result, patch);
5966
- return result;
5967
- }
5968
-
5969
- record.lastSeq = normalized.seq;
5970
-
5971
- if (Object.hasOwn(normalized, "error")) {
5972
- const error = toStableError(normalized.error);
5973
- const result = {
5974
- status: "errored",
5975
- boundary: normalized.boundary,
5976
- seq: normalized.seq,
5977
- error
5978
- };
5979
- record.errored += 1;
5980
- record.lastStatus = result.status;
5981
- remember(result);
5982
- onError?.(error, result, patch);
5983
- if (throwOnError) {
5984
- throw error;
5985
- }
5986
- return result;
5987
- }
5986
+ let releasePending;
5987
+ const previousPending = record.pending ?? Promise.resolve();
5988
+ const pending = new Promise((resolve) => {
5989
+ releasePending = resolve;
5990
+ });
5991
+ record.pending = pending;
5988
5992
 
5989
- if (normalized.signals) {
5990
- if (!signals || typeof signals.set !== "function") {
5991
- throw new Error("Boundary patch includes signals, but no signal registry is available.");
5992
- }
5993
- for (const [path, value] of Object.entries(normalized.signals)) {
5994
- signals.set(path, value);
5993
+ try {
5994
+ await previousPending;
5995
+ if (destroyed) {
5996
+ throw new Error("Boundary receiver has been destroyed.");
5995
5997
  }
5996
- }
5997
-
5998
- if (normalized.cache?.browser) {
5999
- if (!cache || typeof cache.restore !== "function") {
6000
- throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
5998
+ return await applyBoundaryPatch(record, normalized, patch);
5999
+ } finally {
6000
+ releasePending();
6001
+ if (record.pending === pending) {
6002
+ record.pending = undefined;
6001
6003
  }
6002
- cache.restore(normalized.cache.browser);
6003
- }
6004
-
6005
- if (normalized.html != null) {
6006
- loader.swap(normalized.boundary, normalized.html);
6007
6004
  }
6008
-
6009
- await flushScheduler(scheduler, normalized.scope);
6010
-
6011
- if (normalized.redirect) {
6012
- await followRedirect(normalized.redirect, router, loader);
6013
- const result = {
6014
- status: "redirected",
6015
- boundary: normalized.boundary,
6016
- seq: normalized.seq,
6017
- redirect: normalized.redirect
6018
- };
6019
- record.applied += 1;
6020
- record.lastStatus = result.status;
6021
- remember(result);
6022
- onApply?.(result, patch);
6023
- return result;
6024
- }
6025
-
6026
- const result = {
6027
- status: "applied",
6028
- boundary: normalized.boundary,
6029
- seq: normalized.seq
6030
- };
6031
- record.applied += 1;
6032
- record.lastStatus = result.status;
6033
- remember(result);
6034
- onApply?.(result, patch);
6035
- return result;
6036
6005
  },
6037
6006
 
6038
6007
  inspect() {
@@ -6080,6 +6049,105 @@ const __boundaryReceiverModule = (() => {
6080
6049
 
6081
6050
  return receiver;
6082
6051
 
6052
+ async function applyBoundaryPatch(record, normalized, patch) {
6053
+ if (normalized.seq <= record.lastSeq) {
6054
+ const result = {
6055
+ status: "ignored-stale",
6056
+ boundary: normalized.boundary,
6057
+ seq: normalized.seq,
6058
+ lastSeq: record.lastSeq
6059
+ };
6060
+ record.ignored += 1;
6061
+ record.lastStatus = result.status;
6062
+ remember(result);
6063
+ onIgnore?.(result, patch);
6064
+ return result;
6065
+ }
6066
+
6067
+ if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
6068
+ const result = {
6069
+ status: "ignored-destroyed",
6070
+ boundary: normalized.boundary,
6071
+ seq: normalized.seq,
6072
+ parentScope: normalized.parentScope
6073
+ };
6074
+ record.ignored += 1;
6075
+ record.lastStatus = result.status;
6076
+ remember(result);
6077
+ onIgnore?.(result, patch);
6078
+ return result;
6079
+ }
6080
+
6081
+ if (Object.hasOwn(normalized, "error")) {
6082
+ const error = toStableError(normalized.error);
6083
+ const result = {
6084
+ status: "errored",
6085
+ boundary: normalized.boundary,
6086
+ seq: normalized.seq,
6087
+ error
6088
+ };
6089
+ record.lastSeq = normalized.seq;
6090
+ record.errored += 1;
6091
+ record.lastStatus = result.status;
6092
+ remember(result);
6093
+ onError?.(error, result, patch);
6094
+ if (throwOnError) {
6095
+ throw error;
6096
+ }
6097
+ return result;
6098
+ }
6099
+
6100
+ if (normalized.signals) {
6101
+ if (!signals || typeof signals.set !== "function") {
6102
+ throw new Error("Boundary patch includes signals, but no signal registry is available.");
6103
+ }
6104
+ for (const [path, value] of Object.entries(normalized.signals)) {
6105
+ signals.set(path, value);
6106
+ }
6107
+ }
6108
+
6109
+ if (normalized.cache?.browser) {
6110
+ if (!cache || typeof cache.restore !== "function") {
6111
+ throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
6112
+ }
6113
+ cache.restore(normalized.cache.browser);
6114
+ }
6115
+
6116
+ if (normalized.html != null) {
6117
+ loader.swap(normalized.boundary, normalized.html);
6118
+ }
6119
+
6120
+ await flushScheduler(scheduler, normalized.scope);
6121
+
6122
+ if (normalized.redirect) {
6123
+ const result = {
6124
+ status: "redirected",
6125
+ boundary: normalized.boundary,
6126
+ seq: normalized.seq,
6127
+ redirect: normalized.redirect
6128
+ };
6129
+ await followRedirect(normalized.redirect, router, loader);
6130
+ record.applied += 1;
6131
+ record.lastSeq = normalized.seq;
6132
+ record.lastStatus = result.status;
6133
+ remember(result);
6134
+ onApply?.(result, patch);
6135
+ return result;
6136
+ }
6137
+
6138
+ const result = {
6139
+ status: "applied",
6140
+ boundary: normalized.boundary,
6141
+ seq: normalized.seq
6142
+ };
6143
+ record.applied += 1;
6144
+ record.lastSeq = normalized.seq;
6145
+ record.lastStatus = result.status;
6146
+ remember(result);
6147
+ onApply?.(result, patch);
6148
+ return result;
6149
+ }
6150
+
6083
6151
  function boundaryRecord(boundary) {
6084
6152
  if (!boundaries.has(boundary)) {
6085
6153
  boundaries.set(boundary, {
@@ -6087,7 +6155,8 @@ const __boundaryReceiverModule = (() => {
6087
6155
  applied: 0,
6088
6156
  ignored: 0,
6089
6157
  errored: 0,
6090
- lastStatus: undefined
6158
+ lastStatus: undefined,
6159
+ pending: undefined
6091
6160
  });
6092
6161
  }
6093
6162
  return boundaries.get(boundary);