@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.
- package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js +12 -0
- package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js.map +2 -2
- package/dist/modules/documents/backend/documents/[id]/DocumentPageClient.js +15 -1
- package/dist/modules/documents/backend/documents/[id]/DocumentPageClient.js.map +2 -2
- package/dist/modules/documents/backend/documents/[id]/VersionHistoryPanel.js +20 -8
- package/dist/modules/documents/backend/documents/[id]/VersionHistoryPanel.js.map +2 -2
- package/dist/modules/documents/backend/documents/[id]/editorTypes.js.map +2 -2
- package/dist/modules/documents/backend/documents/[id]/saveCollaborativeContent.js +46 -0
- package/dist/modules/documents/backend/documents/[id]/saveCollaborativeContent.js.map +7 -0
- package/dist/modules/documents/data/validators.js +11 -0
- package/dist/modules/documents/data/validators.js.map +2 -2
- package/dist/modules/documents/lib/collabPersistence.js +13 -0
- package/dist/modules/documents/lib/collabPersistence.js.map +7 -0
- package/dist/server/documents-collab-server.js +74 -1
- package/dist/server/documents-collab-server.js.map +2 -2
- package/package.json +6 -6
- package/server/documents-collab-server.ts +79 -0
- package/src/modules/documents/backend/documents/[id]/DocumentEditorSurface.tsx +16 -0
- package/src/modules/documents/backend/documents/[id]/DocumentPageClient.tsx +14 -0
- package/src/modules/documents/backend/documents/[id]/VersionHistoryPanel.tsx +23 -8
- package/src/modules/documents/backend/documents/[id]/editorTypes.ts +1 -0
- package/src/modules/documents/backend/documents/[id]/saveCollaborativeContent.ts +43 -0
- package/src/modules/documents/data/validators.ts +12 -0
- 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": "AAAA,SAAS,SAAS;
|
|
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
|
},
|