@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.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 (235) 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 +450 -0
  95. package/handlers/routers/auth.js +23 -2
  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 +39 -249
  100. package/handlers/routers/integration-defined-routers.js +4 -4
  101. package/handlers/routers/integration-webhook-routers.js +17 -7
  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/websocket.js +9 -3
  106. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  107. package/handlers/workers/db-migration.js +74 -35
  108. package/index.js +50 -16
  109. package/infrastructure/scheduler/index.js +15 -11
  110. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  111. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  112. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  113. package/integrations/index.js +4 -0
  114. package/integrations/integration-base.js +9 -6
  115. package/integrations/integration-router.js +1828 -157
  116. package/integrations/options.js +47 -20
  117. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  118. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  119. package/integrations/repositories/integration-repository-factory.js +6 -6
  120. package/integrations/repositories/integration-repository-interface.js +27 -9
  121. package/integrations/repositories/integration-repository-postgres.js +3 -3
  122. package/integrations/repositories/process-repository-factory.js +4 -7
  123. package/integrations/repositories/process-repository-interface.js +9 -5
  124. package/integrations/repositories/process-repository-mongo.js +7 -3
  125. package/integrations/repositories/process-repository-postgres.js +2 -2
  126. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  127. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  128. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  129. package/integrations/use-cases/create-integration.js +36 -32
  130. package/integrations/use-cases/create-process.js +20 -9
  131. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  132. package/integrations/use-cases/execute-proxy-request.js +564 -0
  133. package/integrations/use-cases/get-integration-for-user.js +25 -10
  134. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  135. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  136. package/integrations/use-cases/get-possible-integrations.js +1 -1
  137. package/integrations/use-cases/get-process.js +5 -6
  138. package/integrations/use-cases/index.js +3 -1
  139. package/integrations/use-cases/load-integration-context.js +1 -5
  140. package/integrations/use-cases/update-process-state.js +10 -8
  141. package/integrations/utils/map-integration-dto.js +9 -3
  142. package/jest-setup.js +1 -1
  143. package/jest-teardown.js +1 -1
  144. package/lambda/TimeoutCatcher.js +32 -30
  145. package/lambda/index.js +1 -1
  146. package/logs/index.js +3 -3
  147. package/modules/domain/entities/AuthorizationSession.js +131 -0
  148. package/modules/domain/entities/index.js +10 -0
  149. package/modules/module.js +4 -9
  150. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  151. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  152. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  153. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  154. package/modules/repositories/module-repository-documentdb.js +89 -41
  155. package/modules/repositories/module-repository-factory.js +4 -6
  156. package/modules/repositories/module-repository-interface.js +13 -0
  157. package/modules/repositories/module-repository-mongo.js +40 -5
  158. package/modules/repositories/module-repository-postgres.js +42 -5
  159. package/modules/repositories/module-repository.js +28 -0
  160. package/modules/requester/api-key.js +0 -2
  161. package/modules/requester/basic.js +0 -1
  162. package/modules/requester/oauth-2.js +3 -14
  163. package/modules/test/mock-api/definition.js +15 -10
  164. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  165. package/modules/tests/doubles/test-module-factory.js +10 -4
  166. package/modules/tests/doubles/test-module-repository.js +1 -1
  167. package/modules/use-cases/delete-module-entity.js +23 -0
  168. package/modules/use-cases/get-authorization-requirements.js +110 -0
  169. package/modules/use-cases/get-entities-for-user.js +1 -1
  170. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  171. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  172. package/modules/use-cases/get-module.js +12 -9
  173. package/modules/use-cases/process-authorization-callback.js +12 -6
  174. package/modules/use-cases/process-authorization-step.js +160 -0
  175. package/modules/use-cases/refresh-entity-options.js +8 -6
  176. package/modules/use-cases/start-authorization-session.js +86 -0
  177. package/modules/use-cases/test-module-auth.js +8 -6
  178. package/modules/use-cases/update-module-entity.js +27 -0
  179. package/modules/utils/map-module-dto.js +2 -2
  180. package/openapi/openapi-spec-generator.js +370 -0
  181. package/openapi/openapi-v1.yaml +1360 -0
  182. package/openapi/openapi-v2.yaml +1535 -0
  183. package/package.json +7 -9
  184. package/prisma-mongodb/schema.prisma +87 -0
  185. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  186. package/prisma-postgresql/schema.prisma +85 -0
  187. package/providers/index.js +13 -0
  188. package/providers/resolve-provider.js +93 -0
  189. package/queues/index.js +19 -0
  190. package/queues/providers/index.js +7 -0
  191. package/queues/providers/netlify-background-provider.js +76 -0
  192. package/queues/providers/qstash-queue-provider.js +122 -0
  193. package/queues/queue-client-interface.js +55 -0
  194. package/queues/queue-provider-factory.js +93 -0
  195. package/queues/queue-provider.js +46 -0
  196. package/queues/queuer-util.js +33 -25
  197. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  198. package/syncs/repositories/sync-repository-factory.js +4 -4
  199. package/syncs/repositories/sync-repository-interface.js +6 -2
  200. package/syncs/repositories/sync-repository-postgres.js +19 -13
  201. package/syncs/sync.js +104 -100
  202. package/token/repositories/token-repository-factory.js +4 -6
  203. package/types/assertions/index.d.ts +72 -72
  204. package/types/core/index.d.ts +98 -53
  205. package/types/errors/index.d.ts +61 -54
  206. package/types/eslint-config/index.d.ts +40 -40
  207. package/types/lambda/index.d.ts +26 -26
  208. package/types/logs/index.d.ts +4 -4
  209. package/types/prettier-config/index.d.ts +5 -5
  210. package/types/test-environment/index.d.ts +14 -14
  211. package/types/tsconfig.json +95 -93
  212. package/user/repositories/user-repository-factory.js +4 -4
  213. package/user/tests/doubles/test-user-repository.js +5 -2
  214. package/user/use-cases/authenticate-user.js +2 -4
  215. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  216. package/user/use-cases/create-individual-user.js +8 -7
  217. package/user/use-cases/create-organization-user.js +1 -1
  218. package/user/use-cases/create-token-for-user-id.js +1 -1
  219. package/user/use-cases/delete-user.js +47 -0
  220. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  221. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  222. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  223. package/user/use-cases/login-user.js +8 -8
  224. package/user/user.js +18 -4
  225. package/utils/backend-path.js +2 -2
  226. package/utils/index.js +4 -1
  227. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  228. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  229. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  230. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  231. package/websocket/repositories/websocket-connection-repository.js +26 -20
  232. package/websocket/websocket-message-sender-interface.js +38 -0
  233. package/database/adapters/lambda-invoker.js +0 -97
  234. package/database/repositories/migration-status-repository-s3.js +0 -137
  235. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -49,16 +49,28 @@ const {
49
49
  const {
50
50
  CheckDatabaseStateUseCase,
51
51
  } = require('../../database/use-cases/check-database-state-use-case');
52
- const {
53
- MigrationStatusRepositoryS3,
54
- } = require('../../database/repositories/migration-status-repository-s3');
55
-
56
52
  // Inject prisma-runner as dependency
57
53
  const prismaRunner = require('../../database/utils/prisma-runner');
58
54
 
59
- // Use S3 repository for migration status tracking (no User table dependency)
60
- const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
61
- const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
55
+ // Migration status repository is loaded from the resolved provider.
56
+ const { resolveProvider } = require('../../providers/resolve-provider');
57
+
58
+ let _migrationStatusRepository = null;
59
+ function getMigrationStatusRepository() {
60
+ if (!_migrationStatusRepository) {
61
+ const provider = resolveProvider();
62
+ const MigrationStatusRepository = provider.MigrationStatusRepositoryS3;
63
+ if (!MigrationStatusRepository) {
64
+ throw new Error(
65
+ `Provider '${provider.name}' does not export a MigrationStatusRepository`
66
+ );
67
+ }
68
+ const bucketName =
69
+ process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
70
+ _migrationStatusRepository = new MigrationStatusRepository(bucketName);
71
+ }
72
+ return _migrationStatusRepository;
73
+ }
62
74
 
63
75
  /**
64
76
  * Sanitizes error messages to prevent credential leaks
@@ -68,19 +80,27 @@ const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
68
80
  function sanitizeError(errorMessage) {
69
81
  if (!errorMessage) return 'Unknown error';
70
82
 
71
- return String(errorMessage)
72
- // Remove PostgreSQL connection strings
73
- .replace(/postgresql:\/\/[^@\s]+@[^\s/]+/gi, 'postgresql://***:***@***')
74
- // Remove MongoDB connection strings
75
- .replace(/mongodb(\+srv)?:\/\/[^@\s]+@[^\s/]+/gi, 'mongodb$1://***:***@***')
76
- // Remove password parameters
77
- .replace(/password[=:]\s*[^\s,;)]+/gi, 'password=***')
78
- // Remove API keys
79
- .replace(/apikey[=:]\s*[^\s,;)]+/gi, 'apikey=***')
80
- .replace(/api[_-]?key[=:]\s*[^\s,;)]+/gi, 'api_key=***')
81
- // Remove tokens
82
- .replace(/token[=:]\s*[^\s,;)]+/gi, 'token=***')
83
- .replace(/bearer\s+[^\s,;)]+/gi, 'bearer ***');
83
+ return (
84
+ String(errorMessage)
85
+ // Remove PostgreSQL connection strings
86
+ .replace(
87
+ /postgresql:\/\/[^@\s]+@[^\s/]+/gi,
88
+ 'postgresql://***:***@***'
89
+ )
90
+ // Remove MongoDB connection strings
91
+ .replace(
92
+ /mongodb(\+srv)?:\/\/[^@\s]+@[^\s/]+/gi,
93
+ 'mongodb$1://***:***@***'
94
+ )
95
+ // Remove password parameters
96
+ .replace(/password[=:]\s*[^\s,;)]+/gi, 'password=***')
97
+ // Remove API keys
98
+ .replace(/apikey[=:]\s*[^\s,;)]+/gi, 'apikey=***')
99
+ .replace(/api[_-]?key[=:]\s*[^\s,;)]+/gi, 'api_key=***')
100
+ // Remove tokens
101
+ .replace(/token[=:]\s*[^\s,;)]+/gi, 'token=***')
102
+ .replace(/bearer\s+[^\s,;)]+/gi, 'bearer ***')
103
+ );
84
104
  }
85
105
 
86
106
  /**
@@ -145,11 +165,18 @@ exports.handler = async (event, context) => {
145
165
  console.log('Database Migration Lambda Started');
146
166
  console.log('========================================');
147
167
  console.log('Event:', JSON.stringify(event, null, 2));
148
- console.log('Context:', JSON.stringify({
149
- requestId: context.requestId,
150
- functionName: context.functionName,
151
- remainingTimeInMillis: context.getRemainingTimeInMillis(),
152
- }, null, 2));
168
+ console.log(
169
+ 'Context:',
170
+ JSON.stringify(
171
+ {
172
+ requestId: context.requestId,
173
+ functionName: context.functionName,
174
+ remainingTimeInMillis: context.getRemainingTimeInMillis(),
175
+ },
176
+ null,
177
+ 2
178
+ )
179
+ );
153
180
 
154
181
  // Extract migration parameters from event
155
182
  const { migrationId, dbType, stage } = extractMigrationParams(event);
@@ -164,7 +191,9 @@ exports.handler = async (event, context) => {
164
191
  console.log(`========================================`);
165
192
 
166
193
  try {
167
- const checkDbStateUseCase = new CheckDatabaseStateUseCase({ prismaRunner });
194
+ const checkDbStateUseCase = new CheckDatabaseStateUseCase({
195
+ prismaRunner,
196
+ });
168
197
  const status = await checkDbStateUseCase.execute(dbType, stage);
169
198
 
170
199
  console.log('✓ Database state check completed');
@@ -217,8 +246,10 @@ exports.handler = async (event, context) => {
217
246
 
218
247
  // Update migration status to RUNNING (if migrationId provided)
219
248
  if (migrationId) {
220
- console.log(`\n✓ Updating migration status to RUNNING: ${migrationId}`);
221
- await migrationStatusRepository.update({
249
+ console.log(
250
+ `\n✓ Updating migration status to RUNNING: ${migrationId}`
251
+ );
252
+ await getMigrationStatusRepository().update({
222
253
  migrationId,
223
254
  stage,
224
255
  state: 'RUNNING',
@@ -255,8 +286,10 @@ exports.handler = async (event, context) => {
255
286
 
256
287
  // Update migration status to COMPLETED (if migrationId provided)
257
288
  if (migrationId) {
258
- console.log(`\n✓ Updating migration status to COMPLETED: ${migrationId}`);
259
- await migrationStatusRepository.update({
289
+ console.log(
290
+ `\n✓ Updating migration status to COMPLETED: ${migrationId}`
291
+ );
292
+ await getMigrationStatusRepository().update({
260
293
  migrationId,
261
294
  stage,
262
295
  state: 'COMPLETED',
@@ -284,7 +317,6 @@ exports.handler = async (event, context) => {
284
317
  statusCode: 200,
285
318
  body: JSON.stringify(responseBody),
286
319
  };
287
-
288
320
  } catch (error) {
289
321
  console.error('\n========================================');
290
322
  console.error('Migration Failed');
@@ -317,8 +349,10 @@ exports.handler = async (event, context) => {
317
349
  // Update migration status to FAILED (if migrationId provided)
318
350
  if (migrationId) {
319
351
  try {
320
- console.log(`\n✓ Updating migration status to FAILED: ${migrationId}`);
321
- await migrationStatusRepository.update({
352
+ console.log(
353
+ `\n✓ Updating migration status to FAILED: ${migrationId}`
354
+ );
355
+ await getMigrationStatusRepository().update({
322
356
  migrationId,
323
357
  stage,
324
358
  state: 'FAILED',
@@ -327,7 +361,10 @@ exports.handler = async (event, context) => {
327
361
  failedAt: new Date().toISOString(),
328
362
  });
329
363
  } catch (updateError) {
330
- console.error('Failed to update migration status:', updateError.message);
364
+ console.error(
365
+ 'Failed to update migration status:',
366
+ updateError.message
367
+ );
331
368
  // Continue - don't let status update failure block error response
332
369
  }
333
370
  }
@@ -337,7 +374,9 @@ exports.handler = async (event, context) => {
337
374
  error: sanitizedError,
338
375
  errorType: error.name || 'Error',
339
376
  // Only include stack traces in development environments
340
- ...(stage === 'dev' || stage === 'local' || stage === 'test' ? { stack: error.stack } : {}),
377
+ ...(stage === 'dev' || stage === 'local' || stage === 'test'
378
+ ? { stack: error.stack }
379
+ : {}),
341
380
  };
342
381
 
343
382
  if (migrationId) {
package/index.js CHANGED
@@ -21,7 +21,6 @@ const {
21
21
  } = require('./database/index');
22
22
  const {
23
23
  createUserRepository,
24
- UserRepositoryMongo,
25
24
  UserRepositoryPostgres,
26
25
  } = require('./user/repositories/user-repository-factory');
27
26
  const {
@@ -30,9 +29,7 @@ const {
30
29
  const {
31
30
  GetUserFromAdopterJwt,
32
31
  } = require('./user/use-cases/get-user-from-adopter-jwt');
33
- const {
34
- AuthenticateUser,
35
- } = require('./user/use-cases/authenticate-user');
32
+ const { AuthenticateUser } = require('./user/use-cases/authenticate-user');
36
33
 
37
34
  const {
38
35
  CredentialRepository,
@@ -43,18 +40,14 @@ const {
43
40
  const {
44
41
  IntegrationMappingRepository,
45
42
  } = require('./integrations/repositories/integration-mapping-repository');
46
- const {
47
- CreateProcess,
48
- } = require('./integrations/use-cases/create-process');
43
+ const { CreateProcess } = require('./integrations/use-cases/create-process');
49
44
  const {
50
45
  UpdateProcessState,
51
46
  } = require('./integrations/use-cases/update-process-state');
52
47
  const {
53
48
  UpdateProcessMetrics,
54
49
  } = require('./integrations/use-cases/update-process-metrics');
55
- const {
56
- GetProcess,
57
- } = require('./integrations/use-cases/get-process');
50
+ const { GetProcess } = require('./integrations/use-cases/get-process');
58
51
  const { Cryptor } = require('./encrypt');
59
52
  const {
60
53
  BaseError,
@@ -70,12 +63,11 @@ const {
70
63
  checkRequiredParams,
71
64
  getModulesDefinitionFromIntegrationClasses,
72
65
  LoadIntegrationContextUseCase,
66
+ createProcessRepository,
73
67
  } = require('./integrations/index');
74
68
  const { TimeoutCatcher } = require('./lambda/index');
75
69
  const { debug, initDebugLog, flushDebugLog } = require('./logs/index');
76
70
  const {
77
- Credential,
78
- Entity,
79
71
  ApiKeyRequester,
80
72
  BasicAuthRequester,
81
73
  OAuth2Requester,
@@ -86,7 +78,31 @@ const {
86
78
  const application = require('./application');
87
79
  const utils = require('./utils');
88
80
 
89
- const { QueuerUtil } = require('./queues');
81
+ const {
82
+ QueuerUtil,
83
+ QueueProvider,
84
+ QueueClientInterface,
85
+ createQueueProvider,
86
+ QUEUE_PROVIDERS,
87
+ } = require('./queues');
88
+
89
+ const {
90
+ EncryptionKeyProviderInterface,
91
+ } = require('./encrypt/encryption-key-provider-interface');
92
+ const {
93
+ AesEncryptionKeyProvider,
94
+ } = require('./encrypt/aes-encryption-key-provider');
95
+ const {
96
+ WebSocketMessageSenderInterface,
97
+ StaleConnectionError,
98
+ } = require('./websocket/websocket-message-sender-interface');
99
+
100
+ const {
101
+ resolveProvider,
102
+ determineProviderName,
103
+ providerPackageName,
104
+ KNOWN_PROVIDERS,
105
+ } = require('./providers');
90
106
 
91
107
  module.exports = {
92
108
  // assertions
@@ -110,7 +126,7 @@ module.exports = {
110
126
  TokenRepository,
111
127
  WebsocketConnectionRepository,
112
128
  createUserRepository,
113
- UserRepositoryMongo,
129
+ get UserRepositoryMongo() { return require('./user/repositories/user-repository-factory').UserRepositoryMongo; },
114
130
  UserRepositoryPostgres,
115
131
  GetUserFromXFriggHeaders,
116
132
  GetUserFromAdopterJwt,
@@ -138,6 +154,7 @@ module.exports = {
138
154
  UpdateProcessState,
139
155
  UpdateProcessMetrics,
140
156
  GetProcess,
157
+ createProcessRepository,
141
158
 
142
159
  // application - Command factories for integration developers
143
160
  application,
@@ -147,6 +164,7 @@ module.exports = {
147
164
  createEntityCommands: application.createEntityCommands,
148
165
  createCredentialCommands: application.createCredentialCommands,
149
166
  createSchedulerCommands: application.createSchedulerCommands,
167
+ createAdminScriptCommands: application.createAdminScriptCommands,
150
168
  findIntegrationContextByExternalEntityId:
151
169
  application.findIntegrationContextByExternalEntityId,
152
170
  integrationCommands: application.integrationCommands,
@@ -160,8 +178,6 @@ module.exports = {
160
178
  flushDebugLog,
161
179
 
162
180
  // module plugin
163
- Credential,
164
- Entity,
165
181
  ApiKeyRequester,
166
182
  BasicAuthRequester,
167
183
  OAuth2Requester,
@@ -170,6 +186,24 @@ module.exports = {
170
186
  ModuleFactory,
171
187
  // queues
172
188
  QueuerUtil,
189
+ QueueProvider,
190
+ QueueClientInterface,
191
+ createQueueProvider,
192
+ QUEUE_PROVIDERS,
193
+
194
+ // encryption interfaces
195
+ EncryptionKeyProviderInterface,
196
+ AesEncryptionKeyProvider,
197
+
198
+ // websocket interfaces
199
+ WebSocketMessageSenderInterface,
200
+ StaleConnectionError,
201
+
202
+ // providers
203
+ resolveProvider,
204
+ determineProviderName,
205
+ providerPackageName,
206
+ KNOWN_PROVIDERS,
173
207
 
174
208
  // utils
175
209
  ...utils,
@@ -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
  };