@apollo-deploy/tesseract 0.1.0

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/adapters/types.d.ts +15 -0
  4. package/dist/adapters/types.d.ts.map +1 -0
  5. package/dist/adapters/types.js +6 -0
  6. package/dist/adapters/types.js.map +1 -0
  7. package/dist/adapters/typescript/index.d.ts +11 -0
  8. package/dist/adapters/typescript/index.d.ts.map +1 -0
  9. package/dist/adapters/typescript/index.js +112 -0
  10. package/dist/adapters/typescript/index.js.map +1 -0
  11. package/dist/adapters/typescript/model-emitter.d.ts +13 -0
  12. package/dist/adapters/typescript/model-emitter.d.ts.map +1 -0
  13. package/dist/adapters/typescript/model-emitter.js +415 -0
  14. package/dist/adapters/typescript/model-emitter.js.map +1 -0
  15. package/dist/adapters/typescript/shared.d.ts +26 -0
  16. package/dist/adapters/typescript/shared.d.ts.map +1 -0
  17. package/dist/adapters/typescript/shared.js +65 -0
  18. package/dist/adapters/typescript/shared.js.map +1 -0
  19. package/dist/cli.d.ts +6 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +53 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/helpers/handlebars.d.ts +15 -0
  24. package/dist/helpers/handlebars.d.ts.map +1 -0
  25. package/dist/helpers/handlebars.js +172 -0
  26. package/dist/helpers/handlebars.js.map +1 -0
  27. package/dist/index.d.ts +16 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +36 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/pipeline/enrich.d.ts +7 -0
  32. package/dist/pipeline/enrich.d.ts.map +1 -0
  33. package/dist/pipeline/enrich.js +277 -0
  34. package/dist/pipeline/enrich.js.map +1 -0
  35. package/dist/pipeline/intake.d.ts +11 -0
  36. package/dist/pipeline/intake.d.ts.map +1 -0
  37. package/dist/pipeline/intake.js +508 -0
  38. package/dist/pipeline/intake.js.map +1 -0
  39. package/dist/pipeline/write.d.ts +15 -0
  40. package/dist/pipeline/write.d.ts.map +1 -0
  41. package/dist/pipeline/write.js +33 -0
  42. package/dist/pipeline/write.js.map +1 -0
  43. package/dist/types/config.d.ts +47 -0
  44. package/dist/types/config.d.ts.map +1 -0
  45. package/dist/types/config.js +24 -0
  46. package/dist/types/config.js.map +1 -0
  47. package/dist/types/ir.d.ts +172 -0
  48. package/dist/types/ir.d.ts.map +1 -0
  49. package/dist/types/ir.js +7 -0
  50. package/dist/types/ir.js.map +1 -0
  51. package/dist/types/manifest.d.ts +112 -0
  52. package/dist/types/manifest.d.ts.map +1 -0
  53. package/dist/types/manifest.js +32 -0
  54. package/dist/types/manifest.js.map +1 -0
  55. package/dist/utils/format.d.ts +5 -0
  56. package/dist/utils/format.d.ts.map +1 -0
  57. package/dist/utils/format.js +21 -0
  58. package/dist/utils/format.js.map +1 -0
  59. package/dist/utils/naming.d.ts +26 -0
  60. package/dist/utils/naming.d.ts.map +1 -0
  61. package/dist/utils/naming.js +270 -0
  62. package/dist/utils/naming.js.map +1 -0
  63. package/package.json +58 -0
  64. package/templates/typescript/client.hbs +145 -0
  65. package/templates/typescript/domain-index.hbs +10 -0
  66. package/templates/typescript/domain.hbs +127 -0
  67. package/templates/typescript/index.hbs +64 -0
  68. package/templates/typescript/package-json.hbs +29 -0
  69. package/templates/typescript/readme.hbs +78 -0
  70. package/templates/typescript/transport-sse.hbs +377 -0
  71. package/templates/typescript/transport.hbs +662 -0
  72. package/templates/typescript/tsconfig.hbs +18 -0
  73. package/templates/typescript/types-common.hbs +98 -0
  74. package/templates/typescript/types-errors.hbs +191 -0
  75. package/templates/typescript/types.hbs +33 -0
  76. package/templates/typescript/utils-index.hbs +7 -0
  77. package/templates/typescript/utils-query.hbs +29 -0
  78. package/templates/typescript/webhook-handler.hbs +447 -0
@@ -0,0 +1,447 @@
1
+ /**
2
+ * @generated
3
+ * This file was automatically generated by sdk-forge.
4
+ * DO NOT MODIFY THIS FILE DIRECTLY.
5
+ */
6
+
7
+ import type {
8
+ {{#each webhookImports}}
9
+ {{this}},
10
+ {{/each}}
11
+ } from '../types/index.js';
12
+
13
+ // ── Webhook Event Map ────────────────────────────────
14
+
15
+ /**
16
+ * Maps webhook event names to their typed payload.
17
+ */
18
+ export interface WebhookEventMap {
19
+ {{#each webhooks}}
20
+ {{#if summary}}
21
+ /** {{summary}} */
22
+ {{/if}}
23
+ '{{name}}': {{payloadType}};
24
+ {{/each}}
25
+ }
26
+
27
+ export type WebhookEventName = keyof WebhookEventMap;
28
+
29
+ // ── Webhook Handler Types ────────────────────────────
30
+
31
+ /**
32
+ * A typed handler function for a specific webhook event.
33
+ */
34
+ export type WebhookHandler<E extends WebhookEventName> = (
35
+ payload: WebhookEventMap[E],
36
+ meta: WebhookMeta,
37
+ ) => void | Promise<void>;
38
+
39
+ /**
40
+ * Metadata about the incoming webhook request.
41
+ */
42
+ export interface WebhookMeta {
43
+ /** Raw headers from the webhook request */
44
+ headers: Record<string, string | string[] | undefined>;
45
+ /** Timestamp when the webhook was received */
46
+ receivedAt: number;
47
+ /** The webhook event name */
48
+ eventName: string;
49
+ /** Unique delivery ID for deduplication (if provided by the sender) */
50
+ deliveryId?: string;
51
+ }
52
+
53
+ /**
54
+ * Options for webhook signature verification.
55
+ */
56
+ export interface WebhookVerifyOptions {
57
+ /**
58
+ * The raw request body as a string or Buffer for signature verification.
59
+ * Must be the unmodified body before JSON parsing.
60
+ */
61
+ rawBody: string | ArrayBuffer;
62
+ /** The signature header value from the incoming request */
63
+ signature: string;
64
+ /** The shared secret used for HMAC verification */
65
+ secret: string;
66
+ /** Hash algorithm for HMAC (default: "sha256") */
67
+ algorithm?: string;
68
+ /** Signature encoding format (default: "hex") */
69
+ encoding?: 'hex' | 'base64';
70
+ /**
71
+ * Tolerance window in milliseconds for timestamp-based replay protection.
72
+ * When set, the `timestamp` field is required and requests outside
73
+ * the tolerance window are rejected. Default: 0 (disabled).
74
+ */
75
+ toleranceMs?: number;
76
+ /**
77
+ * Unix timestamp (seconds) from the webhook request for replay protection.
78
+ * Required when `toleranceMs` is set.
79
+ */
80
+ timestamp?: number;
81
+ }
82
+
83
+ // ── Webhook Configuration ────────────────────────────
84
+
85
+ /**
86
+ * Configuration options for the webhook registry.
87
+ */
88
+ export interface WebhookRegistryConfig {
89
+ /**
90
+ * Custom signature verification function.
91
+ * When provided, replaces the built-in HMAC verifier entirely.
92
+ * Return `true` if the signature is valid.
93
+ */
94
+ verify?: (options: WebhookVerifyOptions) => boolean | Promise<boolean>;
95
+ /**
96
+ * Called when a handler throws an error during dispatch.
97
+ * By default, handler errors are collected and re-thrown as an AggregateError.
98
+ * Return `true` to swallow the error, or `false`/`void` to collect it.
99
+ */
100
+ onHandlerError?: (error: unknown, eventName: string, handler: WebhookHandler<any>) => boolean | void;
101
+ /**
102
+ * Maximum number of handlers allowed per event.
103
+ * Prevents accidental handler accumulation. Default: 100.
104
+ */
105
+ maxHandlersPerEvent?: number;
106
+ /**
107
+ * Per-handler timeout in milliseconds. Handlers that exceed this
108
+ * duration will have their promise rejected. Default: 0 (no timeout).
109
+ */
110
+ handlerTimeoutMs?: number;
111
+ /**
112
+ * Custom function to extract the event name from the raw payload.
113
+ * When provided, the `eventName` argument to `dispatch` is ignored
114
+ * and this function determines the event name instead.
115
+ */
116
+ resolveEventName?: (payload: unknown, meta: WebhookMeta) => string | undefined;
117
+ /**
118
+ * Custom function to transform the raw payload before it reaches handlers.
119
+ * Useful for unwrapping envelopes or normalizing data structures.
120
+ */
121
+ transformPayload?: (payload: unknown, eventName: string) => unknown;
122
+ }
123
+
124
+ // ── Webhook Registry ─────────────────────────────────
125
+
126
+ /**
127
+ * Registry for typed webhook event handlers.
128
+ * Provides type-safe handler registration and dispatch.
129
+ */
130
+ export interface WebhookRegistry {
131
+ /**
132
+ * Register a handler for a specific webhook event.
133
+ * Multiple handlers can be registered per event.
134
+ */
135
+ on<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void;
136
+
137
+ /**
138
+ * Register a one-time handler that automatically unregisters after the first invocation.
139
+ */
140
+ once<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void;
141
+
142
+ /**
143
+ * Remove a previously registered handler.
144
+ */
145
+ off<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void;
146
+
147
+ /**
148
+ * Remove all handlers for a specific event, or all handlers if no event is specified.
149
+ */
150
+ removeAll(event?: WebhookEventName): void;
151
+
152
+ /**
153
+ * Dispatch a webhook event to all registered handlers.
154
+ * Returns a promise that resolves when all handlers have completed.
155
+ * Throws an AggregateError if any handlers fail (unless `onHandlerError` swallows them).
156
+ */
157
+ dispatch(eventName: string, payload: unknown, meta: WebhookMeta): Promise<void>;
158
+
159
+ /**
160
+ * Check whether any handlers are registered for the given event.
161
+ */
162
+ has(event: WebhookEventName): boolean;
163
+
164
+ /**
165
+ * Return the list of event names that have at least one handler registered.
166
+ */
167
+ eventNames(): WebhookEventName[];
168
+
169
+ /**
170
+ * Return the number of handlers registered for a given event.
171
+ */
172
+ listenerCount(event: WebhookEventName): number;
173
+
174
+ /**
175
+ * Verify a webhook signature using HMAC (or a custom verifier).
176
+ * Returns `true` if the signature is valid.
177
+ */
178
+ verify(options: WebhookVerifyOptions): Promise<boolean>;
179
+ }
180
+
181
+ /**
182
+ * Create a typed webhook handler registry.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const webhooks = createWebhookRegistry({
187
+ * handlerTimeoutMs: 5000,
188
+ * onHandlerError: (err, event) => {
189
+ * console.error(`Handler failed for ${event}:`, err);
190
+ * return true; // swallow the error
191
+ * },
192
+ * });
193
+ *
194
+ * webhooks.on('orderCreated', async (payload, meta) => {
195
+ * console.log('New order:', payload.id);
196
+ * });
197
+ *
198
+ * // One-time handler
199
+ * webhooks.once('systemReady', (payload) => {
200
+ * console.log('System is ready');
201
+ * });
202
+ *
203
+ * // In your webhook endpoint handler:
204
+ * app.post('/webhooks', async (req, res) => {
205
+ * const isValid = await webhooks.verify({
206
+ * rawBody: req.rawBody,
207
+ * signature: req.headers['x-webhook-signature'] as string,
208
+ * secret: process.env.WEBHOOK_SECRET!,
209
+ * toleranceMs: 5 * 60 * 1000, // 5 minute replay window
210
+ * timestamp: parseInt(req.headers['x-webhook-timestamp'] as string, 10),
211
+ * });
212
+ * if (!isValid) return res.status(401).send('Invalid signature');
213
+ *
214
+ * await webhooks.dispatch(req.body.event, req.body.data, {
215
+ * headers: req.headers,
216
+ * receivedAt: Date.now(),
217
+ * eventName: req.body.event,
218
+ * deliveryId: req.headers['x-webhook-id'] as string,
219
+ * });
220
+ * res.status(200).send('OK');
221
+ * });
222
+ * ```
223
+ */
224
+ export function createWebhookRegistry(config: WebhookRegistryConfig = {}): WebhookRegistry {
225
+ const handlers = new Map<string, Set<WebhookHandler<any>>>();
226
+ const onceHandlers = new WeakSet<WebhookHandler<any>>();
227
+ const maxPerEvent = config.maxHandlersPerEvent ?? 100;
228
+
229
+ function getOrCreate(event: string): Set<WebhookHandler<any>> {
230
+ let set = handlers.get(event);
231
+ if (!set) {
232
+ set = new Set();
233
+ handlers.set(event, set);
234
+ }
235
+ return set;
236
+ }
237
+
238
+ function wrapWithTimeout<E extends WebhookEventName>(
239
+ handler: WebhookHandler<E>,
240
+ timeoutMs: number,
241
+ ): (payload: WebhookEventMap[E], meta: WebhookMeta) => Promise<void> {
242
+ return (payload, meta) => {
243
+ return new Promise<void>((resolve, reject) => {
244
+ const timer = setTimeout(
245
+ () => reject(new Error(`Webhook handler timed out after ${timeoutMs}ms`)),
246
+ timeoutMs,
247
+ );
248
+ try {
249
+ const result = handler(payload, meta);
250
+ if (result instanceof Promise) {
251
+ result.then(resolve, reject).finally(() => clearTimeout(timer));
252
+ } else {
253
+ clearTimeout(timer);
254
+ resolve();
255
+ }
256
+ } catch (err) {
257
+ clearTimeout(timer);
258
+ reject(err);
259
+ }
260
+ });
261
+ };
262
+ }
263
+
264
+ return {
265
+ on<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void {
266
+ const set = getOrCreate(event);
267
+ if (set.size >= maxPerEvent) {
268
+ throw new Error(
269
+ `Maximum handler limit (${maxPerEvent}) reached for event "${String(event)}". ` +
270
+ 'Remove existing handlers or increase maxHandlersPerEvent.',
271
+ );
272
+ }
273
+ set.add(handler as WebhookHandler<any>);
274
+ },
275
+
276
+ once<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void {
277
+ const set = getOrCreate(event);
278
+ if (set.size >= maxPerEvent) {
279
+ throw new Error(
280
+ `Maximum handler limit (${maxPerEvent}) reached for event "${String(event)}".`,
281
+ );
282
+ }
283
+ onceHandlers.add(handler as WebhookHandler<any>);
284
+ set.add(handler as WebhookHandler<any>);
285
+ },
286
+
287
+ off<E extends WebhookEventName>(event: E, handler: WebhookHandler<E>): void {
288
+ const set = handlers.get(event);
289
+ if (set) {
290
+ set.delete(handler as WebhookHandler<any>);
291
+ onceHandlers.delete(handler as WebhookHandler<any>);
292
+ if (set.size === 0) handlers.delete(event);
293
+ }
294
+ },
295
+
296
+ removeAll(event?: WebhookEventName): void {
297
+ if (event !== undefined) {
298
+ handlers.delete(event);
299
+ } else {
300
+ handlers.clear();
301
+ }
302
+ },
303
+
304
+ async dispatch(eventName: string, payload: unknown, meta: WebhookMeta): Promise<void> {
305
+ // Allow config to override event name resolution
306
+ const resolvedName = config.resolveEventName
307
+ ? config.resolveEventName(payload, meta)
308
+ : eventName;
309
+
310
+ if (resolvedName === undefined) return;
311
+
312
+ const eventHandlers = handlers.get(resolvedName);
313
+ if (!eventHandlers || eventHandlers.size === 0) return;
314
+
315
+ // Allow config to transform the payload
316
+ const resolvedPayload = config.transformPayload
317
+ ? config.transformPayload(payload, resolvedName)
318
+ : payload;
319
+
320
+ const errors: unknown[] = [];
321
+ // Snapshot handlers to avoid mutation during iteration
322
+ const snapshot = Array.from(eventHandlers);
323
+
324
+ const promises: Promise<void>[] = [];
325
+ for (const handler of snapshot) {
326
+ // Clean up once-handlers before invocation
327
+ if (onceHandlers.has(handler)) {
328
+ eventHandlers.delete(handler);
329
+ onceHandlers.delete(handler);
330
+ if (eventHandlers.size === 0) handlers.delete(resolvedName);
331
+ }
332
+
333
+ try {
334
+ const timeoutMs = config.handlerTimeoutMs ?? 0;
335
+ const fn = timeoutMs > 0
336
+ ? wrapWithTimeout(handler, timeoutMs)
337
+ : handler;
338
+
339
+ const result = fn(resolvedPayload, meta);
340
+ if (result instanceof Promise) {
341
+ promises.push(
342
+ result.catch((err) => {
343
+ const swallow = config.onHandlerError?.(err, resolvedName, handler);
344
+ if (swallow !== true) errors.push(err);
345
+ }),
346
+ );
347
+ }
348
+ } catch (err) {
349
+ const swallow = config.onHandlerError?.(err, resolvedName, handler);
350
+ if (swallow !== true) errors.push(err);
351
+ }
352
+ }
353
+
354
+ if (promises.length > 0) {
355
+ await Promise.all(promises);
356
+ }
357
+
358
+ if (errors.length === 1) {
359
+ throw errors[0];
360
+ }
361
+ if (errors.length > 1) {
362
+ throw new AggregateError(errors, `${errors.length} webhook handler(s) failed for "${resolvedName}"`);
363
+ }
364
+ },
365
+
366
+ has(event: WebhookEventName): boolean {
367
+ const set = handlers.get(event);
368
+ return set !== undefined && set.size > 0;
369
+ },
370
+
371
+ eventNames(): WebhookEventName[] {
372
+ return Array.from(handlers.keys()) as WebhookEventName[];
373
+ },
374
+
375
+ listenerCount(event: WebhookEventName): number {
376
+ return handlers.get(event)?.size ?? 0;
377
+ },
378
+
379
+ async verify(options: WebhookVerifyOptions): Promise<boolean> {
380
+ // Replay protection: reject if timestamp is outside tolerance window
381
+ if (options.toleranceMs && options.toleranceMs > 0) {
382
+ if (options.timestamp === undefined) return false;
383
+ const age = Math.abs(Date.now() - options.timestamp * 1000);
384
+ if (age > options.toleranceMs) return false;
385
+ }
386
+
387
+ // Delegate to custom verifier if provided
388
+ if (config.verify) {
389
+ return config.verify(options);
390
+ }
391
+
392
+ const algorithm = options.algorithm ?? 'sha256';
393
+ const encoding = options.encoding ?? 'hex';
394
+
395
+ try {
396
+ const encoder = new TextEncoder();
397
+ const hashName = `SHA-${algorithm.replace(/^sha-?/i, '')}`;
398
+ const key = await crypto.subtle.importKey(
399
+ 'raw',
400
+ encoder.encode(options.secret),
401
+ { name: 'HMAC', hash: hashName },
402
+ false,
403
+ ['sign'],
404
+ );
405
+
406
+ const body = typeof options.rawBody === 'string'
407
+ ? encoder.encode(options.rawBody)
408
+ : new Uint8Array(options.rawBody);
409
+
410
+ const signatureBuffer = await crypto.subtle.sign('HMAC', key, body);
411
+ const signatureBytes = new Uint8Array(signatureBuffer);
412
+
413
+ let computed: string;
414
+ if (encoding === 'base64') {
415
+ // Convert to base64
416
+ let binary = '';
417
+ for (let i = 0; i < signatureBytes.length; i++) {
418
+ binary += String.fromCharCode(signatureBytes[i]!);
419
+ }
420
+ computed = btoa(binary);
421
+ } else {
422
+ computed = Array.from(signatureBytes)
423
+ .map((b) => b.toString(16).padStart(2, '0'))
424
+ .join('');
425
+ }
426
+
427
+ // Strip common algorithm prefixes from the provided signature
428
+ const expected = options.signature
429
+ .replace(/^sha256=/, '')
430
+ .replace(/^sha512=/, '')
431
+ .replace(/^sha1=/, '');
432
+
433
+ // Constant-time comparison — return false early only on length mismatch
434
+ // (length itself is not secret information)
435
+ if (computed.length !== expected.length) return false;
436
+
437
+ let mismatch = 0;
438
+ for (let i = 0; i < computed.length; i++) {
439
+ mismatch |= computed.charCodeAt(i) ^ expected.charCodeAt(i);
440
+ }
441
+ return mismatch === 0;
442
+ } catch {
443
+ return false;
444
+ }
445
+ },
446
+ };
447
+ }