@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 +8 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +55 -7
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
|
|
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
|
-
|
|
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.
|
|
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,
|
package/dist/index.cjs.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/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.
|
|
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.
|
|
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
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",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
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
|
}
|