@platformatic/composer 3.0.0-alpha.4 → 3.0.0-alpha.6
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.
- package/LICENSE +1 -1
- package/eslint.config.js +1 -8
- package/index.d.ts +1 -58
- package/index.js +9 -30
- package/package.json +8 -54
- package/schema.json +1136 -907
- package/scripts/schema.js +12 -0
- package/config.d.ts +0 -997
- package/lib/application.js +0 -186
- package/lib/commands/index.js +0 -15
- package/lib/commands/openapi-fetch-schemas.js +0 -47
- package/lib/composer-hook.js +0 -60
- package/lib/errors.js +0 -18
- package/lib/generator.js +0 -127
- package/lib/graphql-fetch.js +0 -83
- package/lib/graphql-generator.js +0 -33
- package/lib/graphql.js +0 -24
- package/lib/metrics.js +0 -12
- package/lib/not-host-constraints.js +0 -31
- package/lib/openapi-composer.js +0 -101
- package/lib/openapi-config-schema.js +0 -89
- package/lib/openapi-generator.js +0 -213
- package/lib/openapi-load-config.js +0 -31
- package/lib/openapi-modifier.js +0 -128
- package/lib/openapi-scalar.js +0 -22
- package/lib/proxy.js +0 -265
- package/lib/root.js +0 -75
- package/lib/schema.js +0 -258
- package/lib/stackable.js +0 -88
- package/lib/upgrade.js +0 -20
- package/lib/utils.js +0 -16
- package/lib/versions/2.0.0.js +0 -9
- package/lib/versions/3.0.0.js +0 -14
- package/public/images/dark_mode.svg +0 -3
- package/public/images/ellipse.svg +0 -21
- package/public/images/external-link.svg +0 -5
- package/public/images/favicon.ico +0 -0
- package/public/images/graphiql.svg +0 -10
- package/public/images/graphql.svg +0 -10
- package/public/images/light_mode.svg +0 -11
- package/public/images/openapi.svg +0 -13
- package/public/images/platformatic-logo-dark.svg +0 -30
- package/public/images/platformatic-logo-light.svg +0 -30
- package/public/images/reverse-proxy.svg +0 -8
- package/public/images/triangle_dark.svg +0 -3
- package/public/images/triangle_light.svg +0 -3
- package/public/index.html +0 -253
- package/public/index.njk +0 -101
- package/public/main.css +0 -244
package/lib/application.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { isKeyEnabled } from '@platformatic/foundation'
|
|
2
|
-
import { platformaticService } from '@platformatic/service'
|
|
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
|
package/lib/commands/index.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { loadConfiguration } from '@platformatic/foundation'
|
|
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
|
-
}
|
package/lib/composer-hook.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin'
|
|
2
|
-
import rfdc from 'rfdc'
|
|
3
|
-
import { FastifyInstanceIsAlreadyListeningError } from './errors.js'
|
|
4
|
-
|
|
5
|
-
const deepClone = rfdc()
|
|
6
|
-
|
|
7
|
-
async function composerHookPlugin (app) {
|
|
8
|
-
const onRoutesHooks = {}
|
|
9
|
-
|
|
10
|
-
app.addHook('onRoute', routeOptions => {
|
|
11
|
-
if (routeOptions.schema) {
|
|
12
|
-
routeOptions.schema = deepClone(routeOptions.schema)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const method = routeOptions.method
|
|
16
|
-
const openApiPath = routeOptions.config?.openApiPath
|
|
17
|
-
|
|
18
|
-
const onRouteHooks = onRoutesHooks[openApiPath]?.[method]
|
|
19
|
-
if (Array.isArray(onRouteHooks)) {
|
|
20
|
-
for (const onRouteHook of onRouteHooks) {
|
|
21
|
-
onRouteHook(routeOptions)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
let isApplicationReady = false
|
|
27
|
-
app.addHook('onReady', () => {
|
|
28
|
-
isApplicationReady = true
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
function addComposerOnRouteHook (openApiPath, methods, hook) {
|
|
32
|
-
/* c8 ignore next 5 */
|
|
33
|
-
if (isApplicationReady) {
|
|
34
|
-
throw new FastifyInstanceIsAlreadyListeningError()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (onRoutesHooks[openApiPath] === undefined) {
|
|
38
|
-
onRoutesHooks[openApiPath] = {}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const routeHooks = onRoutesHooks[openApiPath]
|
|
42
|
-
|
|
43
|
-
for (let method of methods) {
|
|
44
|
-
method = method.toUpperCase()
|
|
45
|
-
|
|
46
|
-
if (routeHooks[method] === undefined) {
|
|
47
|
-
routeHooks[method] = []
|
|
48
|
-
}
|
|
49
|
-
routeHooks[method].push(hook)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
Object.defineProperty(app.platformatic, 'addComposerOnRouteHook', {
|
|
54
|
-
value: addComposerOnRouteHook,
|
|
55
|
-
writable: false,
|
|
56
|
-
configurable: false
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const composerHook = fp(composerHookPlugin)
|
package/lib/errors.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import createError from '@fastify/error'
|
|
2
|
-
|
|
3
|
-
const ERROR_PREFIX = 'PLT_COMPOSER'
|
|
4
|
-
|
|
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
|
-
)
|
package/lib/generator.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
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
|
-
}
|
package/lib/graphql-fetch.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { compose } from '@platformatic/graphql-composer'
|
|
2
|
-
|
|
3
|
-
const placeholderSdl = 'Query { _info: String }'
|
|
4
|
-
const placeholderResolvers = { Query: { _info: '@platformatic/composer' } }
|
|
5
|
-
|
|
6
|
-
// TODO support subscriptions
|
|
7
|
-
// const defaultSubscriptionsOptions = {
|
|
8
|
-
// onError: function onComposerSubscriptionsError (ctx, topic, err) {
|
|
9
|
-
// // TODO log.error({err})
|
|
10
|
-
// throw err
|
|
11
|
-
// },
|
|
12
|
-
// publish (ctx, topic, payload) {
|
|
13
|
-
// ctx.pubsub.publish({ topic, payload })
|
|
14
|
-
// },
|
|
15
|
-
// subscribe (ctx, topic) {
|
|
16
|
-
// return ctx.pubsub.subscribe(topic)
|
|
17
|
-
// },
|
|
18
|
-
// unsubscribe (ctx, topic) {
|
|
19
|
-
// ctx.pubsub.close()
|
|
20
|
-
// }
|
|
21
|
-
// }
|
|
22
|
-
|
|
23
|
-
function toComposerOptions (options, app) {
|
|
24
|
-
return {
|
|
25
|
-
logger: app.log,
|
|
26
|
-
defaultArgsAdapter: options?.defaultArgsAdapter,
|
|
27
|
-
addEntitiesResolvers: options?.addEntitiesResolvers,
|
|
28
|
-
entities: options?.entities,
|
|
29
|
-
onSubgraphError: (err, subgraphName) => {
|
|
30
|
-
app.log.error({ err }, 'graphql composer error on subgraph ' + subgraphName)
|
|
31
|
-
|
|
32
|
-
if (options?.onSubgraphError) {
|
|
33
|
-
try {
|
|
34
|
-
options.onSubgraphError(err, subgraphName)
|
|
35
|
-
} catch (err) {
|
|
36
|
-
app.log.error({ err }, 'running onSubgraphError')
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
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
|
-
}
|
package/lib/graphql-generator.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin'
|
|
2
|
-
import mercurius from 'mercurius'
|
|
3
|
-
import { fetchGraphqlSubgraphs } from './graphql-fetch.js'
|
|
4
|
-
|
|
5
|
-
async function graphqlGeneratorPlugin (app, opts) {
|
|
6
|
-
if (!opts.services.some(s => s.graphql)) {
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const services = []
|
|
11
|
-
|
|
12
|
-
for (const service of opts.services) {
|
|
13
|
-
if (!service.graphql) {
|
|
14
|
-
continue
|
|
15
|
-
}
|
|
16
|
-
services.push(service)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const graphqlConfig = {
|
|
20
|
-
graphiql: opts.graphql?.graphiql
|
|
21
|
-
}
|
|
22
|
-
if (services.length > 0) {
|
|
23
|
-
const graphqlSupergraph = await fetchGraphqlSubgraphs(services, opts.graphql, app)
|
|
24
|
-
graphqlConfig.schema = graphqlSupergraph.sdl
|
|
25
|
-
graphqlConfig.resolvers = graphqlSupergraph.resolvers
|
|
26
|
-
graphqlConfig.subscription = false // TODO support subscriptions, will be !!opts.graphql.subscriptions
|
|
27
|
-
app.graphqlSupergraph = graphqlSupergraph
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
await app.register(mercurius, graphqlConfig)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const graphqlGenerator = fp(graphqlGeneratorPlugin)
|
package/lib/graphql.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin'
|
|
2
|
-
import { createSupergraph } from './graphql-fetch.js'
|
|
3
|
-
|
|
4
|
-
const graphqlSupergraphSymbol = Symbol('graphqlSupergraph')
|
|
5
|
-
|
|
6
|
-
export async function graphqlPlugin (app, opts) {
|
|
7
|
-
app.decorate('graphqlSupergraph', {
|
|
8
|
-
getter () {
|
|
9
|
-
return this[graphqlSupergraphSymbol]
|
|
10
|
-
},
|
|
11
|
-
setter (v) {
|
|
12
|
-
this[graphqlSupergraphSymbol] = v
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
app.decorate('graphqlComposerOptions', {
|
|
16
|
-
getter () {
|
|
17
|
-
return opts
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
app.graphqlSupergraph = createSupergraph()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const graphql = fp(graphqlPlugin)
|
package/lib/metrics.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function initMetrics (prometheus) {
|
|
2
|
-
if (!prometheus?.registry || !prometheus?.client) return null
|
|
3
|
-
const { client, registry } = prometheus
|
|
4
|
-
|
|
5
|
-
return {
|
|
6
|
-
activeWsConnections: new client.Gauge({
|
|
7
|
-
name: 'active_ws_composer_connections',
|
|
8
|
-
help: 'Active Websocket composer connections in "@platformatic/composer"',
|
|
9
|
-
registers: [registry]
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export const notHostConstraints = {
|
|
2
|
-
name: 'notHost',
|
|
3
|
-
storage () {
|
|
4
|
-
const store = []
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
get (host) {
|
|
8
|
-
if (typeof host === 'string') {
|
|
9
|
-
for (const [hosts, value] of store) {
|
|
10
|
-
if (!hosts.includes(host)) {
|
|
11
|
-
return value
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return null
|
|
17
|
-
},
|
|
18
|
-
set: (hosts, value) => {
|
|
19
|
-
store.push([hosts, value])
|
|
20
|
-
},
|
|
21
|
-
store
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
deriveConstraint (req) {
|
|
25
|
-
return req.headers.host || req.headers[':authority']
|
|
26
|
-
},
|
|
27
|
-
mustMatchWhenDerived: false,
|
|
28
|
-
validate () {
|
|
29
|
-
return true
|
|
30
|
-
}
|
|
31
|
-
}
|