@actuate-media/cms-core 0.20.0 → 0.22.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.
Files changed (150) hide show
  1. package/LICENSE +21 -21
  2. package/dist/__tests__/actions/document-crud.test.js +74 -2
  3. package/dist/__tests__/actions/document-crud.test.js.map +1 -1
  4. package/dist/__tests__/api/cache-headers.test.d.ts +2 -0
  5. package/dist/__tests__/api/cache-headers.test.d.ts.map +1 -0
  6. package/dist/__tests__/api/cache-headers.test.js +135 -0
  7. package/dist/__tests__/api/cache-headers.test.js.map +1 -0
  8. package/dist/__tests__/api/comments-routes.test.d.ts +2 -0
  9. package/dist/__tests__/api/comments-routes.test.d.ts.map +1 -0
  10. package/dist/__tests__/api/comments-routes.test.js +266 -0
  11. package/dist/__tests__/api/comments-routes.test.js.map +1 -0
  12. package/dist/__tests__/api/notifications-routes.test.d.ts +2 -0
  13. package/dist/__tests__/api/notifications-routes.test.d.ts.map +1 -0
  14. package/dist/__tests__/api/notifications-routes.test.js +225 -0
  15. package/dist/__tests__/api/notifications-routes.test.js.map +1 -0
  16. package/dist/__tests__/api/preview-and-scheduling.test.js +53 -0
  17. package/dist/__tests__/api/preview-and-scheduling.test.js.map +1 -1
  18. package/dist/__tests__/fields/component-block.test.d.ts +2 -0
  19. package/dist/__tests__/fields/component-block.test.d.ts.map +1 -0
  20. package/dist/__tests__/fields/component-block.test.js +129 -0
  21. package/dist/__tests__/fields/component-block.test.js.map +1 -0
  22. package/dist/__tests__/fields/relations.test.js +62 -8
  23. package/dist/__tests__/fields/relations.test.js.map +1 -1
  24. package/dist/__tests__/preview-event-bus.test.d.ts +2 -0
  25. package/dist/__tests__/preview-event-bus.test.d.ts.map +1 -0
  26. package/dist/__tests__/preview-event-bus.test.js +89 -0
  27. package/dist/__tests__/preview-event-bus.test.js.map +1 -0
  28. package/dist/__tests__/preview-sse-stream.test.d.ts +2 -0
  29. package/dist/__tests__/preview-sse-stream.test.d.ts.map +1 -0
  30. package/dist/__tests__/preview-sse-stream.test.js +117 -0
  31. package/dist/__tests__/preview-sse-stream.test.js.map +1 -0
  32. package/dist/__tests__/realtime/comments.test.d.ts +2 -0
  33. package/dist/__tests__/realtime/comments.test.d.ts.map +1 -0
  34. package/dist/__tests__/realtime/comments.test.js +508 -0
  35. package/dist/__tests__/realtime/comments.test.js.map +1 -0
  36. package/dist/__tests__/realtime/gateway-ws-integration.test.d.ts +2 -0
  37. package/dist/__tests__/realtime/gateway-ws-integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/realtime/gateway-ws-integration.test.js +170 -0
  39. package/dist/__tests__/realtime/gateway-ws-integration.test.js.map +1 -0
  40. package/dist/__tests__/realtime/gateway.test.d.ts +2 -0
  41. package/dist/__tests__/realtime/gateway.test.d.ts.map +1 -0
  42. package/dist/__tests__/realtime/gateway.test.js +402 -0
  43. package/dist/__tests__/realtime/gateway.test.js.map +1 -0
  44. package/dist/__tests__/realtime/notifications.test.d.ts +2 -0
  45. package/dist/__tests__/realtime/notifications.test.d.ts.map +1 -0
  46. package/dist/__tests__/realtime/notifications.test.js +317 -0
  47. package/dist/__tests__/realtime/notifications.test.js.map +1 -0
  48. package/dist/__tests__/realtime/persistence.test.d.ts +2 -0
  49. package/dist/__tests__/realtime/persistence.test.d.ts.map +1 -0
  50. package/dist/__tests__/realtime/persistence.test.js +124 -0
  51. package/dist/__tests__/realtime/persistence.test.js.map +1 -0
  52. package/dist/__tests__/schema-preview-features.test.d.ts +2 -0
  53. package/dist/__tests__/schema-preview-features.test.d.ts.map +1 -0
  54. package/dist/__tests__/schema-preview-features.test.js +42 -0
  55. package/dist/__tests__/schema-preview-features.test.js.map +1 -0
  56. package/dist/actions.d.ts.map +1 -1
  57. package/dist/actions.js +71 -26
  58. package/dist/actions.js.map +1 -1
  59. package/dist/api/handlers.d.ts.map +1 -1
  60. package/dist/api/handlers.js +734 -188
  61. package/dist/api/handlers.js.map +1 -1
  62. package/dist/cache/__tests__/http.test.d.ts +2 -0
  63. package/dist/cache/__tests__/http.test.d.ts.map +1 -0
  64. package/dist/cache/__tests__/http.test.js +285 -0
  65. package/dist/cache/__tests__/http.test.js.map +1 -0
  66. package/dist/cache/http.d.ts +119 -0
  67. package/dist/cache/http.d.ts.map +1 -0
  68. package/dist/cache/http.js +188 -0
  69. package/dist/cache/http.js.map +1 -0
  70. package/dist/config/types.d.ts +153 -1
  71. package/dist/config/types.d.ts.map +1 -1
  72. package/dist/fields/component-block.d.ts +23 -0
  73. package/dist/fields/component-block.d.ts.map +1 -0
  74. package/dist/fields/component-block.js +137 -0
  75. package/dist/fields/component-block.js.map +1 -0
  76. package/dist/fields/index.d.ts.map +1 -1
  77. package/dist/fields/index.js +2 -0
  78. package/dist/fields/index.js.map +1 -1
  79. package/dist/fields/relations.d.ts.map +1 -1
  80. package/dist/fields/relations.js +43 -11
  81. package/dist/fields/relations.js.map +1 -1
  82. package/dist/index.d.ts +5 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/page-builder/schema.d.ts +2 -2
  87. package/dist/preview/event-bus.d.ts +39 -0
  88. package/dist/preview/event-bus.d.ts.map +1 -0
  89. package/dist/preview/event-bus.js +73 -0
  90. package/dist/preview/event-bus.js.map +1 -0
  91. package/dist/preview/sse-stream.d.ts +50 -0
  92. package/dist/preview/sse-stream.d.ts.map +1 -0
  93. package/dist/preview/sse-stream.js +120 -0
  94. package/dist/preview/sse-stream.js.map +1 -0
  95. package/dist/realtime/comments.d.ts +217 -0
  96. package/dist/realtime/comments.d.ts.map +1 -0
  97. package/dist/realtime/comments.js +282 -0
  98. package/dist/realtime/comments.js.map +1 -0
  99. package/dist/realtime/gateway.d.ts +107 -0
  100. package/dist/realtime/gateway.d.ts.map +1 -0
  101. package/dist/realtime/gateway.js +215 -0
  102. package/dist/realtime/gateway.js.map +1 -0
  103. package/dist/realtime/index.d.ts +18 -0
  104. package/dist/realtime/index.d.ts.map +1 -0
  105. package/dist/realtime/index.js +18 -0
  106. package/dist/realtime/index.js.map +1 -0
  107. package/dist/realtime/notifications.d.ts +209 -0
  108. package/dist/realtime/notifications.d.ts.map +1 -0
  109. package/dist/realtime/notifications.js +209 -0
  110. package/dist/realtime/notifications.js.map +1 -0
  111. package/dist/realtime/persistence.d.ts +72 -0
  112. package/dist/realtime/persistence.d.ts.map +1 -0
  113. package/dist/realtime/persistence.js +53 -0
  114. package/dist/realtime/persistence.js.map +1 -0
  115. package/package.json +13 -4
  116. package/prisma/cms-schema.prisma +437 -306
  117. package/prisma/migrations/0001_init/migration.sql +384 -384
  118. package/prisma/migrations/0002_folders/migration.sql +39 -39
  119. package/prisma/migrations/0003_search_and_webhooks/migration.sql +50 -50
  120. package/prisma/migrations/0004_script_tags/migration.sql +21 -21
  121. package/prisma/migrations/0005_password_reset_tokens/migration.sql +20 -20
  122. package/prisma/migrations/0006_page_builder/migration.sql +38 -38
  123. package/prisma/migrations/migration_lock.toml +3 -3
  124. package/prisma/schema.prisma +563 -549
  125. package/generated/browser.ts +0 -109
  126. package/generated/client.ts +0 -133
  127. package/generated/commonInputTypes.ts +0 -709
  128. package/generated/enums.ts +0 -125
  129. package/generated/internal/class.ts +0 -376
  130. package/generated/internal/prismaNamespace.ts +0 -2617
  131. package/generated/internal/prismaNamespaceBrowser.ts +0 -611
  132. package/generated/models/ApiKey.ts +0 -1550
  133. package/generated/models/AuditLog.ts +0 -1206
  134. package/generated/models/BackupRecord.ts +0 -1250
  135. package/generated/models/ContentLock.ts +0 -1472
  136. package/generated/models/ContentTemplate.ts +0 -1416
  137. package/generated/models/Document.ts +0 -3005
  138. package/generated/models/Folder.ts +0 -1904
  139. package/generated/models/FormSubmission.ts +0 -1200
  140. package/generated/models/InAppNotification.ts +0 -1457
  141. package/generated/models/Media.ts +0 -2340
  142. package/generated/models/MediaUsage.ts +0 -1472
  143. package/generated/models/OAuthAccount.ts +0 -1463
  144. package/generated/models/Redirect.ts +0 -1284
  145. package/generated/models/Session.ts +0 -1492
  146. package/generated/models/Site.ts +0 -1206
  147. package/generated/models/User.ts +0 -3513
  148. package/generated/models/Version.ts +0 -1511
  149. package/generated/models/WorkflowState.ts +0 -1514
  150. package/generated/models.ts +0 -29
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Document comments — anchored, threaded discussion attached to documents.
3
+ *
4
+ * This module owns the *pure* comment logic: validation, normalization, and
5
+ * the storage-agnostic CRUD entry points. The HTTP layer in `api/handlers.ts`
6
+ * is a thin wrapper that handles auth + JSON envelope; everything else lives
7
+ * here so the unit tests don't need a Request loop.
8
+ *
9
+ * Storage shape — see `packages/cms-core/prisma/cms-schema.prisma`,
10
+ * model `DocumentComment`. We require only a minimal Prisma surface so the
11
+ * helpers can be tested against an in-memory fake.
12
+ *
13
+ * Anchors are Yjs *relative positions* serialised as Base64. Storing them in
14
+ * a JSON column (rather than Bytes) keeps the REST contract trivial: the
15
+ * client posts `{ from: "AQ...", to: "AQ..." }`, we round-trip the strings.
16
+ * Slice 5 reconstitutes them with `Y.createAbsolutePositionFromRelativePosition`
17
+ * to paint anchored highlights in the editor; nothing in the API layer needs
18
+ * to interpret the bytes.
19
+ */
20
+ /** Maximum length we accept for a comment body (markdown source). */
21
+ export const MAX_COMMENT_BODY_LENGTH = 10_000;
22
+ /** Cap on returned comments per `listComments` call. */
23
+ export const MAX_COMMENT_LIST_PAGE_SIZE = 200;
24
+ /** Default page size when the caller does not request one. */
25
+ export const DEFAULT_COMMENT_LIST_PAGE_SIZE = 100;
26
+ const MAX_ANCHOR_FIELD_LENGTH = 4_096;
27
+ function err(code, message) {
28
+ return { code, message };
29
+ }
30
+ function toIso(value) {
31
+ if (value === null)
32
+ return null;
33
+ if (value instanceof Date)
34
+ return value.toISOString();
35
+ // Tolerate fakes that already store ISO strings.
36
+ return value;
37
+ }
38
+ function toIsoRequired(value) {
39
+ return value instanceof Date ? value.toISOString() : value;
40
+ }
41
+ /**
42
+ * Strip a comment row down to the public DTO. Centralised so the response
43
+ * shape stays consistent between create / list / update / resolve.
44
+ */
45
+ export function toCommentDTO(row) {
46
+ return {
47
+ id: row.id,
48
+ documentId: row.documentId,
49
+ parentId: row.parentId,
50
+ userId: row.userId,
51
+ body: row.body,
52
+ anchor: row.anchor,
53
+ resolvedAt: toIso(row.resolvedAt),
54
+ resolvedById: row.resolvedById,
55
+ deletedAt: toIso(row.deletedAt),
56
+ createdAt: toIsoRequired(row.createdAt),
57
+ updatedAt: toIsoRequired(row.updatedAt),
58
+ isResolved: row.resolvedAt !== null,
59
+ };
60
+ }
61
+ /**
62
+ * Parse + validate an anchor blob coming from an untrusted client. Returns
63
+ * `null` when the caller did not provide one (document-level comments).
64
+ */
65
+ export function parseAnchor(input) {
66
+ if (input === undefined || input === null) {
67
+ return { ok: true, value: null };
68
+ }
69
+ if (typeof input !== 'object' || Array.isArray(input)) {
70
+ return { ok: false, error: err('validation', 'anchor must be an object with `from` and `to`') };
71
+ }
72
+ const obj = input;
73
+ if (typeof obj.from !== 'string' || typeof obj.to !== 'string') {
74
+ return { ok: false, error: err('validation', 'anchor.from and anchor.to must be strings') };
75
+ }
76
+ if (obj.from.length === 0 ||
77
+ obj.from.length > MAX_ANCHOR_FIELD_LENGTH ||
78
+ obj.to.length === 0 ||
79
+ obj.to.length > MAX_ANCHOR_FIELD_LENGTH) {
80
+ return {
81
+ ok: false,
82
+ error: err('validation', `anchor fields must be 1..${MAX_ANCHOR_FIELD_LENGTH} chars`),
83
+ };
84
+ }
85
+ return { ok: true, value: { from: obj.from, to: obj.to } };
86
+ }
87
+ /** Normalize whitespace + enforce length limits on a posted body. */
88
+ export function normaliseBody(input) {
89
+ if (typeof input !== 'string') {
90
+ return { ok: false, error: err('validation', 'body must be a string') };
91
+ }
92
+ const trimmed = input.trim();
93
+ if (trimmed.length === 0) {
94
+ return { ok: false, error: err('validation', 'body cannot be empty') };
95
+ }
96
+ if (trimmed.length > MAX_COMMENT_BODY_LENGTH) {
97
+ return {
98
+ ok: false,
99
+ error: err('validation', `body cannot exceed ${MAX_COMMENT_BODY_LENGTH} characters`),
100
+ };
101
+ }
102
+ return { ok: true, value: trimmed };
103
+ }
104
+ /**
105
+ * Create a comment. Enforces:
106
+ * - body validation (non-empty, length-capped)
107
+ * - anchor shape validation
108
+ * - single-level threading: a `parentId` must reference an existing,
109
+ * non-deleted, non-reply comment under the same document.
110
+ *
111
+ * The caller (HTTP handler) is responsible for auth — once we reach here we
112
+ * trust the supplied `userId`.
113
+ */
114
+ export async function createComment(db, input) {
115
+ if (typeof input.documentId !== 'string' || input.documentId.length === 0) {
116
+ return { ok: false, error: err('validation', 'documentId is required') };
117
+ }
118
+ if (typeof input.userId !== 'string' || input.userId.length === 0) {
119
+ return { ok: false, error: err('validation', 'userId is required') };
120
+ }
121
+ const body = normaliseBody(input.body);
122
+ if (!body.ok)
123
+ return body;
124
+ const anchor = parseAnchor(input.anchor);
125
+ if (!anchor.ok)
126
+ return anchor;
127
+ let parentId = null;
128
+ if (input.parentId != null && input.parentId !== '') {
129
+ if (typeof input.parentId !== 'string') {
130
+ return { ok: false, error: err('validation', 'parentId must be a string') };
131
+ }
132
+ const parent = await db.documentComment.findUnique({ where: { id: input.parentId } });
133
+ if (!parent) {
134
+ return { ok: false, error: err('not_found', 'parent comment not found') };
135
+ }
136
+ if (parent.documentId !== input.documentId) {
137
+ return { ok: false, error: err('validation', 'parent comment is on a different document') };
138
+ }
139
+ if (parent.parentId !== null) {
140
+ return {
141
+ ok: false,
142
+ error: err('validation', 'replies cannot themselves be replied to (one level only)'),
143
+ };
144
+ }
145
+ if (parent.deletedAt !== null) {
146
+ return { ok: false, error: err('not_found', 'parent comment has been deleted') };
147
+ }
148
+ parentId = parent.id;
149
+ }
150
+ const row = await db.documentComment.create({
151
+ data: {
152
+ documentId: input.documentId,
153
+ userId: input.userId,
154
+ body: body.value,
155
+ anchor: anchor.value,
156
+ parentId,
157
+ },
158
+ });
159
+ return { ok: true, value: toCommentDTO(row) };
160
+ }
161
+ /**
162
+ * List comments for a document. Returns the rows ordered oldest-first so a
163
+ * naive client can render threads top-down. Pagination is "show the first
164
+ * N"; the higher-level UI in slice 5 will switch to cursor pagination once
165
+ * volume justifies it.
166
+ */
167
+ export async function listComments(db, filter) {
168
+ if (typeof filter.documentId !== 'string' || filter.documentId.length === 0) {
169
+ return { ok: false, error: err('validation', 'documentId is required') };
170
+ }
171
+ const where = { documentId: filter.documentId };
172
+ if (!filter.includeResolved)
173
+ where.resolvedAt = null;
174
+ if (!filter.includeDeleted)
175
+ where.deletedAt = null;
176
+ const pageSize = clampPageSize(filter.pageSize);
177
+ const rows = await db.documentComment.findMany({
178
+ where,
179
+ orderBy: { createdAt: 'asc' },
180
+ take: pageSize,
181
+ });
182
+ return { ok: true, value: rows.map(toCommentDTO) };
183
+ }
184
+ function clampPageSize(n) {
185
+ if (n === undefined || Number.isNaN(n))
186
+ return DEFAULT_COMMENT_LIST_PAGE_SIZE;
187
+ if (n < 1)
188
+ return 1;
189
+ if (n > MAX_COMMENT_LIST_PAGE_SIZE)
190
+ return MAX_COMMENT_LIST_PAGE_SIZE;
191
+ return Math.floor(n);
192
+ }
193
+ /**
194
+ * Edit a comment body. Only the author can edit, and only if the comment is
195
+ * not soft-deleted. Resolved threads remain editable so a moderator can
196
+ * append context.
197
+ */
198
+ export async function updateComment(db, id, input, actor) {
199
+ const existing = await db.documentComment.findUnique({ where: { id } });
200
+ if (!existing || existing.deletedAt !== null) {
201
+ return { ok: false, error: err('not_found', 'comment not found') };
202
+ }
203
+ if (!actor.isAdmin && existing.userId !== actor.userId) {
204
+ return { ok: false, error: err('forbidden', 'only the author can edit this comment') };
205
+ }
206
+ const body = normaliseBody(input.body);
207
+ if (!body.ok)
208
+ return body;
209
+ const row = await db.documentComment.update({
210
+ where: { id },
211
+ data: { body: body.value },
212
+ });
213
+ return { ok: true, value: toCommentDTO(row) };
214
+ }
215
+ /**
216
+ * Mark a comment thread as resolved. Either the comment's author or any
217
+ * admin / write-role caller can resolve. Resolving an already-resolved
218
+ * comment is a no-op (returns the existing row) so a duplicate click in the
219
+ * UI does not surface as an error.
220
+ */
221
+ export async function resolveComment(db, id, actor) {
222
+ const existing = await db.documentComment.findUnique({ where: { id } });
223
+ if (!existing || existing.deletedAt !== null) {
224
+ return { ok: false, error: err('not_found', 'comment not found') };
225
+ }
226
+ if (!actor.canResolve && existing.userId !== actor.userId) {
227
+ return { ok: false, error: err('forbidden', 'not allowed to resolve this comment') };
228
+ }
229
+ if (existing.resolvedAt !== null) {
230
+ return { ok: true, value: toCommentDTO(existing) };
231
+ }
232
+ const row = await db.documentComment.update({
233
+ where: { id },
234
+ data: { resolvedAt: new Date(), resolvedById: actor.userId },
235
+ });
236
+ return { ok: true, value: toCommentDTO(row) };
237
+ }
238
+ /**
239
+ * Re-open a resolved thread. Same permission as `resolveComment`.
240
+ */
241
+ export async function reopenComment(db, id, actor) {
242
+ const existing = await db.documentComment.findUnique({ where: { id } });
243
+ if (!existing || existing.deletedAt !== null) {
244
+ return { ok: false, error: err('not_found', 'comment not found') };
245
+ }
246
+ if (!actor.canResolve && existing.userId !== actor.userId) {
247
+ return { ok: false, error: err('forbidden', 'not allowed to re-open this comment') };
248
+ }
249
+ if (existing.resolvedAt === null) {
250
+ return { ok: true, value: toCommentDTO(existing) };
251
+ }
252
+ const row = await db.documentComment.update({
253
+ where: { id },
254
+ data: { resolvedAt: null, resolvedById: null },
255
+ });
256
+ return { ok: true, value: toCommentDTO(row) };
257
+ }
258
+ /**
259
+ * Soft-delete a comment. The row is preserved (so replies don't lose their
260
+ * thread root) but stops being returned by `listComments` unless explicitly
261
+ * requested with `includeDeleted: true`.
262
+ *
263
+ * Only the author or an admin may delete.
264
+ */
265
+ export async function deleteComment(db, id, actor) {
266
+ const existing = await db.documentComment.findUnique({ where: { id } });
267
+ if (!existing) {
268
+ return { ok: false, error: err('not_found', 'comment not found') };
269
+ }
270
+ if (!actor.isAdmin && existing.userId !== actor.userId) {
271
+ return { ok: false, error: err('forbidden', 'only the author can delete this comment') };
272
+ }
273
+ if (existing.deletedAt !== null) {
274
+ return { ok: true, value: toCommentDTO(existing) };
275
+ }
276
+ const row = await db.documentComment.update({
277
+ where: { id },
278
+ data: { deletedAt: new Date() },
279
+ });
280
+ return { ok: true, value: toCommentDTO(row) };
281
+ }
282
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/realtime/comments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Realtime gateway for `@actuate-media/realtime` rooms.
3
+ *
4
+ * The gateway is **transport-agnostic by design**: it does not depend on
5
+ * `ws`, `uWebSockets.js`, Cloudflare Workers, or any specific framework.
6
+ * Consumers create a `WebSocketLike` adapter around their server's socket
7
+ * primitive and pass it to `gateway.handleConnection(socket, req)`.
8
+ *
9
+ * Responsibilities:
10
+ * 1. Authenticate the connection (delegated to `options.authenticate`).
11
+ * 2. Look up (or lazily create) the `Room` for the document.
12
+ * 3. Pump bytes between the socket and the room.
13
+ * 4. Persist edits via the configured `DocumentPersistence`.
14
+ * 5. Reap idle rooms (no connections + no pending writes) to bound memory.
15
+ *
16
+ * The companion file `persistence.ts` provides
17
+ * `createPrismaDocumentPersistence(db)` which the gateway uses by default
18
+ * when callers don't supply their own.
19
+ */
20
+ import { type DocumentPersistence } from '@actuate-media/realtime';
21
+ /**
22
+ * Minimal contract the gateway needs from a real WebSocket implementation.
23
+ * Compatible with `ws.WebSocket`, browser-native `WebSocket`, the
24
+ * Cloudflare Workers `WebSocket` Hibernation API, and Bun's WebSocket.
25
+ *
26
+ * Adapter authors typically just wrap their library's socket in a thin
27
+ * shim that conforms to this shape.
28
+ */
29
+ export interface WebSocketLike {
30
+ /** Send a binary frame. */
31
+ send(data: Uint8Array): void;
32
+ /** Close the socket. Idempotent. */
33
+ close(code?: number, reason?: string): void;
34
+ /** Subscribe to inbound binary frames. */
35
+ on(event: 'message', handler: (data: Uint8Array) => void): void;
36
+ /** Subscribe to close events. */
37
+ on(event: 'close', handler: () => void): void;
38
+ /** Subscribe to error events. */
39
+ on(event: 'error', handler: (err: Error) => void): void;
40
+ }
41
+ /** Information about the inbound HTTP upgrade request. */
42
+ export interface UpgradeRequestInfo {
43
+ /** Path + query, e.g. `/api/cms/realtime/sync?documentId=doc_1`. */
44
+ url: string;
45
+ /** Lowercased header bag. Authentication usually reads `cookie` or `authorization`. */
46
+ headers: Readonly<Record<string, string | undefined>>;
47
+ }
48
+ /**
49
+ * Outcome of the authentication / routing decision. Return `null` to reject
50
+ * the connection (the gateway will close it with code 4401).
51
+ *
52
+ * `connectionId` should be globally unique within the gateway instance —
53
+ * usually a `crypto.randomUUID()` or `req.headers['sec-websocket-key']`.
54
+ */
55
+ export interface AuthenticatedConnection {
56
+ documentId: string;
57
+ connectionId: string;
58
+ /** Tag returned via `room.onUpdate` so applications can audit edits. */
59
+ userId?: string;
60
+ }
61
+ export interface AttachRealtimeGatewayOptions {
62
+ /**
63
+ * Validate the upgrade request and map it to a documentId. Return `null`
64
+ * to reject. Throws are treated as 401 and the connection is closed.
65
+ */
66
+ authenticate: (req: UpgradeRequestInfo) => Promise<AuthenticatedConnection | null>;
67
+ /** Storage adapter. Defaults to a noop in-memory persistence (rooms are not durable). */
68
+ persistence?: DocumentPersistence;
69
+ /**
70
+ * Reap a room after it has had zero connections for this many ms. Default 60000.
71
+ * Set to `Infinity` to disable reaping.
72
+ */
73
+ idleTimeoutMs?: number;
74
+ /** Debounce window for persistence writes (ms). Forwarded to `bindPersistence`. */
75
+ persistenceDebounceMs?: number;
76
+ /** Maximum time between writes (ms). Forwarded to `bindPersistence`. */
77
+ persistenceMaxIdleMs?: number;
78
+ /** Called whenever a room is created. Useful for metrics. */
79
+ onRoomOpen?: (documentId: string) => void;
80
+ /** Called whenever a room is destroyed. Useful for metrics. */
81
+ onRoomClose?: (documentId: string) => void;
82
+ /** Called when a persistence save fails. Defaults to console.error. */
83
+ onPersistenceError?: (documentId: string, err: unknown) => void;
84
+ }
85
+ export interface RealtimeGateway {
86
+ /** Number of currently active rooms. Useful for /health endpoints. */
87
+ readonly roomCount: number;
88
+ /** Number of currently connected sockets across all rooms. */
89
+ readonly connectionCount: number;
90
+ /**
91
+ * Bind an authenticated socket to its room. Call this from your HTTP
92
+ * upgrade handler after `wss.handleUpgrade(...)` has produced a socket.
93
+ */
94
+ handleConnection(socket: WebSocketLike, req: UpgradeRequestInfo): Promise<void>;
95
+ /**
96
+ * Force-close every connection, flush every room, and tear everything
97
+ * down. Call from your process's SIGTERM handler.
98
+ */
99
+ shutdown(): Promise<void>;
100
+ }
101
+ /**
102
+ * Create a realtime gateway. The returned object is intended to live for the
103
+ * lifetime of the server process — one per node. Calling
104
+ * `shutdown()` is mandatory for graceful exit (it flushes pending writes).
105
+ */
106
+ export declare function createRealtimeGateway(opts: AttachRealtimeGatewayOptions): RealtimeGateway;
107
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/realtime/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,yBAAyB,CAAA;AAEhC;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IAC5B,oCAAoC;IACpC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,0CAA0C;IAC1C,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAA;IAC/D,iCAAiC;IACjC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC7C,iCAAiC;IACjC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAA;CACxD;AAED,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAA;IACX,uFAAuF;IACvF,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;CACtD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAClF,yFAAyF;IACzF,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAChE;AAED,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/E;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,4BAA4B,GAAG,eAAe,CAuLzF"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Realtime gateway for `@actuate-media/realtime` rooms.
3
+ *
4
+ * The gateway is **transport-agnostic by design**: it does not depend on
5
+ * `ws`, `uWebSockets.js`, Cloudflare Workers, or any specific framework.
6
+ * Consumers create a `WebSocketLike` adapter around their server's socket
7
+ * primitive and pass it to `gateway.handleConnection(socket, req)`.
8
+ *
9
+ * Responsibilities:
10
+ * 1. Authenticate the connection (delegated to `options.authenticate`).
11
+ * 2. Look up (or lazily create) the `Room` for the document.
12
+ * 3. Pump bytes between the socket and the room.
13
+ * 4. Persist edits via the configured `DocumentPersistence`.
14
+ * 5. Reap idle rooms (no connections + no pending writes) to bound memory.
15
+ *
16
+ * The companion file `persistence.ts` provides
17
+ * `createPrismaDocumentPersistence(db)` which the gateway uses by default
18
+ * when callers don't supply their own.
19
+ */
20
+ import { bindPersistence, createRoom, } from '@actuate-media/realtime';
21
+ /**
22
+ * Create a realtime gateway. The returned object is intended to live for the
23
+ * lifetime of the server process — one per node. Calling
24
+ * `shutdown()` is mandatory for graceful exit (it flushes pending writes).
25
+ */
26
+ export function createRealtimeGateway(opts) {
27
+ const persistence = opts.persistence ?? createNoopPersistence();
28
+ const idleTimeoutMs = opts.idleTimeoutMs ?? 60_000;
29
+ const onError = opts.onPersistenceError ??
30
+ ((documentId, err) => {
31
+ // eslint-disable-next-line no-console
32
+ console.error(`[cms-core realtime] save failed for ${documentId}:`, err);
33
+ });
34
+ const rooms = new Map();
35
+ let connectionCount = 0;
36
+ let shuttingDown = false;
37
+ async function getOrCreateRoom(documentId) {
38
+ const existing = rooms.get(documentId);
39
+ if (existing) {
40
+ if (existing.reapTimer) {
41
+ clearTimeout(existing.reapTimer);
42
+ existing.reapTimer = null;
43
+ }
44
+ return existing;
45
+ }
46
+ const initial = (await persistence.load(documentId)) ?? undefined;
47
+ const room = createRoom({ documentId, initialState: initial });
48
+ const binding = bindPersistence(room, persistence, {
49
+ debounceMs: opts.persistenceDebounceMs,
50
+ maxIdleMs: opts.persistenceMaxIdleMs,
51
+ onError: (err) => onError(documentId, err),
52
+ });
53
+ const entry = { room, binding, connections: 0, reapTimer: null };
54
+ rooms.set(documentId, entry);
55
+ opts.onRoomOpen?.(documentId);
56
+ return entry;
57
+ }
58
+ function scheduleReap(documentId) {
59
+ const entry = rooms.get(documentId);
60
+ if (!entry)
61
+ return;
62
+ if (entry.connections > 0)
63
+ return;
64
+ if (entry.reapTimer)
65
+ return;
66
+ if (!Number.isFinite(idleTimeoutMs))
67
+ return;
68
+ entry.reapTimer = setTimeout(() => {
69
+ void destroyRoom(documentId);
70
+ }, idleTimeoutMs);
71
+ }
72
+ async function destroyRoom(documentId) {
73
+ const entry = rooms.get(documentId);
74
+ if (!entry)
75
+ return;
76
+ rooms.delete(documentId);
77
+ if (entry.reapTimer) {
78
+ clearTimeout(entry.reapTimer);
79
+ entry.reapTimer = null;
80
+ }
81
+ try {
82
+ await entry.binding.flush();
83
+ }
84
+ catch (err) {
85
+ onError(documentId, err);
86
+ }
87
+ entry.binding.unbind();
88
+ entry.room.destroy();
89
+ opts.onRoomClose?.(documentId);
90
+ }
91
+ async function handleConnection(socket, req) {
92
+ if (shuttingDown) {
93
+ try {
94
+ socket.close(1001, 'server shutting down');
95
+ }
96
+ catch {
97
+ /* ignore */
98
+ }
99
+ return;
100
+ }
101
+ let auth;
102
+ try {
103
+ auth = await opts.authenticate(req);
104
+ }
105
+ catch {
106
+ auth = null;
107
+ }
108
+ if (!auth) {
109
+ try {
110
+ socket.close(4401, 'unauthorized');
111
+ }
112
+ catch {
113
+ /* ignore */
114
+ }
115
+ return;
116
+ }
117
+ const entry = await getOrCreateRoom(auth.documentId);
118
+ if (entry.reapTimer) {
119
+ clearTimeout(entry.reapTimer);
120
+ entry.reapTimer = null;
121
+ }
122
+ const conn = {
123
+ id: auth.connectionId,
124
+ send: (bytes) => {
125
+ try {
126
+ socket.send(bytes);
127
+ }
128
+ catch {
129
+ /* surfaced via close handler */
130
+ }
131
+ },
132
+ close: (reason) => {
133
+ try {
134
+ socket.close(1000, reason ?? 'closed');
135
+ }
136
+ catch {
137
+ /* ignore */
138
+ }
139
+ },
140
+ };
141
+ try {
142
+ entry.room.addConnection(conn);
143
+ }
144
+ catch {
145
+ // duplicate connection id — close and bail out.
146
+ try {
147
+ socket.close(4409, 'duplicate connection id');
148
+ }
149
+ catch {
150
+ /* ignore */
151
+ }
152
+ return;
153
+ }
154
+ entry.connections += 1;
155
+ connectionCount += 1;
156
+ socket.on('message', (data) => {
157
+ try {
158
+ entry.room.handleMessage(auth.connectionId, data);
159
+ }
160
+ catch {
161
+ // Malformed frame — close the offending peer; other connections continue.
162
+ try {
163
+ socket.close(1003, 'malformed frame');
164
+ }
165
+ catch {
166
+ /* ignore */
167
+ }
168
+ }
169
+ });
170
+ socket.on('error', () => {
171
+ try {
172
+ socket.close(1011, 'socket error');
173
+ }
174
+ catch {
175
+ /* ignore */
176
+ }
177
+ });
178
+ socket.on('close', () => {
179
+ entry.room.removeConnection(auth.connectionId);
180
+ entry.connections = Math.max(0, entry.connections - 1);
181
+ connectionCount = Math.max(0, connectionCount - 1);
182
+ if (entry.connections === 0) {
183
+ scheduleReap(auth.documentId);
184
+ }
185
+ });
186
+ }
187
+ async function shutdown() {
188
+ shuttingDown = true;
189
+ const docIds = Array.from(rooms.keys());
190
+ await Promise.all(docIds.map((id) => destroyRoom(id)));
191
+ connectionCount = 0;
192
+ }
193
+ return {
194
+ get roomCount() {
195
+ return rooms.size;
196
+ },
197
+ get connectionCount() {
198
+ return connectionCount;
199
+ },
200
+ handleConnection,
201
+ shutdown,
202
+ };
203
+ }
204
+ /** Persistence that swallows writes — used when consumers don't pass one. */
205
+ function createNoopPersistence() {
206
+ return {
207
+ async load() {
208
+ return null;
209
+ },
210
+ async save() {
211
+ /* discard */
212
+ },
213
+ };
214
+ }
215
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/realtime/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Realtime collaboration surface for `@actuate-media/cms-core`.
3
+ *
4
+ * - `createRealtimeGateway` — transport-agnostic gateway that owns rooms,
5
+ * debounces persistence, and reaps idle documents. Consumers wrap their
6
+ * server's socket primitive in a `WebSocketLike` adapter and feed it via
7
+ * `gateway.handleConnection(socket, req)`.
8
+ * - `createPrismaDocumentPersistence` — Prisma-backed implementation of
9
+ * `DocumentPersistence` against the `DocumentCRDT` table.
10
+ *
11
+ * See `packages/realtime/README.md` for the protocol details and
12
+ * `docs/roadmap-status.md` for the Phase 3 slice plan.
13
+ */
14
+ export { createRealtimeGateway, type AttachRealtimeGatewayOptions, type AuthenticatedConnection, type RealtimeGateway, type UpgradeRequestInfo, type WebSocketLike, } from './gateway.js';
15
+ export { createPrismaDocumentPersistence, type PrismaDocumentCRDTClient, type PrismaDocumentPersistenceOptions, } from './persistence.js';
16
+ export { createComment, deleteComment, listComments, normaliseBody, parseAnchor, reopenComment, resolveComment, toCommentDTO, updateComment, DEFAULT_COMMENT_LIST_PAGE_SIZE, MAX_COMMENT_BODY_LENGTH, MAX_COMMENT_LIST_PAGE_SIZE, type CommentAnchor, type CommentDTO, type CommentsDB, type CommentsError, type CommentsResult, type CreateCommentInput, type DocumentCommentRow, type ListCommentsFilter, type UpdateCommentInput, } from './comments.js';
17
+ export { createNotification, excerptForNotification, extractMentionedUserIds, isNotificationKind, listForUser as listNotificationsForUser, markAllRead as markAllNotificationsRead, markRead as markNotificationRead, toNotificationDTO, unreadCount as notificationsUnreadCount, DEFAULT_NOTIFICATION_LIST_PAGE_SIZE, MAX_NOTIFICATION_LIST_PAGE_SIZE, type CreateNotificationInput, type DocumentNotificationRow, type ListNotificationsFilter, type NotificationDTO, type NotificationKind, type NotificationPayload, type NotificationsDB, type NotificationsError, type NotificationsResult, } from './notifications.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,+BAA+B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,GACtC,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,8BAA8B,EAC9B,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,IAAI,wBAAwB,EACvC,WAAW,IAAI,wBAAwB,EACvC,QAAQ,IAAI,oBAAoB,EAChC,iBAAiB,EACjB,WAAW,IAAI,wBAAwB,EACvC,mCAAmC,EACnC,+BAA+B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAA"}