@platformatic/service 0.21.1 → 0.23.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.
Files changed (43) hide show
  1. package/fixtures/hello/warn-log.service.json +19 -0
  2. package/fixtures/hello-client/platformatic.service.json +1 -1
  3. package/fixtures/hello-client-ts/platformatic.service.json +3 -2
  4. package/index.js +139 -229
  5. package/lib/compile.js +9 -5
  6. package/lib/load-config.js +13 -15
  7. package/lib/plugins/clients.js +12 -0
  8. package/lib/plugins/cors.js +29 -0
  9. package/lib/plugins/file-watcher.js +44 -0
  10. package/lib/plugins/health-check.js +17 -0
  11. package/lib/plugins/plugins.js +56 -0
  12. package/lib/plugins/typescript.js +46 -0
  13. package/lib/root-endpoint/index.js +1 -0
  14. package/lib/schema.js +8 -1
  15. package/lib/start.mjs +27 -135
  16. package/lib/utils.js +2 -11
  17. package/package.json +24 -23
  18. package/test/autoload.test.js +77 -59
  19. package/test/cli/compile.test.mjs +45 -36
  20. package/test/cli/start.test.mjs +13 -1
  21. package/test/cli/watch.test.mjs +40 -2
  22. package/test/clients.test.js +25 -18
  23. package/test/config.test.js +67 -27
  24. package/test/cors.test.js +48 -30
  25. package/test/fixtures/bad-typescript-plugin/platformatic.service.json +3 -1
  26. package/test/fixtures/custom-port-placeholder.json +10 -0
  27. package/test/fixtures/typescript-autoload/platformatic.service.json +3 -1
  28. package/test/fixtures/typescript-plugin/platformatic.service.json +3 -1
  29. package/test/fixtures/typescript-plugin-nocompile/platformatic.service.json +2 -1
  30. package/test/graphql.test.js +18 -14
  31. package/test/healthcheck.test.js +22 -13
  32. package/test/https.test.js +13 -11
  33. package/test/lambda.test.js +103 -0
  34. package/test/load-and-reload-files.test.js +98 -109
  35. package/test/load-plugin.test.js +8 -4
  36. package/test/metrics.test.js +59 -39
  37. package/test/routes.test.js +43 -25
  38. package/test/utils.test.js +6 -15
  39. package/test/watch.test.js +182 -0
  40. /package/lib/{graphql.js → plugins/graphql.js} +0 -0
  41. /package/lib/{metrics-plugin.js → plugins/metrics.js} +0 -0
  42. /package/lib/{openapi.js → plugins/openapi.js} +0 -0
  43. /package/lib/{sandbox-wrapper.js → plugins/sandbox-wrapper.js} +0 -0
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const underPressure = require('@fastify/under-pressure')
4
+ const fp = require('fastify-plugin')
5
+
6
+ async function setupClients (app, opts) {
7
+ const healthCheck = opts
8
+
9
+ app.register(underPressure, {
10
+ exposeStatusRoute: '/status',
11
+ healthCheckInterval: healthCheck.interval !== undefined ? healthCheck.interval : 5000,
12
+ ...healthCheck,
13
+ healthCheck: healthCheck.fn
14
+ })
15
+ }
16
+
17
+ module.exports = fp(setupClients)
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+
3
+ const { join, resolve } = require('path')
4
+ const { readFile } = require('fs/promises')
5
+
6
+ const sandbox = require('fastify-sandbox')
7
+ const fp = require('fastify-plugin')
8
+
9
+ const { getJSPluginPath, isFileAccessible } = require('../utils')
10
+
11
+ const wrapperPath = join(__dirname, 'sandbox-wrapper.js')
12
+
13
+ async function loadPlugins (app) {
14
+ const configManager = app.platformatic.configManager
15
+ const config = configManager.current
16
+
17
+ if (config.plugins.typescript) {
18
+ const workingDir = configManager.dirname
19
+ const tsConfigPath = join(workingDir, 'tsconfig.json')
20
+
21
+ const isTsConfigAccessible = await isFileAccessible(tsConfigPath)
22
+ if (!isTsConfigAccessible) {
23
+ throw new Error('Cannot load typescript plugin, tsconfig.json not found')
24
+ }
25
+
26
+ const tsConfig = JSON.parse(await readFile(tsConfigPath, 'utf8'))
27
+ const outDir = resolve(workingDir, tsConfig.compilerOptions.outDir)
28
+
29
+ config.plugins.paths = config.plugins.paths.map((plugin) => {
30
+ /* c8 ignore next 3 */
31
+ return typeof plugin === 'string'
32
+ ? getJSPluginPath(workingDir, plugin, outDir)
33
+ : { ...plugin, path: getJSPluginPath(workingDir, plugin.path, outDir) }
34
+ })
35
+ }
36
+
37
+ if (config.plugins.hotReload !== false) {
38
+ await app.register(sandbox, {
39
+ path: wrapperPath,
40
+ options: { paths: config.plugins.paths },
41
+ customizeGlobalThis (_globalThis) {
42
+ // Taken from https://github.com/nodejs/undici/blob/fa9fd9066569b6357acacffb806aa804b688c9d8/lib/global.js#L5
43
+ const globalDispatcher = Symbol.for('undici.globalDispatcher.1')
44
+ const dispatcher = globalThis[globalDispatcher]
45
+ /* istanbul ignore else */
46
+ if (dispatcher) {
47
+ _globalThis[globalDispatcher] = dispatcher
48
+ }
49
+ }
50
+ })
51
+ } else {
52
+ await app.register(require(wrapperPath), { paths: config.plugins.paths })
53
+ }
54
+ }
55
+
56
+ module.exports = fp(loadPlugins)
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const { isKeyEnabled } = require('@platformatic/utils')
4
+ const fp = require('fastify-plugin')
5
+
6
+ const compiler = require('../compile')
7
+
8
+ async function setupTsCompiler (app) {
9
+ // TODO: move params to opts
10
+
11
+ const configManager = app.platformatic.configManager
12
+ const config = configManager.current
13
+
14
+ const isRestartableApp = app.restarted !== undefined
15
+
16
+ // to run the plugin without restartable
17
+ /* c8 ignore next 1 */
18
+ const persistentRef = isRestartableApp ? app.persistentRef : app
19
+
20
+ const workingDir = configManager.dirname
21
+
22
+ if (isKeyEnabled('watch', config)) {
23
+ let tsCompilerWatcher = persistentRef.tsCompilerWatcher
24
+ if (!tsCompilerWatcher) {
25
+ /* c8 ignore next 5 */
26
+ const { child } = await compiler.compileWatch(workingDir, config)
27
+ app.log.debug('start watching typescript files')
28
+ tsCompilerWatcher = child
29
+ }
30
+ app.decorate('tsCompilerWatcher', tsCompilerWatcher)
31
+ } else {
32
+ await compiler.compile(workingDir, config)
33
+ }
34
+
35
+ app.addHook('onClose', async () => {
36
+ if (!isRestartableApp || app.closingRestartable) {
37
+ /* c8 ignore next 4 */
38
+ if (app.tsCompilerWatcher) {
39
+ app.tsCompilerWatcher.kill('SIGTERM')
40
+ app.log.debug('stop watching typescript files')
41
+ }
42
+ }
43
+ })
44
+ }
45
+
46
+ module.exports = fp(setupTsCompiler)
@@ -1,4 +1,5 @@
1
1
  'use strict'
2
+
2
3
  const path = require('path')
3
4
  const fastifyStatic = require('@fastify/static')
4
5
  const userAgentParser = require('ua-parser-js')
package/lib/schema.js CHANGED
@@ -134,7 +134,8 @@ const server = {
134
134
  type: 'integer'
135
135
  },
136
136
  keepAliveTimeout: {
137
- type: 'integer'
137
+ type: 'integer',
138
+ default: 5000
138
139
  },
139
140
  maxRequestsPerSocket: {
140
141
  type: 'integer'
@@ -313,6 +314,12 @@ const server = {
313
314
  }
314
315
  }
315
316
  ]
317
+ },
318
+ requestCert: {
319
+ type: 'boolean'
320
+ },
321
+ rejectUnauthorized: {
322
+ type: 'boolean'
316
323
  }
317
324
  },
318
325
  additionalProperties: false,
package/lib/start.mjs CHANGED
@@ -1,9 +1,6 @@
1
- import { dirname } from 'path'
2
- import { FileWatcher } from '@platformatic/utils'
3
1
  import { buildServer } from '../index.js'
4
2
  import close from 'close-with-grace'
5
3
  import { loadConfig, generateDefaultConfig } from './load-config.js'
6
- import { compileWatch } from './compile.js'
7
4
  import { addLoggerToTheConfig } from './utils.js'
8
5
 
9
6
  // TODO make sure coverage is reported for Windows
@@ -18,182 +15,77 @@ function defaultConfig () {
18
15
  }
19
16
  }
20
17
 
21
- export function buildStart (_loadConfig, _buildServer) {
18
+ export function buildStart (_loadConfig, _buildServer, _configManagerConfig) {
22
19
  return async function start (_args) {
23
- const _defaultConfig = defaultConfig()
20
+ const _defaultConfig = _configManagerConfig ?? defaultConfig()
24
21
  const { configManager, args } = await _loadConfig({}, _args, _defaultConfig)
25
22
 
26
23
  const config = configManager.current
27
24
 
28
- // Set the logger if not present
25
+ // Disable hot reload from the CLI
26
+ if (args.hotReload === false && configManager.current.plugins) {
27
+ configManager.current.plugins.hotReload = false
28
+ }
29
29
  addLoggerToTheConfig(config)
30
30
 
31
- if (
32
- config.plugins?.typescript &&
33
- config.plugins?.watch !== false
34
- ) {
35
- try {
36
- await compileWatch(dirname(configManager.fullPath))
37
- } catch (error) {
38
- // TODO route this to a logger
39
- console.error(error)
40
- process.exit(1)
31
+ const _transformConfig = configManager._transformConfig.bind(configManager)
32
+ configManager._transformConfig = function () {
33
+ const config = configManager.current
34
+ if (args.hotReload === false && config.plugins) {
35
+ config.plugins.hotReload = false
41
36
  }
37
+ addLoggerToTheConfig(config)
38
+ return _transformConfig(config)
42
39
  }
43
40
 
44
- let server = null
45
-
46
- // Disable hot reload from the CLI
47
- if (args.hotReload === false && configManager.current.plugins) {
48
- configManager.current.plugins.hotReload = false
49
- }
41
+ let app = null
50
42
 
51
43
  try {
52
44
  // Set the location of the config
53
- server = await _buildServer({
54
- ...config,
55
- configManager
56
- })
45
+ app = await _buildServer({ ...config, configManager })
46
+
47
+ await app.start()
48
+ // TODO: this log is used in the start command. Should be replaced
49
+ app.log.info({ url: app.url })
57
50
  } catch (err) {
58
51
  // TODO route this to a logger
59
52
  console.error(err)
60
53
  process.exit(1)
61
54
  }
62
55
 
63
- server.app.platformatic.configManager = configManager
64
- server.app.platformatic.config = config
65
- configManager.on('update', (newConfig) => {
66
- if (args.hotReload === false && configManager.current.plugins) {
67
- configManager.current.plugins.hotReload = false
68
- }
69
- onConfigUpdated(newConfig, server)
70
- })
71
-
72
- if (
73
- config.plugins !== undefined &&
74
- config.watch !== false
75
- ) {
76
- await startFileWatching(server, args.hotReload)
77
- }
78
-
79
- try {
80
- await server.listen()
81
- } catch (err) {
82
- server.app.log.error({ err })
83
- process.exit(1)
84
- }
85
-
86
- configManager.on('error', function (err) {
87
- server.app.log.error({
88
- err
89
- }, 'error reloading the configuration')
90
- })
91
-
92
56
  // Ignore from CI because SIGUSR2 is not available
93
57
  // on Windows
94
58
  process.on('SIGUSR2', function () {
95
- server.app.log.info('reloading configuration')
96
- server.restart()
97
- .catch((err) => {
98
- server.app.log.error({
99
- err: {
100
- message: err.message,
101
- stack: err.stack
102
- }
103
- }, 'failed to restart')
104
- })
59
+ app.log.info('reloading configuration')
60
+ safeRestart(app)
105
61
  return false
106
62
  })
107
63
 
108
64
  close(async ({ signal, err }) => {
109
65
  // Windows does not support trapping signals
110
66
  if (err) {
111
- server.app.log.error({
67
+ app.log.error({
112
68
  err: {
113
69
  message: err.message,
114
70
  stack: err.stack
115
71
  }
116
72
  }, 'exiting')
117
73
  } else if (signal) {
118
- server.app.log.info({ signal }, 'received signal')
74
+ app.log.info({ signal }, 'received signal')
119
75
  }
120
76
 
121
- await server.stop()
77
+ await app.close()
122
78
  })
123
79
  }
124
80
  }
125
81
 
126
82
  const start = buildStart(loadConfig, buildServer)
127
83
 
128
- async function startFileWatching (server, hotReload) {
129
- const configManager = server.app.platformatic.configManager
130
- const config = configManager.current
131
-
132
- const fileWatcher = new FileWatcher({
133
- path: dirname(configManager.fullPath),
134
- allowToWatch: config.watch?.allow,
135
- watchIgnore: config.watch?.ignore
136
- })
137
- fileWatcher.on('update', () => {
138
- onFilesUpdated(server, hotReload)
139
- })
140
- fileWatcher.startWatching()
141
-
142
- server.app.log.debug('start watching files')
143
- server.app.platformatic.fileWatcher = fileWatcher
144
- }
145
-
146
- async function stopFileWatching (server) {
147
- const fileWatcher = server.app.platformatic.fileWatcher
148
- if (fileWatcher !== undefined) {
149
- await fileWatcher.stopWatching()
150
-
151
- server.app.log.debug('stop watching files')
152
- server.app.platformatic.fileWatcher = undefined
153
- }
154
- }
155
-
156
- async function onConfigUpdated (newConfig, server) {
84
+ async function safeRestart (app) {
157
85
  try {
158
- server.app.platformatic.config = newConfig
159
- server.app.log.debug('config changed')
160
- server.app.log.trace({ newConfig }, 'new config')
161
-
162
- await stopFileWatching(server)
163
-
164
- await server.restart(newConfig)
165
- } catch (err) {
166
- // TODO: test this
167
- server.app.log.error({
168
- err: {
169
- message: err.message,
170
- stack: err.stack
171
- }
172
- }, 'failed to reload config')
173
- } finally {
174
- if (
175
- newConfig.plugins !== undefined &&
176
- newConfig.plugins.watch !== false
177
- ) {
178
- await startFileWatching(server)
179
- }
180
- }
181
- }
182
-
183
- async function onFilesUpdated (server, hotReload) {
184
- // Reload the config as well, otherwise we will have problems
185
- // in case the files watcher triggers the config watcher too
186
- const configManager = server.app.platformatic.configManager
187
- try {
188
- server.app.log.debug('files changed')
189
- await configManager.parse()
190
- if (hotReload === false && configManager.current.plugins) {
191
- configManager.current.plugins.hotReload = false
192
- }
193
- await server.restart(configManager.current)
86
+ await app.restart()
194
87
  } catch (err) {
195
- // TODO: test this
196
- server.app.log.error({
88
+ app.log.error({
197
89
  err: {
198
90
  message: err.message,
199
91
  stack: err.stack
package/lib/utils.js CHANGED
@@ -3,12 +3,6 @@
3
3
  const { access } = require('fs/promises')
4
4
  const { resolve, join, relative, dirname, basename } = require('path')
5
5
 
6
- async function findConfigFile (directory, configFileNames) {
7
- const configFilesAccessibility = await Promise.all(configFileNames.map((fileName) => isFileAccessible(fileName, directory)))
8
- const accessibleConfigFilename = configFileNames.find((value, index) => configFilesAccessibility[index])
9
- return accessibleConfigFilename
10
- }
11
-
12
6
  async function isFileAccessible (filename, directory) {
13
7
  try {
14
8
  const filePath = directory ? resolve(directory, filename) : filename
@@ -21,8 +15,6 @@ async function isFileAccessible (filename, directory) {
21
15
 
22
16
  /* c8 ignore start */
23
17
  function addLoggerToTheConfig (config) {
24
- if (config === undefined || config.server === undefined) return
25
-
26
18
  // Set the logger if not present
27
19
  let logger = config.server.logger
28
20
  if (!logger) {
@@ -41,7 +33,7 @@ function addLoggerToTheConfig (config) {
41
33
  }
42
34
  /* c8 ignore stop */
43
35
 
44
- function getJSPluginPath (configPath, tsPluginPath, compileDir) {
36
+ function getJSPluginPath (workingDir, tsPluginPath, compileDir) {
45
37
  if (tsPluginPath.endsWith('js')) {
46
38
  return tsPluginPath
47
39
  }
@@ -57,7 +49,7 @@ function getJSPluginPath (configPath, tsPluginPath, compileDir) {
57
49
  newBaseName = basename(tsPluginPath)
58
50
  }
59
51
 
60
- const tsPluginRelativePath = relative(dirname(configPath), tsPluginPath)
52
+ const tsPluginRelativePath = relative(workingDir, tsPluginPath)
61
53
  const jsPluginRelativePath = join(
62
54
  dirname(tsPluginRelativePath),
63
55
  newBaseName
@@ -66,7 +58,6 @@ function getJSPluginPath (configPath, tsPluginPath, compileDir) {
66
58
  }
67
59
 
68
60
  module.exports = {
69
- findConfigFile,
70
61
  isFileAccessible,
71
62
  getJSPluginPath,
72
63
  addLoggerToTheConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -17,55 +17,56 @@
17
17
  },
18
18
  "homepage": "https://github.com/platformatic/platformatic#readme",
19
19
  "devDependencies": {
20
- "@matteo.collina/worker": "^3.0.0",
20
+ "@fastify/aws-lambda": "^3.2.0",
21
+ "@matteo.collina/worker": "^3.1.0",
21
22
  "bindings": "^1.5.0",
22
23
  "c8": "^7.13.0",
23
24
  "self-cert": "^2.0.0",
24
25
  "snazzy": "^9.0.0",
25
- "split2": "^4.1.0",
26
+ "split2": "^4.2.0",
26
27
  "standard": "^17.0.0",
27
28
  "strip-ansi": "^7.0.1",
28
29
  "tap": "^16.3.4",
29
- "tsd": "^0.28.0",
30
- "typescript": "^5.0.0",
31
- "undici": "^5.20.0",
32
- "vscode-json-languageservice": "^5.3.0",
30
+ "tsd": "^0.28.1",
31
+ "typescript": "^5.0.4",
32
+ "undici": "^5.22.0",
33
+ "vscode-json-languageservice": "^5.3.4",
33
34
  "why-is-node-running": "^2.2.2",
34
- "yaml": "^2.2.1"
35
+ "yaml": "^2.2.2"
35
36
  },
36
37
  "dependencies": {
37
38
  "@fastify/accepts": "^4.1.0",
38
39
  "@fastify/autoload": "^5.7.1",
39
40
  "@fastify/basic-auth": "^5.0.0",
40
- "@fastify/cors": "^8.2.0",
41
+ "@fastify/cors": "^8.2.1",
41
42
  "@fastify/deepmerge": "^1.3.0",
42
- "@fastify/restartable": "^1.4.0",
43
- "@fastify/static": "^6.9.0",
43
+ "@fastify/restartable": "^2.1.0",
44
+ "@fastify/static": "^6.10.1",
44
45
  "@fastify/swagger": "^8.3.1",
45
- "@fastify/swagger-ui": "^1.4.0",
46
+ "@fastify/swagger-ui": "^1.8.1",
46
47
  "@fastify/under-pressure": "^8.2.0",
47
- "@mercuriusjs/federation": "^1.0.1",
48
- "close-with-grace": "^1.1.0",
48
+ "@mercuriusjs/federation": "^2.0.0",
49
+ "close-with-grace": "^1.2.0",
49
50
  "commist": "^3.2.0",
50
51
  "desm": "^1.3.0",
51
52
  "env-schema": "^5.2.0",
52
53
  "es-main": "^1.2.0",
53
- "execa": "^7.0.0",
54
- "fastify": "^4.13.0",
55
- "fastify-metrics": "^10.0.3",
54
+ "execa": "^7.1.1",
55
+ "fastify": "^4.17.0",
56
+ "fastify-metrics": "^10.3.0",
56
57
  "fastify-plugin": "^4.5.0",
57
58
  "fastify-sandbox": "^0.11.0",
58
59
  "graphql": "^16.6.0",
59
60
  "help-me": "^4.2.0",
60
- "mercurius": "^12.2.0",
61
+ "mercurius": "^13.0.0",
61
62
  "minimist": "^1.2.8",
62
- "pino": "^8.11.0",
63
+ "pino": "^8.12.0",
63
64
  "pino-pretty": "^10.0.0",
64
65
  "rfdc": "^1.3.0",
65
- "ua-parser-js": "^1.0.33",
66
- "@platformatic/client": "0.21.1",
67
- "@platformatic/config": "0.21.1",
68
- "@platformatic/utils": "0.21.1"
66
+ "ua-parser-js": "^1.0.35",
67
+ "@platformatic/client": "0.23.0",
68
+ "@platformatic/config": "0.23.0",
69
+ "@platformatic/utils": "0.23.0"
69
70
  },
70
71
  "standard": {
71
72
  "ignore": [