@loro-dev/streams-crdt 0.5.0 → 0.6.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/dist/flock.d.ts +1 -1
- package/dist/flock.js +16 -25
- package/dist/flock.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +1 -1
- package/dist/loro.js +1 -1
- package/dist/loro.js.map +1 -1
- package/dist/{stream-id-nPEdFo7c.js → stream-id-DBym_2Mj.js} +63 -43
- package/dist/stream-id-DBym_2Mj.js.map +1 -0
- package/dist/{stream-id-BCCfzKNm.d.ts → stream-id-Do4h2lOD.d.ts} +38 -9
- package/package.json +1 -1
- package/dist/stream-id-nPEdFo7c.js.map +0 -1
package/dist/flock.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-
|
|
1
|
+
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-Do4h2lOD.js";
|
|
2
2
|
import { ExportBundle, Flock, VersionVector, VersionVector as VersionVector$1 } from "@loro-dev/flock-wasm";
|
|
3
3
|
|
|
4
4
|
//#region src/flock.d.ts
|
package/dist/flock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-DBym_2Mj.js";
|
|
2
2
|
import { Flock } from "@loro-dev/flock-wasm";
|
|
3
3
|
//#region src/flock.ts
|
|
4
4
|
function isAfter(entry, current) {
|
|
@@ -75,6 +75,18 @@ function batchImportBundles(importFn, bundles) {
|
|
|
75
75
|
for (let i = 1; i < bundles.length; i++) Object.assign(combined.entries, bundles[i].entries);
|
|
76
76
|
importFn(combined);
|
|
77
77
|
}
|
|
78
|
+
function applyBundleUpdates(importFn, updates, remoterVersion) {
|
|
79
|
+
let merged = remoterVersion;
|
|
80
|
+
const bundles = [];
|
|
81
|
+
for (const update of updates) {
|
|
82
|
+
const bundle = JSON.parse(new TextDecoder().decode(update));
|
|
83
|
+
if (!hasEntries(bundle)) continue;
|
|
84
|
+
bundles.push(bundle);
|
|
85
|
+
merged = mergeVersions(merged, extractVersionFromBundle(bundle));
|
|
86
|
+
}
|
|
87
|
+
batchImportBundles(importFn, bundles);
|
|
88
|
+
return merged;
|
|
89
|
+
}
|
|
78
90
|
/**
|
|
79
91
|
* Creates a `CrdtAdapter` for one Flock replica.
|
|
80
92
|
*/
|
|
@@ -87,6 +99,7 @@ function createFlockAdapter(flock) {
|
|
|
87
99
|
return {
|
|
88
100
|
emptyVersion: () => ({}),
|
|
89
101
|
mergeVersions,
|
|
102
|
+
flushLocalExports: () => localExportQueue,
|
|
90
103
|
exportSnapshot: () => flock.exportFile(),
|
|
91
104
|
applySnapshot: (snapshot) => {
|
|
92
105
|
flock.importFile(snapshot);
|
|
@@ -103,18 +116,7 @@ function createFlockAdapter(flock) {
|
|
|
103
116
|
version: exportedVersion
|
|
104
117
|
};
|
|
105
118
|
},
|
|
106
|
-
applyRemoteUpdates: (updates, remoterVersion) =>
|
|
107
|
-
let merged = remoterVersion;
|
|
108
|
-
const bundles = [];
|
|
109
|
-
for (const update of updates) {
|
|
110
|
-
const bundle = JSON.parse(new TextDecoder().decode(update));
|
|
111
|
-
if (!hasEntries(bundle)) continue;
|
|
112
|
-
bundles.push(bundle);
|
|
113
|
-
merged = mergeVersions(merged, extractVersionFromBundle(bundle));
|
|
114
|
-
}
|
|
115
|
-
batchImportBundles((b) => flock.importJson(b), bundles);
|
|
116
|
-
return merged;
|
|
117
|
-
},
|
|
119
|
+
applyRemoteUpdates: (updates, remoterVersion) => applyBundleUpdates((b) => flock.importJson(b), updates, remoterVersion),
|
|
118
120
|
createIsolatedAdapter: () => {
|
|
119
121
|
let isolated = new Flock("streams-crdt-isolated");
|
|
120
122
|
return {
|
|
@@ -122,18 +124,7 @@ function createFlockAdapter(flock) {
|
|
|
122
124
|
isolated = snapshot.byteLength === 0 ? new Flock("streams-crdt-isolated") : Flock.fromFile(snapshot, "streams-crdt-isolated");
|
|
123
125
|
return isolated.version();
|
|
124
126
|
},
|
|
125
|
-
applyRemoteUpdates: (updates, remoterVersion) =>
|
|
126
|
-
let merged = remoterVersion;
|
|
127
|
-
const bundles = [];
|
|
128
|
-
for (const update of updates) {
|
|
129
|
-
const bundle = JSON.parse(new TextDecoder().decode(update));
|
|
130
|
-
if (!hasEntries(bundle)) continue;
|
|
131
|
-
bundles.push(bundle);
|
|
132
|
-
merged = mergeVersions(merged, extractVersionFromBundle(bundle));
|
|
133
|
-
}
|
|
134
|
-
batchImportBundles((b) => isolated.importJson(b), bundles);
|
|
135
|
-
return merged;
|
|
136
|
-
}
|
|
127
|
+
applyRemoteUpdates: (updates, remoterVersion) => applyBundleUpdates((b) => isolated.importJson(b), updates, remoterVersion)
|
|
137
128
|
};
|
|
138
129
|
},
|
|
139
130
|
subscribeLocalUpdates: (listener) => {
|
package/dist/flock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flock.js","names":[],"sources":["../src/flock.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock-wasm\";\nimport type {\n EventBatch,\n ExportBundle,\n ExportRecord,\n VersionVector,\n VersionVectorEntry,\n} from \"@loro-dev/flock-wasm\";\nimport type { CrdtAdapter } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport\";\nexport * from \"./shared\";\nexport type { ExportBundle, VersionVector } from \"@loro-dev/flock-wasm\";\n\nfunction isAfter(\n entry: VersionVectorEntry,\n current: VersionVectorEntry,\n): boolean {\n return (\n entry.physicalTime > current.physicalTime ||\n (entry.physicalTime === current.physicalTime &&\n entry.logicalCounter > current.logicalCounter)\n );\n}\n\nfunction mergeVersions(\n base: VersionVector,\n incoming: VersionVector,\n): VersionVector {\n const merged: VersionVector = { ...base };\n for (const [peer, entry] of Object.entries(incoming)) {\n if (entry == null) {\n continue;\n }\n const current = merged[peer];\n if (current == null || isAfter(entry, current)) {\n merged[peer] = { ...entry };\n }\n }\n return merged;\n}\n\nfunction parseClock(\n record: ExportRecord,\n): { peerId: string; entry: VersionVectorEntry } | null {\n const firstComma = record.c.indexOf(\",\");\n const secondComma = record.c.indexOf(\",\", firstComma + 1);\n if (firstComma < 0 || secondComma < 0) {\n return null;\n }\n\n const physicalTime = Number(record.c.slice(0, firstComma));\n const logicalCounter = Number(record.c.slice(firstComma + 1, secondComma));\n if (!Number.isFinite(physicalTime) || !Number.isFinite(logicalCounter)) {\n return null;\n }\n\n return {\n peerId: record.c.slice(secondComma + 1),\n entry: { physicalTime, logicalCounter },\n };\n}\n\nfunction extractVersionFromBundle(bundle: ExportBundle): VersionVector {\n const out: VersionVector = {};\n for (const record of Object.values(bundle.entries)) {\n const parsed = parseClock(record);\n if (parsed == null) {\n continue;\n }\n const current = out[parsed.peerId];\n if (current == null || isAfter(parsed.entry, current)) {\n out[parsed.peerId] = parsed.entry;\n }\n }\n return out;\n}\n\nfunction hasEntries(bundle: ExportBundle): boolean {\n return Object.keys(bundle.entries).length > 0;\n}\n\n/**\n * Try to merge parsed bundles into a single combined bundle.\n * Falls back to per-bundle import when any key appears in more than one\n * bundle, so Flock's LWW conflict resolution sees every entry.\n */\nfunction batchImportBundles(\n importFn: (bundle: ExportBundle) => void,\n bundles: ExportBundle[],\n): void {\n if (bundles.length === 0) {\n return;\n }\n if (bundles.length === 1) {\n importFn(bundles[0]!);\n return;\n }\n\n // Detect key collisions across bundles.\n const seenKeys = new Set<string>();\n let hasCollision = false;\n for (const bundle of bundles) {\n for (const key of Object.keys(bundle.entries)) {\n if (seenKeys.has(key)) {\n hasCollision = true;\n break;\n }\n seenKeys.add(key);\n }\n if (hasCollision) {\n break;\n }\n }\n\n if (hasCollision) {\n for (const bundle of bundles) {\n importFn(bundle);\n }\n return;\n }\n\n // No collisions — merge entries into the first bundle and import once.\n const combined: ExportBundle = {\n ...bundles[0]!,\n entries: { ...bundles[0]!.entries },\n };\n for (let i = 1; i < bundles.length; i++) {\n Object.assign(combined.entries, bundles[i]!.entries);\n }\n importFn(combined);\n}\n\n/**\n * Creates a `CrdtAdapter` for one Flock replica.\n */\nexport function createFlockAdapter(flock: Flock): CrdtAdapter<VersionVector> {\n let lastExportedVersion: VersionVector = {};\n let localExportQueue = Promise.resolve();\n\n const recordExportFrontier = (version: VersionVector): void => {\n lastExportedVersion = mergeVersions(lastExportedVersion, version);\n };\n\n return {\n emptyVersion: () => ({}),\n mergeVersions,\n exportSnapshot: () => flock.exportFile(),\n applySnapshot: (snapshot) => {\n flock.importFile(snapshot);\n return flock.version();\n },\n exportUpdates: (fromVersion) => {\n const bundle = flock.exportJson(fromVersion);\n recordExportFrontier(fromVersion);\n if (!hasEntries(bundle)) {\n return undefined;\n }\n const exportedVersion = extractVersionFromBundle(bundle);\n recordExportFrontier(exportedVersion);\n return {\n updates: [new TextEncoder().encode(JSON.stringify(bundle))],\n version: exportedVersion,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n let merged = remoterVersion;\n const bundles: ExportBundle[] = [];\n for (const update of updates) {\n const bundle = JSON.parse(\n new TextDecoder().decode(update),\n ) as ExportBundle;\n if (!hasEntries(bundle)) {\n continue;\n }\n bundles.push(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n batchImportBundles((b) => flock.importJson(b), bundles);\n return merged;\n },\n createIsolatedAdapter: () => {\n let isolated = new Flock(\"streams-crdt-isolated\");\n return {\n applySnapshot: (snapshot) => {\n isolated =\n snapshot.byteLength === 0\n ? new Flock(\"streams-crdt-isolated\")\n : Flock.fromFile(snapshot, \"streams-crdt-isolated\");\n return isolated.version();\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n let merged = remoterVersion;\n const bundles: ExportBundle[] = [];\n for (const update of updates) {\n const bundle = JSON.parse(\n new TextDecoder().decode(update),\n ) as ExportBundle;\n if (!hasEntries(bundle)) {\n continue;\n }\n bundles.push(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n batchImportBundles((b) => isolated.importJson(b), bundles);\n return merged;\n },\n };\n },\n subscribeLocalUpdates: (listener) => {\n const unsubscribe = flock.subscribe((batch) => {\n if (batch.source !== \"local\") {\n return;\n }\n const drain = async () => {\n const batchPayload = await flock.exportJson({\n from: lastExportedVersion,\n peerId: flock.peerId(),\n });\n if (!hasEntries(batchPayload)) {\n return;\n }\n const exportedVersion = extractVersionFromBundle(batchPayload);\n recordExportFrontier(exportedVersion);\n listener({\n updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],\n version: exportedVersion,\n });\n };\n\n localExportQueue = localExportQueue.then(drain).catch(() => {\n /* swallow to keep queue alive */\n });\n });\n return unsubscribe;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flock.js","names":[],"sources":["../src/flock.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock-wasm\";\nimport type {\n EventBatch,\n ExportBundle,\n ExportRecord,\n VersionVector,\n VersionVectorEntry,\n} from \"@loro-dev/flock-wasm\";\nimport type { CrdtAdapter } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport-runtime\";\nexport * from \"./shared\";\nexport type { ExportBundle, VersionVector } from \"@loro-dev/flock-wasm\";\n\nfunction isAfter(\n entry: VersionVectorEntry,\n current: VersionVectorEntry,\n): boolean {\n return (\n entry.physicalTime > current.physicalTime ||\n (entry.physicalTime === current.physicalTime &&\n entry.logicalCounter > current.logicalCounter)\n );\n}\n\nfunction mergeVersions(\n base: VersionVector,\n incoming: VersionVector,\n): VersionVector {\n const merged: VersionVector = { ...base };\n for (const [peer, entry] of Object.entries(incoming)) {\n if (entry == null) {\n continue;\n }\n const current = merged[peer];\n if (current == null || isAfter(entry, current)) {\n merged[peer] = { ...entry };\n }\n }\n return merged;\n}\n\nfunction parseClock(\n record: ExportRecord,\n): { peerId: string; entry: VersionVectorEntry } | null {\n const firstComma = record.c.indexOf(\",\");\n const secondComma = record.c.indexOf(\",\", firstComma + 1);\n if (firstComma < 0 || secondComma < 0) {\n return null;\n }\n\n const physicalTime = Number(record.c.slice(0, firstComma));\n const logicalCounter = Number(record.c.slice(firstComma + 1, secondComma));\n if (!Number.isFinite(physicalTime) || !Number.isFinite(logicalCounter)) {\n return null;\n }\n\n return {\n peerId: record.c.slice(secondComma + 1),\n entry: { physicalTime, logicalCounter },\n };\n}\n\nfunction extractVersionFromBundle(bundle: ExportBundle): VersionVector {\n const out: VersionVector = {};\n for (const record of Object.values(bundle.entries)) {\n const parsed = parseClock(record);\n if (parsed == null) {\n continue;\n }\n const current = out[parsed.peerId];\n if (current == null || isAfter(parsed.entry, current)) {\n out[parsed.peerId] = parsed.entry;\n }\n }\n return out;\n}\n\nfunction hasEntries(bundle: ExportBundle): boolean {\n return Object.keys(bundle.entries).length > 0;\n}\n\n/**\n * Try to merge parsed bundles into a single combined bundle.\n * Falls back to per-bundle import when any key appears in more than one\n * bundle, so Flock's LWW conflict resolution sees every entry.\n */\nfunction batchImportBundles(\n importFn: (bundle: ExportBundle) => void,\n bundles: ExportBundle[],\n): void {\n if (bundles.length === 0) {\n return;\n }\n if (bundles.length === 1) {\n importFn(bundles[0]!);\n return;\n }\n\n // Detect key collisions across bundles.\n const seenKeys = new Set<string>();\n let hasCollision = false;\n for (const bundle of bundles) {\n for (const key of Object.keys(bundle.entries)) {\n if (seenKeys.has(key)) {\n hasCollision = true;\n break;\n }\n seenKeys.add(key);\n }\n if (hasCollision) {\n break;\n }\n }\n\n if (hasCollision) {\n for (const bundle of bundles) {\n importFn(bundle);\n }\n return;\n }\n\n // No collisions — merge entries into the first bundle and import once.\n const combined: ExportBundle = {\n ...bundles[0]!,\n entries: { ...bundles[0]!.entries },\n };\n for (let i = 1; i < bundles.length; i++) {\n Object.assign(combined.entries, bundles[i]!.entries);\n }\n importFn(combined);\n}\n\nfunction applyBundleUpdates(\n importFn: (bundle: ExportBundle) => void,\n updates: Uint8Array[],\n remoterVersion: VersionVector,\n): VersionVector {\n let merged = remoterVersion;\n const bundles: ExportBundle[] = [];\n for (const update of updates) {\n const bundle = JSON.parse(\n new TextDecoder().decode(update),\n ) as ExportBundle;\n if (!hasEntries(bundle)) {\n continue;\n }\n bundles.push(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n batchImportBundles(importFn, bundles);\n return merged;\n}\n\n/**\n * Creates a `CrdtAdapter` for one Flock replica.\n */\nexport function createFlockAdapter(flock: Flock): CrdtAdapter<VersionVector> {\n let lastExportedVersion: VersionVector = {};\n let localExportQueue = Promise.resolve();\n\n const recordExportFrontier = (version: VersionVector): void => {\n lastExportedVersion = mergeVersions(lastExportedVersion, version);\n };\n\n return {\n emptyVersion: () => ({}),\n mergeVersions,\n flushLocalExports: () => localExportQueue,\n exportSnapshot: () => flock.exportFile(),\n applySnapshot: (snapshot) => {\n flock.importFile(snapshot);\n return flock.version();\n },\n exportUpdates: (fromVersion) => {\n const bundle = flock.exportJson(fromVersion);\n recordExportFrontier(fromVersion);\n if (!hasEntries(bundle)) {\n return undefined;\n }\n const exportedVersion = extractVersionFromBundle(bundle);\n recordExportFrontier(exportedVersion);\n return {\n updates: [new TextEncoder().encode(JSON.stringify(bundle))],\n version: exportedVersion,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) =>\n applyBundleUpdates((b) => flock.importJson(b), updates, remoterVersion),\n createIsolatedAdapter: () => {\n let isolated = new Flock(\"streams-crdt-isolated\");\n return {\n applySnapshot: (snapshot) => {\n isolated =\n snapshot.byteLength === 0\n ? new Flock(\"streams-crdt-isolated\")\n : Flock.fromFile(snapshot, \"streams-crdt-isolated\");\n return isolated.version();\n },\n applyRemoteUpdates: (updates, remoterVersion) =>\n applyBundleUpdates((b) => isolated.importJson(b), updates, remoterVersion),\n };\n },\n subscribeLocalUpdates: (listener) => {\n const unsubscribe = flock.subscribe((batch) => {\n if (batch.source !== \"local\") {\n return;\n }\n const drain = async () => {\n const batchPayload = await flock.exportJson({\n from: lastExportedVersion,\n peerId: flock.peerId(),\n });\n if (!hasEntries(batchPayload)) {\n return;\n }\n const exportedVersion = extractVersionFromBundle(batchPayload);\n recordExportFrontier(exportedVersion);\n listener({\n updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],\n version: exportedVersion,\n });\n };\n\n localExportQueue = localExportQueue.then(drain).catch(() => {\n /* swallow to keep queue alive */\n });\n });\n return unsubscribe;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-
|
|
1
|
+
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-Do4h2lOD.js";
|
|
2
2
|
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-DBym_2Mj.js";
|
|
2
2
|
export { InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/loro.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-
|
|
1
|
+
import { A as RemoteCursorSaveSource, C as TransportSyncSuccess, D as IndexedDbRemoteCursorStore, E as InMemoryRemoteCursorStore, M as createInitialRemoteCursor, O as IndexedDbRemoteCursorStoreOptions, S as TransportSubscription, T as BeforeRemoteCursorSaveHook, _ as TransportCreateStreamSuccess, a as CrdtAdapter, b as TransportJoinParams, c as JsonObject, d as SnapshotCodec, f as SnapshotTransformHook, g as StreamsCrdtOptions, h as StreamsAuthProvider, i as StreamsCrdt, j as RemoteCursorStore, k as RemoteCursor, l as JsonValue, m as StreamsAuthContext, n as isValidBucketId, o as CrdtUpdateBatch, p as SnapshotUploadOptions, r as isValidRillId, s as IsolatedCrdtAdapter, t as createStreamUrl, u as Result, v as TransportDeleteStreamSuccess, w as BeforeRemoteCursorSaveContext, x as TransportRoomStatus, y as TransportError } from "./stream-id-Do4h2lOD.js";
|
|
2
2
|
import { LoroDoc } from "loro-crdt";
|
|
3
3
|
|
|
4
4
|
//#region src/loro.d.ts
|
package/dist/loro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-DBym_2Mj.js";
|
|
2
2
|
import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
|
|
3
3
|
//#region src/loro.ts
|
|
4
4
|
function batchImportUpdates(doc, updates) {
|
package/dist/loro.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loro.js","names":[],"sources":["../src/loro.ts"],"sourcesContent":["import {\n decodeImportBlobMeta,\n LoroDoc,\n VersionVector,\n} from \"loro-crdt\";\n\nimport type { CrdtAdapter, JsonObject } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport\";\nexport * from \"./shared\";\n\nfunction batchImportUpdates(doc: LoroDoc, updates: Uint8Array[]): void {\n if (\"batchImport\" in doc && typeof doc.batchImport === \"function\") {\n doc.batchImport(updates);\n } else {\n doc.importBatch(updates);\n }\n}\n\ntype PeerId = `${number}`;\ntype DocVersionRecord = Record<PeerId, number>;\n\nfunction toDocVersionRecord(version: VersionVector): DocVersionRecord {\n const out = {} as DocVersionRecord;\n for (const [peer, counter] of version.toJSON()) {\n out[peer as PeerId] = counter;\n }\n return out;\n}\n\nfunction toDocVersionMap(version: JsonObject): Map<PeerId, number> {\n const out = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(version)) {\n if (typeof counter === \"number\" && Number.isFinite(counter)) {\n out.set(peer as PeerId, counter);\n }\n }\n return out;\n}\n\nfunction mergeDocVersions(\n base: DocVersionRecord,\n incoming: DocVersionRecord,\n): DocVersionRecord {\n const merged = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(base)) {\n merged.set(peer as PeerId, counter);\n }\n for (const [peer, counter] of Object.entries(incoming)) {\n const peerId = peer as PeerId;\n merged.set(peerId, Math.max(merged.get(peerId) ?? 0, counter));\n }\n return toDocVersionRecord(VersionVector.parseJSON(merged));\n}\n\nfunction hasUpdatesSince(\n fromVersion: DocVersionRecord,\n currentVersion: DocVersionRecord,\n): boolean {\n for (const [peer, counter] of Object.entries(currentVersion)) {\n if (counter > (fromVersion[peer as PeerId] ?? 0)) {\n return true;\n }\n }\n return false;\n}\n\nfunction extractVersionFromUpdate(update: Uint8Array): DocVersionRecord | null {\n try {\n const meta = decodeImportBlobMeta(update, false);\n return toDocVersionRecord(meta.partialEndVersionVector);\n } catch {\n return null;\n }\n}\n\nfunction assertDocAttached(doc: LoroDoc): void {\n if (!doc.isDetached()) {\n return;\n }\n throw new Error(\n \"LoroDoc must be attached before using streams-crdt; \" +\n \"call checkoutToLatest() or attach() first\",\n );\n}\n\n/**\n * Creates a `CrdtAdapter` for one `LoroDoc`.\n *\n * The `LoroDoc` must be attached (not detached or checked out to a historical\n * version). If the document is detached, call `doc.checkoutToLatest()` or\n * `doc.attach()` before passing it here.\n *\n * @example\n * ```ts\n * const doc = new LoroDoc();\n * const adapter = createLoroDocAdapter(doc);\n * const transport = new StreamsCrdt({ streamUrl, adapter, auth });\n * ```\n */\nexport function createLoroDocAdapter(\n doc: LoroDoc,\n): CrdtAdapter<JsonObject> {\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n mergeDocVersions(base as DocVersionRecord, incoming as DocVersionRecord),\n exportSnapshot: () => {\n assertDocAttached(doc);\n return doc.export({ mode: \"snapshot\" });\n },\n applySnapshot: (snapshot) => {\n assertDocAttached(doc);\n doc.import(snapshot);\n return toDocVersionRecord(doc.version());\n },\n exportUpdates: (fromVersion) => {\n assertDocAttached(doc);\n const currentVersion = toDocVersionRecord(doc.version());\n if (\n !hasUpdatesSince(\n fromVersion as DocVersionRecord,\n currentVersion,\n )\n ) {\n return undefined;\n }\n const updates = [\n doc.export({\n mode: \"update\",\n from: VersionVector.parseJSON(toDocVersionMap(fromVersion)),\n }),\n ].filter((update) => update.byteLength > 0);\n if (updates.length === 0) {\n return undefined;\n }\n let version: DocVersionRecord = {};\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return {\n updates,\n version,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n assertDocAttached(doc);\n if (updates.length > 0) {\n batchImportUpdates(doc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n createIsolatedAdapter: () => {\n let isolatedDoc = new LoroDoc();\n return {\n applySnapshot: (snapshot) => {\n isolatedDoc = new LoroDoc();\n if (snapshot.byteLength > 0) {\n isolatedDoc.import(snapshot);\n }\n return toDocVersionRecord(isolatedDoc.version());\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n if (updates.length > 0) {\n batchImportUpdates(isolatedDoc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n };\n },\n subscribeLocalUpdates: (listener) => {\n assertDocAttached(doc);\n return doc.subscribeLocalUpdates((update) => {\n const version = extractVersionFromUpdate(update) ?? {};\n listener({ updates: [update], version });\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loro.js","names":[],"sources":["../src/loro.ts"],"sourcesContent":["import {\n decodeImportBlobMeta,\n LoroDoc,\n VersionVector,\n} from \"loro-crdt\";\n\nimport type { CrdtAdapter, JsonObject } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport-runtime\";\nexport * from \"./shared\";\n\nfunction batchImportUpdates(doc: LoroDoc, updates: Uint8Array[]): void {\n if (\"batchImport\" in doc && typeof doc.batchImport === \"function\") {\n doc.batchImport(updates);\n } else {\n doc.importBatch(updates);\n }\n}\n\ntype PeerId = `${number}`;\ntype DocVersionRecord = Record<PeerId, number>;\n\nfunction toDocVersionRecord(version: VersionVector): DocVersionRecord {\n const out = {} as DocVersionRecord;\n for (const [peer, counter] of version.toJSON()) {\n out[peer as PeerId] = counter;\n }\n return out;\n}\n\nfunction toDocVersionMap(version: JsonObject): Map<PeerId, number> {\n const out = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(version)) {\n if (typeof counter === \"number\" && Number.isFinite(counter)) {\n out.set(peer as PeerId, counter);\n }\n }\n return out;\n}\n\nfunction mergeDocVersions(\n base: DocVersionRecord,\n incoming: DocVersionRecord,\n): DocVersionRecord {\n const merged = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(base)) {\n merged.set(peer as PeerId, counter);\n }\n for (const [peer, counter] of Object.entries(incoming)) {\n const peerId = peer as PeerId;\n merged.set(peerId, Math.max(merged.get(peerId) ?? 0, counter));\n }\n return toDocVersionRecord(VersionVector.parseJSON(merged));\n}\n\nfunction hasUpdatesSince(\n fromVersion: DocVersionRecord,\n currentVersion: DocVersionRecord,\n): boolean {\n for (const [peer, counter] of Object.entries(currentVersion)) {\n if (counter > (fromVersion[peer as PeerId] ?? 0)) {\n return true;\n }\n }\n return false;\n}\n\nfunction extractVersionFromUpdate(update: Uint8Array): DocVersionRecord | null {\n try {\n const meta = decodeImportBlobMeta(update, false);\n return toDocVersionRecord(meta.partialEndVersionVector);\n } catch {\n return null;\n }\n}\n\nfunction assertDocAttached(doc: LoroDoc): void {\n if (!doc.isDetached()) {\n return;\n }\n throw new Error(\n \"LoroDoc must be attached before using streams-crdt; \" +\n \"call checkoutToLatest() or attach() first\",\n );\n}\n\n/**\n * Creates a `CrdtAdapter` for one `LoroDoc`.\n *\n * The `LoroDoc` must be attached (not detached or checked out to a historical\n * version). If the document is detached, call `doc.checkoutToLatest()` or\n * `doc.attach()` before passing it here.\n *\n * @example\n * ```ts\n * const doc = new LoroDoc();\n * const adapter = createLoroDocAdapter(doc);\n * const transport = new StreamsCrdt({ streamUrl, adapter, auth });\n * ```\n */\nexport function createLoroDocAdapter(\n doc: LoroDoc,\n): CrdtAdapter<JsonObject> {\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n mergeDocVersions(base as DocVersionRecord, incoming as DocVersionRecord),\n exportSnapshot: () => {\n assertDocAttached(doc);\n return doc.export({ mode: \"snapshot\" });\n },\n applySnapshot: (snapshot) => {\n assertDocAttached(doc);\n doc.import(snapshot);\n return toDocVersionRecord(doc.version());\n },\n exportUpdates: (fromVersion) => {\n assertDocAttached(doc);\n const currentVersion = toDocVersionRecord(doc.version());\n if (\n !hasUpdatesSince(\n fromVersion as DocVersionRecord,\n currentVersion,\n )\n ) {\n return undefined;\n }\n const updates = [\n doc.export({\n mode: \"update\",\n from: VersionVector.parseJSON(toDocVersionMap(fromVersion)),\n }),\n ].filter((update) => update.byteLength > 0);\n if (updates.length === 0) {\n return undefined;\n }\n let version: DocVersionRecord = {};\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return {\n updates,\n version,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n assertDocAttached(doc);\n if (updates.length > 0) {\n batchImportUpdates(doc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n createIsolatedAdapter: () => {\n let isolatedDoc = new LoroDoc();\n return {\n applySnapshot: (snapshot) => {\n isolatedDoc = new LoroDoc();\n if (snapshot.byteLength > 0) {\n isolatedDoc.import(snapshot);\n }\n return toDocVersionRecord(isolatedDoc.version());\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n if (updates.length > 0) {\n batchImportUpdates(isolatedDoc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n };\n },\n subscribeLocalUpdates: (listener) => {\n assertDocAttached(doc);\n return doc.subscribeLocalUpdates((update) => {\n const version = extractVersionFromUpdate(update) ?? {};\n listener({ updates: [update], version });\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1082,7 +1082,8 @@ function createJoinState(cursor, liveMode = "sse") {
|
|
|
1082
1082
|
cursor,
|
|
1083
1083
|
liveMode,
|
|
1084
1084
|
snapshot: createTransportSnapshotState(),
|
|
1085
|
-
started: Promise.resolve()
|
|
1085
|
+
started: Promise.resolve(),
|
|
1086
|
+
syncWaiters: []
|
|
1086
1087
|
};
|
|
1087
1088
|
}
|
|
1088
1089
|
async function disposeJoinState(state, options) {
|
|
@@ -1094,6 +1095,8 @@ async function disposeJoinState(state, options) {
|
|
|
1094
1095
|
state.unsubscribeLocal = void 0;
|
|
1095
1096
|
requestController?.abort();
|
|
1096
1097
|
unsubscribeLocal?.();
|
|
1098
|
+
const disposedError = /* @__PURE__ */ new Error("subscription closed");
|
|
1099
|
+
for (const waiter of state.syncWaiters.splice(0)) waiter.reject(disposedError);
|
|
1097
1100
|
options.onDisposed?.();
|
|
1098
1101
|
}
|
|
1099
1102
|
//#endregion
|
|
@@ -1156,8 +1159,8 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1156
1159
|
adapter: this.adapter,
|
|
1157
1160
|
config: snapshotUploadConfig,
|
|
1158
1161
|
encodeSnapshot: snapshotCodec?.compress,
|
|
1159
|
-
readSnapshotOffsetValue: () => this.readSnapshotOffsetValue(),
|
|
1160
|
-
putSnapshotAt: (snapshotOffset, snapshot) => this.putSnapshotAt(snapshotOffset, snapshot),
|
|
1162
|
+
readSnapshotOffsetValue: () => this.client.readSnapshotOffsetValue(),
|
|
1163
|
+
putSnapshotAt: (snapshotOffset, snapshot) => this.client.putSnapshotAt(snapshotOffset, snapshot),
|
|
1161
1164
|
enqueueExclusive: (task) => this.enqueueExclusive(task),
|
|
1162
1165
|
logDebug: (message, detail) => this.logDebug(message, detail),
|
|
1163
1166
|
logError: (message, error, detail) => this.logError(message, error, detail),
|
|
@@ -1199,7 +1202,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1199
1202
|
try {
|
|
1200
1203
|
return {
|
|
1201
1204
|
ok: true,
|
|
1202
|
-
value: await this.readSnapshotOffsetValue()
|
|
1205
|
+
value: await this.client.readSnapshotOffsetValue()
|
|
1203
1206
|
};
|
|
1204
1207
|
} catch (error) {
|
|
1205
1208
|
return {
|
|
@@ -1215,7 +1218,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1215
1218
|
try {
|
|
1216
1219
|
return {
|
|
1217
1220
|
ok: true,
|
|
1218
|
-
value: { created: await this.createStreamRequest() }
|
|
1221
|
+
value: { created: await this.client.createStreamRequest() }
|
|
1219
1222
|
};
|
|
1220
1223
|
} catch (error) {
|
|
1221
1224
|
return {
|
|
@@ -1233,7 +1236,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1233
1236
|
return {
|
|
1234
1237
|
ok: true,
|
|
1235
1238
|
value: { deleted: await this.enqueueExclusive(async () => {
|
|
1236
|
-
const deleted = await this.deleteStreamRequest();
|
|
1239
|
+
const deleted = await this.client.deleteStreamRequest();
|
|
1237
1240
|
await this.remoteCursorStore.delete?.(this.streamUrl);
|
|
1238
1241
|
this.producerEpoch = 0;
|
|
1239
1242
|
this.nextProducerSeq = 0;
|
|
@@ -1330,6 +1333,19 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1330
1333
|
return () => {
|
|
1331
1334
|
state.statusListeners.delete(listener);
|
|
1332
1335
|
};
|
|
1336
|
+
},
|
|
1337
|
+
waitUntilSynced: async () => {
|
|
1338
|
+
await this.adapter.flushLocalExports?.();
|
|
1339
|
+
if (state.closed) throw new Error("subscription closed");
|
|
1340
|
+
const remaining = state.pendingLocal.length;
|
|
1341
|
+
if (remaining === 0) return;
|
|
1342
|
+
return new Promise((resolve, reject) => {
|
|
1343
|
+
state.syncWaiters.push({
|
|
1344
|
+
remaining,
|
|
1345
|
+
resolve,
|
|
1346
|
+
reject
|
|
1347
|
+
});
|
|
1348
|
+
});
|
|
1333
1349
|
}
|
|
1334
1350
|
};
|
|
1335
1351
|
}
|
|
@@ -1419,13 +1435,13 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1419
1435
|
return caughtUp;
|
|
1420
1436
|
} catch (error) {
|
|
1421
1437
|
if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) throw error;
|
|
1422
|
-
await this.createStreamRequest();
|
|
1438
|
+
await this.client.createStreamRequest();
|
|
1423
1439
|
return await this.bootstrapState(this.createInitialCursor());
|
|
1424
1440
|
}
|
|
1425
1441
|
}
|
|
1426
1442
|
async bootstrapState(cursor, options = {}) {
|
|
1427
1443
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
1428
|
-
const result = await this.bootstrap(
|
|
1444
|
+
const result = await this.client.bootstrap();
|
|
1429
1445
|
let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset);
|
|
1430
1446
|
let bootstrapCursor = result.streamCursor;
|
|
1431
1447
|
if (!result.upToDate) {
|
|
@@ -1458,7 +1474,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1458
1474
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
1459
1475
|
while (true) {
|
|
1460
1476
|
const requestOffset = current.nextOffset;
|
|
1461
|
-
const response = await this.readFromOffset(
|
|
1477
|
+
const response = await this.client.readFromOffset(requestOffset, currentStreamCursor, options.live);
|
|
1462
1478
|
if (response.kind === "gone") {
|
|
1463
1479
|
if (goneStrategy === "return-gone") return StreamsCrdt.GONE_CATCHUP;
|
|
1464
1480
|
return await this.bootstrapState({
|
|
@@ -1484,7 +1500,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1484
1500
|
nextProducerSeq: this.nextProducerSeq
|
|
1485
1501
|
};
|
|
1486
1502
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1487
|
-
const result = await this.appendUpdates(
|
|
1503
|
+
const result = await this.client.appendUpdates(batch.updates, producerSession);
|
|
1488
1504
|
if (result.kind === "ok") {
|
|
1489
1505
|
this.producerEpoch = result.ackEpoch;
|
|
1490
1506
|
this.nextProducerSeq = result.ackSeq + 1;
|
|
@@ -1538,6 +1554,17 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1538
1554
|
if (isolated == null) return null;
|
|
1539
1555
|
return this.createCursorManager(isolated, async (cursor) => cursor);
|
|
1540
1556
|
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Infers remote state via bootstrap with the given cursor manager,
|
|
1559
|
+
* exports and appends any local delta, then re-bootstraps to converge
|
|
1560
|
+
* persisted cursor and local CRDT state on server truth.
|
|
1561
|
+
*/
|
|
1562
|
+
async inferRemoteAndAppendLocal(bootstrapCursor, cursorManager) {
|
|
1563
|
+
const inferredRemote = await this.bootstrapState(bootstrapCursor, { cursorManager });
|
|
1564
|
+
const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
|
|
1565
|
+
if (localBatch.batch != null) await this.appendLocalBatch(inferredRemote.cursor, localBatch.batch, false);
|
|
1566
|
+
return await this.bootstrapState(bootstrapCursor);
|
|
1567
|
+
}
|
|
1541
1568
|
async recoverFromGoneWithLocalBootstrap(cursor) {
|
|
1542
1569
|
const isolatedCursorManager = await this.createIsolatedCursorManager();
|
|
1543
1570
|
const bootstrapCursor = {
|
|
@@ -1545,12 +1572,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1545
1572
|
nextOffset: "-1",
|
|
1546
1573
|
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
1547
1574
|
};
|
|
1548
|
-
if (isolatedCursorManager != null)
|
|
1549
|
-
const inferredRemote = await this.bootstrapState(bootstrapCursor, { cursorManager: isolatedCursorManager });
|
|
1550
|
-
const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
|
|
1551
|
-
if (localBatch.batch != null) await this.appendLocalBatch(inferredRemote.cursor, localBatch.batch, false);
|
|
1552
|
-
return await this.bootstrapState(bootstrapCursor);
|
|
1553
|
-
}
|
|
1575
|
+
if (isolatedCursorManager != null) return await this.inferRemoteAndAppendLocal(bootstrapCursor, isolatedCursorManager);
|
|
1554
1576
|
const preservedLocalSnapshot = this.adapter.exportSnapshot();
|
|
1555
1577
|
let restoredLocalSnapshot = false;
|
|
1556
1578
|
try {
|
|
@@ -1570,7 +1592,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1570
1592
|
while (!state.closed) try {
|
|
1571
1593
|
if (state.liveMode === "long-poll") {
|
|
1572
1594
|
const requestOffset = state.cursor.nextOffset;
|
|
1573
|
-
const response = await this.readFromOffset(
|
|
1595
|
+
const response = await this.client.readFromOffset(requestOffset, state.streamCursor, "long-poll", state.requestController?.signal);
|
|
1574
1596
|
if (response.kind === "gone") {
|
|
1575
1597
|
const bootstrapped = await this.bootstrapState({
|
|
1576
1598
|
...state.cursor,
|
|
@@ -1600,7 +1622,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1600
1622
|
const sseRequestOffset = state.cursor.nextOffset;
|
|
1601
1623
|
const pendingRemote = [];
|
|
1602
1624
|
let flushChain = Promise.resolve();
|
|
1603
|
-
const sseResult = await this.readFromOffsetSseItems(
|
|
1625
|
+
const sseResult = await this.client.readFromOffsetSseItems(sseRequestOffset, state.streamCursor, (updates, nextOffset, _upToDate, streamCursor) => {
|
|
1604
1626
|
pendingRemote.push({
|
|
1605
1627
|
updates,
|
|
1606
1628
|
nextOffset,
|
|
@@ -1732,6 +1754,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1732
1754
|
while (!state.closed) try {
|
|
1733
1755
|
state.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state.cursor, drained.batch));
|
|
1734
1756
|
for (let i = 0; i < drained.count; i += 1) state.pendingLocal.popFront();
|
|
1757
|
+
this.notifySyncWaiters(state, drained.count);
|
|
1735
1758
|
this.setJoinStatus(state, "joined");
|
|
1736
1759
|
this.snapshotManager.schedule(state);
|
|
1737
1760
|
break;
|
|
@@ -1742,11 +1765,13 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1742
1765
|
streamUrl: this.streamUrl,
|
|
1743
1766
|
code: classified.code
|
|
1744
1767
|
});
|
|
1768
|
+
this.rejectSyncWaiters(state, /* @__PURE__ */ new Error(`local append failed: ${classified.code}`));
|
|
1745
1769
|
this.setJoinStatus(state, "error");
|
|
1746
1770
|
return;
|
|
1747
1771
|
}
|
|
1748
1772
|
const maxAttempts = this.reconnectConfig.maxAttempts;
|
|
1749
1773
|
if (attempts >= maxAttempts) {
|
|
1774
|
+
this.rejectSyncWaiters(state, /* @__PURE__ */ new Error("local append retry budget exhausted"));
|
|
1750
1775
|
this.setJoinStatus(state, "error");
|
|
1751
1776
|
return;
|
|
1752
1777
|
}
|
|
@@ -1760,6 +1785,25 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1760
1785
|
state.flushingLocal = false;
|
|
1761
1786
|
}
|
|
1762
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Decrements all sync waiters by the number of flushed batches and
|
|
1790
|
+
* resolves any that have reached zero.
|
|
1791
|
+
*/
|
|
1792
|
+
notifySyncWaiters(state, flushedCount) {
|
|
1793
|
+
let i = 0;
|
|
1794
|
+
while (i < state.syncWaiters.length) {
|
|
1795
|
+
const waiter = state.syncWaiters[i];
|
|
1796
|
+
waiter.remaining -= flushedCount;
|
|
1797
|
+
if (waiter.remaining <= 0) {
|
|
1798
|
+
state.syncWaiters.splice(i, 1);
|
|
1799
|
+
waiter.resolve();
|
|
1800
|
+
} else i += 1;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
/** Rejects and removes all sync waiters. */
|
|
1804
|
+
rejectSyncWaiters(state, error) {
|
|
1805
|
+
for (const waiter of state.syncWaiters.splice(0)) waiter.reject(error);
|
|
1806
|
+
}
|
|
1763
1807
|
async saveRemoteCursor(cursor, source) {
|
|
1764
1808
|
return await persistRemoteCursor({
|
|
1765
1809
|
remoteCursorStore: this.remoteCursorStore,
|
|
@@ -1773,30 +1817,6 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1773
1817
|
state.status = status;
|
|
1774
1818
|
for (const listener of state.statusListeners) listener(status);
|
|
1775
1819
|
}
|
|
1776
|
-
async createStreamRequest() {
|
|
1777
|
-
return await this.client.createStreamRequest();
|
|
1778
|
-
}
|
|
1779
|
-
async deleteStreamRequest() {
|
|
1780
|
-
return await this.client.deleteStreamRequest();
|
|
1781
|
-
}
|
|
1782
|
-
async readFromOffsetSseItems(_streamUrl, offset, cursor, onControl, onOpen, signal) {
|
|
1783
|
-
return await this.client.readFromOffsetSseItems(offset, cursor, onControl, onOpen, signal);
|
|
1784
|
-
}
|
|
1785
|
-
async readFromOffset(_streamUrl, offset, cursor, live, signal) {
|
|
1786
|
-
return await this.client.readFromOffset(offset, cursor, live, signal);
|
|
1787
|
-
}
|
|
1788
|
-
async bootstrap(_streamUrl) {
|
|
1789
|
-
return await this.client.bootstrap();
|
|
1790
|
-
}
|
|
1791
|
-
async readSnapshotOffsetValue() {
|
|
1792
|
-
return await this.client.readSnapshotOffsetValue();
|
|
1793
|
-
}
|
|
1794
|
-
async putSnapshotAt(snapshotOffset, snapshot) {
|
|
1795
|
-
await this.client.putSnapshotAt(snapshotOffset, snapshot);
|
|
1796
|
-
}
|
|
1797
|
-
async appendUpdates(_streamUrl, updates, producerSession) {
|
|
1798
|
-
return await this.client.appendUpdates(updates, producerSession);
|
|
1799
|
-
}
|
|
1800
1820
|
enqueueExclusive(task) {
|
|
1801
1821
|
const run = this.opQueue.then(task, task);
|
|
1802
1822
|
this.opQueue = run.then(() => void 0, () => void 0);
|
|
@@ -1855,4 +1875,4 @@ function createStreamUrl(input) {
|
|
|
1855
1875
|
//#endregion
|
|
1856
1876
|
export { InMemoryRemoteCursorStore as a, StreamsCrdt as i, isValidBucketId as n, IndexedDbRemoteCursorStore as o, isValidRillId as r, createInitialRemoteCursor as s, createStreamUrl as t };
|
|
1857
1877
|
|
|
1858
|
-
//# sourceMappingURL=stream-id-
|
|
1878
|
+
//# sourceMappingURL=stream-id-DBym_2Mj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-id-DBym_2Mj.js","names":["createTransportJoinState","disposeTransportJoinState"],"sources":["../src/checkpoint.ts","../src/reconnect.ts","../src/items.ts","../src/transport-internal.ts","../src/transport-client.ts","../src/transport-cursor.ts","../src/deque.ts","../src/transport-snapshot.ts","../src/transport-room.ts","../src/transport-runtime.ts","../src/stream-id.ts"],"sourcesContent":["import type { JsonObject } from \"./types\";\n\n/**\n * Persisted replay progress for one stream.\n *\n * This is metadata only — it does **not** contain any CRDT update data.\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`.\n */\nexport interface RemoteCursor<TVersion extends JsonObject = JsonObject> {\n /** Identity of the stream this cursor belongs to. */\n readonly streamUrl: string;\n /** The offset to resume reading from on the next sync/join. */\n readonly nextOffset: string;\n /** The CRDT version that the server data up to `nextOffset` represents. */\n readonly serverLowerBoundVersion: TVersion;\n /** Timestamp (ms) when this cursor was last persisted. */\n readonly updatedAtMs: number;\n}\n\n/**\n * Storage for replay progress metadata.\n *\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`. Storing only the\n * cursor can make transport resume from `nextOffset` against an empty local\n * document, which is not sufficient for delta-only catchup.\n */\nexport interface RemoteCursorStore<TVersion extends JsonObject = JsonObject> {\n load(streamUrl: string): Promise<RemoteCursor<TVersion> | null>;\n save(cursor: RemoteCursor<TVersion>): Promise<void>;\n delete?(streamUrl: string): Promise<void>;\n}\n\n/**\n * Why the transport is advancing the remote cursor.\n */\nexport type RemoteCursorSaveSource = \"local\" | \"remote\" | \"bootstrap\";\n\n/**\n * Context passed to durability hooks before a remote cursor is persisted.\n *\n * Hooks should make local durable state correspond to `cursor` before this\n * cursor is stored, so a future process can restore the same CRDT state before\n * transport resumes from `cursor.nextOffset`.\n */\nexport type BeforeRemoteCursorSaveContext<\n TVersion extends JsonObject = JsonObject,\n> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly source: RemoteCursorSaveSource;\n};\n\n/**\n * Hook invoked before the transport persists replay progress.\n *\n * This is the place to persist local CRDT payloads or snapshots. If that local\n * durability step does not happen here, the remote cursor should not advance.\n */\nexport type BeforeRemoteCursorSaveHook<TVersion extends JsonObject = JsonObject> =\n (context: BeforeRemoteCursorSaveContext<TVersion>) => void | Promise<void>;\n\ntype BeforeRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n> = (context: BeforeRemoteCursorSaveContext<TVersion>) => boolean;\n\nexport function composeBeforeRemoteCursorSaveHooks<\n TVersion extends JsonObject = JsonObject,\n>(\n ...hooks: Array<BeforeRemoteCursorSaveHook<TVersion> | null | undefined>\n): BeforeRemoteCursorSaveHook<TVersion> | undefined {\n const active = hooks.filter(\n (hook): hook is BeforeRemoteCursorSaveHook<TVersion> => hook != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n return async (context) => {\n for (const hook of active) {\n await hook(context);\n }\n };\n}\n\nfunction withRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n>(\n predicate: BeforeRemoteCursorSavePredicate<TVersion>,\n hook: BeforeRemoteCursorSaveHook<TVersion>,\n): BeforeRemoteCursorSaveHook<TVersion> {\n return async (context) => {\n if (!predicate(context)) {\n return;\n }\n await hook(context);\n };\n}\n\nexport async function persistRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n remoteCursorStore: RemoteCursorStore<TVersion>;\n cursor: RemoteCursor<TVersion>;\n source: RemoteCursorSaveSource;\n beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n now?: () => number;\n}): Promise<RemoteCursor<TVersion>> {\n const next: RemoteCursor<TVersion> = {\n ...input.cursor,\n updatedAtMs: (input.now ?? Date.now)(),\n };\n if (input.beforeRemoteCursorSave != null) {\n await input.beforeRemoteCursorSave({\n cursor: next,\n source: input.source,\n });\n }\n await input.remoteCursorStore.save(next);\n return next;\n}\n\n/**\n * Construction options for `IndexedDbRemoteCursorStore`.\n */\nexport interface IndexedDbRemoteCursorStoreOptions {\n readonly dbName?: string;\n readonly storeName?: string;\n readonly dbVersion?: number;\n readonly indexedDb?: IDBFactory;\n}\n\nfunction key(streamUrl: string): string {\n return streamUrl;\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n\nfunction cloneJsonValue<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction cloneRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): RemoteCursor<TVersion> {\n return {\n ...cursor,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n };\n}\n\ntype StoredRemoteCursor = {\n readonly streamUrl: string;\n readonly nextOffset: string;\n readonly serverLowerBoundVersion: JsonObject;\n readonly updatedAtMs: number;\n};\n\nfunction fromStoredRemoteCursor<TVersion extends JsonObject>(\n record: StoredRemoteCursor,\n): RemoteCursor<TVersion> {\n if (!isJsonObject(record.serverLowerBoundVersion)) {\n throw new Error(\n \"invalid remote cursor record: serverLowerBoundVersion is not a JSON object\",\n );\n }\n return {\n streamUrl: record.streamUrl,\n nextOffset: record.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(\n record.serverLowerBoundVersion as TVersion,\n ),\n updatedAtMs: record.updatedAtMs,\n };\n}\n\nfunction toStoredRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): StoredRemoteCursor {\n return {\n streamUrl: cursor.streamUrl,\n nextOffset: cursor.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n updatedAtMs: cursor.updatedAtMs,\n };\n}\n\n/**\n * In-memory cursor store useful for tests or ephemeral runtimes.\n */\nexport class InMemoryRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly data = new Map<string, RemoteCursor<TVersion>>();\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const value = this.data.get(key(streamUrl));\n return value == null ? null : cloneRemoteCursor(value);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n this.data.set(key(cursor.streamUrl), cloneRemoteCursor(cursor));\n }\n\n async delete(streamUrl: string): Promise<void> {\n this.data.delete(key(streamUrl));\n }\n}\n\nconst DEFAULT_DB_NAME = \"streams-crdt\";\nconst DEFAULT_STORE_NAME = \"remote-cursors\";\nconst DEFAULT_DB_VERSION = 1;\n\n/**\n * IndexedDB-backed cursor store for browser clients that need replay progress persistence.\n *\n * This store only persists `RemoteCursor` metadata. It does not persist CRDT\n * payloads. Use it only when the application also persists the corresponding\n * local document state and restores that state before calling `sync()` or\n * `join()` after a reload.\n */\nexport class IndexedDbRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly dbName: string;\n private readonly storeName: string;\n private readonly dbVersion: number;\n private readonly indexedDb: IDBFactory | undefined;\n\n private db: IDBDatabase | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options?: IndexedDbRemoteCursorStoreOptions) {\n this.dbName = options?.dbName ?? DEFAULT_DB_NAME;\n this.storeName = options?.storeName ?? DEFAULT_STORE_NAME;\n this.dbVersion = options?.dbVersion ?? DEFAULT_DB_VERSION;\n this.indexedDb = options?.indexedDb ?? globalThis.indexedDB;\n }\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const db = await this.ensureDb();\n const record = await new Promise<StoredRemoteCursor | null>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readonly\");\n const store = tx.objectStore(this.storeName);\n const request = store.get(key(streamUrl));\n request.onsuccess = () => {\n resolve((request.result as StoredRemoteCursor | undefined) ?? null);\n };\n request.onerror = () => {\n reject(request.error ?? new Error(\"indexeddb load failed\"));\n };\n });\n return record == null ? null : fromStoredRemoteCursor<TVersion>(record);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n const db = await this.ensureDb();\n const record = toStoredRemoteCursor(cursor);\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.put(record, key(cursor.streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? request.error ?? new Error(\"indexeddb save failed\"));\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb save transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb save failed\"));\n });\n }\n\n async delete(streamUrl: string): Promise<void> {\n const db = await this.ensureDb();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.delete(key(streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(\n tx.error ?? request.error ?? new Error(\"indexeddb delete failed\"),\n );\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb delete transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb delete failed\"));\n });\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.db != null) {\n return this.db;\n }\n if (this.initPromise != null) {\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb is not available\");\n }\n return this.db;\n }\n if (this.indexedDb == null) {\n throw new Error(\"indexeddb is not available in this environment\");\n }\n\n this.initPromise = new Promise<void>((resolve, reject) => {\n const request = this.indexedDb!.open(this.dbName, this.dbVersion);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName);\n }\n };\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb open failed\"));\n request.onsuccess = () => {\n this.db = request.result;\n this.db.onversionchange = () => {\n this.db?.close();\n this.db = null;\n };\n resolve();\n };\n }).finally(() => {\n this.initPromise = null;\n });\n\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb open succeeded without a database handle\");\n }\n return this.db;\n }\n}\n\n/**\n * Creates a cursor value suitable for seeding a `RemoteCursorStore`.\n */\nexport function createInitialRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n streamUrl: string;\n nextOffset: string;\n serverLowerBoundVersion: TVersion;\n}): RemoteCursor<TVersion> {\n return {\n streamUrl: input.streamUrl,\n nextOffset: input.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(input.serverLowerBoundVersion),\n updatedAtMs: Date.now(),\n };\n}\n","/**\n * Retry and timeout settings used by `StreamsCrdt`.\n */\nexport interface ReconnectConfig {\n readonly delays: readonly number[];\n readonly jitterFraction: number;\n readonly maxAttempts: number;\n readonly connectTimeoutMs: number;\n readonly pollTimeoutMs: number;\n}\n\nexport const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {\n delays: [0, 500, 1000, 2000, 4000, 8000, 15000],\n jitterFraction: 0.2,\n maxAttempts: Infinity,\n connectTimeoutMs: 10000,\n pollTimeoutMs: 30000,\n};\n\n/**\n * Apply jitter of +/- fraction to a base delay.\n * E.g. applyJitter(1000, 0.2) returns a value in [800, 1200].\n */\nexport function applyJitter(baseMs: number, fraction: number): number {\n if (baseMs === 0) return 0;\n const jitter = baseMs * fraction * (2 * Math.random() - 1);\n return Math.max(0, Math.round(baseMs + jitter));\n}\n\n/**\n * Compute the retry delay for a given attempt number.\n * Uses the delays table from config, clamping at the last entry.\n */\nexport function computeRetryDelay(attempt: number, config: ReconnectConfig = DEFAULT_RECONNECT_CONFIG): number {\n const { delays, jitterFraction } = config;\n const index = Math.min(attempt, delays.length - 1);\n const base = delays[index];\n return applyJitter(base, jitterFraction);\n}\n","export const BINARY_CONTENT_TYPE = \"application/octet-stream\";\n\nexport function encodeItems(items: ReadonlyArray<Uint8Array>): Uint8Array {\n const total = items.reduce((sum, item) => sum + 4 + item.byteLength, 0);\n const out = new Uint8Array(total);\n const view = new DataView(out.buffer, out.byteOffset, out.byteLength);\n let cursor = 0;\n for (const item of items) {\n view.setUint32(cursor, item.byteLength, false);\n cursor += 4;\n out.set(item, cursor);\n cursor += item.byteLength;\n }\n return out;\n}\n\nexport function decodeItems(payload: Uint8Array): Uint8Array[] {\n const out: Uint8Array[] = [];\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n let cursor = 0;\n while (cursor < payload.byteLength) {\n if (cursor + 4 > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated length prefix\");\n }\n const len = view.getUint32(cursor, false);\n cursor += 4;\n if (cursor + len > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated item payload\");\n }\n out.push(payload.slice(cursor, cursor + len));\n cursor += len;\n }\n return out;\n}\n","import type { RemoteCursor } from \"./checkpoint\";\nimport { Deque } from \"./deque\";\nimport { decodeItems } from \"./items\";\nimport type { TransportSnapshotState } from \"./transport-snapshot\";\nimport type {\n CrdtUpdateBatch,\n JsonObject,\n JsonValue,\n MaybePromise,\n SnapshotCodec,\n SnapshotUploadOptions,\n StreamsAuthContext,\n StreamsAuthProvider,\n TransportError,\n TransportRoomStatus,\n} from \"./types\";\n\nexport type FetchLike = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport type ReadResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type BootstrapResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly snapshot: Uint8Array;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type SseControlData = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly streamCursor?: string;\n};\n\nexport type SseControlHandlerResult = void | \"pause\" | \"stop\";\n\nexport type SseItemsContinueResult = {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n readonly paused: boolean;\n};\n\nexport type SseItemsReadResult =\n | {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type SseItemsSession = {\n continue: (\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n ) => Promise<SseItemsContinueResult>;\n cancel: () => Promise<void>;\n};\n\nexport type SseItemsOpenResult =\n | { readonly kind: \"ok\"; readonly session: SseItemsSession }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type AppendResult =\n | {\n readonly kind: \"ok\";\n readonly ackEpoch: number;\n readonly ackSeq: number;\n readonly nextOffset: string;\n }\n | {\n readonly kind: \"stale-epoch\";\n readonly serverEpoch: number;\n }\n | {\n readonly kind: \"seq-gap\";\n readonly expectedSeq: number;\n };\n\nexport type ProducerSession = {\n readonly producerId: string;\n readonly producerEpoch: number;\n readonly nextProducerSeq: number;\n};\n\nexport type LiveReadMode = \"sse\" | \"long-poll\";\n\nexport type CursorSyncResult<TVersion extends JsonObject> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly streamCursor?: string;\n};\n\nexport type LocalExportResult<TVersion extends JsonObject> = {\n readonly batch?: CrdtUpdateBatch<TVersion>;\n readonly nextRefVersion: TVersion;\n};\n\nexport type SnapshotUploadRuntimeConfig = {\n readonly canUpload: () => Promise<boolean>;\n readonly debounceMs: number;\n readonly minBytesSinceRemoteSnapshot: bigint;\n};\n\nexport type SnapshotCodecRuntimeConfig = {\n readonly compress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n readonly decompress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n};\n\n/**\n * A pending waiter created by `waitUntilSynced()`.\n *\n * `remaining` tracks how many batches still need to be flushed before\n * this waiter resolves. It is decremented each time a batch is\n * successfully appended.\n */\nexport type SyncWaiter = {\n remaining: number;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nexport type JoinState<TVersion extends JsonObject> = {\n refCount: number;\n closed: boolean;\n status: \"connecting\" | TransportRoomStatus;\n readonly statusListeners: Set<\n (status: \"connecting\" | TransportRoomStatus) => void\n >;\n readonly pendingLocal: Deque<CrdtUpdateBatch<TVersion>>;\n flushingLocal: boolean;\n retryAttempt: number;\n retrySleepController: AbortController;\n requestController?: AbortController;\n unsubscribeLocal?: () => void;\n cursor: RemoteCursor<TVersion>;\n liveMode: LiveReadMode;\n streamCursor?: string;\n snapshot: TransportSnapshotState;\n started: Promise<void>;\n readonly syncWaiters: SyncWaiter[];\n};\n\nexport type JoinSyncResult<TVersion extends JsonObject> =\n CursorSyncResult<TVersion> & {\n readonly liveMode: LiveReadMode;\n readonly localExportRefVersion: TVersion;\n };\n\nexport type LastError = {\n code:\n | \"network\"\n | \"timeout\"\n | \"auth_failed\"\n | \"gone_410\"\n | \"protocol\"\n | \"internal\";\n message: string;\n retriable: boolean;\n atMs: number;\n};\n\nexport function createProducerId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n return `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\nexport class TimeoutError extends Error {\n readonly timeoutType: \"connect\" | \"poll\";\n readonly timeoutMs: number;\n\n constructor(timeoutType: \"connect\" | \"poll\", timeoutMs: number) {\n super(`TIMEOUT: ${timeoutType} request exceeded ${timeoutMs}ms`);\n this.timeoutType = timeoutType;\n this.timeoutMs = timeoutMs;\n }\n}\n\nexport class ProtocolError extends Error {\n readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.status = status;\n }\n}\n\nexport function getHeader(\n headers: Headers,\n lowerCaseName: string,\n titleCaseName: string,\n): string | null {\n return headers.get(lowerCaseName) ?? headers.get(titleCaseName);\n}\n\nfunction visibleHeaderNames(headers: Headers): string {\n const names: string[] = [];\n headers.forEach((_value, name) => {\n names.push(name);\n });\n if (names.length === 0) {\n return \"(none)\";\n }\n names.sort();\n return names.join(\", \");\n}\n\nexport function missingRequiredHeaderError(\n context: \"read\" | \"bootstrap\" | \"append\",\n requiredHeader: string,\n headers: Headers,\n): ProtocolError {\n return new ProtocolError(\n `${context} response missing ${requiredHeader}. ` +\n `If this is cross-origin, expose it via Access-Control-Expose-Headers (${requiredHeader.toLowerCase()}). ` +\n `Visible headers: ${visibleHeaderNames(headers)}`,\n );\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null;\n}\n\nexport function parseHeaderU64(value: string | null): number | null {\n if (value == null) {\n return null;\n }\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return null;\n }\n return parsed;\n}\n\nfunction decodeBase64Bytes(encoded: string): Uint8Array {\n // specs/ds-protocol.md allows servers to split base64 payloads across\n // multiple SSE data lines, and clients must remove the inserted CR/LF\n // characters before decoding.\n const normalized = encoded.replace(/[\\r\\n]/g, \"\");\n if (typeof Buffer !== \"undefined\") {\n return new Uint8Array(Buffer.from(normalized, \"base64\"));\n }\n if (typeof atob === \"function\") {\n const binary = atob(normalized);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n }\n throw new Error(\"base64 decoding is unavailable\");\n}\n\nfunction normalizeSseDataEncoding(\n dataEncoding: string | null | undefined,\n): \"base64\" | undefined {\n const normalized = dataEncoding?.trim().toLowerCase();\n if (normalized == null || normalized.length === 0) {\n return undefined;\n }\n if (normalized === \"base64\") {\n return \"base64\";\n }\n throw new ProtocolError(\n `unsupported stream-sse-data-encoding '${dataEncoding}'`,\n );\n}\n\nfunction decodeSseRawPayload(\n data: string,\n dataEncoding: \"base64\" | undefined,\n): Uint8Array {\n if (dataEncoding === \"base64\") {\n return decodeBase64Bytes(data);\n }\n return new TextEncoder().encode(data);\n}\n\nexport function decodeSseItemPayloads(\n data: string,\n dataEncoding?: string | null,\n): Uint8Array[] {\n const payload = decodeSseRawPayload(\n data,\n normalizeSseDataEncoding(dataEncoding),\n );\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\n/**\n * Decode a single multipart part body. If the payload contains\n * length-prefixed binary framed items, decode them. Otherwise\n * return the raw bytes as a single-element array. This mirrors the\n * try/fallback approach used by {@link decodeSseItemPayloads}.\n */\nexport function decodeMultipartPartItems(payload: Uint8Array): Uint8Array[] {\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\nexport function parseSseControlData(data: string): SseControlData {\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch {\n throw new ProtocolError(\"invalid sse control event JSON\");\n }\n if (!isObject(parsed) || typeof parsed.streamNextOffset !== \"string\") {\n throw new ProtocolError(\"sse control event missing streamNextOffset\");\n }\n const streamCursor =\n typeof parsed.streamCursor === \"string\" ? parsed.streamCursor : undefined;\n return {\n nextOffset: parsed.streamNextOffset,\n upToDate: parsed.upToDate === true,\n streamCursor,\n };\n}\n\nexport function isSseUnsupportedErrorText(message: string): boolean {\n const normalized = message.trim().toLowerCase();\n if (normalized.length === 0) {\n return false;\n }\n const mentionsSse =\n normalized.includes(\"sse\") || normalized.includes(\"text/event-stream\");\n const mentionsCompatibility =\n normalized.includes(\"unsupported\") ||\n normalized.includes(\"not support\") ||\n normalized.includes(\"incompatible\") ||\n normalized.includes(\"content type\");\n return mentionsSse && mentionsCompatibility;\n}\n\nexport function withCursorQuery(url: string, cursor?: string): string {\n if (cursor == null || cursor.length === 0) {\n return url;\n }\n const target = new URL(url);\n target.searchParams.set(\"cursor\", cursor);\n return target.toString();\n}\n\nexport function bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport function mergeAbortSignals(\n ...signals: ReadonlyArray<AbortSignal | null | undefined>\n): AbortSignal | undefined {\n const active = signals.filter(\n (signal): signal is AbortSignal => signal != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n if (active.length === 1) {\n return active[0];\n }\n if (typeof AbortSignal.any === \"function\") {\n return AbortSignal.any(active);\n }\n\n const controller = new AbortController();\n for (const signal of active) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n }\n return controller.signal;\n}\n\nexport function classifyFetchError(err: unknown): LastError {\n const message = err instanceof Error ? err.message : String(err);\n const status = err instanceof HttpError ? err.status : undefined;\n if (status === 401 || status === 403) {\n return { code: \"auth_failed\", message, retriable: false, atMs: Date.now() };\n }\n if (status === 410) {\n return { code: \"gone_410\", message, retriable: true, atMs: Date.now() };\n }\n if (status === 400 || status === 405 || status === 409) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (err instanceof ProtocolError) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (err instanceof TimeoutError) {\n return { code: \"timeout\", message, retriable: true, atMs: Date.now() };\n }\n if (err instanceof TypeError) {\n return { code: \"network\", message, retriable: true, atMs: Date.now() };\n }\n return { code: \"internal\", message, retriable: false, atMs: Date.now() };\n}\n\nexport function isStreamNotFoundError(error: unknown): boolean {\n return error instanceof HttpError && error.status === 404;\n}\n\nexport function toTransportError(error: unknown): TransportError {\n if (isStreamNotFoundError(error)) {\n return { code: \"stream_not_found\", retryable: false };\n }\n if (\n error instanceof HttpError &&\n (error.status === 401 || error.status === 403)\n ) {\n return {\n code: \"auth_failed\",\n retryable: false,\n status: error.status as 401 | 403,\n message: error.message,\n };\n }\n if (error instanceof HttpError && [400, 405, 409].includes(error.status)) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n };\n }\n if (error instanceof ProtocolError) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n };\n }\n if (error instanceof TimeoutError) {\n return {\n code: \"timeout\",\n retryable: true,\n phase: error.timeoutType,\n message: error.message,\n };\n }\n if (error instanceof HttpError && error.status === 410) {\n return { code: \"gone\", retryable: true, message: error.message };\n }\n if (error instanceof TypeError) {\n return {\n code: \"network_error\",\n retryable: true,\n message: error.message,\n };\n }\n if (error instanceof Error) {\n return {\n code: \"internal_error\",\n retryable: false,\n message: error.message,\n };\n }\n return {\n code: \"unknown\",\n retryable: false,\n message: String(error),\n };\n}\n\nexport function isReadNoProgress(\n requestOffset: string,\n result: ReadResult,\n): boolean {\n return (\n !result.upToDate &&\n result.updates.length === 0 &&\n result.nextOffset === requestOffset\n );\n}\n\nexport function isAuthOrProtocolError(error: unknown): boolean {\n return (\n error instanceof ProtocolError ||\n (error instanceof HttpError &&\n [400, 401, 403, 405, 409].includes(error.status))\n );\n}\n\nexport function normalizeAuthProvider(\n auth: StreamsAuthProvider | undefined,\n): ((context?: StreamsAuthContext) => Promise<string | undefined>) | undefined {\n if (auth == null) {\n return undefined;\n }\n if (typeof auth === \"string\") {\n const token = auth.trim();\n return async () => (token.length === 0 ? undefined : token);\n }\n return async (context) => {\n const value = await auth(context);\n const token = value?.trim() ?? \"\";\n return token.length === 0 ? undefined : token;\n };\n}\n\nexport function normalizeSnapshotUploadConfig(\n config: SnapshotUploadOptions | undefined,\n): SnapshotUploadRuntimeConfig | undefined {\n if (config == null || config.enabled === false) {\n return undefined;\n }\n if (!Number.isFinite(config.debounceMs ?? 10_000)) {\n throw new Error(\"snapshotUpload.debounceMs must be a finite number\");\n }\n if (!Number.isFinite(config.minBytesSinceRemoteSnapshot ?? 100 * 1024)) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be a finite number\",\n );\n }\n const debounceMs = Math.max(0, Math.floor(config.debounceMs ?? 10_000));\n const minBytes = config.minBytesSinceRemoteSnapshot ?? 100 * 1024;\n if (minBytes < 0) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be non-negative\",\n );\n }\n return {\n canUpload: async () => Boolean(await config.canUpload()),\n debounceMs,\n minBytesSinceRemoteSnapshot: BigInt(Math.floor(minBytes)),\n };\n}\n\nfunction assertSnapshotBytes(\n value: unknown,\n label: \"snapshotCodec.compress\" | \"snapshotCodec.decompress\",\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n throw new Error(`${label} must return Uint8Array`);\n}\n\nexport function normalizeSnapshotCodec(\n codec: SnapshotCodec | undefined,\n): SnapshotCodecRuntimeConfig | undefined {\n if (codec == null) {\n return undefined;\n }\n if (\n typeof codec.compress !== \"function\" ||\n typeof codec.decompress !== \"function\"\n ) {\n throw new Error(\n \"snapshotCodec.compress and snapshotCodec.decompress must be provided together\",\n );\n }\n return {\n compress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.compress(snapshot),\n \"snapshotCodec.compress\",\n ),\n decompress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.decompress(snapshot),\n \"snapshotCodec.decompress\",\n ),\n };\n}\n\nfunction jsonValueEquals(a: JsonValue, b: JsonValue): boolean {\n if (a === b) {\n return true;\n }\n if (typeof a !== typeof b || a == null || b == null) {\n return false;\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n if (!jsonValueEquals(a[i] as JsonValue, b[i] as JsonValue)) {\n return false;\n }\n }\n return true;\n }\n if (typeof a !== \"object\" || typeof b !== \"object\") {\n return false;\n }\n const aEntries = Object.entries(a).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n const bEntries = Object.entries(b).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n if (aEntries.length !== bEntries.length) {\n return false;\n }\n for (let i = 0; i < aEntries.length; i += 1) {\n const [aKey, aValue] = aEntries[i];\n const [bKey, bValue] = bEntries[i];\n if (aKey !== bKey || !jsonValueEquals(aValue as JsonValue, bValue as JsonValue)) {\n return false;\n }\n }\n return true;\n}\n\nexport function jsonObjectEquals(a: JsonObject, b: JsonObject): boolean {\n return jsonValueEquals(a, b);\n}\n\nexport function parseSnapshotByteOffset(\n offset: string | null | undefined,\n): bigint | null {\n if (offset == null || offset.length === 0 || offset === \"-1\") {\n return 0n;\n }\n if (!/^\\d+$/.test(offset)) {\n return null;\n }\n return BigInt(offset);\n}\n\nexport function resolveNextOffsetAfterConcurrentAdvance(\n requestOffset: string,\n currentOffset: string,\n responseNextOffset: string,\n): string {\n if (currentOffset === requestOffset) {\n return responseNextOffset;\n }\n if (currentOffset === responseNextOffset) {\n return currentOffset;\n }\n const current = parseSnapshotByteOffset(currentOffset);\n const incoming = parseSnapshotByteOffset(responseNextOffset);\n if (current != null && incoming != null) {\n return incoming > current ? responseNextOffset : currentOffset;\n }\n return currentOffset;\n}\n","import {\n StreamsClient,\n type StreamError,\n} from \"@loro-dev/streams-client\";\n\nimport { BINARY_CONTENT_TYPE, decodeItems, encodeItems } from \"./items\";\nimport type { ReconnectConfig } from \"./reconnect\";\nimport {\n AppendResult,\n BootstrapResult,\n decodeMultipartPartItems,\n decodeSseItemPayloads,\n FetchLike,\n HttpError,\n isSseUnsupportedErrorText,\n ProtocolError,\n parseSseControlData,\n type ProducerSession,\n ReadResult,\n type SseControlHandlerResult,\n type SseItemsOpenResult,\n type SseItemsReadResult,\n TimeoutError,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n JsonObject,\n MaybePromise,\n StreamsAuthContext,\n} from \"./types\";\n\ntype StreamsTransportClientOptions<TVersion extends JsonObject> = {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly authProvider?: (context?: StreamsAuthContext) => Promise<string | undefined>;\n readonly fetchImpl: FetchLike;\n readonly reconnectConfig: ReconnectConfig;\n};\n\n/**\n * Low-level HTTP and SSE client for one stream.\n *\n * This layer owns request headers, timeouts, and response parsing. Higher-level\n * join/sync state transitions stay in `transport-runtime.ts`.\n */\nexport class StreamsTransportClient<TVersion extends JsonObject> {\n readonly streamUrl: string;\n\n private readonly adapter: CrdtAdapter<TVersion>;\n private readonly client: StreamsClient;\n private readonly reconnectConfig: ReconnectConfig;\n\n constructor(options: StreamsTransportClientOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n this.client = new StreamsClient({\n url: options.streamUrl,\n auth: options.authProvider,\n fetch: options.fetchImpl as typeof globalThis.fetch,\n retry: {\n maxAttempts: 0,\n },\n timeout: {\n connectTimeoutMs: options.reconnectConfig.connectTimeoutMs,\n pollTimeoutMs: options.reconnectConfig.pollTimeoutMs,\n },\n });\n this.reconnectConfig = options.reconnectConfig;\n }\n\n async createStreamRequest(): Promise<boolean> {\n const adapterHeaders = this.adapter.getCreateStreamHeaders\n ? await this.adapter.getCreateStreamHeaders()\n : undefined;\n const result = await this.client.create({\n headers: adapterHeaders,\n });\n if (result.ok) {\n return result.result.created;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async deleteStreamRequest(): Promise<boolean> {\n const result = await this.client.delete();\n if (result.ok) {\n return result.result.deleted;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async readFromOffsetSseItems(\n offset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsReadResult> {\n return this.readItemsSse(\n offset,\n cursor,\n onControl,\n onOpen,\n signal,\n );\n }\n\n async readFromOffset(\n offset: string,\n cursor?: string,\n live?: \"long-poll\",\n signal?: AbortSignal,\n ): Promise<{ kind: \"gone\" } | { kind: \"ok\"; value: ReadResult }> {\n const result = await this.client.readOnce({\n offset,\n cursor,\n live,\n signal,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const payload = result.result.payload?.body ?? new Uint8Array();\n return {\n kind: \"ok\",\n value: {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n updates: payload.byteLength === 0 ? [] : decodeItems(payload),\n streamCursor: result.result.cursor,\n },\n };\n }\n\n async bootstrap(): Promise<BootstrapResult> {\n const result = await this.client.bootstrap();\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n\n return {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n snapshot: result.result.snapshot?.body ?? new Uint8Array(),\n updates: result.result.updates.flatMap((part) =>\n decodeMultipartPartItems(part.body)\n ),\n streamCursor: result.result.cursor,\n };\n }\n\n async readSnapshotOffsetValue(): Promise<string | null> {\n const result = await this.client.head();\n if (!result.ok) {\n if (result.result.code === \"not_found\") {\n return null;\n }\n throw this.toTransportFailure(result.result);\n }\n\n const snapshotOffset = result.result.snapshotOffset;\n return snapshotOffset == null || snapshotOffset.length === 0\n ? null\n : snapshotOffset;\n }\n\n async putSnapshotAt(\n snapshotOffset: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const result = await this.client.putSnapshot({\n offset: snapshotOffset,\n part: {\n contentType: \"application/octet-stream\",\n body: snapshot,\n },\n });\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n }\n\n async appendUpdates(\n updates: readonly Uint8Array[],\n producerSession: ProducerSession,\n ): Promise<AppendResult> {\n const result = await this.client.append({\n part: {\n contentType: BINARY_CONTENT_TYPE,\n body: encodeItems(updates),\n },\n producer: {\n producerId: producerSession.producerId,\n epoch: producerSession.producerEpoch,\n seq: producerSession.nextProducerSeq,\n },\n });\n if (result.ok) {\n return {\n kind: \"ok\",\n ackEpoch: result.result.producer?.epoch ?? producerSession.producerEpoch,\n ackSeq: result.result.producer?.seq ?? producerSession.nextProducerSeq,\n nextOffset: result.result.nextOffset,\n };\n }\n\n const error = result.result;\n if (\n error.code === \"conflict\" &&\n error.detail?.kind === \"producer_seq_gap\"\n ) {\n return { kind: \"seq-gap\", expectedSeq: error.detail.expectedSeq };\n }\n if (\n error.code === \"forbidden\" &&\n error.detail?.kind === \"stale_producer_epoch\"\n ) {\n return {\n kind: \"stale-epoch\",\n serverEpoch: error.detail.serverEpoch,\n };\n }\n throw this.toTransportFailure(error);\n }\n\n private toTransportFailure(error: StreamError): Error {\n switch (error.code) {\n case \"bad_request\":\n case \"unauthorized\":\n case \"forbidden\":\n case \"not_found\":\n case \"conflict\":\n case \"gone\":\n case \"rate_limited\":\n return new HttpError(error.message, error.status);\n case \"timeout\":\n return new TimeoutError(\n error.phase,\n error.phase === \"poll\"\n ? this.reconnectConfig.pollTimeoutMs\n : this.reconnectConfig.connectTimeoutMs,\n );\n case \"network_error\":\n return error.cause instanceof TypeError\n ? error.cause\n : new TypeError(error.message);\n case \"protocol_error\":\n return new ProtocolError(error.message, error.status);\n case \"unknown\":\n if (typeof error.status === \"number\") {\n return new HttpError(error.message, error.status);\n }\n if (error.cause instanceof TypeError) {\n return error.cause;\n }\n if (error.cause instanceof Error) {\n return error.cause;\n }\n return new Error(error.message);\n default: {\n const exhaustive: never = error;\n return new Error(String(exhaustive));\n }\n }\n }\n\n private async readItemsSse(\n initialOffset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsReadResult> {\n const opened = await this.openItemsSseSession(\n initialOffset,\n cursor,\n onOpen,\n signal,\n );\n if (opened.kind !== \"ok\") {\n return opened;\n }\n const result = await opened.session.continue(onControl);\n if (result.paused) {\n await opened.session.cancel();\n }\n return {\n kind: \"ok\",\n latestOffset: result.latestOffset,\n latestCursor: result.latestCursor,\n };\n }\n\n private async openItemsSseSession(\n initialOffset: string,\n cursor: string | undefined,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsOpenResult> {\n const result = await this.client.openSseSession({\n offset: initialOffset,\n cursor,\n signal,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n if (result.result.code === \"not_found\") {\n return { kind: \"not-found\" };\n }\n if (\n result.result.code === \"bad_request\" &&\n isSseUnsupportedErrorText(result.result.message)\n ) {\n return { kind: \"unsupported\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const session = result.result;\n onOpen?.();\n let pendingDataEvents: string[] = [];\n let latestOffset = session.nextOffset ?? initialOffset;\n let latestCursor = session.cursor;\n let exhausted = false;\n\n return {\n kind: \"ok\",\n session: {\n continue: async (nextOnControl) => {\n if (exhausted) {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n for await (const event of session.events) {\n if (event.event === \"data\") {\n pendingDataEvents.push(event.data);\n continue;\n }\n if (event.event !== \"control\") {\n continue;\n }\n const control = parseSseControlData(event.data);\n const updates: Uint8Array[] = [];\n for (const eventData of pendingDataEvents) {\n updates.push(\n ...decodeSseItemPayloads(eventData, session.dataEncoding),\n );\n }\n pendingDataEvents = [];\n const handlerResult = await nextOnControl(\n updates,\n control.nextOffset,\n control.upToDate,\n control.streamCursor,\n );\n latestOffset = control.nextOffset;\n if (control.streamCursor != null) {\n latestCursor = control.streamCursor;\n }\n if (handlerResult === \"stop\") {\n exhausted = true;\n await session.cancel();\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n if (handlerResult === \"pause\") {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: true,\n };\n }\n }\n exhausted = true;\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n },\n cancel: async () => {\n if (exhausted) {\n return;\n }\n exhausted = true;\n await session.cancel();\n },\n },\n };\n }\n}\n","import type {\n RemoteCursor,\n RemoteCursorSaveSource,\n} from \"./checkpoint\";\nimport {\n resolveNextOffsetAfterConcurrentAdvance,\n type ReadResult,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n JsonObject,\n MaybePromise,\n} from \"./types\";\n\ntype TransportCursorManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n};\n\nexport class TransportCursorManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n\n constructor(options: TransportCursorManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.decodeSnapshot = options.decodeSnapshot;\n this.saveRemoteCursor = options.saveRemoteCursor;\n }\n\n async applyBootstrapPayload(\n cursor: RemoteCursor<TVersion>,\n snapshot: Uint8Array,\n updates: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n let nextVersion = cursor.serverLowerBoundVersion;\n if (snapshot.byteLength > 0) {\n nextVersion = await this.adapter.applySnapshot(\n this.decodeSnapshot == null\n ? snapshot\n : await this.decodeSnapshot(snapshot),\n );\n }\n if (updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...updates],\n nextVersion,\n );\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n \"bootstrap\",\n );\n }\n\n async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n const snapshot = items[0] ?? new Uint8Array();\n return await this.applyBootstrapPayload(\n cursor,\n snapshot,\n items.slice(1),\n nextOffset,\n );\n }\n\n async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n [...items],\n nextOffset,\n \"remote\",\n undefined,\n );\n }\n\n async applyCatchupResult(\n cursor: RemoteCursor<TVersion>,\n result: ReadResult,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n result.updates,\n result.nextOffset,\n \"remote\",\n onRemoteVersionApplied,\n );\n }\n\n async applyLiveResult(\n cursor: RemoteCursor<TVersion>,\n requestOffset: string,\n result: {\n readonly nextOffset: string;\n readonly updates: readonly Uint8Array[];\n },\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n if (result.updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...result.updates],\n nextVersion,\n );\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset: resolveNextOffsetAfterConcurrentAdvance(\n requestOffset,\n cursor.nextOffset,\n result.nextOffset,\n ),\n serverLowerBoundVersion: nextVersion,\n },\n \"remote\",\n );\n }\n\n private async applyRemotePayload(\n cursor: RemoteCursor<TVersion>,\n updates: readonly Uint8Array[],\n nextOffset: string,\n source: RemoteCursorSaveSource,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n if (updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...updates],\n nextVersion,\n );\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n source,\n );\n }\n\n private async finalizeCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.saveRemoteCursor(cursor, source);\n }\n}\n","const DEFAULT_COMPACT_THRESHOLD = 64;\nconst DEFAULT_MIN_CAPACITY = 16;\n\nexport interface DequeOptions {\n readonly compactThreshold?: number;\n readonly minCapacity?: number;\n}\n\nexport class Deque<T> implements Iterable<T> {\n private buffer: Array<T | null | undefined>;\n private head = 0;\n private tail = 0;\n private readonly compactThreshold: number;\n private readonly minCapacity: number;\n\n constructor(values?: Iterable<T>, options: DequeOptions = {}) {\n this.compactThreshold = Math.max(\n 1,\n options.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD\n );\n this.minCapacity = Math.max(1, options.minCapacity ?? DEFAULT_MIN_CAPACITY);\n const seed = values == null ? [] : Array.from(values);\n this.buffer = seed.slice();\n this.tail = seed.length;\n }\n\n get length(): number {\n return this.tail - this.head;\n }\n\n isEmpty(): boolean {\n return this.length === 0;\n }\n\n clear(): void {\n this.buffer = [];\n this.head = 0;\n this.tail = 0;\n }\n\n peekFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n return value == null ? undefined : value;\n }\n\n peekBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.tail - 1];\n return value == null ? undefined : value;\n }\n\n at(index: number): T | undefined {\n const length = this.length;\n if (length === 0) {\n return undefined;\n }\n let offset = index;\n if (offset < 0) {\n offset += length;\n }\n if (offset < 0 || offset >= length) {\n return undefined;\n }\n const value = this.buffer[this.head + offset];\n return value == null ? undefined : value;\n }\n\n pushBack(value: T): number {\n this.ensureBackCapacity();\n this.buffer[this.tail] = value;\n this.tail += 1;\n return this.length;\n }\n\n pushFront(value: T): number {\n if (this.head === 0) {\n this.recenter(1, 0);\n }\n this.head -= 1;\n this.buffer[this.head] = value;\n return this.length;\n }\n\n popFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.head] = null;\n this.head += 1;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n popBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n this.tail -= 1;\n const value = this.buffer[this.tail];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.tail] = null;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n toArray(): T[] {\n const out: T[] = [];\n for (let i = this.head; i < this.tail; i += 1) {\n out.push(this.buffer[i] as T);\n }\n return out;\n }\n\n *[Symbol.iterator](): IterableIterator<T> {\n for (let i = this.head; i < this.tail; i += 1) {\n yield this.buffer[i] as T;\n }\n }\n\n private ensureBackCapacity(): void {\n if (this.tail < this.buffer.length) {\n return;\n }\n if (this.head > 0) {\n this.compactToFront();\n if (this.tail < this.buffer.length) {\n return;\n }\n }\n this.recenter(0, 1);\n }\n\n private recenter(extraFront: number, extraBack: number): void {\n const length = this.length;\n const needed = length + extraFront + extraBack;\n const currentCapacity = this.buffer.length;\n let nextCapacity = Math.max(currentCapacity, this.minCapacity);\n if (nextCapacity < needed) {\n nextCapacity = Math.max(nextCapacity * 2, needed, this.minCapacity);\n }\n const free = nextCapacity - length;\n const nextHead = Math.max(extraFront, Math.floor(free / 2));\n const next = new Array<T | null | undefined>(nextCapacity);\n for (let i = 0; i < length; i += 1) {\n next[nextHead + i] = this.buffer[this.head + i];\n }\n this.buffer = next;\n this.head = nextHead;\n this.tail = nextHead + length;\n }\n\n private compactIfNeeded(): void {\n const length = this.length;\n if (length === 0) {\n this.clear();\n return;\n }\n if (this.head < this.compactThreshold || this.head < length) {\n return;\n }\n this.compactToFront();\n }\n\n private compactToFront(): void {\n const length = this.length;\n this.buffer = this.buffer.slice(this.head, this.tail);\n this.head = 0;\n this.tail = length;\n }\n}\n","import {\n jsonObjectEquals,\n parseSnapshotByteOffset,\n type JoinState,\n type SnapshotUploadRuntimeConfig,\n} from \"./transport-internal\";\nimport type { CrdtAdapter, JsonObject, MaybePromise } from \"./types\";\n\nexport type TransportSnapshotState = {\n observedForeignWriter: boolean;\n taskScheduled: boolean;\n taskNeedsRerun: boolean;\n debounceTimer?: ReturnType<typeof setTimeout>;\n};\n\nexport function createTransportSnapshotState(): TransportSnapshotState {\n return {\n observedForeignWriter: false,\n taskScheduled: false,\n taskNeedsRerun: false,\n };\n}\n\ntype TransportSnapshotManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n readonly config?: SnapshotUploadRuntimeConfig;\n readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n readonly readSnapshotOffsetValue: () => Promise<string | null>;\n readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n readonly enqueueExclusive: <T>(task: () => MaybePromise<T>) => Promise<T>;\n readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n};\n\n/**\n * Manages debounce and upload gating for remote snapshots.\n *\n * All \"is this state still current / closed / reconnecting / blocked by a\n * foreign writer?\" checks live here so the runtime does not duplicate them.\n */\nexport class TransportSnapshotManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n private readonly config?: SnapshotUploadRuntimeConfig;\n private readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly readSnapshotOffsetValue: () => Promise<string | null>;\n private readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n private readonly enqueueExclusive: <T>(\n task: () => MaybePromise<T>,\n ) => Promise<T>;\n private readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n\n constructor(options: TransportSnapshotManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.config = options.config;\n this.encodeSnapshot = options.encodeSnapshot;\n this.readSnapshotOffsetValue = options.readSnapshotOffsetValue;\n this.putSnapshotAt = options.putSnapshotAt;\n this.enqueueExclusive = options.enqueueExclusive;\n this.logDebug = options.logDebug;\n this.logError = options.logError;\n this.isCurrentState = options.isCurrentState;\n }\n\n dispose(state: JoinState<TVersion>): void {\n this.clearDebounceTimer(state.snapshot);\n }\n\n observeRemoteVersion(\n state: JoinState<TVersion>,\n previousVersion: TVersion,\n nextVersion: TVersion,\n ): void {\n const snapshot = state.snapshot;\n if (snapshot.debounceTimer == null) {\n return;\n }\n if (!jsonObjectEquals(previousVersion, nextVersion)) {\n snapshot.observedForeignWriter = true;\n }\n }\n\n schedule(state: JoinState<TVersion>): void {\n const config = this.config;\n if (config == null || state.closed) {\n return;\n }\n const snapshot = state.snapshot;\n this.clearDebounceTimer(snapshot);\n snapshot.observedForeignWriter = false;\n snapshot.debounceTimer = setTimeout(() => {\n void this.handleDebounceExpiry(state);\n }, config.debounceMs);\n }\n\n private clearDebounceTimer(snapshot: TransportSnapshotState): void {\n if (snapshot.debounceTimer != null) {\n clearTimeout(snapshot.debounceTimer);\n snapshot.debounceTimer = undefined;\n }\n }\n\n private async handleDebounceExpiry(state: JoinState<TVersion>): Promise<void> {\n const snapshot = state.snapshot;\n snapshot.debounceTimer = undefined;\n if (this.getRunnableConfig(state) == null) {\n return;\n }\n if (snapshot.taskScheduled) {\n snapshot.taskNeedsRerun = true;\n return;\n }\n snapshot.taskScheduled = true;\n void this.enqueueExclusive(async () => {\n await this.maybeUpload(state);\n })\n .catch((error) => {\n this.logError(\"snapshot upload task failed\", error);\n })\n .finally(() => {\n snapshot.taskScheduled = false;\n if (snapshot.taskNeedsRerun && !state.closed) {\n snapshot.taskNeedsRerun = false;\n void this.handleDebounceExpiry(state);\n }\n });\n }\n\n private async maybeUpload(state: JoinState<TVersion>): Promise<void> {\n const config = await this.resolveUploadConfig(state);\n if (config == null) {\n return;\n }\n const tailBytes = parseSnapshotByteOffset(state.cursor.nextOffset);\n if (tailBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric offset token\", {\n offset: state.cursor.nextOffset,\n });\n return;\n }\n try {\n const remoteSnapshotOffset = await this.readSnapshotOffsetValue();\n const snapshotBytes = parseSnapshotByteOffset(remoteSnapshotOffset);\n if (snapshotBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric snapshot offset\", {\n offset: remoteSnapshotOffset,\n });\n return;\n }\n if (tailBytes <= snapshotBytes) {\n return;\n }\n if (tailBytes - snapshotBytes < config.minBytesSinceRemoteSnapshot) {\n return;\n }\n const snapshot = this.adapter.exportSnapshot();\n await this.putSnapshotAt(\n state.cursor.nextOffset,\n this.encodeSnapshot == null\n ? snapshot\n : await this.encodeSnapshot(snapshot),\n );\n } catch (error) {\n this.logError(\"snapshot upload failed\", error);\n }\n }\n\n private getRunnableConfig(\n state: JoinState<TVersion>,\n ): SnapshotUploadRuntimeConfig | undefined {\n const config = this.config;\n if (config == null || state.closed || !this.isCurrentState(state)) {\n return undefined;\n }\n if (\n state.snapshot.observedForeignWriter ||\n state.status === \"reconnecting\" ||\n state.status === \"disconnected\" ||\n state.status === \"error\"\n ) {\n return undefined;\n }\n return config;\n }\n\n private async resolveUploadConfig(\n state: JoinState<TVersion>,\n ): Promise<SnapshotUploadRuntimeConfig | undefined> {\n const config = this.getRunnableConfig(state);\n if (config == null) {\n return undefined;\n }\n if (!(await this.canUpload(config))) {\n return undefined;\n }\n return config;\n }\n\n private async canUpload(\n config: SnapshotUploadRuntimeConfig,\n ): Promise<boolean> {\n try {\n return Boolean(await config.canUpload());\n } catch (error) {\n this.logError(\"snapshot upload permission check failed\", error);\n return false;\n }\n }\n}\n","import { Deque } from \"./deque\";\nimport type { RemoteCursor } from \"./checkpoint\";\nimport type { JoinState, LiveReadMode } from \"./transport-internal\";\nimport {\n createTransportSnapshotState,\n type TransportSnapshotManager,\n} from \"./transport-snapshot\";\nimport type { JsonObject } from \"./types\";\n\nexport function createJoinState<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n liveMode: LiveReadMode = \"sse\",\n): JoinState<TVersion> {\n return {\n refCount: 0,\n closed: false,\n status: \"connecting\",\n statusListeners: new Set(),\n pendingLocal: new Deque(),\n flushingLocal: false,\n retryAttempt: 0,\n retrySleepController: new AbortController(),\n cursor,\n liveMode,\n snapshot: createTransportSnapshotState(),\n started: Promise.resolve(),\n syncWaiters: [],\n };\n}\n\ntype DisposeJoinStateOptions<TVersion extends JsonObject> = {\n readonly snapshotManager: Pick<TransportSnapshotManager<TVersion>, \"dispose\">;\n readonly onDisposed?: () => void;\n};\n\nexport async function disposeJoinState<TVersion extends JsonObject>(\n state: JoinState<TVersion>,\n options: DisposeJoinStateOptions<TVersion>,\n): Promise<void> {\n state.closed = true;\n options.snapshotManager.dispose(state);\n const requestController = state.requestController;\n state.requestController = undefined;\n const unsubscribeLocal = state.unsubscribeLocal;\n state.unsubscribeLocal = undefined;\n requestController?.abort();\n unsubscribeLocal?.();\n const disposedError = new Error(\"subscription closed\");\n for (const waiter of state.syncWaiters.splice(0)) {\n waiter.reject(disposedError);\n }\n options.onDisposed?.();\n}\n","import {\n composeBeforeRemoteCursorSaveHooks,\n createInitialRemoteCursor,\n InMemoryRemoteCursorStore,\n persistRemoteCursor,\n type BeforeRemoteCursorSaveHook,\n type RemoteCursor,\n type RemoteCursorSaveSource,\n type RemoteCursorStore,\n} from \"./checkpoint\";\nimport {\n computeRetryDelay,\n DEFAULT_RECONNECT_CONFIG,\n type ReconnectConfig,\n} from \"./reconnect\";\nimport { StreamsTransportClient } from \"./transport-client\";\nimport { TransportCursorManager } from \"./transport-cursor\";\nimport {\n createJoinState as createTransportJoinState,\n disposeJoinState as disposeTransportJoinState,\n} from \"./transport-room\";\nimport { TransportSnapshotManager } from \"./transport-snapshot\";\nimport {\n classifyFetchError,\n createProducerId,\n HttpError,\n isAuthOrProtocolError,\n isReadNoProgress,\n isStreamNotFoundError,\n normalizeAuthProvider,\n normalizeSnapshotCodec,\n normalizeSnapshotUploadConfig,\n sleep,\n toTransportError,\n type CursorSyncResult,\n type JoinState,\n type JoinSyncResult,\n type LiveReadMode,\n type LocalExportResult,\n type ProducerSession,\n type SyncWaiter,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n CrdtUpdateBatch,\n StreamsCrdtLike,\n StreamsCrdtOptions,\n JsonObject,\n MaybePromise,\n Result,\n TransportCreateStreamSuccess,\n TransportDeleteStreamSuccess,\n TransportError,\n TransportJoinParams,\n TransportRoomStatus,\n TransportSubscription,\n TransportSyncSuccess,\n} from \"./types\";\n\n/**\n * Durable Streams transport runtime bound to one stream URL and one local CRDT adapter.\n */\nexport class StreamsCrdt<\n TVersion extends JsonObject,\n> implements StreamsCrdtLike<TVersion> {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly remoteCursorStore: RemoteCursorStore<TVersion>;\n\n private readonly createStreamIfMissing: boolean;\n private readonly client: StreamsTransportClient<TVersion>;\n private readonly cursorManager: TransportCursorManager<TVersion>;\n private readonly volatileCursorManager: TransportCursorManager<TVersion>;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly reconnectConfig: ReconnectConfig;\n private readonly debug: boolean;\n private readonly beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n private readonly snapshotManager: TransportSnapshotManager<TVersion>;\n private readonly producerId = createProducerId();\n private producerEpoch = 0;\n private nextProducerSeq = 0;\n private joinState?: JoinState<TVersion>;\n private opQueue: Promise<void> = Promise.resolve();\n\n /**\n * Internal catch-up result used when callers need to handle 410 by\n * preserving local state before rebuilding through bootstrap.\n */\n private static readonly GONE_CATCHUP = { kind: \"gone\" as const };\n\n /**\n * Creates a transport for one stream URL and one local adapter instance.\n */\n constructor(options: StreamsCrdtOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n const authProvider = normalizeAuthProvider(options.auth);\n this.createStreamIfMissing = options.createStreamIfMissing ?? false;\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.remoteCursorStore =\n options.remoteCursorStore ?? new InMemoryRemoteCursorStore<TVersion>();\n this.reconnectConfig = {\n ...DEFAULT_RECONNECT_CONFIG,\n ...options.reconnectConfig,\n };\n this.debug = options.debug ?? false;\n this.beforeRemoteCursorSave = composeBeforeRemoteCursorSaveHooks(\n options.beforeRemoteCursorSave,\n ...(options.beforeRemoteCursorSaveHooks ?? []),\n );\n const snapshotCodec = normalizeSnapshotCodec(options.snapshotCodec);\n const snapshotUploadConfig = normalizeSnapshotUploadConfig(\n options.snapshotUpload,\n );\n this.decodeSnapshot = snapshotCodec?.decompress;\n this.client = new StreamsTransportClient({\n streamUrl: this.streamUrl,\n adapter: this.adapter,\n authProvider,\n fetchImpl,\n reconnectConfig: this.reconnectConfig,\n });\n this.cursorManager = this.createCursorManager(\n this.adapter,\n (cursor, source) => this.saveRemoteCursor(cursor, source),\n );\n this.volatileCursorManager = this.createCursorManager(\n this.adapter,\n async (cursor) => cursor,\n );\n this.snapshotManager = new TransportSnapshotManager({\n adapter: this.adapter,\n config: snapshotUploadConfig,\n encodeSnapshot: snapshotCodec?.compress,\n readSnapshotOffsetValue: () => this.client.readSnapshotOffsetValue(),\n putSnapshotAt: (snapshotOffset, snapshot) =>\n this.client.putSnapshotAt(snapshotOffset, snapshot),\n enqueueExclusive: (task) => this.enqueueExclusive(task),\n logDebug: (message, detail) => this.logDebug(message, detail),\n logError: (message, error, detail) =>\n this.logError(message, error, detail),\n isCurrentState: (state) => this.joinState === state,\n });\n }\n\n /**\n * Stops any active live join and releases transport-owned state.\n */\n async close(): Promise<void> {\n const state = this.joinState;\n if (state != null) {\n await this.disposeJoinState(state);\n }\n }\n\n /**\n * Resets the retry counter and immediately attempts to reconnect.\n *\n * Use this after device wake or network recovery to skip any pending\n * backoff delay. No-op when the room is already joined or no join is active.\n *\n * If the room reached `\"disconnected\"` status (only possible with a finite\n * `maxAttempts` config), this restarts the read loop.\n */\n rejoin(): void {\n const state = this.joinState;\n if (state == null || state.closed || state.status === \"joined\") {\n return;\n }\n const wasDisconnected = state.status === \"disconnected\";\n state.retryAttempt = 0;\n state.retrySleepController.abort();\n state.retrySleepController = new AbortController();\n if (wasDisconnected) {\n this.setJoinStatus(state, \"reconnecting\");\n void this.runReadLoop(state);\n }\n }\n\n /**\n * Reads the latest remote snapshot offset, if the server exposes one.\n */\n async getSnapshotOffset(): Promise<Result<string | null, TransportError>> {\n try {\n return {\n ok: true,\n value: await this.client.readSnapshotOffsetValue(),\n };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Explicitly creates the target stream.\n */\n async createStream(): Promise<\n Result<TransportCreateStreamSuccess, TransportError>\n > {\n try {\n const created = await this.client.createStreamRequest();\n return { ok: true, value: { created } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Deletes the target stream and clears persisted replay state when supported by the cursor store.\n */\n async deleteStream(): Promise<\n Result<TransportDeleteStreamSuccess, TransportError>\n > {\n try {\n await this.close();\n const deleted = await this.enqueueExclusive(async () => {\n const deleted = await this.client.deleteStreamRequest();\n await this.remoteCursorStore.delete?.(this.streamUrl);\n this.producerEpoch = 0;\n this.nextProducerSeq = 0;\n return deleted;\n });\n return { ok: true, value: { deleted } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Runs one bootstrap/catch-up cycle without entering live mode.\n */\n async sync(): Promise<\n Result<TransportSyncSuccess<TVersion>, TransportError>\n > {\n try {\n const cursor = await this.syncOnce();\n return { ok: true, value: { cursor } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Replays remote state, flushes queued local updates, then enters live read mode.\n */\n async join(\n params?: TransportJoinParams,\n ): Promise<Result<TransportSubscription, TransportError>> {\n const existing = this.joinState;\n if (existing != null) {\n try {\n await existing.started;\n return { ok: true, value: this.createSubscription(existing, params) };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n const initialCursor = createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n const state = createTransportJoinState(initialCursor);\n state.started = this.startJoin(state);\n this.joinState = state;\n try {\n await state.started;\n return { ok: true, value: this.createSubscription(state, params) };\n } catch (error) {\n await this.disposeJoinState(state);\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n private createSubscription(\n state: JoinState<TVersion>,\n params?: TransportJoinParams,\n ): TransportSubscription {\n state.refCount += 1;\n const statusListener = params?.onStatusChange;\n if (statusListener != null) {\n state.statusListeners.add(statusListener);\n statusListener(state.status as TransportRoomStatus);\n }\n let unsubscribed = false;\n return {\n unsubscribe: () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n if (statusListener != null) {\n state.statusListeners.delete(statusListener);\n }\n state.refCount = Math.max(0, state.refCount - 1);\n if (state.refCount !== 0 || state.closed) {\n return;\n }\n void this.disposeJoinState(state);\n },\n get connected() {\n return state.status === \"joined\";\n },\n get status() {\n return state.status as TransportRoomStatus;\n },\n onStatusChange: (listener) => {\n state.statusListeners.add(listener);\n listener(state.status as TransportRoomStatus);\n return () => {\n state.statusListeners.delete(listener);\n };\n },\n waitUntilSynced: async () => {\n // Let adapters with async export queues (e.g. Flock) drain\n // before we snapshot the pending queue length.\n await this.adapter.flushLocalExports?.();\n if (state.closed) {\n throw new Error(\"subscription closed\");\n }\n // drainPendingBatch reads from pendingLocal without popping;\n // items are only removed after a successful append. So\n // pendingLocal.length includes any in-flight batch.\n const remaining = state.pendingLocal.length;\n if (remaining === 0) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n state.syncWaiters.push({ remaining, resolve, reject });\n });\n },\n };\n }\n\n private async disposeJoinState(state: JoinState<TVersion>): Promise<void> {\n await disposeTransportJoinState(state, {\n snapshotManager: this.snapshotManager,\n onDisposed: () => {\n if (this.joinState === state) {\n this.joinState = undefined;\n }\n },\n });\n }\n\n private async startJoin(state: JoinState<TVersion>): Promise<void> {\n try {\n state.requestController ??= new AbortController();\n const joined = await this.performInitialJoinSync(state.liveMode);\n state.cursor = joined.cursor;\n state.streamCursor = joined.streamCursor;\n state.liveMode = joined.liveMode;\n const pendingLocal = this.exportUpdates(\n joined.localExportRefVersion,\n );\n const hasQueuedPreSubscribeBatch = pendingLocal.batch != null;\n if (pendingLocal.batch != null) {\n state.pendingLocal.pushBack(pendingLocal.batch);\n }\n if (state.closed) {\n return;\n }\n state.unsubscribeLocal = this.adapter.subscribeLocalUpdates((batch) => {\n state.pendingLocal.pushBack(batch);\n void this.flushPendingLocal(state);\n });\n if (hasQueuedPreSubscribeBatch) {\n await this.flushPendingLocal(state);\n if (state.status === \"error\" || state.status === \"disconnected\") {\n throw new Error(\"join local sync failed before subscription became ready\");\n }\n }\n if (state.closed) {\n return;\n }\n this.setJoinStatus(state, \"joined\");\n void this.runReadLoop(state);\n } catch (error) {\n if (state.closed) {\n return;\n }\n this.snapshotManager.dispose(state);\n this.logError(\"join first sync failed\", error, {\n streamUrl: this.streamUrl,\n });\n this.setJoinStatus(state, \"error\");\n throw error;\n }\n }\n\n private async syncOnce(): Promise<RemoteCursor<TVersion>> {\n let cursor = (await this.resolveInitialRemoteState()).cursor;\n const localBatch = this.exportUpdates(\n cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n cursor = await this.appendLocalBatch(cursor, localBatch.batch);\n cursor = (await this.catchup(cursor)).cursor;\n }\n return cursor;\n }\n\n private async performInitialJoinSync(\n preferredLiveMode: LiveReadMode,\n ): Promise<JoinSyncResult<TVersion>> {\n const initial = await this.resolveInitialRemoteState();\n let cursor = initial.cursor;\n let localExportRefVersion = cursor.serverLowerBoundVersion;\n let streamCursor = initial.streamCursor;\n let liveMode = preferredLiveMode;\n\n const exportedLocal = this.exportUpdates(localExportRefVersion);\n localExportRefVersion = exportedLocal.nextRefVersion;\n if (exportedLocal.batch != null) {\n cursor = await this.appendLocalBatch(cursor, exportedLocal.batch);\n const caughtUp = await this.catchup(cursor);\n cursor = caughtUp.cursor;\n streamCursor = caughtUp.streamCursor;\n }\n localExportRefVersion = cursor.serverLowerBoundVersion;\n\n return {\n cursor,\n streamCursor,\n liveMode,\n localExportRefVersion,\n };\n }\n\n private createInitialCursor(): RemoteCursor<TVersion> {\n return createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n }\n\n private async resolveInitialRemoteState(): Promise<CursorSyncResult<TVersion>> {\n const loaded = await this.remoteCursorStore.load(this.streamUrl);\n const cursor = loaded ?? this.createInitialCursor();\n\n try {\n // When a cursor already exists, transport resumes from `nextOffset`\n // instead of bootstrapping. That requires the caller to have already\n // restored the matching local CRDT state for `serverLowerBoundVersion`.\n if (loaded == null) {\n return await this.bootstrapState(cursor);\n }\n const caughtUp = await this.catchupOrGone(cursor, {\n goneStrategy: \"return-gone\",\n });\n if (\"kind\" in caughtUp) {\n return await this.recoverFromGoneWithLocalBootstrap(cursor);\n }\n return caughtUp;\n } catch (error) {\n if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) {\n throw error;\n }\n await this.client.createStreamRequest();\n return await this.bootstrapState(this.createInitialCursor());\n }\n }\n\n private async bootstrapState(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly cursorManager?: TransportCursorManager<TVersion>;\n } = {},\n ): Promise<CursorSyncResult<TVersion>> {\n const cursorManager = options.cursorManager ?? this.cursorManager;\n const result = await this.client.bootstrap();\n let saved = await cursorManager.applyBootstrapPayload(\n cursor,\n result.snapshot,\n result.updates,\n result.nextOffset,\n );\n let bootstrapCursor = result.streamCursor;\n if (!result.upToDate) {\n const caughtUp = await this.catchupOrGone(saved, {\n streamCursor: bootstrapCursor,\n cursorManager,\n });\n if (\"kind\" in caughtUp) {\n throw new Error(\"bootstrap catch-up unexpectedly returned gone\");\n }\n saved = caughtUp.cursor;\n bootstrapCursor = caughtUp.streamCursor;\n }\n return { cursor: saved, streamCursor: bootstrapCursor };\n }\n\n private async catchup(\n cursor: RemoteCursor<TVersion>,\n live?: \"long-poll\",\n streamCursor?: string,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<CursorSyncResult<TVersion>> {\n const result = await this.catchupOrGone(cursor, {\n live,\n streamCursor,\n onRemoteVersionApplied,\n });\n if (\"kind\" in result) {\n throw new Error(\"catchup unexpectedly returned gone\");\n }\n return result;\n }\n\n private async catchupOrGone(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly live?: \"long-poll\";\n readonly streamCursor?: string;\n readonly onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>;\n readonly goneStrategy?: \"bootstrap\" | \"return-gone\";\n readonly cursorManager?: TransportCursorManager<TVersion>;\n } = {},\n ): Promise<CursorSyncResult<TVersion> | typeof StreamsCrdt.GONE_CATCHUP> {\n let current = cursor;\n let currentStreamCursor = options.streamCursor;\n const goneStrategy = options.goneStrategy ?? \"bootstrap\";\n const cursorManager = options.cursorManager ?? this.cursorManager;\n while (true) {\n const requestOffset = current.nextOffset;\n const response = await this.client.readFromOffset(\n requestOffset,\n currentStreamCursor,\n options.live,\n );\n if (response.kind === \"gone\") {\n if (goneStrategy === \"return-gone\") {\n return StreamsCrdt.GONE_CATCHUP;\n }\n return await this.bootstrapState(\n {\n ...current,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n { cursorManager },\n );\n }\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n current = await cursorManager.applyCatchupResult(\n current,\n result,\n options.onRemoteVersionApplied,\n );\n currentStreamCursor = result.streamCursor;\n if (result.upToDate) {\n return { cursor: current, streamCursor: currentStreamCursor };\n }\n }\n }\n\n private async appendLocalBatch(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n persist = true,\n ): Promise<RemoteCursor<TVersion>> {\n let producerSession: ProducerSession = {\n producerId: this.producerId,\n producerEpoch: this.producerEpoch,\n nextProducerSeq: this.nextProducerSeq,\n };\n for (let attempt = 0; attempt < 3; attempt += 1) {\n const result = await this.client.appendUpdates(\n batch.updates,\n producerSession,\n );\n if (result.kind === \"ok\") {\n this.producerEpoch = result.ackEpoch;\n this.nextProducerSeq = result.ackSeq + 1;\n const next: RemoteCursor<TVersion> = {\n ...cursor,\n nextOffset: result.nextOffset,\n serverLowerBoundVersion: this.adapter.mergeVersions(\n cursor.serverLowerBoundVersion,\n batch.version,\n ),\n };\n return persist ? await this.saveRemoteCursor(next, \"local\") : next;\n }\n if (result.kind === \"seq-gap\") {\n producerSession = {\n ...producerSession,\n nextProducerSeq: result.expectedSeq,\n };\n continue;\n }\n producerSession = {\n ...producerSession,\n producerEpoch: result.serverEpoch + 1,\n nextProducerSeq: 0,\n };\n }\n throw new Error(\"append retry budget exhausted\");\n }\n\n private exportUpdates(fromVersion: TVersion): LocalExportResult<TVersion> {\n const exported = this.adapter.exportUpdates(fromVersion);\n if (exported == null) {\n return { nextRefVersion: fromVersion };\n }\n const nextRefVersion = this.adapter.mergeVersions(\n fromVersion,\n exported.version,\n );\n if (exported.updates.length === 0) {\n return { nextRefVersion };\n }\n return {\n batch: exported,\n nextRefVersion,\n };\n }\n\n private async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyBootstrapItems(\n cursor,\n items,\n nextOffset,\n );\n }\n\n private async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyRemoteItems(cursor, items, nextOffset);\n }\n\n private createCursorManager(\n adapter: Pick<CrdtAdapter<TVersion>, \"applySnapshot\" | \"applyRemoteUpdates\">,\n saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>,\n ): TransportCursorManager<TVersion> {\n return new TransportCursorManager({\n adapter,\n decodeSnapshot: this.decodeSnapshot,\n saveRemoteCursor,\n });\n }\n\n private async createIsolatedCursorManager(): Promise<\n TransportCursorManager<TVersion> | null\n > {\n const isolated = await this.adapter.createIsolatedAdapter?.();\n if (isolated == null) {\n return null;\n }\n return this.createCursorManager(isolated, async (cursor) => cursor);\n }\n\n /**\n * Infers remote state via bootstrap with the given cursor manager,\n * exports and appends any local delta, then re-bootstraps to converge\n * persisted cursor and local CRDT state on server truth.\n */\n private async inferRemoteAndAppendLocal(\n bootstrapCursor: RemoteCursor<TVersion>,\n cursorManager: TransportCursorManager<TVersion>,\n ): Promise<CursorSyncResult<TVersion>> {\n const inferredRemote = await this.bootstrapState(bootstrapCursor, {\n cursorManager,\n });\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n await this.appendLocalBatch(\n inferredRemote.cursor,\n localBatch.batch,\n false,\n );\n }\n return await this.bootstrapState(bootstrapCursor);\n }\n\n private async recoverFromGoneWithLocalBootstrap(\n cursor: RemoteCursor<TVersion>,\n ): Promise<CursorSyncResult<TVersion>> {\n const isolatedCursorManager = await this.createIsolatedCursorManager();\n const bootstrapCursor = {\n ...cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n };\n\n if (isolatedCursorManager != null) {\n return await this.inferRemoteAndAppendLocal(\n bootstrapCursor,\n isolatedCursorManager,\n );\n }\n\n const preservedLocalSnapshot = this.adapter.exportSnapshot();\n let restoredLocalSnapshot = false;\n\n try {\n // specs/ds-client-state.md §4.2 / §6 and specs/ds-extensions.md\n // require 410 recovery to rebuild through bootstrap. Preserve local\n // cached state long enough to export a first-sync delta against the\n // inferred remote version, then bootstrap again so persisted cursor and\n // visible local CRDT state converge on the same server truth.\n const inferredRemote = await this.bootstrapState(bootstrapCursor, {\n cursorManager: this.volatileCursorManager,\n });\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n restoredLocalSnapshot = true;\n\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n await this.appendLocalBatch(\n inferredRemote.cursor,\n localBatch.batch,\n false,\n );\n }\n return await this.bootstrapState(bootstrapCursor);\n } catch (error) {\n if (!restoredLocalSnapshot) {\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n }\n throw error;\n }\n }\n\n private async runReadLoop(state: JoinState<TVersion>): Promise<void> {\n state.requestController ??= new AbortController();\n while (!state.closed) {\n try {\n if (state.liveMode === \"long-poll\") {\n const requestOffset = state.cursor.nextOffset;\n const response = await this.client.readFromOffset(\n requestOffset,\n state.streamCursor,\n \"long-poll\",\n state.requestController?.signal,\n );\n if (response.kind === \"gone\") {\n const bootstrapped = await this.bootstrapState({\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n continue;\n }\n\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n requestOffset,\n result,\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n if (result.streamCursor != null) {\n state.streamCursor = result.streamCursor;\n }\n });\n if (state.closed) {\n return;\n }\n state.retryAttempt = 0;\n this.setJoinStatus(state, \"joined\");\n await sleep(0);\n continue;\n }\n\n const sseRequestOffset = state.cursor.nextOffset;\n const pendingRemote: Array<{\n updates: Uint8Array[];\n nextOffset: string;\n streamCursor?: string;\n }> = [];\n let flushChain = Promise.resolve();\n const sseResult = await this.client.readFromOffsetSseItems(\n sseRequestOffset,\n state.streamCursor,\n (updates, nextOffset, _upToDate, streamCursor) => {\n pendingRemote.push({ updates, nextOffset, streamCursor });\n flushChain = flushChain.then(async () => {\n const events = pendingRemote.splice(0);\n if (events.length === 0 || state.closed) {\n return;\n }\n const allUpdates = events.flatMap((e) => e.updates);\n const lastEvent = events[events.length - 1]!;\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n sseRequestOffset,\n { nextOffset: lastEvent.nextOffset, updates: allUpdates },\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n for (let i = events.length - 1; i >= 0; i--) {\n if (events[i]!.streamCursor != null) {\n state.streamCursor = events[i]!.streamCursor!;\n break;\n }\n }\n });\n this.setJoinStatus(state, \"joined\");\n });\n },\n () => {\n this.setJoinStatus(state, \"joined\");\n },\n state.requestController?.signal,\n );\n await flushChain;\n if (state.closed) {\n return;\n }\n if (sseResult.kind === \"ok\") {\n state.retryAttempt = 0;\n state.streamCursor = sseResult.latestCursor;\n await sleep(0);\n continue;\n }\n if (sseResult.kind === \"unsupported\") {\n state.liveMode = \"long-poll\";\n await sleep(0);\n continue;\n }\n if (sseResult.kind === \"not-found\") {\n this.logError(\n \"live read failed\",\n new HttpError(\"stream not found\", 404),\n {\n streamUrl: this.streamUrl,\n code: \"stream_not_found\",\n },\n );\n this.setJoinStatus(state, \"error\");\n return;\n }\n const bootstrapped = await this.bootstrapState({\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n } catch (error) {\n if (state.closed) {\n return;\n }\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"live read failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n this.setJoinStatus(state, \"error\");\n return;\n }\n this.setJoinStatus(state, \"reconnecting\");\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (state.retryAttempt >= maxAttempts) {\n this.logError(\"live read reconnect exhausted\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n this.setJoinStatus(state, \"disconnected\");\n return;\n }\n const delay = computeRetryDelay(\n state.retryAttempt,\n this.reconnectConfig,\n );\n state.retryAttempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n\n /** Maximum encoded size (bytes) for a single batched append. */\n private static readonly MAX_BATCH_BYTES = 256 * 1024;\n\n /**\n * Drain up to `MAX_BATCH_BYTES` worth of queued batches from the front of\n * `pendingLocal` and merge them into a single `CrdtUpdateBatch`.\n *\n * Returns `undefined` when the queue is empty.\n */\n private drainPendingBatch(\n state: JoinState<TVersion>,\n ): { batch: CrdtUpdateBatch<TVersion>; count: number } | undefined {\n const first = state.pendingLocal.peekFront();\n if (first == null) {\n return undefined;\n }\n\n const maxBytes = StreamsCrdt.MAX_BATCH_BYTES;\n let totalBytes = first.updates.reduce(\n (sum, u) => sum + 4 + u.byteLength,\n 0,\n );\n let mergedVersion = first.version;\n const allUpdates: Uint8Array[] = Array.from(first.updates);\n let count = 1;\n\n while (count < state.pendingLocal.length) {\n const next = state.pendingLocal.at(count)!;\n const nextBytes = next.updates.reduce(\n (sum, u) => sum + 4 + u.byteLength,\n 0,\n );\n if (totalBytes + nextBytes > maxBytes) {\n break;\n }\n allUpdates.push(...next.updates);\n totalBytes += nextBytes;\n mergedVersion = this.adapter.mergeVersions(mergedVersion, next.version);\n count += 1;\n }\n\n if (count === 1) {\n return { batch: first, count: 1 };\n }\n return { batch: { updates: allUpdates, version: mergedVersion }, count };\n }\n\n private async flushPendingLocal(state: JoinState<TVersion>): Promise<void> {\n if (state.flushingLocal || state.closed) {\n return;\n }\n state.flushingLocal = true;\n try {\n while (!state.closed) {\n const drained = this.drainPendingBatch(state);\n if (drained == null) {\n return;\n }\n let attempts = 0;\n while (!state.closed) {\n try {\n state.cursor = await this.enqueueExclusive(async () =>\n this.appendLocalBatch(state.cursor, drained.batch),\n );\n for (let i = 0; i < drained.count; i += 1) {\n state.pendingLocal.popFront();\n }\n this.notifySyncWaiters(state, drained.count);\n this.setJoinStatus(state, \"joined\");\n this.snapshotManager.schedule(state);\n break;\n } catch (error) {\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"local append failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n this.rejectSyncWaiters(\n state,\n new Error(`local append failed: ${classified.code}`),\n );\n this.setJoinStatus(state, \"error\");\n return;\n }\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (attempts >= maxAttempts) {\n this.rejectSyncWaiters(\n state,\n new Error(\"local append retry budget exhausted\"),\n );\n this.setJoinStatus(state, \"error\");\n return;\n }\n this.setJoinStatus(state, \"reconnecting\");\n const delay = computeRetryDelay(attempts, this.reconnectConfig);\n attempts += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n } finally {\n state.flushingLocal = false;\n }\n }\n\n /**\n * Decrements all sync waiters by the number of flushed batches and\n * resolves any that have reached zero.\n */\n private notifySyncWaiters(\n state: JoinState<TVersion>,\n flushedCount: number,\n ): void {\n let i = 0;\n while (i < state.syncWaiters.length) {\n const waiter = state.syncWaiters[i]!;\n waiter.remaining -= flushedCount;\n if (waiter.remaining <= 0) {\n state.syncWaiters.splice(i, 1);\n waiter.resolve();\n } else {\n i += 1;\n }\n }\n }\n\n /** Rejects and removes all sync waiters. */\n private rejectSyncWaiters(\n state: JoinState<TVersion>,\n error: Error,\n ): void {\n for (const waiter of state.syncWaiters.splice(0)) {\n waiter.reject(error);\n }\n }\n\n private async saveRemoteCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ): Promise<RemoteCursor<TVersion>> {\n return await persistRemoteCursor({\n remoteCursorStore: this.remoteCursorStore,\n cursor,\n source,\n beforeRemoteCursorSave: this.beforeRemoteCursorSave,\n });\n }\n\n private setJoinStatus(\n state: JoinState<TVersion>,\n status: TransportRoomStatus,\n ): void {\n if (state.status === status) {\n return;\n }\n state.status = status;\n for (const listener of state.statusListeners) {\n listener(status);\n }\n }\n\n private enqueueExclusive<T>(task: () => MaybePromise<T>): Promise<T> {\n const run = this.opQueue.then(task, task);\n this.opQueue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private logDebug(message: string, detail?: Record<string, unknown>): void {\n if (!this.debug) {\n return;\n }\n if (detail == null) {\n console.debug(`[streams-crdt] ${message}`);\n return;\n }\n console.debug(`[streams-crdt] ${message}`, detail);\n }\n\n private logError(\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ): void {\n const payload =\n error == null\n ? detail\n : {\n ...detail,\n error: error instanceof Error ? error.message : String(error),\n };\n if (payload == null) {\n console.error(`[streams-crdt] ${message}`);\n return;\n }\n console.error(`[streams-crdt] ${message}`, payload);\n }\n}\n","const MAX_STREAM_ID_BYTES = 512;\nconst textEncoder = new TextEncoder();\nconst DEFAULT_BASE_URL = \"https://streams-api.loro.dev\";\n\nfunction utf8ByteLength(value: string): number {\n return textEncoder.encode(value).byteLength;\n}\n\n/**\n * Validates a stream id accepted by `createStreamUrl()`.\n */\nexport function isValidRillId(id: string): boolean {\n return (\n id.length > 0 &&\n utf8ByteLength(id) <= MAX_STREAM_ID_BYTES &&\n !id.includes(\"/\") &&\n !id.includes(\"\\0\") &&\n !id.includes(\"..\")\n );\n}\n\n/**\n * Validates a bucket id accepted by `createStreamUrl()`.\n */\nexport function isValidBucketId(id: string): boolean {\n return isValidRillId(id);\n}\n\n/**\n * Builds a Durable Streams HTTP URL for one `(bucketId, streamId)` pair.\n */\nexport function createStreamUrl(input: {\n bucketId: string;\n streamId: string;\n baseUrl?: string;\n}): string {\n const baseUrl = (input.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/g, \"\");\n if (!isValidBucketId(input.bucketId)) {\n throw new Error(\n `invalid bucketId: ${JSON.stringify(input.bucketId)} ` +\n `(must be non-empty UTF-8, <= ${MAX_STREAM_ID_BYTES} bytes, and exclude \"/\", \"\\\\0\", \"..\")`,\n );\n }\n if (!isValidRillId(input.streamId)) {\n throw new Error(\n `invalid streamId: ${JSON.stringify(input.streamId)} ` +\n `(must be non-empty UTF-8, <= ${MAX_STREAM_ID_BYTES} bytes, and exclude \"/\", \"\\\\0\", \"..\")`,\n );\n }\n const streamId = input.streamId;\n return `${baseUrl}/ds/${encodeURIComponent(input.bucketId)}/${encodeURIComponent(streamId)}`;\n}\n"],"mappings":"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"}
|
|
@@ -295,6 +295,20 @@ interface TransportSubscription {
|
|
|
295
295
|
readonly status: TransportRoomStatus;
|
|
296
296
|
/** Registers a status listener. Returns an unsubscribe function. */
|
|
297
297
|
readonly onStatusChange: (listener: (status: "connecting" | TransportRoomStatus) => void) => () => void;
|
|
298
|
+
/**
|
|
299
|
+
* Returns a promise that resolves when all local changes that were pending
|
|
300
|
+
* at the time of the call have been successfully flushed to the server.
|
|
301
|
+
*
|
|
302
|
+
* If the adapter implements `flushLocalExports()`, it is awaited first so
|
|
303
|
+
* that asynchronously queued local exports are visible before snapshotting
|
|
304
|
+
* the pending queue.
|
|
305
|
+
*
|
|
306
|
+
* If there are no pending local changes, the promise resolves immediately.
|
|
307
|
+
*
|
|
308
|
+
* The promise rejects if the flush encounters a non-recoverable error or
|
|
309
|
+
* all subscriptions are closed before all pending changes are confirmed.
|
|
310
|
+
*/
|
|
311
|
+
readonly waitUntilSynced: () => Promise<void>;
|
|
298
312
|
}
|
|
299
313
|
/**
|
|
300
314
|
* Local CRDT updates ready to append to Durable Streams.
|
|
@@ -351,6 +365,16 @@ interface CrdtAdapter<TVersion extends JsonObject> {
|
|
|
351
365
|
* bootstrap data without mutating the live CRDT instance first.
|
|
352
366
|
*/
|
|
353
367
|
createIsolatedAdapter?(): MaybePromise<IsolatedCrdtAdapter<TVersion>>;
|
|
368
|
+
/**
|
|
369
|
+
* Waits for any adapter-internal async export queue to drain.
|
|
370
|
+
*
|
|
371
|
+
* Adapters that deliver local updates asynchronously (e.g. through an
|
|
372
|
+
* internal promise chain) should implement this so that
|
|
373
|
+
* `waitUntilSynced()` can observe the full set of pending changes.
|
|
374
|
+
* Adapters that call the `subscribeLocalUpdates` listener synchronously
|
|
375
|
+
* (like LoroDoc) do not need to implement this.
|
|
376
|
+
*/
|
|
377
|
+
flushLocalExports?(): Promise<void>;
|
|
354
378
|
/** Subscribes to local CRDT mutations. Returns an unsubscribe function. */
|
|
355
379
|
subscribeLocalUpdates(listener: (batch: CrdtUpdateBatch<TVersion>) => void): () => void;
|
|
356
380
|
/** Optional headers to include when creating the stream (e.g. retention config). */
|
|
@@ -510,6 +534,12 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
510
534
|
private applyRemoteItems;
|
|
511
535
|
private createCursorManager;
|
|
512
536
|
private createIsolatedCursorManager;
|
|
537
|
+
/**
|
|
538
|
+
* Infers remote state via bootstrap with the given cursor manager,
|
|
539
|
+
* exports and appends any local delta, then re-bootstraps to converge
|
|
540
|
+
* persisted cursor and local CRDT state on server truth.
|
|
541
|
+
*/
|
|
542
|
+
private inferRemoteAndAppendLocal;
|
|
513
543
|
private recoverFromGoneWithLocalBootstrap;
|
|
514
544
|
private runReadLoop;
|
|
515
545
|
/** Maximum encoded size (bytes) for a single batched append. */
|
|
@@ -522,16 +552,15 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
522
552
|
*/
|
|
523
553
|
private drainPendingBatch;
|
|
524
554
|
private flushPendingLocal;
|
|
555
|
+
/**
|
|
556
|
+
* Decrements all sync waiters by the number of flushed batches and
|
|
557
|
+
* resolves any that have reached zero.
|
|
558
|
+
*/
|
|
559
|
+
private notifySyncWaiters;
|
|
560
|
+
/** Rejects and removes all sync waiters. */
|
|
561
|
+
private rejectSyncWaiters;
|
|
525
562
|
private saveRemoteCursor;
|
|
526
563
|
private setJoinStatus;
|
|
527
|
-
private createStreamRequest;
|
|
528
|
-
private deleteStreamRequest;
|
|
529
|
-
private readFromOffsetSseItems;
|
|
530
|
-
private readFromOffset;
|
|
531
|
-
private bootstrap;
|
|
532
|
-
private readSnapshotOffsetValue;
|
|
533
|
-
private putSnapshotAt;
|
|
534
|
-
private appendUpdates;
|
|
535
564
|
private enqueueExclusive;
|
|
536
565
|
private logDebug;
|
|
537
566
|
private logError;
|
|
@@ -556,4 +585,4 @@ declare function createStreamUrl(input: {
|
|
|
556
585
|
}): string;
|
|
557
586
|
//#endregion
|
|
558
587
|
export { RemoteCursorSaveSource as A, TransportSyncSuccess as C, IndexedDbRemoteCursorStore as D, InMemoryRemoteCursorStore as E, createInitialRemoteCursor as M, IndexedDbRemoteCursorStoreOptions as O, TransportSubscription as S, BeforeRemoteCursorSaveHook as T, TransportCreateStreamSuccess as _, CrdtAdapter as a, TransportJoinParams as b, JsonObject as c, SnapshotCodec as d, SnapshotTransformHook as f, StreamsCrdtOptions as g, StreamsAuthProvider as h, StreamsCrdt as i, RemoteCursorStore as j, RemoteCursor as k, JsonValue as l, StreamsAuthContext as m, isValidBucketId as n, CrdtUpdateBatch as o, SnapshotUploadOptions as p, isValidRillId as r, IsolatedCrdtAdapter as s, createStreamUrl as t, Result as u, TransportDeleteStreamSuccess as v, BeforeRemoteCursorSaveContext as w, TransportRoomStatus as x, TransportError as y };
|
|
559
|
-
//# sourceMappingURL=stream-id-
|
|
588
|
+
//# sourceMappingURL=stream-id-Do4h2lOD.d.ts.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stream-id-nPEdFo7c.js","names":["createTransportJoinState","disposeTransportJoinState"],"sources":["../src/checkpoint.ts","../src/reconnect.ts","../src/items.ts","../src/transport-internal.ts","../src/transport-client.ts","../src/transport-cursor.ts","../src/deque.ts","../src/transport-snapshot.ts","../src/transport-room.ts","../src/transport-runtime.ts","../src/stream-id.ts"],"sourcesContent":["import type { JsonObject } from \"./types\";\n\n/**\n * Persisted replay progress for one stream.\n *\n * This is metadata only — it does **not** contain any CRDT update data.\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`.\n */\nexport interface RemoteCursor<TVersion extends JsonObject = JsonObject> {\n /** Identity of the stream this cursor belongs to. */\n readonly streamUrl: string;\n /** The offset to resume reading from on the next sync/join. */\n readonly nextOffset: string;\n /** The CRDT version that the server data up to `nextOffset` represents. */\n readonly serverLowerBoundVersion: TVersion;\n /** Timestamp (ms) when this cursor was last persisted. */\n readonly updatedAtMs: number;\n}\n\n/**\n * Storage for replay progress metadata.\n *\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`. Storing only the\n * cursor can make transport resume from `nextOffset` against an empty local\n * document, which is not sufficient for delta-only catchup.\n */\nexport interface RemoteCursorStore<TVersion extends JsonObject = JsonObject> {\n load(streamUrl: string): Promise<RemoteCursor<TVersion> | null>;\n save(cursor: RemoteCursor<TVersion>): Promise<void>;\n delete?(streamUrl: string): Promise<void>;\n}\n\n/**\n * Why the transport is advancing the remote cursor.\n */\nexport type RemoteCursorSaveSource = \"local\" | \"remote\" | \"bootstrap\";\n\n/**\n * Context passed to durability hooks before a remote cursor is persisted.\n *\n * Hooks should make local durable state correspond to `cursor` before this\n * cursor is stored, so a future process can restore the same CRDT state before\n * transport resumes from `cursor.nextOffset`.\n */\nexport type BeforeRemoteCursorSaveContext<\n TVersion extends JsonObject = JsonObject,\n> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly source: RemoteCursorSaveSource;\n};\n\n/**\n * Hook invoked before the transport persists replay progress.\n *\n * This is the place to persist local CRDT payloads or snapshots. If that local\n * durability step does not happen here, the remote cursor should not advance.\n */\nexport type BeforeRemoteCursorSaveHook<TVersion extends JsonObject = JsonObject> =\n (context: BeforeRemoteCursorSaveContext<TVersion>) => void | Promise<void>;\n\ntype BeforeRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n> = (context: BeforeRemoteCursorSaveContext<TVersion>) => boolean;\n\nexport function composeBeforeRemoteCursorSaveHooks<\n TVersion extends JsonObject = JsonObject,\n>(\n ...hooks: Array<BeforeRemoteCursorSaveHook<TVersion> | null | undefined>\n): BeforeRemoteCursorSaveHook<TVersion> | undefined {\n const active = hooks.filter(\n (hook): hook is BeforeRemoteCursorSaveHook<TVersion> => hook != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n return async (context) => {\n for (const hook of active) {\n await hook(context);\n }\n };\n}\n\nfunction withRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n>(\n predicate: BeforeRemoteCursorSavePredicate<TVersion>,\n hook: BeforeRemoteCursorSaveHook<TVersion>,\n): BeforeRemoteCursorSaveHook<TVersion> {\n return async (context) => {\n if (!predicate(context)) {\n return;\n }\n await hook(context);\n };\n}\n\nexport async function persistRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n remoteCursorStore: RemoteCursorStore<TVersion>;\n cursor: RemoteCursor<TVersion>;\n source: RemoteCursorSaveSource;\n beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n now?: () => number;\n}): Promise<RemoteCursor<TVersion>> {\n const next: RemoteCursor<TVersion> = {\n ...input.cursor,\n updatedAtMs: (input.now ?? Date.now)(),\n };\n if (input.beforeRemoteCursorSave != null) {\n await input.beforeRemoteCursorSave({\n cursor: next,\n source: input.source,\n });\n }\n await input.remoteCursorStore.save(next);\n return next;\n}\n\n/**\n * Construction options for `IndexedDbRemoteCursorStore`.\n */\nexport interface IndexedDbRemoteCursorStoreOptions {\n readonly dbName?: string;\n readonly storeName?: string;\n readonly dbVersion?: number;\n readonly indexedDb?: IDBFactory;\n}\n\nfunction key(streamUrl: string): string {\n return streamUrl;\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n\nfunction cloneJsonValue<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction cloneRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): RemoteCursor<TVersion> {\n return {\n ...cursor,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n };\n}\n\ntype StoredRemoteCursor = {\n readonly streamUrl: string;\n readonly nextOffset: string;\n readonly serverLowerBoundVersion: JsonObject;\n readonly updatedAtMs: number;\n};\n\nfunction fromStoredRemoteCursor<TVersion extends JsonObject>(\n record: StoredRemoteCursor,\n): RemoteCursor<TVersion> {\n if (!isJsonObject(record.serverLowerBoundVersion)) {\n throw new Error(\n \"invalid remote cursor record: serverLowerBoundVersion is not a JSON object\",\n );\n }\n return {\n streamUrl: record.streamUrl,\n nextOffset: record.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(\n record.serverLowerBoundVersion as TVersion,\n ),\n updatedAtMs: record.updatedAtMs,\n };\n}\n\nfunction toStoredRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): StoredRemoteCursor {\n return {\n streamUrl: cursor.streamUrl,\n nextOffset: cursor.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n updatedAtMs: cursor.updatedAtMs,\n };\n}\n\n/**\n * In-memory cursor store useful for tests or ephemeral runtimes.\n */\nexport class InMemoryRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly data = new Map<string, RemoteCursor<TVersion>>();\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const value = this.data.get(key(streamUrl));\n return value == null ? null : cloneRemoteCursor(value);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n this.data.set(key(cursor.streamUrl), cloneRemoteCursor(cursor));\n }\n\n async delete(streamUrl: string): Promise<void> {\n this.data.delete(key(streamUrl));\n }\n}\n\nconst DEFAULT_DB_NAME = \"streams-crdt\";\nconst DEFAULT_STORE_NAME = \"remote-cursors\";\nconst DEFAULT_DB_VERSION = 1;\n\n/**\n * IndexedDB-backed cursor store for browser clients that need replay progress persistence.\n *\n * This store only persists `RemoteCursor` metadata. It does not persist CRDT\n * payloads. Use it only when the application also persists the corresponding\n * local document state and restores that state before calling `sync()` or\n * `join()` after a reload.\n */\nexport class IndexedDbRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly dbName: string;\n private readonly storeName: string;\n private readonly dbVersion: number;\n private readonly indexedDb: IDBFactory | undefined;\n\n private db: IDBDatabase | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options?: IndexedDbRemoteCursorStoreOptions) {\n this.dbName = options?.dbName ?? DEFAULT_DB_NAME;\n this.storeName = options?.storeName ?? DEFAULT_STORE_NAME;\n this.dbVersion = options?.dbVersion ?? DEFAULT_DB_VERSION;\n this.indexedDb = options?.indexedDb ?? globalThis.indexedDB;\n }\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const db = await this.ensureDb();\n const record = await new Promise<StoredRemoteCursor | null>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readonly\");\n const store = tx.objectStore(this.storeName);\n const request = store.get(key(streamUrl));\n request.onsuccess = () => {\n resolve((request.result as StoredRemoteCursor | undefined) ?? null);\n };\n request.onerror = () => {\n reject(request.error ?? new Error(\"indexeddb load failed\"));\n };\n });\n return record == null ? null : fromStoredRemoteCursor<TVersion>(record);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n const db = await this.ensureDb();\n const record = toStoredRemoteCursor(cursor);\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.put(record, key(cursor.streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? request.error ?? new Error(\"indexeddb save failed\"));\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb save transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb save failed\"));\n });\n }\n\n async delete(streamUrl: string): Promise<void> {\n const db = await this.ensureDb();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.delete(key(streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(\n tx.error ?? request.error ?? new Error(\"indexeddb delete failed\"),\n );\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb delete transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb delete failed\"));\n });\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.db != null) {\n return this.db;\n }\n if (this.initPromise != null) {\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb is not available\");\n }\n return this.db;\n }\n if (this.indexedDb == null) {\n throw new Error(\"indexeddb is not available in this environment\");\n }\n\n this.initPromise = new Promise<void>((resolve, reject) => {\n const request = this.indexedDb!.open(this.dbName, this.dbVersion);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName);\n }\n };\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb open failed\"));\n request.onsuccess = () => {\n this.db = request.result;\n this.db.onversionchange = () => {\n this.db?.close();\n this.db = null;\n };\n resolve();\n };\n }).finally(() => {\n this.initPromise = null;\n });\n\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb open succeeded without a database handle\");\n }\n return this.db;\n }\n}\n\n/**\n * Creates a cursor value suitable for seeding a `RemoteCursorStore`.\n */\nexport function createInitialRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n streamUrl: string;\n nextOffset: string;\n serverLowerBoundVersion: TVersion;\n}): RemoteCursor<TVersion> {\n return {\n streamUrl: input.streamUrl,\n nextOffset: input.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(input.serverLowerBoundVersion),\n updatedAtMs: Date.now(),\n };\n}\n","/**\n * Retry and timeout settings used by `StreamsCrdt`.\n */\nexport interface ReconnectConfig {\n readonly delays: readonly number[];\n readonly jitterFraction: number;\n readonly maxAttempts: number;\n readonly connectTimeoutMs: number;\n readonly pollTimeoutMs: number;\n}\n\nexport const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {\n delays: [0, 500, 1000, 2000, 4000, 8000, 15000],\n jitterFraction: 0.2,\n maxAttempts: Infinity,\n connectTimeoutMs: 10000,\n pollTimeoutMs: 30000,\n};\n\n/**\n * Apply jitter of +/- fraction to a base delay.\n * E.g. applyJitter(1000, 0.2) returns a value in [800, 1200].\n */\nexport function applyJitter(baseMs: number, fraction: number): number {\n if (baseMs === 0) return 0;\n const jitter = baseMs * fraction * (2 * Math.random() - 1);\n return Math.max(0, Math.round(baseMs + jitter));\n}\n\n/**\n * Compute the retry delay for a given attempt number.\n * Uses the delays table from config, clamping at the last entry.\n */\nexport function computeRetryDelay(attempt: number, config: ReconnectConfig = DEFAULT_RECONNECT_CONFIG): number {\n const { delays, jitterFraction } = config;\n const index = Math.min(attempt, delays.length - 1);\n const base = delays[index];\n return applyJitter(base, jitterFraction);\n}\n","export const BINARY_CONTENT_TYPE = \"application/octet-stream\";\n\nexport function encodeItems(items: ReadonlyArray<Uint8Array>): Uint8Array {\n const total = items.reduce((sum, item) => sum + 4 + item.byteLength, 0);\n const out = new Uint8Array(total);\n const view = new DataView(out.buffer, out.byteOffset, out.byteLength);\n let cursor = 0;\n for (const item of items) {\n view.setUint32(cursor, item.byteLength, false);\n cursor += 4;\n out.set(item, cursor);\n cursor += item.byteLength;\n }\n return out;\n}\n\nexport function decodeItems(payload: Uint8Array): Uint8Array[] {\n const out: Uint8Array[] = [];\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n let cursor = 0;\n while (cursor < payload.byteLength) {\n if (cursor + 4 > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated length prefix\");\n }\n const len = view.getUint32(cursor, false);\n cursor += 4;\n if (cursor + len > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated item payload\");\n }\n out.push(payload.slice(cursor, cursor + len));\n cursor += len;\n }\n return out;\n}\n","import type { RemoteCursor } from \"./checkpoint\";\nimport { Deque } from \"./deque\";\nimport { decodeItems } from \"./items\";\nimport type { TransportSnapshotState } from \"./transport-snapshot\";\nimport type {\n CrdtUpdateBatch,\n JsonObject,\n JsonValue,\n MaybePromise,\n SnapshotCodec,\n SnapshotUploadOptions,\n StreamsAuthContext,\n StreamsAuthProvider,\n TransportError,\n TransportRoomStatus,\n} from \"./types\";\n\nexport type FetchLike = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport type ReadResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type BootstrapResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly snapshot: Uint8Array;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type SseControlData = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly streamCursor?: string;\n};\n\nexport type SseControlHandlerResult = void | \"pause\" | \"stop\";\n\nexport type SseItemsContinueResult = {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n readonly paused: boolean;\n};\n\nexport type SseItemsReadResult =\n | {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type SseItemsSession = {\n continue: (\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n ) => Promise<SseItemsContinueResult>;\n cancel: () => Promise<void>;\n};\n\nexport type SseItemsOpenResult =\n | { readonly kind: \"ok\"; readonly session: SseItemsSession }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type AppendResult =\n | {\n readonly kind: \"ok\";\n readonly ackEpoch: number;\n readonly ackSeq: number;\n readonly nextOffset: string;\n }\n | {\n readonly kind: \"stale-epoch\";\n readonly serverEpoch: number;\n }\n | {\n readonly kind: \"seq-gap\";\n readonly expectedSeq: number;\n };\n\nexport type ProducerSession = {\n readonly producerId: string;\n readonly producerEpoch: number;\n readonly nextProducerSeq: number;\n};\n\nexport type LiveReadMode = \"sse\" | \"long-poll\";\n\nexport type CursorSyncResult<TVersion extends JsonObject> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly streamCursor?: string;\n};\n\nexport type LocalExportResult<TVersion extends JsonObject> = {\n readonly batch?: CrdtUpdateBatch<TVersion>;\n readonly nextRefVersion: TVersion;\n};\n\nexport type SnapshotUploadRuntimeConfig = {\n readonly canUpload: () => Promise<boolean>;\n readonly debounceMs: number;\n readonly minBytesSinceRemoteSnapshot: bigint;\n};\n\nexport type SnapshotCodecRuntimeConfig = {\n readonly compress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n readonly decompress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n};\n\nexport type JoinState<TVersion extends JsonObject> = {\n refCount: number;\n closed: boolean;\n status: \"connecting\" | TransportRoomStatus;\n readonly statusListeners: Set<\n (status: \"connecting\" | TransportRoomStatus) => void\n >;\n readonly pendingLocal: Deque<CrdtUpdateBatch<TVersion>>;\n flushingLocal: boolean;\n retryAttempt: number;\n retrySleepController: AbortController;\n requestController?: AbortController;\n unsubscribeLocal?: () => void;\n cursor: RemoteCursor<TVersion>;\n liveMode: LiveReadMode;\n streamCursor?: string;\n snapshot: TransportSnapshotState;\n started: Promise<void>;\n};\n\nexport type JoinSyncResult<TVersion extends JsonObject> =\n CursorSyncResult<TVersion> & {\n readonly liveMode: LiveReadMode;\n readonly localExportRefVersion: TVersion;\n };\n\nexport type LastError = {\n code:\n | \"network\"\n | \"timeout\"\n | \"auth_failed\"\n | \"gone_410\"\n | \"protocol\"\n | \"internal\";\n message: string;\n retriable: boolean;\n atMs: number;\n};\n\nexport function createProducerId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n return `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\nexport class TimeoutError extends Error {\n readonly timeoutType: \"connect\" | \"poll\";\n readonly timeoutMs: number;\n\n constructor(timeoutType: \"connect\" | \"poll\", timeoutMs: number) {\n super(`TIMEOUT: ${timeoutType} request exceeded ${timeoutMs}ms`);\n this.timeoutType = timeoutType;\n this.timeoutMs = timeoutMs;\n }\n}\n\nexport class ProtocolError extends Error {\n readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.status = status;\n }\n}\n\nexport function getHeader(\n headers: Headers,\n lowerCaseName: string,\n titleCaseName: string,\n): string | null {\n return headers.get(lowerCaseName) ?? headers.get(titleCaseName);\n}\n\nfunction visibleHeaderNames(headers: Headers): string {\n const names: string[] = [];\n headers.forEach((_value, name) => {\n names.push(name);\n });\n if (names.length === 0) {\n return \"(none)\";\n }\n names.sort();\n return names.join(\", \");\n}\n\nexport function missingRequiredHeaderError(\n context: \"read\" | \"bootstrap\" | \"append\",\n requiredHeader: string,\n headers: Headers,\n): ProtocolError {\n return new ProtocolError(\n `${context} response missing ${requiredHeader}. ` +\n `If this is cross-origin, expose it via Access-Control-Expose-Headers (${requiredHeader.toLowerCase()}). ` +\n `Visible headers: ${visibleHeaderNames(headers)}`,\n );\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null;\n}\n\nexport function parseHeaderU64(value: string | null): number | null {\n if (value == null) {\n return null;\n }\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return null;\n }\n return parsed;\n}\n\nfunction decodeBase64Bytes(encoded: string): Uint8Array {\n // specs/ds-protocol.md allows servers to split base64 payloads across\n // multiple SSE data lines, and clients must remove the inserted CR/LF\n // characters before decoding.\n const normalized = encoded.replace(/[\\r\\n]/g, \"\");\n if (typeof Buffer !== \"undefined\") {\n return new Uint8Array(Buffer.from(normalized, \"base64\"));\n }\n if (typeof atob === \"function\") {\n const binary = atob(normalized);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n }\n throw new Error(\"base64 decoding is unavailable\");\n}\n\nfunction normalizeSseDataEncoding(\n dataEncoding: string | null | undefined,\n): \"base64\" | undefined {\n const normalized = dataEncoding?.trim().toLowerCase();\n if (normalized == null || normalized.length === 0) {\n return undefined;\n }\n if (normalized === \"base64\") {\n return \"base64\";\n }\n throw new ProtocolError(\n `unsupported stream-sse-data-encoding '${dataEncoding}'`,\n );\n}\n\nfunction decodeSseRawPayload(\n data: string,\n dataEncoding: \"base64\" | undefined,\n): Uint8Array {\n if (dataEncoding === \"base64\") {\n return decodeBase64Bytes(data);\n }\n return new TextEncoder().encode(data);\n}\n\nexport function decodeSseItemPayloads(\n data: string,\n dataEncoding?: string | null,\n): Uint8Array[] {\n const payload = decodeSseRawPayload(\n data,\n normalizeSseDataEncoding(dataEncoding),\n );\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\n/**\n * Decode a single multipart part body. If the payload contains\n * length-prefixed binary framed items, decode them. Otherwise\n * return the raw bytes as a single-element array. This mirrors the\n * try/fallback approach used by {@link decodeSseItemPayloads}.\n */\nexport function decodeMultipartPartItems(payload: Uint8Array): Uint8Array[] {\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\nexport function parseSseControlData(data: string): SseControlData {\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch {\n throw new ProtocolError(\"invalid sse control event JSON\");\n }\n if (!isObject(parsed) || typeof parsed.streamNextOffset !== \"string\") {\n throw new ProtocolError(\"sse control event missing streamNextOffset\");\n }\n const streamCursor =\n typeof parsed.streamCursor === \"string\" ? parsed.streamCursor : undefined;\n return {\n nextOffset: parsed.streamNextOffset,\n upToDate: parsed.upToDate === true,\n streamCursor,\n };\n}\n\nexport function isSseUnsupportedErrorText(message: string): boolean {\n const normalized = message.trim().toLowerCase();\n if (normalized.length === 0) {\n return false;\n }\n const mentionsSse =\n normalized.includes(\"sse\") || normalized.includes(\"text/event-stream\");\n const mentionsCompatibility =\n normalized.includes(\"unsupported\") ||\n normalized.includes(\"not support\") ||\n normalized.includes(\"incompatible\") ||\n normalized.includes(\"content type\");\n return mentionsSse && mentionsCompatibility;\n}\n\nexport function withCursorQuery(url: string, cursor?: string): string {\n if (cursor == null || cursor.length === 0) {\n return url;\n }\n const target = new URL(url);\n target.searchParams.set(\"cursor\", cursor);\n return target.toString();\n}\n\nexport function bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport function mergeAbortSignals(\n ...signals: ReadonlyArray<AbortSignal | null | undefined>\n): AbortSignal | undefined {\n const active = signals.filter(\n (signal): signal is AbortSignal => signal != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n if (active.length === 1) {\n return active[0];\n }\n if (typeof AbortSignal.any === \"function\") {\n return AbortSignal.any(active);\n }\n\n const controller = new AbortController();\n for (const signal of active) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n }\n return controller.signal;\n}\n\nexport function classifyFetchError(err: unknown): LastError {\n const message = err instanceof Error ? err.message : String(err);\n const status = err instanceof HttpError ? err.status : undefined;\n if (status === 401 || status === 403) {\n return { code: \"auth_failed\", message, retriable: false, atMs: Date.now() };\n }\n if (status === 410) {\n return { code: \"gone_410\", message, retriable: true, atMs: Date.now() };\n }\n if (status === 400 || status === 405 || status === 409) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (err instanceof ProtocolError) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (err instanceof TimeoutError) {\n return { code: \"timeout\", message, retriable: true, atMs: Date.now() };\n }\n if (err instanceof TypeError) {\n return { code: \"network\", message, retriable: true, atMs: Date.now() };\n }\n return { code: \"internal\", message, retriable: false, atMs: Date.now() };\n}\n\nexport function isStreamNotFoundError(error: unknown): boolean {\n return error instanceof HttpError && error.status === 404;\n}\n\nexport function toTransportError(error: unknown): TransportError {\n if (isStreamNotFoundError(error)) {\n return { code: \"stream_not_found\", retryable: false };\n }\n if (\n error instanceof HttpError &&\n (error.status === 401 || error.status === 403)\n ) {\n return {\n code: \"auth_failed\",\n retryable: false,\n status: error.status as 401 | 403,\n message: error.message,\n };\n }\n if (error instanceof HttpError && [400, 405, 409].includes(error.status)) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n };\n }\n if (error instanceof ProtocolError) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n };\n }\n if (error instanceof TimeoutError) {\n return {\n code: \"timeout\",\n retryable: true,\n phase: error.timeoutType,\n message: error.message,\n };\n }\n if (error instanceof HttpError && error.status === 410) {\n return { code: \"gone\", retryable: true, message: error.message };\n }\n if (error instanceof TypeError) {\n return {\n code: \"network_error\",\n retryable: true,\n message: error.message,\n };\n }\n if (error instanceof Error) {\n return {\n code: \"internal_error\",\n retryable: false,\n message: error.message,\n };\n }\n return {\n code: \"unknown\",\n retryable: false,\n message: String(error),\n };\n}\n\nexport function isReadNoProgress(\n requestOffset: string,\n result: ReadResult,\n): boolean {\n return (\n !result.upToDate &&\n result.updates.length === 0 &&\n result.nextOffset === requestOffset\n );\n}\n\nexport function isAuthOrProtocolError(error: unknown): boolean {\n return (\n error instanceof ProtocolError ||\n (error instanceof HttpError &&\n [400, 401, 403, 405, 409].includes(error.status))\n );\n}\n\nexport function normalizeAuthProvider(\n auth: StreamsAuthProvider | undefined,\n): ((context?: StreamsAuthContext) => Promise<string | undefined>) | undefined {\n if (auth == null) {\n return undefined;\n }\n if (typeof auth === \"string\") {\n const token = auth.trim();\n return async () => (token.length === 0 ? undefined : token);\n }\n return async (context) => {\n const value = await auth(context);\n const token = value?.trim() ?? \"\";\n return token.length === 0 ? undefined : token;\n };\n}\n\nexport function normalizeSnapshotUploadConfig(\n config: SnapshotUploadOptions | undefined,\n): SnapshotUploadRuntimeConfig | undefined {\n if (config == null || config.enabled === false) {\n return undefined;\n }\n if (!Number.isFinite(config.debounceMs ?? 10_000)) {\n throw new Error(\"snapshotUpload.debounceMs must be a finite number\");\n }\n if (!Number.isFinite(config.minBytesSinceRemoteSnapshot ?? 100 * 1024)) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be a finite number\",\n );\n }\n const debounceMs = Math.max(0, Math.floor(config.debounceMs ?? 10_000));\n const minBytes = config.minBytesSinceRemoteSnapshot ?? 100 * 1024;\n if (minBytes < 0) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be non-negative\",\n );\n }\n return {\n canUpload: async () => Boolean(await config.canUpload()),\n debounceMs,\n minBytesSinceRemoteSnapshot: BigInt(Math.floor(minBytes)),\n };\n}\n\nfunction assertSnapshotBytes(\n value: unknown,\n label: \"snapshotCodec.compress\" | \"snapshotCodec.decompress\",\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n throw new Error(`${label} must return Uint8Array`);\n}\n\nexport function normalizeSnapshotCodec(\n codec: SnapshotCodec | undefined,\n): SnapshotCodecRuntimeConfig | undefined {\n if (codec == null) {\n return undefined;\n }\n if (\n typeof codec.compress !== \"function\" ||\n typeof codec.decompress !== \"function\"\n ) {\n throw new Error(\n \"snapshotCodec.compress and snapshotCodec.decompress must be provided together\",\n );\n }\n return {\n compress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.compress(snapshot),\n \"snapshotCodec.compress\",\n ),\n decompress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.decompress(snapshot),\n \"snapshotCodec.decompress\",\n ),\n };\n}\n\nfunction jsonValueEquals(a: JsonValue, b: JsonValue): boolean {\n if (a === b) {\n return true;\n }\n if (typeof a !== typeof b || a == null || b == null) {\n return false;\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n if (!jsonValueEquals(a[i] as JsonValue, b[i] as JsonValue)) {\n return false;\n }\n }\n return true;\n }\n if (typeof a !== \"object\" || typeof b !== \"object\") {\n return false;\n }\n const aEntries = Object.entries(a).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n const bEntries = Object.entries(b).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n if (aEntries.length !== bEntries.length) {\n return false;\n }\n for (let i = 0; i < aEntries.length; i += 1) {\n const [aKey, aValue] = aEntries[i];\n const [bKey, bValue] = bEntries[i];\n if (aKey !== bKey || !jsonValueEquals(aValue as JsonValue, bValue as JsonValue)) {\n return false;\n }\n }\n return true;\n}\n\nexport function jsonObjectEquals(a: JsonObject, b: JsonObject): boolean {\n return jsonValueEquals(a, b);\n}\n\nexport function parseSnapshotByteOffset(\n offset: string | null | undefined,\n): bigint | null {\n if (offset == null || offset.length === 0 || offset === \"-1\") {\n return 0n;\n }\n if (!/^\\d+$/.test(offset)) {\n return null;\n }\n return BigInt(offset);\n}\n\nexport function resolveNextOffsetAfterConcurrentAdvance(\n requestOffset: string,\n currentOffset: string,\n responseNextOffset: string,\n): string {\n if (currentOffset === requestOffset) {\n return responseNextOffset;\n }\n if (currentOffset === responseNextOffset) {\n return currentOffset;\n }\n const current = parseSnapshotByteOffset(currentOffset);\n const incoming = parseSnapshotByteOffset(responseNextOffset);\n if (current != null && incoming != null) {\n return incoming > current ? responseNextOffset : currentOffset;\n }\n return currentOffset;\n}\n","import {\n StreamsClient,\n type StreamError,\n} from \"@loro-dev/streams-client\";\n\nimport { BINARY_CONTENT_TYPE, decodeItems, encodeItems } from \"./items\";\nimport type { ReconnectConfig } from \"./reconnect\";\nimport {\n AppendResult,\n BootstrapResult,\n decodeMultipartPartItems,\n decodeSseItemPayloads,\n FetchLike,\n HttpError,\n isSseUnsupportedErrorText,\n ProtocolError,\n parseSseControlData,\n type ProducerSession,\n ReadResult,\n type SseControlHandlerResult,\n type SseItemsOpenResult,\n type SseItemsReadResult,\n TimeoutError,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n JsonObject,\n MaybePromise,\n StreamsAuthContext,\n} from \"./types\";\n\ntype StreamsTransportClientOptions<TVersion extends JsonObject> = {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly authProvider?: (context?: StreamsAuthContext) => Promise<string | undefined>;\n readonly fetchImpl: FetchLike;\n readonly reconnectConfig: ReconnectConfig;\n};\n\n/**\n * Low-level HTTP and SSE client for one stream.\n *\n * This layer owns request headers, timeouts, and response parsing. Higher-level\n * join/sync state transitions stay in `transport-runtime.ts`.\n */\nexport class StreamsTransportClient<TVersion extends JsonObject> {\n readonly streamUrl: string;\n\n private readonly adapter: CrdtAdapter<TVersion>;\n private readonly client: StreamsClient;\n private readonly reconnectConfig: ReconnectConfig;\n\n constructor(options: StreamsTransportClientOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n this.client = new StreamsClient({\n url: options.streamUrl,\n auth: options.authProvider,\n fetch: options.fetchImpl as typeof globalThis.fetch,\n retry: {\n maxAttempts: 0,\n },\n timeout: {\n connectTimeoutMs: options.reconnectConfig.connectTimeoutMs,\n pollTimeoutMs: options.reconnectConfig.pollTimeoutMs,\n },\n });\n this.reconnectConfig = options.reconnectConfig;\n }\n\n async createStreamRequest(): Promise<boolean> {\n const adapterHeaders = this.adapter.getCreateStreamHeaders\n ? await this.adapter.getCreateStreamHeaders()\n : undefined;\n const result = await this.client.create({\n headers: adapterHeaders,\n });\n if (result.ok) {\n return result.result.created;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async deleteStreamRequest(): Promise<boolean> {\n const result = await this.client.delete();\n if (result.ok) {\n return result.result.deleted;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async readFromOffsetSseItems(\n offset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsReadResult> {\n return this.readItemsSse(\n offset,\n cursor,\n onControl,\n onOpen,\n signal,\n );\n }\n\n async readFromOffset(\n offset: string,\n cursor?: string,\n live?: \"long-poll\",\n signal?: AbortSignal,\n ): Promise<{ kind: \"gone\" } | { kind: \"ok\"; value: ReadResult }> {\n const result = await this.client.readOnce({\n offset,\n cursor,\n live,\n signal,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const payload = result.result.payload?.body ?? new Uint8Array();\n return {\n kind: \"ok\",\n value: {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n updates: payload.byteLength === 0 ? [] : decodeItems(payload),\n streamCursor: result.result.cursor,\n },\n };\n }\n\n async bootstrap(): Promise<BootstrapResult> {\n const result = await this.client.bootstrap();\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n\n return {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n snapshot: result.result.snapshot?.body ?? new Uint8Array(),\n updates: result.result.updates.flatMap((part) =>\n decodeMultipartPartItems(part.body)\n ),\n streamCursor: result.result.cursor,\n };\n }\n\n async readSnapshotOffsetValue(): Promise<string | null> {\n const result = await this.client.head();\n if (!result.ok) {\n if (result.result.code === \"not_found\") {\n return null;\n }\n throw this.toTransportFailure(result.result);\n }\n\n const snapshotOffset = result.result.snapshotOffset;\n return snapshotOffset == null || snapshotOffset.length === 0\n ? null\n : snapshotOffset;\n }\n\n async putSnapshotAt(\n snapshotOffset: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const result = await this.client.putSnapshot({\n offset: snapshotOffset,\n part: {\n contentType: \"application/octet-stream\",\n body: snapshot,\n },\n });\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n }\n\n async appendUpdates(\n updates: readonly Uint8Array[],\n producerSession: ProducerSession,\n ): Promise<AppendResult> {\n const result = await this.client.append({\n part: {\n contentType: BINARY_CONTENT_TYPE,\n body: encodeItems(updates),\n },\n producer: {\n producerId: producerSession.producerId,\n epoch: producerSession.producerEpoch,\n seq: producerSession.nextProducerSeq,\n },\n });\n if (result.ok) {\n return {\n kind: \"ok\",\n ackEpoch: result.result.producer?.epoch ?? producerSession.producerEpoch,\n ackSeq: result.result.producer?.seq ?? producerSession.nextProducerSeq,\n nextOffset: result.result.nextOffset,\n };\n }\n\n const error = result.result;\n if (\n error.code === \"conflict\" &&\n error.detail?.kind === \"producer_seq_gap\"\n ) {\n return { kind: \"seq-gap\", expectedSeq: error.detail.expectedSeq };\n }\n if (\n error.code === \"forbidden\" &&\n error.detail?.kind === \"stale_producer_epoch\"\n ) {\n return {\n kind: \"stale-epoch\",\n serverEpoch: error.detail.serverEpoch,\n };\n }\n throw this.toTransportFailure(error);\n }\n\n private toTransportFailure(error: StreamError): Error {\n switch (error.code) {\n case \"bad_request\":\n case \"unauthorized\":\n case \"forbidden\":\n case \"not_found\":\n case \"conflict\":\n case \"gone\":\n case \"rate_limited\":\n return new HttpError(error.message, error.status);\n case \"timeout\":\n return new TimeoutError(\n error.phase,\n error.phase === \"poll\"\n ? this.reconnectConfig.pollTimeoutMs\n : this.reconnectConfig.connectTimeoutMs,\n );\n case \"network_error\":\n return error.cause instanceof TypeError\n ? error.cause\n : new TypeError(error.message);\n case \"protocol_error\":\n return new ProtocolError(error.message, error.status);\n case \"unknown\":\n if (typeof error.status === \"number\") {\n return new HttpError(error.message, error.status);\n }\n if (error.cause instanceof TypeError) {\n return error.cause;\n }\n if (error.cause instanceof Error) {\n return error.cause;\n }\n return new Error(error.message);\n default: {\n const exhaustive: never = error;\n return new Error(String(exhaustive));\n }\n }\n }\n\n private async readItemsSse(\n initialOffset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsReadResult> {\n const opened = await this.openItemsSseSession(\n initialOffset,\n cursor,\n onOpen,\n signal,\n );\n if (opened.kind !== \"ok\") {\n return opened;\n }\n const result = await opened.session.continue(onControl);\n if (result.paused) {\n await opened.session.cancel();\n }\n return {\n kind: \"ok\",\n latestOffset: result.latestOffset,\n latestCursor: result.latestCursor,\n };\n }\n\n private async openItemsSseSession(\n initialOffset: string,\n cursor: string | undefined,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsOpenResult> {\n const result = await this.client.openSseSession({\n offset: initialOffset,\n cursor,\n signal,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n if (result.result.code === \"not_found\") {\n return { kind: \"not-found\" };\n }\n if (\n result.result.code === \"bad_request\" &&\n isSseUnsupportedErrorText(result.result.message)\n ) {\n return { kind: \"unsupported\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const session = result.result;\n onOpen?.();\n let pendingDataEvents: string[] = [];\n let latestOffset = session.nextOffset ?? initialOffset;\n let latestCursor = session.cursor;\n let exhausted = false;\n\n return {\n kind: \"ok\",\n session: {\n continue: async (nextOnControl) => {\n if (exhausted) {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n for await (const event of session.events) {\n if (event.event === \"data\") {\n pendingDataEvents.push(event.data);\n continue;\n }\n if (event.event !== \"control\") {\n continue;\n }\n const control = parseSseControlData(event.data);\n const updates: Uint8Array[] = [];\n for (const eventData of pendingDataEvents) {\n updates.push(\n ...decodeSseItemPayloads(eventData, session.dataEncoding),\n );\n }\n pendingDataEvents = [];\n const handlerResult = await nextOnControl(\n updates,\n control.nextOffset,\n control.upToDate,\n control.streamCursor,\n );\n latestOffset = control.nextOffset;\n if (control.streamCursor != null) {\n latestCursor = control.streamCursor;\n }\n if (handlerResult === \"stop\") {\n exhausted = true;\n await session.cancel();\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n if (handlerResult === \"pause\") {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: true,\n };\n }\n }\n exhausted = true;\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n },\n cancel: async () => {\n if (exhausted) {\n return;\n }\n exhausted = true;\n await session.cancel();\n },\n },\n };\n }\n}\n","import type {\n RemoteCursor,\n RemoteCursorSaveSource,\n} from \"./checkpoint\";\nimport {\n resolveNextOffsetAfterConcurrentAdvance,\n type ReadResult,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n JsonObject,\n MaybePromise,\n} from \"./types\";\n\ntype TransportCursorManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n};\n\nexport class TransportCursorManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n\n constructor(options: TransportCursorManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.decodeSnapshot = options.decodeSnapshot;\n this.saveRemoteCursor = options.saveRemoteCursor;\n }\n\n async applyBootstrapPayload(\n cursor: RemoteCursor<TVersion>,\n snapshot: Uint8Array,\n updates: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n let nextVersion = cursor.serverLowerBoundVersion;\n if (snapshot.byteLength > 0) {\n nextVersion = await this.adapter.applySnapshot(\n this.decodeSnapshot == null\n ? snapshot\n : await this.decodeSnapshot(snapshot),\n );\n }\n if (updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...updates],\n nextVersion,\n );\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n \"bootstrap\",\n );\n }\n\n async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n const snapshot = items[0] ?? new Uint8Array();\n return await this.applyBootstrapPayload(\n cursor,\n snapshot,\n items.slice(1),\n nextOffset,\n );\n }\n\n async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n [...items],\n nextOffset,\n \"remote\",\n undefined,\n );\n }\n\n async applyCatchupResult(\n cursor: RemoteCursor<TVersion>,\n result: ReadResult,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n result.updates,\n result.nextOffset,\n \"remote\",\n onRemoteVersionApplied,\n );\n }\n\n async applyLiveResult(\n cursor: RemoteCursor<TVersion>,\n requestOffset: string,\n result: {\n readonly nextOffset: string;\n readonly updates: readonly Uint8Array[];\n },\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n if (result.updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...result.updates],\n nextVersion,\n );\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset: resolveNextOffsetAfterConcurrentAdvance(\n requestOffset,\n cursor.nextOffset,\n result.nextOffset,\n ),\n serverLowerBoundVersion: nextVersion,\n },\n \"remote\",\n );\n }\n\n private async applyRemotePayload(\n cursor: RemoteCursor<TVersion>,\n updates: readonly Uint8Array[],\n nextOffset: string,\n source: RemoteCursorSaveSource,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n if (updates.length > 0) {\n nextVersion = await this.adapter.applyRemoteUpdates(\n [...updates],\n nextVersion,\n );\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n source,\n );\n }\n\n private async finalizeCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.saveRemoteCursor(cursor, source);\n }\n}\n","const DEFAULT_COMPACT_THRESHOLD = 64;\nconst DEFAULT_MIN_CAPACITY = 16;\n\nexport interface DequeOptions {\n readonly compactThreshold?: number;\n readonly minCapacity?: number;\n}\n\nexport class Deque<T> implements Iterable<T> {\n private buffer: Array<T | null | undefined>;\n private head = 0;\n private tail = 0;\n private readonly compactThreshold: number;\n private readonly minCapacity: number;\n\n constructor(values?: Iterable<T>, options: DequeOptions = {}) {\n this.compactThreshold = Math.max(\n 1,\n options.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD\n );\n this.minCapacity = Math.max(1, options.minCapacity ?? DEFAULT_MIN_CAPACITY);\n const seed = values == null ? [] : Array.from(values);\n this.buffer = seed.slice();\n this.tail = seed.length;\n }\n\n get length(): number {\n return this.tail - this.head;\n }\n\n isEmpty(): boolean {\n return this.length === 0;\n }\n\n clear(): void {\n this.buffer = [];\n this.head = 0;\n this.tail = 0;\n }\n\n peekFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n return value == null ? undefined : value;\n }\n\n peekBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.tail - 1];\n return value == null ? undefined : value;\n }\n\n at(index: number): T | undefined {\n const length = this.length;\n if (length === 0) {\n return undefined;\n }\n let offset = index;\n if (offset < 0) {\n offset += length;\n }\n if (offset < 0 || offset >= length) {\n return undefined;\n }\n const value = this.buffer[this.head + offset];\n return value == null ? undefined : value;\n }\n\n pushBack(value: T): number {\n this.ensureBackCapacity();\n this.buffer[this.tail] = value;\n this.tail += 1;\n return this.length;\n }\n\n pushFront(value: T): number {\n if (this.head === 0) {\n this.recenter(1, 0);\n }\n this.head -= 1;\n this.buffer[this.head] = value;\n return this.length;\n }\n\n popFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.head] = null;\n this.head += 1;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n popBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n this.tail -= 1;\n const value = this.buffer[this.tail];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.tail] = null;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n toArray(): T[] {\n const out: T[] = [];\n for (let i = this.head; i < this.tail; i += 1) {\n out.push(this.buffer[i] as T);\n }\n return out;\n }\n\n *[Symbol.iterator](): IterableIterator<T> {\n for (let i = this.head; i < this.tail; i += 1) {\n yield this.buffer[i] as T;\n }\n }\n\n private ensureBackCapacity(): void {\n if (this.tail < this.buffer.length) {\n return;\n }\n if (this.head > 0) {\n this.compactToFront();\n if (this.tail < this.buffer.length) {\n return;\n }\n }\n this.recenter(0, 1);\n }\n\n private recenter(extraFront: number, extraBack: number): void {\n const length = this.length;\n const needed = length + extraFront + extraBack;\n const currentCapacity = this.buffer.length;\n let nextCapacity = Math.max(currentCapacity, this.minCapacity);\n if (nextCapacity < needed) {\n nextCapacity = Math.max(nextCapacity * 2, needed, this.minCapacity);\n }\n const free = nextCapacity - length;\n const nextHead = Math.max(extraFront, Math.floor(free / 2));\n const next = new Array<T | null | undefined>(nextCapacity);\n for (let i = 0; i < length; i += 1) {\n next[nextHead + i] = this.buffer[this.head + i];\n }\n this.buffer = next;\n this.head = nextHead;\n this.tail = nextHead + length;\n }\n\n private compactIfNeeded(): void {\n const length = this.length;\n if (length === 0) {\n this.clear();\n return;\n }\n if (this.head < this.compactThreshold || this.head < length) {\n return;\n }\n this.compactToFront();\n }\n\n private compactToFront(): void {\n const length = this.length;\n this.buffer = this.buffer.slice(this.head, this.tail);\n this.head = 0;\n this.tail = length;\n }\n}\n","import {\n jsonObjectEquals,\n parseSnapshotByteOffset,\n type JoinState,\n type SnapshotUploadRuntimeConfig,\n} from \"./transport-internal\";\nimport type { CrdtAdapter, JsonObject, MaybePromise } from \"./types\";\n\nexport type TransportSnapshotState = {\n observedForeignWriter: boolean;\n taskScheduled: boolean;\n taskNeedsRerun: boolean;\n debounceTimer?: ReturnType<typeof setTimeout>;\n};\n\nexport function createTransportSnapshotState(): TransportSnapshotState {\n return {\n observedForeignWriter: false,\n taskScheduled: false,\n taskNeedsRerun: false,\n };\n}\n\ntype TransportSnapshotManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n readonly config?: SnapshotUploadRuntimeConfig;\n readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n readonly readSnapshotOffsetValue: () => Promise<string | null>;\n readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n readonly enqueueExclusive: <T>(task: () => MaybePromise<T>) => Promise<T>;\n readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n};\n\n/**\n * Manages debounce and upload gating for remote snapshots.\n *\n * All \"is this state still current / closed / reconnecting / blocked by a\n * foreign writer?\" checks live here so the runtime does not duplicate them.\n */\nexport class TransportSnapshotManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n private readonly config?: SnapshotUploadRuntimeConfig;\n private readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly readSnapshotOffsetValue: () => Promise<string | null>;\n private readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n private readonly enqueueExclusive: <T>(\n task: () => MaybePromise<T>,\n ) => Promise<T>;\n private readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n\n constructor(options: TransportSnapshotManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.config = options.config;\n this.encodeSnapshot = options.encodeSnapshot;\n this.readSnapshotOffsetValue = options.readSnapshotOffsetValue;\n this.putSnapshotAt = options.putSnapshotAt;\n this.enqueueExclusive = options.enqueueExclusive;\n this.logDebug = options.logDebug;\n this.logError = options.logError;\n this.isCurrentState = options.isCurrentState;\n }\n\n dispose(state: JoinState<TVersion>): void {\n this.clearDebounceTimer(state.snapshot);\n }\n\n observeRemoteVersion(\n state: JoinState<TVersion>,\n previousVersion: TVersion,\n nextVersion: TVersion,\n ): void {\n const snapshot = state.snapshot;\n if (snapshot.debounceTimer == null) {\n return;\n }\n if (!jsonObjectEquals(previousVersion, nextVersion)) {\n snapshot.observedForeignWriter = true;\n }\n }\n\n schedule(state: JoinState<TVersion>): void {\n const config = this.config;\n if (config == null || state.closed) {\n return;\n }\n const snapshot = state.snapshot;\n this.clearDebounceTimer(snapshot);\n snapshot.observedForeignWriter = false;\n snapshot.debounceTimer = setTimeout(() => {\n void this.handleDebounceExpiry(state);\n }, config.debounceMs);\n }\n\n private clearDebounceTimer(snapshot: TransportSnapshotState): void {\n if (snapshot.debounceTimer != null) {\n clearTimeout(snapshot.debounceTimer);\n snapshot.debounceTimer = undefined;\n }\n }\n\n private async handleDebounceExpiry(state: JoinState<TVersion>): Promise<void> {\n const snapshot = state.snapshot;\n snapshot.debounceTimer = undefined;\n if (this.getRunnableConfig(state) == null) {\n return;\n }\n if (snapshot.taskScheduled) {\n snapshot.taskNeedsRerun = true;\n return;\n }\n snapshot.taskScheduled = true;\n void this.enqueueExclusive(async () => {\n await this.maybeUpload(state);\n })\n .catch((error) => {\n this.logError(\"snapshot upload task failed\", error);\n })\n .finally(() => {\n snapshot.taskScheduled = false;\n if (snapshot.taskNeedsRerun && !state.closed) {\n snapshot.taskNeedsRerun = false;\n void this.handleDebounceExpiry(state);\n }\n });\n }\n\n private async maybeUpload(state: JoinState<TVersion>): Promise<void> {\n const config = await this.resolveUploadConfig(state);\n if (config == null) {\n return;\n }\n const tailBytes = parseSnapshotByteOffset(state.cursor.nextOffset);\n if (tailBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric offset token\", {\n offset: state.cursor.nextOffset,\n });\n return;\n }\n try {\n const remoteSnapshotOffset = await this.readSnapshotOffsetValue();\n const snapshotBytes = parseSnapshotByteOffset(remoteSnapshotOffset);\n if (snapshotBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric snapshot offset\", {\n offset: remoteSnapshotOffset,\n });\n return;\n }\n if (tailBytes <= snapshotBytes) {\n return;\n }\n if (tailBytes - snapshotBytes < config.minBytesSinceRemoteSnapshot) {\n return;\n }\n const snapshot = this.adapter.exportSnapshot();\n await this.putSnapshotAt(\n state.cursor.nextOffset,\n this.encodeSnapshot == null\n ? snapshot\n : await this.encodeSnapshot(snapshot),\n );\n } catch (error) {\n this.logError(\"snapshot upload failed\", error);\n }\n }\n\n private getRunnableConfig(\n state: JoinState<TVersion>,\n ): SnapshotUploadRuntimeConfig | undefined {\n const config = this.config;\n if (config == null || state.closed || !this.isCurrentState(state)) {\n return undefined;\n }\n if (\n state.snapshot.observedForeignWriter ||\n state.status === \"reconnecting\" ||\n state.status === \"disconnected\" ||\n state.status === \"error\"\n ) {\n return undefined;\n }\n return config;\n }\n\n private async resolveUploadConfig(\n state: JoinState<TVersion>,\n ): Promise<SnapshotUploadRuntimeConfig | undefined> {\n const config = this.getRunnableConfig(state);\n if (config == null) {\n return undefined;\n }\n if (!(await this.canUpload(config))) {\n return undefined;\n }\n return config;\n }\n\n private async canUpload(\n config: SnapshotUploadRuntimeConfig,\n ): Promise<boolean> {\n try {\n return Boolean(await config.canUpload());\n } catch (error) {\n this.logError(\"snapshot upload permission check failed\", error);\n return false;\n }\n }\n}\n","import { Deque } from \"./deque\";\nimport type { RemoteCursor } from \"./checkpoint\";\nimport type { JoinState, LiveReadMode } from \"./transport-internal\";\nimport {\n createTransportSnapshotState,\n type TransportSnapshotManager,\n} from \"./transport-snapshot\";\nimport type { JsonObject } from \"./types\";\n\nexport function createJoinState<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n liveMode: LiveReadMode = \"sse\",\n): JoinState<TVersion> {\n return {\n refCount: 0,\n closed: false,\n status: \"connecting\",\n statusListeners: new Set(),\n pendingLocal: new Deque(),\n flushingLocal: false,\n retryAttempt: 0,\n retrySleepController: new AbortController(),\n cursor,\n liveMode,\n snapshot: createTransportSnapshotState(),\n started: Promise.resolve(),\n };\n}\n\ntype DisposeJoinStateOptions<TVersion extends JsonObject> = {\n readonly snapshotManager: Pick<TransportSnapshotManager<TVersion>, \"dispose\">;\n readonly onDisposed?: () => void;\n};\n\nexport async function disposeJoinState<TVersion extends JsonObject>(\n state: JoinState<TVersion>,\n options: DisposeJoinStateOptions<TVersion>,\n): Promise<void> {\n state.closed = true;\n options.snapshotManager.dispose(state);\n const requestController = state.requestController;\n state.requestController = undefined;\n const unsubscribeLocal = state.unsubscribeLocal;\n state.unsubscribeLocal = undefined;\n requestController?.abort();\n unsubscribeLocal?.();\n options.onDisposed?.();\n}\n","import {\n composeBeforeRemoteCursorSaveHooks,\n createInitialRemoteCursor,\n InMemoryRemoteCursorStore,\n persistRemoteCursor,\n type BeforeRemoteCursorSaveHook,\n type RemoteCursor,\n type RemoteCursorSaveSource,\n type RemoteCursorStore,\n} from \"./checkpoint\";\nimport {\n computeRetryDelay,\n DEFAULT_RECONNECT_CONFIG,\n type ReconnectConfig,\n} from \"./reconnect\";\nimport { StreamsTransportClient } from \"./transport-client\";\nimport { TransportCursorManager } from \"./transport-cursor\";\nimport {\n createJoinState as createTransportJoinState,\n disposeJoinState as disposeTransportJoinState,\n} from \"./transport-room\";\nimport { TransportSnapshotManager } from \"./transport-snapshot\";\nimport {\n classifyFetchError,\n createProducerId,\n HttpError,\n isAuthOrProtocolError,\n isReadNoProgress,\n isStreamNotFoundError,\n normalizeAuthProvider,\n normalizeSnapshotCodec,\n normalizeSnapshotUploadConfig,\n sleep,\n toTransportError,\n type AppendResult,\n type BootstrapResult,\n type CursorSyncResult,\n type JoinState,\n type JoinSyncResult,\n type LiveReadMode,\n type LocalExportResult,\n type ProducerSession,\n type ReadResult,\n type SseControlHandlerResult,\n type SseItemsReadResult,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n CrdtUpdateBatch,\n StreamsCrdtLike,\n StreamsCrdtOptions,\n JsonObject,\n MaybePromise,\n Result,\n TransportCreateStreamSuccess,\n TransportDeleteStreamSuccess,\n TransportError,\n TransportJoinParams,\n TransportRoomStatus,\n TransportSubscription,\n TransportSyncSuccess,\n} from \"./types\";\n\n/**\n * Durable Streams transport runtime bound to one stream URL and one local CRDT adapter.\n */\nexport class StreamsCrdt<\n TVersion extends JsonObject,\n> implements StreamsCrdtLike<TVersion> {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly remoteCursorStore: RemoteCursorStore<TVersion>;\n\n private readonly createStreamIfMissing: boolean;\n private readonly client: StreamsTransportClient<TVersion>;\n private readonly cursorManager: TransportCursorManager<TVersion>;\n private readonly volatileCursorManager: TransportCursorManager<TVersion>;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n ) => MaybePromise<Uint8Array>;\n private readonly reconnectConfig: ReconnectConfig;\n private readonly debug: boolean;\n private readonly beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n private readonly snapshotManager: TransportSnapshotManager<TVersion>;\n private readonly producerId = createProducerId();\n private producerEpoch = 0;\n private nextProducerSeq = 0;\n private joinState?: JoinState<TVersion>;\n private opQueue: Promise<void> = Promise.resolve();\n\n /**\n * Internal catch-up result used when callers need to handle 410 by\n * preserving local state before rebuilding through bootstrap.\n */\n private static readonly GONE_CATCHUP = { kind: \"gone\" as const };\n\n /**\n * Creates a transport for one stream URL and one local adapter instance.\n */\n constructor(options: StreamsCrdtOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n const authProvider = normalizeAuthProvider(options.auth);\n this.createStreamIfMissing = options.createStreamIfMissing ?? false;\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.remoteCursorStore =\n options.remoteCursorStore ?? new InMemoryRemoteCursorStore<TVersion>();\n this.reconnectConfig = {\n ...DEFAULT_RECONNECT_CONFIG,\n ...options.reconnectConfig,\n };\n this.debug = options.debug ?? false;\n this.beforeRemoteCursorSave = composeBeforeRemoteCursorSaveHooks(\n options.beforeRemoteCursorSave,\n ...(options.beforeRemoteCursorSaveHooks ?? []),\n );\n const snapshotCodec = normalizeSnapshotCodec(options.snapshotCodec);\n const snapshotUploadConfig = normalizeSnapshotUploadConfig(\n options.snapshotUpload,\n );\n this.decodeSnapshot = snapshotCodec?.decompress;\n this.client = new StreamsTransportClient({\n streamUrl: this.streamUrl,\n adapter: this.adapter,\n authProvider,\n fetchImpl,\n reconnectConfig: this.reconnectConfig,\n });\n this.cursorManager = this.createCursorManager(\n this.adapter,\n (cursor, source) => this.saveRemoteCursor(cursor, source),\n );\n this.volatileCursorManager = this.createCursorManager(\n this.adapter,\n async (cursor) => cursor,\n );\n this.snapshotManager = new TransportSnapshotManager({\n adapter: this.adapter,\n config: snapshotUploadConfig,\n encodeSnapshot: snapshotCodec?.compress,\n readSnapshotOffsetValue: () => this.readSnapshotOffsetValue(),\n putSnapshotAt: (snapshotOffset, snapshot) =>\n this.putSnapshotAt(snapshotOffset, snapshot),\n enqueueExclusive: (task) => this.enqueueExclusive(task),\n logDebug: (message, detail) => this.logDebug(message, detail),\n logError: (message, error, detail) =>\n this.logError(message, error, detail),\n isCurrentState: (state) => this.joinState === state,\n });\n }\n\n /**\n * Stops any active live join and releases transport-owned state.\n */\n async close(): Promise<void> {\n const state = this.joinState;\n if (state != null) {\n await this.disposeJoinState(state);\n }\n }\n\n /**\n * Resets the retry counter and immediately attempts to reconnect.\n *\n * Use this after device wake or network recovery to skip any pending\n * backoff delay. No-op when the room is already joined or no join is active.\n *\n * If the room reached `\"disconnected\"` status (only possible with a finite\n * `maxAttempts` config), this restarts the read loop.\n */\n rejoin(): void {\n const state = this.joinState;\n if (state == null || state.closed || state.status === \"joined\") {\n return;\n }\n const wasDisconnected = state.status === \"disconnected\";\n state.retryAttempt = 0;\n state.retrySleepController.abort();\n state.retrySleepController = new AbortController();\n if (wasDisconnected) {\n this.setJoinStatus(state, \"reconnecting\");\n void this.runReadLoop(state);\n }\n }\n\n /**\n * Reads the latest remote snapshot offset, if the server exposes one.\n */\n async getSnapshotOffset(): Promise<Result<string | null, TransportError>> {\n try {\n return {\n ok: true,\n value: await this.readSnapshotOffsetValue(),\n };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Explicitly creates the target stream.\n */\n async createStream(): Promise<\n Result<TransportCreateStreamSuccess, TransportError>\n > {\n try {\n const created = await this.createStreamRequest();\n return { ok: true, value: { created } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Deletes the target stream and clears persisted replay state when supported by the cursor store.\n */\n async deleteStream(): Promise<\n Result<TransportDeleteStreamSuccess, TransportError>\n > {\n try {\n await this.close();\n const deleted = await this.enqueueExclusive(async () => {\n const deleted = await this.deleteStreamRequest();\n await this.remoteCursorStore.delete?.(this.streamUrl);\n this.producerEpoch = 0;\n this.nextProducerSeq = 0;\n return deleted;\n });\n return { ok: true, value: { deleted } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Runs one bootstrap/catch-up cycle without entering live mode.\n */\n async sync(): Promise<\n Result<TransportSyncSuccess<TVersion>, TransportError>\n > {\n try {\n const cursor = await this.syncOnce();\n return { ok: true, value: { cursor } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Replays remote state, flushes queued local updates, then enters live read mode.\n */\n async join(\n params?: TransportJoinParams,\n ): Promise<Result<TransportSubscription, TransportError>> {\n const existing = this.joinState;\n if (existing != null) {\n try {\n await existing.started;\n return { ok: true, value: this.createSubscription(existing, params) };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n const initialCursor = createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n const state = createTransportJoinState(initialCursor);\n state.started = this.startJoin(state);\n this.joinState = state;\n try {\n await state.started;\n return { ok: true, value: this.createSubscription(state, params) };\n } catch (error) {\n await this.disposeJoinState(state);\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n private createSubscription(\n state: JoinState<TVersion>,\n params?: TransportJoinParams,\n ): TransportSubscription {\n state.refCount += 1;\n const statusListener = params?.onStatusChange;\n if (statusListener != null) {\n state.statusListeners.add(statusListener);\n statusListener(state.status as TransportRoomStatus);\n }\n let unsubscribed = false;\n return {\n unsubscribe: () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n if (statusListener != null) {\n state.statusListeners.delete(statusListener);\n }\n state.refCount = Math.max(0, state.refCount - 1);\n if (state.refCount !== 0 || state.closed) {\n return;\n }\n void this.disposeJoinState(state);\n },\n get connected() {\n return state.status === \"joined\";\n },\n get status() {\n return state.status as TransportRoomStatus;\n },\n onStatusChange: (listener) => {\n state.statusListeners.add(listener);\n listener(state.status as TransportRoomStatus);\n return () => {\n state.statusListeners.delete(listener);\n };\n },\n };\n }\n\n private async disposeJoinState(state: JoinState<TVersion>): Promise<void> {\n await disposeTransportJoinState(state, {\n snapshotManager: this.snapshotManager,\n onDisposed: () => {\n if (this.joinState === state) {\n this.joinState = undefined;\n }\n },\n });\n }\n\n private async startJoin(state: JoinState<TVersion>): Promise<void> {\n try {\n state.requestController ??= new AbortController();\n const joined = await this.performInitialJoinSync(state.liveMode);\n state.cursor = joined.cursor;\n state.streamCursor = joined.streamCursor;\n state.liveMode = joined.liveMode;\n const pendingLocal = this.exportUpdates(\n joined.localExportRefVersion,\n );\n const hasQueuedPreSubscribeBatch = pendingLocal.batch != null;\n if (pendingLocal.batch != null) {\n state.pendingLocal.pushBack(pendingLocal.batch);\n }\n if (state.closed) {\n return;\n }\n state.unsubscribeLocal = this.adapter.subscribeLocalUpdates((batch) => {\n state.pendingLocal.pushBack(batch);\n void this.flushPendingLocal(state);\n });\n if (hasQueuedPreSubscribeBatch) {\n await this.flushPendingLocal(state);\n if (state.status === \"error\" || state.status === \"disconnected\") {\n throw new Error(\"join local sync failed before subscription became ready\");\n }\n }\n if (state.closed) {\n return;\n }\n this.setJoinStatus(state, \"joined\");\n void this.runReadLoop(state);\n } catch (error) {\n if (state.closed) {\n return;\n }\n this.snapshotManager.dispose(state);\n this.logError(\"join first sync failed\", error, {\n streamUrl: this.streamUrl,\n });\n this.setJoinStatus(state, \"error\");\n throw error;\n }\n }\n\n private async syncOnce(): Promise<RemoteCursor<TVersion>> {\n let cursor = (await this.resolveInitialRemoteState()).cursor;\n const localBatch = this.exportUpdates(\n cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n cursor = await this.appendLocalBatch(cursor, localBatch.batch);\n cursor = (await this.catchup(cursor)).cursor;\n }\n return cursor;\n }\n\n private async performInitialJoinSync(\n preferredLiveMode: LiveReadMode,\n ): Promise<JoinSyncResult<TVersion>> {\n const initial = await this.resolveInitialRemoteState();\n let cursor = initial.cursor;\n let localExportRefVersion = cursor.serverLowerBoundVersion;\n let streamCursor = initial.streamCursor;\n let liveMode = preferredLiveMode;\n\n const exportedLocal = this.exportUpdates(localExportRefVersion);\n localExportRefVersion = exportedLocal.nextRefVersion;\n if (exportedLocal.batch != null) {\n cursor = await this.appendLocalBatch(cursor, exportedLocal.batch);\n const caughtUp = await this.catchup(cursor);\n cursor = caughtUp.cursor;\n streamCursor = caughtUp.streamCursor;\n }\n localExportRefVersion = cursor.serverLowerBoundVersion;\n\n return {\n cursor,\n streamCursor,\n liveMode,\n localExportRefVersion,\n };\n }\n\n private createInitialCursor(): RemoteCursor<TVersion> {\n return createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n }\n\n private async resolveInitialRemoteState(): Promise<CursorSyncResult<TVersion>> {\n const loaded = await this.remoteCursorStore.load(this.streamUrl);\n const cursor = loaded ?? this.createInitialCursor();\n\n try {\n // When a cursor already exists, transport resumes from `nextOffset`\n // instead of bootstrapping. That requires the caller to have already\n // restored the matching local CRDT state for `serverLowerBoundVersion`.\n if (loaded == null) {\n return await this.bootstrapState(cursor);\n }\n const caughtUp = await this.catchupOrGone(cursor, {\n goneStrategy: \"return-gone\",\n });\n if (\"kind\" in caughtUp) {\n return await this.recoverFromGoneWithLocalBootstrap(cursor);\n }\n return caughtUp;\n } catch (error) {\n if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) {\n throw error;\n }\n await this.createStreamRequest();\n return await this.bootstrapState(this.createInitialCursor());\n }\n }\n\n private async bootstrapState(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly cursorManager?: TransportCursorManager<TVersion>;\n } = {},\n ): Promise<CursorSyncResult<TVersion>> {\n const cursorManager = options.cursorManager ?? this.cursorManager;\n const result = await this.bootstrap(this.streamUrl);\n let saved = await cursorManager.applyBootstrapPayload(\n cursor,\n result.snapshot,\n result.updates,\n result.nextOffset,\n );\n let bootstrapCursor = result.streamCursor;\n if (!result.upToDate) {\n const caughtUp = await this.catchupOrGone(saved, {\n streamCursor: bootstrapCursor,\n cursorManager,\n });\n if (\"kind\" in caughtUp) {\n throw new Error(\"bootstrap catch-up unexpectedly returned gone\");\n }\n saved = caughtUp.cursor;\n bootstrapCursor = caughtUp.streamCursor;\n }\n return { cursor: saved, streamCursor: bootstrapCursor };\n }\n\n private async catchup(\n cursor: RemoteCursor<TVersion>,\n live?: \"long-poll\",\n streamCursor?: string,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<CursorSyncResult<TVersion>> {\n const result = await this.catchupOrGone(cursor, {\n live,\n streamCursor,\n onRemoteVersionApplied,\n });\n if (\"kind\" in result) {\n throw new Error(\"catchup unexpectedly returned gone\");\n }\n return result;\n }\n\n private async catchupOrGone(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly live?: \"long-poll\";\n readonly streamCursor?: string;\n readonly onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>;\n readonly goneStrategy?: \"bootstrap\" | \"return-gone\";\n readonly cursorManager?: TransportCursorManager<TVersion>;\n } = {},\n ): Promise<CursorSyncResult<TVersion> | typeof StreamsCrdt.GONE_CATCHUP> {\n let current = cursor;\n let currentStreamCursor = options.streamCursor;\n const goneStrategy = options.goneStrategy ?? \"bootstrap\";\n const cursorManager = options.cursorManager ?? this.cursorManager;\n while (true) {\n const requestOffset = current.nextOffset;\n const response = await this.readFromOffset(\n this.streamUrl,\n requestOffset,\n currentStreamCursor,\n options.live,\n );\n if (response.kind === \"gone\") {\n if (goneStrategy === \"return-gone\") {\n return StreamsCrdt.GONE_CATCHUP;\n }\n return await this.bootstrapState(\n {\n ...current,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n { cursorManager },\n );\n }\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n current = await cursorManager.applyCatchupResult(\n current,\n result,\n options.onRemoteVersionApplied,\n );\n currentStreamCursor = result.streamCursor;\n if (result.upToDate) {\n return { cursor: current, streamCursor: currentStreamCursor };\n }\n }\n }\n\n private async appendLocalBatch(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n persist = true,\n ): Promise<RemoteCursor<TVersion>> {\n let producerSession: ProducerSession = {\n producerId: this.producerId,\n producerEpoch: this.producerEpoch,\n nextProducerSeq: this.nextProducerSeq,\n };\n for (let attempt = 0; attempt < 3; attempt += 1) {\n const result = await this.appendUpdates(\n this.streamUrl,\n batch.updates,\n producerSession,\n );\n if (result.kind === \"ok\") {\n this.producerEpoch = result.ackEpoch;\n this.nextProducerSeq = result.ackSeq + 1;\n const next: RemoteCursor<TVersion> = {\n ...cursor,\n nextOffset: result.nextOffset,\n serverLowerBoundVersion: this.adapter.mergeVersions(\n cursor.serverLowerBoundVersion,\n batch.version,\n ),\n };\n return persist ? await this.saveRemoteCursor(next, \"local\") : next;\n }\n if (result.kind === \"seq-gap\") {\n producerSession = {\n ...producerSession,\n nextProducerSeq: result.expectedSeq,\n };\n continue;\n }\n producerSession = {\n ...producerSession,\n producerEpoch: result.serverEpoch + 1,\n nextProducerSeq: 0,\n };\n }\n throw new Error(\"append retry budget exhausted\");\n }\n\n private exportUpdates(fromVersion: TVersion): LocalExportResult<TVersion> {\n const exported = this.adapter.exportUpdates(fromVersion);\n if (exported == null) {\n return { nextRefVersion: fromVersion };\n }\n const nextRefVersion = this.adapter.mergeVersions(\n fromVersion,\n exported.version,\n );\n if (exported.updates.length === 0) {\n return { nextRefVersion };\n }\n return {\n batch: exported,\n nextRefVersion,\n };\n }\n\n private async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyBootstrapItems(\n cursor,\n items,\n nextOffset,\n );\n }\n\n private async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyRemoteItems(cursor, items, nextOffset);\n }\n\n private createCursorManager(\n adapter: Pick<CrdtAdapter<TVersion>, \"applySnapshot\" | \"applyRemoteUpdates\">,\n saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>,\n ): TransportCursorManager<TVersion> {\n return new TransportCursorManager({\n adapter,\n decodeSnapshot: this.decodeSnapshot,\n saveRemoteCursor,\n });\n }\n\n private async createIsolatedCursorManager(): Promise<\n TransportCursorManager<TVersion> | null\n > {\n const isolated = await this.adapter.createIsolatedAdapter?.();\n if (isolated == null) {\n return null;\n }\n return this.createCursorManager(isolated, async (cursor) => cursor);\n }\n\n private async recoverFromGoneWithLocalBootstrap(\n cursor: RemoteCursor<TVersion>,\n ): Promise<CursorSyncResult<TVersion>> {\n const isolatedCursorManager = await this.createIsolatedCursorManager();\n const bootstrapCursor = {\n ...cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n };\n\n if (isolatedCursorManager != null) {\n const inferredRemote = await this.bootstrapState(bootstrapCursor, {\n cursorManager: isolatedCursorManager,\n });\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n await this.appendLocalBatch(\n inferredRemote.cursor,\n localBatch.batch,\n false,\n );\n }\n return await this.bootstrapState(bootstrapCursor);\n }\n\n const preservedLocalSnapshot = this.adapter.exportSnapshot();\n let restoredLocalSnapshot = false;\n\n try {\n // specs/ds-client-state.md §4.2 / §6 and specs/ds-extensions.md\n // require 410 recovery to rebuild through bootstrap. Preserve local\n // cached state long enough to export a first-sync delta against the\n // inferred remote version, then bootstrap again so persisted cursor and\n // visible local CRDT state converge on the same server truth.\n const inferredRemote = await this.bootstrapState(bootstrapCursor, {\n cursorManager: this.volatileCursorManager,\n });\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n restoredLocalSnapshot = true;\n\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n await this.appendLocalBatch(\n inferredRemote.cursor,\n localBatch.batch,\n false,\n );\n }\n\n return await this.bootstrapState(bootstrapCursor);\n } catch (error) {\n if (!restoredLocalSnapshot) {\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n }\n throw error;\n }\n }\n\n private async runReadLoop(state: JoinState<TVersion>): Promise<void> {\n state.requestController ??= new AbortController();\n while (!state.closed) {\n try {\n if (state.liveMode === \"long-poll\") {\n const requestOffset = state.cursor.nextOffset;\n const response = await this.readFromOffset(\n this.streamUrl,\n requestOffset,\n state.streamCursor,\n \"long-poll\",\n state.requestController?.signal,\n );\n if (response.kind === \"gone\") {\n const bootstrapped = await this.bootstrapState({\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n continue;\n }\n\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n requestOffset,\n result,\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n if (result.streamCursor != null) {\n state.streamCursor = result.streamCursor;\n }\n });\n if (state.closed) {\n return;\n }\n state.retryAttempt = 0;\n this.setJoinStatus(state, \"joined\");\n await sleep(0);\n continue;\n }\n\n const sseRequestOffset = state.cursor.nextOffset;\n const pendingRemote: Array<{\n updates: Uint8Array[];\n nextOffset: string;\n streamCursor?: string;\n }> = [];\n let flushChain = Promise.resolve();\n const sseResult = await this.readFromOffsetSseItems(\n this.streamUrl,\n sseRequestOffset,\n state.streamCursor,\n (updates, nextOffset, _upToDate, streamCursor) => {\n pendingRemote.push({ updates, nextOffset, streamCursor });\n flushChain = flushChain.then(async () => {\n const events = pendingRemote.splice(0);\n if (events.length === 0 || state.closed) {\n return;\n }\n const allUpdates = events.flatMap((e) => e.updates);\n const lastEvent = events[events.length - 1]!;\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n sseRequestOffset,\n { nextOffset: lastEvent.nextOffset, updates: allUpdates },\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n for (let i = events.length - 1; i >= 0; i--) {\n if (events[i]!.streamCursor != null) {\n state.streamCursor = events[i]!.streamCursor!;\n break;\n }\n }\n });\n this.setJoinStatus(state, \"joined\");\n });\n },\n () => {\n this.setJoinStatus(state, \"joined\");\n },\n state.requestController?.signal,\n );\n await flushChain;\n if (state.closed) {\n return;\n }\n if (sseResult.kind === \"ok\") {\n state.retryAttempt = 0;\n state.streamCursor = sseResult.latestCursor;\n await sleep(0);\n continue;\n }\n if (sseResult.kind === \"unsupported\") {\n state.liveMode = \"long-poll\";\n await sleep(0);\n continue;\n }\n if (sseResult.kind === \"not-found\") {\n this.logError(\n \"live read failed\",\n new HttpError(\"stream not found\", 404),\n {\n streamUrl: this.streamUrl,\n code: \"stream_not_found\",\n },\n );\n this.setJoinStatus(state, \"error\");\n return;\n }\n const bootstrapped = await this.bootstrapState({\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n } catch (error) {\n if (state.closed) {\n return;\n }\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"live read failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n this.setJoinStatus(state, \"error\");\n return;\n }\n this.setJoinStatus(state, \"reconnecting\");\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (state.retryAttempt >= maxAttempts) {\n this.logError(\"live read reconnect exhausted\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n this.setJoinStatus(state, \"disconnected\");\n return;\n }\n const delay = computeRetryDelay(\n state.retryAttempt,\n this.reconnectConfig,\n );\n state.retryAttempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n\n /** Maximum encoded size (bytes) for a single batched append. */\n private static readonly MAX_BATCH_BYTES = 256 * 1024;\n\n /**\n * Drain up to `MAX_BATCH_BYTES` worth of queued batches from the front of\n * `pendingLocal` and merge them into a single `CrdtUpdateBatch`.\n *\n * Returns `undefined` when the queue is empty.\n */\n private drainPendingBatch(\n state: JoinState<TVersion>,\n ): { batch: CrdtUpdateBatch<TVersion>; count: number } | undefined {\n const first = state.pendingLocal.peekFront();\n if (first == null) {\n return undefined;\n }\n\n const maxBytes = StreamsCrdt.MAX_BATCH_BYTES;\n let totalBytes = first.updates.reduce(\n (sum, u) => sum + 4 + u.byteLength,\n 0,\n );\n let mergedVersion = first.version;\n const allUpdates: Uint8Array[] = Array.from(first.updates);\n let count = 1;\n\n while (count < state.pendingLocal.length) {\n const next = state.pendingLocal.at(count)!;\n const nextBytes = next.updates.reduce(\n (sum, u) => sum + 4 + u.byteLength,\n 0,\n );\n if (totalBytes + nextBytes > maxBytes) {\n break;\n }\n allUpdates.push(...next.updates);\n totalBytes += nextBytes;\n mergedVersion = this.adapter.mergeVersions(mergedVersion, next.version);\n count += 1;\n }\n\n if (count === 1) {\n return { batch: first, count: 1 };\n }\n return { batch: { updates: allUpdates, version: mergedVersion }, count };\n }\n\n private async flushPendingLocal(state: JoinState<TVersion>): Promise<void> {\n if (state.flushingLocal || state.closed) {\n return;\n }\n state.flushingLocal = true;\n try {\n while (!state.closed) {\n const drained = this.drainPendingBatch(state);\n if (drained == null) {\n return;\n }\n let attempts = 0;\n while (!state.closed) {\n try {\n state.cursor = await this.enqueueExclusive(async () =>\n this.appendLocalBatch(state.cursor, drained.batch),\n );\n for (let i = 0; i < drained.count; i += 1) {\n state.pendingLocal.popFront();\n }\n this.setJoinStatus(state, \"joined\");\n this.snapshotManager.schedule(state);\n break;\n } catch (error) {\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"local append failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n this.setJoinStatus(state, \"error\");\n return;\n }\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (attempts >= maxAttempts) {\n this.setJoinStatus(state, \"error\");\n return;\n }\n this.setJoinStatus(state, \"reconnecting\");\n const delay = computeRetryDelay(attempts, this.reconnectConfig);\n attempts += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n } finally {\n state.flushingLocal = false;\n }\n }\n\n private async saveRemoteCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ): Promise<RemoteCursor<TVersion>> {\n return await persistRemoteCursor({\n remoteCursorStore: this.remoteCursorStore,\n cursor,\n source,\n beforeRemoteCursorSave: this.beforeRemoteCursorSave,\n });\n }\n\n private setJoinStatus(\n state: JoinState<TVersion>,\n status: TransportRoomStatus,\n ): void {\n if (state.status === status) {\n return;\n }\n state.status = status;\n for (const listener of state.statusListeners) {\n listener(status);\n }\n }\n\n private async createStreamRequest(): Promise<boolean> {\n return await this.client.createStreamRequest();\n }\n\n private async deleteStreamRequest(): Promise<boolean> {\n return await this.client.deleteStreamRequest();\n }\n\n private async readFromOffsetSseItems(\n _streamUrl: string,\n offset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n ): Promise<SseItemsReadResult> {\n return await this.client.readFromOffsetSseItems(\n offset,\n cursor,\n onControl,\n onOpen,\n signal,\n );\n }\n\n private async readFromOffset(\n _streamUrl: string,\n offset: string,\n cursor?: string,\n live?: \"long-poll\",\n signal?: AbortSignal,\n ): Promise<{ kind: \"gone\" } | { kind: \"ok\"; value: ReadResult }> {\n return await this.client.readFromOffset(offset, cursor, live, signal);\n }\n\n private async bootstrap(_streamUrl: string): Promise<BootstrapResult> {\n return await this.client.bootstrap();\n }\n\n private async readSnapshotOffsetValue(): Promise<string | null> {\n return await this.client.readSnapshotOffsetValue();\n }\n\n private async putSnapshotAt(\n snapshotOffset: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n await this.client.putSnapshotAt(snapshotOffset, snapshot);\n }\n\n private async appendUpdates(\n _streamUrl: string,\n updates: readonly Uint8Array[],\n producerSession: ProducerSession,\n ): Promise<AppendResult> {\n return await this.client.appendUpdates(updates, producerSession);\n }\n\n private enqueueExclusive<T>(task: () => MaybePromise<T>): Promise<T> {\n const run = this.opQueue.then(task, task);\n this.opQueue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private logDebug(message: string, detail?: Record<string, unknown>): void {\n if (!this.debug) {\n return;\n }\n if (detail == null) {\n console.debug(`[streams-crdt] ${message}`);\n return;\n }\n console.debug(`[streams-crdt] ${message}`, detail);\n }\n\n private logError(\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ): void {\n const payload =\n error == null\n ? detail\n : {\n ...detail,\n error: error instanceof Error ? error.message : String(error),\n };\n if (payload == null) {\n console.error(`[streams-crdt] ${message}`);\n return;\n }\n console.error(`[streams-crdt] ${message}`, payload);\n }\n}\n","const MAX_STREAM_ID_BYTES = 512;\nconst textEncoder = new TextEncoder();\nconst DEFAULT_BASE_URL = \"https://streams-api.loro.dev\";\n\nfunction utf8ByteLength(value: string): number {\n return textEncoder.encode(value).byteLength;\n}\n\n/**\n * Validates a stream id accepted by `createStreamUrl()`.\n */\nexport function isValidRillId(id: string): boolean {\n return (\n id.length > 0 &&\n utf8ByteLength(id) <= MAX_STREAM_ID_BYTES &&\n !id.includes(\"/\") &&\n !id.includes(\"\\0\") &&\n !id.includes(\"..\")\n );\n}\n\n/**\n * Validates a bucket id accepted by `createStreamUrl()`.\n */\nexport function isValidBucketId(id: string): boolean {\n return isValidRillId(id);\n}\n\n/**\n * Builds a Durable Streams HTTP URL for one `(bucketId, streamId)` pair.\n */\nexport function createStreamUrl(input: {\n bucketId: string;\n streamId: string;\n baseUrl?: string;\n}): string {\n const baseUrl = (input.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/g, \"\");\n if (!isValidBucketId(input.bucketId)) {\n throw new Error(\n `invalid bucketId: ${JSON.stringify(input.bucketId)} ` +\n `(must be non-empty UTF-8, <= ${MAX_STREAM_ID_BYTES} bytes, and exclude \"/\", \"\\\\0\", \"..\")`,\n );\n }\n if (!isValidRillId(input.streamId)) {\n throw new Error(\n `invalid streamId: ${JSON.stringify(input.streamId)} ` +\n `(must be non-empty UTF-8, <= ${MAX_STREAM_ID_BYTES} bytes, and exclude \"/\", \"\\\\0\", \"..\")`,\n );\n }\n const streamId = input.streamId;\n return `${baseUrl}/ds/${encodeURIComponent(input.bucketId)}/${encodeURIComponent(streamId)}`;\n}\n"],"mappings":"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"}
|