@ttt-productions/notification-core 0.15.17 → 0.15.18

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 (87) hide show
  1. package/README.md +9 -10
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +0 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/react/components/NotificationEmptyState.d.ts +0 -3
  6. package/dist/react/components/NotificationEmptyState.d.ts.map +1 -1
  7. package/dist/react/components/NotificationEmptyState.js +0 -3
  8. package/dist/react/components/NotificationEmptyState.js.map +1 -1
  9. package/dist/react/components/NotificationHistoryList.d.ts +0 -6
  10. package/dist/react/components/NotificationHistoryList.d.ts.map +1 -1
  11. package/dist/react/components/NotificationHistoryList.js +0 -8
  12. package/dist/react/components/NotificationHistoryList.js.map +1 -1
  13. package/dist/react/components/NotificationList.d.ts +0 -3
  14. package/dist/react/components/NotificationList.d.ts.map +1 -1
  15. package/dist/react/components/NotificationList.js +2 -29
  16. package/dist/react/components/NotificationList.js.map +1 -1
  17. package/dist/react/components/NotificationUnreadBadge.d.ts +0 -5
  18. package/dist/react/components/NotificationUnreadBadge.d.ts.map +1 -1
  19. package/dist/react/components/NotificationUnreadBadge.js +0 -5
  20. package/dist/react/components/NotificationUnreadBadge.js.map +1 -1
  21. package/dist/react/components/relative-time.d.ts +0 -3
  22. package/dist/react/components/relative-time.d.ts.map +1 -1
  23. package/dist/react/components/relative-time.js +0 -3
  24. package/dist/react/components/relative-time.js.map +1 -1
  25. package/dist/react/hooks/useActiveNotifications.d.ts +0 -4
  26. package/dist/react/hooks/useActiveNotifications.d.ts.map +1 -1
  27. package/dist/react/hooks/useActiveNotifications.js +2 -6
  28. package/dist/react/hooks/useActiveNotifications.js.map +1 -1
  29. package/dist/react/hooks/useArchiveAllNotifications.d.ts +0 -30
  30. package/dist/react/hooks/useArchiveAllNotifications.d.ts.map +1 -1
  31. package/dist/react/hooks/useArchiveAllNotifications.js +1 -43
  32. package/dist/react/hooks/useArchiveAllNotifications.js.map +1 -1
  33. package/dist/react/hooks/useArchiveNotification.d.ts +0 -8
  34. package/dist/react/hooks/useArchiveNotification.d.ts.map +1 -1
  35. package/dist/react/hooks/useArchiveNotification.js +0 -8
  36. package/dist/react/hooks/useArchiveNotification.js.map +1 -1
  37. package/dist/react/hooks/useNotificationHistory.d.ts +0 -14
  38. package/dist/react/hooks/useNotificationHistory.d.ts.map +1 -1
  39. package/dist/react/hooks/useNotificationHistory.js +2 -18
  40. package/dist/react/hooks/useNotificationHistory.js.map +1 -1
  41. package/dist/react/hooks/useUnreadCount.d.ts +0 -22
  42. package/dist/react/hooks/useUnreadCount.d.ts.map +1 -1
  43. package/dist/react/hooks/useUnreadCount.js +1 -22
  44. package/dist/react/hooks/useUnreadCount.js.map +1 -1
  45. package/dist/react/index.d.ts.map +1 -1
  46. package/dist/react/index.js +0 -2
  47. package/dist/react/index.js.map +1 -1
  48. package/dist/react/types.d.ts +0 -42
  49. package/dist/react/types.d.ts.map +1 -1
  50. package/dist/react/types.js.map +1 -1
  51. package/dist/schemas.d.ts +0 -22
  52. package/dist/schemas.d.ts.map +1 -1
  53. package/dist/schemas.js +0 -25
  54. package/dist/schemas.js.map +1 -1
  55. package/dist/server/activeNotificationId.d.ts +1 -22
  56. package/dist/server/activeNotificationId.d.ts.map +1 -1
  57. package/dist/server/activeNotificationId.js +0 -16
  58. package/dist/server/activeNotificationId.js.map +1 -1
  59. package/dist/server/delivery-ledger.d.ts +1 -73
  60. package/dist/server/delivery-ledger.d.ts.map +1 -1
  61. package/dist/server/delivery-ledger.js +7 -59
  62. package/dist/server/delivery-ledger.js.map +1 -1
  63. package/dist/server/errors.d.ts +0 -9
  64. package/dist/server/errors.d.ts.map +1 -1
  65. package/dist/server/errors.js +1 -11
  66. package/dist/server/errors.js.map +1 -1
  67. package/dist/server/index.d.ts.map +1 -1
  68. package/dist/server/index.js +0 -3
  69. package/dist/server/index.js.map +1 -1
  70. package/dist/server/observed-generation.d.ts +1 -41
  71. package/dist/server/observed-generation.d.ts.map +1 -1
  72. package/dist/server/observed-generation.js +1 -31
  73. package/dist/server/observed-generation.js.map +1 -1
  74. package/dist/server/processBatchHelper.d.ts +0 -19
  75. package/dist/server/processBatchHelper.d.ts.map +1 -1
  76. package/dist/server/processBatchHelper.js +2 -46
  77. package/dist/server/processBatchHelper.js.map +1 -1
  78. package/dist/server/types.d.ts +0 -9
  79. package/dist/server/types.d.ts.map +1 -1
  80. package/dist/server/types.js +0 -4
  81. package/dist/server/types.js.map +1 -1
  82. package/dist/types.d.ts +2 -196
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js +0 -6
  85. package/dist/types.js.map +1 -1
  86. package/package.json +5 -5
  87. package/src/styles/notifications.css +0 -14
@@ -1,40 +1,10 @@
1
1
  'use client';
2
2
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
3
  const DEFAULT_POLL_INTERVAL_MS = 1500;
4
- const DEFAULT_MAX_POLLS = 120; // ~3 min at the default interval
4
+ const DEFAULT_MAX_POLLS = 120;
5
5
  function sleep(ms) {
6
6
  return new Promise((resolve) => setTimeout(resolve, ms));
7
7
  }
8
- /**
9
- * Trigger + poll a SERVER-OWNED archive-all job for one notification category.
10
- *
11
- * This is a THIN STATUS POLLER, not a client loop. It:
12
- * 1. enqueues ONE server job via `enqueueArchiveAllFn(category)` (the category — the tab the tray
13
- * was invoked on — flows into the job, preserving per-tab scoping: the worker archives ONLY
14
- * that category's active cards, never all tabs);
15
- * 2. polls `getArchiveAllStatusFn(jobId)` until the job reaches a terminal state
16
- * (`complete` | `incomplete` | `failed`);
17
- * 3. resolves an {@link ArchiveAllPollResult} the caller gates side effects on, and invalidates
18
- * the read keys on a completed drain.
19
- *
20
- * All paging + ownership-checked per-card deletion happen server-side. The hook performs no client
21
- * Firestore writes and never chains archive calls. The generic package hardcodes no callable name —
22
- * the app injects both adapters.
23
- *
24
- * @example
25
- * ```tsx
26
- * const enqueue = httpsCallable(functions, 'enqueueArchiveAll');
27
- * const getStatus = httpsCallable(functions, 'getArchiveAllStatus');
28
- * const archiveAll = useArchiveAllNotifications({
29
- * userId: user.uid,
30
- * category: 'user',
31
- * enqueueArchiveAllFn: (category) => enqueue({ category }).then((r) => r.data),
32
- * getArchiveAllStatusFn: (jobId) => getStatus({ jobId }).then((r) => r.data),
33
- * });
34
- *
35
- * archiveAll.mutate();
36
- * ```
37
- */
38
8
  export function useArchiveAllNotifications({ userId, category, enqueueArchiveAllFn, getArchiveAllStatusFn, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, maxPolls = DEFAULT_MAX_POLLS, invalidateKeys, }) {
39
9
  const queryClient = useQueryClient();
40
10
  const defaultInvalidateKeys = [
@@ -43,15 +13,8 @@ export function useArchiveAllNotifications({ userId, category, enqueueArchiveAll
43
13
  ['notifications', 'history', category, userId],
44
14
  ];
45
15
  return useMutation({
46
- // Server-owned archive-all: enqueue ONE job scoped to `category`, then poll its status until it
47
- // reaches a terminal state. No browser loop, no chain of archive transactions. The mutation
48
- // resolves with an explicit terminal result (never throws for an incomplete drain) — `complete`
49
- // is the load-bearing flag callers gate post-clear side effects on.
50
16
  mutationFn: async () => {
51
17
  const { jobId } = await enqueueArchiveAllFn(category);
52
- // Poll until terminal or until the guard bound is hit. A job that never terminates resolves
53
- // `incomplete` (mirrors the old loop's honest "some remain" result) rather than hanging. The
54
- // first poll happens immediately so a fast/already-complete job resolves without a delay.
55
18
  let lastArchived = 0;
56
19
  for (let poll = 0; poll < maxPolls; poll += 1) {
57
20
  const snap = await getArchiveAllStatusFn(jobId);
@@ -84,11 +47,9 @@ export function useArchiveAllNotifications({ userId, category, enqueueArchiveAll
84
47
  error: snap.error,
85
48
  };
86
49
  }
87
- // Still in-progress — wait before the next poll unless this was the last allowed poll.
88
50
  if (poll < maxPolls - 1)
89
51
  await sleep(pollIntervalMs);
90
52
  }
91
- // Guard: the job never reached a terminal state within the poll budget — report incomplete.
92
53
  return {
93
54
  status: 'incomplete',
94
55
  category,
@@ -98,9 +59,6 @@ export function useArchiveAllNotifications({ userId, category, enqueueArchiveAll
98
59
  };
99
60
  },
100
61
  onSuccess: (result) => {
101
- // Only a completed drain changed the active set; still invalidate on an incomplete drain since
102
- // SOME cards were archived. A hard enqueue/poll failure rejects the mutation (onError), so this
103
- // never runs for `failed`.
104
62
  const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;
105
63
  keysToInvalidate.forEach((key) => {
106
64
  queryClient.invalidateQueries({ queryKey: [...key], exact: false });
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveAllNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { ArchiveAllPollResult, UseArchiveAllNotificationsOptions } from '../../types.js';\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_MAX_POLLS = 120; // ~3 min at the default interval\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Trigger + poll a SERVER-OWNED archive-all job for one notification category.\n *\n * This is a THIN STATUS POLLER, not a client loop. It:\n * 1. enqueues ONE server job via `enqueueArchiveAllFn(category)` (the category — the tab the tray\n * was invoked on — flows into the job, preserving per-tab scoping: the worker archives ONLY\n * that category's active cards, never all tabs);\n * 2. polls `getArchiveAllStatusFn(jobId)` until the job reaches a terminal state\n * (`complete` | `incomplete` | `failed`);\n * 3. resolves an {@link ArchiveAllPollResult} the caller gates side effects on, and invalidates\n * the read keys on a completed drain.\n *\n * All paging + ownership-checked per-card deletion happen server-side. The hook performs no client\n * Firestore writes and never chains archive calls. The generic package hardcodes no callable name —\n * the app injects both adapters.\n *\n * @example\n * ```tsx\n * const enqueue = httpsCallable(functions, 'enqueueArchiveAll');\n * const getStatus = httpsCallable(functions, 'getArchiveAllStatus');\n * const archiveAll = useArchiveAllNotifications({\n * userId: user.uid,\n * category: 'user',\n * enqueueArchiveAllFn: (category) => enqueue({ category }).then((r) => r.data),\n * getArchiveAllStatusFn: (jobId) => getStatus({ jobId }).then((r) => r.data),\n * });\n *\n * archiveAll.mutate();\n * ```\n */\nexport function useArchiveAllNotifications({\n userId,\n category,\n enqueueArchiveAllFn,\n getArchiveAllStatusFn,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n maxPolls = DEFAULT_MAX_POLLS,\n invalidateKeys,\n}: UseArchiveAllNotificationsOptions) {\n const queryClient = useQueryClient();\n\n const defaultInvalidateKeys = [\n ['notifications', 'active', category, userId],\n ['notifications', 'unread-count', category, userId],\n ['notifications', 'history', category, userId],\n ];\n\n return useMutation({\n // Server-owned archive-all: enqueue ONE job scoped to `category`, then poll its status until it\n // reaches a terminal state. No browser loop, no chain of archive transactions. The mutation\n // resolves with an explicit terminal result (never throws for an incomplete drain) — `complete`\n // is the load-bearing flag callers gate post-clear side effects on.\n mutationFn: async (): Promise<ArchiveAllPollResult> => {\n const { jobId } = await enqueueArchiveAllFn(category);\n\n // Poll until terminal or until the guard bound is hit. A job that never terminates resolves\n // `incomplete` (mirrors the old loop's honest \"some remain\" result) rather than hanging. The\n // first poll happens immediately so a fast/already-complete job resolves without a delay.\n let lastArchived = 0;\n for (let poll = 0; poll < maxPolls; poll += 1) {\n const snap = await getArchiveAllStatusFn(jobId);\n lastArchived = snap.archived ?? 0;\n\n if (snap.state === 'complete') {\n return {\n status: 'complete',\n category,\n archived: lastArchived,\n notArchived: snap.notArchived ?? 0,\n complete: true,\n };\n }\n if (snap.state === 'incomplete') {\n return {\n status: 'incomplete',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n };\n }\n if (snap.state === 'failed') {\n return {\n status: 'failed',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n error: snap.error,\n };\n }\n\n // Still in-progress — wait before the next poll unless this was the last allowed poll.\n if (poll < maxPolls - 1) await sleep(pollIntervalMs);\n }\n\n // Guard: the job never reached a terminal state within the poll budget — report incomplete.\n return {\n status: 'incomplete',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n };\n },\n onSuccess: (result) => {\n // Only a completed drain changed the active set; still invalidate on an incomplete drain since\n // SOME cards were archived. A hard enqueue/poll failure rejects the mutation (onError), so this\n // never runs for `failed`.\n const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;\n keysToInvalidate.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key], exact: false });\n });\n void result;\n },\n });\n}\n"]}
1
+ {"version":3,"file":"useArchiveAllNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { ArchiveAllPollResult, UseArchiveAllNotificationsOptions } from '../../types.js';\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_MAX_POLLS = 120;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function useArchiveAllNotifications({\n userId,\n category,\n enqueueArchiveAllFn,\n getArchiveAllStatusFn,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n maxPolls = DEFAULT_MAX_POLLS,\n invalidateKeys,\n}: UseArchiveAllNotificationsOptions) {\n const queryClient = useQueryClient();\n\n const defaultInvalidateKeys = [\n ['notifications', 'active', category, userId],\n ['notifications', 'unread-count', category, userId],\n ['notifications', 'history', category, userId],\n ];\n\n return useMutation({\n mutationFn: async (): Promise<ArchiveAllPollResult> => {\n const { jobId } = await enqueueArchiveAllFn(category);\n\n let lastArchived = 0;\n for (let poll = 0; poll < maxPolls; poll += 1) {\n const snap = await getArchiveAllStatusFn(jobId);\n lastArchived = snap.archived ?? 0;\n\n if (snap.state === 'complete') {\n return {\n status: 'complete',\n category,\n archived: lastArchived,\n notArchived: snap.notArchived ?? 0,\n complete: true,\n };\n }\n if (snap.state === 'incomplete') {\n return {\n status: 'incomplete',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n };\n }\n if (snap.state === 'failed') {\n return {\n status: 'failed',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n error: snap.error,\n };\n }\n\n if (poll < maxPolls - 1) await sleep(pollIntervalMs);\n }\n\n return {\n status: 'incomplete',\n category,\n archived: lastArchived,\n notArchived: 0,\n complete: false,\n };\n },\n onSuccess: (result) => {\n const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;\n keysToInvalidate.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key], exact: false });\n });\n void result;\n },\n });\n}\n"]}
@@ -1,11 +1,3 @@
1
1
  import type { NotificationArchiveResult, NotificationDoc, UseArchiveNotificationOptions } from '../../types.js';
2
- /**
3
- * Archive a single rendered notification through an app-supplied callable adapter.
4
- *
5
- * The notification system is Cloud-Functions-only: clients never write notification
6
- * docs. This hook performs no Firestore writes — it delegates to `archiveFn` with the
7
- * row as rendered, resolves the adapter's `{ archived }` answer, and invalidates the
8
- * read keys on success.
9
- */
10
2
  export declare function useArchiveNotification({ userId, category, archiveFn, invalidateKeys, }: UseArchiveNotificationOptions): import("@tanstack/react-query").UseMutationResult<NotificationArchiveResult, Error, NotificationDoc, unknown>;
11
3
  //# sourceMappingURL=useArchiveNotification.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveNotification.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,yBAAyB,EACzB,eAAe,EACf,6BAA6B,EAC9B,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACf,EAAE,6BAA6B,iHAkB/B"}
1
+ {"version":3,"file":"useArchiveNotification.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,yBAAyB,EACzB,eAAe,EACf,6BAA6B,EAC9B,MAAM,gBAAgB,CAAC;AAExB,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACf,EAAE,6BAA6B,iHAkB/B"}
@@ -1,13 +1,5 @@
1
1
  'use client';
2
2
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- /**
4
- * Archive a single rendered notification through an app-supplied callable adapter.
5
- *
6
- * The notification system is Cloud-Functions-only: clients never write notification
7
- * docs. This hook performs no Firestore writes — it delegates to `archiveFn` with the
8
- * row as rendered, resolves the adapter's `{ archived }` answer, and invalidates the
9
- * read keys on success.
10
- */
11
3
  export function useArchiveNotification({ userId, category, archiveFn, invalidateKeys, }) {
12
4
  const queryClient = useQueryClient();
13
5
  const defaultInvalidateKeys = [
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveNotification.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAOpE;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACgB;IAC9B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,qBAAqB,GAAG;QAC5B,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7C,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QACnD,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C,CAAC;IAEF,OAAO,WAAW,CAAoD;QACpE,UAAU,EAAE,CAAC,YAA6B,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC;QACtE,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,CAAC;YACjE,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n NotificationArchiveResult,\n NotificationDoc,\n UseArchiveNotificationOptions,\n} from '../../types.js';\n\n/**\n * Archive a single rendered notification through an app-supplied callable adapter.\n *\n * The notification system is Cloud-Functions-only: clients never write notification\n * docs. This hook performs no Firestore writes — it delegates to `archiveFn` with the\n * row as rendered, resolves the adapter's `{ archived }` answer, and invalidates the\n * read keys on success.\n */\nexport function useArchiveNotification({\n userId,\n category,\n archiveFn,\n invalidateKeys,\n}: UseArchiveNotificationOptions) {\n const queryClient = useQueryClient();\n\n const defaultInvalidateKeys = [\n ['notifications', 'active', category, userId],\n ['notifications', 'unread-count', category, userId],\n ['notifications', 'history', category, userId],\n ];\n\n return useMutation<NotificationArchiveResult, Error, NotificationDoc>({\n mutationFn: (notification: NotificationDoc) => archiveFn(notification),\n onSuccess: () => {\n const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;\n keysToInvalidate.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key], exact: false });\n });\n },\n });\n}\n"]}
1
+ {"version":3,"file":"useArchiveNotification.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAOpE,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACgB;IAC9B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,qBAAqB,GAAG;QAC5B,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7C,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QACnD,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C,CAAC;IAEF,OAAO,WAAW,CAAoD;QACpE,UAAU,EAAE,CAAC,YAA6B,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC;QACtE,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,CAAC;YACjE,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n NotificationArchiveResult,\n NotificationDoc,\n UseArchiveNotificationOptions,\n} from '../../types.js';\n\nexport function useArchiveNotification({\n userId,\n category,\n archiveFn,\n invalidateKeys,\n}: UseArchiveNotificationOptions) {\n const queryClient = useQueryClient();\n\n const defaultInvalidateKeys = [\n ['notifications', 'active', category, userId],\n ['notifications', 'unread-count', category, userId],\n ['notifications', 'history', category, userId],\n ];\n\n return useMutation<NotificationArchiveResult, Error, NotificationDoc>({\n mutationFn: (notification: NotificationDoc) => archiveFn(notification),\n onSuccess: () => {\n const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;\n keysToInvalidate.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key], exact: false });\n });\n },\n });\n}\n"]}
@@ -1,18 +1,4 @@
1
1
  import type { NotificationHistoryItem, UseNotificationHistoryOptions } from '../../types.js';
2
- /**
3
- * Paginated read of ARCHIVED notification history (the two-tier system's history
4
- * tier). Personal history lives under a per-user path
5
- * (`userProfiles/{uid}/notificationHistory`) and shared/admin history under a
6
- * top-level collection (`adminNotificationHistory`) — both resolved from the
7
- * category's `historyPath`, so no `where` clause is needed either way. Rows are
8
- * immutable and ordered newest-first by `archivedAt`.
9
- *
10
- * Each history doc is a wrapper (`archivedSnapshot` + archive metadata + a
11
- * native-TTL `expireAt`); the `select` mapper flattens it into a
12
- * {@link NotificationHistoryItem} so the read surface renders like the active
13
- * list. Owner-only (user) / admin-only (admin) reads are enforced by Firestore
14
- * rules — this hook performs no writes.
15
- */
16
2
  export declare function useNotificationHistory({ config, userId, category, enabled, pageSize, staleTime, }: UseNotificationHistoryOptions): {
17
3
  retryState: import("@ttt-productions/query-core/react").ReadRetryState<Error>;
18
4
  promise: Promise<import("@ttt-productions/query-core/react").PaginatedPage<import("@ttt-productions/query-core/types").WithId<NotificationHistoryItem>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"useNotificationHistory.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,uBAAuB,EACvB,6BAA6B,EAC9B,MAAM,gBAAgB,CAAC;AAKxB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,QAA4B,EAC5B,SAA8B,GAC/B,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgC/B"}
1
+ {"version":3,"file":"useNotificationHistory.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,uBAAuB,EACvB,6BAA6B,EAC9B,MAAM,gBAAgB,CAAC;AAKxB,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,QAA4B,EAC5B,SAA8B,GAC/B,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8B/B"}
@@ -3,20 +3,6 @@ import { paginatedPageKey, useFirestorePaginated, useReadRetryState, } from '@tt
3
3
  import { orderBy } from 'firebase/firestore';
4
4
  const DEFAULT_PAGE_SIZE = 20;
5
5
  const DEFAULT_STALE_TIME = 60_000;
6
- /**
7
- * Paginated read of ARCHIVED notification history (the two-tier system's history
8
- * tier). Personal history lives under a per-user path
9
- * (`userProfiles/{uid}/notificationHistory`) and shared/admin history under a
10
- * top-level collection (`adminNotificationHistory`) — both resolved from the
11
- * category's `historyPath`, so no `where` clause is needed either way. Rows are
12
- * immutable and ordered newest-first by `archivedAt`.
13
- *
14
- * Each history doc is a wrapper (`archivedSnapshot` + archive metadata + a
15
- * native-TTL `expireAt`); the `select` mapper flattens it into a
16
- * {@link NotificationHistoryItem} so the read surface renders like the active
17
- * list. Owner-only (user) / admin-only (admin) reads are enforced by Firestore
18
- * rules — this hook performs no writes.
19
- */
20
6
  export function useNotificationHistory({ config, userId, category, enabled = true, pageSize = DEFAULT_PAGE_SIZE, staleTime = DEFAULT_STALE_TIME, }) {
21
7
  const categoryConfig = config.categories[category];
22
8
  if (!categoryConfig) {
@@ -36,15 +22,13 @@ export function useNotificationHistory({ config, userId, category, enabled = tru
36
22
  const snapshot = (data.archivedSnapshot ?? {});
37
23
  return {
38
24
  ...snapshot,
39
- // The archive occurrence id is the stable history-doc key; expose
40
- // archivedAt for ordering/display.
41
25
  archiveOccurrenceId: data.id,
42
26
  archivedAt: data.archivedAt ?? snapshot.updatedAt ?? 0,
43
27
  };
44
28
  },
45
29
  });
46
- // The displayed page's failure held through its retry: a refetch of a never-succeeded page clears
47
- // `error` while it runs, so the list would drop its failure under the retry the person pressed.
30
+ // A refetch of a never-succeeded page clears `error` while it runs, so the failure is held
31
+ // through the retry.
48
32
  return { ...read, retryState: useReadRetryState(read, paginatedPageKey(queryKey, read.page)) };
49
33
  }
50
34
  //# sourceMappingURL=useNotificationHistory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNotificationHistory.js","sourceRoot":"","sources":["../../../src/react/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAOnE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,iBAAiB,EAC5B,SAAS,GAAG,kBAAkB,GACA;IAC9B,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,cAAc,GAAG,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1D,MAAM,WAAW,GAAsB,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,qBAAqB,CAA0B;QAC1D,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAoB,CAAC;YAClE,OAAO;gBACL,GAAG,QAAQ;gBACX,kEAAkE;gBAClE,mCAAmC;gBACnC,mBAAmB,EAAE,IAAI,CAAC,EAAE;gBAC5B,UAAU,EAAG,IAAI,CAAC,UAAiC,IAAI,QAAQ,CAAC,SAAS,IAAI,CAAC;aAC/E,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,kGAAkG;IAClG,gGAAgG;IAChG,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AACjG,CAAC","sourcesContent":["'use client';\n\nimport {\n paginatedPageKey,\n useFirestorePaginated,\n useReadRetryState,\n} from '@ttt-productions/query-core/react';\nimport { orderBy, type QueryConstraint } from 'firebase/firestore';\nimport type {\n NotificationDoc,\n NotificationHistoryItem,\n UseNotificationHistoryOptions,\n} from '../../types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME = 60_000;\n\n/**\n * Paginated read of ARCHIVED notification history (the two-tier system's history\n * tier). Personal history lives under a per-user path\n * (`userProfiles/{uid}/notificationHistory`) and shared/admin history under a\n * top-level collection (`adminNotificationHistory`) — both resolved from the\n * category's `historyPath`, so no `where` clause is needed either way. Rows are\n * immutable and ordered newest-first by `archivedAt`.\n *\n * Each history doc is a wrapper (`archivedSnapshot` + archive metadata + a\n * native-TTL `expireAt`); the `select` mapper flattens it into a\n * {@link NotificationHistoryItem} so the read surface renders like the active\n * list. Owner-only (user) / admin-only (admin) reads are enforced by Firestore\n * rules — this hook performs no writes.\n */\nexport function useNotificationHistory({\n config,\n userId,\n category,\n enabled = true,\n pageSize = DEFAULT_PAGE_SIZE,\n staleTime = DEFAULT_STALE_TIME,\n}: UseNotificationHistoryOptions) {\n const categoryConfig = config.categories[category];\n if (!categoryConfig) {\n throw new Error(`[notification-core] Unknown category: ${category}`);\n }\n\n const collectionPath = categoryConfig.historyPath(userId);\n\n const constraints: QueryConstraint[] = [orderBy('archivedAt', 'desc')];\n\n const queryKey = ['notifications', 'history', category, userId, { pageSize }];\n const read = useFirestorePaginated<NotificationHistoryItem>({\n collectionPath,\n queryKey,\n constraints,\n pageSize,\n enabled: enabled && !!userId,\n staleTime,\n select: (data) => {\n const snapshot = (data.archivedSnapshot ?? {}) as NotificationDoc;\n return {\n ...snapshot,\n // The archive occurrence id is the stable history-doc key; expose\n // archivedAt for ordering/display.\n archiveOccurrenceId: data.id,\n archivedAt: (data.archivedAt as number | undefined) ?? snapshot.updatedAt ?? 0,\n };\n },\n });\n // The displayed page's failure held through its retry: a refetch of a never-succeeded page clears\n // `error` while it runs, so the list would drop its failure under the retry the person pressed.\n return { ...read, retryState: useReadRetryState(read, paginatedPageKey(queryKey, read.page)) };\n}\n"]}
1
+ {"version":3,"file":"useNotificationHistory.js","sourceRoot":"","sources":["../../../src/react/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAOnE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,iBAAiB,EAC5B,SAAS,GAAG,kBAAkB,GACA;IAC9B,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,cAAc,GAAG,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1D,MAAM,WAAW,GAAsB,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,qBAAqB,CAA0B;QAC1D,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAoB,CAAC;YAClE,OAAO;gBACL,GAAG,QAAQ;gBACX,mBAAmB,EAAE,IAAI,CAAC,EAAE;gBAC5B,UAAU,EAAG,IAAI,CAAC,UAAiC,IAAI,QAAQ,CAAC,SAAS,IAAI,CAAC;aAC/E,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,2FAA2F;IAC3F,qBAAqB;IACrB,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AACjG,CAAC","sourcesContent":["'use client';\n\nimport {\n paginatedPageKey,\n useFirestorePaginated,\n useReadRetryState,\n} from '@ttt-productions/query-core/react';\nimport { orderBy, type QueryConstraint } from 'firebase/firestore';\nimport type {\n NotificationDoc,\n NotificationHistoryItem,\n UseNotificationHistoryOptions,\n} from '../../types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME = 60_000;\n\nexport function useNotificationHistory({\n config,\n userId,\n category,\n enabled = true,\n pageSize = DEFAULT_PAGE_SIZE,\n staleTime = DEFAULT_STALE_TIME,\n}: UseNotificationHistoryOptions) {\n const categoryConfig = config.categories[category];\n if (!categoryConfig) {\n throw new Error(`[notification-core] Unknown category: ${category}`);\n }\n\n const collectionPath = categoryConfig.historyPath(userId);\n\n const constraints: QueryConstraint[] = [orderBy('archivedAt', 'desc')];\n\n const queryKey = ['notifications', 'history', category, userId, { pageSize }];\n const read = useFirestorePaginated<NotificationHistoryItem>({\n collectionPath,\n queryKey,\n constraints,\n pageSize,\n enabled: enabled && !!userId,\n staleTime,\n select: (data) => {\n const snapshot = (data.archivedSnapshot ?? {}) as NotificationDoc;\n return {\n ...snapshot,\n archiveOccurrenceId: data.id,\n archivedAt: (data.archivedAt as number | undefined) ?? snapshot.updatedAt ?? 0,\n };\n },\n });\n // A refetch of a never-succeeded page clears `error` while it runs, so the failure is held\n // through the retry.\n return { ...read, retryState: useReadRetryState(read, paginatedPageKey(queryKey, read.page)) };\n}\n"]}
@@ -1,26 +1,4 @@
1
1
  import type { UseUnreadCountOptions } from '../../types.js';
2
- /**
3
- * Unread-count badge backed by a Firestore `count()` aggregation (a server-side
4
- * count, not a doc fetch), via query-core's `useFirestoreCount`. Polls for cost
5
- * control; displays capped (`hasMore` → `99+`).
6
- *
7
- * - **personal** categories count **unseen** active items (`seenAt == 0`),
8
- * scoped to the caller (`targetUserId == uid`);
9
- * - **shared** categories have no per-admin seen state, so the indicator is
10
- * existence-based — a count of all active items, with no `seenAt` predicate.
11
- *
12
- * The personal `seenAt == 0` predicate needs a composite index, captured as an
13
- * app-side index step.
14
- *
15
- * @example
16
- * ```tsx
17
- * const { count, isLoading } = useUnreadCount({
18
- * config: TTT_NOTIFICATION_CONFIG,
19
- * userId: user.uid,
20
- * category: 'user',
21
- * });
22
- * ```
23
- */
24
2
  export declare function useUnreadCount({ config, userId, category, enabled, refetchInterval, countLimit, }: UseUnreadCountOptions): {
25
3
  error: Error;
26
4
  isError: true;
@@ -1 +1 @@
1
- {"version":3,"file":"useUnreadCount.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,eAA0C,EAC1C,UAAgC,GACjC,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BvB"}
1
+ {"version":3,"file":"useUnreadCount.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAM5D,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,eAA0C,EAC1C,UAAgC,GACjC,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BvB"}
@@ -3,28 +3,7 @@ import { where } from 'firebase/firestore';
3
3
  import { useFirestoreCount } from '@ttt-productions/query-core/react';
4
4
  const DEFAULT_REFETCH_INTERVAL = 30_000;
5
5
  const DEFAULT_COUNT_LIMIT = 99;
6
- /**
7
- * Unread-count badge backed by a Firestore `count()` aggregation (a server-side
8
- * count, not a doc fetch), via query-core's `useFirestoreCount`. Polls for cost
9
- * control; displays capped (`hasMore` → `99+`).
10
- *
11
- * - **personal** categories count **unseen** active items (`seenAt == 0`),
12
- * scoped to the caller (`targetUserId == uid`);
13
- * - **shared** categories have no per-admin seen state, so the indicator is
14
- * existence-based — a count of all active items, with no `seenAt` predicate.
15
- *
16
- * The personal `seenAt == 0` predicate needs a composite index, captured as an
17
- * app-side index step.
18
- *
19
- * @example
20
- * ```tsx
21
- * const { count, isLoading } = useUnreadCount({
22
- * config: TTT_NOTIFICATION_CONFIG,
23
- * userId: user.uid,
24
- * category: 'user',
25
- * });
26
- * ```
27
- */
6
+ // The personal `seenAt == 0` predicate needs a composite index.
28
7
  export function useUnreadCount({ config, userId, category, enabled = true, refetchInterval = DEFAULT_REFETCH_INTERVAL, countLimit = DEFAULT_COUNT_LIMIT, }) {
29
8
  const categoryConfig = config.categories[category];
30
9
  if (!categoryConfig) {
@@ -1 +1 @@
1
- {"version":3,"file":"useUnreadCount.js","sourceRoot":"","sources":["../../../src/react/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,eAAe,GAAG,wBAAwB,EAC1C,UAAU,GAAG,mBAAmB,GACV;IACtB,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,cAAc,GAAG,cAAc,CAAC,UAAU,CAAC;IACjD,MAAM,UAAU,GAAG,cAAc,CAAC,YAAY,KAAK,UAAU,CAAC;IAE9D,MAAM,WAAW,GACf,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9F,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,iBAAiB,CAAC;QAC1C,cAAc;QACd,QAAQ,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7D,WAAW;QACX,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS,EAAE,eAAe;QAC1B,eAAe;KAChB,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC;IAExB,OAAO;QACL,KAAK;QACL,OAAO,EAAE,KAAK,GAAG,UAAU;QAC3B,GAAG,IAAI;KACR,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { where, type QueryConstraint } from 'firebase/firestore';\nimport { useFirestoreCount } from '@ttt-productions/query-core/react';\nimport type { UseUnreadCountOptions } from '../../types.js';\n\nconst DEFAULT_REFETCH_INTERVAL = 30_000;\nconst DEFAULT_COUNT_LIMIT = 99;\n\n/**\n * Unread-count badge backed by a Firestore `count()` aggregation (a server-side\n * count, not a doc fetch), via query-core's `useFirestoreCount`. Polls for cost\n * control; displays capped (`hasMore` → `99+`).\n *\n * - **personal** categories count **unseen** active items (`seenAt == 0`),\n * scoped to the caller (`targetUserId == uid`);\n * - **shared** categories have no per-admin seen state, so the indicator is\n * existence-based — a count of all active items, with no `seenAt` predicate.\n *\n * The personal `seenAt == 0` predicate needs a composite index, captured as an\n * app-side index step.\n *\n * @example\n * ```tsx\n * const { count, isLoading } = useUnreadCount({\n * config: TTT_NOTIFICATION_CONFIG,\n * userId: user.uid,\n * category: 'user',\n * });\n * ```\n */\nexport function useUnreadCount({\n config,\n userId,\n category,\n enabled = true,\n refetchInterval = DEFAULT_REFETCH_INTERVAL,\n countLimit = DEFAULT_COUNT_LIMIT,\n}: UseUnreadCountOptions) {\n const categoryConfig = config.categories[category];\n if (!categoryConfig) {\n throw new Error(`[notification-core] Unknown category: ${category}`);\n }\n\n const collectionPath = categoryConfig.activePath;\n const isPersonal = categoryConfig.audienceType === 'personal';\n\n const constraints: QueryConstraint[] =\n isPersonal && userId ? [where('targetUserId', '==', userId), where('seenAt', '==', 0)] : [];\n\n const { data, ...rest } = useFirestoreCount({\n collectionPath,\n queryKey: ['notifications', 'unread-count', category, userId],\n constraints,\n enabled: enabled && !!userId,\n staleTime: refetchInterval,\n refetchInterval,\n });\n\n const count = data ?? 0;\n\n return {\n count,\n hasMore: count > countLimit,\n ...rest,\n };\n}\n"]}
1
+ {"version":3,"file":"useUnreadCount.js","sourceRoot":"","sources":["../../../src/react/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,eAAe,GAAG,wBAAwB,EAC1C,UAAU,GAAG,mBAAmB,GACV;IACtB,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,cAAc,GAAG,cAAc,CAAC,UAAU,CAAC;IACjD,MAAM,UAAU,GAAG,cAAc,CAAC,YAAY,KAAK,UAAU,CAAC;IAE9D,MAAM,WAAW,GACf,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9F,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,iBAAiB,CAAC;QAC1C,cAAc;QACd,QAAQ,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7D,WAAW;QACX,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS,EAAE,eAAe;QAC1B,eAAe;KAChB,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC;IAExB,OAAO;QACL,KAAK;QACL,OAAO,EAAE,KAAK,GAAG,UAAU;QAC3B,GAAG,IAAI;KACR,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { where, type QueryConstraint } from 'firebase/firestore';\nimport { useFirestoreCount } from '@ttt-productions/query-core/react';\nimport type { UseUnreadCountOptions } from '../../types.js';\n\nconst DEFAULT_REFETCH_INTERVAL = 30_000;\nconst DEFAULT_COUNT_LIMIT = 99;\n\n// The personal `seenAt == 0` predicate needs a composite index.\nexport function useUnreadCount({\n config,\n userId,\n category,\n enabled = true,\n refetchInterval = DEFAULT_REFETCH_INTERVAL,\n countLimit = DEFAULT_COUNT_LIMIT,\n}: UseUnreadCountOptions) {\n const categoryConfig = config.categories[category];\n if (!categoryConfig) {\n throw new Error(`[notification-core] Unknown category: ${category}`);\n }\n\n const collectionPath = categoryConfig.activePath;\n const isPersonal = categoryConfig.audienceType === 'personal';\n\n const constraints: QueryConstraint[] =\n isPersonal && userId ? [where('targetUserId', '==', userId), where('seenAt', '==', 0)] : [];\n\n const { data, ...rest } = useFirestoreCount({\n collectionPath,\n queryKey: ['notifications', 'unread-count', category, userId],\n constraints,\n enabled: enabled && !!userId,\n staleTime: refetchInterval,\n refetchInterval,\n });\n\n const count = data ?? 0;\n\n return {\n count,\n hasMore: count > countLimit,\n ...rest,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAGnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAElF,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAElF,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
@@ -1,11 +1,9 @@
1
1
  'use client';
2
- // Hooks
3
2
  export { useActiveNotifications } from './hooks/useActiveNotifications.js';
4
3
  export { useNotificationHistory } from './hooks/useNotificationHistory.js';
5
4
  export { useUnreadCount } from './hooks/useUnreadCount.js';
6
5
  export { useArchiveNotification } from './hooks/useArchiveNotification.js';
7
6
  export { useArchiveAllNotifications } from './hooks/useArchiveAllNotifications.js';
8
- // Components
9
7
  export { NotificationList } from './components/NotificationList.js';
10
8
  export { NotificationHistoryList } from './components/NotificationHistoryList.js';
11
9
  export { NotificationEmptyState } from './components/NotificationEmptyState.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,QAAQ;AACR,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAEnF,aAAa;AACb,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC","sourcesContent":["'use client';\n\n// Hooks\nexport { useActiveNotifications } from './hooks/useActiveNotifications.js';\nexport { useNotificationHistory } from './hooks/useNotificationHistory.js';\nexport { useUnreadCount } from './hooks/useUnreadCount.js';\nexport { useArchiveNotification } from './hooks/useArchiveNotification.js';\nexport { useArchiveAllNotifications } from './hooks/useArchiveAllNotifications.js';\n\n// Components\nexport { NotificationList } from './components/NotificationList.js';\nexport { NotificationHistoryList } from './components/NotificationHistoryList.js';\nexport { NotificationEmptyState } from './components/NotificationEmptyState.js';\nexport { NotificationUnreadBadge } from './components/NotificationUnreadBadge.js';\n\nexport type { NotificationListProps, NotificationHistoryListProps } from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC","sourcesContent":["'use client';\n\nexport { useActiveNotifications } from './hooks/useActiveNotifications.js';\nexport { useNotificationHistory } from './hooks/useNotificationHistory.js';\nexport { useUnreadCount } from './hooks/useUnreadCount.js';\nexport { useArchiveNotification } from './hooks/useArchiveNotification.js';\nexport { useArchiveAllNotifications } from './hooks/useArchiveAllNotifications.js';\n\nexport { NotificationList } from './components/NotificationList.js';\nexport { NotificationHistoryList } from './components/NotificationHistoryList.js';\nexport { NotificationEmptyState } from './components/NotificationEmptyState.js';\nexport { NotificationUnreadBadge } from './components/NotificationUnreadBadge.js';\n\nexport type { NotificationListProps, NotificationHistoryListProps } from './types.js';\n"]}
@@ -4,50 +4,16 @@ export interface NotificationListProps {
4
4
  config: NotificationSystemConfig;
5
5
  userId: string;
6
6
  category: string;
7
- /**
8
- * Archives one rendered notification. Passed straight through to
9
- * `useArchiveNotification`; the app wires it to its callable.
10
- */
11
7
  archiveFn: NotificationArchiveFn;
12
- /**
13
- * Adapter that ENQUEUES one server-owned archive-all job scoped to `category` and returns its
14
- * `jobId`. Passed straight through to `useArchiveAllNotifications`; the app wires it to its
15
- * enqueue callable. Per-tab scoping: the category flows through to the job.
16
- */
17
8
  enqueueArchiveAllFn: (category: string) => Promise<EnqueueArchiveAllResult>;
18
- /**
19
- * Adapter that returns the current status snapshot for an archive-all `jobId`. Passed straight
20
- * through to `useArchiveAllNotifications`, which polls it until the job is terminal; the app wires
21
- * it to its status callable / job-doc read.
22
- */
23
9
  getArchiveAllStatusFn: (jobId: string) => Promise<ArchiveAllJobSnapshot>;
24
- /** Left-aligned title for the active-list header. */
25
10
  title?: ReactNode;
26
- /**
27
- * Fires with the terminal result ONLY when the archive-all job completed — the whole category was
28
- * walked. A complete result can still carry `notArchived > 0` (cards that each failed and stay in
29
- * the list); the copy for that is the app's.
30
- */
31
11
  onClearAll?: (result: ArchiveAllPollResult) => void;
32
- /** Re-read the displayed page every `refetchInterval` ms while mounted (default 30s). */
33
12
  refetchInterval?: number;
34
- /** How long a read page counts as fresh, in ms (default 30s). */
35
13
  staleTime?: number;
36
- /** Empty-state text, shown only for an answered, empty first page. */
37
14
  emptyText?: string;
38
- /** The failed-read state, shown before (never instead of) the pager. Copy is the app's. */
39
15
  renderError: (state: NotificationListErrorState) => ReactNode;
40
- /**
41
- * Called with the rows on screen each time the displayed page's answered rows change
42
- * (not while a page is loading or failed with nothing shown) — the rows to mark seen.
43
- */
44
16
  onRenderedRowsChange?: (rows: readonly NotificationDoc[]) => void;
45
- /**
46
- * Per-row control slot. The row itself is inert; the consumer renders the row's
47
- * controls here — typically an ArrowRight "go to" (which may call `actions.archive`
48
- * then navigate) plus a clear/archive button (calls `actions.archive`). Omit to render
49
- * a row with no controls.
50
- */
51
17
  renderRowAction?: (notification: NotificationDoc, actions: NotificationRowActions) => ReactNode;
52
18
  }
53
19
  export interface NotificationHistoryListProps {
@@ -56,17 +22,9 @@ export interface NotificationHistoryListProps {
56
22
  category: string;
57
23
  pageSize?: number;
58
24
  staleTime?: number;
59
- /** Empty-state text, shown only for an answered, empty first page. */
60
25
  emptyText?: string;
61
- /** The failed-read state, shown before (never instead of) the pager. Copy is the app's. */
62
26
  renderError: (state: NotificationListErrorState) => ReactNode;
63
- /** Left-aligned title for the read-only archived-list header. */
64
27
  title?: ReactNode;
65
- /**
66
- * Per-row control slot for archived rows. The row is inert; archived rows are
67
- * read-only (no re-archive), so `actions.archive` is absent — the consumer
68
- * typically renders only an ArrowRight "go to". Omit for a row with no controls.
69
- */
70
28
  renderRowAction?: (notification: NotificationHistoryItem, actions: NotificationRowActions) => ReactNode;
71
29
  }
72
30
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,EAAE,qBAAqB,CAAC;IACjC;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E;;;;OAIG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACzE,qDAAqD;IACrD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACpD,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,WAAW,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC9D;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,sBAAsB,KAAK,SAAS,CAAC;CACjG;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,WAAW,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC9D,iEAAiE;IACjE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAChB,YAAY,EAAE,uBAAuB,EACrC,OAAO,EAAE,sBAAsB,KAC5B,SAAS,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,qBAAqB,CAAC;IACjC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACzE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC9D,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,CAAC;IAClE,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,sBAAsB,KAAK,SAAS,CAAC;CACjG;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC9D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,eAAe,CAAC,EAAE,CAChB,YAAY,EAAE,uBAAuB,EACrC,OAAO,EAAE,sBAAsB,KAC5B,SAAS,CAAC;CAChB"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ArchiveAllJobSnapshot,\n ArchiveAllPollResult,\n EnqueueArchiveAllResult,\n NotificationArchiveFn,\n NotificationDoc,\n NotificationHistoryItem,\n NotificationListErrorState,\n NotificationRowActions,\n NotificationSystemConfig,\n} from '../types.js';\n\nexport interface NotificationListProps {\n config: NotificationSystemConfig;\n userId: string;\n category: string;\n /**\n * Archives one rendered notification. Passed straight through to\n * `useArchiveNotification`; the app wires it to its callable.\n */\n archiveFn: NotificationArchiveFn;\n /**\n * Adapter that ENQUEUES one server-owned archive-all job scoped to `category` and returns its\n * `jobId`. Passed straight through to `useArchiveAllNotifications`; the app wires it to its\n * enqueue callable. Per-tab scoping: the category flows through to the job.\n */\n enqueueArchiveAllFn: (category: string) => Promise<EnqueueArchiveAllResult>;\n /**\n * Adapter that returns the current status snapshot for an archive-all `jobId`. Passed straight\n * through to `useArchiveAllNotifications`, which polls it until the job is terminal; the app wires\n * it to its status callable / job-doc read.\n */\n getArchiveAllStatusFn: (jobId: string) => Promise<ArchiveAllJobSnapshot>;\n /** Left-aligned title for the active-list header. */\n title?: ReactNode;\n /**\n * Fires with the terminal result ONLY when the archive-all job completed — the whole category was\n * walked. A complete result can still carry `notArchived > 0` (cards that each failed and stay in\n * the list); the copy for that is the app's.\n */\n onClearAll?: (result: ArchiveAllPollResult) => void;\n /** Re-read the displayed page every `refetchInterval` ms while mounted (default 30s). */\n refetchInterval?: number;\n /** How long a read page counts as fresh, in ms (default 30s). */\n staleTime?: number;\n /** Empty-state text, shown only for an answered, empty first page. */\n emptyText?: string;\n /** The failed-read state, shown before (never instead of) the pager. Copy is the app's. */\n renderError: (state: NotificationListErrorState) => ReactNode;\n /**\n * Called with the rows on screen each time the displayed page's answered rows change\n * (not while a page is loading or failed with nothing shown) — the rows to mark seen.\n */\n onRenderedRowsChange?: (rows: readonly NotificationDoc[]) => void;\n /**\n * Per-row control slot. The row itself is inert; the consumer renders the row's\n * controls here — typically an ArrowRight \"go to\" (which may call `actions.archive`\n * then navigate) plus a clear/archive button (calls `actions.archive`). Omit to render\n * a row with no controls.\n */\n renderRowAction?: (notification: NotificationDoc, actions: NotificationRowActions) => ReactNode;\n}\n\nexport interface NotificationHistoryListProps {\n config: NotificationSystemConfig;\n userId: string;\n category: string;\n pageSize?: number;\n staleTime?: number;\n /** Empty-state text, shown only for an answered, empty first page. */\n emptyText?: string;\n /** The failed-read state, shown before (never instead of) the pager. Copy is the app's. */\n renderError: (state: NotificationListErrorState) => ReactNode;\n /** Left-aligned title for the read-only archived-list header. */\n title?: ReactNode;\n /**\n * Per-row control slot for archived rows. The row is inert; archived rows are\n * read-only (no re-archive), so `actions.archive` is absent — the consumer\n * typically renders only an ArrowRight \"go to\". Omit for a row with no controls.\n */\n renderRowAction?: (\n notification: NotificationHistoryItem,\n actions: NotificationRowActions,\n ) => ReactNode;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ArchiveAllJobSnapshot,\n ArchiveAllPollResult,\n EnqueueArchiveAllResult,\n NotificationArchiveFn,\n NotificationDoc,\n NotificationHistoryItem,\n NotificationListErrorState,\n NotificationRowActions,\n NotificationSystemConfig,\n} from '../types.js';\n\nexport interface NotificationListProps {\n config: NotificationSystemConfig;\n userId: string;\n category: string;\n archiveFn: NotificationArchiveFn;\n enqueueArchiveAllFn: (category: string) => Promise<EnqueueArchiveAllResult>;\n getArchiveAllStatusFn: (jobId: string) => Promise<ArchiveAllJobSnapshot>;\n title?: ReactNode;\n onClearAll?: (result: ArchiveAllPollResult) => void;\n refetchInterval?: number;\n staleTime?: number;\n emptyText?: string;\n renderError: (state: NotificationListErrorState) => ReactNode;\n onRenderedRowsChange?: (rows: readonly NotificationDoc[]) => void;\n renderRowAction?: (notification: NotificationDoc, actions: NotificationRowActions) => ReactNode;\n}\n\nexport interface NotificationHistoryListProps {\n config: NotificationSystemConfig;\n userId: string;\n category: string;\n pageSize?: number;\n staleTime?: number;\n emptyText?: string;\n renderError: (state: NotificationListErrorState) => ReactNode;\n title?: ReactNode;\n renderRowAction?: (\n notification: NotificationHistoryItem,\n actions: NotificationRowActions,\n ) => ReactNode;\n}\n"]}
package/dist/schemas.d.ts CHANGED
@@ -1,23 +1,4 @@
1
- /**
2
- * @ttt-productions/notification-core — Zod schemas
3
- *
4
- * Runtime validation schemas for the generic notification document shapes whose
5
- * TypeScript types live in `./types.ts`. notification-core OWNS these shapes, so
6
- * it owns their schemas too — the consuming app's data package composes these rather than hand-mirroring them, so the schema can never drift
7
- * from the type.
8
- *
9
- * The interfaces in `./types.ts` remain the documented public type API. Each
10
- * schema is compile-time-checked against its interface via
11
- * `satisfies z.ZodType<Shape>` — a shape drift between the interface and the
12
- * schema fails the build here.
13
- *
14
- * zod-only, no React/firebase: safe for both server (Cloud Functions) and UI.
15
- */
16
1
  import { z } from 'zod';
17
- /**
18
- * Active notification document (`NotificationDoc`). Same shape for both the user
19
- * and admin active collections.
20
- */
21
2
  export declare const NotificationDocSchema: z.ZodObject<{
22
3
  id: z.ZodString;
23
4
  type: z.ZodString;
@@ -36,9 +17,6 @@ export declare const NotificationDocSchema: z.ZodObject<{
36
17
  createdAt: z.ZodNumber;
37
18
  updatedAt: z.ZodNumber;
38
19
  }, z.core.$strip>;
39
- /**
40
- * Pending notification (`PendingNotification`) — queue item for the batch processor.
41
- */
42
20
  export declare const PendingNotificationSchema: z.ZodObject<{
43
21
  id: z.ZodString;
44
22
  type: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;iBAoBK,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;iBAQK,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;iBAiBK,CAAC;AAExC,eAAO,MAAM,yBAAyB;;;;;;;;iBAQK,CAAC"}
package/dist/schemas.js CHANGED
@@ -1,23 +1,4 @@
1
- /**
2
- * @ttt-productions/notification-core — Zod schemas
3
- *
4
- * Runtime validation schemas for the generic notification document shapes whose
5
- * TypeScript types live in `./types.ts`. notification-core OWNS these shapes, so
6
- * it owns their schemas too — the consuming app's data package composes these rather than hand-mirroring them, so the schema can never drift
7
- * from the type.
8
- *
9
- * The interfaces in `./types.ts` remain the documented public type API. Each
10
- * schema is compile-time-checked against its interface via
11
- * `satisfies z.ZodType<Shape>` — a shape drift between the interface and the
12
- * schema fails the build here.
13
- *
14
- * zod-only, no React/firebase: safe for both server (Cloud Functions) and UI.
15
- */
16
1
  import { z } from 'zod';
17
- /**
18
- * Active notification document (`NotificationDoc`). Same shape for both the user
19
- * and admin active collections.
20
- */
21
2
  export const NotificationDocSchema = z.object({
22
3
  id: z.string(),
23
4
  type: z.string(),
@@ -28,20 +9,14 @@ export const NotificationDocSchema = z.object({
28
9
  message: z.string(),
29
10
  count: z.number(),
30
11
  latestActorIds: z.array(z.string()),
31
- // Absent on linkless types (no defaultTargetPath in the type config).
32
12
  targetPath: z.string().optional(),
33
13
  metadata: z.record(z.string(), z.unknown()),
34
14
  seenAt: z.number(),
35
- // Opaque per-generation token rotated on create / material re-light. Optional on
36
- // the legacy active doc shape; the ledger materializer always sets it.
37
15
  activityGeneration: z.string().optional(),
38
16
  seenAtGeneration: z.string().optional(),
39
17
  createdAt: z.number(),
40
18
  updatedAt: z.number(),
41
19
  });
42
- /**
43
- * Pending notification (`PendingNotification`) — queue item for the batch processor.
44
- */
45
20
  export const PendingNotificationSchema = z.object({
46
21
  id: z.string(),
47
22
  type: z.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC,sEAAsE;IACtE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,iFAAiF;IACjF,uEAAuE;IACvE,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAsC,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAA0C,CAAC","sourcesContent":["/**\n * @ttt-productions/notification-core — Zod schemas\n *\n * Runtime validation schemas for the generic notification document shapes whose\n * TypeScript types live in `./types.ts`. notification-core OWNS these shapes, so\n * it owns their schemas too — the consuming app's data package composes these rather than hand-mirroring them, so the schema can never drift\n * from the type.\n *\n * The interfaces in `./types.ts` remain the documented public type API. Each\n * schema is compile-time-checked against its interface via\n * `satisfies z.ZodType<Shape>` — a shape drift between the interface and the\n * schema fails the build here.\n *\n * zod-only, no React/firebase: safe for both server (Cloud Functions) and UI.\n */\n\nimport { z } from 'zod';\nimport type { NotificationDoc, PendingNotification } from './types.js';\n\n/**\n * Active notification document (`NotificationDoc`). Same shape for both the user\n * and admin active collections.\n */\nexport const NotificationDocSchema = z.object({\n id: z.string(),\n type: z.string(),\n dedupKey: z.string(),\n category: z.string(),\n targetUserId: z.string().nullable(),\n title: z.string(),\n message: z.string(),\n count: z.number(),\n latestActorIds: z.array(z.string()),\n // Absent on linkless types (no defaultTargetPath in the type config).\n targetPath: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()),\n seenAt: z.number(),\n // Opaque per-generation token rotated on create / material re-light. Optional on\n // the legacy active doc shape; the ledger materializer always sets it.\n activityGeneration: z.string().optional(),\n seenAtGeneration: z.string().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n}) satisfies z.ZodType<NotificationDoc>;\n\n/**\n * Pending notification (`PendingNotification`) — queue item for the batch processor.\n */\nexport const PendingNotificationSchema = z.object({\n id: z.string(),\n type: z.string(),\n category: z.string(),\n targetUserId: z.string().nullable(),\n actorId: z.string(),\n metadata: z.record(z.string(), z.unknown()),\n createdAt: z.number(),\n}) satisfies z.ZodType<PendingNotification>;\n"]}
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAsC,CAAC;AAExC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAA0C,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { NotificationDoc, PendingNotification } from './types.js';\n\nexport const NotificationDocSchema = z.object({\n id: z.string(),\n type: z.string(),\n dedupKey: z.string(),\n category: z.string(),\n targetUserId: z.string().nullable(),\n title: z.string(),\n message: z.string(),\n count: z.number(),\n latestActorIds: z.array(z.string()),\n targetPath: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()),\n seenAt: z.number(),\n activityGeneration: z.string().optional(),\n seenAtGeneration: z.string().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n}) satisfies z.ZodType<NotificationDoc>;\n\nexport const PendingNotificationSchema = z.object({\n id: z.string(),\n type: z.string(),\n category: z.string(),\n targetUserId: z.string().nullable(),\n actorId: z.string(),\n metadata: z.record(z.string(), z.unknown()),\n createdAt: z.number(),\n}) satisfies z.ZodType<PendingNotification>;\n"]}
@@ -1,30 +1,9 @@
1
- /**
2
- * Deterministic active-notification document IDs.
3
- *
4
- * The active doc for a given dedup scope always lives at the same ID, so
5
- * concurrent writers (real-time sends racing each other or the batch
6
- * materializer) converge on one document inside a transaction instead of
7
- * racing a query-then-write. The ID hashes the same fields the dedup lookup
8
- * used to filter on: category, audience scope, and dedupKey.
9
- */
10
- /**
11
- * Build the deterministic doc ID for an active notification.
12
- *
13
- * Personal notifications scope per recipient (a fan-out to many users must
14
- * never collapse into one recipient's doc); shared notifications are
15
- * intentionally a single doc for the whole audience.
16
- */
17
1
  export declare function buildActiveNotificationDocId(params: {
18
2
  category: string;
19
3
  audienceType: 'personal' | 'shared';
20
4
  targetUserId: string | null | undefined;
21
5
  dedupKey: string;
22
- /**
23
- * When provided, the id becomes **type-scoped** so two notification types that
24
- * share an aggregation key never collapse onto one active doc (the delivery
25
- * ledger materializer always passes it). Omitted => the legacy non-type-scoped
26
- * id, kept byte-identical for the pre-cutover composer path.
27
- */
6
+ /** Omit for processBatchHelper's id, which must stay byte-identical: existing active docs live at it. */
28
7
  notificationType?: string;
29
8
  }): string;
30
9
  //# sourceMappingURL=activeNotificationId.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activeNotificationId.d.ts","sourceRoot":"","sources":["../../src/server/activeNotificationId.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,UAAU,GAAG,QAAQ,CAAC;IACpC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,CAQT"}
1
+ {"version":3,"file":"activeNotificationId.d.ts","sourceRoot":"","sources":["../../src/server/activeNotificationId.ts"],"names":[],"mappings":"AAEA,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,UAAU,GAAG,QAAQ,CAAC;IACpC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,CAQT"}
@@ -1,20 +1,4 @@
1
- /**
2
- * Deterministic active-notification document IDs.
3
- *
4
- * The active doc for a given dedup scope always lives at the same ID, so
5
- * concurrent writers (real-time sends racing each other or the batch
6
- * materializer) converge on one document inside a transaction instead of
7
- * racing a query-then-write. The ID hashes the same fields the dedup lookup
8
- * used to filter on: category, audience scope, and dedupKey.
9
- */
10
1
  import { createHash } from 'node:crypto';
11
- /**
12
- * Build the deterministic doc ID for an active notification.
13
- *
14
- * Personal notifications scope per recipient (a fan-out to many users must
15
- * never collapse into one recipient's doc); shared notifications are
16
- * intentionally a single doc for the whole audience.
17
- */
18
2
  export function buildActiveNotificationDocId(params) {
19
3
  const scope = params.audienceType === 'personal' ? `user:${params.targetUserId ?? 'null'}` : 'shared';
20
4
  const input = params.notificationType !== undefined