@objectstack/plugin-webhooks 17.0.0-rc.0 → 17.0.0-rc.2
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/CHANGELOG.md +632 -0
- package/dist/{chunk-6EPMRZ7I.js → chunk-3QGZLM3T.js} +8 -3
- package/dist/chunk-3QGZLM3T.js.map +1 -0
- package/dist/{chunk-QUTQSOQC.cjs → chunk-JQUVS5KK.cjs} +8 -3
- package/dist/chunk-JQUVS5KK.cjs.map +1 -0
- package/dist/index.cjs +182 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +138 -19
- package/dist/index.js.map +1 -1
- package/dist/schema.cjs +2 -2
- package/dist/schema.d.cts +721 -548
- package/dist/schema.d.ts +721 -548
- package/dist/schema.js +1 -1
- package/dist/{translations-Y3CXWOTM.js → translations-2VGD4XRF.js} +17 -9
- package/dist/translations-2VGD4XRF.js.map +1 -0
- package/dist/{translations-CBQRUIS5.cjs → translations-BWS57U2V.cjs} +17 -9
- package/dist/translations-BWS57U2V.cjs.map +1 -0
- package/package.json +12 -6
- package/.turbo/turbo-build.log +0 -36
- package/dist/chunk-6EPMRZ7I.js.map +0 -1
- package/dist/chunk-QUTQSOQC.cjs.map +0 -1
- package/dist/translations-CBQRUIS5.cjs.map +0 -1
- package/dist/translations-Y3CXWOTM.js.map +0 -1
- package/scripts/i18n-extract.config.ts +0 -34
- package/src/auto-enqueuer.test.ts +0 -431
- package/src/auto-enqueuer.ts +0 -383
- package/src/bootstrap-declared-webhooks.test.ts +0 -283
- package/src/bootstrap-declared-webhooks.ts +0 -205
- package/src/index.ts +0 -29
- package/src/schema.ts +0 -21
- package/src/sys-webhook.object.ts +0 -231
- package/src/translations/bundle-ownership.test.ts +0 -41
- package/src/translations/en.objects.generated.ts +0 -101
- package/src/translations/es-ES.objects.generated.ts +0 -101
- package/src/translations/index.ts +0 -23
- package/src/translations/ja-JP.objects.generated.ts +0 -101
- package/src/translations/zh-CN.objects.generated.ts +0 -101
- package/src/webhook-outbox-plugin.ts +0 -305
- package/src/webhook-provenance.ts +0 -86
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sys-webhook.object.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_webhook — Outbound HTTP integration configuration (runtime).\n *\n * Persists a single {@link Webhook} envelope per row so administrators\n * can author, enable/disable, and edit webhook subscriptions from the\n * Studio UI without code changes. The canonical Zod schema for the\n * `definition_json` envelope lives at `@objectstack/spec/automation/webhook`.\n *\n * ## Two authoring doors, one row\n * Rows land here two ways, distinguished by the `managed_by` provenance column:\n * - **admin** — created/edited directly through this object's CRUD UI.\n * - **package** — declared in code (`defineStack({ webhooks })` /\n * `defineWebhook()`) and materialized on boot by\n * `bootstrapDeclaredWebhooks` (#3461). Re-seeded every boot, but an admin\n * edit stamps `customized: true` and freezes the row (seed-not-clobber,\n * mirrors `sys_sharing_rule` #2909).\n *\n * One row per `name`. This plugin's {@link AutoEnqueuer} loads active rows on\n * boot + on `sys_webhook:changed` events, and turns matching `data.record.*`\n * events into deliveries on the shared `service-messaging` HTTP outbox\n * (ADR-0018 M3 — `sys_http_delivery`, drained by the messaging dispatcher).\n *\n * Ownership (ADR-0029 K2.a): this object is **owned by\n * `@objectstack/plugin-webhooks`** — the plugin that consumes these rows. It\n * used to live in the `@objectstack/platform-objects` monolith and be imported\n * here; the definition now lives with its owner so the plugin ships both data\n * and behavior as one unit.\n *\n * Platform-wide on purpose: every project (standalone, single-tenant,\n * cloud) can integrate with external systems (Slack, Stripe, internal\n * services) the same way.\n *\n * @namespace sys\n */\nexport const SysWebhook = ObjectSchema.create({\n name: 'sys_webhook',\n label: 'Webhook',\n pluralLabel: 'Webhooks',\n icon: 'webhook',\n isSystem: true,\n managedBy: 'config',\n // Authoring a webhook from the UI requires a structured form for the\n // headers / auth / retry / payload blocks — the generic JSON textarea\n // is acceptable as a v1 until a dedicated builder lands. Re-enable\n // create/edit/delete so admins can at least toggle `active` and edit\n // simple URL/method fields without round-tripping through code.\n userActions: { create: true, edit: true, delete: true, import: false },\n description: 'Outbound HTTP webhook subscription. Declared in code via defineStack({ webhooks }) / defineWebhook() (materialized into rows on boot) or authored directly in the Studio editor; dispatched by the webhook auto-enqueuer onto the shared HTTP outbox.',\n displayNameField: 'name',\n nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)\n titleFormat: '{label}',\n highlightFields: ['name', 'object_name', 'url', 'active', 'updated_at'],\n\n listViews: {\n active: {\n type: 'grid',\n name: 'active',\n label: 'Active',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: true }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n inactive: {\n type: 'grid',\n name: 'inactive',\n label: 'Inactive',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: false }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n by_object: {\n type: 'grid',\n name: 'by_object',\n label: 'By Object',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['object_name', 'label', 'url', 'active', 'updated_at'],\n sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],\n grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },\n pagination: { pageSize: 100 },\n },\n all_webhooks: {\n type: 'grid',\n name: 'all_webhooks',\n label: 'All',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n },\n\n fields: {\n id: Field.text({ label: 'Webhook ID', required: true, readonly: true, group: 'System' }),\n\n name: Field.text({\n label: 'Name',\n required: true,\n maxLength: 100,\n description: 'Unique snake_case name — referenced in logs and audit',\n group: 'Definition',\n }),\n\n label: Field.text({\n label: 'Display Label',\n required: false,\n maxLength: 200,\n group: 'Definition',\n }),\n\n object_name: Field.text({\n label: 'Object',\n required: false,\n maxLength: 100,\n // Object picker (same widget as sys_sharing_rule) instead of a free-text\n // machine name. Falls back to a text input when the widget is unavailable.\n widget: 'object-ref',\n description: 'Short object name whose record events (create/update/delete) fire this webhook',\n group: 'Definition',\n }),\n\n triggers: Field.select(\n ['create', 'update', 'delete', 'bulk_update', 'bulk_delete'],\n {\n label: 'Triggers',\n required: false,\n // Multi-select instead of a hand-typed comma-separated string. Stored as\n // an array; the auto-enqueuer parser also tolerates the legacy\n // comma-separated / JSON-string forms so existing rows keep working.\n multiple: true,\n // [#4639] `bulk_*` fire on predicate writes (`multi: true`), whose\n // delivery carries `matched` instead of a record — opt-in precisely\n // because that body is a different shape. Kept in step with\n // `WebhookTriggerType` (`@objectstack/spec/automation`), which is the\n // contract the auto-enqueuer validates against.\n description: 'Record events that fire this webhook (bulk_* deliver a count, not a record)',\n group: 'Definition',\n },\n ),\n\n url: Field.text({\n label: 'Target URL',\n required: true,\n maxLength: 2048,\n description: 'External endpoint that receives the POST',\n group: 'Definition',\n }),\n\n method: Field.select(\n ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n {\n label: 'HTTP Method',\n required: true,\n // Select instead of free text. Option values are lowercased by the\n // Field.select helper (get/post/…); the auto-enqueuer upper-cases the\n // resolved method before delivery, so existing 'POST' rows and the\n // lowercase option values both normalise correctly.\n defaultValue: 'post',\n description: 'HTTP method used for the callback request',\n group: 'Definition',\n },\n ),\n\n description: Field.textarea({ label: 'Description', required: false, group: 'Definition' }),\n\n active: Field.boolean({\n label: 'Active',\n required: true,\n defaultValue: true,\n description: 'Inactive webhooks are skipped by the dispatcher',\n group: 'Definition',\n }),\n\n definition_json: Field.textarea({\n label: 'Definition',\n required: true,\n description: 'Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config',\n group: 'Definition',\n }),\n\n // ── Provenance (#3461 — record-authoritative seed-not-clobber) ──\n // Mirrors sys_sharing_rule (#2909). Both columns are `readonly`: the\n // engine strips them from non-system payloads (forge/clear-proof), while\n // bootstrapDeclaredWebhooks and the provenance stamp hook write with\n // isSystem. Deliberately NOT a write gate: webhooks are a first-class admin\n // authoring/tuning surface — admins may edit or deactivate a package row;\n // the seeder simply stops overwriting it once `customized` is stamped.\n managed_by: Field.select(\n ['platform', 'package', 'admin'],\n {\n label: 'Managed By',\n required: false,\n readonly: true,\n defaultValue: 'admin',\n description:\n 'Record provenance: platform = framework built-in / package = app/package-declared ' +\n '(boot-seeded from defineStack webhooks) / admin = created in Setup.',\n group: 'System',\n },\n ),\n\n customized: Field.boolean({\n label: 'Customized',\n required: false,\n readonly: true,\n defaultValue: false,\n description:\n 'Set when an admin edits a package-declared webhook; boot seeding will no longer ' +\n 'overwrite the row (a deactivated noisy webhook survives redeploys). Meaningless on admin rows.',\n group: 'System',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n required: true,\n defaultValue: 'NOW()',\n readonly: true,\n group: 'System',\n }),\n\n updated_at: Field.datetime({ label: 'Updated At', required: false, group: 'System' }),\n },\n\n indexes: [\n { fields: ['name'], unique: true },\n { fields: ['object_name'] },\n { fields: ['active', 'object_name'] },\n ],\n});\n"],"mappings":"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","names":[]}
|
|
@@ -86,7 +86,7 @@ var SysWebhook = _data.ObjectSchema.create({
|
|
|
86
86
|
group: "Definition"
|
|
87
87
|
}),
|
|
88
88
|
triggers: _data.Field.select(
|
|
89
|
-
["create", "update", "delete"],
|
|
89
|
+
["create", "update", "delete", "bulk_update", "bulk_delete"],
|
|
90
90
|
{
|
|
91
91
|
label: "Triggers",
|
|
92
92
|
required: false,
|
|
@@ -94,7 +94,12 @@ var SysWebhook = _data.ObjectSchema.create({
|
|
|
94
94
|
// an array; the auto-enqueuer parser also tolerates the legacy
|
|
95
95
|
// comma-separated / JSON-string forms so existing rows keep working.
|
|
96
96
|
multiple: true,
|
|
97
|
-
|
|
97
|
+
// [#4639] `bulk_*` fire on predicate writes (`multi: true`), whose
|
|
98
|
+
// delivery carries `matched` instead of a record — opt-in precisely
|
|
99
|
+
// because that body is a different shape. Kept in step with
|
|
100
|
+
// `WebhookTriggerType` (`@objectstack/spec/automation`), which is the
|
|
101
|
+
// contract the auto-enqueuer validates against.
|
|
102
|
+
description: "Record events that fire this webhook (bulk_* deliver a count, not a record)",
|
|
98
103
|
group: "Definition"
|
|
99
104
|
}
|
|
100
105
|
),
|
|
@@ -178,4 +183,4 @@ var SysWebhook = _data.ObjectSchema.create({
|
|
|
178
183
|
|
|
179
184
|
|
|
180
185
|
exports.SysWebhook = SysWebhook;
|
|
181
|
-
//# sourceMappingURL=chunk-
|
|
186
|
+
//# sourceMappingURL=chunk-JQUVS5KK.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/objectstack/objectstack/packages/plugins/plugin-webhooks/dist/chunk-JQUVS5KK.cjs","../src/sys-webhook.object.ts"],"names":[],"mappings":"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","file":"/home/runner/work/objectstack/objectstack/packages/plugins/plugin-webhooks/dist/chunk-JQUVS5KK.cjs","sourcesContent":[null,"// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_webhook — Outbound HTTP integration configuration (runtime).\n *\n * Persists a single {@link Webhook} envelope per row so administrators\n * can author, enable/disable, and edit webhook subscriptions from the\n * Studio UI without code changes. The canonical Zod schema for the\n * `definition_json` envelope lives at `@objectstack/spec/automation/webhook`.\n *\n * ## Two authoring doors, one row\n * Rows land here two ways, distinguished by the `managed_by` provenance column:\n * - **admin** — created/edited directly through this object's CRUD UI.\n * - **package** — declared in code (`defineStack({ webhooks })` /\n * `defineWebhook()`) and materialized on boot by\n * `bootstrapDeclaredWebhooks` (#3461). Re-seeded every boot, but an admin\n * edit stamps `customized: true` and freezes the row (seed-not-clobber,\n * mirrors `sys_sharing_rule` #2909).\n *\n * One row per `name`. This plugin's {@link AutoEnqueuer} loads active rows on\n * boot + on `sys_webhook:changed` events, and turns matching `data.record.*`\n * events into deliveries on the shared `service-messaging` HTTP outbox\n * (ADR-0018 M3 — `sys_http_delivery`, drained by the messaging dispatcher).\n *\n * Ownership (ADR-0029 K2.a): this object is **owned by\n * `@objectstack/plugin-webhooks`** — the plugin that consumes these rows. It\n * used to live in the `@objectstack/platform-objects` monolith and be imported\n * here; the definition now lives with its owner so the plugin ships both data\n * and behavior as one unit.\n *\n * Platform-wide on purpose: every project (standalone, single-tenant,\n * cloud) can integrate with external systems (Slack, Stripe, internal\n * services) the same way.\n *\n * @namespace sys\n */\nexport const SysWebhook = ObjectSchema.create({\n name: 'sys_webhook',\n label: 'Webhook',\n pluralLabel: 'Webhooks',\n icon: 'webhook',\n isSystem: true,\n managedBy: 'config',\n // Authoring a webhook from the UI requires a structured form for the\n // headers / auth / retry / payload blocks — the generic JSON textarea\n // is acceptable as a v1 until a dedicated builder lands. Re-enable\n // create/edit/delete so admins can at least toggle `active` and edit\n // simple URL/method fields without round-tripping through code.\n userActions: { create: true, edit: true, delete: true, import: false },\n description: 'Outbound HTTP webhook subscription. Declared in code via defineStack({ webhooks }) / defineWebhook() (materialized into rows on boot) or authored directly in the Studio editor; dispatched by the webhook auto-enqueuer onto the shared HTTP outbox.',\n displayNameField: 'name',\n nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)\n titleFormat: '{label}',\n highlightFields: ['name', 'object_name', 'url', 'active', 'updated_at'],\n\n listViews: {\n active: {\n type: 'grid',\n name: 'active',\n label: 'Active',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: true }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n inactive: {\n type: 'grid',\n name: 'inactive',\n label: 'Inactive',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: false }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n by_object: {\n type: 'grid',\n name: 'by_object',\n label: 'By Object',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['object_name', 'label', 'url', 'active', 'updated_at'],\n sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],\n grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },\n pagination: { pageSize: 100 },\n },\n all_webhooks: {\n type: 'grid',\n name: 'all_webhooks',\n label: 'All',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n },\n\n fields: {\n id: Field.text({ label: 'Webhook ID', required: true, readonly: true, group: 'System' }),\n\n name: Field.text({\n label: 'Name',\n required: true,\n maxLength: 100,\n description: 'Unique snake_case name — referenced in logs and audit',\n group: 'Definition',\n }),\n\n label: Field.text({\n label: 'Display Label',\n required: false,\n maxLength: 200,\n group: 'Definition',\n }),\n\n object_name: Field.text({\n label: 'Object',\n required: false,\n maxLength: 100,\n // Object picker (same widget as sys_sharing_rule) instead of a free-text\n // machine name. Falls back to a text input when the widget is unavailable.\n widget: 'object-ref',\n description: 'Short object name whose record events (create/update/delete) fire this webhook',\n group: 'Definition',\n }),\n\n triggers: Field.select(\n ['create', 'update', 'delete', 'bulk_update', 'bulk_delete'],\n {\n label: 'Triggers',\n required: false,\n // Multi-select instead of a hand-typed comma-separated string. Stored as\n // an array; the auto-enqueuer parser also tolerates the legacy\n // comma-separated / JSON-string forms so existing rows keep working.\n multiple: true,\n // [#4639] `bulk_*` fire on predicate writes (`multi: true`), whose\n // delivery carries `matched` instead of a record — opt-in precisely\n // because that body is a different shape. Kept in step with\n // `WebhookTriggerType` (`@objectstack/spec/automation`), which is the\n // contract the auto-enqueuer validates against.\n description: 'Record events that fire this webhook (bulk_* deliver a count, not a record)',\n group: 'Definition',\n },\n ),\n\n url: Field.text({\n label: 'Target URL',\n required: true,\n maxLength: 2048,\n description: 'External endpoint that receives the POST',\n group: 'Definition',\n }),\n\n method: Field.select(\n ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n {\n label: 'HTTP Method',\n required: true,\n // Select instead of free text. Option values are lowercased by the\n // Field.select helper (get/post/…); the auto-enqueuer upper-cases the\n // resolved method before delivery, so existing 'POST' rows and the\n // lowercase option values both normalise correctly.\n defaultValue: 'post',\n description: 'HTTP method used for the callback request',\n group: 'Definition',\n },\n ),\n\n description: Field.textarea({ label: 'Description', required: false, group: 'Definition' }),\n\n active: Field.boolean({\n label: 'Active',\n required: true,\n defaultValue: true,\n description: 'Inactive webhooks are skipped by the dispatcher',\n group: 'Definition',\n }),\n\n definition_json: Field.textarea({\n label: 'Definition',\n required: true,\n description: 'Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config',\n group: 'Definition',\n }),\n\n // ── Provenance (#3461 — record-authoritative seed-not-clobber) ──\n // Mirrors sys_sharing_rule (#2909). Both columns are `readonly`: the\n // engine strips them from non-system payloads (forge/clear-proof), while\n // bootstrapDeclaredWebhooks and the provenance stamp hook write with\n // isSystem. Deliberately NOT a write gate: webhooks are a first-class admin\n // authoring/tuning surface — admins may edit or deactivate a package row;\n // the seeder simply stops overwriting it once `customized` is stamped.\n managed_by: Field.select(\n ['platform', 'package', 'admin'],\n {\n label: 'Managed By',\n required: false,\n readonly: true,\n defaultValue: 'admin',\n description:\n 'Record provenance: platform = framework built-in / package = app/package-declared ' +\n '(boot-seeded from defineStack webhooks) / admin = created in Setup.',\n group: 'System',\n },\n ),\n\n customized: Field.boolean({\n label: 'Customized',\n required: false,\n readonly: true,\n defaultValue: false,\n description:\n 'Set when an admin edits a package-declared webhook; boot seeding will no longer ' +\n 'overwrite the row (a deactivated noisy webhook survives redeploys). Meaningless on admin rows.',\n group: 'System',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n required: true,\n defaultValue: 'NOW()',\n readonly: true,\n group: 'System',\n }),\n\n updated_at: Field.datetime({ label: 'Updated At', required: false, group: 'System' }),\n },\n\n indexes: [\n { fields: ['name'], unique: true },\n { fields: ['object_name'] },\n { fields: ['active', 'object_name'] },\n ],\n});\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkJQUVS5KKcjs = require('./chunk-JQUVS5KK.cjs');
|
|
4
4
|
|
|
5
5
|
// src/auto-enqueuer.ts
|
|
6
6
|
var AutoEnqueuer = class {
|
|
@@ -112,7 +112,7 @@ var AutoEnqueuer = class {
|
|
|
112
112
|
const unknown = normalized.filter((t) => !DISPATCHABLE_WEBHOOK_TRIGGERS.has(t));
|
|
113
113
|
if (unknown.length > 0) {
|
|
114
114
|
_optionalChain([this, 'access', _15 => _15.logger, 'access', _16 => _16.warn, 'optionalCall', _17 => _17(
|
|
115
|
-
`[webhook-auto-enqueuer] webhook '${_nullishCoalesce(row.name, () => ( row.id))}' declares trigger(s) the engine never emits: ${unknown.join(", ")} \u2014 ignored. Dispatchable triggers:
|
|
115
|
+
`[webhook-auto-enqueuer] webhook '${_nullishCoalesce(row.name, () => ( row.id))}' declares trigger(s) the engine never emits: ${unknown.join(", ")} \u2014 ignored. Dispatchable triggers: ${[...DISPATCHABLE_WEBHOOK_TRIGGERS].join(", ")}.`,
|
|
116
116
|
{ id: row.id, unknown }
|
|
117
117
|
)]);
|
|
118
118
|
}
|
|
@@ -120,6 +120,10 @@ var AutoEnqueuer = class {
|
|
|
120
120
|
normalized.filter((t) => DISPATCHABLE_WEBHOOK_TRIGGERS.has(t))
|
|
121
121
|
);
|
|
122
122
|
if (triggers.size === 0) {
|
|
123
|
+
_optionalChain([this, 'access', _18 => _18.logger, 'access', _19 => _19.warn, 'optionalCall', _20 => _20(
|
|
124
|
+
`[webhook-auto-enqueuer] webhook '${_nullishCoalesce(row.name, () => ( row.id))}' has no dispatchable triggers \u2014 it will NEVER fire (rule webhook/without-triggers): there is no manual fire path (#3196), so this row is dead while looking armed in Setup. Declare one of: ${[...DISPATCHABLE_WEBHOOK_TRIGGERS].join(", ")}, or set it inactive if it should be off.`,
|
|
125
|
+
{ id: row.id }
|
|
126
|
+
)]);
|
|
123
127
|
return null;
|
|
124
128
|
}
|
|
125
129
|
let defn = {};
|
|
@@ -154,9 +158,13 @@ var AutoEnqueuer = class {
|
|
|
154
158
|
* webhook persistence.
|
|
155
159
|
*/
|
|
156
160
|
handleEvent(event) {
|
|
157
|
-
if (!_optionalChain([event, 'access', _18 => _18.type, 'optionalAccess', _19 => _19.startsWith, 'call', _20 => _20("data.record.")])) return;
|
|
158
161
|
if (!event.object) return;
|
|
159
162
|
if (event.object === this.subscriptionsObject) return;
|
|
163
|
+
if (_optionalChain([event, 'access', _21 => _21.type, 'optionalAccess', _22 => _22.startsWith, 'call', _23 => _23("data.records.")])) {
|
|
164
|
+
this.handleBulkEvent(event);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!_optionalChain([event, 'access', _24 => _24.type, 'optionalAccess', _25 => _25.startsWith, 'call', _26 => _26("data.record.")])) return;
|
|
160
168
|
const action = event.type.slice("data.record.".length);
|
|
161
169
|
const trigger = mapActionToTrigger(action);
|
|
162
170
|
if (!trigger) return;
|
|
@@ -166,7 +174,14 @@ var AutoEnqueuer = class {
|
|
|
166
174
|
];
|
|
167
175
|
if (subs.length === 0) return;
|
|
168
176
|
const payload = _nullishCoalesce(event.payload, () => ( {}));
|
|
169
|
-
const recordId =
|
|
177
|
+
const recordId = payload.recordId;
|
|
178
|
+
if (typeof recordId !== "string" || recordId === "") {
|
|
179
|
+
_optionalChain([this, 'access', _27 => _27.logger, 'access', _28 => _28.warn, 'optionalCall', _29 => _29(
|
|
180
|
+
"[webhook-auto-enqueuer] dropping off-contract data event: payload is not a DataEvent (no top-level string `recordId`) \u2014 fix the producer",
|
|
181
|
+
{ type: event.type, object: event.object }
|
|
182
|
+
)]);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
170
185
|
const eventId = `${event.object}:${recordId}:${action}:${event.timestamp}`;
|
|
171
186
|
for (const sub of subs) {
|
|
172
187
|
if (!sub.triggers.has(trigger)) continue;
|
|
@@ -180,27 +195,105 @@ var AutoEnqueuer = class {
|
|
|
180
195
|
headers: sub.headers,
|
|
181
196
|
signingSecret: sub.secret,
|
|
182
197
|
timeoutMs: sub.timeoutMs,
|
|
198
|
+
// [#3946] Envelope keys are written LAST so the event payload
|
|
199
|
+
// cannot rewrite them. Behaviour-neutral for the engine's own
|
|
200
|
+
// publishers — since #4626 a `data.record.*` payload is a
|
|
201
|
+
// `DataEvent` (`id`, `type`, `object`, `recordId`, `changes?`,
|
|
202
|
+
// `after?`, `userId?`, `timestamp`), whose `object` /
|
|
203
|
+
// `recordId` / `timestamp` carry the SAME values written here
|
|
204
|
+
// and whose record fields stay nested under `after`. It is the
|
|
205
|
+
// shape that was wrong: a publisher that flattened record
|
|
206
|
+
// fields into the payload would have silently rewritten the
|
|
207
|
+
// `object` / `action` / `timestamp` a subscriber receives.
|
|
183
208
|
payload: {
|
|
209
|
+
...payload,
|
|
184
210
|
object: event.object,
|
|
185
211
|
recordId,
|
|
186
212
|
action,
|
|
187
|
-
timestamp: event.timestamp
|
|
188
|
-
|
|
213
|
+
timestamp: event.timestamp
|
|
214
|
+
}
|
|
215
|
+
}).catch(
|
|
216
|
+
(err) => _optionalChain([this, 'access', _30 => _30.logger, 'access', _31 => _31.warn, 'optionalCall', _32 => _32("[webhook-auto-enqueuer] enqueue failed", {
|
|
217
|
+
webhook: sub.name,
|
|
218
|
+
eventId,
|
|
219
|
+
err: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _33 => _33.message]), () => ( err))
|
|
220
|
+
})])
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Handler for aggregate `data.records.*` events — a predicate write
|
|
226
|
+
* (`multi: true`) that the driver reports only as an affected-row count
|
|
227
|
+
* (#4639).
|
|
228
|
+
*
|
|
229
|
+
* Deliberately NOT folded into {@link handleEvent}'s per-record path. The
|
|
230
|
+
* delivered body has no `recordId` and no record fields, so a subscriber
|
|
231
|
+
* to `update` that started receiving these would get a payload missing
|
|
232
|
+
* everything it reads — which is how the pre-#4626 `recordId: ''`
|
|
233
|
+
* fabrication broke consumers, just arriving from the other side. A
|
|
234
|
+
* webhook opts in with `bulk_update` / `bulk_delete`.
|
|
235
|
+
*/
|
|
236
|
+
handleBulkEvent(event) {
|
|
237
|
+
const action = event.type.slice("data.records.".length);
|
|
238
|
+
const trigger = mapBulkActionToTrigger(action);
|
|
239
|
+
if (!trigger) return;
|
|
240
|
+
const subs = [
|
|
241
|
+
..._nullishCoalesce(this.subscriptions.get(event.object), () => ( [])),
|
|
242
|
+
..._nullishCoalesce(this.subscriptions.get("*"), () => ( []))
|
|
243
|
+
];
|
|
244
|
+
if (subs.length === 0) return;
|
|
245
|
+
const payload = _nullishCoalesce(event.payload, () => ( {}));
|
|
246
|
+
const matched = payload.matched;
|
|
247
|
+
if (typeof matched !== "number" || !Number.isInteger(matched) || matched < 0) {
|
|
248
|
+
_optionalChain([this, 'access', _34 => _34.logger, 'access', _35 => _35.warn, 'optionalCall', _36 => _36(
|
|
249
|
+
"[webhook-auto-enqueuer] dropping off-contract bulk data event: payload is not a BulkDataEvent (no top-level non-negative integer `matched`) \u2014 fix the producer",
|
|
250
|
+
{ type: event.type, object: event.object }
|
|
251
|
+
)]);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const eventUuid = payload.id;
|
|
255
|
+
if (typeof eventUuid !== "string" || eventUuid === "") {
|
|
256
|
+
_optionalChain([this, 'access', _37 => _37.logger, 'access', _38 => _38.warn, 'optionalCall', _39 => _39(
|
|
257
|
+
"[webhook-auto-enqueuer] dropping off-contract bulk data event: payload has no top-level string `id` to dedup on \u2014 fix the producer",
|
|
258
|
+
{ type: event.type, object: event.object }
|
|
259
|
+
)]);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const eventId = `${event.object}:${event.type}:${eventUuid}`;
|
|
263
|
+
for (const sub of subs) {
|
|
264
|
+
if (!sub.triggers.has(trigger)) continue;
|
|
265
|
+
void this.enqueue({
|
|
266
|
+
source: "webhook",
|
|
267
|
+
refId: sub.id,
|
|
268
|
+
dedupKey: `${sub.id}:${eventId}`,
|
|
269
|
+
label: event.type,
|
|
270
|
+
url: sub.url,
|
|
271
|
+
method: sub.method,
|
|
272
|
+
headers: sub.headers,
|
|
273
|
+
signingSecret: sub.secret,
|
|
274
|
+
timeoutMs: sub.timeoutMs,
|
|
275
|
+
// [#3946] Envelope keys last so the payload cannot rewrite them.
|
|
276
|
+
payload: {
|
|
277
|
+
...payload,
|
|
278
|
+
object: event.object,
|
|
279
|
+
matched,
|
|
280
|
+
action,
|
|
281
|
+
timestamp: event.timestamp
|
|
189
282
|
}
|
|
190
283
|
}).catch(
|
|
191
|
-
(err) => _optionalChain([this, 'access',
|
|
284
|
+
(err) => _optionalChain([this, 'access', _40 => _40.logger, 'access', _41 => _41.warn, 'optionalCall', _42 => _42("[webhook-auto-enqueuer] bulk enqueue failed", {
|
|
192
285
|
webhook: sub.name,
|
|
193
286
|
eventId,
|
|
194
|
-
err: _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
287
|
+
err: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _43 => _43.message]), () => ( err))
|
|
195
288
|
})])
|
|
196
289
|
);
|
|
197
290
|
}
|
|
198
291
|
}
|
|
199
292
|
handleSelfHealEvent(event) {
|
|
200
293
|
if (event.object !== this.subscriptionsObject) return;
|
|
201
|
-
if (!_optionalChain([event, 'access',
|
|
294
|
+
if (!_optionalChain([event, 'access', _44 => _44.type, 'optionalAccess', _45 => _45.startsWith, 'call', _46 => _46("data.record.")]) && !_optionalChain([event, 'access', _47 => _47.type, 'optionalAccess', _48 => _48.startsWith, 'call', _49 => _49("data.records.")])) return;
|
|
202
295
|
this.refresh().catch(
|
|
203
|
-
(err) => _optionalChain([this, 'access',
|
|
296
|
+
(err) => _optionalChain([this, 'access', _50 => _50.logger, 'access', _51 => _51.warn, 'optionalCall', _52 => _52("[webhook-auto-enqueuer] self-heal refresh failed", err)])
|
|
204
297
|
);
|
|
205
298
|
}
|
|
206
299
|
/** Test / admin accessor. */
|
|
@@ -220,29 +313,45 @@ function mapActionToTrigger(action) {
|
|
|
220
313
|
return null;
|
|
221
314
|
}
|
|
222
315
|
}
|
|
223
|
-
|
|
316
|
+
function mapBulkActionToTrigger(action) {
|
|
317
|
+
switch (action) {
|
|
318
|
+
case "updated":
|
|
319
|
+
return "bulk_update";
|
|
320
|
+
case "deleted":
|
|
321
|
+
return "bulk_delete";
|
|
322
|
+
default:
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
var DISPATCHABLE_WEBHOOK_TRIGGERS = /* @__PURE__ */ new Set([
|
|
327
|
+
"create",
|
|
328
|
+
"update",
|
|
329
|
+
"delete",
|
|
330
|
+
"bulk_update",
|
|
331
|
+
"bulk_delete"
|
|
332
|
+
]);
|
|
224
333
|
|
|
225
334
|
// src/bootstrap-declared-webhooks.ts
|
|
226
335
|
var _automation = require('@objectstack/spec/automation');
|
|
227
336
|
var SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
|
|
228
337
|
function uid(prefix) {
|
|
229
338
|
const g = globalThis;
|
|
230
|
-
if (_optionalChain([g, 'access',
|
|
339
|
+
if (_optionalChain([g, 'access', _53 => _53.crypto, 'optionalAccess', _54 => _54.randomUUID])) return `${prefix}_${g.crypto.randomUUID()}`;
|
|
231
340
|
return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
|
|
232
341
|
}
|
|
233
342
|
function readDeclared(engine, metadataService, type) {
|
|
234
343
|
try {
|
|
235
|
-
const reg = _optionalChain([engine, 'optionalAccess',
|
|
236
|
-
if (_optionalChain([reg, 'optionalAccess',
|
|
237
|
-
const items = (_nullishCoalesce(reg.listItems(type), () => ( []))).map((i) => _nullishCoalesce(_optionalChain([i, 'optionalAccess',
|
|
344
|
+
const reg = _optionalChain([engine, 'optionalAccess', _55 => _55._registry]);
|
|
345
|
+
if (_optionalChain([reg, 'optionalAccess', _56 => _56.listItems])) {
|
|
346
|
+
const items = (_nullishCoalesce(reg.listItems(type), () => ( []))).map((i) => _nullishCoalesce(_optionalChain([i, 'optionalAccess', _57 => _57.content]), () => ( i))).filter(Boolean);
|
|
238
347
|
if (items.length > 0) return items;
|
|
239
348
|
}
|
|
240
349
|
} catch (e3) {
|
|
241
350
|
}
|
|
242
351
|
try {
|
|
243
|
-
const listed = _optionalChain([metadataService, 'optionalAccess',
|
|
244
|
-
const arr = typeof _optionalChain([listed, 'optionalAccess',
|
|
245
|
-
return Array.isArray(arr) ? arr.map((i) => _nullishCoalesce(_optionalChain([i, 'optionalAccess',
|
|
352
|
+
const listed = _optionalChain([metadataService, 'optionalAccess', _58 => _58.list, 'optionalCall', _59 => _59(type)]);
|
|
353
|
+
const arr = typeof _optionalChain([listed, 'optionalAccess', _60 => _60.then]) === "function" ? [] : _nullishCoalesce(listed, () => ( []));
|
|
354
|
+
return Array.isArray(arr) ? arr.map((i) => _nullishCoalesce(_optionalChain([i, 'optionalAccess', _61 => _61.content]), () => ( i))).filter(Boolean) : [];
|
|
246
355
|
} catch (e4) {
|
|
247
356
|
return [];
|
|
248
357
|
}
|
|
@@ -258,23 +367,23 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
|
|
|
258
367
|
try {
|
|
259
368
|
wh = _automation.WebhookSchema.parse(raw);
|
|
260
369
|
} catch (err) {
|
|
261
|
-
_optionalChain([logger, 'optionalAccess',
|
|
262
|
-
name: _optionalChain([raw, 'optionalAccess',
|
|
263
|
-
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
370
|
+
_optionalChain([logger, 'optionalAccess', _62 => _62.warn, 'optionalCall', _63 => _63("[webhook] declared webhook failed validation \u2014 skipped", {
|
|
371
|
+
name: _optionalChain([raw, 'optionalAccess', _64 => _64.name]),
|
|
372
|
+
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _65 => _65.message]), () => ( String(err)))
|
|
264
373
|
})]);
|
|
265
374
|
skipped += 1;
|
|
266
375
|
continue;
|
|
267
376
|
}
|
|
268
377
|
try {
|
|
269
378
|
const existing = await engine.find(subscriptionsObject, {
|
|
270
|
-
|
|
379
|
+
where: { name: wh.name },
|
|
271
380
|
limit: 1,
|
|
272
381
|
context: SYSTEM_CTX
|
|
273
382
|
});
|
|
274
383
|
const row = Array.isArray(existing) ? existing[0] : void 0;
|
|
275
384
|
if (row) {
|
|
276
385
|
if (row.managed_by === "admin") {
|
|
277
|
-
_optionalChain([logger, 'optionalAccess',
|
|
386
|
+
_optionalChain([logger, 'optionalAccess', _66 => _66.warn, 'optionalCall', _67 => _67("[webhook] declared name collides with an admin-authored row \u2014 seed skipped", {
|
|
278
387
|
name: wh.name
|
|
279
388
|
})]);
|
|
280
389
|
skipped += 1;
|
|
@@ -307,14 +416,14 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
|
|
|
307
416
|
await engine.insert(subscriptionsObject, newRow, { context: SYSTEM_CTX });
|
|
308
417
|
seeded += 1;
|
|
309
418
|
} catch (err) {
|
|
310
|
-
_optionalChain([logger, 'optionalAccess',
|
|
419
|
+
_optionalChain([logger, 'optionalAccess', _68 => _68.warn, 'optionalCall', _69 => _69("[webhook] declared webhook seed failed", {
|
|
311
420
|
name: wh.name,
|
|
312
|
-
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
421
|
+
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _70 => _70.message]), () => ( String(err)))
|
|
313
422
|
})]);
|
|
314
423
|
skipped += 1;
|
|
315
424
|
}
|
|
316
425
|
}
|
|
317
|
-
_optionalChain([logger, 'optionalAccess',
|
|
426
|
+
_optionalChain([logger, 'optionalAccess', _71 => _71.info, 'optionalCall', _72 => _72("[webhook] declared webhooks materialized into sys_webhook", {
|
|
318
427
|
seeded,
|
|
319
428
|
skipped,
|
|
320
429
|
total: declared.length
|
|
@@ -341,18 +450,18 @@ function mapWebhookToRow(wh) {
|
|
|
341
450
|
var WEBHOOK_PROVENANCE_PACKAGE = "plugin-webhooks:provenance";
|
|
342
451
|
var SYSTEM_CTX2 = { isSystem: true, positions: [], permissions: [] };
|
|
343
452
|
function bindWebhookProvenanceStamp(engine, logger) {
|
|
344
|
-
if (typeof _optionalChain([engine, 'optionalAccess',
|
|
453
|
+
if (typeof _optionalChain([engine, 'optionalAccess', _73 => _73.registerHook]) !== "function") return;
|
|
345
454
|
engine.registerHook(
|
|
346
455
|
"beforeUpdate",
|
|
347
456
|
async (ctx) => {
|
|
348
|
-
if (_optionalChain([ctx, 'optionalAccess',
|
|
349
|
-
const id = _nullishCoalesce(_optionalChain([ctx, 'optionalAccess',
|
|
457
|
+
if (_optionalChain([ctx, 'optionalAccess', _74 => _74.session, 'optionalAccess', _75 => _75.isSystem])) return;
|
|
458
|
+
const id = _nullishCoalesce(_optionalChain([ctx, 'optionalAccess', _76 => _76.input, 'optionalAccess', _77 => _77.id]), () => ( _optionalChain([ctx, 'optionalAccess', _78 => _78.input, 'optionalAccess', _79 => _79.data, 'optionalAccess', _80 => _80.id])));
|
|
350
459
|
if (!id) return;
|
|
351
|
-
const data = _optionalChain([ctx, 'optionalAccess',
|
|
460
|
+
const data = _optionalChain([ctx, 'optionalAccess', _81 => _81.input, 'optionalAccess', _82 => _82.data]);
|
|
352
461
|
if (!data || typeof data !== "object") return;
|
|
353
462
|
try {
|
|
354
463
|
const rows = await engine.find("sys_webhook", {
|
|
355
|
-
|
|
464
|
+
where: { id },
|
|
356
465
|
fields: ["id", "managed_by", "customized"],
|
|
357
466
|
limit: 1,
|
|
358
467
|
context: SYSTEM_CTX2
|
|
@@ -363,18 +472,18 @@ function bindWebhookProvenanceStamp(engine, logger) {
|
|
|
363
472
|
data.customized = true;
|
|
364
473
|
}
|
|
365
474
|
} catch (err) {
|
|
366
|
-
_optionalChain([logger, 'optionalAccess',
|
|
475
|
+
_optionalChain([logger, 'optionalAccess', _83 => _83.warn, 'optionalCall', _84 => _84("[webhook] provenance stamp failed (edit proceeds unstamped)", {
|
|
367
476
|
id,
|
|
368
|
-
error: _optionalChain([err, 'optionalAccess',
|
|
477
|
+
error: _optionalChain([err, 'optionalAccess', _85 => _85.message])
|
|
369
478
|
})]);
|
|
370
479
|
}
|
|
371
480
|
},
|
|
372
481
|
{ object: "sys_webhook", packageId: WEBHOOK_PROVENANCE_PACKAGE, priority: 150 }
|
|
373
482
|
);
|
|
374
|
-
_optionalChain([logger, 'optionalAccess',
|
|
483
|
+
_optionalChain([logger, 'optionalAccess', _86 => _86.info, 'optionalCall', _87 => _87("[webhook] provenance stamp hook bound")]);
|
|
375
484
|
}
|
|
376
485
|
function unbindWebhookProvenanceStamp(engine) {
|
|
377
|
-
if (typeof _optionalChain([engine, 'optionalAccess',
|
|
486
|
+
if (typeof _optionalChain([engine, 'optionalAccess', _88 => _88.unregisterHooksByPackage]) === "function") {
|
|
378
487
|
engine.unregisterHooksByPackage(WEBHOOK_PROVENANCE_PACKAGE);
|
|
379
488
|
}
|
|
380
489
|
}
|
|
@@ -387,6 +496,16 @@ var WebhookOutboxPlugin = class {
|
|
|
387
496
|
this.version = "2.0.0";
|
|
388
497
|
this.type = "standard";
|
|
389
498
|
this.dependencies = ["com.objectstack.service.messaging"];
|
|
499
|
+
/**
|
|
500
|
+
* `init()` registers this plugin's schema through `manifest` with no
|
|
501
|
+
* fallback. Until #4187 that was safe only TRANSITIVELY — messaging happens
|
|
502
|
+
* to depend on ObjectQL, which provides `manifest` — so the guarantee would
|
|
503
|
+
* have evaporated silently the day messaging stopped depending on the
|
|
504
|
+
* engine, and the failure would have surfaced as an unrelated plugin's
|
|
505
|
+
* init crash. Declaring the requirement directly makes the kernel check it
|
|
506
|
+
* regardless of what messaging depends on.
|
|
507
|
+
*/
|
|
508
|
+
this.requiresServices = ["manifest"];
|
|
390
509
|
}
|
|
391
510
|
async init(ctx) {
|
|
392
511
|
const manifest = ctx.getService("manifest");
|
|
@@ -399,7 +518,7 @@ var WebhookOutboxPlugin = class {
|
|
|
399
518
|
scope: "system",
|
|
400
519
|
name: "Webhook Schemas",
|
|
401
520
|
description: "Registers sys_webhook (configuration). Deliveries use messaging's sys_http_delivery outbox.",
|
|
402
|
-
objects: [
|
|
521
|
+
objects: [_chunkJQUVS5KKcjs.SysWebhook],
|
|
403
522
|
navigationContributions: [
|
|
404
523
|
{
|
|
405
524
|
app: "setup",
|
|
@@ -413,7 +532,7 @@ var WebhookOutboxPlugin = class {
|
|
|
413
532
|
]
|
|
414
533
|
});
|
|
415
534
|
} else {
|
|
416
|
-
_optionalChain([ctx, 'access',
|
|
535
|
+
_optionalChain([ctx, 'access', _89 => _89.logger, 'access', _90 => _90.warn, 'optionalCall', _91 => _91(
|
|
417
536
|
"[webhook-outbox] manifest service unavailable \u2014 sys_webhook will NOT appear in REST or Studio nav. Register MetadataService before WebhookOutboxPlugin."
|
|
418
537
|
)]);
|
|
419
538
|
}
|
|
@@ -422,7 +541,7 @@ var WebhookOutboxPlugin = class {
|
|
|
422
541
|
try {
|
|
423
542
|
const i18n = ctx.getService("i18n");
|
|
424
543
|
if (i18n && typeof i18n.loadTranslations === "function") {
|
|
425
|
-
const { WebhooksTranslations } = await Promise.resolve().then(() => _interopRequireWildcard(require("./translations-
|
|
544
|
+
const { WebhooksTranslations } = await Promise.resolve().then(() => _interopRequireWildcard(require("./translations-BWS57U2V.cjs")));
|
|
426
545
|
for (const [locale, data] of Object.entries(WebhooksTranslations)) {
|
|
427
546
|
i18n.loadTranslations(locale, data);
|
|
428
547
|
}
|
|
@@ -439,12 +558,12 @@ var WebhookOutboxPlugin = class {
|
|
|
439
558
|
this.registerAdminRoutes(ctx);
|
|
440
559
|
});
|
|
441
560
|
}
|
|
442
|
-
_optionalChain([ctx, 'access',
|
|
561
|
+
_optionalChain([ctx, 'access', _92 => _92.logger, 'access', _93 => _93.info, 'optionalCall', _94 => _94("[webhook-outbox] initialised (delivery via shared messaging HTTP outbox)", {
|
|
443
562
|
autoEnqueue: autoEnqueueOpt !== false
|
|
444
563
|
})]);
|
|
445
564
|
}
|
|
446
565
|
async dispose() {
|
|
447
|
-
await _optionalChain([this, 'access',
|
|
566
|
+
await _optionalChain([this, 'access', _95 => _95.autoEnqueuer, 'optionalAccess', _96 => _96.stop, 'call', _97 => _97()]);
|
|
448
567
|
if (this.boundEngine) {
|
|
449
568
|
try {
|
|
450
569
|
unbindWebhookProvenanceStamp(this.boundEngine);
|
|
@@ -472,7 +591,7 @@ var WebhookOutboxPlugin = class {
|
|
|
472
591
|
async bootDeclaredWebhooks(ctx) {
|
|
473
592
|
const engine = this.tryGetService(ctx, ["objectql", "data"]);
|
|
474
593
|
if (!engine) {
|
|
475
|
-
_optionalChain([ctx, 'access',
|
|
594
|
+
_optionalChain([ctx, 'access', _98 => _98.logger, 'access', _99 => _99.warn, 'optionalCall', _100 => _100("[webhook] declared-webhook bootstrap skipped \u2014 no data engine available")]);
|
|
476
595
|
return;
|
|
477
596
|
}
|
|
478
597
|
this.boundEngine = engine;
|
|
@@ -485,8 +604,8 @@ var WebhookOutboxPlugin = class {
|
|
|
485
604
|
try {
|
|
486
605
|
await bootstrapDeclaredWebhooks(engine, metadataService, ctx.logger);
|
|
487
606
|
} catch (err) {
|
|
488
|
-
_optionalChain([ctx, 'access',
|
|
489
|
-
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess',
|
|
607
|
+
_optionalChain([ctx, 'access', _101 => _101.logger, 'access', _102 => _102.warn, 'optionalCall', _103 => _103("[webhook] declared-webhook bootstrap failed (dispatcher still serves admin rows)", {
|
|
608
|
+
error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _104 => _104.message]), () => ( String(err)))
|
|
490
609
|
})]);
|
|
491
610
|
}
|
|
492
611
|
}
|
|
@@ -496,14 +615,14 @@ var WebhookOutboxPlugin = class {
|
|
|
496
615
|
const realtime = this.tryGetService(ctx, ["realtime"]);
|
|
497
616
|
const messaging = this.getMessaging(ctx);
|
|
498
617
|
if (!engine || !realtime || !messaging) {
|
|
499
|
-
_optionalChain([ctx, 'access',
|
|
618
|
+
_optionalChain([ctx, 'access', _105 => _105.logger, 'access', _106 => _106.warn, 'optionalCall', _107 => _107(
|
|
500
619
|
"[webhook-auto-enqueuer] disabled \u2014 ObjectQL, Realtime, or Messaging service not available",
|
|
501
620
|
{ hasEngine: !!engine, hasRealtime: !!realtime, hasMessaging: !!messaging }
|
|
502
621
|
)]);
|
|
503
622
|
return;
|
|
504
623
|
}
|
|
505
624
|
if (!messaging.isHttpDeliveryReady()) {
|
|
506
|
-
_optionalChain([ctx, 'access',
|
|
625
|
+
_optionalChain([ctx, 'access', _108 => _108.logger, 'access', _109 => _109.warn, 'optionalCall', _110 => _110(
|
|
507
626
|
"[webhook-auto-enqueuer] messaging HTTP outbox not ready (no data engine / reliableDelivery off) \u2014 webhook deliveries will not be durable"
|
|
508
627
|
)]);
|
|
509
628
|
}
|
|
@@ -516,7 +635,7 @@ var WebhookOutboxPlugin = class {
|
|
|
516
635
|
);
|
|
517
636
|
await this.autoEnqueuer.start();
|
|
518
637
|
ctx.registerService("webhook.autoEnqueuer", this.autoEnqueuer);
|
|
519
|
-
_optionalChain([ctx, 'access',
|
|
638
|
+
_optionalChain([ctx, 'access', _111 => _111.logger, 'access', _112 => _112.info, 'optionalCall', _113 => _113("[webhook-auto-enqueuer] started (enqueues source=webhook onto sys_http_delivery)")]);
|
|
520
639
|
}
|
|
521
640
|
tryGetService(ctx, names) {
|
|
522
641
|
for (const n of names) {
|
|
@@ -536,7 +655,7 @@ var WebhookOutboxPlugin = class {
|
|
|
536
655
|
registerAdminRoutes(ctx) {
|
|
537
656
|
const http = this.tryGetService(ctx, ["http-server"]);
|
|
538
657
|
if (!http || typeof http.getRawApp !== "function") {
|
|
539
|
-
_optionalChain([ctx, 'access',
|
|
658
|
+
_optionalChain([ctx, 'access', _114 => _114.logger, 'access', _115 => _115.debug, 'optionalCall', _116 => _116("[webhook-outbox] HTTP server not available; redeliver endpoint not mounted")]);
|
|
540
659
|
return;
|
|
541
660
|
}
|
|
542
661
|
const rawApp = http.getRawApp();
|
|
@@ -546,7 +665,7 @@ var WebhookOutboxPlugin = class {
|
|
|
546
665
|
const userId = await this.resolveSessionUserId(ctx, c);
|
|
547
666
|
if (!userId) {
|
|
548
667
|
return c.json(
|
|
549
|
-
{ success: false, error: "
|
|
668
|
+
{ success: false, error: { code: "UNAUTHENTICATED", message: "Sign in to redeliver webhook deliveries." } },
|
|
550
669
|
401
|
|
551
670
|
);
|
|
552
671
|
}
|
|
@@ -554,35 +673,35 @@ var WebhookOutboxPlugin = class {
|
|
|
554
673
|
try {
|
|
555
674
|
body = await c.req.json();
|
|
556
675
|
} catch (e9) {
|
|
557
|
-
return c.json({ success: false, error: "
|
|
676
|
+
return c.json({ success: false, error: { code: "INVALID_REQUEST", message: "Request body must be JSON." } }, 400);
|
|
558
677
|
}
|
|
559
|
-
const deliveryId = typeof _optionalChain([body, 'optionalAccess',
|
|
678
|
+
const deliveryId = typeof _optionalChain([body, 'optionalAccess', _117 => _117.deliveryId]) === "string" ? body.deliveryId.trim() : "";
|
|
560
679
|
if (!deliveryId) {
|
|
561
680
|
return c.json(
|
|
562
|
-
{ success: false, error: "
|
|
681
|
+
{ success: false, error: { code: "MISSING_REQUIRED_FIELD", message: "Body must include `deliveryId: string`." } },
|
|
563
682
|
400
|
|
564
683
|
);
|
|
565
684
|
}
|
|
566
685
|
try {
|
|
567
686
|
const row = await messaging.redeliverHttp(deliveryId);
|
|
568
|
-
_optionalChain([ctx, 'access',
|
|
687
|
+
_optionalChain([ctx, 'access', _118 => _118.logger, 'access', _119 => _119.info, 'optionalCall', _120 => _120("[webhook-outbox] redelivered", { deliveryId, requestedBy: userId })]);
|
|
569
688
|
return c.json({ success: true, data: { id: row.id, status: row.status } });
|
|
570
689
|
} catch (err) {
|
|
571
|
-
const code = _optionalChain([err, 'optionalAccess',
|
|
572
|
-
if (code === "
|
|
573
|
-
return c.json({ success: false, error:
|
|
690
|
+
const code = _optionalChain([err, 'optionalAccess', _121 => _121.code]);
|
|
691
|
+
if (code === "RESOURCE_NOT_FOUND") {
|
|
692
|
+
return c.json({ success: false, error: { code, message: err.message } }, 404);
|
|
574
693
|
}
|
|
575
|
-
if (code === "
|
|
576
|
-
return c.json({ success: false, error:
|
|
694
|
+
if (code === "DELIVERY_NOT_ELIGIBLE") {
|
|
695
|
+
return c.json({ success: false, error: { code, message: err.message } }, 409);
|
|
577
696
|
}
|
|
578
|
-
_optionalChain([ctx, 'access',
|
|
697
|
+
_optionalChain([ctx, 'access', _122 => _122.logger, 'access', _123 => _123.error, 'optionalCall', _124 => _124("[webhook-outbox] redeliver failed", err)]);
|
|
579
698
|
return c.json(
|
|
580
|
-
{ success: false, error: "
|
|
699
|
+
{ success: false, error: { code: "INTERNAL_ERROR", message: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _125 => _125.message]), () => ( String(err))) } },
|
|
581
700
|
500
|
|
582
701
|
);
|
|
583
702
|
}
|
|
584
703
|
});
|
|
585
|
-
_optionalChain([ctx, 'access',
|
|
704
|
+
_optionalChain([ctx, 'access', _126 => _126.logger, 'access', _127 => _127.info, 'optionalCall', _128 => _128("[webhook-outbox] redeliver endpoint mounted at POST /api/v1/webhooks/redeliver")]);
|
|
586
705
|
}
|
|
587
706
|
async resolveSessionUserId(ctx, c) {
|
|
588
707
|
try {
|
|
@@ -592,9 +711,9 @@ var WebhookOutboxPlugin = class {
|
|
|
592
711
|
if (!api && typeof authService.getApi === "function") {
|
|
593
712
|
api = await authService.getApi();
|
|
594
713
|
}
|
|
595
|
-
if (!_optionalChain([api, 'optionalAccess',
|
|
714
|
+
if (!_optionalChain([api, 'optionalAccess', _129 => _129.getSession])) return void 0;
|
|
596
715
|
const session = await api.getSession({ headers: c.req.raw.headers });
|
|
597
|
-
const uid2 = _optionalChain([session, 'optionalAccess',
|
|
716
|
+
const uid2 = _optionalChain([session, 'optionalAccess', _130 => _130.user, 'optionalAccess', _131 => _131.id]);
|
|
598
717
|
return typeof uid2 === "string" && uid2.length > 0 ? uid2 : void 0;
|
|
599
718
|
} catch (e10) {
|
|
600
719
|
return void 0;
|
|
@@ -605,5 +724,5 @@ var WebhookOutboxPlugin = class {
|
|
|
605
724
|
|
|
606
725
|
|
|
607
726
|
|
|
608
|
-
exports.AutoEnqueuer = AutoEnqueuer; exports.SysWebhook =
|
|
727
|
+
exports.AutoEnqueuer = AutoEnqueuer; exports.SysWebhook = _chunkJQUVS5KKcjs.SysWebhook; exports.WebhookOutboxPlugin = WebhookOutboxPlugin;
|
|
609
728
|
//# sourceMappingURL=index.cjs.map
|