@actuate-media/cms-core 0.22.0 → 0.24.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 (38) hide show
  1. package/dist/__tests__/api/collections-discovery.test.d.ts +2 -0
  2. package/dist/__tests__/api/collections-discovery.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/collections-discovery.test.js +129 -0
  4. package/dist/__tests__/api/collections-discovery.test.js.map +1 -0
  5. package/dist/__tests__/api/notifications-routes.test.js +45 -0
  6. package/dist/__tests__/api/notifications-routes.test.js.map +1 -1
  7. package/dist/__tests__/realtime/notification-bus.test.d.ts +2 -0
  8. package/dist/__tests__/realtime/notification-bus.test.d.ts.map +1 -0
  9. package/dist/__tests__/realtime/notification-bus.test.js +79 -0
  10. package/dist/__tests__/realtime/notification-bus.test.js.map +1 -0
  11. package/dist/__tests__/realtime/notification-stream.test.d.ts +2 -0
  12. package/dist/__tests__/realtime/notification-stream.test.d.ts.map +1 -0
  13. package/dist/__tests__/realtime/notification-stream.test.js +157 -0
  14. package/dist/__tests__/realtime/notification-stream.test.js.map +1 -0
  15. package/dist/api/handlers.d.ts.map +1 -1
  16. package/dist/api/handlers.js +79 -3
  17. package/dist/api/handlers.js.map +1 -1
  18. package/dist/preview/sse-stream.d.ts +2 -5
  19. package/dist/preview/sse-stream.d.ts.map +1 -1
  20. package/dist/preview/sse-stream.js +7 -16
  21. package/dist/preview/sse-stream.js.map +1 -1
  22. package/dist/realtime/index.d.ts +3 -0
  23. package/dist/realtime/index.d.ts.map +1 -1
  24. package/dist/realtime/index.js +9 -0
  25. package/dist/realtime/index.js.map +1 -1
  26. package/dist/realtime/notification-bus.d.ts +60 -0
  27. package/dist/realtime/notification-bus.d.ts.map +1 -0
  28. package/dist/realtime/notification-bus.js +77 -0
  29. package/dist/realtime/notification-bus.js.map +1 -0
  30. package/dist/realtime/notification-stream.d.ts +50 -0
  31. package/dist/realtime/notification-stream.d.ts.map +1 -0
  32. package/dist/realtime/notification-stream.js +148 -0
  33. package/dist/realtime/notification-stream.js.map +1 -0
  34. package/dist/realtime/sse.d.ts +20 -0
  35. package/dist/realtime/sse.d.ts.map +1 -0
  36. package/dist/realtime/sse.js +31 -0
  37. package/dist/realtime/sse.js.map +1 -0
  38. package/package.json +1 -1
@@ -17,6 +17,8 @@ import { createPreviewStreamResponse } from '../preview/sse-stream.js';
17
17
  import { notifyPreviewUpdate } from '../preview/event-bus.js';
18
18
  import { createComment as createCommentService, deleteComment as deleteCommentService, listComments as listCommentsService, reopenComment as reopenCommentService, resolveComment as resolveCommentService, updateComment as updateCommentService, } from '../realtime/comments.js';
19
19
  import { createNotification, excerptForNotification, extractMentionedUserIds, listForUser as listNotificationsForUser, markAllRead as markAllNotificationsRead, markRead as markNotificationRead, unreadCount as notificationsUnreadCount, } from '../realtime/notifications.js';
20
+ import { publishNotification } from '../realtime/notification-bus.js';
21
+ import { createNotificationStreamResponse } from '../realtime/notification-stream.js';
20
22
  import { schedulingCronHandler } from '../scheduling/index.js';
21
23
  import { isAuthorizedCronRequest, processCleanup, processSeoScan } from '../cron/index.js';
22
24
  import { verifyCaptcha, getCaptchaConfig } from '../security/captcha.js';
@@ -776,6 +778,44 @@ export function registerCMSRoutes(router) {
776
778
  headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
777
779
  });
778
780
  });
781
+ // ---------------------------------------------------------------------------
782
+ // Collection discovery (Phase 7 PR F)
783
+ //
784
+ // Lightweight enumeration sourced directly from `getActuateConfig()` so
785
+ // agents and the MCP server's `list_collections` tool don't depend on
786
+ // OpenAPI spec generation succeeding. OpenAPI is still the source of
787
+ // truth for full per-field JSON Schemas; this endpoint trades depth
788
+ // for reliability and surfaces just enough metadata for the agent to
789
+ // plan a follow-up call (create_document, list_documents, etc.).
790
+ //
791
+ // Auth: unauthenticated, like /openapi.json — the response contains
792
+ // only schema-shape metadata that is already public via OpenAPI.
793
+ // ---------------------------------------------------------------------------
794
+ router.get('/collections', async () => {
795
+ const config = getActuateConfig();
796
+ if (!config)
797
+ return errorResponse('CMS not configured', 500);
798
+ const collections = Object.entries(config.collections ?? {}).map(([slug, def]) => {
799
+ const fields = (def?.fields ?? {});
800
+ // Reduce each field to `{ name, type, required }` — enough for the
801
+ // agent to plan a create/update without sending the full validator.
802
+ const fieldSummary = Object.entries(fields).map(([name, fieldDef]) => ({
803
+ name,
804
+ type: typeof fieldDef?.type === 'string' ? fieldDef.type : 'unknown',
805
+ required: fieldDef?.required === true,
806
+ }));
807
+ return {
808
+ slug,
809
+ labels: def?.labels ?? { singular: slug, plural: slug },
810
+ type: def?.type ?? 'page',
811
+ urlPrefix: def?.urlPrefix ?? slug,
812
+ hidden: def?.admin?.hidden === true,
813
+ fieldCount: fieldSummary.length,
814
+ fields: fieldSummary,
815
+ };
816
+ });
817
+ return json({ data: collections });
818
+ });
779
819
  router.get('/docs', async () => {
780
820
  const html = `<!DOCTYPE html>
781
821
  <html>
@@ -4672,7 +4712,7 @@ export function registerCMSRoutes(router) {
4672
4712
  if (opts.parentId) {
4673
4713
  const root = await findCommentRecipient(opts.parentId);
4674
4714
  if (root && root.userId && root.userId !== opts.actorId) {
4675
- await createNotification(ndb, {
4715
+ const result = await createNotification(ndb, {
4676
4716
  userId: root.userId,
4677
4717
  documentId: opts.documentId,
4678
4718
  commentId: opts.commentId,
@@ -4683,12 +4723,21 @@ export function registerCMSRoutes(router) {
4683
4723
  excerpt: excerptForNotification(opts.body),
4684
4724
  },
4685
4725
  });
4726
+ // Push the new DTO to any active SSE subscribers for this user
4727
+ // so the bell badge bumps in real time. We only publish AFTER
4728
+ // the DB insert succeeds — otherwise an SSE client would see a
4729
+ // ghost notification that doesn't exist on a subsequent
4730
+ // refresh. Failure to publish (no subscribers, or bus error)
4731
+ // is non-fatal: the poll fallback in the admin UI still
4732
+ // converges.
4733
+ if (result.ok)
4734
+ publishNotification(result.value);
4686
4735
  }
4687
4736
  }
4688
4737
  for (const userId of extractMentionedUserIds(opts.body)) {
4689
4738
  if (userId === opts.actorId)
4690
4739
  continue;
4691
- await createNotification(ndb, {
4740
+ const result = await createNotification(ndb, {
4692
4741
  userId,
4693
4742
  documentId: opts.documentId,
4694
4743
  commentId: opts.commentId,
@@ -4699,6 +4748,8 @@ export function registerCMSRoutes(router) {
4699
4748
  excerpt: excerptForNotification(opts.body),
4700
4749
  },
4701
4750
  });
4751
+ if (result.ok)
4752
+ publishNotification(result.value);
4702
4753
  }
4703
4754
  }
4704
4755
  /** Notify the comment's author when someone else resolves their thread. */
@@ -4706,7 +4757,7 @@ export function registerCMSRoutes(router) {
4706
4757
  const comment = await findCommentRecipient(opts.commentId);
4707
4758
  if (!comment || !comment.userId || comment.userId === opts.actorId)
4708
4759
  return;
4709
- await createNotification(notificationsDb(), {
4760
+ const result = await createNotification(notificationsDb(), {
4710
4761
  userId: comment.userId,
4711
4762
  commentId: opts.commentId,
4712
4763
  payload: {
@@ -4716,6 +4767,8 @@ export function registerCMSRoutes(router) {
4716
4767
  rootExcerpt: excerptForNotification(comment.body),
4717
4768
  },
4718
4769
  });
4770
+ if (result.ok)
4771
+ publishNotification(result.value);
4719
4772
  }
4720
4773
  router.post('/documents/:documentId/comments', async (request, params) => {
4721
4774
  try {
@@ -4967,6 +5020,29 @@ export function registerCMSRoutes(router) {
4967
5020
  return internalError(err, 'mark all notifications read');
4968
5021
  }
4969
5022
  });
5023
+ // Per-user notification SSE push channel (Phase 7 carry-over). The
5024
+ // admin's NotificationBell subscribes via EventSource for real-time
5025
+ // delivery of comment_reply / comment_mention / comment_resolved
5026
+ // events. The existing GET /notifications and
5027
+ // /notifications/unread-count REST endpoints are kept as a
5028
+ // poll-fallback path for multi-node deployments where a write on
5029
+ // node A doesn't reach SSE subscribers on node B (the in-process
5030
+ // bus is single-instance by design — see notification-bus.ts).
5031
+ //
5032
+ // Auth: same scope as every other /notifications route — the
5033
+ // session userId determines which channel the stream subscribes to;
5034
+ // there is no admin override.
5035
+ router.get('/notifications/stream', async (request) => {
5036
+ try {
5037
+ const auth = await requireAuth(request);
5038
+ if (auth.error)
5039
+ return auth.error;
5040
+ return createNotificationStreamResponse(auth.session.userId);
5041
+ }
5042
+ catch (err) {
5043
+ return internalError(err, 'notifications stream');
5044
+ }
5045
+ });
4970
5046
  // ---------------------------------------------------------------------------
4971
5047
  // Workflow routes
4972
5048
  // ---------------------------------------------------------------------------