@platformatic/composer 2.74.3 → 3.0.0-alpha.2

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 +2 -30
  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 +127 -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 +13 -12
  22. package/lib/{root-endpoint/index.js → root.js} +12 -12
  23. package/lib/schema.js +41 -25
  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/lib/versions/3.0.0.js +14 -0
  29. package/package.json +15 -18
  30. package/schema.json +8 -153
  31. package/.c8rc +0 -6
  32. package/composer.mjs +0 -54
  33. package/help/create.txt +0 -11
  34. package/help/help.txt +0 -7
  35. package/help/openapi schemas fetch.txt +0 -9
  36. package/help/start.txt +0 -54
  37. package/index.test-d.ts +0 -23
  38. package/lib/create.mjs +0 -84
  39. package/lib/generator/README.md +0 -30
  40. package/lib/generator/composer-generator.d.ts +0 -11
  41. package/lib/generator/composer-generator.js +0 -128
  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/lib/errors.js CHANGED
@@ -1,13 +1,18 @@
1
- 'use strict'
2
-
3
- const createError = require('@fastify/error')
1
+ import createError from '@fastify/error'
4
2
 
5
3
  const ERROR_PREFIX = 'PLT_COMPOSER'
6
4
 
7
- module.exports = {
8
- FastifyInstanceIsAlreadyListeningError: createError(`${ERROR_PREFIX}_FASTIFY_INSTANCE_IS_ALREADY_LISTENING`, 'Fastify instance is already listening. Cannot call "addComposerOnRouteHook"!'),
9
- FailedToFetchOpenAPISchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_FETCH_OPENAPI_SCHEMA`, 'Failed to fetch OpenAPI schema from %s'),
10
- ValidationErrors: createError(`${ERROR_PREFIX}_VALIDATION_ERRORS`, 'Validation errors: %s'),
11
- PathAlreadyExistsError: createError(`${ERROR_PREFIX}_PATH_ALREADY_EXISTS`, 'Path "%s" already exists'),
12
- CouldNotReadOpenAPIConfigError: createError(`${ERROR_PREFIX}_COULD_NOT_READ_OPENAPI_CONFIG`, 'Could not read openapi config for "%s" service'),
13
- }
5
+ export const FastifyInstanceIsAlreadyListeningError = createError(
6
+ `${ERROR_PREFIX}_FASTIFY_INSTANCE_IS_ALREADY_LISTENING`,
7
+ 'Fastify instance is already listening. Cannot call "addComposerOnRouteHook"!'
8
+ )
9
+ export const FailedToFetchOpenAPISchemaError = createError(
10
+ `${ERROR_PREFIX}_FAILED_TO_FETCH_OPENAPI_SCHEMA`,
11
+ 'Failed to fetch OpenAPI schema from %s'
12
+ )
13
+ export const ValidationErrors = createError(`${ERROR_PREFIX}_VALIDATION_ERRORS`, 'Validation errors: %s')
14
+ export const PathAlreadyExistsError = createError(`${ERROR_PREFIX}_PATH_ALREADY_EXISTS`, 'Path "%s" already exists')
15
+ export const CouldNotReadOpenAPIConfigError = createError(
16
+ `${ERROR_PREFIX}_COULD_NOT_READ_OPENAPI_CONFIG`,
17
+ 'Could not read openapi config for "%s" service'
18
+ )
@@ -0,0 +1,127 @@
1
+ import { Generator as ServiceGenerator } from '@platformatic/service'
2
+
3
+ export class Generator extends ServiceGenerator {
4
+ constructor (opts) {
5
+ super({
6
+ ...opts,
7
+ module: '@platformatic/composer'
8
+ })
9
+ }
10
+
11
+ getDefaultConfig () {
12
+ const defaultBaseConfig = super.getDefaultConfig()
13
+
14
+ return {
15
+ ...defaultBaseConfig,
16
+ plugin: false,
17
+ routes: false,
18
+ tests: false
19
+ }
20
+ }
21
+
22
+ async _beforePrepare () {
23
+ if (this.config.isUpdating) {
24
+ return
25
+ }
26
+
27
+ await super._beforePrepare()
28
+
29
+ this.addEnvVars(
30
+ {
31
+ PLT_EXAMPLE_ORIGIN: 'http://127.0.0.1:3043'
32
+ },
33
+ { overwrite: false, default: true }
34
+ )
35
+
36
+ this.config.dependencies = {
37
+ '@platformatic/composer': `^${this.platformaticVersion}`
38
+ }
39
+ }
40
+
41
+ async _afterPrepare () {
42
+ if (this.config.isUpdating) {
43
+ return
44
+ }
45
+
46
+ await super._afterPrepare()
47
+ const PLT_ENVIRONMENT_TEMPLATE = `
48
+ import { type FastifyInstance } from 'fastify'
49
+ import { PlatformaticApplication, PlatformaticComposerConfig } from '@platformatic/composer'
50
+
51
+ declare module 'fastify' {
52
+ interface FastifyInstance {
53
+ platformatic: PlatformaticApplication<PlatformaticComposerConfig>
54
+ }
55
+ }
56
+ `
57
+
58
+ const README = `
59
+ # Platformatic Composer API
60
+
61
+ This is a generated [Platformatic Composer](https://docs.platformatic.dev/docs/composer/overview) application.
62
+
63
+ ## Requirements
64
+
65
+ Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended).
66
+ You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0 or >= v20.6.0
67
+
68
+ ## Setup
69
+
70
+ 1. Install dependencies:
71
+
72
+ \`\`\`bash
73
+ npm install
74
+ \`\`\`
75
+
76
+ ## Usage
77
+
78
+ Run the API with:
79
+
80
+ \`\`\`bash
81
+ npm start
82
+ \`\`\`
83
+
84
+ ### Explore
85
+ - ⚡ The Platformatic Composer server is running at http://localhost:3042/
86
+ - 📔 View the REST API's Swagger documentation at http://localhost:3042/documentation/
87
+ `
88
+
89
+ this.addFile({ path: '', file: 'plt-env.d.ts', contents: PLT_ENVIRONMENT_TEMPLATE })
90
+ this.addFile({ path: '', file: 'README.md', contents: README })
91
+ }
92
+
93
+ async _getConfigFileContents () {
94
+ const config = await super._getConfigFileContents()
95
+ delete config.service
96
+ config.$schema = `https://schemas.platformatic.dev/@platformatic/composer/${this.platformaticVersion}.json`
97
+
98
+ config.composer = {
99
+ services: [
100
+ {
101
+ id: 'example',
102
+ origin: `{${this.getEnvVarName('PLT_EXAMPLE_ORIGIN')}}`,
103
+ openapi: {
104
+ url: '/documentation/json'
105
+ }
106
+ }
107
+ ],
108
+ refreshTimeout: 1000
109
+ }
110
+
111
+ if (this.runtime !== null) {
112
+ config.composer.services = this.runtime.services
113
+ .filter(serviceMeta => serviceMeta.service.module !== '@platformatic/composer')
114
+ .map(serviceMeta => {
115
+ return {
116
+ id: serviceMeta.name,
117
+ openapi: {
118
+ url: '/documentation/json',
119
+ prefix: `/${serviceMeta.name}`
120
+ }
121
+ }
122
+ })
123
+ }
124
+
125
+ return config
126
+ }
127
+ }
@@ -1,49 +1,7 @@
1
- 'use strict'
2
-
3
- const { compose } = require('@platformatic/graphql-composer')
1
+ import { compose } from '@platformatic/graphql-composer'
4
2
 
5
3
  const placeholderSdl = 'Query { _info: String }'
6
- const placeholderResolvers = { Query: { _info: 'platformatic composer' } }
7
-
8
- function createSupergraph ({ sdl = null, resolvers = {} } = {}) {
9
- // in case of temporary failures of subgraphs on watching, the service can restart if no subgraphs are (tempoary) available
10
- if (!sdl) {
11
- return {
12
- sdl: placeholderSdl,
13
- resolvers: placeholderResolvers,
14
- }
15
- }
16
- return { sdl, resolvers }
17
- }
18
-
19
- function isSameGraphqlSchema (a, b) {
20
- // TODO review
21
- return a?.sdl === b?.sdl
22
- }
23
-
24
- function serviceToSubgraphConfig (service) {
25
- if (!service.graphql) { return }
26
- return {
27
- name: service.graphql.name || service.id || service.origin,
28
- entities: service.graphql.entities,
29
- server: {
30
- host: service.graphql.host || service.origin,
31
- composeEndpoint: service.graphql.composeEndpoint,
32
- graphqlEndpoint: service.graphql.graphqlEndpoint,
33
- },
34
- }
35
- }
36
-
37
- async function fetchGraphqlSubgraphs (services, options, app) {
38
- const subgraphs = services.map(serviceToSubgraphConfig).filter(s => !!s)
39
- const composer = await compose({ ...toComposerOptions(options, app), subgraphs })
40
-
41
- return createSupergraph({
42
- logger: app.log,
43
- sdl: composer.toSdl(),
44
- resolvers: composer.resolvers,
45
- })
46
- }
4
+ const placeholderResolvers = { Query: { _info: '@platformatic/composer' } }
47
5
 
48
6
  // TODO support subscriptions
49
7
  // const defaultSubscriptionsOptions = {
@@ -78,8 +36,48 @@ function toComposerOptions (options, app) {
78
36
  app.log.error({ err }, 'running onSubgraphError')
79
37
  }
80
38
  }
81
- },
39
+ }
82
40
  }
83
41
  }
84
42
 
85
- module.exports = { fetchGraphqlSubgraphs, createSupergraph, isSameGraphqlSchema, serviceToSubgraphConfig }
43
+ export function createSupergraph ({ sdl = null, resolvers = {} } = {}) {
44
+ // in case of temporary failures of subgraphs on watching, the service can restart if no subgraphs are (tempoary) available
45
+ if (!sdl) {
46
+ return {
47
+ sdl: placeholderSdl,
48
+ resolvers: placeholderResolvers
49
+ }
50
+ }
51
+ return { sdl, resolvers }
52
+ }
53
+
54
+ export function isSameGraphqlSchema (a, b) {
55
+ // TODO review
56
+ return a?.sdl === b?.sdl
57
+ }
58
+
59
+ export function serviceToSubgraphConfig (service) {
60
+ if (!service.graphql) {
61
+ return
62
+ }
63
+ return {
64
+ name: service.graphql.name || service.id || service.origin,
65
+ entities: service.graphql.entities,
66
+ server: {
67
+ host: service.graphql.host || service.origin,
68
+ composeEndpoint: service.graphql.composeEndpoint,
69
+ graphqlEndpoint: service.graphql.graphqlEndpoint
70
+ }
71
+ }
72
+ }
73
+
74
+ export async function fetchGraphqlSubgraphs (services, options, app) {
75
+ const subgraphs = services.map(serviceToSubgraphConfig).filter(s => !!s)
76
+ const composer = await compose({ ...toComposerOptions(options, app), subgraphs })
77
+
78
+ return createSupergraph({
79
+ logger: app.log,
80
+ sdl: composer.toSdl(),
81
+ resolvers: composer.resolvers
82
+ })
83
+ }
@@ -1,21 +1,23 @@
1
- 'use strict'
1
+ import fp from 'fastify-plugin'
2
+ import mercurius from 'mercurius'
3
+ import { fetchGraphqlSubgraphs } from './graphql-fetch.js'
2
4
 
3
- const fp = require('fastify-plugin')
4
- const mercurius = require('mercurius')
5
- const { fetchGraphqlSubgraphs } = require('./graphql-fetch')
6
-
7
- async function composeGraphql (app, opts) {
8
- if (!opts.services.some(s => s.graphql)) { return }
5
+ async function graphqlGeneratorPlugin (app, opts) {
6
+ if (!opts.services.some(s => s.graphql)) {
7
+ return
8
+ }
9
9
 
10
10
  const services = []
11
11
 
12
12
  for (const service of opts.services) {
13
- if (!service.graphql) { continue }
13
+ if (!service.graphql) {
14
+ continue
15
+ }
14
16
  services.push(service)
15
17
  }
16
18
 
17
19
  const graphqlConfig = {
18
- graphiql: opts.graphql?.graphiql,
20
+ graphiql: opts.graphql?.graphiql
19
21
  }
20
22
  if (services.length > 0) {
21
23
  const graphqlSupergraph = await fetchGraphqlSubgraphs(services, opts.graphql, app)
@@ -28,4 +30,4 @@ async function composeGraphql (app, opts) {
28
30
  await app.register(mercurius, graphqlConfig)
29
31
  }
30
32
 
31
- module.exports = fp(composeGraphql)
33
+ export const graphqlGenerator = fp(graphqlGeneratorPlugin)
package/lib/graphql.js CHANGED
@@ -1,20 +1,24 @@
1
- 'use strict'
2
-
3
- const fp = require('fastify-plugin')
4
- const { createSupergraph } = require('./graphql-fetch')
1
+ import fp from 'fastify-plugin'
2
+ import { createSupergraph } from './graphql-fetch.js'
5
3
 
6
4
  const graphqlSupergraphSymbol = Symbol('graphqlSupergraph')
7
5
 
8
- async function composeGraphql (app, opts) {
6
+ export async function graphqlPlugin (app, opts) {
9
7
  app.decorate('graphqlSupergraph', {
10
- getter () { return this[graphqlSupergraphSymbol] },
11
- setter (v) { this[graphqlSupergraphSymbol] = v },
8
+ getter () {
9
+ return this[graphqlSupergraphSymbol]
10
+ },
11
+ setter (v) {
12
+ this[graphqlSupergraphSymbol] = v
13
+ }
12
14
  })
13
15
  app.decorate('graphqlComposerOptions', {
14
- getter () { return opts },
16
+ getter () {
17
+ return opts
18
+ }
15
19
  })
16
20
 
17
21
  app.graphqlSupergraph = createSupergraph()
18
22
  }
19
23
 
20
- module.exports = fp(composeGraphql)
24
+ export const graphql = fp(graphqlPlugin)
@@ -1,8 +1,6 @@
1
- 'use strict'
2
-
3
- module.exports = {
1
+ export const notHostConstraints = {
4
2
  name: 'notHost',
5
- storage: () => {
3
+ storage () {
6
4
  const store = []
7
5
 
8
6
  return {
@@ -23,7 +21,7 @@ module.exports = {
23
21
  store
24
22
  }
25
23
  },
26
- deriveConstraint: req => {
24
+ deriveConstraint (req) {
27
25
  return req.headers.host || req.headers[':authority']
28
26
  },
29
27
  mustMatchWhenDerived: false,
@@ -1,9 +1,40 @@
1
- 'use strict'
1
+ import rfdc from 'rfdc'
2
+ import { PathAlreadyExistsError } from './errors.js'
2
3
 
3
- const clone = require('rfdc')()
4
- const errors = require('./errors')
4
+ const clone = rfdc()
5
5
 
6
- function composeOpenApi (apis, options = {}) {
6
+ function generateOperationIdApiPrefix (operationId) {
7
+ return (
8
+ operationId
9
+ .trim()
10
+ .replace(/[^A-Z0-9]+/gi, '_')
11
+ .replace(/^_+|_+$/g, '') + '_'
12
+ )
13
+ }
14
+
15
+ function namespaceSchemaRefs (apiPrefix, schema) {
16
+ if (schema.$ref && schema.$ref.startsWith('#/components/schemas')) {
17
+ schema.$ref = schema.$ref.replace('#/components/schemas/', '#/components/schemas/' + apiPrefix)
18
+ }
19
+ for (const childSchema of Object.values(schema)) {
20
+ if (typeof childSchema === 'object') {
21
+ namespaceSchemaRefs(apiPrefix, childSchema)
22
+ }
23
+ }
24
+ }
25
+
26
+ function namespaceSchemaOperationIds (apiPrefix, schema) {
27
+ if (schema.operationId) {
28
+ schema.operationId = apiPrefix + schema.operationId
29
+ }
30
+ for (const childSchema of Object.values(schema)) {
31
+ if (typeof childSchema === 'object') {
32
+ namespaceSchemaOperationIds(apiPrefix, childSchema)
33
+ }
34
+ }
35
+ }
36
+
37
+ export function composeOpenApi (apis, options = {}) {
7
38
  const mergedPaths = {}
8
39
  const mergedSchemas = {}
9
40
  const mergedSecuritySchemes = {}
@@ -31,7 +62,7 @@ function composeOpenApi (apis, options = {}) {
31
62
  const mergedPath = prefix ? prefix + path : path
32
63
 
33
64
  if (mergedPaths[mergedPath]) {
34
- throw new errors.PathAlreadyExistsError(mergedPath)
65
+ throw new PathAlreadyExistsError(mergedPath)
35
66
  }
36
67
  mergedPaths[mergedPath] = pathSchema
37
68
  }
@@ -59,45 +90,12 @@ function composeOpenApi (apis, options = {}) {
59
90
  openapi: '3.0.0',
60
91
  info: {
61
92
  title: options.title || 'Platformatic Composer',
62
- version: options.version || '1.0.0',
93
+ version: options.version || '1.0.0'
63
94
  },
64
95
  components: {
65
96
  securitySchemes: mergedSecuritySchemes,
66
- schemas: mergedSchemas,
97
+ schemas: mergedSchemas
67
98
  },
68
- paths: mergedPaths,
99
+ paths: mergedPaths
69
100
  }
70
101
  }
71
-
72
- function generateOperationIdApiPrefix (operationId) {
73
- return operationId.trim()
74
- .replace(/[^A-Z0-9]+/ig, '_')
75
- .replace(/^_+|_+$/g, '') + '_'
76
- }
77
-
78
- function namespaceSchemaRefs (apiPrefix, schema) {
79
- if (schema.$ref && schema.$ref.startsWith('#/components/schemas')) {
80
- schema.$ref = schema.$ref.replace(
81
- '#/components/schemas/',
82
- '#/components/schemas/' + apiPrefix
83
- )
84
- }
85
- for (const childSchema of Object.values(schema)) {
86
- if (typeof childSchema === 'object') {
87
- namespaceSchemaRefs(apiPrefix, childSchema)
88
- }
89
- }
90
- }
91
-
92
- function namespaceSchemaOperationIds (apiPrefix, schema) {
93
- if (schema.operationId) {
94
- schema.operationId = apiPrefix + schema.operationId
95
- }
96
- for (const childSchema of Object.values(schema)) {
97
- if (typeof childSchema === 'object') {
98
- namespaceSchemaOperationIds(apiPrefix, childSchema)
99
- }
100
- }
101
- }
102
-
103
- module.exports = composeOpenApi
@@ -1,18 +1,16 @@
1
- 'use strict'
2
-
3
1
  const ignoreSchema = {
4
2
  type: 'object',
5
3
  properties: {
6
- ignore: { type: 'boolean' },
4
+ ignore: { type: 'boolean' }
7
5
  },
8
- additionalProperties: false,
6
+ additionalProperties: false
9
7
  }
10
8
 
11
9
  const aliasSchema = {
12
10
  type: 'object',
13
11
  properties: {
14
- alias: { type: 'string' },
15
- },
12
+ alias: { type: 'string' }
13
+ }
16
14
  }
17
15
 
18
16
  const jsonSchemaSchema = {
@@ -28,16 +26,16 @@ const jsonSchemaSchema = {
28
26
  {
29
27
  type: 'object',
30
28
  properties: {
31
- rename: { type: 'string' },
29
+ rename: { type: 'string' }
32
30
  },
33
- additionalProperties: false,
34
- },
35
- ],
36
- },
31
+ additionalProperties: false
32
+ }
33
+ ]
34
+ }
37
35
  },
38
- items: { $ref: 'json-schema' },
36
+ items: { $ref: 'json-schema' }
39
37
  },
40
- additionalProperties: false,
38
+ additionalProperties: false
41
39
  }
42
40
 
43
41
  const routeSchema = {
@@ -49,15 +47,15 @@ const routeSchema = {
49
47
  responses: {
50
48
  type: 'object',
51
49
  properties: {
52
- 200: { $ref: 'json-schema' },
53
- },
54
- },
55
- },
56
- },
57
- ],
50
+ 200: { $ref: 'json-schema' }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ]
58
56
  }
59
57
 
60
- const openApiConfigSchema = {
58
+ export const openApiConfigSchema = {
61
59
  type: 'object',
62
60
  properties: {
63
61
  paths: {
@@ -76,18 +74,16 @@ const openApiConfigSchema = {
76
74
  delete: routeSchema,
77
75
  options: routeSchema,
78
76
  head: routeSchema,
79
- trace: routeSchema,
77
+ trace: routeSchema
80
78
  },
81
- additionalProperties: false,
82
- },
83
- ],
84
- },
85
- },
79
+ additionalProperties: false
80
+ }
81
+ ]
82
+ }
83
+ }
86
84
  },
87
85
  additionalProperties: false,
88
86
  definitions: {
89
- 'json-schema': jsonSchemaSchema,
90
- },
87
+ 'json-schema': jsonSchemaSchema
88
+ }
91
89
  }
92
-
93
- module.exports = openApiConfigSchema