@platformatic/service 2.0.0-alpha.2 → 2.0.0-alpha.21

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.
package/lib/schema.js CHANGED
@@ -5,39 +5,7 @@
5
5
  const pkg = require('../package.json')
6
6
  const openApiDefs = require('./openapi-schema-defs')
7
7
  const telemetry = require('@platformatic/telemetry').schema
8
- const { server, cors } = require('@platformatic/utils').schemas
9
-
10
- const watch = {
11
- type: 'object',
12
- properties: {
13
- enabled: {
14
- default: true,
15
- anyOf: [{
16
- type: 'boolean'
17
- }, {
18
- type: 'string'
19
- }]
20
- },
21
- allow: {
22
- type: 'array',
23
- items: {
24
- type: 'string'
25
- },
26
- minItems: 1,
27
- nullable: true,
28
- default: null
29
- },
30
- ignore: {
31
- type: 'array',
32
- items: {
33
- type: 'string'
34
- },
35
- nullable: true,
36
- default: null
37
- }
38
- },
39
- additionalProperties: false
40
- }
8
+ const { fastifyServer: server, cors, watch } = require('@platformatic/utils').schemaComponents
41
9
 
42
10
  const plugins = {
43
11
  type: 'object',
@@ -45,123 +13,139 @@ const plugins = {
45
13
  packages: {
46
14
  type: 'array',
47
15
  items: {
48
- anyOf: [{
49
- type: 'string'
50
- }, {
51
- type: 'object',
52
- properties: {
53
- name: {
54
- type: 'string'
55
- },
56
- options: {
57
- type: 'object',
58
- additionalProperties: true
59
- }
16
+ anyOf: [
17
+ {
18
+ type: 'string'
60
19
  },
61
- required: ['name']
62
- }]
20
+ {
21
+ type: 'object',
22
+ properties: {
23
+ name: {
24
+ type: 'string'
25
+ },
26
+ options: {
27
+ type: 'object',
28
+ additionalProperties: true
29
+ }
30
+ },
31
+ required: ['name']
32
+ }
33
+ ]
63
34
  }
64
35
  },
65
36
  paths: {
66
37
  type: 'array',
67
38
  items: {
68
- anyOf: [{
69
- type: 'string',
70
- resolvePath: true
71
- }, {
39
+ anyOf: [
40
+ {
41
+ type: 'string',
42
+ resolvePath: true
43
+ },
44
+ {
45
+ type: 'object',
46
+ properties: {
47
+ path: {
48
+ type: 'string',
49
+ resolvePath: true
50
+ },
51
+ encapsulate: {
52
+ type: 'boolean',
53
+ default: true
54
+ },
55
+ maxDepth: {
56
+ type: 'integer'
57
+ },
58
+ autoHooks: {
59
+ type: 'boolean'
60
+ },
61
+ autoHooksPattern: {
62
+ type: 'string'
63
+ },
64
+ cascadeHooks: {
65
+ type: 'boolean'
66
+ },
67
+ overwriteHooks: {
68
+ type: 'boolean'
69
+ },
70
+ routeParams: {
71
+ type: 'boolean'
72
+ },
73
+ forceESM: {
74
+ type: 'boolean'
75
+ },
76
+ ignoreFilter: {
77
+ type: 'string'
78
+ },
79
+ matchFilter: {
80
+ type: 'string'
81
+ },
82
+ ignorePattern: {
83
+ type: 'string'
84
+ },
85
+ scriptPattern: {
86
+ type: 'string'
87
+ },
88
+ indexPattern: {
89
+ type: 'string'
90
+ },
91
+ options: {
92
+ type: 'object',
93
+ additionalProperties: true
94
+ }
95
+ }
96
+ }
97
+ ]
98
+ }
99
+ },
100
+ typescript: {
101
+ anyOf: [
102
+ {
72
103
  type: 'object',
73
104
  properties: {
74
- path: {
105
+ enabled: {
106
+ anyOf: [
107
+ {
108
+ type: 'boolean'
109
+ },
110
+ {
111
+ type: 'string'
112
+ }
113
+ ]
114
+ },
115
+ tsConfig: {
75
116
  type: 'string',
76
117
  resolvePath: true
77
118
  },
78
- encapsulate: {
79
- type: 'boolean',
80
- default: true
81
- },
82
- maxDepth: {
83
- type: 'integer'
84
- },
85
- autoHooks: {
86
- type: 'boolean'
87
- },
88
- autoHooksPattern: {
89
- type: 'string'
90
- },
91
- cascadeHooks: {
92
- type: 'boolean'
93
- },
94
- overwriteHooks: {
95
- type: 'boolean'
96
- },
97
- routeParams: {
98
- type: 'boolean'
99
- },
100
- forceESM: {
101
- type: 'boolean'
102
- },
103
- ignoreFilter: {
104
- type: 'string'
105
- },
106
- matchFilter: {
107
- type: 'string'
108
- },
109
- ignorePattern: {
110
- type: 'string'
111
- },
112
- scriptPattern: {
113
- type: 'string'
114
- },
115
- indexPattern: {
116
- type: 'string'
119
+ outDir: {
120
+ type: 'string',
121
+ resolvePath: true
117
122
  },
118
- options: {
119
- type: 'object',
120
- additionalProperties: true
121
- }
122
- }
123
- }]
124
- }
125
- },
126
- typescript: {
127
- anyOf: [{
128
- type: 'object',
129
- properties: {
130
- enabled: {
131
- anyOf: [{
132
- type: 'boolean'
133
- }, {
134
- type: 'string'
135
- }]
136
- },
137
- tsConfig: {
138
- type: 'string',
139
- resolvePath: true
140
- },
141
- outDir: {
142
- type: 'string',
143
- resolvePath: true
144
- },
145
- flags: {
146
- type: 'array',
147
- items: {
148
- type: 'string'
123
+ flags: {
124
+ type: 'array',
125
+ items: {
126
+ type: 'string'
127
+ }
149
128
  }
150
129
  }
130
+ },
131
+ {
132
+ type: 'boolean'
133
+ },
134
+ {
135
+ type: 'string'
151
136
  }
152
- }, {
153
- type: 'boolean'
154
- }, {
155
- type: 'string'
156
- }]
137
+ ]
157
138
  }
158
139
  },
159
140
  additionalProperties: false,
160
- anyOf: [{
161
- required: ['paths']
162
- }, {
163
- required: ['packages']
164
- }]
141
+ anyOf: [
142
+ {
143
+ required: ['paths']
144
+ },
145
+ {
146
+ required: ['packages']
147
+ }
148
+ ]
165
149
  }
166
150
 
167
151
  const metrics = {
@@ -171,10 +155,7 @@ const metrics = {
171
155
  type: 'object',
172
156
  properties: {
173
157
  port: {
174
- anyOf: [
175
- { type: 'integer' },
176
- { type: 'string' }
177
- ]
158
+ anyOf: [{ type: 'integer' }, { type: 'string' }]
178
159
  },
179
160
  hostname: { type: 'string' },
180
161
  endpoint: { type: 'string' },
@@ -190,7 +171,6 @@ const metrics = {
190
171
  required: ['enabled'],
191
172
  additionalProperties: false
192
173
  },
193
- prefix: { type: 'string' },
194
174
  auth: {
195
175
  type: 'object',
196
176
  properties: {
@@ -272,12 +252,15 @@ const openApiBase = {
272
252
  }
273
253
 
274
254
  const openapi = {
275
- anyOf: [{
276
- ...openApiBase,
277
- additionalProperties: false
278
- }, {
279
- type: 'boolean'
280
- }]
255
+ anyOf: [
256
+ {
257
+ ...openApiBase,
258
+ additionalProperties: false
259
+ },
260
+ {
261
+ type: 'boolean'
262
+ }
263
+ ]
281
264
  }
282
265
 
283
266
  const graphqlBase = {
@@ -291,12 +274,15 @@ const graphqlBase = {
291
274
  }
292
275
 
293
276
  const graphql = {
294
- anyOf: [{
295
- ...graphqlBase,
296
- additionalProperties: false
297
- }, {
298
- type: 'boolean'
299
- }]
277
+ anyOf: [
278
+ {
279
+ ...graphqlBase,
280
+ additionalProperties: false
281
+ },
282
+ {
283
+ type: 'boolean'
284
+ }
285
+ ]
300
286
  }
301
287
 
302
288
  const service = {
@@ -343,7 +329,7 @@ const clients = {
343
329
  }
344
330
 
345
331
  const platformaticServiceSchema = {
346
- $id: `https://platformatic.dev/schemas/v${pkg.version}/service`,
332
+ $id: `https://schemas.platformatic.dev/@platformatic/service/${pkg.version}.json`,
347
333
  version: pkg.version,
348
334
  title: 'Platformatic Service',
349
335
  type: 'object',
@@ -353,15 +339,22 @@ const platformaticServiceSchema = {
353
339
  metrics,
354
340
  telemetry,
355
341
  watch: {
356
- anyOf: [watch, {
357
- type: 'boolean'
358
- }, {
359
- type: 'string'
360
- }]
342
+ anyOf: [
343
+ watch,
344
+ {
345
+ type: 'boolean'
346
+ },
347
+ {
348
+ type: 'string'
349
+ }
350
+ ]
361
351
  },
362
352
  $schema: {
363
353
  type: 'string'
364
354
  },
355
+ module: {
356
+ type: 'string'
357
+ },
365
358
  service,
366
359
  clients
367
360
  },
@@ -0,0 +1,271 @@
1
+ 'use strict'
2
+
3
+ const { dirname } = require('node:path')
4
+ const { printSchema } = require('graphql')
5
+ const pino = require('pino')
6
+ const httpMetrics = require('@platformatic/fastify-http-metrics')
7
+ const { extractTypeScriptCompileOptionsFromConfig } = require('./compile')
8
+ const { compile } = require('@platformatic/ts-compiler')
9
+ const { deepmerge } = require('@platformatic/utils')
10
+
11
+ class ServiceStackable {
12
+ constructor (options) {
13
+ this.app = null
14
+ this._init = options.init
15
+ this.stackable = options.stackable
16
+ this.metricsRegistry = null
17
+
18
+ this.configManager = options.configManager
19
+ this.context = options.context ?? {}
20
+ this.context.stackable = this
21
+
22
+ this.configManager.on('error', err => {
23
+ /* c8 ignore next */
24
+ this.stackable.log({
25
+ message: 'error reloading the configuration' + err,
26
+ level: 'error'
27
+ })
28
+ })
29
+
30
+ this.#updateConfig()
31
+ }
32
+
33
+ async init () {
34
+ this.#initLogger()
35
+
36
+ if (this.app === null) {
37
+ this.app = await this._init()
38
+
39
+ if (this.metricsRegistry) {
40
+ this.#setHttpMetrics()
41
+ }
42
+ }
43
+ return this.app
44
+ }
45
+
46
+ async start (options = {}) {
47
+ await this.init()
48
+
49
+ if (options.listen === false) {
50
+ await this.app.ready()
51
+ return
52
+ }
53
+ await this.app.start()
54
+ }
55
+
56
+ async stop () {
57
+ if (this.app === null) return
58
+ await this.app.close()
59
+ }
60
+
61
+ async build () {
62
+ this.#initLogger()
63
+ const typeScriptCompileOptions = extractTypeScriptCompileOptionsFromConfig(this.configManager.current)
64
+ const cwd = dirname(this.configManager.fullPath)
65
+ const compileOptions = {
66
+ ...typeScriptCompileOptions,
67
+ cwd,
68
+ logger: this.logger
69
+ }
70
+ if (!(await compile(compileOptions))) {
71
+ throw new Error(`Failed to compile ${cwd}`)
72
+ }
73
+ }
74
+
75
+ getUrl () {
76
+ return this.app !== null ? this.app.url : null
77
+ }
78
+
79
+ async getInfo () {
80
+ const type = this.stackable.configType
81
+ const version = this.stackable.configManagerConfig.version ?? null
82
+ return { type, version }
83
+ }
84
+
85
+ async getConfig () {
86
+ const config = Object.assign({}, this.configManager.current)
87
+ config.server = Object.assign({}, config.server)
88
+ const logger = config.server.loggerInstance
89
+
90
+ if (logger) {
91
+ config.server.logger = {}
92
+
93
+ if (logger.level) {
94
+ config.server.logger.level = logger.level
95
+ }
96
+ }
97
+
98
+ delete config.server.loggerInstance
99
+
100
+ return config
101
+ }
102
+
103
+ async getEnv () {
104
+ return this.configManager.env
105
+ }
106
+
107
+ async getWatchConfig () {
108
+ const config = this.configManager.current
109
+
110
+ const enabled = config.watch?.enabled !== false && config.plugins !== undefined
111
+
112
+ return {
113
+ enabled,
114
+ path: dirname(this.configManager.fullPath),
115
+ allow: config.watch?.allow,
116
+ ignore: config.watch?.ignore
117
+ }
118
+ }
119
+
120
+ async getDispatchFunc () {
121
+ await this.init()
122
+ return this.app
123
+ }
124
+
125
+ async getOpenapiSchema () {
126
+ await this.init()
127
+ await this.app.ready()
128
+ return this.app.swagger ? this.app.swagger() : null
129
+ }
130
+
131
+ async getGraphqlSchema () {
132
+ await this.init()
133
+ await this.app.ready()
134
+ return this.app.graphql ? printSchema(this.app.graphql.schema) : null
135
+ }
136
+
137
+ async collectMetrics ({ registry }) {
138
+ this.metricsRegistry = registry
139
+
140
+ return {
141
+ defaultMetrics: true,
142
+ httpMetrics: false
143
+ }
144
+ }
145
+
146
+ async inject (injectParams) {
147
+ await this.init()
148
+
149
+ const { statusCode, statusMessage, headers, body } = await this.app.inject(injectParams)
150
+ return { statusCode, statusMessage, headers, body }
151
+ }
152
+
153
+ async log (options = {}) {
154
+ await this.init()
155
+
156
+ const logLevel = options.level ?? 'info'
157
+
158
+ const message = options.message
159
+ if (!message) return
160
+
161
+ this.app.log[logLevel](message)
162
+ }
163
+
164
+ async updateContext (context) {
165
+ this.context = { ...this.context, ...context }
166
+ this.#updateConfig()
167
+ }
168
+
169
+ #setHttpMetrics () {
170
+ this.app.register(httpMetrics, {
171
+ registry: this.metricsRegistry,
172
+ customLabels: ['telemetry_id'],
173
+ getCustomLabels: req => {
174
+ const telemetryId = req.headers['x-plt-telemetry-id'] ?? 'unknown'
175
+ return { telemetry_id: telemetryId }
176
+ }
177
+ })
178
+
179
+ this.app.register(httpMetrics, {
180
+ registry: this.metricsRegistry,
181
+ customLabels: ['telemetry_id'],
182
+ getCustomLabels: req => {
183
+ const telemetryId = req.headers['x-plt-telemetry-id'] ?? 'unknown'
184
+ return { telemetry_id: telemetryId }
185
+ },
186
+ histogram: {
187
+ name: 'http_request_all_duration_seconds',
188
+ help: 'request duration in seconds summary for all requests',
189
+ collect: function () {
190
+ process.nextTick(() => this.reset())
191
+ }
192
+ },
193
+ summary: {
194
+ name: 'http_request_all_summary_seconds',
195
+ help: 'request duration in seconds histogram for all requests',
196
+ collect: function () {
197
+ process.nextTick(() => this.reset())
198
+ }
199
+ }
200
+ })
201
+ }
202
+
203
+ #updateConfig () {
204
+ if (!this.context) return
205
+
206
+ const { serviceId, telemetryConfig, metricsConfig, serverConfig, hasManagementApi, isEntrypoint, isProduction } =
207
+ this.context
208
+
209
+ const config = this.configManager.current
210
+
211
+ if (telemetryConfig) {
212
+ config.telemetry = telemetryConfig
213
+ }
214
+ if (metricsConfig) {
215
+ config.metrics = metricsConfig
216
+ }
217
+ if (serverConfig) {
218
+ config.server = deepmerge(config.server ?? {}, serverConfig ?? {})
219
+ }
220
+
221
+ if ((hasManagementApi && config.metrics === undefined) || config.metrics) {
222
+ const labels = config.metrics?.labels || {}
223
+ config.metrics = {
224
+ server: 'hide',
225
+ defaultMetrics: { enabled: isEntrypoint },
226
+ ...config.metrics,
227
+ labels: { serviceId, ...labels }
228
+ }
229
+ }
230
+
231
+ if (!isEntrypoint) {
232
+ config.server = config.server ?? {}
233
+ config.server.trustProxy = true
234
+ }
235
+
236
+ if (isProduction) {
237
+ if (config.plugins) {
238
+ config.plugins.typescript = false
239
+ }
240
+ config.watch = { enabled: false }
241
+ }
242
+
243
+ this.configManager.update(config)
244
+ }
245
+
246
+ #initLogger () {
247
+ if (this.configManager.current.server?.loggerInstance) {
248
+ this.logger = this.configManager.current.server?.loggerInstance
249
+ return
250
+ }
251
+
252
+ this.configManager.current.server ??= {}
253
+ this.loggerConfig = deepmerge(this.context.loggerConfig ?? {}, this.configManager.current.server?.logger ?? {})
254
+
255
+ const pinoOptions = {
256
+ level: this.loggerConfig?.level ?? 'trace'
257
+ }
258
+
259
+ if (this.context?.serviceId) {
260
+ pinoOptions.name = this.context.serviceId
261
+ }
262
+
263
+ this.logger = pino(pinoOptions)
264
+
265
+ // Only one of logger and loggerInstance should be set
266
+ delete this.configManager.current.server.logger
267
+ this.configManager.current.server.loggerInstance = this.logger
268
+ }
269
+ }
270
+
271
+ module.exports = { ServiceStackable }