@platformatic/runtime 0.33.0 → 0.34.0

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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v0.33.0/runtime",
3
+ "entrypoint": "serviceApp",
4
+ "allowCycles": true,
5
+ "hotReload": "{PLT_HOT_RELOAD}",
6
+ "autoload": {
7
+ "path": "../monorepo",
8
+ "exclude": ["docs", "composerApp"],
9
+ "mappings": {
10
+ "serviceAppWithLogger": {
11
+ "id": "with-logger",
12
+ "config": "platformatic.service.json"
13
+ },
14
+ "serviceAppWithMultiplePlugins": {
15
+ "id": "multi-plugin-service",
16
+ "config": "platformatic.service.json"
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,2 @@
1
+ 'use strict'
2
+ module.exports = Math.random()
@@ -0,0 +1,3 @@
1
+ {
2
+ "main": "foo.js"
3
+ }
@@ -0,0 +1 @@
1
+ export default Math.random()
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "foom.js",
3
+ "type": "module"
4
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v0.33.1/service",
3
+ "server": {
4
+ "hostname": "127.0.0.1",
5
+ "port": "{PORT}",
6
+ "logger": {
7
+ "level": "info"
8
+ }
9
+ },
10
+ "service": {
11
+ "openapi": true
12
+ },
13
+ "plugins": {
14
+ "paths": [
15
+ "./plugin.js"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ module.exports = function (fastify, opts, next) {
4
+ fastify.get('/plugin1', function (request, reply) {
5
+ const foo = require('foo')
6
+ reply.send({ hello: foo })
7
+ })
8
+
9
+ fastify.get('/plugin2', async function (request, reply) {
10
+ const foom = (await import('foom')).default
11
+ return { hello: foom }
12
+ })
13
+ next()
14
+ }
package/lib/api.js CHANGED
@@ -49,9 +49,18 @@ class RuntimeApi {
49
49
  }
50
50
 
51
51
  async #handleProcessLevelEvent (message) {
52
- await Promise.allSettled(this.#services.values().map(async (service) => {
52
+ const services = [...this.#services.values()]
53
+ await Promise.allSettled(services.map(async (service) => {
53
54
  await service.handleProcessLevelEvent(message)
54
55
  }))
56
+
57
+ for (const service of services) {
58
+ if (service.getStatus() === 'started') {
59
+ return
60
+ }
61
+ }
62
+
63
+ process.exit() // Exit the worker thread if all services are stopped
55
64
  }
56
65
 
57
66
  async #executeCommand (message) {
package/lib/app.js CHANGED
@@ -148,6 +148,7 @@ class PlatformaticApp {
148
148
 
149
149
  if (this.#started) {
150
150
  await this.stop()
151
+ this.server.log.info('server stopped')
151
152
  }
152
153
  }
153
154
 
package/lib/config.js CHANGED
@@ -222,6 +222,12 @@ platformaticRuntime.configType = 'runtime'
222
222
  platformaticRuntime.configManagerConfig = {
223
223
  schema,
224
224
  allowToWatch: ['.env'],
225
+ schemaOptions: {
226
+ useDefaults: true,
227
+ coerceTypes: true,
228
+ allErrors: true,
229
+ strict: false
230
+ },
225
231
  async transformConfig () {
226
232
  await _transformConfig(this)
227
233
  }
@@ -243,7 +249,16 @@ async function wrapConfigInRuntimeConfig ({ configManager, args }) {
243
249
  }
244
250
  ]
245
251
  }
246
- const cm = new ConfigManager({ source: wrapperConfig, schema })
252
+ const cm = new ConfigManager({
253
+ source: wrapperConfig,
254
+ schema,
255
+ schemaOptions: {
256
+ useDefaults: true,
257
+ coerceTypes: true,
258
+ allErrors: true,
259
+ strict: false
260
+ }
261
+ })
247
262
 
248
263
  await _transformConfig(cm)
249
264
  await cm.parseAndValidate()
package/lib/loader.mjs CHANGED
@@ -20,7 +20,9 @@ function clearCjsCache () {
20
20
  // the entire require() cache. See the DEP0144 documentation for how to do
21
21
  // it.
22
22
  Object.keys(require.cache).forEach((key) => {
23
- delete require.cache[key]
23
+ if (!key.match(/node_modules/)) {
24
+ delete require.cache[key]
25
+ }
24
26
  })
25
27
  }
26
28
 
@@ -89,7 +91,7 @@ export async function resolve (specifier, context, nextResolve) {
89
91
 
90
92
  // If the specifier could not be mapped to a file, or the path is this file,
91
93
  // then don't do anything.
92
- if (typeof url !== 'string' || url === import.meta.url) {
94
+ if (typeof url !== 'string' || url === import.meta.url || url.match(/node_modules/)) {
93
95
  return nextResolve(specifier, context)
94
96
  }
95
97
 
package/lib/schema.js CHANGED
@@ -47,7 +47,6 @@ const platformaticRuntimeSchema = {
47
47
  services: {
48
48
  type: 'array',
49
49
  default: [],
50
- minItems: 1,
51
50
  items: {
52
51
  type: 'object',
53
52
  required: ['id', 'path', 'config'],
package/lib/worker.js CHANGED
@@ -32,14 +32,18 @@ const logger = pino(transport, destination)
32
32
  /* c8 ignore next 4 */
33
33
  process.once('uncaughtException', (err) => {
34
34
  logger.error({ err }, 'runtime error')
35
- throw err
35
+ setImmediate(() => {
36
+ process.exit(1)
37
+ })
36
38
  })
37
39
 
38
40
  // Tested by test/cli/start.test.mjs by C8 does not see it.
39
41
  /* c8 ignore next 4 */
40
42
  process.once('unhandledRejection', (err) => {
41
43
  logger.error({ err }, 'runtime error')
42
- throw err
44
+ setImmediate(() => {
45
+ process.exit(1)
46
+ })
43
47
  })
44
48
 
45
49
  function main () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -24,8 +24,8 @@
24
24
  "standard": "^17.1.0",
25
25
  "tsd": "^0.28.1",
26
26
  "typescript": "^5.1.6",
27
- "@platformatic/sql-graphql": "0.33.0",
28
- "@platformatic/sql-mapper": "0.33.0"
27
+ "@platformatic/sql-graphql": "0.34.0",
28
+ "@platformatic/sql-mapper": "0.34.0"
29
29
  },
30
30
  "dependencies": {
31
31
  "@hapi/topo": "^6.0.2",
@@ -42,12 +42,12 @@
42
42
  "pino": "^8.14.1",
43
43
  "pino-pretty": "^10.0.0",
44
44
  "undici": "^5.22.1",
45
- "@platformatic/composer": "0.33.0",
46
- "@platformatic/config": "0.33.0",
47
- "@platformatic/db": "0.33.0",
48
- "@platformatic/service": "0.33.0",
49
- "@platformatic/telemetry": "0.33.0",
50
- "@platformatic/utils": "0.33.0"
45
+ "@platformatic/composer": "0.34.0",
46
+ "@platformatic/config": "0.34.0",
47
+ "@platformatic/db": "0.34.0",
48
+ "@platformatic/service": "0.34.0",
49
+ "@platformatic/telemetry": "0.34.0",
50
+ "@platformatic/utils": "0.34.0"
51
51
  },
52
52
  "standard": {
53
53
  "ignore": [
@@ -1,11 +1,12 @@
1
1
  import assert from 'node:assert'
2
- import { cp, writeFile, mkdtemp, mkdir, rm } from 'node:fs/promises'
2
+ import { cp, writeFile, mkdtemp, mkdir, rm, utimes } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
4
  import { test } from 'node:test'
5
5
  import { setTimeout as sleep } from 'node:timers/promises'
6
6
  import desm from 'desm'
7
7
  import { request } from 'undici'
8
8
  import { start } from './helper.mjs'
9
+ import { on } from 'node:events'
9
10
 
10
11
  const fixturesDir = join(desm(import.meta.url), '..', '..', 'fixtures')
11
12
 
@@ -205,3 +206,79 @@ test('watches CommonJS files with hotreload on a single service', { timeout: 300
205
206
 
206
207
  assert.ok(restartedThirdTime)
207
208
  })
209
+
210
+ test('do not hot reload dependencies', { timeout: 30000, skip: process.env.CI }, async (t) => {
211
+ process.env.PORT = 0
212
+ const config = join(fixturesDir, 'do-not-reload-dependencies', 'platformatic.service.json')
213
+ const { child, url } = await start('-c', config)
214
+ t.after(() => child.kill('SIGINT'))
215
+ t.after(() => delete process.env.PORT)
216
+
217
+ const res1 = await request(`${url}/plugin1`)
218
+ const plugin1 = (await res1.body.json()).hello
219
+
220
+ const res2 = await request(`${url}/plugin2`)
221
+ const plugin2 = (await res2.body.json()).hello
222
+
223
+ utimes(config, new Date(), new Date()).catch(() => {})
224
+
225
+ // wait for restart
226
+ for await (const messages of on(child.ndj, 'data')) {
227
+ let url
228
+ for (const message of messages) {
229
+ if (message.msg) {
230
+ url = message.msg.match(/server listening at (.+)/i)?.[1]
231
+
232
+ if (url !== undefined) {
233
+ break
234
+ }
235
+ }
236
+ }
237
+
238
+ if (url !== undefined) {
239
+ break
240
+ }
241
+ }
242
+
243
+ const res3 = await request(`${url}/plugin1`)
244
+ assert.strictEqual((await res3.body.json()).hello, plugin1)
245
+
246
+ const res4 = await request(`${url}/plugin2`)
247
+ assert.strictEqual((await res4.body.json()).hello, plugin2)
248
+ })
249
+
250
+ test('watches CommonJS files with hotreload on a single service', { timeout: 30000, skip: process.env.CI }, async (t) => {
251
+ const tmpDir = await mkdtemp(join(base, 'watch-'))
252
+ t.after(() => saferm(tmpDir))
253
+ t.diagnostic(`using ${tmpDir}`)
254
+ const appSrc = join(fixturesDir, 'monorepo', 'serviceAppWithLogger')
255
+ const appDst = join(tmpDir)
256
+ const cjsPluginFilePath = join(appDst, 'plugin.js')
257
+
258
+ await Promise.all([
259
+ cp(appSrc, appDst, { recursive: true })
260
+ ])
261
+
262
+ await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
263
+ const { child } = await start('-c', join(appDst, 'platformatic.service.json'))
264
+ t.after(() => child.kill('SIGINT'))
265
+
266
+ await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
267
+
268
+ let restartedSecondTime = false
269
+ let restartedThirdTime = false
270
+
271
+ for await (const log of child.ndj) {
272
+ if (log.msg === 'RELOADED v2') {
273
+ restartedSecondTime = true
274
+ } else if (log.msg === 'RELOADED v3') {
275
+ assert.ok(restartedSecondTime)
276
+ restartedThirdTime = true
277
+ break
278
+ } else if (log.msg?.match(/listening/)) {
279
+ await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
280
+ }
281
+ }
282
+
283
+ assert.ok(restartedThirdTime)
284
+ })
@@ -3,7 +3,7 @@
3
3
  const assert = require('node:assert')
4
4
  const { join } = require('node:path')
5
5
  const { test } = require('node:test')
6
- const { loadConfig } = require('@platformatic/service')
6
+ const { loadConfig, platformaticService } = require('@platformatic/service')
7
7
  const { parseInspectorOptions, platformaticRuntime } = require('../lib/config')
8
8
  const fixturesDir = join(__dirname, '..', 'fixtures')
9
9
 
@@ -209,3 +209,21 @@ test('parseInspectorOptions()', async (t) => {
209
209
  assert.strictEqual(cm.current.inspectorOptions.port, 65535)
210
210
  })
211
211
  })
212
+
213
+ test('same schemaOptions as platformatic service', async () => {
214
+ assert.deepStrictEqual(platformaticRuntime.schemaOptions, platformaticService.schemaOptions)
215
+ })
216
+
217
+ test('correctly loads the hotReload value from a string', async () => {
218
+ const configFile = join(fixturesDir, 'configs', 'monorepo-hotreload-env.json')
219
+ process.env.PLT_HOT_RELOAD = 'true'
220
+ const loaded = await loadConfig({}, ['-c', configFile], platformaticRuntime)
221
+ assert.strictEqual(loaded.configManager.current.hotReload, true)
222
+ })
223
+
224
+ test('correctly loads the hotReload value from a string', async () => {
225
+ const configFile = join(fixturesDir, 'configs', 'monorepo-hotreload-env.json')
226
+ process.env.PLT_HOT_RELOAD = 'false'
227
+ const loaded = await loadConfig({}, ['-c', configFile], platformaticRuntime)
228
+ assert.strictEqual(loaded.configManager.current.hotReload, false)
229
+ })