@platformatic/runtime 2.0.0-alpha.3 → 2.0.0-alpha.4

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/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha2Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha4Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
+ "entrypoint": "service-1",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "restartOnError": 100,
8
+ "server": {
9
+ "hostname": "127.0.0.1",
10
+ "port": "0",
11
+ "logger": {
12
+ "level": "info"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (app) {
4
+ app.log.info('Service 1 plugin')
5
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function () {
4
+ throw new Error('Crash!')
5
+ }
@@ -1,8 +1,22 @@
1
1
  'use strict'
2
+
3
+ const { request } = require('undici')
4
+
2
5
  module.exports = async function (fastify, opts) {
3
6
  // This returns the traceId set on the span by the service
4
- fastify.get('/', async (request, reply) => {
5
- const traceId = request.span.spanContext().traceId
7
+ fastify.get('/', async (req, reply) => {
8
+ const traceId = req.span.spanContext().traceId
6
9
  return { traceId }
7
10
  })
11
+
12
+ fastify.get('/service-1/echo-headers', async (req, reply) => {
13
+ const res = await request('http://service-1.plt.local/echo-headers', {
14
+ method: 'GET',
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ },
18
+ })
19
+ const body = await res.body.json()
20
+ return body
21
+ })
8
22
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
3
+ "server": {
4
+ "hostname": "127.0.0.1",
5
+ "port": "0",
6
+ "logger": {
7
+ "level": "info"
8
+ }
9
+ },
10
+ "service": {
11
+ "openapi": true
12
+ },
13
+ "plugins": {
14
+ "paths": [
15
+ "./routes"
16
+ ],
17
+ "typescript": false
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+ module.exports = async function (fastify, opts) {
3
+ // This returns the traceId set on the span by the service
4
+ fastify.get('/echo-headers', async (request, reply) => {
5
+ return { headers: request.headers }
6
+ })
7
+ }
package/lib/runtime.js CHANGED
@@ -28,6 +28,8 @@ const MAX_LISTENERS_COUNT = 100
28
28
  const MAX_METRICS_QUEUE_LENGTH = 5 * 60 // 5 minutes in seconds
29
29
  const COLLECT_METRICS_TIMEOUT = 1000
30
30
 
31
+ const MAX_BOOTSTRAP_ATTEMPTS = 5
32
+
31
33
  class Runtime extends EventEmitter {
32
34
  #configManager
33
35
  #runtimeTmpDir
@@ -46,7 +48,8 @@ class Runtime extends EventEmitter {
46
48
  #managementApi
47
49
  #prometheusServer
48
50
  #startedServices
49
- #crashedServicesTimers
51
+ #restartPromises
52
+ #bootstrapAttempts
50
53
 
51
54
  constructor (configManager, runtimeLogsDir, env) {
52
55
  super()
@@ -63,7 +66,8 @@ class Runtime extends EventEmitter {
63
66
  this.#interceptor = createThreadInterceptor({ domain: '.plt.local' })
64
67
  this.#status = undefined
65
68
  this.#startedServices = new Map()
66
- this.#crashedServicesTimers = new Map()
69
+ this.#restartPromises = new Map()
70
+ this.#bootstrapAttempts = new Map()
67
71
  }
68
72
 
69
73
  async init () {
@@ -126,8 +130,15 @@ class Runtime extends EventEmitter {
126
130
  this.#updateStatus('starting')
127
131
 
128
132
  // Important: do not use Promise.all here since it won't properly manage dependencies
129
- for (const service of this.#servicesIds) {
130
- await this.startService(service)
133
+ try {
134
+ for (const service of this.#servicesIds) {
135
+ await this.startService(service)
136
+ }
137
+ } catch (error) {
138
+ // Wait for the next tick so that the error is logged first
139
+ await sleep(1)
140
+ await this.close()
141
+ throw error
131
142
  }
132
143
 
133
144
  this.#updateStatus('started')
@@ -145,13 +156,7 @@ class Runtime extends EventEmitter {
145
156
  }
146
157
 
147
158
  this.#updateStatus('stopping')
148
-
149
- for (const timer of this.#crashedServicesTimers.values()) {
150
- clearTimeout(timer)
151
- }
152
-
153
159
  this.#startedServices.clear()
154
- this.#crashedServicesTimers.clear()
155
160
 
156
161
  await Promise.all(this.#servicesIds.map(service => this._stopService(service)))
157
162
 
@@ -209,12 +214,6 @@ class Runtime extends EventEmitter {
209
214
  // This is set here so that if the service fails while starting we track the status
210
215
  this.#startedServices.set(id, true)
211
216
 
212
- // Make sure we don't restart twice
213
- const crashedTimer = this.#crashedServicesTimers.get(id)
214
- if (crashedTimer) {
215
- clearTimeout(crashedTimer)
216
- }
217
-
218
217
  let service = await this.#getServiceById(id, false, false)
219
218
 
220
219
  // The service was stopped, recreate the thread
@@ -226,10 +225,36 @@ class Runtime extends EventEmitter {
226
225
  service = await this.#getServiceById(id)
227
226
  }
228
227
 
229
- const serviceUrl = await sendViaITC(service, 'start')
228
+ try {
229
+ const serviceUrl = await sendViaITC(service, 'start')
230
+ if (serviceUrl) {
231
+ this.#url = serviceUrl
232
+ }
233
+ this.#bootstrapAttempts.set(id, 0)
234
+ } catch (error) {
235
+ // TODO: handle port allocation error here
236
+ if (error.code === 'EADDRINUSE') throw error
230
237
 
231
- if (serviceUrl) {
232
- this.#url = serviceUrl
238
+ this.logger.error({ error }, `Failed to start service "${id}".`)
239
+
240
+ const config = this.#configManager.current
241
+ const restartOnError = config.restartOnError
242
+
243
+ let bootstrapAttempt = this.#bootstrapAttempts.get(id)
244
+ if (bootstrapAttempt++ >= MAX_BOOTSTRAP_ATTEMPTS || restartOnError === 0) {
245
+ this.logger.error(
246
+ `Failed to start service "${id}" after ${MAX_BOOTSTRAP_ATTEMPTS} attempts.`
247
+ )
248
+ throw error
249
+ }
250
+
251
+ this.logger.warn(
252
+ `Starting a service "${id}" in ${restartOnError}ms. ` +
253
+ `Attempt ${bootstrapAttempt} of ${MAX_BOOTSTRAP_ATTEMPTS}...`
254
+ )
255
+
256
+ this.#bootstrapAttempts.set(id, bootstrapAttempt)
257
+ await this.#restartCrashedService(id)
233
258
  }
234
259
  }
235
260
 
@@ -243,11 +268,13 @@ class Runtime extends EventEmitter {
243
268
 
244
269
  this.#startedServices.set(id, false)
245
270
 
271
+ this.logger?.info(`Stopping service "${id}"...`)
272
+
246
273
  // Always send the stop message, it will shut down workers that only had ITC and interceptors setup
247
274
  try {
248
275
  await Promise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
249
276
  } catch (error) {
250
- this.logger.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
277
+ this.logger?.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
251
278
  }
252
279
 
253
280
  // Wait for the worker thread to finish, we're going to create a new one if the service is ever restarted
@@ -638,6 +665,8 @@ class Runtime extends EventEmitter {
638
665
  }
639
666
 
640
667
  async #setupService (serviceConfig) {
668
+ if (this.#status === 'stopping' || this.#status === 'closed') return
669
+
641
670
  const config = this.#configManager.current
642
671
  const { autoload, restartOnError } = config
643
672
 
@@ -645,6 +674,10 @@ class Runtime extends EventEmitter {
645
674
  const { port1: loggerDestination, port2: loggingPort } = new MessageChannel()
646
675
  loggerDestination.on('message', this.#forwardThreadLog.bind(this))
647
676
 
677
+ if (!this.#bootstrapAttempts.has(id)) {
678
+ this.#bootstrapAttempts.set(id, 0)
679
+ }
680
+
648
681
  const service = new Worker(kWorkerFile, {
649
682
  workerData: {
650
683
  config,
@@ -677,16 +710,21 @@ class Runtime extends EventEmitter {
677
710
  loggerDestination.close()
678
711
  loggingPort.close()
679
712
 
713
+ if (this.#status === 'stopping') return
714
+
680
715
  // Wait for the next tick so that crashed from the thread are logged first
681
716
  setImmediate(() => {
682
- // If this was started, it means it crashed
683
- if (started && (this.#status === 'started' || this.#status === 'starting')) {
717
+ this.logger.warn(`Service "${id}" unexpectedly exited with code ${code}.`)
718
+
719
+ // Restart the service if it was started
720
+ if (started && this.#status === 'started') {
684
721
  if (restartOnError > 0) {
685
- this.#restartCrashedService(serviceConfig, code, started)
722
+ this.logger.warn(`Restarting a service "${id}" in ${restartOnError}ms...`)
723
+ this.#restartCrashedService(id).catch((err) => {
724
+ this.logger.error({ err }, `Failed to restart service "${id}".`)
725
+ })
686
726
  } else {
687
- this.logger.warn(
688
- `Service ${id} unexpectedly exited with code ${code}. The service is no longer available ...`
689
- )
727
+ this.logger.warn(`The "${id}" service is no longer available.`)
690
728
  }
691
729
  }
692
730
  })
@@ -698,6 +736,7 @@ class Runtime extends EventEmitter {
698
736
  // Setup ITC
699
737
  service[kITC] = new ITC({ port: service })
700
738
  service[kITC].listen()
739
+ service[kITC].handle('getServiceMeta', this.#getServiceMeta.bind(this))
701
740
 
702
741
  // Handle services changes
703
742
  // This is not purposely activated on when this.#configManager.current.watch === true
@@ -713,9 +752,9 @@ class Runtime extends EventEmitter {
713
752
  await this.startService(id)
714
753
  }
715
754
 
716
- this.logger.info(`Service ${id} has been successfully reloaded ...`)
755
+ this.logger?.info(`Service ${id} has been successfully reloaded ...`)
717
756
  } catch (e) {
718
- this.logger.error(e)
757
+ this.logger?.error(e)
719
758
  }
720
759
  })
721
760
 
@@ -743,26 +782,38 @@ class Runtime extends EventEmitter {
743
782
  }
744
783
  }
745
784
 
746
- async #restartCrashedService (serviceConfig, code, started) {
747
- const restartTimeout = this.#configManager.current.restartOnError
748
- const id = serviceConfig.id
785
+ async #restartCrashedService (id) {
786
+ const config = this.#configManager.current
787
+ const serviceConfig = config.services.find(s => s.id === id)
788
+
789
+ let restartPromise = this.#restartPromises.get(id)
790
+ if (restartPromise) {
791
+ await restartPromise
792
+ return
793
+ }
749
794
 
750
- this.logger.warn(`Service ${id} unexpectedly exited with code ${code}. Restarting in ${restartTimeout}ms ...`)
795
+ restartPromise = new Promise((resolve, reject) => {
796
+ setTimeout(async () => {
797
+ this.#restartPromises.delete(id)
751
798
 
752
- const timer = setTimeout(async () => {
753
- try {
754
- await this.#setupService(serviceConfig)
799
+ try {
800
+ await this.#setupService(serviceConfig)
755
801
 
756
- if (started) {
757
- this.#startedServices.set(id, false)
758
- await this.startService(id)
802
+ const started = this.#startedServices.get(id)
803
+ if (started) {
804
+ this.#startedServices.set(id, false)
805
+ await this.startService(id)
806
+ }
807
+
808
+ resolve()
809
+ } catch (err) {
810
+ reject(err)
759
811
  }
760
- } catch (err) {
761
- this.logger.error({ err }, `Failed to restart service ${id}.`)
762
- }
763
- }, restartTimeout).unref()
812
+ }, config.restartOnError)
813
+ })
764
814
 
765
- this.#crashedServicesTimers.set(id, timer)
815
+ this.#restartPromises.set(id, restartPromise)
816
+ await restartPromise
766
817
  }
767
818
 
768
819
  async #getServiceById (id, ensureStarted = false, mustExist = true) {
@@ -787,6 +838,25 @@ class Runtime extends EventEmitter {
787
838
  return service
788
839
  }
789
840
 
841
+ async #getServiceMeta (id) {
842
+ const service = this.#services.get(id)
843
+
844
+ if (!service) {
845
+ throw new errors.ServiceNotFoundError(id, Array.from(this.#services.keys()).join(', '))
846
+ }
847
+
848
+ try {
849
+ return await service[kITC].send('getServiceMeta')
850
+ } catch (e) {
851
+ // The service exports no meta, return an empty object
852
+ if (e.code === 'PLT_ITC_HANDLER_NOT_FOUND') {
853
+ return {}
854
+ }
855
+
856
+ throw e
857
+ }
858
+ }
859
+
790
860
  async #getRuntimePackageJson () {
791
861
  const runtimeDir = this.#configManager.dirname
792
862
  const packageJsonPath = join(runtimeDir, 'package.json')
package/lib/worker/app.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { EventEmitter } = require('node:events')
4
4
  const { FileWatcher } = require('@platformatic/utils')
5
+ const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
5
6
  const debounce = require('debounce')
6
7
 
7
8
  const errors = require('../errors')
@@ -31,6 +32,7 @@ class PlatformaticApp extends EventEmitter {
31
32
  serviceId: this.appConfig.id,
32
33
  directory: this.appConfig.path,
33
34
  isEntrypoint: this.appConfig.entrypoint,
35
+ isProduction: false,
34
36
  telemetryConfig,
35
37
  metricsConfig,
36
38
  serverConfig,
@@ -90,6 +92,8 @@ class PlatformaticApp extends EventEmitter {
90
92
  context: this.#context,
91
93
  })
92
94
  this.stackable = this.#wrapStackable(stackable)
95
+
96
+ this.#updateDispatcher()
93
97
  } catch (err) {
94
98
  this.#logAndExit(err)
95
99
  }
@@ -103,7 +107,7 @@ class PlatformaticApp extends EventEmitter {
103
107
  this.#starting = true
104
108
 
105
109
  try {
106
- await this.stackable.init?.()
110
+ await this.stackable.init()
107
111
  } catch (err) {
108
112
  this.#logAndExit(err)
109
113
  }
@@ -219,6 +223,28 @@ class PlatformaticApp extends EventEmitter {
219
223
  }
220
224
  return newStackable
221
225
  }
226
+
227
+ #updateDispatcher () {
228
+ const telemetryConfig = this.#context.telemetryConfig
229
+ const telemetryId = telemetryConfig?.serviceName
230
+
231
+ const interceptor = dispatch => {
232
+ return function InterceptedDispatch (opts, handler) {
233
+ if (telemetryId) {
234
+ opts.headers = {
235
+ ...opts.headers,
236
+ 'x-plt-telemetry-id': telemetryId,
237
+ }
238
+ }
239
+ return dispatch(opts, handler)
240
+ }
241
+ }
242
+
243
+ const dispatcher = getGlobalDispatcher()
244
+ .compose(interceptor)
245
+
246
+ setGlobalDispatcher(dispatcher)
247
+ }
222
248
  }
223
249
 
224
250
  module.exports = { PlatformaticApp }
@@ -13,8 +13,7 @@ const { wire } = require('undici-thread-interceptor')
13
13
  const { PlatformaticApp } = require('./app')
14
14
  const { setupITC } = require('./itc')
15
15
  const loadInterceptors = require('./interceptors')
16
- const { MessagePortWritable } = require('../streams/message-port-writable')
17
- const { createPinoWritable } = require('../streams/pino-writable')
16
+ const { MessagePortWritable, createPinoWritable } = require('@platformatic/utils')
18
17
  const { kId, kITC } = require('./symbols')
19
18
 
20
19
  process.on('uncaughtException', handleUnhandled.bind(null, 'uncaught exception'))
@@ -88,15 +87,23 @@ async function main () {
88
87
  } else if (service.useHttp) {
89
88
  serverConfig = {
90
89
  port: 0,
91
- host: '127.0.0.1',
90
+ hostname: '127.0.0.1',
92
91
  keepAliveTimeout: 5000,
93
92
  }
94
93
  }
95
94
 
95
+ let telemetryConfig = config.telemetry
96
+ if (telemetryConfig) {
97
+ telemetryConfig = {
98
+ ...telemetryConfig,
99
+ serviceName: `${telemetryConfig.serviceName}-${service.id}`,
100
+ }
101
+ }
102
+
96
103
  // Create the application
97
104
  app = new PlatformaticApp(
98
105
  service,
99
- config.telemetry,
106
+ telemetryConfig,
100
107
  serverConfig,
101
108
  !!config.managementApi,
102
109
  !!config.watch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  "express": "^4.18.3",
27
27
  "fast-jwt": "^4.0.0",
28
28
  "get-port": "^7.1.0",
29
+ "json-schema-to-typescript": "^15.0.0",
29
30
  "neostandard": "^0.11.1",
30
31
  "pino-abstract-transport": "^1.1.0",
31
32
  "split2": "^4.2.0",
@@ -33,11 +34,11 @@
33
34
  "typescript": "^5.5.4",
34
35
  "undici-oidc-interceptor": "^0.5.0",
35
36
  "why-is-node-running": "^2.2.2",
36
- "@platformatic/composer": "2.0.0-alpha.3",
37
- "@platformatic/service": "2.0.0-alpha.3",
38
- "@platformatic/sql-graphql": "2.0.0-alpha.3",
39
- "@platformatic/db": "2.0.0-alpha.3",
40
- "@platformatic/sql-mapper": "2.0.0-alpha.3"
37
+ "@platformatic/composer": "2.0.0-alpha.4",
38
+ "@platformatic/db": "2.0.0-alpha.4",
39
+ "@platformatic/service": "2.0.0-alpha.4",
40
+ "@platformatic/sql-graphql": "2.0.0-alpha.4",
41
+ "@platformatic/sql-mapper": "2.0.0-alpha.4"
41
42
  },
42
43
  "dependencies": {
43
44
  "@fastify/error": "^3.4.1",
@@ -65,19 +66,20 @@
65
66
  "undici": "^6.9.0",
66
67
  "undici-thread-interceptor": "^0.5.0",
67
68
  "ws": "^8.16.0",
68
- "@platformatic/basic": "2.0.0-alpha.3",
69
- "@platformatic/itc": "2.0.0-alpha.3",
70
- "@platformatic/telemetry": "2.0.0-alpha.3",
71
- "@platformatic/generators": "2.0.0-alpha.3",
72
- "@platformatic/config": "2.0.0-alpha.3",
73
- "@platformatic/utils": "2.0.0-alpha.3",
74
- "@platformatic/ts-compiler": "2.0.0-alpha.3"
69
+ "@platformatic/config": "2.0.0-alpha.4",
70
+ "@platformatic/basic": "2.0.0-alpha.4",
71
+ "@platformatic/generators": "2.0.0-alpha.4",
72
+ "@platformatic/itc": "2.0.0-alpha.4",
73
+ "@platformatic/telemetry": "2.0.0-alpha.4",
74
+ "@platformatic/ts-compiler": "2.0.0-alpha.4",
75
+ "@platformatic/utils": "2.0.0-alpha.4"
75
76
  },
76
77
  "scripts": {
77
78
  "test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
78
79
  "coverage": "npm run lint && borp -X=fixtures -X=test -C --concurrency=1 --timeout=180000 && tsd",
79
- "lint": "eslint",
80
80
  "gen-schema": "node lib/schema.js > schema.json",
81
- "gen-types": "json2ts > config.d.ts < schema.json"
81
+ "gen-types": "json2ts > config.d.ts < schema.json",
82
+ "build": "pnpm run gen-schema && pnpm run gen-types",
83
+ "lint": "eslint"
82
84
  }
83
85
  }
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.2.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.4.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,44 +0,0 @@
1
- 'use strict'
2
-
3
- const { Writable } = require('node:stream')
4
-
5
- class MessagePortWritable extends Writable {
6
- #port
7
- #metadata
8
-
9
- constructor (options) {
10
- const { port, metadata, ...opts } = options
11
-
12
- super({ ...opts, decodeStrings: false })
13
- this.#port = port
14
- this.#metadata = metadata
15
- }
16
-
17
- // Since this is only invoked by pino, we only receive strings
18
- _write (chunk, encoding, callback) {
19
- this.#port.postMessage({ metadata: this.#metadata, logs: [chunk.toString(encoding ?? 'utf-8')] })
20
-
21
- // Important: do not remove nextTick otherwise _writev will never be used
22
- process.nextTick(callback)
23
- }
24
-
25
- // Since this is only invoked by pino, we only receive strings
26
- _writev (chunks, callback) {
27
- this.#port.postMessage({ metadata: this.#metadata, logs: chunks.map(c => c.chunk.toString(c.encoding ?? 'utf-8')) })
28
-
29
- // Important: do not remove nextTick otherwise _writev will never be used
30
- process.nextTick(callback)
31
- }
32
-
33
- _final (callback) {
34
- this.#port.close()
35
- callback()
36
- }
37
-
38
- _destroy (err, callback) {
39
- this.#port.close()
40
- callback(err)
41
- }
42
- }
43
-
44
- module.exports = { MessagePortWritable }
@@ -1,30 +0,0 @@
1
- 'use strict'
2
-
3
- const { Writable } = require('node:stream')
4
- const inspect = require('node:util')
5
-
6
- class PinoWritable extends Writable {
7
- #write
8
-
9
- constructor (options) {
10
- const { pino, level, ...opts } = options
11
-
12
- super({ ...opts, decodeStrings: false })
13
- this.#write = pino[level].bind(pino)
14
- }
15
-
16
- _write (chunk, encoding, callback) {
17
- this.#write({ raw: encoding === 'buffer' ? inspect(chunk) : chunk.toString(encoding ?? 'utf-8') })
18
- callback()
19
- }
20
-
21
- // We don't define _writev as we have to serialize messages one by one so batching wouldn't make any sense.
22
- }
23
-
24
- function createPinoWritable (pino, level) {
25
- const writable = new PinoWritable({ pino, level })
26
- writable.write = writable.write.bind(writable)
27
- return writable
28
- }
29
-
30
- module.exports = { PinoWritable, createPinoWritable }