@platformatic/runtime 2.0.0-alpha.2 → 2.0.0-alpha.4
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 +285 -0
- package/eslint.config.js +8 -0
- package/fixtures/botched-start/platformatic.runtime.json +1 -1
- package/fixtures/botched-start/services/a/platformatic.service.json +1 -1
- package/fixtures/composerApp/platformatic.composer.json +1 -1
- package/fixtures/configs/invalid-autoload-with-services.json +1 -1
- package/fixtures/configs/invalid-entrypoint.json +1 -1
- package/fixtures/configs/invalid-schema-type.config.json +1 -1
- package/fixtures/configs/missing-property.config.json +1 -1
- package/fixtures/configs/missing-service-config.json +1 -1
- package/fixtures/configs/monorepo-composer-no-autoload.json +2 -2
- package/fixtures/configs/monorepo-composer.json +2 -2
- package/fixtures/configs/monorepo-create-cycle.json +2 -2
- package/fixtures/configs/monorepo-missing-dependencies.json +2 -2
- package/fixtures/configs/monorepo-no-cycles.json +2 -2
- package/fixtures/configs/monorepo-openapi.json +2 -2
- package/fixtures/configs/{monorepo-hotreload-env.json → monorepo-watch-env.json} +2 -2
- package/fixtures/configs/monorepo-watch-single.json +12 -0
- package/fixtures/configs/monorepo-watch.json +26 -9
- package/fixtures/configs/monorepo-with-dependencies.json +2 -2
- package/fixtures/configs/monorepo-with-management-api-without-metrics.json +21 -0
- package/fixtures/configs/monorepo-with-management-api.json +2 -2
- package/fixtures/configs/{monorepo-hotreload.json → monorepo-with-metrics.json} +5 -4
- package/fixtures/configs/monorepo.json +2 -2
- package/fixtures/configs/no-services.config.json +1 -1
- package/fixtures/configs/no-sources.config.json +1 -1
- package/fixtures/configs/service-throws-on-start.json +1 -1
- package/fixtures/configs/service-with-env-port.json +2 -2
- package/fixtures/configs/service-with-stdio.json +12 -0
- package/fixtures/configs/{hotreload.json → watch.json} +2 -2
- package/fixtures/crash-on-bootstrap/platformatic.runtime.json +15 -0
- package/fixtures/crash-on-bootstrap/services/service-1/platformatic.service.json +14 -0
- package/fixtures/crash-on-bootstrap/services/service-1/plugin.js +5 -0
- package/fixtures/crash-on-bootstrap/services/service-2/platformatic.service.json +14 -0
- package/fixtures/crash-on-bootstrap/services/service-2/plugin.js +5 -0
- package/fixtures/dbApp/platformatic.db.json +1 -1
- package/fixtures/dbAppNoName/platformatic.db.json +1 -1
- package/fixtures/dbAppNoPackageJson/platformatic.db.json +1 -1
- package/fixtures/dbAppWithMigrationError/platformatic.db.json +1 -1
- package/fixtures/do-not-reload-dependencies/platformatic.service.json +1 -1
- package/fixtures/do-not-restart-on-crash/platformatic.runtime.json +3 -2
- package/fixtures/do-not-restart-on-crash/services/a/platformatic.service.json +1 -1
- package/fixtures/express/platformatic.runtime.json +1 -1
- package/fixtures/express/services/a/platformatic.service.json +1 -1
- package/fixtures/express/services/b/platformatic.service.json +1 -1
- package/fixtures/external-client/platformatic.service.json +1 -1
- package/fixtures/interceptors/idp.js +2 -2
- package/fixtures/interceptors/platformatic.runtime.json +1 -1
- package/fixtures/interceptors/services/a/platformatic.service.json +1 -1
- package/fixtures/interceptors-2/platformatic.runtime.json +1 -1
- package/fixtures/interceptors-2/services/a/platformatic.service.json +1 -1
- package/fixtures/leven/platformatic.runtime.json +2 -2
- package/fixtures/leven/services/deeply-spittle/platformatic.service.json +1 -1
- package/fixtures/leven/services/rainy-empire/platformatic.composer.json +1 -1
- package/fixtures/management-api/platformatic.json +3 -3
- package/fixtures/management-api/services/service-1/platformatic.json +1 -1
- package/fixtures/management-api/services/service-1/plugin.js +4 -3
- package/fixtures/management-api/services/service-2/platformatic.json +1 -1
- package/fixtures/management-api/services/service-db/platformatic.db.json +1 -1
- package/fixtures/management-api-custom-labels/platformatic.json +2 -2
- package/fixtures/management-api-custom-labels/services/service-1/platformatic.json +1 -1
- package/fixtures/management-api-custom-labels/services/service-1/plugin.js +4 -3
- package/fixtures/management-api-custom-labels/services/service-2/platformatic.json +1 -1
- package/fixtures/management-api-custom-labels/services/service-db/platformatic.db.json +1 -1
- package/fixtures/management-api-without-metrics/platformatic.json +3 -2
- package/fixtures/management-api-without-metrics/services/service-1/platformatic.json +1 -1
- package/fixtures/monorepo/composerApp/platformatic.composer.json +1 -1
- package/fixtures/monorepo/dbApp/platformatic.db.json +1 -1
- package/fixtures/monorepo/serviceApp/platformatic.service.json +3 -2
- package/fixtures/monorepo/serviceApp/with-logger/with-logger.cjs +2 -2
- package/fixtures/monorepo/serviceApp/with-logger/with-logger.d.ts +7 -7
- package/fixtures/monorepo/serviceAppWithLogger/platformatic.service.json +1 -1
- package/fixtures/monorepo/serviceAppWithLogger/plugin.js +12 -0
- package/fixtures/monorepo/serviceAppWithMultiplePlugins/platformatic.service.json +3 -2
- package/fixtures/monorepo-missing-dependencies/composer/platformatic.json +1 -1
- package/fixtures/monorepo-openapi/serviceAppWithoutOpenapi/platformatic.service.json +1 -1
- package/fixtures/monorepo-watch/service1/platformatic.service.json +1 -1
- package/fixtures/monorepo-with-dependencies/main/platformatic.json +1 -1
- package/fixtures/monorepo-with-dependencies/service-1/platformatic.json +1 -1
- package/fixtures/monorepo-with-dependencies/service-2/platformatic.json +1 -1
- package/fixtures/no-env.service.json +1 -1
- package/fixtures/preload/platformatic.runtime.json +1 -1
- package/fixtures/preload/services/a/platformatic.service.json +1 -1
- package/fixtures/prom-server/platformatic.json +2 -2
- package/fixtures/prom-server/services/service-1/platformatic.json +1 -1
- package/fixtures/prom-server/services/service-2/platformatic.json +1 -1
- package/fixtures/restart-on-crash/platformatic.runtime.json +1 -1
- package/fixtures/restart-on-crash/services/a/platformatic.service.json +1 -1
- package/fixtures/sample-runtime/package.json +1 -1
- package/fixtures/sample-runtime/platformatic.json +2 -2
- package/fixtures/sample-runtime/services/rival/package.json +1 -1
- package/fixtures/sample-runtime/services/rival/platformatic.json +1 -1
- package/fixtures/sample-runtime-with-2-services/package.json +1 -1
- package/fixtures/sample-runtime-with-2-services/platformatic.json +2 -2
- package/fixtures/sample-runtime-with-2-services/services/foobar/package.json +1 -1
- package/fixtures/sample-runtime-with-2-services/services/foobar/platformatic.json +1 -1
- package/fixtures/sample-runtime-with-2-services/services/rival/package.json +1 -1
- package/fixtures/sample-runtime-with-2-services/services/rival/platformatic.json +1 -1
- package/fixtures/server/logger-transport/platformatic.runtime.json +2 -2
- package/fixtures/server/logger-transport/services/echo/platformatic.service.json +1 -1
- package/fixtures/server/overrides-service/platformatic.runtime.json +2 -2
- package/fixtures/server/overrides-service/services/echo/platformatic.service.json +1 -1
- package/fixtures/server/runtime-server/platformatic.runtime.json +2 -2
- package/fixtures/server/runtime-server/services/echo/platformatic.service.json +1 -1
- package/fixtures/serviceAppThrowsOnStart/platformatic.service.json +1 -1
- package/fixtures/stackables/node_modules/foo/foo.js +2 -1
- package/fixtures/start-command-in-runtime.js +1 -1
- package/fixtures/stdio/platformatic.service.json +6 -0
- package/fixtures/stdio/plugin.js +24 -0
- package/fixtures/telemetry/platformatic.runtime.json +2 -2
- package/fixtures/telemetry/services/echo/platformatic.service.json +1 -1
- package/fixtures/telemetry/services/echo/routes/span.js +16 -2
- package/fixtures/telemetry/services/service-1/platformatic.service.json +19 -0
- package/fixtures/telemetry/services/service-1/routes/echo.js +7 -0
- package/fixtures/typescript/platformatic.runtime.json +2 -2
- package/fixtures/typescript/services/composer/platformatic.composer.json +1 -1
- package/fixtures/typescript/services/movies/global.d.ts +2 -3
- package/fixtures/typescript/services/movies/platformatic.db.json +1 -1
- package/fixtures/typescript/services/movies/types/Movie.d.ts +3 -3
- package/fixtures/typescript/services/movies/types/index.d.ts +6 -6
- package/fixtures/typescript/services/titles/client/client.d.ts +35 -35
- package/fixtures/typescript/services/titles/platformatic.service.json +1 -1
- package/fixtures/typescript-custom-flags/platformatic.runtime.json +2 -2
- package/fixtures/typescript-custom-flags/services/composer/platformatic.composer.json +1 -1
- package/fixtures/typescript-custom-flags/services/movies/global.d.ts +2 -3
- package/fixtures/typescript-custom-flags/services/movies/platformatic.db.json +1 -1
- package/fixtures/typescript-custom-flags/services/movies/types/Movie.d.ts +3 -3
- package/fixtures/typescript-custom-flags/services/movies/types/index.d.ts +6 -6
- package/fixtures/typescript-custom-flags/services/titles/client/client.d.ts +35 -35
- package/fixtures/typescript-custom-flags/services/titles/platformatic.service.json +1 -1
- package/fixtures/typescript-no-env/platformatic.runtime.json +2 -2
- package/fixtures/typescript-no-env/services/composer/platformatic.composer.json +1 -1
- package/fixtures/typescript-no-env/services/movies/global.d.ts +2 -3
- package/fixtures/typescript-no-env/services/movies/platformatic.db.json +1 -1
- package/fixtures/typescript-no-env/services/movies/types/Movie.d.ts +3 -3
- package/fixtures/typescript-no-env/services/movies/types/index.d.ts +6 -6
- package/fixtures/typescript-no-env/services/titles/client/client.d.ts +35 -35
- package/fixtures/typescript-no-env/services/titles/platformatic.service.json +1 -1
- package/index.d.ts +7 -8
- package/index.js +14 -10
- package/index.test-d.ts +10 -12
- package/lib/build-server.js +5 -11
- package/lib/compile.js +11 -10
- package/lib/config.js +21 -14
- package/lib/dependencies.js +2 -1
- package/lib/errors.js +3 -2
- package/lib/generator/errors.js +1 -1
- package/lib/generator/runtime-generator.d.ts +15 -15
- package/lib/generator/runtime-generator.js +92 -63
- package/lib/logger.js +55 -0
- package/lib/management-api.js +29 -44
- package/lib/prom-server.js +5 -9
- package/lib/runtime.js +955 -0
- package/lib/schema.js +79 -76
- package/lib/start.js +35 -113
- package/lib/upgrade.js +4 -3
- package/lib/utils.js +49 -1
- package/lib/versions/v1.36.0.js +1 -1
- package/lib/versions/v1.5.0.js +1 -1
- package/lib/versions/v2.0.0.js +17 -0
- package/lib/worker/app.js +250 -0
- package/lib/worker/default-stackable.js +27 -0
- package/lib/worker/itc.js +128 -0
- package/lib/worker/main.js +127 -0
- package/lib/worker/symbols.js +7 -0
- package/package.json +25 -25
- package/runtime.mjs +4 -4
- package/schema.json +824 -0
- package/lib/api-client.js +0 -500
- package/lib/api.js +0 -420
- package/lib/app.js +0 -397
- package/lib/load-config.js +0 -12
- package/lib/loader.mjs +0 -103
- package/lib/message-port-writable.js +0 -50
- package/lib/worker.js +0 -182
- /package/lib/{interceptors.js → worker/interceptors.js} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events')
|
|
4
|
+
const { FileWatcher } = require('@platformatic/utils')
|
|
5
|
+
const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
|
|
6
|
+
const debounce = require('debounce')
|
|
7
|
+
|
|
8
|
+
const errors = require('../errors')
|
|
9
|
+
const defaultStackable = require('./default-stackable')
|
|
10
|
+
const { getServiceUrl, loadConfig, loadEmptyConfig } = require('../utils')
|
|
11
|
+
|
|
12
|
+
class PlatformaticApp extends EventEmitter {
|
|
13
|
+
#starting
|
|
14
|
+
#started
|
|
15
|
+
#listening
|
|
16
|
+
#watch
|
|
17
|
+
#fileWatcher
|
|
18
|
+
#debouncedRestart
|
|
19
|
+
#context
|
|
20
|
+
|
|
21
|
+
constructor (appConfig, telemetryConfig, serverConfig, hasManagementApi, watch, metricsConfig) {
|
|
22
|
+
super()
|
|
23
|
+
this.appConfig = appConfig
|
|
24
|
+
this.#watch = watch
|
|
25
|
+
this.#starting = false
|
|
26
|
+
this.#started = false
|
|
27
|
+
this.#listening = false
|
|
28
|
+
this.stackable = null
|
|
29
|
+
this.#fileWatcher = null
|
|
30
|
+
|
|
31
|
+
this.#context = {
|
|
32
|
+
serviceId: this.appConfig.id,
|
|
33
|
+
directory: this.appConfig.path,
|
|
34
|
+
isEntrypoint: this.appConfig.entrypoint,
|
|
35
|
+
isProduction: false,
|
|
36
|
+
telemetryConfig,
|
|
37
|
+
metricsConfig,
|
|
38
|
+
serverConfig,
|
|
39
|
+
hasManagementApi: !!hasManagementApi,
|
|
40
|
+
localServiceEnvVars: this.appConfig.localServiceEnvVars,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getStatus () {
|
|
45
|
+
if (this.#starting) return 'starting'
|
|
46
|
+
if (this.#started) return 'started'
|
|
47
|
+
return 'stopped'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async updateContext (context) {
|
|
51
|
+
this.#context = { ...this.#context, ...context }
|
|
52
|
+
if (this.stackable) {
|
|
53
|
+
this.stackable.updateContext(context)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getBootstrapDependencies () {
|
|
58
|
+
return this.stackable.getBootstrapDependencies()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async init () {
|
|
62
|
+
try {
|
|
63
|
+
const appConfig = this.appConfig
|
|
64
|
+
let loadedConfig
|
|
65
|
+
|
|
66
|
+
if (!appConfig.config) {
|
|
67
|
+
loadedConfig = await loadEmptyConfig(
|
|
68
|
+
appConfig.path,
|
|
69
|
+
{
|
|
70
|
+
onMissingEnv: this.#fetchServiceUrl,
|
|
71
|
+
context: appConfig,
|
|
72
|
+
},
|
|
73
|
+
true
|
|
74
|
+
)
|
|
75
|
+
} else {
|
|
76
|
+
loadedConfig = await loadConfig(
|
|
77
|
+
{},
|
|
78
|
+
['-c', appConfig.config],
|
|
79
|
+
{
|
|
80
|
+
onMissingEnv: this.#fetchServiceUrl,
|
|
81
|
+
context: appConfig,
|
|
82
|
+
},
|
|
83
|
+
true
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const app = loadedConfig.app
|
|
88
|
+
|
|
89
|
+
const stackable = await app.buildStackable({
|
|
90
|
+
onMissingEnv: this.#fetchServiceUrl,
|
|
91
|
+
config: this.appConfig.config,
|
|
92
|
+
context: this.#context,
|
|
93
|
+
})
|
|
94
|
+
this.stackable = this.#wrapStackable(stackable)
|
|
95
|
+
|
|
96
|
+
this.#updateDispatcher()
|
|
97
|
+
} catch (err) {
|
|
98
|
+
this.#logAndExit(err)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async start () {
|
|
103
|
+
if (this.#starting || this.#started) {
|
|
104
|
+
throw new errors.ApplicationAlreadyStartedError()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.#starting = true
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await this.stackable.init()
|
|
111
|
+
} catch (err) {
|
|
112
|
+
this.#logAndExit(err)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (this.#watch) {
|
|
116
|
+
const watchConfig = await this.stackable.getWatchConfig()
|
|
117
|
+
if (watchConfig.enabled !== false) {
|
|
118
|
+
/* c8 ignore next 4 */
|
|
119
|
+
this.#debouncedRestart = debounce(() => {
|
|
120
|
+
this.stackable.log({ message: 'files changed', level: 'debug' })
|
|
121
|
+
this.emit('changed')
|
|
122
|
+
}, 100) // debounce restart for 100ms
|
|
123
|
+
|
|
124
|
+
this.#startFileWatching(watchConfig)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const listen = !!this.appConfig.useHttp
|
|
129
|
+
try {
|
|
130
|
+
await this.stackable.start({ listen })
|
|
131
|
+
this.#listening = listen
|
|
132
|
+
/* c8 ignore next 5 */
|
|
133
|
+
} catch (err) {
|
|
134
|
+
this.stackable.log({ message: err.message, level: 'debug' })
|
|
135
|
+
this.#starting = false
|
|
136
|
+
throw err
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.#started = true
|
|
140
|
+
this.#starting = false
|
|
141
|
+
this.emit('start')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async stop () {
|
|
145
|
+
if (!this.#started || this.#starting) {
|
|
146
|
+
throw new errors.ApplicationNotStartedError()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await this.#stopFileWatching()
|
|
150
|
+
await this.stackable.stop()
|
|
151
|
+
|
|
152
|
+
this.#started = false
|
|
153
|
+
this.#starting = false
|
|
154
|
+
this.#listening = false
|
|
155
|
+
this.emit('stop')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async listen () {
|
|
159
|
+
// This server is not an entrypoint or already listened in start. Behave as no-op.
|
|
160
|
+
if (!this.appConfig.entrypoint || this.appConfig.useHttp || this.#listening) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
await this.stackable.start({ listen: true })
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#fetchServiceUrl (key, { parent, context: service }) {
|
|
168
|
+
if (service.localServiceEnvVars.has(key)) {
|
|
169
|
+
return service.localServiceEnvVars.get(key)
|
|
170
|
+
} else if (!key.endsWith('_URL') || !parent.serviceId) {
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return getServiceUrl(parent.serviceId)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#startFileWatching (watch) {
|
|
178
|
+
if (this.#fileWatcher) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const fileWatcher = new FileWatcher({
|
|
183
|
+
path: watch.path,
|
|
184
|
+
/* c8 ignore next 2 */
|
|
185
|
+
allowToWatch: watch?.allow,
|
|
186
|
+
watchIgnore: watch?.ignore || [],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
fileWatcher.on('update', this.#debouncedRestart)
|
|
190
|
+
|
|
191
|
+
fileWatcher.startWatching()
|
|
192
|
+
this.stackable.log({ message: 'start watching files', level: 'debug' })
|
|
193
|
+
this.#fileWatcher = fileWatcher
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async #stopFileWatching () {
|
|
197
|
+
const watcher = this.#fileWatcher
|
|
198
|
+
|
|
199
|
+
if (watcher) {
|
|
200
|
+
this.stackable.log({ message: 'stop watching files', level: 'debug' })
|
|
201
|
+
await watcher.stopWatching()
|
|
202
|
+
this.#fileWatcher = null
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#logAndExit (err) {
|
|
207
|
+
// Runtime logs here with console.error because stackable is not initialized
|
|
208
|
+
console.error(
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
msg: err.message,
|
|
211
|
+
name: this.appConfig.id,
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
process.exit(1)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#wrapStackable (stackable) {
|
|
218
|
+
const newStackable = {}
|
|
219
|
+
for (const method of Object.keys(defaultStackable)) {
|
|
220
|
+
newStackable[method] = stackable[method]
|
|
221
|
+
? stackable[method].bind(stackable)
|
|
222
|
+
: defaultStackable[method]
|
|
223
|
+
}
|
|
224
|
+
return newStackable
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#updateDispatcher () {
|
|
228
|
+
const telemetryConfig = this.#context.telemetryConfig
|
|
229
|
+
const telemetryId = telemetryConfig?.serviceName
|
|
230
|
+
|
|
231
|
+
const interceptor = dispatch => {
|
|
232
|
+
return function InterceptedDispatch (opts, handler) {
|
|
233
|
+
if (telemetryId) {
|
|
234
|
+
opts.headers = {
|
|
235
|
+
...opts.headers,
|
|
236
|
+
'x-plt-telemetry-id': telemetryId,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return dispatch(opts, handler)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const dispatcher = getGlobalDispatcher()
|
|
244
|
+
.compose(interceptor)
|
|
245
|
+
|
|
246
|
+
setGlobalDispatcher(dispatcher)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = { PlatformaticApp }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const defaultStackable = {
|
|
4
|
+
init: () => {},
|
|
5
|
+
start: () => {
|
|
6
|
+
throw new Error('Stackable start not implemented')
|
|
7
|
+
},
|
|
8
|
+
stop: () => {},
|
|
9
|
+
getUrl: () => null,
|
|
10
|
+
updateContext: () => {},
|
|
11
|
+
getConfig: () => null,
|
|
12
|
+
getInfo: () => null,
|
|
13
|
+
getDispatchFunc: () => null,
|
|
14
|
+
getOpenapiSchema: () => null,
|
|
15
|
+
getGraphqlSchema: () => null,
|
|
16
|
+
getMetrics: () => null,
|
|
17
|
+
inject: () => {
|
|
18
|
+
throw new Error('Stackable inject not implemented')
|
|
19
|
+
},
|
|
20
|
+
log: ({ message }) => {
|
|
21
|
+
console.log(message)
|
|
22
|
+
},
|
|
23
|
+
getBootstrapDependencies: () => [],
|
|
24
|
+
getWatchConfig: () => ({ enabled: false }),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = defaultStackable
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { once } = require('node:events')
|
|
4
|
+
const { parentPort } = require('node:worker_threads')
|
|
5
|
+
|
|
6
|
+
const { ITC } = require('@platformatic/itc')
|
|
7
|
+
|
|
8
|
+
const errors = require('../errors')
|
|
9
|
+
const { kITC, kId } = require('./symbols')
|
|
10
|
+
|
|
11
|
+
async function sendViaITC (worker, name, message) {
|
|
12
|
+
try {
|
|
13
|
+
// Make sure to catch when the worker exits, otherwise we're stuck forever
|
|
14
|
+
const ac = new AbortController()
|
|
15
|
+
let exitCode
|
|
16
|
+
|
|
17
|
+
const response = await Promise.race([
|
|
18
|
+
worker[kITC].send(name, message),
|
|
19
|
+
once(worker, 'exit', { signal: ac.signal }).then(([code]) => {
|
|
20
|
+
exitCode = code
|
|
21
|
+
}),
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
if (typeof exitCode === 'number') {
|
|
25
|
+
throw new errors.ServiceExitedError(worker[kId], exitCode)
|
|
26
|
+
} else {
|
|
27
|
+
ac.abort()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return response
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (!error.handlerError) {
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (error.handlerErrorCode && !error.handlerError.code) {
|
|
37
|
+
error.handlerError.code = error.handlerErrorCode
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw error.handlerError
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setupITC (app, service, dispatcher) {
|
|
45
|
+
const itc = new ITC({ port: parentPort })
|
|
46
|
+
|
|
47
|
+
itc.handle('start', async () => {
|
|
48
|
+
const status = app.getStatus()
|
|
49
|
+
|
|
50
|
+
if (status === 'starting') {
|
|
51
|
+
await once(app, 'start')
|
|
52
|
+
} else {
|
|
53
|
+
await app.start()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (service.entrypoint) {
|
|
57
|
+
await app.listen()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const url = app.stackable.getUrl()
|
|
61
|
+
|
|
62
|
+
const dispatchFunc = await app.stackable.getDispatchFunc()
|
|
63
|
+
dispatcher.replaceServer(url ?? dispatchFunc)
|
|
64
|
+
|
|
65
|
+
return service.entrypoint ? url : null
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
itc.handle('stop', async () => {
|
|
69
|
+
const status = app.getStatus()
|
|
70
|
+
|
|
71
|
+
if (status === 'starting') {
|
|
72
|
+
await once(app, 'start')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (status !== 'stopped') {
|
|
76
|
+
await app.stop()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
dispatcher.interceptor.close()
|
|
80
|
+
itc.close()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
itc.handle('getStatus', async () => {
|
|
84
|
+
return app.getStatus()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
itc.handle('getServiceInfo', async () => {
|
|
88
|
+
return app.stackable.getInfo()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
itc.handle('getServiceConfig', async () => {
|
|
92
|
+
const current = await app.stackable.getConfig()
|
|
93
|
+
// Remove all undefined keys from the config
|
|
94
|
+
return JSON.parse(JSON.stringify(current))
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
itc.handle('getServiceOpenAPISchema', async () => {
|
|
98
|
+
try {
|
|
99
|
+
return app.stackable.getOpenapiSchema()
|
|
100
|
+
} catch (err) {
|
|
101
|
+
throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
itc.handle('getServiceGraphQLSchema', async () => {
|
|
106
|
+
try {
|
|
107
|
+
return app.stackable.getGraphqlSchema()
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
itc.handle('getMetrics', async format => {
|
|
114
|
+
return app.stackable.getMetrics({ format })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
itc.handle('inject', async injectParams => {
|
|
118
|
+
return app.stackable.inject(injectParams)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
app.on('changed', () => {
|
|
122
|
+
itc.notify('changed')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return itc
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { sendViaITC, setupITC }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { createRequire } = require('node:module')
|
|
4
|
+
const { join } = require('node:path')
|
|
5
|
+
const { setTimeout: sleep } = require('node:timers/promises')
|
|
6
|
+
const { parentPort, workerData, threadId } = require('node:worker_threads')
|
|
7
|
+
const { pathToFileURL } = require('node:url')
|
|
8
|
+
|
|
9
|
+
const pino = require('pino')
|
|
10
|
+
const { fetch, setGlobalDispatcher, Agent } = require('undici')
|
|
11
|
+
const { wire } = require('undici-thread-interceptor')
|
|
12
|
+
|
|
13
|
+
const { PlatformaticApp } = require('./app')
|
|
14
|
+
const { setupITC } = require('./itc')
|
|
15
|
+
const loadInterceptors = require('./interceptors')
|
|
16
|
+
const { MessagePortWritable, createPinoWritable } = require('@platformatic/utils')
|
|
17
|
+
const { kId, kITC } = require('./symbols')
|
|
18
|
+
|
|
19
|
+
process.on('uncaughtException', handleUnhandled.bind(null, 'uncaught exception'))
|
|
20
|
+
process.on('unhandledRejection', handleUnhandled.bind(null, 'unhandled rejection'))
|
|
21
|
+
|
|
22
|
+
globalThis.fetch = fetch
|
|
23
|
+
globalThis[kId] = threadId
|
|
24
|
+
|
|
25
|
+
let app
|
|
26
|
+
const config = workerData.config
|
|
27
|
+
const logger = createLogger()
|
|
28
|
+
|
|
29
|
+
function handleUnhandled (type, err) {
|
|
30
|
+
logger.error({ err }, `application ${type}`)
|
|
31
|
+
|
|
32
|
+
Promise.race([app?.stop(), sleep(1000, 'timeout', { ref: false })])
|
|
33
|
+
.catch()
|
|
34
|
+
.finally(() => {
|
|
35
|
+
process.exit(1)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createLogger () {
|
|
40
|
+
const destination = new MessagePortWritable({ port: workerData.loggingPort })
|
|
41
|
+
const loggerInstance = pino({ level: 'trace' }, destination)
|
|
42
|
+
|
|
43
|
+
Reflect.defineProperty(process, 'stdout', { value: createPinoWritable(loggerInstance, 'info') })
|
|
44
|
+
Reflect.defineProperty(process, 'stderr', { value: createPinoWritable(loggerInstance, 'error') })
|
|
45
|
+
|
|
46
|
+
return loggerInstance
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function main () {
|
|
50
|
+
if (config.preload) {
|
|
51
|
+
await import(pathToFileURL(config.preload))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const service = workerData.serviceConfig
|
|
55
|
+
|
|
56
|
+
// Setup undici
|
|
57
|
+
const interceptors = {}
|
|
58
|
+
const composedInterceptors = []
|
|
59
|
+
|
|
60
|
+
if (config.undici?.interceptors) {
|
|
61
|
+
const _require = createRequire(join(workerData.dirname, 'package.json'))
|
|
62
|
+
for (const key of ['Agent', 'Pool', 'Client']) {
|
|
63
|
+
if (config.undici.interceptors[key]) {
|
|
64
|
+
interceptors[key] = await loadInterceptors(_require, config.undici.interceptors[key])
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Array.isArray(config.undici.interceptors)) {
|
|
69
|
+
composedInterceptors.push(...(await loadInterceptors(_require, config.undici.interceptors)))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const globalDispatcher = new Agent({
|
|
74
|
+
...config.undici,
|
|
75
|
+
interceptors,
|
|
76
|
+
}).compose(composedInterceptors)
|
|
77
|
+
|
|
78
|
+
setGlobalDispatcher(globalDispatcher)
|
|
79
|
+
|
|
80
|
+
// Setup mesh networker
|
|
81
|
+
const threadDispatcher = wire({ port: parentPort, useNetwork: service.useHttp })
|
|
82
|
+
|
|
83
|
+
// If the service is an entrypoint and runtime server config is defined, use it.
|
|
84
|
+
let serverConfig = null
|
|
85
|
+
if (config.server && service.entrypoint) {
|
|
86
|
+
serverConfig = config.server
|
|
87
|
+
} else if (service.useHttp) {
|
|
88
|
+
serverConfig = {
|
|
89
|
+
port: 0,
|
|
90
|
+
hostname: '127.0.0.1',
|
|
91
|
+
keepAliveTimeout: 5000,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let telemetryConfig = config.telemetry
|
|
96
|
+
if (telemetryConfig) {
|
|
97
|
+
telemetryConfig = {
|
|
98
|
+
...telemetryConfig,
|
|
99
|
+
serviceName: `${telemetryConfig.serviceName}-${service.id}`,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create the application
|
|
104
|
+
app = new PlatformaticApp(
|
|
105
|
+
service,
|
|
106
|
+
telemetryConfig,
|
|
107
|
+
serverConfig,
|
|
108
|
+
!!config.managementApi,
|
|
109
|
+
!!config.watch,
|
|
110
|
+
config.metrics
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
await app.init()
|
|
114
|
+
|
|
115
|
+
// Setup interaction with parent port
|
|
116
|
+
const itc = setupITC(app, service, threadDispatcher)
|
|
117
|
+
|
|
118
|
+
// Get the dependencies
|
|
119
|
+
const dependencies = config.autoload ? await app.getBootstrapDependencies() : []
|
|
120
|
+
itc.notify('init', { dependencies })
|
|
121
|
+
itc.listen()
|
|
122
|
+
|
|
123
|
+
globalThis[kITC] = itc
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// No need to catch this because there is the unhadledRejection handler on top.
|
|
127
|
+
main()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const kConfig = Symbol.for('plt.runtime.config')
|
|
4
|
+
const kId = Symbol.for('plt.runtime.id') // This is also used to detect if we are running in a Platformatic runtime thread
|
|
5
|
+
const kITC = Symbol.for('plt.runtime.itc')
|
|
6
|
+
|
|
7
|
+
module.exports = { kConfig, kId, kITC }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,26 +19,26 @@
|
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@fastify/express": "^3.0.0",
|
|
21
21
|
"@fastify/formbody": "^7.4.0",
|
|
22
|
-
"
|
|
23
|
-
"borp": "^0.16.0",
|
|
22
|
+
"borp": "^0.17.0",
|
|
24
23
|
"c8": "^10.0.0",
|
|
24
|
+
"eslint": "9",
|
|
25
25
|
"execa": "^8.0.1",
|
|
26
26
|
"express": "^4.18.3",
|
|
27
27
|
"fast-jwt": "^4.0.0",
|
|
28
28
|
"get-port": "^7.1.0",
|
|
29
|
+
"json-schema-to-typescript": "^15.0.0",
|
|
30
|
+
"neostandard": "^0.11.1",
|
|
29
31
|
"pino-abstract-transport": "^1.1.0",
|
|
30
|
-
"snazzy": "^9.0.0",
|
|
31
32
|
"split2": "^4.2.0",
|
|
32
|
-
"standard": "^17.1.0",
|
|
33
33
|
"tsd": "^0.31.0",
|
|
34
|
-
"typescript": "^5.4
|
|
34
|
+
"typescript": "^5.5.4",
|
|
35
35
|
"undici-oidc-interceptor": "^0.5.0",
|
|
36
36
|
"why-is-node-running": "^2.2.2",
|
|
37
|
-
"@platformatic/
|
|
38
|
-
"@platformatic/
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
37
|
+
"@platformatic/composer": "2.0.0-alpha.4",
|
|
38
|
+
"@platformatic/db": "2.0.0-alpha.4",
|
|
39
|
+
"@platformatic/service": "2.0.0-alpha.4",
|
|
40
|
+
"@platformatic/sql-graphql": "2.0.0-alpha.4",
|
|
41
|
+
"@platformatic/sql-mapper": "2.0.0-alpha.4"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@fastify/error": "^3.4.1",
|
|
@@ -62,24 +62,24 @@
|
|
|
62
62
|
"pino-pretty": "^11.0.0",
|
|
63
63
|
"pino-roll": "^1.0.0",
|
|
64
64
|
"semgrator": "^0.3.0",
|
|
65
|
-
"tail-file-stream": "^0.
|
|
65
|
+
"tail-file-stream": "^0.2.0",
|
|
66
66
|
"undici": "^6.9.0",
|
|
67
|
+
"undici-thread-interceptor": "^0.5.0",
|
|
67
68
|
"ws": "^8.16.0",
|
|
68
|
-
"@platformatic/config": "2.0.0-alpha.
|
|
69
|
-
"@platformatic/
|
|
70
|
-
"@platformatic/
|
|
71
|
-
"@platformatic/
|
|
72
|
-
"@platformatic/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"ignore": [
|
|
76
|
-
"**/dist/*",
|
|
77
|
-
"**/test/tmp"
|
|
78
|
-
]
|
|
69
|
+
"@platformatic/config": "2.0.0-alpha.4",
|
|
70
|
+
"@platformatic/basic": "2.0.0-alpha.4",
|
|
71
|
+
"@platformatic/generators": "2.0.0-alpha.4",
|
|
72
|
+
"@platformatic/itc": "2.0.0-alpha.4",
|
|
73
|
+
"@platformatic/telemetry": "2.0.0-alpha.4",
|
|
74
|
+
"@platformatic/ts-compiler": "2.0.0-alpha.4",
|
|
75
|
+
"@platformatic/utils": "2.0.0-alpha.4"
|
|
79
76
|
},
|
|
80
77
|
"scripts": {
|
|
81
78
|
"test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
|
|
82
|
-
"coverage": "npm run lint && borp -X=fixtures -X=test -C --concurrency=1 --timeout=
|
|
83
|
-
"
|
|
79
|
+
"coverage": "npm run lint && borp -X=fixtures -X=test -C --concurrency=1 --timeout=180000 && tsd",
|
|
80
|
+
"gen-schema": "node lib/schema.js > schema.json",
|
|
81
|
+
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
82
|
+
"build": "pnpm run gen-schema && pnpm run gen-types",
|
|
83
|
+
"lint": "eslint"
|
|
84
84
|
}
|
|
85
85
|
}
|
package/runtime.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export const compile = compileCmd
|
|
|
14
14
|
const help = helpMe({
|
|
15
15
|
dir: join(import.meta.url, 'help'),
|
|
16
16
|
// the default
|
|
17
|
-
ext: '.txt'
|
|
17
|
+
ext: '.txt',
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
const program = commist({ maxDistance: 2 })
|
|
@@ -28,8 +28,8 @@ program.register('compile', compile)
|
|
|
28
28
|
export async function run (argv) {
|
|
29
29
|
const args = parseArgs(argv, {
|
|
30
30
|
alias: {
|
|
31
|
-
v: 'version'
|
|
32
|
-
}
|
|
31
|
+
v: 'version',
|
|
32
|
+
},
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
if (args.version) {
|
|
@@ -40,7 +40,7 @@ export async function run (argv) {
|
|
|
40
40
|
/* c8 ignore next 4 */
|
|
41
41
|
return {
|
|
42
42
|
output: await program.parseAsync(argv),
|
|
43
|
-
help
|
|
43
|
+
help,
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|