@checkstack/notification-common 0.1.0 → 0.2.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # @checkstack/notification-common
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [83557c7]
8
+ - @checkstack/common@0.4.0
9
+ - @checkstack/signal-common@0.1.2
10
+
11
+ ## 0.2.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 7a23261: ## TanStack Query Integration
16
+
17
+ Migrated all frontend components to use `usePluginClient` hook with TanStack Query integration, replacing the legacy `forPlugin()` pattern.
18
+
19
+ ### New Features
20
+
21
+ - **`usePluginClient` hook**: Provides type-safe access to plugin APIs with `.useQuery()` and `.useMutation()` methods
22
+ - **Automatic request deduplication**: Multiple components requesting the same data share a single network request
23
+ - **Built-in caching**: Configurable stale time and cache duration per query
24
+ - **Loading/error states**: TanStack Query provides `isLoading`, `error`, `isRefetching` states automatically
25
+ - **Background refetching**: Stale data is automatically refreshed when components mount
26
+
27
+ ### Contract Changes
28
+
29
+ All RPC contracts now require `operationType: "query"` or `operationType: "mutation"` metadata:
30
+
31
+ ```typescript
32
+ const getItems = proc()
33
+ .meta({ operationType: "query", access: [access.read] })
34
+ .output(z.array(itemSchema))
35
+ .query();
36
+
37
+ const createItem = proc()
38
+ .meta({ operationType: "mutation", access: [access.manage] })
39
+ .input(createItemSchema)
40
+ .output(itemSchema)
41
+ .mutation();
42
+ ```
43
+
44
+ ### Migration
45
+
46
+ ```typescript
47
+ // Before (forPlugin pattern)
48
+ const api = useApi(myPluginApiRef);
49
+ const [items, setItems] = useState<Item[]>([]);
50
+ useEffect(() => {
51
+ api.getItems().then(setItems);
52
+ }, [api]);
53
+
54
+ // After (usePluginClient pattern)
55
+ const client = usePluginClient(MyPluginApi);
56
+ const { data: items, isLoading } = client.getItems.useQuery({});
57
+ ```
58
+
59
+ ### Bug Fixes
60
+
61
+ - Fixed `rpc.test.ts` test setup for middleware type inference
62
+ - Fixed `SearchDialog` to use `setQuery` instead of deprecated `search` method
63
+ - Fixed null→undefined warnings in notification and queue frontends
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies [7a23261]
68
+ - @checkstack/common@0.3.0
69
+ - @checkstack/signal-common@0.1.1
70
+
3
71
  ## 0.1.0
4
72
 
5
73
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/notification-common",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,11 +1,7 @@
1
- import { oc } from "@orpc/contract";
2
1
  import { z } from "zod";
3
2
  import { notificationAccess } from "./access";
4
3
  import { pluginMetadata } from "./plugin-metadata";
5
- import {
6
- createClientDefinition,
7
- type ProcedureMetadata,
8
- } from "@checkstack/common";
4
+ import { createClientDefinition, proc } from "@checkstack/common";
9
5
  import {
10
6
  NotificationSchema,
11
7
  NotificationGroupSchema,
@@ -14,9 +10,6 @@ import {
14
10
  PaginationInputSchema,
15
11
  } from "./schemas";
16
12
 
17
- // Base builder with full metadata support (userType + access)
18
- const _base = oc.$meta<ProcedureMetadata>({});
19
-
20
13
  // Notification RPC Contract
21
14
  export const notificationContract = {
22
15
  // ==========================================================================
@@ -24,10 +17,11 @@ export const notificationContract = {
24
17
  // ==========================================================================
25
18
 
26
19
  // Get current user's notifications (paginated)
27
- getNotifications: _base
28
- .meta({
29
- userType: "user",
30
- })
20
+ getNotifications: proc({
21
+ operationType: "query",
22
+ userType: "user",
23
+ access: [],
24
+ })
31
25
  .input(PaginationInputSchema)
32
26
  .output(
33
27
  z.object({
@@ -37,17 +31,18 @@ export const notificationContract = {
37
31
  ),
38
32
 
39
33
  // Get unread count for badge
40
- getUnreadCount: _base
41
- .meta({
42
- userType: "user",
43
- })
44
- .output(z.object({ count: z.number() })),
34
+ getUnreadCount: proc({
35
+ operationType: "query",
36
+ userType: "user",
37
+ access: [],
38
+ }).output(z.object({ count: z.number() })),
45
39
 
46
40
  // Mark notification(s) as read
47
- markAsRead: _base
48
- .meta({
49
- userType: "user",
50
- })
41
+ markAsRead: proc({
42
+ operationType: "mutation",
43
+ userType: "user",
44
+ access: [],
45
+ })
51
46
  .input(
52
47
  z.object({
53
48
  notificationId: z.string().uuid().optional(), // If not provided, mark all as read
@@ -56,10 +51,11 @@ export const notificationContract = {
56
51
  .output(z.void()),
57
52
 
58
53
  // Delete a notification
59
- deleteNotification: _base
60
- .meta({
61
- userType: "user",
62
- })
54
+ deleteNotification: proc({
55
+ operationType: "mutation",
56
+ userType: "user",
57
+ access: [],
58
+ })
63
59
  .input(z.object({ notificationId: z.string().uuid() }))
64
60
  .output(z.void()),
65
61
 
@@ -68,61 +64,61 @@ export const notificationContract = {
68
64
  // ==========================================================================
69
65
 
70
66
  // Get all available notification groups
71
- getGroups: _base
72
- .meta({
73
- userType: "authenticated", // Services can read groups too
74
- })
75
- .output(z.array(NotificationGroupSchema)),
67
+ getGroups: proc({
68
+ operationType: "query",
69
+ userType: "authenticated",
70
+ access: [],
71
+ }).output(z.array(NotificationGroupSchema)),
76
72
 
77
73
  // Get current user's subscriptions with group details
78
- getSubscriptions: _base
79
- .meta({
80
- userType: "user",
81
- })
82
- .output(z.array(EnrichedSubscriptionSchema)),
74
+ getSubscriptions: proc({
75
+ operationType: "query",
76
+ userType: "user",
77
+ access: [],
78
+ }).output(z.array(EnrichedSubscriptionSchema)),
83
79
 
84
80
  // Subscribe to a notification group
85
- subscribe: _base
86
- .meta({
87
- userType: "user",
88
- })
81
+ subscribe: proc({
82
+ operationType: "mutation",
83
+ userType: "user",
84
+ access: [],
85
+ })
89
86
  .input(z.object({ groupId: z.string() }))
90
87
  .output(z.void()),
91
88
 
92
89
  // Unsubscribe from a notification group
93
- unsubscribe: _base
94
- .meta({
95
- userType: "user",
96
- })
90
+ unsubscribe: proc({
91
+ operationType: "mutation",
92
+ userType: "user",
93
+ access: [],
94
+ })
97
95
  .input(z.object({ groupId: z.string() }))
98
96
  .output(z.void()),
99
97
 
100
98
  // ==========================================================================
101
- // ADMIN SETTINGS ENDPOINTS (userType: "user" with admin accesss)
99
+ // ADMIN SETTINGS ENDPOINTS (userType: "user" with admin access)
102
100
  // ==========================================================================
103
101
 
104
102
  // Get retention schema for DynamicForm
105
- getRetentionSchema: _base
106
- .meta({
107
- userType: "user",
108
- access: [notificationAccess.admin],
109
- })
110
- .output(z.record(z.string(), z.unknown())),
103
+ getRetentionSchema: proc({
104
+ operationType: "query",
105
+ userType: "user",
106
+ access: [notificationAccess.admin],
107
+ }).output(z.record(z.string(), z.unknown())),
111
108
 
112
109
  // Get retention settings
113
- getRetentionSettings: _base
114
- .meta({
115
- userType: "user",
116
- access: [notificationAccess.admin],
117
- })
118
- .output(RetentionSettingsSchema),
110
+ getRetentionSettings: proc({
111
+ operationType: "query",
112
+ userType: "user",
113
+ access: [notificationAccess.admin],
114
+ }).output(RetentionSettingsSchema),
119
115
 
120
116
  // Update retention settings
121
- setRetentionSettings: _base
122
- .meta({
123
- userType: "user",
124
- access: [notificationAccess.admin],
125
- })
117
+ setRetentionSettings: proc({
118
+ operationType: "mutation",
119
+ userType: "user",
120
+ access: [notificationAccess.admin],
121
+ })
126
122
  .input(RetentionSettingsSchema)
127
123
  .output(z.void()),
128
124
 
@@ -131,8 +127,11 @@ export const notificationContract = {
131
127
  // ==========================================================================
132
128
 
133
129
  // Create a notification group (for plugins to register their groups)
134
- createGroup: _base
135
- .meta({ userType: "service" })
130
+ createGroup: proc({
131
+ operationType: "mutation",
132
+ userType: "service",
133
+ access: [],
134
+ })
136
135
  .input(
137
136
  z.object({
138
137
  groupId: z
@@ -150,8 +149,11 @@ export const notificationContract = {
150
149
  .output(z.object({ id: z.string() })),
151
150
 
152
151
  // Delete a notification group
153
- deleteGroup: _base
154
- .meta({ userType: "service" })
152
+ deleteGroup: proc({
153
+ operationType: "mutation",
154
+ userType: "service",
155
+ access: [],
156
+ })
155
157
  .input(
156
158
  z.object({
157
159
  groupId: z.string().describe("Full namespaced group ID to delete"),
@@ -163,8 +165,11 @@ export const notificationContract = {
163
165
  .output(z.object({ success: z.boolean() })),
164
166
 
165
167
  // Get subscribers for a specific notification group
166
- getGroupSubscribers: _base
167
- .meta({ userType: "service" })
168
+ getGroupSubscribers: proc({
169
+ operationType: "query",
170
+ userType: "service",
171
+ access: [],
172
+ })
168
173
  .input(
169
174
  z.object({
170
175
  groupId: z
@@ -175,16 +180,17 @@ export const notificationContract = {
175
180
  .output(z.object({ userIds: z.array(z.string()) })),
176
181
 
177
182
  // Send notifications to a list of users (deduplicated by caller)
178
- notifyUsers: _base
179
- .meta({ userType: "service" })
183
+ notifyUsers: proc({
184
+ operationType: "mutation",
185
+ userType: "service",
186
+ access: [],
187
+ })
180
188
  .input(
181
189
  z.object({
182
190
  userIds: z.array(z.string()),
183
191
  title: z.string(),
184
- /** Notification body in markdown format */
185
192
  body: z.string().describe("Notification body (supports markdown)"),
186
193
  importance: z.enum(["info", "warning", "critical"]).optional(),
187
- /** Primary action button */
188
194
  action: z
189
195
  .object({
190
196
  label: z.string(),
@@ -196,20 +202,19 @@ export const notificationContract = {
196
202
  .output(z.object({ notifiedCount: z.number() })),
197
203
 
198
204
  // Notify all subscribers of multiple groups (deduplicates internally)
199
- // Use this when an event affects multiple groups and you want to avoid
200
- // duplicate notifications for users subscribed to multiple affected groups.
201
- notifyGroups: _base
202
- .meta({ userType: "service" })
205
+ notifyGroups: proc({
206
+ operationType: "mutation",
207
+ userType: "service",
208
+ access: [],
209
+ })
203
210
  .input(
204
211
  z.object({
205
212
  groupIds: z
206
213
  .array(z.string())
207
214
  .describe("Full namespaced group IDs to notify"),
208
215
  title: z.string(),
209
- /** Notification body in markdown format */
210
216
  body: z.string().describe("Notification body (supports markdown)"),
211
217
  importance: z.enum(["info", "warning", "critical"]).optional(),
212
- /** Primary action button */
213
218
  action: z
214
219
  .object({
215
220
  label: z.string(),
@@ -220,11 +225,12 @@ export const notificationContract = {
220
225
  )
221
226
  .output(z.object({ notifiedCount: z.number() })),
222
227
 
223
- // Send transactional notification via ALL enabled strategies (no internal notification created)
224
- // For security-critical messages like password reset, 2FA, account verification, etc.
225
- // Unlike regular notifications, this bypasses user preferences and does not create a bell notification.
226
- sendTransactional: _base
227
- .meta({ userType: "service" })
228
+ // Send transactional notification via ALL enabled strategies
229
+ sendTransactional: proc({
230
+ operationType: "mutation",
231
+ userType: "service",
232
+ access: [],
233
+ })
228
234
  .input(
229
235
  z.object({
230
236
  userId: z.string().describe("User to notify"),
@@ -256,62 +262,57 @@ export const notificationContract = {
256
262
  ),
257
263
 
258
264
  // ==========================================================================
259
- // DELIVERY STRATEGY ADMIN ENDPOINTS (userType: "user" with admin accesss)
265
+ // DELIVERY STRATEGY ADMIN ENDPOINTS (userType: "user" with admin access)
260
266
  // ==========================================================================
261
267
 
262
268
  // Get all registered delivery strategies with current config
263
- getDeliveryStrategies: _base
264
- .meta({
265
- userType: "user",
266
- access: [notificationAccess.admin],
267
- })
268
- .output(
269
- z.array(
270
- z.object({
271
- qualifiedId: z.string(),
272
- displayName: z.string(),
273
- description: z.string().optional(),
274
- icon: z.string().optional(),
275
- ownerPluginId: z.string(),
276
- contactResolution: z.object({
277
- type: z.enum([
278
- "auth-email",
279
- "auth-provider",
280
- "user-config",
281
- "oauth-link",
282
- "custom",
283
- ]),
284
- provider: z.string().optional(),
285
- field: z.string().optional(),
286
- }),
287
- requiresUserConfig: z.boolean(),
288
- requiresOAuthLink: z.boolean(),
289
- configSchema: z.record(z.string(), z.unknown()),
290
- userConfigSchema: z.record(z.string(), z.unknown()).optional(),
291
- /** Layout config schema for admin customization (logo, colors, etc.) */
292
- layoutConfigSchema: z.record(z.string(), z.unknown()).optional(),
293
- enabled: z.boolean(),
294
- config: z.record(z.string(), z.unknown()).optional(),
295
- /** Current layout config values */
296
- layoutConfig: z.record(z.string(), z.unknown()).optional(),
297
- /** Markdown instructions for admins (setup guides, etc.) */
298
- adminInstructions: z.string().optional(),
299
- })
300
- )
301
- ),
269
+ getDeliveryStrategies: proc({
270
+ operationType: "query",
271
+ userType: "user",
272
+ access: [notificationAccess.admin],
273
+ }).output(
274
+ z.array(
275
+ z.object({
276
+ qualifiedId: z.string(),
277
+ displayName: z.string(),
278
+ description: z.string().optional(),
279
+ icon: z.string().optional(),
280
+ ownerPluginId: z.string(),
281
+ contactResolution: z.object({
282
+ type: z.enum([
283
+ "auth-email",
284
+ "auth-provider",
285
+ "user-config",
286
+ "oauth-link",
287
+ "custom",
288
+ ]),
289
+ provider: z.string().optional(),
290
+ field: z.string().optional(),
291
+ }),
292
+ requiresUserConfig: z.boolean(),
293
+ requiresOAuthLink: z.boolean(),
294
+ configSchema: z.record(z.string(), z.unknown()),
295
+ userConfigSchema: z.record(z.string(), z.unknown()).optional(),
296
+ layoutConfigSchema: z.record(z.string(), z.unknown()).optional(),
297
+ enabled: z.boolean(),
298
+ config: z.record(z.string(), z.unknown()).optional(),
299
+ layoutConfig: z.record(z.string(), z.unknown()).optional(),
300
+ adminInstructions: z.string().optional(),
301
+ })
302
+ )
303
+ ),
302
304
 
303
305
  // Update strategy enabled state and config
304
- updateDeliveryStrategy: _base
305
- .meta({
306
- userType: "user",
307
- access: [notificationAccess.admin],
308
- })
306
+ updateDeliveryStrategy: proc({
307
+ operationType: "mutation",
308
+ userType: "user",
309
+ access: [notificationAccess.admin],
310
+ })
309
311
  .input(
310
312
  z.object({
311
313
  strategyId: z.string().describe("Qualified strategy ID"),
312
314
  enabled: z.boolean(),
313
315
  config: z.record(z.string(), z.unknown()).optional(),
314
- /** Layout customization (logo, colors, footer) */
315
316
  layoutConfig: z.record(z.string(), z.unknown()).optional(),
316
317
  })
317
318
  )
@@ -322,7 +323,11 @@ export const notificationContract = {
322
323
  // ==========================================================================
323
324
 
324
325
  // Get available delivery channels for current user
325
- getUserDeliveryChannels: _base.meta({ userType: "user" }).output(
326
+ getUserDeliveryChannels: proc({
327
+ operationType: "query",
328
+ userType: "user",
329
+ access: [],
330
+ }).output(
326
331
  z.array(
327
332
  z.object({
328
333
  strategyId: z.string(),
@@ -340,19 +345,19 @@ export const notificationContract = {
340
345
  enabled: z.boolean(),
341
346
  isConfigured: z.boolean(),
342
347
  linkedAt: z.coerce.date().optional(),
343
- /** JSON Schema for user config (for DynamicForm) */
344
348
  userConfigSchema: z.record(z.string(), z.unknown()).optional(),
345
- /** Current user config values */
346
349
  userConfig: z.record(z.string(), z.unknown()).optional(),
347
- /** Markdown instructions for users (connection guides, etc.) */
348
350
  userInstructions: z.string().optional(),
349
351
  })
350
352
  )
351
353
  ),
352
354
 
353
355
  // Update user's preference for a delivery channel
354
- setUserDeliveryPreference: _base
355
- .meta({ userType: "user" })
356
+ setUserDeliveryPreference: proc({
357
+ operationType: "mutation",
358
+ userType: "user",
359
+ access: [],
360
+ })
356
361
  .input(
357
362
  z.object({
358
363
  strategyId: z.string(),
@@ -363,8 +368,11 @@ export const notificationContract = {
363
368
  .output(z.void()),
364
369
 
365
370
  // Get OAuth link URL for a strategy (starts OAuth flow)
366
- getDeliveryOAuthUrl: _base
367
- .meta({ userType: "user" })
371
+ getDeliveryOAuthUrl: proc({
372
+ operationType: "mutation",
373
+ userType: "user",
374
+ access: [],
375
+ })
368
376
  .input(
369
377
  z.object({
370
378
  strategyId: z.string(),
@@ -374,14 +382,20 @@ export const notificationContract = {
374
382
  .output(z.object({ authUrl: z.string() })),
375
383
 
376
384
  // Unlink OAuth-connected delivery channel
377
- unlinkDeliveryChannel: _base
378
- .meta({ userType: "user" })
385
+ unlinkDeliveryChannel: proc({
386
+ operationType: "mutation",
387
+ userType: "user",
388
+ access: [],
389
+ })
379
390
  .input(z.object({ strategyId: z.string() }))
380
391
  .output(z.void()),
381
392
 
382
393
  // Send a test notification to the current user via a specific strategy
383
- sendTestNotification: _base
384
- .meta({ userType: "user" })
394
+ sendTestNotification: proc({
395
+ operationType: "mutation",
396
+ userType: "user",
397
+ access: [],
398
+ })
385
399
  .input(z.object({ strategyId: z.string() }))
386
400
  .output(
387
401
  z.object({