Skip to content

Commit 355e951

Browse files
os-zhuangclaude
andauthored
feat(spec)!: 双源 C1 收敛 — WebhookConfig / WebhookEvent 归 ./integration,./api 侧死删 + 改名 OpenApiWebhookEvent (#4572) (#4581)
* feat(spec)!: resolve the WebhookConfig/WebhookEvent dual source — ./api pair removed/renamed, ./integration keeps the bare names (#4572) The four #4535-C1 baseline rows were the #4411 trap in cross-form: ./api's WebhookEventSchema was a z.object (OpenAPI 3.1 webhook definition) while ./integration's is a z.enum of connector event types — same names, two concepts, and which one you got depended only on the import path. Import-statement-level scan across framework, cloud and objectui: zero external consumers on either side (each pair's only importer is its own unit test; cloud and objectui reference neither name). - api WebhookConfig(Schema): DEAD — wired into nothing, not even RestServerConfigSchema; no runtime reads a REST webhook config. Deleted (major window). Its authorable-surface lines deleted by hand per the #4458/#4568 precedent (plugin-config type, not authorable metadata — no tombstone, no D2 conversion). - api WebhookEvent(Schema): renamed OpenApiWebhookEvent(Schema) — it is the OpenAPI 3.1 top-level `webhooks` descriptor and now sits in the existing OpenApi* family; OpenApi31ExtensionsSchema wiring updated, authored shape unchanged. - integration WebhookConfig/WebhookEvent: untouched, now sole owners of the bare names, so the pair stays one coherent family in one domain. dual-source-exports.baseline.json: exactly the 4 named rows removed (35 → 31). json-schema.manifest: api/WebhookConfig retired, api/WebhookEvent → api/OpenApiWebhookEvent (deliberate rename, not a silent drop). Changeset: @objectstack/spec major with FROM → TO lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * chore(spec): regenerate api-surface + reference docs for #4572 (check:generated --fix, 2 proved stale) api-surface.json: -WebhookConfig(Schema)/-WebhookEvent(Schema) on ./api, +OpenApiWebhookEvent(Schema). Reference docs: the api/connector.mdx page (which documented only the removed ./api pair, under a misleading name) is no longer emitted; rest-server.mdx now documents OpenApiWebhookEvent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * test(spec): make the #4572 bare-name pin compile-time — the runtime barrel import timed out under parallel turbo load typeof import('./rest-server.zod') is type-level only; if a bare WebhookEventSchema/WebhookConfigSchema export returns, the conditional type flips to `true` and `tsc --noEmit` fails the false assignment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d91d39e commit 355e951

10 files changed

Lines changed: 109 additions & 140 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: `@objectstack/spec/api` no longer exports the bare names `WebhookConfig` / `WebhookEvent` — they belong to `./integration` alone (#4572)
6+
7+
The names `WebhookConfig(Schema)` / `WebhookEvent(Schema)` resolved to **two
8+
different declarations** depending on the import path (`./api` vs
9+
`./integration`) — the #4411 dual-source trap, and a cross-form one:
10+
`./api`'s `WebhookEventSchema` was a `z.object` (an OpenAPI 3.1 webhook
11+
*definition* descriptor: `name`/`description`/`method`/`payloadSchema`/
12+
`security`) while `./integration`'s is a `z.enum` of connector event types
13+
(`'record.created'``'rate_limit.exceeded'`). Auto-importing the wrong side
14+
compiled and validated the wrong contract. Resolution (three-repo,
15+
import-statement-level consumer scan: framework, cloud, objectui):
16+
17+
- **Removed** `WebhookConfigSchema` / `WebhookConfig` from
18+
`@objectstack/spec/api`. This pair was dead: wired into nothing — not even
19+
`RestServerConfigSchema` — with zero import-level consumers in all three
20+
repos, and no runtime ever read a REST-server webhook config.
21+
- FROM `import { WebhookConfig } from '@objectstack/spec/api'`
22+
TO: no replacement exists for a REST-server webhook config (it never had a
23+
runtime). For a real outbound webhook use `Webhook` from
24+
`@objectstack/spec/automation`; for a connector webhook use
25+
`WebhookConfig` from `@objectstack/spec/integration` (a **different
26+
shape**: it extends the canonical automation `WebhookSchema` with
27+
`events` / `signatureAlgorithm`, and has no `deliveryConfig` /
28+
`registrationEndpoint` / `enabled`).
29+
- **Renamed** `WebhookEventSchema` / `WebhookEvent` in `@objectstack/spec/api`
30+
`OpenApiWebhookEventSchema` / `OpenApiWebhookEvent` (same shape, rename
31+
only; joins the existing `OpenApi*` family). `OpenApi31ExtensionsSchema.webhooks`
32+
now references the renamed schema — its parsed/authored shape is unchanged.
33+
- FROM `import { WebhookEvent } from '@objectstack/spec/api'`
34+
TO `import { OpenApiWebhookEvent } from '@objectstack/spec/api'` (if you
35+
meant the OpenAPI 3.1 webhook descriptor), or
36+
`import { WebhookEvent } from '@objectstack/spec/integration'` (if you
37+
meant the connector event enum — check which shape you actually consume:
38+
object vs string enum).
39+
- `@objectstack/spec/integration`'s `WebhookConfig(Schema)` /
40+
`WebhookEvent(Schema)` are **unchanged** and are now the sole owners of the
41+
bare names. Imports from `./integration` need no migration.
42+
43+
`dual-source-exports.baseline.json` shrinks by exactly these 4 rows (35 → 31,
44+
#4535 C1).

content/docs/references/api/connector.mdx

Lines changed: 0 additions & 49 deletions
This file was deleted.

content/docs/references/api/meta.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"auth",
2828
"auth-endpoints",
2929
"automation-api",
30-
"connector",
3130
"core-services",
3231
"events",
3332
"export",

content/docs/references/api/rest-server.mdx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ Architecture alignment:
3838
## TypeScript Usage
3939

4040
```typescript
41-
import { BatchEndpointsConfig, Callback, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, OpenApi31Extensions, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';
42-
import type { BatchEndpointsConfig, Callback, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, OpenApi31Extensions, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';
41+
import { BatchEndpointsConfig, Callback, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, OpenApi31Extensions, OpenApiWebhookEvent, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';
42+
import type { BatchEndpointsConfig, Callback, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, OpenApi31Extensions, OpenApiWebhookEvent, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';
4343

4444
// Validate data
4545
const result = BatchEndpointsConfig.parse(data);
@@ -173,6 +173,22 @@ const result = BatchEndpointsConfig.parse(data);
173173
| **pathItemReferences** | `boolean` || Allow $ref in path items (OpenAPI 3.1 feature) |
174174

175175

176+
---
177+
178+
## OpenApiWebhookEvent
179+
180+
### Properties
181+
182+
| Property | Type | Required | Description |
183+
| :--- | :--- | :--- | :--- |
184+
| **name** | `string` || Webhook event identifier (snake_case) |
185+
| **description** | `string` || Human-readable event description |
186+
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` || HTTP method for webhook delivery |
187+
| **payloadSchema** | `string` || JSON Schema $ref for the webhook payload |
188+
| **headers** | `Record<string, string>` | optional | Custom headers to include in webhook delivery |
189+
| **security** | `Enum<'hmac_sha256' \| 'basic' \| 'bearer' \| 'api_key'>[]` || Supported authentication methods for webhook verification |
190+
191+
176192
---
177193

178194
## RestApiConfig

packages/spec/api-surface.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,6 +2939,8 @@
29392939
"OpenApiServerSchema (const)",
29402940
"OpenApiSpec (type)",
29412941
"OpenApiSpecSchema (const)",
2942+
"OpenApiWebhookEvent (type)",
2943+
"OpenApiWebhookEventSchema (const)",
29422944
"OperatorMapping (type)",
29432945
"OperatorMappingSchema (const)",
29442946
"PackageApiContracts (const)",
@@ -3195,10 +3197,6 @@
31953197
"WebSocketPresenceStatus (type)",
31963198
"WebSocketServerConfig (type)",
31973199
"WebSocketServerConfigSchema (const)",
3198-
"WebhookConfig (type)",
3199-
"WebhookConfigSchema (const)",
3200-
"WebhookEvent (type)",
3201-
"WebhookEventSchema (const)",
32023200
"WellKnownCapabilities (type)",
32033201
"WellKnownCapabilitiesSchema (const)",
32043202
"envelopeViolations (function)",

packages/spec/authorable-surface.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,12 @@
16261626
"api/OpenApiSpec:security",
16271627
"api/OpenApiSpec:servers",
16281628
"api/OpenApiSpec:tags",
1629+
"api/OpenApiWebhookEvent:description",
1630+
"api/OpenApiWebhookEvent:headers",
1631+
"api/OpenApiWebhookEvent:method",
1632+
"api/OpenApiWebhookEvent:name",
1633+
"api/OpenApiWebhookEvent:payloadSchema",
1634+
"api/OpenApiWebhookEvent:security",
16291635
"api/OperatorMapping:odata",
16301636
"api/OperatorMapping:operator",
16311637
"api/OperatorMapping:rest",
@@ -2099,16 +2105,6 @@
20992105
"api/WebSocketServerConfig:path",
21002106
"api/WebSocketServerConfig:presence",
21012107
"api/WebSocketServerConfig:reconnectAttempts",
2102-
"api/WebhookConfig:deliveryConfig",
2103-
"api/WebhookConfig:enabled",
2104-
"api/WebhookConfig:events",
2105-
"api/WebhookConfig:registrationEndpoint",
2106-
"api/WebhookEvent:description",
2107-
"api/WebhookEvent:headers",
2108-
"api/WebhookEvent:method",
2109-
"api/WebhookEvent:name",
2110-
"api/WebhookEvent:payloadSchema",
2111-
"api/WebhookEvent:security",
21122108
"api/WellKnownCapabilities:automation",
21132109
"api/WellKnownCapabilities:chunkedUpload",
21142110
"api/WellKnownCapabilities:comments",

packages/spec/dual-source-exports.baseline.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@
3131
"Session — [./api (type)] ≠ [./identity (type)]",
3232
"SessionSchema — [./api (const)] ≠ [./identity (const)]",
3333
"TenantPlan — [./cloud (type)] ≠ [./system (type)]",
34-
"TenantPlanSchema — [./cloud (const)] ≠ [./system (const)]",
35-
"WebhookConfig — [./api (type)] ≠ [./integration (type)]",
36-
"WebhookConfigSchema — [./api (const)] ≠ [./integration (const)]",
37-
"WebhookEvent — [./api (type)] ≠ [./integration (type)]",
38-
"WebhookEventSchema — [./api (const)] ≠ [./integration (const)]"
34+
"TenantPlanSchema — [./cloud (const)] ≠ [./system (const)]"
3935
]
4036
}

packages/spec/json-schema.manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@
366366
"api/OpenApiSecurityScheme",
367367
"api/OpenApiServer",
368368
"api/OpenApiSpec",
369+
"api/OpenApiWebhookEvent",
369370
"api/OperatorMapping",
370371
"api/PackageApiErrorCode",
371372
"api/PackageInstallRequest",
@@ -492,8 +493,6 @@
492493
"api/WebSocketMessageType",
493494
"api/WebSocketPresenceStatus",
494495
"api/WebSocketServerConfig",
495-
"api/WebhookConfig",
496-
"api/WebhookEvent",
497496
"api/WellKnownCapabilities",
498497
"automation/ActionCategory",
499498
"automation/ActionDescriptor",

packages/spec/src/api/rest-server.test.ts

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {
1212
EndpointRegistrySchema,
1313
RestApiConfig,
1414
RestServerConfig,
15-
WebhookEventSchema,
16-
WebhookConfigSchema,
15+
OpenApiWebhookEventSchema,
1716
CallbackSchema,
1817
OpenApi31ExtensionsSchema,
1918
type RestApiConfig as RestApiConfigType,
@@ -656,9 +655,9 @@ describe('Integration Tests', () => {
656655
// OpenAPI 3.1 Webhooks & Callbacks Tests
657656
// ==========================================
658657

659-
describe('WebhookEventSchema', () => {
658+
describe('OpenApiWebhookEventSchema', () => {
660659
it('should accept valid webhook event', () => {
661-
const event = WebhookEventSchema.parse({
660+
const event = OpenApiWebhookEventSchema.parse({
662661
name: 'record_created',
663662
description: 'Fired when a record is created',
664663
payloadSchema: '#/components/schemas/RecordCreated',
@@ -671,7 +670,7 @@ describe('WebhookEventSchema', () => {
671670
});
672671

673672
it('should enforce snake_case name', () => {
674-
expect(() => WebhookEventSchema.parse({
673+
expect(() => OpenApiWebhookEventSchema.parse({
675674
name: 'RecordCreated',
676675
description: 'Bad name',
677676
payloadSchema: '#/ref',
@@ -680,7 +679,7 @@ describe('WebhookEventSchema', () => {
680679
});
681680

682681
it('should accept event with custom headers', () => {
683-
const event = WebhookEventSchema.parse({
682+
const event = OpenApiWebhookEventSchema.parse({
684683
name: 'sync_completed',
685684
description: 'Sync finished',
686685
payloadSchema: '#/ref',
@@ -693,52 +692,35 @@ describe('WebhookEventSchema', () => {
693692

694693
it('should accept all security methods', () => {
695694
const methods = ['hmac_sha256', 'basic', 'bearer', 'api_key'] as const;
696-
const event = WebhookEventSchema.parse({
695+
const event = OpenApiWebhookEventSchema.parse({
697696
name: 'test_event',
698697
description: 'Test',
699698
payloadSchema: '#/ref',
700699
security: [...methods],
701700
});
702701
expect(event.security).toHaveLength(4);
703702
});
704-
});
705-
706-
describe('WebhookConfigSchema', () => {
707-
it('should accept config with defaults', () => {
708-
const config = WebhookConfigSchema.parse({
709-
events: [
710-
{
711-
name: 'record_created',
712-
description: 'Record created',
713-
payloadSchema: '#/ref',
714-
security: ['hmac_sha256'],
715-
},
716-
],
717-
deliveryConfig: {},
718-
});
719-
720-
expect(config.enabled).toBe(false);
721-
expect(config.deliveryConfig.maxRetries).toBe(3);
722-
expect(config.deliveryConfig.timeoutMs).toBe(30000);
723-
expect(config.deliveryConfig.signatureHeader).toBe('X-Signature-256');
724-
expect(config.registrationEndpoint).toBe('/webhooks');
725-
});
726-
727-
it('should accept full delivery config', () => {
728-
const config = WebhookConfigSchema.parse({
729-
enabled: true,
730-
events: [],
731-
deliveryConfig: {
732-
maxRetries: 5,
733-
retryIntervalMs: 10000,
734-
timeoutMs: 60000,
735-
signatureHeader: 'X-Hub-Signature',
736-
},
737-
registrationEndpoint: '/hooks',
738-
});
739703

740-
expect(config.deliveryConfig.maxRetries).toBe(5);
741-
expect(config.registrationEndpoint).toBe('/hooks');
704+
// v17 dual-source cleanup (#4572): the bare names WebhookEvent(Schema) /
705+
// WebhookConfig(Schema) now belong to @objectstack/spec/integration alone
706+
// (connector event enum + connector webhook config). The ./api pair was the
707+
// #4411-style trap: same names, different concepts, different forms
708+
// (z.object here vs z.enum there). WebhookConfig(Schema) on ./api was dead
709+
// — wired into nothing (not even RestServerConfigSchema) — and was removed
710+
// rather than renamed. Pin: this module no longer declares the bare names.
711+
// The pin is compile-time (typeof import is type-level only — no runtime
712+
// barrel load): if either bare name is re-added here, the conditional type
713+
// flips to `true` and the `false` assignment fails `tsc --noEmit`.
714+
it('does not re-expose the bare WebhookEvent/WebhookConfig names from ./api', () => {
715+
type RestServerModule = typeof import('./rest-server.zod');
716+
const hasBareEventSchema: 'WebhookEventSchema' extends keyof RestServerModule
717+
? true
718+
: false = false;
719+
const hasBareConfigSchema: 'WebhookConfigSchema' extends keyof RestServerModule
720+
? true
721+
: false = false;
722+
expect(hasBareEventSchema).toBe(false);
723+
expect(hasBareConfigSchema).toBe(false);
742724
});
743725
});
744726

packages/spec/src/api/rest-server.zod.ts

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,16 @@ export type RouteGenerationConfigInput = z.input<typeof RouteGenerationConfigSch
399399
// ==========================================
400400

401401
/**
402-
* Webhook Event Schema
403-
* Defines an event that can trigger a webhook delivery
402+
* OpenAPI Webhook Event Schema
403+
* Defines a webhook event as declared in an OpenAPI 3.1 document's top-level
404+
* `webhooks` section — an API-documentation descriptor, NOT a runtime
405+
* subscription. (Renamed from `WebhookEventSchema` in v17 — that bare name
406+
* collided with the connector event enum in `@objectstack/spec/integration`,
407+
* a different concept AND a different form: z.object here vs z.enum there.
408+
* The outbound webhook an author configures is `WebhookSchema` in
409+
* `@objectstack/spec/automation`.)
404410
*/
405-
export const WebhookEventSchema = lazySchema(() => z.object({
411+
export const OpenApiWebhookEventSchema = lazySchema(() => z.object({
406412
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Webhook event identifier (snake_case)'),
407413
description: z.string().describe('Human-readable event description'),
408414
method: HttpMethod.default('POST').describe('HTTP method for webhook delivery'),
@@ -413,25 +419,7 @@ export const WebhookEventSchema = lazySchema(() => z.object({
413419
).describe('Supported authentication methods for webhook verification'),
414420
}));
415421

416-
export type WebhookEvent = z.infer<typeof WebhookEventSchema>;
417-
418-
/**
419-
* Webhook Configuration Schema
420-
* Top-level webhook configuration for the REST API
421-
*/
422-
export const WebhookConfigSchema = lazySchema(() => z.object({
423-
enabled: z.boolean().default(false).describe('Enable webhook support'),
424-
events: z.array(WebhookEventSchema).describe('Registered webhook events'),
425-
deliveryConfig: z.object({
426-
maxRetries: z.number().int().default(3).describe('Maximum delivery retry attempts'),
427-
retryIntervalMs: z.number().int().default(5000).describe('Milliseconds between retry attempts'),
428-
timeoutMs: z.number().int().default(30000).describe('Delivery request timeout in milliseconds'),
429-
signatureHeader: z.string().default('X-Signature-256').describe('Header name for webhook signature'),
430-
}).describe('Webhook delivery configuration'),
431-
registrationEndpoint: z.string().default('/webhooks').describe('URL path for webhook registration'),
432-
}));
433-
434-
export type WebhookConfig = z.infer<typeof WebhookConfigSchema>;
422+
export type OpenApiWebhookEvent = z.infer<typeof OpenApiWebhookEventSchema>;
435423

436424
/**
437425
* Callback Schema
@@ -451,7 +439,7 @@ export type Callback = z.infer<typeof CallbackSchema>;
451439
* Extensions specific to OpenAPI 3.1 specification
452440
*/
453441
export const OpenApi31ExtensionsSchema = lazySchema(() => z.object({
454-
webhooks: z.record(z.string(), WebhookEventSchema).optional()
442+
webhooks: z.record(z.string(), OpenApiWebhookEventSchema).optional()
455443
.describe('OpenAPI 3.1 webhooks (top-level webhook definitions)'),
456444
callbacks: z.record(z.string(), z.array(CallbackSchema)).optional()
457445
.describe('OpenAPI 3.1 callbacks (async response definitions)'),

0 commit comments

Comments
 (0)