@happyvertical/smrt-webhooks 0.57.37 → 0.57.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.js +1 -1
- package/dist/chunks/{admin-413jve1I.js → admin-BJWZ3X-5.js} +4 -4
- package/dist/chunks/admin-BJWZ3X-5.js.map +1 -0
- package/dist/chunks/{dispatcher-C5AqtgtK.js → dispatcher-Cm6rXFLZ.js} +2 -2
- package/dist/chunks/{dispatcher-C5AqtgtK.js.map → dispatcher-Cm6rXFLZ.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +8 -2
- package/dist/models.d.ts +2 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +6 -3
- package/package.json +7 -7
- package/dist/chunks/admin-413jve1I.js.map +0 -1
package/dist/admin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as webhookAdminView, t as WebhookAdminService } from "./chunks/admin-
|
|
1
|
+
import { n as webhookAdminView, t as WebhookAdminService } from "./chunks/admin-BJWZ3X-5.js";
|
|
2
2
|
export { WebhookAdminService, webhookAdminView };
|
|
@@ -92,11 +92,11 @@ var WebhookSubscription = class extends SmrtObject {
|
|
|
92
92
|
models = [];
|
|
93
93
|
secret = "";
|
|
94
94
|
enabled = true;
|
|
95
|
-
async save() {
|
|
95
|
+
async save(options) {
|
|
96
96
|
assertWebhookUrl(this.url);
|
|
97
97
|
assertHmacSecret(this.secret);
|
|
98
98
|
for (const values of [this.events, this.models]) if (!Array.isArray(values) || values.length > 100 || values.some((v) => typeof v !== "string" || !v || v.length > 200)) throw new Error("Webhook filters must be lists of nonempty names.");
|
|
99
|
-
return super.save();
|
|
99
|
+
return super.save(options);
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
__publicField(WebhookSubscription, "__smrtPackage__", "@happyvertical/smrt-webhooks");
|
|
@@ -202,7 +202,7 @@ var WebhookDeliveryCollection = class extends SmrtCollection {
|
|
|
202
202
|
};
|
|
203
203
|
var WebhookDeliveryTask = class extends SmrtObject {
|
|
204
204
|
async deliver(args = {}, _context) {
|
|
205
|
-
const { runWebhookDeliveryJob } = await import("./dispatcher-
|
|
205
|
+
const { runWebhookDeliveryJob } = await import("./dispatcher-Cm6rXFLZ.js").then((n) => n.r);
|
|
206
206
|
await runWebhookDeliveryJob(args);
|
|
207
207
|
}
|
|
208
208
|
};
|
|
@@ -315,4 +315,4 @@ var WebhookAdminService = class {
|
|
|
315
315
|
//#endregion
|
|
316
316
|
export { WebhookDeliveryCollection as a, WebhookSubscriptionCollection as c, isPublicAddress as d, sendPublicWebhook as f, WebhookDelivery as i, assertHmacSecret as l, webhookAdminView as n, WebhookDeliveryTask as o, WebhookCursor as r, WebhookSubscription as s, WebhookAdminService as t, assertWebhookUrl as u };
|
|
317
317
|
|
|
318
|
-
//# sourceMappingURL=admin-
|
|
318
|
+
//# sourceMappingURL=admin-BJWZ3X-5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-BJWZ3X-5.js","names":[],"sources":["../../src/security.ts","../../src/models.ts","../../src/admin.ts"],"sourcesContent":["import { lookup } from 'node:dns/promises';\nimport { request } from 'node:https';\nimport { isIP } from 'node:net';\n\n/** Conservative public-unicast allowlist. Transition/mapped IPv6 is excluded. */\nexport function isPublicAddress(address: string): boolean {\n if (isIP(address) === 4) {\n const [a, b, c] = address.split('.').map(Number);\n return !(\n a === 0 ||\n a === 10 ||\n a === 127 ||\n a >= 224 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && (b === 0 || b === 168 || (b === 88 && c === 99))) ||\n (a === 198 && (b === 18 || b === 19 || (b === 51 && c === 100))) ||\n (a === 203 && b === 0 && c === 113)\n );\n }\n if (isIP(address) !== 6) return false;\n const normalized = new URL(`https://[${address}]/`).hostname.slice(1, -1);\n // Only global unicast 2000::/3; exclude IETF special-purpose, documentation,\n // 6to4, and the newer documentation block (3fff::/20).\n const first = Number.parseInt(normalized.split(':')[0], 16);\n return (\n first >= 0x2000 &&\n first < 0x4000 &&\n first !== 0x2002 &&\n !(\n first === 0x2001 &&\n Number.parseInt(normalized.split(':')[1] || '0', 16) < 0x200\n ) &&\n !normalized.startsWith('2001:db8:') &&\n first !== 0x3fff\n );\n}\n\nexport function assertWebhookUrl(value: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error('Webhook URL must be an absolute HTTPS URL.');\n }\n const host = url.hostname.replace(/^\\[|\\]$/g, '');\n if (\n url.protocol !== 'https:' ||\n url.username ||\n url.password ||\n url.hash ||\n (url.port && url.port !== '443') ||\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n (isIP(host) && !isPublicAddress(host))\n ) {\n throw new Error(\n 'Webhook URL must use public HTTPS on port 443 without credentials or a fragment.',\n );\n }\n return url;\n}\n\nexport function assertHmacSecret(secret: string): void {\n if (\n typeof secret !== 'string' ||\n Buffer.byteLength(secret) < 32 ||\n Buffer.byteLength(secret) > 4096\n ) {\n throw new Error('Webhook signing secret must contain 32 to 4096 bytes.');\n }\n}\n\nexport interface WebhookRequest {\n url: string;\n body: string;\n headers: Record<string, string>;\n}\nexport type WebhookTransport = (request: WebhookRequest) => Promise<number>;\n\n/** Validate every DNS answer, then pin the socket lookup; TLS still verifies the hostname.\n * No proxy, redirects, pooled sockets, response body buffering, or second DNS lookup.\n */\nexport const sendPublicWebhook: WebhookTransport = async ({\n url,\n body,\n headers,\n}) => {\n const target = assertWebhookUrl(url);\n const host = target.hostname.replace(/^\\[|\\]$/g, '');\n let dnsTimer: ReturnType<typeof setTimeout> | undefined;\n const answers = isIP(host)\n ? [{ address: host, family: isIP(host) }]\n : await Promise.race([\n lookup(host, { all: true, verbatim: true }),\n new Promise<never>((_, reject) => {\n dnsTimer = setTimeout(\n () => reject(new Error('Webhook DNS timeout.')),\n 5000,\n );\n dnsTimer.unref();\n }),\n ]).finally(() => clearTimeout(dnsTimer));\n if (\n !answers.length ||\n answers.some(({ address }) => !isPublicAddress(address))\n ) {\n throw new Error('Webhook destination is not public.');\n }\n const pinned = answers[0];\n return new Promise<number>((resolve, reject) => {\n const req = request(\n target,\n {\n method: 'POST',\n agent: false,\n headers: {\n ...headers,\n 'content-length': Buffer.byteLength(body).toString(),\n },\n lookup: (_hostname, options, callback) => {\n if (options.all) callback(null, [pinned]);\n else callback(null, pinned.address, pinned.family);\n },\n },\n (response) => {\n const status = response.statusCode ?? 0;\n response.destroy();\n resolve(status);\n },\n );\n const timer = setTimeout(\n () => req.destroy(new Error('Webhook request timeout.')),\n 10000,\n );\n req.on('close', () => clearTimeout(timer));\n req.on('error', () => reject(new Error('Webhook transport failed.')));\n req.end(body);\n });\n};\n","import {\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type JobExecutionContext,\n} from '@happyvertical/smrt-jobs';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertHmacSecret, assertWebhookUrl } from './security.js';\n\n/** Server-only subscription. Hosts authorize administration before accessing it. */\n@smrt({\n tableName: '_smrt_webhook_subscriptions',\n api: false,\n cli: false,\n mcp: false,\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookSubscription extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @field({ type: 'text', required: true }) url: string = '';\n @field({ type: 'json' }) events: string[] = [];\n @field({ type: 'json' }) models: string[] = [];\n @field({ type: 'text', sensitive: true, required: true }) secret: string = '';\n @field({ type: 'boolean', default: true }) enabled: boolean = true;\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n assertWebhookUrl(this.url);\n assertHmacSecret(this.secret);\n for (const values of [this.events, this.models]) {\n if (\n !Array.isArray(values) ||\n values.length > 100 ||\n values.some((v) => typeof v !== 'string' || !v || v.length > 200)\n ) {\n throw new Error('Webhook filters must be lists of nonempty names.');\n }\n }\n return super.save(options);\n }\n}\nexport class WebhookSubscriptionCollection extends SmrtCollection<WebhookSubscription> {\n static readonly _itemClass = WebhookSubscription;\n}\n\n/** Durable outbox and safe outcome metadata; payload never appears in public projections. */\n@smrt({\n tableName: '_smrt_webhook_deliveries',\n api: false,\n cli: false,\n mcp: false,\n indexes: [\n {\n name: '_smrt_webhook_delivery_identity',\n columns: ['tenantId', 'subscriptionId', 'eventId'],\n unique: true,\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookDelivery extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @foreignKey(WebhookSubscription, {\n onUpdate: 'RESTRICT',\n onDelete: 'RESTRICT',\n })\n subscriptionId: string = '';\n @field({ type: 'text', required: true }) eventId: string = '';\n @field({ type: 'text', required: true, sensitive: true }) payload: string =\n '';\n @field({ type: 'text', default: 'pending' }) status: string = 'pending';\n @field({ type: 'integer', default: 0 }) generation: number = 0;\n @field({ type: 'integer', default: 0 }) attempts: number = 0;\n @field({ type: 'integer', nullable: true }) responseStatus: number | null =\n null;\n @field({ type: 'text', nullable: true }) error: string | null = null;\n @field({ type: 'text', nullable: true, sensitive: true }) leaseToken:\n | string\n | null = null;\n @field({ type: 'datetime', nullable: true }) leaseUntil: Date | null = null;\n}\nexport class WebhookDeliveryCollection extends SmrtCollection<WebhookDelivery> {\n static readonly _itemClass = WebhookDelivery;\n}\n\n/** Jobs queue entry point; persisted arguments contain IDs only. */\n@smrt({\n tableName: '_smrt_webhook_delivery_tasks',\n api: false,\n cli: false,\n mcp: false,\n})\nexport class WebhookDeliveryTask extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @backgroundEligible()\n async deliver(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<void> {\n const { runWebhookDeliveryJob } = await import('./dispatcher.js');\n await runWebhookDeliveryJob(args);\n }\n}\n\n/** Tenant-bound durable cursor for replayable model change-feed publication. */\n@smrt({\n tableName: '_smrt_webhook_cursors',\n api: false,\n cli: false,\n mcp: false,\n indexes: [\n {\n name: '_smrt_webhook_cursor_identity',\n columns: ['tenantId', 'runtime'],\n unique: true,\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookCursor extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @field({ type: 'text', required: true }) runtime: string = '';\n @field({ type: 'integer', default: 0 }) position: number = 0;\n}\n","import { requireTenant } from '@happyvertical/smrt-tenancy';\nimport { WebhookSubscriptionCollection } from './models.js';\nimport type { WebhookStore } from './store.js';\nimport type {\n WebhookDeliveryRecord,\n WebhookSubscriptionRecord,\n} from './types.js';\nexport interface WebhookAdminView {\n subscriptions: WebhookSubscriptionRecord[];\n deliveries: WebhookDeliveryRecord[];\n}\n/** Allowlisted projection; never spread persistence objects into a browser response. */\nexport function webhookAdminView(\n subscriptions: WebhookSubscriptionRecord[],\n deliveries: WebhookDeliveryRecord[],\n): WebhookAdminView {\n return {\n subscriptions: subscriptions.map((s) => ({\n id: s.id,\n tenantId: s.tenantId,\n url: s.url,\n models: s.models,\n events: s.events,\n enabled: s.enabled,\n })),\n deliveries: deliveries.map((d) => ({\n id: d.id,\n tenantId: d.tenantId,\n subscriptionId: d.subscriptionId,\n eventId: d.eventId,\n status: d.status,\n attempts: d.attempts,\n responseStatus: d.responseStatus,\n error: d.error,\n })),\n };\n}\n/** Host supplies its administrator permission check and authenticated tenant context. */\nexport class WebhookAdminService {\n constructor(\n private readonly store: WebhookStore,\n private readonly authorize: () => Promise<boolean>,\n ) {}\n private async guard() {\n requireTenant();\n if (!(await this.authorize()))\n throw new Error('Webhook administration forbidden.');\n }\n async view(): Promise<WebhookAdminView> {\n await this.guard();\n await this.store.reconcile();\n const subscriptions = await this.store.subscriptions();\n const deliveries = await this.store.deliveries();\n return webhookAdminView(\n subscriptions.map((s) => ({ ...s, id: s.id ?? '' })),\n deliveries.map((d) => ({ ...d, id: d.id ?? '' })),\n );\n }\n async create(input: {\n url: string;\n events: string[];\n models: string[];\n secret: string;\n }): Promise<void> {\n await this.guard();\n const collection = await WebhookSubscriptionCollection.create({\n db: this.store.db,\n });\n const sub = await collection.create({\n tenantId: requireTenant().tenantId,\n url: input.url,\n events: input.events,\n models: input.models,\n secret: input.secret,\n enabled: true,\n });\n await sub.save();\n }\n async toggle(id: string, enabled: boolean): Promise<void> {\n await this.guard();\n if (typeof enabled !== 'boolean') throw new Error('Invalid enabled value.');\n const sub = await this.store.subscription(id);\n if (!sub) throw new Error('Webhook subscription not found.');\n sub.enabled = enabled;\n await sub.save();\n }\n async replay(id: string): Promise<boolean> {\n await this.guard();\n return this.store.replay(id);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as WebhookDeliveryCollection, c as WebhookSubscriptionCollection, f as sendPublicWebhook, l as assertHmacSecret } from "./admin-
|
|
1
|
+
import { a as WebhookDeliveryCollection, c as WebhookSubscriptionCollection, f as sendPublicWebhook, l as assertHmacSecret } from "./admin-BJWZ3X-5.js";
|
|
2
2
|
import { requireTenant } from "@happyvertical/smrt-tenancy";
|
|
3
3
|
import { SmrtJobCollection } from "@happyvertical/smrt-jobs";
|
|
4
4
|
import { createHmac, randomUUID } from "node:crypto";
|
|
@@ -245,4 +245,4 @@ async function runWebhookDeliveryJob(args) {
|
|
|
245
245
|
//#endregion
|
|
246
246
|
export { runWebhookDeliveryJob as a, registerWebhookRuntime as i, assertWebhookEvent as n, unregisterWebhookRuntime as o, dispatcher_exports as r, WebhookStore as s, WebhookDispatcher as t };
|
|
247
247
|
|
|
248
|
-
//# sourceMappingURL=dispatcher-
|
|
248
|
+
//# sourceMappingURL=dispatcher-Cm6rXFLZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher-C5AqtgtK.js","names":[],"sources":["../../src/store.ts","../../src/dispatcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { SmrtJobCollection } from '@happyvertical/smrt-jobs';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n WebhookDeliveryCollection,\n type WebhookSubscription,\n WebhookSubscriptionCollection,\n} from './models.js';\nimport type { WebhookEventEnvelope } from './types.js';\n\n/** Explicit tenant predicates also protect deployments that have not enabled interceptors. */\nexport class WebhookStore {\n constructor(\n readonly db: DatabaseInterface,\n readonly runtime: string,\n ) {\n if (!runtime) throw new Error('Webhook runtime is required.');\n }\n async subscriptions() {\n const tenantId = requireTenant().tenantId;\n const collection = await WebhookSubscriptionCollection.create({\n db: this.db,\n });\n const result: WebhookSubscription[] = [];\n for (let offset = 0; ; offset += 1000) {\n const page = await collection.list({\n where: { tenantId },\n orderBy: 'id ASC',\n limit: 1000,\n offset,\n });\n result.push(...page);\n if (page.length < 1000) return result;\n }\n }\n async subscription(id: string) {\n const tenantId = requireTenant().tenantId;\n return (await WebhookSubscriptionCollection.create({ db: this.db })).get({\n id,\n tenantId,\n });\n }\n async delivery(id: string) {\n const tenantId = requireTenant().tenantId;\n return (await WebhookDeliveryCollection.create({ db: this.db })).get({\n id,\n tenantId,\n });\n }\n async deliveries() {\n const tenantId = requireTenant().tenantId;\n return (await WebhookDeliveryCollection.create({ db: this.db })).list({\n where: { tenantId },\n orderBy: 'created_at DESC',\n limit: 100,\n });\n }\n\n /** The unique outbox identity and jobs enqueue commit or roll back together. */\n async accept(\n subscriptionId: string,\n event: WebhookEventEnvelope,\n ): Promise<string> {\n const tenantId = requireTenant().tenantId;\n if (!(await this.subscription(subscriptionId)))\n throw new Error('Webhook subscription not found.');\n if (!this.db.transaction)\n throw new Error('Webhooks require a transactional database.');\n return this.db.transaction(async (tx) => {\n const id = randomUUID();\n const now = new Date().toISOString();\n const inserted = await tx.query(\n `INSERT INTO _smrt_webhook_deliveries\n (id, slug, context, tenant_id, subscription_id, event_id, payload, status, attempts, generation, created_at, updated_at)\n VALUES (?, ?, '', ?, ?, ?, ?, 'pending', 0, 0, ?, ?)\n ON CONFLICT (tenant_id, subscription_id, event_id) DO NOTHING RETURNING id`,\n id,\n id,\n tenantId,\n subscriptionId,\n event.id,\n JSON.stringify(event),\n now,\n now,\n );\n if (inserted.rows.length) {\n await this.enqueue(tx, tenantId, id);\n return id;\n }\n const existing = await tx.query(\n 'SELECT CAST(id AS VARCHAR) AS id FROM _smrt_webhook_deliveries WHERE tenant_id = ? AND subscription_id = ? AND event_id = ?',\n tenantId,\n subscriptionId,\n event.id,\n );\n return (existing.rows[0] as { id: string }).id;\n });\n }\n private async enqueue(\n db: DatabaseInterface,\n tenantId: string,\n deliveryId: string,\n generation = 0,\n ) {\n const jobs = await SmrtJobCollection.create({ db });\n await jobs.enqueueJob({\n tenantId,\n queue: 'webhooks',\n objectType: '@happyvertical/smrt-webhooks:WebhookDeliveryTask',\n objectId: null,\n method: 'deliver',\n args: { runtime: this.runtime, tenantId, deliveryId, generation },\n maxAttempts: 5,\n timeout: 30000,\n retryStrategy: {\n type: 'exponential',\n config: { initialDelay: 1000, multiplier: 2, maxDelay: 300000 },\n },\n });\n }\n /** A bounded lease prevents overlapping jobs; expired leases recover after process death. */\n async claim(id: string, generation = 0): Promise<string | null> {\n const tenantId = requireTenant().tenantId;\n const token = randomUUID();\n const now = new Date();\n const result = await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET lease_token = ?, lease_until = ?, attempts = attempts + 1\n WHERE id = ? AND tenant_id = ? AND generation = ? AND status IN ('pending', 'retrying')\n AND (lease_until IS NULL OR lease_until <= ?) RETURNING id`,\n token,\n new Date(now.getTime() + 20000).toISOString(),\n id,\n tenantId,\n generation,\n now.toISOString(),\n );\n return result.rows.length ? token : null;\n }\n async finish(\n id: string,\n token: string,\n status: string,\n responseStatus: number | null,\n error: string | null,\n ) {\n const tenantId = requireTenant().tenantId;\n const result = await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET status = ?, response_status = ?, error = ?, lease_token = NULL, lease_until = NULL, updated_at = ?\n WHERE id = ? AND tenant_id = ? AND lease_token = ? RETURNING id`,\n status,\n responseStatus,\n error,\n new Date().toISOString(),\n id,\n tenantId,\n token,\n );\n if (!result.rows.length) throw new Error('Webhook delivery lease lost.');\n }\n /** Reconcile exhausted/cancelled jobs, including process death before an attempt starts.\n * Tenant-scoped and lease-aware; safe to call on each admin refresh or operator sweep.\n */\n async reconcile(): Promise<void> {\n const tenantId = requireTenant().tenantId;\n const jobs = await SmrtJobCollection.create({ db: this.db });\n for (let offset = 0; ; offset += 1000) {\n const terminal = await jobs.list({\n where: { tenantId, queue: 'webhooks', status: ['failed', 'cancelled'] },\n orderBy: 'id ASC',\n limit: 1000,\n offset,\n });\n for (const job of terminal) {\n if (\n job.args.runtime !== this.runtime ||\n typeof job.args.deliveryId !== 'string'\n )\n continue;\n await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET status = 'failed', error = 'Webhook job exhausted.', lease_token = NULL, lease_until = NULL\n WHERE id = ? AND tenant_id = ? AND generation = ? AND status IN ('pending', 'retrying') AND (lease_until IS NULL OR lease_until <= ?)`,\n job.args.deliveryId,\n tenantId,\n job.args.generation ?? 0,\n new Date().toISOString(),\n );\n }\n if (terminal.length < 1000) return;\n }\n }\n\n /** Explicit operator recovery for exhausted jobs. Repeated/concurrent replay is a no-op. */\n async replay(id: string): Promise<boolean> {\n const tenantId = requireTenant().tenantId;\n if (!this.db.transaction)\n throw new Error('Webhooks require a transactional database.');\n return this.db.transaction(async (tx) => {\n const changed = await tx.query(\n `UPDATE _smrt_webhook_deliveries SET status = 'pending', attempts = 0, generation = generation + 1, error = NULL, lease_token = NULL, lease_until = NULL\n WHERE id = ? AND tenant_id = ? AND status = 'failed' RETURNING generation`,\n id,\n tenantId,\n );\n if (!changed.rows.length) return false;\n await this.enqueue(\n tx,\n tenantId,\n id,\n Number((changed.rows[0] as { generation: number }).generation),\n );\n return true;\n });\n }\n}\n","import { createHmac } from 'node:crypto';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n assertHmacSecret,\n sendPublicWebhook,\n type WebhookTransport,\n} from './security.js';\nimport { WebhookStore } from './store.js';\nimport type { WebhookEventEnvelope } from './types.js';\n\nexport interface WebhookDispatcherOptions {\n db: DatabaseInterface;\n runtime: string;\n /** Trusted server integration seam. Default transport pins validated public DNS answers. */\n transport?: WebhookTransport;\n}\nconst runtimes = new Map<string, WebhookDispatcher>();\nexport function registerWebhookRuntime(\n name: string,\n dispatcher: WebhookDispatcher,\n): void {\n if (!name || runtimes.has(name))\n throw new Error('Webhook runtime already registered or invalid.');\n runtimes.set(name, dispatcher);\n}\nexport function unregisterWebhookRuntime(name: string): void {\n runtimes.delete(name);\n}\n\nexport function assertWebhookEvent(event: WebhookEventEnvelope): void {\n if (\n !event ||\n typeof event !== 'object' ||\n !event.data ||\n Array.isArray(event.data) ||\n typeof event.data !== 'object' ||\n [event.id, event.event, event.model].some(\n (v) => typeof v !== 'string' || !v || v.length > 200 || /[\\r\\n]/.test(v),\n ) ||\n typeof event.occurredAt !== 'string' ||\n !Number.isFinite(Date.parse(event.occurredAt)) ||\n Buffer.byteLength(JSON.stringify(event)) > 262144\n )\n throw new Error('Invalid webhook event.');\n}\n\n/** Durable tenant-scoped outbound events. Receivers deduplicate x-smrt-delivery. */\nexport class WebhookDispatcher {\n readonly store: WebhookStore;\n private readonly transport: WebhookTransport;\n constructor(options: WebhookDispatcherOptions) {\n this.store = new WebhookStore(options.db, options.runtime);\n this.transport = options.transport ?? sendPublicWebhook;\n }\n async dispatch(event: WebhookEventEnvelope): Promise<string[]> {\n requireTenant();\n assertWebhookEvent(event);\n const ids: string[] = [];\n for (const sub of await this.store.subscriptions()) {\n if (\n sub.enabled &&\n (!sub.events.length || sub.events.includes(event.event)) &&\n (!sub.models.length || sub.models.includes(event.model))\n ) {\n ids.push(await this.store.accept(sub.id ?? '', event));\n }\n }\n return ids;\n }\n async deliver(\n deliveryId: string,\n expectedGeneration?: number,\n ): Promise<void> {\n requireTenant();\n const before = await this.store.delivery(deliveryId);\n if (\n !before ||\n (expectedGeneration !== undefined &&\n before.generation !== expectedGeneration) ||\n ['delivered', 'failed', 'cancelled'].includes(before.status)\n )\n return;\n const token = await this.store.claim(deliveryId, before.generation);\n if (!token) throw new Error('Webhook delivery is already leased.');\n const delivery = await this.store.delivery(deliveryId);\n if (!delivery) throw new Error('Webhook delivery unavailable.');\n let responseStatus: number | null = null;\n try {\n const sub = await this.store.subscription(delivery.subscriptionId);\n if (!sub?.enabled) {\n await this.store.finish(\n deliveryId,\n token,\n 'cancelled',\n null,\n 'Subscription disabled.',\n );\n return;\n }\n assertHmacSecret(sub.secret);\n const event = JSON.parse(delivery.payload) as WebhookEventEnvelope;\n assertWebhookEvent(event);\n const timestamp = new Date().toISOString();\n const signature = createHmac('sha256', sub.secret)\n .update(`${timestamp}.${delivery.payload}`)\n .digest('hex');\n responseStatus = await this.transport({\n url: sub.url,\n body: delivery.payload,\n headers: {\n 'content-type': 'application/json',\n 'user-agent': 'smrt-webhooks/1',\n 'x-smrt-event': event.event,\n 'x-smrt-delivery': deliveryId,\n 'x-smrt-timestamp': timestamp,\n 'x-smrt-signature': `sha256=${signature}`,\n },\n });\n if (responseStatus < 200 || responseStatus >= 300)\n throw new Error('Webhook rejected.');\n await this.store.finish(\n deliveryId,\n token,\n 'delivered',\n responseStatus,\n null,\n );\n } catch {\n // Never persist/rethrow endpoint errors: URLs, response bodies and secrets may be embedded.\n await this.store.finish(\n deliveryId,\n token,\n delivery.attempts >= 5 ? 'failed' : 'retrying',\n responseStatus,\n 'Webhook delivery failed.',\n );\n throw new Error('Webhook delivery failed.');\n }\n }\n}\nexport async function runWebhookDeliveryJob(\n args: Record<string, unknown>,\n): Promise<void> {\n const runtime =\n typeof args.runtime === 'string' ? runtimes.get(args.runtime) : undefined;\n if (\n !runtime ||\n typeof args.deliveryId !== 'string' ||\n args.tenantId !== requireTenant().tenantId\n ) {\n throw new Error('Invalid webhook job or unavailable runtime.');\n }\n await runtime.deliver(\n args.deliveryId,\n typeof args.generation === 'number' ? args.generation : 0,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dispatcher-Cm6rXFLZ.js","names":[],"sources":["../../src/store.ts","../../src/dispatcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { SmrtJobCollection } from '@happyvertical/smrt-jobs';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n WebhookDeliveryCollection,\n type WebhookSubscription,\n WebhookSubscriptionCollection,\n} from './models.js';\nimport type { WebhookEventEnvelope } from './types.js';\n\n/** Explicit tenant predicates also protect deployments that have not enabled interceptors. */\nexport class WebhookStore {\n constructor(\n readonly db: DatabaseInterface,\n readonly runtime: string,\n ) {\n if (!runtime) throw new Error('Webhook runtime is required.');\n }\n async subscriptions() {\n const tenantId = requireTenant().tenantId;\n const collection = await WebhookSubscriptionCollection.create({\n db: this.db,\n });\n const result: WebhookSubscription[] = [];\n for (let offset = 0; ; offset += 1000) {\n const page = await collection.list({\n where: { tenantId },\n orderBy: 'id ASC',\n limit: 1000,\n offset,\n });\n result.push(...page);\n if (page.length < 1000) return result;\n }\n }\n async subscription(id: string) {\n const tenantId = requireTenant().tenantId;\n return (await WebhookSubscriptionCollection.create({ db: this.db })).get({\n id,\n tenantId,\n });\n }\n async delivery(id: string) {\n const tenantId = requireTenant().tenantId;\n return (await WebhookDeliveryCollection.create({ db: this.db })).get({\n id,\n tenantId,\n });\n }\n async deliveries() {\n const tenantId = requireTenant().tenantId;\n return (await WebhookDeliveryCollection.create({ db: this.db })).list({\n where: { tenantId },\n orderBy: 'created_at DESC',\n limit: 100,\n });\n }\n\n /** The unique outbox identity and jobs enqueue commit or roll back together. */\n async accept(\n subscriptionId: string,\n event: WebhookEventEnvelope,\n ): Promise<string> {\n const tenantId = requireTenant().tenantId;\n if (!(await this.subscription(subscriptionId)))\n throw new Error('Webhook subscription not found.');\n if (!this.db.transaction)\n throw new Error('Webhooks require a transactional database.');\n return this.db.transaction(async (tx) => {\n const id = randomUUID();\n const now = new Date().toISOString();\n const inserted = await tx.query(\n `INSERT INTO _smrt_webhook_deliveries\n (id, slug, context, tenant_id, subscription_id, event_id, payload, status, attempts, generation, created_at, updated_at)\n VALUES (?, ?, '', ?, ?, ?, ?, 'pending', 0, 0, ?, ?)\n ON CONFLICT (tenant_id, subscription_id, event_id) DO NOTHING RETURNING id`,\n id,\n id,\n tenantId,\n subscriptionId,\n event.id,\n JSON.stringify(event),\n now,\n now,\n );\n if (inserted.rows.length) {\n await this.enqueue(tx, tenantId, id);\n return id;\n }\n const existing = await tx.query(\n 'SELECT CAST(id AS VARCHAR) AS id FROM _smrt_webhook_deliveries WHERE tenant_id = ? AND subscription_id = ? AND event_id = ?',\n tenantId,\n subscriptionId,\n event.id,\n );\n return (existing.rows[0] as { id: string }).id;\n });\n }\n private async enqueue(\n db: DatabaseInterface,\n tenantId: string,\n deliveryId: string,\n generation = 0,\n ) {\n const jobs = await SmrtJobCollection.create({ db });\n await jobs.enqueueJob({\n tenantId,\n queue: 'webhooks',\n objectType: '@happyvertical/smrt-webhooks:WebhookDeliveryTask',\n objectId: null,\n method: 'deliver',\n args: { runtime: this.runtime, tenantId, deliveryId, generation },\n maxAttempts: 5,\n timeout: 30000,\n retryStrategy: {\n type: 'exponential',\n config: { initialDelay: 1000, multiplier: 2, maxDelay: 300000 },\n },\n });\n }\n /** A bounded lease prevents overlapping jobs; expired leases recover after process death. */\n async claim(id: string, generation = 0): Promise<string | null> {\n const tenantId = requireTenant().tenantId;\n const token = randomUUID();\n const now = new Date();\n const result = await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET lease_token = ?, lease_until = ?, attempts = attempts + 1\n WHERE id = ? AND tenant_id = ? AND generation = ? AND status IN ('pending', 'retrying')\n AND (lease_until IS NULL OR lease_until <= ?) RETURNING id`,\n token,\n new Date(now.getTime() + 20000).toISOString(),\n id,\n tenantId,\n generation,\n now.toISOString(),\n );\n return result.rows.length ? token : null;\n }\n async finish(\n id: string,\n token: string,\n status: string,\n responseStatus: number | null,\n error: string | null,\n ) {\n const tenantId = requireTenant().tenantId;\n const result = await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET status = ?, response_status = ?, error = ?, lease_token = NULL, lease_until = NULL, updated_at = ?\n WHERE id = ? AND tenant_id = ? AND lease_token = ? RETURNING id`,\n status,\n responseStatus,\n error,\n new Date().toISOString(),\n id,\n tenantId,\n token,\n );\n if (!result.rows.length) throw new Error('Webhook delivery lease lost.');\n }\n /** Reconcile exhausted/cancelled jobs, including process death before an attempt starts.\n * Tenant-scoped and lease-aware; safe to call on each admin refresh or operator sweep.\n */\n async reconcile(): Promise<void> {\n const tenantId = requireTenant().tenantId;\n const jobs = await SmrtJobCollection.create({ db: this.db });\n for (let offset = 0; ; offset += 1000) {\n const terminal = await jobs.list({\n where: { tenantId, queue: 'webhooks', status: ['failed', 'cancelled'] },\n orderBy: 'id ASC',\n limit: 1000,\n offset,\n });\n for (const job of terminal) {\n if (\n job.args.runtime !== this.runtime ||\n typeof job.args.deliveryId !== 'string'\n )\n continue;\n await this.db.query(\n `UPDATE _smrt_webhook_deliveries SET status = 'failed', error = 'Webhook job exhausted.', lease_token = NULL, lease_until = NULL\n WHERE id = ? AND tenant_id = ? AND generation = ? AND status IN ('pending', 'retrying') AND (lease_until IS NULL OR lease_until <= ?)`,\n job.args.deliveryId,\n tenantId,\n job.args.generation ?? 0,\n new Date().toISOString(),\n );\n }\n if (terminal.length < 1000) return;\n }\n }\n\n /** Explicit operator recovery for exhausted jobs. Repeated/concurrent replay is a no-op. */\n async replay(id: string): Promise<boolean> {\n const tenantId = requireTenant().tenantId;\n if (!this.db.transaction)\n throw new Error('Webhooks require a transactional database.');\n return this.db.transaction(async (tx) => {\n const changed = await tx.query(\n `UPDATE _smrt_webhook_deliveries SET status = 'pending', attempts = 0, generation = generation + 1, error = NULL, lease_token = NULL, lease_until = NULL\n WHERE id = ? AND tenant_id = ? AND status = 'failed' RETURNING generation`,\n id,\n tenantId,\n );\n if (!changed.rows.length) return false;\n await this.enqueue(\n tx,\n tenantId,\n id,\n Number((changed.rows[0] as { generation: number }).generation),\n );\n return true;\n });\n }\n}\n","import { createHmac } from 'node:crypto';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n assertHmacSecret,\n sendPublicWebhook,\n type WebhookTransport,\n} from './security.js';\nimport { WebhookStore } from './store.js';\nimport type { WebhookEventEnvelope } from './types.js';\n\nexport interface WebhookDispatcherOptions {\n db: DatabaseInterface;\n runtime: string;\n /** Trusted server integration seam. Default transport pins validated public DNS answers. */\n transport?: WebhookTransport;\n}\nconst runtimes = new Map<string, WebhookDispatcher>();\nexport function registerWebhookRuntime(\n name: string,\n dispatcher: WebhookDispatcher,\n): void {\n if (!name || runtimes.has(name))\n throw new Error('Webhook runtime already registered or invalid.');\n runtimes.set(name, dispatcher);\n}\nexport function unregisterWebhookRuntime(name: string): void {\n runtimes.delete(name);\n}\n\nexport function assertWebhookEvent(event: WebhookEventEnvelope): void {\n if (\n !event ||\n typeof event !== 'object' ||\n !event.data ||\n Array.isArray(event.data) ||\n typeof event.data !== 'object' ||\n [event.id, event.event, event.model].some(\n (v) => typeof v !== 'string' || !v || v.length > 200 || /[\\r\\n]/.test(v),\n ) ||\n typeof event.occurredAt !== 'string' ||\n !Number.isFinite(Date.parse(event.occurredAt)) ||\n Buffer.byteLength(JSON.stringify(event)) > 262144\n )\n throw new Error('Invalid webhook event.');\n}\n\n/** Durable tenant-scoped outbound events. Receivers deduplicate x-smrt-delivery. */\nexport class WebhookDispatcher {\n readonly store: WebhookStore;\n private readonly transport: WebhookTransport;\n constructor(options: WebhookDispatcherOptions) {\n this.store = new WebhookStore(options.db, options.runtime);\n this.transport = options.transport ?? sendPublicWebhook;\n }\n async dispatch(event: WebhookEventEnvelope): Promise<string[]> {\n requireTenant();\n assertWebhookEvent(event);\n const ids: string[] = [];\n for (const sub of await this.store.subscriptions()) {\n if (\n sub.enabled &&\n (!sub.events.length || sub.events.includes(event.event)) &&\n (!sub.models.length || sub.models.includes(event.model))\n ) {\n ids.push(await this.store.accept(sub.id ?? '', event));\n }\n }\n return ids;\n }\n async deliver(\n deliveryId: string,\n expectedGeneration?: number,\n ): Promise<void> {\n requireTenant();\n const before = await this.store.delivery(deliveryId);\n if (\n !before ||\n (expectedGeneration !== undefined &&\n before.generation !== expectedGeneration) ||\n ['delivered', 'failed', 'cancelled'].includes(before.status)\n )\n return;\n const token = await this.store.claim(deliveryId, before.generation);\n if (!token) throw new Error('Webhook delivery is already leased.');\n const delivery = await this.store.delivery(deliveryId);\n if (!delivery) throw new Error('Webhook delivery unavailable.');\n let responseStatus: number | null = null;\n try {\n const sub = await this.store.subscription(delivery.subscriptionId);\n if (!sub?.enabled) {\n await this.store.finish(\n deliveryId,\n token,\n 'cancelled',\n null,\n 'Subscription disabled.',\n );\n return;\n }\n assertHmacSecret(sub.secret);\n const event = JSON.parse(delivery.payload) as WebhookEventEnvelope;\n assertWebhookEvent(event);\n const timestamp = new Date().toISOString();\n const signature = createHmac('sha256', sub.secret)\n .update(`${timestamp}.${delivery.payload}`)\n .digest('hex');\n responseStatus = await this.transport({\n url: sub.url,\n body: delivery.payload,\n headers: {\n 'content-type': 'application/json',\n 'user-agent': 'smrt-webhooks/1',\n 'x-smrt-event': event.event,\n 'x-smrt-delivery': deliveryId,\n 'x-smrt-timestamp': timestamp,\n 'x-smrt-signature': `sha256=${signature}`,\n },\n });\n if (responseStatus < 200 || responseStatus >= 300)\n throw new Error('Webhook rejected.');\n await this.store.finish(\n deliveryId,\n token,\n 'delivered',\n responseStatus,\n null,\n );\n } catch {\n // Never persist/rethrow endpoint errors: URLs, response bodies and secrets may be embedded.\n await this.store.finish(\n deliveryId,\n token,\n delivery.attempts >= 5 ? 'failed' : 'retrying',\n responseStatus,\n 'Webhook delivery failed.',\n );\n throw new Error('Webhook delivery failed.');\n }\n }\n}\nexport async function runWebhookDeliveryJob(\n args: Record<string, unknown>,\n): Promise<void> {\n const runtime =\n typeof args.runtime === 'string' ? runtimes.get(args.runtime) : undefined;\n if (\n !runtime ||\n typeof args.deliveryId !== 'string' ||\n args.tenantId !== requireTenant().tenantId\n ) {\n throw new Error('Invalid webhook job or unavailable runtime.');\n }\n await runtime.deliver(\n args.deliveryId,\n typeof args.generation === 'number' ? args.generation : 0,\n );\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as runWebhookDeliveryJob, i as registerWebhookRuntime, n as assertWebhookEvent, o as unregisterWebhookRuntime, s as WebhookStore, t as WebhookDispatcher } from "./chunks/dispatcher-
|
|
2
|
-
import { a as WebhookDeliveryCollection, c as WebhookSubscriptionCollection, d as isPublicAddress, f as sendPublicWebhook, i as WebhookDelivery, l as assertHmacSecret, n as webhookAdminView, o as WebhookDeliveryTask, r as WebhookCursor, s as WebhookSubscription, t as WebhookAdminService, u as assertWebhookUrl } from "./chunks/admin-
|
|
1
|
+
import { a as runWebhookDeliveryJob, i as registerWebhookRuntime, n as assertWebhookEvent, o as unregisterWebhookRuntime, s as WebhookStore, t as WebhookDispatcher } from "./chunks/dispatcher-Cm6rXFLZ.js";
|
|
2
|
+
import { a as WebhookDeliveryCollection, c as WebhookSubscriptionCollection, d as isPublicAddress, f as sendPublicWebhook, i as WebhookDelivery, l as assertHmacSecret, n as webhookAdminView, o as WebhookDeliveryTask, r as WebhookCursor, s as WebhookSubscription, t as WebhookAdminService, u as assertWebhookUrl } from "./chunks/admin-BJWZ3X-5.js";
|
|
3
3
|
import { GlobalInterceptors, ObjectRegistry, SmrtRecipe, getChangesSince } from "@happyvertical/smrt-core";
|
|
4
4
|
import { requireTenant } from "@happyvertical/smrt-tenancy";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
//#region src/__smrt-register__.ts
|
|
7
|
-
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-webhooks","packageVersion":"0.57.37","objects":{"@happyvertical/smrt-webhooks:WebhookSubscription":{"name":"webhooksubscription","className":"WebhookSubscription","qualifiedName":"@happyvertical/smrt-webhooks:WebhookSubscription","collection":"webhooksubscriptions","description":"Server-only subscription. Hosts authorize administration before accessing it.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"url":{"type":"text","required":true,"_meta":{"required":true}},"events":{"type":"json","required":false,"_meta":{}},"models":{"type":"json","required":false,"_meta":{}},"secret":{"type":"text","required":true,"_meta":{"sensitive":true,"required":true},"sensitive":true},"enabled":{"type":"boolean","required":false,"default":true,"_meta":{"default":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_webhook_subscriptions","api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookSubscription","collectionExportName":"WebhookSubscriptionCollection","validationRules":[{"field":"url","rule":"required","fieldType":"text"},{"field":"secret","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_subscriptions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_subscriptions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"url\\" TEXT NOT NULL,\\n \\"events\\" JSON,\\n \\"models\\" JSON,\\n \\"secret\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"url":{"type":"TEXT","notNull":true,"unique":false},"events":{"type":"JSON","notNull":false,"unique":false},"models":{"type":"JSON","notNull":false,"unique":false},"secret":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"_smrt_webhook_subscriptions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_subscriptions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4e9ebebe"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection":{"name":"webhooksubscriptioncollection","className":"WebhookSubscriptionCollection","qualifiedName":"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection","collection":"webhooksubscriptions","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_subscriptions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"WebhookSubscription","exportName":"WebhookSubscriptionCollection","collectionExportName":"WebhookSubscriptionCollectionCollection","schema":{"tableName":"_smrt_webhook_subscriptions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_subscriptions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"url\\" TEXT NOT NULL,\\n \\"events\\" JSON,\\n \\"models\\" JSON,\\n \\"secret\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"url":{"type":"TEXT","notNull":true,"unique":false},"events":{"type":"JSON","notNull":false,"unique":false},"models":{"type":"JSON","notNull":false,"unique":false},"secret":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"_smrt_webhook_subscriptions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_subscriptions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4e9ebebe"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDelivery":{"name":"webhookdelivery","className":"WebhookDelivery","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDelivery","collection":"webhookdeliveries","description":"Durable outbox and safe outcome metadata; payload never appears in public projections.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"subscriptionId":{"type":"foreignKey","required":false,"related":"WebhookSubscription","_meta":{"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"eventId":{"type":"text","required":true,"_meta":{"required":true}},"payload":{"type":"text","required":true,"_meta":{"required":true,"sensitive":true},"sensitive":true},"status":{"type":"text","required":false,"default":"pending","_meta":{"default":"pending"}},"generation":{"type":"integer","required":false,"default":0,"_meta":{"default":0}},"attempts":{"type":"integer","required":false,"default":0,"_meta":{"default":0}},"responseStatus":{"type":"integer","required":false,"_meta":{"nullable":true}},"error":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseToken":{"type":"text","required":false,"_meta":{"nullable":true,"sensitive":true},"sensitive":true},"leaseUntil":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_deliveries","api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_webhook_delivery_identity","columns":["tenantId","subscriptionId","eventId"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookDelivery","collectionExportName":"WebhookDeliveryCollection","validationRules":[{"field":"eventId","rule":"required","fieldType":"text"},{"field":"payload","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"subscription_id\\" UUID,\\n \\"event_id\\" TEXT NOT NULL,\\n \\"payload\\" TEXT NOT NULL,\\n \\"status\\" TEXT DEFAULT 'pending',\\n \\"generation\\" INTEGER DEFAULT 0,\\n \\"attempts\\" INTEGER DEFAULT 0,\\n \\"response_status\\" INTEGER,\\n \\"error\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_until\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"subscription_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"_smrt_webhook_subscriptions","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"event_id":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"pending"},"generation":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"response_status":{"type":"INTEGER","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_until":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_webhook_deliveries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_identity","columns":["tenant_id","subscription_id","event_id"],"unique":true},{"name":"_smrt_webhook_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_webhook_deliveries_subscription_id_idx","columns":["subscription_id"]}],"version":"17d7bd27"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDeliveryCollection":{"name":"webhookdeliverycollection","className":"WebhookDeliveryCollection","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDeliveryCollection","collection":"webhookdeliveries","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_deliveries","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"WebhookDelivery","exportName":"WebhookDeliveryCollection","collectionExportName":"WebhookDeliveryCollectionCollection","schema":{"tableName":"_smrt_webhook_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"subscription_id\\" UUID,\\n \\"event_id\\" TEXT NOT NULL,\\n \\"payload\\" TEXT NOT NULL,\\n \\"status\\" TEXT DEFAULT 'pending',\\n \\"generation\\" INTEGER DEFAULT 0,\\n \\"attempts\\" INTEGER DEFAULT 0,\\n \\"response_status\\" INTEGER,\\n \\"error\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_until\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"subscription_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"_smrt_webhook_subscriptions","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"event_id":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"pending"},"generation":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"response_status":{"type":"INTEGER","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_until":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_webhook_deliveries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_identity","columns":["tenant_id","subscription_id","event_id"],"unique":true},{"name":"_smrt_webhook_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_webhook_deliveries_subscription_id_idx","columns":["subscription_id"]}],"version":"17d7bd27"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDeliveryTask":{"name":"webhookdeliverytask","className":"WebhookDeliveryTask","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDeliveryTask","collection":"webhookdeliverytasks","description":"Jobs queue entry point; persisted arguments contain IDs only.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{"deliver":{"name":"deliver","async":true,"parameters":[{"name":"args","type":"Record<string, unknown>","optional":true},{"name":"_context","type":"JobExecutionContext","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_webhook_delivery_tasks","api":false,"cli":false,"mcp":false},"extends":"SmrtObject","exportName":"WebhookDeliveryTask","collectionExportName":"WebhookDeliveryTaskCollection","schema":{"tableName":"_smrt_webhook_delivery_tasks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_delivery_tasks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"}},"indexes":[{"name":"_smrt_webhook_delivery_tasks_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_tasks_created_at_idx","columns":["created_at"]}],"version":"17ba7c43"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookCursor":{"name":"webhookcursor","className":"WebhookCursor","qualifiedName":"@happyvertical/smrt-webhooks:WebhookCursor","collection":"webhookcursors","description":"Tenant-bound durable cursor for replayable model change-feed publication.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"runtime":{"type":"text","required":true,"_meta":{"required":true}},"position":{"type":"integer","required":false,"default":0,"_meta":{"default":0}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_cursors","api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_webhook_cursor_identity","columns":["tenantId","runtime"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookCursor","collectionExportName":"WebhookCursorCollection","validationRules":[{"field":"runtime","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_cursors","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_cursors\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"runtime\\" TEXT NOT NULL,\\n \\"position\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"runtime":{"type":"TEXT","notNull":true,"unique":false},"position":{"type":"INTEGER","notNull":false,"unique":false,"default":0}},"indexes":[{"name":"_smrt_webhook_cursors_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_cursor_identity","columns":["tenant_id","runtime"],"unique":true},{"name":"_smrt_webhook_cursors_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"ba1ea19d"},"importPath":"@happyvertical/smrt-webhooks"}},"moduleType":"smrt","recipes":[{"id":"integrations.webhooks","className":"WebhooksRecipe","label":"Outbound webhooks","summary":"Deliver signed model events and inspect delivery attempts from a server.","synonyms":[],"models":["@happyvertical/smrt-webhooks:WebhookSubscription","@happyvertical/smrt-webhooks:WebhookDelivery","@happyvertical/smrt-webhooks:WebhookDeliveryTask","@happyvertical/smrt-webhooks:WebhookCursor"],"nav":[],"requires":[],"group":{"id":"integrations","label":"Integrations","summary":"Connect your application to external services."},"help":{"markdown":"## Overview\\n\\nSend signed event notifications to an external HTTPS endpoint. Administrators\\ncan pause subscriptions and inspect delivery attempts. This integration requires\\na server and a running background worker.\\n\\n## Tasks\\n\\n- Add a subscription with a public HTTPS endpoint, signing secret, and event/model filters.\\n- Disable a subscription to cancel unsent deliveries.\\n- Inspect the delivery log, then retry failed deliveries after correcting their endpoint.\\n- Ask the endpoint owner to verify signatures and deduplicate delivery IDs.\\n","fieldRefs":[]},"surfaces":[{"kind":"settings-panel","export":"@happyvertical/smrt-webhooks/svelte#WebhookAdmin","label":"Outbound webhooks"}],"runtime":"server","demo":{"mode":"server","reasons":["The recipe declares runtime \\"server\\".","Needs a server because its package does not build for a browser: Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.; and its package depends on @happyvertical/smrt-jobs, @happyvertical/smrt-tenancy, which do not build for a browser (#3615, #3624, #3627, #3717)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3615","#3624","#3627","#3717"],"reason":"Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.","via":["@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
|
|
7
|
+
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-webhooks","packageVersion":"0.57.39","objects":{"@happyvertical/smrt-webhooks:WebhookSubscription":{"name":"webhooksubscription","className":"WebhookSubscription","qualifiedName":"@happyvertical/smrt-webhooks:WebhookSubscription","collection":"webhooksubscriptions","description":"Server-only subscription. Hosts authorize administration before accessing it.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"url":{"type":"text","required":true,"_meta":{"required":true}},"events":{"type":"json","required":false,"_meta":{}},"models":{"type":"json","required":false,"_meta":{}},"secret":{"type":"text","required":true,"_meta":{"sensitive":true,"required":true},"sensitive":true},"enabled":{"type":"boolean","required":false,"default":true,"_meta":{"default":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_webhook_subscriptions","api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookSubscription","collectionExportName":"WebhookSubscriptionCollection","validationRules":[{"field":"url","rule":"required","fieldType":"text"},{"field":"secret","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_subscriptions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_subscriptions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"url\\" TEXT NOT NULL,\\n \\"events\\" JSON,\\n \\"models\\" JSON,\\n \\"secret\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"url":{"type":"TEXT","notNull":true,"unique":false},"events":{"type":"JSON","notNull":false,"unique":false},"models":{"type":"JSON","notNull":false,"unique":false},"secret":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"_smrt_webhook_subscriptions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_subscriptions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4e9ebebe"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection":{"name":"webhooksubscriptioncollection","className":"WebhookSubscriptionCollection","qualifiedName":"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection","collection":"webhooksubscriptions","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_subscriptions","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"WebhookSubscription","exportName":"WebhookSubscriptionCollection","collectionExportName":"WebhookSubscriptionCollectionCollection","schema":{"tableName":"_smrt_webhook_subscriptions","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_subscriptions\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"url\\" TEXT NOT NULL,\\n \\"events\\" JSON,\\n \\"models\\" JSON,\\n \\"secret\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN DEFAULT TRUE\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"url":{"type":"TEXT","notNull":true,"unique":false},"events":{"type":"JSON","notNull":false,"unique":false},"models":{"type":"JSON","notNull":false,"unique":false},"secret":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"_smrt_webhook_subscriptions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_subscriptions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4e9ebebe"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDelivery":{"name":"webhookdelivery","className":"WebhookDelivery","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDelivery","collection":"webhookdeliveries","description":"Durable outbox and safe outcome metadata; payload never appears in public projections.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"subscriptionId":{"type":"foreignKey","required":false,"related":"WebhookSubscription","_meta":{"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"eventId":{"type":"text","required":true,"_meta":{"required":true}},"payload":{"type":"text","required":true,"_meta":{"required":true,"sensitive":true},"sensitive":true},"status":{"type":"text","required":false,"default":"pending","_meta":{"default":"pending"}},"generation":{"type":"integer","required":false,"default":0,"_meta":{"default":0}},"attempts":{"type":"integer","required":false,"default":0,"_meta":{"default":0}},"responseStatus":{"type":"integer","required":false,"_meta":{"nullable":true}},"error":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseToken":{"type":"text","required":false,"_meta":{"nullable":true,"sensitive":true},"sensitive":true},"leaseUntil":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_deliveries","api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_webhook_delivery_identity","columns":["tenantId","subscriptionId","eventId"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookDelivery","collectionExportName":"WebhookDeliveryCollection","validationRules":[{"field":"eventId","rule":"required","fieldType":"text"},{"field":"payload","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"subscription_id\\" UUID,\\n \\"event_id\\" TEXT NOT NULL,\\n \\"payload\\" TEXT NOT NULL,\\n \\"status\\" TEXT DEFAULT 'pending',\\n \\"generation\\" INTEGER DEFAULT 0,\\n \\"attempts\\" INTEGER DEFAULT 0,\\n \\"response_status\\" INTEGER,\\n \\"error\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_until\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"subscription_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"_smrt_webhook_subscriptions","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"event_id":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"pending"},"generation":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"response_status":{"type":"INTEGER","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_until":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_webhook_deliveries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_identity","columns":["tenant_id","subscription_id","event_id"],"unique":true},{"name":"_smrt_webhook_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_webhook_deliveries_subscription_id_idx","columns":["subscription_id"]}],"version":"17d7bd27"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDeliveryCollection":{"name":"webhookdeliverycollection","className":"WebhookDeliveryCollection","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDeliveryCollection","collection":"webhookdeliveries","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_deliveries","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"WebhookDelivery","exportName":"WebhookDeliveryCollection","collectionExportName":"WebhookDeliveryCollectionCollection","schema":{"tableName":"_smrt_webhook_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"subscription_id\\" UUID,\\n \\"event_id\\" TEXT NOT NULL,\\n \\"payload\\" TEXT NOT NULL,\\n \\"status\\" TEXT DEFAULT 'pending',\\n \\"generation\\" INTEGER DEFAULT 0,\\n \\"attempts\\" INTEGER DEFAULT 0,\\n \\"response_status\\" INTEGER,\\n \\"error\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_until\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"subscription_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"_smrt_webhook_subscriptions","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"event_id":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"pending"},"generation":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"attempts":{"type":"INTEGER","notNull":false,"unique":false,"default":0},"response_status":{"type":"INTEGER","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_until":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_webhook_deliveries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_identity","columns":["tenant_id","subscription_id","event_id"],"unique":true},{"name":"_smrt_webhook_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_webhook_deliveries_subscription_id_idx","columns":["subscription_id"]}],"version":"17d7bd27"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookDeliveryTask":{"name":"webhookdeliverytask","className":"WebhookDeliveryTask","qualifiedName":"@happyvertical/smrt-webhooks:WebhookDeliveryTask","collection":"webhookdeliverytasks","description":"Jobs queue entry point; persisted arguments contain IDs only.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{},"methods":{"deliver":{"name":"deliver","async":true,"parameters":[{"name":"args","type":"Record<string, unknown>","optional":true},{"name":"_context","type":"JobExecutionContext","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_webhook_delivery_tasks","api":false,"cli":false,"mcp":false},"extends":"SmrtObject","exportName":"WebhookDeliveryTask","collectionExportName":"WebhookDeliveryTaskCollection","schema":{"tableName":"_smrt_webhook_delivery_tasks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_delivery_tasks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"}},"indexes":[{"name":"_smrt_webhook_delivery_tasks_slug_context_idx","columns":["slug","context"],"unique":true},{"name":"_smrt_webhook_delivery_tasks_created_at_idx","columns":["created_at"]}],"version":"17ba7c43"},"importPath":"@happyvertical/smrt-webhooks"},"@happyvertical/smrt-webhooks:WebhookCursor":{"name":"webhookcursor","className":"WebhookCursor","qualifiedName":"@happyvertical/smrt-webhooks:WebhookCursor","collection":"webhookcursors","description":"Tenant-bound durable cursor for replayable model change-feed publication.","filePath":"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts","packageName":"@happyvertical/smrt-webhooks","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"runtime":{"type":"text","required":true,"_meta":{"required":true}},"position":{"type":"integer","required":false,"default":0,"_meta":{"default":0}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_webhook_cursors","api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_webhook_cursor_identity","columns":["tenantId","runtime"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"WebhookCursor","collectionExportName":"WebhookCursorCollection","validationRules":[{"field":"runtime","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_webhook_cursors","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_webhook_cursors\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"runtime\\" TEXT NOT NULL,\\n \\"position\\" INTEGER DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"runtime":{"type":"TEXT","notNull":true,"unique":false},"position":{"type":"INTEGER","notNull":false,"unique":false,"default":0}},"indexes":[{"name":"_smrt_webhook_cursors_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"_smrt_webhook_cursor_identity","columns":["tenant_id","runtime"],"unique":true},{"name":"_smrt_webhook_cursors_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"ba1ea19d"},"importPath":"@happyvertical/smrt-webhooks"}},"moduleType":"smrt","recipes":[{"id":"integrations.webhooks","className":"WebhooksRecipe","label":"Outbound webhooks","summary":"Deliver signed model events and inspect delivery attempts from a server.","synonyms":[],"models":["@happyvertical/smrt-webhooks:WebhookSubscription","@happyvertical/smrt-webhooks:WebhookDelivery","@happyvertical/smrt-webhooks:WebhookDeliveryTask","@happyvertical/smrt-webhooks:WebhookCursor"],"nav":[],"requires":[],"group":{"id":"integrations","label":"Integrations","summary":"Connect your application to external services."},"help":{"markdown":"## Overview\\n\\nSend signed event notifications to an external HTTPS endpoint. Administrators\\ncan pause subscriptions and inspect delivery attempts. This integration requires\\na server and a running background worker.\\n\\n## Tasks\\n\\n- Add a subscription with a public HTTPS endpoint, signing secret, and event/model filters.\\n- Disable a subscription to cancel unsent deliveries.\\n- Inspect the delivery log, then retry failed deliveries after correcting their endpoint.\\n- Ask the endpoint owner to verify signatures and deduplicate delivery IDs.\\n","fieldRefs":[]},"surfaces":[{"kind":"settings-panel","export":"@happyvertical/smrt-webhooks/svelte#WebhookAdmin","label":"Outbound webhooks"}],"runtime":"server","demo":{"mode":"server","reasons":["The recipe declares runtime \\"server\\".","Needs a server because its package does not build for a browser: Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.; and its package depends on @happyvertical/smrt-jobs, @happyvertical/smrt-tenancy, which do not build for a browser (#3615, #3624, #3627, #3717)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3615","#3624","#3627","#3717"],"reason":"Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.","via":["@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/change-feed.ts
|
|
10
10
|
var WebhookHistoryExpiredError = class extends Error {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/change-feed.ts","../src/events.ts","../src/recipes.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"packageVersion\\\":\\\"0.57.37\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\":{\\\"name\\\":\\\"webhooksubscription\\\",\\\"className\\\":\\\"WebhookSubscription\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\",\\\"collection\\\":\\\"webhooksubscriptions\\\",\\\"description\\\":\\\"Server-only subscription. Hosts authorize administration before accessing it.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"url\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"events\\\":{\\\"type\\\":\\\"json\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"models\\\":{\\\"type\\\":\\\"json\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"secret\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sensitive\\\":true,\\\"required\\\":true},\\\"sensitive\\\":true},\\\"enabled\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":false,\\\"default\\\":true,\\\"_meta\\\":{\\\"default\\\":true}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookSubscription\\\",\\\"collectionExportName\\\":\\\"WebhookSubscriptionCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"url\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"secret\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_subscriptions\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"url\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"events\\\\\\\" JSON,\\\\n \\\\\\\"models\\\\\\\" JSON,\\\\n \\\\\\\"secret\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"enabled\\\\\\\" BOOLEAN DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"url\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"events\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"models\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"secret\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_subscriptions_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_subscriptions_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"4e9ebebe\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection\\\":{\\\"name\\\":\\\"webhooksubscriptioncollection\\\",\\\"className\\\":\\\"WebhookSubscriptionCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection\\\",\\\"collection\\\":\\\"webhooksubscriptions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"WebhookSubscription\\\",\\\"exportName\\\":\\\"WebhookSubscriptionCollection\\\",\\\"collectionExportName\\\":\\\"WebhookSubscriptionCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_subscriptions\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"url\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"events\\\\\\\" JSON,\\\\n \\\\\\\"models\\\\\\\" JSON,\\\\n \\\\\\\"secret\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"enabled\\\\\\\" BOOLEAN DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"url\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"events\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"models\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"secret\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_subscriptions_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_subscriptions_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"4e9ebebe\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\":{\\\"name\\\":\\\"webhookdelivery\\\",\\\"className\\\":\\\"WebhookDelivery\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\",\\\"collection\\\":\\\"webhookdeliveries\\\",\\\"description\\\":\\\"Durable outbox and safe outcome metadata; payload never appears in public projections.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"subscriptionId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"WebhookSubscription\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"eventId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"payload\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true,\\\"sensitive\\\":true},\\\"sensitive\\\":true},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"pending\\\",\\\"_meta\\\":{\\\"default\\\":\\\"pending\\\"}},\\\"generation\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}},\\\"attempts\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}},\\\"responseStatus\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"error\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"leaseToken\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true,\\\"sensitive\\\":true},\\\"sensitive\\\":true},\\\"leaseUntil\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}}},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"subscriptionId\\\",\\\"eventId\\\"],\\\"unique\\\":true}],\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookDelivery\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"eventId\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"payload\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_deliveries\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"subscription_id\\\\\\\" UUID,\\\\n \\\\\\\"event_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"payload\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"status\\\\\\\" TEXT DEFAULT 'pending',\\\\n \\\\\\\"generation\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"attempts\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"response_status\\\\\\\" INTEGER,\\\\n \\\\\\\"error\\\\\\\" TEXT,\\\\n \\\\\\\"lease_token\\\\\\\" TEXT,\\\\n \\\\\\\"lease_until\\\\\\\" TIMESTAMP\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"subscription_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"event_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"payload\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"pending\\\"},\\\"generation\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"attempts\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"response_status\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"error\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_token\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_until\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_deliveries_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"subscription_id\\\",\\\"event_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_deliveries_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"_smrt_webhook_deliveries_subscription_id_idx\\\",\\\"columns\\\":[\\\"subscription_id\\\"]}],\\\"version\\\":\\\"17d7bd27\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDeliveryCollection\\\":{\\\"name\\\":\\\"webhookdeliverycollection\\\",\\\"className\\\":\\\"WebhookDeliveryCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDeliveryCollection\\\",\\\"collection\\\":\\\"webhookdeliveries\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"WebhookDelivery\\\",\\\"exportName\\\":\\\"WebhookDeliveryCollection\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_deliveries\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"subscription_id\\\\\\\" UUID,\\\\n \\\\\\\"event_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"payload\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"status\\\\\\\" TEXT DEFAULT 'pending',\\\\n \\\\\\\"generation\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"attempts\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"response_status\\\\\\\" INTEGER,\\\\n \\\\\\\"error\\\\\\\" TEXT,\\\\n \\\\\\\"lease_token\\\\\\\" TEXT,\\\\n \\\\\\\"lease_until\\\\\\\" TIMESTAMP\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"subscription_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"event_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"payload\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"pending\\\"},\\\"generation\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"attempts\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"response_status\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"error\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_token\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_until\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_deliveries_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"subscription_id\\\",\\\"event_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_deliveries_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"_smrt_webhook_deliveries_subscription_id_idx\\\",\\\"columns\\\":[\\\"subscription_id\\\"]}],\\\"version\\\":\\\"17d7bd27\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\":{\\\"name\\\":\\\"webhookdeliverytask\\\",\\\"className\\\":\\\"WebhookDeliveryTask\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\",\\\"collection\\\":\\\"webhookdeliverytasks\\\",\\\"description\\\":\\\"Jobs queue entry point; persisted arguments contain IDs only.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"deliver\\\":{\\\"name\\\":\\\"deliver\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"args\\\",\\\"type\\\":\\\"Record<string, unknown>\\\",\\\"optional\\\":true},{\\\"name\\\":\\\"_context\\\",\\\"type\\\":\\\"JobExecutionContext\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_delivery_tasks\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookDeliveryTask\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryTaskCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_delivery_tasks\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_delivery_tasks\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_delivery_tasks_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_tasks_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"17ba7c43\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\":{\\\"name\\\":\\\"webhookcursor\\\",\\\"className\\\":\\\"WebhookCursor\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\",\\\"collection\\\":\\\"webhookcursors\\\",\\\"description\\\":\\\"Tenant-bound durable cursor for replayable model change-feed publication.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"runtime\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"position\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}}},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_cursors\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_cursor_identity\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"runtime\\\"],\\\"unique\\\":true}],\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookCursor\\\",\\\"collectionExportName\\\":\\\"WebhookCursorCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"runtime\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_cursors\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_cursors\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"runtime\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"position\\\\\\\" INTEGER DEFAULT 0\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"runtime\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_cursors_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_cursor_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"runtime\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_cursors_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"ba1ea19d\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"recipes\\\":[{\\\"id\\\":\\\"integrations.webhooks\\\",\\\"className\\\":\\\"WebhooksRecipe\\\",\\\"label\\\":\\\"Outbound webhooks\\\",\\\"summary\\\":\\\"Deliver signed model events and inspect delivery attempts from a server.\\\",\\\"synonyms\\\":[],\\\"models\\\":[\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\",\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\",\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\",\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\"],\\\"nav\\\":[],\\\"requires\\\":[],\\\"group\\\":{\\\"id\\\":\\\"integrations\\\",\\\"label\\\":\\\"Integrations\\\",\\\"summary\\\":\\\"Connect your application to external services.\\\"},\\\"help\\\":{\\\"markdown\\\":\\\"## Overview\\\\n\\\\nSend signed event notifications to an external HTTPS endpoint. Administrators\\\\ncan pause subscriptions and inspect delivery attempts. This integration requires\\\\na server and a running background worker.\\\\n\\\\n## Tasks\\\\n\\\\n- Add a subscription with a public HTTPS endpoint, signing secret, and event/model filters.\\\\n- Disable a subscription to cancel unsent deliveries.\\\\n- Inspect the delivery log, then retry failed deliveries after correcting their endpoint.\\\\n- Ask the endpoint owner to verify signatures and deduplicate delivery IDs.\\\\n\\\",\\\"fieldRefs\\\":[]},\\\"surfaces\\\":[{\\\"kind\\\":\\\"settings-panel\\\",\\\"export\\\":\\\"@happyvertical/smrt-webhooks/svelte#WebhookAdmin\\\",\\\"label\\\":\\\"Outbound webhooks\\\"}],\\\"runtime\\\":\\\"server\\\",\\\"demo\\\":{\\\"mode\\\":\\\"server\\\",\\\"reasons\\\":[\\\"The recipe declares runtime \\\\\\\"server\\\\\\\".\\\",\\\"Needs a server because its package does not build for a browser: Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.; and its package depends on @happyvertical/smrt-jobs, @happyvertical/smrt-tenancy, which do not build for a browser (#3615, #3624, #3627, #3717).\\\"]}}],\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-jobs\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3615\\\",\\\"#3624\\\",\\\"#3627\\\",\\\"#3717\\\"],\\\"reason\\\":\\\"Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.\\\",\\\"via\\\":[\\\"@happyvertical/smrt-jobs\\\",\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","import { randomUUID } from 'node:crypto';\nimport { getChangesSince } from '@happyvertical/smrt-core';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { WebhookDispatcher } from './dispatcher.js';\n\nexport class WebhookHistoryExpiredError extends Error {\n constructor(readonly resumeCursor: number) {\n super(\n 'Webhook change history expired; reconcile source state before resetting the cursor.',\n );\n }\n}\n\n/** Durable, replayable lifecycle publication from SMRT's committed change feed.\n * Host scheduling calls poll() in each authenticated tenant context. The mapping\n * is an explicit physical-table allowlist; shared STI tables represent the base model.\n */\nexport class WebhookChangeFeed {\n constructor(\n private readonly dispatcher: WebhookDispatcher,\n private readonly models: Readonly<Record<string, string>>,\n ) {\n if (\n !Object.keys(models).length ||\n Object.keys(models).some((table) => table.startsWith('_smrt_')) ||\n Object.values(models).some((model) => !model)\n )\n throw new Error('Explicit application model tables are required.');\n }\n /** Cursor only advances after all matched changes have durable outbox/jobs.\n * A crash or partial failure replays the same stable identities on the next poll.\n * History pruning fails loudly: the host must reconcile source state before reset.\n */\n async poll(): Promise<{ cursor: number; published: number }> {\n const tenantId = requireTenant().tenantId;\n const { db, runtime } = this.dispatcher.store;\n const now = new Date().toISOString();\n const id = randomUUID();\n await db.query(\n `INSERT INTO _smrt_webhook_cursors (id, slug, context, tenant_id, runtime, position, created_at, updated_at)\n VALUES (?, ?, '', ?, ?, 0, ?, ?) ON CONFLICT (tenant_id, runtime) DO NOTHING`,\n id,\n id,\n tenantId,\n runtime,\n now,\n now,\n );\n const rows = await db.query(\n 'SELECT position FROM _smrt_webhook_cursors WHERE tenant_id = ? AND runtime = ?',\n tenantId,\n runtime,\n );\n const since = Number((rows.rows[0] as { position: number }).position);\n const page = await getChangesSince(db, {\n since,\n tenantId,\n tables: Object.keys(this.models),\n limit: 500,\n });\n if (page.resyncRequired)\n throw new WebhookHistoryExpiredError(page.resyncCursor ?? page.cursor);\n let published = 0;\n for (const change of page.changes) {\n // Core's tenant feed includes global rows; webhooks deliberately do not.\n if (\n change.tenantId !== tenantId ||\n !change.rowId ||\n !this.models[change.table]\n )\n continue;\n await this.dispatcher.dispatch({\n id: `change:${change.seq}`,\n event: change.operation,\n model: this.models[change.table],\n occurredAt: change.timestamp,\n data: { id: change.rowId },\n });\n published++;\n }\n await db.query(\n 'UPDATE _smrt_webhook_cursors SET position = ?, updated_at = ? WHERE tenant_id = ? AND runtime = ? AND position = ?',\n page.cursor,\n new Date().toISOString(),\n tenantId,\n runtime,\n since,\n );\n return { cursor: page.cursor, published };\n }\n /** Operator-only recovery: call after reconciling retained source state externally. */\n async resumeAfterResync(cursor: number): Promise<void> {\n if (!Number.isSafeInteger(cursor) || cursor < 0)\n throw new Error('Invalid webhook change cursor.');\n const tenantId = requireTenant().tenantId;\n const { db, runtime } = this.dispatcher.store;\n await db.query(\n 'UPDATE _smrt_webhook_cursors SET position = ? WHERE tenant_id = ? AND runtime = ?',\n cursor,\n tenantId,\n runtime,\n );\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport {\n GlobalInterceptors,\n type InterceptorContext,\n type SmrtObject,\n} from '@happyvertical/smrt-core';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { WebhookDispatcher } from './dispatcher.js';\n\n/** Opt-in lifecycle binding. Explicit model allowlist prevents recursive/internal data publication.\n * Payloads contain record identity only; use dispatch() for authorized custom projections.\n */\nexport function registerWebhookModelEvents(\n dispatcher: WebhookDispatcher,\n models: readonly string[],\n): () => void {\n const internal = new Set([\n 'WebhookSubscription',\n 'WebhookDelivery',\n 'WebhookDeliveryTask',\n 'WebhookCursor',\n 'SmrtJob',\n 'SmrtJobEvent',\n 'SmrtWorker',\n ]);\n if (models.some((name) => internal.has(name.split(':').at(-1) ?? name))) {\n throw new Error(\n 'Internal queue and webhook models cannot publish lifecycle webhooks.',\n );\n }\n const allowed = new Set(models);\n const pending = new WeakMap<\n InterceptorContext,\n { event: string; id: string }\n >();\n const matches = (context: InterceptorContext) =>\n allowed.has(context.qualifiedClassName ?? context.className);\n async function emit(\n instance: SmrtObject,\n context: InterceptorContext,\n event: string,\n ) {\n if (!matches(context)) return;\n const tenantId = requireTenant().tenantId;\n if ('tenantId' in instance && instance.tenantId !== tenantId)\n throw new Error('Webhook model tenant mismatch.');\n await dispatcher.dispatch({\n id: pending.get(context)?.id ?? randomUUID(),\n event,\n model: context.qualifiedClassName ?? context.className,\n occurredAt: context.timestamp.toISOString(),\n data: { id: instance.id },\n });\n }\n const interceptor = {\n name: `webhook-model-events:${randomUUID()}`,\n beforeSave(instance: SmrtObject, context: InterceptorContext) {\n if (matches(context))\n pending.set(context, {\n event: instance.isPersisted ? 'update' : 'create',\n id: randomUUID(),\n });\n },\n async afterSave(instance: SmrtObject, context: InterceptorContext) {\n await emit(instance, context, pending.get(context)?.event ?? 'update');\n },\n async afterDelete(instance: SmrtObject, context: InterceptorContext) {\n await emit(instance, context, 'delete');\n },\n };\n GlobalInterceptors.register(interceptor);\n return () => {\n GlobalInterceptors.unregister(interceptor);\n };\n}\n","import { SmrtRecipe } from '@happyvertical/smrt-core';\nimport {\n WebhookCursor,\n WebhookDelivery,\n WebhookDeliveryTask,\n WebhookSubscription,\n} from './models.js';\n/** Server-backed administration through authenticated host callbacks. */\nexport class WebhooksRecipe extends SmrtRecipe {\n static id = 'integrations.webhooks';\n static label = 'Outbound webhooks';\n static summary =\n 'Deliver signed model events and inspect delivery attempts from a server.';\n static models = [\n WebhookSubscription,\n WebhookDelivery,\n WebhookDeliveryTask,\n WebhookCursor,\n ];\n static group = {\n id: 'integrations',\n label: 'Integrations',\n summary: 'Connect your application to external services.',\n };\n static runtime = 'server' as const;\n static surfaces = [\n {\n kind: 'settings-panel',\n export: '@happyvertical/smrt-webhooks/svelte#WebhookAdmin',\n label: 'Outbound webhooks',\n },\n ] as const;\n // Administration uses the authorized service, never generated model CRUD.\n static nav = [];\n static help = './webhooks.recipe.md';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/change-feed.ts","../src/events.ts","../src/recipes.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"packageVersion\\\":\\\"0.57.39\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\":{\\\"name\\\":\\\"webhooksubscription\\\",\\\"className\\\":\\\"WebhookSubscription\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\",\\\"collection\\\":\\\"webhooksubscriptions\\\",\\\"description\\\":\\\"Server-only subscription. Hosts authorize administration before accessing it.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"url\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"events\\\":{\\\"type\\\":\\\"json\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"models\\\":{\\\"type\\\":\\\"json\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"secret\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sensitive\\\":true,\\\"required\\\":true},\\\"sensitive\\\":true},\\\"enabled\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":false,\\\"default\\\":true,\\\"_meta\\\":{\\\"default\\\":true}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookSubscription\\\",\\\"collectionExportName\\\":\\\"WebhookSubscriptionCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"url\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"secret\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_subscriptions\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"url\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"events\\\\\\\" JSON,\\\\n \\\\\\\"models\\\\\\\" JSON,\\\\n \\\\\\\"secret\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"enabled\\\\\\\" BOOLEAN DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"url\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"events\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"models\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"secret\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_subscriptions_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_subscriptions_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"4e9ebebe\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection\\\":{\\\"name\\\":\\\"webhooksubscriptioncollection\\\",\\\"className\\\":\\\"WebhookSubscriptionCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookSubscriptionCollection\\\",\\\"collection\\\":\\\"webhooksubscriptions\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"WebhookSubscription\\\",\\\"exportName\\\":\\\"WebhookSubscriptionCollection\\\",\\\"collectionExportName\\\":\\\"WebhookSubscriptionCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_subscriptions\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"url\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"events\\\\\\\" JSON,\\\\n \\\\\\\"models\\\\\\\" JSON,\\\\n \\\\\\\"secret\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"enabled\\\\\\\" BOOLEAN DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"url\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"events\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"models\\\":{\\\"type\\\":\\\"JSON\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"secret\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"enabled\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_subscriptions_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_subscriptions_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"4e9ebebe\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\":{\\\"name\\\":\\\"webhookdelivery\\\",\\\"className\\\":\\\"WebhookDelivery\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\",\\\"collection\\\":\\\"webhookdeliveries\\\",\\\"description\\\":\\\"Durable outbox and safe outcome metadata; payload never appears in public projections.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"subscriptionId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"WebhookSubscription\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"eventId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"payload\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true,\\\"sensitive\\\":true},\\\"sensitive\\\":true},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"pending\\\",\\\"_meta\\\":{\\\"default\\\":\\\"pending\\\"}},\\\"generation\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}},\\\"attempts\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}},\\\"responseStatus\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"error\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"leaseToken\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true,\\\"sensitive\\\":true},\\\"sensitive\\\":true},\\\"leaseUntil\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}}},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"subscriptionId\\\",\\\"eventId\\\"],\\\"unique\\\":true}],\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookDelivery\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"eventId\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"},{\\\"field\\\":\\\"payload\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_deliveries\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"subscription_id\\\\\\\" UUID,\\\\n \\\\\\\"event_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"payload\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"status\\\\\\\" TEXT DEFAULT 'pending',\\\\n \\\\\\\"generation\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"attempts\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"response_status\\\\\\\" INTEGER,\\\\n \\\\\\\"error\\\\\\\" TEXT,\\\\n \\\\\\\"lease_token\\\\\\\" TEXT,\\\\n \\\\\\\"lease_until\\\\\\\" TIMESTAMP\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"subscription_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"event_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"payload\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"pending\\\"},\\\"generation\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"attempts\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"response_status\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"error\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_token\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_until\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_deliveries_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"subscription_id\\\",\\\"event_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_deliveries_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"_smrt_webhook_deliveries_subscription_id_idx\\\",\\\"columns\\\":[\\\"subscription_id\\\"]}],\\\"version\\\":\\\"17d7bd27\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDeliveryCollection\\\":{\\\"name\\\":\\\"webhookdeliverycollection\\\",\\\"className\\\":\\\"WebhookDeliveryCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDeliveryCollection\\\",\\\"collection\\\":\\\"webhookdeliveries\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"WebhookDelivery\\\",\\\"exportName\\\":\\\"WebhookDeliveryCollection\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_deliveries\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_deliveries\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"subscription_id\\\\\\\" UUID,\\\\n \\\\\\\"event_id\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"payload\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"status\\\\\\\" TEXT DEFAULT 'pending',\\\\n \\\\\\\"generation\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"attempts\\\\\\\" INTEGER DEFAULT 0,\\\\n \\\\\\\"response_status\\\\\\\" INTEGER,\\\\n \\\\\\\"error\\\\\\\" TEXT,\\\\n \\\\\\\"lease_token\\\\\\\" TEXT,\\\\n \\\\\\\"lease_until\\\\\\\" TIMESTAMP\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"subscription_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"_smrt_webhook_subscriptions\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"RESTRICT\\\"}},\\\"event_id\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"payload\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":\\\"pending\\\"},\\\"generation\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"attempts\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0},\\\"response_status\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"error\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_token\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"lease_until\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_deliveries_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"subscription_id\\\",\\\"event_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_deliveries_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"_smrt_webhook_deliveries_subscription_id_idx\\\",\\\"columns\\\":[\\\"subscription_id\\\"]}],\\\"version\\\":\\\"17d7bd27\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\":{\\\"name\\\":\\\"webhookdeliverytask\\\",\\\"className\\\":\\\"WebhookDeliveryTask\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\",\\\"collection\\\":\\\"webhookdeliverytasks\\\",\\\"description\\\":\\\"Jobs queue entry point; persisted arguments contain IDs only.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"deliver\\\":{\\\"name\\\":\\\"deliver\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"args\\\",\\\"type\\\":\\\"Record<string, unknown>\\\",\\\"optional\\\":true},{\\\"name\\\":\\\"_context\\\",\\\"type\\\":\\\"JobExecutionContext\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_delivery_tasks\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookDeliveryTask\\\",\\\"collectionExportName\\\":\\\"WebhookDeliveryTaskCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_delivery_tasks\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_delivery_tasks\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_delivery_tasks_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_delivery_tasks_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"17ba7c43\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"},\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\":{\\\"name\\\":\\\"webhookcursor\\\",\\\"className\\\":\\\"WebhookCursor\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\",\\\"collection\\\":\\\"webhookcursors\\\",\\\"description\\\":\\\"Tenant-bound durable cursor for replayable model change-feed publication.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/webhooks/src/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-webhooks\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"runtime\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"required\\\":true}},\\\"position\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"_meta\\\":{\\\"default\\\":0}}},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_webhook_cursors\\\",\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_cursor_identity\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"runtime\\\"],\\\"unique\\\":true}],\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"WebhookCursor\\\",\\\"collectionExportName\\\":\\\"WebhookCursorCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"runtime\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_webhook_cursors\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_webhook_cursors\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"runtime\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"position\\\\\\\" INTEGER DEFAULT 0\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"runtime\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"default\\\":0}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_webhook_cursors_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_cursor_identity\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"runtime\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_webhook_cursors_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"ba1ea19d\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-webhooks\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"recipes\\\":[{\\\"id\\\":\\\"integrations.webhooks\\\",\\\"className\\\":\\\"WebhooksRecipe\\\",\\\"label\\\":\\\"Outbound webhooks\\\",\\\"summary\\\":\\\"Deliver signed model events and inspect delivery attempts from a server.\\\",\\\"synonyms\\\":[],\\\"models\\\":[\\\"@happyvertical/smrt-webhooks:WebhookSubscription\\\",\\\"@happyvertical/smrt-webhooks:WebhookDelivery\\\",\\\"@happyvertical/smrt-webhooks:WebhookDeliveryTask\\\",\\\"@happyvertical/smrt-webhooks:WebhookCursor\\\"],\\\"nav\\\":[],\\\"requires\\\":[],\\\"group\\\":{\\\"id\\\":\\\"integrations\\\",\\\"label\\\":\\\"Integrations\\\",\\\"summary\\\":\\\"Connect your application to external services.\\\"},\\\"help\\\":{\\\"markdown\\\":\\\"## Overview\\\\n\\\\nSend signed event notifications to an external HTTPS endpoint. Administrators\\\\ncan pause subscriptions and inspect delivery attempts. This integration requires\\\\na server and a running background worker.\\\\n\\\\n## Tasks\\\\n\\\\n- Add a subscription with a public HTTPS endpoint, signing secret, and event/model filters.\\\\n- Disable a subscription to cancel unsent deliveries.\\\\n- Inspect the delivery log, then retry failed deliveries after correcting their endpoint.\\\\n- Ask the endpoint owner to verify signatures and deduplicate delivery IDs.\\\\n\\\",\\\"fieldRefs\\\":[]},\\\"surfaces\\\":[{\\\"kind\\\":\\\"settings-panel\\\",\\\"export\\\":\\\"@happyvertical/smrt-webhooks/svelte#WebhookAdmin\\\",\\\"label\\\":\\\"Outbound webhooks\\\"}],\\\"runtime\\\":\\\"server\\\",\\\"demo\\\":{\\\"mode\\\":\\\"server\\\",\\\"reasons\\\":[\\\"The recipe declares runtime \\\\\\\"server\\\\\\\".\\\",\\\"Needs a server because its package does not build for a browser: Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.; and its package depends on @happyvertical/smrt-jobs, @happyvertical/smrt-tenancy, which do not build for a browser (#3615, #3624, #3627, #3717).\\\"]}}],\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-jobs\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3615\\\",\\\"#3624\\\",\\\"#3627\\\",\\\"#3717\\\"],\\\"reason\\\":\\\"Intentionally server-only outbound delivery: node:crypto HMAC and node:https/dns/net pinned transport enforce signing and SSRF protection. Browser hosts use the separate /svelte admin export with authenticated server callbacks.\\\",\\\"via\\\":[\\\"@happyvertical/smrt-jobs\\\",\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","import { randomUUID } from 'node:crypto';\nimport { getChangesSince } from '@happyvertical/smrt-core';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { WebhookDispatcher } from './dispatcher.js';\n\nexport class WebhookHistoryExpiredError extends Error {\n constructor(readonly resumeCursor: number) {\n super(\n 'Webhook change history expired; reconcile source state before resetting the cursor.',\n );\n }\n}\n\n/** Durable, replayable lifecycle publication from SMRT's committed change feed.\n * Host scheduling calls poll() in each authenticated tenant context. The mapping\n * is an explicit physical-table allowlist; shared STI tables represent the base model.\n */\nexport class WebhookChangeFeed {\n constructor(\n private readonly dispatcher: WebhookDispatcher,\n private readonly models: Readonly<Record<string, string>>,\n ) {\n if (\n !Object.keys(models).length ||\n Object.keys(models).some((table) => table.startsWith('_smrt_')) ||\n Object.values(models).some((model) => !model)\n )\n throw new Error('Explicit application model tables are required.');\n }\n /** Cursor only advances after all matched changes have durable outbox/jobs.\n * A crash or partial failure replays the same stable identities on the next poll.\n * History pruning fails loudly: the host must reconcile source state before reset.\n */\n async poll(): Promise<{ cursor: number; published: number }> {\n const tenantId = requireTenant().tenantId;\n const { db, runtime } = this.dispatcher.store;\n const now = new Date().toISOString();\n const id = randomUUID();\n await db.query(\n `INSERT INTO _smrt_webhook_cursors (id, slug, context, tenant_id, runtime, position, created_at, updated_at)\n VALUES (?, ?, '', ?, ?, 0, ?, ?) ON CONFLICT (tenant_id, runtime) DO NOTHING`,\n id,\n id,\n tenantId,\n runtime,\n now,\n now,\n );\n const rows = await db.query(\n 'SELECT position FROM _smrt_webhook_cursors WHERE tenant_id = ? AND runtime = ?',\n tenantId,\n runtime,\n );\n const since = Number((rows.rows[0] as { position: number }).position);\n const page = await getChangesSince(db, {\n since,\n tenantId,\n tables: Object.keys(this.models),\n limit: 500,\n });\n if (page.resyncRequired)\n throw new WebhookHistoryExpiredError(page.resyncCursor ?? page.cursor);\n let published = 0;\n for (const change of page.changes) {\n // Core's tenant feed includes global rows; webhooks deliberately do not.\n if (\n change.tenantId !== tenantId ||\n !change.rowId ||\n !this.models[change.table]\n )\n continue;\n await this.dispatcher.dispatch({\n id: `change:${change.seq}`,\n event: change.operation,\n model: this.models[change.table],\n occurredAt: change.timestamp,\n data: { id: change.rowId },\n });\n published++;\n }\n await db.query(\n 'UPDATE _smrt_webhook_cursors SET position = ?, updated_at = ? WHERE tenant_id = ? AND runtime = ? AND position = ?',\n page.cursor,\n new Date().toISOString(),\n tenantId,\n runtime,\n since,\n );\n return { cursor: page.cursor, published };\n }\n /** Operator-only recovery: call after reconciling retained source state externally. */\n async resumeAfterResync(cursor: number): Promise<void> {\n if (!Number.isSafeInteger(cursor) || cursor < 0)\n throw new Error('Invalid webhook change cursor.');\n const tenantId = requireTenant().tenantId;\n const { db, runtime } = this.dispatcher.store;\n await db.query(\n 'UPDATE _smrt_webhook_cursors SET position = ? WHERE tenant_id = ? AND runtime = ?',\n cursor,\n tenantId,\n runtime,\n );\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport {\n GlobalInterceptors,\n type InterceptorContext,\n type SmrtObject,\n} from '@happyvertical/smrt-core';\nimport { requireTenant } from '@happyvertical/smrt-tenancy';\nimport type { WebhookDispatcher } from './dispatcher.js';\n\n/** Opt-in lifecycle binding. Explicit model allowlist prevents recursive/internal data publication.\n * Payloads contain record identity only; use dispatch() for authorized custom projections.\n */\nexport function registerWebhookModelEvents(\n dispatcher: WebhookDispatcher,\n models: readonly string[],\n): () => void {\n const internal = new Set([\n 'WebhookSubscription',\n 'WebhookDelivery',\n 'WebhookDeliveryTask',\n 'WebhookCursor',\n 'SmrtJob',\n 'SmrtJobEvent',\n 'SmrtWorker',\n ]);\n if (models.some((name) => internal.has(name.split(':').at(-1) ?? name))) {\n throw new Error(\n 'Internal queue and webhook models cannot publish lifecycle webhooks.',\n );\n }\n const allowed = new Set(models);\n const pending = new WeakMap<\n InterceptorContext,\n { event: string; id: string }\n >();\n const matches = (context: InterceptorContext) =>\n allowed.has(context.qualifiedClassName ?? context.className);\n async function emit(\n instance: SmrtObject,\n context: InterceptorContext,\n event: string,\n ) {\n if (!matches(context)) return;\n const tenantId = requireTenant().tenantId;\n if ('tenantId' in instance && instance.tenantId !== tenantId)\n throw new Error('Webhook model tenant mismatch.');\n await dispatcher.dispatch({\n id: pending.get(context)?.id ?? randomUUID(),\n event,\n model: context.qualifiedClassName ?? context.className,\n occurredAt: context.timestamp.toISOString(),\n data: { id: instance.id },\n });\n }\n const interceptor = {\n name: `webhook-model-events:${randomUUID()}`,\n beforeSave(instance: SmrtObject, context: InterceptorContext) {\n if (matches(context))\n pending.set(context, {\n event: instance.isPersisted ? 'update' : 'create',\n id: randomUUID(),\n });\n },\n async afterSave(instance: SmrtObject, context: InterceptorContext) {\n await emit(instance, context, pending.get(context)?.event ?? 'update');\n },\n async afterDelete(instance: SmrtObject, context: InterceptorContext) {\n await emit(instance, context, 'delete');\n },\n };\n GlobalInterceptors.register(interceptor);\n return () => {\n GlobalInterceptors.unregister(interceptor);\n };\n}\n","import { SmrtRecipe } from '@happyvertical/smrt-core';\nimport {\n WebhookCursor,\n WebhookDelivery,\n WebhookDeliveryTask,\n WebhookSubscription,\n} from './models.js';\n/** Server-backed administration through authenticated host callbacks. */\nexport class WebhooksRecipe extends SmrtRecipe {\n static id = 'integrations.webhooks';\n static label = 'Outbound webhooks';\n static summary =\n 'Deliver signed model events and inspect delivery attempts from a server.';\n static models = [\n WebhookSubscription,\n WebhookDelivery,\n WebhookDeliveryTask,\n WebhookCursor,\n ];\n static group = {\n id: 'integrations',\n label: 'Integrations',\n summary: 'Connect your application to external services.',\n };\n static runtime = 'server' as const;\n static surfaces = [\n {\n kind: 'settings-panel',\n export: '@happyvertical/smrt-webhooks/svelte#WebhookAdmin',\n label: 'Outbound webhooks',\n },\n ] as const;\n // Administration uses the authorized service, never generated model CRUD.\n static nav = [];\n static help = './webhooks.recipe.md';\n}\n"],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"timestamp": 0,
|
|
4
4
|
"packageName": "@happyvertical/smrt-webhooks",
|
|
5
|
-
"packageVersion": "0.57.
|
|
5
|
+
"packageVersion": "0.57.39",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-webhooks:WebhookSubscription": {
|
|
8
8
|
"name": "webhooksubscription",
|
|
@@ -69,7 +69,13 @@
|
|
|
69
69
|
"save": {
|
|
70
70
|
"name": "save",
|
|
71
71
|
"async": true,
|
|
72
|
-
"parameters": [
|
|
72
|
+
"parameters": [
|
|
73
|
+
{
|
|
74
|
+
"name": "options",
|
|
75
|
+
"type": "SmrtSaveOptions",
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
78
|
+
],
|
|
73
79
|
"returnType": "Promise<this>",
|
|
74
80
|
"isStatic": false,
|
|
75
81
|
"isPublic": true
|
package/dist/models.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtCollection, SmrtObject, type SmrtSaveOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { type JobExecutionContext } from '@happyvertical/smrt-jobs';
|
|
3
3
|
/** Server-only subscription. Hosts authorize administration before accessing it. */
|
|
4
4
|
export declare class WebhookSubscription extends SmrtObject {
|
|
@@ -8,7 +8,7 @@ export declare class WebhookSubscription extends SmrtObject {
|
|
|
8
8
|
models: string[];
|
|
9
9
|
secret: string;
|
|
10
10
|
enabled: boolean;
|
|
11
|
-
save(): Promise<this>;
|
|
11
|
+
save(options?: SmrtSaveOptions): Promise<this>;
|
|
12
12
|
}
|
|
13
13
|
export declare class WebhookSubscriptionCollection extends SmrtCollection<WebhookSubscription> {
|
|
14
14
|
static readonly _itemClass: typeof WebhookSubscription;
|
package/dist/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EACd,UAAU,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EACd,UAAU,EACV,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAIlC,oFAAoF;AACpF,qBAOa,mBAAoB,SAAQ,UAAU;IACrC,QAAQ,EAAE,MAAM,CAAM;IACO,GAAG,EAAE,MAAM,CAAM;IACjC,MAAM,EAAE,MAAM,EAAE,CAAM;IACtB,MAAM,EAAE,MAAM,EAAE,CAAM;IACW,MAAM,EAAE,MAAM,CAAM;IACnC,OAAO,EAAE,OAAO,CAAQ;IAEpD,IAAI,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;CAc9D;AACD,qBAAa,6BAA8B,SAAQ,cAAc,CAAC,mBAAmB,CAAC;IACpF,MAAM,CAAC,QAAQ,CAAC,UAAU,6BAAuB;CAClD;AAED,6FAA6F;AAC7F,qBAca,eAAgB,SAAQ,UAAU;IACjC,QAAQ,EAAE,MAAM,CAAM;IAKlC,cAAc,EAAE,MAAM,CAAM;IACa,OAAO,EAAE,MAAM,CAAM;IACJ,OAAO,EAAE,MAAM,CACpE;IACwC,MAAM,EAAE,MAAM,CAAa;IAChC,UAAU,EAAE,MAAM,CAAK;IACvB,QAAQ,EAAE,MAAM,CAAK;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAClE;IACkC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IACX,UAAU,EAChE,MAAM,GACN,IAAI,CAAQ;IAC6B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;CAC7E;AACD,qBAAa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;CAC9C;AAED,oEAAoE;AACpE,qBAMa,mBAAoB,SAAQ,UAAU;IAE3C,OAAO,CACX,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC;CAIjB;AAED,gFAAgF;AAChF,qBAca,aAAc,SAAQ,UAAU;IAC/B,QAAQ,EAAE,MAAM,CAAM;IACO,OAAO,EAAE,MAAM,CAAM;IACtB,QAAQ,EAAE,MAAM,CAAK;CAC9D"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-webhooks",
|
|
6
|
-
"packageVersion": "0.57.
|
|
6
|
+
"packageVersion": "0.57.39",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "7c5a49d54fde07da0b6657fd4c6dbcce436d75dc1873ea613d9bbb89b8ea3e3f",
|
|
11
|
+
"packageJson": "871f7d06588b7fbf04081b98190f6bea93a27124fcfa1754a5b2398176565e2b",
|
|
12
12
|
"agents": "f67b2a340106cf4b4b44c6b2bf918086286236ac3cf34b03b24b1db6e17b40a5"
|
|
13
13
|
},
|
|
14
14
|
"exports": [
|
|
@@ -101,6 +101,9 @@
|
|
|
101
101
|
{
|
|
102
102
|
"name": "save",
|
|
103
103
|
"async": true,
|
|
104
|
+
"params": [
|
|
105
|
+
"options?: SmrtSaveOptions"
|
|
106
|
+
],
|
|
104
107
|
"returns": "Promise<this>"
|
|
105
108
|
}
|
|
106
109
|
],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-webhooks",
|
|
3
3
|
"author": "HappyVertical",
|
|
4
|
-
"version": "0.57.
|
|
4
|
+
"version": "0.57.39",
|
|
5
5
|
"smrtJsdoc": "strict",
|
|
6
6
|
"description": "Outbound webhook subscriptions, signed delivery, and durable retries",
|
|
7
7
|
"type": "module",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"./manifest.json": "./dist/manifest.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@happyvertical/smrt-core": "0.57.
|
|
34
|
-
"@happyvertical/smrt-jobs": "0.57.
|
|
35
|
-
"@happyvertical/smrt-tenancy": "0.57.
|
|
36
|
-
"@happyvertical/sql": "^0.
|
|
37
|
-
"@happyvertical/smrt-ui": "0.57.
|
|
33
|
+
"@happyvertical/smrt-core": "0.57.39",
|
|
34
|
+
"@happyvertical/smrt-jobs": "0.57.39",
|
|
35
|
+
"@happyvertical/smrt-tenancy": "0.57.39",
|
|
36
|
+
"@happyvertical/sql": "^0.111.0",
|
|
37
|
+
"@happyvertical/smrt-ui": "0.57.39"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"svelte": "^5.57.0"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@happyvertical/smrt-vitest": "0.57.
|
|
48
|
+
"@happyvertical/smrt-vitest": "0.57.39",
|
|
49
49
|
"@sveltejs/package": "^2.5.8",
|
|
50
50
|
"@types/node": "24.13.6",
|
|
51
51
|
"svelte": "^5.57.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"admin-413jve1I.js","names":[],"sources":["../../src/security.ts","../../src/models.ts","../../src/admin.ts"],"sourcesContent":["import { lookup } from 'node:dns/promises';\nimport { request } from 'node:https';\nimport { isIP } from 'node:net';\n\n/** Conservative public-unicast allowlist. Transition/mapped IPv6 is excluded. */\nexport function isPublicAddress(address: string): boolean {\n if (isIP(address) === 4) {\n const [a, b, c] = address.split('.').map(Number);\n return !(\n a === 0 ||\n a === 10 ||\n a === 127 ||\n a >= 224 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && (b === 0 || b === 168 || (b === 88 && c === 99))) ||\n (a === 198 && (b === 18 || b === 19 || (b === 51 && c === 100))) ||\n (a === 203 && b === 0 && c === 113)\n );\n }\n if (isIP(address) !== 6) return false;\n const normalized = new URL(`https://[${address}]/`).hostname.slice(1, -1);\n // Only global unicast 2000::/3; exclude IETF special-purpose, documentation,\n // 6to4, and the newer documentation block (3fff::/20).\n const first = Number.parseInt(normalized.split(':')[0], 16);\n return (\n first >= 0x2000 &&\n first < 0x4000 &&\n first !== 0x2002 &&\n !(\n first === 0x2001 &&\n Number.parseInt(normalized.split(':')[1] || '0', 16) < 0x200\n ) &&\n !normalized.startsWith('2001:db8:') &&\n first !== 0x3fff\n );\n}\n\nexport function assertWebhookUrl(value: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error('Webhook URL must be an absolute HTTPS URL.');\n }\n const host = url.hostname.replace(/^\\[|\\]$/g, '');\n if (\n url.protocol !== 'https:' ||\n url.username ||\n url.password ||\n url.hash ||\n (url.port && url.port !== '443') ||\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n (isIP(host) && !isPublicAddress(host))\n ) {\n throw new Error(\n 'Webhook URL must use public HTTPS on port 443 without credentials or a fragment.',\n );\n }\n return url;\n}\n\nexport function assertHmacSecret(secret: string): void {\n if (\n typeof secret !== 'string' ||\n Buffer.byteLength(secret) < 32 ||\n Buffer.byteLength(secret) > 4096\n ) {\n throw new Error('Webhook signing secret must contain 32 to 4096 bytes.');\n }\n}\n\nexport interface WebhookRequest {\n url: string;\n body: string;\n headers: Record<string, string>;\n}\nexport type WebhookTransport = (request: WebhookRequest) => Promise<number>;\n\n/** Validate every DNS answer, then pin the socket lookup; TLS still verifies the hostname.\n * No proxy, redirects, pooled sockets, response body buffering, or second DNS lookup.\n */\nexport const sendPublicWebhook: WebhookTransport = async ({\n url,\n body,\n headers,\n}) => {\n const target = assertWebhookUrl(url);\n const host = target.hostname.replace(/^\\[|\\]$/g, '');\n let dnsTimer: ReturnType<typeof setTimeout> | undefined;\n const answers = isIP(host)\n ? [{ address: host, family: isIP(host) }]\n : await Promise.race([\n lookup(host, { all: true, verbatim: true }),\n new Promise<never>((_, reject) => {\n dnsTimer = setTimeout(\n () => reject(new Error('Webhook DNS timeout.')),\n 5000,\n );\n dnsTimer.unref();\n }),\n ]).finally(() => clearTimeout(dnsTimer));\n if (\n !answers.length ||\n answers.some(({ address }) => !isPublicAddress(address))\n ) {\n throw new Error('Webhook destination is not public.');\n }\n const pinned = answers[0];\n return new Promise<number>((resolve, reject) => {\n const req = request(\n target,\n {\n method: 'POST',\n agent: false,\n headers: {\n ...headers,\n 'content-length': Buffer.byteLength(body).toString(),\n },\n lookup: (_hostname, options, callback) => {\n if (options.all) callback(null, [pinned]);\n else callback(null, pinned.address, pinned.family);\n },\n },\n (response) => {\n const status = response.statusCode ?? 0;\n response.destroy();\n resolve(status);\n },\n );\n const timer = setTimeout(\n () => req.destroy(new Error('Webhook request timeout.')),\n 10000,\n );\n req.on('close', () => clearTimeout(timer));\n req.on('error', () => reject(new Error('Webhook transport failed.')));\n req.end(body);\n });\n};\n","import {\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type JobExecutionContext,\n} from '@happyvertical/smrt-jobs';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { assertHmacSecret, assertWebhookUrl } from './security.js';\n\n/** Server-only subscription. Hosts authorize administration before accessing it. */\n@smrt({\n tableName: '_smrt_webhook_subscriptions',\n api: false,\n cli: false,\n mcp: false,\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookSubscription extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @field({ type: 'text', required: true }) url: string = '';\n @field({ type: 'json' }) events: string[] = [];\n @field({ type: 'json' }) models: string[] = [];\n @field({ type: 'text', sensitive: true, required: true }) secret: string = '';\n @field({ type: 'boolean', default: true }) enabled: boolean = true;\n\n override async save(): Promise<this> {\n assertWebhookUrl(this.url);\n assertHmacSecret(this.secret);\n for (const values of [this.events, this.models]) {\n if (\n !Array.isArray(values) ||\n values.length > 100 ||\n values.some((v) => typeof v !== 'string' || !v || v.length > 200)\n ) {\n throw new Error('Webhook filters must be lists of nonempty names.');\n }\n }\n return super.save();\n }\n}\nexport class WebhookSubscriptionCollection extends SmrtCollection<WebhookSubscription> {\n static readonly _itemClass = WebhookSubscription;\n}\n\n/** Durable outbox and safe outcome metadata; payload never appears in public projections. */\n@smrt({\n tableName: '_smrt_webhook_deliveries',\n api: false,\n cli: false,\n mcp: false,\n indexes: [\n {\n name: '_smrt_webhook_delivery_identity',\n columns: ['tenantId', 'subscriptionId', 'eventId'],\n unique: true,\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookDelivery extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @foreignKey(WebhookSubscription, {\n onUpdate: 'RESTRICT',\n onDelete: 'RESTRICT',\n })\n subscriptionId: string = '';\n @field({ type: 'text', required: true }) eventId: string = '';\n @field({ type: 'text', required: true, sensitive: true }) payload: string =\n '';\n @field({ type: 'text', default: 'pending' }) status: string = 'pending';\n @field({ type: 'integer', default: 0 }) generation: number = 0;\n @field({ type: 'integer', default: 0 }) attempts: number = 0;\n @field({ type: 'integer', nullable: true }) responseStatus: number | null =\n null;\n @field({ type: 'text', nullable: true }) error: string | null = null;\n @field({ type: 'text', nullable: true, sensitive: true }) leaseToken:\n | string\n | null = null;\n @field({ type: 'datetime', nullable: true }) leaseUntil: Date | null = null;\n}\nexport class WebhookDeliveryCollection extends SmrtCollection<WebhookDelivery> {\n static readonly _itemClass = WebhookDelivery;\n}\n\n/** Jobs queue entry point; persisted arguments contain IDs only. */\n@smrt({\n tableName: '_smrt_webhook_delivery_tasks',\n api: false,\n cli: false,\n mcp: false,\n})\nexport class WebhookDeliveryTask extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @backgroundEligible()\n async deliver(\n args: Record<string, unknown> = {},\n _context?: JobExecutionContext,\n ): Promise<void> {\n const { runWebhookDeliveryJob } = await import('./dispatcher.js');\n await runWebhookDeliveryJob(args);\n }\n}\n\n/** Tenant-bound durable cursor for replayable model change-feed publication. */\n@smrt({\n tableName: '_smrt_webhook_cursors',\n api: false,\n cli: false,\n mcp: false,\n indexes: [\n {\n name: '_smrt_webhook_cursor_identity',\n columns: ['tenantId', 'runtime'],\n unique: true,\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class WebhookCursor extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-webhooks\";\n @tenantId() tenantId: string = '';\n @field({ type: 'text', required: true }) runtime: string = '';\n @field({ type: 'integer', default: 0 }) position: number = 0;\n}\n","import { requireTenant } from '@happyvertical/smrt-tenancy';\nimport { WebhookSubscriptionCollection } from './models.js';\nimport type { WebhookStore } from './store.js';\nimport type {\n WebhookDeliveryRecord,\n WebhookSubscriptionRecord,\n} from './types.js';\nexport interface WebhookAdminView {\n subscriptions: WebhookSubscriptionRecord[];\n deliveries: WebhookDeliveryRecord[];\n}\n/** Allowlisted projection; never spread persistence objects into a browser response. */\nexport function webhookAdminView(\n subscriptions: WebhookSubscriptionRecord[],\n deliveries: WebhookDeliveryRecord[],\n): WebhookAdminView {\n return {\n subscriptions: subscriptions.map((s) => ({\n id: s.id,\n tenantId: s.tenantId,\n url: s.url,\n models: s.models,\n events: s.events,\n enabled: s.enabled,\n })),\n deliveries: deliveries.map((d) => ({\n id: d.id,\n tenantId: d.tenantId,\n subscriptionId: d.subscriptionId,\n eventId: d.eventId,\n status: d.status,\n attempts: d.attempts,\n responseStatus: d.responseStatus,\n error: d.error,\n })),\n };\n}\n/** Host supplies its administrator permission check and authenticated tenant context. */\nexport class WebhookAdminService {\n constructor(\n private readonly store: WebhookStore,\n private readonly authorize: () => Promise<boolean>,\n ) {}\n private async guard() {\n requireTenant();\n if (!(await this.authorize()))\n throw new Error('Webhook administration forbidden.');\n }\n async view(): Promise<WebhookAdminView> {\n await this.guard();\n await this.store.reconcile();\n const subscriptions = await this.store.subscriptions();\n const deliveries = await this.store.deliveries();\n return webhookAdminView(\n subscriptions.map((s) => ({ ...s, id: s.id ?? '' })),\n deliveries.map((d) => ({ ...d, id: d.id ?? '' })),\n );\n }\n async create(input: {\n url: string;\n events: string[];\n models: string[];\n secret: string;\n }): Promise<void> {\n await this.guard();\n const collection = await WebhookSubscriptionCollection.create({\n db: this.store.db,\n });\n const sub = await collection.create({\n tenantId: requireTenant().tenantId,\n url: input.url,\n events: input.events,\n models: input.models,\n secret: input.secret,\n enabled: true,\n });\n await sub.save();\n }\n async toggle(id: string, enabled: boolean): Promise<void> {\n await this.guard();\n if (typeof enabled !== 'boolean') throw new Error('Invalid enabled value.');\n const sub = await this.store.subscription(id);\n if (!sub) throw new Error('Webhook subscription not found.');\n sub.enabled = enabled;\n await sub.save();\n }\n async replay(id: string): Promise<boolean> {\n await this.guard();\n return this.store.replay(id);\n }\n}\n"],"mappings":"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"}
|