@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -4,30 +4,34 @@
4
4
  * Provides scheduling capabilities for one-time jobs.
5
5
  * Follows hexagonal architecture with interface + adapters pattern.
6
6
  *
7
- * Providers:
8
- * - eventbridge: AWS EventBridge Scheduler (production)
9
- * - mock: In-memory mock scheduler (local development)
7
+ * Use createSchedulerService() to get the correct adapter for the
8
+ * active provider. The factory uses resolveProvider() internally.
9
+ *
10
+ * Adapters:
11
+ * - AWS EventBridge: via @friggframework/provider-aws (SchedulerAdapter)
12
+ * - Netlify: NetlifySchedulerAdapter (ships with core, no AWS deps)
13
+ * - Mock: MockSchedulerAdapter (local development)
10
14
  */
11
15
 
12
16
  const { SchedulerServiceInterface } = require('./scheduler-service-interface');
13
- const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
14
- const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
15
17
  const {
16
18
  createSchedulerService,
17
19
  SCHEDULER_PROVIDERS,
18
- determineProvider,
19
20
  } = require('./scheduler-service-factory');
20
21
 
21
22
  module.exports = {
22
23
  // Interface (Port)
23
24
  SchedulerServiceInterface,
24
25
 
25
- // Adapters
26
- EventBridgeSchedulerAdapter,
27
- MockSchedulerAdapter,
26
+ // Core adapters (no external SDK dependencies)
27
+ get MockSchedulerAdapter() {
28
+ return require('./mock-scheduler-adapter').MockSchedulerAdapter;
29
+ },
30
+ get NetlifySchedulerAdapter() {
31
+ return require('./netlify-scheduler-adapter').NetlifySchedulerAdapter;
32
+ },
28
33
 
29
- // Factory
34
+ // Factory — resolves the correct adapter via provider plugin system
30
35
  createSchedulerService,
31
36
  SCHEDULER_PROVIDERS,
32
- determineProvider,
33
37
  };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Netlify Scheduler Adapter
3
+ *
4
+ * Implements SchedulerServiceInterface for Netlify deployments.
5
+ *
6
+ * Netlify Scheduled Functions use cron syntax but are defined statically in
7
+ * netlify.toml or via the @netlify/functions schedule() helper. They cannot
8
+ * be created dynamically at runtime like EventBridge Scheduler.
9
+ *
10
+ * Strategy for one-time scheduled jobs on Netlify:
11
+ * 1. Store the schedule in the database (same as mock adapter pattern)
12
+ * 2. A Netlify Scheduled Function runs on a cron interval (e.g., every 5 min)
13
+ * 3. The cron function queries for due schedules and dispatches them
14
+ * to a background function for execution
15
+ *
16
+ * This is a "poll-and-dispatch" pattern vs EventBridge's "push" pattern.
17
+ * Trade-off: slightly less precise timing (up to cron interval delay),
18
+ * but works on any platform without cloud-specific scheduling APIs.
19
+ *
20
+ * For integrations that need the queue provider to dispatch jobs:
21
+ * - queueResourceId maps to the background function URL path
22
+ * - Payload is stored and forwarded when the cron fires
23
+ */
24
+
25
+ const { SchedulerServiceInterface } = require('./scheduler-service-interface');
26
+
27
+ class NetlifySchedulerAdapter extends SchedulerServiceInterface {
28
+ /**
29
+ * @param {Object} options
30
+ * @param {Object} options.repository - Repository for persisting schedules
31
+ * Must implement: save(schedule), delete(scheduleName),
32
+ * findByName(scheduleName), findDue(now)
33
+ * @param {Object} [options.queueProvider] - Queue provider for dispatching due jobs
34
+ */
35
+ constructor(options = {}) {
36
+ super();
37
+ this.repository = options.repository;
38
+ this.queueProvider = options.queueProvider;
39
+
40
+ if (!this.repository) {
41
+ console.warn(
42
+ '[NetlifyScheduler] No repository provided. ' +
43
+ 'Schedules will be stored in memory (lost on function restart). ' +
44
+ 'Provide a database-backed repository for production use.'
45
+ );
46
+ this._inMemorySchedules = new Map();
47
+ }
48
+ }
49
+
50
+ async scheduleOneTime({
51
+ scheduleName,
52
+ scheduleAt,
53
+ queueResourceId,
54
+ payload,
55
+ }) {
56
+ if (!scheduleName) throw new Error('scheduleName is required');
57
+ if (!scheduleAt || !(scheduleAt instanceof Date))
58
+ throw new Error('scheduleAt must be a valid Date object');
59
+ if (!queueResourceId) throw new Error('queueResourceId is required');
60
+
61
+ const scheduleData = {
62
+ scheduleName,
63
+ scheduledAt: scheduleAt.toISOString(),
64
+ queueResourceId,
65
+ payload,
66
+ createdAt: new Date().toISOString(),
67
+ state: 'PENDING',
68
+ };
69
+
70
+ if (this.repository) {
71
+ await this.repository.save(scheduleData);
72
+ } else {
73
+ this._inMemorySchedules.set(scheduleName, scheduleData);
74
+ }
75
+
76
+ console.log(
77
+ `[NetlifyScheduler] Scheduled: ${scheduleName} for ${scheduleAt.toISOString()}`
78
+ );
79
+
80
+ return {
81
+ scheduledJobId: `netlify-schedule-${scheduleName}`,
82
+ scheduledAt: scheduleAt.toISOString(),
83
+ };
84
+ }
85
+
86
+ async deleteSchedule(scheduleName) {
87
+ if (!scheduleName) throw new Error('scheduleName is required');
88
+
89
+ if (this.repository) {
90
+ await this.repository.delete(scheduleName);
91
+ } else {
92
+ this._inMemorySchedules.delete(scheduleName);
93
+ }
94
+
95
+ console.log(`[NetlifyScheduler] Deleted schedule: ${scheduleName}`);
96
+ }
97
+
98
+ async getScheduleStatus(scheduleName) {
99
+ if (!scheduleName) throw new Error('scheduleName is required');
100
+
101
+ let schedule;
102
+ if (this.repository) {
103
+ schedule = await this.repository.findByName(scheduleName);
104
+ } else {
105
+ schedule = this._inMemorySchedules.get(scheduleName);
106
+ }
107
+
108
+ if (!schedule) {
109
+ return { exists: false };
110
+ }
111
+
112
+ return {
113
+ exists: true,
114
+ scheduledAt: schedule.scheduledAt,
115
+ state: schedule.state,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Process due schedules. Called by the Netlify cron function.
121
+ *
122
+ * Finds all schedules where scheduledAt <= now, dispatches them
123
+ * to the queue provider, and marks them as completed.
124
+ *
125
+ * @returns {Promise<{ processed: number, errors: number }>}
126
+ */
127
+ async processDueSchedules() {
128
+ if (!this.repository) {
129
+ console.warn(
130
+ '[NetlifyScheduler] Cannot process due schedules without a repository'
131
+ );
132
+ return { processed: 0, errors: 0 };
133
+ }
134
+
135
+ const now = new Date();
136
+ const dueSchedules = await this.repository.findDue(now);
137
+
138
+ let processed = 0;
139
+ let errors = 0;
140
+
141
+ for (const schedule of dueSchedules) {
142
+ try {
143
+ // Mark as PROCESSING before dispatch so the next cron tick
144
+ // won't re-pick this schedule (findDue only returns PENDING).
145
+ await this.repository.save({
146
+ ...schedule,
147
+ state: 'PROCESSING',
148
+ });
149
+
150
+ if (this.queueProvider) {
151
+ await this.queueProvider.send(
152
+ schedule.payload,
153
+ schedule.queueResourceId
154
+ );
155
+ } else {
156
+ console.log(
157
+ `[NetlifyScheduler] No queue provider — logging payload for: ${schedule.scheduleName}`,
158
+ JSON.stringify(schedule.payload)
159
+ );
160
+ }
161
+
162
+ await this.repository.delete(schedule.scheduleName);
163
+ processed++;
164
+ } catch (error) {
165
+ console.error(
166
+ `[NetlifyScheduler] Error processing schedule ${schedule.scheduleName}:`,
167
+ error
168
+ );
169
+ // Mark as FAILED so it won't be re-dispatched automatically.
170
+ // Operators can inspect FAILED schedules and retry manually.
171
+ await this.repository
172
+ .save({ ...schedule, state: 'FAILED' })
173
+ .catch(() => {});
174
+ errors++;
175
+ }
176
+ }
177
+
178
+ console.log(
179
+ `[NetlifyScheduler] Processed ${processed} due schedules, ${errors} errors`
180
+ );
181
+ return { processed, errors };
182
+ }
183
+ }
184
+
185
+ module.exports = { NetlifySchedulerAdapter };
@@ -4,70 +4,77 @@
4
4
  * Creates scheduler service instances based on configuration.
5
5
  * Returns implementations of SchedulerServiceInterface.
6
6
  *
7
+ * Uses the provider plugin system (resolveProvider) to load the correct
8
+ * scheduler adapter for the active platform (AWS, Netlify, etc.).
9
+ *
7
10
  * Environment Detection:
8
- * - SCHEDULER_PROVIDER=eventbridge -> Use AWS EventBridge Scheduler
9
11
  * - SCHEDULER_PROVIDER=mock -> Use in-memory mock scheduler
10
12
  * - Default in dev/test/local stages -> Mock scheduler
11
- * - Default in other stages -> EventBridge scheduler
13
+ * - Otherwise -> Resolved provider's SchedulerAdapter
12
14
  */
13
15
 
14
- const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
15
- const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
16
+ const { resolveProvider } = require('../../providers/resolve-provider');
16
17
 
17
18
  const SCHEDULER_PROVIDERS = {
18
- EVENTBRIDGE: 'eventbridge',
19
19
  MOCK: 'mock',
20
20
  };
21
21
 
22
22
  const LOCAL_STAGES = ['dev', 'test', 'local'];
23
23
 
24
24
  /**
25
- * Determine the scheduler provider based on environment
25
+ * Determine if mock scheduler should be used
26
26
  *
27
- * @returns {string} Provider name
27
+ * @param {string} [explicit] - Explicit provider override
28
+ * @returns {boolean}
28
29
  */
29
- function determineProvider() {
30
- const explicitProvider = process.env.SCHEDULER_PROVIDER;
31
- if (explicitProvider) {
32
- return explicitProvider;
30
+ function shouldUseMock(explicit) {
31
+ if (explicit === SCHEDULER_PROVIDERS.MOCK) {
32
+ return true;
33
33
  }
34
-
35
- const stage = process.env.STAGE || 'dev';
36
- if (LOCAL_STAGES.includes(stage)) {
37
- return SCHEDULER_PROVIDERS.MOCK;
34
+ if (!explicit) {
35
+ const stage = process.env.STAGE || 'dev';
36
+ return LOCAL_STAGES.includes(stage);
38
37
  }
39
-
40
- return SCHEDULER_PROVIDERS.EVENTBRIDGE;
38
+ return false;
41
39
  }
42
40
 
43
41
  /**
44
42
  * Create a scheduler service instance
45
43
  *
46
44
  * @param {Object} options
47
- * @param {string} options.provider - Scheduler provider ('eventbridge' or 'mock')
45
+ * @param {string} options.provider - Scheduler provider ('mock', or omit for auto-detect)
48
46
  * @param {string} options.region - AWS region (for EventBridge)
49
47
  * @param {boolean} options.verbose - Verbose logging (for Mock)
48
+ * @param {Object} options.repository - Schedule repository (for Netlify - persists schedules)
49
+ * @param {Object} options.queueProvider - Queue provider (for Netlify - dispatches due jobs)
50
50
  * @returns {SchedulerServiceInterface} Implementation of scheduler interface
51
51
  */
52
52
  function createSchedulerService(options = {}) {
53
- const provider = options.provider || determineProvider();
53
+ const explicit = options.provider || process.env.SCHEDULER_PROVIDER;
54
+
55
+ if (shouldUseMock(explicit)) {
56
+ const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
57
+ return new MockSchedulerAdapter({ verbose: options.verbose });
58
+ }
59
+
60
+ // Use the resolved provider's scheduler adapter.
61
+ // Legacy SCHEDULER_PROVIDER values (eventbridge, netlify) are mapped
62
+ // to provider names for backward compatibility.
63
+ const LEGACY_MAP = { eventbridge: 'aws', netlify: 'netlify' };
64
+ const providerName = LEGACY_MAP[explicit] || explicit;
65
+ const providerOverride = providerName ? { provider: providerName } : {};
66
+ const provider = resolveProvider(null, providerOverride);
54
67
 
55
- switch (provider) {
56
- case SCHEDULER_PROVIDERS.EVENTBRIDGE:
57
- return new EventBridgeSchedulerAdapter({
58
- region: options.region,
59
- });
60
- case SCHEDULER_PROVIDERS.MOCK:
61
- return new MockSchedulerAdapter({
62
- verbose: options.verbose,
63
- });
64
- default:
65
- throw new Error(`Unknown scheduler provider: ${provider}`);
68
+ const Adapter = provider.SchedulerAdapter;
69
+ if (!Adapter) {
70
+ throw new Error(
71
+ `Provider '${provider.name}' does not export a SchedulerAdapter`
72
+ );
66
73
  }
74
+ return new Adapter(options);
67
75
  }
68
76
 
69
77
  module.exports = {
70
78
  createSchedulerService,
71
79
  SCHEDULER_PROVIDERS,
72
- determineProvider,
73
80
  };
@@ -14,7 +14,7 @@ class MyIntegration extends IntegrationBase {
14
14
  modules: {
15
15
  myapi: { definition: MyApiDefinition },
16
16
  },
17
- webhooks: true, // ← Add this line
17
+ webhooks: true, // ← Add this line
18
18
  };
19
19
  }
20
20
  ```
@@ -55,10 +55,11 @@ POST /api/my-integration-integration/webhooks/:integrationId
55
55
  ## That's It!
56
56
 
57
57
  The default behavior handles:
58
- - ✅ Receiving webhooks (instant 200 OK response)
59
- - ✅ Queuing to SQS
60
- - ✅ Loading your integration with DB and API modules
61
- - ✅ Calling your `onWebhook` handler
58
+
59
+ - ✅ Receiving webhooks (instant 200 OK response)
60
+ - ✅ Queuing to SQS
61
+ - ✅ Loading your integration with DB and API modules
62
+ - ✅ Calling your `onWebhook` handler
62
63
 
63
64
  ## Optional: Custom Signature Verification
64
65
 
@@ -85,20 +86,24 @@ async onWebhookReceived({ req, res }) {
85
86
  ## Two Webhook Routes
86
87
 
87
88
  ### With Integration ID (Recommended)
89
+
88
90
  ```
89
91
  POST /api/{name}-integration/webhooks/:integrationId
90
92
  ```
91
- - Full integration loaded in worker
92
- - Access to DB, config, and API modules
93
- - Use `this.myapi`, `this.config`, etc.
93
+
94
+ - Full integration loaded in worker
95
+ - Access to DB, config, and API modules
96
+ - Use `this.myapi`, `this.config`, etc.
94
97
 
95
98
  ### Without Integration ID
99
+
96
100
  ```
97
101
  POST /api/{name}-integration/webhooks
98
102
  ```
99
- - Unhydrated integration
100
- - Useful for system-wide events
101
- - Limited context
103
+
104
+ - Unhydrated integration
105
+ - Useful for system-wide events
106
+ - Limited context
102
107
 
103
108
  ## Need Help?
104
109
 
@@ -107,6 +112,7 @@ See full documentation: `packages/core/handlers/WEBHOOKS.md`
107
112
  ## Common Patterns
108
113
 
109
114
  ### Slack
115
+
110
116
  ```javascript
111
117
  async onWebhookReceived({ req, res }) {
112
118
  if (req.body.type === 'url_verification') {
@@ -117,6 +123,7 @@ async onWebhookReceived({ req, res }) {
117
123
  ```
118
124
 
119
125
  ### Stripe
126
+
120
127
  ```javascript
121
128
  async onWebhookReceived({ req, res }) {
122
129
  const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
@@ -131,6 +138,7 @@ async onWebhookReceived({ req, res }) {
131
138
  ```
132
139
 
133
140
  ### GitHub
141
+
134
142
  ```javascript
135
143
  async onWebhookReceived({ req, res }) {
136
144
  const crypto = require('crypto');
@@ -139,13 +147,12 @@ async onWebhookReceived({ req, res }) {
139
147
  .createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
140
148
  .update(JSON.stringify(req.body))
141
149
  .digest('hex');
142
-
150
+
143
151
  if (`sha256=${hash}` !== signature) {
144
152
  return res.status(401).json({ error: 'Invalid signature' });
145
153
  }
146
-
154
+
147
155
  await this.queueWebhook({ integrationId: req.params.integrationId, body: req.body });
148
156
  res.status(200).json({ received: true });
149
157
  }
150
158
  ```
151
-
@@ -10,6 +10,9 @@ const {
10
10
  const {
11
11
  LoadIntegrationContextUseCase,
12
12
  } = require('./use-cases/load-integration-context');
13
+ const {
14
+ createProcessRepository,
15
+ } = require('./repositories/process-repository-factory');
13
16
 
14
17
  module.exports = {
15
18
  IntegrationBase,
@@ -18,4 +21,5 @@ module.exports = {
18
21
  checkRequiredParams,
19
22
  getModulesDefinitionFromIntegrationClasses,
20
23
  LoadIntegrationContextUseCase,
24
+ createProcessRepository,
21
25
  };
@@ -204,11 +204,11 @@ class IntegrationBase {
204
204
  /**
205
205
  * Returns the modules as object with keys as module names.
206
206
  * Uses the keys from Definition.modules to attach modules correctly.
207
- *
207
+ *
208
208
  * Example:
209
209
  * Definition.modules = { attio: {...}, quo: { definition: { getName: () => 'quo-attio' } } }
210
210
  * Module with getName()='quo-attio' gets attached as this.quo (not this['quo-attio'])
211
- *
211
+ *
212
212
  * @private
213
213
  * @param {Array} integrationModules - Array of module instances
214
214
  * @returns {Object} The modules object
@@ -220,13 +220,16 @@ class IntegrationBase {
220
220
  // e.g., 'quo-attio' → 'quo', 'attio' → 'attio'
221
221
  const moduleNameToKey = {};
222
222
  if (this.constructor.Definition?.modules) {
223
- for (const [key, moduleConfig] of Object.entries(this.constructor.Definition.modules)) {
223
+ for (const [key, moduleConfig] of Object.entries(
224
+ this.constructor.Definition.modules
225
+ )) {
224
226
  const definition = moduleConfig.definition;
225
227
  if (definition) {
226
228
  // Use getName() if available, fallback to moduleName
227
- const definitionName = typeof definition.getName === 'function'
228
- ? definition.getName()
229
- : definition.moduleName;
229
+ const definitionName =
230
+ typeof definition.getName === 'function'
231
+ ? definition.getName()
232
+ : definition.moduleName;
230
233
  if (definitionName) {
231
234
  moduleNameToKey[definitionName] = key;
232
235
  }