@automate.ax/integration-contracts 0.104.0 → 0.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,8 +110,8 @@ export declare const SLACK_REACTION_ITEM_SCHEMA: z.ZodDiscriminatedUnion<[z.ZodO
110
110
  conversationId: z.ZodString;
111
111
  conversationType: z.ZodOptional<z.ZodEnum<{
112
112
  group: "group";
113
- channel: "channel";
114
113
  im: "im";
114
+ channel: "channel";
115
115
  mpim: "mpim";
116
116
  }>>;
117
117
  messageTimestamp: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/integration-contracts",
3
- "version": "0.104.0",
3
+ "version": "0.105.0",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -49,7 +49,7 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@automate.ax/codec": "0.104.0",
52
+ "@automate.ax/codec": "0.105.0",
53
53
  "@cfworker/json-schema": "^4.1.1",
54
54
  "@googleapis/calendar": "^15.0.0",
55
55
  "@googleapis/forms": "^6.0.1",
@@ -1,6 +1,6 @@
1
1
  import * as z from "zod"
2
2
  import {
3
- NOTION_EVENT_SCHEMA,
3
+ NOTION_PUBLIC_EVENT_SCHEMA,
4
4
  notionSemanticEventSchema,
5
5
  type NotionSemanticWebhookEvent,
6
6
  type NotionWebhookEvent,
@@ -144,7 +144,7 @@ export const notionTriggerContracts: NotionTriggerContracts = {
144
144
  "notion.dataSource.undeleted": semanticContract("data_source.undeleted"),
145
145
  "notion.event": {
146
146
  configSchema: NOTION_TRIGGER_CONFIG_SCHEMA,
147
- eventSchema: NOTION_EVENT_SCHEMA,
147
+ eventSchema: NOTION_PUBLIC_EVENT_SCHEMA,
148
148
  },
149
149
  "notion.fileUpload.completed": semanticContract("file_upload.completed"),
150
150
  "notion.fileUpload.created": semanticContract("file_upload.created"),
@@ -29,6 +29,7 @@ import type {
29
29
  ViewDeletedWebhookPayload,
30
30
  ViewUpdatedWebhookPayload,
31
31
  } from "@notionhq/client"
32
+ import type { CamelCasedPropertiesDeep } from "type-fest"
32
33
  import * as z from "zod"
33
34
 
34
35
  type OfficialNotionWebhookEvent =
@@ -294,15 +295,29 @@ const RAW_NOTION_EVENT_SCHEMA = z.discriminatedUnion("type", [
294
295
  ]) satisfies z.ZodType<ExpectedNotionWebhookEvent>
295
296
 
296
297
  /** Current portable Notion webhook payload validated at ingress. */
297
- export type NotionWebhookEvent = z.infer<typeof RAW_NOTION_EVENT_SCHEMA>
298
+ type ProviderNotionWebhookEvent = z.infer<typeof RAW_NOTION_EVENT_SCHEMA>
298
299
 
299
- export type NotionWebhookEventType = NotionWebhookEvent["type"]
300
+ /** Public camelCase representation of a Notion webhook payload. */
301
+ export type NotionWebhookEvent =
302
+ CamelCasedPropertiesDeep<ExpectedNotionWebhookEvent>
303
+
304
+ export type NotionWebhookEventType = ProviderNotionWebhookEvent["type"]
300
305
 
301
306
  export type NotionSemanticWebhookEvent<TType extends NotionWebhookEventType> =
302
307
  Extract<NotionWebhookEvent, { type: TType }>
303
308
 
304
309
  /** Exact current Notion webhook schema shared by ingress and triggers. */
305
- export const NOTION_EVENT_SCHEMA = RAW_NOTION_EVENT_SCHEMA
310
+ export const NOTION_EVENT_SCHEMA =
311
+ RAW_NOTION_EVENT_SCHEMA.transform(camelCaseNotionEvent)
312
+
313
+ /** Validates an already-normalized Notion webhook event for trigger delivery. */
314
+ export const NOTION_PUBLIC_EVENT_SCHEMA = z.custom<NotionWebhookEvent>(
315
+ (value) =>
316
+ typeof value === "object" &&
317
+ value !== null &&
318
+ "type" in value &&
319
+ typeof value.type === "string",
320
+ )
306
321
 
307
322
  /**
308
323
  * Runtime schema for one semantic Notion webhook event.
@@ -314,7 +329,7 @@ export function notionSemanticEventSchema<TType extends NotionWebhookEventType>(
314
329
  ) {
315
330
  return z.custom<NotionSemanticWebhookEvent<TType>>(
316
331
  (value) =>
317
- RAW_NOTION_EVENT_SCHEMA.safeParse(value).success &&
332
+ NOTION_PUBLIC_EVENT_SCHEMA.safeParse(value).success &&
318
333
  typeof value === "object" &&
319
334
  value !== null &&
320
335
  "type" in value &&
@@ -323,6 +338,45 @@ export function notionSemanticEventSchema<TType extends NotionWebhookEventType>(
323
338
  )
324
339
  }
325
340
 
341
+ /**
342
+ * Converts provider-native webhook fields before public trigger delivery.
343
+ *
344
+ * @param event - Validated provider webhook payload.
345
+ */
346
+ function camelCaseNotionEvent(
347
+ event: ProviderNotionWebhookEvent,
348
+ ): NotionWebhookEvent {
349
+ // REVIEW: the recursive transform preserves the raw schema's JSON structure
350
+ // while changing only property names.
351
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Raw schema validation guarantees this result's shape.
352
+ return camelCaseNotionValue(event) as NotionWebhookEvent
353
+ }
354
+
355
+ /**
356
+ * Recursively camel-cases object property names in a JSON-compatible value.
357
+ *
358
+ * @param value - Provider-native JSON value.
359
+ */
360
+ function camelCaseNotionValue(value: unknown): unknown {
361
+ if (Array.isArray(value)) return value.map(camelCaseNotionValue)
362
+ if (!isPlainRecord(value)) return value
363
+ return Object.fromEntries(
364
+ Object.entries(value).map(([key, nestedValue]) => [
365
+ key.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase()),
366
+ camelCaseNotionValue(nestedValue),
367
+ ]),
368
+ )
369
+ }
370
+
371
+ /**
372
+ * Checks whether a value can have provider field names.
373
+ *
374
+ * @param value - Value to inspect.
375
+ */
376
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
377
+ return typeof value === "object" && value !== null
378
+ }
379
+
326
380
  /**
327
381
  * Builds one provider webhook schema over the shared delivery envelope.
328
382
  *