@platformatic/runtime 2.5.5-alpha.3 → 2.5.6-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 +14 -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 +27 -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 HttpsSchemasPlatformaticDevPlatformaticRuntime256Alpha2Json = {
|
|
9
9
|
[k: string]: unknown;
|
|
10
10
|
} & {
|
|
11
11
|
$schema?: string;
|
|
@@ -109,6 +109,17 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime255Alpha3Json = {
|
|
|
109
109
|
};
|
|
110
110
|
[k: string]: unknown;
|
|
111
111
|
};
|
|
112
|
+
httpCache?:
|
|
113
|
+
| boolean
|
|
114
|
+
| {
|
|
115
|
+
store?: string;
|
|
116
|
+
/**
|
|
117
|
+
* @minItems 1
|
|
118
|
+
*/
|
|
119
|
+
methods?: [string, ...string[]];
|
|
120
|
+
cacheTagsHeader?: string;
|
|
121
|
+
[k: string]: unknown;
|
|
122
|
+
};
|
|
112
123
|
watch?: boolean | string;
|
|
113
124
|
managementApi?:
|
|
114
125
|
| boolean
|
package/lib/management-api.js
CHANGED
|
@@ -22,6 +22,11 @@ async function managementApiPlugin (app, opts) {
|
|
|
22
22
|
|
|
23
23
|
const runtime = opts.runtime
|
|
24
24
|
|
|
25
|
+
app.get('/status', async () => {
|
|
26
|
+
const status = runtime.getRuntimeStatus()
|
|
27
|
+
return { status }
|
|
28
|
+
})
|
|
29
|
+
|
|
25
30
|
app.get('/metadata', async () => {
|
|
26
31
|
return runtime.getRuntimeMetadata()
|
|
27
32
|
})
|
|
@@ -188,6 +193,15 @@ async function managementApiPlugin (app, opts) {
|
|
|
188
193
|
const logFileStream = await runtime.getLogFileStream(logId, runtimePID)
|
|
189
194
|
return logFileStream
|
|
190
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
|
+
})
|
|
191
205
|
}
|
|
192
206
|
|
|
193
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
|
|
|
@@ -202,6 +210,10 @@ class Runtime extends EventEmitter {
|
|
|
202
210
|
return this.#url
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
getRuntimeStatus () {
|
|
214
|
+
return this.#status
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
async close (fromManagementApi = false, silent = false) {
|
|
206
218
|
this.#updateStatus('closing')
|
|
207
219
|
|
|
@@ -751,6 +763,24 @@ class Runtime extends EventEmitter {
|
|
|
751
763
|
return createReadStream(filePath)
|
|
752
764
|
}
|
|
753
765
|
|
|
766
|
+
async getCachedHttpRequests () {
|
|
767
|
+
return this.#sharedHttpCache.getRoutes()
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async invalidateHttpCache (options = {}) {
|
|
771
|
+
const { origin, routes, tags } = options
|
|
772
|
+
|
|
773
|
+
if (!this.#sharedHttpCache) return
|
|
774
|
+
|
|
775
|
+
if (routes && routes.length > 0) {
|
|
776
|
+
await this.#sharedHttpCache.deleteRoutes(routes)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (tags && tags.length > 0) {
|
|
780
|
+
await this.#sharedHttpCache.deleteByCacheTags(origin, tags)
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
754
784
|
#updateStatus (status) {
|
|
755
785
|
this.#status = status
|
|
756
786
|
this.emit(status)
|
|
@@ -861,7 +891,18 @@ class Runtime extends EventEmitter {
|
|
|
861
891
|
port: service,
|
|
862
892
|
handlers: {
|
|
863
893
|
getServiceMeta: this.getServiceMeta.bind(this),
|
|
864
|
-
getServices: this.getServices.bind(this)
|
|
894
|
+
getServices: this.getServices.bind(this),
|
|
895
|
+
isHttpCacheFull: () => this.#sharedHttpCache.isFull(),
|
|
896
|
+
getHttpCacheValue: opts => this.#sharedHttpCache.getValue(opts.request),
|
|
897
|
+
setHttpCacheValue: opts => this.#sharedHttpCache.setValue(
|
|
898
|
+
opts.request,
|
|
899
|
+
opts.response,
|
|
900
|
+
opts.payload
|
|
901
|
+
),
|
|
902
|
+
deleteHttpCacheValue: opts => this.#sharedHttpCache.deleteByOrigin(
|
|
903
|
+
opts.origin
|
|
904
|
+
),
|
|
905
|
+
invalidateHttpCache: opts => this.invalidateHttpCache(opts),
|
|
865
906
|
}
|
|
866
907
|
})
|
|
867
908
|
service[kITC].listen()
|
package/lib/schema.js
CHANGED
|
@@ -142,6 +142,32 @@ const platformaticRuntimeSchema = {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
+
httpCache: {
|
|
146
|
+
oneOf: [
|
|
147
|
+
{
|
|
148
|
+
type: 'boolean'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
store: {
|
|
154
|
+
type: 'string'
|
|
155
|
+
},
|
|
156
|
+
methods: {
|
|
157
|
+
type: 'array',
|
|
158
|
+
items: {
|
|
159
|
+
type: 'string'
|
|
160
|
+
},
|
|
161
|
+
default: ['GET', 'HEAD'],
|
|
162
|
+
minItems: 1
|
|
163
|
+
},
|
|
164
|
+
cacheTagsHeader: {
|
|
165
|
+
type: 'string'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
145
171
|
watch: {
|
|
146
172
|
anyOf: [
|
|
147
173
|
{
|
|
@@ -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
|
@@ -7,9 +7,11 @@ const { pathToFileURL } = require('node:url')
|
|
|
7
7
|
const inspector = require('node:inspector')
|
|
8
8
|
|
|
9
9
|
const pino = require('pino')
|
|
10
|
-
const { fetch, setGlobalDispatcher, Agent } = require('undici')
|
|
10
|
+
const { fetch, setGlobalDispatcher, getGlobalDispatcher, Agent } = require('undici')
|
|
11
11
|
const { wire } = require('undici-thread-interceptor')
|
|
12
|
+
const undici = require('undici')
|
|
12
13
|
|
|
14
|
+
const RemoteCacheStore = require('./http-cache')
|
|
13
15
|
const { PlatformaticApp } = require('./app')
|
|
14
16
|
const { setupITC } = require('./itc')
|
|
15
17
|
const loadInterceptors = require('./interceptors')
|
|
@@ -78,10 +80,21 @@ async function main () {
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const dispatcherOpts = { ...config.undici }
|
|
84
|
+
|
|
85
|
+
if (Object.keys(interceptors).length > 0) {
|
|
86
|
+
const clientInterceptors = [...interceptors.Agent, ...interceptors.Client]
|
|
87
|
+
const poolInterceptors = [...interceptors.Agent, ...interceptors.Pool]
|
|
88
|
+
|
|
89
|
+
dispatcherOpts.factory = (origin, opts) => {
|
|
90
|
+
return opts && opts.connections === 1
|
|
91
|
+
? new undici.Client(origin, opts).compose(clientInterceptors)
|
|
92
|
+
: new undici.Pool(origin, opts).compose(poolInterceptors)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const globalDispatcher = new Agent(dispatcherOpts)
|
|
97
|
+
.compose(composedInterceptors)
|
|
85
98
|
|
|
86
99
|
setGlobalDispatcher(globalDispatcher)
|
|
87
100
|
|
|
@@ -90,6 +103,15 @@ async function main () {
|
|
|
90
103
|
// TODO: make this configurable
|
|
91
104
|
const threadDispatcher = wire({ port: parentPort, useNetwork: service.useHttp, timeout: 5 * 60 * 1000 })
|
|
92
105
|
|
|
106
|
+
if (config.httpCache) {
|
|
107
|
+
setGlobalDispatcher(
|
|
108
|
+
getGlobalDispatcher().compose(undici.interceptors.cache({
|
|
109
|
+
store: new RemoteCacheStore(),
|
|
110
|
+
methods: config.httpCache.methods ?? ['GET', 'HEAD']
|
|
111
|
+
}))
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
// If the service is an entrypoint and runtime server config is defined, use it.
|
|
94
116
|
let serverConfig = null
|
|
95
117
|
if (config.server && service.entrypoint) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6-alpha.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@fastify/express": "^4.0.0",
|
|
21
21
|
"@fastify/formbody": "^8.0.0",
|
|
22
|
-
"borp": "^0.
|
|
22
|
+
"borp": "^0.18.0",
|
|
23
23
|
"c8": "^10.0.0",
|
|
24
24
|
"eslint": "9",
|
|
25
25
|
"execa": "^8.0.1",
|
|
@@ -35,17 +35,18 @@
|
|
|
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.5.
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/sql-graphql": "2.5.
|
|
42
|
-
"@platformatic/sql-mapper": "2.5.
|
|
38
|
+
"@platformatic/composer": "2.5.6-alpha.2",
|
|
39
|
+
"@platformatic/service": "2.5.6-alpha.2",
|
|
40
|
+
"@platformatic/db": "2.5.6-alpha.2",
|
|
41
|
+
"@platformatic/sql-graphql": "2.5.6-alpha.2",
|
|
42
|
+
"@platformatic/sql-mapper": "2.5.6-alpha.2"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@fastify/error": "^4.0.0",
|
|
46
46
|
"@fastify/websocket": "^11.0.0",
|
|
47
47
|
"@hapi/topo": "^6.0.2",
|
|
48
48
|
"@platformatic/http-metrics": "^0.2.1",
|
|
49
|
+
"@platformatic/undici-cache-memory": "^0.3.0",
|
|
49
50
|
"@watchable/unpromise": "^1.0.2",
|
|
50
51
|
"boring-name-generator": "^1.0.3",
|
|
51
52
|
"change-case-all": "^2.1.0",
|
|
@@ -67,16 +68,16 @@
|
|
|
67
68
|
"semgrator": "^0.3.0",
|
|
68
69
|
"tail-file-stream": "^0.2.0",
|
|
69
70
|
"thread-cpu-usage": "^0.2.0",
|
|
70
|
-
"undici": "
|
|
71
|
+
"undici": "7.0.0-alpha.3",
|
|
71
72
|
"undici-thread-interceptor": "^0.7.0",
|
|
72
73
|
"ws": "^8.16.0",
|
|
73
|
-
"@platformatic/basic": "2.5.
|
|
74
|
-
"@platformatic/config": "2.5.
|
|
75
|
-
"@platformatic/generators": "2.5.
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/ts-compiler": "2.5.
|
|
79
|
-
"@platformatic/utils": "2.5.
|
|
74
|
+
"@platformatic/basic": "2.5.6-alpha.2",
|
|
75
|
+
"@platformatic/config": "2.5.6-alpha.2",
|
|
76
|
+
"@platformatic/generators": "2.5.6-alpha.2",
|
|
77
|
+
"@platformatic/telemetry": "2.5.6-alpha.2",
|
|
78
|
+
"@platformatic/itc": "2.5.6-alpha.2",
|
|
79
|
+
"@platformatic/ts-compiler": "2.5.6-alpha.2",
|
|
80
|
+
"@platformatic/utils": "2.5.6-alpha.2"
|
|
80
81
|
},
|
|
81
82
|
"scripts": {
|
|
82
83
|
"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.5.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.5.6-alpha.2.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -379,6 +379,35 @@
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
},
|
|
382
|
+
"httpCache": {
|
|
383
|
+
"oneOf": [
|
|
384
|
+
{
|
|
385
|
+
"type": "boolean"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"type": "object",
|
|
389
|
+
"properties": {
|
|
390
|
+
"store": {
|
|
391
|
+
"type": "string"
|
|
392
|
+
},
|
|
393
|
+
"methods": {
|
|
394
|
+
"type": "array",
|
|
395
|
+
"items": {
|
|
396
|
+
"type": "string"
|
|
397
|
+
},
|
|
398
|
+
"default": [
|
|
399
|
+
"GET",
|
|
400
|
+
"HEAD"
|
|
401
|
+
],
|
|
402
|
+
"minItems": 1
|
|
403
|
+
},
|
|
404
|
+
"cacheTagsHeader": {
|
|
405
|
+
"type": "string"
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
},
|
|
382
411
|
"watch": {
|
|
383
412
|
"anyOf": [
|
|
384
413
|
{
|