AbsoluteJS

@absolutejs/sync-pack-comments

@absolutejs/sync-pack-commentsv0.4.5betaData & Sync

Threaded comments pack for @absolutejs/sync — per-resource ACL-scoped, author/moderator gates, optional CRDT bodies

#Installation

BASH
bun add @absolutejs/sync-pack-comments

#Capabilities

Overview

Threaded comments as a sync pack for @absolutejs/sync. Per-resource ACL injection, author/moderator gates, optional CRDT bodies. Plugs into a SyncEngine with one engine.registerPack(...) call.

The pack exposes

Surface — Name — What it does

Collection — comments — Subscribe with params: { resourceId } — returns the comment tree

Mutation — comments:create — Args: { resourceId, body, parentCommentId? } — stamps authorId

Show 3 more

Mutation — comments:edit — Args: { commentId, body } — author only, stamps editedAt

Mutation — comments:delete — Args: { commentId } — author or moderator

When bodyCrdt is set, the engine auto-registers a comments:merge mutation through registerCrdt — clients call that to merge CRDT body updates concurrently with regular edits.

Row shape

The collection returns a flat list of rows for the resource; the client builds the tree by walking parentCommentId. Depth is stored on the row so a client can short-circuit-render without traversing the full chain.

Storage

Default: per-instance in-memory store. To use a persistent backend (Drizzle, Postgres, …), pass a custom store:

The getById method is required (used by edit + delete to verify ownership and by create to walk the parent chain for depth math).

Multiple instances

To run two comments packs on the same engine (e.g. one per product surface), pass a prefix to each. It scopes the owned table, the collection, and the mutation names:

Composition

This pack composes with the rest of your sync graph via subscriptions. A presence pack that wants to show "Alice is replying to this thread" should subscribe to comments and presence separately — it should NOT call comments: from inside its own handler. See the design doc rules in syncPacks.design.md.

Optional: comments-with-author join collection (0.2+)

Set joinUsers to additionally register a comments-with-author join collection that pairs each comment with the host's user row. The pack does NOT own the users table; it adds it to readsTables so the engine knows the dependency and your devtools see the full graph.

The engine inner-joins on comment.authorId === user.id; comments whose author is missing from the users table are excluded from the join (but still appear in the base comments collection). canReadResource gates the join the same way it gates the base.

Planned for 0.3+

In-thread full-text search via registerSearch on the comments

table.

Reactions — a reactionsTable config that adds an emoji-reaction

Show 1 more

side table with create/remove/list mutations.

Outcomes

What you can build

Build on the supported package contract

Use @absolutejs/sync-pack-comments through its supported public entry points.

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/sync-pack-comments version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Trace from the first failed boundary
Reproduce the smallest canonical @absolutejs/sync-pack-comments example, confirm the supported entry point and version in the API explorer, then inspect the first boundary that did not produce its documented result.

#@absolutejs/sync-pack-comments quick start

Partial snippet

# @absolutejs/sync-pack-comments

BASH
bun add @absolutejs/sync-pack-comments

#Usage

Partial snippet

Working example for Usage.

TS
import { createSyncEngine } from '@absolutejs/sync/engine';
import { createCommentsPack } from '@absolutejs/sync-pack-comments';

const engine = createSyncEngine();
engine.registerPack(
	createCommentsPack({
		// REQUIRED: gate read access on a resource. The host knows which
		// resources a given ctx can see; the pack does not duplicate that.
		canReadResource: (resourceId, ctx) =>
			hostAcl.canRead(resourceId, ctx.session.userId),

		// REQUIRED in practice: how the pack reads the current actor id
		// from your app's ctx. Default is `(ctx) => ctx.userId`.
		getActorId: (ctx) => ctx.session.userId,

		// OPTIONAL: moderator predicate. Used by comments:delete (author OR
		// moderator can delete). Default `() => false`.
		canModerate: (ctx) => ctx.session.isModerator,

		// OPTIONAL: max thread depth (top-level = 0). Default 8.
		maxDepth: 8,

		// OPTIONAL: wire the comment body as a CRDT field via registerCrdt
		// so concurrent edits merge instead of clobbering. Pass anything
		// implementing `CrdtMergeable<T>` — e.g. yjsText from
		// @absolutejs/sync-yjs. The pack does NOT import Yjs.
		// bodyCrdt: yjsText,
	})
);

#Row shape

Partial snippet

Working example for Row shape.

TS
type CommentRow = {
	id: string;
	resourceId: string;
	parentCommentId: string | null; // null on top-level; parent id on replies
	authorId: string;
	body: string;
	depth: number; // 0 for top-level, parent.depth + 1 for replies
	createdAt: number;
	editedAt: number | null;
};

#Storage

Partial snippet

Default: per-instance in-memory store. To use a persistent backend (Drizzle, Postgres, …), pass a custom store:

TS
import {
	createCommentsPack,
	type CommentsStore,
} from '@absolutejs/sync-pack-comments';

const store: CommentsStore = {
	getById: (id) => /* SELECT * FROM comments WHERE id = $1 */,
	reader: { all: () => /* SELECT * FROM comments */ },
	writer: {
		insert: (row) => /* INSERT */,
		update: (row) => /* UPDATE */,
		delete: (row) => /* DELETE WHERE id = $1 */,
	},
};

engine.registerPack(createCommentsPack({ store, canReadResource, getActorId }));

#Public entry points

Supported entry points declared by this package manifest.

Package entry point declared in package.json.

@absolutejs/sync-pack-comments@absolutejs/sync-pack-comments/manifest@absolutejs/sync-pack-comments/manifest.json

#Package commands

Scripts declared by this package manifest.

bun run buildrm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --sourcemap --target=bun --external @absolutejs/sync && tsc --project tsconfig.build.json && absolute-manifest emit
bun run formatprettier --write "./**/*.{ts,json,md}"
bun run testbun test
bun run typechecktsc --noEmit

#API reference

Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.

12 symbols
CommentRowtypePermalinkSource

A comment row stored in the pack's owned table.

TS
type CommentRow = {
    id: string;
    resourceId: string;
    /** Null on top-level comments, the parent's id on replies. */
    parentCommentId: string | null;
    authorId: string;
    body: string;
    /** Depth in the thread (0 for top-level, parent.depth + 1 for replies). */
    depth: number;
    createdAt: number;
    /** Null until the comment is edited; otherwise the timestamp of the most
     * recent edit. */
    editedAt: number | null;
};
Exported from @absolutejs/sync-pack-comments