@platformatic/composer 3.0.0-alpha.5 → 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/proxy.js
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import httpProxy from '@fastify/http-proxy'
|
|
2
|
-
import { ensureLoggableError, loadModule } from '@platformatic/foundation'
|
|
3
|
-
import fp from 'fastify-plugin'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
5
|
-
import { workerData } from 'node:worker_threads'
|
|
6
|
-
import { getGlobalDispatcher } from 'undici'
|
|
7
|
-
import { initMetrics } from './metrics.js'
|
|
8
|
-
|
|
9
|
-
const kITC = Symbol.for('plt.runtime.itc')
|
|
10
|
-
const kProxyRoute = Symbol('plt.composer.proxy.route')
|
|
11
|
-
|
|
12
|
-
const urlPattern = /^https?:\/\//
|
|
13
|
-
|
|
14
|
-
async function resolveServiceProxyParameters (service) {
|
|
15
|
-
// Get meta information from the service, if any, to eventually hook up to a TCP port
|
|
16
|
-
const meta = (await globalThis[kITC]?.send('getServiceMeta', service.id))?.composer ?? { prefix: service.id }
|
|
17
|
-
|
|
18
|
-
// If no prefix could be found, assume the service id
|
|
19
|
-
let prefix = (service.proxy?.prefix ?? meta.prefix ?? service.id).replace(/(\/$)/g, '')
|
|
20
|
-
let rewritePrefix = ''
|
|
21
|
-
let internalRewriteLocationHeader = true
|
|
22
|
-
|
|
23
|
-
if (meta.wantsAbsoluteUrls) {
|
|
24
|
-
const basePath = workerData.config.basePath
|
|
25
|
-
|
|
26
|
-
// Strip the runtime basepath from the prefix when it comes from the service meta
|
|
27
|
-
if (basePath && !service.proxy?.prefix && prefix.startsWith(basePath)) {
|
|
28
|
-
prefix = prefix.substring(basePath.length)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// The rewritePrefix purposely ignores service.proxy?.prefix to let
|
|
32
|
-
// the service always being able to configure their value
|
|
33
|
-
rewritePrefix = meta.prefix ?? service.id
|
|
34
|
-
internalRewriteLocationHeader = false
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (service.proxy?.ws?.hooks) {
|
|
38
|
-
const hooks = await loadModule(createRequire(import.meta.filename), service.proxy.ws.hooks.path)
|
|
39
|
-
service.proxy.ws.hooks = hooks
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
origin: service.origin,
|
|
44
|
-
url: meta.url,
|
|
45
|
-
prefix,
|
|
46
|
-
rewritePrefix,
|
|
47
|
-
internalRewriteLocationHeader,
|
|
48
|
-
needsRootTrailingSlash: meta.needsRootTrailingSlash,
|
|
49
|
-
needsRefererBasedRedirect: meta.needsRefererBasedRedirect,
|
|
50
|
-
upstream: service.proxy?.upstream,
|
|
51
|
-
ws: service.proxy?.ws
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let metrics
|
|
56
|
-
|
|
57
|
-
async function proxyPlugin (app, opts) {
|
|
58
|
-
const meta = { proxies: {} }
|
|
59
|
-
const hostnameLessProxies = []
|
|
60
|
-
|
|
61
|
-
for (const service of opts.services) {
|
|
62
|
-
if (!service.proxy) {
|
|
63
|
-
// When a service defines no expose config at all
|
|
64
|
-
// we assume a proxy exposed with a prefix equals to its id or meta.prefix
|
|
65
|
-
if (service.proxy === false || service.openapi || service.graphql) {
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const parameters = await resolveServiceProxyParameters(service)
|
|
71
|
-
const {
|
|
72
|
-
prefix,
|
|
73
|
-
origin,
|
|
74
|
-
url,
|
|
75
|
-
rewritePrefix,
|
|
76
|
-
internalRewriteLocationHeader,
|
|
77
|
-
needsRootTrailingSlash,
|
|
78
|
-
needsRefererBasedRedirect,
|
|
79
|
-
ws
|
|
80
|
-
} = parameters
|
|
81
|
-
meta.proxies[service.id] = parameters
|
|
82
|
-
|
|
83
|
-
const basePath = `/${prefix ?? ''}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
|
|
84
|
-
const dispatcher = getGlobalDispatcher()
|
|
85
|
-
|
|
86
|
-
let preRewrite = null
|
|
87
|
-
|
|
88
|
-
if (needsRootTrailingSlash) {
|
|
89
|
-
preRewrite = function preRewrite (url) {
|
|
90
|
-
if (url === basePath) {
|
|
91
|
-
url += '/'
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return url
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/*
|
|
99
|
-
Some frontends, like Astro (https://github.com/withastro/astro/issues/11445)
|
|
100
|
-
generate invalid paths in development mode which ignore the basePath.
|
|
101
|
-
In that case we try to properly redirect the browser by trying to understand the prefix
|
|
102
|
-
from the Referer header.
|
|
103
|
-
*/
|
|
104
|
-
if (needsRefererBasedRedirect) {
|
|
105
|
-
app.addHook('preHandler', function refererBasedRedirectPreHandler (req, reply, done) {
|
|
106
|
-
// If the URL is already targeted to the service, do nothing
|
|
107
|
-
if (req.url.startsWith(basePath)) {
|
|
108
|
-
done()
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Use the referer to understand the desired intent
|
|
113
|
-
const referer = req.headers.referer
|
|
114
|
-
|
|
115
|
-
if (!referer) {
|
|
116
|
-
done()
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const path = new URL(referer).pathname
|
|
121
|
-
|
|
122
|
-
// If we have a match redirect
|
|
123
|
-
if (path.startsWith(basePath)) {
|
|
124
|
-
reply.redirect(`${basePath}${req.url}`, 308)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
done()
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Do not show proxied services in Swagger
|
|
132
|
-
if (!service.openapi) {
|
|
133
|
-
app.addHook('onRoute', routeOptions => {
|
|
134
|
-
if (routeOptions.config?.[kProxyRoute] && routeOptions.url.startsWith(basePath)) {
|
|
135
|
-
routeOptions.schema ??= {}
|
|
136
|
-
routeOptions.schema.hide = true
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const toReplace = url
|
|
142
|
-
? new RegExp(
|
|
143
|
-
url
|
|
144
|
-
.replace(/127\.0\.0\.1/, 'localhost')
|
|
145
|
-
.replace(/\[::\]/, 'localhost')
|
|
146
|
-
.replace('http://', 'https?://')
|
|
147
|
-
)
|
|
148
|
-
: null
|
|
149
|
-
|
|
150
|
-
if (!metrics) {
|
|
151
|
-
metrics = initMetrics(globalThis.platformatic?.prometheus)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const proxyOptions = {
|
|
155
|
-
prefix,
|
|
156
|
-
rewritePrefix,
|
|
157
|
-
upstream: service.proxy?.upstream ?? origin,
|
|
158
|
-
preRewrite,
|
|
159
|
-
|
|
160
|
-
websocket: true,
|
|
161
|
-
wsUpstream: ws?.upstream ?? url ?? origin,
|
|
162
|
-
wsReconnect: ws?.reconnect,
|
|
163
|
-
wsHooks: {
|
|
164
|
-
onConnect: (...args) => {
|
|
165
|
-
metrics?.activeWsConnections?.inc()
|
|
166
|
-
ws?.hooks?.onConnect(...args)
|
|
167
|
-
},
|
|
168
|
-
onDisconnect: (...args) => {
|
|
169
|
-
metrics?.activeWsConnections?.dec()
|
|
170
|
-
ws?.hooks?.onDisconnect(...args)
|
|
171
|
-
},
|
|
172
|
-
onReconnect: ws?.hooks?.onReconnect,
|
|
173
|
-
onPong: ws?.hooks?.onPong,
|
|
174
|
-
onIncomingMessage: ws?.hooks?.onIncomingMessage,
|
|
175
|
-
onOutgoingMessage: ws?.hooks?.onOutgoingMessage
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
undici: dispatcher,
|
|
179
|
-
destroyAgent: false,
|
|
180
|
-
config: {
|
|
181
|
-
[kProxyRoute]: true
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
internalRewriteLocationHeader: false,
|
|
185
|
-
replyOptions: {
|
|
186
|
-
rewriteHeaders: headers => {
|
|
187
|
-
let location = headers.location
|
|
188
|
-
if (location) {
|
|
189
|
-
if (toReplace) {
|
|
190
|
-
location = location.replace(toReplace, '')
|
|
191
|
-
}
|
|
192
|
-
if (!urlPattern.test(location) && internalRewriteLocationHeader) {
|
|
193
|
-
location = location.replace(rewritePrefix, prefix)
|
|
194
|
-
}
|
|
195
|
-
headers.location = location
|
|
196
|
-
}
|
|
197
|
-
return headers
|
|
198
|
-
},
|
|
199
|
-
rewriteRequestHeaders: (request, headers) => {
|
|
200
|
-
const targetUrl = `${origin}${request.url}`
|
|
201
|
-
const context = request.span?.context
|
|
202
|
-
const { span, telemetryHeaders } = app.openTelemetry?.startHTTPSpanClient(
|
|
203
|
-
targetUrl,
|
|
204
|
-
request.method,
|
|
205
|
-
context
|
|
206
|
-
) || { span: null, telemetryHeaders: {} }
|
|
207
|
-
// We need to store the span in a different object
|
|
208
|
-
// to correctly close it in the onResponse hook
|
|
209
|
-
// Note that we have 2 spans:
|
|
210
|
-
// - request.span: the span of the request to the proxy
|
|
211
|
-
// - request.proxedCallSpan: the span of the request to the proxied service
|
|
212
|
-
request.proxedCallSpan = span
|
|
213
|
-
|
|
214
|
-
headers = {
|
|
215
|
-
...headers,
|
|
216
|
-
...telemetryHeaders,
|
|
217
|
-
'x-forwarded-for': request.ip,
|
|
218
|
-
'x-forwarded-host': request.host,
|
|
219
|
-
'x-forwarded-proto': request.protocol
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
request.log.trace({ headers }, 'rewritten headers before proxying')
|
|
223
|
-
|
|
224
|
-
return headers
|
|
225
|
-
},
|
|
226
|
-
onResponse: (_, reply, res) => {
|
|
227
|
-
app.openTelemetry?.endHTTPSpanClient(reply.request.proxedCallSpan, {
|
|
228
|
-
statusCode: reply.statusCode,
|
|
229
|
-
headers: res.headers
|
|
230
|
-
})
|
|
231
|
-
reply.send(res.stream)
|
|
232
|
-
},
|
|
233
|
-
onError: (reply, { error }) => {
|
|
234
|
-
app.log.error({ error: ensureLoggableError(error) }, 'Error while proxying request to another service')
|
|
235
|
-
return reply.send(error)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
hostnameLessProxies.push(proxyOptions)
|
|
241
|
-
|
|
242
|
-
const host = service.proxy?.hostname
|
|
243
|
-
|
|
244
|
-
if (host) {
|
|
245
|
-
await app.register(httpProxy, {
|
|
246
|
-
...proxyOptions,
|
|
247
|
-
prefix: '/',
|
|
248
|
-
constraints: { host }
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const hostnames = opts.services.map(s => s.proxy?.hostname).filter(Boolean)
|
|
254
|
-
for (const options of hostnameLessProxies) {
|
|
255
|
-
if (hostnames.length > 0) {
|
|
256
|
-
options.constraints = { notHost: hostnames }
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
await app.register(httpProxy, options)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
opts.stackable?.registerMeta(meta)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export const proxy = fp(proxyPlugin)
|
package/lib/root.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import fastifyStatic from '@fastify/static'
|
|
2
|
-
import fastifyView from '@fastify/view'
|
|
3
|
-
import userAgentParser from 'my-ua-parser'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import nunjucks from 'nunjucks'
|
|
6
|
-
|
|
7
|
-
export default function root (app) {
|
|
8
|
-
app.register(fastifyStatic, {
|
|
9
|
-
root: join(import.meta.dirname, '../public')
|
|
10
|
-
})
|
|
11
|
-
app.register(fastifyView, {
|
|
12
|
-
engine: {
|
|
13
|
-
nunjucks
|
|
14
|
-
},
|
|
15
|
-
root: join(import.meta.dirname, '../public')
|
|
16
|
-
})
|
|
17
|
-
// root endpoint
|
|
18
|
-
app.route({
|
|
19
|
-
method: 'GET',
|
|
20
|
-
path: '/',
|
|
21
|
-
schema: { hide: true },
|
|
22
|
-
handler: async (req, reply) => {
|
|
23
|
-
const uaString = req.headers['user-agent']
|
|
24
|
-
let hasOpenAPIServices = false
|
|
25
|
-
let hasGraphQLServices = false
|
|
26
|
-
if (uaString) {
|
|
27
|
-
const parsed = userAgentParser(uaString)
|
|
28
|
-
if (parsed.browser.name !== undefined) {
|
|
29
|
-
const serviceTypes = {
|
|
30
|
-
proxy: {
|
|
31
|
-
title: 'Reverse Proxy',
|
|
32
|
-
icon: './images/reverse-proxy.svg',
|
|
33
|
-
services: []
|
|
34
|
-
},
|
|
35
|
-
openapi: {
|
|
36
|
-
title: 'OpenAPI',
|
|
37
|
-
icon: './images/openapi.svg',
|
|
38
|
-
services: []
|
|
39
|
-
},
|
|
40
|
-
graphql: {
|
|
41
|
-
title: 'GraphQL',
|
|
42
|
-
icon: './images/graphql.svg',
|
|
43
|
-
services: []
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
app.platformatic.config.composer.services.forEach(s => {
|
|
48
|
-
if (s.openapi) {
|
|
49
|
-
hasOpenAPIServices = true
|
|
50
|
-
serviceTypes.openapi.services.push(s)
|
|
51
|
-
}
|
|
52
|
-
if (s.graphql) {
|
|
53
|
-
hasGraphQLServices = true
|
|
54
|
-
serviceTypes.graphql.services.push(s)
|
|
55
|
-
}
|
|
56
|
-
if (s.proxy) {
|
|
57
|
-
serviceTypes.proxy.services.push({
|
|
58
|
-
...s,
|
|
59
|
-
externalLink: `${s.proxy.prefix}/`
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
return reply.view('index.njk', {
|
|
65
|
-
hasGraphQLServices,
|
|
66
|
-
hasOpenAPIServices,
|
|
67
|
-
services: serviceTypes
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// Load services
|
|
72
|
-
return { message: 'Welcome to Platformatic! Please visit https://docs.platformatic.dev' }
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
}
|
package/lib/schema.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
|
|
4
|
-
import {
|
|
5
|
-
fastifyServer as server,
|
|
6
|
-
schemaComponents as utilsSchemaComponents,
|
|
7
|
-
watch,
|
|
8
|
-
wrappedRuntime
|
|
9
|
-
} from '@platformatic/foundation'
|
|
10
|
-
import { schemaComponents as serviceSchemaComponents } from '@platformatic/service'
|
|
11
|
-
import { readFileSync } from 'node:fs'
|
|
12
|
-
import { resolve } from 'node:path'
|
|
13
|
-
|
|
14
|
-
const { $defs, graphqlBase, openApiBase, plugins } = serviceSchemaComponents
|
|
15
|
-
|
|
16
|
-
export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
|
|
17
|
-
export const version = packageJson.version
|
|
18
|
-
|
|
19
|
-
export const openApiService = {
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
url: { type: 'string' },
|
|
23
|
-
file: { type: 'string', resolvePath: true },
|
|
24
|
-
prefix: { type: 'string' },
|
|
25
|
-
config: { type: 'string', resolvePath: true }
|
|
26
|
-
},
|
|
27
|
-
anyOf: [{ required: ['url'] }, { required: ['file'] }],
|
|
28
|
-
additionalProperties: false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const entityResolver = {
|
|
32
|
-
type: 'object',
|
|
33
|
-
properties: {
|
|
34
|
-
name: { type: 'string' },
|
|
35
|
-
argsAdapter: {
|
|
36
|
-
anyOf: [{ typeof: 'function' }, { type: 'string' }]
|
|
37
|
-
},
|
|
38
|
-
partialResults: {
|
|
39
|
-
anyOf: [{ typeof: 'function' }, { type: 'string' }]
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
required: ['name'],
|
|
43
|
-
additionalProperties: false
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const entities = {
|
|
47
|
-
type: 'object',
|
|
48
|
-
patternProperties: {
|
|
49
|
-
'^.*$': {
|
|
50
|
-
type: 'object',
|
|
51
|
-
properties: {
|
|
52
|
-
pkey: { type: 'string' },
|
|
53
|
-
resolver: entityResolver,
|
|
54
|
-
fkeys: {
|
|
55
|
-
type: 'array',
|
|
56
|
-
items: {
|
|
57
|
-
type: 'object',
|
|
58
|
-
properties: {
|
|
59
|
-
type: { type: 'string' },
|
|
60
|
-
field: { type: 'string' },
|
|
61
|
-
as: { type: 'string' },
|
|
62
|
-
pkey: { type: 'string' },
|
|
63
|
-
subgraph: { type: 'string' },
|
|
64
|
-
resolver: entityResolver
|
|
65
|
-
},
|
|
66
|
-
required: ['type']
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
many: {
|
|
70
|
-
type: 'array',
|
|
71
|
-
items: {
|
|
72
|
-
type: 'object',
|
|
73
|
-
properties: {
|
|
74
|
-
type: { type: 'string' },
|
|
75
|
-
fkey: { type: 'string' },
|
|
76
|
-
as: { type: 'string' },
|
|
77
|
-
pkey: { type: 'string' },
|
|
78
|
-
subgraph: { type: 'string' },
|
|
79
|
-
resolver: entityResolver
|
|
80
|
-
},
|
|
81
|
-
required: ['type', 'fkey', 'resolver']
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const graphqlService = {
|
|
90
|
-
anyOf: [
|
|
91
|
-
{ type: 'boolean' },
|
|
92
|
-
{
|
|
93
|
-
type: 'object',
|
|
94
|
-
properties: {
|
|
95
|
-
host: { type: 'string' },
|
|
96
|
-
name: { type: 'string' },
|
|
97
|
-
graphqlEndpoint: { type: 'string', default: '/graphql' },
|
|
98
|
-
composeEndpoint: { type: 'string', default: '/.well-known/graphql-composition' },
|
|
99
|
-
entities
|
|
100
|
-
},
|
|
101
|
-
additionalProperties: false
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export const graphqlComposerOptions = {
|
|
107
|
-
type: 'object',
|
|
108
|
-
properties: {
|
|
109
|
-
...graphqlBase.properties,
|
|
110
|
-
// TODO support subscriptions, subscriptions: { type: 'boolean', default: false },
|
|
111
|
-
onSubgraphError: { typeof: 'function' },
|
|
112
|
-
defaultArgsAdapter: {
|
|
113
|
-
oneOf: [{ typeof: 'function' }, { type: 'string' }]
|
|
114
|
-
},
|
|
115
|
-
entities,
|
|
116
|
-
addEntitiesResolvers: { type: 'boolean', default: false }
|
|
117
|
-
},
|
|
118
|
-
additionalProperties: false
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export const composer = {
|
|
122
|
-
type: 'object',
|
|
123
|
-
properties: {
|
|
124
|
-
services: {
|
|
125
|
-
type: 'array',
|
|
126
|
-
items: {
|
|
127
|
-
type: 'object',
|
|
128
|
-
properties: {
|
|
129
|
-
id: { type: 'string' },
|
|
130
|
-
origin: { type: 'string' },
|
|
131
|
-
openapi: openApiService,
|
|
132
|
-
graphql: graphqlService,
|
|
133
|
-
proxy: {
|
|
134
|
-
anyOf: [
|
|
135
|
-
{ type: 'boolean', const: false },
|
|
136
|
-
{
|
|
137
|
-
type: 'object',
|
|
138
|
-
properties: {
|
|
139
|
-
upstream: { type: 'string' },
|
|
140
|
-
prefix: { type: 'string' },
|
|
141
|
-
hostname: { type: 'string' },
|
|
142
|
-
ws: {
|
|
143
|
-
type: 'object',
|
|
144
|
-
properties: {
|
|
145
|
-
upstream: { type: 'string' },
|
|
146
|
-
reconnect: {
|
|
147
|
-
type: 'object',
|
|
148
|
-
properties: {
|
|
149
|
-
pingInterval: { type: 'number' },
|
|
150
|
-
maxReconnectionRetries: { type: 'number' },
|
|
151
|
-
reconnectInterval: { type: 'number' },
|
|
152
|
-
reconnectDecay: { type: 'number' },
|
|
153
|
-
connectionTimeout: { type: 'number' },
|
|
154
|
-
reconnectOnClose: { type: 'boolean' },
|
|
155
|
-
logs: { type: 'boolean' }
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
hooks: {
|
|
159
|
-
type: 'object',
|
|
160
|
-
properties: {
|
|
161
|
-
path: { type: 'string' }
|
|
162
|
-
},
|
|
163
|
-
required: ['path'],
|
|
164
|
-
additionalProperties: false
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
required: [],
|
|
168
|
-
additionalProperties: false
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
required: [],
|
|
172
|
-
additionalProperties: false
|
|
173
|
-
}
|
|
174
|
-
]
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
required: ['id'],
|
|
178
|
-
additionalProperties: false
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
openapi: openApiBase,
|
|
182
|
-
graphql: graphqlComposerOptions,
|
|
183
|
-
addEmptySchema: { type: 'boolean', default: false },
|
|
184
|
-
refreshTimeout: { type: 'integer', minimum: 0, default: 1000 }
|
|
185
|
-
},
|
|
186
|
-
required: [],
|
|
187
|
-
default: {},
|
|
188
|
-
additionalProperties: false
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export const types = {
|
|
192
|
-
type: 'object',
|
|
193
|
-
properties: {
|
|
194
|
-
autogenerate: {
|
|
195
|
-
type: 'boolean'
|
|
196
|
-
},
|
|
197
|
-
dir: {
|
|
198
|
-
description: 'The path to the directory the types should be generated in.',
|
|
199
|
-
type: 'string',
|
|
200
|
-
default: 'types',
|
|
201
|
-
resolvePath: true
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
additionalProperties: false
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export const schemaComponents = {
|
|
208
|
-
openApiService,
|
|
209
|
-
entityResolver,
|
|
210
|
-
entities,
|
|
211
|
-
graphqlService,
|
|
212
|
-
graphqlComposerOptions,
|
|
213
|
-
composer,
|
|
214
|
-
types
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export const schema = {
|
|
218
|
-
$id: `https://schemas.platformatic.dev/@platformatic/composer/${packageJson.version}.json`,
|
|
219
|
-
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
220
|
-
title: 'Platformatic Composer Config',
|
|
221
|
-
type: 'object',
|
|
222
|
-
properties: {
|
|
223
|
-
basePath: {
|
|
224
|
-
type: 'string'
|
|
225
|
-
},
|
|
226
|
-
server,
|
|
227
|
-
composer,
|
|
228
|
-
types,
|
|
229
|
-
plugins,
|
|
230
|
-
application: basicSchemaComponents.application,
|
|
231
|
-
runtime: wrappedRuntime,
|
|
232
|
-
telemetry: utilsSchemaComponents.telemetry,
|
|
233
|
-
watch: {
|
|
234
|
-
anyOf: [
|
|
235
|
-
watch,
|
|
236
|
-
{
|
|
237
|
-
type: 'boolean'
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
type: 'string'
|
|
241
|
-
}
|
|
242
|
-
]
|
|
243
|
-
},
|
|
244
|
-
$schema: {
|
|
245
|
-
type: 'string'
|
|
246
|
-
},
|
|
247
|
-
module: {
|
|
248
|
-
type: 'string'
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
additionalProperties: false,
|
|
252
|
-
$defs
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/* c8 ignore next 3 */
|
|
256
|
-
if (process.argv[1] === import.meta.filename) {
|
|
257
|
-
console.log(JSON.stringify(schema, null, 2))
|
|
258
|
-
}
|
package/lib/stackable.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { kMetadata, replaceEnv } from '@platformatic/foundation'
|
|
2
|
-
import { ServiceStackable } from '@platformatic/service'
|
|
3
|
-
import { ensureServices, platformaticComposer } from './application.js'
|
|
4
|
-
import { notHostConstraints } from './not-host-constraints.js'
|
|
5
|
-
import { packageJson } from './schema.js'
|
|
6
|
-
|
|
7
|
-
const kITC = Symbol.for('plt.runtime.itc')
|
|
8
|
-
|
|
9
|
-
export class ComposerStackable extends ServiceStackable {
|
|
10
|
-
#meta
|
|
11
|
-
#dependencies
|
|
12
|
-
|
|
13
|
-
constructor (root, config, context) {
|
|
14
|
-
super(root, config, context)
|
|
15
|
-
this.type = 'composer'
|
|
16
|
-
this.version = packageJson.version
|
|
17
|
-
|
|
18
|
-
this.applicationFactory = this.context.applicationFactory ?? platformaticComposer
|
|
19
|
-
this.fastifyOptions ??= {}
|
|
20
|
-
this.fastifyOptions.constraints = { notHost: notHostConstraints }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async getBootstrapDependencies () {
|
|
24
|
-
await ensureServices(this.serviceId, this.config)
|
|
25
|
-
|
|
26
|
-
const composedServices = this.config.composer?.services
|
|
27
|
-
const dependencies = []
|
|
28
|
-
|
|
29
|
-
if (Array.isArray(composedServices)) {
|
|
30
|
-
dependencies.push(
|
|
31
|
-
...(await Promise.all(
|
|
32
|
-
composedServices.map(async service => {
|
|
33
|
-
return this.#parseDependency(service.id, service.origin)
|
|
34
|
-
})
|
|
35
|
-
))
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
this.#dependencies = dependencies
|
|
40
|
-
return this.#dependencies
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
registerMeta (meta) {
|
|
44
|
-
this.#meta = Object.assign(this.#meta ?? {}, meta)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getMeta () {
|
|
48
|
-
const serviceMeta = super.getMeta()
|
|
49
|
-
const composerMeta = this.#meta ? { composer: this.#meta } : undefined
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
...serviceMeta,
|
|
53
|
-
...composerMeta
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async isHealthy () {
|
|
58
|
-
// If no dependencies (still booting), assume healthy
|
|
59
|
-
if (this.#dependencies) {
|
|
60
|
-
const composedServices = this.#dependencies.map(dep => dep.id)
|
|
61
|
-
const workers = await globalThis[kITC].send('getWorkers')
|
|
62
|
-
|
|
63
|
-
for (const worker of Object.values(workers)) {
|
|
64
|
-
if (composedServices.includes(worker.service) && !worker.status.startsWith('start')) {
|
|
65
|
-
globalThis[kITC].notify('event', { event: 'unhealthy' })
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
globalThis[kITC].notify('event', { event: 'healthy' })
|
|
72
|
-
return true
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async #parseDependency (id, urlString) {
|
|
76
|
-
let url = `http://${id}.plt.local`
|
|
77
|
-
|
|
78
|
-
if (urlString) {
|
|
79
|
-
const remoteUrl = await replaceEnv(urlString, this.config[kMetadata].env)
|
|
80
|
-
|
|
81
|
-
if (remoteUrl) {
|
|
82
|
-
url = remoteUrl
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { id, url, local: url.endsWith('.plt.local') }
|
|
87
|
-
}
|
|
88
|
-
}
|
package/lib/upgrade.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { abstractLogger } from '@platformatic/foundation'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { semgrator } from 'semgrator'
|
|
4
|
-
|
|
5
|
-
export async function upgrade (logger, config, version) {
|
|
6
|
-
const iterator = semgrator({
|
|
7
|
-
version,
|
|
8
|
-
path: resolve(import.meta.dirname, 'versions'),
|
|
9
|
-
input: config,
|
|
10
|
-
logger: logger?.child({ name: '@platformatic/composer' }) ?? abstractLogger
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
let result
|
|
14
|
-
|
|
15
|
-
for await (const updated of iterator) {
|
|
16
|
-
result = updated.result
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return result
|
|
20
|
-
}
|