Per-resource comments for Basalt: threads (nested replies), @mentions, and resolve/reopen, isolated by tenant, emitting events that connect to @basaltkit/realtime (live discussion) and @basaltkit/notifications (notify who was mentioned). You need this module when you want collaboration — commenting on a note, a project, a task.
A comment system involves more than storing text: threads with replies, extracting @mentions to notify, marking a discussion as resolved, and restricting edits to the author. This module gives you all of that — attached to any resource (resourceType:resourceId) and isolated by tenant — and emits events for the rest of the ecosystem to react to.
Depends on @basaltkit/core and @basaltkit/fastify (routes). No database required: the default store is in-memory (CommentStore contract for production).
Registers the COMMENTS token. mentionPattern is a regex whose first group is the mentioned id (default @([\w-]+)). Bodies are capped at maxBodyLength characters (default 10 000) and maxMentions distinct mentions (default 50); resolveMentions(ids, tenantId) keeps only the ids that may be mentioned (e.g. tenant members).
Without tenantId, uses ctx().tenant.id (otherwise CommentTenantRequiredError). Inside a tenant context an explicit tenantId must equal it (CommentTenantMismatchError, 403). An app without @basaltkit/tenancy has no tenant dimension: its comments are keyed by SINGLE_TENANT_SCOPE ('@single', a sentinel outside the tenant-id grammar; a tenant carrying it is refused with CommentTenantReservedError, COMMENT_TENANT_RESERVED, 400).
> Upgrading from 3.x (single-tenant data): the key used to be 'default', a valid tenant id — a tenant named default could read, edit and delete the single-tenant comments. Re-key persisted rows once: UPDATE comments SET "tenantId" = '@single' WHERE "tenantId" = 'default' (@basaltkit/comments-prisma; with @basaltkit/comments-sqlite the column is tenant_id). Skip it if default was ever a real tenant in that database.
Package reference
Mirrors the package README (single source). Install
@basaltkit/commentsv4.0.0— npm · source.<p align="center"> <a href="https://basaltkit-docs.pages.dev"> <img src="https://basaltkit-docs.pages.dev/social-card.png" alt="Basalt" width="440"> </a> </p>
@basaltkit/comments
Per-resource comments for Basalt: threads (nested replies), @mentions, and resolve/reopen, isolated by tenant, emitting events that connect to
@basaltkit/realtime(live discussion) and@basaltkit/notifications(notify who was mentioned). You need this module when you want collaboration — commenting on a note, a project, a task.What this module solves
A comment system involves more than storing text: threads with replies, extracting @mentions to notify, marking a discussion as resolved, and restricting edits to the author. This module gives you all of that — attached to any resource (
resourceType:resourceId) and isolated by tenant — and emits events for the rest of the ecosystem to react to.Installation
Depends on
@basaltkit/coreand@basaltkit/fastify(routes). No database required: the default store is in-memory (CommentStorecontract for production).Get started in 5 minutes
addextracts @mentions from the body (default@id), stores them on the comment, and emitscomment:mentionedfor each mentioned user.Connecting to realtime and notifications
The power is in the events. Push comments live and notify mentioned users without coupling anything:
Routes
commentRoutes()(all require login; author comes fromctx().user):GET /comments?resourceType=&resourceId=POST /comments{ resourceType, resourceId, body, parentId? }parentIdmust be a comment of the same resource, otherwise 400COMMENT_PARENT_NOT_FOUND.PATCH /comments/:id{ body }DELETE /comments/:idPOST /comments/:id/resolve·/reopenPass
commentRoutes({ authorize: (action, { resourceType, resourceId, comment? }, user) => boolean })to apply your own per-resource policy (it replaces the default; compose withdefaultCommentPolicy).API reference
commentsPlugin({ store?, mentionPattern?, maxBodyLength?, maxMentions?, resolveMentions? })Registers the
COMMENTStoken.mentionPatternis a regex whose first group is the mentioned id (default@([\w-]+)). Bodies are capped atmaxBodyLengthcharacters (default 10 000) andmaxMentionsdistinct mentions (default 50);resolveMentions(ids, tenantId)keeps only the ids that may be mentioned (e.g. tenant members).class Commentson(resourceType, resourceId, tenantId?){ add, list, tree }for a resource.get(id, tenantId?)edit(id, body, tenantId?)comment:updated.remove(id, tenantId?)comment:deleted.resolve(id, by, tenantId?)·reopen(id, tenantId?)comment:resolved/comment:reopened.Without
tenantId, usesctx().tenant.id(otherwiseCommentTenantRequiredError). Inside a tenant context an explicittenantIdmust equal it (CommentTenantMismatchError, 403). An app without@basaltkit/tenancyhas no tenant dimension: its comments are keyed bySINGLE_TENANT_SCOPE('@single', a sentinel outside the tenant-id grammar; a tenant carrying it is refused withCommentTenantReservedError,COMMENT_TENANT_RESERVED, 400).> Upgrading from 3.x (single-tenant data): the key used to be
'default', a valid tenant id — a tenant nameddefaultcould read, edit and delete the single-tenant comments. Re-key persisted rows once:UPDATE comments SET "tenantId" = '@single' WHERE "tenantId" = 'default'(@basaltkit/comments-prisma; with@basaltkit/comments-sqlitethe column istenant_id). Skip it ifdefaultwas ever a real tenant in that database.Events
comment:created·comment:updated·comment:deleted·comment:resolved·comment:reopened·comment:mentioned(one per mentioned user).How it connects to other modules
@basaltkit/realtime— pushescomment:createdto the resource's channel (live discussion).@basaltkit/notifications— reacts tocomment:mentionedto notify mentioned users.@basaltkit/auth/@basaltkit/tenancy— provide the user (author) and tenant from context.