@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.694.f66a64a.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.
Files changed (267) hide show
  1. package/CLAUDE.md +0 -28
  2. package/README.md +0 -105
  3. package/application/commands/integration-commands.js +10 -20
  4. package/application/commands/scheduler-commands.js +7 -11
  5. package/application/commands/user-commands.js +5 -17
  6. package/application/index.js +2 -6
  7. package/core/Worker.js +7 -31
  8. package/core/create-handler.js +0 -9
  9. package/credential/repositories/credential-repository-documentdb.js +0 -34
  10. package/credential/repositories/credential-repository-interface.js +0 -15
  11. package/credential/repositories/credential-repository-mongo.js +0 -23
  12. package/credential/repositories/credential-repository-postgres.js +0 -28
  13. package/database/config.js +24 -48
  14. package/database/documentdb-utils.js +2 -2
  15. package/database/encryption/encryption-schema-registry.js +12 -93
  16. package/database/prisma.js +2 -4
  17. package/errors/index.js +4 -0
  18. package/errors/mapping-already-exists-error.js +14 -0
  19. package/generated/prisma-mongodb/edge.js +38 -33
  20. package/generated/prisma-mongodb/index-browser.js +35 -30
  21. package/generated/prisma-mongodb/index.d.ts +1880 -3191
  22. package/generated/prisma-mongodb/index.js +38 -33
  23. package/generated/prisma-mongodb/package.json +1 -1
  24. package/generated/prisma-mongodb/schema.prisma +50 -86
  25. package/generated/prisma-mongodb/wasm.js +38 -33
  26. package/generated/prisma-postgresql/edge.js +38 -33
  27. package/generated/prisma-postgresql/index-browser.js +35 -30
  28. package/generated/prisma-postgresql/index.d.ts +2050 -3390
  29. package/generated/prisma-postgresql/index.js +38 -33
  30. package/generated/prisma-postgresql/package.json +1 -1
  31. package/generated/prisma-postgresql/schema.prisma +48 -81
  32. package/generated/prisma-postgresql/wasm.js +38 -33
  33. package/handlers/WEBHOOKS.md +29 -0
  34. package/handlers/app-definition-loader.js +19 -128
  35. package/handlers/app-handler-helpers.js +3 -25
  36. package/handlers/backend-utils.js +25 -31
  37. package/handlers/middleware/admin-auth.js +1 -14
  38. package/handlers/routers/auth.js +10 -46
  39. package/handlers/routers/health.js +4 -13
  40. package/handlers/routers/integration-defined-routers.js +92 -144
  41. package/handlers/routers/integration-webhook-routers.js +64 -102
  42. package/handlers/workers/integration-defined-workers.js +2 -5
  43. package/handlers/workers/integration-triggers.js +25 -0
  44. package/index.js +3 -65
  45. package/infrastructure/scheduler/index.js +6 -19
  46. package/infrastructure/scheduler/recurrence.js +204 -0
  47. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  48. package/integrations/data-defined/README.md +185 -0
  49. package/integrations/data-defined/canonical.js +11 -0
  50. package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
  51. package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
  52. package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
  53. package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
  54. package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
  55. package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
  56. package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
  57. package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
  58. package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
  59. package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
  60. package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
  61. package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
  62. package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
  63. package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
  64. package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
  65. package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
  66. package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
  67. package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
  68. package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
  69. package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
  70. package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
  71. package/integrations/data-defined/compile.js +80 -0
  72. package/integrations/data-defined/config-form.js +137 -0
  73. package/integrations/data-defined/data-defined-integration.js +417 -0
  74. package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
  75. package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
  76. package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
  77. package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
  78. package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
  79. package/integrations/data-defined/examples/github-linear.definition.json +316 -0
  80. package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
  81. package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
  82. package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
  83. package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
  84. package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
  85. package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
  86. package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
  87. package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
  88. package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
  89. package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
  90. package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
  91. package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
  92. package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
  93. package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
  94. package/integrations/data-defined/expressions.js +225 -0
  95. package/integrations/data-defined/index.js +39 -0
  96. package/integrations/data-defined/interpreter.js +89 -0
  97. package/integrations/data-defined/manifests.js +125 -0
  98. package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
  99. package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
  100. package/integrations/data-defined/router.js +97 -0
  101. package/integrations/data-defined/runtime.js +208 -0
  102. package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
  103. package/integrations/data-defined/schema/installation.v0.1.schema.json +130 -0
  104. package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +902 -0
  105. package/integrations/data-defined/schema/records.md +98 -0
  106. package/integrations/data-defined/schema-check.js +58 -0
  107. package/integrations/data-defined/scripts/validate.js +66 -0
  108. package/integrations/data-defined/steps.js +89 -0
  109. package/integrations/data-defined/transforms.js +91 -0
  110. package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
  111. package/integrations/data-defined/validate.js +384 -0
  112. package/integrations/data-defined/versioning.js +176 -0
  113. package/integrations/data-defined/webhooks.js +119 -0
  114. package/integrations/index.js +0 -1
  115. package/integrations/integration-base.js +117 -0
  116. package/integrations/integration-router.js +101 -131
  117. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  118. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  119. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  120. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  121. package/integrations/repositories/integration-mapping-repository.js +29 -0
  122. package/integrations/repositories/integration-repository-documentdb.js +9 -37
  123. package/integrations/repositories/integration-repository-interface.js +4 -17
  124. package/integrations/repositories/integration-repository-mongo.js +96 -64
  125. package/integrations/repositories/integration-repository-postgres.js +81 -63
  126. package/integrations/repositories/process-repository-mongo.js +1 -1
  127. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  128. package/integrations/triggers.js +244 -0
  129. package/integrations/use-cases/create-integration.js +20 -55
  130. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  131. package/integrations/use-cases/index.js +1 -7
  132. package/integrations/use-cases/run-trigger-tick.js +132 -0
  133. package/integrations/use-cases/run-trigger.js +77 -0
  134. package/integrations/utils/map-integration-dto.js +14 -43
  135. package/modules/index.js +0 -14
  136. package/modules/module-factory.js +0 -12
  137. package/modules/module.js +2 -53
  138. package/modules/repositories/module-repository-documentdb.js +6 -51
  139. package/modules/repositories/module-repository-interface.js +2 -19
  140. package/modules/repositories/module-repository-mongo.js +74 -67
  141. package/modules/repositories/module-repository-postgres.js +74 -66
  142. package/modules/repositories/module-repository.js +1 -2
  143. package/modules/requester/requester.js +5 -35
  144. package/modules/use-cases/get-entities-for-user.js +13 -26
  145. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  146. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  147. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  148. package/modules/use-cases/get-module.js +4 -10
  149. package/modules/use-cases/process-authorization-callback.js +19 -53
  150. package/modules/use-cases/refresh-entity-options.js +4 -10
  151. package/modules/use-cases/test-module-auth.js +4 -10
  152. package/package.json +6 -6
  153. package/prisma-mongodb/schema.prisma +50 -86
  154. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
  155. package/prisma-postgresql/schema.prisma +48 -81
  156. package/queues/index.js +0 -25
  157. package/queues/queuer-util.js +9 -2
  158. package/types/index.d.ts +0 -2
  159. package/user/repositories/user-repository-documentdb.js +2 -27
  160. package/user/repositories/user-repository-interface.js +0 -18
  161. package/user/repositories/user-repository-mongo.js +0 -24
  162. package/user/repositories/user-repository-postgres.js +0 -18
  163. package/user/use-cases/authenticate-user.js +1 -7
  164. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  165. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  166. package/user/user.js +1 -64
  167. package/credential/use-cases/credential-operations-for-user.js +0 -202
  168. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  169. package/database/organization-where.js +0 -18
  170. package/database/unique-violation.js +0 -25
  171. package/extensions/bootstrap-runner.js +0 -43
  172. package/extensions/extension-loader.js +0 -187
  173. package/extensions/index.js +0 -41
  174. package/extensions/initialize-app.js +0 -123
  175. package/extensions/route-mounter.js +0 -103
  176. package/extensions/schema-composer.js +0 -188
  177. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  178. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  179. package/integrations/repositories/integration-not-found.js +0 -14
  180. package/integrations/schedule-config.js +0 -80
  181. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  182. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  183. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  184. package/management-api/create-management-api-router.js +0 -49
  185. package/management-api/error-response.js +0 -116
  186. package/management-api/management-api-config.js +0 -151
  187. package/management-api/meta-router.js +0 -76
  188. package/management-api/openapi/build-openapi.js +0 -144
  189. package/management-api/openapi/components.js +0 -415
  190. package/management-api/openapi/v1-operations.js +0 -83
  191. package/management-api/openapi/v2-operations.js +0 -264
  192. package/management-api/route-registry.js +0 -411
  193. package/management-api/test-support/http-client.js +0 -39
  194. package/management-api/use-cases/get-management-api-meta.js +0 -57
  195. package/management-api/v1-deprecation.js +0 -120
  196. package/management-api/v2/create-v2-router.js +0 -85
  197. package/management-api/v2/dependencies.js +0 -281
  198. package/management-api/v2/dto.js +0 -143
  199. package/management-api/v2/handlers/authorization.js +0 -94
  200. package/management-api/v2/handlers/credentials.js +0 -33
  201. package/management-api/v2/handlers/entities.js +0 -77
  202. package/management-api/v2/handlers/integrations.js +0 -128
  203. package/management-api/v2/handlers/proxy.js +0 -34
  204. package/management-api/v2/request.js +0 -85
  205. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  206. package/modules/domain/authorization-session.js +0 -144
  207. package/modules/entity-scope.js +0 -15
  208. package/modules/entity-type-registry.js +0 -311
  209. package/modules/proxy/proxy-bases.js +0 -169
  210. package/modules/proxy/proxy-errors.js +0 -31
  211. package/modules/proxy/proxy-path.js +0 -183
  212. package/modules/proxy/proxy-policy.js +0 -277
  213. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  214. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  215. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  216. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  217. package/modules/repositories/entity-not-found.js +0 -18
  218. package/modules/requester/proxy-request.js +0 -245
  219. package/modules/scope-config.js +0 -192
  220. package/modules/scope-policy.js +0 -112
  221. package/modules/scope-resolution.js +0 -288
  222. package/modules/scope-visibility.js +0 -247
  223. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  224. package/modules/tests/doubles/ownership-matching.js +0 -18
  225. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  226. package/modules/use-cases/create-entity-from-credential.js +0 -113
  227. package/modules/use-cases/entity-operations-for-user.js +0 -162
  228. package/modules/use-cases/entity-types.js +0 -139
  229. package/modules/use-cases/execute-entity-proxy-request.js +0 -331
  230. package/modules/use-cases/get-authorization-requirements.js +0 -100
  231. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  232. package/modules/use-cases/manage-access.js +0 -21
  233. package/modules/use-cases/multi-step-authorization.js +0 -329
  234. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  235. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  236. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  237. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  238. package/providers/index.js +0 -23
  239. package/providers/resolve-provider.js +0 -93
  240. package/providers/runtime-detection.js +0 -124
  241. package/queues/providers/index.js +0 -11
  242. package/queues/providers/netlify-background-provider.js +0 -150
  243. package/queues/providers/qstash-queue-provider.js +0 -219
  244. package/queues/providers/sqs-queue-provider.js +0 -70
  245. package/queues/queue-client-interface.js +0 -55
  246. package/queues/queue-message-auth.js +0 -193
  247. package/queues/queue-provider-factory.js +0 -101
  248. package/queues/queue-provider.js +0 -65
  249. package/queues/sqs-queue-client.js +0 -81
  250. package/types/modules/index.d.ts +0 -214
  251. package/types/user/index.d.ts +0 -134
  252. package/user/adopter-jwt-config.js +0 -177
  253. package/user/adopter-jwt-errors.js +0 -59
  254. package/user/adopter-token-verifier.js +0 -122
  255. package/user/app-user-helpers.js +0 -97
  256. package/user/create-user-authentication.js +0 -64
  257. package/user/membership.js +0 -51
  258. package/user/repositories/organization-membership-record.js +0 -19
  259. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  260. package/user/repositories/organization-membership-repository-factory.js +0 -42
  261. package/user/repositories/organization-membership-repository-interface.js +0 -57
  262. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  263. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  264. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  265. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  266. package/user/use-cases/ensure-organization-membership.js +0 -92
  267. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Turning a provider's webhook delivery into the module events a definition listens for. Pure; no I/O.
3
+ *
4
+ * Providers disagree about everything: where the topic is (a body field, a header, two fields combined), whether one
5
+ * POST carries one event or a batch (HubSpot sends an array, QuickBooks nests entities inside notifications), which
6
+ * account it is about, and whether the first request is a handshake to answer synchronously (Slack url_verification,
7
+ * Asana X-Hook-Secret). A manifest says so in its `webhook` block, with the same template expressions definitions
8
+ * use, so none of it is code:
9
+ *
10
+ * "webhook": {
11
+ * "scope": "app", // one URL for every account ("installation": one per install)
12
+ * "batch": "body", // path to the array of events; [outer, inner] for two levels
13
+ * "topic": "{{item.subscriptionType}}", // the provider's topic; a list tries each until one is non-empty
14
+ * "eventId": "{{item.eventId}}", // dedupe id
15
+ * "account": "{{item.portalId}}", // the connected account (entity externalId) the event is about
16
+ * "verify": "verifyWebhookSignature", // optional Api method: ({ headers, body, query }) -> boolean
17
+ * "handshake": { "when": "...", "status": 200, "headers": {...}, "body": {...} },
18
+ * "unwrap": "payload" // a form-encoded field holding the JSON body (Slack interactivity)
19
+ * }
20
+ *
21
+ * and each event says which provider topic it is (`match`, default: the event key) and optionally a condition on the
22
+ * record (`when`): a phone module reports a missed call as call.completed with status missed.
23
+ *
24
+ * Template context: { body, headers, query, item, parent }. `item` is the event (the body itself when there is no
25
+ * batch); `parent` is the outer item of a two-level batch (a QuickBooks notification, which carries the realmId).
26
+ */
27
+ const { resolve, evaluate, getPath } = require('./expressions');
28
+
29
+ const list = (v) => (v == null ? [] : Array.isArray(v) ? v : [v]);
30
+ const str = (v) => (v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v));
31
+
32
+ /** The first template in `tpls` that renders to a non-empty string. */
33
+ function renderFirst(tpls, ctx) {
34
+ for (const t of list(tpls)) {
35
+ const v = str(resolve(t, ctx)).trim();
36
+ if (v) return v;
37
+ }
38
+ return '';
39
+ }
40
+
41
+ /** Lower-case header names, as Express and Lambda deliver them, so manifests can always write headers.x-shopify-topic. */
42
+ function normaliseHeaders(headers = {}) {
43
+ return Object.fromEntries(Object.entries(headers || {}).map(([k, v]) => [k.toLowerCase(), Array.isArray(v) ? v[0] : v]));
44
+ }
45
+
46
+ /** The delivery as the manifest sees it: lower-case headers, and the JSON body unwrapped from a form field if declared. */
47
+ function prepare(webhook = {}, delivery) {
48
+ let body = delivery.body;
49
+ if (webhook.unwrap && body && typeof body[webhook.unwrap] === 'string') {
50
+ try { body = JSON.parse(body[webhook.unwrap]); } catch { /* not JSON: leave the body as delivered */ }
51
+ }
52
+ return { body, headers: normaliseHeaders(delivery.headers), query: delivery.query || {} };
53
+ }
54
+
55
+ /** Split one delivery into events: [{ item, parent }]. */
56
+ function splitBatch(webhook = {}, input) {
57
+ const paths = list(webhook.batch);
58
+ const at = (obj, p) => (p === 'body' ? obj.body : p === '$' ? obj : getPath(obj, '$.' + p));
59
+ if (!paths.length) return [{ item: input.body, parent: null }];
60
+ const outer = at(input, paths[0]);
61
+ const outerItems = Array.isArray(outer) ? outer : outer == null ? [] : [outer];
62
+ if (paths.length === 1) return outerItems.map((item) => ({ item, parent: null }));
63
+ return outerItems.flatMap((parent) => {
64
+ const inner = getPath(parent, '$.' + paths[1]);
65
+ return (Array.isArray(inner) ? inner : inner == null ? [] : [inner]).map((item) => ({ item, parent }));
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Every module event in one delivery.
71
+ * @param {object} manifest the module's capability manifest
72
+ * @param {{ body, headers?, query? }} delivery
73
+ * @returns {Array<{ topic: string, providerTopic: string, eventId: string|null, account: string|null, event: object, record: any }>}
74
+ * topic is the manifest's event key (what definitions name); event is the item the workflow sees as $.event.
75
+ */
76
+ function eventsIn(manifest, delivery) {
77
+ const webhook = manifest?.webhook;
78
+ if (!webhook) return [];
79
+ const input = prepare(webhook, delivery);
80
+ const out = [];
81
+ for (const { item, parent } of splitBatch(webhook, input)) {
82
+ const ctx = { ...input, item, parent };
83
+ const providerTopic = renderFirst(webhook.topic, ctx);
84
+ if (!providerTopic) continue;
85
+ const eventId = renderFirst(webhook.eventId, ctx) || null;
86
+ const account = webhook.account ? renderFirst(webhook.account, ctx) || null : null;
87
+ for (const [key, ev] of Object.entries(manifest.events || {})) {
88
+ if (ev.delivery && ev.delivery !== 'webhook') continue;
89
+ if ((ev.match ?? key) !== providerTopic) continue;
90
+ const record = ev.record ? getPath(item, '$.' + ev.record) : item;
91
+ if (ev.when && !evaluate(ev.when, { event: item, record, headers: input.headers, parent })) continue;
92
+ out.push({ topic: key, providerTopic, eventId, account, event: item, record });
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+
98
+ /** A handshake to answer at receipt, before anything is queued: { status, headers, body } or null. */
99
+ function handshakeFor(manifest, delivery) {
100
+ const h = manifest?.webhook?.handshake;
101
+ if (!h) return null;
102
+ const ctx = prepare(manifest.webhook, delivery);
103
+ if (!evaluate(h.when, ctx)) return null;
104
+ return { status: h.status || 200, headers: resolve(h.headers || {}, ctx), body: h.body === undefined ? '' : resolve(h.body, ctx) };
105
+ }
106
+
107
+ /**
108
+ * Which module of a definition a delivery came from. The URL names it (?module=<module name>); without that, the one
109
+ * module whose manifest recognises an event in the body. Returns module keys of the definition (usually one).
110
+ */
111
+ function modulesFor(def, manifests, delivery) {
112
+ const named = delivery.query?.module;
113
+ const keys = Object.entries(def.modules || {}).filter(([, m]) => (named ? m.module === named : true)).map(([k]) => k);
114
+ if (named) return keys;
115
+ const recognised = keys.filter((k) => eventsIn(manifests[def.modules[k].module], delivery).length > 0);
116
+ return recognised.length === 1 ? recognised : [];
117
+ }
118
+
119
+ module.exports = { eventsIn, handshakeFor, modulesFor, splitBatch, renderFirst, normaliseHeaders };
@@ -21,7 +21,6 @@ module.exports = {
21
21
  Options,
22
22
  createIntegrationRouter,
23
23
  checkRequiredParams,
24
- // Deprecated (ADR-059): a wrapper over getEntityTypeRegistry.
25
24
  getModulesDefinitionFromIntegrationClasses,
26
25
  LoadIntegrationContextUseCase,
27
26
  validateExtensionBinding,
@@ -22,6 +22,7 @@ const { getTelemetry } = require('../telemetry/telemetry-runtime');
22
22
  const { instrumentHandler } = require('../telemetry/instrument-handler');
23
23
  const { bindTelemetryContext } = require('../telemetry/bind-telemetry-context');
24
24
  const { getLogger } = require('../logs');
25
+ const { TRIGGER_RUN, TRIGGER_TICK, resolveTrigger } = require('./triggers');
25
26
 
26
27
  const constantsToBeMigrated = {
27
28
  defaultEvents: {
@@ -35,6 +36,8 @@ const constantsToBeMigrated = {
35
36
  REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
36
37
  WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
37
38
  ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
39
+ [TRIGGER_RUN]: TRIGGER_RUN, // Queue worker: one recurring trigger run (ADR-055)
40
+ [TRIGGER_TICK]: TRIGGER_TICK, // Queue worker: a trigger tick carrying on from its page cursor
38
41
  // etc...
39
42
  },
40
43
  types: {
@@ -91,6 +94,9 @@ class IntegrationBase {
91
94
  extensions: {},
92
95
  // usage: { canonical: ['records.synced'], custom: { 'deals.enriched': { unit, label } } }
93
96
  usage: {},
97
+ // Recurring triggers (ADR-055) — see integrations/triggers.js
98
+ // Shape: { [name]: { every | at | cron, timezone?, tick?, when?, overlap?, configurable?, handler } }
99
+ triggers: {},
94
100
  display: {
95
101
  name: 'Integration Name',
96
102
  logo: '',
@@ -171,6 +177,14 @@ class IntegrationBase {
171
177
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
172
178
  handler: this.onWebhook,
173
179
  },
180
+ [TRIGGER_RUN]: {
181
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
182
+ handler: this.onTriggerRun,
183
+ },
184
+ [TRIGGER_TICK]: {
185
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
186
+ handler: this.onTriggerTick,
187
+ },
174
188
  };
175
189
  }
176
190
 
@@ -457,6 +471,35 @@ class IntegrationBase {
457
471
  );
458
472
  }
459
473
 
474
+ /**
475
+ * Insert-only mapping write; usable as an atomic claim/lock row.
476
+ * @throws {MappingAlreadyExistsError} If a mapping for sourceId exists
477
+ */
478
+ async createMapping(sourceId, mapping) {
479
+ if (!sourceId) {
480
+ throw new Error(`sourceId must be set`);
481
+ }
482
+ return await this.integrationMappingRepository.createMapping(
483
+ this.id,
484
+ sourceId,
485
+ mapping
486
+ );
487
+ }
488
+
489
+ /**
490
+ * Deletes the mapping for sourceId. A missing mapping is not an error
491
+ * (resolves with deletedCount 0).
492
+ */
493
+ async deleteMapping(sourceId) {
494
+ if (!sourceId) {
495
+ throw new Error(`sourceId must be set`);
496
+ }
497
+ return await this.integrationMappingRepository.deleteMapping(
498
+ this.id,
499
+ sourceId
500
+ );
501
+ }
502
+
460
503
  /**
461
504
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
462
505
  */
@@ -493,10 +536,84 @@ class IntegrationBase {
493
536
  } else if (this.status === 'NEEDS_CONFIG') {
494
537
  await this.persistStatus('ENABLED');
495
538
  }
539
+ await this.reportTriggerChoices();
496
540
  }
497
541
 
498
542
  async onDelete(params) {}
499
543
 
544
+ /**
545
+ * The recurring triggers declared for one installation (ADR-055): `Definition.triggers`, each with its `name`.
546
+ * Static so the tick can read them from a record without hydrating the installation; a class whose triggers
547
+ * depend on the installation (data-defined integrations, `Definition.instanceTriggers`) overrides it.
548
+ * @param {{ config?: Object, record?: Object }} [installation]
549
+ * @returns {Promise<Array<Object>>}
550
+ */
551
+ static async triggersFor(installation = {}) {
552
+ return Object.entries(this.Definition?.triggers || {}).map(([name, t]) => ({ ...t, name }));
553
+ }
554
+
555
+ /** This installation's trigger declarations (the static `triggersFor`, unless a subclass knows better). */
556
+ async triggerDeclarations() {
557
+ return this.constructor.triggersFor({ config: this.config || {}, record: this.record });
558
+ }
559
+
560
+ /**
561
+ * Run one recurring trigger: the queue worker dispatches `FRIGG_TRIGGER_RUN` here with
562
+ * `{ integrationId, trigger, scheduledFor }` after hydrating the installation. The handler is called as
563
+ * `handler(payload, ctx)` with `payload = { trigger, scheduledFor }` and `ctx = { modules, config, log, integration }`
564
+ * (ADR-054); a string handler names a method on this instance. Overlap and the run record: use-cases/run-trigger.js.
565
+ */
566
+ async onTriggerRun({ data = {} } = {}) {
567
+ const declarations = await this.triggerDeclarations();
568
+ const declaration = declarations.find((t) => t.name === data.trigger);
569
+ const resolved = declaration ? resolveTrigger(declaration.name, declaration, this.config || {}) : null;
570
+ const payload = { trigger: data.trigger, scheduledFor: data.scheduledFor };
571
+ const invoke = async (p) => {
572
+ const handler = typeof declaration.handler === 'string' ? this[declaration.handler] : declaration.handler;
573
+ if (typeof handler !== 'function') throw new Error(`Trigger "${declaration.name}" handler "${declaration.handler}" is not a method of ${this.constructor.Definition?.name}`);
574
+ return handler.call(this, p, { modules: this.modules, config: this.config || {}, log: this.logger, integration: this });
575
+ };
576
+ const { RunTrigger } = require('./use-cases/run-trigger');
577
+ if (!this.triggerProcessRepository) {
578
+ const { createProcessRepository } = require('./repositories/process-repository-factory');
579
+ this.triggerProcessRepository = createProcessRepository();
580
+ }
581
+ const outcome = await new RunTrigger({ processRepository: this.triggerProcessRepository }).execute({ integration: this, resolved, invoke, payload });
582
+ if (outcome.status !== 'ran') {
583
+ this.logger.info('Trigger run not started', { eventName: `${this.logger.name}.trigger_${outcome.status}`, trigger: data.trigger, reason: outcome.reason });
584
+ }
585
+ return outcome;
586
+ }
587
+
588
+ /** A tick that ran out of time continues here, on a dry instance, from its page cursor. */
589
+ async onTriggerTick({ data = {}, context } = {}) {
590
+ const { runTriggerTick } = require('./use-cases/run-trigger-tick');
591
+ return runTriggerTick(this.constructor, data, context);
592
+ }
593
+
594
+ /**
595
+ * Report each trigger timing this installation chose that cannot be used (not configurable, or under the
596
+ * floor): the trigger keeps its default, and the integration gets a warning message saying why. No-op for an
597
+ * integration without triggers.
598
+ */
599
+ async reportTriggerChoices() {
600
+ if (this.id === undefined || this.id === null) return [];
601
+ try {
602
+ const declarations = await this.triggerDeclarations();
603
+ const problems = declarations
604
+ .map((t) => ({ name: t.name, problems: resolveTrigger(t.name, t, this.config || {}).problems }))
605
+ .filter((p) => p.problems.length);
606
+ for (const p of problems) {
607
+ await this.updateIntegrationMessages.execute(this.id, 'warnings', 'Trigger timing not changed', `${p.name}: ${p.problems.join('; ')}`, Date.now());
608
+ }
609
+ return problems;
610
+ } catch (error) {
611
+ // A report, never a reason to fail the update.
612
+ this.logger.warn('Trigger choices not checked', { eventName: `${this.logger.name}.trigger_choices_unchecked`, error });
613
+ return [];
614
+ }
615
+ }
616
+
500
617
  async getConfigOptions() {
501
618
  const options = {
502
619
  jsonSchema: {},