@platformatic/runtime 1.18.0 → 1.20.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,27 @@
1
+ 'use strict'
2
+
3
+ const fastify = require('fastify')
4
+
5
+ async function start (opts) {
6
+ const app = fastify()
7
+
8
+ // Dumb bearer token implementation
9
+ app.get('/hello', async (req, res) => {
10
+ // We just check if there is an authorization header,
11
+ // which is insecure,
12
+ if (req.headers.authorization) {
13
+ return { hello: 'world' }
14
+ } else {
15
+ res.code(401)
16
+ return { error: 'Unauthorized' }
17
+ }
18
+ })
19
+
20
+ return app
21
+ }
22
+
23
+ module.exports = start
24
+
25
+ if (require.main === module) {
26
+ start({ logger: { name: 'external' } }).then(app => app.listen({ port: process.env.PORT || 3001 }))
27
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const fastify = require('fastify')
4
+ const { createSigner } = require('fast-jwt')
5
+
6
+ async function start (opts) {
7
+ const app = fastify(opts)
8
+ let port = opts.port
9
+
10
+ const signSync = createSigner({
11
+ key: 'secret',
12
+ expiresIn: '1h'
13
+ })
14
+
15
+ app.decorate('refreshToken', '')
16
+ app.decorate('signSync', signSync)
17
+
18
+ // Dump bearer token implementation
19
+ app.post('/token', async (req, res) => {
20
+ return { access_token: signSync({}) }
21
+ })
22
+
23
+ await app.listen({ port })
24
+
25
+ port = app.server.address().port
26
+
27
+ const refreshToken = signSync({
28
+ iss: `http://localhost:${port}`
29
+ })
30
+
31
+ app.refreshToken = refreshToken
32
+
33
+ app.log.info({ refreshToken }, 'refresh token')
34
+
35
+ return app
36
+ }
37
+
38
+ module.exports = start
39
+
40
+ if (require.main === module) {
41
+ start({ logger: { name: 'idp' }, port: process.env.PORT || 3000 }).catch((err) => {
42
+ console.error(err)
43
+ process.exit(1)
44
+ })
45
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.20.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{{PORT}}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ },
14
+ "undici": {
15
+ "interceptors": {
16
+ "Agent": [{
17
+ "module": "undici-oauth-interceptor",
18
+ "options": {
19
+ "refreshToken": "{{PLT_REFRESH_TOKEN}}",
20
+ "origins": ["{{PLT_EXTERNAL_SERVICE}}"],
21
+ "clientId": "my-client-id"
22
+ }
23
+ }]
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false,
12
+ "options": {
13
+ "externalService": "{{PLT_EXTERNAL_SERVICE}}"
14
+ }
15
+ }]
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { request } = require('undici')
4
+
5
+ module.exports = async function (fastify, options) {
6
+ fastify.get('/hello', async (_, reply) => {
7
+ const res = await request(`${options.externalService}/hello`)
8
+ if (res.statusCode !== 200) {
9
+ reply.code(res.statusCode)
10
+ }
11
+ reply.log.info('response received')
12
+ const data = await res.body.json()
13
+ return data
14
+ })
15
+ }
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
3
4
  const FastifyUndiciDispatcher = require('fastify-undici-dispatcher')
4
- const { setGlobalDispatcher, getGlobalDispatcher } = require('undici')
5
5
  const { PlatformaticApp } = require('./app')
6
6
  const errors = require('./errors')
7
7
  const { printSchema } = require('graphql')
@@ -29,20 +29,18 @@ class RuntimeApi {
29
29
  keepAliveTimeout: 5000
30
30
  }
31
31
  }
32
+
32
33
  const app = new PlatformaticApp(service, loaderPort, logger, serviceTelemetryConfig, serverConfig)
33
34
 
34
35
  this.#services.set(service.id, app)
35
36
  }
36
37
 
37
- const globalAgent = getGlobalDispatcher()
38
- const globalDispatcher = new FastifyUndiciDispatcher({
39
- dispatcher: globalAgent,
38
+ this.#dispatcher = new FastifyUndiciDispatcher({
39
+ dispatcher: getGlobalDispatcher(),
40
40
  // setting the domain here allows for fail-fast scenarios
41
41
  domain: '.plt.local'
42
42
  })
43
-
44
- setGlobalDispatcher(globalDispatcher)
45
- this.#dispatcher = globalDispatcher
43
+ setGlobalDispatcher(this.#dispatcher)
46
44
  }
47
45
 
48
46
  async startListening (parentPort) {
package/lib/schema.js CHANGED
@@ -105,6 +105,28 @@ const platformaticRuntimeSchema = {
105
105
  }
106
106
  }
107
107
  },
108
+ undici: {
109
+ agentOptions: {
110
+ type: 'object',
111
+ additionalProperties: true
112
+ },
113
+ interceptors: {
114
+ type: 'array',
115
+ items: {
116
+ type: 'object',
117
+ properties: {
118
+ module: {
119
+ type: 'string'
120
+ },
121
+ options: {
122
+ type: 'object',
123
+ additionalProperties: true
124
+ }
125
+ },
126
+ required: ['module', 'options']
127
+ }
128
+ }
129
+ },
108
130
  $schema: {
109
131
  type: 'string'
110
132
  }
package/lib/start.js CHANGED
@@ -36,6 +36,9 @@ async function startWithConfig (configManager, env = process.env) {
36
36
  if (config.hotReload) {
37
37
  config.loaderFile = kLoaderFile
38
38
  }
39
+
40
+ const dirname = configManager.dirname
41
+
39
42
  // The configManager cannot be transferred to the worker, so remove it.
40
43
  delete config.configManager
41
44
 
@@ -43,7 +46,7 @@ async function startWithConfig (configManager, env = process.env) {
43
46
  /* c8 ignore next */
44
47
  execArgv: config.hotReload ? kWorkerExecArgv : [],
45
48
  transferList: config.loggingPort ? [config.loggingPort] : [],
46
- workerData: { config },
49
+ workerData: { config, dirname },
47
50
  env
48
51
  })
49
52
 
package/lib/worker.js CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const inspector = require('node:inspector')
4
- const { register } = require('node:module')
4
+ const { register, createRequire } = require('node:module')
5
5
  const { isatty } = require('node:tty')
6
+ const { pathToFileURL } = require('node:url')
7
+ const { join } = require('node:path')
6
8
  const {
7
9
  MessageChannel,
8
10
  parentPort,
@@ -10,6 +12,7 @@ const {
10
12
  } = require('node:worker_threads')
11
13
  const undici = require('undici')
12
14
  const pino = require('pino')
15
+ const { setGlobalDispatcher, Agent } = require('undici')
13
16
  const RuntimeApi = require('./api')
14
17
  const { MessagePortWritable } = require('./message-port-writable')
15
18
  let loaderPort
@@ -58,7 +61,48 @@ if (config.server) {
58
61
  config.server.logger = logger
59
62
  }
60
63
 
61
- function main () {
64
+ let stop
65
+
66
+ /* c8 ignore next 4 */
67
+ process.on('uncaughtException', (err) => {
68
+ logger.error({ err }, 'runtime uncaught exception')
69
+
70
+ if (stop) {
71
+ stop().then(() => {
72
+ process.exit(1)
73
+ })
74
+ } else {
75
+ process.exit(1)
76
+ }
77
+ })
78
+
79
+ // Tested by test/cli/start.test.mjs by C8 does not see it.
80
+ /* c8 ignore next 4 */
81
+ process.on('unhandledRejection', (err) => {
82
+ logger.error({ err }, 'runtime unhandled rejection')
83
+
84
+ if (stop) {
85
+ stop().then(() => {
86
+ process.exit(1)
87
+ })
88
+ } else {
89
+ process.exit(1)
90
+ }
91
+ })
92
+
93
+ async function loadInterceptor (_require, module, options) {
94
+ const url = pathToFileURL(_require.resolve(module))
95
+ const interceptor = (await import(url)).default
96
+ return interceptor(options)
97
+ }
98
+
99
+ function loadInterceptors (_require, interceptors) {
100
+ return Promise.all(interceptors.map(async ({ module, options }) => {
101
+ return loadInterceptor(_require, module, options)
102
+ }))
103
+ }
104
+
105
+ async function main () {
62
106
  const { inspectorOptions } = workerData.config
63
107
 
64
108
  if (inspectorOptions) {
@@ -70,6 +114,23 @@ function main () {
70
114
  inspector.open(inspectorOptions.port, inspectorOptions.host, inspectorOptions.breakFirstLine)
71
115
  }
72
116
 
117
+ const interceptors = {}
118
+
119
+ if (config.undici?.interceptors) {
120
+ const _require = createRequire(join(workerData.dirname, 'package.json'))
121
+ for (const key of ['Agent', 'Pool', 'Client']) {
122
+ if (config.undici.interceptors[key]) {
123
+ interceptors[key] = await loadInterceptors(_require, config.undici.interceptors[key])
124
+ }
125
+ }
126
+ }
127
+
128
+ const globalDispatcher = new Agent({
129
+ ...config.undici,
130
+ interceptors
131
+ })
132
+ setGlobalDispatcher(globalDispatcher)
133
+
73
134
  const runtime = new RuntimeApi(workerData.config, logger, loaderPort)
74
135
  runtime.startListening(parentPort)
75
136
 
@@ -77,7 +138,7 @@ function main () {
77
138
 
78
139
  let stopping = false
79
140
 
80
- async function stop () {
141
+ stop = async function () {
81
142
  if (stopping) {
82
143
  return
83
144
  }
@@ -89,23 +150,7 @@ function main () {
89
150
  logger.error({ err }, 'error while stopping services')
90
151
  }
91
152
  }
92
-
93
- /* c8 ignore next 4 */
94
- process.on('uncaughtException', (err) => {
95
- logger.error({ err }, 'runtime error')
96
- stop().then(() => {
97
- process.exit(1)
98
- })
99
- })
100
-
101
- // Tested by test/cli/start.test.mjs by C8 does not see it.
102
- /* c8 ignore next 4 */
103
- process.on('unhandledRejection', (err) => {
104
- logger.error({ err }, 'runtime error')
105
- stop().then(() => {
106
- process.exit(1)
107
- })
108
- })
109
153
  }
110
154
 
155
+ // No need to catch this because there is the unhadledRejection handler on top.
111
156
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -18,45 +18,48 @@
18
18
  "homepage": "https://github.com/platformatic/platformatic#readme",
19
19
  "devDependencies": {
20
20
  "@fastify/express": "^2.3.0",
21
- "c8": "^9.0.0",
21
+ "borp": "^0.9.0",
22
+ "c8": "^9.1.0",
22
23
  "execa": "^8.0.1",
23
24
  "express": "^4.18.2",
25
+ "fast-jwt": "^3.3.3",
24
26
  "glob": "^10.3.10",
25
27
  "pino-abstract-transport": "^1.1.0",
26
28
  "snazzy": "^9.0.0",
27
29
  "split2": "^4.2.0",
28
30
  "standard": "^17.1.0",
29
- "tsd": "^0.30.0",
30
- "typescript": "^5.2.2",
31
- "@platformatic/sql-graphql": "1.18.0",
32
- "@platformatic/sql-mapper": "1.18.0"
31
+ "tsd": "^0.30.4",
32
+ "typescript": "^5.3.3",
33
+ "undici-oauth-interceptor": "^0.4.2",
34
+ "@platformatic/sql-graphql": "1.20.0",
35
+ "@platformatic/sql-mapper": "1.20.0"
33
36
  },
34
37
  "dependencies": {
35
- "@fastify/error": "^3.4.0",
38
+ "@fastify/error": "^3.4.1",
36
39
  "@hapi/topo": "^6.0.2",
37
40
  "boring-name-generator": "^1.0.3",
38
41
  "close-with-grace": "^1.2.0",
39
42
  "commist": "^3.2.0",
40
43
  "debounce": "^2.0.0",
41
- "desm": "^1.3.0",
44
+ "desm": "^1.3.1",
42
45
  "es-main": "^1.3.0",
43
46
  "fastest-levenshtein": "^1.0.16",
44
- "fastify": "^4.24.1",
47
+ "fastify": "^4.26.0",
45
48
  "fastify-undici-dispatcher": "^0.5.0",
46
49
  "graphql": "^16.8.1",
47
50
  "help-me": "^5.0.0",
48
51
  "minimist": "^1.2.8",
49
- "pino": "^8.16.0",
50
- "pino-pretty": "^10.2.3",
51
- "undici": "^6.2.0",
52
+ "pino": "^8.17.2",
53
+ "pino-pretty": "^10.3.1",
54
+ "undici": "^6.6.0",
52
55
  "why-is-node-running": "^2.2.2",
53
- "@platformatic/config": "1.18.0",
54
- "@platformatic/db": "1.18.0",
55
- "@platformatic/generators": "1.18.0",
56
- "@platformatic/service": "1.18.0",
57
- "@platformatic/telemetry": "1.18.0",
58
- "@platformatic/utils": "1.18.0",
59
- "@platformatic/composer": "1.18.0"
56
+ "@platformatic/composer": "1.20.0",
57
+ "@platformatic/config": "1.20.0",
58
+ "@platformatic/generators": "1.20.0",
59
+ "@platformatic/db": "1.20.0",
60
+ "@platformatic/service": "1.20.0",
61
+ "@platformatic/telemetry": "1.20.0",
62
+ "@platformatic/utils": "1.20.0"
60
63
  },
61
64
  "standard": {
62
65
  "ignore": [
@@ -65,8 +68,8 @@
65
68
  ]
66
69
  },
67
70
  "scripts": {
68
- "test": "npm run lint && node ./test/runner.js && tsd",
69
- "coverage": "npm run lint && c8 -x fixtures -x test node ./test/runner.js && tsd",
71
+ "test": "npm run lint && borp --concurrency=1 --timeout=120000 && tsd",
72
+ "coverage": "npm run lint && borp -X=fixtures -X=test -C --concurrency=1 --timeout=120000 && tsd",
70
73
  "lint": "standard | snazzy"
71
74
  }
72
75
  }