@platformatic/runtime 3.4.1 → 3.5.1
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/README.md +1 -1
- package/config.d.ts +224 -77
- package/eslint.config.js +3 -5
- package/index.d.ts +73 -24
- package/index.js +173 -29
- package/lib/config.js +279 -197
- package/lib/errors.js +126 -34
- package/lib/generator.js +640 -0
- package/lib/logger.js +43 -41
- package/lib/management-api.js +109 -118
- package/lib/prom-server.js +202 -16
- package/lib/runtime.js +1963 -585
- package/lib/scheduler.js +119 -0
- package/lib/schema.js +22 -234
- package/lib/shared-http-cache.js +43 -0
- package/lib/upgrade.js +6 -8
- package/lib/utils.js +6 -61
- package/lib/version.js +7 -0
- package/lib/versions/v1.36.0.js +2 -4
- package/lib/versions/v1.5.0.js +2 -4
- package/lib/versions/v2.0.0.js +3 -5
- package/lib/versions/v3.0.0.js +16 -0
- package/lib/worker/controller.js +302 -0
- package/lib/worker/http-cache.js +171 -0
- package/lib/worker/interceptors.js +190 -10
- package/lib/worker/itc.js +146 -59
- package/lib/worker/main.js +220 -81
- package/lib/worker/messaging.js +182 -0
- package/lib/worker/round-robin-map.js +62 -0
- package/lib/worker/shared-context.js +22 -0
- package/lib/worker/symbols.js +14 -5
- package/package.json +47 -38
- package/schema.json +1383 -55
- package/help/compile.txt +0 -8
- package/help/help.txt +0 -5
- package/help/start.txt +0 -21
- package/index.test-d.ts +0 -41
- package/lib/build-server.js +0 -69
- package/lib/compile.js +0 -98
- package/lib/dependencies.js +0 -59
- package/lib/generator/README.md +0 -32
- package/lib/generator/errors.js +0 -10
- package/lib/generator/runtime-generator.d.ts +0 -37
- package/lib/generator/runtime-generator.js +0 -498
- package/lib/start.js +0 -190
- package/lib/worker/app.js +0 -278
- package/lib/worker/default-stackable.js +0 -33
- package/lib/worker/metrics.js +0 -122
- package/runtime.mjs +0 -54
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { executeWithTimeout, kTimeout } from '@platformatic/foundation'
|
|
2
|
+
import { ITC, generateResponse, sanitize } from '@platformatic/itc'
|
|
3
|
+
import { MessagingError } from '../errors.js'
|
|
4
|
+
import { RoundRobinMap } from './round-robin-map.js'
|
|
5
|
+
import { kITC, kWorkersBroadcast } from './symbols.js'
|
|
6
|
+
|
|
7
|
+
const kPendingResponses = Symbol('plt.messaging.pendingResponses')
|
|
8
|
+
|
|
9
|
+
export class MessagingITC extends ITC {
|
|
10
|
+
#timeout
|
|
11
|
+
#listener
|
|
12
|
+
#closeResolvers
|
|
13
|
+
#broadcastChannel
|
|
14
|
+
#workers
|
|
15
|
+
#sources
|
|
16
|
+
|
|
17
|
+
constructor (id, runtimeConfig) {
|
|
18
|
+
super({
|
|
19
|
+
throwOnMissingHandler: true,
|
|
20
|
+
name: `${id}-messaging`
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
this.#timeout = runtimeConfig.messagingTimeout
|
|
24
|
+
this.#workers = new RoundRobinMap()
|
|
25
|
+
this.#sources = new Set()
|
|
26
|
+
|
|
27
|
+
// Start listening on the BroadcastChannel for the list of applications
|
|
28
|
+
this.#broadcastChannel = new BroadcastChannel(kWorkersBroadcast)
|
|
29
|
+
this.#broadcastChannel.onmessage = this.#updateWorkers.bind(this)
|
|
30
|
+
|
|
31
|
+
this.listen()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_setupListener (listener) {
|
|
35
|
+
this.#listener = listener
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
handle (message, handler) {
|
|
39
|
+
if (typeof message === 'object') {
|
|
40
|
+
for (const [name, fn] of Object.entries(message)) {
|
|
41
|
+
super.handle(name, fn)
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
super.handle(message, handler)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async send (application, name, message, options) {
|
|
49
|
+
// Get the next worker for the application
|
|
50
|
+
const worker = this.#workers.next(application)
|
|
51
|
+
|
|
52
|
+
if (!worker) {
|
|
53
|
+
throw new MessagingError(application, 'No workers available')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!worker.channel) {
|
|
57
|
+
// Use twice the value here as a fallback measure. The target handler in the main thread is forwarding
|
|
58
|
+
// the request to the worker, using executeWithTimeout with the user set timeout value.
|
|
59
|
+
const channel = await executeWithTimeout(
|
|
60
|
+
globalThis[kITC].send('getWorkerMessagingChannel', { application: worker.application, worker: worker.worker }),
|
|
61
|
+
this.#timeout * 2
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
/* c8 ignore next 3 - Hard to test */
|
|
65
|
+
if (channel === kTimeout) {
|
|
66
|
+
throw new MessagingError(application, 'Timeout while waiting for a communication channel.')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
worker.channel = channel
|
|
70
|
+
this.#setupChannel(channel)
|
|
71
|
+
|
|
72
|
+
channel[kPendingResponses] = new Map()
|
|
73
|
+
channel.on('close', this.#handlePendingResponse.bind(this, channel))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const context = { ...options }
|
|
77
|
+
context.channel = worker.channel
|
|
78
|
+
context.application = worker.application
|
|
79
|
+
context.trackResponse = true
|
|
80
|
+
|
|
81
|
+
const response = await executeWithTimeout(super.send(name, message, context), this.#timeout)
|
|
82
|
+
|
|
83
|
+
if (response === kTimeout) {
|
|
84
|
+
throw new MessagingError(application, 'Timeout while waiting for a response.')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return response
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async addSource (channel) {
|
|
91
|
+
this.#sources.add(channel)
|
|
92
|
+
this.#setupChannel(channel)
|
|
93
|
+
|
|
94
|
+
// This has been closed on the other side.
|
|
95
|
+
// Pending messages will be silently discarded by Node (as postMessage does not throw) so we don't need to handle them.
|
|
96
|
+
channel.on('close', () => {
|
|
97
|
+
this.#sources.delete(channel)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_send (request, context) {
|
|
102
|
+
const { channel, transferList } = context
|
|
103
|
+
|
|
104
|
+
if (context.trackResponse) {
|
|
105
|
+
const application = context.application
|
|
106
|
+
channel[kPendingResponses].set(request.reqId, { application, request })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
channel.postMessage(sanitize(request, transferList), { transferList })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_createClosePromise () {
|
|
113
|
+
const { promise, resolve, reject } = Promise.withResolvers()
|
|
114
|
+
this.#closeResolvers = { resolve, reject }
|
|
115
|
+
return promise
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_close () {
|
|
119
|
+
this.#closeResolvers.resolve()
|
|
120
|
+
this.#broadcastChannel.close()
|
|
121
|
+
|
|
122
|
+
for (const source of this.#sources) {
|
|
123
|
+
source.close()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const worker of this.#workers.values()) {
|
|
127
|
+
worker.channel?.close()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.#sources.clear()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#setupChannel (channel) {
|
|
134
|
+
// Setup the message for processing
|
|
135
|
+
channel.on('message', event => {
|
|
136
|
+
this.#listener(event, { channel })
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#updateWorkers (event) {
|
|
141
|
+
// Gather all existing channels by thread, it will make them reusable
|
|
142
|
+
const existingChannels = new Map()
|
|
143
|
+
for (const source of this.#workers.values()) {
|
|
144
|
+
existingChannels.set(source.thread, source.channel)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Create a brand new map
|
|
148
|
+
this.#workers = new RoundRobinMap()
|
|
149
|
+
|
|
150
|
+
const instances = []
|
|
151
|
+
for (const [application, workers] of event.data) {
|
|
152
|
+
const count = workers.length
|
|
153
|
+
const next = Math.floor(Math.random() * count)
|
|
154
|
+
|
|
155
|
+
instances.push({ id: application, next, workers: count })
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < count; i++) {
|
|
158
|
+
const worker = workers[i]
|
|
159
|
+
const channel = existingChannels.get(worker.thread)
|
|
160
|
+
|
|
161
|
+
// Note i is not the worker index as in runtime, but the index in the list of current alive workers for the application
|
|
162
|
+
this.#workers.set(`${application}:${i}`, { ...worker, channel })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.#workers.configure(instances)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#handlePendingResponse (channel) {
|
|
170
|
+
for (const { application, request } of channel[kPendingResponses].values()) {
|
|
171
|
+
this._emitResponse(
|
|
172
|
+
generateResponse(
|
|
173
|
+
request,
|
|
174
|
+
new MessagingError(application, 'The communication channel was closed before receiving a response.'),
|
|
175
|
+
null
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
channel[kPendingResponses].clear()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export class RoundRobinMap extends Map {
|
|
2
|
+
#instances
|
|
3
|
+
|
|
4
|
+
constructor (iterable, instances = {}) {
|
|
5
|
+
super(iterable)
|
|
6
|
+
this.#instances = instances
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get configuration () {
|
|
10
|
+
return { ...this.#instances }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
configure (applications) {
|
|
14
|
+
this.#instances = {}
|
|
15
|
+
|
|
16
|
+
for (const application of applications) {
|
|
17
|
+
this.#instances[application.id] = { next: application.next ?? 0, count: application.workers }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getCount (application) {
|
|
22
|
+
if (!this.#instances[application]) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return this.#instances[application].count
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setCount (application, count) {
|
|
30
|
+
if (!this.#instances[application]) {
|
|
31
|
+
throw new Error(`Application ${application} is not configured.`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.#instances[application].count = count
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
next (application) {
|
|
38
|
+
if (!this.#instances[application]) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let worker
|
|
43
|
+
let { next, count } = this.#instances[application]
|
|
44
|
+
|
|
45
|
+
// Try count times to get the next worker. This is to handle the case where a worker is being restarted.
|
|
46
|
+
for (let i = 0; i < count; i++) {
|
|
47
|
+
const current = next++
|
|
48
|
+
if (next >= count) {
|
|
49
|
+
next = 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
worker = this.get(`${application}:${current}`)
|
|
53
|
+
|
|
54
|
+
if (worker) {
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.#instances[application].next = next
|
|
60
|
+
return worker
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { kITC } from './symbols.js'
|
|
2
|
+
|
|
3
|
+
export class SharedContext {
|
|
4
|
+
constructor () {
|
|
5
|
+
this.sharedContext = null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
update (context, options = {}) {
|
|
9
|
+
return globalThis[kITC].send('updateSharedContext', { ...options, context })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get () {
|
|
13
|
+
if (this.sharedContext === null) {
|
|
14
|
+
this.sharedContext = globalThis[kITC].send('getSharedContext')
|
|
15
|
+
}
|
|
16
|
+
return this.sharedContext
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_set (context) {
|
|
20
|
+
this.sharedContext = context
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/worker/symbols.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export const kConfig = Symbol.for('plt.runtime.config')
|
|
2
|
+
export const kId = Symbol.for('plt.runtime.id') // This is also used to detect if we are running in a Platformatic runtime thread
|
|
3
|
+
export const kFullId = Symbol.for('plt.runtime.fullId')
|
|
4
|
+
export const kApplicationId = Symbol.for('plt.runtime.application.id')
|
|
5
|
+
export const kWorkerId = Symbol.for('plt.runtime.worker.id')
|
|
6
|
+
export const kITC = Symbol.for('plt.runtime.itc')
|
|
7
|
+
export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer')
|
|
8
|
+
export const kWorkerStatus = Symbol('plt.runtime.worker.status')
|
|
9
|
+
export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
|
|
10
|
+
export const kLastELU = Symbol.for('plt.runtime.worker.lastELU')
|
|
2
11
|
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const kITC = Symbol.for('plt.runtime.itc')
|
|
12
|
+
// This string marker should be safe to use since it belongs to Unicode private area
|
|
13
|
+
export const kStderrMarker = '\ue002'
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
// Note that this is used to create a BroadcastChannel so it must be a string
|
|
16
|
+
export const kWorkersBroadcast = 'plt.runtime.workers'
|
package/package.json
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
"author": "Matteo Collina <hello@matteocollina.com>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
10
8
|
"repository": {
|
|
11
9
|
"type": "git",
|
|
12
10
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
@@ -17,71 +15,82 @@
|
|
|
17
15
|
},
|
|
18
16
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
19
17
|
"devDependencies": {
|
|
18
|
+
"@fastify/compress": "^8.0.0",
|
|
20
19
|
"@fastify/express": "^4.0.0",
|
|
21
20
|
"@fastify/formbody": "^8.0.0",
|
|
22
|
-
"
|
|
21
|
+
"autocannon": "^8.0.0",
|
|
23
22
|
"c8": "^10.0.0",
|
|
23
|
+
"cleaner-spec-reporter": "^0.5.0",
|
|
24
24
|
"eslint": "9",
|
|
25
|
-
"execa": "^
|
|
25
|
+
"execa": "^9.0.0",
|
|
26
26
|
"express": "^4.18.3",
|
|
27
|
-
"fast-jwt": "^
|
|
27
|
+
"fast-jwt": "^5.0.0",
|
|
28
28
|
"get-port": "^7.1.0",
|
|
29
|
+
"inspector-client": "^0.2.0",
|
|
29
30
|
"json-schema-to-typescript": "^15.0.0",
|
|
30
|
-
"neostandard": "^0.
|
|
31
|
+
"neostandard": "^0.12.0",
|
|
31
32
|
"pino-abstract-transport": "^2.0.0",
|
|
32
33
|
"split2": "^4.2.0",
|
|
33
|
-
"tsd": "^0.31.0",
|
|
34
34
|
"typescript": "^5.5.4",
|
|
35
35
|
"undici-oidc-interceptor": "^0.5.0",
|
|
36
36
|
"why-is-node-running": "^2.2.2",
|
|
37
|
-
"@platformatic/
|
|
38
|
-
"@platformatic/
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
37
|
+
"@platformatic/db": "3.5.1",
|
|
38
|
+
"@platformatic/gateway": "3.5.1",
|
|
39
|
+
"@platformatic/composer": "3.5.1",
|
|
40
|
+
"@platformatic/node": "3.5.1",
|
|
41
|
+
"@platformatic/service": "3.5.1",
|
|
42
|
+
"@platformatic/sql-graphql": "3.5.1",
|
|
43
|
+
"@platformatic/wattpm-pprof-capture": "3.5.1",
|
|
44
|
+
"@platformatic/sql-mapper": "3.5.1"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
47
|
+
"@fastify/accepts": "^5.0.0",
|
|
48
|
+
"@fastify/basic-auth": "^6.0.0",
|
|
44
49
|
"@fastify/error": "^4.0.0",
|
|
45
50
|
"@fastify/websocket": "^11.0.0",
|
|
46
|
-
"@
|
|
47
|
-
"@platformatic/
|
|
51
|
+
"@opentelemetry/api": "^1.9.0",
|
|
52
|
+
"@platformatic/undici-cache-memory": "^0.8.1",
|
|
48
53
|
"@watchable/unpromise": "^1.0.2",
|
|
49
|
-
"boring-name-generator": "^1.0.3",
|
|
50
54
|
"change-case-all": "^2.1.0",
|
|
51
|
-
"close-with-grace": "^2.
|
|
52
|
-
"
|
|
55
|
+
"close-with-grace": "^2.2.0",
|
|
56
|
+
"colorette": "^2.0.20",
|
|
57
|
+
"cron": "^4.1.0",
|
|
53
58
|
"debounce": "^2.0.0",
|
|
54
|
-
"
|
|
59
|
+
"dotenv": "^16.4.5",
|
|
55
60
|
"dotenv-tool": "^0.1.1",
|
|
56
|
-
"es-main": "^1.3.0",
|
|
57
61
|
"fastest-levenshtein": "^1.0.16",
|
|
58
62
|
"fastify": "^5.0.0",
|
|
59
63
|
"graphql": "^16.8.1",
|
|
60
64
|
"help-me": "^5.0.0",
|
|
61
65
|
"minimist": "^1.2.8",
|
|
62
|
-
"pino": "^
|
|
63
|
-
"pino-pretty": "^
|
|
64
|
-
"pino-roll": "^2.0.0",
|
|
66
|
+
"pino": "^9.9.0",
|
|
67
|
+
"pino-pretty": "^13.0.0",
|
|
65
68
|
"prom-client": "^15.1.2",
|
|
66
69
|
"semgrator": "^0.3.0",
|
|
67
|
-
"
|
|
68
|
-
"undici": "^
|
|
69
|
-
"undici-thread-interceptor": "^0.
|
|
70
|
+
"sonic-boom": "^4.2.0",
|
|
71
|
+
"undici": "^7.0.0",
|
|
72
|
+
"undici-thread-interceptor": "^0.14.0",
|
|
70
73
|
"ws": "^8.16.0",
|
|
71
|
-
"@platformatic/
|
|
72
|
-
"@platformatic/
|
|
73
|
-
"@platformatic/
|
|
74
|
-
"@platformatic/itc": "3.
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/
|
|
77
|
-
|
|
74
|
+
"@platformatic/basic": "3.5.1",
|
|
75
|
+
"@platformatic/generators": "3.5.1",
|
|
76
|
+
"@platformatic/foundation": "3.5.1",
|
|
77
|
+
"@platformatic/itc": "3.5.1",
|
|
78
|
+
"@platformatic/telemetry": "3.5.1",
|
|
79
|
+
"@platformatic/metrics": "3.5.1"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=22.19.0"
|
|
78
83
|
},
|
|
79
84
|
"scripts": {
|
|
80
|
-
"test": "npm run
|
|
81
|
-
"
|
|
85
|
+
"test": "npm run test:main && npm run test:api && npm run test:cli && npm run test:start && npm run test:multiple-workers",
|
|
86
|
+
"test:main": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/versions/*.test.js",
|
|
87
|
+
"test:api": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/api/*.test.js test/management-api/*.test.js",
|
|
88
|
+
"test:cli": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/cli/*.test.js test/cli/**/*.test.js",
|
|
89
|
+
"test:start": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/start/*.test.js",
|
|
90
|
+
"test:multiple-workers": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/multiple-workers/*.test.js",
|
|
82
91
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
83
92
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
84
|
-
"build": "
|
|
93
|
+
"build": "npm run gen-schema && npm run gen-types",
|
|
85
94
|
"lint": "eslint"
|
|
86
95
|
}
|
|
87
96
|
}
|