@phosphorco/bb-identity 0.3.0 → 0.4.0
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/EDITING.md +131 -0
- package/NATIVE-SOURCES.md +222 -0
- package/README.md +18 -0
- package/dist/bb-entry-runtime.js +107 -87
- package/dist/client-entry-runtime.js +128 -36
- package/dist/editing-runtime.js +1845 -0
- package/dist/host-runtime.js +54 -29
- package/dist/index-runtime.js +21 -0
- package/dist/model-runtime.js +21 -0
- package/dist/native-runtime.js +476 -0
- package/dist/native-testing-runtime.js +11 -0
- package/dist/react-entry-runtime.js +253 -51
- package/dist/state-controller-runtime.js +96 -11
- package/dist/state-runtime.js +95 -11
- package/dist/testing-runtime.js +112 -92
- package/editing.d.ts +137 -0
- package/host.d.ts +1 -0
- package/native-testing.d.ts +15 -0
- package/native.d.ts +100 -0
- package/package.json +20 -3
- package/react.d.ts +16 -0
|
@@ -1584,6 +1584,9 @@ function sameVersion(a, b) {
|
|
|
1584
1584
|
return a.status === b.status && version(a).epoch === version(b).epoch && version(a).sequence === version(b).sequence;
|
|
1585
1585
|
}
|
|
1586
1586
|
function createIdentityState(options) {
|
|
1587
|
+
return createStateController(options);
|
|
1588
|
+
}
|
|
1589
|
+
function createStateController(options, editing = false) {
|
|
1587
1590
|
const address = frozen({ ...options.address });
|
|
1588
1591
|
const expected = frozen({ ...options.expected });
|
|
1589
1592
|
const definition = options.definition;
|
|
@@ -1617,6 +1620,12 @@ function createIdentityState(options) {
|
|
|
1617
1620
|
let closing = false;
|
|
1618
1621
|
let checkpoint = null;
|
|
1619
1622
|
let recoverySource = null;
|
|
1623
|
+
let checkpointError = null;
|
|
1624
|
+
let checkpointWriting = 0;
|
|
1625
|
+
let lastRejection = null;
|
|
1626
|
+
let rejectedGeneration = -1;
|
|
1627
|
+
let ownerFenced = false;
|
|
1628
|
+
let loadFailed = false;
|
|
1620
1629
|
let automaticSuspensions = 0;
|
|
1621
1630
|
let automaticEpoch = 0;
|
|
1622
1631
|
let persistQueue = Promise.resolve();
|
|
@@ -1632,7 +1641,7 @@ function createIdentityState(options) {
|
|
|
1632
1641
|
timer?.();
|
|
1633
1642
|
timer = null;
|
|
1634
1643
|
};
|
|
1635
|
-
const draft = () => base === null || desired === null ? null : frozen({ formatVersion: 1, key: { address, actor: expected.actor, ownerSession: options.ownerSession }, schemaVersion: definition.schemaVersion, acknowledged: base, desired: desired.value, localGeneration, inFlight: pending?.mutation ?? null });
|
|
1644
|
+
const draft = () => base === null || desired === null ? null : frozen({ formatVersion: 1, key: { address, actor: expected.actor, ownerSession: options.ownerSession }, schemaVersion: definition.schemaVersion, acknowledged: base, desired: desired.value, localGeneration, inFlight: pending?.mutation ?? null, ...editing ? { rejection: lastRejection, rejectedGeneration } : {} });
|
|
1636
1645
|
function emit() {
|
|
1637
1646
|
if (lifecycle === "detached" || lifecycle === "disposed")
|
|
1638
1647
|
snapshot = { status: "detached", address, draft: draft() };
|
|
@@ -1656,6 +1665,8 @@ function createIdentityState(options) {
|
|
|
1656
1665
|
function sessionCheck() {
|
|
1657
1666
|
if (!active())
|
|
1658
1667
|
return err("disposed", "This state controller is no longer active.");
|
|
1668
|
+
if (editing && ownerFenced)
|
|
1669
|
+
return err("stale-context", "This editing owner was retired.");
|
|
1659
1670
|
const result = options.currentSession();
|
|
1660
1671
|
if (!result.ok) {
|
|
1661
1672
|
block("identity-unavailable", result.error.message);
|
|
@@ -1667,6 +1678,8 @@ function createIdentityState(options) {
|
|
|
1667
1678
|
return { ok: false, error: session.error };
|
|
1668
1679
|
}
|
|
1669
1680
|
if (session.instanceId !== address.instanceId || session.actor.identity.key !== expected.actor || session.stamp !== expected.session) {
|
|
1681
|
+
if (editing)
|
|
1682
|
+
ownerFenced = true;
|
|
1670
1683
|
block("owner-changed", "The identity session changed.");
|
|
1671
1684
|
return err("stale-context", "The identity session changed.", "after-refresh");
|
|
1672
1685
|
}
|
|
@@ -1687,11 +1700,23 @@ function createIdentityState(options) {
|
|
|
1687
1700
|
const capturedEpoch = epoch;
|
|
1688
1701
|
const serial = ++persistSerial;
|
|
1689
1702
|
const work = persistQueue.then(async () => {
|
|
1703
|
+
checkpointWriting++;
|
|
1704
|
+
if (editing)
|
|
1705
|
+
emit();
|
|
1690
1706
|
const result = await safe(() => drafts.write(captured, checkpoint?.revision ?? null));
|
|
1691
|
-
|
|
1707
|
+
checkpointWriting--;
|
|
1708
|
+
if (result.ok) {
|
|
1692
1709
|
checkpoint = result.value;
|
|
1693
|
-
|
|
1694
|
-
|
|
1710
|
+
if (editing)
|
|
1711
|
+
checkpointError = null;
|
|
1712
|
+
} else if (active(capturedEpoch) && serial === persistSerial) {
|
|
1713
|
+
if (editing)
|
|
1714
|
+
checkpointError = result.error;
|
|
1715
|
+
else
|
|
1716
|
+
block("storage-error", result.error.message);
|
|
1717
|
+
}
|
|
1718
|
+
if (editing)
|
|
1719
|
+
emit();
|
|
1695
1720
|
return result;
|
|
1696
1721
|
});
|
|
1697
1722
|
persistQueue = work.then(() => {
|
|
@@ -1701,7 +1726,7 @@ function createIdentityState(options) {
|
|
|
1701
1726
|
}
|
|
1702
1727
|
function schedule() {
|
|
1703
1728
|
clearTimer();
|
|
1704
|
-
if (!active() || automaticSuspensions || closing || blocked || pending || !needsSave())
|
|
1729
|
+
if (editing || !active() || automaticSuspensions || closing || blocked || pending || !needsSave())
|
|
1705
1730
|
return;
|
|
1706
1731
|
timer = scheduler.schedule(debounce, () => {
|
|
1707
1732
|
timer = null;
|
|
@@ -1770,7 +1795,7 @@ function createIdentityState(options) {
|
|
|
1770
1795
|
return ok(undefined);
|
|
1771
1796
|
if (base && dirty()) {
|
|
1772
1797
|
if (sameVersion(base, read)) {
|
|
1773
|
-
if (!blocked?.conflict)
|
|
1798
|
+
if (!blocked?.conflict && (!editing || blocked?.reason !== "rejected"))
|
|
1774
1799
|
blocked = null;
|
|
1775
1800
|
emit();
|
|
1776
1801
|
schedule();
|
|
@@ -1780,7 +1805,8 @@ function createIdentityState(options) {
|
|
|
1780
1805
|
}
|
|
1781
1806
|
base = read;
|
|
1782
1807
|
desired = { value: valueClone(codec, currentValue(read)) };
|
|
1783
|
-
blocked
|
|
1808
|
+
if (!editing || blocked?.reason !== "rejected")
|
|
1809
|
+
blocked = null;
|
|
1784
1810
|
emit();
|
|
1785
1811
|
schedule();
|
|
1786
1812
|
return ok(undefined);
|
|
@@ -1806,14 +1832,17 @@ function createIdentityState(options) {
|
|
|
1806
1832
|
if (!live.ok)
|
|
1807
1833
|
return live;
|
|
1808
1834
|
if (!loaded.ok) {
|
|
1835
|
+
loadFailed = true;
|
|
1809
1836
|
block("storage-error", loaded.error.message);
|
|
1810
1837
|
return loaded;
|
|
1811
1838
|
}
|
|
1812
1839
|
const parsed = readClone(loaded.value);
|
|
1813
1840
|
if (!parsed.ok) {
|
|
1841
|
+
loadFailed = true;
|
|
1814
1842
|
block("storage-error", parsed.error.message);
|
|
1815
1843
|
return parsed;
|
|
1816
1844
|
}
|
|
1845
|
+
loadFailed = false;
|
|
1817
1846
|
return applyRemote(parsed.value, cause);
|
|
1818
1847
|
})();
|
|
1819
1848
|
activeLoad = work;
|
|
@@ -1827,6 +1856,10 @@ function createIdentityState(options) {
|
|
|
1827
1856
|
return work;
|
|
1828
1857
|
}
|
|
1829
1858
|
function outcomeCheck(outcome, operationId) {
|
|
1859
|
+
if (editing && outcome.status === "rejected")
|
|
1860
|
+
return outcome.operationId === operationId ? ok(frozen(outcome)) : err("incompatible", "Rejected receipt has another operation.");
|
|
1861
|
+
if (outcome.status === "rejected")
|
|
1862
|
+
return err("incompatible", "Rejection is not a legacy state outcome.");
|
|
1830
1863
|
const parsed = codecs.save.decode(structuredClone(codecs.save.encode(outcome)));
|
|
1831
1864
|
if (!parsed.ok)
|
|
1832
1865
|
return parsed;
|
|
@@ -1846,6 +1879,17 @@ function createIdentityState(options) {
|
|
|
1846
1879
|
pending = null;
|
|
1847
1880
|
loadId++;
|
|
1848
1881
|
blocked = null;
|
|
1882
|
+
if (outcome.status === "rejected") {
|
|
1883
|
+
lastRejection = outcome.evidence;
|
|
1884
|
+
rejectedGeneration = captured.generation;
|
|
1885
|
+
block("rejected", "The submitted edit was definitively rejected.");
|
|
1886
|
+
const saved2 = await persist();
|
|
1887
|
+
return saved2.ok ? err("invalid-input", "The submitted edit was definitively rejected.") : saved2;
|
|
1888
|
+
}
|
|
1889
|
+
if (editing && (outcome.status === "saved" || outcome.status === "unchanged")) {
|
|
1890
|
+
lastRejection = null;
|
|
1891
|
+
rejectedGeneration = -1;
|
|
1892
|
+
}
|
|
1849
1893
|
if (outcome.status === "saved" || outcome.status === "unchanged") {
|
|
1850
1894
|
base = { status: "present", envelope: outcome.envelope };
|
|
1851
1895
|
acknowledgedGeneration = Math.max(acknowledgedGeneration, captured.generation);
|
|
@@ -1877,6 +1921,8 @@ function createIdentityState(options) {
|
|
|
1877
1921
|
return reload("reconnect");
|
|
1878
1922
|
schedule();
|
|
1879
1923
|
}
|
|
1924
|
+
if (editing && snapshot.status === "blocked" && snapshot.reason === "conflict")
|
|
1925
|
+
return err("conflict", snapshot.message);
|
|
1880
1926
|
return ok(undefined);
|
|
1881
1927
|
}
|
|
1882
1928
|
function transmit(captured, automatic = false) {
|
|
@@ -1945,7 +1991,9 @@ function createIdentityState(options) {
|
|
|
1945
1991
|
const valid = sessionCheck();
|
|
1946
1992
|
if (!valid.ok)
|
|
1947
1993
|
return Promise.resolve(valid);
|
|
1948
|
-
if (
|
|
1994
|
+
if (editing && lastRejection && localGeneration <= rejectedGeneration)
|
|
1995
|
+
return Promise.resolve(err("invalid-input", "Correct the rejected draft before submitting a new operation."));
|
|
1996
|
+
if (blocked && !(editing && blocked.reason === "rejected" && localGeneration > rejectedGeneration))
|
|
1949
1997
|
return Promise.resolve(err("conflict", blocked.message));
|
|
1950
1998
|
if (activeSave)
|
|
1951
1999
|
return activeSave;
|
|
@@ -1954,6 +2002,8 @@ function createIdentityState(options) {
|
|
|
1954
2002
|
if (!base || !desired || !needsSave())
|
|
1955
2003
|
return Promise.resolve(ok(undefined));
|
|
1956
2004
|
const mutation = frozen({ kind: base.status === "empty" ? "initialize" : "replace", address, expectedVersion: { ...version(base) }, expected, ownerSession: options.ownerSession, localGeneration, operationId: newOperationId(), schemaVersion: definition.schemaVersion, value: valueClone(codec, desired.value) });
|
|
2005
|
+
if (editing && blocked?.reason === "rejected")
|
|
2006
|
+
blocked = null;
|
|
1957
2007
|
pending = { mutation, base, generation: localGeneration };
|
|
1958
2008
|
return transmit(pending, automatic);
|
|
1959
2009
|
}
|
|
@@ -1978,7 +2028,7 @@ function createIdentityState(options) {
|
|
|
1978
2028
|
const valid = sessionCheck();
|
|
1979
2029
|
if (!valid.ok)
|
|
1980
2030
|
return valid;
|
|
1981
|
-
const result = await safe(() => options.transport.reconcile({ address, expected, operationId: captured.mutation.operationId }, { signal: abort.signal }));
|
|
2031
|
+
const result = await safe(() => options.transport.reconcile({ address, expected, operationId: captured.mutation.operationId, mutation: captured.mutation }, { signal: abort.signal }));
|
|
1982
2032
|
if (!active(capturedEpoch) || pending !== captured)
|
|
1983
2033
|
return err("cancelled", "State reconciliation was superseded.");
|
|
1984
2034
|
const live = sessionCheck();
|
|
@@ -1990,7 +2040,7 @@ function createIdentityState(options) {
|
|
|
1990
2040
|
}
|
|
1991
2041
|
if (result.value.status === "final")
|
|
1992
2042
|
return applyOutcome(captured, result.value.outcome);
|
|
1993
|
-
if (result.value.status === "absent-final" && allowReplay) {
|
|
2043
|
+
if (result.value.status === "absent-final" && allowReplay && !editing) {
|
|
1994
2044
|
if (captured.mutation.expected.actor !== expected.actor || captured.mutation.expected.session !== expected.session) {
|
|
1995
2045
|
block("draft-recovery", "The original operation cannot be re-authored under a new session.");
|
|
1996
2046
|
return err("stale-context", "The original operation requires its original session.");
|
|
@@ -2006,6 +2056,13 @@ function createIdentityState(options) {
|
|
|
2006
2056
|
const valid = sessionCheck();
|
|
2007
2057
|
if (!valid.ok)
|
|
2008
2058
|
return valid;
|
|
2059
|
+
if (editing) {
|
|
2060
|
+
if (activeSave)
|
|
2061
|
+
return activeSave;
|
|
2062
|
+
if (pending)
|
|
2063
|
+
return reconcile(false);
|
|
2064
|
+
return sendNew();
|
|
2065
|
+
}
|
|
2009
2066
|
const goal = localGeneration;
|
|
2010
2067
|
let retries = 0;
|
|
2011
2068
|
if (activeSave) {
|
|
@@ -2059,6 +2116,27 @@ function createIdentityState(options) {
|
|
|
2059
2116
|
}
|
|
2060
2117
|
return {
|
|
2061
2118
|
getSnapshot: () => snapshot,
|
|
2119
|
+
inspect() {
|
|
2120
|
+
const retained = draft();
|
|
2121
|
+
const checkpointMatches = checkpoint !== null && retained !== null && checkpoint.draft.localGeneration === retained.localGeneration && JSON.stringify(checkpoint.draft.inFlight ? codecs.mutation.encode(checkpoint.draft.inFlight) : null) === JSON.stringify(retained.inFlight ? codecs.mutation.encode(retained.inFlight) : null) && JSON.stringify(checkpoint.draft.rejection ?? null) === JSON.stringify(retained.rejection ?? null) && JSON.stringify(codecs.read.encode(checkpoint.draft.acknowledged)) === JSON.stringify(codecs.read.encode(retained.acknowledged));
|
|
2122
|
+
return {
|
|
2123
|
+
draft: retained,
|
|
2124
|
+
base,
|
|
2125
|
+
desired: desired?.value,
|
|
2126
|
+
generation: localGeneration,
|
|
2127
|
+
pending: pending?.mutation ?? null,
|
|
2128
|
+
saving: networkActive,
|
|
2129
|
+
dirty: dirty(),
|
|
2130
|
+
saveEligible: needsSave() && (!lastRejection || localGeneration > rejectedGeneration),
|
|
2131
|
+
editable: active() && !closing && !ownerFenced && !loadFailed && base !== null && desired !== null && (!blocked || ["conflict", "indeterminate", "rejected", "draft-recovery"].includes(blocked.reason)),
|
|
2132
|
+
checkpoint: {
|
|
2133
|
+
status: !drafts ? "disabled" : checkpointWriting ? "writing" : checkpointError ? "failed" : checkpointMatches ? "backed-up" : "local-only",
|
|
2134
|
+
generation: checkpoint?.draft.localGeneration ?? null,
|
|
2135
|
+
revision: checkpoint?.revision ?? null,
|
|
2136
|
+
error: checkpointError
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
},
|
|
2062
2140
|
subscribe(listener) {
|
|
2063
2141
|
if (lifecycle === "disposed")
|
|
2064
2142
|
return () => {};
|
|
@@ -2094,7 +2172,7 @@ function createIdentityState(options) {
|
|
|
2094
2172
|
const valid = sessionCheck();
|
|
2095
2173
|
if (!valid.ok)
|
|
2096
2174
|
return valid;
|
|
2097
|
-
if (closing || blocked || snapshot.status !== "ready")
|
|
2175
|
+
if (closing || (editing ? ownerFenced || loadFailed || !base || !desired || !!blocked && !["conflict", "indeterminate", "rejected", "draft-recovery"].includes(blocked.reason) : blocked || snapshot.status !== "ready"))
|
|
2098
2176
|
return err("unavailable", "State is not editable.");
|
|
2099
2177
|
try {
|
|
2100
2178
|
desired = { value: valueClone(codec, value) };
|
|
@@ -2102,6 +2180,8 @@ function createIdentityState(options) {
|
|
|
2102
2180
|
return err("invalid-input", error instanceof Error ? error.message : "Invalid state.");
|
|
2103
2181
|
}
|
|
2104
2182
|
localGeneration++;
|
|
2183
|
+
if (editing && blocked?.conflict)
|
|
2184
|
+
blocked = { ...blocked, conflict: frozen({ ...blocked.conflict, token: newConflictToken(), local: desired.value }) };
|
|
2105
2185
|
emit();
|
|
2106
2186
|
schedule();
|
|
2107
2187
|
return ok(undefined);
|
|
@@ -2170,6 +2250,10 @@ function createIdentityState(options) {
|
|
|
2170
2250
|
return err("invalid-input", "Draft operation does not match its owner or generation.");
|
|
2171
2251
|
mutation = frozen(result.value);
|
|
2172
2252
|
}
|
|
2253
|
+
if (editing) {
|
|
2254
|
+
lastRejection = source.rejection ?? null;
|
|
2255
|
+
rejectedGeneration = source.rejectedGeneration ?? -1;
|
|
2256
|
+
}
|
|
2173
2257
|
clearTimer();
|
|
2174
2258
|
base = parsed.value;
|
|
2175
2259
|
desired = { value: valueClone(codec, source.desired) };
|
|
@@ -2345,6 +2429,35 @@ function createStateTransport(options) {
|
|
|
2345
2429
|
// client-state-binding-runtime.ts
|
|
2346
2430
|
var waiting = Object.freeze({ status: "waiting-for-identity" });
|
|
2347
2431
|
function bindIdentityState(options, retirementOwner) {
|
|
2432
|
+
return bindOwnedIdentityController({
|
|
2433
|
+
...options,
|
|
2434
|
+
hasPending: (state) => state.status === "ready" ? state.dirty || state.saving : (state.status === "blocked" || state.status === "detached") && state.draft !== null,
|
|
2435
|
+
create: (input) => options.create ? options.create(input) : createIdentityState({
|
|
2436
|
+
address: {
|
|
2437
|
+
instanceId: input.session.instanceId,
|
|
2438
|
+
pluginId: options.resource.pluginId,
|
|
2439
|
+
collection: options.resource.definition.collection,
|
|
2440
|
+
recordId: options.recordId,
|
|
2441
|
+
owner: input.subject.key
|
|
2442
|
+
},
|
|
2443
|
+
expected: { actor: input.session.actor.identity.key, session: input.session.stamp },
|
|
2444
|
+
ownerSession: input.ownerSession,
|
|
2445
|
+
definition: options.resource.definition,
|
|
2446
|
+
transport: createStateTransport({ connection: options.client.connection, resource: options.resource }),
|
|
2447
|
+
...options.drafts ? { drafts: options.drafts } : {},
|
|
2448
|
+
onConflict: options.onConflict,
|
|
2449
|
+
initializeEmpty: options.initializeEmpty && (options.editPolicy === "collaborators" || input.subject.key === input.session.actor.identity.key),
|
|
2450
|
+
...options.scheduler ? { scheduler: options.scheduler } : {},
|
|
2451
|
+
currentSession: () => options.client.currentSession()
|
|
2452
|
+
}),
|
|
2453
|
+
preservation: (controller, session) => options.resource && options.drafts ? { storage: options.drafts, scope: {
|
|
2454
|
+
address: controller.getSnapshot().address,
|
|
2455
|
+
actor: session.actor.identity.key,
|
|
2456
|
+
schemaVersion: options.resource.definition.schemaVersion
|
|
2457
|
+
} } : null
|
|
2458
|
+
}, retirementOwner);
|
|
2459
|
+
}
|
|
2460
|
+
function bindOwnedIdentityController(options, retirementOwner) {
|
|
2348
2461
|
let disposed = false;
|
|
2349
2462
|
let viewDisposed = false;
|
|
2350
2463
|
let current = null;
|
|
@@ -2360,7 +2473,7 @@ function bindIdentityState(options, retirementOwner) {
|
|
|
2360
2473
|
} catch {}
|
|
2361
2474
|
}
|
|
2362
2475
|
};
|
|
2363
|
-
const hasPending =
|
|
2476
|
+
const hasPending = options.hasPending;
|
|
2364
2477
|
const live = () => {
|
|
2365
2478
|
if (disposed || viewDisposed)
|
|
2366
2479
|
return null;
|
|
@@ -2454,34 +2567,13 @@ function bindIdentityState(options, retirementOwner) {
|
|
|
2454
2567
|
const owner = idCodec("owner-session").decode(`owner-${newOperationId()}`);
|
|
2455
2568
|
if (!owner.ok)
|
|
2456
2569
|
throw new Error(owner.error.message);
|
|
2457
|
-
const controller = options.create
|
|
2458
|
-
address: {
|
|
2459
|
-
instanceId: next.session.instanceId,
|
|
2460
|
-
pluginId: options.resource.pluginId,
|
|
2461
|
-
collection: options.resource.definition.collection,
|
|
2462
|
-
recordId: options.recordId,
|
|
2463
|
-
owner: next.subject.key
|
|
2464
|
-
},
|
|
2465
|
-
expected: { actor: next.session.actor.identity.key, session: next.session.stamp },
|
|
2466
|
-
ownerSession: owner.value,
|
|
2467
|
-
definition: options.resource.definition,
|
|
2468
|
-
transport: createStateTransport({ connection: options.client.connection, resource: options.resource }),
|
|
2469
|
-
...options.drafts ? { drafts: options.drafts } : {},
|
|
2470
|
-
onConflict: options.onConflict,
|
|
2471
|
-
initializeEmpty: next.writable && options.initializeEmpty,
|
|
2472
|
-
...options.scheduler ? { scheduler: options.scheduler } : {},
|
|
2473
|
-
currentSession: () => options.client.currentSession()
|
|
2474
|
-
});
|
|
2570
|
+
const controller = options.create({ session: next.session, subject: next.subject, ownerSession: owner.value });
|
|
2475
2571
|
if (live()?.key !== next.key) {
|
|
2476
2572
|
controller.dispose();
|
|
2477
2573
|
observeAgain = true;
|
|
2478
2574
|
continue;
|
|
2479
2575
|
}
|
|
2480
|
-
const preservation = options.
|
|
2481
|
-
address: controller.getSnapshot().address,
|
|
2482
|
-
actor: next.session.actor.identity.key,
|
|
2483
|
-
schemaVersion: options.resource.definition.schemaVersion
|
|
2484
|
-
} } : null;
|
|
2576
|
+
const preservation = options.preservation?.(controller, next.session) ?? null;
|
|
2485
2577
|
const entry = { key: next.key, recoveryKey: JSON.stringify([next.session.instanceId, next.session.actor.identity.key, next.subject.key]), owner: owner.value, controller, stop: () => {}, writable: next.writable, reconnect: null, preservation };
|
|
2486
2578
|
current = entry;
|
|
2487
2579
|
const update = () => {
|
|
@@ -2573,7 +2665,7 @@ function bindIdentityState(options, retirementOwner) {
|
|
|
2573
2665
|
if (!valid(entry) || transition.signal.aborted)
|
|
2574
2666
|
return err("stale-context", "View transition expired.");
|
|
2575
2667
|
pause = token;
|
|
2576
|
-
resume = entry.controller.suspendAutomaticDispatch();
|
|
2668
|
+
resume = entry.controller.suspendAutomaticDispatch?.() ?? (() => {});
|
|
2577
2669
|
if (transition.pending === "block" && hasPending(entry.controller.getSnapshot()))
|
|
2578
2670
|
return err("conflict", "New edits arrived before the view transition.");
|
|
2579
2671
|
const result = transition.pending === "flush" ? await entry.controller.flush() : transition.pending === "preserve" ? await entry.controller.checkpoint() : ok(undefined);
|