@platformatic/runtime 2.0.0-alpha.4 → 2.0.0-alpha.6

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
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha4Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha6Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -201,10 +201,6 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha4Json = {
201
201
  };
202
202
  restartOnError?: boolean | number;
203
203
  services?: {
204
- id: string;
205
- path: string;
206
- config?: string;
207
- useHttp?: boolean;
208
204
  [k: string]: unknown;
209
205
  }[];
210
206
  };
package/lib/errors.js CHANGED
@@ -13,6 +13,7 @@ module.exports = {
13
13
  ServiceNotStartedError: createError(`${ERROR_PREFIX}_SERVICE_NOT_STARTED`, "Service with id '%s' is not started"),
14
14
  FailedToRetrieveOpenAPISchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_OPENAPI_SCHEMA`, 'Failed to retrieve OpenAPI schema for service with id "%s": %s'),
15
15
  FailedToRetrieveGraphQLSchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_GRAPHQL_SCHEMA`, 'Failed to retrieve GraphQL schema for service with id "%s": %s'),
16
+ FailedToRetrieveMetaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_META`, 'Failed to retrieve metadata for service with id "%s": %s'),
16
17
  ApplicationAlreadyStartedError: createError(`${ERROR_PREFIX}_APPLICATION_ALREADY_STARTED`, 'Application is already started'),
17
18
  ApplicationNotStartedError: createError(`${ERROR_PREFIX}_APPLICATION_NOT_STARTED`, 'Application has not been started'),
18
19
  ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
@@ -202,7 +202,7 @@ class RuntimeGenerator extends BaseGenerator {
202
202
  })
203
203
 
204
204
  if (!this.existingConfig) {
205
- this.addFile({ path: '', file: 'README.md', contents: await readFile(join(__dirname, 'README.md')) })
205
+ this.addFile({ path: '', file: 'README.md', contents: await readFile(join(__dirname, 'README.md'), 'utf-8') })
206
206
  }
207
207
 
208
208
  return {
@@ -375,7 +375,7 @@ class RuntimeGenerator extends BaseGenerator {
375
375
 
376
376
  // delete dependencies
377
377
  const servicePackageJson = JSON.parse(
378
- await readFile(join(this.targetDirectory, 'services', s.name, 'platformatic.json'))
378
+ await readFile(join(this.targetDirectory, 'services', s.name, 'platformatic.json'), 'utf-8')
379
379
  )
380
380
  if (servicePackageJson.plugins && servicePackageJson.plugins.packages) {
381
381
  servicePackageJson.plugins.packages.forEach(p => {
package/lib/runtime.js CHANGED
@@ -9,6 +9,7 @@ const { setTimeout: sleep } = require('node:timers/promises')
9
9
  const { Worker } = require('node:worker_threads')
10
10
 
11
11
  const { ITC } = require('@platformatic/itc')
12
+ const { Unpromise } = require('@watchable/unpromise')
12
13
  const ts = require('tail-file-stream')
13
14
  const { createThreadInterceptor } = require('undici-thread-interceptor')
14
15
 
@@ -240,17 +241,20 @@ class Runtime extends EventEmitter {
240
241
  const config = this.#configManager.current
241
242
  const restartOnError = config.restartOnError
242
243
 
244
+ if (!restartOnError) {
245
+ this.logger.error(`Failed to start service "${id}".`)
246
+ throw error
247
+ }
248
+
243
249
  let bootstrapAttempt = this.#bootstrapAttempts.get(id)
244
250
  if (bootstrapAttempt++ >= MAX_BOOTSTRAP_ATTEMPTS || restartOnError === 0) {
245
- this.logger.error(
246
- `Failed to start service "${id}" after ${MAX_BOOTSTRAP_ATTEMPTS} attempts.`
247
- )
251
+ this.logger.error(`Failed to start service "${id}" after ${MAX_BOOTSTRAP_ATTEMPTS} attempts.`)
248
252
  throw error
249
253
  }
250
254
 
251
255
  this.logger.warn(
252
256
  `Starting a service "${id}" in ${restartOnError}ms. ` +
253
- `Attempt ${bootstrapAttempt} of ${MAX_BOOTSTRAP_ATTEMPTS}...`
257
+ `Attempt ${bootstrapAttempt} of ${MAX_BOOTSTRAP_ATTEMPTS}...`
254
258
  )
255
259
 
256
260
  this.#bootstrapAttempts.set(id, bootstrapAttempt)
@@ -272,13 +276,13 @@ class Runtime extends EventEmitter {
272
276
 
273
277
  // Always send the stop message, it will shut down workers that only had ITC and interceptors setup
274
278
  try {
275
- await Promise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
279
+ await Unpromise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
276
280
  } catch (error) {
277
281
  this.logger?.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
278
282
  }
279
283
 
280
284
  // Wait for the worker thread to finish, we're going to create a new one if the service is ever restarted
281
- const res = await Promise.race([once(service, 'exit'), sleep(10000, 'timeout', { ref: false })])
285
+ const res = await Unpromise.race([once(service, 'exit'), sleep(10000, 'timeout', { ref: false })])
282
286
 
283
287
  // If the worker didn't exit in time, kill it
284
288
  if (res === 'timeout') {
@@ -419,7 +423,7 @@ class Runtime extends EventEmitter {
419
423
  packageName: packageJson.name ?? null,
420
424
  packageVersion: packageJson.version ?? null,
421
425
  url: entrypointDetails?.url ?? null,
422
- platformaticVersion,
426
+ platformaticVersion
423
427
  }
424
428
  }
425
429
 
@@ -446,7 +450,7 @@ class Runtime extends EventEmitter {
446
450
  async getServices () {
447
451
  return {
448
452
  entrypoint: this.#entrypointId,
449
- services: await Promise.all(this.#servicesIds.map(id => this.getServiceDetails(id))),
453
+ services: await Promise.all(this.#servicesIds.map(id => this.getServiceDetails(id)))
450
454
  }
451
455
  }
452
456
 
@@ -475,7 +479,7 @@ class Runtime extends EventEmitter {
475
479
  version,
476
480
  localUrl,
477
481
  entrypoint,
478
- dependencies,
482
+ dependencies
479
483
  }
480
484
 
481
485
  if (entrypoint) {
@@ -607,9 +611,9 @@ class Runtime extends EventEmitter {
607
611
  p50: p50Value,
608
612
  p90: p90Value,
609
613
  p95: p95Value,
610
- p99: p99Value,
611
- },
612
- },
614
+ p99: p99Value
615
+ }
616
+ }
613
617
  }
614
618
 
615
619
  return formattedMetrics
@@ -646,7 +650,7 @@ class Runtime extends EventEmitter {
646
650
  }
647
651
  runtimesLogsIds.push({
648
652
  pid: runtime.runtimePID,
649
- indexes: runtimeLogIds,
653
+ indexes: runtimeLogIds
650
654
  })
651
655
  }
652
656
 
@@ -684,7 +688,7 @@ class Runtime extends EventEmitter {
684
688
  serviceConfig,
685
689
  dirname: this.#configManager.dirname,
686
690
  runtimeLogsDir: this.#runtimeLogsDir,
687
- loggingPort,
691
+ loggingPort
688
692
  },
689
693
  execArgv: [], // Avoid side effects
690
694
  env: this.#env,
@@ -697,7 +701,7 @@ class Runtime extends EventEmitter {
697
701
  The author of this (Paolo and Matteo) are not proud of the solution. Forgive us.
698
702
  */
699
703
  stdout: true,
700
- stderr: true,
704
+ stderr: true
701
705
  })
702
706
 
703
707
  // Make sure the listener can handle a lot of API requests at once before raising a warning
@@ -720,7 +724,7 @@ class Runtime extends EventEmitter {
720
724
  if (started && this.#status === 'started') {
721
725
  if (restartOnError > 0) {
722
726
  this.logger.warn(`Restarting a service "${id}" in ${restartOnError}ms...`)
723
- this.#restartCrashedService(id).catch((err) => {
727
+ this.#restartCrashedService(id).catch(err => {
724
728
  this.logger.error({ err }, `Failed to restart service "${id}".`)
725
729
  })
726
730
  } else {
@@ -734,9 +738,13 @@ class Runtime extends EventEmitter {
734
738
  service[kConfig] = serviceConfig
735
739
 
736
740
  // Setup ITC
737
- service[kITC] = new ITC({ port: service })
741
+ service[kITC] = new ITC({
742
+ port: service,
743
+ handlers: {
744
+ getServiceMeta: this.getServiceMeta.bind(this)
745
+ }
746
+ })
738
747
  service[kITC].listen()
739
- service[kITC].handle('getServiceMeta', this.#getServiceMeta.bind(this))
740
748
 
741
749
  // Handle services changes
742
750
  // This is not purposely activated on when this.#configManager.current.watch === true
@@ -838,7 +846,7 @@ class Runtime extends EventEmitter {
838
846
  return service
839
847
  }
840
848
 
841
- async #getServiceMeta (id) {
849
+ async getServiceMeta (id) {
842
850
  const service = this.#services.get(id)
843
851
 
844
852
  if (!service) {
@@ -901,7 +909,7 @@ class Runtime extends EventEmitter {
901
909
  runtimesLogFiles.push({
902
910
  runtimePID: parseInt(runtimePID),
903
911
  runtimeLogFiles,
904
- lastModified,
912
+ lastModified
905
913
  })
906
914
  }
907
915
 
package/lib/schema.js CHANGED
@@ -191,7 +191,10 @@ const platformaticRuntimeSchema = {
191
191
  type: 'array',
192
192
  items: {
193
193
  type: 'object',
194
- required: ['id', 'path'],
194
+ anyOf: [
195
+ { required: ['id', 'path'] },
196
+ { required: ['id', 'url'] },
197
+ ],
195
198
  properties: {
196
199
  id: {
197
200
  type: 'string',
@@ -203,6 +206,9 @@ const platformaticRuntimeSchema = {
203
206
  config: {
204
207
  type: 'string',
205
208
  },
209
+ url: {
210
+ type: 'string',
211
+ },
206
212
  useHttp: {
207
213
  type: 'boolean',
208
214
  },
package/lib/worker/app.js CHANGED
@@ -205,21 +205,14 @@ class PlatformaticApp extends EventEmitter {
205
205
 
206
206
  #logAndExit (err) {
207
207
  // Runtime logs here with console.error because stackable is not initialized
208
- console.error(
209
- JSON.stringify({
210
- msg: err.message,
211
- name: this.appConfig.id,
212
- })
213
- )
208
+ console.error(err.message)
214
209
  process.exit(1)
215
210
  }
216
211
 
217
212
  #wrapStackable (stackable) {
218
213
  const newStackable = {}
219
214
  for (const method of Object.keys(defaultStackable)) {
220
- newStackable[method] = stackable[method]
221
- ? stackable[method].bind(stackable)
222
- : defaultStackable[method]
215
+ newStackable[method] = stackable[method] ? stackable[method].bind(stackable) : defaultStackable[method]
223
216
  }
224
217
  return newStackable
225
218
  }
@@ -240,8 +233,7 @@ class PlatformaticApp extends EventEmitter {
240
233
  }
241
234
  }
242
235
 
243
- const dispatcher = getGlobalDispatcher()
244
- .compose(interceptor)
236
+ const dispatcher = getGlobalDispatcher().compose(interceptor)
245
237
 
246
238
  setGlobalDispatcher(dispatcher)
247
239
  }
@@ -13,6 +13,7 @@ const defaultStackable = {
13
13
  getDispatchFunc: () => null,
14
14
  getOpenapiSchema: () => null,
15
15
  getGraphqlSchema: () => null,
16
+ getMeta: () => ({}),
16
17
  getMetrics: () => null,
17
18
  inject: () => {
18
19
  throw new Error('Stackable inject not implemented')
package/lib/worker/itc.js CHANGED
@@ -4,6 +4,7 @@ const { once } = require('node:events')
4
4
  const { parentPort } = require('node:worker_threads')
5
5
 
6
6
  const { ITC } = require('@platformatic/itc')
7
+ const { Unpromise } = require('@watchable/unpromise')
7
8
 
8
9
  const errors = require('../errors')
9
10
  const { kITC, kId } = require('./symbols')
@@ -14,11 +15,11 @@ async function sendViaITC (worker, name, message) {
14
15
  const ac = new AbortController()
15
16
  let exitCode
16
17
 
17
- const response = await Promise.race([
18
+ const response = await Unpromise.race([
18
19
  worker[kITC].send(name, message),
19
20
  once(worker, 'exit', { signal: ac.signal }).then(([code]) => {
20
21
  exitCode = code
21
- }),
22
+ })
22
23
  ])
23
24
 
24
25
  if (typeof exitCode === 'number') {
@@ -42,80 +43,91 @@ async function sendViaITC (worker, name, message) {
42
43
  }
43
44
 
44
45
  function setupITC (app, service, dispatcher) {
45
- const itc = new ITC({ port: parentPort })
46
-
47
- itc.handle('start', async () => {
48
- const status = app.getStatus()
49
-
50
- if (status === 'starting') {
51
- await once(app, 'start')
52
- } else {
53
- await app.start()
54
- }
55
-
56
- if (service.entrypoint) {
57
- await app.listen()
58
- }
59
-
60
- const url = app.stackable.getUrl()
61
-
62
- const dispatchFunc = await app.stackable.getDispatchFunc()
63
- dispatcher.replaceServer(url ?? dispatchFunc)
64
-
65
- return service.entrypoint ? url : null
66
- })
67
-
68
- itc.handle('stop', async () => {
69
- const status = app.getStatus()
70
-
71
- if (status === 'starting') {
72
- await once(app, 'start')
46
+ const itc = new ITC({
47
+ port: parentPort,
48
+ handlers: {
49
+ async start () {
50
+ const status = app.getStatus()
51
+
52
+ if (status === 'starting') {
53
+ await once(app, 'start')
54
+ } else {
55
+ await app.start()
56
+ }
57
+
58
+ if (service.entrypoint) {
59
+ await app.listen()
60
+ }
61
+
62
+ const url = app.stackable.getUrl()
63
+
64
+ const dispatchFunc = await app.stackable.getDispatchFunc()
65
+ dispatcher.replaceServer(url ?? dispatchFunc)
66
+
67
+ return service.entrypoint ? url : null
68
+ },
69
+
70
+ async stop () {
71
+ const status = app.getStatus()
72
+
73
+ if (status === 'starting') {
74
+ await once(app, 'start')
75
+ }
76
+
77
+ if (status !== 'stopped') {
78
+ await app.stop()
79
+ }
80
+
81
+ dispatcher.interceptor.close()
82
+ itc.close()
83
+ },
84
+
85
+ getStatus () {
86
+ return app.getStatus()
87
+ },
88
+
89
+ getServiceInfo () {
90
+ return app.stackable.getInfo()
91
+ },
92
+
93
+ async getServiceConfig () {
94
+ const current = await app.stackable.getConfig()
95
+ // Remove all undefined keys from the config
96
+ return JSON.parse(JSON.stringify(current))
97
+ },
98
+
99
+ async getServiceOpenAPISchema () {
100
+ try {
101
+ return await app.stackable.getOpenapiSchema()
102
+ } catch (err) {
103
+ throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
104
+ }
105
+ },
106
+
107
+ async getServiceGraphQLSchema () {
108
+ try {
109
+ return await app.stackable.getGraphqlSchema()
110
+ } catch (err) {
111
+ throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
112
+ }
113
+ },
114
+
115
+ async getServiceMeta () {
116
+ try {
117
+ return await app.stackable.getMeta()
118
+ } catch (err) {
119
+ throw new errors.FailedToRetrieveMetaError(service.id, err.message)
120
+ }
121
+ },
122
+
123
+ getMetrics (format) {
124
+ return app.stackable.getMetrics({ format })
125
+ },
126
+
127
+ inject (injectParams) {
128
+ return app.stackable.inject(injectParams)
129
+ }
73
130
  }
74
-
75
- if (status !== 'stopped') {
76
- await app.stop()
77
- }
78
-
79
- dispatcher.interceptor.close()
80
- itc.close()
81
- })
82
-
83
- itc.handle('getStatus', async () => {
84
- return app.getStatus()
85
- })
86
-
87
- itc.handle('getServiceInfo', async () => {
88
- return app.stackable.getInfo()
89
- })
90
-
91
- itc.handle('getServiceConfig', async () => {
92
- const current = await app.stackable.getConfig()
93
- // Remove all undefined keys from the config
94
- return JSON.parse(JSON.stringify(current))
95
- })
96
-
97
- itc.handle('getServiceOpenAPISchema', async () => {
98
- try {
99
- return app.stackable.getOpenapiSchema()
100
- } catch (err) {
101
- throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
102
- }
103
- })
104
-
105
- itc.handle('getServiceGraphQLSchema', async () => {
106
- try {
107
- return app.stackable.getGraphqlSchema()
108
- } catch (err) {
109
- throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
110
- }
111
- })
112
-
113
- itc.handle('getMetrics', async format => {
114
- return app.stackable.getMetrics({ format })
115
- })
116
-
117
- itc.handle('inject', async injectParams => {
118
- return app.stackable.inject(injectParams)
119
131
  })
120
132
 
121
133
  app.on('changed', () => {
@@ -6,6 +6,7 @@ const { setTimeout: sleep } = require('node:timers/promises')
6
6
  const { parentPort, workerData, threadId } = require('node:worker_threads')
7
7
  const { pathToFileURL } = require('node:url')
8
8
 
9
+ const { Unpromise } = require('@watchable/unpromise')
9
10
  const pino = require('pino')
10
11
  const { fetch, setGlobalDispatcher, Agent } = require('undici')
11
12
  const { wire } = require('undici-thread-interceptor')
@@ -29,7 +30,7 @@ const logger = createLogger()
29
30
  function handleUnhandled (type, err) {
30
31
  logger.error({ err }, `application ${type}`)
31
32
 
32
- Promise.race([app?.stop(), sleep(1000, 'timeout', { ref: false })])
33
+ Unpromise.race([app?.stop(), sleep(1000, 'timeout', { ref: false })])
33
34
  .catch()
34
35
  .finally(() => {
35
36
  process.exit(1)
@@ -38,7 +39,7 @@ function handleUnhandled (type, err) {
38
39
 
39
40
  function createLogger () {
40
41
  const destination = new MessagePortWritable({ port: workerData.loggingPort })
41
- const loggerInstance = pino({ level: 'trace' }, destination)
42
+ const loggerInstance = pino({ level: 'trace', name: workerData.serviceConfig.id }, destination)
42
43
 
43
44
  Reflect.defineProperty(process, 'stdout', { value: createPinoWritable(loggerInstance, 'info') })
44
45
  Reflect.defineProperty(process, 'stderr', { value: createPinoWritable(loggerInstance, 'error') })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.6",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -34,19 +34,20 @@
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/composer": "2.0.0-alpha.4",
38
- "@platformatic/db": "2.0.0-alpha.4",
39
- "@platformatic/service": "2.0.0-alpha.4",
40
- "@platformatic/sql-graphql": "2.0.0-alpha.4",
41
- "@platformatic/sql-mapper": "2.0.0-alpha.4"
37
+ "@platformatic/composer": "2.0.0-alpha.6",
38
+ "@platformatic/service": "2.0.0-alpha.6",
39
+ "@platformatic/sql-graphql": "2.0.0-alpha.6",
40
+ "@platformatic/sql-mapper": "2.0.0-alpha.6",
41
+ "@platformatic/db": "2.0.0-alpha.6"
42
42
  },
43
43
  "dependencies": {
44
44
  "@fastify/error": "^3.4.1",
45
45
  "@fastify/websocket": "^10.0.0",
46
46
  "@hapi/topo": "^6.0.2",
47
+ "@watchable/unpromise": "^1.0.2",
47
48
  "boring-name-generator": "^1.0.3",
48
49
  "change-case-all": "^2.1.0",
49
- "close-with-grace": "^1.3.0",
50
+ "close-with-grace": "^2.0.0",
50
51
  "commist": "^3.2.0",
51
52
  "debounce": "^2.0.0",
52
53
  "desm": "^1.3.1",
@@ -64,15 +65,15 @@
64
65
  "semgrator": "^0.3.0",
65
66
  "tail-file-stream": "^0.2.0",
66
67
  "undici": "^6.9.0",
67
- "undici-thread-interceptor": "^0.5.0",
68
+ "undici-thread-interceptor": "^0.5.1",
68
69
  "ws": "^8.16.0",
69
- "@platformatic/config": "2.0.0-alpha.4",
70
- "@platformatic/basic": "2.0.0-alpha.4",
71
- "@platformatic/generators": "2.0.0-alpha.4",
72
- "@platformatic/itc": "2.0.0-alpha.4",
73
- "@platformatic/telemetry": "2.0.0-alpha.4",
74
- "@platformatic/ts-compiler": "2.0.0-alpha.4",
75
- "@platformatic/utils": "2.0.0-alpha.4"
70
+ "@platformatic/basic": "2.0.0-alpha.6",
71
+ "@platformatic/generators": "2.0.0-alpha.6",
72
+ "@platformatic/config": "2.0.0-alpha.6",
73
+ "@platformatic/utils": "2.0.0-alpha.6",
74
+ "@platformatic/itc": "2.0.0-alpha.6",
75
+ "@platformatic/ts-compiler": "2.0.0-alpha.6",
76
+ "@platformatic/telemetry": "2.0.0-alpha.6"
76
77
  },
77
78
  "scripts": {
78
79
  "test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
package/runtime.mjs CHANGED
@@ -33,7 +33,7 @@ export async function run (argv) {
33
33
  })
34
34
 
35
35
  if (args.version) {
36
- console.log('v' + JSON.parse(await readFile(join(import.meta.url, 'package.json'))).version)
36
+ console.log('v' + JSON.parse(await readFile(join(import.meta.url, 'package.json'), 'utf-8')).version)
37
37
  process.exit(0)
38
38
  }
39
39
 
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.4.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.6.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {
@@ -766,9 +766,19 @@
766
766
  "type": "array",
767
767
  "items": {
768
768
  "type": "object",
769
- "required": [
770
- "id",
771
- "path"
769
+ "anyOf": [
770
+ {
771
+ "required": [
772
+ "id",
773
+ "path"
774
+ ]
775
+ },
776
+ {
777
+ "required": [
778
+ "id",
779
+ "url"
780
+ ]
781
+ }
772
782
  ],
773
783
  "properties": {
774
784
  "id": {
@@ -781,6 +791,9 @@
781
791
  "config": {
782
792
  "type": "string"
783
793
  },
794
+ "url": {
795
+ "type": "string"
796
+ },
784
797
  "useHttp": {
785
798
  "type": "boolean"
786
799
  }