@platformatic/runtime 0.29.0 → 0.30.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.
@@ -18,5 +18,10 @@
18
18
  "versions": true
19
19
  },
20
20
  "events": false
21
+ },
22
+ "plugins": {
23
+ "paths": [
24
+ "plugin.js"
25
+ ]
21
26
  }
22
27
  }
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ /** @param {import('fastify').FastifyInstance} app */
4
+ module.exports = async function (app) {
5
+ app.get('/async_crash', async () => {
6
+ setImmediate(() => {
7
+ throw new Error('boom')
8
+ })
9
+
10
+ return 'ok'
11
+ })
12
+ }
@@ -5,7 +5,8 @@ const { startCommandInRuntime } = require('../lib/unified-api')
5
5
 
6
6
  async function main () {
7
7
  const entrypoint = await startCommandInRuntime(['-c', process.argv[2]])
8
- const res = await request(entrypoint)
8
+ const endpoint = process.argv[3] ?? '/'
9
+ const res = await request(entrypoint + endpoint)
9
10
 
10
11
  assert.strictEqual(res.statusCode, 200)
11
12
  process.exit(42)
package/lib/api-client.js CHANGED
@@ -6,11 +6,15 @@ const { randomUUID } = require('node:crypto')
6
6
  const MAX_LISTENERS_COUNT = 100
7
7
 
8
8
  class RuntimeApiClient extends EventEmitter {
9
+ #exitCode
10
+ #exitPromise
11
+
9
12
  constructor (worker) {
10
13
  super()
11
14
  this.setMaxListeners(MAX_LISTENERS_COUNT)
12
15
 
13
16
  this.worker = worker
17
+ this.#exitPromise = this.#exitHandler()
14
18
  this.worker.on('message', (message) => {
15
19
  if (message.operationId) {
16
20
  this.emit(message.operationId, message)
@@ -24,7 +28,7 @@ class RuntimeApiClient extends EventEmitter {
24
28
 
25
29
  async close () {
26
30
  await this.#sendCommand('plt:stop-services')
27
- await once(this.worker, 'exit')
31
+ await this.#exitPromise
28
32
  }
29
33
 
30
34
  async restart () {
@@ -59,16 +63,29 @@ class RuntimeApiClient extends EventEmitter {
59
63
  const operationId = randomUUID()
60
64
 
61
65
  this.worker.postMessage({ operationId, command, params })
62
- const [message] = await once(this, operationId)
66
+ const [message] = await Promise.race(
67
+ [once(this, operationId), this.#exitPromise]
68
+ )
69
+
70
+ if (this.#exitCode !== undefined) {
71
+ throw new Error('The runtime exited before the operation completed')
72
+ }
63
73
 
64
74
  const { error, data } = message
65
75
  if (error !== null) {
66
- this.emit('error', new Error(error))
67
- return
76
+ throw new Error(error)
68
77
  }
69
78
 
70
79
  return JSON.parse(data)
71
80
  }
81
+
82
+ async #exitHandler () {
83
+ this.#exitCode = undefined
84
+ return once(this.worker, 'exit').then((msg) => {
85
+ this.#exitCode = msg[0]
86
+ return msg
87
+ })
88
+ }
72
89
  }
73
90
 
74
91
  module.exports = RuntimeApiClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "0.29.0",
3
+ "version": "0.30.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-mapper": "0.29.0",
28
- "@platformatic/sql-graphql": "0.29.0"
27
+ "@platformatic/sql-mapper": "0.30.0",
28
+ "@platformatic/sql-graphql": "0.30.0"
29
29
  },
30
30
  "dependencies": {
31
31
  "@hapi/topo": "^6.0.2",
@@ -40,11 +40,11 @@
40
40
  "pino": "^8.14.1",
41
41
  "pino-pretty": "^10.0.0",
42
42
  "undici": "^5.22.1",
43
- "@platformatic/composer": "0.29.0",
44
- "@platformatic/config": "0.29.0",
45
- "@platformatic/db": "0.29.0",
46
- "@platformatic/service": "0.29.0",
47
- "@platformatic/utils": "0.29.0"
43
+ "@platformatic/composer": "0.30.0",
44
+ "@platformatic/config": "0.30.0",
45
+ "@platformatic/db": "0.30.0",
46
+ "@platformatic/service": "0.30.0",
47
+ "@platformatic/utils": "0.30.0"
48
48
  },
49
49
  "standard": {
50
50
  "ignore": [
package/test/api.test.js CHANGED
@@ -275,6 +275,16 @@ test('should fail inject request is service is not started', async (t) => {
275
275
  }
276
276
  })
277
277
 
278
+ test('does not wait forever if worker exits during api operation', async (t) => {
279
+ const configFile = join(fixturesDir, 'configs', 'service-throws-on-start.json')
280
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
281
+ const app = await buildServer(config.configManager.current)
282
+
283
+ await assert.rejects(async () => {
284
+ await app.start()
285
+ }, /The runtime exited before the operation completed/)
286
+ })
287
+
278
288
  test('should handle a lot of runtime api requests', async (t) => {
279
289
  const configFile = join(fixturesDir, 'configs', 'monorepo.json')
280
290
  const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
@@ -5,6 +5,7 @@ import path from 'node:path'
5
5
  import { cliPath } from './helper.mjs'
6
6
  import { execa } from 'execa'
7
7
  import { mkdtemp, rm, cp, mkdir } from 'node:fs/promises'
8
+ import { setTimeout as sleep } from 'node:timers/promises'
8
9
 
9
10
  const base = join(import.meta.url, '..', 'tmp')
10
11
 
@@ -20,7 +21,6 @@ test('compile without tsconfigs', async () => {
20
21
 
21
22
  test('compile with tsconfig', async (t) => {
22
23
  const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
23
-
24
24
  const prev = process.cwd()
25
25
  process.chdir(tmpDir)
26
26
  t.after(() => {
@@ -28,7 +28,20 @@ test('compile with tsconfig', async (t) => {
28
28
  })
29
29
 
30
30
  t.after(async () => {
31
- await rm(tmpDir, { recursive: true, force: true })
31
+ // We give up after 10s.
32
+ // This is because on Windows, it's very hard to delete files if the file
33
+ // system is not collaborating.
34
+ for (let i = 0; i < 10; i++) {
35
+ try {
36
+ await rm(tmpDir, { recursive: true, force: true })
37
+ break
38
+ } catch (err) {
39
+ if (err.code === 'EBUSY') {
40
+ await sleep(1000)
41
+ continue
42
+ }
43
+ }
44
+ }
32
45
  })
33
46
 
34
47
  const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript')
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert'
2
- import { cp, writeFile, mkdtemp, mkdir } from 'node:fs/promises'
2
+ import { cp, writeFile, mkdtemp, mkdir, rm } 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'
@@ -49,6 +49,7 @@ function createEsmLoggingPlugin (text, reloaded) {
49
49
 
50
50
  test('watches CommonJS files', async (t) => {
51
51
  const tmpDir = await mkdtemp(join(base, 'watch-'))
52
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
52
53
  t.diagnostic(`using ${tmpDir}`)
53
54
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
54
55
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -78,6 +79,7 @@ test('watches CommonJS files', async (t) => {
78
79
 
79
80
  test('watches ESM files', async (t) => {
80
81
  const tmpDir = await mkdtemp(join(base, 'watch-'))
82
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
81
83
  t.diagnostic(`using ${tmpDir}`)
82
84
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
83
85
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -106,6 +108,7 @@ test('watches ESM files', async (t) => {
106
108
 
107
109
  test('should not hot reload files with `--hot-reload false', async (t) => {
108
110
  const tmpDir = await mkdtemp(join(base, 'watch-'))
111
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
109
112
  t.diagnostic(`using ${tmpDir}`)
110
113
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
111
114
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
  const assert = require('node:assert')
3
+ const { spawn } = require('node:child_process')
3
4
  const { once } = require('node:events')
4
5
  const { join } = require('node:path')
5
6
  const { test } = require('node:test')
@@ -143,3 +144,15 @@ test('can start with a custom environment', async (t) => {
143
144
  assert.strictEqual(res.statusCode, 200)
144
145
  assert.deepStrictEqual(await res.body.json(), { A_CUSTOM_ENV_VAR: 'foobar' })
145
146
  })
147
+
148
+ test('handles uncaught exceptions with db app', async (t) => {
149
+ // Test for https://github.com/platformatic/platformatic/issues/1193
150
+ const scriptFile = join(fixturesDir, 'start-command-in-runtime.js')
151
+ const configFile = join(fixturesDir, 'dbApp', 'platformatic.db.json')
152
+ const child = spawn(process.execPath, [scriptFile, configFile, '/async_crash'])
153
+ child.stdout.pipe(process.stdout)
154
+ child.stderr.pipe(process.stderr)
155
+ const [exitCode] = await once(child, 'exit')
156
+
157
+ assert.strictEqual(exitCode, 42)
158
+ })
@@ -1,17 +0,0 @@
1
- {
2
- "scripts": {
3
- "start": "npm run clean && platformatic start",
4
- "clean": "rm -fr ./dist",
5
- "build": "npx tsc",
6
- "migrate": "platformatic db migrations apply"
7
- },
8
- "devDependencies": {
9
- "fastify": "^4.18.0"
10
- },
11
- "dependencies": {
12
- "platformatic": "^0.28.1"
13
- },
14
- "engines": {
15
- "node": "^18.8.0 || >=19"
16
- }
17
- }