jazz-webhook 0.18.28

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 (46) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +16 -0
  3. package/LICENSE.txt +19 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +3 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/successMap.d.ts +13 -0
  9. package/dist/successMap.d.ts.map +1 -0
  10. package/dist/successMap.js +32 -0
  11. package/dist/successMap.js.map +1 -0
  12. package/dist/test/http-server.d.ts +73 -0
  13. package/dist/test/http-server.d.ts.map +1 -0
  14. package/dist/test/http-server.js +177 -0
  15. package/dist/test/http-server.js.map +1 -0
  16. package/dist/test/setup.d.ts +2 -0
  17. package/dist/test/setup.d.ts.map +1 -0
  18. package/dist/test/setup.js +11 -0
  19. package/dist/test/setup.js.map +1 -0
  20. package/dist/test/successMap.test.d.ts +2 -0
  21. package/dist/test/successMap.test.d.ts.map +1 -0
  22. package/dist/test/successMap.test.js +172 -0
  23. package/dist/test/successMap.test.js.map +1 -0
  24. package/dist/test/webhook.test.d.ts +2 -0
  25. package/dist/test/webhook.test.d.ts.map +1 -0
  26. package/dist/test/webhook.test.js +356 -0
  27. package/dist/test/webhook.test.js.map +1 -0
  28. package/dist/types.d.ts +87 -0
  29. package/dist/types.d.ts.map +1 -0
  30. package/dist/types.js +15 -0
  31. package/dist/types.js.map +1 -0
  32. package/dist/webhook.d.ts +67 -0
  33. package/dist/webhook.d.ts.map +1 -0
  34. package/dist/webhook.js +281 -0
  35. package/dist/webhook.js.map +1 -0
  36. package/package.json +34 -0
  37. package/src/index.ts +2 -0
  38. package/src/successMap.ts +55 -0
  39. package/src/test/http-server.ts +233 -0
  40. package/src/test/setup.ts +12 -0
  41. package/src/test/successMap.test.ts +215 -0
  42. package/src/test/webhook.test.ts +586 -0
  43. package/src/types.ts +106 -0
  44. package/src/webhook.ts +387 -0
  45. package/tsconfig.json +17 -0
  46. package/vitest.config.ts +14 -0
@@ -0,0 +1,586 @@
1
+ import { describe, expect, test, onTestFinished, assert } from "vitest";
2
+ import { co, z, Group } from "jazz-tools";
3
+ import { createJazzTestAccount } from "jazz-tools/testing";
4
+ import { WebhookTestServer } from "./http-server.js";
5
+ import {
6
+ WebhookRegistry,
7
+ WebhookRegistration,
8
+ RegistryState,
9
+ JazzWebhookOptions,
10
+ } from "../webhook.js";
11
+ import { isTxSuccessful } from "../successMap.js";
12
+ import { CojsonInternalTypes } from "cojson";
13
+
14
+ // Define test schemas
15
+ const TestCoMap = co.map({
16
+ value: z.string(),
17
+ });
18
+
19
+ const TestRoot = co.map({
20
+ webhookRegistry: RegistryState,
21
+ });
22
+
23
+ const TestAccount = co
24
+ .account({
25
+ root: TestRoot,
26
+ profile: co.profile(),
27
+ })
28
+ .withMigration((account, creationProps) => {
29
+ const group = Group.create({ owner: account });
30
+ group.addMember("everyone", "reader");
31
+
32
+ if (!account.$jazz.has("profile")) {
33
+ account.$jazz.set(
34
+ "profile",
35
+ co.profile().create(
36
+ {
37
+ name: creationProps?.name ?? "Test Account",
38
+ },
39
+ group,
40
+ ),
41
+ );
42
+ }
43
+
44
+ if (!account.$jazz.has("root")) {
45
+ account.$jazz.set(
46
+ "root",
47
+ TestRoot.create(
48
+ {
49
+ webhookRegistry: WebhookRegistry.createRegistry(group),
50
+ },
51
+ group,
52
+ ),
53
+ );
54
+ }
55
+ });
56
+
57
+ interface TestContext {
58
+ account: any;
59
+ webhookServer: WebhookTestServer;
60
+ registryState: RegistryState;
61
+ webhookRegistry: WebhookRegistry;
62
+ }
63
+
64
+ async function setupTest(options?: JazzWebhookOptions): Promise<TestContext> {
65
+ const account = await createJazzTestAccount({
66
+ AccountSchema: TestAccount,
67
+ isCurrentActiveAccount: true,
68
+ });
69
+
70
+ const webhookServer = new WebhookTestServer();
71
+ await webhookServer.start();
72
+ const registryState = account.root.webhookRegistry;
73
+ const webhookRegistry = new WebhookRegistry(
74
+ registryState,
75
+ options || {
76
+ baseDelayMs: 10,
77
+ },
78
+ );
79
+ webhookRegistry.start();
80
+
81
+ // Set up cleanup for this test
82
+ onTestFinished(async () => {
83
+ if (webhookServer) {
84
+ webhookServer.close();
85
+ }
86
+ webhookRegistry.shutdown();
87
+ });
88
+
89
+ return {
90
+ account,
91
+ webhookServer,
92
+ registryState,
93
+ webhookRegistry,
94
+ };
95
+ }
96
+
97
+ describe("jazz-webhook", () => {
98
+ describe("webhook registration", () => {
99
+ test("should register a valid webhook", async () => {
100
+ const { webhookServer, registryState, webhookRegistry } =
101
+ await setupTest();
102
+
103
+ const webhookId = await webhookRegistry.register(
104
+ webhookServer.getUrl(),
105
+ "co_z1234567890abcdef",
106
+ );
107
+
108
+ expect(webhookId).toBeDefined();
109
+ expect(registryState[webhookId]).toBeDefined();
110
+ expect(registryState[webhookId]!.webhookUrl).toBe(webhookServer.getUrl());
111
+ expect(registryState[webhookId]!.coValueId).toBe("co_z1234567890abcdef");
112
+ expect(registryState[webhookId]!.active).toBe(true);
113
+ expect(Object.keys(registryState[webhookId]!.successMap!).length).toBe(0);
114
+ });
115
+
116
+ test("should throw error for invalid callback URL", async () => {
117
+ const { webhookRegistry: webhookManager } = await setupTest();
118
+
119
+ await expect(
120
+ webhookManager.register("not-a-url", "co_z1234567890abcdef"),
121
+ ).rejects.toThrow("Invalid webhook URL: not-a-url");
122
+ });
123
+
124
+ test("should throw error for invalid CoValue ID format", async () => {
125
+ const { webhookServer, webhookRegistry: webhookManager } =
126
+ await setupTest();
127
+
128
+ await expect(
129
+ webhookManager.register(webhookServer.getUrl(), "invalid-id"),
130
+ ).rejects.toThrow(
131
+ "Invalid CoValue ID format: invalid-id. Expected format: co_z...",
132
+ );
133
+
134
+ await expect(
135
+ webhookManager.register(webhookServer.getUrl(), "co_invalid"),
136
+ ).rejects.toThrow(
137
+ "Invalid CoValue ID format: co_invalid. Expected format: co_z...",
138
+ );
139
+ });
140
+
141
+ test("should create multiple webhooks with different IDs", async () => {
142
+ const { webhookServer, registryState, webhookRegistry } =
143
+ await setupTest();
144
+
145
+ const webhook1 = await webhookRegistry.register(
146
+ webhookServer.getUrl(),
147
+ "co_z1111111111111111",
148
+ );
149
+ const webhook2 = await webhookRegistry.register(
150
+ webhookServer.getUrl(),
151
+ "co_z2222222222222222",
152
+ );
153
+
154
+ expect(webhook1).not.toBe(webhook2);
155
+ expect(registryState[webhook1]!.coValueId).toBe("co_z1111111111111111");
156
+ expect(registryState[webhook2]!.coValueId).toBe("co_z2222222222222222");
157
+ });
158
+ });
159
+
160
+ describe("webhook unregistration", () => {
161
+ test("should unregister a webhook", async () => {
162
+ const { webhookServer, registryState, webhookRegistry } =
163
+ await setupTest();
164
+
165
+ const webhookId = await webhookRegistry.register(
166
+ webhookServer.getUrl(),
167
+ "co_z1234567890abcdef",
168
+ );
169
+
170
+ const webhook = registryState[webhookId];
171
+ expect(webhook!.active).toBe(true);
172
+
173
+ webhookRegistry.unregister(webhookId);
174
+
175
+ expect(registryState[webhookId]).toBeUndefined();
176
+ expect(webhook!.active).toBe(false);
177
+ });
178
+
179
+ test("should throw error for non-existent webhook", async () => {
180
+ const { webhookRegistry } = await setupTest();
181
+
182
+ expect(() => {
183
+ webhookRegistry.unregister("fake-id");
184
+ }).toThrow("Webhook with ID fake-id not found");
185
+ });
186
+ });
187
+
188
+ describe("webhook emission with real HTTP server", () => {
189
+ test("should emit webhook when CoValue changes", async () => {
190
+ const {
191
+ account,
192
+ webhookServer,
193
+ webhookRegistry: webhookManager,
194
+ } = await setupTest();
195
+
196
+ // Create a test CoMap
197
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
198
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
199
+
200
+ // Register webhook
201
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
202
+
203
+ // Make a change to trigger webhook
204
+ testMap.$jazz.set("value", "changed");
205
+ const txID = testMap.$jazz.raw.lastEditAt("value")?.tx;
206
+
207
+ // Wait for webhook to be emitted
208
+ const requests = await webhookServer.waitForRequests(2, 3000);
209
+
210
+ expect(requests.length).toBe(2);
211
+ const lastRequest = webhookServer.getLastRequest();
212
+ expect(lastRequest.coValueId).toBe(coValueId);
213
+ expect(lastRequest.txID).toEqual(txID);
214
+ });
215
+
216
+ test("should queue multiple changes and emit only the latest", async () => {
217
+ const {
218
+ account,
219
+ webhookServer,
220
+ webhookRegistry: webhookManager,
221
+ } = await setupTest();
222
+
223
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
224
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
225
+
226
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
227
+
228
+ // Make multiple rapid changes
229
+ testMap.$jazz.set("value", "change1");
230
+ testMap.$jazz.set("value", "change2");
231
+ testMap.$jazz.set("value", "change3");
232
+
233
+ // Wait for webhook to be processed
234
+ await new Promise((resolve) => setTimeout(resolve, 500));
235
+
236
+ // Should only receive one webhook with the latest state
237
+ expect(webhookServer.requests.length).toBeGreaterThanOrEqual(1);
238
+
239
+ // The last request should be for the final change
240
+ const lastRequest = webhookServer.requests.at(-1);
241
+ expect(lastRequest?.coValueId).toBe(coValueId);
242
+ });
243
+
244
+ test("should update lastSuccessfulEmit after successful webhook", async () => {
245
+ const { account, webhookServer, registryState, webhookRegistry } =
246
+ await setupTest();
247
+
248
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
249
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
250
+
251
+ const webhookId = await webhookRegistry.register(
252
+ webhookServer.getUrl(),
253
+ coValueId,
254
+ );
255
+
256
+ testMap.$jazz.set("value", "changed");
257
+
258
+ const requests = await webhookServer.waitForRequests(1, 3000);
259
+
260
+ expect(requests.length).toBeGreaterThanOrEqual(1);
261
+
262
+ const lastRequest = webhookServer.getLastRequest();
263
+ const webhook = registryState[webhookId];
264
+
265
+ await new Promise((resolve) => setTimeout(resolve, 100));
266
+
267
+ expect(isTxSuccessful(webhook!.successMap!, lastRequest.txID)).toBe(true);
268
+ });
269
+
270
+ test("should retry failed webhooks with exponential backoff", async () => {
271
+ const {
272
+ account,
273
+ webhookServer,
274
+ webhookRegistry: webhookManager,
275
+ } = await setupTest();
276
+
277
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
278
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
279
+
280
+ // Set up server to fail first 2 requests, then succeed
281
+ webhookServer.setResponse(0, 500, "Server Error");
282
+ webhookServer.setResponse(1, 500, "Server Error");
283
+ webhookServer.setResponse(2, 200, "Success");
284
+
285
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
286
+
287
+ testMap.$jazz.set("value", "changed");
288
+
289
+ // Should eventually succeed after retries
290
+ const requests = await webhookServer.waitForRequests(3, 10000);
291
+
292
+ expect(requests).toHaveLength(3);
293
+ expect(requests[0]!.coValueId).toBe(coValueId);
294
+ expect(requests[1]!.coValueId).toBe(coValueId);
295
+ expect(requests[2]!.coValueId).toBe(coValueId);
296
+ });
297
+
298
+ test("should respect Retry-After response header", async () => {
299
+ const {
300
+ account,
301
+ webhookServer,
302
+ webhookRegistry: webhookManager,
303
+ } = await setupTest({
304
+ baseDelayMs: 100000, // Really long default delay, we expect this to be overridden by the Retry-After header
305
+ });
306
+
307
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
308
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
309
+
310
+ // Set up server to fail with Retry-After header
311
+ webhookServer.setResponse(0, 500, "Server Error", undefined, {
312
+ "Retry-After": "0.05",
313
+ }); // 50ms
314
+ webhookServer.setResponse(1, 500, "Server Error", undefined, {
315
+ "Retry-After": "0.05",
316
+ }); // 50ms
317
+ webhookServer.setResponse(2, 200, "Success");
318
+
319
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
320
+
321
+ testMap.$jazz.set("value", "changed");
322
+
323
+ // Should eventually succeed after retry
324
+ const requests = await webhookServer.waitForRequests(3, 1000);
325
+
326
+ expect(requests).toHaveLength(3);
327
+ expect(requests[0]!.coValueId).toBe(coValueId);
328
+ expect(requests[1]!.coValueId).toBe(coValueId);
329
+ expect(requests[2]!.coValueId).toBe(coValueId);
330
+ });
331
+
332
+ test("should give up after max retries", async () => {
333
+ const {
334
+ account,
335
+ webhookServer,
336
+ webhookRegistry: webhookManager,
337
+ } = await setupTest();
338
+
339
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
340
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
341
+
342
+ // Set up server to always fail
343
+ for (let i = 0; i < 10; i++) {
344
+ webhookServer.setResponse(i, 500, "Server Error");
345
+ }
346
+
347
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
348
+
349
+ testMap.$jazz.set("value", "changed");
350
+
351
+ // Should retry 5 times then give up
352
+ const requests = await webhookServer.waitForRequests(5, 1000);
353
+
354
+ expect(requests).toHaveLength(5); // 5 retries
355
+ expect(requests.every((req) => req.coValueId === coValueId)).toBe(true);
356
+ });
357
+
358
+ test("should handle slow server responses", async () => {
359
+ const {
360
+ account,
361
+ webhookServer,
362
+ webhookRegistry: webhookManager,
363
+ } = await setupTest();
364
+
365
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
366
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
367
+
368
+ // Set up server with slow response
369
+ webhookServer.setResponse(0, 200, "Success", 500);
370
+
371
+ await webhookManager.register(webhookServer.getUrl(), coValueId);
372
+
373
+ testMap.$jazz.set("value", "changed");
374
+
375
+ await webhookServer.waitForRequests(1, 5000);
376
+
377
+ const request = webhookServer.expectSingleRequest();
378
+
379
+ expect(request.coValueId).toBe(coValueId);
380
+ });
381
+
382
+ test("should handle webhook unregistration cleanup", async () => {
383
+ const { account, webhookServer, webhookRegistry } = await setupTest();
384
+
385
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
386
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
387
+
388
+ const webhookId = await webhookRegistry.register(
389
+ webhookServer.getUrl(),
390
+ coValueId,
391
+ );
392
+
393
+ // Make initial change
394
+ testMap.$jazz.set("value", "changed");
395
+ await webhookServer.waitForRequests(2, 3000);
396
+
397
+ const initialRequestCount = webhookServer.requests.length;
398
+
399
+ // Unregister webhook
400
+ webhookRegistry.unregister(webhookId);
401
+
402
+ // Wait a bit to ensure the unregistration is processed
403
+ await new Promise((resolve) => setTimeout(resolve, 100));
404
+
405
+ // Make another change - should not trigger webhook
406
+ testMap.$jazz.set("value", "changed_again");
407
+
408
+ // Wait a bit to ensure no additional requests
409
+ await new Promise((resolve) => setTimeout(resolve, 200));
410
+
411
+ expect(webhookServer.getRequestCount()).toBe(initialRequestCount);
412
+ });
413
+
414
+ test("should start all active webhook subscriptions", async () => {
415
+ const { account, webhookServer, webhookRegistry } = await setupTest();
416
+
417
+ const testMap1 = TestCoMap.create({ value: "initial1" }, account.root);
418
+ const testMap2 = TestCoMap.create({ value: "initial2" }, account.root);
419
+ const coValueId1 = testMap1.$jazz.id as `co_z${string}`;
420
+ const coValueId2 = testMap2.$jazz.id as `co_z${string}`;
421
+
422
+ // Register webhooks (this automatically starts subscriptions)
423
+ const webhookId1 = await webhookRegistry.register(
424
+ webhookServer.getUrl(),
425
+ coValueId1,
426
+ );
427
+ const webhookId2 = await webhookRegistry.register(
428
+ webhookServer.getUrl(),
429
+ coValueId2,
430
+ );
431
+
432
+ // Shutdown all subscriptions
433
+ webhookRegistry.shutdown();
434
+
435
+ // Verify no subscriptions are active
436
+ expect(webhookRegistry["activeSubscriptions"].size).toBe(0);
437
+
438
+ // Start all active webhook subscriptions
439
+ await webhookRegistry.start();
440
+
441
+ // Verify subscriptions are active again
442
+ expect(webhookRegistry["activeSubscriptions"].size).toBe(2);
443
+ expect(webhookRegistry["activeSubscriptions"].has(webhookId1)).toBe(true);
444
+ expect(webhookRegistry["activeSubscriptions"].has(webhookId2)).toBe(true);
445
+
446
+ // Make changes to both CoValues to verify webhooks are working
447
+ testMap1.$jazz.set("value", "changed1");
448
+ testMap2.$jazz.set("value", "changed2");
449
+
450
+ // Wait for webhooks to be emitted
451
+ // TODO: why are we getting two requests for webhook1
452
+ const requests = await webhookServer.waitForRequests(4, 3000);
453
+
454
+ expect(requests.length).toBeGreaterThanOrEqual(2);
455
+
456
+ // Verify both webhooks were triggered
457
+ const coValueIds = requests.map((req) => req.coValueId);
458
+ expect(coValueIds).toContain(coValueId1);
459
+ expect(coValueIds).toContain(coValueId2);
460
+ });
461
+
462
+ test("should when restarting, only the changed covalues should trigger webhooks", async () => {
463
+ const { account, webhookServer, webhookRegistry } = await setupTest();
464
+
465
+ const testMap1 = TestCoMap.create({ value: "initial1" }, account.root);
466
+ const initialTxID1 = testMap1.$jazz.raw.lastEditAt("value")!.tx;
467
+ const testMap2 = TestCoMap.create({ value: "initial2" }, account.root);
468
+ const initialTxID2 = testMap2.$jazz.raw.lastEditAt("value")!.tx;
469
+ const coValueId1 = testMap1.$jazz.id as `co_z${string}`;
470
+ const coValueId2 = testMap2.$jazz.id as `co_z${string}`;
471
+
472
+ // Register webhooks (this automatically starts subscriptions)
473
+ const webhookId1 = await webhookRegistry.register(
474
+ webhookServer.getUrl(),
475
+ coValueId1,
476
+ );
477
+ const webhookId2 = await webhookRegistry.register(
478
+ webhookServer.getUrl(),
479
+ coValueId2,
480
+ );
481
+
482
+ await waitForWebhookEmitted(webhookId1, initialTxID1);
483
+ await waitForWebhookEmitted(webhookId2, initialTxID2);
484
+
485
+ webhookRegistry.shutdown();
486
+
487
+ testMap1.$jazz.set("value", "changed");
488
+ const changedTxID1 = testMap1.$jazz.raw.lastEditAt("value")!.tx;
489
+
490
+ webhookRegistry.start();
491
+
492
+ await waitForWebhookEmitted(webhookId1, changedTxID1);
493
+
494
+ // Wait some extra time to ensure that we have only one extra request
495
+ await new Promise((resolve) => setTimeout(resolve, 100));
496
+
497
+ // The two initial updates, plus the one we made before restarting
498
+ expect(webhookServer.getRequestCount()).toBe(3);
499
+ });
500
+
501
+ test("should not start subscriptions for inactive webhooks", async () => {
502
+ const { account, webhookServer, webhookRegistry } = await setupTest();
503
+
504
+ const testMap = TestCoMap.create({ value: "initial" }, account.root);
505
+ const coValueId = testMap.$jazz.id as `co_z${string}`;
506
+
507
+ // Register webhook
508
+ const webhookId = await webhookRegistry.register(
509
+ webhookServer.getUrl(),
510
+ coValueId,
511
+ );
512
+
513
+ // Wait a bit to ensure no webhook is sent
514
+ await new Promise((resolve) => setTimeout(resolve, 200));
515
+
516
+ expect(webhookServer.getRequestCount()).toBe(1);
517
+
518
+ // Deactivate the webhook
519
+ const webhook = webhookRegistry.state[webhookId];
520
+ expect(webhook).toBeDefined();
521
+ webhook!.$jazz.set("active", false);
522
+
523
+ // Shutdown all subscriptions
524
+ webhookRegistry.shutdown();
525
+
526
+ // Start all active webhook subscriptions
527
+ webhookRegistry.start();
528
+
529
+ // Verify no subscriptions are active (since webhook is inactive)
530
+ expect(webhookRegistry["activeSubscriptions"].size).toBe(0);
531
+
532
+ // Make a change - should not trigger webhook
533
+ testMap.$jazz.set("value", "changed");
534
+
535
+ // Wait a bit to ensure no webhook is sent
536
+ await new Promise((resolve) => setTimeout(resolve, 200));
537
+
538
+ expect(webhookServer.getRequestCount()).toBe(1);
539
+ });
540
+ });
541
+ });
542
+
543
+ async function waitForWebhookEmitted(
544
+ id: string,
545
+ txID: CojsonInternalTypes.TransactionID,
546
+ ) {
547
+ const webhook = await WebhookRegistration.load(id, {
548
+ resolve: {
549
+ successMap: { $each: true },
550
+ },
551
+ });
552
+
553
+ assert(webhook);
554
+
555
+ return waitFor(() => {
556
+ expect(isTxSuccessful(webhook.successMap!, txID)).toBe(true);
557
+ });
558
+ }
559
+
560
+ function waitFor(callback: () => boolean | void | Promise<boolean | void>) {
561
+ return new Promise<void>((resolve, reject) => {
562
+ const checkPassed = async () => {
563
+ try {
564
+ return { ok: await callback(), error: null };
565
+ } catch (error) {
566
+ return { ok: false, error };
567
+ }
568
+ };
569
+
570
+ let retries = 0;
571
+
572
+ const interval = setInterval(async () => {
573
+ const { ok, error } = await checkPassed();
574
+
575
+ if (ok !== false) {
576
+ clearInterval(interval);
577
+ resolve();
578
+ }
579
+
580
+ if (++retries > 10) {
581
+ clearInterval(interval);
582
+ reject(error);
583
+ }
584
+ }, 100);
585
+ });
586
+ }
package/src/types.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Comprehensive type definitions for Jazz Webhook service
3
+ */
4
+
5
+ // Request types
6
+ export interface WebhookServiceOptions {
7
+ /** Port to run the service on (default: 3000) */
8
+ port?: number;
9
+ /** Host to bind the service to (default: "localhost") */
10
+ host?: string;
11
+ /** Enable CORS for all origins (default: true) */
12
+ enableCors?: boolean;
13
+ /** Maximum number of retry attempts for failed webhooks (default: 5) */
14
+ maxRetries?: number;
15
+ }
16
+
17
+ export interface RegisterWebhookRequest {
18
+ webhookUrl: string;
19
+ coValueId: string;
20
+ }
21
+
22
+ // Data types
23
+ export interface WebhookInfo {
24
+ id: string;
25
+ webhookUrl: string;
26
+ coValueId: string;
27
+ active: boolean;
28
+ successMap: {
29
+ [transactionID: string]: boolean;
30
+ };
31
+ }
32
+
33
+ export interface RegisterWebhookResponse {
34
+ webhookId: string;
35
+ message: string;
36
+ }
37
+
38
+ // Test server types
39
+ export interface WebhookTestResponse {
40
+ statusCode: number;
41
+ body?: string;
42
+ delay?: number;
43
+ }
44
+
45
+ /**
46
+ * Consolidated interface for all webhook service response types
47
+ */
48
+ export interface WebhookServiceResponses {
49
+ // Success responses
50
+ RegisterWebhookSuccess: {
51
+ success: true;
52
+ data: RegisterWebhookResponse;
53
+ };
54
+
55
+ WebhookInfoSuccess: {
56
+ success: true;
57
+ data: WebhookInfo;
58
+ };
59
+
60
+ HealthCheckSuccess: {
61
+ success: true;
62
+ message: string;
63
+ data: {
64
+ timestamp: string;
65
+ webhookCount: number;
66
+ };
67
+ };
68
+
69
+ DeleteWebhookSuccess: {
70
+ success: true;
71
+ message: string;
72
+ };
73
+
74
+ // Error responses
75
+ WebhookNotFoundError: {
76
+ success: false;
77
+ error: string;
78
+ };
79
+
80
+ ValidationError: {
81
+ success: false;
82
+ error: string;
83
+ };
84
+
85
+ InternalServerError: {
86
+ success: false;
87
+ error: string;
88
+ };
89
+
90
+ // Generic error response
91
+ GenericError: {
92
+ success: false;
93
+ error: string;
94
+ };
95
+ }
96
+
97
+ /**
98
+ * HTTP status codes used by the webhook service
99
+ */
100
+ export enum WebhookServiceStatusCodes {
101
+ OK = 200,
102
+ CREATED = 201,
103
+ BAD_REQUEST = 400,
104
+ NOT_FOUND = 404,
105
+ INTERNAL_SERVER_ERROR = 500,
106
+ }