@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.
- package/config.d.ts +1 -19
- package/eslint.config.js +11 -2
- package/index.d.ts +58 -17
- package/index.js +29 -212
- package/lib/application.js +186 -0
- package/lib/commands/index.js +15 -0
- package/lib/commands/openapi-fetch-schemas.js +47 -0
- package/lib/composer-hook.js +9 -9
- package/lib/errors.js +15 -10
- package/lib/generator.js +158 -0
- package/lib/graphql-fetch.js +44 -46
- package/lib/graphql-generator.js +12 -10
- package/lib/graphql.js +13 -9
- package/lib/{proxy/not-host-constraints.js → not-host-constraints.js} +3 -5
- package/lib/openapi-composer.js +39 -41
- package/lib/openapi-config-schema.js +26 -30
- package/lib/openapi-generator.js +115 -112
- package/lib/openapi-load-config.js +14 -14
- package/lib/openapi-modifier.js +12 -21
- package/lib/openapi-scalar.js +3 -5
- package/lib/proxy.js +21 -34
- package/lib/{root-endpoint/index.js → root.js} +12 -12
- package/lib/schema.js +34 -24
- package/lib/stackable.js +29 -39
- package/lib/upgrade.js +6 -8
- package/lib/utils.js +5 -16
- package/lib/versions/2.0.0.js +4 -6
- package/package.json +14 -18
- package/schema.json +2 -73
- package/.c8rc +0 -6
- package/composer.mjs +0 -54
- package/help/create.txt +0 -11
- package/help/help.txt +0 -7
- package/help/openapi schemas fetch.txt +0 -9
- package/help/start.txt +0 -54
- package/index.test-d.ts +0 -23
- package/lib/create.mjs +0 -84
- package/lib/generator/README.md +0 -30
- package/lib/generator/composer-generator.d.ts +0 -11
- package/lib/generator/composer-generator.js +0 -128
- package/lib/metrics.js +0 -12
- package/lib/openapi-fetch-schemas.mjs +0 -61
- /package/{lib/root-endpoint/public → public}/images/dark_mode.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/ellipse.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/external-link.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/favicon.ico +0 -0
- /package/{lib/root-endpoint/public → public}/images/graphiql.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/graphql.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/light_mode.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/openapi.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/platformatic-logo-dark.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/platformatic-logo-light.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/reverse-proxy.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/triangle_dark.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/triangle_light.svg +0 -0
- /package/{lib/root-endpoint/public → public}/index.html +0 -0
- /package/{lib/root-endpoint/public → public}/index.njk +0 -0
- /package/{lib/root-endpoint/public → public}/main.css +0 -0
package/lib/openapi-generator.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { prefixWithSlash } = require('./utils.js')
|
|
13
|
-
const openApiScalar = require('./openapi-scalar')
|
|
1
|
+
import fastifyReplyFrom from '@fastify/reply-from'
|
|
2
|
+
import fastifySwagger from '@fastify/swagger'
|
|
3
|
+
import fp from 'fastify-plugin'
|
|
4
|
+
import { readFile } from 'node:fs/promises'
|
|
5
|
+
import { getGlobalDispatcher, request } from 'undici'
|
|
6
|
+
import { CouldNotReadOpenAPIConfigError } from './errors.js'
|
|
7
|
+
import { composeOpenApi } from './openapi-composer.js'
|
|
8
|
+
import { loadOpenApiConfig } from './openapi-load-config.js'
|
|
9
|
+
import { modifyOpenApiSchema, originPathSymbol } from './openapi-modifier.js'
|
|
10
|
+
import { openApiScalar } from './openapi-scalar.js'
|
|
11
|
+
import { prefixWithSlash } from './utils.js'
|
|
14
12
|
|
|
15
13
|
async function fetchOpenApiSchema (openApiUrl) {
|
|
16
14
|
const { body } = await request(openApiUrl)
|
|
@@ -31,95 +29,35 @@ async function getOpenApiSchema (origin, openapi) {
|
|
|
31
29
|
return readOpenApiSchema(openapi.file)
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const { services } = opts
|
|
38
|
-
|
|
39
|
-
const openApiSchemas = []
|
|
40
|
-
const apiByApiRoutes = {}
|
|
41
|
-
|
|
42
|
-
for (const { id, origin, openapi } of services) {
|
|
43
|
-
if (!openapi) continue
|
|
44
|
-
|
|
45
|
-
let openapiConfig = null
|
|
46
|
-
if (openapi.config) {
|
|
47
|
-
try {
|
|
48
|
-
openapiConfig = await loadOpenApiConfig(openapi.config)
|
|
49
|
-
} catch (error) {
|
|
50
|
-
app.log.error(error)
|
|
51
|
-
throw new errors.CouldNotReadOpenAPIConfigError(id)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let originSchema = null
|
|
56
|
-
try {
|
|
57
|
-
originSchema = await getOpenApiSchema(origin, openapi)
|
|
58
|
-
} catch (error) {
|
|
59
|
-
app.log.error(error, `failed to fetch schema for "${id} service"`)
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const schema = modifyOpenApiSchema(app, originSchema, openapiConfig)
|
|
64
|
-
|
|
65
|
-
const prefix = openapi.prefix ? prefixWithSlash(openapi.prefix) : ''
|
|
66
|
-
for (const path in schema.paths) {
|
|
67
|
-
apiByApiRoutes[prefix + path] = {
|
|
68
|
-
origin,
|
|
69
|
-
prefix,
|
|
70
|
-
schema: schema.paths[path],
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
openApiSchemas.push({ id, prefix, schema, originSchema, config: openapiConfig })
|
|
32
|
+
function createPathMapper (originOpenApiPath, renamedOpenApiPath, prefix) {
|
|
33
|
+
if (prefix + originOpenApiPath === renamedOpenApiPath) {
|
|
34
|
+
return path => path.slice(prefix.length)
|
|
75
35
|
}
|
|
76
36
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
exposeRoute: true,
|
|
84
|
-
openapi: {
|
|
85
|
-
info: {
|
|
86
|
-
title: opts.openapi?.title || 'Platformatic Composer',
|
|
87
|
-
version: opts.openapi?.version || '1.0.0'
|
|
88
|
-
},
|
|
89
|
-
servers: [{ url: globalThis.platformatic?.runtimeBasePath ?? '/' }],
|
|
90
|
-
components: app.composedOpenApiSchema.components
|
|
91
|
-
},
|
|
92
|
-
transform ({ schema, url }) {
|
|
93
|
-
for (const service of opts.services) {
|
|
94
|
-
if (!service.proxy) continue
|
|
95
|
-
|
|
96
|
-
const prefix = service.proxy.prefix ?? ''
|
|
97
|
-
const proxyPrefix = prefix.at(-1) === '/' ? prefix.slice(0, -1) : prefix
|
|
98
|
-
|
|
99
|
-
const proxyUrls = [proxyPrefix + '/', proxyPrefix + '/*']
|
|
100
|
-
if (proxyUrls.includes(url)) {
|
|
101
|
-
schema = schema ?? {}
|
|
102
|
-
schema.hide = true
|
|
103
|
-
break
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return { schema, url }
|
|
107
|
-
}
|
|
108
|
-
})
|
|
37
|
+
const extractParamsRegexp = generateRouteRegex(renamedOpenApiPath)
|
|
38
|
+
return path => {
|
|
39
|
+
const routeParams = path.match(extractParamsRegexp).slice(1)
|
|
40
|
+
return generateRenamedPath(originOpenApiPath, routeParams)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
109
43
|
|
|
110
|
-
|
|
44
|
+
function generateRouteRegex (route) {
|
|
45
|
+
const regex = route.replace(/{(.*?)}/g, '(.*)')
|
|
46
|
+
return new RegExp(regex)
|
|
47
|
+
}
|
|
111
48
|
|
|
112
|
-
|
|
49
|
+
function generateRenamedPath (renamedOpenApiPath, routeParams) {
|
|
50
|
+
return renamedOpenApiPath.replace(/{(.*?)}/g, () => routeParams.shift())
|
|
113
51
|
}
|
|
114
52
|
|
|
115
|
-
async function
|
|
53
|
+
async function openApiComposerPlugin (app, { opts, generated }) {
|
|
116
54
|
const { apiByApiRoutes } = generated
|
|
117
55
|
|
|
118
56
|
const dispatcher = getGlobalDispatcher()
|
|
119
57
|
|
|
120
|
-
await app.register(
|
|
58
|
+
await app.register(fastifyReplyFrom, {
|
|
121
59
|
undici: dispatcher,
|
|
122
|
-
destroyAgent: false
|
|
60
|
+
destroyAgent: false
|
|
123
61
|
})
|
|
124
62
|
|
|
125
63
|
await app.register(await import('@platformatic/fastify-openapi-glue'), {
|
|
@@ -152,7 +90,11 @@ async function openApiComposer (app, { opts, generated }) {
|
|
|
152
90
|
const rewriteRequestHeaders = (request, headers) => {
|
|
153
91
|
const targetUrl = `${origin}${request.url}`
|
|
154
92
|
const context = request.span?.context
|
|
155
|
-
const { span, telemetryHeaders } = app.openTelemetry?.startHTTPSpanClient(
|
|
93
|
+
const { span, telemetryHeaders } = app.openTelemetry?.startHTTPSpanClient(
|
|
94
|
+
targetUrl,
|
|
95
|
+
request.method,
|
|
96
|
+
context
|
|
97
|
+
) || { span: null, telemetryHeaders: {} }
|
|
156
98
|
// We need to store the span in a different object
|
|
157
99
|
// to correctly close it in the onResponse hook
|
|
158
100
|
// Note that we have 2 spans:
|
|
@@ -164,7 +106,7 @@ async function openApiComposer (app, { opts, generated }) {
|
|
|
164
106
|
...headers,
|
|
165
107
|
...telemetryHeaders,
|
|
166
108
|
'x-forwarded-for': request.ip,
|
|
167
|
-
'x-forwarded-host': request.host
|
|
109
|
+
'x-forwarded-host': request.host
|
|
168
110
|
}
|
|
169
111
|
|
|
170
112
|
return headers
|
|
@@ -173,38 +115,99 @@ async function openApiComposer (app, { opts, generated }) {
|
|
|
173
115
|
replyOptions.rewriteRequestHeaders = rewriteRequestHeaders
|
|
174
116
|
|
|
175
117
|
reply.from(origin + newRoutePath, replyOptions)
|
|
176
|
-
}
|
|
118
|
+
}
|
|
177
119
|
}
|
|
178
|
-
}
|
|
120
|
+
}
|
|
179
121
|
})
|
|
180
122
|
|
|
181
|
-
app.addHook('preValidation', async
|
|
123
|
+
app.addHook('preValidation', async req => {
|
|
182
124
|
if (typeof req.query.fields === 'string') {
|
|
183
125
|
req.query.fields = req.query.fields.split(',')
|
|
184
126
|
}
|
|
185
127
|
})
|
|
186
128
|
}
|
|
187
129
|
|
|
188
|
-
function
|
|
189
|
-
if (
|
|
190
|
-
return
|
|
130
|
+
export async function openApiGenerator (app, opts) {
|
|
131
|
+
if (!opts.services.some(s => s.openapi)) {
|
|
132
|
+
return
|
|
191
133
|
}
|
|
192
134
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
135
|
+
const { services } = opts
|
|
136
|
+
|
|
137
|
+
const openApiSchemas = []
|
|
138
|
+
const apiByApiRoutes = {}
|
|
139
|
+
|
|
140
|
+
for (const { id, origin, openapi } of services) {
|
|
141
|
+
if (!openapi) continue
|
|
142
|
+
|
|
143
|
+
let openapiConfig = null
|
|
144
|
+
if (openapi.config) {
|
|
145
|
+
try {
|
|
146
|
+
openapiConfig = await loadOpenApiConfig(openapi.config)
|
|
147
|
+
} catch (error) {
|
|
148
|
+
app.log.error(error)
|
|
149
|
+
throw new CouldNotReadOpenAPIConfigError(id)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let originSchema = null
|
|
154
|
+
try {
|
|
155
|
+
originSchema = await getOpenApiSchema(origin, openapi)
|
|
156
|
+
} catch (error) {
|
|
157
|
+
app.log.error(error, `failed to fetch schema for "${id} service"`)
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const schema = modifyOpenApiSchema(app, originSchema, openapiConfig)
|
|
162
|
+
|
|
163
|
+
const prefix = openapi.prefix ? prefixWithSlash(openapi.prefix) : ''
|
|
164
|
+
for (const path in schema.paths) {
|
|
165
|
+
apiByApiRoutes[prefix + path] = {
|
|
166
|
+
origin,
|
|
167
|
+
prefix,
|
|
168
|
+
schema: schema.paths[path]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
openApiSchemas.push({ id, prefix, schema, originSchema, config: openapiConfig })
|
|
197
173
|
}
|
|
198
|
-
}
|
|
199
174
|
|
|
200
|
-
|
|
201
|
-
const regex = route.replace(/{(.*?)}/g, '(.*)')
|
|
202
|
-
return new RegExp(regex)
|
|
203
|
-
}
|
|
175
|
+
const composedOpenApiSchema = composeOpenApi(openApiSchemas, opts.openapi)
|
|
204
176
|
|
|
205
|
-
|
|
206
|
-
|
|
177
|
+
app.decorate('openApiSchemas', openApiSchemas)
|
|
178
|
+
app.decorate('composedOpenApiSchema', composedOpenApiSchema)
|
|
179
|
+
|
|
180
|
+
await app.register(fastifySwagger, {
|
|
181
|
+
exposeRoute: true,
|
|
182
|
+
openapi: {
|
|
183
|
+
info: {
|
|
184
|
+
title: opts.openapi?.title || 'Platformatic Composer',
|
|
185
|
+
version: opts.openapi?.version || '1.0.0'
|
|
186
|
+
},
|
|
187
|
+
servers: [{ url: globalThis.platformatic?.runtimeBasePath ?? '/' }],
|
|
188
|
+
components: app.composedOpenApiSchema.components
|
|
189
|
+
},
|
|
190
|
+
transform ({ schema, url }) {
|
|
191
|
+
for (const service of opts.services) {
|
|
192
|
+
if (!service.proxy) continue
|
|
193
|
+
|
|
194
|
+
const prefix = service.proxy.prefix ?? ''
|
|
195
|
+
const proxyPrefix = prefix.at(-1) === '/' ? prefix.slice(0, -1) : prefix
|
|
196
|
+
|
|
197
|
+
const proxyUrls = [proxyPrefix + '/', proxyPrefix + '/*']
|
|
198
|
+
if (proxyUrls.includes(url)) {
|
|
199
|
+
schema = schema ?? {}
|
|
200
|
+
schema.hide = true
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { schema, url }
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
await app.register(openApiScalar, opts)
|
|
209
|
+
|
|
210
|
+
return { apiByApiRoutes }
|
|
207
211
|
}
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
module.exports.openApiComposer = fp(openApiComposer)
|
|
213
|
+
export const openApiComposer = fp(openApiComposerPlugin)
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const openApiConfigSchema = require('./openapi-config-schema')
|
|
6
|
-
const errors = require('./errors')
|
|
1
|
+
import Ajv from 'ajv'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { ValidationErrors } from './errors.js'
|
|
4
|
+
import { openApiConfigSchema } from './openapi-config-schema.js'
|
|
7
5
|
|
|
8
6
|
const ajv = new Ajv()
|
|
9
7
|
const ajvValidate = ajv.compile(openApiConfigSchema)
|
|
10
8
|
|
|
11
|
-
async function loadOpenApiConfig (pathToConfig) {
|
|
9
|
+
export async function loadOpenApiConfig (pathToConfig) {
|
|
12
10
|
const openApiConfigFile = await readFile(pathToConfig, 'utf-8')
|
|
13
11
|
const openApiConfig = JSON.parse(openApiConfigFile)
|
|
14
12
|
|
|
15
13
|
if (!ajvValidate(openApiConfig)) {
|
|
16
|
-
const validationErrors = ajvValidate.errors.map(
|
|
14
|
+
const validationErrors = ajvValidate.errors.map(err => {
|
|
17
15
|
return {
|
|
18
16
|
/* c8 ignore next 1 */
|
|
19
17
|
path: err.instancePath === '' ? '/' : err.instancePath,
|
|
20
|
-
message: err.message + ' ' + JSON.stringify(err.params)
|
|
18
|
+
message: err.message + ' ' + JSON.stringify(err.params)
|
|
21
19
|
}
|
|
22
20
|
})
|
|
23
|
-
throw new
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
throw new ValidationErrors(
|
|
22
|
+
validationErrors
|
|
23
|
+
.map(err => {
|
|
24
|
+
return err.message
|
|
25
|
+
})
|
|
26
|
+
.join('\n')
|
|
27
|
+
)
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
return openApiConfig
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
module.exports = loadOpenApiConfig
|
package/lib/openapi-modifier.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import traverse from 'json-schema-traverse'
|
|
2
|
+
import rfdc from 'rfdc'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
const clone = require('rfdc')()
|
|
4
|
+
const clone = rfdc()
|
|
5
5
|
|
|
6
|
-
const originPathSymbol = Symbol('originPath')
|
|
7
6
|
const MODIFICATION_KEYWORDS = ['rename']
|
|
8
7
|
|
|
8
|
+
export const originPathSymbol = Symbol('originPath')
|
|
9
|
+
|
|
9
10
|
function findDataBySchemaPointer (schemaPointer, schema, data, parentData, callback) {
|
|
10
11
|
const schemaPointerParts = schemaPointer.split('/').slice(1)
|
|
11
12
|
|
|
@@ -34,9 +35,7 @@ function getModificationRules (modificationSchema) {
|
|
|
34
35
|
|
|
35
36
|
function getModificationRules (schema, jsonPointer) {
|
|
36
37
|
const schemaKeys = Object.keys(schema)
|
|
37
|
-
const modificationKeys = schemaKeys.filter(
|
|
38
|
-
key => MODIFICATION_KEYWORDS.includes(key)
|
|
39
|
-
)
|
|
38
|
+
const modificationKeys = schemaKeys.filter(key => MODIFICATION_KEYWORDS.includes(key))
|
|
40
39
|
|
|
41
40
|
if (modificationKeys.length === 0) return
|
|
42
41
|
modificationRules[jsonPointer] = schema
|
|
@@ -70,22 +69,16 @@ function modifyPayload (payload, originSchema, modificationRules) {
|
|
|
70
69
|
for (const schemaJsonPointer in modificationRules) {
|
|
71
70
|
const rule = modificationRules[schemaJsonPointer]
|
|
72
71
|
|
|
73
|
-
findDataBySchemaPointer(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
null,
|
|
78
|
-
(data, parentData) => {
|
|
79
|
-
if (rule.rename) {
|
|
80
|
-
parentData[rule.rename] = data
|
|
81
|
-
delete parentData[schemaJsonPointer.split('/').pop()]
|
|
82
|
-
}
|
|
72
|
+
findDataBySchemaPointer(schemaJsonPointer, originSchema, payload, null, (data, parentData) => {
|
|
73
|
+
if (rule.rename) {
|
|
74
|
+
parentData[rule.rename] = data
|
|
75
|
+
delete parentData[schemaJsonPointer.split('/').pop()]
|
|
83
76
|
}
|
|
84
|
-
)
|
|
77
|
+
})
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
function modifyOpenApiSchema (app, schema, config) {
|
|
81
|
+
export function modifyOpenApiSchema (app, schema, config) {
|
|
89
82
|
const newSchemaPaths = {}
|
|
90
83
|
const { paths } = clone(schema)
|
|
91
84
|
|
|
@@ -133,5 +126,3 @@ function modifyOpenApiSchema (app, schema, config) {
|
|
|
133
126
|
|
|
134
127
|
return { ...schema, paths: newSchemaPaths }
|
|
135
128
|
}
|
|
136
|
-
|
|
137
|
-
module.exports = { modifyOpenApiSchema, originPathSymbol }
|
package/lib/openapi-scalar.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import fp from 'fastify-plugin'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
async function openApiScalar (app, opts) {
|
|
3
|
+
async function openApiScalarPlugin (app, opts) {
|
|
6
4
|
const { default: scalarTheme } = await import('@platformatic/scalar-theme')
|
|
7
5
|
const { default: scalarApiReference } = await import('@scalar/fastify-api-reference')
|
|
8
6
|
|
|
@@ -21,4 +19,4 @@ async function openApiScalar (app, opts) {
|
|
|
21
19
|
})
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
export const openApiScalar = fp(openApiScalarPlugin)
|
package/lib/proxy.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { getGlobalDispatcher } = require('undici')
|
|
8
|
-
const { initMetrics } = require('./metrics')
|
|
1
|
+
import httpProxy from '@fastify/http-proxy'
|
|
2
|
+
import { ensureLoggableError, loadModule } from '@platformatic/utils'
|
|
3
|
+
import fp from 'fastify-plugin'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { workerData } from 'node:worker_threads'
|
|
6
|
+
import { getGlobalDispatcher } from 'undici'
|
|
9
7
|
|
|
10
8
|
const kITC = Symbol.for('plt.runtime.itc')
|
|
11
9
|
const kProxyRoute = Symbol('plt.composer.proxy.route')
|
|
@@ -36,7 +34,7 @@ async function resolveServiceProxyParameters (service) {
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
if (service.proxy?.ws?.hooks) {
|
|
39
|
-
const hooks =
|
|
37
|
+
const hooks = await loadModule(createRequire(import.meta.filename), service.proxy.ws.hooks.path)
|
|
40
38
|
service.proxy.ws.hooks = hooks
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -53,9 +51,7 @@ async function resolveServiceProxyParameters (service) {
|
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
module.exports = fp(async function (app, opts) {
|
|
54
|
+
async function proxyPlugin (app, opts) {
|
|
59
55
|
const meta = { proxies: {} }
|
|
60
56
|
const hostnameLessProxies = []
|
|
61
57
|
|
|
@@ -84,16 +80,16 @@ module.exports = fp(async function (app, opts) {
|
|
|
84
80
|
const basePath = `/${prefix ?? ''}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
|
|
85
81
|
const dispatcher = getGlobalDispatcher()
|
|
86
82
|
|
|
87
|
-
let preRewrite = null
|
|
88
|
-
|
|
89
83
|
if (needsRootTrailingSlash) {
|
|
90
|
-
|
|
91
|
-
if (url
|
|
92
|
-
|
|
84
|
+
app.addHook('preHandler', function rootTrailingSlashPreHandler (req, reply, done) {
|
|
85
|
+
if (req.url !== basePath) {
|
|
86
|
+
done()
|
|
87
|
+
return
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const { url, options } = reply.fromParameters(req.url + '/', req.params, prefix)
|
|
91
|
+
reply.from(url.replace(/\/+$/, '/'), options)
|
|
92
|
+
})
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
/*
|
|
@@ -148,28 +144,17 @@ module.exports = fp(async function (app, opts) {
|
|
|
148
144
|
)
|
|
149
145
|
: null
|
|
150
146
|
|
|
151
|
-
if (!metrics) {
|
|
152
|
-
metrics = initMetrics(globalThis.platformatic?.prometheus)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
147
|
const proxyOptions = {
|
|
156
148
|
prefix,
|
|
157
149
|
rewritePrefix,
|
|
158
150
|
upstream: service.proxy?.upstream ?? origin,
|
|
159
|
-
preRewrite,
|
|
160
151
|
|
|
161
152
|
websocket: true,
|
|
162
153
|
wsUpstream: ws?.upstream ?? url ?? origin,
|
|
163
154
|
wsReconnect: ws?.reconnect,
|
|
164
155
|
wsHooks: {
|
|
165
|
-
onConnect:
|
|
166
|
-
|
|
167
|
-
ws?.hooks?.onConnect(...args)
|
|
168
|
-
},
|
|
169
|
-
onDisconnect: (...args) => {
|
|
170
|
-
metrics?.activeWsConnections?.dec()
|
|
171
|
-
ws?.hooks?.onDisconnect(...args)
|
|
172
|
-
},
|
|
156
|
+
onConnect: ws?.hooks?.onConnect,
|
|
157
|
+
onDisconnect: ws?.hooks?.onDisconnect,
|
|
173
158
|
onReconnect: ws?.hooks?.onReconnect,
|
|
174
159
|
onPong: ws?.hooks?.onPong,
|
|
175
160
|
onIncomingMessage: ws?.hooks?.onIncomingMessage,
|
|
@@ -260,5 +245,7 @@ module.exports = fp(async function (app, opts) {
|
|
|
260
245
|
await app.register(httpProxy, options)
|
|
261
246
|
}
|
|
262
247
|
|
|
263
|
-
opts.
|
|
264
|
-
}
|
|
248
|
+
opts.stackable?.registerMeta(meta)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const proxy = fp(proxyPlugin)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
|
|
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'
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
const fastifyStatic = require('@fastify/static')
|
|
5
|
-
const userAgentParser = require('my-ua-parser')
|
|
6
|
-
|
|
7
|
-
module.exports = async (app, opts) => {
|
|
7
|
+
export default function root (app) {
|
|
8
8
|
app.register(fastifyStatic, {
|
|
9
|
-
root: join(
|
|
9
|
+
root: join(import.meta.dirname, '../public')
|
|
10
10
|
})
|
|
11
|
-
app.register(
|
|
11
|
+
app.register(fastifyView, {
|
|
12
12
|
engine: {
|
|
13
|
-
nunjucks
|
|
13
|
+
nunjucks
|
|
14
14
|
},
|
|
15
|
-
root: join(
|
|
15
|
+
root: join(import.meta.dirname, '../public')
|
|
16
16
|
})
|
|
17
17
|
// root endpoint
|
|
18
18
|
app.route({
|
|
@@ -44,7 +44,7 @@ module.exports = async (app, opts) => {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
app.platformatic.config.composer.services.forEach(
|
|
47
|
+
app.platformatic.config.composer.services.forEach(s => {
|
|
48
48
|
if (s.openapi) {
|
|
49
49
|
hasOpenAPIServices = true
|
|
50
50
|
serviceTypes.openapi.services.push(s)
|
|
@@ -70,6 +70,6 @@ module.exports = async (app, opts) => {
|
|
|
70
70
|
}
|
|
71
71
|
// Load services
|
|
72
72
|
return { message: 'Welcome to Platformatic! Please visit https://docs.platformatic.dev' }
|
|
73
|
-
}
|
|
73
|
+
}
|
|
74
74
|
})
|
|
75
75
|
}
|