@instantdb/webhooks 0.0.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.
@@ -0,0 +1,551 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Webhooks = exports.WebhooksManager = void 0;
4
+ const core_1 = require("@instantdb/core");
5
+ const knownKeys = {
6
+ 'https://api.instantdb.com': {
7
+ keys: [
8
+ {
9
+ kty: 'OKP',
10
+ crv: 'Ed25519',
11
+ alg: 'EdDSA',
12
+ use: 'sig',
13
+ kid: '1034696293',
14
+ x: 'N-C41432STKAKkXAWmeIOXMnZcGRR1b9u1L3bTVqI_o',
15
+ },
16
+ ],
17
+ },
18
+ 'http://localhost:8888': {
19
+ keys: [
20
+ {
21
+ kty: 'OKP',
22
+ crv: 'Ed25519',
23
+ alg: 'EdDSA',
24
+ use: 'sig',
25
+ kid: '503090235',
26
+ x: 'qrSkwDaMITRMF9nOgpueqxgaAiuFmJperYE3mkyl8Ow',
27
+ },
28
+ ],
29
+ },
30
+ };
31
+ function inferWebCryptoAlg(jwk) {
32
+ if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') {
33
+ return { name: 'Ed25519' };
34
+ }
35
+ if (jwk.kty === 'EC') {
36
+ return { name: 'ECDSA', namedCurve: jwk.crv }; // e.g., P-256, P-384
37
+ }
38
+ if (jwk.kty === 'RSA') {
39
+ // RSA keys often specify the exact hash in the 'alg' field (e.g., RS256)
40
+ const hashMap = {
41
+ RS256: 'SHA-256',
42
+ RS384: 'SHA-384',
43
+ RS512: 'SHA-512',
44
+ };
45
+ return {
46
+ name: 'RSASSA-PKCS1-v1_5',
47
+ hash: hashMap[jwk.alg] || 'SHA-256',
48
+ };
49
+ }
50
+ throw new Error(`Unsupported JWK configuration: kty=${jwk.kty}, crv=${jwk.crv}`);
51
+ }
52
+ async function importKey(jwk) {
53
+ const alg = inferWebCryptoAlg(jwk);
54
+ const key = await crypto.subtle.importKey('jwk', jwk, alg, false, ['verify']);
55
+ return { alg, key };
56
+ }
57
+ function verify(alg, key, signature, message) {
58
+ return crypto.subtle.verify(alg, key, signature, message);
59
+ }
60
+ function hexToUint8Array(hexString) {
61
+ const bytes = new Uint8Array(Math.ceil(hexString.length / 2));
62
+ for (let i = 0; i < bytes.length; i++) {
63
+ bytes[i] = parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
64
+ }
65
+ return bytes;
66
+ }
67
+ // XXX: Test in nextjs app router
68
+ // XXX: Test in nextjs pages router
69
+ // XXX: Test with express, deno, koa, nestjs
70
+ // XXX: Test with cloudflare
71
+ function parseSignatureHeader(h) {
72
+ let t, kid, v1;
73
+ for (const part of h.split(',')) {
74
+ const [k, v] = part.split('=');
75
+ switch (k) {
76
+ case 't': {
77
+ t = v;
78
+ break;
79
+ }
80
+ case 'kid': {
81
+ kid = v;
82
+ break;
83
+ }
84
+ case 'v1': {
85
+ v1 = v;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ const missingKeys = [];
91
+ if (!t) {
92
+ missingKeys.push('t');
93
+ }
94
+ if (!kid) {
95
+ missingKeys.push('kid');
96
+ }
97
+ if (!v1) {
98
+ missingKeys.push('v1');
99
+ }
100
+ if (missingKeys.length || !t || !kid || !v1) {
101
+ throw new core_1.InstantError('Invalid Instant-Signature header.', {
102
+ header: h,
103
+ missingKeys,
104
+ });
105
+ }
106
+ return { t, kid, v1 };
107
+ }
108
+ function validateT(receivedAt, t, tolerance) {
109
+ const age = Math.floor(receivedAt.getTime() / 1000) - parseInt(t, 10);
110
+ if (age > tolerance) {
111
+ throw new core_1.InstantError('Webhook signature is too old', {
112
+ tolerance,
113
+ receivedAt,
114
+ t,
115
+ });
116
+ }
117
+ }
118
+ // We make this a global cache so that it will survive across
119
+ // restarts. It will only store valid signing keys from instant, and we don't
120
+ // create many of them, so the memory usage will be only 1 or 2 keys.
121
+ const keyCache = {};
122
+ const defaultTolerance = 300; // 5 minutes
123
+ async function jsonReject(rejectFn, res) {
124
+ const body = await res.text();
125
+ try {
126
+ const json = JSON.parse(body);
127
+ return rejectFn(new core_1.InstantAPIError({ status: res.status, body: json }));
128
+ }
129
+ catch (_e) {
130
+ return rejectFn(new core_1.InstantAPIError({
131
+ status: res.status,
132
+ body: { type: undefined, message: body },
133
+ }));
134
+ }
135
+ }
136
+ const defaultJsonFetch = async (input, init) => {
137
+ const headers = {
138
+ ...(init?.headers || {}),
139
+ 'Instant-Core-Version': core_1.version,
140
+ };
141
+ const res = await fetch(input, { ...init, headers });
142
+ if (res.status === 200) {
143
+ return res.json();
144
+ }
145
+ return jsonReject((x) => Promise.reject(x), res);
146
+ };
147
+ function parseDate(s) {
148
+ return s ? new Date(s) : null;
149
+ }
150
+ function toWebhookInfo(raw) {
151
+ return {
152
+ id: raw.id,
153
+ sink: raw.sink,
154
+ etypes: raw.etypes ?? null,
155
+ actions: raw.actions,
156
+ status: raw.status,
157
+ disabledReason: raw.disabled_reason ?? null,
158
+ createdAt: new Date(raw.created_at),
159
+ updatedAt: new Date(raw.updated_at),
160
+ };
161
+ }
162
+ function toWebhookAttempt(raw) {
163
+ return {
164
+ attemptAt: parseDate(raw['attempt-at']),
165
+ durationMs: raw['duration-ms'] ?? null,
166
+ success: raw['success?'] ?? null,
167
+ statusCode: raw['status-code'] ?? null,
168
+ responseText: raw['response-text'] ?? null,
169
+ errorType: raw['error-type'] ?? null,
170
+ errorMessage: raw['error-message'] ?? null,
171
+ };
172
+ }
173
+ function toWebhookEventInfo(raw) {
174
+ return {
175
+ isn: raw.isn,
176
+ status: raw.status,
177
+ attempts: raw.attempts ? raw.attempts.map(toWebhookAttempt) : null,
178
+ nextAttemptAfter: parseDate(raw.next_attempt_after),
179
+ createdAt: new Date(raw.created_at),
180
+ updatedAt: new Date(raw.updated_at),
181
+ };
182
+ }
183
+ class WebhooksManager {
184
+ #appId;
185
+ #apiURI;
186
+ #token;
187
+ #withAuth;
188
+ #jsonFetch;
189
+ constructor(opts) {
190
+ this.#appId = opts.appId;
191
+ this.#apiURI = opts.apiURI;
192
+ this.#token = opts.token;
193
+ this.#withAuth = opts.withAuth;
194
+ this.#jsonFetch = opts.jsonFetch;
195
+ }
196
+ #authedFetch(path, opts) {
197
+ if (!this.#appId) {
198
+ throw new core_1.InstantError('appId is required to manage webhooks. Pass it to the Webhooks constructor.');
199
+ }
200
+ const run = (token) => {
201
+ const init = {
202
+ method: opts?.method,
203
+ headers: {
204
+ authorization: `Bearer ${token}`,
205
+ 'content-type': 'application/json',
206
+ },
207
+ };
208
+ if (opts?.body !== undefined) {
209
+ init.body = JSON.stringify(opts.body);
210
+ }
211
+ return this.#jsonFetch(`${this.#apiURI}${path}`, init);
212
+ };
213
+ if (this.#withAuth) {
214
+ return this.#withAuth(run);
215
+ }
216
+ if (!this.#token) {
217
+ throw new core_1.InstantError('A token is required to manage webhooks. Pass `adminToken` or `token` to the Webhooks constructor.');
218
+ }
219
+ return run(this.#token);
220
+ }
221
+ /**
222
+ * Returns every webhook configured on the app, newest first. Includes both
223
+ * active and disabled webhooks.
224
+ */
225
+ async list() {
226
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks`);
227
+ return (res.webhooks || []).map(toWebhookInfo);
228
+ }
229
+ /**
230
+ * Creates a new webhook. The webhook is created in the `active` state and
231
+ * starts receiving matching events immediately.
232
+ *
233
+ * The server rejects the request if `url` is not an HTTPS URL pointing at a
234
+ * public host, if `etypes` doesn't reference any entity in the app's
235
+ * schema, if `actions` is empty, or if the app has hit its webhook limit.
236
+ *
237
+ * @example
238
+ * const webhook = await db.webhooks.manager.create({
239
+ * url: 'https://example.com/instant',
240
+ * etypes: ['posts', 'comments'],
241
+ * actions: ['create', 'update'],
242
+ * });
243
+ */
244
+ async create(params) {
245
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks`, {
246
+ method: 'POST',
247
+ body: params,
248
+ });
249
+ return toWebhookInfo(res.webhook);
250
+ }
251
+ /**
252
+ * Updates a webhook's `url`, `etypes`, and/or `actions`. Pass only the
253
+ * fields you want to change; omitted fields keep their current value.
254
+ *
255
+ * Does not affect the webhook's status — use {@link enable} or
256
+ * {@link disable} for that.
257
+ */
258
+ async update(webhookId, params) {
259
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}`, { method: 'POST', body: params });
260
+ return toWebhookInfo(res.webhook);
261
+ }
262
+ /**
263
+ * Deletes a webhook. No further events will be queued for it. Returns the
264
+ * webhook as it looked just before deletion.
265
+ */
266
+ async delete(webhookId) {
267
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}`, { method: 'DELETE' });
268
+ return toWebhookInfo(res.webhook);
269
+ }
270
+ /**
271
+ * Re-enables a disabled webhook. Clears `disabledReason` and resumes
272
+ * delivery for new events. Has no effect if the webhook is already active.
273
+ *
274
+ * Events that occurred while the webhook was disabled are not retroactively
275
+ * delivered.
276
+ */
277
+ async enable(webhookId) {
278
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}/enable`, { method: 'POST', body: {} });
279
+ return toWebhookInfo(res.webhook);
280
+ }
281
+ /**
282
+ * Disables a webhook. No new events will be queued until it is re-enabled
283
+ * via {@link enable}. In-flight events already being processed will still
284
+ * complete.
285
+ *
286
+ * @param opts.reason Optional human-readable note stored on the webhook
287
+ * and surfaced in the dashboard.
288
+ */
289
+ async disable(webhookId, opts) {
290
+ const body = opts?.reason ? { reason: opts.reason } : {};
291
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}/disable`, { method: 'POST', body });
292
+ return toWebhookInfo(res.webhook);
293
+ }
294
+ /**
295
+ * Returns a page of events for a webhook, newest first.
296
+ *
297
+ * Events are retained for ~60 days. To paginate, pass the previous page's
298
+ * `pageInfo.endCursor` as `opts.after`; stop when `pageInfo.hasNextPage`
299
+ * is `false`.
300
+ */
301
+ async listEvents(webhookId, opts) {
302
+ const qs = opts?.after ? `?after=${encodeURIComponent(opts.after)}` : '';
303
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}/events${qs}`);
304
+ return {
305
+ events: (res.events || []).map(toWebhookEventInfo),
306
+ pageInfo: {
307
+ startCursor: res.pageInfo?.startCursor ?? null,
308
+ endCursor: res.pageInfo?.endCursor ?? null,
309
+ hasNextPage: !!res.pageInfo?.hasNextPage,
310
+ },
311
+ };
312
+ }
313
+ /**
314
+ * Fetches a single webhook event by its `isn`.
315
+ */
316
+ async getEvent(webhookId, isn) {
317
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}/events/${isn}`);
318
+ return toWebhookEventInfo(res.event);
319
+ }
320
+ /** Returns the full payload for an event. */
321
+ async getPayload(webhookId, isn) {
322
+ return this.#authedFetch(`/webhooks/payload/${this.#appId}/${webhookId}/${isn}`);
323
+ }
324
+ /**
325
+ * Re-queues an event for delivery, regardless of its current status. Use
326
+ * this to retry a `failed` event or force a redelivery of a `success` one.
327
+ *
328
+ * The server rate-limits resends; if the event was queued or resent very
329
+ * recently the call will fail with a validation error asking you to try
330
+ * again in about a minute.
331
+ */
332
+ async resendEvent(webhookId, isn) {
333
+ const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks/${webhookId}/events/${isn}`, { method: 'POST', body: {} });
334
+ return toWebhookEventInfo(res.event);
335
+ }
336
+ }
337
+ exports.WebhooksManager = WebhooksManager;
338
+ /**
339
+ * Verify incoming webhook requests from Instant, dispatch their records to
340
+ * typed handlers, and manage webhook subscriptions (via {@link manager}).
341
+ *
342
+ * Usually accessed as `db.webhooks` on the admin or platform SDK rather than
343
+ * constructed directly.
344
+ */
345
+ class Webhooks {
346
+ /** App this instance is bound to. */
347
+ appId;
348
+ /** Schema used to type webhook payloads and handler records. */
349
+ schema;
350
+ #token;
351
+ /** Base URL for the Instant API. */
352
+ apiURI;
353
+ #jsonFetch;
354
+ /** Manage webhook subscriptions and inspect delivery events. */
355
+ manager;
356
+ /**
357
+ * Schema-bound helpers for building typed handler maps.
358
+ *
359
+ * - `typedHandlers(etype, action, handler)` builds a single typed entry.
360
+ * Pass `'$default'` for `etype` to register a catch-all handler.
361
+ * - `combineHandlers(...entries)` merges entries into a
362
+ * {@link WebhookHandlers} object suitable for {@link processPayload} and
363
+ * {@link processRequest}.
364
+ *
365
+ * @example
366
+ * const { typedHandlers, combineHandlers } = Webhooks.helpers<typeof schema>();
367
+ * const handlers = combineHandlers(
368
+ * typedHandlers('posts', 'create', (record) => { ... }),
369
+ * typedHandlers('comments', '$default', (record) => { ... }),
370
+ * typedHandlers('$default', (record) => { ... }),
371
+ * );
372
+ */
373
+ static helpers() {
374
+ function typedHandlers(...args) {
375
+ if (args.length === 2) {
376
+ return { $default: args[1] };
377
+ }
378
+ const [etype, action, handler] = args;
379
+ return { [etype]: { [action]: handler } };
380
+ }
381
+ function combineHandlers(...entries) {
382
+ const result = {};
383
+ for (const entry of entries) {
384
+ for (const key of Object.keys(entry)) {
385
+ if (key === '$default') {
386
+ result.$default = entry.$default;
387
+ }
388
+ else {
389
+ result[key] = { ...result[key], ...entry[key] };
390
+ }
391
+ }
392
+ }
393
+ return result;
394
+ }
395
+ return {
396
+ typedHandlers: typedHandlers,
397
+ combineHandlers: combineHandlers,
398
+ };
399
+ }
400
+ constructor(config, jsonFetch) {
401
+ this.appId = config.appId;
402
+ this.schema = config.schema;
403
+ this.#token = config.adminToken || config.token;
404
+ this.apiURI = config.apiURI || 'https://api.instantdb.com';
405
+ this.#jsonFetch = jsonFetch || defaultJsonFetch;
406
+ this.manager = new WebhooksManager({
407
+ appId: this.appId,
408
+ apiURI: this.apiURI,
409
+ token: this.#token,
410
+ withAuth: config.withAuth,
411
+ jsonFetch: this.#jsonFetch,
412
+ });
413
+ }
414
+ /** Fetches Instant's JWK set for verifying webhook signatures. */
415
+ async fetchJwks() {
416
+ const resp = await this.#jsonFetch(`${this.apiURI}/.well-known/webhooks/jwks.json`);
417
+ return resp;
418
+ }
419
+ /**
420
+ * Resolves a `kid` to an imported {@link CryptoKey}, hitting a
421
+ * process-wide cache on repeat calls. Falls back to {@link fetchJwks} if
422
+ * the key isn't already known.
423
+ */
424
+ async keyOfKid(kid) {
425
+ const cached = keyCache[kid];
426
+ if (cached) {
427
+ return cached;
428
+ }
429
+ const jwk = knownKeys[this.apiURI]?.keys.find((k) => k.kid === kid) ||
430
+ (await this.fetchJwks())?.keys?.find((k) => k.kid === kid);
431
+ if (!jwk) {
432
+ throw new core_1.InstantError('Could not find matching signing key', { kid });
433
+ }
434
+ const res = await importKey(jwk);
435
+ keyCache[kid] = res;
436
+ return res;
437
+ }
438
+ /**
439
+ * Verifies an `Instant-Signature` header against a body and returns the
440
+ * parsed {@link WebhookBody} (containing the `payloadUrl` and a JWT
441
+ * `token` for fetching the records).
442
+ *
443
+ * Throws if the signature doesn't validate, the signature is older than
444
+ * `opts.tolerance` (default 300 seconds), or the body doesn't decode to
445
+ * the expected shape.
446
+ *
447
+ * @param body Either the raw body string, or a function returning it.
448
+ * Use a function to defer reading the body until after the
449
+ * header has been parsed.
450
+ */
451
+ async validate(signatureHeader, body, opts) {
452
+ const receivedAt = opts?.receivedAt || new Date();
453
+ const { t, kid, v1 } = parseSignatureHeader(signatureHeader);
454
+ const tolerance = opts?.tolerance || defaultTolerance;
455
+ validateT(receivedAt, t, tolerance);
456
+ const { alg, key } = await this.keyOfKid(kid);
457
+ const bodyText = typeof body === 'function' ? await body() : body;
458
+ const message = new TextEncoder().encode(`${t}.${bodyText}`);
459
+ const verified = await verify(alg, key, hexToUint8Array(v1), message);
460
+ if (!verified) {
461
+ throw new core_1.InstantError('Instant Signature did not validate', {
462
+ header: signatureHeader,
463
+ });
464
+ }
465
+ const res = JSON.parse(bodyText);
466
+ if (typeof res !== 'object' ||
467
+ typeof res.payloadUrl !== 'string' ||
468
+ typeof res.token !== 'string') {
469
+ throw new core_1.InstantError('Invalid webhook body, expected an object with payloadUrl and token fields', { body: res });
470
+ }
471
+ return res;
472
+ }
473
+ /**
474
+ * Pulls the `Instant-Signature` header and body from a `Request` and
475
+ * delegates to {@link validate}. Throws if the header is missing.
476
+ */
477
+ async validateRequest(req, opts) {
478
+ const signatureHeader = req.headers.get('instant-signature');
479
+ if (!signatureHeader) {
480
+ throw new core_1.InstantError('Request is missing Instant-Signature header');
481
+ }
482
+ return this.validate(signatureHeader, () => req.text(), opts);
483
+ }
484
+ /**
485
+ * Fetches the records and `idempotencyKey` for a validated
486
+ * {@link WebhookBody}, authenticating with the JWT `token` it carries.
487
+ */
488
+ fetchPayloads({ payloadUrl, token, }) {
489
+ return this.#jsonFetch(payloadUrl, {
490
+ headers: { Authorization: `Bearer ${token}`, accept: 'application/json' },
491
+ });
492
+ }
493
+ /**
494
+ * Dispatches each record in `payload` to its matching handler in
495
+ * `handlers`. Resolution order per record: exact `etype` + `action` →
496
+ * `etype`'s `$default` → top-level `$default`. Records with no matching
497
+ * handler are skipped.
498
+ *
499
+ * Handlers run concurrently. The returned promise resolves once every
500
+ * handler has settled (success or failure); rejections in individual
501
+ * handlers do not bubble up.
502
+ */
503
+ async processPayload(handlers, payload) {
504
+ const results = [];
505
+ for (const record of payload.data) {
506
+ const { etype, action } = record;
507
+ const handler = handlers?.[etype]?.[action] ||
508
+ handlers?.[etype]?.$default ||
509
+ handlers?.$default;
510
+ if (handler) {
511
+ // We need the as any here because typescript
512
+ // has trouble correlating the handler to the
513
+ // record etype and action
514
+ results.push(handler(record));
515
+ }
516
+ }
517
+ await Promise.allSettled(results);
518
+ }
519
+ /**
520
+ * The one-liner for handling webhooks. Hand it your handlers and the
521
+ * incoming `Request` — it verifies the signature, fetches the records, and
522
+ * dispatches each one to your code.
523
+ *
524
+ * Async handlers are executed in parallel, the return promise will resolve once
525
+ * all handlers complete and will reject if any of the handlers fails.
526
+ *
527
+ * @example
528
+ * const { typedHandlers, combineHandlers } = Webhooks.helpers<typeof schema>();
529
+ *
530
+ * const handlers = combineHandlers(
531
+ * typedHandlers('posts', 'create', async (record) => {
532
+ * await sendNewPostEmail(record.after);
533
+ * }),
534
+ * typedHandlers('$default', (record) => {
535
+ * console.log('webhook event', record);
536
+ * }),
537
+ * );
538
+ *
539
+ * export async function POST(req: Request) {
540
+ * await db.webhooks.processRequest(handlers, req);
541
+ * return new Response('ok');
542
+ * }
543
+ */
544
+ async processRequest(handlers, req, opts) {
545
+ const body = await this.validateRequest(req, opts);
546
+ const payload = await this.fetchPayloads(body);
547
+ await this.processPayload(handlers, payload);
548
+ }
549
+ }
550
+ exports.Webhooks = Webhooks;
551
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,0CAMyB;AAqUzB,MAAM,SAAS,GAAG;IAChB,2BAA2B,EAAE;QAC3B,IAAI,EAAE;YACJ;gBACE,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,SAAS;gBACd,GAAG,EAAE,OAAO;gBACZ,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,YAAY;gBACjB,CAAC,EAAE,6CAA6C;aACjD;SACF;KACF;IACD,uBAAuB,EAAE;QACvB,IAAI,EAAE;YACJ;gBACE,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,SAAS;gBACd,GAAG,EAAE,OAAO;gBACZ,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,WAAW;gBAChB,CAAC,EAAE,6CAA6C;aACjD;SACF;KACF;CACF,CAAC;AAOF,SAAS,iBAAiB,CAAC,GAAQ;IACjC,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC/C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC7B,CAAC;IAED,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,qBAAqB;IACtE,CAAC;IAED,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;QACtB,yEAAyE;QACzE,MAAM,OAAO,GAA2B;YACtC,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,SAAS;SACjB,CAAC;QACF,OAAO;YACL,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,SAAS;SACpC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,KAAK,CACb,sCAAsC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,EAAE,CAChE,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,GAAQ;IAER,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,MAAM,CACb,GAAoB,EACpB,GAAc,EACd,SAAuB,EACvB,OAAqB;IAErB,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CACzB,GAA0B,EAC1B,GAAG,EACH,SAAS,EACT,OAAO,CACR,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iCAAiC;AACjC,mCAAmC;AACnC,4CAA4C;AAC5C,4BAA4B;AAE5B,SAAS,oBAAoB,CAAC,CAAS;IAKrC,IAAI,CAAqB,EAAE,GAAuB,EAAE,EAAsB,CAAC;IAC3E,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,QAAQ,CAAC,EAAE,CAAC;YACV,KAAK,GAAG,CAAC,CAAC,CAAC;gBACT,CAAC,GAAG,CAAC,CAAC;gBACN,MAAM;YACR,CAAC;YACD,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,GAAG,GAAG,CAAC,CAAC;gBACR,MAAM;YACR,CAAC;YACD,KAAK,IAAI,CAAC,CAAC,CAAC;gBACV,EAAE,GAAG,CAAC,CAAC;gBACP,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAY,CAAC,mCAAmC,EAAE;YAC1D,MAAM,EAAE,CAAC;YACT,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,SAAS,CAAC,UAAgB,EAAE,CAAS,EAAE,SAAiB;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtE,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,mBAAY,CAAC,8BAA8B,EAAE;YACrD,SAAS;YACT,UAAU;YACV,CAAC;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,6EAA6E;AAC7E,qEAAqE;AACrE,MAAM,QAAQ,GAA6D,EAAE,CAAC;AAC9E,MAAM,gBAAgB,GAAG,GAAG,CAAC,CAAC,YAAY;AAE1C,KAAK,UAAU,UAAU,CACvB,QAAuC,EACvC,GAAa;IAEb,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,QAAQ,CAAC,IAAI,sBAAe,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3E,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,QAAQ,CACb,IAAI,sBAAe,CAAC;YAClB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;SACzC,CAAC,CACH,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAAc,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IACxD,MAAM,OAAO,GAAG;QACd,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;QACxB,sBAAsB,EAAE,cAAW;KACpC,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACrD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IACD,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,SAAS,SAAS,CAAC,CAA4B;IAC7C,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,SAAS,aAAa,CAAC,GAAQ;IAC7B,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI;QAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,cAAc,EAAE,GAAG,CAAC,eAAe,IAAI,IAAI;QAC3C,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;QACnC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAQ;IAChC,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACvC,UAAU,EAAE,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI;QACtC,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI;QAChC,UAAU,EAAE,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI;QACtC,YAAY,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI;QAC1C,SAAS,EAAE,GAAG,CAAC,YAAY,CAAC,IAAI,IAAI;QACpC,YAAY,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI;KAC3C,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAQ;IAClC,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI;QAClE,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC;QACnD,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;QACnC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,MAAa,eAAe;IAC1B,MAAM,CAA4B;IAClC,OAAO,CAAS;IAChB,MAAM,CAA4B;IAClC,SAAS,CAAuB;IAChC,UAAU,CAAY;IAEtB,YAAY,IAMX;QACC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;IACnC,CAAC;IAED,YAAY,CACV,IAAY,EACZ,IAA0C;QAE1C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,mBAAY,CACpB,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAgB;gBACxB,MAAM,EAAE,IAAI,EAAE,MAAM;gBACpB,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,KAAK,EAAE;oBAChC,cAAc,EAAE,kBAAkB;iBACnC;aACF,CAAC;YACF,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC;QACF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,mBAAY,CACpB,mGAAmG,CACpG,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,IAAI,CAAC,MAAM,WAAW,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,MAAM,CAAC,MAAmC;QAC9C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,IAAI,CAAC,MAAM,WAAW,EAAE;YACxE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,MAAmC;QAEnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,EAAE,EACjD,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CACjC,CAAC;QACF,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,EAAE,EACjD,EAAE,MAAM,EAAE,QAAQ,EAAE,CACrB,CAAC;QACF,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,SAAS,EACxD,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAC7B,CAAC;QACF,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,CACX,SAAiB,EACjB,IAAgE;QAEhE,MAAM,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,UAAU,EACzD,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CACzB,CAAC;QACF,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,IAA+D;QAE/D,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,UAAU,EAAE,EAAE,CAC9D,CAAC;QACF,OAAO;YACL,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;YAClD,QAAQ,EAAE;gBACR,WAAW,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,IAAI,IAAI;gBAC9C,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,SAAS,IAAI,IAAI;gBAC1C,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW;aACzC;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,GAAW;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,WAAW,GAAG,EAAE,CAChE,CAAC;QACF,OAAO,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,6CAA6C;IAC7C,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,GAAW;QAEX,OAAO,IAAI,CAAC,YAAY,CACtB,qBAAqB,IAAI,CAAC,MAAM,IAAI,SAAS,IAAI,GAAG,EAAE,CACvD,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,GAAW;QAC9C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CACjC,cAAc,IAAI,CAAC,MAAM,aAAa,SAAS,WAAW,GAAG,EAAE,EAC/D,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAC7B,CAAC;QACF,OAAO,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;CACF;AAnND,0CAmNC;AAED;;;;;;GAMG;AACH,MAAa,QAAQ;IACnB,qCAAqC;IACrC,KAAK,CAA4B;IACjC,gEAAgE;IAChE,MAAM,CAA4B;IAClC,MAAM,CAA4B;IAClC,oCAAoC;IACpC,MAAM,CAAS;IACf,UAAU,CAAY;IACtB,gEAAgE;IAChE,OAAO,CAA0B;IAEjC;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,OAAO;QAGZ,SAAS,aAAa,CAAC,GAAG,IAAW;YACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/B,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YACtC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC5C,CAAC;QACD,SAAS,eAAe,CAAC,GAAG,OAAc;YACxC,MAAM,MAAM,GAAQ,EAAE,CAAC;YACvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBACrC,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;wBACvB,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;oBACnC,CAAC;yBAAM,CAAC;wBACN,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClD,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO;YACL,aAAa,EAAE,aAAoB;YACnC,eAAe,EAAE,eAAsB;SACxC,CAAC;IACJ,CAAC;IAED,YAAY,MAAsB,EAAE,SAAqB;QACvD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAE5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,2BAA2B,CAAC;QAC3D,IAAI,CAAC,UAAU,GAAG,SAAS,IAAI,gBAAgB,CAAC;QAChD,IAAI,CAAC,OAAO,GAAG,IAAI,eAAe,CAAS;YACzC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,IAAI,CAAC,UAAU;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAChC,GAAG,IAAI,CAAC,MAAM,iCAAiC,CAChD,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CACZ,GAAW;QAEX,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,GAAG,GACP,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;YAC5D,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAElE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,mBAAY,CAAC,qCAAqC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACpB,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,QAAQ,CACZ,eAAuB,EACvB,IAAsC,EACtC,IAMa;QAEb,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC;QAClD,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,gBAAgB,CAAC;QACtD,SAAS,CAAC,UAAU,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAEpC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAElE,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QAE7D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,eAAe,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;QAEtE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,mBAAY,CAAC,oCAAoC,EAAE;gBAC3D,MAAM,EAAE,eAAe;aACxB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjC,IACE,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;YAClC,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAC7B,CAAC;YACD,MAAM,IAAI,mBAAY,CACpB,2EAA2E,EAC3E,EAAE,IAAI,EAAE,GAAG,EAAE,CACd,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CACnB,GAAY,EACZ,IAMa;QAEb,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAC7D,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,mBAAY,CAAC,6CAA6C,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,EACZ,UAAU,EACV,KAAK,GACO;QACZ,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE;YACjC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SAC1E,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,cAAc,CAClB,QAAiC,EACjC,OAA+B;QAE/B,MAAM,OAAO,GAAU,EAAE,CAAC;QAC1B,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;YACjC,MAAM,OAAO,GACX,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC;gBAC3B,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC;YACrB,IAAI,OAAO,EAAE,CAAC;gBACZ,6CAA6C;gBAC7C,6CAA6C;gBAC7C,0BAA0B;gBAC1B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAa,CAAC,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,CAAC,cAAc,CAClB,QAAiC,EACjC,GAAY,EACZ,IAMa;QAEb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;CACF;AA7QD,4BA6QC","sourcesContent":["import {\n InstantAPIError,\n InstantError,\n InstantSchemaDef,\n ResolveAttrs,\n version as coreVersion,\n} from '@instantdb/core';\n\ntype Config<Schema extends InstantSchemaDef<any, any, any>> = {\n appId?: string | null | undefined;\n adminToken?: string | null | undefined;\n token?: string | null | undefined;\n apiURI?: string | null | undefined;\n schema?: Schema | null | undefined;\n /**\n * Optional hook used by {@link WebhooksManager} to obtain the bearer token\n * for each management request. Lets callers (e.g. the platform SDK) wrap\n * the operation in token-refresh / retry logic.\n *\n * If omitted, the manager uses the static `adminToken`/`token` from this\n * config.\n */\n withAuth?: WithAuth;\n};\n\ntype JsonFetch = (\n input: RequestInfo,\n init?: RequestInit | undefined,\n) => Promise<any>;\n\n/**\n * Runs a webhook management operation that needs a bearer token. The runner\n * is responsible for supplying the token, and may retry the operation with a\n * fresh token if the first attempt fails with an auth error.\n */\nexport type WithAuth = <T>(\n operation: (token: string) => Promise<T>,\n) => Promise<T>;\n\nexport type WebhookBody = {\n payloadUrl: string;\n token: string;\n};\n\nexport type WebhookEntity<\n Schema extends InstantSchemaDef<any, any, any>,\n EtypeName extends keyof Schema['entities'],\n> = { id: string } & ResolveAttrs<Schema['entities'], EtypeName, false>;\n\nexport type WebhookPayloadRecord<\n Schema extends InstantSchemaDef<any, any, any>,\n> = {\n [EtypeName in keyof Schema['entities']]:\n | {\n etype: EtypeName;\n id: string;\n action: 'create';\n before: null;\n after: WebhookEntity<Schema, EtypeName>;\n idempotencyKey: string;\n }\n | {\n etype: EtypeName;\n id: string;\n action: 'update';\n before: WebhookEntity<Schema, EtypeName>;\n after: WebhookEntity<Schema, EtypeName>;\n idempotencyKey: string;\n }\n | {\n etype: EtypeName;\n id: string;\n action: 'delete';\n before: WebhookEntity<Schema, EtypeName>;\n after: null;\n idempotencyKey: string;\n };\n}[keyof Schema['entities']];\n\nexport type WebhookPayload<Schema extends InstantSchemaDef<any, any, any>> = {\n data: WebhookPayloadRecord<Schema>[];\n idempotencyKey: string;\n};\n\nexport type WebhookAction = 'create' | 'update' | 'delete';\n\n/**\n * Whether Instant will currently deliver events for a webhook.\n * `disabled` webhooks remain configured but no new events are queued.\n */\nexport type WebhookStatus = 'active' | 'disabled';\n\n/**\n * Stage in the delivery lifecycle of a single webhook event.\n *\n * - `pending`: queued, not yet picked up for delivery\n * - `processing`: a sender is actively attempting delivery\n * - `success`: the receiver acknowledged with a 2xx response\n * - `error`: an attempt failed; another retry is scheduled\n * - `failed`: all retries exhausted; will not be retried automatically\n * (use {@link WebhooksManager.resendEvent} to retry manually)\n */\nexport type WebhookEventStatus =\n | 'pending'\n | 'processing'\n | 'success'\n | 'error'\n | 'failed';\n\nexport type WebhookInfo = {\n /** Unique identifier for the webhook. */\n id: string;\n /** Where Instant POSTs event payloads to. */\n sink: {\n /** HTTPS endpoint that Instant POSTs to. */\n url: string;\n };\n /**\n * The entity types (namespaces) this webhook listens to. `null` if every\n * etype the webhook referenced has since been removed from the schema.\n */\n etypes: string[] | null;\n /** Which write actions trigger delivery. */\n actions: WebhookAction[];\n /** Whether the webhook is currently delivering events. */\n status: WebhookStatus;\n /**\n * Human-readable reason the webhook is disabled. Set automatically when\n * Instant disables the webhook (e.g. after repeated delivery failures) or\n * supplied by the caller via {@link WebhooksManager.disable}. `null` when\n * `status` is `'active'`.\n */\n disabledReason: string | null;\n /** When the webhook was created. */\n createdAt: Date;\n /** When the webhook's config was last changed. */\n updatedAt: Date;\n};\n\n/**\n * Record of a single HTTP delivery attempt for a webhook event.\n * Stored in attempt order (oldest first) on the event's `attempts` array.\n */\nexport type WebhookAttempt = {\n /** When the attempt started. */\n attemptAt: Date | null;\n /** Time from request start to response received (or error), in milliseconds. */\n durationMs: number | null;\n /** `true` if the receiver returned a 2xx response. */\n success: boolean | null;\n /** HTTP status code returned by the receiver, if a response was received. */\n statusCode: number | null;\n /**\n * First 256 bytes of the response body, for debugging. `null` if no\n * response was received (e.g. on a network error).\n */\n responseText: string | null;\n /**\n * Short tag classifying a delivery failure. One of `timeout`, `dns`,\n * `connect`, `tls`, `protocol`, `network`, or `unknown`. `null` on success.\n */\n errorType: string | null;\n /** Free-form description of the failure. `null` on success. */\n errorMessage: string | null;\n};\n\nexport type WebhookEventInfo = {\n /**\n * Instant Sequence Number — a stable, totally ordered identifier for the\n * event. Doubles as the pagination cursor and is used to address the event\n * in {@link WebhooksManager.getEvent} and {@link WebhooksManager.resendEvent}.\n */\n isn: string;\n /** Current stage in the delivery lifecycle. */\n status: WebhookEventStatus;\n /**\n * Per-attempt records, in attempt order (oldest first). `null` if the\n * event has not been attempted yet.\n */\n attempts: WebhookAttempt[] | null;\n /**\n * The next retry will not happen before this time. `null` once the event\n * reaches a terminal status (`success` or `failed`).\n */\n nextAttemptAfter: Date | null;\n /** When the event was queued. */\n createdAt: Date;\n /** When the event last transitioned status. */\n updatedAt: Date;\n};\n\nexport type WebhookEventsPage = {\n /** The events on this page, newest first. */\n events: WebhookEventInfo[];\n pageInfo: {\n /** Cursor pointing to the first event on this page. */\n startCursor: string | null;\n /**\n * Cursor pointing to the last event on this page. Pass as\n * {@link WebhooksManager.listEvents}'s `after` option to fetch the next page.\n */\n endCursor: string | null;\n /** Whether more events are available after `endCursor`. */\n hasNextPage: boolean;\n };\n};\n\nexport type CreateWebhookParams<\n Schema extends InstantSchemaDef<any, any, any>,\n> = {\n /**\n * HTTPS endpoint Instant will POST events to. Must use the `https` scheme\n * and resolve to a public host.\n */\n url: string;\n /**\n * Entity types (namespaces) the webhook will listen to. Must reference at\n * least one entity in the app's schema.\n */\n etypes: (keyof Schema['entities'] & string)[];\n /** Write actions that should trigger delivery. Must contain at least one. */\n actions: WebhookAction[];\n};\n\nexport type UpdateWebhookParams<\n Schema extends InstantSchemaDef<any, any, any>,\n> = {\n /** New delivery URL. Omit to leave unchanged. */\n url?: string;\n /** New set of entity types. Omit to leave unchanged. */\n etypes?: (keyof Schema['entities'] & string)[];\n /** New set of actions. Omit to leave unchanged. */\n actions?: WebhookAction[];\n};\n\nexport type WebhookPayloadRecordFor<\n Schema extends InstantSchemaDef<any, any, any>,\n EtypeName extends keyof Schema['entities'],\n Action extends WebhookAction,\n> = Extract<WebhookPayloadRecord<Schema>, { etype: EtypeName; action: Action }>;\n\nexport type WebhookHandlerFn<\n Schema extends InstantSchemaDef<any, any, any>,\n EtypeName extends keyof Schema['entities'],\n Action extends WebhookAction,\n Result = any,\n> = (\n record: WebhookPayloadRecordFor<Schema, EtypeName, Action>,\n) => Result | Promise<Result>;\n\nexport type DefaultKey = '$default';\n\nexport type ResolveHandlerAction<Action> = Action extends DefaultKey\n ? WebhookAction\n : Action extends WebhookAction\n ? Action\n : never;\n\nexport type WebhookHandlers<Schema extends InstantSchemaDef<any, any, any>> = {\n [EtypeName in keyof Schema['entities']]?: {\n [Action in WebhookAction | DefaultKey]?: WebhookHandlerFn<\n Schema,\n EtypeName,\n ResolveHandlerAction<Action>,\n any\n >;\n };\n} & {\n $default?: WebhookHandlerFn<\n Schema,\n keyof Schema['entities'],\n WebhookAction,\n any\n >;\n};\n\nexport type TypedHandlerEntry<\n Schema extends InstantSchemaDef<any, any, any>,\n EtypeName extends keyof Schema['entities'],\n Action extends WebhookAction | DefaultKey,\n> = {\n [E in EtypeName]: {\n [A in Action]: WebhookHandlerFn<\n Schema,\n EtypeName,\n ResolveHandlerAction<Action>,\n any\n >;\n };\n};\n\nexport type TypedDefaultEntry<Schema extends InstantSchemaDef<any, any, any>> =\n {\n $default: WebhookHandlerFn<\n Schema,\n keyof Schema['entities'],\n WebhookAction,\n any\n >;\n };\n\nexport type WebhookHelpers<Schema extends InstantSchemaDef<any, any, any>> = {\n typedHandlers: {\n (\n etype: DefaultKey,\n handler: WebhookHandlerFn<\n Schema,\n keyof Schema['entities'],\n WebhookAction,\n any\n >,\n ): TypedDefaultEntry<Schema>;\n <\n EtypeName extends keyof Schema['entities'],\n Action extends WebhookAction | DefaultKey,\n >(\n etype: EtypeName,\n action: Action,\n handler: WebhookHandlerFn<\n Schema,\n EtypeName,\n ResolveHandlerAction<Action>,\n any\n >,\n ): TypedHandlerEntry<Schema, EtypeName, Action>;\n };\n combineHandlers: (\n ...entries: Array<WebhookHandlers<Schema>>\n ) => WebhookHandlers<Schema>;\n};\n\nconst knownKeys = {\n 'https://api.instantdb.com': {\n keys: [\n {\n kty: 'OKP',\n crv: 'Ed25519',\n alg: 'EdDSA',\n use: 'sig',\n kid: '1034696293',\n x: 'N-C41432STKAKkXAWmeIOXMnZcGRR1b9u1L3bTVqI_o',\n },\n ],\n },\n 'http://localhost:8888': {\n keys: [\n {\n kty: 'OKP',\n crv: 'Ed25519',\n alg: 'EdDSA',\n use: 'sig',\n kid: '503090235',\n x: 'qrSkwDaMITRMF9nOgpueqxgaAiuFmJperYE3mkyl8Ow',\n },\n ],\n },\n};\n\ntype ImportAlgorithm =\n | AlgorithmIdentifier\n | RsaHashedImportParams\n | EcKeyImportParams;\n\nfunction inferWebCryptoAlg(jwk: any): ImportAlgorithm {\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') {\n return { name: 'Ed25519' };\n }\n\n if (jwk.kty === 'EC') {\n return { name: 'ECDSA', namedCurve: jwk.crv }; // e.g., P-256, P-384\n }\n\n if (jwk.kty === 'RSA') {\n // RSA keys often specify the exact hash in the 'alg' field (e.g., RS256)\n const hashMap: Record<string, string> = {\n RS256: 'SHA-256',\n RS384: 'SHA-384',\n RS512: 'SHA-512',\n };\n return {\n name: 'RSASSA-PKCS1-v1_5',\n hash: hashMap[jwk.alg] || 'SHA-256',\n };\n }\n\n throw new Error(\n `Unsupported JWK configuration: kty=${jwk.kty}, crv=${jwk.crv}`,\n );\n}\n\nasync function importKey(\n jwk: any,\n): Promise<{ alg: ImportAlgorithm; key: CryptoKey }> {\n const alg = inferWebCryptoAlg(jwk);\n const key = await crypto.subtle.importKey('jwk', jwk, alg, false, ['verify']);\n return { alg, key };\n}\n\nfunction verify(\n alg: ImportAlgorithm,\n key: CryptoKey,\n signature: BufferSource,\n message: BufferSource,\n): Promise<boolean> {\n return crypto.subtle.verify(\n alg as AlgorithmIdentifier,\n key,\n signature,\n message,\n );\n}\n\nfunction hexToUint8Array(hexString: string): Uint8Array<ArrayBuffer> {\n const bytes = new Uint8Array(Math.ceil(hexString.length / 2));\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(hexString.substring(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n// XXX: Test in nextjs app router\n// XXX: Test in nextjs pages router\n// XXX: Test with express, deno, koa, nestjs\n// XXX: Test with cloudflare\n\nfunction parseSignatureHeader(h: string): {\n t: string;\n kid: string;\n v1: string;\n} {\n let t: string | undefined, kid: string | undefined, v1: string | undefined;\n for (const part of h.split(',')) {\n const [k, v] = part.split('=');\n switch (k) {\n case 't': {\n t = v;\n break;\n }\n case 'kid': {\n kid = v;\n break;\n }\n case 'v1': {\n v1 = v;\n break;\n }\n }\n }\n\n const missingKeys: string[] = [];\n if (!t) {\n missingKeys.push('t');\n }\n if (!kid) {\n missingKeys.push('kid');\n }\n if (!v1) {\n missingKeys.push('v1');\n }\n\n if (missingKeys.length || !t || !kid || !v1) {\n throw new InstantError('Invalid Instant-Signature header.', {\n header: h,\n missingKeys,\n });\n }\n\n return { t, kid, v1 };\n}\n\nfunction validateT(receivedAt: Date, t: string, tolerance: number): void {\n const age = Math.floor(receivedAt.getTime() / 1000) - parseInt(t, 10);\n if (age > tolerance) {\n throw new InstantError('Webhook signature is too old', {\n tolerance,\n receivedAt,\n t,\n });\n }\n}\n\n// We make this a global cache so that it will survive across\n// restarts. It will only store valid signing keys from instant, and we don't\n// create many of them, so the memory usage will be only 1 or 2 keys.\nconst keyCache: Record<string, { alg: ImportAlgorithm; key: CryptoKey }> = {};\nconst defaultTolerance = 300; // 5 minutes\n\nasync function jsonReject(\n rejectFn: (err: InstantAPIError) => any,\n res: Response,\n) {\n const body = await res.text();\n try {\n const json = JSON.parse(body);\n return rejectFn(new InstantAPIError({ status: res.status, body: json }));\n } catch (_e) {\n return rejectFn(\n new InstantAPIError({\n status: res.status,\n body: { type: undefined, message: body },\n }),\n );\n }\n}\n\nconst defaultJsonFetch: JsonFetch = async (input, init) => {\n const headers = {\n ...(init?.headers || {}),\n 'Instant-Core-Version': coreVersion,\n };\n const res = await fetch(input, { ...init, headers });\n if (res.status === 200) {\n return res.json();\n }\n return jsonReject((x) => Promise.reject(x), res);\n};\n\nfunction parseDate(s: string | null | undefined): Date | null {\n return s ? new Date(s) : null;\n}\n\nfunction toWebhookInfo(raw: any): WebhookInfo {\n return {\n id: raw.id,\n sink: raw.sink,\n etypes: raw.etypes ?? null,\n actions: raw.actions,\n status: raw.status,\n disabledReason: raw.disabled_reason ?? null,\n createdAt: new Date(raw.created_at),\n updatedAt: new Date(raw.updated_at),\n };\n}\n\nfunction toWebhookAttempt(raw: any): WebhookAttempt {\n return {\n attemptAt: parseDate(raw['attempt-at']),\n durationMs: raw['duration-ms'] ?? null,\n success: raw['success?'] ?? null,\n statusCode: raw['status-code'] ?? null,\n responseText: raw['response-text'] ?? null,\n errorType: raw['error-type'] ?? null,\n errorMessage: raw['error-message'] ?? null,\n };\n}\n\nfunction toWebhookEventInfo(raw: any): WebhookEventInfo {\n return {\n isn: raw.isn,\n status: raw.status,\n attempts: raw.attempts ? raw.attempts.map(toWebhookAttempt) : null,\n nextAttemptAfter: parseDate(raw.next_attempt_after),\n createdAt: new Date(raw.created_at),\n updatedAt: new Date(raw.updated_at),\n };\n}\n\nexport class WebhooksManager<Schema extends InstantSchemaDef<any, any, any>> {\n #appId: string | null | undefined;\n #apiURI: string;\n #token: string | null | undefined;\n #withAuth: WithAuth | undefined;\n #jsonFetch: JsonFetch;\n\n constructor(opts: {\n appId: string | null | undefined;\n apiURI: string;\n token: string | null | undefined;\n withAuth?: WithAuth;\n jsonFetch: JsonFetch;\n }) {\n this.#appId = opts.appId;\n this.#apiURI = opts.apiURI;\n this.#token = opts.token;\n this.#withAuth = opts.withAuth;\n this.#jsonFetch = opts.jsonFetch;\n }\n\n #authedFetch(\n path: string,\n opts?: { method?: string; body?: unknown },\n ): Promise<any> {\n if (!this.#appId) {\n throw new InstantError(\n 'appId is required to manage webhooks. Pass it to the Webhooks constructor.',\n );\n }\n const run = (token: string) => {\n const init: RequestInit = {\n method: opts?.method,\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json',\n },\n };\n if (opts?.body !== undefined) {\n init.body = JSON.stringify(opts.body);\n }\n return this.#jsonFetch(`${this.#apiURI}${path}`, init);\n };\n if (this.#withAuth) {\n return this.#withAuth(run);\n }\n if (!this.#token) {\n throw new InstantError(\n 'A token is required to manage webhooks. Pass `adminToken` or `token` to the Webhooks constructor.',\n );\n }\n return run(this.#token);\n }\n\n /**\n * Returns every webhook configured on the app, newest first. Includes both\n * active and disabled webhooks.\n */\n async list(): Promise<WebhookInfo[]> {\n const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks`);\n return (res.webhooks || []).map(toWebhookInfo);\n }\n\n /**\n * Creates a new webhook. The webhook is created in the `active` state and\n * starts receiving matching events immediately.\n *\n * The server rejects the request if `url` is not an HTTPS URL pointing at a\n * public host, if `etypes` doesn't reference any entity in the app's\n * schema, if `actions` is empty, or if the app has hit its webhook limit.\n *\n * @example\n * const webhook = await db.webhooks.manager.create({\n * url: 'https://example.com/instant',\n * etypes: ['posts', 'comments'],\n * actions: ['create', 'update'],\n * });\n */\n async create(params: CreateWebhookParams<Schema>): Promise<WebhookInfo> {\n const res = await this.#authedFetch(`/dash/apps/${this.#appId}/webhooks`, {\n method: 'POST',\n body: params,\n });\n return toWebhookInfo(res.webhook);\n }\n\n /**\n * Updates a webhook's `url`, `etypes`, and/or `actions`. Pass only the\n * fields you want to change; omitted fields keep their current value.\n *\n * Does not affect the webhook's status — use {@link enable} or\n * {@link disable} for that.\n */\n async update(\n webhookId: string,\n params: UpdateWebhookParams<Schema>,\n ): Promise<WebhookInfo> {\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}`,\n { method: 'POST', body: params },\n );\n return toWebhookInfo(res.webhook);\n }\n\n /**\n * Deletes a webhook. No further events will be queued for it. Returns the\n * webhook as it looked just before deletion.\n */\n async delete(webhookId: string): Promise<WebhookInfo> {\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}`,\n { method: 'DELETE' },\n );\n return toWebhookInfo(res.webhook);\n }\n\n /**\n * Re-enables a disabled webhook. Clears `disabledReason` and resumes\n * delivery for new events. Has no effect if the webhook is already active.\n *\n * Events that occurred while the webhook was disabled are not retroactively\n * delivered.\n */\n async enable(webhookId: string): Promise<WebhookInfo> {\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}/enable`,\n { method: 'POST', body: {} },\n );\n return toWebhookInfo(res.webhook);\n }\n\n /**\n * Disables a webhook. No new events will be queued until it is re-enabled\n * via {@link enable}. In-flight events already being processed will still\n * complete.\n *\n * @param opts.reason Optional human-readable note stored on the webhook\n * and surfaced in the dashboard.\n */\n async disable(\n webhookId: string,\n opts?: { reason?: string | null | undefined } | null | undefined,\n ): Promise<WebhookInfo> {\n const body = opts?.reason ? { reason: opts.reason } : {};\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}/disable`,\n { method: 'POST', body },\n );\n return toWebhookInfo(res.webhook);\n }\n\n /**\n * Returns a page of events for a webhook, newest first.\n *\n * Events are retained for ~60 days. To paginate, pass the previous page's\n * `pageInfo.endCursor` as `opts.after`; stop when `pageInfo.hasNextPage`\n * is `false`.\n */\n async listEvents(\n webhookId: string,\n opts?: { after?: string | null | undefined } | null | undefined,\n ): Promise<WebhookEventsPage> {\n const qs = opts?.after ? `?after=${encodeURIComponent(opts.after)}` : '';\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}/events${qs}`,\n );\n return {\n events: (res.events || []).map(toWebhookEventInfo),\n pageInfo: {\n startCursor: res.pageInfo?.startCursor ?? null,\n endCursor: res.pageInfo?.endCursor ?? null,\n hasNextPage: !!res.pageInfo?.hasNextPage,\n },\n };\n }\n\n /**\n * Fetches a single webhook event by its `isn`.\n */\n async getEvent(webhookId: string, isn: string): Promise<WebhookEventInfo> {\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}/events/${isn}`,\n );\n return toWebhookEventInfo(res.event);\n }\n\n /** Returns the full payload for an event. */\n async getPayload(\n webhookId: string,\n isn: string,\n ): Promise<WebhookPayload<Schema>> {\n return this.#authedFetch(\n `/webhooks/payload/${this.#appId}/${webhookId}/${isn}`,\n );\n }\n\n /**\n * Re-queues an event for delivery, regardless of its current status. Use\n * this to retry a `failed` event or force a redelivery of a `success` one.\n *\n * The server rate-limits resends; if the event was queued or resent very\n * recently the call will fail with a validation error asking you to try\n * again in about a minute.\n */\n async resendEvent(webhookId: string, isn: string): Promise<WebhookEventInfo> {\n const res = await this.#authedFetch(\n `/dash/apps/${this.#appId}/webhooks/${webhookId}/events/${isn}`,\n { method: 'POST', body: {} },\n );\n return toWebhookEventInfo(res.event);\n }\n}\n\n/**\n * Verify incoming webhook requests from Instant, dispatch their records to\n * typed handlers, and manage webhook subscriptions (via {@link manager}).\n *\n * Usually accessed as `db.webhooks` on the admin or platform SDK rather than\n * constructed directly.\n */\nexport class Webhooks<Schema extends InstantSchemaDef<any, any, any>> {\n /** App this instance is bound to. */\n appId: string | null | undefined;\n /** Schema used to type webhook payloads and handler records. */\n schema: Schema | null | undefined;\n #token: string | null | undefined;\n /** Base URL for the Instant API. */\n apiURI: string;\n #jsonFetch: JsonFetch;\n /** Manage webhook subscriptions and inspect delivery events. */\n manager: WebhooksManager<Schema>;\n\n /**\n * Schema-bound helpers for building typed handler maps.\n *\n * - `typedHandlers(etype, action, handler)` builds a single typed entry.\n * Pass `'$default'` for `etype` to register a catch-all handler.\n * - `combineHandlers(...entries)` merges entries into a\n * {@link WebhookHandlers} object suitable for {@link processPayload} and\n * {@link processRequest}.\n *\n * @example\n * const { typedHandlers, combineHandlers } = Webhooks.helpers<typeof schema>();\n * const handlers = combineHandlers(\n * typedHandlers('posts', 'create', (record) => { ... }),\n * typedHandlers('comments', '$default', (record) => { ... }),\n * typedHandlers('$default', (record) => { ... }),\n * );\n */\n static helpers<\n Schema extends InstantSchemaDef<any, any, any>,\n >(): WebhookHelpers<Schema> {\n function typedHandlers(...args: any[]): any {\n if (args.length === 2) {\n return { $default: args[1] };\n }\n const [etype, action, handler] = args;\n return { [etype]: { [action]: handler } };\n }\n function combineHandlers(...entries: any[]): any {\n const result: any = {};\n for (const entry of entries) {\n for (const key of Object.keys(entry)) {\n if (key === '$default') {\n result.$default = entry.$default;\n } else {\n result[key] = { ...result[key], ...entry[key] };\n }\n }\n }\n return result;\n }\n return {\n typedHandlers: typedHandlers as any,\n combineHandlers: combineHandlers as any,\n };\n }\n\n constructor(config: Config<Schema>, jsonFetch?: JsonFetch) {\n this.appId = config.appId;\n this.schema = config.schema;\n\n this.#token = config.adminToken || config.token;\n this.apiURI = config.apiURI || 'https://api.instantdb.com';\n this.#jsonFetch = jsonFetch || defaultJsonFetch;\n this.manager = new WebhooksManager<Schema>({\n appId: this.appId,\n apiURI: this.apiURI,\n token: this.#token,\n withAuth: config.withAuth,\n jsonFetch: this.#jsonFetch,\n });\n }\n\n /** Fetches Instant's JWK set for verifying webhook signatures. */\n async fetchJwks() {\n const resp = await this.#jsonFetch(\n `${this.apiURI}/.well-known/webhooks/jwks.json`,\n );\n return resp;\n }\n\n /**\n * Resolves a `kid` to an imported {@link CryptoKey}, hitting a\n * process-wide cache on repeat calls. Falls back to {@link fetchJwks} if\n * the key isn't already known.\n */\n async keyOfKid(\n kid: string,\n ): Promise<{ alg: ImportAlgorithm; key: CryptoKey }> {\n const cached = keyCache[kid];\n if (cached) {\n return cached;\n }\n\n const jwk =\n knownKeys[this.apiURI]?.keys.find((k: any) => k.kid === kid) ||\n (await this.fetchJwks())?.keys?.find((k: any) => k.kid === kid);\n\n if (!jwk) {\n throw new InstantError('Could not find matching signing key', { kid });\n }\n\n const res = await importKey(jwk);\n keyCache[kid] = res;\n return res;\n }\n\n /**\n * Verifies an `Instant-Signature` header against a body and returns the\n * parsed {@link WebhookBody} (containing the `payloadUrl` and a JWT\n * `token` for fetching the records).\n *\n * Throws if the signature doesn't validate, the signature is older than\n * `opts.tolerance` (default 300 seconds), or the body doesn't decode to\n * the expected shape.\n *\n * @param body Either the raw body string, or a function returning it.\n * Use a function to defer reading the body until after the\n * header has been parsed.\n */\n async validate(\n signatureHeader: string,\n body: string | (() => Promise<string>),\n opts?:\n | {\n receivedAt?: Date | null | undefined;\n tolerance?: number | null | undefined;\n }\n | null\n | undefined,\n ): Promise<WebhookBody> {\n const receivedAt = opts?.receivedAt || new Date();\n const { t, kid, v1 } = parseSignatureHeader(signatureHeader);\n const tolerance = opts?.tolerance || defaultTolerance;\n validateT(receivedAt, t, tolerance);\n\n const { alg, key } = await this.keyOfKid(kid);\n const bodyText = typeof body === 'function' ? await body() : body;\n\n const message = new TextEncoder().encode(`${t}.${bodyText}`);\n\n const verified = await verify(alg, key, hexToUint8Array(v1), message);\n\n if (!verified) {\n throw new InstantError('Instant Signature did not validate', {\n header: signatureHeader,\n });\n }\n\n const res = JSON.parse(bodyText);\n if (\n typeof res !== 'object' ||\n typeof res.payloadUrl !== 'string' ||\n typeof res.token !== 'string'\n ) {\n throw new InstantError(\n 'Invalid webhook body, expected an object with payloadUrl and token fields',\n { body: res },\n );\n }\n return res;\n }\n\n /**\n * Pulls the `Instant-Signature` header and body from a `Request` and\n * delegates to {@link validate}. Throws if the header is missing.\n */\n async validateRequest(\n req: Request,\n opts?:\n | {\n tolerance?: number | null | undefined;\n receivedAt?: Date | null | undefined;\n }\n | null\n | undefined,\n ): Promise<WebhookBody> {\n const signatureHeader = req.headers.get('instant-signature');\n if (!signatureHeader) {\n throw new InstantError('Request is missing Instant-Signature header');\n }\n return this.validate(signatureHeader, () => req.text(), opts);\n }\n\n /**\n * Fetches the records and `idempotencyKey` for a validated\n * {@link WebhookBody}, authenticating with the JWT `token` it carries.\n */\n fetchPayloads({\n payloadUrl,\n token,\n }: WebhookBody): Promise<WebhookPayload<Schema>> {\n return this.#jsonFetch(payloadUrl, {\n headers: { Authorization: `Bearer ${token}`, accept: 'application/json' },\n });\n }\n\n /**\n * Dispatches each record in `payload` to its matching handler in\n * `handlers`. Resolution order per record: exact `etype` + `action` →\n * `etype`'s `$default` → top-level `$default`. Records with no matching\n * handler are skipped.\n *\n * Handlers run concurrently. The returned promise resolves once every\n * handler has settled (success or failure); rejections in individual\n * handlers do not bubble up.\n */\n async processPayload(\n handlers: WebhookHandlers<Schema>,\n payload: WebhookPayload<Schema>,\n ): Promise<void> {\n const results: any[] = [];\n for (const record of payload.data) {\n const { etype, action } = record;\n const handler =\n handlers?.[etype]?.[action] ||\n handlers?.[etype]?.$default ||\n handlers?.$default;\n if (handler) {\n // We need the as any here because typescript\n // has trouble correlating the handler to the\n // record etype and action\n results.push(handler(record as any));\n }\n }\n await Promise.allSettled(results);\n }\n\n /**\n * The one-liner for handling webhooks. Hand it your handlers and the\n * incoming `Request` — it verifies the signature, fetches the records, and\n * dispatches each one to your code.\n *\n * Async handlers are executed in parallel, the return promise will resolve once\n * all handlers complete and will reject if any of the handlers fails.\n *\n * @example\n * const { typedHandlers, combineHandlers } = Webhooks.helpers<typeof schema>();\n *\n * const handlers = combineHandlers(\n * typedHandlers('posts', 'create', async (record) => {\n * await sendNewPostEmail(record.after);\n * }),\n * typedHandlers('$default', (record) => {\n * console.log('webhook event', record);\n * }),\n * );\n *\n * export async function POST(req: Request) {\n * await db.webhooks.processRequest(handlers, req);\n * return new Response('ok');\n * }\n */\n async processRequest(\n handlers: WebhookHandlers<Schema>,\n req: Request,\n opts?:\n | {\n tolerance?: number | null | undefined;\n receivedAt?: Date | null | undefined;\n }\n | null\n | undefined,\n ): Promise<void> {\n const body = await this.validateRequest(req, opts);\n const payload = await this.fetchPayloads(body);\n await this.processPayload(handlers, payload);\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }