@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/CHANGELOG.md +27 -0
- package/browser.js +164 -95
- package/browser.min.js +1 -1
- package/browser.ts +164 -95
- package/browser.umd.js +164 -95
- package/browser.umd.min.js +1 -1
- package/framework.ts +164 -95
- package/package.json +1 -1
- package/server.js +164 -95
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.12 - 2026-06-18
|
|
4
|
+
|
|
5
|
+
- Deferred boundary receiver sequence commits until patch effects complete so
|
|
6
|
+
failed DOM swaps, scheduler flushes, redirects, or missing capabilities no
|
|
7
|
+
longer make the same streamed sequence stale.
|
|
8
|
+
- Serialized same-boundary patch application to keep concurrent patches from
|
|
9
|
+
committing out of order while preserving stale rejection after successful
|
|
10
|
+
commits.
|
|
11
|
+
- Added regression coverage for retryable DOM, scheduler, redirect, and
|
|
12
|
+
capability failures plus idempotent partial-effect replay.
|
|
13
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 186,064 B raw /
|
|
14
|
+
35,039 B gzip -> `browser.min.js` 79,042 B raw / 23,445 B gzip
|
|
15
|
+
(-107,022 B raw, -11,594 B gzip).
|
|
16
|
+
|
|
17
|
+
## 0.11.11 - 2026-06-18
|
|
18
|
+
|
|
19
|
+
- Serialized rejected async-signal snapshot errors to stable `name`, `message`,
|
|
20
|
+
and `code` records so JSON SSR snapshots preserve documented
|
|
21
|
+
`$error.message` bindings during browser activation.
|
|
22
|
+
- Normalized non-Error rejections into readable error records while omitting
|
|
23
|
+
arbitrary error object properties from snapshots by default.
|
|
24
|
+
- Added regression coverage for JSON snapshot round-trips, error code
|
|
25
|
+
preservation, non-Error rejection normalization, and SSR activation bindings.
|
|
26
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 185,440 B raw /
|
|
27
|
+
34,884 B gzip -> `browser.min.js` 78,754 B raw / 23,362 B gzip
|
|
28
|
+
(-106,686 B raw, -11,522 B gzip).
|
|
29
|
+
|
|
3
30
|
## 0.11.10 - 2026-06-18
|
|
4
31
|
|
|
5
32
|
- Routed automatic microtask scheduler flush failures through an explicit error
|
package/browser.js
CHANGED
|
@@ -131,9 +131,9 @@ const __asyncSignalModule = (() => {
|
|
|
131
131
|
|
|
132
132
|
snapshot() {
|
|
133
133
|
return {
|
|
134
|
-
value,
|
|
134
|
+
value: value === undefined && error !== null ? null : value,
|
|
135
135
|
loading,
|
|
136
|
-
error,
|
|
136
|
+
error: serializeAsyncError(error),
|
|
137
137
|
status,
|
|
138
138
|
version
|
|
139
139
|
};
|
|
@@ -150,7 +150,7 @@ const __asyncSignalModule = (() => {
|
|
|
150
150
|
cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
151
151
|
value = snapshot.value;
|
|
152
152
|
loading = Boolean(snapshot.loading);
|
|
153
|
-
error = snapshot.error
|
|
153
|
+
error = restoreAsyncError(snapshot.error);
|
|
154
154
|
status = typeof snapshot.status === "string" ? snapshot.status : inferStatus({ value, loading, error });
|
|
155
155
|
if (Number.isFinite(snapshot.version)) {
|
|
156
156
|
version = snapshot.version;
|
|
@@ -402,6 +402,51 @@ const __asyncSignalModule = (() => {
|
|
|
402
402
|
return value === undefined ? "idle" : "ready";
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
function serializeAsyncError(value) {
|
|
406
|
+
if (value == null) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const record = {
|
|
411
|
+
name: readErrorName(value),
|
|
412
|
+
message: readErrorMessage(value)
|
|
413
|
+
};
|
|
414
|
+
const code = readErrorCode(value);
|
|
415
|
+
if (code !== undefined) {
|
|
416
|
+
record.code = code;
|
|
417
|
+
}
|
|
418
|
+
return record;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function restoreAsyncError(value) {
|
|
422
|
+
return serializeAsyncError(value);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function readErrorName(value) {
|
|
426
|
+
if (value && typeof value === "object" && typeof value.name === "string" && value.name.length > 0) {
|
|
427
|
+
return value.name;
|
|
428
|
+
}
|
|
429
|
+
return "Error";
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function readErrorMessage(value) {
|
|
433
|
+
if (value instanceof Error) {
|
|
434
|
+
return value.message;
|
|
435
|
+
}
|
|
436
|
+
if (value && typeof value === "object" && typeof value.message === "string") {
|
|
437
|
+
return value.message;
|
|
438
|
+
}
|
|
439
|
+
return String(value);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function readErrorCode(value) {
|
|
443
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, "code")) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
const code = value.code;
|
|
447
|
+
return typeof code === "string" || typeof code === "number" ? code : undefined;
|
|
448
|
+
}
|
|
449
|
+
|
|
405
450
|
function attachCancel(signal, controller, onCancel) {
|
|
406
451
|
Object.defineProperty(signal, "cancel", {
|
|
407
452
|
configurable: true,
|
|
@@ -5770,101 +5815,25 @@ const __boundaryReceiverModule = (() => {
|
|
|
5770
5815
|
|
|
5771
5816
|
const normalized = validatePatch(patch);
|
|
5772
5817
|
const record = boundaryRecord(normalized.boundary);
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
};
|
|
5780
|
-
record.ignored += 1;
|
|
5781
|
-
record.lastStatus = result.status;
|
|
5782
|
-
remember(result);
|
|
5783
|
-
onIgnore?.(result, patch);
|
|
5784
|
-
return result;
|
|
5785
|
-
}
|
|
5786
|
-
|
|
5787
|
-
if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
|
|
5788
|
-
const result = {
|
|
5789
|
-
status: "ignored-destroyed",
|
|
5790
|
-
boundary: normalized.boundary,
|
|
5791
|
-
seq: normalized.seq,
|
|
5792
|
-
parentScope: normalized.parentScope
|
|
5793
|
-
};
|
|
5794
|
-
record.ignored += 1;
|
|
5795
|
-
record.lastStatus = result.status;
|
|
5796
|
-
remember(result);
|
|
5797
|
-
onIgnore?.(result, patch);
|
|
5798
|
-
return result;
|
|
5799
|
-
}
|
|
5800
|
-
|
|
5801
|
-
record.lastSeq = normalized.seq;
|
|
5802
|
-
|
|
5803
|
-
if (Object.hasOwn(normalized, "error")) {
|
|
5804
|
-
const error = toStableError(normalized.error);
|
|
5805
|
-
const result = {
|
|
5806
|
-
status: "errored",
|
|
5807
|
-
boundary: normalized.boundary,
|
|
5808
|
-
seq: normalized.seq,
|
|
5809
|
-
error
|
|
5810
|
-
};
|
|
5811
|
-
record.errored += 1;
|
|
5812
|
-
record.lastStatus = result.status;
|
|
5813
|
-
remember(result);
|
|
5814
|
-
onError?.(error, result, patch);
|
|
5815
|
-
if (throwOnError) {
|
|
5816
|
-
throw error;
|
|
5817
|
-
}
|
|
5818
|
-
return result;
|
|
5819
|
-
}
|
|
5818
|
+
let releasePending;
|
|
5819
|
+
const previousPending = record.pending ?? Promise.resolve();
|
|
5820
|
+
const pending = new Promise((resolve) => {
|
|
5821
|
+
releasePending = resolve;
|
|
5822
|
+
});
|
|
5823
|
+
record.pending = pending;
|
|
5820
5824
|
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
for (const [path, value] of Object.entries(normalized.signals)) {
|
|
5826
|
-
signals.set(path, value);
|
|
5825
|
+
try {
|
|
5826
|
+
await previousPending;
|
|
5827
|
+
if (destroyed) {
|
|
5828
|
+
throw new Error("Boundary receiver has been destroyed.");
|
|
5827
5829
|
}
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
if (
|
|
5832
|
-
|
|
5830
|
+
return await applyBoundaryPatch(record, normalized, patch);
|
|
5831
|
+
} finally {
|
|
5832
|
+
releasePending();
|
|
5833
|
+
if (record.pending === pending) {
|
|
5834
|
+
record.pending = undefined;
|
|
5833
5835
|
}
|
|
5834
|
-
cache.restore(normalized.cache.browser);
|
|
5835
|
-
}
|
|
5836
|
-
|
|
5837
|
-
if (normalized.html != null) {
|
|
5838
|
-
loader.swap(normalized.boundary, normalized.html);
|
|
5839
5836
|
}
|
|
5840
|
-
|
|
5841
|
-
await flushScheduler(scheduler, normalized.scope);
|
|
5842
|
-
|
|
5843
|
-
if (normalized.redirect) {
|
|
5844
|
-
await followRedirect(normalized.redirect, router, loader);
|
|
5845
|
-
const result = {
|
|
5846
|
-
status: "redirected",
|
|
5847
|
-
boundary: normalized.boundary,
|
|
5848
|
-
seq: normalized.seq,
|
|
5849
|
-
redirect: normalized.redirect
|
|
5850
|
-
};
|
|
5851
|
-
record.applied += 1;
|
|
5852
|
-
record.lastStatus = result.status;
|
|
5853
|
-
remember(result);
|
|
5854
|
-
onApply?.(result, patch);
|
|
5855
|
-
return result;
|
|
5856
|
-
}
|
|
5857
|
-
|
|
5858
|
-
const result = {
|
|
5859
|
-
status: "applied",
|
|
5860
|
-
boundary: normalized.boundary,
|
|
5861
|
-
seq: normalized.seq
|
|
5862
|
-
};
|
|
5863
|
-
record.applied += 1;
|
|
5864
|
-
record.lastStatus = result.status;
|
|
5865
|
-
remember(result);
|
|
5866
|
-
onApply?.(result, patch);
|
|
5867
|
-
return result;
|
|
5868
5837
|
},
|
|
5869
5838
|
|
|
5870
5839
|
inspect() {
|
|
@@ -5912,6 +5881,105 @@ const __boundaryReceiverModule = (() => {
|
|
|
5912
5881
|
|
|
5913
5882
|
return receiver;
|
|
5914
5883
|
|
|
5884
|
+
async function applyBoundaryPatch(record, normalized, patch) {
|
|
5885
|
+
if (normalized.seq <= record.lastSeq) {
|
|
5886
|
+
const result = {
|
|
5887
|
+
status: "ignored-stale",
|
|
5888
|
+
boundary: normalized.boundary,
|
|
5889
|
+
seq: normalized.seq,
|
|
5890
|
+
lastSeq: record.lastSeq
|
|
5891
|
+
};
|
|
5892
|
+
record.ignored += 1;
|
|
5893
|
+
record.lastStatus = result.status;
|
|
5894
|
+
remember(result);
|
|
5895
|
+
onIgnore?.(result, patch);
|
|
5896
|
+
return result;
|
|
5897
|
+
}
|
|
5898
|
+
|
|
5899
|
+
if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
|
|
5900
|
+
const result = {
|
|
5901
|
+
status: "ignored-destroyed",
|
|
5902
|
+
boundary: normalized.boundary,
|
|
5903
|
+
seq: normalized.seq,
|
|
5904
|
+
parentScope: normalized.parentScope
|
|
5905
|
+
};
|
|
5906
|
+
record.ignored += 1;
|
|
5907
|
+
record.lastStatus = result.status;
|
|
5908
|
+
remember(result);
|
|
5909
|
+
onIgnore?.(result, patch);
|
|
5910
|
+
return result;
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5913
|
+
if (Object.hasOwn(normalized, "error")) {
|
|
5914
|
+
const error = toStableError(normalized.error);
|
|
5915
|
+
const result = {
|
|
5916
|
+
status: "errored",
|
|
5917
|
+
boundary: normalized.boundary,
|
|
5918
|
+
seq: normalized.seq,
|
|
5919
|
+
error
|
|
5920
|
+
};
|
|
5921
|
+
record.lastSeq = normalized.seq;
|
|
5922
|
+
record.errored += 1;
|
|
5923
|
+
record.lastStatus = result.status;
|
|
5924
|
+
remember(result);
|
|
5925
|
+
onError?.(error, result, patch);
|
|
5926
|
+
if (throwOnError) {
|
|
5927
|
+
throw error;
|
|
5928
|
+
}
|
|
5929
|
+
return result;
|
|
5930
|
+
}
|
|
5931
|
+
|
|
5932
|
+
if (normalized.signals) {
|
|
5933
|
+
if (!signals || typeof signals.set !== "function") {
|
|
5934
|
+
throw new Error("Boundary patch includes signals, but no signal registry is available.");
|
|
5935
|
+
}
|
|
5936
|
+
for (const [path, value] of Object.entries(normalized.signals)) {
|
|
5937
|
+
signals.set(path, value);
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
if (normalized.cache?.browser) {
|
|
5942
|
+
if (!cache || typeof cache.restore !== "function") {
|
|
5943
|
+
throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
|
|
5944
|
+
}
|
|
5945
|
+
cache.restore(normalized.cache.browser);
|
|
5946
|
+
}
|
|
5947
|
+
|
|
5948
|
+
if (normalized.html != null) {
|
|
5949
|
+
loader.swap(normalized.boundary, normalized.html);
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5952
|
+
await flushScheduler(scheduler, normalized.scope);
|
|
5953
|
+
|
|
5954
|
+
if (normalized.redirect) {
|
|
5955
|
+
const result = {
|
|
5956
|
+
status: "redirected",
|
|
5957
|
+
boundary: normalized.boundary,
|
|
5958
|
+
seq: normalized.seq,
|
|
5959
|
+
redirect: normalized.redirect
|
|
5960
|
+
};
|
|
5961
|
+
await followRedirect(normalized.redirect, router, loader);
|
|
5962
|
+
record.applied += 1;
|
|
5963
|
+
record.lastSeq = normalized.seq;
|
|
5964
|
+
record.lastStatus = result.status;
|
|
5965
|
+
remember(result);
|
|
5966
|
+
onApply?.(result, patch);
|
|
5967
|
+
return result;
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5970
|
+
const result = {
|
|
5971
|
+
status: "applied",
|
|
5972
|
+
boundary: normalized.boundary,
|
|
5973
|
+
seq: normalized.seq
|
|
5974
|
+
};
|
|
5975
|
+
record.applied += 1;
|
|
5976
|
+
record.lastSeq = normalized.seq;
|
|
5977
|
+
record.lastStatus = result.status;
|
|
5978
|
+
remember(result);
|
|
5979
|
+
onApply?.(result, patch);
|
|
5980
|
+
return result;
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5915
5983
|
function boundaryRecord(boundary) {
|
|
5916
5984
|
if (!boundaries.has(boundary)) {
|
|
5917
5985
|
boundaries.set(boundary, {
|
|
@@ -5919,7 +5987,8 @@ const __boundaryReceiverModule = (() => {
|
|
|
5919
5987
|
applied: 0,
|
|
5920
5988
|
ignored: 0,
|
|
5921
5989
|
errored: 0,
|
|
5922
|
-
lastStatus: undefined
|
|
5990
|
+
lastStatus: undefined,
|
|
5991
|
+
pending: undefined
|
|
5923
5992
|
});
|
|
5924
5993
|
}
|
|
5925
5994
|
return boundaries.get(boundary);
|