@platformatic/runtime 1.51.8 → 2.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.
- package/fixtures/configs/hotreload.json +0 -1
- package/fixtures/configs/{monorepo-client-without-id.json → invalid-autoload-with-services.json} +17 -9
- package/fixtures/configs/monorepo-composer-no-autoload.json +32 -0
- package/fixtures/configs/monorepo-create-cycle.json +0 -1
- package/fixtures/configs/monorepo-hotreload-env.json +0 -1
- package/fixtures/configs/monorepo-hotreload.json +0 -1
- package/fixtures/configs/monorepo-missing-dependencies.json +9 -0
- package/fixtures/configs/monorepo-no-cycles.json +0 -1
- package/fixtures/configs/monorepo-with-management-api.json +0 -1
- package/fixtures/configs/monorepo.json +0 -1
- package/fixtures/configs/service-with-env-port.json +0 -1
- package/fixtures/leven/platformatic.runtime.json +0 -1
- package/fixtures/management-api/platformatic.json +0 -1
- package/fixtures/management-api-custom-labels/platformatic.json +0 -1
- package/fixtures/management-api-without-metrics/platformatic.json +0 -1
- package/fixtures/monorepo/composerApp/platformatic.composer.json +0 -1
- package/fixtures/monorepo-missing-dependencies/composer/platformatic.json +24 -0
- package/fixtures/prom-server/platformatic.json +0 -1
- package/fixtures/sample-runtime/platformatic.json +0 -1
- package/fixtures/sample-runtime-with-2-services/platformatic.json +0 -1
- package/fixtures/server/logger-transport/platformatic.runtime.json +0 -1
- package/fixtures/server/overrides-service/platformatic.runtime.json +0 -1
- package/fixtures/server/runtime-server/platformatic.runtime.json +0 -1
- package/fixtures/telemetry/platformatic.runtime.json +0 -1
- package/fixtures/typescript/platformatic.runtime.json +0 -1
- package/fixtures/typescript-custom-flags/platformatic.runtime.json +0 -1
- package/fixtures/typescript-no-env/platformatic.runtime.json +0 -1
- package/lib/api.js +45 -22
- package/lib/app.js +48 -12
- package/lib/build-server.js +12 -2
- package/lib/compile.js +49 -7
- package/lib/config.js +8 -127
- package/lib/dependencies.js +58 -0
- package/lib/errors.js +1 -0
- package/lib/generator/runtime-generator.js +0 -1
- package/lib/load-config.js +2 -10
- package/lib/schema.js +1 -4
- package/lib/start.js +7 -8
- package/package.json +11 -12
- package/runtime.mjs +6 -1
- package/fixtures/monorepo/serviceApp/platformatic.service-client-without-id.json +0 -21
package/fixtures/configs/{monorepo-client-without-id.json → invalid-autoload-with-services.json}
RENAMED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
|
|
3
|
-
"entrypoint": "
|
|
4
|
-
"allowCycles": true,
|
|
5
|
-
"hotReload": false,
|
|
3
|
+
"entrypoint": "db-app",
|
|
6
4
|
"autoload": {
|
|
7
5
|
"path": "../monorepo",
|
|
8
|
-
"exclude": [
|
|
6
|
+
"exclude": [
|
|
7
|
+
"docs",
|
|
8
|
+
"composerApp"
|
|
9
|
+
],
|
|
9
10
|
"mappings": {
|
|
10
|
-
"serviceApp": {
|
|
11
|
-
"id": "serviceApp",
|
|
12
|
-
"config": "platformatic.service-client-without-id.json"
|
|
13
|
-
},
|
|
14
11
|
"serviceAppWithLogger": {
|
|
15
12
|
"id": "with-logger",
|
|
16
13
|
"config": "platformatic.service.json"
|
|
@@ -18,7 +15,18 @@
|
|
|
18
15
|
"serviceAppWithMultiplePlugins": {
|
|
19
16
|
"id": "multi-plugin-service",
|
|
20
17
|
"config": "platformatic.service.json"
|
|
18
|
+
},
|
|
19
|
+
"dbApp": {
|
|
20
|
+
"id": "db-app",
|
|
21
|
+
"config": "platformatic.db.json"
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
}
|
|
24
|
+
},
|
|
25
|
+
"services": [
|
|
26
|
+
{
|
|
27
|
+
"id": "with-logger",
|
|
28
|
+
"path": "../monorepo/serviceAppWithLogger",
|
|
29
|
+
"config": "platformatic.service.json"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
24
32
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
|
|
3
|
+
"entrypoint": "composerApp",
|
|
4
|
+
"hotReload": false,
|
|
5
|
+
"services": [
|
|
6
|
+
{
|
|
7
|
+
"id": "with-logger",
|
|
8
|
+
"path": "../monorepo/serviceAppWithLogger",
|
|
9
|
+
"config": "platformatic.service.json"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "db-app",
|
|
13
|
+
"path": "../monorepo/dbApp",
|
|
14
|
+
"config": "platformatic.db.json"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "composerApp",
|
|
18
|
+
"path": "../monorepo/composerApp",
|
|
19
|
+
"config": "platformatic.composer.json"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "multi-plugin-service",
|
|
23
|
+
"path": "../monorepo/serviceAppWithMultiplePlugins",
|
|
24
|
+
"config": "platformatic.service.json"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "serviceApp",
|
|
28
|
+
"path": "../monorepo/serviceApp",
|
|
29
|
+
"config": "platformatic.service.json"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.23.2/composer",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0,
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "info"
|
|
8
|
+
},
|
|
9
|
+
"pluginTimeout": 0
|
|
10
|
+
},
|
|
11
|
+
"composer": {
|
|
12
|
+
"services": [
|
|
13
|
+
{
|
|
14
|
+
"id": "missing",
|
|
15
|
+
"openapi": {
|
|
16
|
+
"url": "/documentation/json",
|
|
17
|
+
"prefix": "/missing"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"refreshTimeout": 5000
|
|
22
|
+
},
|
|
23
|
+
"watch": false
|
|
24
|
+
}
|
package/lib/api.js
CHANGED
|
@@ -5,20 +5,22 @@ const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
|
|
|
5
5
|
const { createFastifyInterceptor } = require('fastify-undici-dispatcher')
|
|
6
6
|
const { PlatformaticApp } = require('./app')
|
|
7
7
|
const errors = require('./errors')
|
|
8
|
+
const { checkDependencies, topologicalSort } = require('./dependencies')
|
|
8
9
|
const { printSchema } = require('graphql')
|
|
9
|
-
const { Bus } = require('@platformatic/bus')
|
|
10
10
|
|
|
11
11
|
class RuntimeApi {
|
|
12
12
|
#services
|
|
13
13
|
#dispatcher
|
|
14
14
|
#interceptor
|
|
15
15
|
#logger
|
|
16
|
-
#
|
|
16
|
+
#config
|
|
17
|
+
#bootstrapDependenciesResolved
|
|
17
18
|
|
|
18
19
|
constructor (config, logger, loaderPort, composedInterceptors = []) {
|
|
20
|
+
this.#config = config
|
|
19
21
|
this.#services = new Map()
|
|
20
22
|
this.#logger = logger
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
const telemetryConfig = config.telemetry
|
|
23
25
|
const metricsConfig = config.metrics
|
|
24
26
|
|
|
@@ -153,19 +155,25 @@ class RuntimeApi {
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
async startServices () {
|
|
156
|
-
|
|
158
|
+
if (!this.#bootstrapDependenciesResolved) {
|
|
159
|
+
await this._resolveBootstrapDependencies()
|
|
160
|
+
this.#bootstrapDependenciesResolved = true
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let entrypoint
|
|
157
164
|
for (const service of this.#services.values()) {
|
|
158
165
|
await service.start()
|
|
159
166
|
|
|
160
167
|
if (service.appConfig.entrypoint) {
|
|
161
|
-
|
|
168
|
+
entrypoint = service
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
const serviceUrl = new URL(service.appConfig.localUrl)
|
|
165
172
|
this.#interceptor.route(serviceUrl.host, service.server)
|
|
166
173
|
}
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
|
|
175
|
+
await entrypoint?.listen()
|
|
176
|
+
return entrypoint?.server.url
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
async stopServices () {
|
|
@@ -181,29 +189,50 @@ class RuntimeApi {
|
|
|
181
189
|
stopServiceReqs.push(service.stop())
|
|
182
190
|
}
|
|
183
191
|
}
|
|
192
|
+
|
|
184
193
|
await Promise.all(stopServiceReqs)
|
|
185
|
-
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async _resolveBootstrapDependencies () {
|
|
197
|
+
if (this.#config.autoload) {
|
|
198
|
+
for (const service of this.#config.services) {
|
|
199
|
+
const dependencies = await this.#services.get(service.id).getBootstrapDependencies()
|
|
200
|
+
service.dependencies = dependencies
|
|
201
|
+
|
|
202
|
+
for (const { envVar, url } of dependencies) {
|
|
203
|
+
if (envVar) {
|
|
204
|
+
service.localServiceEnvVars.set(envVar, url)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
checkDependencies(this.#config.services)
|
|
210
|
+
this.#services = topologicalSort(this.#services, this.#config)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return this.#services
|
|
186
214
|
}
|
|
187
215
|
|
|
188
216
|
async #restartServices () {
|
|
189
|
-
let
|
|
217
|
+
let entrypoint
|
|
190
218
|
for (const service of this.#services.values()) {
|
|
191
219
|
const serviceStatus = service.getStatus()
|
|
220
|
+
|
|
221
|
+
if (service.appConfig.entrypoint) {
|
|
222
|
+
entrypoint = service
|
|
223
|
+
}
|
|
224
|
+
|
|
192
225
|
if (serviceStatus === 'starting') {
|
|
193
226
|
await once(service, 'start')
|
|
194
227
|
}
|
|
195
228
|
|
|
196
|
-
await service.restart(
|
|
197
|
-
|
|
198
|
-
if (service.appConfig.entrypoint) {
|
|
199
|
-
entrypointUrl = service.server.url
|
|
200
|
-
}
|
|
229
|
+
await service.restart()
|
|
201
230
|
|
|
202
231
|
const serviceUrl = new URL(service.appConfig.localUrl)
|
|
203
232
|
this.#interceptor.route(serviceUrl.host, service.server)
|
|
204
233
|
}
|
|
205
|
-
|
|
206
|
-
return
|
|
234
|
+
|
|
235
|
+
return entrypoint?.server.url
|
|
207
236
|
}
|
|
208
237
|
|
|
209
238
|
#getEntrypointDetails () {
|
|
@@ -356,7 +385,6 @@ class RuntimeApi {
|
|
|
356
385
|
} else {
|
|
357
386
|
await service.start()
|
|
358
387
|
}
|
|
359
|
-
this.#bus.broadcast('runtime:service:started', id)
|
|
360
388
|
}
|
|
361
389
|
|
|
362
390
|
async #stopService ({ id }) {
|
|
@@ -368,7 +396,6 @@ class RuntimeApi {
|
|
|
368
396
|
}
|
|
369
397
|
|
|
370
398
|
await service.stop()
|
|
371
|
-
this.#bus.broadcast('runtime:service:stopped', id)
|
|
372
399
|
}
|
|
373
400
|
|
|
374
401
|
async #inject ({ id, injectParams }) {
|
|
@@ -388,10 +415,6 @@ class RuntimeApi {
|
|
|
388
415
|
body: res.body
|
|
389
416
|
}
|
|
390
417
|
}
|
|
391
|
-
|
|
392
|
-
#setupBus (config) {
|
|
393
|
-
this.#bus = new Bus('$root')
|
|
394
|
-
}
|
|
395
418
|
}
|
|
396
419
|
|
|
397
420
|
module.exports = RuntimeApi
|
package/lib/app.js
CHANGED
|
@@ -55,8 +55,8 @@ class PlatformaticApp extends EventEmitter {
|
|
|
55
55
|
return 'stopped'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async restart (
|
|
59
|
-
if (this.#starting || !this.#started
|
|
58
|
+
async restart () {
|
|
59
|
+
if (this.#starting || !this.#started) {
|
|
60
60
|
return
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -92,6 +92,15 @@ class PlatformaticApp extends EventEmitter {
|
|
|
92
92
|
this.emit('restart')
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
async getBootstrapDependencies () {
|
|
96
|
+
await this.#loadConfig()
|
|
97
|
+
const resolver = this.config.app.getBootstrapDependencies
|
|
98
|
+
if (typeof resolver === 'function') {
|
|
99
|
+
return resolver(this.appConfig, this.config.configManager)
|
|
100
|
+
}
|
|
101
|
+
return []
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
async start () {
|
|
96
105
|
if (this.#starting || this.#started) {
|
|
97
106
|
throw new errors.ApplicationAlreadyStartedError()
|
|
@@ -99,7 +108,7 @@ class PlatformaticApp extends EventEmitter {
|
|
|
99
108
|
|
|
100
109
|
this.#starting = true
|
|
101
110
|
|
|
102
|
-
await this.#
|
|
111
|
+
await this.#applyConfig()
|
|
103
112
|
await this.#updateConfig()
|
|
104
113
|
|
|
105
114
|
const configManager = this.config.configManager
|
|
@@ -121,16 +130,13 @@ class PlatformaticApp extends EventEmitter {
|
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
if (
|
|
124
|
-
(
|
|
125
|
-
config.plugins !== undefined ||
|
|
126
|
-
config.versions !== undefined
|
|
127
|
-
) &&
|
|
133
|
+
(config.plugins !== undefined) &&
|
|
128
134
|
this.#originalWatch?.enabled !== false
|
|
129
135
|
) {
|
|
130
136
|
this.#startFileWatching()
|
|
131
137
|
}
|
|
132
138
|
|
|
133
|
-
if (this.appConfig.
|
|
139
|
+
if (this.appConfig.useHttp) {
|
|
134
140
|
try {
|
|
135
141
|
await this.server.start()
|
|
136
142
|
/* c8 ignore next 5 */
|
|
@@ -162,6 +168,15 @@ class PlatformaticApp extends EventEmitter {
|
|
|
162
168
|
this.emit('stop')
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
async listen () {
|
|
172
|
+
// This server is not an entrypoint or already listened in start. Behave as no-op.
|
|
173
|
+
if (!this.appConfig.entrypoint || this.appConfig.useHttp) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await this.server.start()
|
|
178
|
+
}
|
|
179
|
+
|
|
165
180
|
async handleProcessLevelEvent ({ signal, err }) {
|
|
166
181
|
/* c8 ignore next 3 */
|
|
167
182
|
if (!this.server) {
|
|
@@ -195,21 +210,28 @@ class PlatformaticApp extends EventEmitter {
|
|
|
195
210
|
}
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
async #
|
|
213
|
+
async #loadConfig () {
|
|
199
214
|
const appConfig = this.appConfig
|
|
200
215
|
|
|
201
216
|
let _config
|
|
202
217
|
try {
|
|
203
218
|
_config = await loadConfig({}, ['-c', appConfig.config], {
|
|
204
|
-
onMissingEnv
|
|
205
|
-
|
|
206
|
-
}
|
|
219
|
+
onMissingEnv: this.#fetchServiceUrl,
|
|
220
|
+
context: appConfig
|
|
207
221
|
}, true, this.#logger)
|
|
208
222
|
} catch (err) {
|
|
209
223
|
this.#logAndExit(err)
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
this.config = _config
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async #applyConfig () {
|
|
230
|
+
if (!this.config) {
|
|
231
|
+
await this.#loadConfig()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const appConfig = this.appConfig
|
|
213
235
|
const { configManager } = this.config
|
|
214
236
|
|
|
215
237
|
function applyOverrides () {
|
|
@@ -306,6 +328,16 @@ class PlatformaticApp extends EventEmitter {
|
|
|
306
328
|
: this.#logger
|
|
307
329
|
}
|
|
308
330
|
|
|
331
|
+
#fetchServiceUrl (key, { parent, context: service }) {
|
|
332
|
+
if (service.localServiceEnvVars.has(key)) {
|
|
333
|
+
return service.localServiceEnvVars.get(key)
|
|
334
|
+
} else if (!key.endsWith('_URL') || !parent.serviceId) {
|
|
335
|
+
return null
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return getServiceUrl(parent.serviceId)
|
|
339
|
+
}
|
|
340
|
+
|
|
309
341
|
#startFileWatching () {
|
|
310
342
|
if (this.#fileWatcher) {
|
|
311
343
|
return
|
|
@@ -358,4 +390,8 @@ function clearCjsCache () {
|
|
|
358
390
|
})
|
|
359
391
|
}
|
|
360
392
|
|
|
393
|
+
function getServiceUrl (id) {
|
|
394
|
+
return `http://${id}.plt.local`
|
|
395
|
+
}
|
|
396
|
+
|
|
361
397
|
module.exports = { PlatformaticApp }
|
package/lib/build-server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { createRequire } = require('node:module')
|
|
4
|
+
const { join } = require('node:path')
|
|
3
5
|
const ConfigManager = require('@platformatic/config')
|
|
4
6
|
const { platformaticRuntime } = require('./config')
|
|
5
7
|
const { buildRuntime } = require('./start')
|
|
6
|
-
const { buildServer: buildServerService } = require('@platformatic/service')
|
|
7
8
|
const { loadConfig } = require('./load-config')
|
|
8
9
|
|
|
9
10
|
async function buildServerRuntime (options = {}) {
|
|
@@ -55,7 +56,16 @@ async function buildServer (options) {
|
|
|
55
56
|
return buildServerRuntime(options)
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
if (app.buildServer) {
|
|
60
|
+
return app.buildServer(options)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// App is a stackable. Hopefully we have `@platformatic/service` available.
|
|
64
|
+
const projectRoot = join(options.configManager.dirname, 'package.json')
|
|
65
|
+
const require = createRequire(projectRoot)
|
|
66
|
+
const { buildServer } = require('@platformatic/service')
|
|
67
|
+
|
|
68
|
+
return buildServer(options, app)
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
module.exports = { buildServer }
|
package/lib/compile.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const tsCompiler = require('@platformatic/ts-compiler')
|
|
4
4
|
const { loadConfig } = require('./load-config')
|
|
5
|
-
const { dirname } = require('node:path')
|
|
5
|
+
const { dirname, join } = require('node:path')
|
|
6
|
+
const { createRequire } = require('node:module')
|
|
7
|
+
const { pathToFileURL } = require('node:url')
|
|
6
8
|
|
|
7
9
|
const pino = require('pino')
|
|
8
10
|
const pretty = require('pino-pretty')
|
|
9
11
|
const { isatty } = require('node:tty')
|
|
10
12
|
|
|
11
13
|
async function compile (argv, logger) {
|
|
12
|
-
const { configManager, configType } = await loadConfig({}, argv, {
|
|
14
|
+
const { configManager, configType, app } = await loadConfig({}, argv, {
|
|
13
15
|
watch: false
|
|
14
16
|
}, false)
|
|
15
17
|
/* c8 ignore next */
|
|
@@ -35,21 +37,61 @@ async function compile (argv, logger) {
|
|
|
35
37
|
const childLogger = logger.child({ name: service.id })
|
|
36
38
|
|
|
37
39
|
const serviceConfigPath = service.config
|
|
38
|
-
const { configManager } = await loadConfig({}, ['-c', serviceConfigPath], {
|
|
40
|
+
const { configManager, app } = await loadConfig({}, ['-c', serviceConfigPath], {
|
|
39
41
|
onMissingEnv (key) {
|
|
40
42
|
return service.localServiceEnvVars.get(key)
|
|
41
43
|
},
|
|
42
44
|
watch: false
|
|
43
45
|
}, false)
|
|
44
46
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
+
const tsOptions = await extract(configManager, app)
|
|
48
|
+
|
|
49
|
+
if (tsOptions) {
|
|
50
|
+
const serviceWasCompiled = await tsCompiler.compile({
|
|
51
|
+
...compileOptions,
|
|
52
|
+
...tsOptions,
|
|
53
|
+
cwd: service.path,
|
|
54
|
+
logger: childLogger
|
|
55
|
+
})
|
|
56
|
+
compiled ||= serviceWasCompiled
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
59
|
} else {
|
|
49
|
-
|
|
60
|
+
const tsOptions = await extract(configManager, app)
|
|
61
|
+
if (tsOptions) {
|
|
62
|
+
compiled = await tsCompiler.compile({
|
|
63
|
+
...compileOptions,
|
|
64
|
+
...tsOptions,
|
|
65
|
+
cwd: dirname(configManager.fullPath),
|
|
66
|
+
logger
|
|
67
|
+
})
|
|
68
|
+
}
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
return compiled
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
async function extract (configManager, app) {
|
|
75
|
+
let extractTypeScriptCompileOptionsFromConfig = app.extractTypeScriptCompileOptionsFromConfig
|
|
76
|
+
|
|
77
|
+
if (!extractTypeScriptCompileOptionsFromConfig) {
|
|
78
|
+
// This is a bit of a hack, but it is needed to avoid a circular dependency
|
|
79
|
+
// it also allow for customizations if needed
|
|
80
|
+
const _require = createRequire(join(configManager.dirname, 'package.json'))
|
|
81
|
+
const toLoad = _require.resolve('@platformatic/service')
|
|
82
|
+
try {
|
|
83
|
+
extractTypeScriptCompileOptionsFromConfig = (await import(pathToFileURL(toLoad))).extractTypeScriptCompileOptionsFromConfig
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
// If we can't load `@platformatic/service` we just return null
|
|
87
|
+
// and we won't be compiling typescript
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!extractTypeScriptCompileOptionsFromConfig) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return extractTypeScriptCompileOptionsFromConfig(configManager.current)
|
|
95
|
+
}
|
|
96
|
+
|
|
55
97
|
module.exports.compile = compile
|
package/lib/config.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const {
|
|
2
|
+
const { readdir } = require('node:fs/promises')
|
|
3
3
|
const { join, resolve: pathResolve } = require('node:path')
|
|
4
|
-
const { closest } = require('fastest-levenshtein')
|
|
5
|
-
const Topo = require('@hapi/topo')
|
|
6
4
|
const ConfigManager = require('@platformatic/config')
|
|
7
5
|
const { schema } = require('./schema')
|
|
8
6
|
const errors = require('./errors')
|
|
9
7
|
const upgrade = require('./upgrade')
|
|
10
8
|
|
|
9
|
+
const kServicesAutoloaded = Symbol('plt.servicesAutoloaded')
|
|
10
|
+
|
|
11
11
|
async function _transformConfig (configManager) {
|
|
12
12
|
const config = configManager.current
|
|
13
13
|
const services = config.services ?? []
|
|
14
14
|
|
|
15
15
|
if (config.autoload) {
|
|
16
|
+
if (config.services && !config.services[kServicesAutoloaded]) {
|
|
17
|
+
throw new errors.InvalidAutoloadWithServicesError()
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
const { exclude = [], mappings = {} } = config.autoload
|
|
17
21
|
let { path } = config.autoload
|
|
18
22
|
|
|
@@ -53,8 +57,6 @@ async function _transformConfig (configManager) {
|
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
configManager.current.allowCycles = !!configManager.current.allowCycles
|
|
57
|
-
|
|
58
60
|
configManager.current.serviceMap = new Map()
|
|
59
61
|
configManager.current.inspectorOptions = undefined
|
|
60
62
|
|
|
@@ -69,7 +71,6 @@ async function _transformConfig (configManager) {
|
|
|
69
71
|
service.entrypoint = service.id === config.entrypoint
|
|
70
72
|
service.hotReload = !!config.hotReload
|
|
71
73
|
service.dependencies = []
|
|
72
|
-
service.dependents = []
|
|
73
74
|
service.localServiceEnvVars = new Map()
|
|
74
75
|
service.localUrl = `http://${service.id}.plt.local`
|
|
75
76
|
|
|
@@ -85,126 +86,7 @@ async function _transformConfig (configManager) {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
configManager.current.services = services
|
|
88
|
-
|
|
89
|
-
await parseClientsAndComposer(configManager)
|
|
90
|
-
|
|
91
|
-
if (!configManager.current.allowCycles) {
|
|
92
|
-
topologicalSort(configManager)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function missingDependencyErrorMessage (clientName, service, configManager) {
|
|
97
|
-
const closestName = closest(clientName, [...configManager.current.serviceMap.keys()])
|
|
98
|
-
let errorMsg = `service '${service.id}' has unknown dependency: '${clientName}'.`
|
|
99
|
-
if (closestName) {
|
|
100
|
-
errorMsg += ` Did you mean '${closestName}'?`
|
|
101
|
-
}
|
|
102
|
-
return errorMsg
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function parseClientsAndComposer (configManager) {
|
|
106
|
-
for (const service of configManager.current.services) {
|
|
107
|
-
const cm = new ConfigManager({ source: service.config })
|
|
108
|
-
const configString = await cm.load()
|
|
109
|
-
const serviceConfig = cm._parser(configString)
|
|
110
|
-
|
|
111
|
-
async function parseConfigUrl (urlString) {
|
|
112
|
-
if (!urlString) {
|
|
113
|
-
return { url: null, envVar: null }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const url = await cm.replaceEnv(urlString)
|
|
118
|
-
return { url, envVar: null }
|
|
119
|
-
} catch (err) {
|
|
120
|
-
// The MissingValueError is an error coming from pupa
|
|
121
|
-
// https://github.com/sindresorhus/pupa#missingvalueerror
|
|
122
|
-
// All other errors are simply re-thrown.
|
|
123
|
-
if (err.name !== 'MissingValueError' || urlString !== `{${err.key}}`) {
|
|
124
|
-
throw err
|
|
125
|
-
}
|
|
126
|
-
return { url: null, envVar: err.key }
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function addServiceDependency (service, dependencyId, urlString) {
|
|
131
|
-
let { url, envVar } = await parseConfigUrl(urlString)
|
|
132
|
-
if (url !== null) {
|
|
133
|
-
service.dependencies.push({ id: dependencyId, url, local: false })
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const depService = configManager.current.serviceMap.get(dependencyId)
|
|
138
|
-
if (depService === undefined) {
|
|
139
|
-
throw new errors.MissingDependencyError(
|
|
140
|
-
missingDependencyErrorMessage(dependencyId, service, configManager)
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
url = `http://${dependencyId}.plt.local`
|
|
145
|
-
|
|
146
|
-
if (envVar !== null) {
|
|
147
|
-
service.localServiceEnvVars.set(envVar, url)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
depService.dependents.push(service.id)
|
|
151
|
-
service.dependencies.push({ id: dependencyId, url, local: true })
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const composedServices = serviceConfig.composer?.services
|
|
155
|
-
if (Array.isArray(composedServices)) {
|
|
156
|
-
await Promise.all(
|
|
157
|
-
composedServices.map(async (composedService) =>
|
|
158
|
-
addServiceDependency(
|
|
159
|
-
service,
|
|
160
|
-
composedService.id,
|
|
161
|
-
composedService.origin
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (Array.isArray(serviceConfig.clients)) {
|
|
168
|
-
await Promise.all(
|
|
169
|
-
serviceConfig.clients.map(async (client) => {
|
|
170
|
-
let clientServiceId = client.serviceId
|
|
171
|
-
if (!clientServiceId) {
|
|
172
|
-
try {
|
|
173
|
-
const clientPath = pathResolve(service.path, client.path)
|
|
174
|
-
const clientPackageJsonPath = join(clientPath, 'package.json')
|
|
175
|
-
const clientPackageJSONFile = await readFile(clientPackageJsonPath, 'utf8')
|
|
176
|
-
const clientPackageJSON = JSON.parse(clientPackageJSONFile)
|
|
177
|
-
clientServiceId = clientPackageJSON.name ?? ''
|
|
178
|
-
} catch (err) {
|
|
179
|
-
if (client.url === undefined || client.name === undefined) {
|
|
180
|
-
throw err
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
await addServiceDependency(service, clientServiceId, client.url)
|
|
185
|
-
})
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function topologicalSort (configManager) {
|
|
192
|
-
const { services } = configManager.current
|
|
193
|
-
const topo = new Topo.Sorter()
|
|
194
|
-
|
|
195
|
-
for (const service of services) {
|
|
196
|
-
const localDependencyIds = service.dependencies
|
|
197
|
-
.filter(dep => dep.local)
|
|
198
|
-
.map(dep => dep.id)
|
|
199
|
-
|
|
200
|
-
topo.add(service, {
|
|
201
|
-
group: service.id,
|
|
202
|
-
after: localDependencyIds,
|
|
203
|
-
manual: true
|
|
204
|
-
})
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
configManager.current.services = topo.sort()
|
|
89
|
+
configManager.current.services[kServicesAutoloaded] = true
|
|
208
90
|
}
|
|
209
91
|
|
|
210
92
|
async function platformaticRuntime () {
|
|
@@ -246,7 +128,6 @@ async function wrapConfigInRuntimeConfig ({ configManager, args }) {
|
|
|
246
128
|
const wrapperConfig = {
|
|
247
129
|
$schema: schema.$id,
|
|
248
130
|
entrypoint: serviceId,
|
|
249
|
-
allowCycles: false,
|
|
250
131
|
hotReload: true,
|
|
251
132
|
services: [
|
|
252
133
|
{
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Topo = require('@hapi/topo')
|
|
4
|
+
const { closest } = require('fastest-levenshtein')
|
|
5
|
+
const errors = require('./errors')
|
|
6
|
+
|
|
7
|
+
function missingDependencyErrorMessage (clientName, service, services) {
|
|
8
|
+
const closestName = closest(clientName, [...services.keys()])
|
|
9
|
+
let errorMsg = `service '${service.id}' has unknown dependency: '${clientName}'.`
|
|
10
|
+
if (closestName) {
|
|
11
|
+
errorMsg += ` Did you mean '${closestName}'?`
|
|
12
|
+
}
|
|
13
|
+
return errorMsg
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function checkDependencies (services) {
|
|
17
|
+
const allServices = new Set(services.map(s => s.id))
|
|
18
|
+
|
|
19
|
+
for (const service of services) {
|
|
20
|
+
for (const dependency of service.dependencies) {
|
|
21
|
+
if (dependency.local && !allServices.has(dependency.id)) {
|
|
22
|
+
throw new errors.MissingDependencyError(
|
|
23
|
+
missingDependencyErrorMessage(dependency.id, service, services)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function topologicalSort (services, config) {
|
|
31
|
+
const topo = new Topo.Sorter()
|
|
32
|
+
|
|
33
|
+
for (const service of config.services) {
|
|
34
|
+
const localDependencyIds = Array.from(service.dependencies)
|
|
35
|
+
.filter(dep => dep.local)
|
|
36
|
+
.map(dep => dep.id)
|
|
37
|
+
|
|
38
|
+
topo.add(service, {
|
|
39
|
+
group: service.id,
|
|
40
|
+
after: localDependencyIds,
|
|
41
|
+
manual: true
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
config.services = topo.sort()
|
|
46
|
+
|
|
47
|
+
return new Map(Array.from(services.entries()).sort((a, b) => {
|
|
48
|
+
if (a[0] === b[0]) {
|
|
49
|
+
return 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const aIndex = config.services.findIndex(s => s.id === a[0])
|
|
53
|
+
const bIndex = config.services.findIndex(s => s.id === b[0])
|
|
54
|
+
return aIndex - bIndex
|
|
55
|
+
}))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { checkDependencies, topologicalSort }
|
package/lib/errors.js
CHANGED
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
|
|
18
18
|
NoConfigFileFoundError: createError(`${ERROR_PREFIX}_NO_CONFIG_FILE_FOUND`, "No config file found for service '%s'"),
|
|
19
19
|
InvalidEntrypointError: createError(`${ERROR_PREFIX}_INVALID_ENTRYPOINT`, "Invalid entrypoint: '%s' does not exist"),
|
|
20
|
+
InvalidAutoloadWithServicesError: createError(`${ERROR_PREFIX}_INVALID_AUTOLOAD_WITH_SERVICES`, 'Autoload cannot be used when services is defined'),
|
|
20
21
|
MissingDependencyError: createError(`${ERROR_PREFIX}_MISSING_DEPENDENCY`, 'Missing dependency: "%s"'),
|
|
21
22
|
InspectAndInspectBrkError: createError(`${ERROR_PREFIX}_INSPECT_AND_INSPECT_BRK`, '--inspect and --inspect-brk cannot be used together'),
|
|
22
23
|
InspectorPortError: createError(`${ERROR_PREFIX}_INSPECTOR_PORT`, 'Inspector port must be 0 or in range 1024 to 65535'),
|
|
@@ -155,7 +155,6 @@ class RuntimeGenerator extends BaseGenerator {
|
|
|
155
155
|
const config = {
|
|
156
156
|
$schema: `https://platformatic.dev/schemas/v${this.platformaticVersion}/runtime`,
|
|
157
157
|
entrypoint: this.entryPoint.name,
|
|
158
|
-
allowCycles: false,
|
|
159
158
|
hotReload: true,
|
|
160
159
|
autoload: {
|
|
161
160
|
path: 'services',
|
package/lib/load-config.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Store, loadConfig } = require('@platformatic/config')
|
|
4
|
-
|
|
5
|
-
const { platformaticService } = require('@platformatic/service')
|
|
6
|
-
const { platformaticDB } = require('@platformatic/db')
|
|
7
|
-
const { platformaticComposer } = require('@platformatic/composer')
|
|
8
4
|
const { platformaticRuntime } = require('./config')
|
|
9
5
|
|
|
10
|
-
const store = new Store()
|
|
11
|
-
store.add(platformaticService)
|
|
12
|
-
store.add(platformaticDB)
|
|
13
|
-
store.add(platformaticComposer)
|
|
14
|
-
store.add(platformaticRuntime)
|
|
15
|
-
|
|
16
6
|
function _loadConfig (minimistConfig, args, overrides, replaceEnv = true) {
|
|
7
|
+
const store = new Store()
|
|
8
|
+
store.add(platformaticRuntime)
|
|
17
9
|
return loadConfig(minimistConfig, args, store, overrides, replaceEnv)
|
|
18
10
|
}
|
|
19
11
|
|
package/lib/schema.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
4
|
const telemetry = require('@platformatic/telemetry').schema
|
|
5
|
-
const { server } = require('@platformatic/
|
|
5
|
+
const { schemas: { server } } = require('@platformatic/utils')
|
|
6
6
|
const pkg = require('../package.json')
|
|
7
7
|
const version = 'v' + pkg.version
|
|
8
8
|
const platformaticRuntimeSchema = {
|
|
@@ -69,9 +69,6 @@ const platformaticRuntimeSchema = {
|
|
|
69
69
|
}
|
|
70
70
|
]
|
|
71
71
|
},
|
|
72
|
-
allowCycles: {
|
|
73
|
-
type: 'boolean'
|
|
74
|
-
},
|
|
75
72
|
inspectorOptions: {
|
|
76
73
|
type: 'object',
|
|
77
74
|
properties: {
|
package/lib/start.js
CHANGED
|
@@ -6,7 +6,6 @@ const { join, resolve, dirname } = require('node:path')
|
|
|
6
6
|
const { writeFile } = require('node:fs/promises')
|
|
7
7
|
const { pathToFileURL } = require('node:url')
|
|
8
8
|
const { Worker } = require('node:worker_threads')
|
|
9
|
-
const { start: serviceStart } = require('@platformatic/service')
|
|
10
9
|
const { printConfigValidationErrors } = require('@platformatic/config')
|
|
11
10
|
const closeWithGrace = require('close-with-grace')
|
|
12
11
|
const { loadConfig } = require('./load-config')
|
|
@@ -130,14 +129,14 @@ async function buildRuntime (configManager, env) {
|
|
|
130
129
|
async function start (args) {
|
|
131
130
|
const config = await loadConfig({}, args)
|
|
132
131
|
|
|
133
|
-
if (config.configType
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await app.start()
|
|
137
|
-
return app
|
|
132
|
+
if (config.configType !== 'runtime') {
|
|
133
|
+
const configManager = await wrapConfigInRuntimeConfig(config)
|
|
134
|
+
config.configManager = configManager
|
|
138
135
|
}
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
const app = await buildRuntime(config.configManager)
|
|
138
|
+
await app.start()
|
|
139
|
+
return app
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
async function setupAndStartRuntime (config) {
|
|
@@ -165,7 +164,7 @@ async function setupAndStartRuntime (config) {
|
|
|
165
164
|
startErr = err
|
|
166
165
|
if (err.code === 'EADDRINUSE') {
|
|
167
166
|
await runtime.close()
|
|
168
|
-
if (runtimeConfig.current
|
|
167
|
+
if (runtimeConfig.current?.server?.port > MAX_PORT) throw err
|
|
169
168
|
runtimeConfig.current.server.port++
|
|
170
169
|
runtime = await buildRuntime(runtimeConfig)
|
|
171
170
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,8 +34,11 @@
|
|
|
34
34
|
"typescript": "^5.4.2",
|
|
35
35
|
"undici-oidc-interceptor": "^0.5.0",
|
|
36
36
|
"why-is-node-running": "^2.2.2",
|
|
37
|
-
"@platformatic/sql-graphql": "
|
|
38
|
-
"@platformatic/sql-mapper": "
|
|
37
|
+
"@platformatic/sql-graphql": "2.0.0-alpha.2",
|
|
38
|
+
"@platformatic/sql-mapper": "2.0.0-alpha.2",
|
|
39
|
+
"@platformatic/composer": "2.0.0-alpha.2",
|
|
40
|
+
"@platformatic/service": "2.0.0-alpha.2",
|
|
41
|
+
"@platformatic/db": "2.0.0-alpha.2"
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"@fastify/error": "^3.4.1",
|
|
@@ -61,16 +64,12 @@
|
|
|
61
64
|
"semgrator": "^0.3.0",
|
|
62
65
|
"tail-file-stream": "^0.1.0",
|
|
63
66
|
"undici": "^6.9.0",
|
|
64
|
-
"why-is-node-running": "^2.2.2",
|
|
65
67
|
"ws": "^8.16.0",
|
|
66
|
-
"@platformatic/
|
|
67
|
-
"@platformatic/
|
|
68
|
-
"@platformatic/
|
|
69
|
-
"@platformatic/
|
|
70
|
-
"@platformatic/generators": "
|
|
71
|
-
"@platformatic/service": "1.51.8",
|
|
72
|
-
"@platformatic/telemetry": "1.51.8",
|
|
73
|
-
"@platformatic/utils": "1.51.8"
|
|
68
|
+
"@platformatic/config": "2.0.0-alpha.2",
|
|
69
|
+
"@platformatic/telemetry": "2.0.0-alpha.2",
|
|
70
|
+
"@platformatic/utils": "2.0.0-alpha.2",
|
|
71
|
+
"@platformatic/ts-compiler": "2.0.0-alpha.2",
|
|
72
|
+
"@platformatic/generators": "2.0.0-alpha.2"
|
|
74
73
|
},
|
|
75
74
|
"standard": {
|
|
76
75
|
"ignore": [
|
package/runtime.mjs
CHANGED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://platformatic.dev/schemas/v0.20.0/service",
|
|
3
|
-
"server": {
|
|
4
|
-
"hostname": "127.0.0.1",
|
|
5
|
-
"port": 0
|
|
6
|
-
},
|
|
7
|
-
"service": {
|
|
8
|
-
"openapi": true
|
|
9
|
-
},
|
|
10
|
-
"plugins": {
|
|
11
|
-
"paths": [
|
|
12
|
-
"plugin.js"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
"clients": [
|
|
16
|
-
{
|
|
17
|
-
"path": "./with-logger",
|
|
18
|
-
"url": "{PLT_WITH_LOGGER_URL}"
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|