@happyvertical/smrt-comments 0.57.1 → 0.57.3
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 +72 -0
- package/dist/chunks/participation-LA_cfrM2.js +276 -0
- package/dist/chunks/participation-LA_cfrM2.js.map +1 -0
- package/dist/collections/CommentReportCollection.d.ts +6 -0
- package/dist/collections/CommentReportCollection.d.ts.map +1 -0
- package/dist/collections/CommentRevisionCollection.d.ts +6 -0
- package/dist/collections/CommentRevisionCollection.d.ts.map +1 -0
- package/dist/collections/ParticipationCollection.d.ts +14 -0
- package/dist/collections/ParticipationCollection.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +364 -59
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1853 -224
- package/dist/models/Comment.d.ts +8 -1
- package/dist/models/Comment.d.ts.map +1 -1
- package/dist/models/CommentReport.d.ts +10 -0
- package/dist/models/CommentReport.d.ts.map +1 -0
- package/dist/models/CommentRevision.d.ts +9 -0
- package/dist/models/CommentRevision.d.ts.map +1 -0
- package/dist/models/ParticipationBan.d.ts +12 -0
- package/dist/models/ParticipationBan.d.ts.map +1 -0
- package/dist/models/ParticipationPolicy.d.ts +13 -0
- package/dist/models/ParticipationPolicy.d.ts.map +1 -0
- package/dist/models/ParticipationRateBucket.d.ts +8 -0
- package/dist/models/ParticipationRateBucket.d.ts.map +1 -0
- package/dist/participation-contract.d.ts +37 -0
- package/dist/participation-contract.d.ts.map +1 -0
- package/dist/participation.d.ts +4 -0
- package/dist/participation.d.ts.map +1 -0
- package/dist/participation.js +2 -0
- package/dist/recipes.d.ts +6 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/services/CommentService.d.ts +10 -1
- package/dist/services/CommentService.d.ts.map +1 -1
- package/dist/services/ParticipationService.d.ts +25 -0
- package/dist/services/ParticipationService.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +477 -12
- package/dist/svelte/ModerationQueue.svelte +60 -0
- package/dist/svelte/ModerationQueue.svelte.d.ts +17 -0
- package/dist/svelte/ModerationQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/RecordComments.svelte +21 -6
- package/dist/svelte/RecordComments.svelte.d.ts +2 -0
- package/dist/svelte/RecordComments.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -5
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["row"],"sources":["../src/__smrt-register__.ts","../src/models/Comment.ts","../src/collections/CommentCollection.ts","../src/recipes.ts","../src/services/CommentService.ts","../src/services/mentionNotifications.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-comments\\\",\\\"packageVersion\\\":\\\"0.57.1\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-comments:CommentCollection\\\":{\\\"name\\\":\\\"commentcollection\\\",\\\"className\\\":\\\"CommentCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-comments:CommentCollection\\\",\\\"collection\\\":\\\"comments\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/comments/src/collections/CommentCollection.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-comments\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"listForRecord\\\":{\\\"name\\\":\\\"listForRecord\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"recordType\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"recordId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<Comment[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"record_comments\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"Comment\\\",\\\"exportName\\\":\\\"CommentCollection\\\",\\\"collectionExportName\\\":\\\"CommentCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"record_comments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"record_comments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"meta_type\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"meta_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"role\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"sort_order\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"author_user_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"body\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"mentions\\\\\\\" TEXT,\\\\n \\\\\\\"idempotency_key\\\\\\\" TEXT,\\\\n \\\\\\\"retry_tenant_id\\\\\\\" TEXT,\\\\n \\\\\\\"request_digest\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"meta_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"meta_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"role\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"sort_order\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"author_user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"mentions\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"idempotency_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"retry_tenant_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"request_digest\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"record_comments_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"record_comments_tenant_request_key\\\",\\\"columns\\\":[\\\"retry_tenant_id\\\",\\\"idempotency_key\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"record_comments_record_created_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"meta_type\\\",\\\"meta_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"record_comments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"record_comments_meta_type_meta_id_idx\\\",\\\"columns\\\":[\\\"meta_type\\\",\\\"meta_id\\\"]},{\\\"name\\\":\\\"record_comments_author_user_id_idx\\\",\\\"columns\\\":[\\\"author_user_id\\\"]}],\\\"version\\\":\\\"5095dfc9\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-comments\\\"},\\\"@happyvertical/smrt-comments:Comment\\\":{\\\"name\\\":\\\"comment\\\",\\\"className\\\":\\\"Comment\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-comments:Comment\\\",\\\"collection\\\":\\\"comments\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/comments/src/models/Comment.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-comments\\\",\\\"fields\\\":{\\\"metaType\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"metaId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"role\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"sortOrder\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0},\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"authorUserId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":true,\\\"related\\\":\\\"@happyvertical/smrt-users:User\\\"},\\\"body\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"mentions\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"idempotencyKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true,\\\"readonly\\\":true},\\\"readonly\\\":true},\\\"retryTenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true,\\\"readonly\\\":true},\\\"readonly\\\":true},\\\"requestDigest\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true,\\\"readonly\\\":true},\\\"readonly\\\":true}},\\\"methods\\\":{\\\"hydrate\\\":{\\\"name\\\":\\\"hydrate\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<T | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"record_comments\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"record_comments_tenant_request_key\\\",\\\"columns\\\":[\\\"retryTenantId\\\",\\\"idempotencyKey\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"record_comments_record_created_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"metaType\\\",\\\"metaId\\\",\\\"createdAt\\\"]}],\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtPolymorphicAssociation\\\",\\\"exportName\\\":\\\"Comment\\\",\\\"collectionExportName\\\":\\\"CommentCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"metaType\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"metaId\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"role\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"authorUserId\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"crossPackageRef\\\"},{\\\"field\\\":\\\"body\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"record_comments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"record_comments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"meta_type\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"meta_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"role\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"sort_order\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"author_user_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"body\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"mentions\\\\\\\" TEXT,\\\\n \\\\\\\"idempotency_key\\\\\\\" TEXT,\\\\n \\\\\\\"retry_tenant_id\\\\\\\" TEXT,\\\\n \\\\\\\"request_digest\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"meta_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"meta_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"role\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"sort_order\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"author_user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"mentions\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"idempotency_key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"retry_tenant_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"request_digest\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"record_comments_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"record_comments_tenant_request_key\\\",\\\"columns\\\":[\\\"retry_tenant_id\\\",\\\"idempotency_key\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"record_comments_record_created_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"meta_type\\\",\\\"meta_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"record_comments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"record_comments_meta_type_meta_id_idx\\\",\\\"columns\\\":[\\\"meta_type\\\",\\\"meta_id\\\"]},{\\\"name\\\":\\\"record_comments_author_user_id_idx\\\",\\\"columns\\\":[\\\"author_user_id\\\"]}],\\\"version\\\":\\\"5095dfc9\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-comments\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"recipes\\\":[{\\\"id\\\":\\\"comments.records\\\",\\\"className\\\":\\\"RecordCommentsRecipe\\\",\\\"label\\\":\\\"Record comments\\\",\\\"summary\\\":\\\"Discuss work directly on the records it concerns.\\\",\\\"synonyms\\\":[\\\"comments\\\",\\\"mentions\\\",\\\"discussion\\\"],\\\"models\\\":[\\\"@happyvertical/smrt-comments:Comment\\\"],\\\"nav\\\":[],\\\"requires\\\":[],\\\"group\\\":{\\\"id\\\":\\\"comments\\\",\\\"label\\\":\\\"Comments\\\",\\\"summary\\\":\\\"Keep discussions alongside the records your team works on.\\\"},\\\"help\\\":{\\\"markdown\\\":\\\"## Overview\\\\n\\\\nRecord comments keep a discussion next to the work it concerns. Open a record\\\\nyou can access to read its discussion or add a comment. Comments are part of\\\\nthe record page, rather than a separate list of every discussion.\\\\n\\\\n## Tasks\\\\n\\\\n### Add a comment\\\\n\\\\nOpen the record, find Comments, write your message and choose Post comment.\\\\nYour app controls who can read the record and who can contribute to its discussion.\\\\n\\\\n### Mention someone\\\\n\\\\nIf your app provides mention selection, choose a teammate who can access the\\\\nrecord. When mention notifications are connected, that person receives a\\\\nnotification. Typing a name alone does not select a recipient.\\\\n\\\\n### Check a failed post\\\\n\\\\nIf posting reports an error, refresh the discussion before trying again. A\\\\ncomment may have been saved even when its mention notification could not be\\\\ndelivered. If it appears in the discussion, do not post it a second time.\\\\n\\\",\\\"fieldRefs\\\":[]},\\\"surfaces\\\":[{\\\"kind\\\":\\\"playground\\\",\\\"export\\\":\\\"@happyvertical/smrt-comments/svelte#RecordCommentsPlayground\\\",\\\"label\\\":\\\"Record discussion preview\\\"}],\\\"runtime\\\":\\\"both\\\",\\\"demoSeed\\\":{\\\"export\\\":\\\"@happyvertical/smrt-comments/svelte#recordCommentsDemo\\\"},\\\"demo\\\":{\\\"mode\\\":\\\"server\\\",\\\"reasons\\\":[\\\"Needs a server because its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624).\\\"]}}],\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-messages\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3624\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","import {\n crossPackageRef,\n field,\n SmrtPolymorphicAssociation,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { CommentOptions } from '../types.js';\n\nconst MAX_MENTIONS = 50;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'record_comments',\n api: false,\n mcp: false,\n cli: false,\n indexes: [\n {\n name: 'record_comments_tenant_request_key',\n columns: ['retryTenantId', 'idempotencyKey'],\n unique: true,\n },\n {\n name: 'record_comments_record_created_idx',\n columns: ['tenantId', 'metaType', 'metaId', 'createdAt'],\n },\n ],\n})\nexport class Comment extends SmrtPolymorphicAssociation { static __smrtPackage__ = \"@happyvertical/smrt-comments\";\n @tenantId()\n tenantId = '';\n\n @crossPackageRef('@happyvertical/smrt-users:User', { required: true })\n authorUserId = '';\n\n @field({ required: true })\n body = '';\n\n /** JSON array of referenced User UUIDs; use mentionUserIds for validation. */\n mentions = '[]';\n\n /** Service-owned binding retained after generic runOnce claim retention. */\n @field({ type: 'text', nullable: true, readonly: true })\n idempotencyKey: string | null = null;\n\n /** Canonical retry namespace; never rewrites stored tenant/user references. */\n @field({ type: 'text', nullable: true, readonly: true })\n retryTenantId: string | null = null;\n\n /** Digest of the original authenticated author, parent and normalized payload. */\n @field({ type: 'text', nullable: true, readonly: true })\n requestDigest: string | null = null;\n\n constructor(options: CommentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.authorUserId !== undefined)\n this.authorUserId = options.authorUserId;\n if (options.body !== undefined) this.body = options.body;\n if (options.idempotencyKey !== undefined)\n this.idempotencyKey = options.idempotencyKey;\n if (options.retryTenantId !== undefined)\n this.retryTenantId = options.retryTenantId;\n if (options.requestDigest !== undefined)\n this.requestDigest = options.requestDigest;\n const mentions = options.mentions as unknown;\n if (Array.isArray(mentions)) this.mentionUserIds = mentions;\n else if (typeof mentions === 'string') this.mentions = mentions;\n }\n\n get mentionUserIds(): string[] {\n try {\n const parsed: unknown = JSON.parse(this.mentions);\n return Array.isArray(parsed) &&\n parsed.every((value) => typeof value === 'string')\n ? parsed\n : [];\n } catch {\n return [];\n }\n }\n\n set mentionUserIds(value: readonly string[]) {\n const unique = [...new Set(value.map((id) => id.trim()).filter(Boolean))];\n if (unique.length > MAX_MENTIONS || unique.some((id) => !UUID.test(id))) {\n throw new TypeError(\n 'Comment mentions must contain at most 50 user UUIDs.',\n );\n }\n this.mentions = JSON.stringify(unique);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { Comment } from '../models/Comment.js';\n\nexport class CommentCollection extends SmrtCollection<Comment> {\n static readonly _itemClass = Comment;\n\n async listForRecord(\n recordType: string,\n recordId: string,\n ): Promise<Comment[]> {\n const tenantId = getCurrentTenant()?.tenantId;\n if (!tenantId) {\n throw new TenantIsolationError(\n 'Record comments require a tenant context.',\n );\n }\n return this.list({\n where: { tenantId, metaType: recordType, metaId: recordId },\n orderBy: ['createdAt ASC'],\n });\n }\n}\n","import { SmrtRecipe } from '@happyvertical/smrt-core';\nimport { Comment } from './models/Comment.js';\n\n/** Discussion attached to any authorized business record. */\nexport class RecordCommentsRecipe extends SmrtRecipe {\n static id = 'comments.records';\n static label = 'Record comments';\n static summary = 'Discuss work directly on the records it concerns.';\n static synonyms = ['comments', 'mentions', 'discussion'];\n static group = {\n id: 'comments',\n label: 'Comments',\n summary: 'Keep discussions alongside the records your team works on.',\n };\n static models = [Comment];\n // Record discussions need an authorized parent record, not a global list.\n static nav = [];\n static runtime = 'both' as const;\n static surfaces = [\n {\n kind: 'playground',\n export: '@happyvertical/smrt-comments/svelte#RecordCommentsPlayground',\n label: 'Record discussion preview',\n },\n ] as const;\n static demoSeed = {\n export: '@happyvertical/smrt-comments/svelte#recordCommentsDemo',\n } as const;\n static help = './comments.records.recipe.md';\n}\n","import {\n digestRunOnceContent,\n runOnce,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { withTenant } from '@happyvertical/smrt-tenancy';\nimport { CommentCollection } from '../collections/CommentCollection.js';\nimport { Comment } from '../models/Comment.js';\nimport type {\n CommentActorContext,\n CommentMentionNotificationAdapter,\n CommentRecordAccess,\n CreateCommentInput,\n} from '../types.js';\n\n// Native UUID columns canonicalize case; identity and digests must agree with them.\nconst canonicalUuid = (value: string) =>\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)\n ? value.toLowerCase()\n : value;\n\n/** A reused request differs, or its retained comment is no longer available. */\nexport class CommentIdempotencyError extends Error {\n constructor(\n readonly code: 'COMMENT_REQUEST_CONFLICT' | 'COMMENT_REQUEST_UNAVAILABLE',\n ) {\n super(\n code === 'COMMENT_REQUEST_CONFLICT'\n ? 'Comment request key already used with different content.'\n : 'Comment request no longer has an unchanged comment.',\n );\n this.name = 'CommentIdempotencyError';\n }\n}\n\nexport class CommentService {\n constructor(\n private readonly options: {\n db: SmrtClassOptions['db'];\n actor: CommentActorContext;\n authorizeRecord: (access: CommentRecordAccess) => Promise<boolean>;\n mentionNotifications?: CommentMentionNotificationAdapter;\n },\n ) {}\n\n private async authorize(\n recordType: string,\n recordId: string,\n action: 'read' | 'comment',\n ) {\n const { actor } = this.options;\n if (\n !actor.tenantId ||\n !actor.userId ||\n !recordType ||\n !recordId ||\n !(await this.options.authorizeRecord({\n ...actor,\n recordType,\n recordId,\n action,\n }))\n ) {\n throw new Error('Comment record access denied.');\n }\n }\n\n async listForRecord(\n recordType: string,\n recordId: string,\n ): Promise<Comment[]> {\n await this.authorize(recordType, recordId, 'read');\n const tenantId = this.options.actor.tenantId;\n const binding = canonicalUuid(tenantId);\n const rows = await withTenant({ tenantId }, async () =>\n (await CommentCollection.create({ db: this.options.db })).listForRecord(\n recordType,\n recordId,\n ),\n );\n const collection = await withTenant({ tenantId }, () =>\n CommentCollection.create({ db: this.options.db }),\n );\n // The canonical retry namespace is separate from legacy reference spelling.\n // This query is tenant/parent-bound before any scoped hydration.\n const bound = await collection.db.query(\n `SELECT CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id FROM \"${collection.tableName.replace(/\"/g, '\"\"')}\" WHERE retry_tenant_id = ? AND meta_type = ? AND meta_id = ? AND idempotency_key IS NOT NULL`,\n binding,\n recordType,\n recordId,\n );\n for (const stored of bound.rows) {\n const row = await this.loadBoundComment(collection.db, stored, binding);\n if (row.metaType !== recordType || row.metaId !== recordId) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n rows.push(row);\n }\n for (const row of rows) {\n if (row.idempotencyKey !== null && row.retryTenantId !== binding) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n }\n return [...new Map(rows.map((row) => [row.id, row])).values()].sort(\n (a, b) =>\n (a.created_at?.getTime() ?? 0) - (b.created_at?.getTime() ?? 0) ||\n (a.id ?? '').localeCompare(b.id ?? ''),\n );\n }\n\n private async loadBoundComment(\n db: CommentCollection['db'],\n stored: { [key: string]: unknown },\n binding: string,\n ): Promise<Comment> {\n const id = stored.id;\n const tenantId = stored.tenant_id;\n if (\n typeof id !== 'string' ||\n typeof tenantId !== 'string' ||\n canonicalUuid(tenantId) !== binding\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n // This is the original spelling of the same authorized tenant, not a bypass.\n const row = await withTenant({ tenantId }, async () =>\n (await CommentCollection.create({ db })).get(id),\n );\n if (\n !row ||\n canonicalUuid(row.tenantId) !== binding ||\n row.retryTenantId !== binding ||\n row.idempotencyKey === null\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n return row;\n }\n\n async create(commentOptions: CreateCommentInput): Promise<Comment> {\n const {\n tenantId: inputTenantId,\n authorUserId: inputAuthorUserId,\n metaType,\n metaId,\n body,\n mentions,\n idempotencyKey,\n } = commentOptions;\n if (!inputTenantId || !inputAuthorUserId) {\n throw new TypeError('Comments require a tenant and author.');\n }\n if (\n canonicalUuid(inputTenantId) !==\n canonicalUuid(this.options.actor.tenantId) ||\n canonicalUuid(inputAuthorUserId) !==\n canonicalUuid(this.options.actor.userId)\n ) {\n throw new Error('Comment actor context mismatch.');\n }\n // Preserve the trusted context's stored spelling on text-backed databases.\n const tenantId = this.options.actor.tenantId;\n const authorUserId = this.options.actor.userId;\n await this.authorize(metaType ?? '', metaId ?? '', 'comment');\n if (!body?.trim()) throw new TypeError('Comment body is required.');\n // Validate through the model's public setter before collection.create()\n // persists. Supply its normalized JSON in that single insert.\n const prepared = new Comment();\n if (mentions !== undefined) prepared.mentionUserIds = mentions;\n if (\n idempotencyKey !== undefined &&\n (typeof idempotencyKey !== 'string' ||\n !idempotencyKey.trim() ||\n idempotencyKey !== idempotencyKey.trim() ||\n idempotencyKey.length > 200)\n ) {\n throw new TypeError(\n 'Comment idempotencyKey must be a nonempty trimmed string of at most 200 characters.',\n );\n }\n const normalizedMentions = [...prepared.mentionUserIds].sort();\n const requestDigest = digestRunOnceContent({\n tenantId: canonicalUuid(tenantId),\n authorUserId: canonicalUuid(authorUserId),\n metaType,\n metaId,\n body,\n mentions: normalizedMentions,\n });\n const comment = await withTenant({ tenantId }, async () => {\n const collection = await CommentCollection.create({\n db: this.options.db,\n });\n const insert = async (target: CommentCollection) => {\n const id = crypto.randomUUID();\n return target.create({\n tenantId,\n authorUserId,\n metaType,\n metaId,\n body,\n id,\n slug: id,\n context: '',\n _insertOnly: true,\n mentions: prepared.mentions,\n idempotencyKey: idempotencyKey ?? null,\n requestDigest: idempotencyKey === undefined ? null : requestDigest,\n retryTenantId:\n idempotencyKey === undefined ? null : canonicalUuid(tenantId),\n });\n };\n if (idempotencyKey === undefined) return insert(collection);\n // The internal actor is an arbitration namespace, NOT the authenticated\n // author. All authors sharing a tenant/key must contend on the same claim.\n // Actual author and payload are bound by the independently checked digest.\n const receipt = await runOnce(\n {\n db: collection.db,\n tenantId: canonicalUuid(tenantId),\n actor: 'smrt-comments.create:v1',\n token: idempotencyKey,\n content: 'comment-request-binding:v1',\n },\n async (tx) => {\n const scoped = await CommentCollection.create({ db: tx });\n const matches = await tx.query(\n `SELECT CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id FROM \"${scoped.tableName.replace(/\"/g, '\"\"')}\" WHERE retry_tenant_id = ? AND idempotency_key = ?`,\n canonicalUuid(tenantId),\n idempotencyKey,\n );\n if (matches.rows.length > 1) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n const row = matches.rows[0]\n ? await this.loadBoundComment(\n tx,\n matches.rows[0],\n canonicalUuid(tenantId),\n )\n : await insert(scoped);\n return {\n id: row.id,\n digest: row.requestDigest,\n tenant_id: row.tenantId,\n };\n },\n );\n if (receipt.digest !== requestDigest)\n throw new CommentIdempotencyError('COMMENT_REQUEST_CONFLICT');\n const row = await this.loadBoundComment(\n collection.db,\n receipt,\n canonicalUuid(tenantId),\n );\n if (\n !row ||\n canonicalUuid(row.tenantId) !== canonicalUuid(tenantId) ||\n row.idempotencyKey !== idempotencyKey ||\n row.requestDigest !== requestDigest ||\n digestRunOnceContent({\n tenantId: canonicalUuid(row.tenantId),\n authorUserId: canonicalUuid(row.authorUserId),\n metaType: row.metaType,\n metaId: row.metaId,\n body: row.body,\n mentions: [...row.mentionUserIds].sort(),\n }) !== requestDigest\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n return row;\n });\n if (!this.options.mentionNotifications || !comment.id) return comment;\n for (const recipientUserId of comment.mentionUserIds) {\n if (\n canonicalUuid(recipientUserId) === canonicalUuid(comment.authorUserId)\n )\n continue;\n await this.options.mentionNotifications.notifyMention({\n commentId: comment.id,\n tenantId: comment.tenantId,\n authorUserId: comment.authorUserId,\n recipientUserId,\n recordType: comment.metaType,\n recordId: comment.metaId,\n body: comment.body,\n });\n }\n return comment;\n }\n}\n","import type {\n CommentMention,\n CommentMentionNotificationAdapter,\n} from '../types.js';\n\n/** Structural subset implemented by smrt-messages UserNotificationService. */\nexport interface CommentNotificationService {\n notify(input: {\n tenantId: string;\n recipientUserId: string;\n kind: string;\n title: string;\n body: string;\n sourceRef: string;\n }): Promise<unknown>;\n}\n\n/** Hosts must check recipient membership AND access to the referenced record. */\nexport function createCommentMentionNotifications(options: {\n notifications: CommentNotificationService;\n canNotify: (mention: CommentMention) => Promise<boolean>;\n}): CommentMentionNotificationAdapter {\n return {\n async notifyMention(mention) {\n if (!(await options.canNotify(mention))) return;\n await options.notifications.notify({\n tenantId: mention.tenantId,\n recipientUserId: mention.recipientUserId,\n kind: 'comments.mention',\n title: 'You were mentioned in a comment',\n body: mention.body,\n sourceRef: `comments.mention:${mention.commentId}`,\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["row"],"sources":["../src/models/Comment.ts","../src/collections/CommentCollection.ts","../src/models/CommentReport.ts","../src/collections/CommentReportCollection.ts","../src/models/CommentRevision.ts","../src/collections/CommentRevisionCollection.ts","../src/recipes.ts","../src/services/CommentService.ts","../src/services/mentionNotifications.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtPolymorphicAssociation,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { CommentOptions, CommentStatus } from '../types.js';\n\nconst MAX_MENTIONS = 50;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'record_comments',\n api: false,\n mcp: false,\n cli: false,\n indexes: [\n {\n name: 'record_comments_tenant_request_key',\n columns: ['retryTenantId', 'idempotencyKey'],\n unique: true,\n },\n {\n name: 'record_comments_record_created_idx',\n columns: ['tenantId', 'metaType', 'metaId', 'createdAt'],\n },\n {\n name: 'record_comments_parent_created_idx',\n columns: ['tenantId', 'parentCommentId', 'createdAt'],\n },\n {\n name: 'record_comments_moderation_idx',\n columns: ['tenantId', 'status', 'createdAt'],\n },\n ],\n})\nexport class Comment extends SmrtPolymorphicAssociation { static __smrtPackage__ = \"@happyvertical/smrt-comments\";\n @tenantId()\n tenantId = '';\n\n @crossPackageRef('@happyvertical/smrt-users:User', { required: true })\n authorUserId = '';\n\n @field({ required: true })\n body = '';\n\n /** JSON array of referenced User UUIDs; use mentionUserIds for validation. */\n mentions = '[]';\n\n /** Service-owned binding retained after generic runOnce claim retention. */\n @field({ type: 'text', nullable: true, readonly: true })\n idempotencyKey: string | null = null;\n\n /** Canonical retry namespace; never rewrites stored tenant/user references. */\n @field({ type: 'text', nullable: true, readonly: true })\n retryTenantId: string | null = null;\n\n /** Digest of the original authenticated author, parent and normalized payload. */\n @field({ type: 'text', nullable: true, readonly: true })\n requestDigest: string | null = null;\n\n /** A reply stays within this comment's already-authorized record thread. */\n // DuckDB cannot safely insert self-referential FKs; CommentService binds it.\n @foreignKey('Comment', { nullable: true, constraint: false })\n parentCommentId: string | null = null;\n\n /** Visible comments are public to the host's authorized record readers. */\n @field({ required: true })\n status: CommentStatus = 'visible';\n\n /** JSON retained from the host participation decision at post time. */\n @field({ type: 'text', nullable: true, readonly: true })\n participationSnapshot: string | null = null;\n\n @field({ type: 'datetime', nullable: true, readonly: true })\n editedAt: Date | null = null;\n\n constructor(options: CommentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.authorUserId !== undefined)\n this.authorUserId = options.authorUserId;\n if (options.body !== undefined) this.body = options.body;\n if (options.idempotencyKey !== undefined)\n this.idempotencyKey = options.idempotencyKey;\n if (options.retryTenantId !== undefined)\n this.retryTenantId = options.retryTenantId;\n if (options.requestDigest !== undefined)\n this.requestDigest = options.requestDigest;\n if (options.parentCommentId !== undefined)\n this.parentCommentId = options.parentCommentId;\n if (options.status !== undefined) this.status = options.status;\n if (options.participationSnapshot !== undefined)\n this.participationSnapshot = options.participationSnapshot;\n const mentions = options.mentions as unknown;\n if (Array.isArray(mentions)) this.mentionUserIds = mentions;\n else if (typeof mentions === 'string') this.mentions = mentions;\n }\n\n get mentionUserIds(): string[] {\n try {\n const parsed: unknown = JSON.parse(this.mentions);\n return Array.isArray(parsed) &&\n parsed.every((value) => typeof value === 'string')\n ? parsed\n : [];\n } catch {\n return [];\n }\n }\n\n set mentionUserIds(value: readonly string[]) {\n const unique = [...new Set(value.map((id) => id.trim()).filter(Boolean))];\n if (unique.length > MAX_MENTIONS || unique.some((id) => !UUID.test(id))) {\n throw new TypeError(\n 'Comment mentions must contain at most 50 user UUIDs.',\n );\n }\n this.mentions = JSON.stringify(unique);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { Comment } from '../models/Comment.js';\n\nexport class CommentCollection extends SmrtCollection<Comment> {\n static readonly _itemClass = Comment;\n\n async listForRecord(\n recordType: string,\n recordId: string,\n ): Promise<Comment[]> {\n const tenantId = getCurrentTenant()?.tenantId;\n if (!tenantId) {\n throw new TenantIsolationError(\n 'Record comments require a tenant context.',\n );\n }\n return this.list({\n where: { tenantId, metaType: recordType, metaId: recordId },\n orderBy: ['createdAt ASC'],\n });\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/** A tenant-local report; moderation resolution never deletes the report. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'comment_reports',\n api: false,\n mcp: false,\n cli: false,\n indexes: [\n {\n name: 'comment_reports_actor_target_unique',\n columns: ['tenantId', 'reporterUserId', 'commentId'],\n unique: true,\n },\n {\n name: 'comment_reports_queue_idx',\n columns: ['tenantId', 'status', 'createdAt'],\n },\n ],\n})\nexport class CommentReport extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-comments\";\n @tenantId()\n tenantId = '';\n\n // Reports outlive comment-state changes and must work on DuckDB.\n @foreignKey('Comment', { required: true, constraint: false })\n commentId = '';\n\n @crossPackageRef('@happyvertical/smrt-users:User', { required: true })\n reporterUserId = '';\n\n @field({ required: true })\n reason = '';\n\n @field({ required: true })\n status: 'open' | 'resolved' | 'dismissed' = 'open';\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CommentReport } from '../models/CommentReport.js';\n\nexport class CommentReportCollection extends SmrtCollection<CommentReport> {\n static readonly _itemClass = CommentReport;\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/** Immutable prior body retained whenever an author edits a comment. */\n@TenantScoped({ mode: 'required' })\n@smrt({ tableName: 'comment_revisions', api: false, mcp: false, cli: false })\nexport class CommentRevision extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-comments\";\n @tenantId()\n tenantId = '';\n\n // Retained history is service-owned; keep cross-dialect schema portable.\n @foreignKey('Comment', { required: true, constraint: false })\n commentId = '';\n\n @crossPackageRef('@happyvertical/smrt-users:User', { required: true })\n editorUserId = '';\n\n @field({ required: true })\n body = '';\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CommentRevision } from '../models/CommentRevision.js';\n\nexport class CommentRevisionCollection extends SmrtCollection<CommentRevision> {\n static readonly _itemClass = CommentRevision;\n}\n","import { SmrtRecipe } from '@happyvertical/smrt-core';\nimport { Comment } from './models/Comment.js';\nimport { CommentReport } from './models/CommentReport.js';\nimport { CommentRevision } from './models/CommentRevision.js';\nimport { ParticipationBan } from './models/ParticipationBan.js';\nimport { ParticipationPolicy } from './models/ParticipationPolicy.js';\nimport { ParticipationRateBucket } from './models/ParticipationRateBucket.js';\n\n/** Discussion attached to any authorized business record. */\nexport class RecordCommentsRecipe extends SmrtRecipe {\n static id = 'comments.records';\n static label = 'Record comments';\n static summary = 'Discuss work directly on the records it concerns.';\n static synonyms = ['comments', 'mentions', 'discussion'];\n static group = {\n id: 'comments',\n label: 'Comments',\n summary: 'Keep discussions alongside the records your team works on.',\n };\n static models = [\n Comment,\n CommentRevision,\n CommentReport,\n ParticipationPolicy,\n ParticipationBan,\n ParticipationRateBucket,\n ];\n // Record discussions need an authorized parent record, not a global list.\n static nav = [];\n static runtime = 'both' as const;\n static surfaces = [\n {\n kind: 'playground',\n export: '@happyvertical/smrt-comments/svelte#RecordCommentsPlayground',\n label: 'Record discussion preview',\n },\n ] as const;\n static demoSeed = {\n export: '@happyvertical/smrt-comments/svelte#recordCommentsDemo',\n } as const;\n static help = './comments.records.recipe.md';\n}\n","import {\n digestRunOnceContent,\n runOnce,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { withTenant } from '@happyvertical/smrt-tenancy';\nimport { CommentCollection } from '../collections/CommentCollection.js';\nimport { CommentReportCollection } from '../collections/CommentReportCollection.js';\nimport { CommentRevisionCollection } from '../collections/CommentRevisionCollection.js';\nimport { Comment } from '../models/Comment.js';\nimport type {\n ParticipationAuthorizer,\n ParticipationDecision,\n} from '../participation-contract.js';\nimport type {\n CommentActorContext,\n CommentMentionNotificationAdapter,\n CommentRecordAccess,\n CommentStatus,\n CreateCommentInput,\n} from '../types.js';\nimport { ParticipationService } from './ParticipationService.js';\n\n// Native UUID columns canonicalize case; identity and digests must agree with them.\nconst canonicalUuid = (value: string) =>\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)\n ? value.toLowerCase()\n : value;\n\n/** A reused request differs, or its retained comment is no longer available. */\nexport class CommentIdempotencyError extends Error {\n constructor(\n readonly code: 'COMMENT_REQUEST_CONFLICT' | 'COMMENT_REQUEST_UNAVAILABLE',\n ) {\n super(\n code === 'COMMENT_REQUEST_CONFLICT'\n ? 'Comment request key already used with different content.'\n : 'Comment request no longer has a valid retained creation binding.',\n );\n this.name = 'CommentIdempotencyError';\n }\n}\n\nexport class CommentService {\n constructor(\n private readonly options: {\n db: SmrtClassOptions['db'];\n actor: CommentActorContext;\n authorizeRecord: (access: CommentRecordAccess) => Promise<boolean>;\n participation?: ParticipationAuthorizer;\n mentionNotifications?: CommentMentionNotificationAdapter;\n },\n ) {}\n\n private async authorize(\n recordType: string,\n recordId: string,\n action: 'read' | 'comment' | 'moderate',\n ) {\n const { actor } = this.options;\n if (\n !actor.tenantId ||\n !actor.userId ||\n !recordType ||\n !recordId ||\n !(await this.options.authorizeRecord({\n ...actor,\n recordType,\n recordId,\n action,\n }))\n ) {\n throw new Error('Comment record access denied.');\n }\n }\n\n private async getAuthorizedComment(\n commentId: string,\n action: 'read' | 'moderate',\n ) {\n const tenantId = this.options.actor.tenantId;\n const comment = await withTenant({ tenantId }, async () =>\n (await CommentCollection.create({ db: this.options.db })).get(commentId),\n );\n if (!comment || canonicalUuid(comment.tenantId) !== canonicalUuid(tenantId))\n throw new Error('Comment not found.');\n await this.authorize(comment.metaType, comment.metaId, action);\n return comment;\n }\n\n /** Edits retain the exact former body before changing the visible comment. */\n async edit(commentId: string, body: string): Promise<Comment> {\n if (!body.trim()) throw new TypeError('Comment body is required.');\n const comment = await this.getAuthorizedComment(commentId, 'read');\n if (\n canonicalUuid(comment.authorUserId) !==\n canonicalUuid(this.options.actor.userId)\n )\n throw new Error('Comment edit denied.');\n await this.authorize(comment.metaType, comment.metaId, 'comment');\n const tenantId = this.options.actor.tenantId;\n return withTenant({ tenantId }, async () => {\n const collection = await CommentCollection.create({\n db: this.options.db,\n });\n if (!collection.db.transaction)\n throw new Error('Comment edits require transactions.');\n return collection.db.transaction(async (tx) => {\n const scoped = await CommentCollection.create({ db: tx });\n // Acquire the row's write lock before reading the body retained as history.\n const table = `\"${scoped.tableName.replace(/\"/g, '\"\"')}\"`;\n await tx.query(\n `UPDATE ${table} SET body = body WHERE id = ? AND tenant_id = ?`,\n commentId,\n tenantId,\n );\n const current = await scoped.get(commentId);\n if (\n !current ||\n canonicalUuid(current.authorUserId) !==\n canonicalUuid(this.options.actor.userId)\n )\n throw new Error('Comment edit denied.');\n const participation = new ParticipationService({\n db: tx,\n entitlement: this.options.participation,\n });\n return participation.executeInTransaction(\n tx,\n {\n tenantId,\n actorUserId: this.options.actor.userId,\n action: 'comment',\n recordType: current.metaType,\n recordId: current.metaId,\n },\n async (guardedTx, decision) =>\n withTenant({ tenantId: current.tenantId }, async () => {\n const revisions = await CommentRevisionCollection.create({\n db: guardedTx,\n });\n await revisions.create({\n tenantId: current.tenantId,\n commentId,\n editorUserId: this.options.actor.userId,\n body: current.body,\n });\n if (\n current.status === 'visible' &&\n decision.initialCommentStatus === 'pending'\n ) {\n current.status = 'pending';\n }\n current.body = body;\n current.editedAt = new Date();\n await current.save();\n return current;\n }),\n );\n });\n });\n }\n\n /** A moderator may make a row visible, hidden, pending, or deleted. */\n async moderate(commentId: string, status: CommentStatus): Promise<Comment> {\n const comment = await this.getAuthorizedComment(commentId, 'moderate');\n if (!['visible', 'hidden', 'pending', 'deleted'].includes(status))\n throw new TypeError('Invalid comment moderation status.');\n return withTenant({ tenantId: comment.tenantId }, async () => {\n const collection = await CommentCollection.create({\n db: this.options.db,\n });\n if (!collection.db.transaction)\n throw new Error('Comment moderation requires transactions.');\n return collection.db.transaction(async (tx) => {\n const scoped = await CommentCollection.create({ db: tx });\n const table = `\"${scoped.tableName.replace(/\"/g, '\"\"')}\"`;\n // Reload after locking so the save lifecycle cannot clobber a concurrent edit.\n await tx.query(\n `UPDATE ${table} SET body = body WHERE id = ? AND tenant_id = ?`,\n commentId,\n comment.tenantId,\n );\n const current = await scoped.get(commentId);\n if (!current) throw new Error('Comment not found.');\n current.status = status;\n await current.save();\n return current;\n });\n });\n }\n\n /** One retained report per actor/comment; new reports consume shared report quota. */\n async report(commentId: string, reason: string) {\n if (!reason.trim())\n throw new TypeError('Comment report reason is required.');\n const comment = await this.getAuthorizedComment(commentId, 'read');\n if (comment.status !== 'visible')\n throw new Error('Comment is not reportable.');\n commentId = canonicalUuid(comment.id ?? commentId);\n const tenantId = canonicalUuid(this.options.actor.tenantId);\n const reporterUserId = canonicalUuid(this.options.actor.userId);\n const request = {\n tenantId,\n actorUserId: reporterUserId,\n action: 'report' as const,\n recordType: comment.metaType,\n recordId: comment.metaId,\n };\n const guard = new ParticipationService({\n db: this.options.db,\n entitlement: this.options.participation,\n });\n await guard.authorize(request);\n return withTenant({ tenantId }, async () => {\n const reports = await CommentReportCollection.create({\n db: this.options.db,\n });\n const id = await runOnce(\n {\n db: reports.db,\n tenantId,\n actor: 'smrt-comments.report:v1',\n token: `${reporterUserId}:${canonicalUuid(commentId)}`,\n content: 'retained-report:v1',\n },\n async (tx) => {\n const scoped = await CommentReportCollection.create({ db: tx });\n const existing = await scoped.list({\n where: { tenantId, reporterUserId, commentId },\n });\n if (existing[0]) return existing[0].id;\n return guard.executeInTransaction(tx, request, async (guardedTx) => {\n const current = await withTenant(\n { tenantId: comment.tenantId },\n async () =>\n (await CommentCollection.create({ db: guardedTx })).get(\n commentId,\n ),\n );\n if (!current || current.status !== 'visible')\n throw new Error('Comment is not reportable.');\n return (\n await (\n await CommentReportCollection.create({ db: guardedTx })\n ).create({\n tenantId,\n commentId,\n reporterUserId,\n reason: reason.trim(),\n status: 'open',\n })\n ).id;\n });\n },\n );\n const report = id ? await reports.get(id) : null;\n if (\n !report ||\n report.commentId !== canonicalUuid(commentId) ||\n report.reporterUserId !== reporterUserId\n )\n throw new Error('Retained report unavailable.');\n return report;\n });\n }\n\n async listForRecord(\n recordType: string,\n recordId: string,\n ): Promise<Comment[]> {\n await this.authorize(recordType, recordId, 'read');\n const tenantId = this.options.actor.tenantId;\n const binding = canonicalUuid(tenantId);\n const rows = await withTenant({ tenantId }, async () =>\n (await CommentCollection.create({ db: this.options.db })).listForRecord(\n recordType,\n recordId,\n ),\n );\n const collection = await withTenant({ tenantId }, () =>\n CommentCollection.create({ db: this.options.db }),\n );\n // The canonical retry namespace is separate from legacy reference spelling.\n // This query is tenant/parent-bound before any scoped hydration.\n const bound = await collection.db.query(\n `SELECT CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id FROM \"${collection.tableName.replace(/\"/g, '\"\"')}\" WHERE retry_tenant_id = ? AND meta_type = ? AND meta_id = ? AND idempotency_key IS NOT NULL`,\n binding,\n recordType,\n recordId,\n );\n for (const stored of bound.rows) {\n const row = await this.loadBoundComment(collection.db, stored, binding);\n if (row.metaType !== recordType || row.metaId !== recordId) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n rows.push(row);\n }\n for (const row of rows) {\n if (row.idempotencyKey !== null && row.retryTenantId !== binding) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n }\n return [...new Map(rows.map((row) => [row.id, row])).values()].sort(\n (a, b) =>\n (a.created_at?.getTime() ?? 0) - (b.created_at?.getTime() ?? 0) ||\n (a.id ?? '').localeCompare(b.id ?? ''),\n );\n }\n\n private async loadBoundComment(\n db: CommentCollection['db'],\n stored: { [key: string]: unknown },\n binding: string,\n ): Promise<Comment> {\n const id = stored.id;\n const tenantId = stored.tenant_id;\n if (\n typeof id !== 'string' ||\n typeof tenantId !== 'string' ||\n canonicalUuid(tenantId) !== binding\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n // This is the original spelling of the same authorized tenant, not a bypass.\n const row = await withTenant({ tenantId }, async () =>\n (await CommentCollection.create({ db })).get(id),\n );\n if (\n !row ||\n canonicalUuid(row.tenantId) !== binding ||\n row.retryTenantId !== binding ||\n row.idempotencyKey === null\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n return row;\n }\n\n async create(commentOptions: CreateCommentInput): Promise<Comment> {\n const {\n tenantId: inputTenantId,\n authorUserId: inputAuthorUserId,\n metaType,\n metaId,\n body,\n mentions,\n idempotencyKey,\n parentCommentId: inputParentCommentId,\n } = commentOptions;\n const parentCommentId = inputParentCommentId\n ? canonicalUuid(inputParentCommentId)\n : inputParentCommentId;\n if (!inputTenantId || !inputAuthorUserId) {\n throw new TypeError('Comments require a tenant and author.');\n }\n if (\n canonicalUuid(inputTenantId) !==\n canonicalUuid(this.options.actor.tenantId) ||\n canonicalUuid(inputAuthorUserId) !==\n canonicalUuid(this.options.actor.userId)\n ) {\n throw new Error('Comment actor context mismatch.');\n }\n // Preserve the trusted context's stored spelling on text-backed databases.\n const tenantId = this.options.actor.tenantId;\n const authorUserId = this.options.actor.userId;\n await this.authorize(metaType ?? '', metaId ?? '', 'comment');\n const participationRequest = {\n tenantId,\n actorUserId: authorUserId,\n action: 'comment' as const,\n recordType: metaType ?? '',\n recordId: metaId ?? '',\n };\n await new ParticipationService({\n db: this.options.db,\n entitlement: this.options.participation,\n }).authorize(participationRequest);\n if (!body?.trim()) throw new TypeError('Comment body is required.');\n // Validate through the model's public setter before collection.create()\n // persists. Supply its normalized JSON in that single insert.\n const prepared = new Comment();\n if (mentions !== undefined) prepared.mentionUserIds = mentions;\n if (\n idempotencyKey !== undefined &&\n (typeof idempotencyKey !== 'string' ||\n !idempotencyKey.trim() ||\n idempotencyKey !== idempotencyKey.trim() ||\n idempotencyKey.length > 200)\n ) {\n throw new TypeError(\n 'Comment idempotencyKey must be a nonempty trimmed string of at most 200 characters.',\n );\n }\n const normalizedMentions = [...prepared.mentionUserIds].sort();\n const requestDigest = digestRunOnceContent({\n tenantId: canonicalUuid(tenantId),\n authorUserId: canonicalUuid(authorUserId),\n metaType,\n metaId,\n body,\n mentions: normalizedMentions,\n ...(parentCommentId ? { parentCommentId } : {}),\n });\n const comment = await withTenant({ tenantId }, async () => {\n const collection = await CommentCollection.create({\n db: this.options.db,\n });\n const insert = async (\n target: CommentCollection,\n inTransaction = false,\n ) => {\n const guard = new ParticipationService({\n db: target.db,\n entitlement: this.options.participation,\n });\n const work = async (\n tx: CommentCollection['db'],\n participation: ParticipationDecision,\n ) =>\n withTenant({ tenantId }, async () => {\n target = await CommentCollection.create({ db: tx });\n if (parentCommentId) {\n const parent = await target.get(parentCommentId);\n if (\n !parent ||\n canonicalUuid(parent.tenantId) !== canonicalUuid(tenantId) ||\n parent.metaType !== metaType ||\n parent.metaId !== metaId\n ) {\n throw new Error(\n 'Comment parent does not belong to this record.',\n );\n }\n }\n const id = crypto.randomUUID();\n return target.create({\n tenantId,\n authorUserId,\n metaType,\n metaId,\n body,\n id,\n slug: id,\n context: '',\n _insertOnly: true,\n mentions: prepared.mentions,\n idempotencyKey: idempotencyKey ?? null,\n requestDigest:\n idempotencyKey === undefined ? null : requestDigest,\n retryTenantId:\n idempotencyKey === undefined ? null : canonicalUuid(tenantId),\n parentCommentId: parentCommentId ?? null,\n status: participation.initialCommentStatus ?? 'visible',\n participationSnapshot: participation.snapshot\n ? JSON.stringify(participation.snapshot)\n : null,\n });\n });\n return inTransaction\n ? guard.executeInTransaction(target.db, participationRequest, work)\n : guard.execute(participationRequest, work);\n };\n if (idempotencyKey === undefined) return insert(collection);\n // The internal actor is an arbitration namespace, NOT the authenticated\n // author. All authors sharing a tenant/key must contend on the same claim.\n // Actual author and payload are bound by the independently checked digest.\n const receipt = await runOnce(\n {\n db: collection.db,\n tenantId: canonicalUuid(tenantId),\n actor: 'smrt-comments.create:v1',\n token: idempotencyKey,\n content: 'comment-request-binding:v1',\n },\n async (tx) => {\n const scoped = await CommentCollection.create({ db: tx });\n const matches = await tx.query(\n `SELECT CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id FROM \"${scoped.tableName.replace(/\"/g, '\"\"')}\" WHERE retry_tenant_id = ? AND idempotency_key = ?`,\n canonicalUuid(tenantId),\n idempotencyKey,\n );\n if (matches.rows.length > 1) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n const row = matches.rows[0]\n ? await this.loadBoundComment(\n tx,\n matches.rows[0],\n canonicalUuid(tenantId),\n )\n : await insert(scoped, true);\n return {\n id: row.id,\n digest: row.requestDigest,\n tenant_id: row.tenantId,\n };\n },\n );\n if (receipt.digest !== requestDigest)\n throw new CommentIdempotencyError('COMMENT_REQUEST_CONFLICT');\n const row = await this.loadBoundComment(\n collection.db,\n receipt,\n canonicalUuid(tenantId),\n );\n const retainedBodies = row.editedAt\n ? await withTenant({ tenantId: row.tenantId }, async () =>\n (\n await (\n await CommentRevisionCollection.create({ db: collection.db })\n ).list({\n where: { commentId: row.id, tenantId: row.tenantId },\n })\n ).map((revision) => revision.body),\n )\n : [row.body];\n if (\n canonicalUuid(row.tenantId) !== canonicalUuid(tenantId) ||\n row.idempotencyKey !== idempotencyKey ||\n row.requestDigest !== requestDigest ||\n !retainedBodies.some(\n (retainedBody) =>\n digestRunOnceContent({\n tenantId: canonicalUuid(row.tenantId),\n authorUserId: canonicalUuid(row.authorUserId),\n metaType: row.metaType,\n metaId: row.metaId,\n body: retainedBody,\n mentions: [...row.mentionUserIds].sort(),\n ...(row.parentCommentId\n ? { parentCommentId: canonicalUuid(row.parentCommentId) }\n : {}),\n }) === requestDigest,\n )\n ) {\n throw new CommentIdempotencyError('COMMENT_REQUEST_UNAVAILABLE');\n }\n return row;\n });\n if (\n !this.options.mentionNotifications ||\n !comment.id ||\n comment.status !== 'visible'\n )\n return comment;\n for (const recipientUserId of comment.mentionUserIds) {\n if (\n canonicalUuid(recipientUserId) === canonicalUuid(comment.authorUserId)\n )\n continue;\n await this.options.mentionNotifications.notifyMention({\n commentId: comment.id,\n tenantId: comment.tenantId,\n authorUserId: comment.authorUserId,\n recipientUserId,\n recordType: comment.metaType,\n recordId: comment.metaId,\n body: comment.body,\n });\n }\n return comment;\n }\n}\n","import type {\n CommentMention,\n CommentMentionNotificationAdapter,\n} from '../types.js';\n\n/** Structural subset implemented by smrt-messages UserNotificationService. */\nexport interface CommentNotificationService {\n notify(input: {\n tenantId: string;\n recipientUserId: string;\n kind: string;\n title: string;\n body: string;\n sourceRef: string;\n }): Promise<unknown>;\n}\n\n/** Hosts must check recipient membership AND access to the referenced record. */\nexport function createCommentMentionNotifications(options: {\n notifications: CommentNotificationService;\n canNotify: (mention: CommentMention) => Promise<boolean>;\n}): CommentMentionNotificationAdapter {\n return {\n async notifyMention(mention) {\n if (!(await options.canNotify(mention))) return;\n await options.notifications.notify({\n tenantId: mention.tenantId,\n recipientUserId: mention.recipientUserId,\n kind: 'comments.mention',\n title: 'You were mentioned in a comment',\n body: mention.body,\n sourceRef: `comments.mention:${mention.commentId}`,\n });\n },\n };\n}\n"],"mappings":"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"}
|