@open-mercato/documents 0.10.1-develop.7925.1.ec40f75262 → 0.10.1-develop.7926.1.673c22ad09

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.
Files changed (24) hide show
  1. package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js +12 -0
  2. package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js.map +2 -2
  3. package/dist/modules/documents/backend/documents/[id]/DocumentPageClient.js +15 -1
  4. package/dist/modules/documents/backend/documents/[id]/DocumentPageClient.js.map +2 -2
  5. package/dist/modules/documents/backend/documents/[id]/VersionHistoryPanel.js +20 -8
  6. package/dist/modules/documents/backend/documents/[id]/VersionHistoryPanel.js.map +2 -2
  7. package/dist/modules/documents/backend/documents/[id]/editorTypes.js.map +2 -2
  8. package/dist/modules/documents/backend/documents/[id]/saveCollaborativeContent.js +46 -0
  9. package/dist/modules/documents/backend/documents/[id]/saveCollaborativeContent.js.map +7 -0
  10. package/dist/modules/documents/data/validators.js +11 -0
  11. package/dist/modules/documents/data/validators.js.map +2 -2
  12. package/dist/modules/documents/lib/collabPersistence.js +13 -0
  13. package/dist/modules/documents/lib/collabPersistence.js.map +7 -0
  14. package/dist/server/documents-collab-server.js +74 -1
  15. package/dist/server/documents-collab-server.js.map +2 -2
  16. package/package.json +6 -6
  17. package/server/documents-collab-server.ts +79 -0
  18. package/src/modules/documents/backend/documents/[id]/DocumentEditorSurface.tsx +16 -0
  19. package/src/modules/documents/backend/documents/[id]/DocumentPageClient.tsx +14 -0
  20. package/src/modules/documents/backend/documents/[id]/VersionHistoryPanel.tsx +23 -8
  21. package/src/modules/documents/backend/documents/[id]/editorTypes.ts +1 -0
  22. package/src/modules/documents/backend/documents/[id]/saveCollaborativeContent.ts +43 -0
  23. package/src/modules/documents/data/validators.ts +12 -0
  24. package/src/modules/documents/lib/collabPersistence.ts +10 -0
@@ -6,6 +6,15 @@ import {
6
6
  } from "../lib/resourceLimits.js";
7
7
  import { containsCanonicalUuid } from "../lib/displayLabels.js";
8
8
  import { DOCUMENT_VERSION_LABEL_MAX_LENGTH } from "../lib/versionLabels.js";
9
+ const collabContentSaveRequestSchema = z.object({
10
+ type: z.literal("documents.content.save"),
11
+ requestId: z.string().uuid()
12
+ });
13
+ const collabContentSaveResponseSchema = z.object({
14
+ type: z.literal("documents.content.saved"),
15
+ requestId: z.string().uuid(),
16
+ saved: z.boolean()
17
+ });
9
18
  const uuid = () => z.string().uuid("documents.validation.common.invalidUuid");
10
19
  function blankStringToNull(value) {
11
20
  if (typeof value !== "string") return value;
@@ -191,6 +200,8 @@ const documentTemplateInstantiateSchema = documentTemplateRenderBaseSchema.exten
191
200
  previewDigest: z.string().regex(/^sha256:[0-9a-f]{64}$/)
192
201
  });
193
202
  export {
203
+ collabContentSaveRequestSchema,
204
+ collabContentSaveResponseSchema,
194
205
  documentCommentAnchorWriteSchema,
195
206
  documentCommentCreateSchema,
196
207
  documentContentPutSchema,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/documents/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport {\n DOCUMENTS_MAX_CONTENT_HTML_BYTES,\n DOCUMENTS_MAX_CONTENT_TEXT_BYTES,\n isUtf8WithinLimit,\n} from '../lib/resourceLimits'\nimport { containsCanonicalUuid } from '../lib/displayLabels'\nimport { DOCUMENT_VERSION_LABEL_MAX_LENGTH } from '../lib/versionLabels'\n\nconst uuid = () => z.string().uuid('documents.validation.common.invalidUuid')\n\nfunction blankStringToNull(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction requiredTrimmedString(max: number, message: string) {\n return z.string().trim().min(1, { message }).max(max)\n}\n\nexport const documentEntityLabelSchema = requiredTrimmedString(\n 512,\n 'documents.validation.links.labelRequired',\n).refine((label) => !containsCanonicalUuid(label), {\n message: 'documents.validation.links.readableLabelRequired',\n})\n\nexport const documentVersionLabelSchema = z.preprocess(\n blankStringToNull,\n z.string()\n .trim()\n .max(DOCUMENT_VERSION_LABEL_MAX_LENGTH)\n .refine((label) => !containsCanonicalUuid(label), {\n message: 'documents.validation.versions.readableLabelRequired',\n })\n .optional()\n .nullable(),\n)\n\nconst clearableUuidSchema = z.preprocess(\n blankStringToNull,\n uuid().optional().nullable(),\n)\n\nconst finiteNumberSchema = z.number().refine(Number.isFinite, {\n message: 'documents.validation.templates.finiteNumberRequired',\n})\n\nconst localeSchema = z.string().trim().min(2).max(64).superRefine((value, context) => {\n try {\n new Intl.Locale(value)\n } catch {\n context.addIssue({ code: 'custom', message: 'documents.validation.templates.invalidLocale' })\n }\n})\n\nconst relativePositionSchema = z.string().min(1).max(4096).regex(\n /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,\n 'documents.validation.comment.invalidAnchor',\n)\n\nconst legacyNumericAnchorSchema = z.object({\n from: z.number().int().min(0),\n to: z.number().int().min(0),\n}).strict().refine((anchor) => anchor.to >= anchor.from, {\n message: 'documents.validation.comment.invalidAnchor',\n})\n\nconst relativeAnchorSchema = z.object({\n version: z.literal(2),\n relativeFrom: relativePositionSchema,\n relativeTo: relativePositionSchema,\n}).strict()\n\nexport const documentCommentAnchorWriteSchema = z.union([\n legacyNumericAnchorSchema,\n relativeAnchorSchema,\n])\n\nexport type DocumentCommentAnchor = z.infer<typeof documentCommentAnchorWriteSchema>\n\nexport const documentSharePrincipalTypeSchema = z.enum(['user', 'role'])\nexport const documentSharePermissionSchema = z.enum(['viewer', 'commenter', 'editor'])\nexport const documentEntityTypeSchema = z.enum([\n 'customer-person',\n 'customer-company',\n 'deal',\n 'product',\n 'catalog-offer',\n 'quote',\n 'sales-order',\n 'document',\n])\nexport const documentEntityLinkSourceSchema = z.enum(['chip', 'template', 'related-panel'])\nexport const documentTitleSchema = requiredTrimmedString(512, 'documents.validation.title.required')\n\nexport const documentCreateSchema = z.object({\n title: documentTitleSchema,\n folderId: clearableUuidSchema,\n})\n\nexport const documentUpdateSchema = z.object({\n id: uuid(),\n title: documentTitleSchema.optional(),\n folderId: clearableUuidSchema,\n})\n\nexport const documentFolderCreateSchema = z.object({\n name: requiredTrimmedString(256, 'documents.validation.folder.nameRequired'),\n parentFolderId: clearableUuidSchema,\n})\n\nexport const documentFolderUpdateSchema = z.object({\n id: uuid(),\n name: requiredTrimmedString(256, 'documents.validation.folder.nameRequired').optional(),\n parentFolderId: clearableUuidSchema,\n})\n\nexport const documentShareCreateSchema = z.object({\n principalType: documentSharePrincipalTypeSchema,\n principalId: uuid(),\n permission: documentSharePermissionSchema,\n})\n\nexport const documentShareUpdateSchema = z.object({\n id: uuid(),\n permission: documentSharePermissionSchema,\n})\n\nexport const documentCommentCreateSchema = z.object({\n body: requiredTrimmedString(8000, 'documents.validation.comment.bodyRequired'),\n anchor: documentCommentAnchorWriteSchema.optional().nullable(),\n mentions: z.array(z.object({ userId: z.string().uuid() })).max(50).optional(),\n grantAccessTo: z.array(uuid()).max(50).optional(),\n parentCommentId: clearableUuidSchema,\n})\n\nconst documentHtmlSchema = z.string().refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_HTML_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n)\nconst documentTemplateHtmlSchema = z.string().max(500000).refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_HTML_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n)\n\nexport const documentContentPutSchema = z.object({\n contentHtml: documentHtmlSchema,\n contentText: z.string().refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_TEXT_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n ).optional().nullable(),\n})\n\nexport const documentTemplateContextSlotSchema = z.object({\n slot: z.string().min(1).max(64).regex(/^[a-z][a-zA-Z0-9]*$/),\n entityType: documentEntityTypeSchema,\n required: z.boolean().optional(),\n})\n\nexport const documentTemplateContextSlotsSchema = z.array(documentTemplateContextSlotSchema).max(20).superRefine(\n (slots, context) => {\n const seen = new Set<string>()\n for (const [index, slot] of slots.entries()) {\n if (seen.has(slot.slot)) {\n context.addIssue({\n code: 'custom',\n path: [index, 'slot'],\n message: 'documents.validation.templates.duplicateSlot',\n })\n }\n seen.add(slot.slot)\n }\n },\n)\n\nexport const documentTemplateCreateSchema = z.object({\n name: z.string().min(1).max(256),\n description: z.string().max(2000).nullish(),\n bodyHtml: documentTemplateHtmlSchema,\n contextSlots: documentTemplateContextSlotsSchema.nullish(),\n isActive: z.boolean().optional(),\n})\n\nexport const documentTemplateUpdateSchema = z.object({\n id: uuid(),\n name: z.string().min(1).max(256).optional(),\n description: z.string().max(2000).nullish(),\n bodyHtml: documentTemplateHtmlSchema.optional(),\n contextSlots: documentTemplateContextSlotsSchema.nullish(),\n isActive: z.boolean().optional(),\n})\n\nexport const documentEntityLinkCreateSchema = z.object({\n entityType: documentEntityTypeSchema,\n entityId: uuid(),\n label: documentEntityLabelSchema,\n href: z.string().trim().min(1).max(1024).startsWith('/backend/'),\n source: documentEntityLinkSourceSchema,\n})\n\nexport const documentTemplateFillSlotSchema = z.object({\n slot: z.string().min(1).max(64).regex(/^[a-z][a-zA-Z0-9]*$/),\n entityType: documentEntityTypeSchema,\n entityId: uuid(),\n label: documentEntityLabelSchema,\n href: z.string().trim().min(1).max(1024).startsWith('/backend/'),\n values: z.record(z.string().min(1).max(64), z.union([\n z.string().max(10000),\n finiteNumberSchema,\n z.null(),\n ])),\n})\n\nconst documentTemplateRenderBaseSchema = z.object({\n templateUpdatedAt: z.string().datetime({ offset: true }),\n title: documentTitleSchema,\n locale: localeSchema,\n effectiveDate: z.string().datetime({ offset: true }),\n slots: z.array(documentTemplateFillSlotSchema).max(20),\n})\n\nexport const documentTemplatePreviewSchema = documentTemplateRenderBaseSchema\n\nexport const documentTemplateInstantiateSchema = documentTemplateRenderBaseSchema.extend({\n templateId: uuid(),\n folderId: clearableUuidSchema,\n previewDigest: z.string().regex(/^sha256:[0-9a-f]{64}$/),\n})\n\nexport type DocumentCreateInput = z.infer<typeof documentCreateSchema>\nexport type DocumentUpdateInput = z.infer<typeof documentUpdateSchema>\nexport type DocumentFolderCreateInput = z.infer<typeof documentFolderCreateSchema>\nexport type DocumentFolderUpdateInput = z.infer<typeof documentFolderUpdateSchema>\nexport type DocumentShareCreateInput = z.infer<typeof documentShareCreateSchema>\nexport type DocumentShareUpdateInput = z.infer<typeof documentShareUpdateSchema>\nexport type DocumentCommentCreateInput = z.infer<typeof documentCommentCreateSchema>\nexport type DocumentContentPutInput = z.infer<typeof documentContentPutSchema>\nexport type DocumentTemplateContextSlotInput = z.infer<typeof documentTemplateContextSlotSchema>\nexport type DocumentTemplateCreateInput = z.infer<typeof documentTemplateCreateSchema>\nexport type DocumentTemplateUpdateInput = z.infer<typeof documentTemplateUpdateSchema>\nexport type DocumentEntityType = z.infer<typeof documentEntityTypeSchema>\nexport type DocumentEntityLinkSourceInput = z.infer<typeof documentEntityLinkSourceSchema>\nexport type DocumentEntityLinkCreateInput = z.infer<typeof documentEntityLinkCreateSchema>\nexport type DocumentTemplateFillSlotInput = z.infer<typeof documentTemplateFillSlotSchema>\nexport type DocumentTemplatePreviewInput = z.infer<typeof documentTemplatePreviewSchema>\nexport type DocumentTemplateInstantiateInput = z.infer<typeof documentTemplateInstantiateSchema>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nimport {\n DOCUMENTS_MAX_CONTENT_HTML_BYTES,\n DOCUMENTS_MAX_CONTENT_TEXT_BYTES,\n isUtf8WithinLimit,\n} from '../lib/resourceLimits'\nimport { containsCanonicalUuid } from '../lib/displayLabels'\nimport { DOCUMENT_VERSION_LABEL_MAX_LENGTH } from '../lib/versionLabels'\n\nexport const collabContentSaveRequestSchema = z.object({\n type: z.literal('documents.content.save'),\n requestId: z.string().uuid(),\n})\n\nexport const collabContentSaveResponseSchema = z.object({\n type: z.literal('documents.content.saved'),\n requestId: z.string().uuid(),\n saved: z.boolean(),\n})\n\nconst uuid = () => z.string().uuid('documents.validation.common.invalidUuid')\n\nfunction blankStringToNull(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction requiredTrimmedString(max: number, message: string) {\n return z.string().trim().min(1, { message }).max(max)\n}\n\nexport const documentEntityLabelSchema = requiredTrimmedString(\n 512,\n 'documents.validation.links.labelRequired',\n).refine((label) => !containsCanonicalUuid(label), {\n message: 'documents.validation.links.readableLabelRequired',\n})\n\nexport const documentVersionLabelSchema = z.preprocess(\n blankStringToNull,\n z.string()\n .trim()\n .max(DOCUMENT_VERSION_LABEL_MAX_LENGTH)\n .refine((label) => !containsCanonicalUuid(label), {\n message: 'documents.validation.versions.readableLabelRequired',\n })\n .optional()\n .nullable(),\n)\n\nconst clearableUuidSchema = z.preprocess(\n blankStringToNull,\n uuid().optional().nullable(),\n)\n\nconst finiteNumberSchema = z.number().refine(Number.isFinite, {\n message: 'documents.validation.templates.finiteNumberRequired',\n})\n\nconst localeSchema = z.string().trim().min(2).max(64).superRefine((value, context) => {\n try {\n new Intl.Locale(value)\n } catch {\n context.addIssue({ code: 'custom', message: 'documents.validation.templates.invalidLocale' })\n }\n})\n\nconst relativePositionSchema = z.string().min(1).max(4096).regex(\n /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,\n 'documents.validation.comment.invalidAnchor',\n)\n\nconst legacyNumericAnchorSchema = z.object({\n from: z.number().int().min(0),\n to: z.number().int().min(0),\n}).strict().refine((anchor) => anchor.to >= anchor.from, {\n message: 'documents.validation.comment.invalidAnchor',\n})\n\nconst relativeAnchorSchema = z.object({\n version: z.literal(2),\n relativeFrom: relativePositionSchema,\n relativeTo: relativePositionSchema,\n}).strict()\n\nexport const documentCommentAnchorWriteSchema = z.union([\n legacyNumericAnchorSchema,\n relativeAnchorSchema,\n])\n\nexport type DocumentCommentAnchor = z.infer<typeof documentCommentAnchorWriteSchema>\n\nexport const documentSharePrincipalTypeSchema = z.enum(['user', 'role'])\nexport const documentSharePermissionSchema = z.enum(['viewer', 'commenter', 'editor'])\nexport const documentEntityTypeSchema = z.enum([\n 'customer-person',\n 'customer-company',\n 'deal',\n 'product',\n 'catalog-offer',\n 'quote',\n 'sales-order',\n 'document',\n])\nexport const documentEntityLinkSourceSchema = z.enum(['chip', 'template', 'related-panel'])\nexport const documentTitleSchema = requiredTrimmedString(512, 'documents.validation.title.required')\n\nexport const documentCreateSchema = z.object({\n title: documentTitleSchema,\n folderId: clearableUuidSchema,\n})\n\nexport const documentUpdateSchema = z.object({\n id: uuid(),\n title: documentTitleSchema.optional(),\n folderId: clearableUuidSchema,\n})\n\nexport const documentFolderCreateSchema = z.object({\n name: requiredTrimmedString(256, 'documents.validation.folder.nameRequired'),\n parentFolderId: clearableUuidSchema,\n})\n\nexport const documentFolderUpdateSchema = z.object({\n id: uuid(),\n name: requiredTrimmedString(256, 'documents.validation.folder.nameRequired').optional(),\n parentFolderId: clearableUuidSchema,\n})\n\nexport const documentShareCreateSchema = z.object({\n principalType: documentSharePrincipalTypeSchema,\n principalId: uuid(),\n permission: documentSharePermissionSchema,\n})\n\nexport const documentShareUpdateSchema = z.object({\n id: uuid(),\n permission: documentSharePermissionSchema,\n})\n\nexport const documentCommentCreateSchema = z.object({\n body: requiredTrimmedString(8000, 'documents.validation.comment.bodyRequired'),\n anchor: documentCommentAnchorWriteSchema.optional().nullable(),\n mentions: z.array(z.object({ userId: z.string().uuid() })).max(50).optional(),\n grantAccessTo: z.array(uuid()).max(50).optional(),\n parentCommentId: clearableUuidSchema,\n})\n\nconst documentHtmlSchema = z.string().refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_HTML_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n)\nconst documentTemplateHtmlSchema = z.string().max(500000).refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_HTML_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n)\n\nexport const documentContentPutSchema = z.object({\n contentHtml: documentHtmlSchema,\n contentText: z.string().refine(\n (value) => isUtf8WithinLimit(value, DOCUMENTS_MAX_CONTENT_TEXT_BYTES),\n { message: 'documents.validation.content.tooLarge' },\n ).optional().nullable(),\n})\n\nexport const documentTemplateContextSlotSchema = z.object({\n slot: z.string().min(1).max(64).regex(/^[a-z][a-zA-Z0-9]*$/),\n entityType: documentEntityTypeSchema,\n required: z.boolean().optional(),\n})\n\nexport const documentTemplateContextSlotsSchema = z.array(documentTemplateContextSlotSchema).max(20).superRefine(\n (slots, context) => {\n const seen = new Set<string>()\n for (const [index, slot] of slots.entries()) {\n if (seen.has(slot.slot)) {\n context.addIssue({\n code: 'custom',\n path: [index, 'slot'],\n message: 'documents.validation.templates.duplicateSlot',\n })\n }\n seen.add(slot.slot)\n }\n },\n)\n\nexport const documentTemplateCreateSchema = z.object({\n name: z.string().min(1).max(256),\n description: z.string().max(2000).nullish(),\n bodyHtml: documentTemplateHtmlSchema,\n contextSlots: documentTemplateContextSlotsSchema.nullish(),\n isActive: z.boolean().optional(),\n})\n\nexport const documentTemplateUpdateSchema = z.object({\n id: uuid(),\n name: z.string().min(1).max(256).optional(),\n description: z.string().max(2000).nullish(),\n bodyHtml: documentTemplateHtmlSchema.optional(),\n contextSlots: documentTemplateContextSlotsSchema.nullish(),\n isActive: z.boolean().optional(),\n})\n\nexport const documentEntityLinkCreateSchema = z.object({\n entityType: documentEntityTypeSchema,\n entityId: uuid(),\n label: documentEntityLabelSchema,\n href: z.string().trim().min(1).max(1024).startsWith('/backend/'),\n source: documentEntityLinkSourceSchema,\n})\n\nexport const documentTemplateFillSlotSchema = z.object({\n slot: z.string().min(1).max(64).regex(/^[a-z][a-zA-Z0-9]*$/),\n entityType: documentEntityTypeSchema,\n entityId: uuid(),\n label: documentEntityLabelSchema,\n href: z.string().trim().min(1).max(1024).startsWith('/backend/'),\n values: z.record(z.string().min(1).max(64), z.union([\n z.string().max(10000),\n finiteNumberSchema,\n z.null(),\n ])),\n})\n\nconst documentTemplateRenderBaseSchema = z.object({\n templateUpdatedAt: z.string().datetime({ offset: true }),\n title: documentTitleSchema,\n locale: localeSchema,\n effectiveDate: z.string().datetime({ offset: true }),\n slots: z.array(documentTemplateFillSlotSchema).max(20),\n})\n\nexport const documentTemplatePreviewSchema = documentTemplateRenderBaseSchema\n\nexport const documentTemplateInstantiateSchema = documentTemplateRenderBaseSchema.extend({\n templateId: uuid(),\n folderId: clearableUuidSchema,\n previewDigest: z.string().regex(/^sha256:[0-9a-f]{64}$/),\n})\n\nexport type DocumentCreateInput = z.infer<typeof documentCreateSchema>\nexport type DocumentUpdateInput = z.infer<typeof documentUpdateSchema>\nexport type DocumentFolderCreateInput = z.infer<typeof documentFolderCreateSchema>\nexport type DocumentFolderUpdateInput = z.infer<typeof documentFolderUpdateSchema>\nexport type DocumentShareCreateInput = z.infer<typeof documentShareCreateSchema>\nexport type DocumentShareUpdateInput = z.infer<typeof documentShareUpdateSchema>\nexport type DocumentCommentCreateInput = z.infer<typeof documentCommentCreateSchema>\nexport type DocumentContentPutInput = z.infer<typeof documentContentPutSchema>\nexport type DocumentTemplateContextSlotInput = z.infer<typeof documentTemplateContextSlotSchema>\nexport type DocumentTemplateCreateInput = z.infer<typeof documentTemplateCreateSchema>\nexport type DocumentTemplateUpdateInput = z.infer<typeof documentTemplateUpdateSchema>\nexport type DocumentEntityType = z.infer<typeof documentEntityTypeSchema>\nexport type DocumentEntityLinkSourceInput = z.infer<typeof documentEntityLinkSourceSchema>\nexport type DocumentEntityLinkCreateInput = z.infer<typeof documentEntityLinkCreateSchema>\nexport type DocumentTemplateFillSlotInput = z.infer<typeof documentTemplateFillSlotSchema>\nexport type DocumentTemplatePreviewInput = z.infer<typeof documentTemplatePreviewSchema>\nexport type DocumentTemplateInstantiateInput = z.infer<typeof documentTemplateInstantiateSchema>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,13 @@
1
+ import * as Y from "yjs";
2
+ const COLLAB_CONTENT_SAVE_TIMEOUT_MS = 15e3;
3
+ function containsDocumentSnapshot(durable, requested) {
4
+ for (const [clientId, clock] of requested.sv) {
5
+ if ((durable.sv.get(clientId) ?? 0) < clock) return false;
6
+ }
7
+ return Y.equalDeleteSets(Y.mergeDeleteSets([durable.ds, requested.ds]), durable.ds);
8
+ }
9
+ export {
10
+ COLLAB_CONTENT_SAVE_TIMEOUT_MS,
11
+ containsDocumentSnapshot
12
+ };
13
+ //# sourceMappingURL=collabPersistence.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/documents/lib/collabPersistence.ts"],
4
+ "sourcesContent": ["import * as Y from 'yjs'\n\nexport const COLLAB_CONTENT_SAVE_TIMEOUT_MS = 15000\n\nexport function containsDocumentSnapshot(durable: Y.Snapshot, requested: Y.Snapshot): boolean {\n for (const [clientId, clock] of requested.sv) {\n if ((durable.sv.get(clientId) ?? 0) < clock) return false\n }\n return Y.equalDeleteSets(Y.mergeDeleteSets([durable.ds, requested.ds]), durable.ds)\n}\n"],
5
+ "mappings": "AAAA,YAAY,OAAO;AAEZ,MAAM,iCAAiC;AAEvC,SAAS,yBAAyB,SAAqB,WAAgC;AAC5F,aAAW,CAAC,UAAU,KAAK,KAAK,UAAU,IAAI;AAC5C,SAAK,QAAQ,GAAG,IAAI,QAAQ,KAAK,KAAK,MAAO,QAAO;AAAA,EACtD;AACA,SAAO,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,QAAQ,IAAI,UAAU,EAAE,CAAC,GAAG,QAAQ,EAAE;AACpF;",
6
+ "names": []
7
+ }
@@ -11,6 +11,8 @@ import {
11
11
  } from "@hocuspocus/server";
12
12
  import { Redis as HocuspocusRedis } from "@hocuspocus/extension-redis";
13
13
  import * as Y from "yjs";
14
+ import { collabContentSaveRequestSchema } from "@open-mercato/documents/modules/documents/data/validators";
15
+ import { containsDocumentSnapshot } from "@open-mercato/documents/modules/documents/lib/collabPersistence";
14
16
  import { hasAllFeatures } from "@open-mercato/shared/lib/auth/featureMatch";
15
17
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
16
18
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
@@ -1531,6 +1533,24 @@ function createCollabHooks(deps) {
1531
1533
  assertConnectionAuthorization,
1532
1534
  establishConnectionAuthorization,
1533
1535
  releaseConnectionAuthorization,
1536
+ async saveContent(data) {
1537
+ try {
1538
+ assertScopedContext(data.context);
1539
+ const scope = roomScopes.get(data.document);
1540
+ const isCurrent = () => data.context.documentId === data.documentName && scope?.tenantId === data.context.tenantId && scope.organizationId === data.context.organizationId && !data.context.readOnly && !isInvalidated(data.documentName, data.document) && !isFinalDrainPending(data.documentName);
1541
+ if (!isCurrent() || !await deps.authorizeContext(data.context) || !isCurrent()) return false;
1542
+ const requested = Y.snapshot(data.document);
1543
+ const confirmation = { snapshot: null };
1544
+ await data.store((snapshot) => {
1545
+ confirmation.snapshot = snapshot;
1546
+ });
1547
+ if (!isCurrent() || !await deps.authorizeContext(data.context) || !isCurrent()) return false;
1548
+ const durable = confirmation.snapshot;
1549
+ return Boolean(durable && containsDocumentSnapshot(durable, requested));
1550
+ } catch {
1551
+ return false;
1552
+ }
1553
+ },
1534
1554
  reauthorizeActiveConnection,
1535
1555
  recordRedisAggregate(document, byteLength) {
1536
1556
  const previous = roomResourceBudgets.get(document);
@@ -1779,6 +1799,7 @@ function createCollabHooks(deps) {
1779
1799
  logger.warn("materialization failed; preserving previous html/text", { room: data.documentName });
1780
1800
  }
1781
1801
  const yjsState = Buffer.from(Y.encodeStateAsUpdate(data.document));
1802
+ const storingSnapshot = Y.snapshot(data.document);
1782
1803
  const resourceBudgetRevision = roomResourceBudgets.get(data.document)?.revision ?? 0;
1783
1804
  assertDocumentContentResourceLimits({
1784
1805
  yjsState,
@@ -1873,6 +1894,7 @@ function createCollabHooks(deps) {
1873
1894
  });
1874
1895
  }
1875
1896
  deps.onPersisted?.(data.document, yjsState, collaborationGeneration);
1897
+ if (materialized) data.confirmPersistence?.(storingSnapshot);
1876
1898
  if (finalDrainAuthorized) deps.finalDrainRegistry?.complete(data.document);
1877
1899
  return;
1878
1900
  }
@@ -1968,6 +1990,8 @@ async function main() {
1968
1990
  const finalDrainRegistry = createCollabFinalDrainRegistry();
1969
1991
  let server = null;
1970
1992
  let redisStoreExtension = null;
1993
+ const savingConnections = /* @__PURE__ */ new WeakSet();
1994
+ const storeConfirmations = /* @__PURE__ */ new WeakMap();
1971
1995
  const closeRoomConnectionsForContentReset = (documentName) => {
1972
1996
  if (server) closeCollabRoomConnectionsForContentReset(server.hocuspocus, documentName);
1973
1997
  };
@@ -2144,9 +2168,58 @@ async function main() {
2144
2168
  return await hooks.onStoreDocument({
2145
2169
  documentName: data.documentName,
2146
2170
  context: data.lastContext,
2147
- document: data.document
2171
+ document: data.document,
2172
+ confirmPersistence: storeConfirmations.get(data)
2148
2173
  });
2149
2174
  },
2175
+ async onStateless(data) {
2176
+ let value;
2177
+ try {
2178
+ value = JSON.parse(data.payload);
2179
+ } catch {
2180
+ return;
2181
+ }
2182
+ const request = collabContentSaveRequestSchema.safeParse(value);
2183
+ if (!request.success) return;
2184
+ const respond = (saved) => data.connection.sendStateless(JSON.stringify({
2185
+ type: "documents.content.saved",
2186
+ requestId: request.data.requestId,
2187
+ saved
2188
+ }));
2189
+ if (savingConnections.has(data.connection) || !server) {
2190
+ respond(false);
2191
+ return;
2192
+ }
2193
+ savingConnections.add(data.connection);
2194
+ try {
2195
+ const runtime = server.hocuspocus;
2196
+ const context = data.connection.context;
2197
+ const saved = await hooks.saveContent({
2198
+ documentName: data.documentName,
2199
+ document: data.document,
2200
+ context,
2201
+ store: async (confirmPersistence) => {
2202
+ const payload = {
2203
+ instance: runtime,
2204
+ document: data.document,
2205
+ documentName: data.documentName,
2206
+ clientsCount: data.document.getConnectionsCount(),
2207
+ lastContext: context,
2208
+ lastTransactionOrigin: { source: "local", context }
2209
+ };
2210
+ storeConfirmations.set(payload, confirmPersistence);
2211
+ try {
2212
+ await runtime.storeDocumentHooks(data.document, payload, true);
2213
+ } finally {
2214
+ storeConfirmations.delete(payload);
2215
+ }
2216
+ }
2217
+ });
2218
+ respond(saved);
2219
+ } finally {
2220
+ savingConnections.delete(data.connection);
2221
+ }
2222
+ },
2150
2223
  async onRequest(data) {
2151
2224
  return handleCollabServerRequest(data);
2152
2225
  },