@platformatic/itc 3.4.1 → 3.5.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.
package/eslint.config.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict'
1
+ import neostandard from 'neostandard'
2
2
 
3
- module.exports = require('neostandard')({ ts: true })
3
+ export default neostandard({ ts: true })
package/lib/errors.js CHANGED
@@ -1,24 +1,32 @@
1
- 'use strict'
1
+ import createError from '@fastify/error'
2
2
 
3
- const createError = require('@fastify/error')
3
+ export const ERROR_PREFIX = 'PLT_ITC'
4
4
 
5
- const ERROR_PREFIX = 'PLT_ITC'
6
-
7
- module.exports = {
8
- HandlerFailed: createError(`${ERROR_PREFIX}_HANDLER_FAILED`, 'Handler failed with error: %s'),
9
- HandlerNotFound: createError(`${ERROR_PREFIX}_HANDLER_NOT_FOUND`, 'Handler not found for request: "%s"'),
10
- PortAlreadyListening: createError(`${ERROR_PREFIX}_ALREADY_LISTENING`, 'ITC is already listening'),
11
- SendBeforeListen: createError(`${ERROR_PREFIX}_SEND_BEFORE_LISTEN`, 'ITC cannot send requests before listening'),
12
- InvalidRequestVersion: createError(`${ERROR_PREFIX}_INVALID_REQUEST_VERSION`, 'Invalid ITC request version: "%s"'),
13
- InvalidResponseVersion: createError(`${ERROR_PREFIX}_INVALID_RESPONSE_VERSION`, 'Invalid ITC response version: "%s"'),
14
- MissingRequestName: createError(`${ERROR_PREFIX}_MISSING_REQUEST_NAME`, 'ITC request name is missing'),
15
- MissingResponseName: createError(`${ERROR_PREFIX}_MISSING_RESPONSE_NAME`, 'ITC response name is missing'),
16
- MissingRequestReqId: createError(`${ERROR_PREFIX}_MISSING_REQUEST_REQ_ID`, 'ITC request reqId is missing'),
17
- MissingResponseReqId: createError(`${ERROR_PREFIX}_MISSING_RESPONSE_REQ_ID`, 'ITC response reqId is missing'),
18
- RequestNameIsNotString: createError(
19
- `${ERROR_PREFIX}_REQUEST_NAME_IS_NOT_STRING`,
20
- 'ITC request name is not a string: "%s"'
21
- ),
22
- MessagePortClosed: createError(`${ERROR_PREFIX}_MESSAGE_PORT_CLOSED`, 'ITC MessagePort is closed'),
23
- MissingName: createError(`${ERROR_PREFIX}_MISSING_NAME`, 'ITC name is missing')
24
- }
5
+ export const HandlerFailed = createError(`${ERROR_PREFIX}_HANDLER_FAILED`, 'Handler failed with error: %s')
6
+ export const HandlerNotFound = createError(`${ERROR_PREFIX}_HANDLER_NOT_FOUND`, 'Handler not found for request: "%s"')
7
+ export const PortAlreadyListening = createError(`${ERROR_PREFIX}_ALREADY_LISTENING`, 'ITC is already listening')
8
+ export const SendBeforeListen = createError(
9
+ `${ERROR_PREFIX}_SEND_BEFORE_LISTEN`,
10
+ 'ITC cannot send requests before listening'
11
+ )
12
+ export const InvalidRequestVersion = createError(
13
+ `${ERROR_PREFIX}_INVALID_REQUEST_VERSION`,
14
+ 'Invalid ITC request version: "%s"'
15
+ )
16
+ export const InvalidResponseVersion = createError(
17
+ `${ERROR_PREFIX}_INVALID_RESPONSE_VERSION`,
18
+ 'Invalid ITC response version: "%s"'
19
+ )
20
+ export const MissingRequestName = createError(`${ERROR_PREFIX}_MISSING_REQUEST_NAME`, 'ITC request name is missing')
21
+ export const MissingResponseName = createError(`${ERROR_PREFIX}_MISSING_RESPONSE_NAME`, 'ITC response name is missing')
22
+ export const MissingRequestReqId = createError(`${ERROR_PREFIX}_MISSING_REQUEST_REQ_ID`, 'ITC request reqId is missing')
23
+ export const MissingResponseReqId = createError(
24
+ `${ERROR_PREFIX}_MISSING_RESPONSE_REQ_ID`,
25
+ 'ITC response reqId is missing'
26
+ )
27
+ export const RequestNameIsNotString = createError(
28
+ `${ERROR_PREFIX}_REQUEST_NAME_IS_NOT_STRING`,
29
+ 'ITC request name is not a string: "%s"'
30
+ )
31
+ export const MessagePortClosed = createError(`${ERROR_PREFIX}_MESSAGE_PORT_CLOSED`, 'ITC MessagePort is closed')
32
+ export const MissingName = createError(`${ERROR_PREFIX}_MISSING_NAME`, 'ITC name is missing')
package/lib/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import { MessagePort } from 'node:worker_threads'
3
+
4
+ export type Handler = ((data: any) => any) | ((data: any) => Promise<any>)
5
+
6
+ export interface ITCConstructorOptions {
7
+ port: MessagePort
8
+ }
9
+
10
+ export class ITC extends EventEmitter {
11
+ constructor (options: ITCConstructorOptions)
12
+
13
+ send (name: string, message: any, options?: Record<string, any>): Promise<any>
14
+ notify (name: string, message: any, options?: Record<string, any>): void
15
+ handle (message: string, handler: Handler): void
16
+ getHandler (message: string): Handler | undefined
17
+ listen (): void
18
+ close (): void
19
+ }
20
+
21
+ declare module '@platformatic/itc' {
22
+ export { ITC }
23
+ }
@@ -1,9 +1,21 @@
1
- 'use strict'
2
-
3
- const { randomUUID } = require('node:crypto')
4
- const { EventEmitter, once } = require('node:events')
5
- const { Unpromise } = require('@watchable/unpromise')
6
- const errors = require('./errors.js')
1
+ import { Unpromise } from '@watchable/unpromise'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { EventEmitter, once } from 'node:events'
4
+ import {
5
+ HandlerFailed,
6
+ HandlerNotFound,
7
+ InvalidRequestVersion,
8
+ InvalidResponseVersion,
9
+ MessagePortClosed,
10
+ MissingName,
11
+ MissingRequestName,
12
+ MissingRequestReqId,
13
+ MissingResponseName,
14
+ MissingResponseReqId,
15
+ PortAlreadyListening,
16
+ RequestNameIsNotString,
17
+ SendBeforeListen
18
+ } from './errors.js'
7
19
 
8
20
  const PLT_ITC_REQUEST_TYPE = 'PLT_ITC_REQUEST'
9
21
  const PLT_ITC_RESPONSE_TYPE = 'PLT_ITC_RESPONSE'
@@ -11,35 +23,35 @@ const PLT_ITC_NOTIFICATION_TYPE = 'PLT_ITC_NOTIFICATION'
11
23
  const PLT_ITC_UNHANDLED_ERROR_TYPE = 'PLT_ITC_UNHANDLED_ERROR'
12
24
  const PLT_ITC_VERSION = '1.0.0'
13
25
 
14
- function parseRequest (request) {
26
+ export function parseRequest (request) {
15
27
  if (request.reqId === undefined) {
16
- throw new errors.MissingRequestReqId()
28
+ throw new MissingRequestReqId()
17
29
  }
18
30
  if (request.version !== PLT_ITC_VERSION) {
19
- throw new errors.InvalidRequestVersion(request.version)
31
+ throw new InvalidRequestVersion(request.version)
20
32
  }
21
33
  if (request.name === undefined) {
22
- throw new errors.MissingRequestName()
34
+ throw new MissingRequestName()
23
35
  }
24
36
  return request
25
37
  }
26
38
 
27
- function parseResponse (response) {
39
+ export function parseResponse (response) {
28
40
  if (response.reqId === undefined) {
29
- throw new errors.MissingResponseReqId()
41
+ throw new MissingResponseReqId()
30
42
  }
31
43
  if (response.version !== PLT_ITC_VERSION) {
32
- throw new errors.InvalidResponseVersion(response.version)
44
+ throw new InvalidResponseVersion(response.version)
33
45
  }
34
46
  if (response.name === undefined) {
35
- throw new errors.MissingResponseName()
47
+ throw new MissingResponseName()
36
48
  }
37
49
  return response
38
50
  }
39
51
 
40
- function generateRequest (name, data) {
52
+ export function generateRequest (name, data) {
41
53
  if (typeof name !== 'string') {
42
- throw new errors.RequestNameIsNotString(name.toString())
54
+ throw new RequestNameIsNotString(name.toString())
43
55
  }
44
56
 
45
57
  return {
@@ -47,31 +59,31 @@ function generateRequest (name, data) {
47
59
  version: PLT_ITC_VERSION,
48
60
  reqId: randomUUID(),
49
61
  name,
50
- data: sanitize(data)
62
+ data
51
63
  }
52
64
  }
53
65
 
54
- function generateResponse (request, error, data) {
66
+ export function generateResponse (request, error, data) {
55
67
  return {
56
68
  type: PLT_ITC_RESPONSE_TYPE,
57
69
  version: PLT_ITC_VERSION,
58
70
  reqId: request.reqId,
59
71
  name: request.name,
60
72
  error,
61
- data: sanitize(data)
73
+ data
62
74
  }
63
75
  }
64
76
 
65
- function generateNotification (name, data) {
77
+ export function generateNotification (name, data) {
66
78
  return {
67
79
  type: PLT_ITC_NOTIFICATION_TYPE,
68
80
  version: PLT_ITC_VERSION,
69
81
  name,
70
- data: sanitize(data)
82
+ data
71
83
  }
72
84
  }
73
85
 
74
- function generateUnhandledErrorResponse (error) {
86
+ export function generateUnhandledErrorResponse (error) {
75
87
  return {
76
88
  type: PLT_ITC_UNHANDLED_ERROR_TYPE,
77
89
  version: PLT_ITC_VERSION,
@@ -80,17 +92,16 @@ function generateUnhandledErrorResponse (error) {
80
92
  }
81
93
  }
82
94
 
83
- function sanitize (data) {
84
- if (!data || typeof data !== 'object') {
95
+ export function sanitize (data, transferList) {
96
+ if (!data || typeof data !== 'object' || transferList?.includes(data) || data instanceof Error) {
85
97
  return data
86
98
  }
87
99
 
88
100
  let sanitized
89
101
 
90
- if (Buffer.isBuffer(data)) {
91
- return {
92
- data: Array.from(data.values())
93
- }
102
+ if (Buffer.isBuffer(data) || data instanceof Uint8Array) {
103
+ // This will convert as Uint8Array
104
+ return data
94
105
  } else if (Array.isArray(data)) {
95
106
  sanitized = []
96
107
 
@@ -102,7 +113,7 @@ function sanitize (data) {
102
113
  continue
103
114
  }
104
115
 
105
- sanitized.push(value && typeof value === 'object' ? sanitize(value) : value)
116
+ sanitized.push(value && typeof value === 'object' ? sanitize(value, transferList) : value)
106
117
  }
107
118
  } else {
108
119
  sanitized = {}
@@ -114,14 +125,14 @@ function sanitize (data) {
114
125
  continue
115
126
  }
116
127
 
117
- sanitized[key] = value && typeof value === 'object' ? sanitize(value) : value
128
+ sanitized[key] = value && typeof value === 'object' ? sanitize(value, transferList) : value
118
129
  }
119
130
  }
120
131
 
121
132
  return sanitized
122
133
  }
123
134
 
124
- class ITC extends EventEmitter {
135
+ export class ITC extends EventEmitter {
125
136
  #requestEmitter
126
137
  #handlers
127
138
  #listening
@@ -136,7 +147,7 @@ class ITC extends EventEmitter {
136
147
  super()
137
148
 
138
149
  if (!name) {
139
- throw new errors.MissingName()
150
+ throw new MissingName()
140
151
  }
141
152
 
142
153
  // The name property is useful only for debugging purposes.
@@ -177,17 +188,20 @@ class ITC extends EventEmitter {
177
188
  }
178
189
  }
179
190
 
180
- async send (name, message) {
191
+ getHandler (message) {
192
+ return this.#handlers.get(message)
193
+ }
194
+
195
+ async send (name, message, options) {
181
196
  if (!this.#listening) {
182
- throw new errors.SendBeforeListen()
197
+ throw new SendBeforeListen()
183
198
  }
184
199
 
185
200
  try {
186
201
  this._enableKeepAlive()
187
202
 
188
203
  const request = generateRequest(name, message)
189
-
190
- this._send(request)
204
+ this._send(request, options)
191
205
 
192
206
  const responsePromise = once(this.#requestEmitter, request.reqId).then(([response]) => response)
193
207
 
@@ -200,8 +214,8 @@ class ITC extends EventEmitter {
200
214
  }
201
215
  }
202
216
 
203
- async notify (name, message) {
204
- this._send(generateNotification(name, message))
217
+ notify (name, message, options) {
218
+ this._send(generateNotification(name, message), options)
205
219
  }
206
220
 
207
221
  handle (message, handler) {
@@ -210,18 +224,20 @@ class ITC extends EventEmitter {
210
224
 
211
225
  listen () {
212
226
  if (this.#listening) {
213
- throw new errors.PortAlreadyListening()
227
+ throw new PortAlreadyListening()
214
228
  }
215
229
  this.#listening = true
216
230
 
217
- this._setupListener(message => {
231
+ this._setupListener((message, context) => {
232
+ context ??= {}
233
+
218
234
  const messageType = message.type
219
235
  if (messageType === PLT_ITC_REQUEST_TYPE) {
220
- this.#handleRequest(message)
236
+ this.#handleRequest(message, context)
221
237
  return
222
238
  }
223
239
  if (messageType === PLT_ITC_RESPONSE_TYPE) {
224
- this.#handleResponse(message)
240
+ this.#handleResponse(message, context)
225
241
  return
226
242
  }
227
243
  if (messageType === PLT_ITC_NOTIFICATION_TYPE) {
@@ -234,7 +250,7 @@ class ITC extends EventEmitter {
234
250
 
235
251
  this.#closePromise = this._createClosePromise().then(() => {
236
252
  this.#listening = false
237
- const error = new errors.MessagePortClosed()
253
+ const error = new MessagePortClosed()
238
254
  clearInterval(this.#keepAlive)
239
255
  this.#keepAliveCount = -1000
240
256
  return { error, data: null }
@@ -254,8 +270,9 @@ class ITC extends EventEmitter {
254
270
  this.port.on('message', listener)
255
271
  }
256
272
 
257
- _send (request) {
258
- this.port.postMessage(request)
273
+ _send (request, options) {
274
+ const transferList = options?.transferList || []
275
+ this.port.postMessage(sanitize(request, transferList), transferList)
259
276
  }
260
277
 
261
278
  _createClosePromise () {
@@ -267,7 +284,7 @@ class ITC extends EventEmitter {
267
284
  this.port?.close?.()
268
285
  }
269
286
 
270
- async #handleRequest (raw) {
287
+ async #handleRequest (raw, context) {
271
288
  let request = null
272
289
  let handler = null
273
290
  let response = null
@@ -279,11 +296,11 @@ class ITC extends EventEmitter {
279
296
  handler = this.#handlers.get(request.name)
280
297
 
281
298
  if (handler) {
282
- const result = await handler(request.data)
299
+ const result = await handler(request.data, context)
283
300
  response = generateResponse(request, null, result)
284
301
  } else {
285
302
  if (this.#throwOnMissingHandler) {
286
- throw new errors.HandlerNotFound(request.name)
303
+ throw new HandlerNotFound(request.name)
287
304
  }
288
305
 
289
306
  response = generateResponse(request, null)
@@ -294,7 +311,7 @@ class ITC extends EventEmitter {
294
311
  } else if (!handler) {
295
312
  response = generateResponse(request, error, null)
296
313
  } else {
297
- const failedError = new errors.HandlerFailed(error.message)
314
+ const failedError = new HandlerFailed(error.message)
298
315
  failedError.handlerError = error
299
316
  // This is needed as the code might be lost when sending the message over the port
300
317
  failedError.handlerErrorCode = error.code
@@ -305,24 +322,27 @@ class ITC extends EventEmitter {
305
322
  this.#handling = false
306
323
  }
307
324
 
308
- this._send(response)
325
+ this._send(response, context)
309
326
 
310
327
  if (this.#closeAfterCurrentRequest) {
311
328
  this.close()
312
329
  }
313
330
  }
314
331
 
315
- #handleResponse (response) {
332
+ #handleResponse (response, context) {
316
333
  try {
317
334
  response = parseResponse(response)
318
335
  } catch (error) {
319
336
  response = generateUnhandledErrorResponse(error)
320
- this._send(response)
337
+ this._send(response, context)
321
338
  return
322
339
  }
323
340
 
324
- const reqId = response.reqId
325
- this.#requestEmitter.emit(reqId, response)
341
+ this._emitResponse(response)
342
+ }
343
+
344
+ _emitResponse (response) {
345
+ this.#requestEmitter.emit(response.reqId, response)
326
346
  }
327
347
 
328
348
  _enableKeepAlive () {
@@ -343,13 +363,4 @@ class ITC extends EventEmitter {
343
363
  }
344
364
  }
345
365
 
346
- module.exports = {
347
- ITC,
348
- parseRequest,
349
- parseResponse,
350
- generateRequest,
351
- generateResponse,
352
- generateNotification,
353
- generateUnhandledErrorResponse,
354
- sanitize
355
- }
366
+ export * as errors from './errors.js'
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@platformatic/itc",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "",
5
- "main": "index.js",
6
- "author": "Matteo Collina <hello@matteocollina.com>",
5
+ "main": "lib/index.js",
6
+ "type": "module",
7
+ "types": "lib/index.d.ts",
8
+ "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
7
9
  "repository": {
8
10
  "type": "git",
9
11
  "url": "git+https://github.com/platformatic/platformatic.git"
@@ -15,18 +17,22 @@
15
17
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
18
  "devDependencies": {
17
19
  "@types/node": "^22.5.0",
18
- "borp": "^0.17.0",
20
+ "cleaner-spec-reporter": "^0.5.0",
19
21
  "eslint": "9",
20
- "neostandard": "^0.11.1",
21
- "tsd": "^0.31.0",
22
+ "neostandard": "^0.12.0",
23
+ "tsd": "^0.33.0",
22
24
  "typescript": "^5.5.4"
23
25
  },
24
26
  "dependencies": {
25
27
  "@fastify/error": "^4.0.0",
26
28
  "@watchable/unpromise": "^1.0.2"
27
29
  },
30
+ "engines": {
31
+ "node": ">=22.19.0"
32
+ },
28
33
  "scripts": {
29
- "test": "npm run lint && borp --timeout=180000 --concurrency=1 --coverage && tsd",
34
+ "test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
35
+ "posttest": "tsd -t index.test-d.ts test",
30
36
  "lint": "eslint"
31
37
  }
32
38
  }
package/index.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import { EventEmitter } from 'node:events'
2
- import { MessagePort } from 'node:worker_threads'
3
-
4
- export interface ITCConstructorOptions {
5
- port: MessagePort;
6
- }
7
-
8
- export class ITC extends EventEmitter {
9
- constructor (options: ITCConstructorOptions)
10
-
11
- send (name: string, message: any): Promise<any>
12
- notify (name: string, message: any): void
13
- handle (message: string, handler: (data: any) => Promise<any>): void
14
- listen (): void
15
- close (): void
16
- }
17
-
18
- declare module '@platformatic/itc' {
19
- export { ITC }
20
- }
package/index.js DELETED
@@ -1,3 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = require('./lib/itc')
package/index.test-d.ts DELETED
@@ -1,38 +0,0 @@
1
- import { MessagePort } from 'node:worker_threads'
2
- import { expectError, expectType } from 'tsd'
3
- import { ITC, ITCConstructorOptions } from './index'
4
-
5
- const mockPort = {} as MessagePort
6
-
7
- const options: ITCConstructorOptions = { port: mockPort }
8
- expectType<ITCConstructorOptions>(options)
9
-
10
- const itc = new ITC({ port: mockPort })
11
- expectType<ITC>(itc)
12
-
13
- expectType<Promise<any>>(itc.send('testMessage', { key: 'value' }))
14
- expectType<void>(itc.notify('testMessage', { key: 'value' }))
15
- expectType<void>(
16
- itc.handle('testMessage', async (data) => {
17
- return { key: 'value' }
18
- })
19
- )
20
- expectType<void>(itc.listen())
21
- expectType<void>(itc.close())
22
-
23
- expectError(itc.send(123, { key: 'value' })) // send name must be a string
24
- expectError(itc.notify(123, { key: 'value' })) // send name must be a string
25
- expectError(
26
- itc.handle(123, async (data) => {
27
- return { key: 'value' }
28
- })
29
- ) // handle message must be a string
30
- expectError(
31
- itc.handle('testMessage', (data) => {
32
- return 'string'
33
- })
34
- ) // handler must return a Promise
35
-
36
- itc.on('unhandledError', (error: Error) => {
37
- expectType<Error>(error)
38
- })