@platformatic/runtime 3.32.0-alpha.0 → 3.32.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/config.d.ts +37 -3
- package/lib/runtime.js +144 -11
- package/lib/worker/interceptors.js +20 -1
- package/lib/worker/main.js +58 -5
- package/package.json +15 -15
- package/schema.json +131 -56
package/config.d.ts
CHANGED
|
@@ -42,7 +42,6 @@ export type PlatformaticRuntimeConfig = {
|
|
|
42
42
|
maxHeapTotal?: number | string;
|
|
43
43
|
maxYoungGeneration?: number | string;
|
|
44
44
|
codeRangeSize?: number | string;
|
|
45
|
-
noHeapCheck?: boolean | string;
|
|
46
45
|
};
|
|
47
46
|
dependencies?: string[];
|
|
48
47
|
arguments?: string[];
|
|
@@ -79,6 +78,18 @@ export type PlatformaticRuntimeConfig = {
|
|
|
79
78
|
)[];
|
|
80
79
|
[k: string]: unknown;
|
|
81
80
|
};
|
|
81
|
+
compileCache?:
|
|
82
|
+
| boolean
|
|
83
|
+
| {
|
|
84
|
+
/**
|
|
85
|
+
* Enable Node.js module compile cache for faster startup
|
|
86
|
+
*/
|
|
87
|
+
enabled?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Directory to store compile cache. Defaults to .plt/compile-cache in app root
|
|
90
|
+
*/
|
|
91
|
+
directory?: string;
|
|
92
|
+
};
|
|
82
93
|
};
|
|
83
94
|
};
|
|
84
95
|
};
|
|
@@ -109,7 +120,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
109
120
|
};
|
|
110
121
|
workersRestartDelay?: number | string;
|
|
111
122
|
logger?: {
|
|
112
|
-
level
|
|
123
|
+
level?: (
|
|
113
124
|
| ("fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent")
|
|
114
125
|
| {
|
|
115
126
|
[k: string]: unknown;
|
|
@@ -213,7 +224,6 @@ export type PlatformaticRuntimeConfig = {
|
|
|
213
224
|
maxHeapTotal?: number | string;
|
|
214
225
|
maxYoungGeneration?: number | string;
|
|
215
226
|
codeRangeSize?: number | string;
|
|
216
|
-
noHeapCheck?: boolean | string;
|
|
217
227
|
};
|
|
218
228
|
undici?: {
|
|
219
229
|
agentOptions?: {
|
|
@@ -265,6 +275,18 @@ export type PlatformaticRuntimeConfig = {
|
|
|
265
275
|
maxSize?: number;
|
|
266
276
|
maxEntrySize?: number;
|
|
267
277
|
maxCount?: number;
|
|
278
|
+
/**
|
|
279
|
+
* Whitelist of origins to cache. Supports exact strings and regex patterns (e.g., "/https:\\/\\/.*\\.example\\.com/").
|
|
280
|
+
*/
|
|
281
|
+
origins?: string[];
|
|
282
|
+
/**
|
|
283
|
+
* Default cache duration in seconds for responses without explicit expiration headers.
|
|
284
|
+
*/
|
|
285
|
+
cacheByDefault?: number;
|
|
286
|
+
/**
|
|
287
|
+
* Cache type. "shared" caches may be shared between users, "private" caches are user-specific.
|
|
288
|
+
*/
|
|
289
|
+
type?: "shared" | "private";
|
|
268
290
|
[k: string]: unknown;
|
|
269
291
|
};
|
|
270
292
|
watch?: boolean | string;
|
|
@@ -503,4 +525,16 @@ export type PlatformaticRuntimeConfig = {
|
|
|
503
525
|
[k: string]: string | [string, ...string[]];
|
|
504
526
|
};
|
|
505
527
|
};
|
|
528
|
+
compileCache?:
|
|
529
|
+
| boolean
|
|
530
|
+
| {
|
|
531
|
+
/**
|
|
532
|
+
* Enable Node.js module compile cache for faster startup
|
|
533
|
+
*/
|
|
534
|
+
enabled?: boolean;
|
|
535
|
+
/**
|
|
536
|
+
* Directory to store compile cache. Defaults to .plt/compile-cache in app root
|
|
537
|
+
*/
|
|
538
|
+
directory?: string;
|
|
539
|
+
};
|
|
506
540
|
};
|
package/lib/runtime.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parseMemorySize
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
12
|
import { ITC } from '@platformatic/itc'
|
|
13
|
+
import { client as metricsClient, collectProcessMetrics } from '@platformatic/metrics'
|
|
13
14
|
import fastify from 'fastify'
|
|
14
15
|
import { EventEmitter, once } from 'node:events'
|
|
15
16
|
import { existsSync } from 'node:fs'
|
|
@@ -68,9 +69,26 @@ import {
|
|
|
68
69
|
|
|
69
70
|
const kWorkerFile = join(import.meta.dirname, 'worker/main.js')
|
|
70
71
|
const kInspectorOptions = Symbol('plt.runtime.worker.inspectorOptions')
|
|
72
|
+
const kHeapCheckCounter = Symbol('plt.runtime.worker.heapCheckCounter')
|
|
73
|
+
const kLastHeapStats = Symbol('plt.runtime.worker.lastHeapStats')
|
|
71
74
|
|
|
72
75
|
const MAX_LISTENERS_COUNT = 100
|
|
73
76
|
|
|
77
|
+
function parseOrigins (origins) {
|
|
78
|
+
if (!origins) return undefined
|
|
79
|
+
|
|
80
|
+
return origins.map(origin => {
|
|
81
|
+
// Check if the origin is a regex pattern (starts and ends with /)
|
|
82
|
+
if (origin.startsWith('/') && origin.lastIndexOf('/') > 0) {
|
|
83
|
+
const lastSlash = origin.lastIndexOf('/')
|
|
84
|
+
const pattern = origin.slice(1, lastSlash)
|
|
85
|
+
const flags = origin.slice(lastSlash + 1)
|
|
86
|
+
return new RegExp(pattern, flags)
|
|
87
|
+
}
|
|
88
|
+
return origin
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
const MAX_CONCURRENCY = 5
|
|
75
93
|
const MAX_BOOTSTRAP_ATTEMPTS = 5
|
|
76
94
|
const IMMEDIATE_RESTART_MAX_THRESHOLD = 10
|
|
@@ -119,6 +137,8 @@ export class Runtime extends EventEmitter {
|
|
|
119
137
|
|
|
120
138
|
#channelCreationHook
|
|
121
139
|
|
|
140
|
+
#processMetricsRegistry
|
|
141
|
+
|
|
122
142
|
constructor (config, context) {
|
|
123
143
|
super()
|
|
124
144
|
this.setMaxListeners(MAX_LISTENERS_COUNT)
|
|
@@ -197,6 +217,14 @@ export class Runtime extends EventEmitter {
|
|
|
197
217
|
this.#metricsLabelName = 'applicationId'
|
|
198
218
|
}
|
|
199
219
|
|
|
220
|
+
// Initialize process-level metrics registry in the main thread if metrics or management API is enabled
|
|
221
|
+
// These metrics are the same across all workers and only need to be collected once
|
|
222
|
+
// We need this for management API as it can request metrics even without explicit metrics config
|
|
223
|
+
if (config.metrics || config.managementApi) {
|
|
224
|
+
this.#processMetricsRegistry = new metricsClient.Registry()
|
|
225
|
+
collectProcessMetrics(this.#processMetricsRegistry)
|
|
226
|
+
}
|
|
227
|
+
|
|
200
228
|
// Create the logger
|
|
201
229
|
const [logger, destination, context] = await createLogger(config)
|
|
202
230
|
this.logger = logger
|
|
@@ -346,6 +374,12 @@ export class Runtime extends EventEmitter {
|
|
|
346
374
|
await this.#prometheusServer.close()
|
|
347
375
|
}
|
|
348
376
|
|
|
377
|
+
// Clean up process metrics registry
|
|
378
|
+
if (this.#processMetricsRegistry) {
|
|
379
|
+
this.#processMetricsRegistry.clear()
|
|
380
|
+
this.#processMetricsRegistry = null
|
|
381
|
+
}
|
|
382
|
+
|
|
349
383
|
if (this.#sharedHttpCache?.close) {
|
|
350
384
|
await this.#sharedHttpCache.close()
|
|
351
385
|
}
|
|
@@ -1059,6 +1093,12 @@ export class Runtime extends EventEmitter {
|
|
|
1059
1093
|
async getMetrics (format = 'json') {
|
|
1060
1094
|
let metrics = null
|
|
1061
1095
|
|
|
1096
|
+
// Get process-level metrics once from main thread registry (if available)
|
|
1097
|
+
let processMetricsJson = null
|
|
1098
|
+
if (this.#processMetricsRegistry) {
|
|
1099
|
+
processMetricsJson = await this.#processMetricsRegistry.getMetricsAsJSON()
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1062
1102
|
for (const worker of this.#workers.values()) {
|
|
1063
1103
|
try {
|
|
1064
1104
|
// The application might be temporarily unavailable
|
|
@@ -1066,6 +1106,7 @@ export class Runtime extends EventEmitter {
|
|
|
1066
1106
|
continue
|
|
1067
1107
|
}
|
|
1068
1108
|
|
|
1109
|
+
// Get thread-specific metrics from worker
|
|
1069
1110
|
const applicationMetrics = await executeWithTimeout(
|
|
1070
1111
|
sendViaITC(worker, 'getMetrics', format),
|
|
1071
1112
|
this.#config.metrics?.timeout ?? 10000
|
|
@@ -1076,9 +1117,30 @@ export class Runtime extends EventEmitter {
|
|
|
1076
1117
|
metrics = format === 'json' ? [] : ''
|
|
1077
1118
|
}
|
|
1078
1119
|
|
|
1120
|
+
// Build worker labels including custom labels from metrics config
|
|
1121
|
+
const workerLabels = {
|
|
1122
|
+
...this.#config.metrics?.labels,
|
|
1123
|
+
[this.#metricsLabelName]: worker[kApplicationId]
|
|
1124
|
+
}
|
|
1125
|
+
const workerId = worker[kWorkerId]
|
|
1126
|
+
if (workerId >= 0) {
|
|
1127
|
+
workerLabels.workerId = workerId
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1079
1130
|
if (format === 'json') {
|
|
1080
|
-
metrics
|
|
1131
|
+
// Duplicate process metrics with worker labels and add to output
|
|
1132
|
+
if (processMetricsJson) {
|
|
1133
|
+
this.#applyLabelsToMetrics(processMetricsJson, workerLabels, metrics)
|
|
1134
|
+
}
|
|
1135
|
+
// Add worker's thread-specific metrics
|
|
1136
|
+
for (let i = 0; i < applicationMetrics.length; i++) {
|
|
1137
|
+
metrics.push(applicationMetrics[i])
|
|
1138
|
+
}
|
|
1081
1139
|
} else {
|
|
1140
|
+
// Text format: format process metrics with worker labels
|
|
1141
|
+
if (processMetricsJson) {
|
|
1142
|
+
metrics += this.#formatProcessMetricsText(processMetricsJson, workerLabels)
|
|
1143
|
+
}
|
|
1082
1144
|
metrics += applicationMetrics
|
|
1083
1145
|
}
|
|
1084
1146
|
}
|
|
@@ -1099,6 +1161,65 @@ export class Runtime extends EventEmitter {
|
|
|
1099
1161
|
return { metrics }
|
|
1100
1162
|
}
|
|
1101
1163
|
|
|
1164
|
+
// Apply labels to process metrics and push to output array (for JSON format)
|
|
1165
|
+
#applyLabelsToMetrics (processMetrics, labels, outputArray) {
|
|
1166
|
+
for (let i = 0; i < processMetrics.length; i++) {
|
|
1167
|
+
const metric = processMetrics[i]
|
|
1168
|
+
const newValues = []
|
|
1169
|
+
const values = metric.values
|
|
1170
|
+
for (let j = 0; j < values.length; j++) {
|
|
1171
|
+
const v = values[j]
|
|
1172
|
+
newValues.push({
|
|
1173
|
+
value: v.value,
|
|
1174
|
+
labels: { ...labels, ...v.labels },
|
|
1175
|
+
metricName: v.metricName
|
|
1176
|
+
})
|
|
1177
|
+
}
|
|
1178
|
+
outputArray.push({
|
|
1179
|
+
name: metric.name,
|
|
1180
|
+
help: metric.help,
|
|
1181
|
+
type: metric.type,
|
|
1182
|
+
aggregator: metric.aggregator,
|
|
1183
|
+
values: newValues
|
|
1184
|
+
})
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Format process metrics as Prometheus text format with labels
|
|
1189
|
+
#formatProcessMetricsText (processMetricsJson, labels) {
|
|
1190
|
+
let output = ''
|
|
1191
|
+
|
|
1192
|
+
for (let i = 0; i < processMetricsJson.length; i++) {
|
|
1193
|
+
const metric = processMetricsJson[i]
|
|
1194
|
+
const name = metric.name
|
|
1195
|
+
const help = metric.help
|
|
1196
|
+
const type = metric.type
|
|
1197
|
+
|
|
1198
|
+
// Add HELP and TYPE lines
|
|
1199
|
+
output += `# HELP ${name} ${help}\n`
|
|
1200
|
+
output += `# TYPE ${name} ${type}\n`
|
|
1201
|
+
|
|
1202
|
+
const values = metric.values
|
|
1203
|
+
for (let j = 0; j < values.length; j++) {
|
|
1204
|
+
const v = values[j]
|
|
1205
|
+
const combinedLabels = { ...labels, ...v.labels }
|
|
1206
|
+
const labelParts = []
|
|
1207
|
+
|
|
1208
|
+
for (const [key, val] of Object.entries(combinedLabels)) {
|
|
1209
|
+
// Escape label values for Prometheus format
|
|
1210
|
+
const escapedVal = String(val).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')
|
|
1211
|
+
labelParts.push(`${key}="${escapedVal}"`)
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const labelStr = labelParts.length > 0 ? `{${labelParts.join(',')}}` : ''
|
|
1215
|
+
const metricName = v.metricName || name
|
|
1216
|
+
output += `${metricName}${labelStr} ${v.value}\n`
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return output
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1102
1223
|
async getFormattedMetrics () {
|
|
1103
1224
|
try {
|
|
1104
1225
|
const { metrics } = await this.getMetrics()
|
|
@@ -1349,11 +1470,20 @@ export class Runtime extends EventEmitter {
|
|
|
1349
1470
|
elu = worker.performance.eventLoopUtilization(elu, previousELU)
|
|
1350
1471
|
}
|
|
1351
1472
|
|
|
1352
|
-
if (!features.node.worker.getHeapStatistics
|
|
1473
|
+
if (!features.node.worker.getHeapStatistics) {
|
|
1353
1474
|
return { elu: elu.utilization, currentELU }
|
|
1354
1475
|
}
|
|
1355
1476
|
|
|
1356
|
-
|
|
1477
|
+
// Only check heap statistics every 60 health checks (once per minute)
|
|
1478
|
+
const counter = (worker[kHeapCheckCounter] ?? 0) + 1
|
|
1479
|
+
worker[kHeapCheckCounter] = counter >= 60 ? 0 : counter
|
|
1480
|
+
|
|
1481
|
+
if (counter >= 60 || !worker[kLastHeapStats]) {
|
|
1482
|
+
const { used_heap_size: heapUsed, total_heap_size: heapTotal } = await worker.getHeapStatistics()
|
|
1483
|
+
worker[kLastHeapStats] = { heapUsed, heapTotal }
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const { heapUsed, heapTotal } = worker[kLastHeapStats]
|
|
1357
1487
|
return { elu: elu.utilization, heapUsed, heapTotal, currentELU }
|
|
1358
1488
|
}
|
|
1359
1489
|
|
|
@@ -1396,7 +1526,10 @@ export class Runtime extends EventEmitter {
|
|
|
1396
1526
|
interceptors.push(
|
|
1397
1527
|
undiciInterceptors.cache({
|
|
1398
1528
|
store: this.#sharedHttpCache,
|
|
1399
|
-
methods: config.httpCache.methods ?? ['GET', 'HEAD']
|
|
1529
|
+
methods: config.httpCache.methods ?? ['GET', 'HEAD'],
|
|
1530
|
+
origins: parseOrigins(config.httpCache.origins),
|
|
1531
|
+
cacheByDefault: config.httpCache.cacheByDefault,
|
|
1532
|
+
type: config.httpCache.type
|
|
1400
1533
|
})
|
|
1401
1534
|
)
|
|
1402
1535
|
}
|
|
@@ -1564,7 +1697,8 @@ export class Runtime extends EventEmitter {
|
|
|
1564
1697
|
codeRangeSizeMb
|
|
1565
1698
|
},
|
|
1566
1699
|
stdout: true,
|
|
1567
|
-
stderr: true
|
|
1700
|
+
stderr: true,
|
|
1701
|
+
name: workerId
|
|
1568
1702
|
})
|
|
1569
1703
|
|
|
1570
1704
|
this.#handleWorkerStandardStreams(worker, applicationId, index)
|
|
@@ -1753,8 +1887,7 @@ export class Runtime extends EventEmitter {
|
|
|
1753
1887
|
let health = null
|
|
1754
1888
|
try {
|
|
1755
1889
|
health = await this.getWorkerHealth(worker, {
|
|
1756
|
-
previousELU: worker[kLastHealthCheckELU]
|
|
1757
|
-
noHeapCheck: worker[kConfig]?.health?.noHeapCheck
|
|
1890
|
+
previousELU: worker[kLastHealthCheckELU]
|
|
1758
1891
|
})
|
|
1759
1892
|
} catch (err) {
|
|
1760
1893
|
this.logger.error({ err }, `Failed to get health for ${errorLabel}.`)
|
|
@@ -1798,7 +1931,7 @@ export class Runtime extends EventEmitter {
|
|
|
1798
1931
|
|
|
1799
1932
|
const healthConfig = worker[kConfig].health
|
|
1800
1933
|
|
|
1801
|
-
let { maxELU, maxHeapUsed, maxHeapTotal, maxUnhealthyChecks, interval
|
|
1934
|
+
let { maxELU, maxHeapUsed, maxHeapTotal, maxUnhealthyChecks, interval } = worker[kConfig].health
|
|
1802
1935
|
|
|
1803
1936
|
if (typeof maxHeapTotal === 'string') {
|
|
1804
1937
|
maxHeapTotal = parseMemorySize(maxHeapTotal)
|
|
@@ -1829,8 +1962,8 @@ export class Runtime extends EventEmitter {
|
|
|
1829
1962
|
|
|
1830
1963
|
if (lastHealthMetrics) {
|
|
1831
1964
|
const health = lastHealthMetrics.currentHealth
|
|
1832
|
-
const memoryUsage =
|
|
1833
|
-
const unhealthy = health.elu > maxELU ||
|
|
1965
|
+
const memoryUsage = health.heapUsed / maxHeapTotal
|
|
1966
|
+
const unhealthy = health.elu > maxELU || memoryUsage > maxHeapUsed
|
|
1834
1967
|
|
|
1835
1968
|
this.emit('application:worker:health', {
|
|
1836
1969
|
id: worker[kId],
|
|
@@ -1848,7 +1981,7 @@ export class Runtime extends EventEmitter {
|
|
|
1848
1981
|
)
|
|
1849
1982
|
}
|
|
1850
1983
|
|
|
1851
|
-
if (
|
|
1984
|
+
if (memoryUsage > maxHeapUsed) {
|
|
1852
1985
|
this.logger.error(
|
|
1853
1986
|
`The ${errorLabel} is using ${(memoryUsage * 100).toFixed(2)} % of the memory, ` +
|
|
1854
1987
|
`above the maximum allowed usage of ${(maxHeapUsed * 100).toFixed(2)} %.`
|
|
@@ -178,7 +178,23 @@ function createThreadInterceptor (runtimeConfig) {
|
|
|
178
178
|
return threadDispatcher
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
function parseOrigins (origins) {
|
|
182
|
+
if (!origins) return undefined
|
|
183
|
+
|
|
184
|
+
return origins.map(origin => {
|
|
185
|
+
// Check if the origin is a regex pattern (starts and ends with /)
|
|
186
|
+
if (origin.startsWith('/') && origin.lastIndexOf('/') > 0) {
|
|
187
|
+
const lastSlash = origin.lastIndexOf('/')
|
|
188
|
+
const pattern = origin.slice(1, lastSlash)
|
|
189
|
+
const flags = origin.slice(lastSlash + 1)
|
|
190
|
+
return new RegExp(pattern, flags)
|
|
191
|
+
}
|
|
192
|
+
return origin
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
181
196
|
function createHttpCacheInterceptor (runtimeConfig) {
|
|
197
|
+
const httpCache = runtimeConfig.httpCache
|
|
182
198
|
const cacheInterceptor = httpCacheInterceptor({
|
|
183
199
|
store: new RemoteCacheStore({
|
|
184
200
|
onRequest: opts => {
|
|
@@ -192,7 +208,10 @@ function createHttpCacheInterceptor (runtimeConfig) {
|
|
|
192
208
|
},
|
|
193
209
|
logger: globalThis.platformatic.logger
|
|
194
210
|
}),
|
|
195
|
-
methods:
|
|
211
|
+
methods: httpCache.methods ?? ['GET', 'HEAD'],
|
|
212
|
+
origins: parseOrigins(httpCache.origins),
|
|
213
|
+
cacheByDefault: httpCache.cacheByDefault,
|
|
214
|
+
type: httpCache.type,
|
|
196
215
|
logger: globalThis.platformatic.logger
|
|
197
216
|
})
|
|
198
217
|
return cacheInterceptor
|
package/lib/worker/main.js
CHANGED
|
@@ -9,11 +9,11 @@ import { EventEmitter } from 'node:events'
|
|
|
9
9
|
import { ServerResponse } from 'node:http'
|
|
10
10
|
import inspector from 'node:inspector'
|
|
11
11
|
import { hostname } from 'node:os'
|
|
12
|
-
import { resolve } from 'node:path'
|
|
12
|
+
import { join, resolve } from 'node:path'
|
|
13
13
|
import { pathToFileURL } from 'node:url'
|
|
14
14
|
import { threadId, workerData } from 'node:worker_threads'
|
|
15
15
|
import pino from 'pino'
|
|
16
|
-
import {
|
|
16
|
+
import { install as installUndiciGlobals } from 'undici'
|
|
17
17
|
import { Controller } from './controller.js'
|
|
18
18
|
import { setDispatcher } from './interceptors.js'
|
|
19
19
|
import { setupITC } from './itc.js'
|
|
@@ -85,8 +85,59 @@ async function performPreloading (...sources) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Enable compile cache if configured (Node.js 22.1.0+)
|
|
89
|
+
async function setupCompileCache (runtimeConfig, applicationConfig, logger) {
|
|
90
|
+
// Normalize boolean shorthand: true -> { enabled: true }
|
|
91
|
+
const normalizeConfig = cfg => {
|
|
92
|
+
if (cfg === true) return { enabled: true }
|
|
93
|
+
if (cfg === false) return { enabled: false }
|
|
94
|
+
return cfg
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Merge runtime and app-level config (app overrides runtime)
|
|
98
|
+
const runtimeCache = normalizeConfig(runtimeConfig.compileCache)
|
|
99
|
+
const appCache = normalizeConfig(applicationConfig.compileCache)
|
|
100
|
+
const config = { ...runtimeCache, ...appCache }
|
|
101
|
+
|
|
102
|
+
if (!config.enabled) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if API is available (Node.js 22.1.0+)
|
|
107
|
+
let moduleApi
|
|
108
|
+
try {
|
|
109
|
+
moduleApi = await import('node:module')
|
|
110
|
+
if (typeof moduleApi.enableCompileCache !== 'function') {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Determine cache directory - use applicationConfig.path for the app root
|
|
118
|
+
const cacheDir = config.directory ?? join(applicationConfig.path, '.plt', 'compile-cache')
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = moduleApi.enableCompileCache(cacheDir)
|
|
122
|
+
|
|
123
|
+
const { compileCacheStatus } = moduleApi.constants ?? {}
|
|
124
|
+
|
|
125
|
+
if (result.status === compileCacheStatus?.ENABLED) {
|
|
126
|
+
logger.debug({ directory: result.directory }, 'Module compile cache enabled')
|
|
127
|
+
} else if (result.status === compileCacheStatus?.ALREADY_ENABLED) {
|
|
128
|
+
logger.debug({ directory: result.directory }, 'Module compile cache already enabled')
|
|
129
|
+
} else if (result.status === compileCacheStatus?.FAILED) {
|
|
130
|
+
logger.warn({ message: result.message }, 'Failed to enable module compile cache')
|
|
131
|
+
} else if (result.status === compileCacheStatus?.DISABLED) {
|
|
132
|
+
logger.debug('Module compile cache disabled via NODE_DISABLE_COMPILE_CACHE')
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
logger.warn({ err }, 'Error enabling module compile cache')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
88
139
|
async function main () {
|
|
89
|
-
globalThis
|
|
140
|
+
installUndiciGlobals(globalThis)
|
|
90
141
|
globalThis[kId] = threadId
|
|
91
142
|
globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, {
|
|
92
143
|
logger: createLogger(),
|
|
@@ -94,10 +145,12 @@ async function main () {
|
|
|
94
145
|
})
|
|
95
146
|
|
|
96
147
|
const runtimeConfig = workerData.config
|
|
148
|
+
const applicationConfig = workerData.applicationConfig
|
|
97
149
|
|
|
98
|
-
|
|
150
|
+
// Enable compile cache early before loading user modules
|
|
151
|
+
await setupCompileCache(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
|
|
99
152
|
|
|
100
|
-
|
|
153
|
+
await performPreloading(runtimeConfig, applicationConfig)
|
|
101
154
|
|
|
102
155
|
// Load env file and mixin env vars from application config
|
|
103
156
|
let envfile
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.32.0
|
|
3
|
+
"version": "3.32.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"typescript": "^5.5.4",
|
|
36
36
|
"undici-oidc-interceptor": "^0.5.0",
|
|
37
37
|
"why-is-node-running": "^2.2.2",
|
|
38
|
-
"@platformatic/composer": "3.32.0
|
|
39
|
-
"@platformatic/db": "3.32.0
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/
|
|
38
|
+
"@platformatic/composer": "3.32.0",
|
|
39
|
+
"@platformatic/db": "3.32.0",
|
|
40
|
+
"@platformatic/gateway": "3.32.0",
|
|
41
|
+
"@platformatic/node": "3.32.0",
|
|
42
|
+
"@platformatic/sql-graphql": "3.32.0",
|
|
43
|
+
"@platformatic/service": "3.32.0",
|
|
44
|
+
"@platformatic/sql-mapper": "3.32.0",
|
|
45
|
+
"@platformatic/wattpm-pprof-capture": "3.32.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -71,12 +71,12 @@
|
|
|
71
71
|
"undici": "^7.0.0",
|
|
72
72
|
"undici-thread-interceptor": "^1.0.0",
|
|
73
73
|
"ws": "^8.16.0",
|
|
74
|
-
"@platformatic/basic": "3.32.0
|
|
75
|
-
"@platformatic/foundation": "3.32.0
|
|
76
|
-
"@platformatic/generators": "3.32.0
|
|
77
|
-
"@platformatic/itc": "3.32.0
|
|
78
|
-
"@platformatic/metrics": "3.32.0
|
|
79
|
-
"@platformatic/telemetry": "3.32.0
|
|
74
|
+
"@platformatic/basic": "3.32.0",
|
|
75
|
+
"@platformatic/foundation": "3.32.0",
|
|
76
|
+
"@platformatic/generators": "3.32.0",
|
|
77
|
+
"@platformatic/itc": "3.32.0",
|
|
78
|
+
"@platformatic/metrics": "3.32.0",
|
|
79
|
+
"@platformatic/telemetry": "3.32.0"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.32.0
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.32.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -208,16 +208,6 @@
|
|
|
208
208
|
"type": "string"
|
|
209
209
|
}
|
|
210
210
|
]
|
|
211
|
-
},
|
|
212
|
-
"noHeapCheck": {
|
|
213
|
-
"anyOf": [
|
|
214
|
-
{
|
|
215
|
-
"type": "boolean"
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
"type": "string"
|
|
219
|
-
}
|
|
220
|
-
]
|
|
221
211
|
}
|
|
222
212
|
},
|
|
223
213
|
"additionalProperties": false
|
|
@@ -342,6 +332,28 @@
|
|
|
342
332
|
}
|
|
343
333
|
}
|
|
344
334
|
}
|
|
335
|
+
},
|
|
336
|
+
"compileCache": {
|
|
337
|
+
"anyOf": [
|
|
338
|
+
{
|
|
339
|
+
"type": "boolean"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"type": "object",
|
|
343
|
+
"properties": {
|
|
344
|
+
"enabled": {
|
|
345
|
+
"type": "boolean",
|
|
346
|
+
"default": true,
|
|
347
|
+
"description": "Enable Node.js module compile cache for faster startup"
|
|
348
|
+
},
|
|
349
|
+
"directory": {
|
|
350
|
+
"type": "string",
|
|
351
|
+
"description": "Directory to store compile cache. Defaults to .plt/compile-cache in app root"
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"additionalProperties": false
|
|
355
|
+
}
|
|
356
|
+
]
|
|
345
357
|
}
|
|
346
358
|
}
|
|
347
359
|
}
|
|
@@ -532,16 +544,6 @@
|
|
|
532
544
|
"type": "string"
|
|
533
545
|
}
|
|
534
546
|
]
|
|
535
|
-
},
|
|
536
|
-
"noHeapCheck": {
|
|
537
|
-
"anyOf": [
|
|
538
|
-
{
|
|
539
|
-
"type": "boolean"
|
|
540
|
-
},
|
|
541
|
-
{
|
|
542
|
-
"type": "string"
|
|
543
|
-
}
|
|
544
|
-
]
|
|
545
547
|
}
|
|
546
548
|
},
|
|
547
549
|
"additionalProperties": false
|
|
@@ -666,6 +668,28 @@
|
|
|
666
668
|
}
|
|
667
669
|
}
|
|
668
670
|
}
|
|
671
|
+
},
|
|
672
|
+
"compileCache": {
|
|
673
|
+
"anyOf": [
|
|
674
|
+
{
|
|
675
|
+
"type": "boolean"
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
"type": "object",
|
|
679
|
+
"properties": {
|
|
680
|
+
"enabled": {
|
|
681
|
+
"type": "boolean",
|
|
682
|
+
"default": true,
|
|
683
|
+
"description": "Enable Node.js module compile cache for faster startup"
|
|
684
|
+
},
|
|
685
|
+
"directory": {
|
|
686
|
+
"type": "string",
|
|
687
|
+
"description": "Directory to store compile cache. Defaults to .plt/compile-cache in app root"
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
"additionalProperties": false
|
|
691
|
+
}
|
|
692
|
+
]
|
|
669
693
|
}
|
|
670
694
|
}
|
|
671
695
|
}
|
|
@@ -854,16 +878,6 @@
|
|
|
854
878
|
"type": "string"
|
|
855
879
|
}
|
|
856
880
|
]
|
|
857
|
-
},
|
|
858
|
-
"noHeapCheck": {
|
|
859
|
-
"anyOf": [
|
|
860
|
-
{
|
|
861
|
-
"type": "boolean"
|
|
862
|
-
},
|
|
863
|
-
{
|
|
864
|
-
"type": "string"
|
|
865
|
-
}
|
|
866
|
-
]
|
|
867
881
|
}
|
|
868
882
|
},
|
|
869
883
|
"additionalProperties": false
|
|
@@ -988,6 +1002,28 @@
|
|
|
988
1002
|
}
|
|
989
1003
|
}
|
|
990
1004
|
}
|
|
1005
|
+
},
|
|
1006
|
+
"compileCache": {
|
|
1007
|
+
"anyOf": [
|
|
1008
|
+
{
|
|
1009
|
+
"type": "boolean"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
"type": "object",
|
|
1013
|
+
"properties": {
|
|
1014
|
+
"enabled": {
|
|
1015
|
+
"type": "boolean",
|
|
1016
|
+
"default": true,
|
|
1017
|
+
"description": "Enable Node.js module compile cache for faster startup"
|
|
1018
|
+
},
|
|
1019
|
+
"directory": {
|
|
1020
|
+
"type": "string",
|
|
1021
|
+
"description": "Directory to store compile cache. Defaults to .plt/compile-cache in app root"
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
"additionalProperties": false
|
|
1025
|
+
}
|
|
1026
|
+
]
|
|
991
1027
|
}
|
|
992
1028
|
}
|
|
993
1029
|
}
|
|
@@ -1176,16 +1212,6 @@
|
|
|
1176
1212
|
"type": "string"
|
|
1177
1213
|
}
|
|
1178
1214
|
]
|
|
1179
|
-
},
|
|
1180
|
-
"noHeapCheck": {
|
|
1181
|
-
"anyOf": [
|
|
1182
|
-
{
|
|
1183
|
-
"type": "boolean"
|
|
1184
|
-
},
|
|
1185
|
-
{
|
|
1186
|
-
"type": "string"
|
|
1187
|
-
}
|
|
1188
|
-
]
|
|
1189
1215
|
}
|
|
1190
1216
|
},
|
|
1191
1217
|
"additionalProperties": false
|
|
@@ -1310,6 +1336,28 @@
|
|
|
1310
1336
|
}
|
|
1311
1337
|
}
|
|
1312
1338
|
}
|
|
1339
|
+
},
|
|
1340
|
+
"compileCache": {
|
|
1341
|
+
"anyOf": [
|
|
1342
|
+
{
|
|
1343
|
+
"type": "boolean"
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
"type": "object",
|
|
1347
|
+
"properties": {
|
|
1348
|
+
"enabled": {
|
|
1349
|
+
"type": "boolean",
|
|
1350
|
+
"default": true,
|
|
1351
|
+
"description": "Enable Node.js module compile cache for faster startup"
|
|
1352
|
+
},
|
|
1353
|
+
"directory": {
|
|
1354
|
+
"type": "string",
|
|
1355
|
+
"description": "Directory to store compile cache. Defaults to .plt/compile-cache in app root"
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
"additionalProperties": false
|
|
1359
|
+
}
|
|
1360
|
+
]
|
|
1313
1361
|
}
|
|
1314
1362
|
}
|
|
1315
1363
|
}
|
|
@@ -1389,7 +1437,6 @@
|
|
|
1389
1437
|
"properties": {
|
|
1390
1438
|
"level": {
|
|
1391
1439
|
"type": "string",
|
|
1392
|
-
"default": "info",
|
|
1393
1440
|
"oneOf": [
|
|
1394
1441
|
{
|
|
1395
1442
|
"enum": [
|
|
@@ -1536,9 +1583,6 @@
|
|
|
1536
1583
|
"default": true
|
|
1537
1584
|
}
|
|
1538
1585
|
},
|
|
1539
|
-
"required": [
|
|
1540
|
-
"level"
|
|
1541
|
-
],
|
|
1542
1586
|
"default": {},
|
|
1543
1587
|
"additionalProperties": true
|
|
1544
1588
|
},
|
|
@@ -1834,17 +1878,6 @@
|
|
|
1834
1878
|
}
|
|
1835
1879
|
],
|
|
1836
1880
|
"default": 268435456
|
|
1837
|
-
},
|
|
1838
|
-
"noHeapCheck": {
|
|
1839
|
-
"anyOf": [
|
|
1840
|
-
{
|
|
1841
|
-
"type": "boolean"
|
|
1842
|
-
},
|
|
1843
|
-
{
|
|
1844
|
-
"type": "string"
|
|
1845
|
-
}
|
|
1846
|
-
],
|
|
1847
|
-
"default": false
|
|
1848
1881
|
}
|
|
1849
1882
|
},
|
|
1850
1883
|
"additionalProperties": false
|
|
@@ -1976,6 +2009,26 @@
|
|
|
1976
2009
|
},
|
|
1977
2010
|
"maxCount": {
|
|
1978
2011
|
"type": "integer"
|
|
2012
|
+
},
|
|
2013
|
+
"origins": {
|
|
2014
|
+
"type": "array",
|
|
2015
|
+
"items": {
|
|
2016
|
+
"type": "string"
|
|
2017
|
+
},
|
|
2018
|
+
"description": "Whitelist of origins to cache. Supports exact strings and regex patterns (e.g., \"/https:\\\\/\\\\/.*\\\\.example\\\\.com/\")."
|
|
2019
|
+
},
|
|
2020
|
+
"cacheByDefault": {
|
|
2021
|
+
"type": "integer",
|
|
2022
|
+
"description": "Default cache duration in seconds for responses without explicit expiration headers."
|
|
2023
|
+
},
|
|
2024
|
+
"type": {
|
|
2025
|
+
"type": "string",
|
|
2026
|
+
"enum": [
|
|
2027
|
+
"shared",
|
|
2028
|
+
"private"
|
|
2029
|
+
],
|
|
2030
|
+
"default": "shared",
|
|
2031
|
+
"description": "Cache type. \"shared\" caches may be shared between users, \"private\" caches are user-specific."
|
|
1979
2032
|
}
|
|
1980
2033
|
}
|
|
1981
2034
|
}
|
|
@@ -2621,6 +2674,28 @@
|
|
|
2621
2674
|
"deny"
|
|
2622
2675
|
],
|
|
2623
2676
|
"additionalProperties": false
|
|
2677
|
+
},
|
|
2678
|
+
"compileCache": {
|
|
2679
|
+
"anyOf": [
|
|
2680
|
+
{
|
|
2681
|
+
"type": "boolean"
|
|
2682
|
+
},
|
|
2683
|
+
{
|
|
2684
|
+
"type": "object",
|
|
2685
|
+
"properties": {
|
|
2686
|
+
"enabled": {
|
|
2687
|
+
"type": "boolean",
|
|
2688
|
+
"default": true,
|
|
2689
|
+
"description": "Enable Node.js module compile cache for faster startup"
|
|
2690
|
+
},
|
|
2691
|
+
"directory": {
|
|
2692
|
+
"type": "string",
|
|
2693
|
+
"description": "Directory to store compile cache. Defaults to .plt/compile-cache in app root"
|
|
2694
|
+
}
|
|
2695
|
+
},
|
|
2696
|
+
"additionalProperties": false
|
|
2697
|
+
}
|
|
2698
|
+
]
|
|
2624
2699
|
}
|
|
2625
2700
|
},
|
|
2626
2701
|
"anyOf": [
|