@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +632 -0
  2. package/dist/{chunk-6EPMRZ7I.js → chunk-3QGZLM3T.js} +8 -3
  3. package/dist/chunk-3QGZLM3T.js.map +1 -0
  4. package/dist/{chunk-QUTQSOQC.cjs → chunk-JQUVS5KK.cjs} +8 -3
  5. package/dist/chunk-JQUVS5KK.cjs.map +1 -0
  6. package/dist/index.cjs +182 -63
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +37 -1
  9. package/dist/index.d.ts +37 -1
  10. package/dist/index.js +138 -19
  11. package/dist/index.js.map +1 -1
  12. package/dist/schema.cjs +2 -2
  13. package/dist/schema.d.cts +721 -548
  14. package/dist/schema.d.ts +721 -548
  15. package/dist/schema.js +1 -1
  16. package/dist/{translations-Y3CXWOTM.js → translations-2VGD4XRF.js} +17 -9
  17. package/dist/translations-2VGD4XRF.js.map +1 -0
  18. package/dist/{translations-CBQRUIS5.cjs → translations-BWS57U2V.cjs} +17 -9
  19. package/dist/translations-BWS57U2V.cjs.map +1 -0
  20. package/package.json +12 -6
  21. package/.turbo/turbo-build.log +0 -36
  22. package/dist/chunk-6EPMRZ7I.js.map +0 -1
  23. package/dist/chunk-QUTQSOQC.cjs.map +0 -1
  24. package/dist/translations-CBQRUIS5.cjs.map +0 -1
  25. package/dist/translations-Y3CXWOTM.js.map +0 -1
  26. package/scripts/i18n-extract.config.ts +0 -34
  27. package/src/auto-enqueuer.test.ts +0 -431
  28. package/src/auto-enqueuer.ts +0 -383
  29. package/src/bootstrap-declared-webhooks.test.ts +0 -283
  30. package/src/bootstrap-declared-webhooks.ts +0 -205
  31. package/src/index.ts +0 -29
  32. package/src/schema.ts +0 -21
  33. package/src/sys-webhook.object.ts +0 -231
  34. package/src/translations/bundle-ownership.test.ts +0 -41
  35. package/src/translations/en.objects.generated.ts +0 -101
  36. package/src/translations/es-ES.objects.generated.ts +0 -101
  37. package/src/translations/index.ts +0 -23
  38. package/src/translations/ja-JP.objects.generated.ts +0 -101
  39. package/src/translations/zh-CN.objects.generated.ts +0 -101
  40. package/src/webhook-outbox-plugin.ts +0 -305
  41. package/src/webhook-provenance.ts +0 -86
  42. package/tsconfig.json +0 -10
  43. package/tsup.config.ts +0 -14
@@ -1,205 +0,0 @@
1
- // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * bootstrapDeclaredWebhooks — materialize stack/connector-declared `webhooks`
5
- * into `sys_webhook` rows so the dispatcher can actually see them (closes #3461).
6
- *
7
- * ## The disconnect this closes
8
- * The spec authoring surface (`WebhookSchema` — `defineStack({ webhooks })`,
9
- * `@objectstack/spec/automation/webhook`) declares `object` / `isActive`, and
10
- * is generically decomposed into the ObjectQL registry at boot as metadata
11
- * type `webhook`. But the runtime dispatcher ({@link AutoEnqueuer}) reads
12
- * `sys_webhook` DATA rows (`object_name` / `active`), which until now were only
13
- * ever written by hand through the object's CRUD UI. Nothing bridged the two —
14
- * so authoring `webhooks:` on a stack produced metadata artifacts that never
15
- * became dispatchable rows (a silent no-op; ADR-0078). This seeder is that
16
- * missing ingestion path.
17
- *
18
- * ## Shape translation (authoring → runtime row)
19
- * The spec shape diverges from the runtime column names; we map only at this
20
- * boundary and stash the full validated envelope in `definition_json` (whence
21
- * the enqueuer reads headers / secret / timeout):
22
- * - `object` → `object_name`
23
- * - `isActive` → `active`
24
- * - `triggers` / `url` / `method` / `label` / `description` → same-named columns
25
- * - the entire parsed {@link Webhook} → `definition_json` (JSON string)
26
- *
27
- * Each item is validated through `WebhookSchema.parse()` first — this gives the
28
- * spec schema a real consumer (defaults for `method`/`isActive`/`timeoutMs` get
29
- * applied) and rejects malformed authoring with a warning instead of crashing
30
- * boot.
31
- *
32
- * ## Seed-not-clobber (mirrors sys_sharing_rule, #2909)
33
- * `sys_webhook` is admin-editable (`managedBy: 'config'`). Declared webhooks
34
- * ship with the app/package, so they seed with `managed_by: 'package'`
35
- * provenance and re-seed on every boot — but a row an admin has created
36
- * (`managed_by: 'admin'`) or edited (`customized: true`, stamped by
37
- * {@link bindWebhookProvenanceStamp}) is never overwritten. Most importantly,
38
- * an admin's `active: false` on a noisy webhook survives redeploys.
39
- *
40
- * MUST run before {@link AutoEnqueuer.start} so the enqueuer's first cache
41
- * refresh already sees the declared rows.
42
- */
43
-
44
- import type { IDataEngine } from '@objectstack/spec/contracts';
45
- import { WebhookSchema, type Webhook } from '@objectstack/spec/automation';
46
-
47
- /** System write context — the boot seeder is not an admin authoring action. */
48
- const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;
49
-
50
- interface Logger {
51
- info?: (msg: string, meta?: unknown) => void;
52
- warn?: (msg: string, meta?: unknown) => void;
53
- }
54
-
55
- /** Random id with a stable prefix — mirrors the sharing-rule seeder. */
56
- function uid(prefix: string): string {
57
- const g: any = globalThis as any;
58
- if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;
59
- return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
60
- }
61
-
62
- /**
63
- * Read declared `webhook` items from the ObjectQL registry (where the manifest
64
- * decomposition parks `stack.webhooks`), falling back to the metadata service.
65
- * Items may be wrapped as `{ content }` — unwrap to the raw authoring object.
66
- */
67
- function readDeclared(engine: any, metadataService: any, type: string): any[] {
68
- try {
69
- const reg = engine?._registry;
70
- if (reg?.listItems) {
71
- const items = (reg.listItems(type) ?? []).map((i: any) => i?.content ?? i).filter(Boolean);
72
- if (items.length > 0) return items;
73
- }
74
- } catch {
75
- /* fall through to metadata service */
76
- }
77
- try {
78
- const listed = metadataService?.list?.(type);
79
- const arr = typeof (listed as any)?.then === 'function' ? [] : (listed ?? []);
80
- return Array.isArray(arr) ? arr.map((i: any) => i?.content ?? i).filter(Boolean) : [];
81
- } catch {
82
- return [];
83
- }
84
- }
85
-
86
- export interface BootstrapDeclaredWebhooksResult {
87
- seeded: number;
88
- skipped: number;
89
- }
90
-
91
- /**
92
- * Materialize declared webhooks into `sys_webhook`. Idempotent and safe to run
93
- * on every boot.
94
- */
95
- export async function bootstrapDeclaredWebhooks(
96
- engine: IDataEngine,
97
- metadataService: any,
98
- logger?: Logger,
99
- subscriptionsObject = 'sys_webhook',
100
- ): Promise<BootstrapDeclaredWebhooksResult> {
101
- const declared = readDeclared(engine, metadataService, 'webhook');
102
- if (declared.length === 0) return { seeded: 0, skipped: 0 };
103
-
104
- const now = new Date().toISOString();
105
- let seeded = 0;
106
- let skipped = 0;
107
-
108
- for (const raw of declared) {
109
- // Validate + fill defaults through the canonical spec schema. A real
110
- // consumer at last — a malformed webhook warns and is skipped, never
111
- // crashing boot.
112
- let wh: Webhook;
113
- try {
114
- wh = WebhookSchema.parse(raw);
115
- } catch (err: any) {
116
- logger?.warn?.('[webhook] declared webhook failed validation — skipped', {
117
- name: (raw as any)?.name,
118
- error: err?.message ?? String(err),
119
- });
120
- skipped += 1;
121
- continue;
122
- }
123
-
124
- try {
125
- const existing = await engine.find(subscriptionsObject, {
126
- filter: { name: wh.name },
127
- limit: 1,
128
- context: SYSTEM_CTX,
129
- } as any);
130
- const row: any = Array.isArray(existing) ? existing[0] : undefined;
131
-
132
- if (row) {
133
- // Admin owns a same-named row, or has edited this seeded one — never
134
- // clobber. `active: false` on a noisy webhook must survive redeploys.
135
- if (row.managed_by === 'admin') {
136
- logger?.warn?.('[webhook] declared name collides with an admin-authored row — seed skipped', {
137
- name: wh.name,
138
- });
139
- skipped += 1;
140
- continue;
141
- }
142
- if (row.customized === true) {
143
- skipped += 1;
144
- continue;
145
- }
146
- const patch = {
147
- id: row.id,
148
- ...mapWebhookToRow(wh),
149
- // Adopt pristine/legacy (pre-provenance) rows so future boots
150
- // recognize them as package-managed.
151
- managed_by: 'package',
152
- updated_at: now,
153
- };
154
- await engine.update(subscriptionsObject, patch, { context: SYSTEM_CTX } as any);
155
- seeded += 1;
156
- continue;
157
- }
158
-
159
- const newRow = {
160
- id: uid('whk'),
161
- ...mapWebhookToRow(wh),
162
- managed_by: 'package',
163
- customized: false,
164
- created_at: now,
165
- updated_at: now,
166
- };
167
- await engine.insert(subscriptionsObject, newRow, { context: SYSTEM_CTX } as any);
168
- seeded += 1;
169
- } catch (err: any) {
170
- logger?.warn?.('[webhook] declared webhook seed failed', {
171
- name: wh.name,
172
- error: err?.message ?? String(err),
173
- });
174
- skipped += 1;
175
- }
176
- }
177
-
178
- logger?.info?.('[webhook] declared webhooks materialized into sys_webhook', {
179
- seeded,
180
- skipped,
181
- total: declared.length,
182
- });
183
- return { seeded, skipped };
184
- }
185
-
186
- /**
187
- * Translate a validated {@link Webhook} into `sys_webhook` column values.
188
- * `object → object_name`, `isActive → active`; the full envelope is stashed in
189
- * `definition_json` for the enqueuer's advanced-config read (headers/secret/…).
190
- */
191
- function mapWebhookToRow(wh: Webhook): Record<string, unknown> {
192
- return {
193
- name: wh.name,
194
- label: wh.label ?? wh.name,
195
- object_name: wh.object ?? null,
196
- triggers: wh.triggers ?? [],
197
- url: wh.url,
198
- // Store lowercase to match the object's Field.select option values
199
- // (get/post/…); the enqueuer upper-cases before delivery either way.
200
- method: String(wh.method ?? 'POST').toLowerCase(),
201
- description: wh.description ?? null,
202
- active: wh.isActive !== false,
203
- definition_json: JSON.stringify(wh),
204
- };
205
- }
package/src/index.ts DELETED
@@ -1,29 +0,0 @@
1
- // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * @objectstack/plugin-webhooks
5
- *
6
- * Webhook fan-out on top of the shared outbound-HTTP delivery substrate
7
- * (ADR-0018 M3). The durable outbox, cluster-coordinated dispatcher, retry /
8
- * backoff / dead-letter, and retention all live in
9
- * `@objectstack/service-messaging` (`sys_http_delivery` + `HttpDispatcher`).
10
- *
11
- * This package ships only the webhook-specific concerns:
12
- * - the `sys_webhook` configuration object,
13
- * - the {@link AutoEnqueuer} that turns `data.record.*` events into outbox
14
- * rows (`source: 'webhook'`),
15
- * - the redeliver admin endpoint.
16
- *
17
- * **Requires** `MessagingServicePlugin` (a foundational, always-on capability).
18
- *
19
- * ## Subpath exports
20
- * - `@objectstack/plugin-webhooks/schema` — `SysWebhook` object schema.
21
- */
22
-
23
- export {
24
- WebhookOutboxPlugin,
25
- type WebhookOutboxPluginOptions,
26
- } from './webhook-outbox-plugin.js';
27
-
28
- export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn } from './auto-enqueuer.js';
29
- export { SysWebhook } from './sys-webhook.object.js';
package/src/schema.ts DELETED
@@ -1,21 +0,0 @@
1
- // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * Public schema subpath: `@objectstack/plugin-webhooks/schema`.
5
- *
6
- * Thin re-export barrel kept stable across refactors. The object definition
7
- * lives in `sys-webhook.object.ts` (matching the `*.object.ts` convention used
8
- * everywhere else in the monorepo for `sys_*` schemas).
9
- *
10
- * `sys_webhook` moved here from `@objectstack/platform-objects` per ADR-0029
11
- * (K2.a) so this plugin owns its configuration object. Delivery telemetry is no
12
- * longer a webhook-owned object: post-ADR-0018 M3 deliveries are rows in the
13
- * shared `sys_http_delivery` outbox owned by `@objectstack/service-messaging`.
14
- *
15
- * Note: callers that just need the runtime should import from the package root
16
- * (`@objectstack/plugin-webhooks`), which auto-registers `sys_webhook` via the
17
- * plugin manifest. This subpath exists for read-only inspection from a
18
- * different runtime.
19
- */
20
-
21
- export { SysWebhook } from './sys-webhook.object.js';
@@ -1,231 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { ObjectSchema, Field } from '@objectstack/spec/data';
4
-
5
- /**
6
- * sys_webhook — Outbound HTTP integration configuration (runtime).
7
- *
8
- * Persists a single {@link Webhook} envelope per row so administrators
9
- * can author, enable/disable, and edit webhook subscriptions from the
10
- * Studio UI without code changes. The canonical Zod schema for the
11
- * `definition_json` envelope lives at `@objectstack/spec/automation/webhook`.
12
- *
13
- * ## Two authoring doors, one row
14
- * Rows land here two ways, distinguished by the `managed_by` provenance column:
15
- * - **admin** — created/edited directly through this object's CRUD UI.
16
- * - **package** — declared in code (`defineStack({ webhooks })` /
17
- * `defineWebhook()`) and materialized on boot by
18
- * `bootstrapDeclaredWebhooks` (#3461). Re-seeded every boot, but an admin
19
- * edit stamps `customized: true` and freezes the row (seed-not-clobber,
20
- * mirrors `sys_sharing_rule` #2909).
21
- *
22
- * One row per `name`. This plugin's {@link AutoEnqueuer} loads active rows on
23
- * boot + on `sys_webhook:changed` events, and turns matching `data.record.*`
24
- * events into deliveries on the shared `service-messaging` HTTP outbox
25
- * (ADR-0018 M3 — `sys_http_delivery`, drained by the messaging dispatcher).
26
- *
27
- * Ownership (ADR-0029 K2.a): this object is **owned by
28
- * `@objectstack/plugin-webhooks`** — the plugin that consumes these rows. It
29
- * used to live in the `@objectstack/platform-objects` monolith and be imported
30
- * here; the definition now lives with its owner so the plugin ships both data
31
- * and behavior as one unit.
32
- *
33
- * Platform-wide on purpose: every project (standalone, single-tenant,
34
- * cloud) can integrate with external systems (Slack, Stripe, internal
35
- * services) the same way.
36
- *
37
- * @namespace sys
38
- */
39
- export const SysWebhook = ObjectSchema.create({
40
- name: 'sys_webhook',
41
- label: 'Webhook',
42
- pluralLabel: 'Webhooks',
43
- icon: 'webhook',
44
- isSystem: true,
45
- managedBy: 'config',
46
- // Authoring a webhook from the UI requires a structured form for the
47
- // headers / auth / retry / payload blocks — the generic JSON textarea
48
- // is acceptable as a v1 until a dedicated builder lands. Re-enable
49
- // create/edit/delete so admins can at least toggle `active` and edit
50
- // simple URL/method fields without round-tripping through code.
51
- userActions: { create: true, edit: true, delete: true, import: false },
52
- 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.',
53
- displayNameField: 'name',
54
- nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
55
- titleFormat: '{label}',
56
- highlightFields: ['name', 'object_name', 'url', 'active', 'updated_at'],
57
-
58
- listViews: {
59
- active: {
60
- type: 'grid',
61
- name: 'active',
62
- label: 'Active',
63
- data: { provider: 'object', object: 'sys_webhook' },
64
- columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],
65
- filter: [{ field: 'active', operator: 'equals', value: true }],
66
- sort: [{ field: 'label', order: 'asc' }],
67
- pagination: { pageSize: 50 },
68
- },
69
- inactive: {
70
- type: 'grid',
71
- name: 'inactive',
72
- label: 'Inactive',
73
- data: { provider: 'object', object: 'sys_webhook' },
74
- columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],
75
- filter: [{ field: 'active', operator: 'equals', value: false }],
76
- sort: [{ field: 'label', order: 'asc' }],
77
- pagination: { pageSize: 50 },
78
- },
79
- by_object: {
80
- type: 'grid',
81
- name: 'by_object',
82
- label: 'By Object',
83
- data: { provider: 'object', object: 'sys_webhook' },
84
- columns: ['object_name', 'label', 'url', 'active', 'updated_at'],
85
- sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],
86
- grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },
87
- pagination: { pageSize: 100 },
88
- },
89
- all_webhooks: {
90
- type: 'grid',
91
- name: 'all_webhooks',
92
- label: 'All',
93
- data: { provider: 'object', object: 'sys_webhook' },
94
- columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],
95
- sort: [{ field: 'label', order: 'asc' }],
96
- pagination: { pageSize: 50 },
97
- },
98
- },
99
-
100
- fields: {
101
- id: Field.text({ label: 'Webhook ID', required: true, readonly: true, group: 'System' }),
102
-
103
- name: Field.text({
104
- label: 'Name',
105
- required: true,
106
- maxLength: 100,
107
- description: 'Unique snake_case name — referenced in logs and audit',
108
- group: 'Definition',
109
- }),
110
-
111
- label: Field.text({
112
- label: 'Display Label',
113
- required: false,
114
- maxLength: 200,
115
- group: 'Definition',
116
- }),
117
-
118
- object_name: Field.text({
119
- label: 'Object',
120
- required: false,
121
- maxLength: 100,
122
- // Object picker (same widget as sys_sharing_rule) instead of a free-text
123
- // machine name. Falls back to a text input when the widget is unavailable.
124
- widget: 'object-ref',
125
- description: 'Short object name whose record events (create/update/delete) fire this webhook',
126
- group: 'Definition',
127
- }),
128
-
129
- triggers: Field.select(
130
- ['create', 'update', 'delete'],
131
- {
132
- label: 'Triggers',
133
- required: false,
134
- // Multi-select instead of a hand-typed comma-separated string. Stored as
135
- // an array; the auto-enqueuer parser also tolerates the legacy
136
- // comma-separated / JSON-string forms so existing rows keep working.
137
- multiple: true,
138
- description: 'Record events that fire this webhook',
139
- group: 'Definition',
140
- },
141
- ),
142
-
143
- url: Field.text({
144
- label: 'Target URL',
145
- required: true,
146
- maxLength: 2048,
147
- description: 'External endpoint that receives the POST',
148
- group: 'Definition',
149
- }),
150
-
151
- method: Field.select(
152
- ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
153
- {
154
- label: 'HTTP Method',
155
- required: true,
156
- // Select instead of free text. Option values are lowercased by the
157
- // Field.select helper (get/post/…); the auto-enqueuer upper-cases the
158
- // resolved method before delivery, so existing 'POST' rows and the
159
- // lowercase option values both normalise correctly.
160
- defaultValue: 'post',
161
- description: 'HTTP method used for the callback request',
162
- group: 'Definition',
163
- },
164
- ),
165
-
166
- description: Field.textarea({ label: 'Description', required: false, group: 'Definition' }),
167
-
168
- active: Field.boolean({
169
- label: 'Active',
170
- required: true,
171
- defaultValue: true,
172
- description: 'Inactive webhooks are skipped by the dispatcher',
173
- group: 'Definition',
174
- }),
175
-
176
- definition_json: Field.textarea({
177
- label: 'Definition',
178
- required: true,
179
- description: 'Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config',
180
- group: 'Definition',
181
- }),
182
-
183
- // ── Provenance (#3461 — record-authoritative seed-not-clobber) ──
184
- // Mirrors sys_sharing_rule (#2909). Both columns are `readonly`: the
185
- // engine strips them from non-system payloads (forge/clear-proof), while
186
- // bootstrapDeclaredWebhooks and the provenance stamp hook write with
187
- // isSystem. Deliberately NOT a write gate: webhooks are a first-class admin
188
- // authoring/tuning surface — admins may edit or deactivate a package row;
189
- // the seeder simply stops overwriting it once `customized` is stamped.
190
- managed_by: Field.select(
191
- ['platform', 'package', 'admin'],
192
- {
193
- label: 'Managed By',
194
- required: false,
195
- readonly: true,
196
- defaultValue: 'admin',
197
- description:
198
- 'Record provenance: platform = framework built-in / package = app/package-declared ' +
199
- '(boot-seeded from defineStack webhooks) / admin = created in Setup.',
200
- group: 'System',
201
- },
202
- ),
203
-
204
- customized: Field.boolean({
205
- label: 'Customized',
206
- required: false,
207
- readonly: true,
208
- defaultValue: false,
209
- description:
210
- 'Set when an admin edits a package-declared webhook; boot seeding will no longer ' +
211
- 'overwrite the row (a deactivated noisy webhook survives redeploys). Meaningless on admin rows.',
212
- group: 'System',
213
- }),
214
-
215
- created_at: Field.datetime({
216
- label: 'Created At',
217
- required: true,
218
- defaultValue: 'NOW()',
219
- readonly: true,
220
- group: 'System',
221
- }),
222
-
223
- updated_at: Field.datetime({ label: 'Updated At', required: false, group: 'System' }),
224
- },
225
-
226
- indexes: [
227
- { fields: ['name'], unique: true },
228
- { fields: ['object_name'] },
229
- { fields: ['active', 'object_name'] },
230
- ],
231
- });
@@ -1,41 +0,0 @@
1
- // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2
- //
3
- // Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4
- // object-translation bundles must carry ONLY objects its extract config
5
- // (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6
- // another package, its translations move with it — a leftover copy here
7
- // silently DIES on the next `os i18n extract` run, taking curated translations
8
- // with it (the sys_audit_log incident). This test turns that silent loss into a
9
- // red build: an object present in the bundle but not in the ownership list below
10
- // means either (a) the extract config gained an object — add it here — or (b) a
11
- // moved/removed object's keys were left behind — remove them from the bundles
12
- // (or migrate them to the owning package), then keep this list in sync.
13
-
14
- import { describe, it, expect } from 'vitest';
15
- import { enObjects } from './en.objects.generated.js';
16
-
17
- // Objects the extract config (scripts/i18n-extract.config.ts) imports —
18
- // keep the two lists in sync when adding/moving objects.
19
- const OWNED_OBJECTS = new Set([
20
- 'sys_webhook',
21
- ]);
22
-
23
- describe('objects translation bundle ownership (ADR-0029 D8)', () => {
24
- it('the en bundle contains no objects owned by other packages', () => {
25
- const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
26
- expect(
27
- strays,
28
- `bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
29
- 'their curated translations would be silently deleted on the next `os i18n extract`. ' +
30
- 'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
31
- ).toEqual([]);
32
- });
33
-
34
- it('every owned object is present in the bundle (extract config regression)', () => {
35
- const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
36
- expect(
37
- missing,
38
- `objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
39
- ).toEqual([]);
40
- });
41
- });
@@ -1,101 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * Auto-generated by 'os i18n extract' for locale 'en'.
5
- * Edit translations in place; re-run extract (with --merge) to fill new gaps.
6
- * Do not hand-edit the structure — only the leaf string values.
7
- */
8
-
9
- import type { TranslationData } from '@objectstack/spec/system';
10
-
11
- export const enObjects: NonNullable<TranslationData['objects']> = {
12
- sys_webhook: {
13
- label: "Webhook",
14
- pluralLabel: "Webhooks",
15
- description: "Outbound HTTP webhook subscription. Authored via defineWebhook() in code or the Studio editor; executed by the HTTP connector plugin.",
16
- fields: {
17
- id: {
18
- label: "Webhook ID"
19
- },
20
- name: {
21
- label: "Name",
22
- help: "Unique snake_case name — referenced in logs and audit"
23
- },
24
- label: {
25
- label: "Display Label"
26
- },
27
- object_name: {
28
- label: "Object",
29
- help: "Short object name whose record events (create/update/delete) fire this webhook"
30
- },
31
- triggers: {
32
- label: "Triggers",
33
- help: "Comma-separated event list: create,update,delete",
34
- options: {
35
- create: "create",
36
- update: "update",
37
- delete: "delete"
38
- }
39
- },
40
- url: {
41
- label: "Target URL",
42
- help: "External endpoint that receives the POST"
43
- },
44
- method: {
45
- label: "HTTP Method",
46
- help: "GET / POST / PUT / PATCH / DELETE",
47
- options: {
48
- get: "GET",
49
- post: "POST",
50
- put: "PUT",
51
- patch: "PATCH",
52
- delete: "DELETE"
53
- }
54
- },
55
- description: {
56
- label: "Description"
57
- },
58
- active: {
59
- label: "Active",
60
- help: "Inactive webhooks are skipped by the dispatcher"
61
- },
62
- definition_json: {
63
- label: "Definition",
64
- help: "Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — full headers/auth/retry/payload config"
65
- },
66
- managed_by: {
67
- label: "Managed By",
68
- help: "Record provenance: platform = framework built-in / package = app/package-declared (boot-seeded from defineStack webhooks) / admin = created in Setup.",
69
- options: {
70
- platform: "platform",
71
- package: "package",
72
- admin: "admin"
73
- }
74
- },
75
- customized: {
76
- label: "Customized",
77
- help: "Set when an admin edits a package-declared webhook; boot seeding will no longer overwrite the row (a deactivated noisy webhook survives redeploys). Meaningless on admin rows."
78
- },
79
- created_at: {
80
- label: "Created At"
81
- },
82
- updated_at: {
83
- label: "Updated At"
84
- }
85
- },
86
- _views: {
87
- active: {
88
- label: "Active"
89
- },
90
- inactive: {
91
- label: "Inactive"
92
- },
93
- by_object: {
94
- label: "By Object"
95
- },
96
- all_webhooks: {
97
- label: "All"
98
- }
99
- }
100
- }
101
- };