@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.
@@ -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
+ }
@@ -8,13 +8,13 @@
8
8
  "openapi": true
9
9
  },
10
10
  "plugins": {
11
- "hotReload": true,
12
11
  "paths": [
13
12
  "plugin.js"
14
13
  ]
15
14
  },
16
15
  "clients": [
17
16
  {
17
+ "serviceId": "with-logger",
18
18
  "path": "./with-logger",
19
19
  "url": "{PLT_WITH_LOGGER_URL}"
20
20
  }
@@ -5,7 +5,6 @@
5
5
  "port": 0
6
6
  },
7
7
  "plugins": {
8
- "hotReload": true,
9
8
  "paths": [
10
9
  "plugin.js"
11
10
  ]
@@ -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()
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+ const { startCommand } = require('../lib/unified-api')
3
+
4
+ async function main () {
5
+ await startCommand(['-c', process.argv[2]])
6
+ process.exit(42)
7
+ }
8
+
9
+ main()
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+ const { start } = require('../lib/unified-api')
3
+
4
+ async function main () {
5
+ await start(['-c', process.argv[2]])
6
+ process.exit(42)
7
+ }
8
+
9
+ main()
package/index.js CHANGED
@@ -1,33 +1,13 @@
1
1
  'use strict'
2
- const ConfigManager = require('@platformatic/config')
2
+ const { buildServer } = require('./lib/build-server')
3
3
  const { platformaticRuntime } = require('./lib/config')
4
- const { start, startWithConfig } = require('./lib/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 }