@platformatic/runtime 3.63.0 → 3.65.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/index.d.ts +131 -4
- package/index.js +2 -0
- package/lib/errors.js +12 -1
- package/lib/management-api.js +32 -2
- package/lib/management-handlers.js +20 -0
- package/lib/runtime.js +333 -50
- package/lib/scaling-algorithm.js +3 -3
- package/lib/scheduler.js +192 -60
- package/lib/worker/controller.js +2 -2
- package/lib/worker/itc.js +34 -4
- package/lib/worker/main.js +16 -1
- package/lib/worker/management.js +16 -0
- package/package.json +16 -16
- package/schema.json +1 -1
package/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { EventEmitter } from 'node:events'
|
|
|
9
9
|
import { Logger } from 'pino'
|
|
10
10
|
import { PlatformaticRuntimeConfig } from './config.js'
|
|
11
11
|
|
|
12
|
-
export type RuntimeConfiguration =
|
|
12
|
+
export type RuntimeConfiguration = Configuration<PlatformaticRuntimeConfig>
|
|
13
13
|
|
|
14
14
|
export type ApplicationCommandContext = {
|
|
15
15
|
colorette: typeof colorette
|
|
@@ -72,6 +72,8 @@ export namespace errors {
|
|
|
72
72
|
) => FastifyError
|
|
73
73
|
export const ExtensionHealthRoutesUnavailableError: () => FastifyError
|
|
74
74
|
export const LastProfileTimeoutError: (id: string) => FastifyError
|
|
75
|
+
export const SchedulerJobNotFoundError: (name: string) => FastifyError
|
|
76
|
+
export const DuplicateSchedulerJobError: (name: string) => FastifyError
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export namespace symbols {
|
|
@@ -145,6 +147,45 @@ export interface RuntimeSharedContextUpdateOptions extends SharedContextUpdateOp
|
|
|
145
147
|
context?: object
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
export interface SchedulerJobBase {
|
|
151
|
+
name: string
|
|
152
|
+
cron: string
|
|
153
|
+
paused: boolean
|
|
154
|
+
maxRetries: number
|
|
155
|
+
lastExecutedAt?: string | null
|
|
156
|
+
lastStatus?: 'success' | 'failed' | null
|
|
157
|
+
nextRunAt?: string | null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ConfiguredSchedulerJob extends SchedulerJobBase {
|
|
161
|
+
source: 'config'
|
|
162
|
+
callbackUrl: string
|
|
163
|
+
method: string
|
|
164
|
+
headers?: Record<string, string>
|
|
165
|
+
body?: string | Record<string, unknown>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ApplicationSchedulerJob extends SchedulerJobBase {
|
|
169
|
+
source: 'application'
|
|
170
|
+
applicationId: string
|
|
171
|
+
scheduleId: string
|
|
172
|
+
tasks: string[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type SchedulerJob = ConfiguredSchedulerJob | ApplicationSchedulerJob
|
|
176
|
+
|
|
177
|
+
export interface SchedulerRunResult {
|
|
178
|
+
name: string
|
|
179
|
+
success: boolean
|
|
180
|
+
executedAt: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ApplicationSchedule {
|
|
184
|
+
id: string
|
|
185
|
+
cron: string
|
|
186
|
+
tasks: string[]
|
|
187
|
+
}
|
|
188
|
+
|
|
148
189
|
export interface RuntimeMetadata {
|
|
149
190
|
pid: number
|
|
150
191
|
cwd: string
|
|
@@ -159,6 +200,40 @@ export interface RuntimeMetadata {
|
|
|
159
200
|
platformaticVersion: string
|
|
160
201
|
}
|
|
161
202
|
|
|
203
|
+
export interface WorkerLifecycleEvent {
|
|
204
|
+
application: string
|
|
205
|
+
worker: number
|
|
206
|
+
workersCount?: number
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface ProfileCapturedEvent extends WorkerLifecycleEvent {
|
|
210
|
+
id: string
|
|
211
|
+
type: 'cpu' | 'heap'
|
|
212
|
+
timestamp: number
|
|
213
|
+
sampleCount: number | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface RuntimeHealthSignal {
|
|
217
|
+
type: string
|
|
218
|
+
max?: number
|
|
219
|
+
mean?: number
|
|
220
|
+
p99?: number
|
|
221
|
+
timestamp?: number
|
|
222
|
+
[key: string]: unknown
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface WorkerCurrentHealth {
|
|
226
|
+
elu: number
|
|
227
|
+
heapUsed?: number | null
|
|
228
|
+
heapTotal?: number | null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface HealthMetricsEvent extends WorkerLifecycleEvent {
|
|
232
|
+
id: string
|
|
233
|
+
currentHealth: WorkerCurrentHealth | null
|
|
234
|
+
healthSignals: RuntimeHealthSignal[]
|
|
235
|
+
}
|
|
236
|
+
|
|
162
237
|
export declare class ManagementClient {
|
|
163
238
|
constructor (allowedOperations?: string[])
|
|
164
239
|
|
|
@@ -175,6 +250,10 @@ export declare class ManagementClient {
|
|
|
175
250
|
getApplicationOpenapiSchema (id: string): Promise<unknown>
|
|
176
251
|
getApplicationGraphqlSchema (id: string): Promise<unknown>
|
|
177
252
|
getMetrics (format?: string): Promise<{ metrics: unknown }>
|
|
253
|
+
getSchedulerJobs (): Promise<SchedulerJob[]>
|
|
254
|
+
pauseSchedulerJob (name: string): Promise<SchedulerJob>
|
|
255
|
+
resumeSchedulerJob (name: string): Promise<SchedulerJob>
|
|
256
|
+
runSchedulerJob (name: string): Promise<SchedulerRunResult>
|
|
178
257
|
startApplication (id: string): Promise<void>
|
|
179
258
|
stopApplication (id: string): Promise<void>
|
|
180
259
|
restartApplication (id: string): Promise<void>
|
|
@@ -286,6 +365,11 @@ export declare class Runtime extends EventEmitter {
|
|
|
286
365
|
getRuntimeStatus (): string
|
|
287
366
|
getRuntimeMetadata (): Promise<RuntimeMetadata>
|
|
288
367
|
getRuntimeEnv (): Record<string, string>
|
|
368
|
+
// `includeMeta: true` returns the full merged Configuration object (which
|
|
369
|
+
// carries the kMetadata symbol key: `{ root, path, env, module }`), not a
|
|
370
|
+
// plain Record; the argument-invariant overload below covers every other
|
|
371
|
+
// call.
|
|
372
|
+
getRuntimeConfig (includeMeta: true): RuntimeConfiguration
|
|
289
373
|
getRuntimeConfig (includeMeta?: boolean): Record<string, unknown>
|
|
290
374
|
getApplicationsIds (): string[]
|
|
291
375
|
/**
|
|
@@ -339,9 +423,38 @@ export declare class Runtime extends EventEmitter {
|
|
|
339
423
|
restartApplication (id: string): Promise<void>
|
|
340
424
|
addApplications (applications: unknown[], start?: boolean): Promise<ApplicationDetails[]>
|
|
341
425
|
removeApplications (applications: string[], silent?: boolean): Promise<ApplicationDetails[]>
|
|
426
|
+
startApplicationProfiling (id: string, options: Record<string, unknown> & { allWorkers: true }, ensureStarted?: boolean): Promise<{ workers: number[] }>
|
|
342
427
|
startApplicationProfiling (id: string, options?: Record<string, unknown>, ensureStarted?: boolean): Promise<void>
|
|
428
|
+
stopApplicationProfiling (id: string, options: Record<string, unknown> & { allWorkers: true }, ensureStarted?: boolean): Promise<Array<{ workerIndex: number, profile: Buffer }>>
|
|
429
|
+
stopApplicationProfiling (id: string, options: Record<string, unknown> & { includeSampleCount: true }, ensureStarted?: boolean): Promise<{ profile: Buffer, sampleCount: number }>
|
|
343
430
|
stopApplicationProfiling (id: string, options?: Record<string, unknown>, ensureStarted?: boolean): Promise<Buffer>
|
|
344
|
-
getApplicationLastProfile (id: string, options?: Record<string, unknown>, ensureStarted?: boolean): Promise<{ profile: Buffer, timestamp: number | null, preserved: boolean }>
|
|
431
|
+
getApplicationLastProfile (id: string, options?: Record<string, unknown>, ensureStarted?: boolean): Promise<{ profile: Buffer, timestamp: number | null, sampleCount: number | null, preserved: boolean }>
|
|
432
|
+
// Patches one application's resolved configuration through the same
|
|
433
|
+
// per-application JSON-Patch mechanism the runtime already applies during
|
|
434
|
+
// create()/start().
|
|
435
|
+
setApplicationConfigPatch (applicationId: string, patch: Array<Record<string, unknown>>): void
|
|
436
|
+
removeApplicationConfigPatch (applicationId: string): void
|
|
437
|
+
|
|
438
|
+
// Typed worker lifecycle events. Catch-all overloads preserve EventEmitter's
|
|
439
|
+
// open event surface so listeners for other runtime events still type-check.
|
|
440
|
+
on (event: 'application:worker:started', listener: (event: WorkerLifecycleEvent) => void): this
|
|
441
|
+
on (event: 'application:worker:exited', listener: (event: WorkerLifecycleEvent) => void): this
|
|
442
|
+
on (event: 'application:worker:profile:captured', listener: (event: ProfileCapturedEvent) => void): this
|
|
443
|
+
on (event: 'application:worker:health:metrics', listener: (event: HealthMetricsEvent) => void): this
|
|
444
|
+
on (event: string | symbol, listener: (...args: any[]) => void): this
|
|
445
|
+
|
|
446
|
+
off (event: 'application:worker:started', listener: (event: WorkerLifecycleEvent) => void): this
|
|
447
|
+
off (event: 'application:worker:exited', listener: (event: WorkerLifecycleEvent) => void): this
|
|
448
|
+
off (event: 'application:worker:profile:captured', listener: (event: ProfileCapturedEvent) => void): this
|
|
449
|
+
off (event: 'application:worker:health:metrics', listener: (event: HealthMetricsEvent) => void): this
|
|
450
|
+
off (event: string | symbol, listener: (...args: any[]) => void): this
|
|
451
|
+
|
|
452
|
+
getSchedulerJobs (): SchedulerJob[]
|
|
453
|
+
pauseSchedulerJob (name: string): Promise<SchedulerJob>
|
|
454
|
+
resumeSchedulerJob (name: string): Promise<SchedulerJob>
|
|
455
|
+
runSchedulerJob (name: string): Promise<SchedulerRunResult>
|
|
456
|
+
getApplicationScheduledTasks (id: string): Promise<ApplicationSchedule[]>
|
|
457
|
+
runApplicationScheduledTasks (id: string, scheduleId: string, scheduledTime: number): Promise<unknown>
|
|
345
458
|
}
|
|
346
459
|
|
|
347
460
|
export function wrapInRuntimeConfig (
|
|
@@ -357,13 +470,27 @@ export declare function loadConfiguration (
|
|
|
357
470
|
context?: ConfigurationOptions
|
|
358
471
|
): Promise<RuntimeConfiguration>
|
|
359
472
|
|
|
473
|
+
// create() additionally accepts `setupSignals`, a Runtime-only option (it is
|
|
474
|
+
// never read outside this package) that controls whether SIGUSR2/close-with-grace
|
|
475
|
+
// handlers are installed.
|
|
476
|
+
export interface RuntimeCreateContext extends ConfigurationOptions<PlatformaticRuntimeConfig> {
|
|
477
|
+
setupSignals?: boolean
|
|
478
|
+
}
|
|
479
|
+
|
|
360
480
|
export function create (
|
|
361
481
|
root: string,
|
|
362
482
|
source?: string | PlatformaticRuntimeConfig,
|
|
363
|
-
context?:
|
|
483
|
+
context?: RuntimeCreateContext
|
|
364
484
|
): Promise<Runtime>
|
|
365
485
|
|
|
366
|
-
|
|
486
|
+
// The runtime's actual per-application preparation (worker defaults, paths,
|
|
487
|
+
// telemetry hooks, capability resolution) also takes the resolved worker
|
|
488
|
+
// count as a third argument, and is itself async.
|
|
489
|
+
export declare function prepareApplication (
|
|
490
|
+
config: RuntimeConfiguration,
|
|
491
|
+
application: object,
|
|
492
|
+
workers?: PlatformaticRuntimeConfig['workers']
|
|
493
|
+
): Promise<object>
|
|
367
494
|
|
|
368
495
|
export declare function transform (
|
|
369
496
|
config: RuntimeConfiguration,
|
package/index.js
CHANGED
package/lib/errors.js
CHANGED
|
@@ -140,6 +140,17 @@ export const MessagingError = createError(
|
|
|
140
140
|
'Cannot send a message to application "%s": %s'
|
|
141
141
|
)
|
|
142
142
|
|
|
143
|
+
export const SchedulerJobNotFoundError = createError(
|
|
144
|
+
`${ERROR_PREFIX}_SCHEDULER_JOB_NOT_FOUND`,
|
|
145
|
+
'Scheduler "%s" not found',
|
|
146
|
+
404
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
export const DuplicateSchedulerJobError = createError(
|
|
150
|
+
`${ERROR_PREFIX}_DUPLICATE_SCHEDULER_JOB`,
|
|
151
|
+
'Scheduler "%s" is already registered'
|
|
152
|
+
)
|
|
153
|
+
|
|
143
154
|
export const MissingPprofCapture = createError(
|
|
144
155
|
`${ERROR_PREFIX}_MISSING_PPROF_CAPTURE`,
|
|
145
156
|
'Please install @platformatic/wattpm-pprof-capture'
|
|
@@ -167,7 +178,7 @@ export const FailedToStopExtensionError = createError(
|
|
|
167
178
|
|
|
168
179
|
export const InvalidExtensionError = createError(
|
|
169
180
|
`${ERROR_PREFIX}_INVALID_EXTENSION`,
|
|
170
|
-
'The extension "%s" must export a setup function as its default export'
|
|
181
|
+
'The extension "%s" must export a setup function as its default export or as a named "setup" export'
|
|
171
182
|
)
|
|
172
183
|
|
|
173
184
|
export const ReservedITCHandlerNameError = createError(
|
package/lib/management-api.js
CHANGED
|
@@ -79,6 +79,23 @@ export async function managementApiPlugin (app, opts) {
|
|
|
79
79
|
await runtime.restart(applications)
|
|
80
80
|
})
|
|
81
81
|
|
|
82
|
+
app.get('/scheduler', async () => {
|
|
83
|
+
const jobs = await runtime.getSchedulerJobs()
|
|
84
|
+
return { jobs }
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
app.post('/scheduler/:name/pause', async request => {
|
|
88
|
+
return runtime.pauseSchedulerJob(request.params.name)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
app.post('/scheduler/:name/resume', async request => {
|
|
92
|
+
return runtime.resumeSchedulerJob(request.params.name)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
app.post('/scheduler/:name/run', async request => {
|
|
96
|
+
return runtime.runSchedulerJob(request.params.name)
|
|
97
|
+
})
|
|
98
|
+
|
|
82
99
|
app.get('/applications', async () => {
|
|
83
100
|
return runtime.getApplications()
|
|
84
101
|
})
|
|
@@ -203,8 +220,8 @@ export async function managementApiPlugin (app, opts) {
|
|
|
203
220
|
app.log.debug('start profiling', { id })
|
|
204
221
|
|
|
205
222
|
const options = request.body || {}
|
|
206
|
-
await runtime.startApplicationProfiling(id, options)
|
|
207
|
-
reply.code(200).send({})
|
|
223
|
+
const result = await runtime.startApplicationProfiling(id, options)
|
|
224
|
+
reply.code(200).send(result ?? {})
|
|
208
225
|
})
|
|
209
226
|
|
|
210
227
|
app.post('/applications/:id/pprof/stop', async (request, reply) => {
|
|
@@ -213,6 +230,19 @@ export async function managementApiPlugin (app, opts) {
|
|
|
213
230
|
|
|
214
231
|
const options = request.body || {}
|
|
215
232
|
const profileData = await runtime.stopApplicationProfiling(id, options)
|
|
233
|
+
|
|
234
|
+
// With allWorkers there is one profile per worker: return them as JSON
|
|
235
|
+
// with base64 encoded payloads since a single binary body cannot carry
|
|
236
|
+
// multiple profiles.
|
|
237
|
+
if (options.allWorkers && Array.isArray(profileData)) {
|
|
238
|
+
const profiles = profileData.map(({ workerIndex, profile }) => ({
|
|
239
|
+
workerIndex,
|
|
240
|
+
profile: Buffer.from(profile).toString('base64')
|
|
241
|
+
}))
|
|
242
|
+
|
|
243
|
+
return { profiles }
|
|
244
|
+
}
|
|
245
|
+
|
|
216
246
|
reply.type('application/octet-stream').code(200).send(profileData)
|
|
217
247
|
})
|
|
218
248
|
|
|
@@ -19,6 +19,10 @@ const allOperations = [
|
|
|
19
19
|
'getApplicationOpenapiSchema',
|
|
20
20
|
'getApplicationGraphqlSchema',
|
|
21
21
|
'getMetrics',
|
|
22
|
+
'getSchedulerJobs',
|
|
23
|
+
'pauseSchedulerJob',
|
|
24
|
+
'resumeSchedulerJob',
|
|
25
|
+
'runSchedulerJob',
|
|
22
26
|
'startApplication',
|
|
23
27
|
'stopApplication',
|
|
24
28
|
'restartApplication',
|
|
@@ -96,6 +100,10 @@ export function createManagementHandlers (runtime, allowedOperations) {
|
|
|
96
100
|
return runtime.getMetrics(format)
|
|
97
101
|
})
|
|
98
102
|
|
|
103
|
+
register('getSchedulerJobs', async () => {
|
|
104
|
+
return runtime.getSchedulerJobs()
|
|
105
|
+
})
|
|
106
|
+
|
|
99
107
|
// Write operations
|
|
100
108
|
register('startApplication', async ({ id }) => {
|
|
101
109
|
await runtime.startApplication(id)
|
|
@@ -113,6 +121,18 @@ export function createManagementHandlers (runtime, allowedOperations) {
|
|
|
113
121
|
return runtime.restart(applications)
|
|
114
122
|
})
|
|
115
123
|
|
|
124
|
+
register('pauseSchedulerJob', async ({ name }) => {
|
|
125
|
+
return runtime.pauseSchedulerJob(name)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
register('resumeSchedulerJob', async ({ name }) => {
|
|
129
|
+
return runtime.resumeSchedulerJob(name)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
register('runSchedulerJob', async ({ name }) => {
|
|
133
|
+
return runtime.runSchedulerJob(name)
|
|
134
|
+
})
|
|
135
|
+
|
|
116
136
|
register('addApplications', async ({ applications, start }) => {
|
|
117
137
|
const config = runtime.getRuntimeConfig(true)
|
|
118
138
|
|