@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
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Dynamic OpenAPI Spec Generator
3
+ *
4
+ * Generates OpenAPI specifications dynamically from appDefinition and installed modules.
5
+ * Supports both v1 (legacy) and v2 (current) API versions.
6
+ *
7
+ * Usage:
8
+ * const { generateOpenApiSpecV1, generateOpenApiSpecV2 } = require('./openapi-spec-generator');
9
+ * const v1Spec = generateOpenApiSpecV1(appDefinition, { serverUrl });
10
+ * const v2Spec = generateOpenApiSpecV2(appDefinition, { serverUrl });
11
+ */
12
+
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+ const yaml = require('js-yaml');
16
+
17
+ const V1_SPEC_PATH = path.join(__dirname, 'openapi-v1.yaml');
18
+ const V2_SPEC_PATH = path.join(__dirname, 'openapi-v2.yaml');
19
+
20
+ // Separate caches for each version
21
+ const cache = {
22
+ v1: { spec: null, modules: null },
23
+ v2: { spec: null, modules: null },
24
+ legacy: { spec: null, modules: null },
25
+ };
26
+
27
+ /**
28
+ * Load a YAML spec file
29
+ * @param {string} specPath - Path to the YAML file
30
+ * @returns {Object} Parsed spec object
31
+ */
32
+ function loadSpecFile(specPath) {
33
+ if (!fs.existsSync(specPath)) {
34
+ throw new Error(`OpenAPI spec not found: ${specPath}`);
35
+ }
36
+ const specContent = fs.readFileSync(specPath, 'utf8');
37
+ return yaml.load(specContent);
38
+ }
39
+
40
+ /**
41
+ * Load the base OpenAPI spec (defaults to v2)
42
+ * @deprecated Use loadV1Spec or loadV2Spec instead
43
+ */
44
+ function loadBaseSpec() {
45
+ return loadSpecFile(V2_SPEC_PATH);
46
+ }
47
+
48
+ /**
49
+ * Load the v1 OpenAPI spec
50
+ */
51
+ function loadV1Spec() {
52
+ return loadSpecFile(V1_SPEC_PATH);
53
+ }
54
+
55
+ /**
56
+ * Load the v2 OpenAPI spec
57
+ */
58
+ function loadV2Spec() {
59
+ return loadSpecFile(V2_SPEC_PATH);
60
+ }
61
+
62
+ /**
63
+ * Extract module metadata for OpenAPI documentation
64
+ * @param {Object} moduleDefinition - Module definition object
65
+ * @returns {Object} Module metadata
66
+ */
67
+ function extractModuleMetadata(moduleDefinition) {
68
+ const Definition = moduleDefinition.Definition || moduleDefinition;
69
+ const name = Definition?.getName?.() || Definition?.name || 'unknown';
70
+ const displayName = Definition?.display?.name || name;
71
+ const description =
72
+ Definition?.display?.description || `${displayName} integration`;
73
+ const moduleName = Definition?.moduleName || name;
74
+
75
+ // Extract auth type from first module
76
+ const moduleKeys = Object.keys(Definition?.modules || {});
77
+ const firstModule =
78
+ moduleKeys.length > 0 ? Definition.modules[moduleKeys[0]] : null;
79
+ const authType =
80
+ firstModule?.definition?.getAuthType?.() ||
81
+ firstModule?.authType ||
82
+ 'oauth2';
83
+ const stepCount = firstModule?.definition?.getAuthStepCount?.() || 1;
84
+
85
+ return {
86
+ name,
87
+ displayName,
88
+ description,
89
+ moduleName,
90
+ authType,
91
+ stepCount,
92
+ isMultiStep: stepCount > 1,
93
+ hasOptions: typeof Definition?.Options !== 'undefined',
94
+ hasEvents: typeof Definition?.events !== 'undefined',
95
+ capabilities: firstModule?.definition?.getCapabilities?.() || [],
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Enrich spec with installed module information
101
+ * @param {Object} spec - OpenAPI spec object
102
+ * @param {Array} installedModules - Array of module metadata
103
+ * @returns {Object} Enriched spec
104
+ */
105
+ function enrichSpecWithModules(spec, installedModules) {
106
+ if (!installedModules.length) return spec;
107
+
108
+ // Update ListEntityTypesResponse with actual modules
109
+ if (spec.components?.schemas?.ListEntityTypesResponse) {
110
+ spec.components.schemas.ListEntityTypesResponse.properties.types.example =
111
+ installedModules.map((m) => ({
112
+ type: m.name,
113
+ name: m.displayName,
114
+ description: m.description,
115
+ authType: m.authType,
116
+ isMultiStep: m.isMultiStep,
117
+ stepCount: m.stepCount,
118
+ }));
119
+ }
120
+
121
+ // Update IntegrationOption examples
122
+ if (spec.components?.schemas?.IntegrationOption) {
123
+ const examples = installedModules.slice(0, 3).map((m) => ({
124
+ type: m.name,
125
+ name: m.displayName,
126
+ description: m.description,
127
+ hasAuth: true,
128
+ }));
129
+ if (
130
+ spec.components.schemas.ListIntegrationOptionsResponse?.properties
131
+ ?.integrations
132
+ ) {
133
+ spec.components.schemas.ListIntegrationOptionsResponse.properties.integrations.example =
134
+ examples;
135
+ }
136
+ }
137
+
138
+ // Add module-specific enum values to parameters
139
+ const moduleNames = installedModules.map((m) => m.name);
140
+ if (moduleNames.length > 0) {
141
+ Object.values(spec.paths || {}).forEach((pathItem) => {
142
+ Object.values(pathItem).forEach((operation) => {
143
+ if (operation.parameters) {
144
+ operation.parameters.forEach((param) => {
145
+ if (
146
+ param.name === 'entityType' ||
147
+ param.name === 'typeName' ||
148
+ param.name === 'moduleType'
149
+ ) {
150
+ param.schema = param.schema || { type: 'string' };
151
+ param.schema.enum = moduleNames;
152
+ param.schema.example = moduleNames[0];
153
+ }
154
+ });
155
+ }
156
+ });
157
+ });
158
+ }
159
+
160
+ // Add installed modules section to spec info
161
+ const moduleList = installedModules
162
+ .map((m) => `- **${m.displayName}** (\`${m.name}\`): ${m.description}`)
163
+ .join('\n');
164
+
165
+ spec.info.description = `${
166
+ spec.info.description || ''
167
+ }\n\n## Installed Modules\n${moduleList}`;
168
+
169
+ return spec;
170
+ }
171
+
172
+ /**
173
+ * Extract installed modules from appDefinition
174
+ * @param {Object} appDefinition - App definition object
175
+ * @returns {Array} Array of module metadata
176
+ */
177
+ function extractInstalledModules(appDefinition) {
178
+ const installedModules = [];
179
+
180
+ if (appDefinition?.integrations) {
181
+ appDefinition.integrations.forEach((integration) => {
182
+ try {
183
+ const metadata = extractModuleMetadata(integration);
184
+ if (metadata.name !== 'unknown') {
185
+ installedModules.push(metadata);
186
+ }
187
+ } catch (e) {
188
+ // Skip modules that can't be processed
189
+ }
190
+ });
191
+ }
192
+
193
+ return installedModules;
194
+ }
195
+
196
+ /**
197
+ * Add server URL and generation metadata to spec
198
+ * @param {Object} spec - OpenAPI spec
199
+ * @param {Object} options - Options including serverUrl
200
+ * @param {Array} installedModules - Installed modules for metadata
201
+ * @returns {Object} Updated spec
202
+ */
203
+ function finalizeSpec(spec, options, installedModules) {
204
+ const { serverUrl } = options;
205
+
206
+ // Add custom server URL if provided
207
+ if (serverUrl) {
208
+ spec.servers = [
209
+ { url: serverUrl, description: 'Current server' },
210
+ ...(spec.servers || []),
211
+ ];
212
+ }
213
+
214
+ // Add generation metadata
215
+ spec.info['x-generated'] = {
216
+ timestamp: new Date().toISOString(),
217
+ moduleCount: installedModules.length,
218
+ modules: installedModules.map((m) => m.name),
219
+ };
220
+
221
+ return spec;
222
+ }
223
+
224
+ /**
225
+ * Generate v1 (legacy) OpenAPI spec from appDefinition
226
+ * @param {Object} appDefinition - The app definition containing integrations
227
+ * @param {Object} options - Generation options
228
+ * @returns {Object} Complete v1 OpenAPI specification
229
+ */
230
+ function generateOpenApiSpecV1(appDefinition = null, options = {}) {
231
+ const { useCache = true, serverUrl = null } = options;
232
+ const modulesKey = JSON.stringify(appDefinition?.integrations);
233
+
234
+ // Return cached spec if available
235
+ if (useCache && cache.v1.spec && cache.v1.modules === modulesKey) {
236
+ // Clone and update server URL if different
237
+ const spec = JSON.parse(JSON.stringify(cache.v1.spec));
238
+ if (serverUrl) {
239
+ spec.servers = [
240
+ { url: serverUrl, description: 'Current server' },
241
+ ...(spec.servers?.filter(
242
+ (s) => s.description !== 'Current server'
243
+ ) || []),
244
+ ];
245
+ }
246
+ return spec;
247
+ }
248
+
249
+ // Load v1 spec
250
+ const spec = loadV1Spec();
251
+
252
+ // Extract and enrich with installed modules
253
+ const installedModules = extractInstalledModules(appDefinition);
254
+ if (installedModules.length > 0) {
255
+ enrichSpecWithModules(spec, installedModules);
256
+ }
257
+
258
+ // Finalize spec
259
+ finalizeSpec(spec, { serverUrl }, installedModules);
260
+
261
+ // Cache result
262
+ if (useCache) {
263
+ cache.v1.spec = JSON.parse(JSON.stringify(spec));
264
+ cache.v1.modules = modulesKey;
265
+ }
266
+
267
+ return spec;
268
+ }
269
+
270
+ /**
271
+ * Generate v2 (current) OpenAPI spec from appDefinition
272
+ * @param {Object} appDefinition - The app definition containing integrations
273
+ * @param {Object} options - Generation options
274
+ * @returns {Object} Complete v2 OpenAPI specification
275
+ */
276
+ function generateOpenApiSpecV2(appDefinition = null, options = {}) {
277
+ const { useCache = true, serverUrl = null } = options;
278
+ const modulesKey = JSON.stringify(appDefinition?.integrations);
279
+
280
+ // Return cached spec if available
281
+ if (useCache && cache.v2.spec && cache.v2.modules === modulesKey) {
282
+ // Clone and update server URL if different
283
+ const spec = JSON.parse(JSON.stringify(cache.v2.spec));
284
+ if (serverUrl) {
285
+ spec.servers = [
286
+ { url: serverUrl, description: 'Current server' },
287
+ ...(spec.servers?.filter(
288
+ (s) => s.description !== 'Current server'
289
+ ) || []),
290
+ ];
291
+ }
292
+ return spec;
293
+ }
294
+
295
+ // Load v2 spec
296
+ const spec = loadV2Spec();
297
+
298
+ // Extract and enrich with installed modules
299
+ const installedModules = extractInstalledModules(appDefinition);
300
+ if (installedModules.length > 0) {
301
+ enrichSpecWithModules(spec, installedModules);
302
+ }
303
+
304
+ // Finalize spec
305
+ finalizeSpec(spec, { serverUrl }, installedModules);
306
+
307
+ // Cache result
308
+ if (useCache) {
309
+ cache.v2.spec = JSON.parse(JSON.stringify(spec));
310
+ cache.v2.modules = modulesKey;
311
+ }
312
+
313
+ return spec;
314
+ }
315
+
316
+ /**
317
+ * Generate OpenAPI spec (defaults to v2 for backwards compatibility)
318
+ * @deprecated Use generateOpenApiSpecV1 or generateOpenApiSpecV2 instead
319
+ * @param {Object} appDefinition - The app definition containing integrations
320
+ * @param {Object} options - Generation options
321
+ * @returns {Object} Complete OpenAPI specification
322
+ */
323
+ function generateOpenApiSpec(appDefinition = null, options = {}) {
324
+ return generateOpenApiSpecV2(appDefinition, options);
325
+ }
326
+
327
+ /**
328
+ * Clear all cached specs
329
+ */
330
+ function clearCache() {
331
+ cache.v1.spec = null;
332
+ cache.v1.modules = null;
333
+ cache.v2.spec = null;
334
+ cache.v2.modules = null;
335
+ cache.legacy.spec = null;
336
+ cache.legacy.modules = null;
337
+ }
338
+
339
+ /**
340
+ * Get spec as YAML string
341
+ * @param {Object} appDefinition - App definition
342
+ * @param {Object} options - Options including version ('v1' or 'v2')
343
+ * @returns {string} YAML string
344
+ */
345
+ function generateOpenApiYaml(appDefinition = null, options = {}) {
346
+ const { version = 'v2', ...restOptions } = options;
347
+ const spec =
348
+ version === 'v1'
349
+ ? generateOpenApiSpecV1(appDefinition, restOptions)
350
+ : generateOpenApiSpecV2(appDefinition, restOptions);
351
+ return yaml.dump(spec);
352
+ }
353
+
354
+ module.exports = {
355
+ // Primary exports for v1/v2
356
+ generateOpenApiSpecV1,
357
+ generateOpenApiSpecV2,
358
+
359
+ // Legacy/utility exports
360
+ generateOpenApiSpec,
361
+ generateOpenApiYaml,
362
+ clearCache,
363
+ extractModuleMetadata,
364
+
365
+ // Internal utilities (exported for testing)
366
+ loadBaseSpec,
367
+ loadV1Spec,
368
+ loadV2Spec,
369
+ enrichSpecWithModules,
370
+ };