@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.
Files changed (41) hide show
  1. package/fixtures/configs/hotreload.json +0 -1
  2. package/fixtures/configs/{monorepo-client-without-id.json → invalid-autoload-with-services.json} +17 -9
  3. package/fixtures/configs/monorepo-composer-no-autoload.json +32 -0
  4. package/fixtures/configs/monorepo-create-cycle.json +0 -1
  5. package/fixtures/configs/monorepo-hotreload-env.json +0 -1
  6. package/fixtures/configs/monorepo-hotreload.json +0 -1
  7. package/fixtures/configs/monorepo-missing-dependencies.json +9 -0
  8. package/fixtures/configs/monorepo-no-cycles.json +0 -1
  9. package/fixtures/configs/monorepo-with-management-api.json +0 -1
  10. package/fixtures/configs/monorepo.json +0 -1
  11. package/fixtures/configs/service-with-env-port.json +0 -1
  12. package/fixtures/leven/platformatic.runtime.json +0 -1
  13. package/fixtures/management-api/platformatic.json +0 -1
  14. package/fixtures/management-api-custom-labels/platformatic.json +0 -1
  15. package/fixtures/management-api-without-metrics/platformatic.json +0 -1
  16. package/fixtures/monorepo/composerApp/platformatic.composer.json +0 -1
  17. package/fixtures/monorepo-missing-dependencies/composer/platformatic.json +24 -0
  18. package/fixtures/prom-server/platformatic.json +0 -1
  19. package/fixtures/sample-runtime/platformatic.json +0 -1
  20. package/fixtures/sample-runtime-with-2-services/platformatic.json +0 -1
  21. package/fixtures/server/logger-transport/platformatic.runtime.json +0 -1
  22. package/fixtures/server/overrides-service/platformatic.runtime.json +0 -1
  23. package/fixtures/server/runtime-server/platformatic.runtime.json +0 -1
  24. package/fixtures/telemetry/platformatic.runtime.json +0 -1
  25. package/fixtures/typescript/platformatic.runtime.json +0 -1
  26. package/fixtures/typescript-custom-flags/platformatic.runtime.json +0 -1
  27. package/fixtures/typescript-no-env/platformatic.runtime.json +0 -1
  28. package/lib/api.js +45 -22
  29. package/lib/app.js +48 -12
  30. package/lib/build-server.js +12 -2
  31. package/lib/compile.js +49 -7
  32. package/lib/config.js +8 -127
  33. package/lib/dependencies.js +58 -0
  34. package/lib/errors.js +1 -0
  35. package/lib/generator/runtime-generator.js +0 -1
  36. package/lib/load-config.js +2 -10
  37. package/lib/schema.js +1 -4
  38. package/lib/start.js +7 -8
  39. package/package.json +11 -12
  40. package/runtime.mjs +6 -1
  41. package/fixtures/monorepo/serviceApp/platformatic.service-client-without-id.json +0 -21
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.32.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
- "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
- "hotReload": false,
3
+ "entrypoint": "db-app",
6
4
  "autoload": {
7
5
  "path": "../monorepo",
8
- "exclude": ["docs", "composerApp"],
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
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
3
  "entrypoint": "with-logger",
4
- "allowCycles": false,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.33.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": "{PLT_HOT_RELOAD}",
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
+ "entrypoint": "composer",
4
+ "hotReload": false,
5
+ "managementApi": false,
6
+ "autoload": {
7
+ "path": "../monorepo-missing-dependencies"
8
+ }
9
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": false,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.21.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "../monorepo",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "managementApi": false,
7
6
  "autoload": {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
3
3
  "entrypoint": "serviceApp",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "managementApi": false,
7
6
  "autoload": {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
3
3
  "entrypoint": "rainy-empire",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.22.0/runtime",
3
3
  "entrypoint": "service-1",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "./services"
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.22.0/runtime",
3
3
  "entrypoint": "service-1",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "./services"
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.22.0/runtime",
3
3
  "entrypoint": "service-1",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "./services"
@@ -19,7 +19,6 @@
19
19
  },
20
20
  {
21
21
  "id": "multi-plugin-service",
22
- "origin": "{PLT_ORIGIN_BUT_NOT_PROVIDED}",
23
22
  "openapi": {
24
23
  "url": "/documentation/json",
25
24
  "prefix": "/multi-plugin-service"
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.22.0/runtime",
3
3
  "entrypoint": "service-1",
4
- "allowCycles": true,
5
4
  "hotReload": false,
6
5
  "autoload": {
7
6
  "path": "./services"
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.25.0/runtime",
3
3
  "entrypoint": "rival",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v1.25.0/runtime",
3
3
  "entrypoint": "rival",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
3
3
  "entrypoint": "echo",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
3
3
  "entrypoint": "echo",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
3
3
  "entrypoint": "echo",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
3
3
  "entrypoint": "echo",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.28.1/runtime",
3
3
  "entrypoint": "composer",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.28.1/runtime",
3
3
  "entrypoint": "composer",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://platformatic.dev/schemas/v0.28.1/runtime",
3
3
  "entrypoint": "composer",
4
- "allowCycles": false,
5
4
  "hotReload": true,
6
5
  "autoload": {
7
6
  "path": "services",
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
- #bus
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
- this.#setupBus()
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
- let entrypointUrl = null
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
- entrypointUrl = service.server.url
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
- this.#bus.broadcast('runtime:services:started')
168
- return entrypointUrl
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
- this.#bus.broadcast('runtime:services:stopped')
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 entrypointUrl = null
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(true)
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
- this.#bus.broadcast('runtime:services:restarted')
206
- return entrypointUrl
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 (force) {
59
- if (this.#starting || !this.#started || (!this.#hotReload && !force)) {
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.#initializeConfig()
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.entrypoint || this.appConfig.useHttp) {
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 #initializeConfig () {
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 (key) {
205
- return appConfig.localServiceEnvVars.get(key)
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 }
@@ -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
- return buildServerService(options, app)
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 { tsCompiler } = require('@platformatic/service')
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 serviceWasCompiled = await tsCompiler.compile(service.path, configManager.current, childLogger, compileOptions)
46
- compiled ||= serviceWasCompiled
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
- compiled = await tsCompiler.compile(dirname(configManager.fullPath), configManager.current, logger, compileOptions)
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 { readFile, readdir } = require('node:fs/promises')
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',
@@ -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/service').schema
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 === 'runtime') {
134
- config.configManager.args = config.args
135
- const app = await buildRuntime(config.configManager)
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
- return serviceStart(config.app, args)
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.server.port > MAX_PORT) throw err
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": "1.51.8",
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": "1.51.8",
38
- "@platformatic/sql-mapper": "1.51.8"
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/bus": "1.51.8",
67
- "@platformatic/composer": "1.51.8",
68
- "@platformatic/config": "1.51.8",
69
- "@platformatic/db": "1.51.8",
70
- "@platformatic/generators": "1.51.8",
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
@@ -45,5 +45,10 @@ export async function run (argv) {
45
45
  }
46
46
 
47
47
  if (isMain(import.meta)) {
48
- await run(process.argv.slice(2))
48
+ try {
49
+ await run(process.argv.slice(2))
50
+ } catch (err) {
51
+ console.error(err)
52
+ process.exit(1)
53
+ }
49
54
  }
@@ -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
- }