notifkit 0.1.5 → 0.1.7

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 (29) hide show
  1. package/README.md +5 -17
  2. package/dist/index.d.mts +3012 -550
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +2 -2
  5. package/dist/{main-DeNFQ-UL.mjs → main-Bgi2wWck.mjs} +2 -2
  6. package/dist/{main-DeNFQ-UL.mjs.map → main-Bgi2wWck.mjs.map} +1 -1
  7. package/dist/{main-40zwq6b0.mjs → main-CFSukWm8.mjs} +73 -39
  8. package/dist/main-CFSukWm8.mjs.map +1 -0
  9. package/dist/{main-DmCPcxOc.mjs → main-CRPxM-O3.mjs} +2 -2
  10. package/dist/{main-DmCPcxOc.mjs.map → main-CRPxM-O3.mjs.map} +1 -1
  11. package/dist/{main-BFre2-HQ.mjs → main-CjbVdUqa.mjs} +2 -2
  12. package/dist/{main-BFre2-HQ.mjs.map → main-CjbVdUqa.mjs.map} +1 -1
  13. package/dist/{main-BNJtzY61.mjs → main-D6SG3Isk.mjs} +2 -2
  14. package/dist/{main-BNJtzY61.mjs.map → main-D6SG3Isk.mjs.map} +1 -1
  15. package/dist/{main-DvgJSm11.mjs → main-DFMHcN_d.mjs} +2 -2
  16. package/dist/{main-DvgJSm11.mjs.map → main-DFMHcN_d.mjs.map} +1 -1
  17. package/dist/{main-BOPMYqsW.mjs → main-Yorxzw0Z.mjs} +2 -2
  18. package/dist/{main-BOPMYqsW.mjs.map → main-Yorxzw0Z.mjs.map} +1 -1
  19. package/dist/{main-CiigNpsP.mjs → main-pFAfCkVX.mjs} +2 -2
  20. package/dist/{main-CiigNpsP.mjs.map → main-pFAfCkVX.mjs.map} +1 -1
  21. package/dist/{src-vG79L-8m.mjs → src-CPMwsUCJ.mjs} +70 -41
  22. package/dist/src-CPMwsUCJ.mjs.map +1 -0
  23. package/package.json +1 -1
  24. package/src/client.ts +111 -40
  25. package/src/config/index.ts +5 -1
  26. package/src/contracts/sdk.ts +143 -3
  27. package/src/services/api/handlers.ts +94 -30
  28. package/dist/main-40zwq6b0.mjs.map +0 -1
  29. package/dist/src-vG79L-8m.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notifkit",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Self-hosted notification infrastructure. One call delivers to email, SMS, push, and webhook — routed by preference, quiet hours, and consent.",
5
5
  "license": "MIT",
6
6
  "author": "devkitshq",
package/src/client.ts CHANGED
@@ -2,12 +2,29 @@ import type {
2
2
  AddUserInput,
3
3
  UpdateUserInput,
4
4
  AddContactInput,
5
+ UserContactInput,
5
6
  SyncTemplatesInput,
7
+ TemplateInput,
6
8
  NotifyRequestInput,
7
9
  TriggerWorkflowInput,
8
10
  CreateWorkflowInput,
9
11
  IngestEventInput,
10
12
  UpdateProjectInput,
13
+ Preferences,
14
+ UserContactResponse,
15
+ UserResponse,
16
+ UserDetailResponse,
17
+ UserProfileResponse,
18
+ WorkflowDefinitionRecord,
19
+ WorkflowInstanceRecord,
20
+ NotificationLogRecord,
21
+ TemplateRecordResponse,
22
+ SuppressionRecord,
23
+ ProjectRecord,
24
+ ProjectKeyRecord,
25
+ SystemHealthRecord,
26
+ SystemMetricsRecord,
27
+ DLQMessageRecord,
11
28
  } from "./contracts/sdk.js";
12
29
 
13
30
  export interface NotifkitClientOptions {
@@ -53,37 +70,86 @@ export class NotifkitClient {
53
70
  return data as T;
54
71
  }
55
72
 
56
- /** Sync templates with the server. */
57
- async syncTemplates(input: SyncTemplatesInput): Promise<{ synced: number }> {
58
- return this.request("/v1/templates", "PUT", input);
73
+ /** Sync templates with the server. Accepts either { templates: [...] } or an array of templates directly. */
74
+ async syncTemplates(input: SyncTemplatesInput | TemplateInput[]): Promise<{ synced: number }> {
75
+ const payload = Array.isArray(input) ? { templates: input } : input;
76
+ return this.request("/v1/templates", "PUT", payload);
59
77
  }
60
78
 
61
- /** Create/upsert a user profile and contacts. */
62
- async addUser(input: AddUserInput): Promise<{ id: string }> {
63
- return this.request("/v1/users", "POST", input);
79
+ /**
80
+ * Create/upsert a user profile and contacts.
81
+ * Can be called with (id, contacts, options) or (inputObject).
82
+ */
83
+ async addUser(
84
+ id: string,
85
+ contacts?: UserContactInput[],
86
+ options?: Omit<AddUserInput, "id" | "contacts">,
87
+ ): Promise<{ id: string }>;
88
+ async addUser(input: AddUserInput): Promise<{ id: string }>;
89
+ async addUser(
90
+ idOrInput: string | AddUserInput,
91
+ contacts?: UserContactInput[],
92
+ options?: Omit<AddUserInput, "id" | "contacts">,
93
+ ): Promise<{ id: string }> {
94
+ if (typeof idOrInput === "string") {
95
+ return this.request("/v1/users", "POST", {
96
+ id: idOrInput,
97
+ contacts: contacts ?? [],
98
+ ...options,
99
+ });
100
+ }
101
+ return this.request("/v1/users", "POST", idOrInput);
102
+ }
103
+
104
+ /**
105
+ * Convenient alias for `addUser` matching standard CDP/notification SDK conventions.
106
+ */
107
+ async identify(
108
+ id: string,
109
+ contacts?: UserContactInput[],
110
+ options?: Omit<AddUserInput, "id" | "contacts">,
111
+ ): Promise<{ id: string }>;
112
+ async identify(input: AddUserInput): Promise<{ id: string }>;
113
+ async identify(
114
+ idOrInput: string | AddUserInput,
115
+ contacts?: UserContactInput[],
116
+ options?: Omit<AddUserInput, "id" | "contacts">,
117
+ ): Promise<{ id: string }> {
118
+ return (this.addUser as any)(idOrInput, contacts, options);
64
119
  }
65
120
 
66
121
  /** Update user profile. */
67
122
  async updateUser(id: string, input: UpdateUserInput): Promise<{ id: string }> {
68
- return this.request(`/v1/users/${id}`, "PATCH", input);
123
+ return this.request(`/v1/users/${encodeURIComponent(id)}`, "PATCH", input);
69
124
  }
70
125
 
71
126
  /** Delete user profile. */
72
127
  async deleteUser(id: string): Promise<void> {
73
- return this.request(`/v1/users/${id}`, "DELETE");
128
+ return this.request(`/v1/users/${encodeURIComponent(id)}`, "DELETE");
74
129
  }
75
130
 
76
- /** Add contact targets to user profile. */
131
+ /** Add contact target to user profile. */
77
132
  async addContact(
78
133
  userId: string,
79
134
  input: AddContactInput,
80
135
  ): Promise<{ userId: string; channel: string; target: string }> {
81
- return this.request(`/v1/users/${userId}/contacts`, "POST", input);
136
+ return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "POST", input);
137
+ }
138
+
139
+ /** Add multiple contact targets to user profile in a batch. */
140
+ async addContacts(
141
+ userId: string,
142
+ contacts: AddContactInput[],
143
+ ): Promise<{ userId: string; contacts: Array<{ channel: string; target: string }> }> {
144
+ return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "POST", contacts);
82
145
  }
83
146
 
84
147
  /** Delete a specific contact channel target. */
85
148
  async deleteContact(userId: string, channel: string, target: string): Promise<void> {
86
- return this.request(`/v1/users/${userId}/contacts/${channel}/${target}`, "DELETE");
149
+ return this.request(
150
+ `/v1/users/${encodeURIComponent(userId)}/contacts/${encodeURIComponent(channel)}/${encodeURIComponent(target)}`,
151
+ "DELETE",
152
+ );
87
153
  }
88
154
 
89
155
  /** Request a notification dispatch. */
@@ -124,7 +190,7 @@ export class NotifkitClient {
124
190
  async listWorkflows(options?: {
125
191
  limit?: number;
126
192
  search?: string;
127
- }): Promise<{ workflows: any[] }> {
193
+ }): Promise<{ workflows: WorkflowDefinitionRecord[] }> {
128
194
  const params = new URLSearchParams();
129
195
  if (options?.limit) params.set("limit", options.limit.toString());
130
196
  if (options?.search) params.set("search", options.search);
@@ -133,13 +199,13 @@ export class NotifkitClient {
133
199
  }
134
200
 
135
201
  /** Get a workflow instance by ID. */
136
- async getWorkflow(instanceId: string): Promise<any> {
137
- return this.request(`/v1/workflows/instances/${instanceId}`, "GET");
202
+ async getWorkflow(instanceId: string): Promise<WorkflowInstanceRecord> {
203
+ return this.request(`/v1/workflows/instances/${encodeURIComponent(instanceId)}`, "GET");
138
204
  }
139
205
 
140
206
  /** Cancel a running/suspended workflow instance. */
141
207
  async cancelWorkflow(instanceId: string): Promise<void> {
142
- return this.request(`/v1/workflows/instances/${instanceId}`, "DELETE");
208
+ return this.request(`/v1/workflows/instances/${encodeURIComponent(instanceId)}`, "DELETE");
143
209
  }
144
210
 
145
211
  /** Get notification logs for the project. */
@@ -153,7 +219,7 @@ export class NotifkitClient {
153
219
  taskId?: string;
154
220
  campaign?: string;
155
221
  search?: string;
156
- }): Promise<{ logs: any[]; nextCursor: string | null }> {
222
+ }): Promise<{ logs: NotificationLogRecord[]; nextCursor: string | null }> {
157
223
  let url = "/v1/notifications/logs";
158
224
  if (options) {
159
225
  const params = new URLSearchParams();
@@ -182,7 +248,7 @@ export class NotifkitClient {
182
248
  language?: string;
183
249
  timezone?: string;
184
250
  channel?: string;
185
- }): Promise<{ users: any[]; nextCursor: string | null }> {
251
+ }): Promise<{ users: UserProfileResponse[]; nextCursor: string | null }> {
186
252
  const params = new URLSearchParams();
187
253
  if (options?.limit) params.set("limit", options.limit.toString());
188
254
  if (options?.cursor) params.set("cursor", options.cursor);
@@ -197,45 +263,48 @@ export class NotifkitClient {
197
263
 
198
264
  /** Delete a template. */
199
265
  async deleteTemplate(id: string): Promise<void> {
200
- return this.request(`/v1/templates/${id}`, "DELETE");
266
+ return this.request(`/v1/templates/${encodeURIComponent(id)}`, "DELETE");
201
267
  }
202
268
 
203
269
  /** Get a user's contacts. */
204
- async getUserContacts(userId: string): Promise<{ contacts: any[] }> {
205
- return this.request(`/v1/users/${userId}/contacts`, "GET");
270
+ async getUserContacts(userId: string): Promise<{ contacts: UserContactResponse[] }> {
271
+ return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "GET");
206
272
  }
207
273
 
208
274
  /** List projects (Admin only). */
209
- async listProjects(): Promise<{ projects: any[] }> {
275
+ async listProjects(): Promise<{ projects: ProjectRecord[] }> {
210
276
  return this.request("/v1/projects", "GET");
211
277
  }
212
278
 
213
279
  /** Delete a project (Admin only). */
214
280
  async deleteProject(id: string): Promise<void> {
215
- return this.request(`/v1/projects/${id}`, "DELETE");
281
+ return this.request(`/v1/projects/${encodeURIComponent(id)}`, "DELETE");
216
282
  }
217
283
 
218
284
  /** Create a new project API key (Admin only). */
219
285
  async createProjectKey(
220
286
  id: string,
221
287
  input?: { role?: "admin" | "read_only" },
222
- ): Promise<{ id: string; apiKey: string; role: string }> {
223
- return this.request(`/v1/projects/${id}/keys`, "POST", input || {});
288
+ ): Promise<ProjectKeyRecord> {
289
+ return this.request(`/v1/projects/${encodeURIComponent(id)}/keys`, "POST", input || {});
224
290
  }
225
291
 
226
292
  /** List project API keys (Admin only). */
227
- async listProjectKeys(id: string): Promise<{ keys: any[] }> {
228
- return this.request(`/v1/projects/${id}/keys`, "GET");
293
+ async listProjectKeys(id: string): Promise<{ keys: ProjectKeyRecord[] }> {
294
+ return this.request(`/v1/projects/${encodeURIComponent(id)}/keys`, "GET");
229
295
  }
230
296
 
231
297
  /** Delete a project API key (Admin only). */
232
298
  async deleteProjectKey(id: string, keyId: string): Promise<void> {
233
- return this.request(`/v1/projects/${id}/keys/${keyId}`, "DELETE");
299
+ return this.request(
300
+ `/v1/projects/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}`,
301
+ "DELETE",
302
+ );
234
303
  }
235
304
 
236
305
  /** Update project settings (Admin only). */
237
306
  async updateProject(id: string, input: UpdateProjectInput): Promise<{ id: string }> {
238
- return this.request(`/v1/projects/${id}`, "PATCH", input);
307
+ return this.request(`/v1/projects/${encodeURIComponent(id)}`, "PATCH", input);
239
308
  }
240
309
 
241
310
  /** List unique segment tags. */
@@ -302,7 +371,7 @@ export class NotifkitClient {
302
371
  channel?: string;
303
372
  reason?: string;
304
373
  target?: string;
305
- }): Promise<{ suppressions: any[] }> {
374
+ }): Promise<{ suppressions: SuppressionRecord[] }> {
306
375
  const params = new URLSearchParams();
307
376
  if (options?.limit) params.set("limit", options.limit.toString());
308
377
  if (options?.channel) params.set("channel", options.channel);
@@ -332,7 +401,9 @@ export class NotifkitClient {
332
401
  // ─── Notification Status & Cancellation ─────────────────────────────────────
333
402
 
334
403
  /** Get real-time status and delivery logs for a specific notification task. */
335
- async getNotificationStatus(taskId: string): Promise<{ status: string; logs: any[] }> {
404
+ async getNotificationStatus(
405
+ taskId: string,
406
+ ): Promise<{ status: string; logs: NotificationLogRecord[] }> {
336
407
  return this.request(`/v1/notifications/${encodeURIComponent(taskId)}`, "GET");
337
408
  }
338
409
 
@@ -349,54 +420,54 @@ export class NotifkitClient {
349
420
  // ─── User Profile & Preferences ─────────────────────────────────────────────
350
421
 
351
422
  /** Get user profile and contacts by ID. */
352
- async getUser(id: string): Promise<any> {
423
+ async getUser(id: string): Promise<UserResponse> {
353
424
  return this.request(`/v1/users/${encodeURIComponent(id)}`, "GET");
354
425
  }
355
426
 
356
427
  /** Get user details including contacts and recent message logs. */
357
- async getUserDetails(id: string): Promise<any> {
428
+ async getUserDetails(id: string): Promise<UserDetailResponse> {
358
429
  return this.request(`/v1/users/${encodeURIComponent(id)}/details`, "GET");
359
430
  }
360
431
 
361
432
  /** Get user preferences. */
362
- async getUserPreferences(id: string): Promise<any> {
433
+ async getUserPreferences(id: string): Promise<Preferences> {
363
434
  return this.request(`/v1/users/${encodeURIComponent(id)}/preferences`, "GET");
364
435
  }
365
436
 
366
437
  /** Update user preferences. */
367
438
  async updateUserPreferences(
368
439
  id: string,
369
- preferences: Record<string, any>,
370
- ): Promise<{ id: string; preferences: any }> {
440
+ preferences: Preferences,
441
+ ): Promise<{ id: string; preferences: Preferences }> {
371
442
  return this.request(`/v1/users/${encodeURIComponent(id)}/preferences`, "PATCH", preferences);
372
443
  }
373
444
 
374
445
  // ─── Templates Querying ─────────────────────────────────────────────────────
375
446
 
376
447
  /** List all templates for the project. */
377
- async listTemplates(): Promise<{ templates: any[] }> {
448
+ async listTemplates(): Promise<{ templates: TemplateRecordResponse[] }> {
378
449
  return this.request("/v1/templates", "GET");
379
450
  }
380
451
 
381
452
  /** Get a template by ID. */
382
- async getTemplate(id: string): Promise<any> {
453
+ async getTemplate(id: string): Promise<TemplateRecordResponse> {
383
454
  return this.request(`/v1/templates/${encodeURIComponent(id)}`, "GET");
384
455
  }
385
456
 
386
457
  // ─── System Health, Metrics & DLQ ───────────────────────────────────────────
387
458
 
388
459
  /** Get system health and worker status. */
389
- async getSystemHealth(): Promise<any> {
460
+ async getSystemHealth(): Promise<SystemHealthRecord> {
390
461
  return this.request("/v1/system/health", "GET");
391
462
  }
392
463
 
393
464
  /** Get system metrics and queue lengths. */
394
- async getSystemMetrics(): Promise<any> {
465
+ async getSystemMetrics(): Promise<SystemMetricsRecord> {
395
466
  return this.request("/v1/system/metrics", "GET");
396
467
  }
397
468
 
398
469
  /** Get dead-letter queue messages. */
399
- async getDLQMessages(): Promise<{ messages: any[] }> {
470
+ async getDLQMessages(): Promise<{ messages: DLQMessageRecord[] }> {
400
471
  return this.request("/v1/dlq", "GET");
401
472
  }
402
473
 
@@ -38,7 +38,11 @@ export const baseConfigSchema = z.object({
38
38
  HOST: z.string().default("127.0.0.1"),
39
39
  REDIS_URL: z.string().url().default("redis://localhost:6379"),
40
40
  DATABASE_URL: z.string().url().default("postgres://platform:platform@localhost:5432/notifkit"),
41
- ADMIN_API_KEY: z.string().optional(),
41
+ // Trimmed because the incoming bearer token is trimmed before comparison, so
42
+ // an untrimmed value here could never match it. `set KEY=value && cmd` on
43
+ // Windows puts a trailing space in the variable, which otherwise turns every
44
+ // admin request into a 401 that reads like a wrong key.
45
+ ADMIN_API_KEY: z.string().trim().optional(),
42
46
  WORKER_CONCURRENCY: z.coerce.number().int().min(1).default(10),
43
47
  QUEUE_MAX_LEN: z.coerce.number().int().min(1).default(10000000),
44
48
  DB_MAX_CONNECTIONS: z.coerce.number().int().min(1).default(2),
@@ -28,6 +28,7 @@ export const ContactChannelSchema = z.enum([
28
28
  "sms",
29
29
  "push",
30
30
  "webhook",
31
+ "in-app",
31
32
  "telegram",
32
33
  "discord",
33
34
  "whatsapp",
@@ -40,16 +41,28 @@ const stringOrArray = z
40
41
  .union([z.string().min(1), z.array(z.string().min(1))])
41
42
  .transform((v) => (Array.isArray(v) ? v : [v]));
42
43
 
44
+ export const UserContactInputSchema = z.object({
45
+ channel: ContactChannelSchema,
46
+ target: z.string().min(1),
47
+ label: z.string().optional(),
48
+ isPrimary: z.boolean().optional(),
49
+ enabled: z.boolean().optional(),
50
+ preferences: PreferencesSchema.optional(),
51
+ });
52
+ export type UserContactInput = z.infer<typeof UserContactInputSchema>;
53
+
43
54
  // ─── Users ────────────────────────────────────────────────────────────────────
44
55
 
45
56
  /**
46
- * addUser({ id, email, phone, pushToken, segments, preferences })
47
- * email / phone / pushToken accept a single string or an array.
57
+ * addUser({ id, contacts, email, phone, pushToken, segments, preferences })
58
+ * email / phone / pushToken accept a single string or an array for backwards compatibility.
59
+ * Prefer `contacts: [{ channel, target }]`.
48
60
  */
49
61
  export const AddUserSchema = z.object({
50
62
  id: z.string().min(1),
51
63
  language: z.string().optional(),
52
64
  timezone: z.string().optional(),
65
+ contacts: z.array(UserContactInputSchema).optional(),
53
66
  email: stringOrArray.optional(),
54
67
  phone: stringOrArray.optional(),
55
68
  pushToken: stringOrArray.optional(),
@@ -62,6 +75,7 @@ export type AddUserInput = z.input<typeof AddUserSchema>;
62
75
  export const UpdateUserSchema = z.object({
63
76
  language: z.string().optional(),
64
77
  timezone: z.string().optional(),
78
+ contacts: z.array(UserContactInputSchema).optional(),
65
79
  email: stringOrArray.optional(),
66
80
  phone: stringOrArray.optional(),
67
81
  pushToken: stringOrArray.optional(),
@@ -70,14 +84,20 @@ export const UpdateUserSchema = z.object({
70
84
  });
71
85
  export type UpdateUserInput = z.input<typeof UpdateUserSchema>;
72
86
 
73
- /** addUserContact(userId, channel, { target, preferences }) channel carried in body. */
87
+ /** addUserContact(userId, channel, { target, preferences, label, isPrimary, enabled }) */
74
88
  export const AddContactSchema = z.object({
75
89
  channel: ContactChannelSchema,
76
90
  target: z.string().min(1),
91
+ label: z.string().optional(),
92
+ isPrimary: z.boolean().optional(),
93
+ enabled: z.boolean().optional(),
77
94
  preferences: PreferencesSchema.optional(),
78
95
  });
79
96
  export type AddContactInput = z.infer<typeof AddContactSchema>;
80
97
 
98
+ export const BatchAddContactsSchema = z.union([AddContactSchema, z.array(AddContactSchema).min(1)]);
99
+ export type BatchAddContactsInput = z.infer<typeof BatchAddContactsSchema>;
100
+
81
101
  // ─── Templates ────────────────────────────────────────────────────────────────
82
102
 
83
103
  export const TemplateSchema = z.object({
@@ -104,6 +124,7 @@ export const InlineUserSchema = z.object({
104
124
  id: z.string().min(1),
105
125
  language: z.string().optional(),
106
126
  timezone: z.string().optional(),
127
+ contacts: z.array(UserContactInputSchema).optional(),
107
128
  email: stringOrArray.optional(),
108
129
  phone: stringOrArray.optional(),
109
130
  pushToken: stringOrArray.optional(),
@@ -240,3 +261,122 @@ export const UpdateProjectSchema = z.object({
240
261
  throttleWindowHours: z.number().nullable().optional(),
241
262
  });
242
263
  export type UpdateProjectInput = z.infer<typeof UpdateProjectSchema>;
264
+
265
+ // ─── Response Entities ────────────────────────────────────────────────────────
266
+
267
+ export interface UserContactResponse {
268
+ id: string;
269
+ userId: string;
270
+ channel: ContactChannel;
271
+ target: string;
272
+ label?: string | null;
273
+ isPrimary?: boolean;
274
+ active?: boolean;
275
+ enabled?: boolean;
276
+ preferences?: Preferences;
277
+ }
278
+
279
+ export interface UserProfileResponse {
280
+ userId: string;
281
+ language?: string;
282
+ timezone?: string;
283
+ email?: string | null;
284
+ }
285
+
286
+ export interface UserResponse extends UserProfileResponse {
287
+ segments: string[];
288
+ preferences: Preferences;
289
+ contacts?: UserContactResponse[];
290
+ }
291
+
292
+ export interface UserDetailResponse extends UserResponse {
293
+ contacts: UserContactResponse[];
294
+ recentLogs?: unknown[];
295
+ }
296
+
297
+ export interface WorkflowDefinitionRecord {
298
+ id: string;
299
+ name: string;
300
+ steps: unknown[];
301
+ createdAt?: string;
302
+ updatedAt?: string;
303
+ }
304
+
305
+ export interface WorkflowInstanceRecord {
306
+ id: string;
307
+ name: string;
308
+ status: string;
309
+ currentStepIndex: number;
310
+ input?: Record<string, unknown>;
311
+ output?: Record<string, unknown>;
312
+ createdAt: string;
313
+ updatedAt: string;
314
+ }
315
+
316
+ export interface NotificationLogRecord {
317
+ id: string;
318
+ notificationId: string;
319
+ taskId?: string;
320
+ channel: string;
321
+ target?: string;
322
+ status: string;
323
+ templateId?: string;
324
+ campaign?: string;
325
+ createdAt: string;
326
+ dispatchedAt?: string;
327
+ error?: string;
328
+ metadata?: Record<string, unknown>;
329
+ }
330
+
331
+ export interface TemplateRecordResponse {
332
+ id: string;
333
+ channel: string;
334
+ topics: string[];
335
+ content: Record<string, unknown>;
336
+ aiPrompts?: Record<string, string> | null;
337
+ }
338
+
339
+ export interface SuppressionRecord {
340
+ id: string;
341
+ channel: string;
342
+ target: string;
343
+ reason: string;
344
+ createdAt: string;
345
+ }
346
+
347
+ export interface ProjectRecord {
348
+ id: string;
349
+ name: string;
350
+ rateLimitRpm?: number | null;
351
+ throttleLimit?: number | null;
352
+ throttleWindowHours?: number | null;
353
+ createdAt: string;
354
+ }
355
+
356
+ export interface ProjectKeyRecord {
357
+ id: string;
358
+ apiKey: string;
359
+ role: string;
360
+ createdAt: string;
361
+ }
362
+
363
+ export interface SystemHealthRecord {
364
+ status: string;
365
+ timestamp: string;
366
+ redis?: { status: string };
367
+ db?: { status: string };
368
+ workers?: Record<string, unknown>;
369
+ }
370
+
371
+ export interface SystemMetricsRecord {
372
+ queues: Record<string, number>;
373
+ throughput?: Record<string, unknown>;
374
+ }
375
+
376
+ export interface DLQMessageRecord {
377
+ id: string;
378
+ stream: string;
379
+ payload: Record<string, unknown>;
380
+ error?: string;
381
+ timestamp: string;
382
+ }