@ttt-productions/notification-core 0.15.16 → 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 (98) hide show
  1. package/README.md +9 -10
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +0 -3
  5. package/dist/index.js.map +1 -1
  6. package/dist/react/components/NotificationEmptyState.d.ts +0 -3
  7. package/dist/react/components/NotificationEmptyState.d.ts.map +1 -1
  8. package/dist/react/components/NotificationEmptyState.js +0 -3
  9. package/dist/react/components/NotificationEmptyState.js.map +1 -1
  10. package/dist/react/components/NotificationHistoryList.d.ts +1 -7
  11. package/dist/react/components/NotificationHistoryList.d.ts.map +1 -1
  12. package/dist/react/components/NotificationHistoryList.js +4 -9
  13. package/dist/react/components/NotificationHistoryList.js.map +1 -1
  14. package/dist/react/components/NotificationList.d.ts +1 -4
  15. package/dist/react/components/NotificationList.d.ts.map +1 -1
  16. package/dist/react/components/NotificationList.js +26 -38
  17. package/dist/react/components/NotificationList.js.map +1 -1
  18. package/dist/react/components/NotificationUnreadBadge.d.ts +0 -5
  19. package/dist/react/components/NotificationUnreadBadge.d.ts.map +1 -1
  20. package/dist/react/components/NotificationUnreadBadge.js +1 -6
  21. package/dist/react/components/NotificationUnreadBadge.js.map +1 -1
  22. package/dist/react/components/relative-time.d.ts +0 -3
  23. package/dist/react/components/relative-time.d.ts.map +1 -1
  24. package/dist/react/components/relative-time.js +0 -3
  25. package/dist/react/components/relative-time.js.map +1 -1
  26. package/dist/react/hooks/useActiveNotifications.d.ts +3 -7
  27. package/dist/react/hooks/useActiveNotifications.d.ts.map +1 -1
  28. package/dist/react/hooks/useActiveNotifications.js +3 -8
  29. package/dist/react/hooks/useActiveNotifications.js.map +1 -1
  30. package/dist/react/hooks/useArchiveAllNotifications.d.ts +0 -30
  31. package/dist/react/hooks/useArchiveAllNotifications.d.ts.map +1 -1
  32. package/dist/react/hooks/useArchiveAllNotifications.js +15 -45
  33. package/dist/react/hooks/useArchiveAllNotifications.js.map +1 -1
  34. package/dist/react/hooks/useArchiveNotification.d.ts +0 -8
  35. package/dist/react/hooks/useArchiveNotification.d.ts.map +1 -1
  36. package/dist/react/hooks/useArchiveNotification.js +0 -8
  37. package/dist/react/hooks/useArchiveNotification.js.map +1 -1
  38. package/dist/react/hooks/useNotificationHistory.d.ts +3 -17
  39. package/dist/react/hooks/useNotificationHistory.d.ts.map +1 -1
  40. package/dist/react/hooks/useNotificationHistory.js +3 -20
  41. package/dist/react/hooks/useNotificationHistory.js.map +1 -1
  42. package/dist/react/hooks/useUnreadCount.d.ts +0 -22
  43. package/dist/react/hooks/useUnreadCount.d.ts.map +1 -1
  44. package/dist/react/hooks/useUnreadCount.js +2 -25
  45. package/dist/react/hooks/useUnreadCount.js.map +1 -1
  46. package/dist/react/index.d.ts +10 -2
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/index.js +10 -5
  49. package/dist/react/index.js.map +1 -1
  50. package/dist/react/types.d.ts +30 -0
  51. package/dist/react/types.d.ts.map +1 -0
  52. package/dist/react/types.js +2 -0
  53. package/dist/react/types.js.map +1 -0
  54. package/dist/schemas.d.ts +0 -22
  55. package/dist/schemas.d.ts.map +1 -1
  56. package/dist/schemas.js +0 -25
  57. package/dist/schemas.js.map +1 -1
  58. package/dist/server/activeNotificationId.d.ts +1 -22
  59. package/dist/server/activeNotificationId.d.ts.map +1 -1
  60. package/dist/server/activeNotificationId.js +0 -16
  61. package/dist/server/activeNotificationId.js.map +1 -1
  62. package/dist/server/delivery-ledger.d.ts +1 -73
  63. package/dist/server/delivery-ledger.d.ts.map +1 -1
  64. package/dist/server/delivery-ledger.js +13 -63
  65. package/dist/server/delivery-ledger.js.map +1 -1
  66. package/dist/server/errors.d.ts +0 -9
  67. package/dist/server/errors.d.ts.map +1 -1
  68. package/dist/server/errors.js +1 -11
  69. package/dist/server/errors.js.map +1 -1
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +0 -3
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/observed-generation.d.ts +1 -41
  74. package/dist/server/observed-generation.d.ts.map +1 -1
  75. package/dist/server/observed-generation.js +1 -31
  76. package/dist/server/observed-generation.js.map +1 -1
  77. package/dist/server/processBatchHelper.d.ts +0 -19
  78. package/dist/server/processBatchHelper.d.ts.map +1 -1
  79. package/dist/server/processBatchHelper.js +5 -51
  80. package/dist/server/processBatchHelper.js.map +1 -1
  81. package/dist/server/types.d.ts +0 -9
  82. package/dist/server/types.d.ts.map +1 -1
  83. package/dist/server/types.js +0 -4
  84. package/dist/server/types.js.map +1 -1
  85. package/dist/types.d.ts +2 -266
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/types.js +0 -6
  88. package/dist/types.js.map +1 -1
  89. package/package.json +5 -5
  90. package/src/styles/notifications.css +0 -14
  91. package/dist/react/components/index.d.ts +0 -5
  92. package/dist/react/components/index.d.ts.map +0 -1
  93. package/dist/react/components/index.js +0 -5
  94. package/dist/react/components/index.js.map +0 -1
  95. package/dist/react/hooks/index.d.ts +0 -6
  96. package/dist/react/hooks/index.d.ts.map +0 -1
  97. package/dist/react/hooks/index.js +0 -6
  98. package/dist/react/hooks/index.js.map +0 -1
@@ -1,14 +1,9 @@
1
1
  'use client';
2
- import { paginatedPageKey } from '@ttt-productions/query-core';
3
- import { useFirestorePaginated, useReadRetryState } from '@ttt-productions/query-core/react';
2
+ import { paginatedPageKey, useFirestorePaginated, useReadRetryState, } from '@ttt-productions/query-core/react';
4
3
  import { orderBy, where } from 'firebase/firestore';
5
4
  const DEFAULT_PAGE_SIZE = 20;
6
5
  const DEFAULT_REFETCH_INTERVAL = 30_000;
7
6
  const DEFAULT_STALE_TIME = 30_000;
8
- /**
9
- * Paginated read of active notifications. The displayed page is re-read every
10
- * `refetchInterval` ms while mounted — polling, not a realtime listener, for cost.
11
- */
12
7
  export function useActiveNotifications({ config, userId, category, enabled = true, pageSize = DEFAULT_PAGE_SIZE, refetchInterval = DEFAULT_REFETCH_INTERVAL, staleTime = DEFAULT_STALE_TIME, }) {
13
8
  const categoryConfig = config.categories[category];
14
9
  if (!categoryConfig) {
@@ -30,8 +25,8 @@ export function useActiveNotifications({ config, userId, category, enabled = tru
30
25
  refetchInterval,
31
26
  staleTime,
32
27
  });
33
- // The displayed page's failure held through its retry: a refetch of a never-succeeded page clears
34
- // `error` while it runs, so the list would drop its failure under the retry the person pressed.
28
+ // A refetch of a never-succeeded page clears `error` while it runs, so the failure is held
29
+ // through the retry.
35
30
  return { ...read, retryState: useReadRetryState(read, paginatedPageKey(queryKey, read.page)) };
36
31
  }
37
32
  //# sourceMappingURL=useActiveNotifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useActiveNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useActiveNotifications.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC7F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAG1E,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,iBAAiB,EAC5B,eAAe,GAAG,wBAAwB,EAC1C,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,UAAU,CAAC;IACjD,MAAM,UAAU,GAAG,cAAc,CAAC,YAAY,KAAK,UAAU,CAAC;IAE9D,MAAM,WAAW,GAAsB;QACrC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC;KAC7B,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,qBAAqB,CAAkB;QAClD,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,eAAe;QACf,SAAS;KACV,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 { paginatedPageKey } from '@ttt-productions/query-core';\nimport { useFirestorePaginated, useReadRetryState } from '@ttt-productions/query-core/react';\nimport { orderBy, where, type QueryConstraint } from 'firebase/firestore';\nimport type { NotificationDoc, UseActiveNotificationsOptions } from '../../types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_REFETCH_INTERVAL = 30_000;\nconst DEFAULT_STALE_TIME = 30_000;\n\n/**\n * Paginated read of active notifications. The displayed page is re-read every\n * `refetchInterval` ms while mounted — polling, not a realtime listener, for cost.\n */\nexport function useActiveNotifications({\n config,\n userId,\n category,\n enabled = true,\n pageSize = DEFAULT_PAGE_SIZE,\n refetchInterval = DEFAULT_REFETCH_INTERVAL,\n staleTime = DEFAULT_STALE_TIME,\n}: UseActiveNotificationsOptions) {\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 ? [where('targetUserId', '==', userId)] : []),\n orderBy('updatedAt', 'desc'),\n ];\n\n const queryKey = ['notifications', 'active', category, userId, { pageSize }];\n const read = useFirestorePaginated<NotificationDoc>({\n collectionPath,\n queryKey,\n constraints,\n pageSize,\n enabled: enabled && !!userId,\n refetchInterval,\n staleTime,\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":"useActiveNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useActiveNotifications.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAG1E,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,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,eAAe,GAAG,wBAAwB,EAC1C,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,UAAU,CAAC;IACjD,MAAM,UAAU,GAAG,cAAc,CAAC,YAAY,KAAK,UAAU,CAAC;IAE9D,MAAM,WAAW,GAAsB;QACrC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC;KAC7B,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,qBAAqB,CAAkB;QAClD,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,eAAe;QACf,SAAS;KACV,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, where, type QueryConstraint } from 'firebase/firestore';\nimport type { NotificationDoc, UseActiveNotificationsOptions } from '../../types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_REFETCH_INTERVAL = 30_000;\nconst DEFAULT_STALE_TIME = 30_000;\n\nexport function useActiveNotifications({\n config,\n userId,\n category,\n enabled = true,\n pageSize = DEFAULT_PAGE_SIZE,\n refetchInterval = DEFAULT_REFETCH_INTERVAL,\n staleTime = DEFAULT_STALE_TIME,\n}: UseActiveNotificationsOptions) {\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 ? [where('targetUserId', '==', userId)] : []),\n orderBy('updatedAt', 'desc'),\n ];\n\n const queryKey = ['notifications', 'active', category, userId, { pageSize }];\n const read = useFirestorePaginated<NotificationDoc>({\n collectionPath,\n queryKey,\n constraints,\n pageSize,\n enabled: enabled && !!userId,\n refetchInterval,\n staleTime,\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,33 +1,3 @@
1
1
  import type { ArchiveAllPollResult, UseArchiveAllNotificationsOptions } from '../../types.js';
2
- /**
3
- * Trigger + poll a SERVER-OWNED archive-all job for one notification category.
4
- *
5
- * This is a THIN STATUS POLLER, not a client loop. It:
6
- * 1. enqueues ONE server job via `enqueueArchiveAllFn(category)` (the category — the tab the tray
7
- * was invoked on — flows into the job, preserving per-tab scoping: the worker archives ONLY
8
- * that category's active cards, never all tabs);
9
- * 2. polls `getArchiveAllStatusFn(jobId)` until the job reaches a terminal state
10
- * (`complete` | `incomplete` | `failed`);
11
- * 3. resolves an {@link ArchiveAllPollResult} the caller gates side effects on, and invalidates
12
- * the read keys on a completed drain.
13
- *
14
- * All paging + ownership-checked per-card deletion happen server-side. The hook performs no client
15
- * Firestore writes and never chains archive calls. The generic package hardcodes no callable name —
16
- * the app injects both adapters.
17
- *
18
- * @example
19
- * ```tsx
20
- * const enqueue = httpsCallable(functions, 'enqueueArchiveAll');
21
- * const getStatus = httpsCallable(functions, 'getArchiveAllStatus');
22
- * const archiveAll = useArchiveAllNotifications({
23
- * userId: user.uid,
24
- * category: 'user',
25
- * enqueueArchiveAllFn: (category) => enqueue({ category }).then((r) => r.data),
26
- * getArchiveAllStatusFn: (jobId) => getStatus({ jobId }).then((r) => r.data),
27
- * });
28
- *
29
- * archiveAll.mutate();
30
- * ```
31
- */
32
2
  export declare function useArchiveAllNotifications({ userId, category, enqueueArchiveAllFn, getArchiveAllStatusFn, pollIntervalMs, maxPolls, invalidateKeys, }: UseArchiveAllNotificationsOptions): import("@tanstack/react-query").UseMutationResult<ArchiveAllPollResult, Error, void, unknown>;
33
3
  //# sourceMappingURL=useArchiveAllNotifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveAllNotifications.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AASxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,qBAAqB,EACrB,cAAyC,EACzC,QAA4B,EAC5B,cAAc,GACf,EAAE,iCAAiC,iGAkEnC"}
1
+ {"version":3,"file":"useArchiveAllNotifications.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iCAAiC,EAAE,MAAM,gBAAgB,CAAC;AAS9F,wBAAgB,0BAA0B,CAAC,EACzC,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,qBAAqB,EACrB,cAAyC,EACzC,QAA4B,EAC5B,cAAc,GACf,EAAE,iCAAiC,iGAkEnC"}
@@ -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);
@@ -66,7 +29,13 @@ export function useArchiveAllNotifications({ userId, category, enqueueArchiveAll
66
29
  };
67
30
  }
68
31
  if (snap.state === 'incomplete') {
69
- return { status: 'incomplete', category, archived: lastArchived, notArchived: 0, complete: false };
32
+ return {
33
+ status: 'incomplete',
34
+ category,
35
+ archived: lastArchived,
36
+ notArchived: 0,
37
+ complete: false,
38
+ };
70
39
  }
71
40
  if (snap.state === 'failed') {
72
41
  return {
@@ -78,17 +47,18 @@ export function useArchiveAllNotifications({ userId, category, enqueueArchiveAll
78
47
  error: snap.error,
79
48
  };
80
49
  }
81
- // Still in-progress — wait before the next poll unless this was the last allowed poll.
82
50
  if (poll < maxPolls - 1)
83
51
  await sleep(pollIntervalMs);
84
52
  }
85
- // Guard: the job never reached a terminal state within the poll budget — report incomplete.
86
- return { status: 'incomplete', category, archived: lastArchived, notArchived: 0, complete: false };
53
+ return {
54
+ status: 'incomplete',
55
+ category,
56
+ archived: lastArchived,
57
+ notArchived: 0,
58
+ complete: false,
59
+ };
87
60
  },
88
61
  onSuccess: (result) => {
89
- // Only a completed drain changed the active set; still invalidate on an incomplete drain since
90
- // SOME cards were archived. A hard enqueue/poll failure rejects the mutation (onError), so this
91
- // never runs for `failed`.
92
62
  const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;
93
63
  keysToInvalidate.forEach((key) => {
94
64
  queryClient.invalidateQueries({ queryKey: [...key], exact: false });
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveAllNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMpE,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,iBAAiB,GAAG,GAAG,CAAC,CAAC,iCAAiC;AAEhE,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,GAAG,wBAAwB,EACzC,QAAQ,GAAG,iBAAiB,EAC5B,cAAc,GACoB;IAClC,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,CAAC;QACjB,gGAAgG;QAChG,4FAA4F;QAC5F,gGAAgG;QAChG,oEAAoE;QACpE,UAAU,EAAE,KAAK,IAAmC,EAAE;YACpD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAEtD,4FAA4F;YAC5F,6FAA6F;YAC7F,0FAA0F;YAC1F,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;gBAC9C,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBAChD,YAAY,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;gBAElC,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;oBAC9B,OAAO;wBACL,MAAM,EAAE,UAAU;wBAClB,QAAQ;wBACR,QAAQ,EAAE,YAAY;wBACtB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;wBAClC,QAAQ,EAAE,IAAI;qBACf,CAAC;gBACJ,CAAC;gBACD,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;oBAChC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;gBACrG,CAAC;gBACD,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO;wBACL,MAAM,EAAE,QAAQ;wBAChB,QAAQ;wBACR,QAAQ,EAAE,YAAY;wBACtB,WAAW,EAAE,CAAC;wBACd,QAAQ,EAAE,KAAK;wBACf,KAAK,EAAE,IAAI,CAAC,KAAK;qBAClB,CAAC;gBACJ,CAAC;gBAED,uFAAuF;gBACvF,IAAI,IAAI,GAAG,QAAQ,GAAG,CAAC;oBAAE,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;YACvD,CAAC;YAED,4FAA4F;YAC5F,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACrG,CAAC;QACD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;YACpB,+FAA+F;YAC/F,gGAAgG;YAChG,2BAA2B;YAC3B,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;YACH,KAAK,MAAM,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n ArchiveAllPollResult,\n UseArchiveAllNotificationsOptions,\n} 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 { status: 'incomplete', category, archived: lastArchived, notArchived: 0, complete: false };\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 { status: 'incomplete', category, archived: lastArchived, notArchived: 0, complete: false };\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,21 +1,7 @@
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
- promise: Promise<import("@ttt-productions/query-core").PaginatedPage<import("@ttt-productions/query-core").WithId<NotificationHistoryItem>>>;
4
+ promise: Promise<import("@ttt-productions/query-core/react").PaginatedPage<import("@ttt-productions/query-core/types").WithId<NotificationHistoryItem>>>;
19
5
  error: Error | null;
20
6
  isError: boolean;
21
7
  isPending: boolean;
@@ -38,9 +24,9 @@ export declare function useNotificationHistory({ config, userId, category, enabl
38
24
  isRefetching: boolean;
39
25
  isStale: boolean;
40
26
  isEnabled: boolean;
41
- refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").PaginatedPage<import("@ttt-productions/query-core").WithId<NotificationHistoryItem>>, Error>>;
27
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core/react").PaginatedPage<import("@ttt-productions/query-core/types").WithId<NotificationHistoryItem>>, Error>>;
42
28
  fetchStatus: import("@tanstack/query-core").FetchStatus;
43
- data: import("@ttt-productions/query-core").WithId<NotificationHistoryItem>[] | undefined;
29
+ data: import("@ttt-productions/query-core/types").WithId<NotificationHistoryItem>[] | undefined;
44
30
  page: number;
45
31
  pageSize: number;
46
32
  hasNextPage: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useNotificationHistory.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAKA,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"}
@@ -1,23 +1,8 @@
1
1
  'use client';
2
- import { paginatedPageKey } from '@ttt-productions/query-core';
3
- import { useFirestorePaginated, useReadRetryState } from '@ttt-productions/query-core/react';
2
+ import { paginatedPageKey, useFirestorePaginated, useReadRetryState, } from '@ttt-productions/query-core/react';
4
3
  import { orderBy } from 'firebase/firestore';
5
4
  const DEFAULT_PAGE_SIZE = 20;
6
5
  const DEFAULT_STALE_TIME = 60_000;
7
- /**
8
- * Paginated read of ARCHIVED notification history (the two-tier system's history
9
- * tier). Personal history lives under a per-user path
10
- * (`userProfiles/{uid}/notificationHistory`) and shared/admin history under a
11
- * top-level collection (`adminNotificationHistory`) — both resolved from the
12
- * category's `historyPath`, so no `where` clause is needed either way. Rows are
13
- * immutable and ordered newest-first by `archivedAt`.
14
- *
15
- * Each history doc is a wrapper (`archivedSnapshot` + archive metadata + a
16
- * native-TTL `expireAt`); the `select` mapper flattens it into a
17
- * {@link NotificationHistoryItem} so the read surface renders like the active
18
- * list. Owner-only (user) / admin-only (admin) reads are enforced by Firestore
19
- * rules — this hook performs no writes.
20
- */
21
6
  export function useNotificationHistory({ config, userId, category, enabled = true, pageSize = DEFAULT_PAGE_SIZE, staleTime = DEFAULT_STALE_TIME, }) {
22
7
  const categoryConfig = config.categories[category];
23
8
  if (!categoryConfig) {
@@ -37,15 +22,13 @@ export function useNotificationHistory({ config, userId, category, enabled = tru
37
22
  const snapshot = (data.archivedSnapshot ?? {});
38
23
  return {
39
24
  ...snapshot,
40
- // The archive occurrence id is the stable history-doc key; expose
41
- // archivedAt for ordering/display.
42
25
  archiveOccurrenceId: data.id,
43
26
  archivedAt: data.archivedAt ?? snapshot.updatedAt ?? 0,
44
27
  };
45
28
  },
46
29
  });
47
- // The displayed page's failure held through its retry: a refetch of a never-succeeded page clears
48
- // `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.
49
32
  return { ...read, retryState: useReadRetryState(read, paginatedPageKey(queryKey, read.page)) };
50
33
  }
51
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,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC7F,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 { paginatedPageKey } from '@ttt-productions/query-core';\nimport { useFirestorePaginated, useReadRetryState } 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BvB"}
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) {
@@ -32,9 +11,7 @@ export function useUnreadCount({ config, userId, category, enabled = true, refet
32
11
  }
33
12
  const collectionPath = categoryConfig.activePath;
34
13
  const isPersonal = categoryConfig.audienceType === 'personal';
35
- const constraints = isPersonal && userId
36
- ? [where('targetUserId', '==', userId), where('seenAt', '==', 0)]
37
- : [];
14
+ const constraints = isPersonal && userId ? [where('targetUserId', '==', userId), where('seenAt', '==', 0)] : [];
38
15
  const { data, ...rest } = useFirestoreCount({
39
16
  collectionPath,
40
17
  queryKey: ['notifications', 'unread-count', category, userId],
@@ -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;QAClB,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,EAAE,CAAC;IAET,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\n ? [where('targetUserId', '==', userId), where('seenAt', '==', 0)]\n : [];\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,3 +1,11 @@
1
- export { useActiveNotifications, useNotificationHistory, useUnreadCount, useArchiveNotification, useArchiveAllNotifications, } from './hooks/index.js';
2
- export { NotificationList, NotificationHistoryList, NotificationEmptyState, NotificationUnreadBadge, } from './components/index.js';
1
+ export { useActiveNotifications } from './hooks/useActiveNotifications.js';
2
+ export { useNotificationHistory } from './hooks/useNotificationHistory.js';
3
+ export { useUnreadCount } from './hooks/useUnreadCount.js';
4
+ export { useArchiveNotification } from './hooks/useArchiveNotification.js';
5
+ export { useArchiveAllNotifications } from './hooks/useArchiveAllNotifications.js';
6
+ export { NotificationList } from './components/NotificationList.js';
7
+ export { NotificationHistoryList } from './components/NotificationHistoryList.js';
8
+ export { NotificationEmptyState } from './components/NotificationEmptyState.js';
9
+ export { NotificationUnreadBadge } from './components/NotificationUnreadBadge.js';
10
+ export type { NotificationListProps, NotificationHistoryListProps } from './types.js';
3
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,uBAAuB,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,6 +1,11 @@
1
- "use client";
2
- // Hooks
3
- export { useActiveNotifications, useNotificationHistory, useUnreadCount, useArchiveNotification, useArchiveAllNotifications, } from './hooks/index.js';
4
- // Components
5
- export { NotificationList, NotificationHistoryList, NotificationEmptyState, NotificationUnreadBadge, } from './components/index.js';
1
+ 'use client';
2
+ export { useActiveNotifications } from './hooks/useActiveNotifications.js';
3
+ export { useNotificationHistory } from './hooks/useNotificationHistory.js';
4
+ export { useUnreadCount } from './hooks/useUnreadCount.js';
5
+ export { useArchiveNotification } from './hooks/useArchiveNotification.js';
6
+ export { useArchiveAllNotifications } from './hooks/useArchiveAllNotifications.js';
7
+ export { NotificationList } from './components/NotificationList.js';
8
+ export { NotificationHistoryList } from './components/NotificationHistoryList.js';
9
+ export { NotificationEmptyState } from './components/NotificationEmptyState.js';
10
+ export { NotificationUnreadBadge } from './components/NotificationUnreadBadge.js';
6
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,QAAQ;AACR,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAE1B,aAAa;AACb,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC","sourcesContent":["\"use client\";\n\n// Hooks\nexport {\n useActiveNotifications,\n useNotificationHistory,\n useUnreadCount,\n useArchiveNotification,\n useArchiveAllNotifications,\n} from './hooks/index.js';\n\n// Components\nexport {\n NotificationList,\n NotificationHistoryList,\n NotificationEmptyState,\n NotificationUnreadBadge,\n} from './components/index.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"]}
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ArchiveAllJobSnapshot, ArchiveAllPollResult, EnqueueArchiveAllResult, NotificationArchiveFn, NotificationDoc, NotificationHistoryItem, NotificationListErrorState, NotificationRowActions, NotificationSystemConfig } from '../types.js';
3
+ export interface NotificationListProps {
4
+ config: NotificationSystemConfig;
5
+ userId: string;
6
+ category: string;
7
+ archiveFn: NotificationArchiveFn;
8
+ enqueueArchiveAllFn: (category: string) => Promise<EnqueueArchiveAllResult>;
9
+ getArchiveAllStatusFn: (jobId: string) => Promise<ArchiveAllJobSnapshot>;
10
+ title?: ReactNode;
11
+ onClearAll?: (result: ArchiveAllPollResult) => void;
12
+ refetchInterval?: number;
13
+ staleTime?: number;
14
+ emptyText?: string;
15
+ renderError: (state: NotificationListErrorState) => ReactNode;
16
+ onRenderedRowsChange?: (rows: readonly NotificationDoc[]) => void;
17
+ renderRowAction?: (notification: NotificationDoc, actions: NotificationRowActions) => ReactNode;
18
+ }
19
+ export interface NotificationHistoryListProps {
20
+ config: NotificationSystemConfig;
21
+ userId: string;
22
+ category: string;
23
+ pageSize?: number;
24
+ staleTime?: number;
25
+ emptyText?: string;
26
+ renderError: (state: NotificationListErrorState) => ReactNode;
27
+ title?: ReactNode;
28
+ renderRowAction?: (notification: NotificationHistoryItem, actions: NotificationRowActions) => ReactNode;
29
+ }
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +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 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"}