@platformatic/runtime 2.7.0 → 2.7.1-alpha.2
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/management-api.js +9 -0
- package/lib/runtime.js +42 -1
- package/lib/schema.js +26 -0
- package/lib/shared-http-cache.js +45 -0
- package/lib/worker/http-cache.js +83 -0
- package/lib/worker/main.js +40 -5
- package/package.json +16 -15
- 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 HttpsSchemasPlatformaticDevPlatformaticRuntime271Alpha2Json = {
|
|
9
9
|
[k: string]: unknown;
|
|
10
10
|
} & {
|
|
11
11
|
$schema?: string;
|
|
@@ -110,6 +110,17 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime270Json = {
|
|
|
110
110
|
};
|
|
111
111
|
[k: string]: unknown;
|
|
112
112
|
};
|
|
113
|
+
httpCache?:
|
|
114
|
+
| boolean
|
|
115
|
+
| {
|
|
116
|
+
store?: string;
|
|
117
|
+
/**
|
|
118
|
+
* @minItems 1
|
|
119
|
+
*/
|
|
120
|
+
methods?: [string, ...string[]];
|
|
121
|
+
cacheTagsHeader?: string;
|
|
122
|
+
[k: string]: unknown;
|
|
123
|
+
};
|
|
113
124
|
watch?: boolean | string;
|
|
114
125
|
managementApi?:
|
|
115
126
|
| boolean
|
package/lib/management-api.js
CHANGED
|
@@ -193,6 +193,15 @@ async function managementApiPlugin (app, opts) {
|
|
|
193
193
|
const logFileStream = await runtime.getLogFileStream(logId, runtimePID)
|
|
194
194
|
return logFileStream
|
|
195
195
|
})
|
|
196
|
+
|
|
197
|
+
app.get('/http-cache/requests', async () => {
|
|
198
|
+
return runtime.getCachedHttpRequests()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
app.post('/http-cache/invalidate', async (req) => {
|
|
202
|
+
const { origin, routes, tags } = req.body
|
|
203
|
+
await runtime.invalidateHttpCache({ origin, routes, tags })
|
|
204
|
+
})
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
async function startManagementApi (runtime, configManager) {
|
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 { kId, kITC, kConfig } = require('./worker/symbols')
|
|
@@ -53,6 +54,7 @@ class Runtime extends EventEmitter {
|
|
|
53
54
|
#bootstrapAttempts
|
|
54
55
|
#inspectors
|
|
55
56
|
#inspectorServer
|
|
57
|
+
#sharedHttpCache
|
|
56
58
|
|
|
57
59
|
constructor (configManager, runtimeLogsDir, env) {
|
|
58
60
|
super()
|
|
@@ -72,6 +74,7 @@ class Runtime extends EventEmitter {
|
|
|
72
74
|
this.#restartPromises = new Map()
|
|
73
75
|
this.#bootstrapAttempts = new Map()
|
|
74
76
|
this.#inspectors = []
|
|
77
|
+
this.#sharedHttpCache = null
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
async init () {
|
|
@@ -116,6 +119,11 @@ class Runtime extends EventEmitter {
|
|
|
116
119
|
throw e
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
this.#sharedHttpCache = createSharedStore(
|
|
123
|
+
this.#configManager.dirname,
|
|
124
|
+
config.httpCache
|
|
125
|
+
)
|
|
126
|
+
|
|
119
127
|
this.#updateStatus('init')
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -235,6 +243,10 @@ class Runtime extends EventEmitter {
|
|
|
235
243
|
this.#loggerDestination = null
|
|
236
244
|
}
|
|
237
245
|
|
|
246
|
+
if (this.#sharedHttpCache?.close) {
|
|
247
|
+
await this.#sharedHttpCache.close()
|
|
248
|
+
}
|
|
249
|
+
|
|
238
250
|
this.#updateStatus('closed')
|
|
239
251
|
}
|
|
240
252
|
|
|
@@ -755,6 +767,24 @@ class Runtime extends EventEmitter {
|
|
|
755
767
|
return createReadStream(filePath)
|
|
756
768
|
}
|
|
757
769
|
|
|
770
|
+
async getCachedHttpRequests () {
|
|
771
|
+
return this.#sharedHttpCache.getRoutes()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async invalidateHttpCache (options = {}) {
|
|
775
|
+
const { origin, routes, tags } = options
|
|
776
|
+
|
|
777
|
+
if (!this.#sharedHttpCache) return
|
|
778
|
+
|
|
779
|
+
if (routes && routes.length > 0) {
|
|
780
|
+
await this.#sharedHttpCache.deleteRoutes(routes)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (tags && tags.length > 0) {
|
|
784
|
+
await this.#sharedHttpCache.deleteByCacheTags(origin, tags)
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
758
788
|
#updateStatus (status) {
|
|
759
789
|
this.#status = status
|
|
760
790
|
this.emit(status)
|
|
@@ -865,7 +895,18 @@ class Runtime extends EventEmitter {
|
|
|
865
895
|
port: service,
|
|
866
896
|
handlers: {
|
|
867
897
|
getServiceMeta: this.getServiceMeta.bind(this),
|
|
868
|
-
getServices: this.getServices.bind(this)
|
|
898
|
+
getServices: this.getServices.bind(this),
|
|
899
|
+
isHttpCacheFull: () => this.#sharedHttpCache.isFull(),
|
|
900
|
+
getHttpCacheValue: opts => this.#sharedHttpCache.getValue(opts.request),
|
|
901
|
+
setHttpCacheValue: opts => this.#sharedHttpCache.setValue(
|
|
902
|
+
opts.request,
|
|
903
|
+
opts.response,
|
|
904
|
+
opts.payload
|
|
905
|
+
),
|
|
906
|
+
deleteHttpCacheValue: opts => this.#sharedHttpCache.deleteByOrigin(
|
|
907
|
+
opts.origin
|
|
908
|
+
),
|
|
909
|
+
invalidateHttpCache: opts => this.invalidateHttpCache(opts),
|
|
869
910
|
}
|
|
870
911
|
})
|
|
871
912
|
service[kITC].listen()
|
package/lib/schema.js
CHANGED
|
@@ -145,6 +145,32 @@ const platformaticRuntimeSchema = {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
},
|
|
148
|
+
httpCache: {
|
|
149
|
+
oneOf: [
|
|
150
|
+
{
|
|
151
|
+
type: 'boolean'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
store: {
|
|
157
|
+
type: 'string'
|
|
158
|
+
},
|
|
159
|
+
methods: {
|
|
160
|
+
type: 'array',
|
|
161
|
+
items: {
|
|
162
|
+
type: 'string'
|
|
163
|
+
},
|
|
164
|
+
default: ['GET', 'HEAD'],
|
|
165
|
+
minItems: 1
|
|
166
|
+
},
|
|
167
|
+
cacheTagsHeader: {
|
|
168
|
+
type: 'string'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
},
|
|
148
174
|
watch: {
|
|
149
175
|
anyOf: [
|
|
150
176
|
{
|
|
@@ -0,0 +1,45 @@
|
|
|
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 readStream = await this.createReadStream(req)
|
|
16
|
+
if (!readStream) return null
|
|
17
|
+
|
|
18
|
+
let payload = ''
|
|
19
|
+
for await (const chunk of readStream) {
|
|
20
|
+
payload += chunk
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = this.#sanitizeResponse(readStream.value)
|
|
24
|
+
return { response, payload }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setValue (req, opts, data) {
|
|
28
|
+
const writeStream = this.createWriteStream(req, opts)
|
|
29
|
+
writeStream.write(data)
|
|
30
|
+
writeStream.end()
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#sanitizeResponse (response) {
|
|
35
|
+
return {
|
|
36
|
+
...response,
|
|
37
|
+
rawHeaders: response.rawHeaders.map(header => header.toString())
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return new SharedCacheStore(storeConfig)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { createSharedStore }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Readable, Writable } = require('node:stream')
|
|
4
|
+
const { kITC } = require('./symbols')
|
|
5
|
+
|
|
6
|
+
class RemoteCacheStore {
|
|
7
|
+
get isFull () {
|
|
8
|
+
// TODO: make an itc call to the shared cache when interceptor supports
|
|
9
|
+
// an async isFull method
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async createReadStream (request) {
|
|
14
|
+
const itc = globalThis[kITC]
|
|
15
|
+
if (!itc) return
|
|
16
|
+
|
|
17
|
+
const cachedValue = await itc.send('getHttpCacheValue', {
|
|
18
|
+
request: this.#sanitizeRequest(request)
|
|
19
|
+
})
|
|
20
|
+
if (!cachedValue) return
|
|
21
|
+
|
|
22
|
+
const readable = new Readable({
|
|
23
|
+
read () {}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
Object.defineProperty(readable, 'value', {
|
|
27
|
+
get () { return cachedValue.response }
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
readable.push(cachedValue.payload)
|
|
31
|
+
readable.push(null)
|
|
32
|
+
|
|
33
|
+
return readable
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
createWriteStream (request, response) {
|
|
37
|
+
const itc = globalThis[kITC]
|
|
38
|
+
if (!itc) throw new Error('Cannot write to cache without an ITC instance')
|
|
39
|
+
|
|
40
|
+
let payload = ''
|
|
41
|
+
|
|
42
|
+
request = this.#sanitizeRequest(request)
|
|
43
|
+
response = this.#sanitizeResponse(response)
|
|
44
|
+
|
|
45
|
+
return new Writable({
|
|
46
|
+
write (chunk, encoding, callback) {
|
|
47
|
+
payload += chunk
|
|
48
|
+
callback()
|
|
49
|
+
},
|
|
50
|
+
final (callback) {
|
|
51
|
+
itc.send('setHttpCacheValue', { request, response, payload })
|
|
52
|
+
.then(() => callback())
|
|
53
|
+
.catch((err) => callback(err))
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
deleteByOrigin (origin) {
|
|
59
|
+
const itc = globalThis[kITC]
|
|
60
|
+
if (!itc) throw new Error('Cannot delete from cache without an ITC instance')
|
|
61
|
+
|
|
62
|
+
itc.send('deleteHttpCacheValue', { origin })
|
|
63
|
+
// TODO: return a Promise
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#sanitizeRequest (request) {
|
|
67
|
+
return {
|
|
68
|
+
origin: request.origin,
|
|
69
|
+
method: request.method,
|
|
70
|
+
path: request.path,
|
|
71
|
+
headers: request.headers
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#sanitizeResponse (response) {
|
|
76
|
+
return {
|
|
77
|
+
...response,
|
|
78
|
+
rawHeaders: response.rawHeaders.map(header => header.toString())
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = RemoteCacheStore
|
package/lib/worker/main.js
CHANGED
|
@@ -9,9 +9,11 @@ const diagnosticChannel = require('node:diagnostics_channel')
|
|
|
9
9
|
const { ServerResponse } = require('node:http')
|
|
10
10
|
|
|
11
11
|
const pino = require('pino')
|
|
12
|
-
const { fetch, setGlobalDispatcher, Agent } = require('undici')
|
|
12
|
+
const { fetch, setGlobalDispatcher, getGlobalDispatcher, Agent } = require('undici')
|
|
13
13
|
const { wire } = require('undici-thread-interceptor')
|
|
14
|
+
const undici = require('undici')
|
|
14
15
|
|
|
16
|
+
const RemoteCacheStore = require('./http-cache')
|
|
15
17
|
const { PlatformaticApp } = require('./app')
|
|
16
18
|
const { setupITC } = require('./itc')
|
|
17
19
|
const loadInterceptors = require('./interceptors')
|
|
@@ -80,10 +82,34 @@ async function main () {
|
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const dispatcherOpts = { ...config.undici }
|
|
86
|
+
|
|
87
|
+
if (Object.keys(interceptors).length > 0) {
|
|
88
|
+
const clientInterceptors = []
|
|
89
|
+
const poolInterceptors = []
|
|
90
|
+
|
|
91
|
+
if (interceptors.Agent) {
|
|
92
|
+
clientInterceptors.push(...interceptors.Agent)
|
|
93
|
+
poolInterceptors.push(...interceptors.Agent)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (interceptors.Pool) {
|
|
97
|
+
poolInterceptors.push(...interceptors.Pool)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (interceptors.Client) {
|
|
101
|
+
clientInterceptors.push(...interceptors.Client)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dispatcherOpts.factory = (origin, opts) => {
|
|
105
|
+
return opts && opts.connections === 1
|
|
106
|
+
? new undici.Client(origin, opts).compose(clientInterceptors)
|
|
107
|
+
: new undici.Pool(origin, opts).compose(poolInterceptors)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const globalDispatcher = new Agent(dispatcherOpts)
|
|
112
|
+
.compose(composedInterceptors)
|
|
87
113
|
|
|
88
114
|
setGlobalDispatcher(globalDispatcher)
|
|
89
115
|
|
|
@@ -92,6 +118,15 @@ async function main () {
|
|
|
92
118
|
// TODO: make this configurable
|
|
93
119
|
const threadDispatcher = wire({ port: parentPort, useNetwork: service.useHttp, timeout: 5 * 60 * 1000 })
|
|
94
120
|
|
|
121
|
+
if (config.httpCache) {
|
|
122
|
+
setGlobalDispatcher(
|
|
123
|
+
getGlobalDispatcher().compose(undici.interceptors.cache({
|
|
124
|
+
store: new RemoteCacheStore(),
|
|
125
|
+
methods: config.httpCache.methods ?? ['GET', 'HEAD']
|
|
126
|
+
}))
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
95
130
|
// If the service is an entrypoint and runtime server config is defined, use it.
|
|
96
131
|
let serverConfig = null
|
|
97
132
|
if (config.server && service.entrypoint) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1-alpha.2",
|
|
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/
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/service": "2.7.
|
|
41
|
-
"@platformatic/node": "2.7.
|
|
42
|
-
"@platformatic/sql-graphql": "2.7.
|
|
43
|
-
"@platformatic/sql-mapper": "2.7.
|
|
38
|
+
"@platformatic/db": "2.7.1-alpha.2",
|
|
39
|
+
"@platformatic/composer": "2.7.1-alpha.2",
|
|
40
|
+
"@platformatic/service": "2.7.1-alpha.2",
|
|
41
|
+
"@platformatic/node": "2.7.1-alpha.2",
|
|
42
|
+
"@platformatic/sql-graphql": "2.7.1-alpha.2",
|
|
43
|
+
"@platformatic/sql-mapper": "2.7.1-alpha.2"
|
|
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.3.0",
|
|
50
51
|
"@watchable/unpromise": "^1.0.2",
|
|
51
52
|
"boring-name-generator": "^1.0.3",
|
|
52
53
|
"change-case-all": "^2.1.0",
|
|
@@ -68,16 +69,16 @@
|
|
|
68
69
|
"semgrator": "^0.3.0",
|
|
69
70
|
"tail-file-stream": "^0.2.0",
|
|
70
71
|
"thread-cpu-usage": "^0.2.0",
|
|
71
|
-
"undici": "
|
|
72
|
+
"undici": "7.0.0-alpha.3",
|
|
72
73
|
"undici-thread-interceptor": "^0.7.0",
|
|
73
74
|
"ws": "^8.16.0",
|
|
74
|
-
"@platformatic/basic": "2.7.
|
|
75
|
-
"@platformatic/config": "2.7.
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/ts-compiler": "2.7.
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/
|
|
80
|
-
"@platformatic/utils": "2.7.
|
|
75
|
+
"@platformatic/basic": "2.7.1-alpha.2",
|
|
76
|
+
"@platformatic/config": "2.7.1-alpha.2",
|
|
77
|
+
"@platformatic/itc": "2.7.1-alpha.2",
|
|
78
|
+
"@platformatic/ts-compiler": "2.7.1-alpha.2",
|
|
79
|
+
"@platformatic/telemetry": "2.7.1-alpha.2",
|
|
80
|
+
"@platformatic/generators": "2.7.1-alpha.2",
|
|
81
|
+
"@platformatic/utils": "2.7.1-alpha.2"
|
|
81
82
|
},
|
|
82
83
|
"scripts": {
|
|
83
84
|
"test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.7.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.7.1-alpha.2.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -382,6 +382,35 @@
|
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
384
|
},
|
|
385
|
+
"httpCache": {
|
|
386
|
+
"oneOf": [
|
|
387
|
+
{
|
|
388
|
+
"type": "boolean"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"type": "object",
|
|
392
|
+
"properties": {
|
|
393
|
+
"store": {
|
|
394
|
+
"type": "string"
|
|
395
|
+
},
|
|
396
|
+
"methods": {
|
|
397
|
+
"type": "array",
|
|
398
|
+
"items": {
|
|
399
|
+
"type": "string"
|
|
400
|
+
},
|
|
401
|
+
"default": [
|
|
402
|
+
"GET",
|
|
403
|
+
"HEAD"
|
|
404
|
+
],
|
|
405
|
+
"minItems": 1
|
|
406
|
+
},
|
|
407
|
+
"cacheTagsHeader": {
|
|
408
|
+
"type": "string"
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
},
|
|
385
414
|
"watch": {
|
|
386
415
|
"anyOf": [
|
|
387
416
|
{
|