@platformatic/composer 2.72.0 → 3.0.0-alpha.1

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 (58) hide show
  1. package/config.d.ts +1 -19
  2. package/eslint.config.js +11 -2
  3. package/index.d.ts +58 -17
  4. package/index.js +29 -212
  5. package/lib/application.js +186 -0
  6. package/lib/commands/index.js +15 -0
  7. package/lib/commands/openapi-fetch-schemas.js +47 -0
  8. package/lib/composer-hook.js +9 -9
  9. package/lib/errors.js +15 -10
  10. package/lib/generator.js +158 -0
  11. package/lib/graphql-fetch.js +44 -46
  12. package/lib/graphql-generator.js +12 -10
  13. package/lib/graphql.js +13 -9
  14. package/lib/{proxy/not-host-constraints.js → not-host-constraints.js} +3 -5
  15. package/lib/openapi-composer.js +39 -41
  16. package/lib/openapi-config-schema.js +26 -30
  17. package/lib/openapi-generator.js +115 -112
  18. package/lib/openapi-load-config.js +14 -14
  19. package/lib/openapi-modifier.js +12 -21
  20. package/lib/openapi-scalar.js +3 -5
  21. package/lib/proxy.js +21 -34
  22. package/lib/{root-endpoint/index.js → root.js} +12 -12
  23. package/lib/schema.js +34 -24
  24. package/lib/stackable.js +29 -39
  25. package/lib/upgrade.js +6 -8
  26. package/lib/utils.js +5 -16
  27. package/lib/versions/2.0.0.js +4 -6
  28. package/package.json +14 -18
  29. package/schema.json +2 -73
  30. package/.c8rc +0 -6
  31. package/composer.mjs +0 -54
  32. package/help/create.txt +0 -11
  33. package/help/help.txt +0 -7
  34. package/help/openapi schemas fetch.txt +0 -9
  35. package/help/start.txt +0 -54
  36. package/index.test-d.ts +0 -23
  37. package/lib/create.mjs +0 -84
  38. package/lib/generator/README.md +0 -30
  39. package/lib/generator/composer-generator.d.ts +0 -11
  40. package/lib/generator/composer-generator.js +0 -128
  41. package/lib/metrics.js +0 -12
  42. package/lib/openapi-fetch-schemas.mjs +0 -61
  43. /package/{lib/root-endpoint/public → public}/images/dark_mode.svg +0 -0
  44. /package/{lib/root-endpoint/public → public}/images/ellipse.svg +0 -0
  45. /package/{lib/root-endpoint/public → public}/images/external-link.svg +0 -0
  46. /package/{lib/root-endpoint/public → public}/images/favicon.ico +0 -0
  47. /package/{lib/root-endpoint/public → public}/images/graphiql.svg +0 -0
  48. /package/{lib/root-endpoint/public → public}/images/graphql.svg +0 -0
  49. /package/{lib/root-endpoint/public → public}/images/light_mode.svg +0 -0
  50. /package/{lib/root-endpoint/public → public}/images/openapi.svg +0 -0
  51. /package/{lib/root-endpoint/public → public}/images/platformatic-logo-dark.svg +0 -0
  52. /package/{lib/root-endpoint/public → public}/images/platformatic-logo-light.svg +0 -0
  53. /package/{lib/root-endpoint/public → public}/images/reverse-proxy.svg +0 -0
  54. /package/{lib/root-endpoint/public → public}/images/triangle_dark.svg +0 -0
  55. /package/{lib/root-endpoint/public → public}/images/triangle_light.svg +0 -0
  56. /package/{lib/root-endpoint/public → public}/index.html +0 -0
  57. /package/{lib/root-endpoint/public → public}/index.njk +0 -0
  58. /package/{lib/root-endpoint/public → public}/main.css +0 -0
package/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export interface PlatformaticComposer {
8
+ export interface PlatformaticComposerConfig {
9
9
  basePath?: string;
10
10
  server?: {
11
11
  hostname?: string;
@@ -367,24 +367,6 @@ export interface PlatformaticComposer {
367
367
  addEmptySchema?: boolean;
368
368
  refreshTimeout?: number;
369
369
  };
370
- metrics?:
371
- | boolean
372
- | {
373
- port?: number | string;
374
- hostname?: string;
375
- endpoint?: string;
376
- server?: "own" | "parent" | "hide";
377
- defaultMetrics?: {
378
- enabled: boolean;
379
- };
380
- auth?: {
381
- username: string;
382
- password: string;
383
- };
384
- labels?: {
385
- [k: string]: string;
386
- };
387
- };
388
370
  types?: {
389
371
  autogenerate?: boolean;
390
372
  /**
package/eslint.config.js CHANGED
@@ -1,3 +1,12 @@
1
- 'use strict'
1
+ import neostandard from 'neostandard'
2
2
 
3
- module.exports = require('neostandard')({})
3
+ export default neostandard({
4
+ ts: true,
5
+ ignores: [
6
+ ...neostandard.resolveIgnoresFromGitignore(),
7
+ 'test/tmp/**/*',
8
+ 'test/fixtures/*/dist/**/*',
9
+ '**/dist/*',
10
+ 'tmp/**/*'
11
+ ]
12
+ })
package/index.d.ts CHANGED
@@ -1,17 +1,58 @@
1
- import { FastifyInstance } from 'fastify'
2
- import ConfigManager from '@platformatic/config'
3
- import type { ConfigManagerConfig, StackableInterface } from '@platformatic/config'
4
- import { PlatformaticComposer } from './config'
5
-
6
- export { PlatformaticApp } from '@platformatic/service'
7
- export type PlatformaticComposerConfig = PlatformaticComposer
8
-
9
- export function buildServer (opts: object, app?: object, ConfigManagerContructor?: object): Promise<FastifyInstance>
10
-
11
- export function buildStackable (opts: object, app?: object): Promise<{
12
- configType: string,
13
- configManager?: ConfigManager<PlatformaticComposerConfig>,
14
- configManagerConfig?: ConfigManagerConfig<PlatformaticComposerConfig>,
15
- schema?: object,
16
- stackable?: StackableInterface
17
- }>
1
+ import { BaseStackable } from '@platformatic/basic'
2
+ import { BaseGenerator } from '@platformatic/generators'
3
+ import {
4
+ Configuration,
5
+ ConfigurationOptions,
6
+ ServerInstance as ServiceInstance,
7
+ ServiceStackable
8
+ } from '@platformatic/service'
9
+ import { JSONSchemaType } from 'ajv'
10
+ import { FastifyError, FastifyInstance } from 'fastify'
11
+ import { PlatformaticComposerConfig } from './config'
12
+
13
+ export { PlatformaticApplication } from '@platformatic/service'
14
+ export { PlatformaticComposerConfig } from './config'
15
+
16
+ export type ComposerStackable = ServiceStackable<PlatformaticComposerConfig>
17
+
18
+ export type ServerInstance = ServiceInstance<PlatformaticComposerConfig>
19
+
20
+ type ComposerConfiguration = Configuration<PlatformaticComposerConfig>
21
+
22
+ export declare function loadConfiguration (
23
+ root: string | PlatformaticServiceConfig,
24
+ source?: string | PlatformaticServiceConfig,
25
+ context?: ConfigurationOptions
26
+ ): Promise<ComposerConfiguration>
27
+
28
+ export function create (
29
+ root: string,
30
+ source?: string | PlatformaticComposerConfig,
31
+ context?: ConfigurationOptions
32
+ ): Promise<ComposerStackable>
33
+
34
+ export declare function platformaticComposer (app: FastifyInstance, stackable: BaseStackable): Promise<void>
35
+
36
+ export class Generator extends BaseGenerator.BaseGenerator {}
37
+
38
+ export declare const packageJson: Record<string, unknown>
39
+
40
+ export declare const schema: JSONSchemaType<PlatformaticComposerConfig>
41
+
42
+ export declare const schemaComponents: {
43
+ openApiService: JSONSchemaType<object>
44
+ entityResolver: JSONSchemaType<object>
45
+ entities: JSONSchemaType<object>
46
+ graphqlService: JSONSchemaType<object>
47
+ graphqlComposerOptions: JSONSchemaType<object>
48
+ composer: JSONSchemaType<object>
49
+ types: JSONSchemaType<object>
50
+ }
51
+
52
+ export declare const version: string
53
+
54
+ export function FastifyInstanceIsAlreadyListeningError (): FastifyError
55
+ export function FailedToFetchOpenAPISchemaError (): FastifyError
56
+ export function ValidationErrors (): FastifyError
57
+ export function PathAlreadyExistsError (): FastifyError
58
+ export function CouldNotReadOpenAPIConfigError (): FastifyError
package/index.js CHANGED
@@ -1,217 +1,34 @@
1
- 'use strict'
2
-
3
- const deepEqual = require('fast-deep-equal')
4
- const ConfigManager = require('@platformatic/config')
5
- const { platformaticService, buildServer, buildStackable } = require('@platformatic/service')
6
- const { isKeyEnabled } = require('@platformatic/utils')
7
-
8
- const { schema } = require('./lib/schema')
9
- const serviceProxy = require('./lib/proxy')
10
- const graphql = require('./lib/graphql')
11
- const composerHook = require('./lib/composer-hook')
12
- const { openApiGenerator, openApiComposer } = require('./lib/openapi-generator')
13
- const graphqlGenerator = require('./lib/graphql-generator')
14
- const { isSameGraphqlSchema, fetchGraphqlSubgraphs } = require('./lib/graphql-fetch')
15
- const notHostConstraints = require('./lib/proxy/not-host-constraints')
16
- const { isFetchable } = require('./lib/utils')
17
- const { ComposerStackable, ensureServices } = require('./lib/stackable')
18
- const errors = require('./lib/errors')
19
- const upgrade = require('./lib/upgrade')
20
-
21
- const EXPERIMENTAL_GRAPHQL_COMPOSER_FEATURE_MESSAGE = 'graphql composer is an experimental feature'
22
-
23
- async function platformaticComposer (app, opts) {
24
- const configManager = app.platformatic.configManager
25
- const config = configManager.current
26
- let hasGraphqlServices, hasOpenapiServices
27
-
28
- // When no services are specified, get the list from the runtime.
29
- await ensureServices(opts.context?.stackable?.serviceId, config)
30
-
31
- const { services } = configManager.current.composer
32
-
33
- for (const service of services) {
34
- if (!service.origin) {
35
- service.origin = `http://${service.id}.plt.local`
36
- }
37
- if (service.openapi && !hasOpenapiServices) {
38
- hasOpenapiServices = true
39
- }
40
- if (service.graphql && !hasGraphqlServices) {
41
- hasGraphqlServices = true
42
- }
43
- }
44
-
45
- await app.register(composerHook)
46
-
47
- let generatedComposedOpenAPI = null
48
- if (hasOpenapiServices) {
49
- generatedComposedOpenAPI = await openApiGenerator(app, config.composer)
50
- }
51
-
52
- if (isKeyEnabled('healthCheck', config.server)) {
53
- if (typeof config.server.healthCheck !== 'object') {
54
- config.server.healthCheck = {}
55
- }
56
-
57
- const stackable = opts.context.stackable
58
- config.server.healthCheck.fn = stackable.isHealthy.bind(stackable)
59
- }
60
-
61
- app.register(serviceProxy, { ...config.composer, context: opts.context })
62
- await app.register(platformaticService, { config: { ...config, openapi: false }, context: opts.context })
63
-
64
- if (generatedComposedOpenAPI) {
65
- await app.register(openApiComposer, { opts: config.composer, generated: generatedComposedOpenAPI })
66
- }
67
-
68
- if (hasGraphqlServices) {
69
- app.log.warn(EXPERIMENTAL_GRAPHQL_COMPOSER_FEATURE_MESSAGE)
70
- app.register(graphql, config.composer)
71
- await app.register(graphqlGenerator, config.composer)
72
- }
73
-
74
- if (!app.hasRoute({ url: '/', method: 'GET' }) && !app.hasRoute({ url: '/*', method: 'GET' })) {
75
- await app.register(require('./lib/root-endpoint'), config)
76
- }
77
-
78
- if (!opts.context?.isProduction) {
79
- await watchServices(app, config)
80
- }
81
- }
82
-
83
- platformaticComposer[Symbol.for('skip-override')] = true
84
- platformaticComposer.schema = schema
85
- platformaticComposer.configType = 'composer'
86
- platformaticComposer.isPLTService = true
87
- platformaticComposer.configManagerConfig = {
88
- version: require('./package.json').version,
89
- schema,
90
- allowToWatch: ['.env'],
91
- schemaOptions: {
92
- useDefaults: true,
93
- coerceTypes: true,
94
- allErrors: true,
95
- strict: false
96
- },
97
- transformConfig: platformaticService.configManagerConfig.transformConfig,
98
- upgrade
99
- }
100
-
101
- // TODO review no need to be async
102
- async function buildComposerServer (options) {
103
- // TODO ConfigManager is not been used, it's attached to platformaticComposer, can be removed
104
- return buildServer(options, platformaticComposer, ConfigManager)
105
- }
106
-
107
- async function detectServicesUpdate ({ app, services, fetchOpenApiSchema, fetchGraphqlSubgraphs }) {
108
- let changed
109
-
110
- const graphqlServices = []
111
- // assumes services here are fetchable
112
- for (const service of services) {
113
- const { id, origin, openapi, graphql } = service
114
-
115
- if (openapi) {
116
- const currentSchema = app.openApiSchemas.find(schema => schema.id === id)?.originSchema || null
117
-
118
- let fetchedSchema = null
119
- try {
120
- fetchedSchema = await fetchOpenApiSchema({ origin, openapi })
121
- } catch (err) {
122
- app.log.error({ err }, 'failed to fetch schema (watch) for service ' + id)
123
- }
124
-
125
- if (!changed && !deepEqual(fetchedSchema, currentSchema)) {
126
- changed = true
127
- // it stops at first schema difference since all the schemas will be updated on reload
128
- break
129
- }
130
- }
131
-
132
- if (graphql) {
133
- graphqlServices.push(service)
134
- }
135
- }
136
-
137
- if (!changed && graphqlServices.length > 0) {
138
- const graphqlSupergraph = await fetchGraphqlSubgraphs(graphqlServices, app.graphqlComposerOptions, app)
139
- if (!isSameGraphqlSchema(graphqlSupergraph, app.graphqlSupergraph)) {
140
- changed = true
141
- app.graphqlSupergraph = graphqlSupergraph
142
- }
143
- }
144
-
145
- return changed
146
- }
147
-
148
- /**
149
- * poll services to detect changes, every `opts.composer.refreshTimeout`
150
- * polling is disabled on refreshTimeout = 0
151
- * or there are no network openapi nor graphql remote services (the services are from file or they don't have a schema/graph to fetch)
152
- */
153
- async function watchServices (app, opts) {
154
- const { services, refreshTimeout } = opts.composer
155
- if (refreshTimeout < 1) {
156
- return
157
- }
158
-
159
- const watching = services.filter(isFetchable)
160
- if (watching.length < 1) {
161
- return
162
- }
163
-
164
- if (!globalThis[Symbol.for('plt.runtime.id')]) {
165
- app.log.warn('Watching services is only supported when running within a Platformatic Runtime.')
166
- return
167
- }
168
-
169
- const { fetchOpenApiSchema } = await import('./lib/openapi-fetch-schemas.mjs')
170
-
171
- app.log.info({ services: watching }, 'start watching services')
172
-
173
- const timer = setInterval(async () => {
174
- try {
175
- if (await detectServicesUpdate({ app, services: watching, fetchOpenApiSchema, fetchGraphqlSubgraphs })) {
176
- clearInterval(timer)
177
- app.log.info('detected services changes, restarting ...')
178
-
179
- globalThis[Symbol.for('plt.runtime.itc')].notify('changed')
180
- }
181
- } catch (error) {
182
- app.log.error(
183
- {
184
- err: {
185
- message: error.message,
186
- stack: error.stack
187
- }
188
- },
189
- 'failed to get services info'
190
- )
191
- }
192
- }, refreshTimeout).unref()
193
-
194
- app.addHook('onClose', async () => {
195
- clearInterval(timer)
1
+ import { resolve, validationOptions } from '@platformatic/basic'
2
+ import { transform } from '@platformatic/service'
3
+ import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/utils'
4
+ import { schema } from './lib/schema.js'
5
+ import { ComposerStackable } from './lib/stackable.js'
6
+ import { upgrade } from './lib/upgrade.js'
7
+
8
+ export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
9
+ const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'composer')
10
+
11
+ return utilsLoadConfiguration(source, context?.schema ?? schema, {
12
+ validationOptions,
13
+ transform,
14
+ upgrade,
15
+ replaceEnv: true,
16
+ root,
17
+ ...context
196
18
  })
197
19
  }
198
20
 
199
- async function buildComposerStackable (options) {
200
- options.context ??= {}
201
- options.context.fastifyOptions ??= {
202
- constraints: {
203
- notHost: notHostConstraints
204
- }
205
- }
206
-
207
- return buildStackable(options, platformaticComposer, ComposerStackable)
21
+ export async function create (configOrRoot, sourceOrConfig, context) {
22
+ const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
23
+ return new ComposerStackable(config[kMetadata].root, config, context)
208
24
  }
209
25
 
210
- module.exports = platformaticComposer
211
- module.exports.schema = schema
212
- module.exports.platformaticComposer = platformaticComposer
213
- module.exports.buildServer = buildComposerServer
214
- module.exports.errors = errors
215
- module.exports.Generator = require('./lib/generator/composer-generator')
216
- module.exports.ConfigManager = ConfigManager
217
- module.exports.buildStackable = buildComposerStackable
26
+ export const skipTelemetryHooks = true
27
+
28
+ export { platformaticComposer } from './lib/application.js'
29
+ export * from './lib/commands/index.js'
30
+ export * from './lib/errors.js'
31
+ export * as errors from './lib/errors.js'
32
+ export { Generator } from './lib/generator.js'
33
+ export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
34
+ export { ComposerStackable } from './lib/stackable.js'
@@ -0,0 +1,186 @@
1
+ import { platformaticService } from '@platformatic/service'
2
+ import { isKeyEnabled } from '@platformatic/utils'
3
+ import deepEqual from 'fast-deep-equal'
4
+ import { fetchOpenApiSchema } from './commands/openapi-fetch-schemas.js'
5
+ import { composerHook } from './composer-hook.js'
6
+ import { fetchGraphqlSubgraphs, isSameGraphqlSchema } from './graphql-fetch.js'
7
+ import { graphqlGenerator } from './graphql-generator.js'
8
+ import { graphql } from './graphql.js'
9
+ import { openApiComposer, openApiGenerator } from './openapi-generator.js'
10
+ import { proxy } from './proxy.js'
11
+ import { isFetchable } from './utils.js'
12
+
13
+ const kITC = Symbol.for('plt.runtime.itc')
14
+ const EXPERIMENTAL_GRAPHQL_COMPOSER_FEATURE_MESSAGE = 'graphql composer is an experimental feature'
15
+
16
+ async function detectServicesUpdate ({ app, services, fetchOpenApiSchema, fetchGraphqlSubgraphs }) {
17
+ let changed
18
+
19
+ const graphqlServices = []
20
+ // assumes services here are fetchable
21
+ for (const service of services) {
22
+ const { id, origin, openapi, graphql } = service
23
+
24
+ if (openapi) {
25
+ const currentSchema = app.openApiSchemas.find(schema => schema.id === id)?.originSchema || null
26
+
27
+ let fetchedSchema = null
28
+ try {
29
+ fetchedSchema = await fetchOpenApiSchema({ origin, openapi })
30
+ } catch (err) {
31
+ app.log.error({ err }, 'failed to fetch schema (watch) for service ' + id)
32
+ }
33
+
34
+ if (!changed && !deepEqual(fetchedSchema, currentSchema)) {
35
+ changed = true
36
+ // it stops at first schema difference since all the schemas will be updated on reload
37
+ break
38
+ }
39
+ }
40
+
41
+ if (graphql) {
42
+ graphqlServices.push(service)
43
+ }
44
+ }
45
+
46
+ if (!changed && graphqlServices.length > 0) {
47
+ const graphqlSupergraph = await fetchGraphqlSubgraphs(graphqlServices, app.graphqlComposerOptions, app)
48
+ if (!isSameGraphqlSchema(graphqlSupergraph, app.graphqlSupergraph)) {
49
+ changed = true
50
+ app.graphqlSupergraph = graphqlSupergraph
51
+ }
52
+ }
53
+
54
+ return changed
55
+ }
56
+
57
+ /**
58
+ * poll services to detect changes, every `opts.composer.refreshTimeout`
59
+ * polling is disabled on refreshTimeout = 0
60
+ * or there are no network openapi nor graphql remote services (the services are from file or they don't have a schema/graph to fetch)
61
+ */
62
+ async function watchServices (app, { config, stackable }) {
63
+ const { services, refreshTimeout } = config.composer
64
+ if (refreshTimeout < 1) {
65
+ return
66
+ }
67
+
68
+ const watching = services.filter(isFetchable)
69
+ if (watching.length < 1) {
70
+ return
71
+ }
72
+
73
+ if (!globalThis[Symbol.for('plt.runtime.id')]) {
74
+ app.log.warn('Watching services is only supported when running within a Platformatic Runtime.')
75
+ return
76
+ }
77
+
78
+ stackable.emit('watch:start')
79
+ app.log.info({ services: watching }, 'start watching services')
80
+
81
+ const timer = setInterval(async () => {
82
+ try {
83
+ if (await detectServicesUpdate({ app, services: watching, fetchOpenApiSchema, fetchGraphqlSubgraphs })) {
84
+ clearInterval(timer)
85
+ app.log.info('detected services changes, restarting ...')
86
+
87
+ globalThis[Symbol.for('plt.runtime.itc')].notify('changed')
88
+ }
89
+ } catch (error) {
90
+ app.log.error(
91
+ {
92
+ err: {
93
+ message: error.message,
94
+ stack: error.stack
95
+ }
96
+ },
97
+ 'failed to get services info'
98
+ )
99
+ }
100
+ }, refreshTimeout).unref()
101
+
102
+ app.addHook('onClose', async () => {
103
+ clearInterval(timer)
104
+ })
105
+ }
106
+
107
+ export async function ensureServices (composerId, config) {
108
+ if (config.composer?.services?.length) {
109
+ return
110
+ }
111
+
112
+ composerId ??= globalThis.platformatic?.serviceId
113
+ config.composer ??= {}
114
+ config.composer.services ??= []
115
+
116
+ // When no services are defined, all services are exposed in the composer
117
+ const services = await globalThis[kITC]?.send('listServices')
118
+
119
+ if (services) {
120
+ config.composer.services = services
121
+ .filter(id => id !== composerId) // Remove ourself
122
+ .map(id => ({ id, proxy: { prefix: `/${id}` } }))
123
+ }
124
+ }
125
+
126
+ export async function platformaticComposer (app, stackable) {
127
+ const config = await stackable.getConfig()
128
+ let hasGraphqlServices, hasOpenapiServices
129
+
130
+ // When no services are specified, get the list from the runtime.
131
+ await ensureServices(stackable.serviceId, config)
132
+
133
+ const { services } = config.composer
134
+
135
+ for (const service of services) {
136
+ if (!service.origin) {
137
+ service.origin = `http://${service.id}.plt.local`
138
+ }
139
+ if (service.openapi && !hasOpenapiServices) {
140
+ hasOpenapiServices = true
141
+ }
142
+ if (service.graphql && !hasGraphqlServices) {
143
+ hasGraphqlServices = true
144
+ }
145
+ }
146
+
147
+ await app.register(composerHook)
148
+
149
+ let generatedComposedOpenAPI = null
150
+ if (hasOpenapiServices) {
151
+ generatedComposedOpenAPI = await openApiGenerator(app, config.composer)
152
+ }
153
+
154
+ if (isKeyEnabled('healthCheck', config.server)) {
155
+ if (typeof config.server.healthCheck !== 'object') {
156
+ config.server.healthCheck = {}
157
+ }
158
+
159
+ config.server.healthCheck.fn = stackable.isHealthy.bind(stackable)
160
+ }
161
+
162
+ await app.register(proxy, { ...config.composer, stackable, context: stackable.context })
163
+
164
+ await platformaticService(app, stackable)
165
+
166
+ if (generatedComposedOpenAPI) {
167
+ await app.register(openApiComposer, { opts: config.composer, generated: generatedComposedOpenAPI })
168
+ }
169
+
170
+ if (hasGraphqlServices) {
171
+ app.log.warn(EXPERIMENTAL_GRAPHQL_COMPOSER_FEATURE_MESSAGE)
172
+ app.register(graphql, config.composer)
173
+ await app.register(graphqlGenerator, config.composer)
174
+ }
175
+
176
+ if (!app.hasRoute({ url: '/', method: 'GET' }) && !app.hasRoute({ url: '/*', method: 'GET' })) {
177
+ const rootHandler = await import('./root.js')
178
+ await app.register(rootHandler.default, config)
179
+ }
180
+
181
+ if (!stackable.context?.isProduction) {
182
+ await watchServices(app, { config, stackable, context: stackable.context })
183
+ }
184
+ }
185
+
186
+ platformaticComposer[Symbol.for('skip-override')] = true
@@ -0,0 +1,15 @@
1
+ import { fetchOpenApiSchemas } from './openapi-fetch-schemas.js'
2
+
3
+ export function createCommands (id) {
4
+ return {
5
+ commands: {
6
+ [`${id}:fetch-openapi-schemas`]: fetchOpenApiSchemas
7
+ },
8
+ help: {
9
+ [`${id}:fetch-openapi-schemas`]: {
10
+ usage: `${id}:fetch-openapi-schemas`,
11
+ description: 'Fetch OpenAPI schemas from remote services'
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,47 @@
1
+ import { loadConfiguration } from '@platformatic/utils'
2
+ import { writeFile } from 'node:fs/promises'
3
+ import { request } from 'undici'
4
+ import { FailedToFetchOpenAPISchemaError } from '../errors.js'
5
+ import { schema } from '../schema.js'
6
+ import { prefixWithSlash } from '../utils.js'
7
+
8
+ export async function fetchOpenApiSchema (service) {
9
+ const { origin, openapi } = service
10
+
11
+ const openApiUrl = origin + prefixWithSlash(openapi.url)
12
+ const { statusCode, body } = await request(openApiUrl)
13
+
14
+ if (statusCode !== 200 && statusCode !== 201) {
15
+ throw new FailedToFetchOpenAPISchemaError(openApiUrl)
16
+ }
17
+ const schema = await body.json()
18
+
19
+ if (openapi.file !== undefined) {
20
+ await writeFile(openapi.file, JSON.stringify(schema, null, 2))
21
+ }
22
+
23
+ return schema
24
+ }
25
+
26
+ export async function fetchOpenApiSchemas (logger, configFile, _args, { colorette }) {
27
+ const { bold } = colorette
28
+ const config = await loadConfiguration(configFile, schema)
29
+ const { services } = config.composer
30
+
31
+ const servicesWithValidOpenApi = services.filter(({ openapi }) => openapi && openapi.url && openapi.file)
32
+
33
+ const fetchOpenApiRequests = servicesWithValidOpenApi.map(service => fetchOpenApiSchema(service))
34
+
35
+ const fetchOpenApiResults = await Promise.allSettled(fetchOpenApiRequests)
36
+
37
+ logger.info('Fetching schemas for all services.')
38
+
39
+ fetchOpenApiResults.forEach((result, index) => {
40
+ const serviceId = servicesWithValidOpenApi[index].id
41
+ if (result.status === 'rejected') {
42
+ logger.error(`Failed to fetch OpenAPI schema for service with id ${bold(serviceId)}: ${result.reason}`)
43
+ } else {
44
+ logger.info(`Successfully fetched OpenAPI schema for service with id ${bold(serviceId)}`)
45
+ }
46
+ })
47
+ }
@@ -1,13 +1,13 @@
1
- 'use strict'
1
+ import fp from 'fastify-plugin'
2
+ import rfdc from 'rfdc'
3
+ import { FastifyInstanceIsAlreadyListeningError } from './errors.js'
2
4
 
3
- const deepClone = require('rfdc')()
4
- const fp = require('fastify-plugin')
5
- const errors = require('./errors')
5
+ const deepClone = rfdc()
6
6
 
7
- async function composeOpenAPI (app) {
7
+ async function composerHookPlugin (app) {
8
8
  const onRoutesHooks = {}
9
9
 
10
- app.addHook('onRoute', (routeOptions) => {
10
+ app.addHook('onRoute', routeOptions => {
11
11
  if (routeOptions.schema) {
12
12
  routeOptions.schema = deepClone(routeOptions.schema)
13
13
  }
@@ -31,7 +31,7 @@ async function composeOpenAPI (app) {
31
31
  function addComposerOnRouteHook (openApiPath, methods, hook) {
32
32
  /* c8 ignore next 5 */
33
33
  if (isApplicationReady) {
34
- throw new errors.FastifyInstanceIsAlreadyListeningError()
34
+ throw new FastifyInstanceIsAlreadyListeningError()
35
35
  }
36
36
 
37
37
  if (onRoutesHooks[openApiPath] === undefined) {
@@ -53,8 +53,8 @@ async function composeOpenAPI (app) {
53
53
  Object.defineProperty(app.platformatic, 'addComposerOnRouteHook', {
54
54
  value: addComposerOnRouteHook,
55
55
  writable: false,
56
- configurable: false,
56
+ configurable: false
57
57
  })
58
58
  }
59
59
 
60
- module.exports = fp(composeOpenAPI)
60
+ export const composerHook = fp(composerHookPlugin)