@ttt-productions/notification-core 0.2.1 → 0.2.2

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 (90) hide show
  1. package/dist/components/NotificationEmptyState.d.ts +6 -0
  2. package/dist/components/NotificationEmptyState.d.ts.map +1 -0
  3. package/dist/components/NotificationEmptyState.js +9 -0
  4. package/dist/components/NotificationEmptyState.js.map +1 -0
  5. package/dist/components/NotificationHistoryList.d.ts +7 -0
  6. package/dist/components/NotificationHistoryList.d.ts.map +1 -0
  7. package/dist/components/NotificationHistoryList.js +41 -0
  8. package/dist/components/NotificationHistoryList.js.map +1 -0
  9. package/dist/components/NotificationList.d.ts +6 -0
  10. package/dist/components/NotificationList.d.ts.map +1 -0
  11. package/dist/components/NotificationList.js +79 -0
  12. package/dist/components/NotificationList.js.map +1 -0
  13. package/dist/components/NotificationUnreadBadge.d.ts +8 -0
  14. package/dist/components/NotificationUnreadBadge.d.ts.map +1 -0
  15. package/dist/components/NotificationUnreadBadge.js +21 -0
  16. package/dist/components/NotificationUnreadBadge.js.map +1 -0
  17. package/dist/components/index.d.ts +5 -0
  18. package/dist/components/index.d.ts.map +1 -0
  19. package/dist/components/index.js +5 -0
  20. package/dist/components/index.js.map +1 -0
  21. package/dist/components/relative-time.d.ts +5 -0
  22. package/dist/components/relative-time.d.ts.map +1 -0
  23. package/dist/components/relative-time.js +23 -0
  24. package/dist/components/relative-time.js.map +1 -0
  25. package/dist/hooks/index.d.ts +4 -7
  26. package/dist/hooks/index.d.ts.map +1 -1
  27. package/dist/hooks/index.js +4 -3
  28. package/dist/hooks/index.js.map +1 -1
  29. package/dist/hooks/useActiveNotifications.d.ts +7 -0
  30. package/dist/hooks/useActiveNotifications.d.ts.map +1 -0
  31. package/dist/hooks/useActiveNotifications.js +30 -0
  32. package/dist/hooks/useActiveNotifications.js.map +1 -0
  33. package/dist/hooks/useArchiveAllNotifications.d.ts +24 -0
  34. package/dist/hooks/useArchiveAllNotifications.d.ts.map +1 -0
  35. package/dist/hooks/useArchiveAllNotifications.js +87 -0
  36. package/dist/hooks/useArchiveAllNotifications.js.map +1 -0
  37. package/dist/hooks/useArchiveNotification.d.ts +23 -0
  38. package/dist/hooks/useArchiveNotification.d.ts.map +1 -0
  39. package/dist/hooks/useArchiveNotification.js +68 -0
  40. package/dist/hooks/useArchiveNotification.js.map +1 -0
  41. package/dist/hooks/useNotificationHistory.d.ts +16 -0
  42. package/dist/hooks/useNotificationHistory.d.ts.map +1 -0
  43. package/dist/hooks/useNotificationHistory.js +35 -0
  44. package/dist/hooks/useNotificationHistory.js.map +1 -0
  45. package/dist/hooks/useUnreadCount.d.ts +175 -15
  46. package/dist/hooks/useUnreadCount.d.ts.map +1 -1
  47. package/dist/hooks/useUnreadCount.js +27 -13
  48. package/dist/hooks/useUnreadCount.js.map +1 -1
  49. package/dist/index.d.ts +3 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/server/archiveNotificationHelper.d.ts +31 -0
  54. package/dist/server/archiveNotificationHelper.d.ts.map +1 -0
  55. package/dist/server/archiveNotificationHelper.js +83 -0
  56. package/dist/server/archiveNotificationHelper.js.map +1 -0
  57. package/dist/server/createNotificationHelper.d.ts +25 -0
  58. package/dist/server/createNotificationHelper.d.ts.map +1 -0
  59. package/dist/server/createNotificationHelper.js +118 -0
  60. package/dist/server/createNotificationHelper.js.map +1 -0
  61. package/dist/server/index.d.ts +5 -0
  62. package/dist/server/index.d.ts.map +1 -0
  63. package/dist/server/index.js +5 -0
  64. package/dist/server/index.js.map +1 -0
  65. package/dist/server/processBatchHelper.d.ts +29 -0
  66. package/dist/server/processBatchHelper.d.ts.map +1 -0
  67. package/dist/server/processBatchHelper.js +158 -0
  68. package/dist/server/processBatchHelper.js.map +1 -0
  69. package/dist/server/types.d.ts +81 -0
  70. package/dist/server/types.d.ts.map +1 -0
  71. package/dist/server/types.js +6 -0
  72. package/dist/server/types.js.map +1 -0
  73. package/dist/types.d.ts +159 -44
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.js +6 -0
  76. package/dist/types.js.map +1 -1
  77. package/package.json +2 -2
  78. package/src/styles/notifications.css +206 -0
  79. package/dist/hooks/useMarkAllAsRead.d.ts +0 -30
  80. package/dist/hooks/useMarkAllAsRead.d.ts.map +0 -1
  81. package/dist/hooks/useMarkAllAsRead.js +0 -44
  82. package/dist/hooks/useMarkAllAsRead.js.map +0 -1
  83. package/dist/hooks/useMarkAsRead.d.ts +0 -30
  84. package/dist/hooks/useMarkAsRead.d.ts.map +0 -1
  85. package/dist/hooks/useMarkAsRead.js +0 -40
  86. package/dist/hooks/useMarkAsRead.js.map +0 -1
  87. package/dist/hooks/useNotifications.d.ts +0 -25
  88. package/dist/hooks/useNotifications.d.ts.map +0 -1
  89. package/dist/hooks/useNotifications.js +0 -31
  90. package/dist/hooks/useNotifications.js.map +0 -1
@@ -0,0 +1,16 @@
1
+ import type { NotificationHistoryDoc, UseNotificationHistoryOptions } from '../types.js';
2
+ /**
3
+ * Paginated fetch of notification history (archived notifications).
4
+ * No polling — history is a static archive.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const { data, page, nextPage, hasNextPage } = useNotificationHistory({
9
+ * config: TTT_NOTIFICATION_CONFIG,
10
+ * userId: user.uid,
11
+ * category: 'user',
12
+ * });
13
+ * ```
14
+ */
15
+ export declare function useNotificationHistory({ config, userId, category, enabled, pageSize, }: UseNotificationHistoryOptions): import("@ttt-productions/query-core").UseFirestorePaginatedResult<NotificationHistoryDoc>;
16
+ //# sourceMappingURL=useNotificationHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNotificationHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAIzF;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,QAA4B,GAC7B,EAAE,6BAA6B,6FAmB/B"}
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { useFirestorePaginated } from '@ttt-productions/query-core';
3
+ import { orderBy } from 'firebase/firestore';
4
+ const DEFAULT_PAGE_SIZE = 20;
5
+ /**
6
+ * Paginated fetch of notification history (archived notifications).
7
+ * No polling — history is a static archive.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { data, page, nextPage, hasNextPage } = useNotificationHistory({
12
+ * config: TTT_NOTIFICATION_CONFIG,
13
+ * userId: user.uid,
14
+ * category: 'user',
15
+ * });
16
+ * ```
17
+ */
18
+ export function useNotificationHistory({ config, userId, category, enabled = true, pageSize = DEFAULT_PAGE_SIZE, }) {
19
+ const categoryConfig = config.categories[category];
20
+ if (!categoryConfig) {
21
+ throw new Error(`[notification-core] Unknown category: ${category}`);
22
+ }
23
+ const collectionPath = categoryConfig.historyPath(userId);
24
+ const constraints = [
25
+ orderBy('archival.archivedAt', 'desc'),
26
+ ];
27
+ return useFirestorePaginated({
28
+ collectionPath,
29
+ queryKey: ['notifications', 'history', category, userId, { pageSize }],
30
+ constraints,
31
+ pageSize,
32
+ enabled: enabled && !!userId,
33
+ });
34
+ }
35
+ //# sourceMappingURL=useNotificationHistory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNotificationHistory.js","sourceRoot":"","sources":["../../src/hooks/useNotificationHistory.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAGnE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,iBAAiB,GACE;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;QACrC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;KACvC,CAAC;IAEF,OAAO,qBAAqB,CAAyB;QACnD,cAAc;QACd,QAAQ,EAAE,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC;QACtE,WAAW;QACX,QAAQ;QACR,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;KAC7B,CAAC,CAAC;AACL,CAAC"}
@@ -1,24 +1,184 @@
1
- export interface UseUnreadCountOptions {
2
- /** User ID to fetch unread count for */
3
- userId: string;
4
- /** Enable/disable the query */
5
- enabled?: boolean;
6
- /** Enable realtime updates (default: true) */
7
- subscribe?: boolean;
8
- }
1
+ import type { NotificationDoc, UseUnreadCountOptions } from '../types.js';
9
2
  /**
10
- * Get the count of unread notifications for a user.
3
+ * Lightweight unread count query. Fetches up to `countLimit` docs to determine count.
4
+ * Uses polling for cost control.
11
5
  *
12
6
  * @example
13
7
  * ```tsx
14
- * const { data: unreadCount } = useUnreadCount({
15
- * userId: currentUser.uid,
16
- * subscribe: true,
8
+ * const { count, isLoading } = useUnreadCount({
9
+ * config: TTT_NOTIFICATION_CONFIG,
10
+ * userId: user.uid,
11
+ * category: 'user',
17
12
  * });
18
13
  * ```
19
14
  */
20
- export declare function useUnreadCount({ userId, enabled, subscribe, }: UseUnreadCountOptions): {
21
- data: any;
22
- notifications: any;
15
+ export declare function useUnreadCount({ config, userId, category, enabled, refetchInterval, countLimit, }: UseUnreadCountOptions): {
16
+ error: Error;
17
+ isError: true;
18
+ isPending: false;
19
+ isLoading: false;
20
+ isLoadingError: false;
21
+ isRefetchError: true;
22
+ isSuccess: false;
23
+ isPlaceholderData: false;
24
+ status: "error";
25
+ dataUpdatedAt: number;
26
+ errorUpdatedAt: number;
27
+ failureCount: number;
28
+ failureReason: Error | null;
29
+ errorUpdateCount: number;
30
+ isFetched: boolean;
31
+ isFetchedAfterMount: boolean;
32
+ isFetching: boolean;
33
+ isInitialLoading: boolean;
34
+ isPaused: boolean;
35
+ isRefetching: boolean;
36
+ isStale: boolean;
37
+ isEnabled: boolean;
38
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
39
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
40
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
41
+ count: number;
42
+ hasMore: boolean;
43
+ } | {
44
+ error: null;
45
+ isError: false;
46
+ isPending: false;
47
+ isLoading: false;
48
+ isLoadingError: false;
49
+ isRefetchError: false;
50
+ isSuccess: true;
51
+ isPlaceholderData: false;
52
+ status: "success";
53
+ dataUpdatedAt: number;
54
+ errorUpdatedAt: number;
55
+ failureCount: number;
56
+ failureReason: Error | null;
57
+ errorUpdateCount: number;
58
+ isFetched: boolean;
59
+ isFetchedAfterMount: boolean;
60
+ isFetching: boolean;
61
+ isInitialLoading: boolean;
62
+ isPaused: boolean;
63
+ isRefetching: boolean;
64
+ isStale: boolean;
65
+ isEnabled: boolean;
66
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
67
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
68
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
69
+ count: number;
70
+ hasMore: boolean;
71
+ } | {
72
+ error: Error;
73
+ isError: true;
74
+ isPending: false;
75
+ isLoading: false;
76
+ isLoadingError: true;
77
+ isRefetchError: false;
78
+ isSuccess: false;
79
+ isPlaceholderData: false;
80
+ status: "error";
81
+ dataUpdatedAt: number;
82
+ errorUpdatedAt: number;
83
+ failureCount: number;
84
+ failureReason: Error | null;
85
+ errorUpdateCount: number;
86
+ isFetched: boolean;
87
+ isFetchedAfterMount: boolean;
88
+ isFetching: boolean;
89
+ isInitialLoading: boolean;
90
+ isPaused: boolean;
91
+ isRefetching: boolean;
92
+ isStale: boolean;
93
+ isEnabled: boolean;
94
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
95
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
96
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
97
+ count: number;
98
+ hasMore: boolean;
99
+ } | {
100
+ error: null;
101
+ isError: false;
102
+ isPending: true;
103
+ isLoading: true;
104
+ isLoadingError: false;
105
+ isRefetchError: false;
106
+ isSuccess: false;
107
+ isPlaceholderData: false;
108
+ status: "pending";
109
+ dataUpdatedAt: number;
110
+ errorUpdatedAt: number;
111
+ failureCount: number;
112
+ failureReason: Error | null;
113
+ errorUpdateCount: number;
114
+ isFetched: boolean;
115
+ isFetchedAfterMount: boolean;
116
+ isFetching: boolean;
117
+ isInitialLoading: boolean;
118
+ isPaused: boolean;
119
+ isRefetching: boolean;
120
+ isStale: boolean;
121
+ isEnabled: boolean;
122
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
123
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
124
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
125
+ count: number;
126
+ hasMore: boolean;
127
+ } | {
128
+ error: null;
129
+ isError: false;
130
+ isPending: true;
131
+ isLoadingError: false;
132
+ isRefetchError: false;
133
+ isSuccess: false;
134
+ isPlaceholderData: false;
135
+ status: "pending";
136
+ dataUpdatedAt: number;
137
+ errorUpdatedAt: number;
138
+ failureCount: number;
139
+ failureReason: Error | null;
140
+ errorUpdateCount: number;
141
+ isFetched: boolean;
142
+ isFetchedAfterMount: boolean;
143
+ isFetching: boolean;
144
+ isLoading: boolean;
145
+ isInitialLoading: boolean;
146
+ isPaused: boolean;
147
+ isRefetching: boolean;
148
+ isStale: boolean;
149
+ isEnabled: boolean;
150
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
151
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
152
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
153
+ count: number;
154
+ hasMore: boolean;
155
+ } | {
156
+ isError: false;
157
+ error: null;
158
+ isPending: false;
159
+ isLoading: false;
160
+ isLoadingError: false;
161
+ isRefetchError: false;
162
+ isSuccess: true;
163
+ isPlaceholderData: true;
164
+ status: "success";
165
+ dataUpdatedAt: number;
166
+ errorUpdatedAt: number;
167
+ failureCount: number;
168
+ failureReason: Error | null;
169
+ errorUpdateCount: number;
170
+ isFetched: boolean;
171
+ isFetchedAfterMount: boolean;
172
+ isFetching: boolean;
173
+ isInitialLoading: boolean;
174
+ isPaused: boolean;
175
+ isRefetching: boolean;
176
+ isStale: boolean;
177
+ isEnabled: boolean;
178
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@ttt-productions/query-core").WithId<NotificationDoc>[], Error>>;
179
+ fetchStatus: import("@tanstack/query-core").FetchStatus;
180
+ promise: Promise<import("@ttt-productions/query-core").WithId<NotificationDoc>[]>;
181
+ count: number;
182
+ hasMore: boolean;
23
183
  };
24
184
  //# sourceMappingURL=useUnreadCount.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUnreadCount.d.ts","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAc,EACd,SAAgB,GACjB,EAAE,qBAAqB;;;EAiBvB"}
1
+ {"version":3,"file":"useUnreadCount.d.ts","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAK1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAc,EACd,eAA0C,EAC1C,UAAgC,GACjC,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BvB"}
@@ -1,31 +1,45 @@
1
1
  'use client';
2
2
  import { useFirestoreCollection } from '@ttt-productions/query-core';
3
- import { where } from 'firebase/firestore';
3
+ import { where, orderBy, limit } from 'firebase/firestore';
4
+ const DEFAULT_REFETCH_INTERVAL = 30_000;
5
+ const DEFAULT_COUNT_LIMIT = 99;
4
6
  /**
5
- * Get the count of unread notifications for a user.
7
+ * Lightweight unread count query. Fetches up to `countLimit` docs to determine count.
8
+ * Uses polling for cost control.
6
9
  *
7
10
  * @example
8
11
  * ```tsx
9
- * const { data: unreadCount } = useUnreadCount({
10
- * userId: currentUser.uid,
11
- * subscribe: true,
12
+ * const { count, isLoading } = useUnreadCount({
13
+ * config: TTT_NOTIFICATION_CONFIG,
14
+ * userId: user.uid,
15
+ * category: 'user',
12
16
  * });
13
17
  * ```
14
18
  */
15
- export function useUnreadCount({ userId, enabled = true, subscribe = true, }) {
19
+ export function useUnreadCount({ config, userId, category, enabled = true, refetchInterval = DEFAULT_REFETCH_INTERVAL, countLimit = DEFAULT_COUNT_LIMIT, }) {
20
+ const categoryConfig = config.categories[category];
21
+ if (!categoryConfig) {
22
+ throw new Error(`[notification-core] Unknown category: ${category}`);
23
+ }
24
+ const collectionPath = categoryConfig.activePath;
25
+ const isPersonal = categoryConfig.audienceType === 'personal';
16
26
  const constraints = [
17
- where('isRead', '==', false),
27
+ ...(isPersonal ? [where('targetUserId', '==', userId)] : []),
28
+ orderBy('updatedAt', 'desc'),
29
+ limit(countLimit),
18
30
  ];
19
- const { data: notifications } = useFirestoreCollection({
20
- collectionPath: `userData/${userId}/metadata/notifications`,
21
- queryKey: ['notifications-unread-count', userId],
31
+ const { data: notifications, ...rest } = useFirestoreCollection({
32
+ collectionPath,
33
+ queryKey: ['notifications', 'unread-count', category, userId],
22
34
  constraints,
23
35
  enabled: enabled && !!userId,
24
- subscribe,
36
+ subscribe: false,
37
+ staleTime: refetchInterval,
25
38
  });
26
39
  return {
27
- data: notifications?.length ?? 0,
28
- notifications,
40
+ count: notifications?.length ?? 0,
41
+ hasMore: (notifications?.length ?? 0) >= countLimit,
42
+ ...rest,
29
43
  };
30
44
  }
31
45
  //# sourceMappingURL=useUnreadCount.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUnreadCount.js","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAYjE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,OAAO,GAAG,IAAI,EACd,SAAS,GAAG,IAAI,GACM;IACtB,MAAM,WAAW,GAAsB;QACrC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC;KAC7B,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,sBAAsB,CAAe;QACnE,cAAc,EAAE,YAAY,MAAM,yBAAyB;QAC3D,QAAQ,EAAE,CAAC,4BAA4B,EAAE,MAAM,CAAC;QAChD,WAAW;QACX,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS;KACV,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;QAChC,aAAa;KACd,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"useUnreadCount.js","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAGjF,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;;;;;;;;;;;GAYG;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,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;QAC5B,KAAK,CAAC,UAAU,CAAC;KAClB,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,sBAAsB,CAAkB;QAC/E,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,KAAK;QAChB,SAAS,EAAE,eAAe;KAC3B,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;QACjC,OAAO,EAAE,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAC,IAAI,UAAU;QACnD,GAAG,IAAI;KACR,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { Notification, NotificationInput, NotificationType, MarkAsReadOptions, DeleteNotificationOptions, NavigationHandler, NotificationTypeConfig, NotificationConfig, } from './types.js';
2
- export { useNotifications, useUnreadCount, useMarkAsRead, useMarkAllAsRead, } from './hooks/index.js';
3
- export type { UseNotificationsOptions, UseUnreadCountOptions, UseMarkAsReadOptions, UseMarkAllAsReadOptions, } from './hooks/index.js';
1
+ export type { NotificationDoc, NotificationHistoryDoc, ArchivalInfo, PendingNotification, NotificationCategoryConfig, NotificationTypeConfig, NotificationSystemConfig, UseActiveNotificationsOptions, UseUnreadCountOptions, UseArchiveNotificationOptions, UseArchiveAllNotificationsOptions, UseNotificationHistoryOptions, NotificationListProps, NotificationEmptyStateProps, NotificationHistoryListProps, NotificationUnreadBadgeProps, } from './types.js';
2
+ export { useActiveNotifications, useUnreadCount, useArchiveNotification, useArchiveAllNotifications, useNotificationHistory, } from './hooks/index.js';
3
+ export { NotificationList, NotificationEmptyState, NotificationHistoryList, NotificationUnreadBadge, } from './components/index.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAEV,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EAEnB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EAExB,6BAA6B,EAC7B,qBAAqB,EACrB,6BAA6B,EAC7B,iCAAiC,EACjC,6BAA6B,EAE7B,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  // Hooks
2
- export { useNotifications, useUnreadCount, useMarkAsRead, useMarkAllAsRead, } from './hooks/index.js';
2
+ export { useActiveNotifications, useUnreadCount, useArchiveNotification, useArchiveAllNotifications, useNotificationHistory, } from './hooks/index.js';
3
+ // Components
4
+ export { NotificationList, NotificationEmptyState, NotificationHistoryList, NotificationUnreadBadge, } from './components/index.js';
3
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,QAAQ;AACR,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,QAAQ;AACR,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,aAAa;AACb,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Archive notification helper — used by callable Cloud Functions.
3
+ * Moves active → history with ArchivalInfo, deletes from active.
4
+ */
5
+ import type { NotificationSystemConfig, ArchivalInfo } from '../types.js';
6
+ import type { ServerFirestore } from './types.js';
7
+ interface ArchiveInput {
8
+ notificationId: string;
9
+ category: string;
10
+ userId: string;
11
+ archivalInfo: ArchivalInfo;
12
+ }
13
+ interface ArchiveAllInput {
14
+ category: string;
15
+ userId: string;
16
+ archivalInfo: ArchivalInfo;
17
+ }
18
+ interface ArchiveResult {
19
+ success: boolean;
20
+ archived: number;
21
+ }
22
+ /**
23
+ * Archive a single notification on the server side.
24
+ */
25
+ export declare function archiveNotificationHelper(db: ServerFirestore, config: NotificationSystemConfig, input: ArchiveInput): Promise<ArchiveResult>;
26
+ /**
27
+ * Archive all active notifications for a user/category on the server side.
28
+ */
29
+ export declare function archiveAllNotificationsHelper(db: ServerFirestore, config: NotificationSystemConfig, input: ArchiveAllInput): Promise<ArchiveResult>;
30
+ export {};
31
+ //# sourceMappingURL=archiveNotificationHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archiveNotificationHelper.d.ts","sourceRoot":"","sources":["../../src/server/archiveNotificationHelper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,UAAU,YAAY;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,UAAU,aAAa;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,wBAAwB,EAChC,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,aAAa,CAAC,CAmCxB;AAED;;GAEG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,wBAAwB,EAChC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,aAAa,CAAC,CAoDxB"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Archive notification helper — used by callable Cloud Functions.
3
+ * Moves active → history with ArchivalInfo, deletes from active.
4
+ */
5
+ /**
6
+ * Archive a single notification on the server side.
7
+ */
8
+ export async function archiveNotificationHelper(db, config, input) {
9
+ const { notificationId, category, userId, archivalInfo } = input;
10
+ const categoryConfig = config.categories[category];
11
+ if (!categoryConfig) {
12
+ throw new Error(`[notification-core] Unknown category: ${category}`);
13
+ }
14
+ const activePath = categoryConfig.activePath;
15
+ const historyPath = categoryConfig.historyPath(userId);
16
+ const activeRef = db.doc(`${activePath}/${notificationId}`);
17
+ const activeSnap = await activeRef.get();
18
+ if (!activeSnap.exists) {
19
+ return { success: true, archived: 0 };
20
+ }
21
+ const activeData = activeSnap.data();
22
+ const historyDoc = {
23
+ ...activeData,
24
+ id: notificationId,
25
+ archival: archivalInfo,
26
+ ...(categoryConfig.audienceType === 'shared'
27
+ ? { handledBy: archivalInfo.archivedBy }
28
+ : {}),
29
+ };
30
+ const batch = db.batch();
31
+ const historyRef = db.doc(`${historyPath}/${notificationId}`);
32
+ batch.set(historyRef, historyDoc);
33
+ batch.delete(activeRef);
34
+ await batch.commit();
35
+ return { success: true, archived: 1 };
36
+ }
37
+ /**
38
+ * Archive all active notifications for a user/category on the server side.
39
+ */
40
+ export async function archiveAllNotificationsHelper(db, config, input) {
41
+ const { category, userId, archivalInfo } = input;
42
+ const categoryConfig = config.categories[category];
43
+ if (!categoryConfig) {
44
+ throw new Error(`[notification-core] Unknown category: ${category}`);
45
+ }
46
+ const activePath = categoryConfig.activePath;
47
+ const historyPath = categoryConfig.historyPath(userId);
48
+ const isPersonal = categoryConfig.audienceType === 'personal';
49
+ let totalArchived = 0;
50
+ let hasMore = true;
51
+ while (hasMore) {
52
+ const baseQuery = db.collection(activePath);
53
+ const q = isPersonal
54
+ ? baseQuery.where('targetUserId', '==', userId).orderBy('updatedAt', 'desc').limit(50)
55
+ : baseQuery.orderBy('updatedAt', 'desc').limit(50);
56
+ const snapshot = await q.get();
57
+ if (snapshot.empty) {
58
+ hasMore = false;
59
+ break;
60
+ }
61
+ const batch = db.batch();
62
+ for (const docSnap of snapshot.docs) {
63
+ const data = docSnap.data();
64
+ const historyRef = db.doc(`${historyPath}/${docSnap.id}`);
65
+ batch.set(historyRef, {
66
+ ...data,
67
+ id: docSnap.id,
68
+ archival: archivalInfo,
69
+ ...(categoryConfig.audienceType === 'shared'
70
+ ? { handledBy: archivalInfo.archivedBy }
71
+ : {}),
72
+ });
73
+ batch.delete(docSnap.ref);
74
+ }
75
+ await batch.commit();
76
+ totalArchived += snapshot.docs.length;
77
+ if (snapshot.docs.length < 50) {
78
+ hasMore = false;
79
+ }
80
+ }
81
+ return { success: true, archived: totalArchived };
82
+ }
83
+ //# sourceMappingURL=archiveNotificationHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archiveNotificationHelper.js","sourceRoot":"","sources":["../../src/server/archiveNotificationHelper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Factory that creates the notification sending helper.
3
+ * This is THE single entry point for all notification creation in any app.
4
+ */
5
+ import type { NotificationSystemConfig } from '../types.js';
6
+ import type { ServerFirestore, NotificationHelper } from './types.js';
7
+ /**
8
+ * Create a notification helper bound to a Firestore instance and config.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // In your Cloud Function:
13
+ * import admin from 'firebase-admin';
14
+ * import { createNotificationHelper } from '@ttt-productions/notification-core/server';
15
+ * import { TTT_NOTIFICATION_CONFIG } from './notification-config.js';
16
+ *
17
+ * const db = admin.firestore();
18
+ * const notifier = createNotificationHelper(db as any, TTT_NOTIFICATION_CONFIG);
19
+ *
20
+ * // Auto-selects delivery mode based on type config:
21
+ * await notifier.send({ type: 'content_report', actorId: '...', actorName: '...', metadata: {...} });
22
+ * ```
23
+ */
24
+ export declare function createNotificationHelper(db: ServerFirestore, config: NotificationSystemConfig): NotificationHelper;
25
+ //# sourceMappingURL=createNotificationHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNotificationHelper.d.ts","sourceRoot":"","sources":["../../src/server/createNotificationHelper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EACV,eAAe,EAEf,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,wBAAwB,GAC/B,kBAAkB,CAgHpB"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Factory that creates the notification sending helper.
3
+ * This is THE single entry point for all notification creation in any app.
4
+ */
5
+ const DEFAULT_COUNT_CAP = 5000;
6
+ const DEFAULT_ACTOR_CAP = 5;
7
+ /**
8
+ * Create a notification helper bound to a Firestore instance and config.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // In your Cloud Function:
13
+ * import admin from 'firebase-admin';
14
+ * import { createNotificationHelper } from '@ttt-productions/notification-core/server';
15
+ * import { TTT_NOTIFICATION_CONFIG } from './notification-config.js';
16
+ *
17
+ * const db = admin.firestore();
18
+ * const notifier = createNotificationHelper(db as any, TTT_NOTIFICATION_CONFIG);
19
+ *
20
+ * // Auto-selects delivery mode based on type config:
21
+ * await notifier.send({ type: 'content_report', actorId: '...', actorName: '...', metadata: {...} });
22
+ * ```
23
+ */
24
+ export function createNotificationHelper(db, config) {
25
+ const pendingPath = config.pendingCollectionPath ?? 'pendingNotifications';
26
+ function getTypeConfig(type) {
27
+ const typeConfig = config.types[type];
28
+ if (!typeConfig) {
29
+ throw new Error(`[notification-core] Unknown notification type: ${type}`);
30
+ }
31
+ const categoryConfig = config.categories[typeConfig.category];
32
+ if (!categoryConfig) {
33
+ throw new Error(`[notification-core] Unknown category: ${typeConfig.category}`);
34
+ }
35
+ return { typeConfig, categoryConfig };
36
+ }
37
+ function resolveTargetPath(defaultPath, metadata) {
38
+ return typeof defaultPath === 'function' ? defaultPath(metadata) : defaultPath;
39
+ }
40
+ async function sendRealTime(input) {
41
+ const { type, actorId, actorName, targetUserId, metadata } = input;
42
+ const { typeConfig, categoryConfig } = getTypeConfig(type);
43
+ const activePath = categoryConfig.activePath;
44
+ const dedupKey = typeConfig.dedupKeyPattern(metadata);
45
+ const countCap = typeConfig.countCap ?? DEFAULT_COUNT_CAP;
46
+ const actorCap = typeConfig.actorCap ?? DEFAULT_ACTOR_CAP;
47
+ // Dedup check: query for existing doc with same dedupKey
48
+ const activeCollection = db.collection(activePath);
49
+ const existingQuery = activeCollection
50
+ .where('dedupKey', '==', dedupKey)
51
+ .where('category', '==', typeConfig.category)
52
+ .limit(1);
53
+ const existingSnap = await existingQuery.get();
54
+ if (!existingSnap.empty) {
55
+ // Increment existing notification
56
+ const existingDoc = existingSnap.docs[0];
57
+ const existingData = existingDoc.data();
58
+ const currentCount = existingData.count || 1;
59
+ const currentActorIds = existingData.latestActorIds || [];
60
+ const currentActorNames = existingData.latestActorNames || [];
61
+ // Cap actors
62
+ const newActorIds = [actorId, ...currentActorIds.filter((id) => id !== actorId)].slice(0, actorCap);
63
+ const newActorNames = [actorName, ...currentActorNames.filter((_, i) => currentActorIds[i] !== actorId)].slice(0, actorCap);
64
+ const newCount = Math.min(currentCount + 1, countCap);
65
+ await existingDoc.ref.update({
66
+ count: newCount,
67
+ latestActorIds: newActorIds,
68
+ latestActorNames: newActorNames,
69
+ message: typeConfig.messagePattern(metadata, newCount),
70
+ updatedAt: Date.now(),
71
+ });
72
+ }
73
+ else {
74
+ // Create new notification
75
+ const targetPath = resolveTargetPath(typeConfig.defaultTargetPath, metadata);
76
+ const now = Date.now();
77
+ const newDoc = {
78
+ type,
79
+ dedupKey,
80
+ category: typeConfig.category,
81
+ targetUserId: targetUserId ?? null,
82
+ title: typeConfig.titlePattern(metadata),
83
+ message: typeConfig.messagePattern(metadata, 1),
84
+ count: 1,
85
+ latestActorIds: [actorId],
86
+ latestActorNames: [actorName],
87
+ targetPath,
88
+ metadata,
89
+ createdAt: now,
90
+ updatedAt: now,
91
+ };
92
+ await activeCollection.add(newDoc);
93
+ }
94
+ }
95
+ async function queueForBatch(input) {
96
+ const { type, actorId, actorName, targetUserId, metadata } = input;
97
+ const { typeConfig } = getTypeConfig(type);
98
+ const pendingDoc = {
99
+ type,
100
+ category: typeConfig.category,
101
+ targetUserId: targetUserId ?? null,
102
+ actorId,
103
+ actorName,
104
+ metadata,
105
+ createdAt: Date.now(),
106
+ };
107
+ await db.collection(pendingPath).add(pendingDoc);
108
+ }
109
+ async function send(input) {
110
+ const { typeConfig } = getTypeConfig(input.type);
111
+ if (typeConfig.delivery === 'realtime') {
112
+ return sendRealTime(input);
113
+ }
114
+ return queueForBatch(input);
115
+ }
116
+ return { send, sendRealTime, queueForBatch };
117
+ }
118
+ //# sourceMappingURL=createNotificationHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNotificationHelper.js","sourceRoot":"","sources":["../../src/server/createNotificationHelper.ts"],"names":[],"mappings":"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"}