@friggframework/core 2.0.0--canary.550.b8b9a2b.0 → 2.0.0--canary.545.e256e95.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 (254) 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 +67 -21
  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 +27 -16
  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/extensions/bootstrap-runner.js +43 -0
  74. package/extensions/extension-loader.js +167 -0
  75. package/extensions/index.js +37 -0
  76. package/extensions/initialize-app.js +123 -0
  77. package/extensions/route-mounter.js +59 -0
  78. package/extensions/schema-composer.js +188 -0
  79. package/generated/prisma-mongodb/edge.js +60 -5
  80. package/generated/prisma-mongodb/index-browser.js +49 -1
  81. package/generated/prisma-mongodb/index.d.ts +4025 -63
  82. package/generated/prisma-mongodb/index.js +66 -11
  83. package/generated/{prisma-postgresql/query-engine-debian-openssl-3.0.x → prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node} +0 -0
  84. package/generated/{prisma-postgresql/query-engine-rhel-openssl-3.0.x → prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node} +0 -0
  85. package/generated/prisma-mongodb/package.json +1 -1
  86. package/generated/prisma-mongodb/runtime/library.js +146 -0
  87. package/generated/prisma-mongodb/schema.prisma +89 -2
  88. package/generated/prisma-mongodb/wasm.js +60 -5
  89. package/generated/prisma-postgresql/edge.js +59 -5
  90. package/generated/prisma-postgresql/index-browser.js +48 -1
  91. package/generated/prisma-postgresql/index.d.ts +4742 -54
  92. package/generated/prisma-postgresql/index.js +65 -11
  93. package/generated/{prisma-mongodb/query-engine-debian-openssl-3.0.x → prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node} +0 -0
  94. package/generated/{prisma-mongodb/query-engine-rhel-openssl-3.0.x → prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node} +0 -0
  95. package/generated/prisma-postgresql/package.json +1 -1
  96. package/generated/prisma-postgresql/runtime/library.js +146 -0
  97. package/generated/prisma-postgresql/schema.prisma +87 -2
  98. package/generated/prisma-postgresql/wasm.js +59 -5
  99. package/handlers/WEBHOOKS.md +104 -81
  100. package/handlers/app-definition-loader.js +53 -10
  101. package/handlers/app-handler-helpers.js +14 -3
  102. package/handlers/backend-utils.js +3 -17
  103. package/handlers/database-migration-handler.js +64 -45
  104. package/handlers/middleware/admin-auth.js +53 -0
  105. package/handlers/routers/HEALTHCHECK.md +170 -135
  106. package/handlers/routers/admin.js +474 -0
  107. package/handlers/routers/auth.js +51 -9
  108. package/handlers/routers/db-migration.handler.js +2 -3
  109. package/handlers/routers/db-migration.js +102 -66
  110. package/handlers/routers/docs.js +180 -0
  111. package/handlers/routers/health.js +84 -283
  112. package/handlers/routers/integration-defined-routers.js +44 -33
  113. package/handlers/routers/integration-webhook-routers.js +73 -52
  114. package/handlers/routers/middleware/loadUser.js +39 -0
  115. package/handlers/routers/middleware/requireAdmin.js +43 -0
  116. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  117. package/handlers/routers/user.js +30 -14
  118. package/handlers/routers/websocket.js +18 -4
  119. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  120. package/handlers/workers/db-migration.js +74 -35
  121. package/handlers/workers/integration-defined-workers.js +31 -20
  122. package/index.js +60 -16
  123. package/infrastructure/scheduler/index.js +15 -11
  124. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  125. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  126. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  127. package/integrations/index.js +4 -0
  128. package/integrations/integration-base.js +9 -6
  129. package/integrations/integration-router.js +1841 -153
  130. package/integrations/options.js +47 -20
  131. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  132. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  133. package/integrations/repositories/integration-repository-factory.js +6 -6
  134. package/integrations/repositories/integration-repository-interface.js +27 -9
  135. package/integrations/repositories/integration-repository-postgres.js +3 -3
  136. package/integrations/repositories/process-repository-factory.js +4 -7
  137. package/integrations/repositories/process-repository-interface.js +9 -5
  138. package/integrations/repositories/process-repository-mongo.js +7 -3
  139. package/integrations/repositories/process-repository-postgres.js +2 -2
  140. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  141. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  142. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  143. package/integrations/use-cases/create-integration.js +36 -32
  144. package/integrations/use-cases/create-process.js +20 -9
  145. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  146. package/integrations/use-cases/execute-proxy-request.js +564 -0
  147. package/integrations/use-cases/get-integration-for-user.js +25 -10
  148. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  149. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  150. package/integrations/use-cases/get-possible-integrations.js +1 -1
  151. package/integrations/use-cases/get-process.js +5 -6
  152. package/integrations/use-cases/index.js +3 -1
  153. package/integrations/use-cases/load-integration-context.js +1 -5
  154. package/integrations/use-cases/update-process-state.js +10 -8
  155. package/integrations/utils/map-integration-dto.js +9 -3
  156. package/jest-setup.js +1 -1
  157. package/jest-teardown.js +1 -1
  158. package/lambda/TimeoutCatcher.js +32 -30
  159. package/lambda/index.js +1 -1
  160. package/logs/index.js +3 -3
  161. package/modules/domain/entities/AuthorizationSession.js +131 -0
  162. package/modules/domain/entities/index.js +10 -0
  163. package/modules/module-factory.js +1 -1
  164. package/modules/module.js +44 -11
  165. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  166. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  167. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  168. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  169. package/modules/repositories/module-repository-documentdb.js +89 -41
  170. package/modules/repositories/module-repository-factory.js +4 -6
  171. package/modules/repositories/module-repository-interface.js +13 -0
  172. package/modules/repositories/module-repository-mongo.js +40 -5
  173. package/modules/repositories/module-repository-postgres.js +42 -5
  174. package/modules/repositories/module-repository.js +28 -0
  175. package/modules/requester/api-key.js +0 -2
  176. package/modules/requester/basic.js +0 -1
  177. package/modules/requester/oauth-2.js +3 -14
  178. package/modules/test/mock-api/definition.js +15 -10
  179. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  180. package/modules/tests/doubles/test-module-factory.js +10 -4
  181. package/modules/tests/doubles/test-module-repository.js +1 -1
  182. package/modules/use-cases/delete-module-entity.js +23 -0
  183. package/modules/use-cases/get-authorization-requirements.js +110 -0
  184. package/modules/use-cases/get-entities-for-user.js +1 -1
  185. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  186. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  187. package/modules/use-cases/get-module.js +12 -9
  188. package/modules/use-cases/process-authorization-callback.js +12 -6
  189. package/modules/use-cases/process-authorization-step.js +160 -0
  190. package/modules/use-cases/refresh-entity-options.js +8 -6
  191. package/modules/use-cases/start-authorization-session.js +86 -0
  192. package/modules/use-cases/test-module-auth.js +8 -6
  193. package/modules/use-cases/update-module-entity.js +27 -0
  194. package/modules/utils/map-module-dto.js +2 -2
  195. package/openapi/openapi-spec-generator.js +370 -0
  196. package/openapi/openapi-v1.yaml +1360 -0
  197. package/openapi/openapi-v2.yaml +1535 -0
  198. package/package.json +7 -9
  199. package/prisma-mongodb/schema.prisma +89 -2
  200. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  201. package/prisma-postgresql/schema.prisma +87 -2
  202. package/providers/index.js +13 -0
  203. package/providers/resolve-provider.js +93 -0
  204. package/queues/index.js +19 -0
  205. package/queues/providers/index.js +7 -0
  206. package/queues/providers/netlify-background-provider.js +76 -0
  207. package/queues/providers/qstash-queue-provider.js +122 -0
  208. package/queues/queue-client-interface.js +55 -0
  209. package/queues/queue-provider-factory.js +93 -0
  210. package/queues/queue-provider.js +46 -0
  211. package/queues/queuer-util.js +33 -25
  212. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  213. package/syncs/repositories/sync-repository-factory.js +4 -4
  214. package/syncs/repositories/sync-repository-interface.js +6 -2
  215. package/syncs/repositories/sync-repository-postgres.js +19 -13
  216. package/syncs/sync.js +104 -100
  217. package/token/repositories/token-repository-factory.js +4 -6
  218. package/types/assertions/index.d.ts +72 -72
  219. package/types/core/index.d.ts +98 -53
  220. package/types/errors/index.d.ts +61 -54
  221. package/types/eslint-config/index.d.ts +40 -40
  222. package/types/lambda/index.d.ts +26 -26
  223. package/types/logs/index.d.ts +4 -4
  224. package/types/prettier-config/index.d.ts +5 -5
  225. package/types/test-environment/index.d.ts +14 -14
  226. package/types/tsconfig.json +95 -93
  227. package/user/repositories/user-repository-factory.js +4 -4
  228. package/user/tests/doubles/test-user-repository.js +5 -2
  229. package/user/use-cases/authenticate-user.js +2 -4
  230. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  231. package/user/use-cases/create-individual-user.js +8 -7
  232. package/user/use-cases/create-organization-user.js +1 -1
  233. package/user/use-cases/create-token-for-user-id.js +1 -1
  234. package/user/use-cases/delete-user.js +47 -0
  235. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  236. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  237. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  238. package/user/use-cases/login-user.js +8 -8
  239. package/user/user.js +18 -4
  240. package/utils/backend-path.js +2 -2
  241. package/utils/index.js +4 -1
  242. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  243. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  244. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  245. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  246. package/websocket/repositories/websocket-connection-repository.js +26 -20
  247. package/websocket/websocket-message-sender-interface.js +38 -0
  248. package/database/adapters/lambda-invoker.js +0 -97
  249. package/database/repositories/migration-status-repository-s3.js +0 -137
  250. package/generated/prisma-mongodb/runtime/binary.d.ts +0 -1
  251. package/generated/prisma-mongodb/runtime/binary.js +0 -289
  252. package/generated/prisma-postgresql/runtime/binary.d.ts +0 -1
  253. package/generated/prisma-postgresql/runtime/binary.js +0 -289
  254. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -3,8 +3,10 @@ const { ObjectId } = require('bson');
3
3
  function toObjectId(value) {
4
4
  if (value === null || value === undefined || value === '') return undefined;
5
5
  if (value instanceof ObjectId) return value;
6
- if (typeof value === 'object' && value.$oid) return new ObjectId(value.$oid);
7
- if (typeof value === 'string') return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
6
+ if (typeof value === 'object' && value.$oid)
7
+ return new ObjectId(value.$oid);
8
+ if (typeof value === 'string')
9
+ return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
8
10
  return undefined;
9
11
  }
10
12
 
@@ -15,7 +17,8 @@ function toObjectIdArray(values) {
15
17
 
16
18
  function fromObjectId(value) {
17
19
  if (value instanceof ObjectId) return value.toHexString();
18
- if (typeof value === 'object' && value !== null && value.$oid) return value.$oid;
20
+ if (typeof value === 'object' && value !== null && value.$oid)
21
+ return value.$oid;
19
22
  if (typeof value === 'string') return value;
20
23
  return value === undefined || value === null ? value : String(value);
21
24
  }
@@ -30,7 +33,10 @@ async function findMany(client, collection, filter = {}, options = {}) {
30
33
  }
31
34
 
32
35
  async function findOne(client, collection, filter = {}, options = {}) {
33
- const docs = await findMany(client, collection, filter, { ...options, limit: 1 });
36
+ const docs = await findMany(client, collection, filter, {
37
+ ...options,
38
+ limit: 1,
39
+ });
34
40
  return docs[0] || null;
35
41
  }
36
42
 
@@ -47,7 +53,9 @@ async function insertOne(client, collection, document) {
47
53
  // Validate insert succeeded
48
54
  if (result.ok !== 1) {
49
55
  throw new Error(
50
- `Insert command failed for collection '${collection}': ${JSON.stringify(result)}`
56
+ `Insert command failed for collection '${collection}': ${JSON.stringify(
57
+ result
58
+ )}`
51
59
  );
52
60
  }
53
61
 
@@ -67,7 +75,7 @@ async function insertOne(client, collection, document) {
67
75
  if (result.n !== 1) {
68
76
  throw new Error(
69
77
  `Expected to insert 1 document into '${collection}', but inserted ${result.n}. ` +
70
- `Result: ${JSON.stringify(result)}`
78
+ `Result: ${JSON.stringify(result)}`
71
79
  );
72
80
  }
73
81
 
@@ -75,11 +83,13 @@ async function insertOne(client, collection, document) {
75
83
  }
76
84
 
77
85
  async function updateOne(client, collection, filter, update, options = {}) {
78
- const updates = [{
79
- q: filter,
80
- u: update,
81
- upsert: Boolean(options.upsert),
82
- }];
86
+ const updates = [
87
+ {
88
+ q: filter,
89
+ u: update,
90
+ upsert: Boolean(options.upsert),
91
+ },
92
+ ];
83
93
  if (options.arrayFilters) updates[0].arrayFilters = options.arrayFilters;
84
94
  const result = await client.$runCommandRaw({
85
95
  update: collection,
@@ -133,4 +143,3 @@ module.exports = {
133
143
  deleteMany,
134
144
  aggregate,
135
145
  };
136
-
@@ -125,10 +125,11 @@ Or simply don't configure any encryption keys. In Production field level encrypt
125
125
  Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
126
126
 
127
127
  **Core fields include**:
128
- - OAuth tokens: `access_token`, `refresh_token`, `id_token`
129
- - API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
130
- - Basic auth: `password`
131
- - OAuth client credentials: `client_secret`
128
+
129
+ - OAuth tokens: `access_token`, `refresh_token`, `id_token`
130
+ - API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
131
+ - Basic auth: `password`
132
+ - OAuth client credentials: `client_secret`
132
133
 
133
134
  **Note**: API modules should use `api_key` (snake_case) in their `apiPropertiesToPersist.credential` arrays for consistency with OAuth2Requester and BasicAuthRequester conventions.
134
135
 
@@ -137,34 +138,36 @@ Core and custom encrypted fields are defined in `encryption-schema-registry.js`.
137
138
  When creating API module definitions, use **snake_case** for credential property names to ensure automatic encryption:
138
139
 
139
140
  **✅ Recommended (automatically encrypted):**
141
+
140
142
  ```javascript
141
143
  // API Module Definition
142
144
  const Definition = {
143
145
  requiredAuthMethods: {
144
146
  apiPropertiesToPersist: {
145
147
  // For API key authentication
146
- credential: ['api_key'], // ✅ Automatically encrypted
148
+ credential: ['api_key'], // ✅ Automatically encrypted
147
149
  // or for OAuth authentication
148
- credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
150
+ credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
149
151
  // or for Basic authentication
150
- credential: ['username', 'password'], // ✅ Basic auth - encrypted
151
- }
152
- }
152
+ credential: ['username', 'password'], // ✅ Basic auth - encrypted
153
+ },
154
+ },
153
155
  };
154
156
 
155
157
  // API class (extends ApiKeyRequester)
156
158
  class MyApi extends ApiKeyRequester {
157
159
  constructor(params) {
158
160
  super(params);
159
- this.api_key = params.api_key; // ✅ snake_case convention
161
+ this.api_key = params.api_key; // ✅ snake_case convention
160
162
  }
161
163
  }
162
164
  ```
163
165
 
164
166
  **❌ Avoid (requires manual encryption schema):**
167
+
165
168
  ```javascript
166
169
  apiPropertiesToPersist: {
167
- credential: ['customToken', 'proprietaryKey'] // ❌ Not in core schema
170
+ credential: ['customToken', 'proprietaryKey']; // ❌ Not in core schema
168
171
  }
169
172
  ```
170
173
 
@@ -184,57 +187,61 @@ const Definition = {
184
187
 
185
188
  // Declare which credential fields need encryption
186
189
  encryption: {
187
- credentialFields: ['api_key', 'webhook_secret']
190
+ credentialFields: ['api_key', 'webhook_secret'],
188
191
  },
189
192
 
190
193
  requiredAuthMethods: {
191
194
  apiPropertiesToPersist: {
192
- credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
193
- entity: []
195
+ credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
196
+ entity: [],
194
197
  },
195
198
  // ... other methods
196
- }
199
+ },
197
200
  };
198
201
  ```
199
202
 
200
203
  **How it works**:
204
+
201
205
  1. Module declares `encryption.credentialFields` array
202
206
  2. Framework automatically adds `data.` prefix: `['api_key']` → `['data.api_key']`
203
207
  3. Fields are merged with core encryption schema on app startup
204
208
  4. All modules across all integrations are scanned and combined
205
209
 
206
210
  **Benefits**:
207
- - ✅ Module authors control their own security requirements
208
- - ✅ No need to modify core framework or app configuration
209
- - ✅ Automatic encryption for API key-based integrations
210
- - ✅ Works seamlessly with `apiPropertiesToPersist`
211
+
212
+ - ✅ Module authors control their own security requirements
213
+ - ✅ No need to modify core framework or app configuration
214
+ - ✅ Automatic encryption for API key-based integrations
215
+ - ✅ Works seamlessly with `apiPropertiesToPersist`
211
216
 
212
217
  **Example - API Key Module**:
218
+
213
219
  ```javascript
214
220
  // API Module Definition
215
221
  const Definition = {
216
222
  moduleName: 'axiscare',
217
223
  API: AxisCareApi,
218
224
  encryption: {
219
- credentialFields: ['api_key'] // Auto-encrypted as 'data.api_key'
225
+ credentialFields: ['api_key'], // Auto-encrypted as 'data.api_key'
220
226
  },
221
227
  requiredAuthMethods: {
222
228
  apiPropertiesToPersist: {
223
- credential: ['api_key'] // Will be encrypted automatically
224
- }
225
- }
229
+ credential: ['api_key'], // Will be encrypted automatically
230
+ },
231
+ },
226
232
  };
227
233
 
228
234
  // API Class (extends ApiKeyRequester)
229
235
  class AxisCareApi extends ApiKeyRequester {
230
236
  constructor(params) {
231
237
  super(params);
232
- this.api_key = params.api_key; // snake_case convention
238
+ this.api_key = params.api_key; // snake_case convention
233
239
  }
234
240
  }
235
241
  ```
236
242
 
237
243
  **Example - Custom Authentication**:
244
+
238
245
  ```javascript
239
246
  const Definition = {
240
247
  moduleName: 'customService',
@@ -242,16 +249,17 @@ const Definition = {
242
249
  credentialFields: [
243
250
  'signing_key',
244
251
  'webhook_secret',
245
- 'data.custom_nested_field' // Can specify data. prefix explicitly
246
- ]
247
- }
252
+ 'data.custom_nested_field', // Can specify data. prefix explicitly
253
+ ],
254
+ },
248
255
  };
249
256
  ```
250
257
 
251
258
  **Limitations**:
252
- - Only supports Credential model fields (stored in `credential.data`)
253
- - Cannot encrypt entity fields or custom models (use app-level schema for those)
254
- - Applied globally once - module schemas loaded at app startup
259
+
260
+ - Only supports Credential model fields (stored in `credential.data`)
261
+ - Cannot encrypt entity fields or custom models (use app-level schema for those)
262
+ - Applied globally once - module schemas loaded at app startup
255
263
 
256
264
  #### Option 2: App-Level Custom Schema (Integration Developers)
257
265
 
@@ -529,7 +537,9 @@ For DocumentDB repositories, use `DocumentDBEncryptionService` to manually encry
529
537
  #### Usage Example
530
538
 
531
539
  ```javascript
532
- const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
540
+ const {
541
+ DocumentDBEncryptionService,
542
+ } = require('../documentdb-encryption-service');
533
543
  const { insertOne, findOne } = require('../documentdb-utils');
534
544
 
535
545
  class MyRepositoryDocumentDB {
@@ -539,12 +549,18 @@ class MyRepositoryDocumentDB {
539
549
 
540
550
  async create(data) {
541
551
  // Encrypt before write
542
- const encrypted = await this.encryptionService.encryptFields('ModelName', data);
552
+ const encrypted = await this.encryptionService.encryptFields(
553
+ 'ModelName',
554
+ data
555
+ );
543
556
  const id = await insertOne(this.prisma, 'CollectionName', encrypted);
544
557
 
545
558
  // Decrypt after read
546
559
  const doc = await findOne(this.prisma, 'CollectionName', { _id: id });
547
- const decrypted = await this.encryptionService.decryptFields('ModelName', doc);
560
+ const decrypted = await this.encryptionService.decryptFields(
561
+ 'ModelName',
562
+ doc
563
+ );
548
564
 
549
565
  return decrypted;
550
566
  }
@@ -554,9 +570,10 @@ class MyRepositoryDocumentDB {
554
570
  #### Configuration
555
571
 
556
572
  Uses the same environment variables and Cryptor as the Prisma Extension:
557
- - `STAGE`: Bypasses encryption for dev/test/local
558
- - `KMS_KEY_ARN`: AWS KMS encryption (production)
559
- - `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
573
+
574
+ - `STAGE`: Bypasses encryption for dev/test/local
575
+ - `KMS_KEY_ARN`: AWS KMS encryption (production)
576
+ - `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
560
577
 
561
578
  ## Usage Examples
562
579
 
@@ -72,7 +72,9 @@ function validateCustomSchema(schema) {
72
72
 
73
73
  for (const fieldPath of config.fields) {
74
74
  if (typeof fieldPath !== 'string' || !fieldPath) {
75
- errors.push(`Model "${modelName}" has invalid field path: ${fieldPath}`);
75
+ errors.push(
76
+ `Model "${modelName}" has invalid field path: ${fieldPath}`
77
+ );
76
78
  }
77
79
 
78
80
  // Check if trying to override core fields
@@ -104,13 +106,17 @@ function registerCustomSchema(schema) {
104
106
  const validation = validateCustomSchema(schema);
105
107
  if (!validation.valid) {
106
108
  throw new Error(
107
- `Invalid custom encryption schema:\n- ${validation.errors.join('\n- ')}`
109
+ `Invalid custom encryption schema:\n- ${validation.errors.join(
110
+ '\n- '
111
+ )}`
108
112
  );
109
113
  }
110
114
 
111
115
  customSchema = { ...schema };
112
116
  logger.info(
113
- `Registered custom encryption schema for models: ${Object.keys(customSchema).join(', ')}`
117
+ `Registered custom encryption schema for models: ${Object.keys(
118
+ customSchema
119
+ ).join(', ')}`
114
120
  );
115
121
  }
116
122
 
@@ -133,7 +139,9 @@ function extractCredentialFieldsFromModules(moduleDefinitions) {
133
139
  }
134
140
 
135
141
  for (const field of credentialFields) {
136
- const prefixedField = field.startsWith('data.') ? field : `data.${field}`;
142
+ const prefixedField = field.startsWith('data.')
143
+ ? field
144
+ : `data.${field}`;
137
145
  fields.push(prefixedField);
138
146
  }
139
147
  }
@@ -160,10 +168,14 @@ function loadModuleEncryptionSchemas(integrations) {
160
168
  return;
161
169
  }
162
170
 
163
- const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
171
+ const {
172
+ getModulesDefinitionFromIntegrationClasses,
173
+ } = require('../../integrations/utils/map-integration-dto');
164
174
 
165
- const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
166
- const credentialFields = extractCredentialFieldsFromModules(moduleDefinitions);
175
+ const moduleDefinitions =
176
+ getModulesDefinitionFromIntegrationClasses(integrations);
177
+ const credentialFields =
178
+ extractCredentialFieldsFromModules(moduleDefinitions);
167
179
 
168
180
  if (credentialFields.length === 0) {
169
181
  return;
@@ -171,8 +183,8 @@ function loadModuleEncryptionSchemas(integrations) {
171
183
 
172
184
  const moduleSchema = {
173
185
  Credential: {
174
- fields: credentialFields
175
- }
186
+ fields: credentialFields,
187
+ },
176
188
  };
177
189
 
178
190
  logger.info(
@@ -191,22 +203,48 @@ function loadModuleEncryptionSchemas(integrations) {
191
203
  *
192
204
  * Used by both Prisma (MongoDB/PostgreSQL) and DocumentDB encryption services.
193
205
  */
194
- function loadCustomEncryptionSchema() {
195
- try {
196
- // Lazy require to avoid circular dependency issues
197
- const path = require('node:path');
198
- const { findNearestBackendPackageJson } = require('../../utils');
206
+ /**
207
+ * Registers encryption schemas from extensions.
208
+ * Each extension can declare encrypted fields for its custom models.
209
+ *
210
+ * @param {Array<Object>} extensions - Normalized extensions array
211
+ */
212
+ function loadExtensionEncryptionSchemas(extensions) {
213
+ if (!extensions || !Array.isArray(extensions) || extensions.length === 0) {
214
+ return;
215
+ }
216
+
217
+ for (const ext of extensions) {
218
+ if (!ext.encryption || typeof ext.encryption !== 'object') {
219
+ continue;
220
+ }
199
221
 
200
- const backendPackagePath = findNearestBackendPackageJson();
201
- if (!backendPackagePath) {
202
- return; // No backend found, skip custom schema
222
+ const schema = {};
223
+ for (const [modelName, config] of Object.entries(ext.encryption)) {
224
+ if (config && Array.isArray(config.fields) && config.fields.length > 0) {
225
+ schema[modelName] = { fields: config.fields };
226
+ }
203
227
  }
204
228
 
205
- const backendDir = path.dirname(backendPackagePath);
206
- const backendIndexPath = path.join(backendDir, 'index.js');
229
+ if (Object.keys(schema).length > 0) {
230
+ logger.info(
231
+ `Registering encryption schema from extension "${ext.name}" for models: ${Object.keys(schema).join(', ')}`
232
+ );
233
+ registerCustomSchema(schema);
234
+ }
235
+ }
236
+ }
207
237
 
208
- const backendModule = require(backendIndexPath);
209
- const appDefinition = backendModule?.Definition;
238
+ function loadCustomEncryptionSchema() {
239
+ try {
240
+ // Uses loadAppDefinition() which respects setAppDefinition() cache.
241
+ // This is critical for platforms like Netlify where process.cwd()-based
242
+ // discovery fails at runtime (process.cwd() is /var/task, not the project root).
243
+ const {
244
+ loadAppDefinition,
245
+ } = require('../../handlers/app-definition-loader');
246
+
247
+ const { appDefinition } = loadAppDefinition();
210
248
 
211
249
  if (!appDefinition) {
212
250
  return; // No app definition found
@@ -223,6 +261,13 @@ function loadCustomEncryptionSchema() {
223
261
  if (integrations && Array.isArray(integrations)) {
224
262
  loadModuleEncryptionSchemas(integrations);
225
263
  }
264
+
265
+ // Load extension-level encryption schemas
266
+ const { loadExtensions } = require('../../extensions/extension-loader');
267
+ const extensions = loadExtensions(appDefinition);
268
+ if (extensions.length > 0) {
269
+ loadExtensionEncryptionSchemas(extensions);
270
+ }
226
271
  } catch (error) {
227
272
  // Silently ignore errors - custom schema is optional
228
273
  // This handles cases like:
@@ -262,6 +307,7 @@ module.exports = {
262
307
  registerCustomSchema,
263
308
  loadCustomEncryptionSchema,
264
309
  loadModuleEncryptionSchemas,
310
+ loadExtensionEncryptionSchemas,
265
311
  extractCredentialFieldsFromModules,
266
312
  validateCustomSchema,
267
313
  resetCustomSchema,
@@ -35,7 +35,9 @@ class FieldEncryptionService {
35
35
 
36
36
  if (this._shouldEncrypt(value)) {
37
37
  const serializedValue = this._serializeForEncryption(value);
38
- const encryptedValue = await this.cryptor.encrypt(serializedValue);
38
+ const encryptedValue = await this.cryptor.encrypt(
39
+ serializedValue
40
+ );
39
41
  return { fieldPath, encryptedValue };
40
42
  }
41
43
  return null;
@@ -46,7 +48,11 @@ class FieldEncryptionService {
46
48
  // Apply encrypted values
47
49
  for (const result of results) {
48
50
  if (result) {
49
- this._setNestedValue(encrypted, result.fieldPath, result.encryptedValue);
51
+ this._setNestedValue(
52
+ encrypted,
53
+ result.fieldPath,
54
+ result.encryptedValue
55
+ );
50
56
  }
51
57
  }
52
58
 
@@ -71,7 +77,8 @@ class FieldEncryptionService {
71
77
 
72
78
  if (this._isEncrypted(value)) {
73
79
  const decryptedValue = await this.cryptor.decrypt(value);
74
- const deserializedValue = this._deserializeAfterDecryption(decryptedValue);
80
+ const deserializedValue =
81
+ this._deserializeAfterDecryption(decryptedValue);
75
82
  return { fieldPath, decryptedValue: deserializedValue };
76
83
  }
77
84
  return null;
@@ -82,7 +89,11 @@ class FieldEncryptionService {
82
89
  // Apply decrypted values
83
90
  for (const result of results) {
84
91
  if (result) {
85
- this._setNestedValue(decrypted, result.fieldPath, result.decryptedValue);
92
+ this._setNestedValue(
93
+ decrypted,
94
+ result.fieldPath,
95
+ result.decryptedValue
96
+ );
86
97
  }
87
98
  }
88
99
 
@@ -30,8 +30,9 @@ class EncryptionLogger {
30
30
  // Remove potential key material or encrypted data from logs
31
31
  if (typeof message === 'string') {
32
32
  // Truncate long base64 strings that might be keys or encrypted data
33
- return message.replace(/([A-Za-z0-9+/=]{50,})/g, (match) =>
34
- `${match.substring(0, 10)}...[${match.length} chars]`
33
+ return message.replace(
34
+ /([A-Za-z0-9+/=]{50,})/g,
35
+ (match) => `${match.substring(0, 10)}...[${match.length} chars]`
35
36
  );
36
37
  }
37
38
  return message;
@@ -12,9 +12,7 @@ function createEncryptionExtension({ cryptor, enabled = true }) {
12
12
  }
13
13
 
14
14
  if (!cryptor) {
15
- throw new Error(
16
- 'Cryptor instance required for encryption extension'
17
- );
15
+ throw new Error('Cryptor instance required for encryption extension');
18
16
  }
19
17
 
20
18
  const encryptionService = new FieldEncryptionService({
@@ -48,11 +46,10 @@ function createEncryptionExtension({ cryptor, enabled = true }) {
48
46
 
49
47
  async createMany({ model, args, query }) {
50
48
  if (args.data && Array.isArray(args.data)) {
51
- args.data =
52
- await encryptionService.encryptFieldsInBulk(
53
- model,
54
- args.data
55
- );
49
+ args.data = await encryptionService.encryptFieldsInBulk(
50
+ model,
51
+ args.data
52
+ );
56
53
  } else if (args.data) {
57
54
  args.data = await encryptionService.encryptFields(
58
55
  model,
@@ -1,16 +1,19 @@
1
1
  const {
2
2
  createEncryptionExtension,
3
3
  } = require('./encryption/prisma-encryption-extension');
4
- const { loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
4
+ const {
5
+ loadCustomEncryptionSchema,
6
+ } = require('./encryption/encryption-schema-registry');
5
7
  const { logger } = require('./encryption/logger');
6
8
  const { Cryptor } = require('../encrypt/Cryptor');
9
+ const path = require('path');
7
10
  const config = require('./config');
8
11
 
9
12
  /**
10
13
  * Ensures DATABASE_URL is set for MongoDB connections
11
14
  * Falls back to MONGO_URI if DATABASE_URL is not set
12
15
  * Infrastructure layer concern - maps legacy MONGO_URI to Prisma's expected DATABASE_URL
13
- *
16
+ *
14
17
  * Note: This should only be called when DB_TYPE is 'mongodb' or 'documentdb'
15
18
  */
16
19
  function ensureMongoDbUrl() {
@@ -22,7 +25,9 @@ function ensureMongoDbUrl() {
22
25
  // Fallback to MONGO_URI for backwards compatibility with DocumentDB deployments
23
26
  if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
24
27
  process.env.DATABASE_URL = process.env.MONGO_URI;
25
- logger.debug('Using MONGO_URI as DATABASE_URL for Mongo-compatible connection');
28
+ logger.debug(
29
+ 'Using MONGO_URI as DATABASE_URL for Mongo-compatible connection'
30
+ );
26
31
  return;
27
32
  }
28
33
 
@@ -48,7 +53,7 @@ function getEncryptionConfig() {
48
53
  if (!hasKMS && !hasAES) {
49
54
  logger.warn(
50
55
  'No encryption keys configured (KMS_KEY_ARN or AES_KEY_ID). ' +
51
- 'Field-level encryption disabled. Set STAGE=production and configure keys to enable.'
56
+ 'Field-level encryption disabled. Set STAGE=production and configure keys to enable.'
52
57
  );
53
58
  return { enabled: false };
54
59
  }
@@ -67,20 +72,24 @@ const prismaClientSingleton = () => {
67
72
  const paths = [
68
73
  // Lambda layer location (when using Prisma Lambda layer)
69
74
  `/opt/nodejs/node_modules/generated/prisma-${dbType}`,
75
+ // __dirname-based resolution (works on Netlify + any bundler)
76
+ path.resolve(__dirname, `../generated/prisma-${dbType}`),
70
77
  // Local development location (relative to core package)
71
78
  `../generated/prisma-${dbType}`,
72
79
  ];
73
80
 
74
- for (const path of paths) {
81
+ for (const tryPath of paths) {
75
82
  try {
76
- return require(path).PrismaClient;
83
+ return require(tryPath).PrismaClient;
77
84
  } catch (err) {
78
85
  // Continue to next path
79
86
  }
80
87
  }
81
88
 
82
89
  throw new Error(
83
- `Cannot find Prisma client for ${dbType}. Tried paths: ${paths.join(', ')}`
90
+ `Cannot find Prisma client for ${dbType}. Tried paths: ${paths.join(
91
+ ', '
92
+ )}`
84
93
  );
85
94
  };
86
95
 
@@ -125,10 +134,7 @@ const prismaClientSingleton = () => {
125
134
  `Field-level encryption enabled using ${encryptionConfig.method.toUpperCase()}`
126
135
  );
127
136
  } catch (error) {
128
- logger.error(
129
- 'Failed to initialize encryption extension:',
130
- error
131
- );
137
+ logger.error('Failed to initialize encryption extension:', error);
132
138
  logger.warn('Continuing without encryption...');
133
139
  }
134
140
  } else {
@@ -149,11 +155,14 @@ function getPrismaClient() {
149
155
  }
150
156
 
151
157
  // Export a getter for lazy initialization
152
- const prisma = new Proxy({}, {
153
- get(target, prop) {
154
- return getPrismaClient()[prop];
158
+ const prisma = new Proxy(
159
+ {},
160
+ {
161
+ get(target, prop) {
162
+ return getPrismaClient()[prop];
163
+ },
155
164
  }
156
- });
165
+ );
157
166
 
158
167
  async function disconnectPrisma() {
159
168
  await getPrismaClient().$disconnect();
@@ -166,7 +175,9 @@ async function connectPrisma() {
166
175
  // Only run for MongoDB/DocumentDB (not PostgreSQL)
167
176
  // This prevents "Cannot create namespace in multi-document transaction" errors
168
177
  if (config.DB_TYPE === 'mongodb' || config.DB_TYPE === 'documentdb') {
169
- const { initializeMongoDBSchema } = require('./utils/mongodb-schema-init');
178
+ const {
179
+ initializeMongoDBSchema,
180
+ } = require('./utils/mongodb-schema-init');
170
181
  await initializeMongoDBSchema();
171
182
  }
172
183
 
@@ -1,6 +1,6 @@
1
- const { HealthCheckRepositoryMongoDB } = require('./health-check-repository-mongodb');
2
- const { HealthCheckRepositoryPostgreSQL } = require('./health-check-repository-postgres');
3
- const { HealthCheckRepositoryDocumentDB } = require('./health-check-repository-documentdb');
1
+ const {
2
+ HealthCheckRepositoryPostgreSQL,
3
+ } = require('./health-check-repository-postgres');
4
4
  const config = require('../config');
5
5
 
6
6
  /**
@@ -25,12 +25,14 @@ function createHealthCheckRepository({ prismaClient } = {}) {
25
25
 
26
26
  switch (dbType) {
27
27
  case 'mongodb':
28
+ const { HealthCheckRepositoryMongoDB } = require('./health-check-repository-mongodb');
28
29
  return new HealthCheckRepositoryMongoDB({ prismaClient });
29
30
 
30
31
  case 'postgresql':
31
32
  return new HealthCheckRepositoryPostgreSQL({ prismaClient });
32
33
 
33
34
  case 'documentdb':
35
+ const { HealthCheckRepositoryDocumentDB } = require('./health-check-repository-documentdb');
34
36
  return new HealthCheckRepositoryDocumentDB({ prismaClient });
35
37
 
36
38
  default:
@@ -42,7 +44,7 @@ function createHealthCheckRepository({ prismaClient } = {}) {
42
44
 
43
45
  module.exports = {
44
46
  createHealthCheckRepository,
45
- HealthCheckRepositoryMongoDB,
47
+ get HealthCheckRepositoryMongoDB() { return require('./health-check-repository-mongodb').HealthCheckRepositoryMongoDB; },
46
48
  HealthCheckRepositoryPostgreSQL,
47
- HealthCheckRepositoryDocumentDB,
49
+ get HealthCheckRepositoryDocumentDB() { return require('./health-check-repository-documentdb').HealthCheckRepositoryDocumentDB; },
48
50
  };