@platformatic/runtime 2.19.0-alpha.7 → 2.19.0-alpha.9
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 +12 -1
- package/lib/runtime.js +48 -23
- package/lib/schema.js +26 -0
- package/lib/shared-http-cache.js +39 -0
- package/lib/worker/http-cache.js +73 -0
- package/lib/worker/main.js +41 -6
- package/package.json +18 -16
- package/schema.json +30 -1
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
|
|
8
|
+
export type HttpsSchemasPlatformaticDevPlatformaticRuntime2190Alpha9Json = {
|
|
9
9
|
[k: string]: unknown;
|
|
10
10
|
} & {
|
|
11
11
|
$schema?: string;
|
|
@@ -133,6 +133,17 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2190Alpha7Json = {
|
|
|
133
133
|
};
|
|
134
134
|
[k: string]: unknown;
|
|
135
135
|
};
|
|
136
|
+
httpCache?:
|
|
137
|
+
| boolean
|
|
138
|
+
| {
|
|
139
|
+
store?: string;
|
|
140
|
+
/**
|
|
141
|
+
* @minItems 1
|
|
142
|
+
*/
|
|
143
|
+
methods?: [string, ...string[]];
|
|
144
|
+
cacheTagsHeader?: string;
|
|
145
|
+
[k: string]: unknown;
|
|
146
|
+
};
|
|
136
147
|
watch?: boolean | string;
|
|
137
148
|
managementApi?:
|
|
138
149
|
| boolean
|
package/lib/runtime.js
CHANGED
|
@@ -16,6 +16,7 @@ const errors = require('./errors')
|
|
|
16
16
|
const { createLogger } = require('./logger')
|
|
17
17
|
const { startManagementApi } = require('./management-api')
|
|
18
18
|
const { startPrometheusServer } = require('./prom-server')
|
|
19
|
+
const { createSharedStore } = require('./shared-http-cache')
|
|
19
20
|
const { getRuntimeTmpDir } = require('./utils')
|
|
20
21
|
const { sendViaITC, waitEventFromITC } = require('./worker/itc')
|
|
21
22
|
const { RoundRobinMap } = require('./worker/round-robin-map.js')
|
|
@@ -66,6 +67,7 @@ class Runtime extends EventEmitter {
|
|
|
66
67
|
#inspectorServer
|
|
67
68
|
#workers
|
|
68
69
|
#restartingWorkers
|
|
70
|
+
#sharedHttpCache
|
|
69
71
|
|
|
70
72
|
constructor (configManager, runtimeLogsDir, env) {
|
|
71
73
|
super()
|
|
@@ -85,6 +87,7 @@ class Runtime extends EventEmitter {
|
|
|
85
87
|
})
|
|
86
88
|
this.#status = undefined
|
|
87
89
|
this.#restartingWorkers = new Map()
|
|
90
|
+
this.#sharedHttpCache = null
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
async init () {
|
|
@@ -134,26 +137,16 @@ class Runtime extends EventEmitter {
|
|
|
134
137
|
|
|
135
138
|
// Recompute the list of services after sorting
|
|
136
139
|
this.#servicesIds = config.services.map(service => service.id)
|
|
137
|
-
|
|
138
|
-
// When autoloading is disabled, add a warning if a service is defined before its dependencies
|
|
139
|
-
if (!autoloadEnabled) {
|
|
140
|
-
for (let i = 0; i < config.services.length; i++) {
|
|
141
|
-
const current = config.services[i]
|
|
142
|
-
|
|
143
|
-
for (const dep of current.dependencies ?? []) {
|
|
144
|
-
if (config.services.findIndex(s => s.id === dep.id) > i) {
|
|
145
|
-
this.logger.warn(
|
|
146
|
-
`Service "${current.id}" depends on service "${dep.id}", but it is defined and it will be started before it. Please check your configuration file.`
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
140
|
} catch (e) {
|
|
153
141
|
await this.close()
|
|
154
142
|
throw e
|
|
155
143
|
}
|
|
156
144
|
|
|
145
|
+
this.#sharedHttpCache = createSharedStore(
|
|
146
|
+
this.#configManager.dirname,
|
|
147
|
+
config.httpCache
|
|
148
|
+
)
|
|
149
|
+
|
|
157
150
|
this.#updateStatus('init')
|
|
158
151
|
}
|
|
159
152
|
|
|
@@ -282,6 +275,10 @@ class Runtime extends EventEmitter {
|
|
|
282
275
|
this.#loggerDestination = null
|
|
283
276
|
}
|
|
284
277
|
|
|
278
|
+
if (this.#sharedHttpCache?.close) {
|
|
279
|
+
await this.#sharedHttpCache.close()
|
|
280
|
+
}
|
|
281
|
+
|
|
285
282
|
this.#updateStatus('closed')
|
|
286
283
|
}
|
|
287
284
|
|
|
@@ -744,6 +741,23 @@ class Runtime extends EventEmitter {
|
|
|
744
741
|
return createReadStream(filePath)
|
|
745
742
|
}
|
|
746
743
|
|
|
744
|
+
async invalidateHttpCache (options = {}) {
|
|
745
|
+
const { keys, tags } = options
|
|
746
|
+
|
|
747
|
+
if (!this.#sharedHttpCache) return
|
|
748
|
+
|
|
749
|
+
const promises = []
|
|
750
|
+
if (keys && keys.length > 0) {
|
|
751
|
+
promises.push(this.#sharedHttpCache.deleteKeys(keys))
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (tags && tags.length > 0) {
|
|
755
|
+
promises.push(this.#sharedHttpCache.deleteTags(tags))
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
await Promise.all(promises)
|
|
759
|
+
}
|
|
760
|
+
|
|
747
761
|
async sendCommandToService (id, name, message) {
|
|
748
762
|
const service = await this.#getServiceById(id)
|
|
749
763
|
|
|
@@ -781,7 +795,7 @@ class Runtime extends EventEmitter {
|
|
|
781
795
|
}
|
|
782
796
|
|
|
783
797
|
async #setupWorker (config, serviceConfig, workersCount, serviceId, index) {
|
|
784
|
-
const { restartOnError } = config
|
|
798
|
+
const { autoload, restartOnError } = config
|
|
785
799
|
const workerId = `${serviceId}:${index}`
|
|
786
800
|
|
|
787
801
|
const { port1: loggerDestination, port2: loggingPort } = new MessageChannel()
|
|
@@ -900,9 +914,18 @@ class Runtime extends EventEmitter {
|
|
|
900
914
|
port: worker,
|
|
901
915
|
handlers: {
|
|
902
916
|
getServiceMeta: this.getServiceMeta.bind(this),
|
|
903
|
-
listServices: () =>
|
|
904
|
-
|
|
905
|
-
|
|
917
|
+
listServices: () => this.#servicesIds,
|
|
918
|
+
getServices: this.getServices.bind(this),
|
|
919
|
+
getHttpCacheValue: opts => this.#sharedHttpCache.getValue(opts.request),
|
|
920
|
+
setHttpCacheValue: opts => this.#sharedHttpCache.setValue(
|
|
921
|
+
opts.request,
|
|
922
|
+
opts.response,
|
|
923
|
+
opts.payload
|
|
924
|
+
),
|
|
925
|
+
deleteHttpCacheValue: opts => this.#sharedHttpCache.delete(
|
|
926
|
+
opts.request
|
|
927
|
+
),
|
|
928
|
+
invalidateHttpCache: opts => this.invalidateHttpCache(opts),
|
|
906
929
|
}
|
|
907
930
|
})
|
|
908
931
|
worker[kITC].listen()
|
|
@@ -946,10 +969,12 @@ class Runtime extends EventEmitter {
|
|
|
946
969
|
// Store dependencies
|
|
947
970
|
const [{ dependencies }] = await waitEventFromITC(worker, 'init')
|
|
948
971
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
972
|
+
if (autoload) {
|
|
973
|
+
serviceConfig.dependencies = dependencies
|
|
974
|
+
for (const { envVar, url } of dependencies) {
|
|
975
|
+
if (envVar) {
|
|
976
|
+
serviceConfig.localServiceEnvVars.set(envVar, url)
|
|
977
|
+
}
|
|
953
978
|
}
|
|
954
979
|
}
|
|
955
980
|
|
package/lib/schema.js
CHANGED
|
@@ -194,6 +194,32 @@ const platformaticRuntimeSchema = {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
},
|
|
197
|
+
httpCache: {
|
|
198
|
+
oneOf: [
|
|
199
|
+
{
|
|
200
|
+
type: 'boolean'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
store: {
|
|
206
|
+
type: 'string'
|
|
207
|
+
},
|
|
208
|
+
methods: {
|
|
209
|
+
type: 'array',
|
|
210
|
+
items: {
|
|
211
|
+
type: 'string'
|
|
212
|
+
},
|
|
213
|
+
default: ['GET', 'HEAD'],
|
|
214
|
+
minItems: 1
|
|
215
|
+
},
|
|
216
|
+
cacheTagsHeader: {
|
|
217
|
+
type: 'string'
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
197
223
|
watch: {
|
|
198
224
|
anyOf: [
|
|
199
225
|
{
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { join } = require('node:path')
|
|
4
|
+
const { createRequire } = require('node:module')
|
|
5
|
+
const MemoryCacheStore = require('@platformatic/undici-cache-memory')
|
|
6
|
+
|
|
7
|
+
function createSharedStore (projectDir, httpCacheConfig = {}) {
|
|
8
|
+
const runtimeRequire = createRequire(join(projectDir, 'file'))
|
|
9
|
+
|
|
10
|
+
const { store, ...storeConfig } = httpCacheConfig
|
|
11
|
+
const CacheStore = store ? runtimeRequire(store) : MemoryCacheStore
|
|
12
|
+
|
|
13
|
+
class SharedCacheStore extends CacheStore {
|
|
14
|
+
async getValue (req) {
|
|
15
|
+
const cachedValue = await this.get(req)
|
|
16
|
+
if (!cachedValue) return null
|
|
17
|
+
|
|
18
|
+
const { body, ...response } = cachedValue
|
|
19
|
+
|
|
20
|
+
let payload = ''
|
|
21
|
+
for await (const chunk of body) {
|
|
22
|
+
payload += chunk
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { response, payload }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setValue (req, opts, data) {
|
|
29
|
+
const writeStream = this.createWriteStream(req, opts)
|
|
30
|
+
writeStream.write(data)
|
|
31
|
+
writeStream.end()
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new SharedCacheStore(storeConfig)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { createSharedStore }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Readable, Writable } = require('node:stream')
|
|
4
|
+
const { kITC } = require('./symbols')
|
|
5
|
+
|
|
6
|
+
class RemoteCacheStore {
|
|
7
|
+
async get (request) {
|
|
8
|
+
const itc = globalThis[kITC]
|
|
9
|
+
if (!itc) return
|
|
10
|
+
|
|
11
|
+
const cachedValue = await itc.send('getHttpCacheValue', {
|
|
12
|
+
request: this.#sanitizeRequest(request)
|
|
13
|
+
})
|
|
14
|
+
if (!cachedValue) return
|
|
15
|
+
|
|
16
|
+
const readable = new Readable({
|
|
17
|
+
read () {}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
Object.defineProperty(readable, 'value', {
|
|
21
|
+
get () { return cachedValue.response }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
readable.push(cachedValue.payload)
|
|
25
|
+
readable.push(null)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...cachedValue.response,
|
|
29
|
+
body: readable
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createWriteStream (request, response) {
|
|
34
|
+
const itc = globalThis[kITC]
|
|
35
|
+
if (!itc) throw new Error('Cannot write to cache without an ITC instance')
|
|
36
|
+
|
|
37
|
+
let payload = ''
|
|
38
|
+
|
|
39
|
+
request = this.#sanitizeRequest(request)
|
|
40
|
+
|
|
41
|
+
return new Writable({
|
|
42
|
+
write (chunk, encoding, callback) {
|
|
43
|
+
payload += chunk
|
|
44
|
+
callback()
|
|
45
|
+
},
|
|
46
|
+
final (callback) {
|
|
47
|
+
itc.send('setHttpCacheValue', { request, response, payload })
|
|
48
|
+
.then(() => callback())
|
|
49
|
+
.catch((err) => callback(err))
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
delete (request) {
|
|
55
|
+
const itc = globalThis[kITC]
|
|
56
|
+
if (!itc) throw new Error('Cannot delete from cache without an ITC instance')
|
|
57
|
+
|
|
58
|
+
request = this.#sanitizeRequest(request)
|
|
59
|
+
itc.send('deleteHttpCacheValue', { request })
|
|
60
|
+
// TODO: return a Promise
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#sanitizeRequest (request) {
|
|
64
|
+
return {
|
|
65
|
+
origin: request.origin,
|
|
66
|
+
method: request.method,
|
|
67
|
+
path: request.path,
|
|
68
|
+
headers: request.headers
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = RemoteCacheStore
|
package/lib/worker/main.js
CHANGED
|
@@ -10,9 +10,11 @@ const diagnosticChannel = require('node:diagnostics_channel')
|
|
|
10
10
|
const { ServerResponse } = require('node:http')
|
|
11
11
|
|
|
12
12
|
const pino = require('pino')
|
|
13
|
-
const { fetch, setGlobalDispatcher, Agent } = require('undici')
|
|
13
|
+
const { fetch, setGlobalDispatcher, getGlobalDispatcher, Agent } = require('undici')
|
|
14
14
|
const { wire } = require('undici-thread-interceptor')
|
|
15
|
+
const undici = require('undici')
|
|
15
16
|
|
|
17
|
+
const RemoteCacheStore = require('./http-cache')
|
|
16
18
|
const { PlatformaticApp } = require('./app')
|
|
17
19
|
const { setupITC } = require('./itc')
|
|
18
20
|
const loadInterceptors = require('./interceptors')
|
|
@@ -92,10 +94,34 @@ async function main () {
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
const dispatcherOpts = { ...config.undici }
|
|
98
|
+
|
|
99
|
+
if (Object.keys(interceptors).length > 0) {
|
|
100
|
+
const clientInterceptors = []
|
|
101
|
+
const poolInterceptors = []
|
|
102
|
+
|
|
103
|
+
if (interceptors.Agent) {
|
|
104
|
+
clientInterceptors.push(...interceptors.Agent)
|
|
105
|
+
poolInterceptors.push(...interceptors.Agent)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (interceptors.Pool) {
|
|
109
|
+
poolInterceptors.push(...interceptors.Pool)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (interceptors.Client) {
|
|
113
|
+
clientInterceptors.push(...interceptors.Client)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
dispatcherOpts.factory = (origin, opts) => {
|
|
117
|
+
return opts && opts.connections === 1
|
|
118
|
+
? new undici.Client(origin, opts).compose(clientInterceptors)
|
|
119
|
+
: new undici.Pool(origin, opts).compose(poolInterceptors)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const globalDispatcher = new Agent(dispatcherOpts)
|
|
124
|
+
.compose(composedInterceptors)
|
|
99
125
|
|
|
100
126
|
setGlobalDispatcher(globalDispatcher)
|
|
101
127
|
|
|
@@ -109,6 +135,15 @@ async function main () {
|
|
|
109
135
|
...hooks
|
|
110
136
|
})
|
|
111
137
|
|
|
138
|
+
if (config.httpCache) {
|
|
139
|
+
setGlobalDispatcher(
|
|
140
|
+
getGlobalDispatcher().compose(undici.interceptors.cache({
|
|
141
|
+
store: new RemoteCacheStore(),
|
|
142
|
+
methods: config.httpCache.methods ?? ['GET', 'HEAD']
|
|
143
|
+
}))
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
112
147
|
// If the service is an entrypoint and runtime server config is defined, use it.
|
|
113
148
|
let serverConfig = null
|
|
114
149
|
if (config.server && service.entrypoint) {
|
|
@@ -165,7 +200,7 @@ async function main () {
|
|
|
165
200
|
globalThis[kITC] = itc
|
|
166
201
|
|
|
167
202
|
// Get the dependencies
|
|
168
|
-
const dependencies = await app.getBootstrapDependencies()
|
|
203
|
+
const dependencies = config.autoload ? await app.getBootstrapDependencies() : []
|
|
169
204
|
itc.notify('init', { dependencies })
|
|
170
205
|
}
|
|
171
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "2.19.0-alpha.
|
|
3
|
+
"version": "2.19.0-alpha.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,18 +35,19 @@
|
|
|
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": "2.19.0-alpha.
|
|
39
|
-
"@platformatic/db": "2.19.0-alpha.
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/sql-graphql": "2.19.0-alpha.
|
|
43
|
-
"@platformatic/
|
|
38
|
+
"@platformatic/composer": "2.19.0-alpha.9",
|
|
39
|
+
"@platformatic/db": "2.19.0-alpha.9",
|
|
40
|
+
"@platformatic/service": "2.19.0-alpha.9",
|
|
41
|
+
"@platformatic/sql-mapper": "2.19.0-alpha.9",
|
|
42
|
+
"@platformatic/sql-graphql": "2.19.0-alpha.9",
|
|
43
|
+
"@platformatic/node": "2.19.0-alpha.9"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@fastify/error": "^4.0.0",
|
|
47
47
|
"@fastify/websocket": "^11.0.0",
|
|
48
48
|
"@hapi/topo": "^6.0.2",
|
|
49
49
|
"@platformatic/http-metrics": "^0.2.1",
|
|
50
|
+
"@platformatic/undici-cache-memory": "^0.8.1",
|
|
50
51
|
"@watchable/unpromise": "^1.0.2",
|
|
51
52
|
"boring-name-generator": "^1.0.3",
|
|
52
53
|
"change-case-all": "^2.1.0",
|
|
@@ -67,16 +68,17 @@
|
|
|
67
68
|
"prom-client": "^15.1.2",
|
|
68
69
|
"semgrator": "^0.3.0",
|
|
69
70
|
"tail-file-stream": "^0.2.0",
|
|
70
|
-
"
|
|
71
|
-
"undici
|
|
71
|
+
"thread-cpu-usage": "^0.2.0",
|
|
72
|
+
"undici": "^7.0.0",
|
|
73
|
+
"undici-thread-interceptor": "^0.10.0",
|
|
72
74
|
"ws": "^8.16.0",
|
|
73
|
-
"@platformatic/basic": "2.19.0-alpha.
|
|
74
|
-
"@platformatic/
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/
|
|
75
|
+
"@platformatic/basic": "2.19.0-alpha.9",
|
|
76
|
+
"@platformatic/telemetry": "2.19.0-alpha.9",
|
|
77
|
+
"@platformatic/generators": "2.19.0-alpha.9",
|
|
78
|
+
"@platformatic/config": "2.19.0-alpha.9",
|
|
79
|
+
"@platformatic/itc": "2.19.0-alpha.9",
|
|
80
|
+
"@platformatic/ts-compiler": "2.19.0-alpha.9",
|
|
81
|
+
"@platformatic/utils": "2.19.0-alpha.9"
|
|
80
82
|
},
|
|
81
83
|
"scripts": {
|
|
82
84
|
"test": "npm run lint && borp --concurrency=1 --timeout=300000 && tsd",
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.19.0-alpha.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.19.0-alpha.9.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -840,6 +840,35 @@
|
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
842
|
},
|
|
843
|
+
"httpCache": {
|
|
844
|
+
"oneOf": [
|
|
845
|
+
{
|
|
846
|
+
"type": "boolean"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
"type": "object",
|
|
850
|
+
"properties": {
|
|
851
|
+
"store": {
|
|
852
|
+
"type": "string"
|
|
853
|
+
},
|
|
854
|
+
"methods": {
|
|
855
|
+
"type": "array",
|
|
856
|
+
"items": {
|
|
857
|
+
"type": "string"
|
|
858
|
+
},
|
|
859
|
+
"default": [
|
|
860
|
+
"GET",
|
|
861
|
+
"HEAD"
|
|
862
|
+
],
|
|
863
|
+
"minItems": 1
|
|
864
|
+
},
|
|
865
|
+
"cacheTagsHeader": {
|
|
866
|
+
"type": "string"
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
]
|
|
871
|
+
},
|
|
843
872
|
"watch": {
|
|
844
873
|
"anyOf": [
|
|
845
874
|
{
|