@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
@@ -56,6 +56,14 @@ model User {
56
56
  integrations Integration[]
57
57
  processes Process[]
58
58
 
59
+ // Organization memberships (ADR-056 §3.6). `organizationId` above is the legacy
60
+ // single-org pointer: set on a person's first link, never moved afterwards, and
61
+ // read as an implicit membership when no row exists for it.
62
+ // As an individual: the organizations this person belongs to.
63
+ memberships OrganizationMembership[] @relation("IndividualMemberships")
64
+ // As an organization: the people who belong to it.
65
+ orgMembers OrganizationMembership[] @relation("OrganizationMemberships")
66
+
59
67
  @@unique([username, appUserId])
60
68
  @@index([type])
61
69
  @@index([appUserId])
@@ -126,6 +134,14 @@ model Entity {
126
134
  moduleName String?
127
135
  externalId String?
128
136
 
137
+ // Organization this entity was connected in (ADR-056 §3.6): the active
138
+ // organization for `individual`, the owner for `organization`, null for
139
+ // `global`. Null on an `individual` entity is also what every row written
140
+ // before this column existed has, and what `individualAcross: 'all'` writes.
141
+ // Deliberately not a relation: a deleted organization must leave its rows
142
+ // invisible, not turn them into "unscoped" ones (SetNull would do that).
143
+ organizationId String? @db.ObjectId
144
+
129
145
  data Json @default("{}")
130
146
 
131
147
  createdAt DateTime @default(now())
@@ -142,6 +158,7 @@ model Entity {
142
158
  associationObjects AssociationObject[]
143
159
 
144
160
  @@index([userId])
161
+ @@index([userId, organizationId])
145
162
  @@index([externalId])
146
163
  @@index([moduleName])
147
164
  @@index([credentialId])
@@ -159,6 +176,11 @@ model Integration {
159
176
  user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
160
177
  status IntegrationStatus @default(IN_CREATION)
161
178
 
179
+ // Organization this integration was created in (ADR-056 §3.6): the active
180
+ // organization at create time. Null for rows that predate the column. Not a
181
+ // relation, for the same reason as Entity.organizationId.
182
+ organizationId String? @db.ObjectId
183
+
162
184
  // Configuration and version
163
185
  config Json? // Integration configuration object
164
186
  version String?
@@ -181,9 +203,9 @@ model Integration {
181
203
  syncs Sync[]
182
204
  mappings IntegrationMapping[]
183
205
  processes Process[]
184
- workflowRuns WorkflowRun[]
185
206
 
186
207
  @@index([userId])
208
+ @@index([userId, organizationId])
187
209
  @@index([status])
188
210
  @@map("Integration")
189
211
  }
@@ -455,66 +477,71 @@ model UsageCounter {
455
477
  @@map("UsageCounter")
456
478
  }
457
479
 
458
- // Integrations published as definitions (ADR-063): one immutable row per published definition version.
459
- enum DefinitionStatus {
460
- DRAFT
461
- PUBLISHED
462
- DEPRECATED
463
- RETIRED
480
+ // ============================================================================
481
+ // MANAGEMENT API MODELS
482
+ // ============================================================================
483
+
484
+ /// Multi-step authorization state for Management API v2 (/api/v2/authorize,
485
+ /// /api/v2/credentials/:id/reauthorize). Short-lived: expired documents are
486
+ /// removed opportunistically. stepData is encrypted at rest.
487
+ model AuthorizationSession {
488
+ id String @id @default(auto()) @map("_id") @db.ObjectId
489
+ sessionId String @unique
490
+ userId String
491
+ entityType String
492
+ credentialId String?
493
+ currentStep Int @default(1)
494
+ maxSteps Int
495
+ stepData Json @default("{}")
496
+ expiresAt DateTime
497
+ completed Boolean @default(false)
498
+
499
+ createdAt DateTime @default(now())
500
+ updatedAt DateTime @updatedAt
501
+
502
+ @@index([userId, entityType])
503
+ @@index([expiresAt])
504
+ @@map("AuthorizationSession")
464
505
  }
465
506
 
466
- model IntegrationDefinitionVersion {
467
- id String @id @default(auto()) @map("_id") @db.ObjectId
468
- definitionId String
469
- version String
470
- schemaVersion String
471
- status DefinitionStatus @default(DRAFT)
472
- checksum String
473
- document Json
474
- changeLevel String?
475
- previousVersion String?
476
- notes String?
477
- createdBy String?
478
- // The workspace an integration a user built belongs to (null: the app's own catalogue, offered to everyone).
479
- workspaceId String?
480
- publishedAt DateTime?
481
- createdAt DateTime @default(now())
482
- updatedAt DateTime @updatedAt
483
-
484
- @@unique([definitionId, version])
485
- @@index([definitionId, status])
486
- @@index([workspaceId, status])
487
- @@map("IntegrationDefinitionVersion")
507
+ // ============================================================================
508
+ // ORGANIZATION MEMBERSHIP (ADR-056 §3.6)
509
+ // ============================================================================
510
+
511
+ /// One person's membership of one organization. Unique on the pair, so adding a
512
+ /// membership is an idempotent upsert. `orgRole` is the organization role the
513
+ /// adopter token carried when the membership was created (MEMBER when it named none). NoAction on delete
514
+ /// because MongoDB gives no referential integrity: delete a user's memberships
515
+ /// before the user.
516
+ model OrganizationMembership {
517
+ id String @id @default(auto()) @map("_id") @db.ObjectId
518
+ individualUserId String @db.ObjectId
519
+ organizationUserId String @db.ObjectId
520
+ orgRole String @default("MEMBER")
521
+ createdAt DateTime @default(now())
522
+
523
+ individualUser User @relation("IndividualMemberships", fields: [individualUserId], references: [id], onDelete: NoAction, onUpdate: NoAction)
524
+ organizationUser User @relation("OrganizationMemberships", fields: [organizationUserId], references: [id], onDelete: NoAction, onUpdate: NoAction)
525
+
526
+ @@unique([individualUserId, organizationUserId])
527
+ @@index([organizationUserId])
528
+ @@map("OrganizationMembership")
488
529
  }
489
530
 
490
- // One row per workflow run. trace holds step ids, statuses, timings and error messages, never request or
491
- // response bodies.
492
- model WorkflowRun {
493
- id String @id @default(auto()) @map("_id") @db.ObjectId
494
- integrationId String @db.ObjectId
495
- integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
496
- definitionId String
497
- definitionVersion String
498
- workflowId String
499
- triggerType String
500
- eventId String?
501
- status String
502
- reason String?
503
- error String?
504
- retryable Boolean @default(false)
505
- trace Json @default("[]")
506
- /// The runtime's run id (run_...): how routes and resume jobs address a run
507
- runKey String?
508
- /// A paused run (status waiting): which step, and what it waits for. Never request or response bodies.
509
- waitingFor Json?
510
- resumeAt DateTime?
511
- startedAt DateTime
512
- endedAt DateTime?
513
- createdAt DateTime @default(now())
514
-
515
- @@index([integrationId, runKey])
516
- @@index([integrationId, startedAt])
517
- @@index([integrationId, workflowId, startedAt])
518
- @@index([integrationId, status])
519
- @@map("WorkflowRun")
531
+ /// One-time scheduled jobs for poll-and-dispatch scheduling pattern.
532
+ /// Used by providers without native one-time scheduling APIs (e.g., Netlify).
533
+ /// A cron function queries for due jobs and dispatches them to a queue.
534
+ model ScheduledJob {
535
+ id String @id @default(auto()) @map("_id") @db.ObjectId
536
+ scheduleName String @unique
537
+ scheduledAt DateTime
538
+ queueResourceId String
539
+ payload Json?
540
+ state String @default("PENDING")
541
+
542
+ createdAt DateTime @default(now())
543
+ updatedAt DateTime @updatedAt
544
+
545
+ @@index([state, scheduledAt])
546
+ @@map("ScheduledJob")
520
547
  }
@@ -0,0 +1,28 @@
1
+ -- CreateTable
2
+ -- Multi-step authorization state for Management API v2 (/api/v2/authorize).
3
+ -- Short-lived rows; "stepData" is encrypted at rest by the application.
4
+ CREATE TABLE "AuthorizationSession" (
5
+ "id" SERIAL NOT NULL,
6
+ "sessionId" TEXT NOT NULL,
7
+ "userId" TEXT NOT NULL,
8
+ "entityType" TEXT NOT NULL,
9
+ "credentialId" TEXT,
10
+ "currentStep" INTEGER NOT NULL DEFAULT 1,
11
+ "maxSteps" INTEGER NOT NULL,
12
+ "stepData" JSONB NOT NULL DEFAULT '{}',
13
+ "expiresAt" TIMESTAMP(3) NOT NULL,
14
+ "completed" BOOLEAN NOT NULL DEFAULT false,
15
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ "updatedAt" TIMESTAMP(3) NOT NULL,
17
+
18
+ CONSTRAINT "AuthorizationSession_pkey" PRIMARY KEY ("id")
19
+ );
20
+
21
+ -- CreateIndex
22
+ CREATE UNIQUE INDEX "AuthorizationSession_sessionId_key" ON "AuthorizationSession"("sessionId");
23
+
24
+ -- CreateIndex
25
+ CREATE INDEX "AuthorizationSession_userId_entityType_idx" ON "AuthorizationSession"("userId", "entityType");
26
+
27
+ -- CreateIndex
28
+ CREATE INDEX "AuthorizationSession_expiresAt_idx" ON "AuthorizationSession"("expiresAt");
@@ -0,0 +1,26 @@
1
+ -- CreateTable
2
+ -- Organization memberships (ADR-056 §3.6). One row per person per organization.
3
+ -- "User"."organizationId" stays as the legacy single-org pointer. It is NOT
4
+ -- backfilled into this table: the domain (user/membership.js) reads it as an
5
+ -- implicit membership when no row exists (see docs/guides/organization-memberships.md).
6
+ CREATE TABLE "OrganizationMembership" (
7
+ "id" SERIAL NOT NULL,
8
+ "individualUserId" INTEGER NOT NULL,
9
+ "organizationUserId" INTEGER NOT NULL,
10
+ "orgRole" TEXT NOT NULL DEFAULT 'MEMBER',
11
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+
13
+ CONSTRAINT "OrganizationMembership_pkey" PRIMARY KEY ("id")
14
+ );
15
+
16
+ -- CreateIndex
17
+ CREATE INDEX "OrganizationMembership_organizationUserId_idx" ON "OrganizationMembership"("organizationUserId");
18
+
19
+ -- CreateIndex
20
+ CREATE UNIQUE INDEX "OrganizationMembership_individualUserId_organizationUserId_key" ON "OrganizationMembership"("individualUserId", "organizationUserId");
21
+
22
+ -- AddForeignKey
23
+ ALTER TABLE "OrganizationMembership" ADD CONSTRAINT "OrganizationMembership_individualUserId_fkey" FOREIGN KEY ("individualUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
24
+
25
+ -- AddForeignKey
26
+ ALTER TABLE "OrganizationMembership" ADD CONSTRAINT "OrganizationMembership_organizationUserId_fkey" FOREIGN KEY ("organizationUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
@@ -0,0 +1,25 @@
1
+ -- Entity and Integration carry the organization they were created in (ADR-056 §3.6).
2
+ -- Nullable, no default and no backfill: a row written before this migration keeps
3
+ -- NULL, and the domain (modules/entity-scope.js) reads NULL as "predates scoping"
4
+ -- (docs/guides/entity-scopes.md). No scope column: the owner's user type says it.
5
+ -- Deliberately no foreign key: a deleted organization must leave its rows
6
+ -- invisible, and ON DELETE SET NULL would turn them into unscoped ones.
7
+ --
8
+ -- Release note: the two CREATE INDEX statements below are plain (not
9
+ -- CONCURRENTLY, which Prisma migrations cannot run inside a transaction). Each
10
+ -- takes a SHARE lock on its table while it builds, so writes to "Entity" and
11
+ -- "Integration" wait for it. Both tables are small in a typical Frigg
12
+ -- deployment; on a very large one, create the indexes by hand first with
13
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS under the same names, then deploy.
14
+
15
+ -- AlterTable
16
+ ALTER TABLE "Entity" ADD COLUMN "organizationId" INTEGER;
17
+
18
+ -- AlterTable
19
+ ALTER TABLE "Integration" ADD COLUMN "organizationId" INTEGER;
20
+
21
+ -- CreateIndex
22
+ CREATE INDEX "Entity_userId_organizationId_idx" ON "Entity"("userId", "organizationId");
23
+
24
+ -- CreateIndex
25
+ CREATE INDEX "Integration_userId_organizationId_idx" ON "Integration"("userId", "organizationId");
@@ -0,0 +1,28 @@
1
+ -- One-time scheduled jobs for the poll-and-dispatch scheduler
2
+ -- (NetlifySchedulerAdapter, friggframework/frigg#545): a cron function reads
3
+ -- PENDING rows that are due and dispatches them to a queue.
4
+ --
5
+ -- IF NOT EXISTS on purpose: #545 shipped this model in the Prisma schema with
6
+ -- no migration, so an app that ran its canary may already have the table from
7
+ -- `prisma db push`. On such a database this migration is a no-op; on a fresh
8
+ -- one it is the SQL `prisma migrate diff` generates.
9
+
10
+ -- CreateTable
11
+ CREATE TABLE IF NOT EXISTS "ScheduledJob" (
12
+ "id" SERIAL NOT NULL,
13
+ "scheduleName" TEXT NOT NULL,
14
+ "scheduledAt" TIMESTAMP(3) NOT NULL,
15
+ "queueResourceId" TEXT NOT NULL,
16
+ "payload" JSONB,
17
+ "state" TEXT NOT NULL DEFAULT 'PENDING',
18
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+ "updatedAt" TIMESTAMP(3) NOT NULL,
20
+
21
+ CONSTRAINT "ScheduledJob_pkey" PRIMARY KEY ("id")
22
+ );
23
+
24
+ -- CreateIndex
25
+ CREATE UNIQUE INDEX IF NOT EXISTS "ScheduledJob_scheduleName_key" ON "ScheduledJob"("scheduleName");
26
+
27
+ -- CreateIndex
28
+ CREATE INDEX IF NOT EXISTS "ScheduledJob_state_scheduledAt_idx" ON "ScheduledJob"("state", "scheduledAt");
@@ -56,6 +56,14 @@ model User {
56
56
  integrations Integration[]
57
57
  processes Process[]
58
58
 
59
+ // Organization memberships (ADR-056 §3.6). `organizationId` above is the legacy
60
+ // single-org pointer: set on a person's first link, never moved afterwards, and
61
+ // read as an implicit membership when no row exists for it.
62
+ // As an individual: the organizations this person belongs to.
63
+ memberships OrganizationMembership[] @relation("IndividualMemberships")
64
+ // As an organization: the people who belong to it.
65
+ orgMembers OrganizationMembership[] @relation("OrganizationMemberships")
66
+
59
67
  @@unique([username, appUserId])
60
68
  @@index([type])
61
69
  @@index([appUserId])
@@ -123,6 +131,14 @@ model Entity {
123
131
  moduleName String?
124
132
  externalId String?
125
133
 
134
+ // Organization this entity was connected in (ADR-056 §3.6): the active
135
+ // organization for `individual`, the owner for `organization`, null for
136
+ // `global`. Null on an `individual` entity is also what every row written
137
+ // before this column existed has, and what `individualAcross: 'all'` writes.
138
+ // Deliberately not a relation: a deleted organization must leave its rows
139
+ // invisible, not turn them into "unscoped" ones (SetNull would do that).
140
+ organizationId Int?
141
+
126
142
  data Json @default("{}")
127
143
 
128
144
  createdAt DateTime @default(now())
@@ -136,6 +152,7 @@ model Entity {
136
152
  associationObjects AssociationObject[]
137
153
 
138
154
  @@index([userId])
155
+ @@index([userId, organizationId])
139
156
  @@index([externalId])
140
157
  @@index([moduleName])
141
158
  @@index([credentialId])
@@ -152,6 +169,11 @@ model Integration {
152
169
  user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
153
170
  status IntegrationStatus @default(IN_CREATION)
154
171
 
172
+ // Organization this integration was created in (ADR-056 §3.6): the active
173
+ // organization at create time. Null for rows that predate the column. Not a
174
+ // relation, for the same reason as Entity.organizationId.
175
+ organizationId Int?
176
+
155
177
  // Configuration and version
156
178
  config Json? // Integration configuration object
157
179
  version String?
@@ -173,9 +195,9 @@ model Integration {
173
195
  syncs Sync[]
174
196
  mappings IntegrationMapping[]
175
197
  processes Process[]
176
- workflowRuns WorkflowRun[]
177
198
 
178
199
  @@index([userId])
200
+ @@index([userId, organizationId])
179
201
  @@index([status])
180
202
  }
181
203
 
@@ -436,64 +458,66 @@ model UsageCounter {
436
458
  @@index([metric, integrationType, window])
437
459
  }
438
460
 
439
- // Integrations published as definitions (ADR-063): one immutable row per published definition version.
440
- enum DefinitionStatus {
441
- DRAFT
442
- PUBLISHED
443
- DEPRECATED
444
- RETIRED
461
+ // ============================================================================
462
+ // MANAGEMENT API MODELS
463
+ // ============================================================================
464
+
465
+ /// Multi-step authorization state for Management API v2 (/api/v2/authorize,
466
+ /// /api/v2/credentials/:id/reauthorize). Short-lived: expired rows are removed
467
+ /// opportunistically. stepData is encrypted at rest.
468
+ model AuthorizationSession {
469
+ id Int @id @default(autoincrement())
470
+ sessionId String @unique
471
+ userId String
472
+ entityType String
473
+ credentialId String?
474
+ currentStep Int @default(1)
475
+ maxSteps Int
476
+ stepData Json @default("{}")
477
+ expiresAt DateTime
478
+ completed Boolean @default(false)
479
+
480
+ createdAt DateTime @default(now())
481
+ updatedAt DateTime @updatedAt
482
+
483
+ @@index([userId, entityType])
484
+ @@index([expiresAt])
445
485
  }
446
486
 
447
- model IntegrationDefinitionVersion {
448
- id Int @id @default(autoincrement())
449
- definitionId String
450
- version String
451
- schemaVersion String
452
- status DefinitionStatus @default(DRAFT)
453
- checksum String
454
- document Json
455
- changeLevel String?
456
- previousVersion String?
457
- notes String?
458
- createdBy String?
459
- // The workspace an integration a user built belongs to (null: the app's own catalogue, offered to everyone).
460
- workspaceId String?
461
- publishedAt DateTime?
462
- createdAt DateTime @default(now())
463
- updatedAt DateTime @updatedAt
464
-
465
- @@unique([definitionId, version])
466
- @@index([definitionId, status])
467
- @@index([workspaceId, status])
487
+ // ============================================================================
488
+ // ORGANIZATION MEMBERSHIP (ADR-056 §3.6)
489
+ // ============================================================================
490
+
491
+ /// One person's membership of one organization. Unique on the pair, so adding a
492
+ /// membership is an idempotent upsert. `orgRole` is the organization role the
493
+ /// adopter token carried when the membership was created (MEMBER when it named none).
494
+ model OrganizationMembership {
495
+ id Int @id @default(autoincrement())
496
+ individualUserId Int
497
+ organizationUserId Int
498
+ orgRole String @default("MEMBER")
499
+ createdAt DateTime @default(now())
500
+
501
+ individualUser User @relation("IndividualMemberships", fields: [individualUserId], references: [id], onDelete: Cascade, onUpdate: NoAction)
502
+ organizationUser User @relation("OrganizationMemberships", fields: [organizationUserId], references: [id], onDelete: Cascade, onUpdate: NoAction)
503
+
504
+ @@unique([individualUserId, organizationUserId])
505
+ @@index([organizationUserId])
468
506
  }
469
507
 
470
- // One row per workflow run. trace holds step ids, statuses, timings and error messages, never request or
471
- // response bodies.
472
- model WorkflowRun {
473
- id Int @id @default(autoincrement())
474
- integrationId Int
475
- integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
476
- definitionId String
477
- definitionVersion String
478
- workflowId String
479
- triggerType String
480
- eventId String?
481
- status String
482
- reason String?
483
- error String?
484
- retryable Boolean @default(false)
485
- trace Json @default("[]")
486
- /// The runtime's run id (run_...): how routes and resume jobs address a run
487
- runKey String?
488
- /// A paused run (status waiting): which step, and what it waits for. Never request or response bodies.
489
- waitingFor Json?
490
- resumeAt DateTime?
491
- startedAt DateTime
492
- endedAt DateTime?
493
- createdAt DateTime @default(now())
494
-
495
- @@index([integrationId, runKey])
496
- @@index([integrationId, startedAt])
497
- @@index([integrationId, workflowId, startedAt])
498
- @@index([integrationId, status])
508
+ /// One-time scheduled jobs for poll-and-dispatch scheduling pattern.
509
+ /// Used by providers without native one-time scheduling APIs (e.g., Netlify).
510
+ /// A cron function queries for due jobs and dispatches them to a queue.
511
+ model ScheduledJob {
512
+ id Int @id @default(autoincrement())
513
+ scheduleName String @unique
514
+ scheduledAt DateTime
515
+ queueResourceId String
516
+ payload Json?
517
+ state String @default("PENDING")
518
+
519
+ createdAt DateTime @default(now())
520
+ updatedAt DateTime @updatedAt
521
+
522
+ @@index([state, scheduledAt])
499
523
  }
@@ -0,0 +1,23 @@
1
+ const {
2
+ resolveProvider,
3
+ determineProviderName,
4
+ providerPackageName,
5
+ KNOWN_PROVIDERS,
6
+ } = require('./resolve-provider');
7
+ const {
8
+ isDeployedRuntime,
9
+ isAwsLambdaRuntime,
10
+ isLocalRun,
11
+ resolveActiveProvider,
12
+ } = require('./runtime-detection');
13
+
14
+ module.exports = {
15
+ resolveProvider,
16
+ determineProviderName,
17
+ providerPackageName,
18
+ KNOWN_PROVIDERS,
19
+ isDeployedRuntime,
20
+ isAwsLambdaRuntime,
21
+ isLocalRun,
22
+ resolveActiveProvider,
23
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Provider Resolver
3
+ *
4
+ * Resolves a provider name from the appDefinition into the corresponding
5
+ * @friggframework/provider-{name} package. This is the bridge between
6
+ * `appDefinition.provider` and the provider plugin interface.
7
+ *
8
+ * Resolution order:
9
+ * 1. Explicit `providerName` argument
10
+ * 2. `appDefinition.provider`
11
+ * 3. `FRIGG_PROVIDER` environment variable
12
+ * 4. Default: 'aws'
13
+ *
14
+ * Package naming convention:
15
+ * provider: 'netlify' → require('@friggframework/provider-netlify')
16
+ * provider: 'aws' → require('@friggframework/provider-aws')
17
+ *
18
+ * @example
19
+ * const provider = resolveProvider({ provider: 'netlify' });
20
+ * // provider.name === 'netlify'
21
+ * // provider.deploy(appDefinition, options)
22
+ * // provider.createHandler(options)
23
+ * // provider.QueueProvider (class)
24
+ * // etc.
25
+ */
26
+
27
+ const KNOWN_PROVIDERS = ['aws', 'netlify'];
28
+
29
+ /**
30
+ * Determine the provider name from available sources.
31
+ *
32
+ * @param {Object} [appDefinition] - Frigg app definition
33
+ * @param {string} [providerName] - Explicit override
34
+ * @returns {string} Provider name (e.g. 'netlify', 'aws')
35
+ */
36
+ function determineProviderName(appDefinition, providerName) {
37
+ if (providerName) {
38
+ return providerName;
39
+ }
40
+ if (appDefinition?.provider) {
41
+ return appDefinition.provider;
42
+ }
43
+ if (process.env.FRIGG_PROVIDER) {
44
+ return process.env.FRIGG_PROVIDER;
45
+ }
46
+ return 'aws';
47
+ }
48
+
49
+ /**
50
+ * Resolve a provider name to a package name.
51
+ *
52
+ * @param {string} name - Provider name (e.g. 'netlify')
53
+ * @returns {string} npm package name (e.g. '@friggframework/provider-netlify')
54
+ */
55
+ function providerPackageName(name) {
56
+ return `@friggframework/provider-${name}`;
57
+ }
58
+
59
+ /**
60
+ * Resolve and load the provider plugin for the given app definition.
61
+ *
62
+ * @param {Object} [appDefinition] - Frigg app definition (reads .provider)
63
+ * @param {Object} [options]
64
+ * @param {string} [options.provider] - Explicit provider name override
65
+ * @returns {Object} Provider plugin (conforms to the provider plugin interface)
66
+ * @throws {Error} If the provider package is not installed
67
+ */
68
+ function resolveProvider(appDefinition, options = {}) {
69
+ const name = determineProviderName(appDefinition, options.provider);
70
+ const packageName = providerPackageName(name);
71
+
72
+ try {
73
+ return require(packageName);
74
+ } catch (error) {
75
+ if (error.code === 'MODULE_NOT_FOUND') {
76
+ const hint = KNOWN_PROVIDERS.includes(name)
77
+ ? `Install it with: npm install ${packageName}`
78
+ : `Is '${name}' a valid Frigg provider? Known providers: ${KNOWN_PROVIDERS.join(', ')}`;
79
+
80
+ throw new Error(
81
+ `Provider '${name}' specified in appDefinition but package '${packageName}' is not installed.\n ${hint}`
82
+ );
83
+ }
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ resolveProvider,
90
+ determineProviderName,
91
+ providerPackageName,
92
+ KNOWN_PROVIDERS,
93
+ };