loro-repo 0.5.0 → 0.5.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broadcast-channel.js","names":["resolve!: () => void","listener: MetaListener","listener: DocListener","state: DocChannelState"],"sources":["../../src/transport/broadcast-channel.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\nimport type {\n TransportAdapter,\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n} from \"../types\";\n\ntype BroadcastChannelMessageEvent<T = unknown> = {\n data: T;\n};\n\ninterface BroadcastChannelLike {\n readonly name?: string;\n onmessage?: ((event: BroadcastChannelMessageEvent) => void) | null;\n onmessageerror?: ((event: BroadcastChannelMessageEvent) => void) | null;\n postMessage(message: unknown): void;\n addEventListener(\n type: \"message\",\n listener: (event: BroadcastChannelMessageEvent) => void,\n ): void;\n removeEventListener(\n type: \"message\",\n listener: (event: BroadcastChannelMessageEvent) => void,\n ): void;\n close(): void;\n}\n\ntype BroadcastChannelConstructor = new (name: string) => BroadcastChannelLike;\n\ndeclare const BroadcastChannel:\n | BroadcastChannelConstructor\n | undefined;\n\ntype FlockExport = Awaited<ReturnType<Flock[\"exportJson\"]>>;\n\ntype BroadcastMessage =\n | {\n kind: \"meta-export\";\n from: string;\n bundle: FlockExport;\n }\n | {\n kind: \"meta-request\";\n from: string;\n };\n\ntype DocMessage =\n | {\n kind: \"doc-update\";\n docId: string;\n from: string;\n mode: \"snapshot\" | \"update\";\n payload: Uint8Array;\n }\n | {\n kind: \"doc-request\";\n docId: string;\n from: string;\n };\n\ntype MetaListener = {\n flock: Flock;\n unsubscribe: () => void;\n muted: boolean;\n resolveFirst: () => void;\n firstSynced: Promise<void>;\n};\n\ntype MetaChannelState = {\n channel: BroadcastChannelLike;\n listeners: Set<MetaListener>;\n onMessage: (event: BroadcastChannelMessageEvent) => void;\n};\n\ntype DocListener = {\n doc: LoroDoc;\n unsubscribe: () => void;\n muted: boolean;\n resolveFirst: () => void;\n firstSynced: Promise<void>;\n};\n\ntype DocChannelState = {\n channel: BroadcastChannelLike;\n listeners: Set<DocListener>;\n onMessage: (event: BroadcastChannelMessageEvent) => void;\n};\n\nfunction deferred(): {\n promise: Promise<void>;\n resolve: () => void;\n} {\n let resolve!: () => void;\n const promise = new Promise<void>((res) => {\n resolve = res;\n });\n return { promise, resolve };\n}\n\nfunction randomInstanceId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return Math.random().toString(36).slice(2);\n}\n\nfunction ensureBroadcastChannel(): BroadcastChannelConstructor {\n if (typeof BroadcastChannel === \"undefined\") {\n throw new Error(\"BroadcastChannel API is not available in this environment\");\n }\n return BroadcastChannel;\n}\n\nfunction encodeDocChannelId(docId: string): string {\n try {\n return encodeURIComponent(docId);\n } catch {\n return docId.replace(/[^a-z0-9_-]/gi, \"_\");\n }\n}\n\nfunction postChannelMessage(\n channel: BroadcastChannelLike,\n message: BroadcastMessage | DocMessage,\n): void {\n // BroadcastChannel.postMessage does not accept targetOrigin, so we intentionally\n // bypass the unicorn/require-post-message-target-origin rule here.\n // eslint-disable-next-line unicorn/require-post-message-target-origin\n channel.postMessage(message);\n}\n\nexport interface BroadcastChannelTransportOptions {\n /**\n * Namespace used to derive broadcast channel names. Defaults to `loro-repo`.\n */\n readonly namespace?: string;\n /**\n * Explicit channel name for metadata broadcasts. When omitted, resolves to `${namespace}-meta`.\n */\n readonly metaChannelName?: string;\n}\n\n/**\n * TransportAdapter that relies on the BroadcastChannel API to fan out metadata\n * and document updates between browser tabs within the same origin.\n */\nexport class BroadcastChannelTransportAdapter implements TransportAdapter {\n private readonly instanceId = randomInstanceId();\n private readonly namespace: string;\n private readonly metaChannelName: string;\n private connected = false;\n\n private metaState?: MetaChannelState;\n private readonly docStates = new Map<string, DocChannelState>();\n\n constructor(options: BroadcastChannelTransportOptions = {}) {\n ensureBroadcastChannel();\n this.namespace = options.namespace ?? \"loro-repo\";\n this.metaChannelName = options.metaChannelName ?? `${this.namespace}-meta`;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async close(): Promise<void> {\n this.connected = false;\n if (this.metaState) {\n for (const entry of this.metaState.listeners) {\n entry.unsubscribe();\n }\n this.metaState.channel.close();\n this.metaState = undefined;\n }\n for (const [docId] of this.docStates) {\n this.teardownDocChannel(docId);\n }\n this.docStates.clear();\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n async syncMeta(\n flock: Flock,\n _options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n const subscription = this.joinMetaRoom(flock);\n subscription.firstSyncedWithRemote.catch(() => undefined);\n await subscription.firstSyncedWithRemote;\n subscription.unsubscribe();\n return { ok: true };\n }\n\n joinMetaRoom(\n flock: Flock,\n _params?: TransportJoinParams,\n ): TransportSubscription {\n const state = this.ensureMetaChannel();\n const { promise, resolve } = deferred();\n const listener: MetaListener = {\n flock,\n muted: false,\n unsubscribe: flock.subscribe(() => {\n if (listener.muted) return;\n void Promise.resolve(flock.exportJson()).then((bundle) => {\n postChannelMessage(state.channel, {\n kind: \"meta-export\",\n from: this.instanceId,\n bundle,\n } satisfies BroadcastMessage);\n });\n }),\n resolveFirst: resolve,\n firstSynced: promise,\n };\n state.listeners.add(listener);\n\n // Request current state from peers and share our snapshot.\n postChannelMessage(state.channel, {\n kind: \"meta-request\",\n from: this.instanceId,\n } satisfies BroadcastMessage);\n void Promise.resolve(flock.exportJson()).then((bundle) => {\n postChannelMessage(state.channel, {\n kind: \"meta-export\",\n from: this.instanceId,\n bundle,\n } satisfies BroadcastMessage);\n });\n\n // Resolve immediately if nothing arrives.\n queueMicrotask(() => resolve());\n\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n listener.unsubscribe();\n state.listeners.delete(listener);\n if (!state.listeners.size) {\n state.channel.removeEventListener(\"message\", state.onMessage);\n state.channel.close();\n this.metaState = undefined;\n }\n },\n firstSyncedWithRemote: listener.firstSynced,\n get connected() {\n return true;\n },\n };\n return subscription;\n }\n\n async syncDoc(\n docId: string,\n doc: LoroDoc,\n _options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n const subscription = this.joinDocRoom(docId, doc);\n subscription.firstSyncedWithRemote.catch(() => undefined);\n await subscription.firstSyncedWithRemote;\n subscription.unsubscribe();\n return { ok: true };\n }\n\n joinDocRoom(\n docId: string,\n doc: LoroDoc,\n _params?: TransportJoinParams,\n ): TransportSubscription {\n const state = this.ensureDocChannel(docId);\n const { promise, resolve } = deferred();\n const listener: DocListener = {\n doc,\n muted: false,\n unsubscribe: doc.subscribe(() => {\n if (listener.muted) return;\n const payload = doc.export({ mode: \"update\" });\n postChannelMessage(state.channel, {\n kind: \"doc-update\",\n docId,\n from: this.instanceId,\n mode: \"update\",\n payload,\n } satisfies DocMessage);\n }),\n resolveFirst: resolve,\n firstSynced: promise,\n };\n state.listeners.add(listener);\n\n postChannelMessage(state.channel, {\n kind: \"doc-request\",\n docId,\n from: this.instanceId,\n } satisfies DocMessage);\n postChannelMessage(state.channel, {\n kind: \"doc-update\",\n docId,\n from: this.instanceId,\n mode: \"snapshot\",\n payload: doc.export({ mode: \"snapshot\" }),\n } satisfies DocMessage);\n\n queueMicrotask(() => resolve());\n\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n listener.unsubscribe();\n state.listeners.delete(listener);\n if (!state.listeners.size) {\n this.teardownDocChannel(docId);\n }\n },\n firstSyncedWithRemote: listener.firstSynced,\n get connected() {\n return true;\n },\n };\n return subscription;\n }\n\n private ensureMetaChannel(): MetaChannelState {\n if (this.metaState) {\n return this.metaState;\n }\n const Channel = ensureBroadcastChannel();\n const channel = new Channel(this.metaChannelName);\n const listeners = new Set<MetaListener>();\n const onMessage = (event: BroadcastChannelMessageEvent) => {\n const message = event.data as BroadcastMessage | undefined;\n if (!message || message.from === this.instanceId) {\n return;\n }\n if (message.kind === \"meta-export\") {\n for (const entry of listeners) {\n entry.muted = true;\n entry.flock.importJson(message.bundle);\n entry.muted = false;\n entry.resolveFirst();\n }\n } else if (message.kind === \"meta-request\") {\n const first = listeners.values().next().value;\n if (!first) return;\n void Promise.resolve(first.flock.exportJson()).then((bundle) => {\n postChannelMessage(channel, {\n kind: \"meta-export\",\n from: this.instanceId,\n bundle,\n } satisfies BroadcastMessage);\n });\n }\n };\n channel.addEventListener(\"message\", onMessage);\n this.metaState = { channel, listeners, onMessage };\n return this.metaState;\n }\n\n private ensureDocChannel(docId: string): DocChannelState {\n const existing = this.docStates.get(docId);\n if (existing) return existing;\n const Channel = ensureBroadcastChannel();\n const channelName = `${this.namespace}-doc-${encodeDocChannelId(docId)}`;\n const channel = new Channel(channelName);\n const listeners = new Set<DocListener>();\n const onMessage = (event: BroadcastChannelMessageEvent) => {\n const message = event.data as DocMessage | undefined;\n if (!message || message.from === this.instanceId) {\n return;\n }\n if (message.kind === \"doc-update\") {\n for (const entry of listeners) {\n entry.muted = true;\n entry.doc.import(message.payload);\n entry.muted = false;\n entry.resolveFirst();\n }\n } else if (message.kind === \"doc-request\") {\n const first = listeners.values().next().value;\n if (!first) return;\n const payload =\n message.docId === docId\n ? first.doc.export({ mode: \"snapshot\" })\n : undefined;\n if (!payload) return;\n postChannelMessage(channel, {\n kind: \"doc-update\",\n docId,\n from: this.instanceId,\n mode: \"snapshot\",\n payload,\n } satisfies DocMessage);\n }\n };\n channel.addEventListener(\"message\", onMessage);\n const state: DocChannelState = { channel, listeners, onMessage };\n this.docStates.set(docId, state);\n return state;\n }\n\n private teardownDocChannel(docId: string): void {\n const state = this.docStates.get(docId);\n if (!state) return;\n for (const entry of state.listeners) {\n entry.unsubscribe();\n }\n state.channel.removeEventListener(\"message\", state.onMessage);\n state.channel.close();\n this.docStates.delete(docId);\n }\n}\n"],"mappings":";AA0FA,SAAS,WAGP;CACA,IAAIA;AAIJ,QAAO;EAAE,SAHO,IAAI,SAAe,QAAQ;AACzC,aAAU;IACV;EACgB;EAAS;;AAG7B,SAAS,mBAA2B;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,WAChE,QAAO,OAAO,YAAY;AAE5B,QAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;AAG5C,SAAS,yBAAsD;AAC7D,KAAI,OAAO,qBAAqB,YAC9B,OAAM,IAAI,MAAM,4DAA4D;AAE9E,QAAO;;AAGT,SAAS,mBAAmB,OAAuB;AACjD,KAAI;AACF,SAAO,mBAAmB,MAAM;SAC1B;AACN,SAAO,MAAM,QAAQ,iBAAiB,IAAI;;;AAI9C,SAAS,mBACP,SACA,SACM;AAIN,SAAQ,YAAY,QAAQ;;;;;;AAkB9B,IAAa,mCAAb,MAA0E;CACxE,AAAiB,aAAa,kBAAkB;CAChD,AAAiB;CACjB,AAAiB;CACjB,AAAQ,YAAY;CAEpB,AAAQ;CACR,AAAiB,4BAAY,IAAI,KAA8B;CAE/D,YAAY,UAA4C,EAAE,EAAE;AAC1D,0BAAwB;AACxB,OAAK,YAAY,QAAQ,aAAa;AACtC,OAAK,kBAAkB,QAAQ,mBAAmB,GAAG,KAAK,UAAU;;CAGtE,MAAM,UAAyB;AAC7B,OAAK,YAAY;;CAGnB,MAAM,QAAuB;AAC3B,OAAK,YAAY;AACjB,MAAI,KAAK,WAAW;AAClB,QAAK,MAAM,SAAS,KAAK,UAAU,UACjC,OAAM,aAAa;AAErB,QAAK,UAAU,QAAQ,OAAO;AAC9B,QAAK,YAAY;;AAEnB,OAAK,MAAM,CAAC,UAAU,KAAK,UACzB,MAAK,mBAAmB,MAAM;AAEhC,OAAK,UAAU,OAAO;;CAGxB,cAAuB;AACrB,SAAO,KAAK;;CAGd,MAAM,SACJ,OACA,UAC8B;EAC9B,MAAM,eAAe,KAAK,aAAa,MAAM;AAC7C,eAAa,sBAAsB,YAAY,OAAU;AACzD,QAAM,aAAa;AACnB,eAAa,aAAa;AAC1B,SAAO,EAAE,IAAI,MAAM;;CAGrB,aACE,OACA,SACuB;EACvB,MAAM,QAAQ,KAAK,mBAAmB;EACtC,MAAM,EAAE,SAAS,YAAY,UAAU;EACvC,MAAMC,WAAyB;GAC7B;GACA,OAAO;GACP,aAAa,MAAM,gBAAgB;AACjC,QAAI,SAAS,MAAO;AACpB,IAAK,QAAQ,QAAQ,MAAM,YAAY,CAAC,CAAC,MAAM,WAAW;AACxD,wBAAmB,MAAM,SAAS;MAChC,MAAM;MACN,MAAM,KAAK;MACX;MACD,CAA4B;MAC7B;KACF;GACF,cAAc;GACd,aAAa;GACd;AACD,QAAM,UAAU,IAAI,SAAS;AAG7B,qBAAmB,MAAM,SAAS;GAChC,MAAM;GACN,MAAM,KAAK;GACZ,CAA4B;AAC7B,EAAK,QAAQ,QAAQ,MAAM,YAAY,CAAC,CAAC,MAAM,WAAW;AACxD,sBAAmB,MAAM,SAAS;IAChC,MAAM;IACN,MAAM,KAAK;IACX;IACD,CAA4B;IAC7B;AAGF,uBAAqB,SAAS,CAAC;AAiB/B,SAf4C;GAC1C,mBAAmB;AACjB,aAAS,aAAa;AACtB,UAAM,UAAU,OAAO,SAAS;AAChC,QAAI,CAAC,MAAM,UAAU,MAAM;AACzB,WAAM,QAAQ,oBAAoB,WAAW,MAAM,UAAU;AAC7D,WAAM,QAAQ,OAAO;AACrB,UAAK,YAAY;;;GAGrB,uBAAuB,SAAS;GAChC,IAAI,YAAY;AACd,WAAO;;GAEV;;CAIH,MAAM,QACJ,OACA,KACA,UAC8B;EAC9B,MAAM,eAAe,KAAK,YAAY,OAAO,IAAI;AACjD,eAAa,sBAAsB,YAAY,OAAU;AACzD,QAAM,aAAa;AACnB,eAAa,aAAa;AAC1B,SAAO,EAAE,IAAI,MAAM;;CAGrB,YACE,OACA,KACA,SACuB;EACvB,MAAM,QAAQ,KAAK,iBAAiB,MAAM;EAC1C,MAAM,EAAE,SAAS,YAAY,UAAU;EACvC,MAAMC,WAAwB;GAC5B;GACA,OAAO;GACP,aAAa,IAAI,gBAAgB;AAC/B,QAAI,SAAS,MAAO;IACpB,MAAM,UAAU,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAC9C,uBAAmB,MAAM,SAAS;KAChC,MAAM;KACN;KACA,MAAM,KAAK;KACX,MAAM;KACN;KACD,CAAsB;KACvB;GACF,cAAc;GACd,aAAa;GACd;AACD,QAAM,UAAU,IAAI,SAAS;AAE7B,qBAAmB,MAAM,SAAS;GAChC,MAAM;GACN;GACA,MAAM,KAAK;GACZ,CAAsB;AACvB,qBAAmB,MAAM,SAAS;GAChC,MAAM;GACN;GACA,MAAM,KAAK;GACX,MAAM;GACN,SAAS,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;GAC1C,CAAsB;AAEvB,uBAAqB,SAAS,CAAC;AAe/B,SAb4C;GAC1C,mBAAmB;AACjB,aAAS,aAAa;AACtB,UAAM,UAAU,OAAO,SAAS;AAChC,QAAI,CAAC,MAAM,UAAU,KACnB,MAAK,mBAAmB,MAAM;;GAGlC,uBAAuB,SAAS;GAChC,IAAI,YAAY;AACd,WAAO;;GAEV;;CAIH,AAAQ,oBAAsC;AAC5C,MAAI,KAAK,UACP,QAAO,KAAK;EAGd,MAAM,UAAU,KADA,wBAAwB,EACZ,KAAK,gBAAgB;EACjD,MAAM,4BAAY,IAAI,KAAmB;EACzC,MAAM,aAAa,UAAwC;GACzD,MAAM,UAAU,MAAM;AACtB,OAAI,CAAC,WAAW,QAAQ,SAAS,KAAK,WACpC;AAEF,OAAI,QAAQ,SAAS,cACnB,MAAK,MAAM,SAAS,WAAW;AAC7B,UAAM,QAAQ;AACd,UAAM,MAAM,WAAW,QAAQ,OAAO;AACtC,UAAM,QAAQ;AACd,UAAM,cAAc;;YAEb,QAAQ,SAAS,gBAAgB;IAC1C,MAAM,QAAQ,UAAU,QAAQ,CAAC,MAAM,CAAC;AACxC,QAAI,CAAC,MAAO;AACZ,IAAK,QAAQ,QAAQ,MAAM,MAAM,YAAY,CAAC,CAAC,MAAM,WAAW;AAC9D,wBAAmB,SAAS;MAC1B,MAAM;MACN,MAAM,KAAK;MACX;MACD,CAA4B;MAC7B;;;AAGN,UAAQ,iBAAiB,WAAW,UAAU;AAC9C,OAAK,YAAY;GAAE;GAAS;GAAW;GAAW;AAClD,SAAO,KAAK;;CAGd,AAAQ,iBAAiB,OAAgC;EACvD,MAAM,WAAW,KAAK,UAAU,IAAI,MAAM;AAC1C,MAAI,SAAU,QAAO;EAGrB,MAAM,UAAU,KAFA,wBAAwB,EACpB,GAAG,KAAK,UAAU,OAAO,mBAAmB,MAAM,GAC9B;EACxC,MAAM,4BAAY,IAAI,KAAkB;EACxC,MAAM,aAAa,UAAwC;GACzD,MAAM,UAAU,MAAM;AACtB,OAAI,CAAC,WAAW,QAAQ,SAAS,KAAK,WACpC;AAEF,OAAI,QAAQ,SAAS,aACnB,MAAK,MAAM,SAAS,WAAW;AAC7B,UAAM,QAAQ;AACd,UAAM,IAAI,OAAO,QAAQ,QAAQ;AACjC,UAAM,QAAQ;AACd,UAAM,cAAc;;YAEb,QAAQ,SAAS,eAAe;IACzC,MAAM,QAAQ,UAAU,QAAQ,CAAC,MAAM,CAAC;AACxC,QAAI,CAAC,MAAO;IACZ,MAAM,UACJ,QAAQ,UAAU,QACd,MAAM,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC,GACtC;AACN,QAAI,CAAC,QAAS;AACd,uBAAmB,SAAS;KAC1B,MAAM;KACN;KACA,MAAM,KAAK;KACX,MAAM;KACN;KACD,CAAsB;;;AAG3B,UAAQ,iBAAiB,WAAW,UAAU;EAC9C,MAAMC,QAAyB;GAAE;GAAS;GAAW;GAAW;AAChE,OAAK,UAAU,IAAI,OAAO,MAAM;AAChC,SAAO;;CAGT,AAAQ,mBAAmB,OAAqB;EAC9C,MAAM,QAAQ,KAAK,UAAU,IAAI,MAAM;AACvC,MAAI,CAAC,MAAO;AACZ,OAAK,MAAM,SAAS,MAAM,UACxB,OAAM,aAAa;AAErB,QAAM,QAAQ,oBAAoB,WAAW,MAAM,UAAU;AAC7D,QAAM,QAAQ,OAAO;AACrB,OAAK,UAAU,OAAO,MAAM"}
@@ -0,0 +1,435 @@
1
+ const require_chunk = require('../chunk.cjs');
2
+ let loro_adaptors_loro = require("loro-adaptors/loro");
3
+ loro_adaptors_loro = require_chunk.__toESM(loro_adaptors_loro);
4
+ let loro_protocol = require("loro-protocol");
5
+ loro_protocol = require_chunk.__toESM(loro_protocol);
6
+ let loro_websocket = require("loro-websocket");
7
+ loro_websocket = require_chunk.__toESM(loro_websocket);
8
+ let loro_adaptors_flock = require("loro-adaptors/flock");
9
+ loro_adaptors_flock = require_chunk.__toESM(loro_adaptors_flock);
10
+
11
+ //#region src/internal/debug.ts
12
+ const getEnv = () => {
13
+ if (typeof globalThis !== "object" || globalThis === null) return;
14
+ return globalThis.process?.env;
15
+ };
16
+ const rawNamespaceConfig = (getEnv()?.LORO_REPO_DEBUG ?? "").trim();
17
+ const normalizedNamespaces = rawNamespaceConfig.length > 0 ? rawNamespaceConfig.split(/[\s,]+/).map((token) => token.toLowerCase()).filter(Boolean) : [];
18
+ const wildcardTokens = new Set([
19
+ "*",
20
+ "1",
21
+ "true",
22
+ "all"
23
+ ]);
24
+ const namespaceSet = new Set(normalizedNamespaces);
25
+ const hasWildcard = namespaceSet.size > 0 && normalizedNamespaces.some((token) => wildcardTokens.has(token));
26
+ const isDebugEnabled = (namespace) => {
27
+ if (!namespaceSet.size) return false;
28
+ if (!namespace) return hasWildcard;
29
+ const normalized = namespace.toLowerCase();
30
+ if (hasWildcard) return true;
31
+ if (namespaceSet.has(normalized)) return true;
32
+ const [root] = normalized.split(":");
33
+ return namespaceSet.has(root);
34
+ };
35
+ const createDebugLogger = (namespace) => {
36
+ const normalized = namespace.toLowerCase();
37
+ return (...args) => {
38
+ if (!isDebugEnabled(normalized)) return;
39
+ const prefix = `[loro-repo:${namespace}]`;
40
+ if (args.length === 0) {
41
+ console.info(prefix);
42
+ return;
43
+ }
44
+ console.info(prefix, ...args);
45
+ };
46
+ };
47
+
48
+ //#endregion
49
+ //#region src/transport/websocket.ts
50
+ const debug = createDebugLogger("transport:websocket");
51
+ function withTimeout(promise, timeoutMs) {
52
+ if (!timeoutMs || timeoutMs <= 0) return promise;
53
+ return new Promise((resolve, reject) => {
54
+ const timer = setTimeout(() => {
55
+ reject(/* @__PURE__ */ new Error(`Operation timed out after ${timeoutMs}ms`));
56
+ }, timeoutMs);
57
+ promise.then((value) => {
58
+ clearTimeout(timer);
59
+ resolve(value);
60
+ }).catch((error) => {
61
+ clearTimeout(timer);
62
+ reject(error);
63
+ });
64
+ });
65
+ }
66
+ function normalizeRoomId(roomId, fallback) {
67
+ if (typeof roomId === "string" && roomId.length > 0) return roomId;
68
+ if (roomId instanceof Uint8Array && roomId.length > 0) try {
69
+ return (0, loro_protocol.bytesToHex)(roomId);
70
+ } catch {
71
+ return fallback;
72
+ }
73
+ return fallback;
74
+ }
75
+ function bytesEqual(a, b) {
76
+ if (a === b) return true;
77
+ if (!a || !b) return false;
78
+ if (a.length !== b.length) return false;
79
+ for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
80
+ return true;
81
+ }
82
+ /**
83
+ * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
84
+ * It uses loro-protocol as the underlying protocol for the transport.
85
+ */
86
+ var WebSocketTransportAdapter = class {
87
+ options;
88
+ client;
89
+ metadataSession;
90
+ docSessions = /* @__PURE__ */ new Map();
91
+ constructor(options) {
92
+ this.options = options;
93
+ }
94
+ async connect(_options) {
95
+ const client = this.ensureClient();
96
+ debug("connect requested", { status: client.getStatus() });
97
+ try {
98
+ await client.connect();
99
+ debug("client.connect resolved");
100
+ await client.waitConnected();
101
+ debug("client.waitConnected resolved", { status: client.getStatus() });
102
+ } catch (error) {
103
+ debug("connect failed", error);
104
+ throw error;
105
+ }
106
+ }
107
+ async close() {
108
+ debug("close requested", {
109
+ docSessions: this.docSessions.size,
110
+ metadataSession: Boolean(this.metadataSession)
111
+ });
112
+ for (const [docId] of this.docSessions) await this.leaveDocSession(docId).catch(() => {});
113
+ this.docSessions.clear();
114
+ await this.teardownMetadataSession().catch(() => {});
115
+ if (this.client) {
116
+ const client = this.client;
117
+ this.client = void 0;
118
+ client.destroy();
119
+ debug("websocket client destroyed");
120
+ }
121
+ debug("close completed");
122
+ }
123
+ isConnected() {
124
+ return this.client?.getStatus() === "connected";
125
+ }
126
+ async syncMeta(flock, options) {
127
+ debug("syncMeta requested", { roomId: this.options.metadataRoomId });
128
+ try {
129
+ let auth;
130
+ if (this.options.metadataAuth) if (typeof this.options.metadataAuth === "function") auth = await this.options.metadataAuth();
131
+ else auth = this.options.metadataAuth;
132
+ await withTimeout((await this.ensureMetadataSession(flock, {
133
+ roomId: this.options.metadataRoomId ?? "repo:meta",
134
+ auth
135
+ })).firstSynced, options?.timeout);
136
+ debug("syncMeta completed", { roomId: this.options.metadataRoomId });
137
+ return { ok: true };
138
+ } catch (error) {
139
+ debug("syncMeta failed", error);
140
+ return { ok: false };
141
+ }
142
+ }
143
+ joinMetaRoom(flock, params) {
144
+ const fallback = this.options.metadataRoomId ?? "";
145
+ const roomId = normalizeRoomId(params?.roomId, fallback);
146
+ if (!roomId) throw new Error("Metadata room id not configured");
147
+ const session = (async () => {
148
+ let auth;
149
+ const authWay = params?.auth ?? this.options.metadataAuth;
150
+ if (typeof authWay === "function") auth = await authWay();
151
+ else auth = authWay;
152
+ debug("joinMetaRoom requested", {
153
+ roomId,
154
+ hasAuth: Boolean(auth && auth.length)
155
+ });
156
+ return this.ensureMetadataSession(flock, {
157
+ roomId,
158
+ auth
159
+ });
160
+ })();
161
+ const firstSynced = session.then((session$1) => session$1.firstSynced);
162
+ const getConnected = () => this.isConnected();
163
+ const subscription = {
164
+ unsubscribe: () => {
165
+ session.then((session$1) => {
166
+ session$1.refCount = Math.max(0, session$1.refCount - 1);
167
+ debug("metadata session refCount decremented", {
168
+ roomId: session$1.roomId,
169
+ refCount: session$1.refCount
170
+ });
171
+ if (session$1.refCount === 0) {
172
+ debug("tearing down metadata session due to refCount=0", { roomId: session$1.roomId });
173
+ this.teardownMetadataSession(session$1).catch(() => {});
174
+ }
175
+ });
176
+ },
177
+ firstSyncedWithRemote: firstSynced,
178
+ get connected() {
179
+ return getConnected();
180
+ }
181
+ };
182
+ session.then((session$1) => {
183
+ session$1.refCount += 1;
184
+ debug("metadata session refCount incremented", {
185
+ roomId: session$1.roomId,
186
+ refCount: session$1.refCount
187
+ });
188
+ });
189
+ return subscription;
190
+ }
191
+ async syncDoc(docId, doc, options) {
192
+ debug("syncDoc requested", { docId });
193
+ try {
194
+ const session = await this.ensureDocSession(docId, doc, {});
195
+ await withTimeout(session.firstSynced, options?.timeout);
196
+ debug("syncDoc completed", {
197
+ docId,
198
+ roomId: session.roomId
199
+ });
200
+ return { ok: true };
201
+ } catch (error) {
202
+ debug("syncDoc failed", {
203
+ docId,
204
+ error
205
+ });
206
+ return { ok: false };
207
+ }
208
+ }
209
+ joinDocRoom(docId, doc, params) {
210
+ debug("joinDocRoom requested", {
211
+ docId,
212
+ roomParamType: params?.roomId ? typeof params.roomId === "string" ? "string" : "uint8array" : void 0,
213
+ hasAuthOverride: Boolean(params?.auth && params.auth.length)
214
+ });
215
+ const ensure = this.ensureDocSession(docId, doc, params ?? {});
216
+ const firstSynced = ensure.then((session) => session.firstSynced);
217
+ const getConnected = () => this.isConnected();
218
+ const subscription = {
219
+ unsubscribe: () => {
220
+ ensure.then((session) => {
221
+ session.refCount = Math.max(0, session.refCount - 1);
222
+ debug("doc session refCount decremented", {
223
+ docId,
224
+ roomId: session.roomId,
225
+ refCount: session.refCount
226
+ });
227
+ if (session.refCount === 0) this.leaveDocSession(docId).catch(() => {});
228
+ });
229
+ },
230
+ firstSyncedWithRemote: firstSynced,
231
+ get connected() {
232
+ return getConnected();
233
+ }
234
+ };
235
+ ensure.then((session) => {
236
+ session.refCount += 1;
237
+ debug("doc session refCount incremented", {
238
+ docId,
239
+ roomId: session.roomId,
240
+ refCount: session.refCount
241
+ });
242
+ });
243
+ return subscription;
244
+ }
245
+ ensureClient() {
246
+ if (this.client) {
247
+ debug("reusing websocket client", { status: this.client.getStatus() });
248
+ return this.client;
249
+ }
250
+ const { url, client: clientOptions } = this.options;
251
+ debug("creating websocket client", {
252
+ url,
253
+ clientOptionsKeys: clientOptions ? Object.keys(clientOptions) : []
254
+ });
255
+ const client = new loro_websocket.LoroWebsocketClient({
256
+ url,
257
+ ...clientOptions
258
+ });
259
+ this.client = client;
260
+ return client;
261
+ }
262
+ async ensureMetadataSession(flock, params) {
263
+ debug("ensureMetadataSession invoked", {
264
+ roomId: params.roomId,
265
+ hasAuth: Boolean(params.auth && params.auth.length)
266
+ });
267
+ const client = this.ensureClient();
268
+ await client.waitConnected();
269
+ debug("websocket client ready for metadata session", { status: client.getStatus() });
270
+ if (this.metadataSession && this.metadataSession.flock === flock && this.metadataSession.roomId === params.roomId && bytesEqual(this.metadataSession.auth, params.auth)) {
271
+ debug("reusing metadata session", {
272
+ roomId: this.metadataSession.roomId,
273
+ refCount: this.metadataSession.refCount
274
+ });
275
+ return this.metadataSession;
276
+ }
277
+ if (this.metadataSession) {
278
+ debug("tearing down previous metadata session", { roomId: this.metadataSession.roomId });
279
+ await this.teardownMetadataSession(this.metadataSession).catch(() => {});
280
+ }
281
+ const adaptor = new loro_adaptors_flock.FlockAdaptor(flock, this.options.metadataAdaptorConfig);
282
+ debug("joining metadata room", {
283
+ roomId: params.roomId,
284
+ hasAuth: Boolean(params.auth && params.auth.length)
285
+ });
286
+ const room = await client.join({
287
+ roomId: params.roomId,
288
+ crdtAdaptor: adaptor,
289
+ auth: params.auth
290
+ });
291
+ const firstSynced = room.waitForReachingServerVersion();
292
+ firstSynced.then(() => {
293
+ debug("metadata session firstSynced resolved", { roomId: params.roomId });
294
+ }, (error) => {
295
+ debug("metadata session firstSynced rejected", {
296
+ roomId: params.roomId,
297
+ error
298
+ });
299
+ });
300
+ const session = {
301
+ adaptor,
302
+ room,
303
+ firstSynced,
304
+ flock,
305
+ roomId: params.roomId,
306
+ auth: params.auth,
307
+ refCount: 0
308
+ };
309
+ this.metadataSession = session;
310
+ return session;
311
+ }
312
+ async teardownMetadataSession(session) {
313
+ const target = session ?? this.metadataSession;
314
+ if (!target) return;
315
+ debug("teardownMetadataSession invoked", { roomId: target.roomId });
316
+ if (this.metadataSession === target) this.metadataSession = void 0;
317
+ const { adaptor, room } = target;
318
+ try {
319
+ await room.leave();
320
+ debug("metadata room left", { roomId: target.roomId });
321
+ } catch (error) {
322
+ debug("metadata room leave failed; destroying", {
323
+ roomId: target.roomId,
324
+ error
325
+ });
326
+ await room.destroy().catch(() => {});
327
+ }
328
+ adaptor.destroy();
329
+ debug("metadata session destroyed", { roomId: target.roomId });
330
+ }
331
+ async ensureDocSession(docId, doc, params) {
332
+ debug("ensureDocSession invoked", { docId });
333
+ const client = this.ensureClient();
334
+ await client.waitConnected();
335
+ debug("websocket client ready for doc session", {
336
+ docId,
337
+ status: client.getStatus()
338
+ });
339
+ const existing = this.docSessions.get(docId);
340
+ const derivedRoomId = this.options.docRoomId?.(docId) ?? docId;
341
+ const roomId = normalizeRoomId(params.roomId, derivedRoomId);
342
+ let auth;
343
+ auth = await (params.auth ?? this.options.docAuth?.(docId));
344
+ debug("doc session params resolved", {
345
+ docId,
346
+ roomId,
347
+ hasAuth: Boolean(auth && auth.length)
348
+ });
349
+ if (existing && existing.doc === doc && existing.roomId === roomId) {
350
+ debug("reusing doc session", {
351
+ docId,
352
+ roomId,
353
+ refCount: existing.refCount
354
+ });
355
+ return existing;
356
+ }
357
+ if (existing) {
358
+ debug("doc session mismatch; leaving existing session", {
359
+ docId,
360
+ previousRoomId: existing.roomId,
361
+ nextRoomId: roomId
362
+ });
363
+ await this.leaveDocSession(docId).catch(() => {});
364
+ }
365
+ const adaptor = new loro_adaptors_loro.LoroAdaptor(doc);
366
+ debug("joining doc room", {
367
+ docId,
368
+ roomId,
369
+ hasAuth: Boolean(auth && auth.length)
370
+ });
371
+ const room = await client.join({
372
+ roomId,
373
+ crdtAdaptor: adaptor,
374
+ auth
375
+ });
376
+ const firstSynced = room.waitForReachingServerVersion();
377
+ firstSynced.then(() => {
378
+ debug("doc session firstSynced resolved", {
379
+ docId,
380
+ roomId
381
+ });
382
+ }, (error) => {
383
+ debug("doc session firstSynced rejected", {
384
+ docId,
385
+ roomId,
386
+ error
387
+ });
388
+ });
389
+ const session = {
390
+ adaptor,
391
+ room,
392
+ firstSynced,
393
+ doc,
394
+ roomId,
395
+ refCount: 0
396
+ };
397
+ this.docSessions.set(docId, session);
398
+ return session;
399
+ }
400
+ async leaveDocSession(docId) {
401
+ const session = this.docSessions.get(docId);
402
+ if (!session) {
403
+ debug("leaveDocSession invoked but no session found", { docId });
404
+ return;
405
+ }
406
+ this.docSessions.delete(docId);
407
+ debug("leaving doc session", {
408
+ docId,
409
+ roomId: session.roomId
410
+ });
411
+ try {
412
+ await session.room.leave();
413
+ debug("doc room left", {
414
+ docId,
415
+ roomId: session.roomId
416
+ });
417
+ } catch (error) {
418
+ debug("doc room leave failed; destroying", {
419
+ docId,
420
+ roomId: session.roomId,
421
+ error
422
+ });
423
+ await session.room.destroy().catch(() => {});
424
+ }
425
+ session.adaptor.destroy();
426
+ debug("doc session destroyed", {
427
+ docId,
428
+ roomId: session.roomId
429
+ });
430
+ }
431
+ };
432
+
433
+ //#endregion
434
+ exports.WebSocketTransportAdapter = WebSocketTransportAdapter;
435
+ //# sourceMappingURL=websocket.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.cjs","names":["auth: Uint8Array | undefined","session","subscription: TransportSubscription","LoroWebsocketClient","FlockAdaptor","session: MetadataSession","LoroAdaptor","session: DocSession"],"sources":["../../src/internal/debug.ts","../../src/transport/websocket.ts"],"sourcesContent":["type EnvRecord = Record<string, string | undefined>;\n\nconst getEnv = (): EnvRecord | undefined => {\n if (typeof globalThis !== \"object\" || globalThis === null) {\n return undefined;\n }\n const processLike = (globalThis as { process?: { env?: EnvRecord } }).process;\n return processLike?.env;\n};\n\nconst rawNamespaceConfig = (getEnv()?.LORO_REPO_DEBUG ?? \"\").trim();\n\nconst normalizedNamespaces =\n rawNamespaceConfig.length > 0\n ? rawNamespaceConfig\n .split(/[\\s,]+/)\n .map((token) => token.toLowerCase())\n .filter(Boolean)\n : [];\n\nconst wildcardTokens = new Set([\"*\", \"1\", \"true\", \"all\"]);\nconst namespaceSet = new Set(normalizedNamespaces);\nconst hasWildcard =\n namespaceSet.size > 0 &&\n normalizedNamespaces.some((token) => wildcardTokens.has(token));\n\nexport const isDebugEnabled = (namespace?: string): boolean => {\n if (!namespaceSet.size) {\n return false;\n }\n if (!namespace) {\n return hasWildcard;\n }\n const normalized = namespace.toLowerCase();\n if (hasWildcard) {\n return true;\n }\n if (namespaceSet.has(normalized)) {\n return true;\n }\n const [root] = normalized.split(\":\");\n return namespaceSet.has(root);\n};\n\nexport type DebugLogger = (...args: unknown[]) => void;\n\nexport const createDebugLogger = (namespace: string): DebugLogger => {\n const normalized = namespace.toLowerCase();\n return (...args: unknown[]) => {\n if (!isDebugEnabled(normalized)) {\n return;\n }\n const prefix = `[loro-repo:${namespace}]`;\n if (args.length === 0) {\n console.info(prefix);\n return;\n }\n console.info(prefix, ...args);\n };\n};\n","import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\nimport { type CrdtDocAdaptor } from \"loro-adaptors\"\nimport { LoroAdaptor, LoroAdaptorConfig } from \"loro-adaptors/loro\";\nimport { bytesToHex } from \"loro-protocol\";\nimport {\n LoroWebsocketClient,\n type LoroWebsocketClientOptions,\n type LoroWebsocketClientRoom,\n} from \"loro-websocket\";\nimport { createDebugLogger } from \"../internal/debug\";\n\nimport type {\n RepoSyncOptions,\n TransportAdapter,\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n} from \"../types\";\nimport { FlockAdaptor } from \"loro-adaptors/flock\";\n\ntype MetadataSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n flock: Flock;\n roomId: string;\n auth?: Uint8Array;\n refCount: number;\n};\n\ntype DocSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n doc: LoroDoc;\n roomId: string;\n refCount: number;\n};\n\nconst debug = createDebugLogger(\"transport:websocket\");\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs?: number): Promise<T> {\n if (!timeoutMs || timeoutMs <= 0) {\n return promise;\n }\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n promise\n .then((value) => {\n clearTimeout(timer);\n resolve(value);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n\nfunction normalizeRoomId(roomId: unknown, fallback: string): string {\n if (typeof roomId === \"string\" && roomId.length > 0) {\n return roomId;\n }\n if (roomId instanceof Uint8Array && roomId.length > 0) {\n try {\n return bytesToHex(roomId);\n } catch {\n return fallback;\n }\n }\n return fallback;\n}\n\nexport interface WebSocketTransportOptions {\n /**\n * WebSocket endpoint provided to the loro-websocket client.\n */\n readonly url: string;\n /**\n * Metadata room identifier. Defaults to \"repo:meta\".\n */\n readonly metadataRoomId?: string;\n /**\n * Optional loro-websocket client configuration.\n */\n readonly client?: Omit<LoroWebsocketClientOptions, \"url\">;\n /**\n * Optional adaptor configuration for metadata joins.\n */\n readonly metadataAdaptorConfig?: LoroAdaptorConfig;\n /**\n * Static auth payload for metadata joins.\n */\n readonly metadataAuth?: Uint8Array | (() => Promise<Uint8Array>);\n /**\n * Factory that maps document ids to room identifiers. Defaults to the doc id.\n */\n readonly docRoomId?: (docId: string) => string;\n /**\n * Optional auth provider for document joins.\n */\n readonly docAuth?: (docId: string) => Promise<Uint8Array>;\n}\n\nfunction bytesEqual(a?: Uint8Array, b?: Uint8Array): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo. \n * It uses loro-protocol as the underlying protocol for the transport.\n */\nexport class WebSocketTransportAdapter implements TransportAdapter {\n private readonly options: WebSocketTransportOptions;\n private client?: LoroWebsocketClient;\n private metadataSession?: MetadataSession;\n private readonly docSessions = new Map<string, DocSession>();\n\n constructor(options: WebSocketTransportOptions) {\n this.options = options;\n }\n\n async connect(_options?: { timeout?: number }): Promise<void> {\n const client = this.ensureClient();\n debug(\"connect requested\", { status: client.getStatus() });\n try {\n await client.connect();\n debug(\"client.connect resolved\");\n await client.waitConnected();\n debug(\"client.waitConnected resolved\", { status: client.getStatus() });\n } catch (error) {\n debug(\"connect failed\", error);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n debug(\"close requested\", {\n docSessions: this.docSessions.size,\n metadataSession: Boolean(this.metadataSession),\n });\n for (const [docId] of this.docSessions) {\n await this.leaveDocSession(docId).catch(() => { });\n }\n this.docSessions.clear();\n\n await this.teardownMetadataSession().catch(() => { });\n\n if (this.client) {\n const client = this.client;\n this.client = undefined;\n client.destroy();\n debug(\"websocket client destroyed\");\n }\n debug(\"close completed\");\n }\n\n isConnected(): boolean {\n return this.client?.getStatus() === \"connected\";\n }\n\n async syncMeta(\n flock: Flock,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncMeta requested\", { roomId: this.options.metadataRoomId });\n try {\n let auth: Uint8Array | undefined;\n if (this.options.metadataAuth) {\n if (typeof this.options.metadataAuth === \"function\") {\n auth = await this.options.metadataAuth();\n } else {\n auth = this.options.metadataAuth;\n }\n }\n const session = await this.ensureMetadataSession(flock, {\n roomId: this.options.metadataRoomId ?? \"repo:meta\",\n auth: auth\n });\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncMeta completed\", { roomId: this.options.metadataRoomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncMeta failed\", error);\n return { ok: false };\n }\n }\n\n joinMetaRoom(\n flock: Flock,\n params?: TransportJoinParams,\n ): TransportSubscription {\n const fallback = this.options.metadataRoomId ?? \"\";\n const roomId = normalizeRoomId(params?.roomId, fallback);\n if (!roomId) {\n throw new Error(\"Metadata room id not configured\");\n }\n\n const session = (async () => {\n let auth: Uint8Array | undefined;\n const authWay = params?.auth ?? this.options.metadataAuth;\n if (typeof authWay === \"function\") {\n auth = await authWay();\n } else {\n auth = authWay;\n }\n debug(\"joinMetaRoom requested\", {\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const ensure = this.ensureMetadataSession(flock, {\n roomId,\n auth,\n });\n return ensure;\n })();\n const firstSynced = session.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void session.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"metadata session refCount decremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n debug(\"tearing down metadata session due to refCount=0\", {\n roomId: session.roomId,\n });\n void this.teardownMetadataSession(session).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n };\n\n void session.then((session) => {\n session.refCount += 1;\n debug(\"metadata session refCount incremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n async syncDoc(\n docId: string,\n doc: LoroDoc,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncDoc requested\", { docId });\n try {\n const session = await this.ensureDocSession(docId, doc, {});\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncDoc completed\", { docId, roomId: session.roomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncDoc failed\", { docId, error });\n return { ok: false };\n }\n }\n\n joinDocRoom(\n docId: string,\n doc: LoroDoc,\n params?: TransportJoinParams,\n ): TransportSubscription {\n debug(\"joinDocRoom requested\", {\n docId,\n roomParamType: params?.roomId\n ? typeof params.roomId === \"string\"\n ? \"string\"\n : \"uint8array\"\n : undefined,\n hasAuthOverride: Boolean(params?.auth && params.auth.length),\n });\n const ensure = this.ensureDocSession(docId, doc, params ?? {});\n const firstSynced = ensure.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void ensure.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"doc session refCount decremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n void this.leaveDocSession(docId).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n };\n void ensure.then((session) => {\n session.refCount += 1;\n debug(\"doc session refCount incremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n private ensureClient(): LoroWebsocketClient {\n if (this.client) {\n debug(\"reusing websocket client\", { status: this.client.getStatus() });\n return this.client;\n }\n const { url, client: clientOptions } = this.options;\n debug(\"creating websocket client\", {\n url,\n clientOptionsKeys: clientOptions ? Object.keys(clientOptions) : [],\n });\n const client = new LoroWebsocketClient({\n url,\n ...clientOptions,\n });\n this.client = client;\n return client;\n }\n\n private async ensureMetadataSession(\n flock: Flock,\n params: { roomId: string; auth?: Uint8Array },\n ): Promise<MetadataSession> {\n debug(\"ensureMetadataSession invoked\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for metadata session\", {\n status: client.getStatus(),\n });\n\n if (\n this.metadataSession &&\n this.metadataSession.flock === flock &&\n this.metadataSession.roomId === params.roomId &&\n bytesEqual(this.metadataSession.auth, params.auth)\n ) {\n debug(\"reusing metadata session\", {\n roomId: this.metadataSession.roomId,\n refCount: this.metadataSession.refCount,\n });\n return this.metadataSession;\n }\n\n if (this.metadataSession) {\n debug(\"tearing down previous metadata session\", {\n roomId: this.metadataSession.roomId,\n });\n await this.teardownMetadataSession(this.metadataSession).catch(() => { });\n }\n\n const adaptor = new FlockAdaptor(\n flock,\n this.options.metadataAdaptorConfig\n );\n debug(\"joining metadata room\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const room = await client.join({\n roomId: params.roomId,\n crdtAdaptor: adaptor,\n auth: params.auth,\n });\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"metadata session firstSynced resolved\", {\n roomId: params.roomId,\n });\n },\n (error) => {\n debug(\"metadata session firstSynced rejected\", {\n roomId: params.roomId,\n error,\n });\n },\n );\n const session: MetadataSession = {\n adaptor,\n room,\n firstSynced,\n flock,\n roomId: params.roomId,\n auth: params.auth,\n refCount: 0,\n };\n this.metadataSession = session;\n return session;\n }\n\n private async teardownMetadataSession(\n session?: MetadataSession,\n ): Promise<void> {\n const target = session ?? this.metadataSession;\n if (!target) return;\n debug(\"teardownMetadataSession invoked\", { roomId: target.roomId });\n if (this.metadataSession === target) {\n this.metadataSession = undefined;\n }\n const { adaptor, room } = target;\n try {\n await room.leave();\n debug(\"metadata room left\", { roomId: target.roomId });\n } catch (error) {\n debug(\"metadata room leave failed; destroying\", {\n roomId: target.roomId,\n error,\n });\n await room.destroy().catch(() => { });\n }\n adaptor.destroy();\n debug(\"metadata session destroyed\", { roomId: target.roomId });\n }\n\n private async ensureDocSession(\n docId: string,\n doc: LoroDoc,\n params: TransportJoinParams,\n ): Promise<DocSession> {\n debug(\"ensureDocSession invoked\", { docId });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for doc session\", {\n docId,\n status: client.getStatus(),\n });\n\n const existing = this.docSessions.get(docId);\n const derivedRoomId = this.options.docRoomId?.(docId) ?? docId;\n const roomId = normalizeRoomId(params.roomId, derivedRoomId);\n let auth: Uint8Array | undefined;\n const authWay = params.auth ?? this.options.docAuth?.(docId);\n auth = await authWay;\n debug(\"doc session params resolved\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n\n if (existing && existing.doc === doc && existing.roomId === roomId) {\n debug(\"reusing doc session\", {\n docId,\n roomId,\n refCount: existing.refCount,\n });\n return existing;\n }\n\n if (existing) {\n debug(\"doc session mismatch; leaving existing session\", {\n docId,\n previousRoomId: existing.roomId,\n nextRoomId: roomId,\n });\n await this.leaveDocSession(docId).catch(() => { });\n }\n\n const adaptor = new LoroAdaptor(doc);\n debug(\"joining doc room\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const room = await client.join({\n roomId,\n crdtAdaptor: adaptor,\n auth,\n });\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"doc session firstSynced resolved\", { docId, roomId });\n },\n (error) => {\n debug(\"doc session firstSynced rejected\", { docId, roomId, error });\n },\n );\n const session: DocSession = {\n adaptor,\n room,\n firstSynced,\n doc,\n roomId,\n refCount: 0,\n };\n this.docSessions.set(docId, session);\n return session;\n }\n\n private async leaveDocSession(docId: string): Promise<void> {\n const session = this.docSessions.get(docId);\n if (!session) {\n debug(\"leaveDocSession invoked but no session found\", { docId });\n return;\n }\n this.docSessions.delete(docId);\n debug(\"leaving doc session\", { docId, roomId: session.roomId });\n try {\n await session.room.leave();\n debug(\"doc room left\", { docId, roomId: session.roomId });\n } catch (error) {\n debug(\"doc room leave failed; destroying\", {\n docId,\n roomId: session.roomId,\n error,\n });\n await session.room.destroy().catch(() => { });\n }\n session.adaptor.destroy();\n debug(\"doc session destroyed\", { docId, roomId: session.roomId });\n }\n}\n\nexport type {\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n RepoSyncOptions,\n};\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { A as TransportSyncResult, D as TransportJoinParams, E as TransportAdapter, O as TransportSubscription, x as RepoSyncOptions } from "../types.cjs";
2
+ import { Flock } from "@loro-dev/flock";
3
+ import { LoroDoc } from "loro-crdt";
4
+ import { LoroAdaptorConfig } from "loro-adaptors/loro";
5
+ import { LoroWebsocketClientOptions } from "loro-websocket";
6
+
7
+ //#region src/transport/websocket.d.ts
8
+ interface WebSocketTransportOptions {
9
+ /**
10
+ * WebSocket endpoint provided to the loro-websocket client.
11
+ */
12
+ readonly url: string;
13
+ /**
14
+ * Metadata room identifier. Defaults to "repo:meta".
15
+ */
16
+ readonly metadataRoomId?: string;
17
+ /**
18
+ * Optional loro-websocket client configuration.
19
+ */
20
+ readonly client?: Omit<LoroWebsocketClientOptions, "url">;
21
+ /**
22
+ * Optional adaptor configuration for metadata joins.
23
+ */
24
+ readonly metadataAdaptorConfig?: LoroAdaptorConfig;
25
+ /**
26
+ * Static auth payload for metadata joins.
27
+ */
28
+ readonly metadataAuth?: Uint8Array | (() => Promise<Uint8Array>);
29
+ /**
30
+ * Factory that maps document ids to room identifiers. Defaults to the doc id.
31
+ */
32
+ readonly docRoomId?: (docId: string) => string;
33
+ /**
34
+ * Optional auth provider for document joins.
35
+ */
36
+ readonly docAuth?: (docId: string) => Promise<Uint8Array>;
37
+ }
38
+ /**
39
+ * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
40
+ * It uses loro-protocol as the underlying protocol for the transport.
41
+ */
42
+ declare class WebSocketTransportAdapter implements TransportAdapter {
43
+ private readonly options;
44
+ private client?;
45
+ private metadataSession?;
46
+ private readonly docSessions;
47
+ constructor(options: WebSocketTransportOptions);
48
+ connect(_options?: {
49
+ timeout?: number;
50
+ }): Promise<void>;
51
+ close(): Promise<void>;
52
+ isConnected(): boolean;
53
+ syncMeta(flock: Flock, options?: {
54
+ timeout?: number;
55
+ }): Promise<TransportSyncResult>;
56
+ joinMetaRoom(flock: Flock, params?: TransportJoinParams): TransportSubscription;
57
+ syncDoc(docId: string, doc: LoroDoc, options?: {
58
+ timeout?: number;
59
+ }): Promise<TransportSyncResult>;
60
+ joinDocRoom(docId: string, doc: LoroDoc, params?: TransportJoinParams): TransportSubscription;
61
+ private ensureClient;
62
+ private ensureMetadataSession;
63
+ private teardownMetadataSession;
64
+ private ensureDocSession;
65
+ private leaveDocSession;
66
+ }
67
+ //#endregion
68
+ export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketTransportAdapter, WebSocketTransportOptions };
69
+ //# sourceMappingURL=websocket.d.cts.map
@@ -0,0 +1,69 @@
1
+ import { A as TransportSyncResult, D as TransportJoinParams, E as TransportAdapter, O as TransportSubscription, x as RepoSyncOptions } from "../types.js";
2
+ import { Flock } from "@loro-dev/flock";
3
+ import { LoroDoc } from "loro-crdt";
4
+ import { LoroAdaptorConfig } from "loro-adaptors/loro";
5
+ import { LoroWebsocketClientOptions } from "loro-websocket";
6
+
7
+ //#region src/transport/websocket.d.ts
8
+ interface WebSocketTransportOptions {
9
+ /**
10
+ * WebSocket endpoint provided to the loro-websocket client.
11
+ */
12
+ readonly url: string;
13
+ /**
14
+ * Metadata room identifier. Defaults to "repo:meta".
15
+ */
16
+ readonly metadataRoomId?: string;
17
+ /**
18
+ * Optional loro-websocket client configuration.
19
+ */
20
+ readonly client?: Omit<LoroWebsocketClientOptions, "url">;
21
+ /**
22
+ * Optional adaptor configuration for metadata joins.
23
+ */
24
+ readonly metadataAdaptorConfig?: LoroAdaptorConfig;
25
+ /**
26
+ * Static auth payload for metadata joins.
27
+ */
28
+ readonly metadataAuth?: Uint8Array | (() => Promise<Uint8Array>);
29
+ /**
30
+ * Factory that maps document ids to room identifiers. Defaults to the doc id.
31
+ */
32
+ readonly docRoomId?: (docId: string) => string;
33
+ /**
34
+ * Optional auth provider for document joins.
35
+ */
36
+ readonly docAuth?: (docId: string) => Promise<Uint8Array>;
37
+ }
38
+ /**
39
+ * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
40
+ * It uses loro-protocol as the underlying protocol for the transport.
41
+ */
42
+ declare class WebSocketTransportAdapter implements TransportAdapter {
43
+ private readonly options;
44
+ private client?;
45
+ private metadataSession?;
46
+ private readonly docSessions;
47
+ constructor(options: WebSocketTransportOptions);
48
+ connect(_options?: {
49
+ timeout?: number;
50
+ }): Promise<void>;
51
+ close(): Promise<void>;
52
+ isConnected(): boolean;
53
+ syncMeta(flock: Flock, options?: {
54
+ timeout?: number;
55
+ }): Promise<TransportSyncResult>;
56
+ joinMetaRoom(flock: Flock, params?: TransportJoinParams): TransportSubscription;
57
+ syncDoc(docId: string, doc: LoroDoc, options?: {
58
+ timeout?: number;
59
+ }): Promise<TransportSyncResult>;
60
+ joinDocRoom(docId: string, doc: LoroDoc, params?: TransportJoinParams): TransportSubscription;
61
+ private ensureClient;
62
+ private ensureMetadataSession;
63
+ private teardownMetadataSession;
64
+ private ensureDocSession;
65
+ private leaveDocSession;
66
+ }
67
+ //#endregion
68
+ export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketTransportAdapter, WebSocketTransportOptions };
69
+ //# sourceMappingURL=websocket.d.ts.map