@loro-dev/streams-crdt 0.1.1 → 0.2.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/README.md CHANGED
@@ -184,8 +184,31 @@ the transport also clears the stored replay cursor for that stream.
184
184
  - a sync callback returning a string
185
185
  - an async callback returning a string
186
186
 
187
- This is intended for refresh-token flows. `StreamsCrdt` asks for a token before
188
- requests; token refresh itself remains caller-owned.
187
+ `StreamsCrdt` asks for a token before each request. If a request comes back
188
+ with `401` or `403`, it calls `auth` one more time with
189
+ `{ reason: "unauthorized", status, previousToken }` and retries that request
190
+ once.
191
+
192
+ This lets the caller keep a cached token in the normal path, and only refresh
193
+ after the server rejects it:
194
+
195
+ ```ts
196
+ let token = readCachedGatewayJwt();
197
+
198
+ const transport = new StreamsCrdt({
199
+ streamUrl,
200
+ adapter,
201
+ auth: async (context) => {
202
+ if (context?.reason === "unauthorized") {
203
+ token = await refreshGatewayJwt();
204
+ }
205
+ return token;
206
+ },
207
+ });
208
+ ```
209
+
210
+ If your callback already returns a fresh token every time, you can ignore the
211
+ `context` argument.
189
212
 
190
213
  ## Remote Cursor Store
191
214
 
@@ -474,7 +497,7 @@ const transport = new StreamsCrdt({
474
497
  `@loro-dev/streams-crdt/flock` re-exports everything from the root plus:
475
498
 
476
499
  - `createFlockAdapter(flock)` — creates a `CrdtAdapter` for one `Flock` replica
477
- - `FlockVersionVector` — Flock-specific version vector type
500
+ - `VersionVector`, `ExportBundle` — re-exported from `@loro-dev/flock-wasm`
478
501
 
479
502
  ### Zstd Entry Point
480
503
 
@@ -491,7 +514,7 @@ const transport = new StreamsCrdt({
491
514
  - `createStreamIfMissing` defaults to `false` and only affects the initial sync/join bootstrap
492
515
  - `deleteStream()` returns `{ deleted: false }` when the target stream is already missing
493
516
  - `sync()` and `join()` return `Result` objects instead of implicitly creating missing streams
494
- - `join()` resolves only after the initial replay has succeeded; without a stored remote cursor it prefers bootstrap-live SSE and falls back sticky to long-poll
517
+ - `join()` resolves only after the initial replay has succeeded; without a stored remote cursor it bootstraps once, then prefers ordinary live SSE and falls back sticky to long-poll
495
518
  - One transport instance maps to one DS stream
496
519
  - One adapter instance maps to one local CRDT instance
497
520
  - Built-in remote cursor stores implement `delete(streamUrl)` so `deleteStream()` can reset replay state cleanly
package/dist/flock.d.ts CHANGED
@@ -1,36 +1,11 @@
1
- import { A as createInitialRemoteCursor, C as BeforeRemoteCursorSaveHook, D as RemoteCursor, E as IndexedDbRemoteCursorStoreOptions, O as RemoteCursorSaveSource, S as BeforeRemoteCursorSaveContext, T as IndexedDbRemoteCursorStore, _ as TransportError, a as CrdtAdapter, b as TransportSubscription, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportDeleteStreamSuccess, h as TransportCreateStreamSuccess, i as StreamsCrdt, k as RemoteCursorStore, l as Result, m as StreamsCrdtOptions, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthProvider, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportJoinParams, w as InMemoryRemoteCursorStore, x as TransportSyncSuccess, y as TransportRoomStatus } from "./stream-id-B9n-uFFB.js";
1
+ import { A as RemoteCursorStore, C as BeforeRemoteCursorSaveContext, D as IndexedDbRemoteCursorStoreOptions, E as IndexedDbRemoteCursorStore, O as RemoteCursor, S as TransportSyncSuccess, T as InMemoryRemoteCursorStore, _ as TransportDeleteStreamSuccess, a as CrdtAdapter, b as TransportRoomStatus, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportCreateStreamSuccess, h as StreamsCrdtOptions, i as StreamsCrdt, j as createInitialRemoteCursor, k as RemoteCursorSaveSource, l as Result, m as StreamsAuthProvider, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthContext, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportError, w as BeforeRemoteCursorSaveHook, x as TransportSubscription, y as TransportJoinParams } from "./stream-id-BxGVitUa.js";
2
+ import { ExportBundle, Flock, VersionVector, VersionVector as VersionVector$1 } from "@loro-dev/flock-wasm";
2
3
 
3
4
  //#region src/flock.d.ts
4
- type FlockVersionEntry = {
5
- logicalCounter: number;
6
- physicalTime?: number;
7
- };
8
- /**
9
- * Flock version vector shape used by the built-in Flock adapter.
10
- */
11
- type FlockVersionVector = Record<string, FlockVersionEntry>;
12
- type FlockExportBundle = {
13
- readonly version?: number;
14
- readonly entries: Record<string, unknown>;
15
- };
16
- type FlockLike = {
17
- exportJson(options?: {
18
- from?: FlockVersionVector;
19
- peerId?: string;
20
- }): FlockExportBundle;
21
- importJson(bundle: FlockExportBundle): unknown;
22
- merge?(replica: unknown): unknown;
23
- peerId?(): string;
24
- version(): unknown;
25
- exportFile?(): Uint8Array;
26
- subscribe?(listener: (batch: {
27
- source?: string;
28
- }) => void): () => void;
29
- };
30
5
  /**
31
6
  * Creates a `CrdtAdapter` for one Flock replica.
32
7
  */
33
- declare function createFlockAdapter(flock: FlockLike): CrdtAdapter<JsonObject>;
8
+ declare function createFlockAdapter(flock: Flock): CrdtAdapter<VersionVector$1>;
34
9
  //#endregion
35
- export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, FlockVersionVector, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
10
+ export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, type ExportBundle, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, type VersionVector, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
36
11
  //# sourceMappingURL=flock.d.ts.map
package/dist/flock.js CHANGED
@@ -1,74 +1,45 @@
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-YOFZ4I6Y.js";
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-kM9JM1F7.js";
2
2
  //#region src/flock.ts
3
- function toVersionVector(version) {
4
- const out = {};
5
- for (const [peer, raw] of Object.entries(version)) {
6
- if (typeof raw !== "object" || raw == null || Array.isArray(raw)) continue;
7
- const logicalCounter = typeof raw.logicalCounter === "number" ? raw.logicalCounter : void 0;
8
- if (logicalCounter == null || !Number.isFinite(logicalCounter)) continue;
9
- out[peer] = typeof raw.physicalTime === "number" && Number.isFinite(raw.physicalTime) ? {
10
- logicalCounter,
11
- physicalTime: raw.physicalTime
12
- } : { logicalCounter };
13
- }
14
- return out;
15
- }
16
- function fromVersionVector(version) {
17
- const out = {};
18
- for (const [peer, entry] of Object.entries(version)) out[peer] = entry.physicalTime === void 0 ? { logicalCounter: entry.logicalCounter } : {
19
- logicalCounter: entry.logicalCounter,
20
- physicalTime: entry.physicalTime
21
- };
22
- return out;
3
+ function isAfter(entry, current) {
4
+ return entry.physicalTime > current.physicalTime || entry.physicalTime === current.physicalTime && entry.logicalCounter > current.logicalCounter;
23
5
  }
24
6
  function mergeVersions(base, incoming) {
25
7
  const merged = { ...base };
26
8
  for (const [peer, entry] of Object.entries(incoming)) {
9
+ if (entry == null) continue;
27
10
  const current = merged[peer];
28
- if (current == null || entry.logicalCounter > current.logicalCounter) {
29
- merged[peer] = { ...entry };
30
- continue;
31
- }
32
- if (entry.logicalCounter < current.logicalCounter) continue;
33
- const physicalTime = Math.max(current.physicalTime ?? Number.NEGATIVE_INFINITY, entry.physicalTime ?? Number.NEGATIVE_INFINITY);
34
- merged[peer] = physicalTime === Number.NEGATIVE_INFINITY ? { logicalCounter: current.logicalCounter } : {
35
- logicalCounter: current.logicalCounter,
36
- physicalTime
37
- };
11
+ if (current == null || isAfter(entry, current)) merged[peer] = { ...entry };
38
12
  }
39
13
  return merged;
40
14
  }
15
+ function parseClock(record) {
16
+ const firstComma = record.c.indexOf(",");
17
+ const secondComma = record.c.indexOf(",", firstComma + 1);
18
+ if (firstComma < 0 || secondComma < 0) return null;
19
+ const physicalTime = Number(record.c.slice(0, firstComma));
20
+ const logicalCounter = Number(record.c.slice(firstComma + 1, secondComma));
21
+ if (!Number.isFinite(physicalTime) || !Number.isFinite(logicalCounter)) return null;
22
+ return {
23
+ peerId: record.c.slice(secondComma + 1),
24
+ entry: {
25
+ physicalTime,
26
+ logicalCounter
27
+ }
28
+ };
29
+ }
41
30
  function extractVersionFromBundle(bundle) {
42
31
  const out = {};
43
- for (const raw of Object.values(bundle.entries)) {
44
- if (typeof raw !== "object" || raw == null || Array.isArray(raw)) continue;
45
- const entry = raw;
46
- if (typeof entry.peerId !== "string") continue;
47
- const logicalCounter = typeof entry.logicalCounter === "number" ? entry.logicalCounter : typeof entry.counter === "number" ? entry.counter : void 0;
48
- if (logicalCounter == null || !Number.isFinite(logicalCounter)) continue;
49
- out[entry.peerId] = typeof entry.physicalTime === "number" && Number.isFinite(entry.physicalTime) ? {
50
- logicalCounter,
51
- physicalTime: entry.physicalTime
52
- } : { logicalCounter };
32
+ for (const record of Object.values(bundle.entries)) {
33
+ const parsed = parseClock(record);
34
+ if (parsed == null) continue;
35
+ const current = out[parsed.peerId];
36
+ if (current == null || isAfter(parsed.entry, current)) out[parsed.peerId] = parsed.entry;
53
37
  }
54
38
  return out;
55
39
  }
56
40
  function hasEntries(bundle) {
57
41
  return Object.keys(bundle.entries).length > 0;
58
42
  }
59
- function tryMergeSnapshotFromFile(flock, snapshot) {
60
- if (typeof flock.merge !== "function") return null;
61
- const ctor = flock.constructor;
62
- if (ctor == null || typeof ctor.fromFile !== "function") return null;
63
- try {
64
- const peerId = typeof flock.peerId === "function" ? flock.peerId() : void 0;
65
- const replica = ctor.fromFile(snapshot, peerId);
66
- flock.merge(replica);
67
- return toVersionVector(flock.version() ?? {});
68
- } catch {
69
- return null;
70
- }
71
- }
72
43
  /**
73
44
  * Creates a `CrdtAdapter` for one Flock replica.
74
45
  */
@@ -80,59 +51,46 @@ function createFlockAdapter(flock) {
80
51
  };
81
52
  return {
82
53
  emptyVersion: () => ({}),
83
- mergeVersions: (base, incoming) => fromVersionVector(mergeVersions(toVersionVector(base), toVersionVector(incoming))),
84
- exportSnapshot: () => {
85
- if (typeof flock.exportFile === "function") return flock.exportFile();
86
- return new TextEncoder().encode(JSON.stringify({
87
- version: 0,
88
- entries: {}
89
- }));
90
- },
54
+ mergeVersions,
55
+ exportSnapshot: () => flock.exportFile(),
91
56
  applySnapshot: (snapshot) => {
92
- if (snapshot.byteLength === 0) return {};
93
- const mergedVersion = tryMergeSnapshotFromFile(flock, snapshot);
94
- if (mergedVersion != null) return fromVersionVector(mergedVersion);
95
- const bundle = JSON.parse(new TextDecoder().decode(snapshot));
96
- if (hasEntries(bundle)) flock.importJson(bundle);
97
- return fromVersionVector(extractVersionFromBundle(bundle));
57
+ flock.importFile(snapshot);
58
+ return flock.version();
98
59
  },
99
60
  exportUpdates: (fromVersion) => {
100
- const from = toVersionVector(fromVersion);
101
- const bundle = flock.exportJson({ from });
102
- recordExportFrontier(from);
61
+ const bundle = flock.exportJson(fromVersion);
62
+ recordExportFrontier(fromVersion);
103
63
  if (!hasEntries(bundle)) return;
104
64
  const exportedVersion = extractVersionFromBundle(bundle);
105
65
  recordExportFrontier(exportedVersion);
106
66
  return {
107
67
  updates: [new TextEncoder().encode(JSON.stringify(bundle))],
108
- version: fromVersionVector(exportedVersion)
68
+ version: exportedVersion
109
69
  };
110
70
  },
111
71
  applyRemoteUpdates: (updates, remoterVersion) => {
112
- let merged = toVersionVector(remoterVersion);
72
+ let merged = remoterVersion;
113
73
  for (const update of updates) {
114
74
  const bundle = JSON.parse(new TextDecoder().decode(update));
115
75
  if (!hasEntries(bundle)) continue;
116
76
  flock.importJson(bundle);
117
77
  merged = mergeVersions(merged, extractVersionFromBundle(bundle));
118
78
  }
119
- return fromVersionVector(merged);
79
+ return merged;
120
80
  },
121
81
  subscribeLocalUpdates: (listener) => {
122
- if (typeof flock.subscribe !== "function") return () => void 0;
123
82
  return flock.subscribe((batch) => {
124
83
  if (batch.source !== "local") return;
125
- const drain = () => {
126
- const peerId = typeof flock.peerId === "function" ? flock.peerId() : void 0;
127
- const batchPayload = flock.exportJson({
84
+ const drain = async () => {
85
+ const batchPayload = await flock.exportJson({
128
86
  from: lastExportedVersion,
129
- peerId
87
+ peerId: flock.peerId()
130
88
  });
131
89
  if (!hasEntries(batchPayload)) return;
132
- recordExportFrontier(toVersionVector(fromVersionVector(flock.version() ?? {})));
90
+ recordExportFrontier(flock.version());
133
91
  listener({
134
92
  updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],
135
- version: fromVersionVector(extractVersionFromBundle(batchPayload))
93
+ version: extractVersionFromBundle(batchPayload)
136
94
  });
137
95
  };
138
96
  localExportQueue = localExportQueue.then(drain).catch(() => {});
package/dist/flock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"flock.js","names":[],"sources":["../src/flock.ts"],"sourcesContent":["import type { JsonObject, CrdtAdapter } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport\";\nexport * from \"./shared\";\n\ntype FlockVersionEntry = {\n logicalCounter: number;\n physicalTime?: number;\n};\n\n/**\n * Flock version vector shape used by the built-in Flock adapter.\n */\nexport type FlockVersionVector = Record<string, FlockVersionEntry>;\n\ntype FlockExportBundle = {\n readonly version?: number;\n readonly entries: Record<string, unknown>;\n};\n\ntype FlockLike = {\n exportJson(options?: {\n from?: FlockVersionVector;\n peerId?: string;\n }): FlockExportBundle;\n importJson(bundle: FlockExportBundle): unknown;\n merge?(replica: unknown): unknown;\n peerId?(): string;\n version(): unknown;\n exportFile?(): Uint8Array;\n subscribe?(listener: (batch: { source?: string }) => void): () => void;\n};\n\nfunction toVersionVector(version: JsonObject): FlockVersionVector {\n const out: FlockVersionVector = {};\n for (const [peer, raw] of Object.entries(version)) {\n if (typeof raw !== \"object\" || raw == null || Array.isArray(raw)) {\n continue;\n }\n const logicalCounter =\n typeof raw.logicalCounter === \"number\" ? raw.logicalCounter : undefined;\n if (logicalCounter == null || !Number.isFinite(logicalCounter)) {\n continue;\n }\n out[peer] =\n typeof raw.physicalTime === \"number\" && Number.isFinite(raw.physicalTime)\n ? { logicalCounter, physicalTime: raw.physicalTime }\n : { logicalCounter };\n }\n return out;\n}\n\nfunction fromVersionVector(version: FlockVersionVector): JsonObject {\n const out: Record<string, JsonObject> = {};\n for (const [peer, entry] of Object.entries(version)) {\n out[peer] =\n entry.physicalTime === undefined\n ? { logicalCounter: entry.logicalCounter }\n : {\n logicalCounter: entry.logicalCounter,\n physicalTime: entry.physicalTime,\n };\n }\n return out;\n}\n\nfunction mergeVersions(\n base: FlockVersionVector,\n incoming: FlockVersionVector,\n): FlockVersionVector {\n const merged: FlockVersionVector = { ...base };\n for (const [peer, entry] of Object.entries(incoming)) {\n const current = merged[peer];\n if (current == null || entry.logicalCounter > current.logicalCounter) {\n merged[peer] = { ...entry };\n continue;\n }\n if (entry.logicalCounter < current.logicalCounter) {\n continue;\n }\n const physicalTime = Math.max(\n current.physicalTime ?? Number.NEGATIVE_INFINITY,\n entry.physicalTime ?? Number.NEGATIVE_INFINITY,\n );\n merged[peer] =\n physicalTime === Number.NEGATIVE_INFINITY\n ? { logicalCounter: current.logicalCounter }\n : { logicalCounter: current.logicalCounter, physicalTime };\n }\n return merged;\n}\n\nfunction extractVersionFromBundle(\n bundle: FlockExportBundle,\n): FlockVersionVector {\n const out: FlockVersionVector = {};\n for (const raw of Object.values(bundle.entries)) {\n if (typeof raw !== \"object\" || raw == null || Array.isArray(raw)) {\n continue;\n }\n const entry = raw as {\n peerId?: unknown;\n logicalCounter?: unknown;\n counter?: unknown;\n physicalTime?: unknown;\n };\n if (typeof entry.peerId !== \"string\") {\n continue;\n }\n const logicalCounter =\n typeof entry.logicalCounter === \"number\"\n ? entry.logicalCounter\n : typeof entry.counter === \"number\"\n ? entry.counter\n : undefined;\n if (logicalCounter == null || !Number.isFinite(logicalCounter)) {\n continue;\n }\n out[entry.peerId] =\n typeof entry.physicalTime === \"number\" &&\n Number.isFinite(entry.physicalTime)\n ? { logicalCounter, physicalTime: entry.physicalTime }\n : { logicalCounter };\n }\n return out;\n}\n\nfunction hasEntries(bundle: FlockExportBundle): boolean {\n return Object.keys(bundle.entries).length > 0;\n}\n\nfunction tryMergeSnapshotFromFile(\n flock: FlockLike,\n snapshot: Uint8Array,\n): FlockVersionVector | null {\n if (typeof flock.merge !== \"function\") {\n return null;\n }\n const ctor = (\n flock as unknown as {\n readonly constructor?: {\n fromFile?: (bytes: Uint8Array, peerId?: string) => unknown;\n };\n }\n ).constructor;\n if (ctor == null || typeof ctor.fromFile !== \"function\") {\n return null;\n }\n try {\n const peerId =\n typeof flock.peerId === \"function\" ? flock.peerId() : undefined;\n const replica = ctor.fromFile(snapshot, peerId);\n flock.merge(replica);\n return toVersionVector((flock.version() as JsonObject) ?? {});\n } catch {\n return null;\n }\n}\n\n/**\n * Creates a `CrdtAdapter` for one Flock replica.\n */\nexport function createFlockAdapter(flock: FlockLike): CrdtAdapter<JsonObject> {\n let lastExportedVersion: FlockVersionVector = {};\n let localExportQueue = Promise.resolve();\n\n const recordExportFrontier = (version: FlockVersionVector): void => {\n lastExportedVersion = mergeVersions(lastExportedVersion, version);\n };\n\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n fromVersionVector(\n mergeVersions(toVersionVector(base), toVersionVector(incoming)),\n ),\n exportSnapshot: () => {\n if (typeof flock.exportFile === \"function\") {\n return flock.exportFile();\n }\n return new TextEncoder().encode(\n JSON.stringify({ version: 0, entries: {} }),\n );\n },\n applySnapshot: (snapshot) => {\n if (snapshot.byteLength === 0) {\n return {};\n }\n const mergedVersion = tryMergeSnapshotFromFile(flock, snapshot);\n if (mergedVersion != null) {\n return fromVersionVector(mergedVersion);\n }\n const bundle = JSON.parse(\n new TextDecoder().decode(snapshot),\n ) as FlockExportBundle;\n if (hasEntries(bundle)) {\n flock.importJson(bundle);\n }\n return fromVersionVector(extractVersionFromBundle(bundle));\n },\n exportUpdates: (fromVersion) => {\n const from = toVersionVector(fromVersion);\n const bundle = flock.exportJson({ from });\n recordExportFrontier(from);\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: fromVersionVector(exportedVersion),\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n let merged = toVersionVector(remoterVersion);\n for (const update of updates) {\n const bundle = JSON.parse(\n new TextDecoder().decode(update),\n ) as FlockExportBundle;\n if (!hasEntries(bundle)) {\n continue;\n }\n flock.importJson(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n return fromVersionVector(merged);\n },\n subscribeLocalUpdates: (listener) => {\n if (typeof flock.subscribe !== \"function\") {\n return () => undefined;\n }\n const unsubscribe = flock.subscribe((batch) => {\n if (batch.source !== \"local\") {\n return;\n }\n const drain = () => {\n const peerId =\n typeof flock.peerId === \"function\" ? flock.peerId() : undefined;\n const batchPayload = flock.exportJson({\n from: lastExportedVersion,\n peerId,\n });\n if (!hasEntries(batchPayload)) {\n return;\n }\n const currentVersion = toVersionVector(\n fromVersionVector((flock.version() as FlockVersionVector) ?? {}),\n );\n recordExportFrontier(currentVersion);\n listener({\n updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],\n version: fromVersionVector(extractVersionFromBundle(batchPayload)),\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\";\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 * 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 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 flock.importJson(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n return merged;\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 recordExportFrontier(flock.version());\n listener({\n updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],\n version: extractVersionFromBundle(batchPayload),\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 createInitialRemoteCursor, C as BeforeRemoteCursorSaveHook, D as RemoteCursor, E as IndexedDbRemoteCursorStoreOptions, O as RemoteCursorSaveSource, S as BeforeRemoteCursorSaveContext, T as IndexedDbRemoteCursorStore, _ as TransportError, a as CrdtAdapter, b as TransportSubscription, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportDeleteStreamSuccess, h as TransportCreateStreamSuccess, i as StreamsCrdt, k as RemoteCursorStore, l as Result, m as StreamsCrdtOptions, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthProvider, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportJoinParams, w as InMemoryRemoteCursorStore, x as TransportSyncSuccess, y as TransportRoomStatus } from "./stream-id-B9n-uFFB.js";
2
- export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
1
+ import { A as RemoteCursorStore, C as BeforeRemoteCursorSaveContext, D as IndexedDbRemoteCursorStoreOptions, E as IndexedDbRemoteCursorStore, O as RemoteCursor, S as TransportSyncSuccess, T as InMemoryRemoteCursorStore, _ as TransportDeleteStreamSuccess, a as CrdtAdapter, b as TransportRoomStatus, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportCreateStreamSuccess, h as StreamsCrdtOptions, i as StreamsCrdt, j as createInitialRemoteCursor, k as RemoteCursorSaveSource, l as Result, m as StreamsAuthProvider, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthContext, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportError, w as BeforeRemoteCursorSaveHook, x as TransportSubscription, y as TransportJoinParams } from "./stream-id-BxGVitUa.js";
2
+ export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, 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-YOFZ4I6Y.js";
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-kM9JM1F7.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 createInitialRemoteCursor, C as BeforeRemoteCursorSaveHook, D as RemoteCursor, E as IndexedDbRemoteCursorStoreOptions, O as RemoteCursorSaveSource, S as BeforeRemoteCursorSaveContext, T as IndexedDbRemoteCursorStore, _ as TransportError, a as CrdtAdapter, b as TransportSubscription, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportDeleteStreamSuccess, h as TransportCreateStreamSuccess, i as StreamsCrdt, k as RemoteCursorStore, l as Result, m as StreamsCrdtOptions, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthProvider, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportJoinParams, w as InMemoryRemoteCursorStore, x as TransportSyncSuccess, y as TransportRoomStatus } from "./stream-id-B9n-uFFB.js";
1
+ import { A as RemoteCursorStore, C as BeforeRemoteCursorSaveContext, D as IndexedDbRemoteCursorStoreOptions, E as IndexedDbRemoteCursorStore, O as RemoteCursor, S as TransportSyncSuccess, T as InMemoryRemoteCursorStore, _ as TransportDeleteStreamSuccess, a as CrdtAdapter, b as TransportRoomStatus, c as JsonValue, d as SnapshotTransformHook, f as SnapshotUploadOptions, g as TransportCreateStreamSuccess, h as StreamsCrdtOptions, i as StreamsCrdt, j as createInitialRemoteCursor, k as RemoteCursorSaveSource, l as Result, m as StreamsAuthProvider, n as isValidBucketId, o as CrdtUpdateBatch, p as StreamsAuthContext, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotCodec, v as TransportError, w as BeforeRemoteCursorSaveHook, x as TransportSubscription, y as TransportJoinParams } from "./stream-id-BxGVitUa.js";
2
2
  import { LoroDoc } from "loro-crdt";
3
3
 
4
4
  //#region src/loro.d.ts
@@ -18,5 +18,5 @@ import { LoroDoc } from "loro-crdt";
18
18
  */
19
19
  declare function createLoroDocAdapter(doc: LoroDoc): CrdtAdapter<JsonObject>;
20
20
  //#endregion
21
- export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createLoroDocAdapter, createStreamUrl, isValidBucketId, isValidRillId };
21
+ export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createLoroDocAdapter, createStreamUrl, isValidBucketId, isValidRillId };
22
22
  //# sourceMappingURL=loro.d.ts.map
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-YOFZ4I6Y.js";
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-kM9JM1F7.js";
2
2
  import { VersionVector, decodeImportBlobMeta } from "loro-crdt";
3
3
  //#region src/loro.ts
4
4
  function toDocVersionRecord(version) {
@@ -119,15 +119,38 @@ type JsonPrimitive = string | number | boolean | null;
119
119
  */
120
120
  type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
121
121
  /**
122
- * JSON object shape used by public transport types.
122
+ * JSON-like object shape used by public transport types.
123
+ *
124
+ * Sparse records may expose `undefined` for omitted keys at runtime (for
125
+ * example Flock's `VersionVector` index signature), so object fields allow it.
123
126
  */
124
127
  type JsonObject = {
125
- readonly [key: string]: JsonValue;
128
+ readonly [key: string]: JsonValue | undefined;
126
129
  };
127
130
  /**
128
131
  * Authorization source for gateway requests.
129
132
  */
130
- type StreamsAuthProvider = string | (() => MaybePromise<string | null | undefined>);
133
+ type StreamsAuthContext = {
134
+ /** Default path before sending a request. */readonly reason: "request";
135
+ } | {
136
+ /**
137
+ * The previous request was rejected by the server with 401/403.
138
+ *
139
+ * Return a freshly refreshed token here when the caller keeps its own
140
+ * token cache.
141
+ */
142
+ readonly reason: "unauthorized";
143
+ readonly status: 401 | 403;
144
+ readonly previousToken?: string;
145
+ };
146
+ /**
147
+ * Authorization source for gateway requests.
148
+ *
149
+ * The callback is invoked before each request. When a request comes back with
150
+ * 401/403, transport invokes it one more time with `reason: "unauthorized"`
151
+ * and retries that request once.
152
+ */
153
+ type StreamsAuthProvider = string | ((context?: StreamsAuthContext) => MaybePromise<string | null | undefined>);
131
154
  /**
132
155
  * Rust-style result for transport operations that may fail for expected reasons.
133
156
  */
@@ -330,6 +353,13 @@ interface StreamsCrdtOptions<TVersion extends JsonObject> {
330
353
  * Defaults to false so missing streams are surfaced explicitly.
331
354
  */
332
355
  readonly createStreamIfMissing?: boolean;
356
+ /**
357
+ * Auth token source.
358
+ *
359
+ * String and zero-arg callback forms keep the old behavior. Callbacks may
360
+ * also inspect `context.reason` to refresh a cached token only after the
361
+ * server rejects the current one.
362
+ */
333
363
  readonly auth?: StreamsAuthProvider;
334
364
  readonly fetch?: (input: string, init?: RequestInit) => Promise<Response>;
335
365
  /**
@@ -433,10 +463,6 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
433
463
  private syncOnce;
434
464
  private performInitialJoinSync;
435
465
  private bootstrapIntoState;
436
- private bootstrapIntoJoinedState;
437
- private bootstrapIntoJoinedStateViaSse;
438
- private continueJoinedSseSessionUntilCaughtUp;
439
- private bootstrapIntoJoinedStateViaLongPoll;
440
466
  private catchup;
441
467
  private appendLocalBatch;
442
468
  private exportUpdates;
@@ -458,8 +484,6 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
458
484
  private createStreamRequest;
459
485
  private deleteStreamRequest;
460
486
  private readFromOffsetSseItems;
461
- private openBootstrapSseSession;
462
- private readBootstrapLongPoll;
463
487
  private readFromOffset;
464
488
  private bootstrap;
465
489
  private readSnapshotOffsetValue;
@@ -488,5 +512,5 @@ declare function createStreamUrl(input: {
488
512
  baseUrl?: string;
489
513
  }): string;
490
514
  //#endregion
491
- export { createInitialRemoteCursor as A, BeforeRemoteCursorSaveHook as C, RemoteCursor as D, IndexedDbRemoteCursorStoreOptions as E, RemoteCursorSaveSource as O, BeforeRemoteCursorSaveContext as S, IndexedDbRemoteCursorStore as T, TransportError as _, CrdtAdapter as a, TransportSubscription as b, JsonValue as c, SnapshotTransformHook as d, SnapshotUploadOptions as f, TransportDeleteStreamSuccess as g, TransportCreateStreamSuccess as h, StreamsCrdt as i, RemoteCursorStore as k, Result as l, StreamsCrdtOptions as m, isValidBucketId as n, CrdtUpdateBatch as o, StreamsAuthProvider as p, isValidRillId as r, JsonObject as s, createStreamUrl as t, SnapshotCodec as u, TransportJoinParams as v, InMemoryRemoteCursorStore as w, TransportSyncSuccess as x, TransportRoomStatus as y };
492
- //# sourceMappingURL=stream-id-B9n-uFFB.d.ts.map
515
+ export { RemoteCursorStore as A, BeforeRemoteCursorSaveContext as C, IndexedDbRemoteCursorStoreOptions as D, IndexedDbRemoteCursorStore as E, RemoteCursor as O, TransportSyncSuccess as S, InMemoryRemoteCursorStore as T, TransportDeleteStreamSuccess as _, CrdtAdapter as a, TransportRoomStatus as b, JsonValue as c, SnapshotTransformHook as d, SnapshotUploadOptions as f, TransportCreateStreamSuccess as g, StreamsCrdtOptions as h, StreamsCrdt as i, createInitialRemoteCursor as j, RemoteCursorSaveSource as k, Result as l, StreamsAuthProvider as m, isValidBucketId as n, CrdtUpdateBatch as o, StreamsAuthContext as p, isValidRillId as r, JsonObject as s, createStreamUrl as t, SnapshotCodec as u, TransportError as v, BeforeRemoteCursorSaveHook as w, TransportSubscription as x, TransportJoinParams as y };
516
+ //# sourceMappingURL=stream-id-BxGVitUa.d.ts.map