@basaltkit/comments 1.0.2 → 1.1.0

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.
@@ -12,6 +12,12 @@ export declare class CommentTenantRequiredError extends BasaltError {
12
12
  export interface CommentNode extends Comment {
13
13
  replies: CommentNode[];
14
14
  }
15
+ /**
16
+ * Store key every comment is filed under when the app has no tenancy at all.
17
+ * The {@link CommentStore} contract is tenant-keyed, so a single-tenant app
18
+ * still needs one stable key — it just shouldn't have to invent it.
19
+ */
20
+ export declare const SINGLE_TENANT_SCOPE = "default";
15
21
  export interface CommentsOptions {
16
22
  store?: CommentStore;
17
23
  hooks?: HookBus;
@@ -36,11 +42,25 @@ export interface ResourceComments {
36
42
  * updates (@basaltkit/realtime) and notifications wire up without coupling.
37
43
  */
38
44
  export declare class Comments {
45
+ /**
46
+ * Whether the host app registered `@basaltkit/tenancy`. `commentsPlugin`
47
+ * wires this to the container's `'tenancy:active'` metadata marker — a
48
+ * signal, not an import, so this generic package never depends on the
49
+ * opt-in SaaS layer. Defaults to `false` (single-tenant).
50
+ */
51
+ private readonly tenancyActive;
39
52
  private readonly store;
40
53
  private readonly hooks;
41
54
  private readonly mentionPattern;
42
55
  private readonly now;
43
- constructor(options?: CommentsOptions);
56
+ constructor(options?: CommentsOptions,
57
+ /**
58
+ * Whether the host app registered `@basaltkit/tenancy`. `commentsPlugin`
59
+ * wires this to the container's `'tenancy:active'` metadata marker — a
60
+ * signal, not an import, so this generic package never depends on the
61
+ * opt-in SaaS layer. Defaults to `false` (single-tenant).
62
+ */
63
+ tenancyActive?: () => boolean);
44
64
  on(resourceType: string, resourceId: string, tenantId?: string): ResourceComments;
45
65
  get(id: string, tenantId?: string): Promise<Comment | null>;
46
66
  edit(id: string, body: string, tenantId?: string): Promise<Comment>;
@@ -50,5 +70,12 @@ export declare class Comments {
50
70
  private add;
51
71
  private mutate;
52
72
  private mentions;
73
+ /**
74
+ * The tenant a call is scoped to.
75
+ *
76
+ * With `@basaltkit/tenancy` registered an unresolvable tenant is an error: an
77
+ * unscoped read or write would cross tenants. Without it there is no tenant
78
+ * dimension, so every comment shares {@link SINGLE_TENANT_SCOPE}.
79
+ */
53
80
  private tenant;
54
81
  }
package/dist/comments.js CHANGED
@@ -13,6 +13,12 @@ export class CommentTenantRequiredError extends BasaltError {
13
13
  super('COMMENT_TENANT_REQUIRED', 'A tenant is required — pass tenantId or run inside a tenant context.');
14
14
  }
15
15
  }
16
+ /**
17
+ * Store key every comment is filed under when the app has no tenancy at all.
18
+ * The {@link CommentStore} contract is tenant-keyed, so a single-tenant app
19
+ * still needs one stable key — it just shouldn't have to invent it.
20
+ */
21
+ export const SINGLE_TENANT_SCOPE = 'default';
16
22
  const buildTree = (comments) => {
17
23
  const nodes = new Map(comments.map((c) => [c.id, { ...c, replies: [] }]));
18
24
  const roots = [];
@@ -31,11 +37,20 @@ const buildTree = (comments) => {
31
37
  * updates (@basaltkit/realtime) and notifications wire up without coupling.
32
38
  */
33
39
  export class Comments {
40
+ tenancyActive;
34
41
  store;
35
42
  hooks;
36
43
  mentionPattern;
37
44
  now;
38
- constructor(options = {}) {
45
+ constructor(options = {},
46
+ /**
47
+ * Whether the host app registered `@basaltkit/tenancy`. `commentsPlugin`
48
+ * wires this to the container's `'tenancy:active'` metadata marker — a
49
+ * signal, not an import, so this generic package never depends on the
50
+ * opt-in SaaS layer. Defaults to `false` (single-tenant).
51
+ */
52
+ tenancyActive = () => false) {
53
+ this.tenancyActive = tenancyActive;
39
54
  this.store = options.store ?? new MemoryCommentStore();
40
55
  this.hooks = options.hooks;
41
56
  this.mentionPattern = options.mentionPattern ?? /@([\w-]+)/g;
@@ -115,10 +130,19 @@ export class Comments {
115
130
  ids.add(match[1]);
116
131
  return [...ids];
117
132
  }
133
+ /**
134
+ * The tenant a call is scoped to.
135
+ *
136
+ * With `@basaltkit/tenancy` registered an unresolvable tenant is an error: an
137
+ * unscoped read or write would cross tenants. Without it there is no tenant
138
+ * dimension, so every comment shares {@link SINGLE_TENANT_SCOPE}.
139
+ */
118
140
  tenant(explicit) {
119
141
  const id = explicit ?? tryCtx()?.['tenant']?.id;
120
- if (!id)
142
+ if (id)
143
+ return id;
144
+ if (this.tenancyActive())
121
145
  throw new CommentTenantRequiredError();
122
- return id;
146
+ return SINGLE_TENANT_SCOPE;
123
147
  }
124
148
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { Comments, CommentNotFoundError, CommentTenantRequiredError, type CommentsOptions, type AddCommentInput, type ResourceComments, type CommentNode, } from './comments.js';
1
+ export { Comments, CommentNotFoundError, CommentTenantRequiredError, SINGLE_TENANT_SCOPE, type CommentsOptions, type AddCommentInput, type ResourceComments, type CommentNode, } from './comments.js';
2
2
  export { MemoryCommentStore, type Comment, type CommentStore, type CommentPatch } from './store.js';
3
3
  export { commentsPlugin, commentRoutes, COMMENTS, type CommentsPluginOptions } from './plugin.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Comments, CommentNotFoundError, CommentTenantRequiredError, } from './comments.js';
1
+ export { Comments, CommentNotFoundError, CommentTenantRequiredError, SINGLE_TENANT_SCOPE, } from './comments.js';
2
2
  export { MemoryCommentStore } from './store.js';
3
3
  export { commentsPlugin, commentRoutes, COMMENTS } from './plugin.js';
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createToken, ctx, definePlugin, BasaltError } from '@basaltkit/core';
1
+ import { createToken, ctx, definePlugin, BasaltError, ensureMetadata } from '@basaltkit/core';
2
2
  import { route } from '@basaltkit/http';
3
3
  import { z } from 'zod';
4
4
  import { Comments } from './comments.js';
@@ -7,7 +7,10 @@ export function commentsPlugin(options = {}) {
7
7
  return definePlugin({
8
8
  name: 'basalt:comments',
9
9
  register({ container, hooks }) {
10
- container.singleton(COMMENTS, () => new Comments({ ...options, hooks }));
10
+ // 'tenancy:active' is tenancyPlugin's marker: how a generic package
11
+ // learns the app is multi-tenant without importing @basaltkit/tenancy.
12
+ const metadata = ensureMetadata(container);
13
+ container.singleton(COMMENTS, () => new Comments({ ...options, hooks }, () => metadata.get('tenancy:active').length > 0));
11
14
  },
12
15
  });
13
16
  }
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@basaltkit/comments",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
+ "engines": {
5
+ "node": ">=22.5.0"
6
+ },
4
7
  "description": "Comments for Basalt: per-resource threads with @mentions and resolve/reopen, tenant-scoped, emitting events that bridge to realtime and notifications.",
5
8
  "license": "MIT",
6
9
  "type": "module",
10
+ "sideEffects": false,
7
11
  "exports": {
8
12
  ".": {
9
13
  "types": "./dist/index.d.ts",
@@ -14,8 +18,8 @@
14
18
  "dist"
15
19
  ],
16
20
  "dependencies": {
17
- "@basaltkit/core": "^1.1.2",
18
- "@basaltkit/http": "^1.9.1"
21
+ "@basaltkit/core": "^1.3.1",
22
+ "@basaltkit/http": "^1.14.0"
19
23
  },
20
24
  "peerDependencies": {
21
25
  "zod": "^3.24.0 || ^4.0.0"
@@ -25,9 +29,9 @@
25
29
  "typescript": "^7.0.2",
26
30
  "vitest": "^4.1.11",
27
31
  "zod": "^3.24.0 || ^4.0.0",
28
- "@basaltkit/auth": "^1.6.3",
29
- "@basaltkit/tenancy": "^1.3.3",
30
- "@basaltkit/fastify": "^1.6.1",
32
+ "@basaltkit/auth": "^1.8.0",
33
+ "@basaltkit/fastify": "^1.8.1",
34
+ "@basaltkit/tenancy": "^1.4.2",
31
35
  "@basaltkit/tsconfig": "^0.24.0"
32
36
  },
33
37
  "publishConfig": {