@mdfn/collab 0.1.0 → 0.1.1

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
@@ -6,13 +6,19 @@ and contract negotiation. Transport remains host-controlled: exchange
6
6
  `encodeUpdate()` bytes over an authorized provider and call `applyUpdate()` for
7
7
  remote data.
8
8
 
9
- Each remote update is size checked and authorized, then applied to an isolated
9
+ Each remote update is size checked, passed to `authorizeUpdate` when configured,
10
+ then applied to an isolated
10
11
  candidate document. The candidate must retain the expected document ID, schema
11
12
  hash, profile, protocol version, extension set, Markdown, and sidecar validity
12
13
  before the live document is changed. Rejections and queue lifecycle events are
13
14
  available through the audit callback.
14
15
 
15
- Queue compaction merges only unsent dependency-complete updates. It never
16
+ The host must authorize its transport or supply `authorizeUpdate`; there is no
17
+ default identity check. Use `authorizeSidecarUpdate` for protected editorial changes.
18
+
19
+ Queue compaction merges unsent updates. A merged batch can depend on the preceding
20
+ in-flight update; preserve queue delivery order rather than treating it as a
21
+ standalone snapshot. It never
16
22
  re-encodes the full long-lived `Y.Doc`, whose retained CRDT history can exceed
17
23
  the transport limit. If a merged row would exceed `maxUpdateBytes`, the session
18
24
  keeps the original incremental rows and flushes them independently.
package/dist/index.cjs CHANGED
@@ -311,7 +311,7 @@ function createCollaborationSession(options) {
311
311
  }
312
312
  };
313
313
  }
314
- var MDFN_COLLAB_VERSION = "0.1.0";
314
+ var MDFN_COLLAB_VERSION = "0.1.1";
315
315
  // Annotate the CommonJS export names for ESM import in node:
316
316
  0 && (module.exports = {
317
317
  Awareness,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { Transaction, validateMdfnSidecar, type EditorController, type MdfnJsonValue, type MdfnSidecar } from \"@mdfn/core\";\n\nexport interface CollaborationUser {\n readonly id: string;\n readonly name?: string;\n readonly color?: string;\n readonly metadata?: Readonly<Record<string, MdfnJsonValue>>;\n}\n\nexport interface CollaborationSessionOptions {\n readonly controller: EditorController;\n readonly documentId: string;\n readonly user: CollaborationUser;\n readonly doc?: Y.Doc;\n readonly awareness?: Awareness;\n readonly authorizeUpdate?: (update: Uint8Array, origin: unknown) => boolean | Promise<boolean>;\n /** Protected editorial sidecar changes require an authoritative host decision. */\n readonly authorizeSidecarUpdate?: (previous: MdfnSidecar | undefined, next: MdfnSidecar | undefined, origin: unknown) => boolean | Promise<boolean>;\n readonly profileId?: string;\n readonly protocolVersion?: number;\n /** Maximum valid UTF-8 Markdown size; used to derive safe Yjs update limits. */\n readonly maxDocumentBytes?: number;\n readonly maxUpdateBytes?: number;\n readonly online?: boolean;\n readonly sendUpdate?: (update: Uint8Array) => void | Promise<void>;\n readonly compactionThresholdBytes?: number;\n readonly onAudit?: (event: CollaborationAuditEvent) => void;\n}\n\nexport interface CollaborationAuditEvent {\n readonly type: \"local-update\" | \"remote-update\" | \"remote-rejected\" | \"offline\" | \"online\" | \"flush\" | \"compact\";\n readonly documentId: string;\n readonly userId: string;\n readonly byteLength?: number;\n readonly pending?: number;\n readonly error?: string;\n}\n\nfunction parseSidecar(value: string | undefined, markdownLength?: number): MdfnSidecar | undefined {\n if (value === undefined) return undefined;\n try {\n return validateMdfnSidecar(JSON.parse(value), { markdownLength });\n } catch (error) {\n throw new Error(`MDFN_COLLAB_SIDECAR_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport interface CollaborationSession {\n readonly doc: Y.Doc;\n readonly awareness: Awareness;\n encodeUpdate(stateVector?: Uint8Array): Uint8Array;\n encodeStateVector(): Uint8Array;\n applyUpdate(update: Uint8Array, origin?: unknown): Promise<void>;\n setPresence(value: Readonly<Record<string, MdfnJsonValue>>): void;\n getPresence(): ReadonlyMap<number, unknown>;\n isOnline(): boolean;\n pendingUpdateCount(): number;\n setOnline(online: boolean): Promise<void>;\n flush(): Promise<void>;\n compact(): Uint8Array;\n destroy(): void;\n}\n\nfunction changedRange(previous: string, current: string): { from: number; to: number; inserted: string } {\n let from = 0;\n while (from < previous.length && from < current.length && previous[from] === current[from]) from += 1;\n let oldEnd = previous.length;\n let newEnd = current.length;\n while (oldEnd > from && newEnd > from && previous[oldEnd - 1] === current[newEnd - 1]) { oldEnd -= 1; newEnd -= 1; }\n return { from, to: oldEnd, inserted: current.slice(from, newEnd) };\n}\n\nexport function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession {\n const doc = options.doc ?? new Y.Doc({ guid: options.documentId });\n const ownsAwareness = !options.awareness;\n const awareness = options.awareness ?? new Awareness(doc);\n const source = doc.getText(\"markdown\");\n const sidecar = doc.getMap<string>(\"sidecar\");\n const metadata = doc.getMap<string>(\"metadata\");\n const sharedDocumentInitialized = options.doc !== undefined && (source.length > 0 || sidecar.size > 0 || metadata.size > 0);\n const localOrigin = Object.freeze({ session: options.user.id });\n let applyingRemote = false;\n let destroyed = false;\n let online = options.online ?? true;\n interface PendingUpdate { readonly id: number; readonly update: Uint8Array; }\n let nextPendingId = 0;\n let pendingUpdates: PendingUpdate[] = [];\n let inFlight: PendingUpdate | null = null;\n let flushing: Promise<void> | null = null;\n let remoteApplication: Promise<void> = Promise.resolve();\n let documentGeneration = 0;\n const maxDocumentBytes = options.maxDocumentBytes ?? 2 * 1024 * 1024;\n const maxUpdateBytes = options.maxUpdateBytes ?? maxDocumentBytes + Math.max(64 * 1024, Math.ceil(maxDocumentBytes / 16));\n\n const emitAudit = (event: Omit<CollaborationAuditEvent, \"documentId\" | \"userId\">): void => {\n options.onAudit?.({ ...event, documentId: options.documentId, userId: options.user.id });\n };\n\n const expectedSchema = options.controller.getState().schemaHash;\n const expectedDocumentId = options.documentId;\n const expectedProfile = options.profileId ?? \"default\";\n const expectedProtocol = String(options.protocolVersion ?? 1);\n const expectedExtensions = JSON.stringify(options.controller.extensions.extensions.map((extension) => ({ name: extension.name, version: extension.version })));\n const bindMetadata = (key: string, expected: string, code: string): void => {\n const current = metadata.get(key);\n if (current !== undefined && current !== expected) throw new Error(`${code}:${current}:${expected}`);\n if (current === undefined) metadata.set(key, expected);\n };\n bindMetadata(\"schemaHash\", expectedSchema, \"MDFN_COLLAB_SCHEMA_MISMATCH\");\n bindMetadata(\"documentId\", expectedDocumentId, \"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n bindMetadata(\"profileId\", expectedProfile, \"MDFN_COLLAB_PROFILE_MISMATCH\");\n bindMetadata(\"protocolVersion\", expectedProtocol, \"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n bindMetadata(\"extensions\", expectedExtensions, \"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n\n const assertContract = (candidate: Y.Doc): void => {\n const candidateMetadata = candidate.getMap<string>(\"metadata\");\n const schema = candidateMetadata.get(\"schemaHash\");\n if (schema !== expectedSchema) throw new Error(`MDFN_COLLAB_SCHEMA_MISMATCH:${schema ?? \"missing\"}:${expectedSchema}`);\n if (candidateMetadata.get(\"documentId\") !== expectedDocumentId) throw new Error(\"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n if (candidateMetadata.get(\"profileId\") !== expectedProfile) throw new Error(\"MDFN_COLLAB_PROFILE_MISMATCH\");\n if (candidateMetadata.get(\"protocolVersion\") !== expectedProtocol) throw new Error(\"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n if (candidateMetadata.get(\"extensions\") !== expectedExtensions) throw new Error(\"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n const markdown = candidate.getText(\"markdown\").toString();\n try {\n options.controller.validateMarkdown(markdown);\n } catch (error) {\n throw new Error(`MDFN_COLLAB_MARKDOWN_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), markdown.length);\n };\n\n const canonicalJson = (value: unknown): unknown => {\n if (Array.isArray(value)) return value.map(canonicalJson);\n if (value === null || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)\n .map(([key, entry]) => [key, canonicalJson(entry)]),\n );\n };\n const protectedSidecar = (value: MdfnSidecar | undefined): string => JSON.stringify(canonicalJson({\n comments: value?.comments ?? [],\n suggestions: value?.suggestions ?? [],\n reviewState: value?.reviewState ?? \"draft\",\n audit: value?.audit ?? [],\n }));\n\n const validateUpdate = async (update: Uint8Array, origin: unknown): Promise<number> => {\n const baseline = documentGeneration;\n const candidate = new Y.Doc({ guid: expectedDocumentId });\n try {\n Y.applyUpdate(candidate, Y.encodeStateAsUpdate(doc), \"validation:baseline\");\n Y.applyUpdate(candidate, update, \"validation:candidate\");\n assertContract(candidate);\n const previousSidecar = parseSidecar(sidecar.get(\"value\"), source.length);\n const candidateSidecar = parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), candidate.getText(\"markdown\").length);\n if (protectedSidecar(previousSidecar) !== protectedSidecar(candidateSidecar)) {\n if (!options.authorizeSidecarUpdate || !(await options.authorizeSidecarUpdate(previousSidecar, candidateSidecar, origin))) {\n throw new Error(\"MDFN_COLLAB_EDITORIAL_UPDATE_FORBIDDEN\");\n }\n }\n return baseline;\n } finally {\n candidate.destroy();\n }\n };\n\n if (!sharedDocumentInitialized) {\n if (source.length === 0) source.insert(0, options.controller.getState().markdown);\n if (!sidecar.has(\"value\") && options.controller.getState().sidecar) sidecar.set(\"value\", JSON.stringify(options.controller.getState().sidecar));\n } else {\n assertContract(doc);\n const state = options.controller.getState();\n const sharedMarkdown = source.toString();\n const sharedSidecar = parseSidecar(sidecar.get(\"value\"), sharedMarkdown.length);\n let initial = new Transaction().withSource(\"collab:initial\").withMetadata({ addToHistory: false });\n if (sharedMarkdown !== state.markdown) initial = initial.replaceSource(0, state.markdown.length, sharedMarkdown);\n if (JSON.stringify(sharedSidecar) !== JSON.stringify(state.sidecar)) initial = initial.setSidecar(sharedSidecar);\n if (initial.operations.length > 0) options.controller.dispatch(initial);\n }\n\n awareness.setLocalState({ user: options.user, documentId: options.documentId });\n\n const trackDocumentGeneration = (): void => { documentGeneration += 1; };\n doc.on(\"afterTransaction\", trackDocumentGeneration);\n\n const unsubscribe = options.controller.subscribe((change) => {\n if (applyingRemote || destroyed) return;\n doc.transact(() => {\n if (change.documentChanged) {\n const current = source.toString();\n const range = changedRange(current, change.current.markdown);\n if (range.to > range.from) source.delete(range.from, range.to - range.from);\n if (range.inserted) source.insert(range.from, range.inserted);\n }\n if (change.sidecarChanged) {\n if (change.current.sidecar) sidecar.set(\"value\", JSON.stringify(change.current.sidecar));\n else sidecar.delete(\"value\");\n }\n }, localOrigin);\n });\n\n const applySharedState = (transaction: Y.Transaction): void => {\n if (destroyed || transaction.origin === localOrigin) return;\n applyingRemote = true;\n try {\n const state = options.controller.getState();\n const nextSource = source.toString();\n let edit = new Transaction().withSource(\"collab:remote\").withMetadata({ addToHistory: false });\n if (nextSource !== state.markdown) edit = edit.replaceSource(0, state.markdown.length, nextSource);\n assertContract(doc);\n const nextSidecar = parseSidecar(sidecar.get(\"value\"), nextSource.length);\n if (JSON.stringify(nextSidecar) !== JSON.stringify(state.sidecar)) edit = edit.setSidecar(nextSidecar);\n if (edit.operations.length > 0) options.controller.dispatch(edit);\n } finally { applyingRemote = false; }\n };\n doc.on(\"afterTransaction\", applySharedState);\n\n const compactPending = (): Uint8Array => {\n const compactable = pendingUpdates.filter((entry) => entry !== inFlight);\n const update = Y.mergeUpdates(compactable.map((entry) => entry.update));\n if (update.byteLength > maxUpdateBytes) {\n throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n }\n if (compactable.length === 0) return update;\n const replacement = { id: ++nextPendingId, update };\n pendingUpdates = inFlight ? [inFlight, replacement] : [replacement];\n emitAudit({ type: \"compact\", byteLength: update.byteLength, pending: pendingUpdates.length });\n return update;\n };\n\n const flush = async (): Promise<void> => {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (!online || !options.sendUpdate || pendingUpdates.length === 0) return;\n if (flushing) return flushing;\n flushing = (async () => {\n while (online && pendingUpdates.length > 0) {\n const entry = pendingUpdates[0];\n inFlight = entry;\n try {\n await options.sendUpdate!(entry.update);\n pendingUpdates = pendingUpdates.filter((candidate) => candidate !== entry);\n emitAudit({ type: \"flush\", byteLength: entry.update.byteLength, pending: pendingUpdates.length });\n } finally {\n if (inFlight === entry) inFlight = null;\n }\n }\n })().finally(() => { flushing = null; });\n return flushing;\n };\n\n const queueLocalUpdate = (update: Uint8Array, origin: unknown): void => {\n if (destroyed || origin !== localOrigin) return;\n pendingUpdates.push({ id: ++nextPendingId, update: update.slice() });\n emitAudit({ type: \"local-update\", byteLength: update.byteLength, pending: pendingUpdates.length });\n const threshold = options.compactionThresholdBytes ?? 2 * 1024 * 1024;\n if (pendingUpdates.reduce((total, entry) => total + entry.update.byteLength, 0) > threshold) {\n try {\n compactPending();\n } catch (error) {\n if (!(error instanceof RangeError) || !error.message.startsWith(\"MDFN_COLLAB_UPDATE_TOO_LARGE:\")) throw error;\n }\n }\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n };\n doc.on(\"update\", queueLocalUpdate);\n if (options.sendUpdate) {\n const initial = Y.encodeStateAsUpdate(doc);\n pendingUpdates.push({ id: ++nextPendingId, update: initial });\n emitAudit({ type: \"local-update\", byteLength: initial.byteLength, pending: pendingUpdates.length });\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n }\n\n return {\n doc,\n awareness,\n encodeUpdate: (stateVector) => stateVector ? Y.encodeStateAsUpdate(doc, stateVector) : Y.encodeStateAsUpdate(doc),\n encodeStateVector: () => Y.encodeStateVector(doc),\n async applyUpdate(update, origin = \"transport\") {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n const apply = async (): Promise<void> => {\n try {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (update.byteLength > maxUpdateBytes) throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n if (options.authorizeUpdate && !(await options.authorizeUpdate(update, origin))) throw new Error(\"MDFN_COLLAB_UPDATE_FORBIDDEN\");\n for (;;) {\n const baseline = await validateUpdate(update, origin);\n if (baseline !== documentGeneration) continue;\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n Y.applyUpdate(doc, update, origin);\n emitAudit({ type: \"remote-update\", byteLength: update.byteLength });\n return;\n }\n } catch (error) {\n emitAudit({ type: \"remote-rejected\", byteLength: update.byteLength, error: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n };\n const queued = remoteApplication.then(apply, apply);\n remoteApplication = queued.catch(() => undefined);\n return queued;\n },\n setPresence(value) { if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\"); awareness.setLocalStateField(\"presence\", value); },\n getPresence: () => awareness.getStates(),\n isOnline: () => online,\n pendingUpdateCount: () => pendingUpdates.length,\n async setOnline(value) {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n online = value;\n emitAudit({ type: value ? \"online\" : \"offline\", pending: pendingUpdates.length });\n if (value) await flush();\n },\n flush,\n compact: compactPending,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n unsubscribe();\n doc.off(\"afterTransaction\", applySharedState);\n doc.off(\"afterTransaction\", trackDocumentGeneration);\n doc.off(\"update\", queueLocalUpdate);\n pendingUpdates = [];\n awareness.setLocalState(null);\n if (ownsAwareness) awareness.destroy();\n },\n };\n}\n\nexport { Awareness, Y };\nexport const MDFN_COLLAB_VERSION = \"0.1.0\" as const;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { Transaction, validateMdfnSidecar, type EditorController, type MdfnJsonValue, type MdfnSidecar } from \"@mdfn/core\";\n\nexport interface CollaborationUser {\n readonly id: string;\n readonly name?: string;\n readonly color?: string;\n readonly metadata?: Readonly<Record<string, MdfnJsonValue>>;\n}\n\nexport interface CollaborationSessionOptions {\n readonly controller: EditorController;\n readonly documentId: string;\n readonly user: CollaborationUser;\n readonly doc?: Y.Doc;\n readonly awareness?: Awareness;\n readonly authorizeUpdate?: (update: Uint8Array, origin: unknown) => boolean | Promise<boolean>;\n /** Protected editorial sidecar changes require an authoritative host decision. */\n readonly authorizeSidecarUpdate?: (previous: MdfnSidecar | undefined, next: MdfnSidecar | undefined, origin: unknown) => boolean | Promise<boolean>;\n readonly profileId?: string;\n readonly protocolVersion?: number;\n /** Maximum valid UTF-8 Markdown size; used to derive safe Yjs update limits. */\n readonly maxDocumentBytes?: number;\n readonly maxUpdateBytes?: number;\n readonly online?: boolean;\n readonly sendUpdate?: (update: Uint8Array) => void | Promise<void>;\n readonly compactionThresholdBytes?: number;\n readonly onAudit?: (event: CollaborationAuditEvent) => void;\n}\n\nexport interface CollaborationAuditEvent {\n readonly type: \"local-update\" | \"remote-update\" | \"remote-rejected\" | \"offline\" | \"online\" | \"flush\" | \"compact\";\n readonly documentId: string;\n readonly userId: string;\n readonly byteLength?: number;\n readonly pending?: number;\n readonly error?: string;\n}\n\nfunction parseSidecar(value: string | undefined, markdownLength?: number): MdfnSidecar | undefined {\n if (value === undefined) return undefined;\n try {\n return validateMdfnSidecar(JSON.parse(value), { markdownLength });\n } catch (error) {\n throw new Error(`MDFN_COLLAB_SIDECAR_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport interface CollaborationSession {\n readonly doc: Y.Doc;\n readonly awareness: Awareness;\n encodeUpdate(stateVector?: Uint8Array): Uint8Array;\n encodeStateVector(): Uint8Array;\n applyUpdate(update: Uint8Array, origin?: unknown): Promise<void>;\n setPresence(value: Readonly<Record<string, MdfnJsonValue>>): void;\n getPresence(): ReadonlyMap<number, unknown>;\n isOnline(): boolean;\n pendingUpdateCount(): number;\n setOnline(online: boolean): Promise<void>;\n flush(): Promise<void>;\n compact(): Uint8Array;\n destroy(): void;\n}\n\nfunction changedRange(previous: string, current: string): { from: number; to: number; inserted: string } {\n let from = 0;\n while (from < previous.length && from < current.length && previous[from] === current[from]) from += 1;\n let oldEnd = previous.length;\n let newEnd = current.length;\n while (oldEnd > from && newEnd > from && previous[oldEnd - 1] === current[newEnd - 1]) { oldEnd -= 1; newEnd -= 1; }\n return { from, to: oldEnd, inserted: current.slice(from, newEnd) };\n}\n\nexport function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession {\n const doc = options.doc ?? new Y.Doc({ guid: options.documentId });\n const ownsAwareness = !options.awareness;\n const awareness = options.awareness ?? new Awareness(doc);\n const source = doc.getText(\"markdown\");\n const sidecar = doc.getMap<string>(\"sidecar\");\n const metadata = doc.getMap<string>(\"metadata\");\n const sharedDocumentInitialized = options.doc !== undefined && (source.length > 0 || sidecar.size > 0 || metadata.size > 0);\n const localOrigin = Object.freeze({ session: options.user.id });\n let applyingRemote = false;\n let destroyed = false;\n let online = options.online ?? true;\n interface PendingUpdate { readonly id: number; readonly update: Uint8Array; }\n let nextPendingId = 0;\n let pendingUpdates: PendingUpdate[] = [];\n let inFlight: PendingUpdate | null = null;\n let flushing: Promise<void> | null = null;\n let remoteApplication: Promise<void> = Promise.resolve();\n let documentGeneration = 0;\n const maxDocumentBytes = options.maxDocumentBytes ?? 2 * 1024 * 1024;\n const maxUpdateBytes = options.maxUpdateBytes ?? maxDocumentBytes + Math.max(64 * 1024, Math.ceil(maxDocumentBytes / 16));\n\n const emitAudit = (event: Omit<CollaborationAuditEvent, \"documentId\" | \"userId\">): void => {\n options.onAudit?.({ ...event, documentId: options.documentId, userId: options.user.id });\n };\n\n const expectedSchema = options.controller.getState().schemaHash;\n const expectedDocumentId = options.documentId;\n const expectedProfile = options.profileId ?? \"default\";\n const expectedProtocol = String(options.protocolVersion ?? 1);\n const expectedExtensions = JSON.stringify(options.controller.extensions.extensions.map((extension) => ({ name: extension.name, version: extension.version })));\n const bindMetadata = (key: string, expected: string, code: string): void => {\n const current = metadata.get(key);\n if (current !== undefined && current !== expected) throw new Error(`${code}:${current}:${expected}`);\n if (current === undefined) metadata.set(key, expected);\n };\n bindMetadata(\"schemaHash\", expectedSchema, \"MDFN_COLLAB_SCHEMA_MISMATCH\");\n bindMetadata(\"documentId\", expectedDocumentId, \"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n bindMetadata(\"profileId\", expectedProfile, \"MDFN_COLLAB_PROFILE_MISMATCH\");\n bindMetadata(\"protocolVersion\", expectedProtocol, \"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n bindMetadata(\"extensions\", expectedExtensions, \"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n\n const assertContract = (candidate: Y.Doc): void => {\n const candidateMetadata = candidate.getMap<string>(\"metadata\");\n const schema = candidateMetadata.get(\"schemaHash\");\n if (schema !== expectedSchema) throw new Error(`MDFN_COLLAB_SCHEMA_MISMATCH:${schema ?? \"missing\"}:${expectedSchema}`);\n if (candidateMetadata.get(\"documentId\") !== expectedDocumentId) throw new Error(\"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n if (candidateMetadata.get(\"profileId\") !== expectedProfile) throw new Error(\"MDFN_COLLAB_PROFILE_MISMATCH\");\n if (candidateMetadata.get(\"protocolVersion\") !== expectedProtocol) throw new Error(\"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n if (candidateMetadata.get(\"extensions\") !== expectedExtensions) throw new Error(\"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n const markdown = candidate.getText(\"markdown\").toString();\n try {\n options.controller.validateMarkdown(markdown);\n } catch (error) {\n throw new Error(`MDFN_COLLAB_MARKDOWN_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), markdown.length);\n };\n\n const canonicalJson = (value: unknown): unknown => {\n if (Array.isArray(value)) return value.map(canonicalJson);\n if (value === null || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)\n .map(([key, entry]) => [key, canonicalJson(entry)]),\n );\n };\n const protectedSidecar = (value: MdfnSidecar | undefined): string => JSON.stringify(canonicalJson({\n comments: value?.comments ?? [],\n suggestions: value?.suggestions ?? [],\n reviewState: value?.reviewState ?? \"draft\",\n audit: value?.audit ?? [],\n }));\n\n const validateUpdate = async (update: Uint8Array, origin: unknown): Promise<number> => {\n const baseline = documentGeneration;\n const candidate = new Y.Doc({ guid: expectedDocumentId });\n try {\n Y.applyUpdate(candidate, Y.encodeStateAsUpdate(doc), \"validation:baseline\");\n Y.applyUpdate(candidate, update, \"validation:candidate\");\n assertContract(candidate);\n const previousSidecar = parseSidecar(sidecar.get(\"value\"), source.length);\n const candidateSidecar = parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), candidate.getText(\"markdown\").length);\n if (protectedSidecar(previousSidecar) !== protectedSidecar(candidateSidecar)) {\n if (!options.authorizeSidecarUpdate || !(await options.authorizeSidecarUpdate(previousSidecar, candidateSidecar, origin))) {\n throw new Error(\"MDFN_COLLAB_EDITORIAL_UPDATE_FORBIDDEN\");\n }\n }\n return baseline;\n } finally {\n candidate.destroy();\n }\n };\n\n if (!sharedDocumentInitialized) {\n if (source.length === 0) source.insert(0, options.controller.getState().markdown);\n if (!sidecar.has(\"value\") && options.controller.getState().sidecar) sidecar.set(\"value\", JSON.stringify(options.controller.getState().sidecar));\n } else {\n assertContract(doc);\n const state = options.controller.getState();\n const sharedMarkdown = source.toString();\n const sharedSidecar = parseSidecar(sidecar.get(\"value\"), sharedMarkdown.length);\n let initial = new Transaction().withSource(\"collab:initial\").withMetadata({ addToHistory: false });\n if (sharedMarkdown !== state.markdown) initial = initial.replaceSource(0, state.markdown.length, sharedMarkdown);\n if (JSON.stringify(sharedSidecar) !== JSON.stringify(state.sidecar)) initial = initial.setSidecar(sharedSidecar);\n if (initial.operations.length > 0) options.controller.dispatch(initial);\n }\n\n awareness.setLocalState({ user: options.user, documentId: options.documentId });\n\n const trackDocumentGeneration = (): void => { documentGeneration += 1; };\n doc.on(\"afterTransaction\", trackDocumentGeneration);\n\n const unsubscribe = options.controller.subscribe((change) => {\n if (applyingRemote || destroyed) return;\n doc.transact(() => {\n if (change.documentChanged) {\n const current = source.toString();\n const range = changedRange(current, change.current.markdown);\n if (range.to > range.from) source.delete(range.from, range.to - range.from);\n if (range.inserted) source.insert(range.from, range.inserted);\n }\n if (change.sidecarChanged) {\n if (change.current.sidecar) sidecar.set(\"value\", JSON.stringify(change.current.sidecar));\n else sidecar.delete(\"value\");\n }\n }, localOrigin);\n });\n\n const applySharedState = (transaction: Y.Transaction): void => {\n if (destroyed || transaction.origin === localOrigin) return;\n applyingRemote = true;\n try {\n const state = options.controller.getState();\n const nextSource = source.toString();\n let edit = new Transaction().withSource(\"collab:remote\").withMetadata({ addToHistory: false });\n if (nextSource !== state.markdown) edit = edit.replaceSource(0, state.markdown.length, nextSource);\n assertContract(doc);\n const nextSidecar = parseSidecar(sidecar.get(\"value\"), nextSource.length);\n if (JSON.stringify(nextSidecar) !== JSON.stringify(state.sidecar)) edit = edit.setSidecar(nextSidecar);\n if (edit.operations.length > 0) options.controller.dispatch(edit);\n } finally { applyingRemote = false; }\n };\n doc.on(\"afterTransaction\", applySharedState);\n\n const compactPending = (): Uint8Array => {\n const compactable = pendingUpdates.filter((entry) => entry !== inFlight);\n const update = Y.mergeUpdates(compactable.map((entry) => entry.update));\n if (update.byteLength > maxUpdateBytes) {\n throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n }\n if (compactable.length === 0) return update;\n const replacement = { id: ++nextPendingId, update };\n pendingUpdates = inFlight ? [inFlight, replacement] : [replacement];\n emitAudit({ type: \"compact\", byteLength: update.byteLength, pending: pendingUpdates.length });\n return update;\n };\n\n const flush = async (): Promise<void> => {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (!online || !options.sendUpdate || pendingUpdates.length === 0) return;\n if (flushing) return flushing;\n flushing = (async () => {\n while (online && pendingUpdates.length > 0) {\n const entry = pendingUpdates[0];\n inFlight = entry;\n try {\n await options.sendUpdate!(entry.update);\n pendingUpdates = pendingUpdates.filter((candidate) => candidate !== entry);\n emitAudit({ type: \"flush\", byteLength: entry.update.byteLength, pending: pendingUpdates.length });\n } finally {\n if (inFlight === entry) inFlight = null;\n }\n }\n })().finally(() => { flushing = null; });\n return flushing;\n };\n\n const queueLocalUpdate = (update: Uint8Array, origin: unknown): void => {\n if (destroyed || origin !== localOrigin) return;\n pendingUpdates.push({ id: ++nextPendingId, update: update.slice() });\n emitAudit({ type: \"local-update\", byteLength: update.byteLength, pending: pendingUpdates.length });\n const threshold = options.compactionThresholdBytes ?? 2 * 1024 * 1024;\n if (pendingUpdates.reduce((total, entry) => total + entry.update.byteLength, 0) > threshold) {\n try {\n compactPending();\n } catch (error) {\n if (!(error instanceof RangeError) || !error.message.startsWith(\"MDFN_COLLAB_UPDATE_TOO_LARGE:\")) throw error;\n }\n }\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n };\n doc.on(\"update\", queueLocalUpdate);\n if (options.sendUpdate) {\n const initial = Y.encodeStateAsUpdate(doc);\n pendingUpdates.push({ id: ++nextPendingId, update: initial });\n emitAudit({ type: \"local-update\", byteLength: initial.byteLength, pending: pendingUpdates.length });\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n }\n\n return {\n doc,\n awareness,\n encodeUpdate: (stateVector) => stateVector ? Y.encodeStateAsUpdate(doc, stateVector) : Y.encodeStateAsUpdate(doc),\n encodeStateVector: () => Y.encodeStateVector(doc),\n async applyUpdate(update, origin = \"transport\") {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n const apply = async (): Promise<void> => {\n try {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (update.byteLength > maxUpdateBytes) throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n if (options.authorizeUpdate && !(await options.authorizeUpdate(update, origin))) throw new Error(\"MDFN_COLLAB_UPDATE_FORBIDDEN\");\n for (;;) {\n const baseline = await validateUpdate(update, origin);\n if (baseline !== documentGeneration) continue;\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n Y.applyUpdate(doc, update, origin);\n emitAudit({ type: \"remote-update\", byteLength: update.byteLength });\n return;\n }\n } catch (error) {\n emitAudit({ type: \"remote-rejected\", byteLength: update.byteLength, error: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n };\n const queued = remoteApplication.then(apply, apply);\n remoteApplication = queued.catch(() => undefined);\n return queued;\n },\n setPresence(value) { if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\"); awareness.setLocalStateField(\"presence\", value); },\n getPresence: () => awareness.getStates(),\n isOnline: () => online,\n pendingUpdateCount: () => pendingUpdates.length,\n async setOnline(value) {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n online = value;\n emitAudit({ type: value ? \"online\" : \"offline\", pending: pendingUpdates.length });\n if (value) await flush();\n },\n flush,\n compact: compactPending,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n unsubscribe();\n doc.off(\"afterTransaction\", applySharedState);\n doc.off(\"afterTransaction\", trackDocumentGeneration);\n doc.off(\"update\", queueLocalUpdate);\n pendingUpdates = [];\n awareness.setLocalState(null);\n if (ownsAwareness) awareness.destroy();\n },\n };\n}\n\nexport { Awareness, Y };\nexport const MDFN_COLLAB_VERSION = \"0.1.1\" as const;\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -54,6 +54,6 @@ interface CollaborationSession {
54
54
  }
55
55
  declare function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession;
56
56
 
57
- declare const MDFN_COLLAB_VERSION: "0.1.0";
57
+ declare const MDFN_COLLAB_VERSION: "0.1.1";
58
58
 
59
59
  export { type CollaborationAuditEvent, type CollaborationSession, type CollaborationSessionOptions, type CollaborationUser, MDFN_COLLAB_VERSION, createCollaborationSession };
package/dist/index.d.ts CHANGED
@@ -54,6 +54,6 @@ interface CollaborationSession {
54
54
  }
55
55
  declare function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession;
56
56
 
57
- declare const MDFN_COLLAB_VERSION: "0.1.0";
57
+ declare const MDFN_COLLAB_VERSION: "0.1.1";
58
58
 
59
59
  export { type CollaborationAuditEvent, type CollaborationSession, type CollaborationSessionOptions, type CollaborationUser, MDFN_COLLAB_VERSION, createCollaborationSession };
package/dist/index.js CHANGED
@@ -274,7 +274,7 @@ function createCollaborationSession(options) {
274
274
  }
275
275
  };
276
276
  }
277
- var MDFN_COLLAB_VERSION = "0.1.0";
277
+ var MDFN_COLLAB_VERSION = "0.1.1";
278
278
  export {
279
279
  Awareness,
280
280
  MDFN_COLLAB_VERSION,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { Transaction, validateMdfnSidecar, type EditorController, type MdfnJsonValue, type MdfnSidecar } from \"@mdfn/core\";\n\nexport interface CollaborationUser {\n readonly id: string;\n readonly name?: string;\n readonly color?: string;\n readonly metadata?: Readonly<Record<string, MdfnJsonValue>>;\n}\n\nexport interface CollaborationSessionOptions {\n readonly controller: EditorController;\n readonly documentId: string;\n readonly user: CollaborationUser;\n readonly doc?: Y.Doc;\n readonly awareness?: Awareness;\n readonly authorizeUpdate?: (update: Uint8Array, origin: unknown) => boolean | Promise<boolean>;\n /** Protected editorial sidecar changes require an authoritative host decision. */\n readonly authorizeSidecarUpdate?: (previous: MdfnSidecar | undefined, next: MdfnSidecar | undefined, origin: unknown) => boolean | Promise<boolean>;\n readonly profileId?: string;\n readonly protocolVersion?: number;\n /** Maximum valid UTF-8 Markdown size; used to derive safe Yjs update limits. */\n readonly maxDocumentBytes?: number;\n readonly maxUpdateBytes?: number;\n readonly online?: boolean;\n readonly sendUpdate?: (update: Uint8Array) => void | Promise<void>;\n readonly compactionThresholdBytes?: number;\n readonly onAudit?: (event: CollaborationAuditEvent) => void;\n}\n\nexport interface CollaborationAuditEvent {\n readonly type: \"local-update\" | \"remote-update\" | \"remote-rejected\" | \"offline\" | \"online\" | \"flush\" | \"compact\";\n readonly documentId: string;\n readonly userId: string;\n readonly byteLength?: number;\n readonly pending?: number;\n readonly error?: string;\n}\n\nfunction parseSidecar(value: string | undefined, markdownLength?: number): MdfnSidecar | undefined {\n if (value === undefined) return undefined;\n try {\n return validateMdfnSidecar(JSON.parse(value), { markdownLength });\n } catch (error) {\n throw new Error(`MDFN_COLLAB_SIDECAR_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport interface CollaborationSession {\n readonly doc: Y.Doc;\n readonly awareness: Awareness;\n encodeUpdate(stateVector?: Uint8Array): Uint8Array;\n encodeStateVector(): Uint8Array;\n applyUpdate(update: Uint8Array, origin?: unknown): Promise<void>;\n setPresence(value: Readonly<Record<string, MdfnJsonValue>>): void;\n getPresence(): ReadonlyMap<number, unknown>;\n isOnline(): boolean;\n pendingUpdateCount(): number;\n setOnline(online: boolean): Promise<void>;\n flush(): Promise<void>;\n compact(): Uint8Array;\n destroy(): void;\n}\n\nfunction changedRange(previous: string, current: string): { from: number; to: number; inserted: string } {\n let from = 0;\n while (from < previous.length && from < current.length && previous[from] === current[from]) from += 1;\n let oldEnd = previous.length;\n let newEnd = current.length;\n while (oldEnd > from && newEnd > from && previous[oldEnd - 1] === current[newEnd - 1]) { oldEnd -= 1; newEnd -= 1; }\n return { from, to: oldEnd, inserted: current.slice(from, newEnd) };\n}\n\nexport function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession {\n const doc = options.doc ?? new Y.Doc({ guid: options.documentId });\n const ownsAwareness = !options.awareness;\n const awareness = options.awareness ?? new Awareness(doc);\n const source = doc.getText(\"markdown\");\n const sidecar = doc.getMap<string>(\"sidecar\");\n const metadata = doc.getMap<string>(\"metadata\");\n const sharedDocumentInitialized = options.doc !== undefined && (source.length > 0 || sidecar.size > 0 || metadata.size > 0);\n const localOrigin = Object.freeze({ session: options.user.id });\n let applyingRemote = false;\n let destroyed = false;\n let online = options.online ?? true;\n interface PendingUpdate { readonly id: number; readonly update: Uint8Array; }\n let nextPendingId = 0;\n let pendingUpdates: PendingUpdate[] = [];\n let inFlight: PendingUpdate | null = null;\n let flushing: Promise<void> | null = null;\n let remoteApplication: Promise<void> = Promise.resolve();\n let documentGeneration = 0;\n const maxDocumentBytes = options.maxDocumentBytes ?? 2 * 1024 * 1024;\n const maxUpdateBytes = options.maxUpdateBytes ?? maxDocumentBytes + Math.max(64 * 1024, Math.ceil(maxDocumentBytes / 16));\n\n const emitAudit = (event: Omit<CollaborationAuditEvent, \"documentId\" | \"userId\">): void => {\n options.onAudit?.({ ...event, documentId: options.documentId, userId: options.user.id });\n };\n\n const expectedSchema = options.controller.getState().schemaHash;\n const expectedDocumentId = options.documentId;\n const expectedProfile = options.profileId ?? \"default\";\n const expectedProtocol = String(options.protocolVersion ?? 1);\n const expectedExtensions = JSON.stringify(options.controller.extensions.extensions.map((extension) => ({ name: extension.name, version: extension.version })));\n const bindMetadata = (key: string, expected: string, code: string): void => {\n const current = metadata.get(key);\n if (current !== undefined && current !== expected) throw new Error(`${code}:${current}:${expected}`);\n if (current === undefined) metadata.set(key, expected);\n };\n bindMetadata(\"schemaHash\", expectedSchema, \"MDFN_COLLAB_SCHEMA_MISMATCH\");\n bindMetadata(\"documentId\", expectedDocumentId, \"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n bindMetadata(\"profileId\", expectedProfile, \"MDFN_COLLAB_PROFILE_MISMATCH\");\n bindMetadata(\"protocolVersion\", expectedProtocol, \"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n bindMetadata(\"extensions\", expectedExtensions, \"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n\n const assertContract = (candidate: Y.Doc): void => {\n const candidateMetadata = candidate.getMap<string>(\"metadata\");\n const schema = candidateMetadata.get(\"schemaHash\");\n if (schema !== expectedSchema) throw new Error(`MDFN_COLLAB_SCHEMA_MISMATCH:${schema ?? \"missing\"}:${expectedSchema}`);\n if (candidateMetadata.get(\"documentId\") !== expectedDocumentId) throw new Error(\"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n if (candidateMetadata.get(\"profileId\") !== expectedProfile) throw new Error(\"MDFN_COLLAB_PROFILE_MISMATCH\");\n if (candidateMetadata.get(\"protocolVersion\") !== expectedProtocol) throw new Error(\"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n if (candidateMetadata.get(\"extensions\") !== expectedExtensions) throw new Error(\"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n const markdown = candidate.getText(\"markdown\").toString();\n try {\n options.controller.validateMarkdown(markdown);\n } catch (error) {\n throw new Error(`MDFN_COLLAB_MARKDOWN_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), markdown.length);\n };\n\n const canonicalJson = (value: unknown): unknown => {\n if (Array.isArray(value)) return value.map(canonicalJson);\n if (value === null || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)\n .map(([key, entry]) => [key, canonicalJson(entry)]),\n );\n };\n const protectedSidecar = (value: MdfnSidecar | undefined): string => JSON.stringify(canonicalJson({\n comments: value?.comments ?? [],\n suggestions: value?.suggestions ?? [],\n reviewState: value?.reviewState ?? \"draft\",\n audit: value?.audit ?? [],\n }));\n\n const validateUpdate = async (update: Uint8Array, origin: unknown): Promise<number> => {\n const baseline = documentGeneration;\n const candidate = new Y.Doc({ guid: expectedDocumentId });\n try {\n Y.applyUpdate(candidate, Y.encodeStateAsUpdate(doc), \"validation:baseline\");\n Y.applyUpdate(candidate, update, \"validation:candidate\");\n assertContract(candidate);\n const previousSidecar = parseSidecar(sidecar.get(\"value\"), source.length);\n const candidateSidecar = parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), candidate.getText(\"markdown\").length);\n if (protectedSidecar(previousSidecar) !== protectedSidecar(candidateSidecar)) {\n if (!options.authorizeSidecarUpdate || !(await options.authorizeSidecarUpdate(previousSidecar, candidateSidecar, origin))) {\n throw new Error(\"MDFN_COLLAB_EDITORIAL_UPDATE_FORBIDDEN\");\n }\n }\n return baseline;\n } finally {\n candidate.destroy();\n }\n };\n\n if (!sharedDocumentInitialized) {\n if (source.length === 0) source.insert(0, options.controller.getState().markdown);\n if (!sidecar.has(\"value\") && options.controller.getState().sidecar) sidecar.set(\"value\", JSON.stringify(options.controller.getState().sidecar));\n } else {\n assertContract(doc);\n const state = options.controller.getState();\n const sharedMarkdown = source.toString();\n const sharedSidecar = parseSidecar(sidecar.get(\"value\"), sharedMarkdown.length);\n let initial = new Transaction().withSource(\"collab:initial\").withMetadata({ addToHistory: false });\n if (sharedMarkdown !== state.markdown) initial = initial.replaceSource(0, state.markdown.length, sharedMarkdown);\n if (JSON.stringify(sharedSidecar) !== JSON.stringify(state.sidecar)) initial = initial.setSidecar(sharedSidecar);\n if (initial.operations.length > 0) options.controller.dispatch(initial);\n }\n\n awareness.setLocalState({ user: options.user, documentId: options.documentId });\n\n const trackDocumentGeneration = (): void => { documentGeneration += 1; };\n doc.on(\"afterTransaction\", trackDocumentGeneration);\n\n const unsubscribe = options.controller.subscribe((change) => {\n if (applyingRemote || destroyed) return;\n doc.transact(() => {\n if (change.documentChanged) {\n const current = source.toString();\n const range = changedRange(current, change.current.markdown);\n if (range.to > range.from) source.delete(range.from, range.to - range.from);\n if (range.inserted) source.insert(range.from, range.inserted);\n }\n if (change.sidecarChanged) {\n if (change.current.sidecar) sidecar.set(\"value\", JSON.stringify(change.current.sidecar));\n else sidecar.delete(\"value\");\n }\n }, localOrigin);\n });\n\n const applySharedState = (transaction: Y.Transaction): void => {\n if (destroyed || transaction.origin === localOrigin) return;\n applyingRemote = true;\n try {\n const state = options.controller.getState();\n const nextSource = source.toString();\n let edit = new Transaction().withSource(\"collab:remote\").withMetadata({ addToHistory: false });\n if (nextSource !== state.markdown) edit = edit.replaceSource(0, state.markdown.length, nextSource);\n assertContract(doc);\n const nextSidecar = parseSidecar(sidecar.get(\"value\"), nextSource.length);\n if (JSON.stringify(nextSidecar) !== JSON.stringify(state.sidecar)) edit = edit.setSidecar(nextSidecar);\n if (edit.operations.length > 0) options.controller.dispatch(edit);\n } finally { applyingRemote = false; }\n };\n doc.on(\"afterTransaction\", applySharedState);\n\n const compactPending = (): Uint8Array => {\n const compactable = pendingUpdates.filter((entry) => entry !== inFlight);\n const update = Y.mergeUpdates(compactable.map((entry) => entry.update));\n if (update.byteLength > maxUpdateBytes) {\n throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n }\n if (compactable.length === 0) return update;\n const replacement = { id: ++nextPendingId, update };\n pendingUpdates = inFlight ? [inFlight, replacement] : [replacement];\n emitAudit({ type: \"compact\", byteLength: update.byteLength, pending: pendingUpdates.length });\n return update;\n };\n\n const flush = async (): Promise<void> => {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (!online || !options.sendUpdate || pendingUpdates.length === 0) return;\n if (flushing) return flushing;\n flushing = (async () => {\n while (online && pendingUpdates.length > 0) {\n const entry = pendingUpdates[0];\n inFlight = entry;\n try {\n await options.sendUpdate!(entry.update);\n pendingUpdates = pendingUpdates.filter((candidate) => candidate !== entry);\n emitAudit({ type: \"flush\", byteLength: entry.update.byteLength, pending: pendingUpdates.length });\n } finally {\n if (inFlight === entry) inFlight = null;\n }\n }\n })().finally(() => { flushing = null; });\n return flushing;\n };\n\n const queueLocalUpdate = (update: Uint8Array, origin: unknown): void => {\n if (destroyed || origin !== localOrigin) return;\n pendingUpdates.push({ id: ++nextPendingId, update: update.slice() });\n emitAudit({ type: \"local-update\", byteLength: update.byteLength, pending: pendingUpdates.length });\n const threshold = options.compactionThresholdBytes ?? 2 * 1024 * 1024;\n if (pendingUpdates.reduce((total, entry) => total + entry.update.byteLength, 0) > threshold) {\n try {\n compactPending();\n } catch (error) {\n if (!(error instanceof RangeError) || !error.message.startsWith(\"MDFN_COLLAB_UPDATE_TOO_LARGE:\")) throw error;\n }\n }\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n };\n doc.on(\"update\", queueLocalUpdate);\n if (options.sendUpdate) {\n const initial = Y.encodeStateAsUpdate(doc);\n pendingUpdates.push({ id: ++nextPendingId, update: initial });\n emitAudit({ type: \"local-update\", byteLength: initial.byteLength, pending: pendingUpdates.length });\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n }\n\n return {\n doc,\n awareness,\n encodeUpdate: (stateVector) => stateVector ? Y.encodeStateAsUpdate(doc, stateVector) : Y.encodeStateAsUpdate(doc),\n encodeStateVector: () => Y.encodeStateVector(doc),\n async applyUpdate(update, origin = \"transport\") {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n const apply = async (): Promise<void> => {\n try {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (update.byteLength > maxUpdateBytes) throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n if (options.authorizeUpdate && !(await options.authorizeUpdate(update, origin))) throw new Error(\"MDFN_COLLAB_UPDATE_FORBIDDEN\");\n for (;;) {\n const baseline = await validateUpdate(update, origin);\n if (baseline !== documentGeneration) continue;\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n Y.applyUpdate(doc, update, origin);\n emitAudit({ type: \"remote-update\", byteLength: update.byteLength });\n return;\n }\n } catch (error) {\n emitAudit({ type: \"remote-rejected\", byteLength: update.byteLength, error: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n };\n const queued = remoteApplication.then(apply, apply);\n remoteApplication = queued.catch(() => undefined);\n return queued;\n },\n setPresence(value) { if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\"); awareness.setLocalStateField(\"presence\", value); },\n getPresence: () => awareness.getStates(),\n isOnline: () => online,\n pendingUpdateCount: () => pendingUpdates.length,\n async setOnline(value) {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n online = value;\n emitAudit({ type: value ? \"online\" : \"offline\", pending: pendingUpdates.length });\n if (value) await flush();\n },\n flush,\n compact: compactPending,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n unsubscribe();\n doc.off(\"afterTransaction\", applySharedState);\n doc.off(\"afterTransaction\", trackDocumentGeneration);\n doc.off(\"update\", queueLocalUpdate);\n pendingUpdates = [];\n awareness.setLocalState(null);\n if (ownsAwareness) awareness.destroy();\n },\n };\n}\n\nexport { Awareness, Y };\nexport const MDFN_COLLAB_VERSION = \"0.1.0\" as const;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { Transaction, validateMdfnSidecar, type EditorController, type MdfnJsonValue, type MdfnSidecar } from \"@mdfn/core\";\n\nexport interface CollaborationUser {\n readonly id: string;\n readonly name?: string;\n readonly color?: string;\n readonly metadata?: Readonly<Record<string, MdfnJsonValue>>;\n}\n\nexport interface CollaborationSessionOptions {\n readonly controller: EditorController;\n readonly documentId: string;\n readonly user: CollaborationUser;\n readonly doc?: Y.Doc;\n readonly awareness?: Awareness;\n readonly authorizeUpdate?: (update: Uint8Array, origin: unknown) => boolean | Promise<boolean>;\n /** Protected editorial sidecar changes require an authoritative host decision. */\n readonly authorizeSidecarUpdate?: (previous: MdfnSidecar | undefined, next: MdfnSidecar | undefined, origin: unknown) => boolean | Promise<boolean>;\n readonly profileId?: string;\n readonly protocolVersion?: number;\n /** Maximum valid UTF-8 Markdown size; used to derive safe Yjs update limits. */\n readonly maxDocumentBytes?: number;\n readonly maxUpdateBytes?: number;\n readonly online?: boolean;\n readonly sendUpdate?: (update: Uint8Array) => void | Promise<void>;\n readonly compactionThresholdBytes?: number;\n readonly onAudit?: (event: CollaborationAuditEvent) => void;\n}\n\nexport interface CollaborationAuditEvent {\n readonly type: \"local-update\" | \"remote-update\" | \"remote-rejected\" | \"offline\" | \"online\" | \"flush\" | \"compact\";\n readonly documentId: string;\n readonly userId: string;\n readonly byteLength?: number;\n readonly pending?: number;\n readonly error?: string;\n}\n\nfunction parseSidecar(value: string | undefined, markdownLength?: number): MdfnSidecar | undefined {\n if (value === undefined) return undefined;\n try {\n return validateMdfnSidecar(JSON.parse(value), { markdownLength });\n } catch (error) {\n throw new Error(`MDFN_COLLAB_SIDECAR_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport interface CollaborationSession {\n readonly doc: Y.Doc;\n readonly awareness: Awareness;\n encodeUpdate(stateVector?: Uint8Array): Uint8Array;\n encodeStateVector(): Uint8Array;\n applyUpdate(update: Uint8Array, origin?: unknown): Promise<void>;\n setPresence(value: Readonly<Record<string, MdfnJsonValue>>): void;\n getPresence(): ReadonlyMap<number, unknown>;\n isOnline(): boolean;\n pendingUpdateCount(): number;\n setOnline(online: boolean): Promise<void>;\n flush(): Promise<void>;\n compact(): Uint8Array;\n destroy(): void;\n}\n\nfunction changedRange(previous: string, current: string): { from: number; to: number; inserted: string } {\n let from = 0;\n while (from < previous.length && from < current.length && previous[from] === current[from]) from += 1;\n let oldEnd = previous.length;\n let newEnd = current.length;\n while (oldEnd > from && newEnd > from && previous[oldEnd - 1] === current[newEnd - 1]) { oldEnd -= 1; newEnd -= 1; }\n return { from, to: oldEnd, inserted: current.slice(from, newEnd) };\n}\n\nexport function createCollaborationSession(options: CollaborationSessionOptions): CollaborationSession {\n const doc = options.doc ?? new Y.Doc({ guid: options.documentId });\n const ownsAwareness = !options.awareness;\n const awareness = options.awareness ?? new Awareness(doc);\n const source = doc.getText(\"markdown\");\n const sidecar = doc.getMap<string>(\"sidecar\");\n const metadata = doc.getMap<string>(\"metadata\");\n const sharedDocumentInitialized = options.doc !== undefined && (source.length > 0 || sidecar.size > 0 || metadata.size > 0);\n const localOrigin = Object.freeze({ session: options.user.id });\n let applyingRemote = false;\n let destroyed = false;\n let online = options.online ?? true;\n interface PendingUpdate { readonly id: number; readonly update: Uint8Array; }\n let nextPendingId = 0;\n let pendingUpdates: PendingUpdate[] = [];\n let inFlight: PendingUpdate | null = null;\n let flushing: Promise<void> | null = null;\n let remoteApplication: Promise<void> = Promise.resolve();\n let documentGeneration = 0;\n const maxDocumentBytes = options.maxDocumentBytes ?? 2 * 1024 * 1024;\n const maxUpdateBytes = options.maxUpdateBytes ?? maxDocumentBytes + Math.max(64 * 1024, Math.ceil(maxDocumentBytes / 16));\n\n const emitAudit = (event: Omit<CollaborationAuditEvent, \"documentId\" | \"userId\">): void => {\n options.onAudit?.({ ...event, documentId: options.documentId, userId: options.user.id });\n };\n\n const expectedSchema = options.controller.getState().schemaHash;\n const expectedDocumentId = options.documentId;\n const expectedProfile = options.profileId ?? \"default\";\n const expectedProtocol = String(options.protocolVersion ?? 1);\n const expectedExtensions = JSON.stringify(options.controller.extensions.extensions.map((extension) => ({ name: extension.name, version: extension.version })));\n const bindMetadata = (key: string, expected: string, code: string): void => {\n const current = metadata.get(key);\n if (current !== undefined && current !== expected) throw new Error(`${code}:${current}:${expected}`);\n if (current === undefined) metadata.set(key, expected);\n };\n bindMetadata(\"schemaHash\", expectedSchema, \"MDFN_COLLAB_SCHEMA_MISMATCH\");\n bindMetadata(\"documentId\", expectedDocumentId, \"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n bindMetadata(\"profileId\", expectedProfile, \"MDFN_COLLAB_PROFILE_MISMATCH\");\n bindMetadata(\"protocolVersion\", expectedProtocol, \"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n bindMetadata(\"extensions\", expectedExtensions, \"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n\n const assertContract = (candidate: Y.Doc): void => {\n const candidateMetadata = candidate.getMap<string>(\"metadata\");\n const schema = candidateMetadata.get(\"schemaHash\");\n if (schema !== expectedSchema) throw new Error(`MDFN_COLLAB_SCHEMA_MISMATCH:${schema ?? \"missing\"}:${expectedSchema}`);\n if (candidateMetadata.get(\"documentId\") !== expectedDocumentId) throw new Error(\"MDFN_COLLAB_DOCUMENT_MISMATCH\");\n if (candidateMetadata.get(\"profileId\") !== expectedProfile) throw new Error(\"MDFN_COLLAB_PROFILE_MISMATCH\");\n if (candidateMetadata.get(\"protocolVersion\") !== expectedProtocol) throw new Error(\"MDFN_COLLAB_PROTOCOL_MISMATCH\");\n if (candidateMetadata.get(\"extensions\") !== expectedExtensions) throw new Error(\"MDFN_COLLAB_EXTENSIONS_MISMATCH\");\n const markdown = candidate.getText(\"markdown\").toString();\n try {\n options.controller.validateMarkdown(markdown);\n } catch (error) {\n throw new Error(`MDFN_COLLAB_MARKDOWN_INVALID:${error instanceof Error ? error.message : String(error)}`);\n }\n parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), markdown.length);\n };\n\n const canonicalJson = (value: unknown): unknown => {\n if (Array.isArray(value)) return value.map(canonicalJson);\n if (value === null || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)\n .map(([key, entry]) => [key, canonicalJson(entry)]),\n );\n };\n const protectedSidecar = (value: MdfnSidecar | undefined): string => JSON.stringify(canonicalJson({\n comments: value?.comments ?? [],\n suggestions: value?.suggestions ?? [],\n reviewState: value?.reviewState ?? \"draft\",\n audit: value?.audit ?? [],\n }));\n\n const validateUpdate = async (update: Uint8Array, origin: unknown): Promise<number> => {\n const baseline = documentGeneration;\n const candidate = new Y.Doc({ guid: expectedDocumentId });\n try {\n Y.applyUpdate(candidate, Y.encodeStateAsUpdate(doc), \"validation:baseline\");\n Y.applyUpdate(candidate, update, \"validation:candidate\");\n assertContract(candidate);\n const previousSidecar = parseSidecar(sidecar.get(\"value\"), source.length);\n const candidateSidecar = parseSidecar(candidate.getMap<string>(\"sidecar\").get(\"value\"), candidate.getText(\"markdown\").length);\n if (protectedSidecar(previousSidecar) !== protectedSidecar(candidateSidecar)) {\n if (!options.authorizeSidecarUpdate || !(await options.authorizeSidecarUpdate(previousSidecar, candidateSidecar, origin))) {\n throw new Error(\"MDFN_COLLAB_EDITORIAL_UPDATE_FORBIDDEN\");\n }\n }\n return baseline;\n } finally {\n candidate.destroy();\n }\n };\n\n if (!sharedDocumentInitialized) {\n if (source.length === 0) source.insert(0, options.controller.getState().markdown);\n if (!sidecar.has(\"value\") && options.controller.getState().sidecar) sidecar.set(\"value\", JSON.stringify(options.controller.getState().sidecar));\n } else {\n assertContract(doc);\n const state = options.controller.getState();\n const sharedMarkdown = source.toString();\n const sharedSidecar = parseSidecar(sidecar.get(\"value\"), sharedMarkdown.length);\n let initial = new Transaction().withSource(\"collab:initial\").withMetadata({ addToHistory: false });\n if (sharedMarkdown !== state.markdown) initial = initial.replaceSource(0, state.markdown.length, sharedMarkdown);\n if (JSON.stringify(sharedSidecar) !== JSON.stringify(state.sidecar)) initial = initial.setSidecar(sharedSidecar);\n if (initial.operations.length > 0) options.controller.dispatch(initial);\n }\n\n awareness.setLocalState({ user: options.user, documentId: options.documentId });\n\n const trackDocumentGeneration = (): void => { documentGeneration += 1; };\n doc.on(\"afterTransaction\", trackDocumentGeneration);\n\n const unsubscribe = options.controller.subscribe((change) => {\n if (applyingRemote || destroyed) return;\n doc.transact(() => {\n if (change.documentChanged) {\n const current = source.toString();\n const range = changedRange(current, change.current.markdown);\n if (range.to > range.from) source.delete(range.from, range.to - range.from);\n if (range.inserted) source.insert(range.from, range.inserted);\n }\n if (change.sidecarChanged) {\n if (change.current.sidecar) sidecar.set(\"value\", JSON.stringify(change.current.sidecar));\n else sidecar.delete(\"value\");\n }\n }, localOrigin);\n });\n\n const applySharedState = (transaction: Y.Transaction): void => {\n if (destroyed || transaction.origin === localOrigin) return;\n applyingRemote = true;\n try {\n const state = options.controller.getState();\n const nextSource = source.toString();\n let edit = new Transaction().withSource(\"collab:remote\").withMetadata({ addToHistory: false });\n if (nextSource !== state.markdown) edit = edit.replaceSource(0, state.markdown.length, nextSource);\n assertContract(doc);\n const nextSidecar = parseSidecar(sidecar.get(\"value\"), nextSource.length);\n if (JSON.stringify(nextSidecar) !== JSON.stringify(state.sidecar)) edit = edit.setSidecar(nextSidecar);\n if (edit.operations.length > 0) options.controller.dispatch(edit);\n } finally { applyingRemote = false; }\n };\n doc.on(\"afterTransaction\", applySharedState);\n\n const compactPending = (): Uint8Array => {\n const compactable = pendingUpdates.filter((entry) => entry !== inFlight);\n const update = Y.mergeUpdates(compactable.map((entry) => entry.update));\n if (update.byteLength > maxUpdateBytes) {\n throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n }\n if (compactable.length === 0) return update;\n const replacement = { id: ++nextPendingId, update };\n pendingUpdates = inFlight ? [inFlight, replacement] : [replacement];\n emitAudit({ type: \"compact\", byteLength: update.byteLength, pending: pendingUpdates.length });\n return update;\n };\n\n const flush = async (): Promise<void> => {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (!online || !options.sendUpdate || pendingUpdates.length === 0) return;\n if (flushing) return flushing;\n flushing = (async () => {\n while (online && pendingUpdates.length > 0) {\n const entry = pendingUpdates[0];\n inFlight = entry;\n try {\n await options.sendUpdate!(entry.update);\n pendingUpdates = pendingUpdates.filter((candidate) => candidate !== entry);\n emitAudit({ type: \"flush\", byteLength: entry.update.byteLength, pending: pendingUpdates.length });\n } finally {\n if (inFlight === entry) inFlight = null;\n }\n }\n })().finally(() => { flushing = null; });\n return flushing;\n };\n\n const queueLocalUpdate = (update: Uint8Array, origin: unknown): void => {\n if (destroyed || origin !== localOrigin) return;\n pendingUpdates.push({ id: ++nextPendingId, update: update.slice() });\n emitAudit({ type: \"local-update\", byteLength: update.byteLength, pending: pendingUpdates.length });\n const threshold = options.compactionThresholdBytes ?? 2 * 1024 * 1024;\n if (pendingUpdates.reduce((total, entry) => total + entry.update.byteLength, 0) > threshold) {\n try {\n compactPending();\n } catch (error) {\n if (!(error instanceof RangeError) || !error.message.startsWith(\"MDFN_COLLAB_UPDATE_TOO_LARGE:\")) throw error;\n }\n }\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n };\n doc.on(\"update\", queueLocalUpdate);\n if (options.sendUpdate) {\n const initial = Y.encodeStateAsUpdate(doc);\n pendingUpdates.push({ id: ++nextPendingId, update: initial });\n emitAudit({ type: \"local-update\", byteLength: initial.byteLength, pending: pendingUpdates.length });\n if (online) void flush().catch((error: unknown) => {\n online = false;\n emitAudit({ type: \"offline\", pending: pendingUpdates.length, error: error instanceof Error ? error.message : String(error) });\n });\n }\n\n return {\n doc,\n awareness,\n encodeUpdate: (stateVector) => stateVector ? Y.encodeStateAsUpdate(doc, stateVector) : Y.encodeStateAsUpdate(doc),\n encodeStateVector: () => Y.encodeStateVector(doc),\n async applyUpdate(update, origin = \"transport\") {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n const apply = async (): Promise<void> => {\n try {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n if (update.byteLength > maxUpdateBytes) throw new RangeError(`MDFN_COLLAB_UPDATE_TOO_LARGE:${maxUpdateBytes}`);\n if (options.authorizeUpdate && !(await options.authorizeUpdate(update, origin))) throw new Error(\"MDFN_COLLAB_UPDATE_FORBIDDEN\");\n for (;;) {\n const baseline = await validateUpdate(update, origin);\n if (baseline !== documentGeneration) continue;\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n Y.applyUpdate(doc, update, origin);\n emitAudit({ type: \"remote-update\", byteLength: update.byteLength });\n return;\n }\n } catch (error) {\n emitAudit({ type: \"remote-rejected\", byteLength: update.byteLength, error: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n };\n const queued = remoteApplication.then(apply, apply);\n remoteApplication = queued.catch(() => undefined);\n return queued;\n },\n setPresence(value) { if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\"); awareness.setLocalStateField(\"presence\", value); },\n getPresence: () => awareness.getStates(),\n isOnline: () => online,\n pendingUpdateCount: () => pendingUpdates.length,\n async setOnline(value) {\n if (destroyed) throw new Error(\"MDFN_COLLAB_DESTROYED\");\n online = value;\n emitAudit({ type: value ? \"online\" : \"offline\", pending: pendingUpdates.length });\n if (value) await flush();\n },\n flush,\n compact: compactPending,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n unsubscribe();\n doc.off(\"afterTransaction\", applySharedState);\n doc.off(\"afterTransaction\", trackDocumentGeneration);\n doc.off(\"update\", queueLocalUpdate);\n pendingUpdates = [];\n awareness.setLocalState(null);\n if (ownsAwareness) awareness.destroy();\n },\n };\n}\n\nexport { Awareness, Y };\nexport const MDFN_COLLAB_VERSION = \"0.1.1\" as const;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,9 +1,57 @@
1
1
  {
2
- "name": "@mdfn/collab", "version": "0.1.0", "description": "Yjs collaboration binding for mdfn source, presence, and sidecar state", "type": "module",
3
- "main": "dist/index.cjs", "module": "dist/index.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } },
4
- "scripts": { "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean", "test": "vitest run", "typecheck": "tsc --noEmit -p tsconfig.json", "lint": "tsc --noEmit -p tsconfig.json", "prepack": "npm run build" },
5
- "dependencies": { "@mdfn/core": "0.1.0", "y-protocols": "^1.0.6", "yjs": "^13.6.32" },
6
- "devDependencies": { "@mdfn/markdown": "0.1.0", "tsup": "^8.0.0", "typescript": "^5.3.0", "vitest": "3.2.4" },
7
- "files": ["dist", "README.md"], "sideEffects": false, "license": "MIT", "engines": { "node": ">=20 <25" }, "repository": { "type": "git", "url": "git+https://github.com/21nCo/super-functions.git", "directory": "mdfn/collab" }, "publishConfig": { "access": "public" },
8
- "mdfn": { "status": "optional", "layer": "platform", "schemaVersion": 1 }
2
+ "name": "@mdfn/collab",
3
+ "version": "0.1.1",
4
+ "description": "Yjs collaboration binding for mdfn source, presence, and sidecar state",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
18
+ "test": "vitest run",
19
+ "typecheck": "tsc --noEmit -p tsconfig.json",
20
+ "lint": "tsc --noEmit -p tsconfig.json",
21
+ "prepack": "npm run build"
22
+ },
23
+ "dependencies": {
24
+ "@mdfn/core": "0.1.1",
25
+ "y-protocols": "^1.0.6",
26
+ "yjs": "^13.6.32"
27
+ },
28
+ "devDependencies": {
29
+ "@mdfn/markdown": "0.1.1",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.3.0",
32
+ "vitest": "3.2.4"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=20 <25"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/21nCo/superfunctions.git",
46
+ "directory": "mdfn/collab"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "mdfn": {
52
+ "status": "optional",
53
+ "layer": "platform",
54
+ "schemaVersion": 1
55
+ },
56
+ "gitHead": "699d9d09932199d660333c57fe7bc646492ea3bf"
9
57
  }