@friggframework/core 2.0.0--canary.703.12b980b.0 → 2.0.0--canary.707.df80bfd.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 (308) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +28 -0
  3. package/README.md +105 -0
  4. package/application/commands/integration-commands.js +20 -10
  5. package/application/commands/scheduler-commands.js +11 -7
  6. package/application/commands/user-commands.js +17 -5
  7. package/application/index.js +6 -2
  8. package/core/Worker.js +31 -7
  9. package/core/create-handler.js +9 -0
  10. package/credential/repositories/credential-repository-documentdb.js +34 -0
  11. package/credential/repositories/credential-repository-interface.js +15 -0
  12. package/credential/repositories/credential-repository-mongo.js +23 -0
  13. package/credential/repositories/credential-repository-postgres.js +28 -0
  14. package/credential/use-cases/credential-operations-for-user.js +202 -0
  15. package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
  16. package/database/config.js +48 -24
  17. package/database/documentdb-encryption-service.js +9 -22
  18. package/database/documentdb-utils.js +2 -2
  19. package/database/encryption/README.md +78 -14
  20. package/database/encryption/documentdb-encryption-service.md +78 -1
  21. package/database/encryption/encryption-config.js +219 -0
  22. package/database/encryption/encryption-schema-registry.js +121 -24
  23. package/database/encryption/field-encryption-service.js +69 -5
  24. package/database/encryption/integration-mapping-encryption.js +1 -1
  25. package/database/encryption/test-support/fake-ciphertext.js +15 -0
  26. package/database/organization-where.js +18 -0
  27. package/database/prisma.js +15 -38
  28. package/database/unique-violation.js +25 -0
  29. package/database/use-cases/check-encryption-health-use-case.js +43 -52
  30. package/errors/index.js +0 -4
  31. package/extensions/bootstrap-runner.js +43 -0
  32. package/extensions/extension-loader.js +230 -0
  33. package/extensions/index.js +44 -0
  34. package/extensions/initialize-app.js +123 -0
  35. package/extensions/prisma-composer.js +270 -0
  36. package/extensions/route-mounter.js +103 -0
  37. package/extensions/schema-composer.js +188 -0
  38. package/generated/prisma-mongodb/edge.js +33 -42
  39. package/generated/prisma-mongodb/index-browser.js +30 -39
  40. package/generated/prisma-mongodb/index.d.ts +3187 -2005
  41. package/generated/prisma-mongodb/index.js +33 -42
  42. package/generated/prisma-mongodb/package.json +1 -1
  43. package/generated/prisma-mongodb/schema.prisma +86 -59
  44. package/generated/prisma-mongodb/wasm.js +33 -42
  45. package/generated/prisma-postgresql/edge.js +33 -42
  46. package/generated/prisma-postgresql/index-browser.js +30 -39
  47. package/generated/prisma-postgresql/index.d.ts +3380 -2177
  48. package/generated/prisma-postgresql/index.js +33 -42
  49. package/generated/prisma-postgresql/package.json +1 -1
  50. package/generated/prisma-postgresql/schema.prisma +81 -57
  51. package/generated/prisma-postgresql/wasm.js +33 -42
  52. package/handlers/WEBHOOKS.md +0 -29
  53. package/handlers/app-definition-loader.js +128 -19
  54. package/handlers/app-handler-helpers.js +27 -8
  55. package/handlers/backend-utils.js +31 -25
  56. package/handlers/integration-event-dispatcher.js +3 -7
  57. package/handlers/middleware/admin-auth.js +14 -1
  58. package/handlers/routers/auth.js +46 -10
  59. package/handlers/routers/health.js +13 -4
  60. package/handlers/routers/integration-defined-routers.js +144 -92
  61. package/handlers/routers/integration-webhook-routers.js +100 -50
  62. package/handlers/workers/integration-defined-workers.js +5 -2
  63. package/index.js +66 -16
  64. package/infrastructure/scheduler/index.js +19 -6
  65. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  66. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  67. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  68. package/integrations/index.js +1 -14
  69. package/integrations/integration-base.js +2 -163
  70. package/integrations/integration-router.js +132 -102
  71. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  72. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  73. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  74. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  75. package/integrations/repositories/integration-mapping-repository.js +0 -29
  76. package/integrations/repositories/integration-not-found.js +14 -0
  77. package/integrations/repositories/integration-repository-documentdb.js +37 -9
  78. package/integrations/repositories/integration-repository-interface.js +17 -4
  79. package/integrations/repositories/integration-repository-mongo.js +64 -96
  80. package/integrations/repositories/integration-repository-postgres.js +63 -81
  81. package/integrations/repositories/process-repository-mongo.js +1 -1
  82. package/integrations/schedule-config.js +80 -0
  83. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  84. package/integrations/use-cases/create-integration.js +56 -31
  85. package/integrations/use-cases/delete-integration-for-user.js +4 -4
  86. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  87. package/integrations/use-cases/get-integration-for-user.js +2 -4
  88. package/integrations/use-cases/get-integration-instance.js +4 -4
  89. package/integrations/use-cases/get-integrations-for-user.js +79 -39
  90. package/integrations/use-cases/get-possible-integrations.js +2 -7
  91. package/integrations/use-cases/index.js +7 -1
  92. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  93. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  94. package/integrations/use-cases/update-integration.js +4 -4
  95. package/integrations/utils/map-integration-dto.js +43 -14
  96. package/management-api/create-management-api-router.js +49 -0
  97. package/management-api/error-response.js +116 -0
  98. package/management-api/management-api-config.js +151 -0
  99. package/management-api/meta-router.js +76 -0
  100. package/management-api/openapi/build-openapi.js +144 -0
  101. package/management-api/openapi/components.js +415 -0
  102. package/management-api/openapi/v1-operations.js +83 -0
  103. package/management-api/openapi/v2-operations.js +264 -0
  104. package/management-api/route-registry.js +411 -0
  105. package/management-api/test-support/http-client.js +39 -0
  106. package/management-api/use-cases/get-management-api-meta.js +57 -0
  107. package/management-api/v1-deprecation.js +120 -0
  108. package/management-api/v2/create-v2-router.js +85 -0
  109. package/management-api/v2/dependencies.js +281 -0
  110. package/management-api/v2/dto.js +143 -0
  111. package/management-api/v2/handlers/authorization.js +94 -0
  112. package/management-api/v2/handlers/credentials.js +33 -0
  113. package/management-api/v2/handlers/entities.js +77 -0
  114. package/management-api/v2/handlers/integrations.js +128 -0
  115. package/management-api/v2/handlers/proxy.js +34 -0
  116. package/management-api/v2/request.js +85 -0
  117. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  118. package/modules/domain/authorization-session.js +144 -0
  119. package/modules/entity-scope.js +15 -0
  120. package/modules/entity-type-registry.js +311 -0
  121. package/modules/index.js +14 -0
  122. package/modules/module-factory.js +12 -0
  123. package/modules/module.js +53 -2
  124. package/modules/proxy/proxy-bases.js +169 -0
  125. package/modules/proxy/proxy-errors.js +31 -0
  126. package/modules/proxy/proxy-path.js +183 -0
  127. package/modules/proxy/proxy-policy.js +277 -0
  128. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  129. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  130. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  131. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  132. package/modules/repositories/entity-not-found.js +18 -0
  133. package/modules/repositories/module-repository-documentdb.js +51 -6
  134. package/modules/repositories/module-repository-interface.js +19 -2
  135. package/modules/repositories/module-repository-mongo.js +67 -74
  136. package/modules/repositories/module-repository-postgres.js +66 -74
  137. package/modules/repositories/module-repository.js +2 -1
  138. package/modules/requester/proxy-request.js +245 -0
  139. package/modules/requester/requester.js +35 -5
  140. package/modules/scope-config.js +192 -0
  141. package/modules/scope-policy.js +112 -0
  142. package/modules/scope-resolution.js +288 -0
  143. package/modules/scope-visibility.js +247 -0
  144. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  145. package/modules/tests/doubles/ownership-matching.js +18 -0
  146. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  147. package/modules/use-cases/create-entity-from-credential.js +113 -0
  148. package/modules/use-cases/entity-operations-for-user.js +162 -0
  149. package/modules/use-cases/entity-types.js +139 -0
  150. package/modules/use-cases/execute-entity-proxy-request.js +331 -0
  151. package/modules/use-cases/get-authorization-requirements.js +100 -0
  152. package/modules/use-cases/get-entities-for-user.js +26 -13
  153. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  154. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  155. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  156. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  157. package/modules/use-cases/get-module.js +10 -4
  158. package/modules/use-cases/manage-access.js +21 -0
  159. package/modules/use-cases/multi-step-authorization.js +329 -0
  160. package/modules/use-cases/process-authorization-callback.js +53 -19
  161. package/modules/use-cases/refresh-entity-options.js +10 -4
  162. package/modules/use-cases/test-module-auth.js +10 -4
  163. package/package.json +6 -7
  164. package/prisma-mongodb/schema.prisma +86 -59
  165. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  166. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  167. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  168. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  169. package/prisma-postgresql/schema.prisma +81 -57
  170. package/providers/index.js +23 -0
  171. package/providers/resolve-provider.js +93 -0
  172. package/providers/runtime-detection.js +124 -0
  173. package/queues/index.js +25 -0
  174. package/queues/providers/index.js +11 -0
  175. package/queues/providers/netlify-background-provider.js +150 -0
  176. package/queues/providers/qstash-queue-provider.js +219 -0
  177. package/queues/providers/sqs-queue-provider.js +70 -0
  178. package/queues/queue-client-interface.js +55 -0
  179. package/queues/queue-message-auth.js +193 -0
  180. package/queues/queue-provider-factory.js +101 -0
  181. package/queues/queue-provider.js +65 -0
  182. package/queues/queuer-util.js +2 -9
  183. package/queues/sqs-queue-client.js +81 -0
  184. package/types/index.d.ts +2 -0
  185. package/types/modules/index.d.ts +214 -0
  186. package/types/user/index.d.ts +134 -0
  187. package/user/adopter-jwt-config.js +177 -0
  188. package/user/adopter-jwt-errors.js +59 -0
  189. package/user/adopter-token-verifier.js +122 -0
  190. package/user/app-user-helpers.js +97 -0
  191. package/user/create-user-authentication.js +64 -0
  192. package/user/membership.js +51 -0
  193. package/user/repositories/organization-membership-record.js +19 -0
  194. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  195. package/user/repositories/organization-membership-repository-factory.js +42 -0
  196. package/user/repositories/organization-membership-repository-interface.js +57 -0
  197. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  198. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  199. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  200. package/user/repositories/user-repository-documentdb.js +27 -2
  201. package/user/repositories/user-repository-interface.js +18 -0
  202. package/user/repositories/user-repository-mongo.js +24 -0
  203. package/user/repositories/user-repository-postgres.js +18 -0
  204. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  205. package/user/use-cases/authenticate-user.js +7 -1
  206. package/user/use-cases/ensure-organization-membership.js +92 -0
  207. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  208. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  209. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  210. package/user/user.js +64 -1
  211. package/errors/mapping-already-exists-error.js +0 -14
  212. package/handlers/workers/integration-triggers.js +0 -25
  213. package/infrastructure/scheduler/recurrence.js +0 -204
  214. package/integrations/define/DESIGN.md +0 -122
  215. package/integrations/define/config.js +0 -98
  216. package/integrations/define/define-app.js +0 -66
  217. package/integrations/define/define-extension.js +0 -154
  218. package/integrations/define/define-integration.js +0 -335
  219. package/integrations/define/examples/deal-alerts.js +0 -140
  220. package/integrations/define/extension-binding.js +0 -174
  221. package/integrations/define/index.js +0 -11
  222. package/integrations/define/json-schema-lite.js +0 -68
  223. package/integrations/define/subscriptions.js +0 -186
  224. package/integrations/define/tests/fixtures/hubspot.js +0 -109
  225. package/integrations/define/tests/fixtures/slack.js +0 -26
  226. package/integrations/define/use.js +0 -39
  227. package/integrations/definitions/README.md +0 -278
  228. package/integrations/definitions/canonical.js +0 -11
  229. package/integrations/definitions/capabilities/AUDIT.md +0 -480
  230. package/integrations/definitions/capabilities/acmeerp.capabilities.json +0 -633
  231. package/integrations/definitions/capabilities/asana.capabilities.json +0 -279
  232. package/integrations/definitions/capabilities/calendly.capabilities.json +0 -385
  233. package/integrations/definitions/capabilities/docusign.capabilities.json +0 -314
  234. package/integrations/definitions/capabilities/fathom.capabilities.json +0 -238
  235. package/integrations/definitions/capabilities/github.capabilities.json +0 -585
  236. package/integrations/definitions/capabilities/hubspot.capabilities.json +0 -822
  237. package/integrations/definitions/capabilities/jira.capabilities.json +0 -391
  238. package/integrations/definitions/capabilities/klaviyo.capabilities.json +0 -503
  239. package/integrations/definitions/capabilities/linear.capabilities.json +0 -378
  240. package/integrations/definitions/capabilities/netsuite.capabilities.json +0 -535
  241. package/integrations/definitions/capabilities/notion.capabilities.json +0 -208
  242. package/integrations/definitions/capabilities/phone.capabilities.json +0 -511
  243. package/integrations/definitions/capabilities/pipedrive.capabilities.json +0 -430
  244. package/integrations/definitions/capabilities/quickbooks.capabilities.json +0 -635
  245. package/integrations/definitions/capabilities/salesforce.capabilities.json +0 -568
  246. package/integrations/definitions/capabilities/shopify.capabilities.json +0 -1128
  247. package/integrations/definitions/capabilities/slack.capabilities.json +0 -373
  248. package/integrations/definitions/capabilities/stripe.capabilities.json +0 -630
  249. package/integrations/definitions/capabilities/webflow.capabilities.json +0 -196
  250. package/integrations/definitions/capabilities/zendesk.capabilities.json +0 -422
  251. package/integrations/definitions/compile.js +0 -80
  252. package/integrations/definitions/config-form.js +0 -137
  253. package/integrations/definitions/examples/acme-erp-quickbooks.definition.json +0 -467
  254. package/integrations/definitions/examples/asana-slack.definition.json +0 -219
  255. package/integrations/definitions/examples/calendly-salesforce.definition.json +0 -459
  256. package/integrations/definitions/examples/docusign-salesforce.definition.json +0 -305
  257. package/integrations/definitions/examples/fathom-hubspot-notion.definition.json +0 -575
  258. package/integrations/definitions/examples/github-linear.definition.json +0 -322
  259. package/integrations/definitions/examples/hubspot-slack.definition.json +0 -427
  260. package/integrations/definitions/examples/klaviyo-hubspot.definition.json +0 -336
  261. package/integrations/definitions/examples/phone-hubspot.definition.json +0 -633
  262. package/integrations/definitions/examples/pipedrive-hubspot.definition.json +0 -136
  263. package/integrations/definitions/examples/pipedrive-phone.definition.json +0 -340
  264. package/integrations/definitions/examples/records/phone-hubspot.definition-version.json +0 -652
  265. package/integrations/definitions/examples/records/phone-hubspot.integration.json +0 -86
  266. package/integrations/definitions/examples/records/phone-hubspot.mappings.json +0 -39
  267. package/integrations/definitions/examples/records/phone-hubspot.workflow-run.json +0 -62
  268. package/integrations/definitions/examples/shopify-klaviyo.definition.json +0 -488
  269. package/integrations/definitions/examples/shopify-netsuite.definition.json +0 -622
  270. package/integrations/definitions/examples/stripe-quickbooks.definition.json +0 -476
  271. package/integrations/definitions/examples/webflow-hubspot-slack.definition.json +0 -262
  272. package/integrations/definitions/examples/zendesk-jira.definition.json +0 -604
  273. package/integrations/definitions/expressions.js +0 -229
  274. package/integrations/definitions/index.js +0 -48
  275. package/integrations/definitions/integration-runtime.js +0 -779
  276. package/integrations/definitions/interpreter.js +0 -104
  277. package/integrations/definitions/manifests.js +0 -177
  278. package/integrations/definitions/renderers/common.js +0 -67
  279. package/integrations/definitions/renderers/html.js +0 -129
  280. package/integrations/definitions/renderers/hubspot.js +0 -59
  281. package/integrations/definitions/renderers/preview.js +0 -154
  282. package/integrations/definitions/renderers/slack.js +0 -146
  283. package/integrations/definitions/repositories/definition-version-repository.js +0 -67
  284. package/integrations/definitions/repositories/workflow-run-repository.js +0 -80
  285. package/integrations/definitions/router.js +0 -170
  286. package/integrations/definitions/runtime.js +0 -268
  287. package/integrations/definitions/schema/capability-manifest.v0.1.schema.json +0 -549
  288. package/integrations/definitions/schema/installation.v0.1.schema.json +0 -130
  289. package/integrations/definitions/schema/integration-definition.v0.1.schema.json +0 -909
  290. package/integrations/definitions/schema/records.md +0 -105
  291. package/integrations/definitions/schema-check.js +0 -58
  292. package/integrations/definitions/scripts/check-manifests.js +0 -220
  293. package/integrations/definitions/scripts/validate.js +0 -66
  294. package/integrations/definitions/steps.js +0 -123
  295. package/integrations/definitions/sync-engine.js +0 -297
  296. package/integrations/definitions/tests/helpers-fake-crm.js +0 -49
  297. package/integrations/definitions/transforms.js +0 -91
  298. package/integrations/definitions/use-cases/definition-use-cases.js +0 -245
  299. package/integrations/definitions/validate.js +0 -436
  300. package/integrations/definitions/versioning.js +0 -176
  301. package/integrations/definitions/webhooks.js +0 -119
  302. package/integrations/integration-types.js +0 -73
  303. package/integrations/triggers.js +0 -244
  304. package/integrations/use-cases/run-trigger-tick.js +0 -155
  305. package/integrations/use-cases/run-trigger.js +0 -77
  306. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +0 -66
  307. package/prisma-postgresql/migrations/20261019000000_workflow_run_waits/migration.sql +0 -7
  308. package/prisma-postgresql/migrations/20261019120000_definition_workspace/migration.sql +0 -5
@@ -1,278 +0,0 @@
1
- # Integrations published as definitions (v0.1)
2
-
3
- ADR-063, implemented to the point where a definition can be validated, compiled into Frigg events, executed, rendered
4
- and versioned. An integration becomes a **document** (what it connects, what it syncs, which workflows and UI it has)
5
- run by **one** runtime class, instead of a hand-written class per integration.
6
-
7
- ```
8
- definition (JSON, immutable per version) ─┐
9
- capability manifests (one per API module) ─┼─ validate ─ compile ─▶ this.events (same shape as hand-written)
10
- installation (Integration.config) ─┘ │
11
- event arrives ─▶ runWorkflow ─▶ steps ─▶ Api methods
12
- ```
13
-
14
- ## Layout
15
-
16
- | Path | What |
17
- |---|---|
18
- | `schema/integration-definition.v0.1.schema.json` | The definition format (JSON Schema 2020-12) |
19
- | `schema/capability-manifest.v0.1.schema.json` | What an API module declares it can do |
20
- | `schema/installation.v0.1.schema.json` | `Integration.config` for an installation |
21
- | `schema/records.md` | Database records: definition versions, installation, mappings, workflow runs |
22
- | `capabilities/*.capabilities.json` | Capability manifests. Nine are checked against the real Api classes (`capabilities/AUDIT.md`); the other twelve are still mock-shaped |
23
- | `scripts/check-manifests.js` | Checks every manifest `method` exists on the real module's Api class, with the arguments it takes |
24
- | `examples/*.definition.json` | Example definitions; `examples/records/` has Phone ↔ HubSpot database records |
25
- | `expressions.js` | `$.ref`, `{{template \| filter}}` and conditions. No eval. |
26
- | `steps.js`, `interpreter.js`, `transforms.js` | The step catalogue and the interpreter |
27
- | `validate.js` | Structure + cross-references against the manifests (steps, templates, views, settings, `$.install`) |
28
- | `manifests.js` | `validateManifest`: the manifests themselves (kinds, option lists, account fields) |
29
- | `compile.js` | Definition + installation → events, webhook routes, recurring triggers, user actions |
30
- | `sync-engine.js` | Syncs: link → identity → search → create/update, master rules, backfill pages, polls |
31
- | `runtime.js` | Ports (Api calls, link store), `runWorkflow`, `runQuery`, `renderSurface`, `runLifecycle`, `resolveOptions`, `installContext` |
32
- | `versioning.js` | `diff`, `checkBump`, `planUpgrade` |
33
- | `integration-runtime.js` | `fromDefinitionDocument` (was `defineIntegrationClass`), `definitionDocumentHost`, `embedDefinitionDocument`: the IntegrationBase subclasses that run it all. `defineIntegration` (`../define`) is the code front door to the same compile target. |
34
- | `use-cases/definition-use-cases.js` | Publish, deprecate, catalogue, history, upgrade plan; `createDefinitionCatalog()` (the store the runtime reads) |
35
- | `repositories/` | `IntegrationDefinitionVersion` and `WorkflowRun` rows (MongoDB, PostgreSQL, DocumentDB) |
36
- | `router.js` | The publish, catalogue, upgrade and run-history routes |
37
-
38
- ## The pieces of a definition
39
-
40
- - **modules** — module keys used everywhere else (`phone`, `hubspot`), each naming a capability manifest.
41
- - **settings** — integration-level choices (`fromNumber`); workflows can declare their own. An option setting draws
42
- its choices from a module's option list, and may pass it inputs read from earlier choices
43
- (`{ "source": "asana.project", "with": { "workspaceGid": "$.settings.workspace" } }`); `resolveOptions` fills the
44
- dropdown and returns nothing until those inputs are chosen.
45
- - **syncs** — records kept as copies on both sides: identity keys, field mapping (with transforms and master rules),
46
- backfill and ongoing mode. A sync maintains the link + identity index every workflow uses (`links.find`).
47
- A definition can have no workflows at all (`pipedrive-hubspot` is sync only).
48
- - **links** — link sets: records on two modules known to be the same thing, with no sync behind them (a Calendly
49
- invitee and the Salesforce Lead it booked as). Same storage and identity index as a sync, addressed as
50
- `links.find { link: "people", ... }`.
51
- - **workflows** — trigger (`webhook`, `schedule`, `userAction`, `syncRecord`) + steps. Default on or off.
52
- - **queries** — read-only steps that return data, used by UI surfaces.
53
- - **ui** — a surface in a host app (`hubspot` `crm.record.sidebar`): a query for data and a view tree from a fixed
54
- component set. A generic renderer deployed once per host draws it; changing the card is a publish, not a deploy.
55
- - **templates** — message bodies. **lifecycle** — steps run on install / uninstall (webhook subscriptions). Each
56
- `onInstall` step with an `id` keeps its result as `$.install.runtime.installed.<id>`, which is how `onUninstall`
57
- deletes exactly what install created.
58
-
59
- ## How it plugs into Frigg
60
-
61
- - **One kind of integration**: every published definition is an integration type named by its id. The shared class
62
- is a *provider* (`static IntegrationProvider`, see `../integration-types.js`): wherever Frigg turns `config.type`
63
- into a class (create, get, update, delete, the queue worker, `GET /api/integrations` options), a published id
64
- resolves to a class of its own, with its own name, label and modules. Registered code classes win over a
65
- definition with the same name. What is shared is the deployment underneath: published types queue and book one-time jobs
66
- on the provider's queue (`IntegrationBase.getDeploymentName()`), their triggers run on the provider's tick, and their webhooks reach the provider's function.
67
- Logs, telemetry, schedule job ids and installation webhook URLs carry each integration's own name.
68
- - **Hydration**: Frigg's use cases call `initialize()` on every hydrated integration; the class loads its pinned
69
- definition there.
70
- - **Webhooks**: install steps register `$.install.webhookUrls.<key>` (the URL names the module). An installation's
71
- webhooks arrive at its own type's URL (`/api/<id>-integration/webhooks/<integrationId>`), which the provider's
72
- webhook function also serves (devtools adds `/api/{integrationType}/webhooks[/{integrationId}]` to it; literal
73
- routes of registered integrations win). App-level webhooks use the provider's URL, one per vendor app. The manifest's
74
- `webhook` block (`webhooks.js`) splits batches and reads each event's topic, id and account; `verify` must return
75
- true; handshakes are answered at receipt. App-level webhooks (`webhook.scope: "app"`, one URL per provider app)
76
- are fanned out to the installations connected to each account.
77
- - **Routes**: Frigg's existing authenticated ones. `GET/POST /api/integrations/:id/config/options[/refresh]` is the
78
- generated configure form (dependent dropdowns on refresh); `POST /api/integrations/:id/actions/:event` runs a
79
- button workflow (`WF_*`), a backfill, or renders a UI surface (`UI_*`, body `{ record }`).
80
-
81
- `$.install` is the same everywhere a definition runs: integration settings, `webhookUrl`, `integrationId`,
82
- `runtime.installed`, and `accounts.<module key>.<field>` — values about the connected account that the module's
83
- manifest declares under `account` (`$.install.accounts.quickbooks.realmId`). Nothing undeclared is exposed.
84
-
85
- ## Code integrations use the same runtime
86
-
87
- A hand-written `IntegrationBase` subclass can embed a definition and get everything a published integration has:
88
- workflows with delay and approval pauses, syncs, UI surfaces, the generated configure form, and run history.
89
-
90
- ```js
91
- class PhoneReports extends IntegrationBase { static Definition = { name: 'phone-reports', modules, … }; async notifyTeam(args) { … } }
92
- module.exports = embedDefinitionDocument(PhoneReports, { definition: require('./phone-reports.definition.json'), manifests });
93
- ```
94
-
95
- - The class keeps its name, modules, routes, schedules, lifecycle and handlers; the definition's id must be its name.
96
- - A `code` step with `ref: "#notifyTeam"` calls that method on the integration (checked at class creation; a
97
- published definition cannot use `#` refs).
98
- - What the definition does not declare stays the code's: with no webhook-triggered workflow or sync, the class's
99
- own webhook handling runs; with no settings, its own configure form; its own schedules run beside the compiled ones.
100
- - Each queued event the class handles itself is recorded as a run (`triggerType: "code"`) in the same history as its
101
- workflows (`IntegrationEventDispatcher.dispatchJob` → `runEvent`). The history never decides a handler's outcome.
102
- - The definition ships with the code, so installations run the deployed version; `upgrade` says so and moves nothing.
103
-
104
- ## Publishing, installing, upgrading
105
-
106
- Definitions live in the `IntegrationDefinitionVersion` table (Prisma models in both schemas, PostgreSQL migration
107
- `20261018000000_add_data_defined_integrations`), and runs in `WorkflowRun`. Point the class at the store:
108
-
109
- ```js
110
- const { definitionDocumentHost, createDefinitionCatalog } = require('@friggframework/core/integrations/definitions');
111
- // A database store is read asynchronously, so it goes with the shared class (one class, every definition).
112
- const IntegrationRuntime = definitionDocumentHost({ definitions: createDefinitionCatalog(), manifests, modules });
113
- ```
114
-
115
- | Route | Auth | |
116
- |---|---|---|
117
- | `POST /admin/integrations/versions` `{ document, notes?, publish?, strict? }` | admin key | Validate (both layers) and publish, or save a draft with `publish: false` |
118
- | `GET /admin/integrations/:id/versions` | admin key | Version history |
119
- | `POST /admin/integrations/:id/versions/:v/deprecate` | admin key | Stop new installs of a version |
120
- | `GET /api/integrations` | user | What can be installed: code and published integrations in one list of options |
121
- | `GET /api/integration-definitions` | user | The published ones, with their counts |
122
- | `POST /api/integrations` with `config: { type: '<id>' }` | user | Install; pins the latest published version and its checksum |
123
- | `GET` / `POST /api/integrations/:id/upgrade` `{ to? }` | user | Plan / apply an upgrade (409 with the plan while something is needed) |
124
- | `GET /api/integrations/:id/runs` | user | Run history, newest first |
125
- | `GET /api/integrations/:id/syncs` | user | Each sync's backfill progress and poll marks |
126
- | `POST /api/integrations/:id/runs/:runId/decision` `{ decision, note? }` | user | Decide a paused approval. The approval step's `approvers` may decide without owning the installation; anyone else gets a 404 |
127
-
128
- A signed-in user can also build their own: `POST /api/integration-definitions` `{ document, notes?, publish? }` publishes
129
- into their workspace (the user's Frigg id; `IntegrationDefinitionVersion.workspaceId`). It is then listed and
130
- installable for that workspace like any other integration (its install option carries `editable: true`, the cue for
131
- the editor views), and invisible to everyone else: not listed, not resolvable when they install, not readable.
132
- `GET /api/integration-definitions/:id` gives its owner every version, drafts included, and the newest document for
133
- the editor. An id has one owner. The app's catalogue's ids and a code integration's name are public, and refused with
134
- 409 `ID_TAKEN`; an id owned by another workspace answers 404 `NOT_FOUND`, byte for byte what reading it answers, so one
135
- tenant cannot learn that another's id exists (ADR-052 section 3). (The admin route, which is not a tenant, still gets 409.)
136
- A published definition whose name matches a registered code integration is ignored (code wins); that is logged once per
137
- process as `frigg.integrations.definition.shadowed_by_code`. PostgreSQL migration `20261019120000_definition_workspace`.
138
-
139
- The installation
140
- routes answer for every integration, code included: one with no run history, syncs or other versions gets `[]`, or a
141
- plan that says there is nothing to move to, never a 404 for how it was made.
142
-
143
- Published versions cannot change. Publishing a document that is identical to a published one returns that row. A
144
- different document at the same version is refused. So is a version that is not newer than the latest, and so is a
145
- semver bump smaller than the changes. Publishing goes through a conditional write, so two concurrent publishes of one
146
- version cannot both win. With `memoryDefinitions()` (definitions bundled in the app) the admin routes answer 501.
147
-
148
- ## Previews: in the app
149
-
150
- An integration shows up inside the apps it connects: a HubSpot card, a Slack message, digest or approval request. A
151
- preview renders exactly that from the current settings and sample or real records, with the same query, view and step
152
- code production runs (`renderers/`):
153
-
154
- | Function | Does |
155
- |---|---|
156
- | `previewSurface({ def, installation, surfaceId, record, ports, host? })` | `{ surface, view, html, slack?, hubspot? }`. `slack` is Block Kit (`{ blocks, text, truncated }`), `hubspot` a data description of `@hubspot/ui-extensions` components (names and props checked against 0.17.1; `props` are real component props, a userAction button carries our own node-level `action` for the card to post, an `open_url` button is a real `href`), `html` the same card framed as its host looks. `host` defaults to the surface's own |
157
- | `previewWorkflow({ def, installation, workflowId, event?, record?, manifests, fixtures? })` | Dry run: `{ status, sends, calls, trace, waiting? }`. Each call is `{ stepId, module, capability, kind, args, output, rendered }`; `rendered` is `{ html, slack? }` for writes (a Slack post renders as the message, anything else as a "would call" card) |
158
- | `recordingPorts({ def, manifests, fixtures })` | Ports that record and answer instead of calling an API; give them to `previewSurface` for sample data |
159
- | `viewToSlack`, `viewToHubSpot`, `viewToHtml`, `slackMessageToHtml` | The renderers on their own. `decodeAction(value)` reads a Slack button click back to `{ integrationId?, workflow, surface }` |
160
-
161
- Dry-run rules: nothing is performed (the base ports' API function throws, and the recorder sits in front of it).
162
- `fixtures` answers calls, keyed `"<module key>.<capability>"` (a value, or `(args) => value`; `code:<ref>` for code
163
- steps, which are never run), otherwise the manifest's own record shape with ids filled. A webhook workflow with no
164
- `event` runs on the manifest's sample payload. A pausing step ends the preview with `waiting`.
165
-
166
- Everything that is record data is escaped for its target (mrkdwn `& < >` plus fenced `* _ ~`, HTML, text children for
167
- HubSpot), links are http(s) only, Block Kit limits are met by truncating with a marker (50 blocks, 3000/2000/150
168
- characters, 25 buttons), and nothing throws on an odd tree. Text a *definition* writes into a Slack message (templates)
169
- is sent as authored: a preview shows it as sent, including any raw record data a template interpolates.
170
-
171
- ## Syncs
172
-
173
- `sync-engine.js` runs `syncs`: backfill (a button or `onInstall`, one queued page per message, progress at
174
- `GET /api/integrations/:id/syncs`), webhook or polling updates, both directions for `two_way`, per-field `master`
175
- rules, and echo suppression. How each object is listed, fetched, searched and written is declared once in its
176
- manifest (`objects.<obj>.sync`); ADR-063 §15b has the rules.
177
-
178
- ## Schedules
179
-
180
- A schedule-triggered workflow or a polling sync is a recurring trigger (ADR-055), the same mechanism hand-written
181
- integrations declare as `Definition.triggers` (ADR-063 §15a). Nothing is scheduled per installation: the class
182
- declares `instanceTriggers: { tick: '5m' }`, so devtools provisions one tick function for it, and each tick compiles
183
- every ENABLED installation's pinned definition with its config and queues the runs that are due. Mark one
184
- `configurable` to let installations pick their own timing, optionally with a floor:
185
-
186
- ```json
187
- {
188
- "trigger": { "type": "schedule", "cron": "0 16 * * 1-5", "timezone": "America/New_York", "configurable": { "min": "1h" } },
189
- "ongoing": { "mode": "poll", "every": "15m", "configurable": true }
190
- }
191
- ```
192
-
193
- (`trigger` belongs to a workflow, `ongoing` to a sync.)
194
-
195
- The choice goes in `workflows.<id>.schedule` / `syncs.<id>.schedule` (the configure form offers the field). An
196
- interval runs on whole multiples of the 5-minute tick; a cron runs on the first tick after it fires. Create, update,
197
- upgrade and delete need no scheduler call: the next tick reads the new state. A run is deduped on
198
- `tick:<event>:<scheduledFor>`, so a redelivered run happens once.
199
-
200
- ## Steps (v0.1)
201
-
202
- Built-ins: `links.find`, `links.upsert`, `map`, `merge`, `template`, `set`, `stop`, `foreach` (≤1000), `branch`, `code`,
203
- and the pausing steps `delay` and `approval`.
204
-
205
- - `delay` (`for: '2h'` or `until`) and `approval` (`prompt`, `approvers`, `timeout`, `onTimeout`) end the run for now with
206
- status `waiting` and carry it on later from the next step (ADR-063 §15c). Top level of a workflow only. Decide with
207
- `POST /api/integrations/:id/runs/:runId/decision`; pending ones are `GET …/runs?status=waiting`.
208
-
209
- - `map` rows read a `source` field or write a fixed `value` (`{ "target": "Line[0].DetailType", "value": "SalesItemLineDetail" }`).
210
- - `merge` deep-merges objects (later wins, lists replaced).
211
- - `foreach` takes `where` (skip items, checked before the cap) and `collect` (the step's result becomes one value per
212
- item, which may read that iteration's steps); `do` is optional when `collect` is set. A digest is
213
- `collect` + `{{steps.lines | join: '\n'}}`, not a string grown with `set`.
214
- - Template filters include arithmetic as named, total functions: `plus`, `minus`, `times`, `divided_by`, `round`,
215
- `abs`, `number`, `to_cents` / `from_cents`, `to_minor_units` / `from_minor_units` (currency-aware: JPY has no minor
216
- unit, KWD three), `from_unix` / `to_unix`, and `join`, `size`, `first`. Bad input gives blank, never a throw. A
217
- string that is exactly one tag keeps the value's type, so `"{{item.Amount | to_cents}}"` is the number 2000.
218
- Everything else is `<moduleKey>.<object>.<verb>`, a capability the module's manifest declares and maps to one Api
219
- method. There is no http, script or eval step. `code` calls a named, versioned function shipped in reviewed code
220
- (`@scope/pkg#fn@1.2.0`) for logic that does not fit steps. Per step: `when` (skip), `onError`
221
- (`fail` → the queue retries the run, `continue`, `stop`).
222
-
223
- ## Versioning
224
-
225
- Published versions are immutable and each installation pins one. `checkBump` refuses a release whose declared semver
226
- bump is smaller than its changes (patch: steps/templates/view/mapping; minor: additive, including a new link set or
227
- changed option-list inputs; major: removed workflow or link set, required setting, identity change, new module). `planUpgrade` lists what a person must supply first and what the
228
- runtime does (enable/disable workflows, rebuild links, re-run install steps). Queued messages carry the version
229
- they were routed under and finish on it.
230
-
231
- ## Running the tests
232
-
233
- ```bash
234
- npx jest --config '{}' --rootDir packages/core/integrations/definitions tests
235
- ```
236
-
237
- `tests/persistence.test.js` runs the repositories and the publish rules against real databases. Each leg runs only
238
- when its URL is set. Point them at a migrated PostgreSQL database and a MongoDB replica set with the schema pushed:
239
-
240
- ```bash
241
- INTEGRATION_DEFINITIONS_POSTGRES_URL=postgresql://… INTEGRATION_DEFINITIONS_MONGO_URL=mongodb://… npx jest … tests
242
- ```
243
-
244
- ## Validating
245
-
246
- Validation is two layers, and a document is valid only when both pass (`schema-check.js`):
247
-
248
- 1. **JSON Schema** (`schema/*.schema.json`): the published shape.
249
- 2. **References** (`validate.js`, `manifests.js`): what a schema cannot see — steps name declared capabilities,
250
- `$.steps.x` is an earlier step, view paths exist, option inputs are declared, `$.install` paths resolve.
251
-
252
- ```bash
253
- npm run validate:definitions --workspace @friggframework/core # bundled manifests + examples
254
- node packages/core/integrations/definitions/scripts/validate.js my.definition.json --strict
255
- node packages/core/integrations/definitions/scripts/validate.js --manifests ./capabilities --json defs/
256
- ```
257
-
258
- Exit codes: `0` valid, `1` invalid (or a warning with `--strict`), `2` could not run (unreadable JSON, missing file,
259
- unknown option, `ajv` not installed). Manifests are checked first and an invalid manifest fails the run, because a
260
- definition is only as trustworthy as the manifests it is checked against.
261
-
262
- A manifest is only as real as its methods. `scripts/check-manifests.js <modules-dir>...` loads each module's Api class
263
- (no network, nothing instantiated) and checks that every capability's `method` exists and is called the way it takes its
264
- arguments (`args`: positional inputs, `"$rest"`, or a `{ "value" }` constant; no `args` means one object of all inputs).
265
- A capability the real Api lacks is marked `"status": "unimplemented"` with a `note`: a definition that uses it validates
266
- with a warning, not an error. `capabilities/AUDIT.md` has the table.
267
-
268
- `ajv` comes from the monorepo root (`@friggframework/schemas` depends on it); core's runtime stays dependency-free and
269
- loads it only for validation. `tests/schemas.test.js` runs both layers over every example and manifest and drives the
270
- CLI as a child process; it fails, rather than skips, if `ajv` is missing. Core's `npm test`, and so CI's
271
- `npm run test:all`, runs it on every change.
272
-
273
- ## Not in v0.1
274
-
275
- - Per-module rate limits.
276
- - The host renderers (the HubSpot app extension and Phone panel that draw `renderSurface` output).
277
- - Rebuilding a link set's (`links`, not a sync's) identity index after its keys change: it has no backfill to
278
- rebuild from, so `planUpgrade` reports `reindex`. A sync's `relink` is done by a backfill.
@@ -1,11 +0,0 @@
1
- /** Canonical JSON (sorted keys, no whitespace) and its sha256: the checksum a published version is stored under. */
2
- const crypto = require('crypto');
3
- // Canonical order is by UTF-16 code unit (what a bare sort() does), never locale: the checksum must not depend on the host's locale.
4
- const byCodeUnit = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
5
- function canonical(v) {
6
- if (Array.isArray(v)) return '[' + v.map(canonical).join(',') + ']';
7
- if (v && typeof v === 'object') return '{' + Object.keys(v).sort(byCodeUnit).filter((k) => v[k] !== undefined).map((k) => JSON.stringify(k) + ':' + canonical(v[k])).join(',') + '}';
8
- return JSON.stringify(v);
9
- }
10
- const checksum = (doc) => 'sha256:' + crypto.createHash('sha256').update(canonical(doc)).digest('hex');
11
- module.exports = { canonical, checksum };