@platformatic/runtime 0.26.1 → 0.28.1
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/fixtures/composerApp/api1.json +65 -0
- package/fixtures/composerApp/platformatic.composer.json +23 -0
- package/fixtures/configs/invalid-schema-type.config.json +19 -0
- package/fixtures/configs/monorepo-client-without-id.json +24 -0
- package/fixtures/configs/no-schema.config.json +18 -0
- package/fixtures/dbApp/platformatic.db.json +22 -0
- package/fixtures/monorepo/serviceApp/platformatic.service-client-without-id.json +21 -0
- package/fixtures/monorepo/serviceApp/platformatic.service.json +1 -1
- package/fixtures/serviceAppThrowsOnStart/platformatic.service.json +0 -1
- package/fixtures/start-command-in-runtime.js +14 -0
- package/fixtures/start-command.js +9 -0
- package/fixtures/starter.js +9 -0
- package/index.js +5 -25
- package/lib/api.js +257 -0
- package/lib/app.js +62 -68
- package/lib/build-server.js +28 -0
- package/lib/config.js +36 -8
- package/lib/loader.mjs +21 -13
- package/lib/start.js +7 -19
- package/lib/unified-api.js +198 -0
- package/lib/worker.js +29 -53
- package/package.json +16 -13
- package/test/api.test.js +294 -0
- package/test/app.test.js +76 -13
- package/test/cli/start.test.mjs +4 -4
- package/test/config.test.js +9 -0
- package/test/unified-api.test.js +354 -0
- package/test/worker.test.js +0 -21
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"version": "8.3.1",
|
|
5
|
+
"title": "@fastify/swagger"
|
|
6
|
+
},
|
|
7
|
+
"components": {
|
|
8
|
+
"schemas": {}
|
|
9
|
+
},
|
|
10
|
+
"paths": {
|
|
11
|
+
"/users": {
|
|
12
|
+
"get": {
|
|
13
|
+
"responses": {
|
|
14
|
+
"200": {
|
|
15
|
+
"description": "Default Response"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"post": {
|
|
20
|
+
"responses": {
|
|
21
|
+
"200": {
|
|
22
|
+
"description": "Default Response"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"put": {
|
|
27
|
+
"responses": {
|
|
28
|
+
"200": {
|
|
29
|
+
"description": "Default Response"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"/users/{id}": {
|
|
35
|
+
"get": {
|
|
36
|
+
"responses": {
|
|
37
|
+
"200": {
|
|
38
|
+
"description": "Default Response"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"post": {
|
|
43
|
+
"responses": {
|
|
44
|
+
"200": {
|
|
45
|
+
"description": "Default Response"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"put": {
|
|
50
|
+
"responses": {
|
|
51
|
+
"200": {
|
|
52
|
+
"description": "Default Response"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"delete": {
|
|
57
|
+
"responses": {
|
|
58
|
+
"200": {
|
|
59
|
+
"description": "Default Response"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.23.2/composer",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0,
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "info"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"composer": {
|
|
11
|
+
"services": [
|
|
12
|
+
{
|
|
13
|
+
"id": "api1",
|
|
14
|
+
"origin": "http://127.0.0.1",
|
|
15
|
+
"openapi": {
|
|
16
|
+
"file": "./api1.json"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"refreshTimeout": 1000
|
|
21
|
+
},
|
|
22
|
+
"watch": false
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.22.0/trickortreat",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0,
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "info",
|
|
8
|
+
"name": "hello server"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"plugins": {
|
|
12
|
+
"paths": ["./plugin.js"]
|
|
13
|
+
},
|
|
14
|
+
"service": {
|
|
15
|
+
"openapi": true
|
|
16
|
+
},
|
|
17
|
+
"metrics": false,
|
|
18
|
+
"watch": false
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
|
|
3
|
+
"entrypoint": "serviceApp",
|
|
4
|
+
"allowCycles": true,
|
|
5
|
+
"hotReload": false,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "../monorepo",
|
|
8
|
+
"exclude": ["docs", "composerApp"],
|
|
9
|
+
"mappings": {
|
|
10
|
+
"serviceApp": {
|
|
11
|
+
"id": "serviceApp",
|
|
12
|
+
"config": "platformatic.service-client-without-id.json"
|
|
13
|
+
},
|
|
14
|
+
"serviceAppWithLogger": {
|
|
15
|
+
"id": "with-logger",
|
|
16
|
+
"config": "platformatic.service.json"
|
|
17
|
+
},
|
|
18
|
+
"serviceAppWithMultiplePlugins": {
|
|
19
|
+
"id": "multi-plugin-service",
|
|
20
|
+
"config": "platformatic.service.json"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": {
|
|
3
|
+
"hostname": "127.0.0.1",
|
|
4
|
+
"port": 0,
|
|
5
|
+
"logger": {
|
|
6
|
+
"level": "info",
|
|
7
|
+
"name": "hello server"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"plugins": {
|
|
11
|
+
"paths": ["./plugin.js"]
|
|
12
|
+
},
|
|
13
|
+
"service": {
|
|
14
|
+
"openapi": true
|
|
15
|
+
},
|
|
16
|
+
"metrics": false,
|
|
17
|
+
"watch": false
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.22.0/db",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0
|
|
6
|
+
},
|
|
7
|
+
"migrations": {
|
|
8
|
+
"dir": "migrations",
|
|
9
|
+
"table": "versions"
|
|
10
|
+
},
|
|
11
|
+
"types": {
|
|
12
|
+
"autogenerate": false
|
|
13
|
+
},
|
|
14
|
+
"db": {
|
|
15
|
+
"connectionString": "sqlite://db.sqlite",
|
|
16
|
+
"graphql": true,
|
|
17
|
+
"ignore": {
|
|
18
|
+
"versions": true
|
|
19
|
+
},
|
|
20
|
+
"events": false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.20.0/service",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0
|
|
6
|
+
},
|
|
7
|
+
"service": {
|
|
8
|
+
"openapi": true
|
|
9
|
+
},
|
|
10
|
+
"plugins": {
|
|
11
|
+
"paths": [
|
|
12
|
+
"plugin.js"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"clients": [
|
|
16
|
+
{
|
|
17
|
+
"path": "./with-logger",
|
|
18
|
+
"url": "{PLT_WITH_LOGGER_URL}"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const assert = require('node:assert')
|
|
3
|
+
const { request } = require('undici')
|
|
4
|
+
const { startCommandInRuntime } = require('../lib/unified-api')
|
|
5
|
+
|
|
6
|
+
async function main () {
|
|
7
|
+
const entrypoint = await startCommandInRuntime(['-c', process.argv[2]])
|
|
8
|
+
const res = await request(entrypoint)
|
|
9
|
+
|
|
10
|
+
assert.strictEqual(res.statusCode, 200)
|
|
11
|
+
process.exit(42)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
main()
|
package/index.js
CHANGED
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const
|
|
2
|
+
const { buildServer } = require('./lib/build-server')
|
|
3
3
|
const { platformaticRuntime } = require('./lib/config')
|
|
4
|
-
const { start
|
|
5
|
-
|
|
6
|
-
async function buildServer (options = {}) {
|
|
7
|
-
if (!options.configManager) {
|
|
8
|
-
// Instantiate a new config manager from the current options.
|
|
9
|
-
const cm = new ConfigManager({
|
|
10
|
-
...platformaticRuntime.configManagerConfig,
|
|
11
|
-
source: options
|
|
12
|
-
})
|
|
13
|
-
await cm.parseAndValidate()
|
|
14
|
-
|
|
15
|
-
if (typeof options === 'string') {
|
|
16
|
-
options = { configManager: cm }
|
|
17
|
-
} else {
|
|
18
|
-
options.configManager = cm
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// The transformConfig() function can't be sent between threads.
|
|
23
|
-
delete options.configManager._transformConfig
|
|
24
|
-
|
|
25
|
-
return startWithConfig(options.configManager)
|
|
26
|
-
}
|
|
4
|
+
const { start } = require('./lib/start')
|
|
5
|
+
const unifiedApi = require('./lib/unified-api')
|
|
27
6
|
|
|
28
7
|
module.exports = {
|
|
29
8
|
buildServer,
|
|
30
9
|
platformaticRuntime,
|
|
31
10
|
schema: platformaticRuntime.schema,
|
|
32
|
-
start
|
|
11
|
+
start,
|
|
12
|
+
unifiedApi
|
|
33
13
|
}
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { once, EventEmitter } = require('node:events')
|
|
4
|
+
const { randomUUID } = require('node:crypto')
|
|
5
|
+
|
|
6
|
+
const MAX_LISTENERS_COUNT = 100
|
|
7
|
+
|
|
8
|
+
class RuntimeApiClient extends EventEmitter {
|
|
9
|
+
#worker
|
|
10
|
+
|
|
11
|
+
constructor (worker) {
|
|
12
|
+
super()
|
|
13
|
+
this.setMaxListeners(MAX_LISTENERS_COUNT)
|
|
14
|
+
|
|
15
|
+
this.#worker = worker
|
|
16
|
+
this.#worker.on('message', (message) => {
|
|
17
|
+
if (message.operationId) {
|
|
18
|
+
this.emit(message.operationId, message)
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async start () {
|
|
24
|
+
return this.#sendCommand('plt:start-services')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async close () {
|
|
28
|
+
await this.#sendCommand('plt:stop-services')
|
|
29
|
+
await once(this.#worker, 'exit')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async restart () {
|
|
33
|
+
return this.#sendCommand('plt:restart-services')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getServices () {
|
|
37
|
+
return this.#sendCommand('plt:get-services')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getServiceDetails (id) {
|
|
41
|
+
return this.#sendCommand('plt:get-service-details', { id })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getServiceConfig (id) {
|
|
45
|
+
return this.#sendCommand('plt:get-service-config', { id })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async startService (id) {
|
|
49
|
+
return this.#sendCommand('plt:start-service', { id })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async stopService (id) {
|
|
53
|
+
return this.#sendCommand('plt:stop-service', { id })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async inject (id, injectParams) {
|
|
57
|
+
return this.#sendCommand('plt:inject', { id, injectParams })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async #sendCommand (command, params = {}) {
|
|
61
|
+
const operationId = randomUUID()
|
|
62
|
+
|
|
63
|
+
this.#worker.postMessage({ operationId, command, params })
|
|
64
|
+
const [message] = await once(this, operationId)
|
|
65
|
+
|
|
66
|
+
const { error, data } = message
|
|
67
|
+
if (error !== null) {
|
|
68
|
+
throw new Error(error)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return JSON.parse(data)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class RuntimeApi {
|
|
76
|
+
#services
|
|
77
|
+
#dispatcher
|
|
78
|
+
|
|
79
|
+
constructor (services, dispatcher) {
|
|
80
|
+
this.#services = services
|
|
81
|
+
this.#dispatcher = dispatcher
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async startListening (parentPort) {
|
|
85
|
+
parentPort.on('message', async (message) => {
|
|
86
|
+
const command = message?.command
|
|
87
|
+
if (command) {
|
|
88
|
+
const res = await this.#executeCommand(message)
|
|
89
|
+
parentPort.postMessage(res)
|
|
90
|
+
|
|
91
|
+
if (command === 'plt:stop-services') {
|
|
92
|
+
process.exit() // Exit the worker thread.
|
|
93
|
+
}
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
await this.#handleProcessLevelEvent(message)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async #handleProcessLevelEvent (message) {
|
|
101
|
+
for (const service of this.#services.values()) {
|
|
102
|
+
await service.handleProcessLevelEvent(message)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async #executeCommand (message) {
|
|
107
|
+
const { operationId, command, params } = message
|
|
108
|
+
try {
|
|
109
|
+
const res = await this.#runCommandHandler(command, params)
|
|
110
|
+
return { operationId, error: null, data: JSON.stringify(res || null) }
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return { operationId, error: err.message }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async #runCommandHandler (command, params) {
|
|
117
|
+
switch (command) {
|
|
118
|
+
case 'plt:start-services':
|
|
119
|
+
return this.#startServices(params)
|
|
120
|
+
case 'plt:stop-services':
|
|
121
|
+
return this.#stopServices(params)
|
|
122
|
+
case 'plt:restart-services':
|
|
123
|
+
return this.#restartServices(params)
|
|
124
|
+
case 'plt:get-services':
|
|
125
|
+
return this.#getServices(params)
|
|
126
|
+
case 'plt:get-service-details':
|
|
127
|
+
return this.#getServiceDetails(params)
|
|
128
|
+
case 'plt:get-service-config':
|
|
129
|
+
return this.#getServiceConfig(params)
|
|
130
|
+
case 'plt:start-service':
|
|
131
|
+
return this.#startService(params)
|
|
132
|
+
case 'plt:stop-service':
|
|
133
|
+
return this.#stopService(params)
|
|
134
|
+
case 'plt:inject':
|
|
135
|
+
return this.#inject(params)
|
|
136
|
+
/* c8 ignore next 2 */
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`Unknown Runtime API command: '${command}'`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async #startServices () {
|
|
143
|
+
let entrypointUrl = null
|
|
144
|
+
for (const service of this.#services.values()) {
|
|
145
|
+
await service.start()
|
|
146
|
+
|
|
147
|
+
if (service.appConfig.entrypoint) {
|
|
148
|
+
entrypointUrl = service.server.url
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const serviceUrl = new URL(service.appConfig.localUrl)
|
|
152
|
+
this.#dispatcher.route(serviceUrl.host, service.server)
|
|
153
|
+
}
|
|
154
|
+
return entrypointUrl
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async #stopServices () {
|
|
158
|
+
for (const service of this.#services.values()) {
|
|
159
|
+
const serviceStatus = service.getStatus()
|
|
160
|
+
if (serviceStatus === 'started') {
|
|
161
|
+
await service.stop()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async #restartServices () {
|
|
167
|
+
let entrypointUrl = null
|
|
168
|
+
for (const service of this.#services.values()) {
|
|
169
|
+
if (service.server) {
|
|
170
|
+
await service.restart(true)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (service.appConfig.entrypoint) {
|
|
174
|
+
entrypointUrl = service.server.url
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const serviceUrl = new URL(service.appConfig.localUrl)
|
|
178
|
+
this.#dispatcher.route(serviceUrl.host, service.server)
|
|
179
|
+
}
|
|
180
|
+
return entrypointUrl
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#getServices () {
|
|
184
|
+
const services = { services: [] }
|
|
185
|
+
|
|
186
|
+
for (const service of this.#services.values()) {
|
|
187
|
+
const serviceId = service.appConfig.id
|
|
188
|
+
const serviceDetails = this.#getServiceDetails({ id: serviceId })
|
|
189
|
+
if (serviceDetails.entrypoint) {
|
|
190
|
+
services.entrypoint = serviceId
|
|
191
|
+
}
|
|
192
|
+
services.services.push(serviceDetails)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return services
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#getServiceById (id) {
|
|
199
|
+
const service = this.#services.get(id)
|
|
200
|
+
|
|
201
|
+
if (!service) {
|
|
202
|
+
throw new Error(`Service with id '${id}' not found`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return service
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#getServiceDetails ({ id }) {
|
|
209
|
+
const service = this.#getServiceById(id)
|
|
210
|
+
const status = service.getStatus()
|
|
211
|
+
|
|
212
|
+
const { entrypoint, dependencies, localUrl } = service.appConfig
|
|
213
|
+
return { id, status, localUrl, entrypoint, dependencies }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#getServiceConfig ({ id }) {
|
|
217
|
+
const service = this.#getServiceById(id)
|
|
218
|
+
|
|
219
|
+
const { config } = service
|
|
220
|
+
if (!config) {
|
|
221
|
+
throw new Error(`Service with id '${id}' is not started`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return config.configManager.current
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async #startService ({ id }) {
|
|
228
|
+
const service = this.#getServiceById(id)
|
|
229
|
+
await service.start()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async #stopService ({ id }) {
|
|
233
|
+
const service = this.#getServiceById(id)
|
|
234
|
+
await service.stop()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async #inject ({ id, injectParams }) {
|
|
238
|
+
const service = this.#getServiceById(id)
|
|
239
|
+
|
|
240
|
+
const serviceStatus = service.getStatus()
|
|
241
|
+
if (serviceStatus !== 'started') {
|
|
242
|
+
throw new Error(`Service with id '${id}' is not started`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const res = await service.server.inject(injectParams)
|
|
246
|
+
// Return only serializable properties.
|
|
247
|
+
return {
|
|
248
|
+
statusCode: res.statusCode,
|
|
249
|
+
statusMessage: res.statusMessage,
|
|
250
|
+
headers: res.headers,
|
|
251
|
+
body: res.body,
|
|
252
|
+
payload: res.payload
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = { RuntimeApi, RuntimeApiClient }
|