@platformatic/runtime 3.9.0 → 3.10.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 CHANGED
@@ -31,10 +31,39 @@ export type PlatformaticRuntimeConfig = {
31
31
  maxHeapTotal?: number | string;
32
32
  maxYoungGeneration?: number | string;
33
33
  };
34
- preload?: string | string[];
35
34
  dependencies?: string[];
36
35
  arguments?: string[];
36
+ env?: {
37
+ [k: string]: string;
38
+ };
39
+ envfile?: string;
40
+ sourceMaps?: boolean;
41
+ packageManager?: "npm" | "pnpm" | "yarn";
42
+ preload?: string | string[];
37
43
  nodeOptions?: string;
44
+ permissions?: {
45
+ fs?: {
46
+ read?: string[];
47
+ write?: string[];
48
+ };
49
+ };
50
+ telemetry?: {
51
+ /**
52
+ * An array of instrumentations loaded if telemetry is enabled
53
+ */
54
+ instrumentations?: (
55
+ | string
56
+ | {
57
+ package: string;
58
+ exportName?: string;
59
+ options?: {
60
+ [k: string]: unknown;
61
+ };
62
+ [k: string]: unknown;
63
+ }
64
+ )[];
65
+ [k: string]: unknown;
66
+ };
38
67
  };
39
68
  };
40
69
  };
@@ -255,6 +284,7 @@ export type PlatformaticRuntimeConfig = {
255
284
  };
256
285
  };
257
286
  plugins?: string[];
287
+ timeout?: number | string;
258
288
  };
259
289
  telemetry?: {
260
290
  enabled?: boolean | string;
@@ -343,6 +373,7 @@ export type PlatformaticRuntimeConfig = {
343
373
  timeWindowSec?: number;
344
374
  cooldownSec?: number;
345
375
  scaleIntervalSec?: number;
376
+ gracePeriod?: number;
346
377
  applications?: {
347
378
  [k: string]: {
348
379
  minWorkers?: number;
package/lib/config.js CHANGED
@@ -45,7 +45,7 @@ function raiseInvalidWorkersError (location, received, hint) {
45
45
  throw new InvalidArgumentError(`${location} workers must be a positive integer; received "${received}"${extra}`)
46
46
  }
47
47
 
48
- export function autoDetectPprofCapture (config) {
48
+ export function pprofCapturePreloadPath () {
49
49
  const require = createRequire(import.meta.url)
50
50
 
51
51
  let pprofCapturePath
@@ -55,6 +55,12 @@ export function autoDetectPprofCapture (config) {
55
55
  // No-op
56
56
  }
57
57
 
58
+ return pprofCapturePath
59
+ }
60
+
61
+ export function autoDetectPprofCapture (config) {
62
+ const pprofCapturePath = pprofCapturePreloadPath()
63
+
58
64
  // Add to preload if not already present
59
65
  if (!config.preload) {
60
66
  config.preload = []
@@ -192,14 +198,7 @@ export async function transform (config, _, context) {
192
198
  config = join(entryPath, configFilename)
193
199
  }
194
200
 
195
- const application = {
196
- id,
197
- config,
198
- path: entryPath,
199
- useHttp: !!mapping.useHttp,
200
- health: mapping.health,
201
- dependencies: mapping.dependencies
202
- }
201
+ const application = { id, config, path: entryPath, ...mapping }
203
202
  const existingApplicationId = applications.findIndex(application => application.id === id)
204
203
 
205
204
  if (existingApplicationId !== -1) {
@@ -336,6 +335,13 @@ export async function transform (config, _, context) {
336
335
  // like adding other applications.
337
336
  }
338
337
 
338
+ if (config.metrics === true) {
339
+ config.metrics = {
340
+ enabled: true,
341
+ timeout: 1000
342
+ }
343
+ }
344
+
339
345
  config.applications = applications
340
346
  config.web = undefined
341
347
  config.services = undefined
package/lib/runtime.js CHANGED
@@ -9,7 +9,6 @@ import {
9
9
  kTimeout,
10
10
  parseMemorySize
11
11
  } from '@platformatic/foundation'
12
- import os from 'node:os'
13
12
  import { ITC } from '@platformatic/itc'
14
13
  import fastify from 'fastify'
15
14
  import { EventEmitter, once } from 'node:events'
@@ -17,13 +16,15 @@ import { existsSync } from 'node:fs'
17
16
  import { readFile } from 'node:fs/promises'
18
17
  import { STATUS_CODES } from 'node:http'
19
18
  import { createRequire } from 'node:module'
20
- import { join } from 'node:path'
19
+ import os from 'node:os'
20
+ import { dirname, isAbsolute, join } from 'node:path'
21
21
  import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
22
22
  import { pathToFileURL } from 'node:url'
23
23
  import { Worker } from 'node:worker_threads'
24
24
  import SonicBoom from 'sonic-boom'
25
25
  import { Agent, request, interceptors as undiciInterceptors } from 'undici'
26
26
  import { createThreadInterceptor } from 'undici-thread-interceptor'
27
+ import { pprofCapturePreloadPath } from './config.js'
27
28
  import {
28
29
  ApplicationAlreadyStartedError,
29
30
  ApplicationNotFoundError,
@@ -40,12 +41,12 @@ import {
40
41
  import { abstractLogger, createLogger } from './logger.js'
41
42
  import { startManagementApi } from './management-api.js'
42
43
  import { startPrometheusServer } from './prom-server.js'
44
+ import ScalingAlgorithm from './scaling-algorithm.js'
43
45
  import { startScheduler } from './scheduler.js'
44
46
  import { createSharedStore } from './shared-http-cache.js'
45
47
  import { version } from './version.js'
46
48
  import { sendViaITC, waitEventFromITC } from './worker/itc.js'
47
49
  import { RoundRobinMap } from './worker/round-robin-map.js'
48
- import ScalingAlgorithm from './scaling-algorithm.js'
49
50
  import {
50
51
  kApplicationId,
51
52
  kConfig,
@@ -57,7 +58,8 @@ import {
57
58
  kStderrMarker,
58
59
  kWorkerId,
59
60
  kWorkersBroadcast,
60
- kWorkerStatus
61
+ kWorkerStatus,
62
+ kWorkerStartTime
61
63
  } from './worker/symbols.js'
62
64
 
63
65
  const kWorkerFile = join(import.meta.dirname, 'worker/main.js')
@@ -277,7 +279,7 @@ export class Runtime extends EventEmitter {
277
279
  }
278
280
 
279
281
  if (this.#config.verticalScaler?.enabled) {
280
- this.#setupVerticalScaler()
282
+ await this.#setupVerticalScaler()
281
283
  }
282
284
 
283
285
  this.#showUrl()
@@ -891,8 +893,12 @@ export class Runtime extends EventEmitter {
891
893
  continue
892
894
  }
893
895
 
894
- const applicationMetrics = await sendViaITC(worker, 'getMetrics', format)
895
- if (applicationMetrics) {
896
+ const applicationMetrics = await executeWithTimeout(
897
+ sendViaITC(worker, 'getMetrics', format),
898
+ this.#config.metrics?.timeout ?? 10000
899
+ )
900
+
901
+ if (applicationMetrics && applicationMetrics !== kTimeout) {
896
902
  if (metrics === null) {
897
903
  metrics = format === 'json' ? [] : ''
898
904
  }
@@ -1315,6 +1321,17 @@ export class Runtime extends EventEmitter {
1315
1321
  execArgv.push('--enable-source-maps')
1316
1322
  }
1317
1323
 
1324
+ if (applicationConfig.permissions?.fs) {
1325
+ execArgv.push(...this.#setupPermissions(applicationConfig))
1326
+ }
1327
+
1328
+ let preload = config.preload
1329
+ if (execArgv.includes('--permission')) {
1330
+ // Remove wattpm-pprof-capture from preload since it is not supported
1331
+ const pprofCapturePath = pprofCapturePreloadPath()
1332
+ preload = preload.filter(p => p !== pprofCapturePath)
1333
+ }
1334
+
1318
1335
  const workerEnv = structuredClone(this.#env)
1319
1336
 
1320
1337
  if (applicationConfig.nodeOptions?.trim().length > 0) {
@@ -1337,7 +1354,10 @@ export class Runtime extends EventEmitter {
1337
1354
 
1338
1355
  const worker = new Worker(kWorkerFile, {
1339
1356
  workerData: {
1340
- config,
1357
+ config: {
1358
+ ...config,
1359
+ preload
1360
+ },
1341
1361
  applicationConfig: {
1342
1362
  ...applicationConfig,
1343
1363
  isProduction: this.#isProduction,
@@ -1642,6 +1662,8 @@ export class Runtime extends EventEmitter {
1642
1662
  }
1643
1663
 
1644
1664
  worker[kWorkerStatus] = 'started'
1665
+ worker[kWorkerStartTime] = Date.now()
1666
+
1645
1667
  this.emitAndNotify('application:worker:started', eventPayload)
1646
1668
  this.#broadcastWorkers()
1647
1669
 
@@ -2441,9 +2463,14 @@ export class Runtime extends EventEmitter {
2441
2463
  }
2442
2464
  }
2443
2465
 
2444
- #setupVerticalScaler () {
2445
- const isWorkersFixed = this.#config.workers !== undefined
2446
- if (isWorkersFixed) return
2466
+ async #setupVerticalScaler () {
2467
+ const fixedWorkersCount = this.#config.workers
2468
+ if (fixedWorkersCount !== undefined) {
2469
+ this.logger.warn(
2470
+ `Vertical scaler disabled because the "workers" configuration is set to ${fixedWorkersCount}`
2471
+ )
2472
+ return
2473
+ }
2447
2474
 
2448
2475
  const scalerConfig = this.#config.verticalScaler
2449
2476
 
@@ -2456,6 +2483,7 @@ export class Runtime extends EventEmitter {
2456
2483
  scalerConfig.minELUDiff ??= 0.2
2457
2484
  scalerConfig.scaleIntervalSec ??= 60
2458
2485
  scalerConfig.timeWindowSec ??= 60
2486
+ scalerConfig.gracePeriod ??= 30 * 1000
2459
2487
  scalerConfig.applications ??= {}
2460
2488
 
2461
2489
  const maxTotalWorkers = scalerConfig.maxTotalWorkers
@@ -2468,9 +2496,18 @@ export class Runtime extends EventEmitter {
2468
2496
  const scaleIntervalSec = scalerConfig.scaleIntervalSec
2469
2497
  const timeWindowSec = scalerConfig.timeWindowSec
2470
2498
  const applicationsConfigs = scalerConfig.applications
2499
+ const gracePeriod = scalerConfig.gracePeriod
2500
+ const healthCheckInterval = 1000
2501
+
2502
+ const initialResourcesUpdates = []
2471
2503
 
2472
2504
  for (const application of this.#config.applications) {
2473
2505
  if (application.entrypoint && !features.node.reusePort) {
2506
+ this.logger.warn(
2507
+ `The "${application.id}" application cannot be scaled because it is an entrypoint` +
2508
+ ' and the "reusePort" feature is not available in your OS.'
2509
+ )
2510
+
2474
2511
  applicationsConfigs[application.id] = {
2475
2512
  minWorkers: 1,
2476
2513
  maxWorkers: 1
@@ -2478,6 +2515,10 @@ export class Runtime extends EventEmitter {
2478
2515
  continue
2479
2516
  }
2480
2517
  if (application.workers !== undefined) {
2518
+ this.logger.warn(
2519
+ `The "${application.id}" application cannot be scaled because` +
2520
+ ` it has a fixed number of workers (${application.workers}).`
2521
+ )
2481
2522
  applicationsConfigs[application.id] = {
2482
2523
  minWorkers: application.workers,
2483
2524
  maxWorkers: application.workers
@@ -2488,12 +2529,22 @@ export class Runtime extends EventEmitter {
2488
2529
  applicationsConfigs[application.id] ??= {}
2489
2530
  applicationsConfigs[application.id].minWorkers ??= minWorkers
2490
2531
  applicationsConfigs[application.id].maxWorkers ??= maxWorkers
2532
+
2533
+ const appMinWorkers = applicationsConfigs[application.id].minWorkers
2534
+ if (appMinWorkers > 1) {
2535
+ initialResourcesUpdates.push({
2536
+ application: application.id,
2537
+ workers: minWorkers
2538
+ })
2539
+ }
2540
+ }
2541
+
2542
+ if (initialResourcesUpdates.length > 0) {
2543
+ await this.updateApplicationsResources(initialResourcesUpdates)
2491
2544
  }
2492
2545
 
2493
2546
  for (const applicationId in applicationsConfigs) {
2494
- const application = this.#config.applications.find(
2495
- app => app.id === applicationId
2496
- )
2547
+ const application = this.#config.applications.find(app => app.id === applicationId)
2497
2548
  if (!application) {
2498
2549
  delete applicationsConfigs[applicationId]
2499
2550
 
@@ -2512,18 +2563,43 @@ export class Runtime extends EventEmitter {
2512
2563
  applications: applicationsConfigs
2513
2564
  })
2514
2565
 
2515
- this.on('application:worker:health', async (healthInfo) => {
2516
- if (!healthInfo) {
2517
- this.logger.error('No health info received')
2518
- return
2519
- }
2566
+ const healthCheckTimeout = setTimeout(async () => {
2567
+ let shouldCheckForScaling = false
2568
+
2569
+ const now = Date.now()
2520
2570
 
2521
- scalingAlgorithm.addWorkerHealthInfo(healthInfo)
2571
+ for (const worker of this.#workers.values()) {
2572
+ if (
2573
+ worker[kWorkerStatus] !== 'started' ||
2574
+ worker[kWorkerStartTime] + gracePeriod > now
2575
+ ) {
2576
+ continue
2577
+ }
2578
+
2579
+ try {
2580
+ const health = await this.#getHealth(worker)
2581
+ if (!health) continue
2582
+
2583
+ scalingAlgorithm.addWorkerHealthInfo({
2584
+ workerId: worker[kId],
2585
+ applicationId: worker[kApplicationId],
2586
+ elu: health.elu
2587
+ })
2522
2588
 
2523
- if (healthInfo.currentHealth.elu > scaleUpELU) {
2589
+ if (health.elu > scaleUpELU) {
2590
+ shouldCheckForScaling = true
2591
+ }
2592
+ } catch (err) {
2593
+ this.logger.error({ err }, 'Failed to get health for worker')
2594
+ }
2595
+ }
2596
+
2597
+ if (shouldCheckForScaling) {
2524
2598
  await checkForScaling()
2525
2599
  }
2526
- })
2600
+
2601
+ healthCheckTimeout.refresh()
2602
+ }, healthCheckInterval).unref()
2527
2603
 
2528
2604
  let isScaling = false
2529
2605
  let lastScaling = 0
@@ -2548,16 +2624,16 @@ export class Runtime extends EventEmitter {
2548
2624
  const recommendations = scalingAlgorithm.getRecommendations(appsWorkersInfo)
2549
2625
  if (recommendations.length > 0) {
2550
2626
  await applyRecommendations(recommendations)
2627
+ lastScaling = Date.now()
2551
2628
  }
2552
2629
  } catch (err) {
2553
2630
  this.logger.error({ err }, 'Failed to scale applications')
2554
2631
  } finally {
2555
2632
  isScaling = false
2556
- lastScaling = Date.now()
2557
2633
  }
2558
2634
  }
2559
2635
 
2560
- const applyRecommendations = async (recommendations) => {
2636
+ const applyRecommendations = async recommendations => {
2561
2637
  const resourcesUpdates = []
2562
2638
  for (const recommendation of recommendations) {
2563
2639
  const { applicationId, workersCount, direction } = recommendation
@@ -2574,4 +2650,48 @@ export class Runtime extends EventEmitter {
2574
2650
  // Interval for periodic scaling checks
2575
2651
  setInterval(checkForScaling, scaleIntervalSec * 1000).unref()
2576
2652
  }
2653
+
2654
+ #setupPermissions (applicationConfig) {
2655
+ const argv = []
2656
+ const allows = new Set()
2657
+ const { read, write } = applicationConfig.permissions.fs
2658
+
2659
+ if (read?.length) {
2660
+ for (const p of read) {
2661
+ allows.add(`--allow-fs-read=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
2662
+ }
2663
+ }
2664
+
2665
+ if (write?.length) {
2666
+ for (const p of write) {
2667
+ allows.add(`--allow-fs-write=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
2668
+ }
2669
+ }
2670
+
2671
+ if (allows.size === 0) {
2672
+ return argv
2673
+ }
2674
+
2675
+ // We need to allow read access to the node_modules folder both at the runtime level and at the application level
2676
+ allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
2677
+ allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
2678
+
2679
+ // Since we can't really predict how dependencies are installed (symlinks, pnpm store, and so forth), we also
2680
+ // add any node_modules folder found in the ancestors of the current file
2681
+ let lastPath = import.meta.dirname
2682
+ let currentPath = import.meta.dirname
2683
+
2684
+ do {
2685
+ lastPath = currentPath
2686
+ const nodeModules = join(currentPath, 'node_modules')
2687
+ if (existsSync(nodeModules)) {
2688
+ allows.add(`--allow-fs-read=${join(nodeModules, '*')}`)
2689
+ }
2690
+
2691
+ currentPath = dirname(currentPath)
2692
+ } while (lastPath !== currentPath)
2693
+
2694
+ argv.push('--permission', ...allows)
2695
+ return argv
2696
+ }
2577
2697
  }
@@ -19,9 +19,7 @@ class ScalingAlgorithm {
19
19
  }
20
20
 
21
21
  addWorkerHealthInfo (healthInfo) {
22
- const workerId = healthInfo.id
23
- const applicationId = healthInfo.application
24
- const elu = healthInfo.currentHealth.elu
22
+ const { workerId, applicationId, elu } = healthInfo
25
23
  const timestamp = Date.now()
26
24
 
27
25
  if (!this.#appsELUs[applicationId]) {
@@ -59,6 +57,27 @@ class ScalingAlgorithm {
59
57
 
60
58
  for (const { applicationId, elu, workersCount } of appsInfo) {
61
59
  const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
60
+ const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
61
+
62
+ if (workersCount < appMinWorkers) {
63
+ recommendations.push({
64
+ applicationId,
65
+ workersCount: appMinWorkers,
66
+ direction: 'up'
67
+ })
68
+ totalWorkersCount += appMinWorkers - workersCount
69
+ continue
70
+ }
71
+
72
+ if (workersCount > appMaxWorkers) {
73
+ recommendations.push({
74
+ applicationId,
75
+ workersCount: appMaxWorkers,
76
+ direction: 'down'
77
+ })
78
+ totalWorkersCount -= workersCount - appMaxWorkers
79
+ continue
80
+ }
62
81
 
63
82
  if (elu < this.#scaleDownELU && workersCount > appMinWorkers) {
64
83
  recommendations.push({
@@ -75,6 +94,12 @@ class ScalingAlgorithm {
75
94
 
76
95
  const { applicationId, workersCount } = scaleUpCandidate
77
96
 
97
+ const isScaled = recommendations.some(
98
+ r => r.applicationId === applicationId &&
99
+ r.direction === 'up'
100
+ )
101
+ if (isScaled) continue
102
+
78
103
  const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
79
104
  if (workersCount >= appMaxWorkers) continue
80
105
 
@@ -6,6 +6,7 @@ export const kWorkerId = Symbol.for('plt.runtime.worker.id')
6
6
  export const kITC = Symbol.for('plt.runtime.itc')
7
7
  export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer')
8
8
  export const kWorkerStatus = Symbol('plt.runtime.worker.status')
9
+ export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
9
10
  export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
10
11
  export const kLastELU = Symbol.for('plt.runtime.worker.lastELU')
11
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.9.0",
3
+ "version": "3.10.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.9.0",
39
- "@platformatic/db": "3.9.0",
40
- "@platformatic/gateway": "3.9.0",
41
- "@platformatic/sql-graphql": "3.9.0",
42
- "@platformatic/node": "3.9.0",
43
- "@platformatic/sql-mapper": "3.9.0",
44
- "@platformatic/service": "3.9.0",
45
- "@platformatic/wattpm-pprof-capture": "3.9.0"
38
+ "@platformatic/composer": "3.10.0",
39
+ "@platformatic/db": "3.10.0",
40
+ "@platformatic/node": "3.10.0",
41
+ "@platformatic/gateway": "3.10.0",
42
+ "@platformatic/service": "3.10.0",
43
+ "@platformatic/sql-graphql": "3.10.0",
44
+ "@platformatic/sql-mapper": "3.10.0",
45
+ "@platformatic/wattpm-pprof-capture": "3.10.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@fastify/accepts": "^5.0.0",
@@ -72,12 +72,12 @@
72
72
  "undici": "^7.0.0",
73
73
  "undici-thread-interceptor": "^0.14.0",
74
74
  "ws": "^8.16.0",
75
- "@platformatic/foundation": "3.9.0",
76
- "@platformatic/generators": "3.9.0",
77
- "@platformatic/itc": "3.9.0",
78
- "@platformatic/basic": "3.9.0",
79
- "@platformatic/metrics": "3.9.0",
80
- "@platformatic/telemetry": "3.9.0"
75
+ "@platformatic/basic": "3.10.0",
76
+ "@platformatic/foundation": "3.10.0",
77
+ "@platformatic/itc": "3.10.0",
78
+ "@platformatic/generators": "3.10.0",
79
+ "@platformatic/metrics": "3.10.0",
80
+ "@platformatic/telemetry": "3.10.0"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.9.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.10.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -171,6 +171,40 @@
171
171
  },
172
172
  "additionalProperties": false
173
173
  },
174
+ "dependencies": {
175
+ "type": "array",
176
+ "items": {
177
+ "type": "string"
178
+ },
179
+ "default": []
180
+ },
181
+ "arguments": {
182
+ "type": "array",
183
+ "items": {
184
+ "type": "string"
185
+ }
186
+ },
187
+ "env": {
188
+ "type": "object",
189
+ "additionalProperties": {
190
+ "type": "string"
191
+ }
192
+ },
193
+ "envfile": {
194
+ "type": "string"
195
+ },
196
+ "sourceMaps": {
197
+ "type": "boolean",
198
+ "default": false
199
+ },
200
+ "packageManager": {
201
+ "type": "string",
202
+ "enum": [
203
+ "npm",
204
+ "pnpm",
205
+ "yarn"
206
+ ]
207
+ },
174
208
  "preload": {
175
209
  "anyOf": [
176
210
  {
@@ -186,20 +220,66 @@
186
220
  }
187
221
  ]
188
222
  },
189
- "dependencies": {
190
- "type": "array",
191
- "items": {
192
- "type": "string"
193
- }
194
- },
195
- "arguments": {
196
- "type": "array",
197
- "items": {
198
- "type": "string"
199
- }
200
- },
201
223
  "nodeOptions": {
202
224
  "type": "string"
225
+ },
226
+ "permissions": {
227
+ "type": "object",
228
+ "properties": {
229
+ "fs": {
230
+ "type": "object",
231
+ "properties": {
232
+ "read": {
233
+ "type": "array",
234
+ "items": {
235
+ "type": "string"
236
+ }
237
+ },
238
+ "write": {
239
+ "type": "array",
240
+ "items": {
241
+ "type": "string"
242
+ }
243
+ }
244
+ },
245
+ "additionalProperties": false
246
+ }
247
+ },
248
+ "additionalProperties": false
249
+ },
250
+ "telemetry": {
251
+ "type": "object",
252
+ "properties": {
253
+ "instrumentations": {
254
+ "type": "array",
255
+ "description": "An array of instrumentations loaded if telemetry is enabled",
256
+ "items": {
257
+ "oneOf": [
258
+ {
259
+ "type": "string"
260
+ },
261
+ {
262
+ "type": "object",
263
+ "properties": {
264
+ "package": {
265
+ "type": "string"
266
+ },
267
+ "exportName": {
268
+ "type": "string"
269
+ },
270
+ "options": {
271
+ "type": "object",
272
+ "additionalProperties": true
273
+ }
274
+ },
275
+ "required": [
276
+ "package"
277
+ ]
278
+ }
279
+ ]
280
+ }
281
+ }
282
+ }
203
283
  }
204
284
  }
205
285
  }
@@ -405,6 +485,30 @@
405
485
  "nodeOptions": {
406
486
  "type": "string"
407
487
  },
488
+ "permissions": {
489
+ "type": "object",
490
+ "properties": {
491
+ "fs": {
492
+ "type": "object",
493
+ "properties": {
494
+ "read": {
495
+ "type": "array",
496
+ "items": {
497
+ "type": "string"
498
+ }
499
+ },
500
+ "write": {
501
+ "type": "array",
502
+ "items": {
503
+ "type": "string"
504
+ }
505
+ }
506
+ },
507
+ "additionalProperties": false
508
+ }
509
+ },
510
+ "additionalProperties": false
511
+ },
408
512
  "telemetry": {
409
513
  "type": "object",
410
514
  "properties": {
@@ -641,6 +745,30 @@
641
745
  "nodeOptions": {
642
746
  "type": "string"
643
747
  },
748
+ "permissions": {
749
+ "type": "object",
750
+ "properties": {
751
+ "fs": {
752
+ "type": "object",
753
+ "properties": {
754
+ "read": {
755
+ "type": "array",
756
+ "items": {
757
+ "type": "string"
758
+ }
759
+ },
760
+ "write": {
761
+ "type": "array",
762
+ "items": {
763
+ "type": "string"
764
+ }
765
+ }
766
+ },
767
+ "additionalProperties": false
768
+ }
769
+ },
770
+ "additionalProperties": false
771
+ },
644
772
  "telemetry": {
645
773
  "type": "object",
646
774
  "properties": {
@@ -877,6 +1005,30 @@
877
1005
  "nodeOptions": {
878
1006
  "type": "string"
879
1007
  },
1008
+ "permissions": {
1009
+ "type": "object",
1010
+ "properties": {
1011
+ "fs": {
1012
+ "type": "object",
1013
+ "properties": {
1014
+ "read": {
1015
+ "type": "array",
1016
+ "items": {
1017
+ "type": "string"
1018
+ }
1019
+ },
1020
+ "write": {
1021
+ "type": "array",
1022
+ "items": {
1023
+ "type": "string"
1024
+ }
1025
+ }
1026
+ },
1027
+ "additionalProperties": false
1028
+ }
1029
+ },
1030
+ "additionalProperties": false
1031
+ },
880
1032
  "telemetry": {
881
1033
  "type": "object",
882
1034
  "properties": {
@@ -1691,6 +1843,17 @@
1691
1843
  }
1692
1844
  ]
1693
1845
  }
1846
+ },
1847
+ "timeout": {
1848
+ "anyOf": [
1849
+ {
1850
+ "type": "integer"
1851
+ },
1852
+ {
1853
+ "type": "string"
1854
+ }
1855
+ ],
1856
+ "default": 10000
1694
1857
  }
1695
1858
  },
1696
1859
  "additionalProperties": false
@@ -1873,6 +2036,10 @@
1873
2036
  "type": "number",
1874
2037
  "minimum": 0
1875
2038
  },
2039
+ "gracePeriod": {
2040
+ "type": "number",
2041
+ "minimum": 0
2042
+ },
1876
2043
  "applications": {
1877
2044
  "type": "object",
1878
2045
  "additionalProperties": {