@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.
Files changed (176) hide show
  1. package/config.d.ts +285 -0
  2. package/eslint.config.js +8 -0
  3. package/fixtures/botched-start/platformatic.runtime.json +1 -1
  4. package/fixtures/botched-start/services/a/platformatic.service.json +1 -1
  5. package/fixtures/composerApp/platformatic.composer.json +1 -1
  6. package/fixtures/configs/invalid-autoload-with-services.json +1 -1
  7. package/fixtures/configs/invalid-entrypoint.json +1 -1
  8. package/fixtures/configs/invalid-schema-type.config.json +1 -1
  9. package/fixtures/configs/missing-property.config.json +1 -1
  10. package/fixtures/configs/missing-service-config.json +1 -1
  11. package/fixtures/configs/monorepo-composer-no-autoload.json +2 -2
  12. package/fixtures/configs/monorepo-composer.json +2 -2
  13. package/fixtures/configs/monorepo-create-cycle.json +2 -2
  14. package/fixtures/configs/monorepo-missing-dependencies.json +2 -2
  15. package/fixtures/configs/monorepo-no-cycles.json +2 -2
  16. package/fixtures/configs/monorepo-openapi.json +2 -2
  17. package/fixtures/configs/{monorepo-hotreload-env.json → monorepo-watch-env.json} +2 -2
  18. package/fixtures/configs/monorepo-watch-single.json +12 -0
  19. package/fixtures/configs/monorepo-watch.json +26 -9
  20. package/fixtures/configs/monorepo-with-dependencies.json +2 -2
  21. package/fixtures/configs/monorepo-with-management-api-without-metrics.json +21 -0
  22. package/fixtures/configs/monorepo-with-management-api.json +2 -2
  23. package/fixtures/configs/{monorepo-hotreload.json → monorepo-with-metrics.json} +5 -4
  24. package/fixtures/configs/monorepo.json +2 -2
  25. package/fixtures/configs/no-services.config.json +1 -1
  26. package/fixtures/configs/no-sources.config.json +1 -1
  27. package/fixtures/configs/service-throws-on-start.json +1 -1
  28. package/fixtures/configs/service-with-env-port.json +2 -2
  29. package/fixtures/configs/service-with-stdio.json +12 -0
  30. package/fixtures/configs/{hotreload.json → watch.json} +2 -2
  31. package/fixtures/crash-on-bootstrap/platformatic.runtime.json +15 -0
  32. package/fixtures/crash-on-bootstrap/services/service-1/platformatic.service.json +14 -0
  33. package/fixtures/crash-on-bootstrap/services/service-1/plugin.js +5 -0
  34. package/fixtures/crash-on-bootstrap/services/service-2/platformatic.service.json +14 -0
  35. package/fixtures/crash-on-bootstrap/services/service-2/plugin.js +5 -0
  36. package/fixtures/dbApp/platformatic.db.json +1 -1
  37. package/fixtures/dbAppNoName/platformatic.db.json +1 -1
  38. package/fixtures/dbAppNoPackageJson/platformatic.db.json +1 -1
  39. package/fixtures/dbAppWithMigrationError/platformatic.db.json +1 -1
  40. package/fixtures/do-not-reload-dependencies/platformatic.service.json +1 -1
  41. package/fixtures/do-not-restart-on-crash/platformatic.runtime.json +3 -2
  42. package/fixtures/do-not-restart-on-crash/services/a/platformatic.service.json +1 -1
  43. package/fixtures/express/platformatic.runtime.json +1 -1
  44. package/fixtures/express/services/a/platformatic.service.json +1 -1
  45. package/fixtures/express/services/b/platformatic.service.json +1 -1
  46. package/fixtures/external-client/platformatic.service.json +1 -1
  47. package/fixtures/interceptors/idp.js +2 -2
  48. package/fixtures/interceptors/platformatic.runtime.json +1 -1
  49. package/fixtures/interceptors/services/a/platformatic.service.json +1 -1
  50. package/fixtures/interceptors-2/platformatic.runtime.json +1 -1
  51. package/fixtures/interceptors-2/services/a/platformatic.service.json +1 -1
  52. package/fixtures/leven/platformatic.runtime.json +2 -2
  53. package/fixtures/leven/services/deeply-spittle/platformatic.service.json +1 -1
  54. package/fixtures/leven/services/rainy-empire/platformatic.composer.json +1 -1
  55. package/fixtures/management-api/platformatic.json +3 -3
  56. package/fixtures/management-api/services/service-1/platformatic.json +1 -1
  57. package/fixtures/management-api/services/service-1/plugin.js +4 -3
  58. package/fixtures/management-api/services/service-2/platformatic.json +1 -1
  59. package/fixtures/management-api/services/service-db/platformatic.db.json +1 -1
  60. package/fixtures/management-api-custom-labels/platformatic.json +2 -2
  61. package/fixtures/management-api-custom-labels/services/service-1/platformatic.json +1 -1
  62. package/fixtures/management-api-custom-labels/services/service-1/plugin.js +4 -3
  63. package/fixtures/management-api-custom-labels/services/service-2/platformatic.json +1 -1
  64. package/fixtures/management-api-custom-labels/services/service-db/platformatic.db.json +1 -1
  65. package/fixtures/management-api-without-metrics/platformatic.json +3 -2
  66. package/fixtures/management-api-without-metrics/services/service-1/platformatic.json +1 -1
  67. package/fixtures/monorepo/composerApp/platformatic.composer.json +1 -1
  68. package/fixtures/monorepo/dbApp/platformatic.db.json +1 -1
  69. package/fixtures/monorepo/serviceApp/platformatic.service.json +3 -2
  70. package/fixtures/monorepo/serviceApp/with-logger/with-logger.cjs +2 -2
  71. package/fixtures/monorepo/serviceApp/with-logger/with-logger.d.ts +7 -7
  72. package/fixtures/monorepo/serviceAppWithLogger/platformatic.service.json +1 -1
  73. package/fixtures/monorepo/serviceAppWithLogger/plugin.js +12 -0
  74. package/fixtures/monorepo/serviceAppWithMultiplePlugins/platformatic.service.json +3 -2
  75. package/fixtures/monorepo-missing-dependencies/composer/platformatic.json +1 -1
  76. package/fixtures/monorepo-openapi/serviceAppWithoutOpenapi/platformatic.service.json +1 -1
  77. package/fixtures/monorepo-watch/service1/platformatic.service.json +1 -1
  78. package/fixtures/monorepo-with-dependencies/main/platformatic.json +1 -1
  79. package/fixtures/monorepo-with-dependencies/service-1/platformatic.json +1 -1
  80. package/fixtures/monorepo-with-dependencies/service-2/platformatic.json +1 -1
  81. package/fixtures/no-env.service.json +1 -1
  82. package/fixtures/preload/platformatic.runtime.json +1 -1
  83. package/fixtures/preload/services/a/platformatic.service.json +1 -1
  84. package/fixtures/prom-server/platformatic.json +2 -2
  85. package/fixtures/prom-server/services/service-1/platformatic.json +1 -1
  86. package/fixtures/prom-server/services/service-2/platformatic.json +1 -1
  87. package/fixtures/restart-on-crash/platformatic.runtime.json +1 -1
  88. package/fixtures/restart-on-crash/services/a/platformatic.service.json +1 -1
  89. package/fixtures/sample-runtime/package.json +1 -1
  90. package/fixtures/sample-runtime/platformatic.json +2 -2
  91. package/fixtures/sample-runtime/services/rival/package.json +1 -1
  92. package/fixtures/sample-runtime/services/rival/platformatic.json +1 -1
  93. package/fixtures/sample-runtime-with-2-services/package.json +1 -1
  94. package/fixtures/sample-runtime-with-2-services/platformatic.json +2 -2
  95. package/fixtures/sample-runtime-with-2-services/services/foobar/package.json +1 -1
  96. package/fixtures/sample-runtime-with-2-services/services/foobar/platformatic.json +1 -1
  97. package/fixtures/sample-runtime-with-2-services/services/rival/package.json +1 -1
  98. package/fixtures/sample-runtime-with-2-services/services/rival/platformatic.json +1 -1
  99. package/fixtures/server/logger-transport/platformatic.runtime.json +2 -2
  100. package/fixtures/server/logger-transport/services/echo/platformatic.service.json +1 -1
  101. package/fixtures/server/overrides-service/platformatic.runtime.json +2 -2
  102. package/fixtures/server/overrides-service/services/echo/platformatic.service.json +1 -1
  103. package/fixtures/server/runtime-server/platformatic.runtime.json +2 -2
  104. package/fixtures/server/runtime-server/services/echo/platformatic.service.json +1 -1
  105. package/fixtures/serviceAppThrowsOnStart/platformatic.service.json +1 -1
  106. package/fixtures/stackables/node_modules/foo/foo.js +2 -1
  107. package/fixtures/start-command-in-runtime.js +1 -1
  108. package/fixtures/stdio/platformatic.service.json +6 -0
  109. package/fixtures/stdio/plugin.js +24 -0
  110. package/fixtures/telemetry/platformatic.runtime.json +2 -2
  111. package/fixtures/telemetry/services/echo/platformatic.service.json +1 -1
  112. package/fixtures/telemetry/services/echo/routes/span.js +16 -2
  113. package/fixtures/telemetry/services/service-1/platformatic.service.json +19 -0
  114. package/fixtures/telemetry/services/service-1/routes/echo.js +7 -0
  115. package/fixtures/typescript/platformatic.runtime.json +2 -2
  116. package/fixtures/typescript/services/composer/platformatic.composer.json +1 -1
  117. package/fixtures/typescript/services/movies/global.d.ts +2 -3
  118. package/fixtures/typescript/services/movies/platformatic.db.json +1 -1
  119. package/fixtures/typescript/services/movies/types/Movie.d.ts +3 -3
  120. package/fixtures/typescript/services/movies/types/index.d.ts +6 -6
  121. package/fixtures/typescript/services/titles/client/client.d.ts +35 -35
  122. package/fixtures/typescript/services/titles/platformatic.service.json +1 -1
  123. package/fixtures/typescript-custom-flags/platformatic.runtime.json +2 -2
  124. package/fixtures/typescript-custom-flags/services/composer/platformatic.composer.json +1 -1
  125. package/fixtures/typescript-custom-flags/services/movies/global.d.ts +2 -3
  126. package/fixtures/typescript-custom-flags/services/movies/platformatic.db.json +1 -1
  127. package/fixtures/typescript-custom-flags/services/movies/types/Movie.d.ts +3 -3
  128. package/fixtures/typescript-custom-flags/services/movies/types/index.d.ts +6 -6
  129. package/fixtures/typescript-custom-flags/services/titles/client/client.d.ts +35 -35
  130. package/fixtures/typescript-custom-flags/services/titles/platformatic.service.json +1 -1
  131. package/fixtures/typescript-no-env/platformatic.runtime.json +2 -2
  132. package/fixtures/typescript-no-env/services/composer/platformatic.composer.json +1 -1
  133. package/fixtures/typescript-no-env/services/movies/global.d.ts +2 -3
  134. package/fixtures/typescript-no-env/services/movies/platformatic.db.json +1 -1
  135. package/fixtures/typescript-no-env/services/movies/types/Movie.d.ts +3 -3
  136. package/fixtures/typescript-no-env/services/movies/types/index.d.ts +6 -6
  137. package/fixtures/typescript-no-env/services/titles/client/client.d.ts +35 -35
  138. package/fixtures/typescript-no-env/services/titles/platformatic.service.json +1 -1
  139. package/index.d.ts +7 -8
  140. package/index.js +14 -10
  141. package/index.test-d.ts +10 -12
  142. package/lib/build-server.js +5 -11
  143. package/lib/compile.js +11 -10
  144. package/lib/config.js +21 -14
  145. package/lib/dependencies.js +2 -1
  146. package/lib/errors.js +3 -2
  147. package/lib/generator/errors.js +1 -1
  148. package/lib/generator/runtime-generator.d.ts +15 -15
  149. package/lib/generator/runtime-generator.js +92 -63
  150. package/lib/logger.js +55 -0
  151. package/lib/management-api.js +29 -44
  152. package/lib/prom-server.js +5 -9
  153. package/lib/runtime.js +955 -0
  154. package/lib/schema.js +79 -76
  155. package/lib/start.js +35 -113
  156. package/lib/upgrade.js +4 -3
  157. package/lib/utils.js +49 -1
  158. package/lib/versions/v1.36.0.js +1 -1
  159. package/lib/versions/v1.5.0.js +1 -1
  160. package/lib/versions/v2.0.0.js +17 -0
  161. package/lib/worker/app.js +250 -0
  162. package/lib/worker/default-stackable.js +27 -0
  163. package/lib/worker/itc.js +128 -0
  164. package/lib/worker/main.js +127 -0
  165. package/lib/worker/symbols.js +7 -0
  166. package/package.json +25 -25
  167. package/runtime.mjs +4 -4
  168. package/schema.json +824 -0
  169. package/lib/api-client.js +0 -500
  170. package/lib/api.js +0 -420
  171. package/lib/app.js +0 -397
  172. package/lib/load-config.js +0 -12
  173. package/lib/loader.mjs +0 -103
  174. package/lib/message-port-writable.js +0 -50
  175. package/lib/worker.js +0 -182
  176. /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.2",
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
- "@matteo.collina/tspl": "^0.1.1",
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.2",
34
+ "typescript": "^5.5.4",
35
35
  "undici-oidc-interceptor": "^0.5.0",
36
36
  "why-is-node-running": "^2.2.2",
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"
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.1.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.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"
73
- },
74
- "standard": {
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=120000 && tsd",
83
- "lint": "standard | snazzy"
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