@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/lib/scheduler.js
CHANGED
|
@@ -1,68 +1,176 @@
|
|
|
1
1
|
import { CronJob, validateCronExpression } from 'cron'
|
|
2
2
|
import { setTimeout } from 'node:timers/promises'
|
|
3
3
|
import { request } from 'undici'
|
|
4
|
+
import { DuplicateSchedulerJobError, SchedulerJobNotFoundError } from './errors.js'
|
|
4
5
|
|
|
5
6
|
export class SchedulerService {
|
|
6
7
|
constructor (schedulerConfig, dispatcher, logger) {
|
|
7
8
|
this.logger = logger
|
|
8
|
-
this.jobsConfig = []
|
|
9
|
-
this.cronJobs = []
|
|
10
9
|
this.dispatcher = dispatcher
|
|
11
|
-
this.
|
|
12
|
-
|
|
10
|
+
this.jobs = new Map()
|
|
11
|
+
this.started = false
|
|
13
12
|
|
|
14
|
-
validateCronSchedulers (schedulerConfig) {
|
|
15
13
|
for (const config of schedulerConfig) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
14
|
+
this.addJob({ ...config, source: 'config' })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
addJob (config, executor) {
|
|
19
|
+
// Skip disabled schedulers
|
|
20
|
+
if (config.enabled === false) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
// Validate cron expression
|
|
25
|
+
const validation = validateCronExpression(config.cron)
|
|
26
|
+
if (!validation.valid) {
|
|
27
|
+
throw new Error(`Invalid cron expression "${config.cron}" for scheduler "${config.name}"`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Set defaults for optional fields
|
|
31
|
+
const job = {
|
|
32
|
+
...config,
|
|
33
|
+
headers: config.headers || {},
|
|
34
|
+
body: config.body || {},
|
|
35
|
+
maxRetries: config.maxRetries ?? 3,
|
|
36
|
+
source: config.source ?? 'application',
|
|
37
|
+
paused: false,
|
|
38
|
+
lastExecutedAt: null,
|
|
39
|
+
lastStatus: null,
|
|
40
|
+
cronJob: null,
|
|
41
|
+
executor
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
if (this.jobs.has(job.name)) {
|
|
45
|
+
throw new DuplicateSchedulerJobError(job.name)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.jobs.set(job.name, job)
|
|
49
|
+
|
|
50
|
+
if (this.started) {
|
|
51
|
+
this.#startJob(job)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.#serializeJob(job)
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
start () {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
onTick: async () => {
|
|
44
|
-
this.logger.info(`Executing scheduler "${job.name}"`)
|
|
45
|
-
// This cannot throw, the try/catch is inside
|
|
46
|
-
await this.executeCallback(job)
|
|
47
|
-
},
|
|
48
|
-
start: true,
|
|
49
|
-
timeZone: 'UTC',
|
|
50
|
-
waitForCompletion: true
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
this.cronJobs.push(cronJob)
|
|
58
|
+
this.started = true
|
|
59
|
+
|
|
60
|
+
for (const job of this.jobs.values()) {
|
|
61
|
+
this.#startJob(job)
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
async stop () {
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
this.started = false
|
|
67
|
+
|
|
68
|
+
for (const job of this.jobs.values()) {
|
|
69
|
+
await this.#stopJob(job)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getJobs () {
|
|
74
|
+
return Array.from(this.jobs.values(), job => this.#serializeJob(job))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Stops the local cron trigger for a job without removing its configuration.
|
|
78
|
+
// Used when an external coordinator (e.g. ICC) takes over the scheduling, so
|
|
79
|
+
// that multiple instances of the same runtime do not execute the job concurrently.
|
|
80
|
+
async pauseJob (name) {
|
|
81
|
+
const job = this.#getJobOrThrow(name)
|
|
82
|
+
|
|
83
|
+
if (job.paused) {
|
|
84
|
+
return this.#serializeJob(job)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
job.paused = true
|
|
88
|
+
await this.#stopJob(job)
|
|
89
|
+
this.logger.info(`Scheduler "${name}" paused`)
|
|
90
|
+
return this.#serializeJob(job)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async resumeJob (name) {
|
|
94
|
+
const job = this.#getJobOrThrow(name)
|
|
95
|
+
|
|
96
|
+
if (!job.paused) {
|
|
97
|
+
return this.#serializeJob(job)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
job.paused = false
|
|
101
|
+
|
|
102
|
+
if (this.started) {
|
|
103
|
+
this.#startJob(job)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.logger.info(`Scheduler "${name}" resumed`)
|
|
107
|
+
return this.#serializeJob(job)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async removeApplicationJobs (applicationId) {
|
|
111
|
+
for (const [name, job] of this.jobs) {
|
|
112
|
+
if (job.applicationId === applicationId) {
|
|
113
|
+
await this.#stopJob(job)
|
|
114
|
+
this.jobs.delete(name)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async stopApplicationJobs (applicationId) {
|
|
120
|
+
for (const job of this.jobs.values()) {
|
|
121
|
+
if (job.applicationId === applicationId) {
|
|
122
|
+
await this.#stopJob(job)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Executes a job immediately, regardless of its cron schedule or paused state.
|
|
128
|
+
// Used by an external coordinator to trigger the execution in this instance.
|
|
129
|
+
async runJob (name) {
|
|
130
|
+
const job = this.#getJobOrThrow(name)
|
|
131
|
+
return this.executeCallback(job)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#getJobOrThrow (name) {
|
|
135
|
+
const job = this.jobs.get(name)
|
|
136
|
+
if (!job) {
|
|
137
|
+
throw new SchedulerJobNotFoundError(name)
|
|
138
|
+
}
|
|
139
|
+
return job
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#startJob (job) {
|
|
143
|
+
if (job.cronJob || job.paused) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.logger.info(`Configuring scheduler "${job.name}" with cron "${job.cron}"`)
|
|
148
|
+
job.cronJob = CronJob.from({
|
|
149
|
+
cronTime: job.cron,
|
|
150
|
+
onTick: async () => {
|
|
151
|
+
this.logger.info(`Executing scheduler "${job.name}"`)
|
|
152
|
+
// This cannot throw, the try/catch is inside
|
|
153
|
+
await this.executeCallback(job)
|
|
154
|
+
},
|
|
155
|
+
start: true,
|
|
156
|
+
timeZone: 'UTC',
|
|
157
|
+
waitForCompletion: true
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async #stopJob (job) {
|
|
162
|
+
if (!job.cronJob) {
|
|
163
|
+
return
|
|
60
164
|
}
|
|
165
|
+
|
|
166
|
+
await job.cronJob.stop()
|
|
167
|
+
job.cronJob = null
|
|
61
168
|
}
|
|
62
169
|
|
|
63
170
|
async executeCallback (scheduler) {
|
|
64
171
|
let attempt = 0
|
|
65
172
|
let success = false
|
|
173
|
+
const scheduledTime = Date.now()
|
|
66
174
|
|
|
67
175
|
while (!success && attempt < scheduler.maxRetries) {
|
|
68
176
|
try {
|
|
@@ -74,29 +182,32 @@ export class SchedulerService {
|
|
|
74
182
|
)
|
|
75
183
|
await setTimeout(delay)
|
|
76
184
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
...scheduler.headers
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const bodyString = typeof scheduler.body === 'string' ? scheduler.body : JSON.stringify(scheduler.body)
|
|
83
|
-
const response = await request(scheduler.callbackUrl, {
|
|
84
|
-
method: scheduler.method,
|
|
85
|
-
headers,
|
|
86
|
-
body: bodyString,
|
|
87
|
-
dispatcher: this.dispatcher
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// Consumes the body, but we are not interested in the body content,
|
|
91
|
-
// we don't save it anywere, so we just dump it
|
|
92
|
-
await response.body.dump()
|
|
93
|
-
|
|
94
|
-
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
95
|
-
this.logger.info(`Scheduler "${scheduler.name}" executed successfully`)
|
|
96
|
-
success = true
|
|
185
|
+
if (scheduler.executor) {
|
|
186
|
+
await scheduler.executor({ scheduledTime })
|
|
97
187
|
} else {
|
|
98
|
-
|
|
188
|
+
const headers = {
|
|
189
|
+
'x-retry-attempt': attempt + 1,
|
|
190
|
+
...scheduler.headers
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const bodyString = typeof scheduler.body === 'string' ? scheduler.body : JSON.stringify(scheduler.body)
|
|
194
|
+
const response = await request(scheduler.callbackUrl, {
|
|
195
|
+
method: scheduler.method,
|
|
196
|
+
headers,
|
|
197
|
+
body: bodyString,
|
|
198
|
+
dispatcher: this.dispatcher
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Consume the body to release the connection.
|
|
202
|
+
await response.body.dump()
|
|
203
|
+
|
|
204
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
205
|
+
throw new Error(`HTTP error ${response.statusCode}`)
|
|
206
|
+
}
|
|
99
207
|
}
|
|
208
|
+
|
|
209
|
+
this.logger.info(`Scheduler "${scheduler.name}" executed successfully`)
|
|
210
|
+
success = true
|
|
100
211
|
} catch (error) {
|
|
101
212
|
this.logger.error(
|
|
102
213
|
`Error executing scheduler "${scheduler.name}" (attempt ${attempt + 1}/${scheduler.maxRetries}):`,
|
|
@@ -106,9 +217,30 @@ export class SchedulerService {
|
|
|
106
217
|
}
|
|
107
218
|
}
|
|
108
219
|
|
|
220
|
+
scheduler.lastExecutedAt = new Date().toISOString()
|
|
221
|
+
scheduler.lastStatus = success ? 'success' : 'failed'
|
|
222
|
+
|
|
109
223
|
if (!success) {
|
|
110
224
|
this.logger.error(`Scheduler "${scheduler.name}" failed after ${scheduler.maxRetries} attempts`)
|
|
111
225
|
}
|
|
226
|
+
|
|
227
|
+
return { name: scheduler.name, success, executedAt: scheduler.lastExecutedAt }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#serializeJob (job) {
|
|
231
|
+
const { cronJob, executor, ...serializable } = job
|
|
232
|
+
let nextRunAt = null
|
|
233
|
+
|
|
234
|
+
if (cronJob) {
|
|
235
|
+
try {
|
|
236
|
+
nextRunAt = cronJob.nextDate().toJSDate().toISOString()
|
|
237
|
+
/* c8 ignore next 3 */
|
|
238
|
+
} catch {
|
|
239
|
+
nextRunAt = null
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { ...serializable, nextRunAt }
|
|
112
244
|
}
|
|
113
245
|
}
|
|
114
246
|
|
package/lib/worker/controller.js
CHANGED
|
@@ -24,7 +24,7 @@ import { resolve } from 'node:path'
|
|
|
24
24
|
import { getActiveResourcesInfo } from 'node:process'
|
|
25
25
|
import { workerData } from 'node:worker_threads'
|
|
26
26
|
import { getGlobalDispatcher, setGlobalDispatcher } from 'undici'
|
|
27
|
-
import { ApplicationAlreadyStartedError, RuntimeNotStartedError } from '../errors.js'
|
|
27
|
+
import { ApplicationAlreadyStartedError, exitCodes, RuntimeNotStartedError } from '../errors.js'
|
|
28
28
|
import { getApplicationUrl } from '../utils.js'
|
|
29
29
|
import { markAsPlatformaticDispatcher, refreshGlobalDispatcher } from './interceptors.js'
|
|
30
30
|
|
|
@@ -43,7 +43,7 @@ function handleUnhandled (app, event, listeners, timeout, err, ...args) {
|
|
|
43
43
|
logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${event} event.`)
|
|
44
44
|
|
|
45
45
|
// Give some time to the listeners, logger and ITC notifications to land before shutting down
|
|
46
|
-
setTimeout(() => process.exit(
|
|
46
|
+
setTimeout(() => process.exit(exitCodes.PROCESS_UNHANDLED_ERROR), timeout)
|
|
47
47
|
|
|
48
48
|
for (const listener of listeners) {
|
|
49
49
|
try {
|
package/lib/worker/itc.js
CHANGED
|
@@ -167,6 +167,10 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
167
167
|
}
|
|
168
168
|
})
|
|
169
169
|
|
|
170
|
+
// ITC handlers run concurrently, so later start/stop requests must wait for
|
|
171
|
+
// the actual in-flight start operation rather than a controller event.
|
|
172
|
+
let controllerStartPromise
|
|
173
|
+
|
|
170
174
|
const itc = new ITC({
|
|
171
175
|
name: controller.applicationConfig.id + '-worker',
|
|
172
176
|
port: parentPort,
|
|
@@ -175,14 +179,15 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
175
179
|
const status = controller.getStatus()
|
|
176
180
|
|
|
177
181
|
if (status === 'starting') {
|
|
178
|
-
await
|
|
182
|
+
await controllerStartPromise
|
|
179
183
|
} else {
|
|
180
184
|
// This gives a chance to a capability to perform custom logic
|
|
181
185
|
const events = getEvents()
|
|
182
186
|
events.emit('start')
|
|
183
187
|
|
|
184
188
|
try {
|
|
185
|
-
|
|
189
|
+
controllerStartPromise = controller.start()
|
|
190
|
+
await controllerStartPromise
|
|
186
191
|
} catch (e) {
|
|
187
192
|
await controller.stop(true)
|
|
188
193
|
|
|
@@ -205,7 +210,16 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
dispatcher.replaceServer(await controller.capability.getDispatchTarget())
|
|
208
|
-
|
|
213
|
+
|
|
214
|
+
const scheduledTasks =
|
|
215
|
+
typeof controller.capability.getScheduledTasks === 'function'
|
|
216
|
+
? await controller.capability.getScheduledTasks()
|
|
217
|
+
: []
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
url: application.entrypoint ? controller.capability.getUrl() : null,
|
|
221
|
+
scheduledTasks
|
|
222
|
+
}
|
|
209
223
|
},
|
|
210
224
|
|
|
211
225
|
async stop ({ force, dependents }) {
|
|
@@ -213,7 +227,7 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
213
227
|
const status = controller.getStatus()
|
|
214
228
|
|
|
215
229
|
if (!force && status === 'starting') {
|
|
216
|
-
await
|
|
230
|
+
await controllerStartPromise
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
if (force || status.startsWith('start')) {
|
|
@@ -305,6 +319,22 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
305
319
|
}
|
|
306
320
|
},
|
|
307
321
|
|
|
322
|
+
async getApplicationScheduledTasks () {
|
|
323
|
+
if (typeof controller.capability.getScheduledTasks !== 'function') {
|
|
324
|
+
return []
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return controller.capability.getScheduledTasks()
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
async runApplicationScheduledTasks ({ scheduleId, scheduledTime }) {
|
|
331
|
+
if (typeof controller.capability.runScheduledTasks !== 'function') {
|
|
332
|
+
throw new Error(`Application "${application.id}" does not support scheduled task execution`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return controller.capability.runScheduledTasks(scheduleId, scheduledTime)
|
|
336
|
+
},
|
|
337
|
+
|
|
308
338
|
async getApplicationMeta () {
|
|
309
339
|
try {
|
|
310
340
|
return await controller.capability.getMeta()
|
package/lib/worker/main.js
CHANGED
|
@@ -11,12 +11,14 @@ import { addPinoInstrumentation } from '@platformatic/telemetry'
|
|
|
11
11
|
import { Buffer } from 'node:buffer'
|
|
12
12
|
import { subscribe } from 'node:diagnostics_channel'
|
|
13
13
|
import { EventEmitter } from 'node:events'
|
|
14
|
+
import { readFile } from 'node:fs/promises'
|
|
14
15
|
import { ServerResponse } from 'node:http'
|
|
15
16
|
import inspector from 'node:inspector'
|
|
16
17
|
import { hostname } from 'node:os'
|
|
17
18
|
import { join, resolve } from 'node:path'
|
|
18
19
|
import { setDefaultHighWaterMark } from 'node:stream'
|
|
19
20
|
import { pathToFileURL } from 'node:url'
|
|
21
|
+
import { parseEnv } from 'node:util'
|
|
20
22
|
import { threadId, workerData } from 'node:worker_threads'
|
|
21
23
|
import pino from 'pino'
|
|
22
24
|
import { install as installUndiciGlobals } from 'undici'
|
|
@@ -240,8 +242,21 @@ async function main () {
|
|
|
240
242
|
const logger = getLogger()
|
|
241
243
|
logger.debug({ envfile }, 'Loading envfile...')
|
|
242
244
|
|
|
245
|
+
// Note that process.loadEnvFile is not used here as it never overrides an already defined
|
|
246
|
+
// variable. The worker environment is seeded from the environment the runtime resolved, which
|
|
247
|
+
// might contain values coming from an env file of the runtime. Those are only defaults, so the
|
|
248
|
+
// env file of this application, which is more specific, is allowed to override them. Real
|
|
249
|
+
// environment variables are never overridden.
|
|
250
|
+
const envFileFallbackKeys = new Set(workerData.envFileFallbackKeys ?? [])
|
|
251
|
+
|
|
243
252
|
try {
|
|
244
|
-
|
|
253
|
+
const applicationEnv = parseEnv(await readFile(envfile, 'utf-8'))
|
|
254
|
+
|
|
255
|
+
for (const [key, value] of Object.entries(applicationEnv)) {
|
|
256
|
+
if (!(key in process.env) || envFileFallbackKeys.has(key)) {
|
|
257
|
+
process.env[key] = value
|
|
258
|
+
}
|
|
259
|
+
}
|
|
245
260
|
} catch {
|
|
246
261
|
// Ignore if the file doesn't exist, similar to dotenv behavior
|
|
247
262
|
}
|
package/lib/worker/management.js
CHANGED
|
@@ -70,6 +70,22 @@ export class ManagementClient {
|
|
|
70
70
|
return this.#send('getMetrics', { format })
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
getSchedulerJobs () {
|
|
74
|
+
return this.#send('getSchedulerJobs')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pauseSchedulerJob (name) {
|
|
78
|
+
return this.#send('pauseSchedulerJob', { name })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resumeSchedulerJob (name) {
|
|
82
|
+
return this.#send('resumeSchedulerJob', { name })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
runSchedulerJob (name) {
|
|
86
|
+
return this.#send('runSchedulerJob', { name })
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
startApplication (id) {
|
|
74
90
|
return this.#send('startApplication', { id })
|
|
75
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.65.0",
|
|
4
4
|
"description": "Platformatic runtime",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
40
|
"undici-oidc-interceptor": "^0.9.0",
|
|
41
41
|
"why-is-node-running": "^2.2.2",
|
|
42
|
-
"@platformatic/composer": "3.
|
|
43
|
-
"@platformatic/
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/service": "3.
|
|
46
|
-
"@platformatic/
|
|
47
|
-
"@platformatic/sql-
|
|
48
|
-
"@platformatic/
|
|
49
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
42
|
+
"@platformatic/composer": "3.65.0",
|
|
43
|
+
"@platformatic/gateway": "3.65.0",
|
|
44
|
+
"@platformatic/node": "3.65.0",
|
|
45
|
+
"@platformatic/service": "3.65.0",
|
|
46
|
+
"@platformatic/db": "3.65.0",
|
|
47
|
+
"@platformatic/sql-graphql": "3.65.0",
|
|
48
|
+
"@platformatic/sql-mapper": "3.65.0",
|
|
49
|
+
"@platformatic/wattpm-pprof-capture": "3.65.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
"undici": "^7.27.2",
|
|
78
78
|
"undici-thread-interceptor": "^1.3.1",
|
|
79
79
|
"ws": "^8.16.0",
|
|
80
|
-
"@platformatic/basic": "3.
|
|
81
|
-
"@platformatic/generators": "3.
|
|
82
|
-
"@platformatic/
|
|
83
|
-
"@platformatic/
|
|
84
|
-
"@platformatic/
|
|
85
|
-
"@platformatic/
|
|
86
|
-
"@platformatic/
|
|
80
|
+
"@platformatic/basic": "3.65.0",
|
|
81
|
+
"@platformatic/generators": "3.65.0",
|
|
82
|
+
"@platformatic/globals": "3.65.0",
|
|
83
|
+
"@platformatic/itc": "3.65.0",
|
|
84
|
+
"@platformatic/telemetry": "3.65.0",
|
|
85
|
+
"@platformatic/metrics": "3.65.0",
|
|
86
|
+
"@platformatic/foundation": "3.65.0"
|
|
87
87
|
},
|
|
88
88
|
"engines": {
|
|
89
89
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.65.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|