@platformatic/runtime 1.44.0 → 1.45.0

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,204 @@
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
+ "/posts": {
12
+ "get": {
13
+ "responses": {
14
+ "200": {
15
+ "description": "Default Response",
16
+ "content": {
17
+ "application/json": {
18
+ "schema": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "object",
22
+ "properties": {
23
+ "id": {
24
+ "type": "number"
25
+ },
26
+ "name": {
27
+ "type": "string"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "post": {
38
+ "requestBody": {
39
+ "content": {
40
+ "application/json": {
41
+ "schema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "name": {
45
+ "type": "string"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "responses": {
53
+ "200": {
54
+ "description": "Default Response",
55
+ "content": {
56
+ "application/json": {
57
+ "schema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "id": {
61
+ "type": "number"
62
+ },
63
+ "name": {
64
+ "type": "string"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ "put": {
74
+ "requestBody": {
75
+ "content": {
76
+ "application/json": {
77
+ "schema": {
78
+ "type": "object",
79
+ "properties": {
80
+ "id": {
81
+ "type": "number"
82
+ },
83
+ "name": {
84
+ "type": "string"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ },
91
+ "responses": {
92
+ "200": {
93
+ "description": "Default Response",
94
+ "content": {
95
+ "application/json": {
96
+ "schema": {
97
+ "type": "object",
98
+ "properties": {
99
+ "id": {
100
+ "type": "number"
101
+ },
102
+ "name": {
103
+ "type": "string"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "/posts/{id}": {
114
+ "get": {
115
+ "responses": {
116
+ "200": {
117
+ "description": "Default Response",
118
+ "content": {
119
+ "application/json": {
120
+ "schema": {
121
+ "type": "object",
122
+ "properties": {
123
+ "id": {
124
+ "type": "number"
125
+ },
126
+ "name": {
127
+ "type": "string"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ },
136
+ "post": {
137
+ "responses": {
138
+ "200": {
139
+ "description": "Default Response",
140
+ "content": {
141
+ "application/json": {
142
+ "schema": {
143
+ "type": "object",
144
+ "properties": {
145
+ "id": {
146
+ "type": "number"
147
+ },
148
+ "name": {
149
+ "type": "string"
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "put": {
159
+ "responses": {
160
+ "200": {
161
+ "description": "Default Response",
162
+ "content": {
163
+ "application/json": {
164
+ "schema": {
165
+ "type": "object",
166
+ "properties": {
167
+ "id": {
168
+ "type": "number"
169
+ },
170
+ "name": {
171
+ "type": "string"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ },
180
+ "delete": {
181
+ "responses": {
182
+ "200": {
183
+ "description": "Default Response",
184
+ "content": {
185
+ "application/json": {
186
+ "schema": {
187
+ "type": "object",
188
+ "properties": {
189
+ "id": {
190
+ "type": "number"
191
+ },
192
+ "name": {
193
+ "type": "string"
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
@@ -0,0 +1,204 @@
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
+ "/posts": {
12
+ "get": {
13
+ "responses": {
14
+ "200": {
15
+ "description": "Default Response",
16
+ "content": {
17
+ "application/json": {
18
+ "schema": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "object",
22
+ "properties": {
23
+ "id": {
24
+ "type": "number"
25
+ },
26
+ "name": {
27
+ "type": "string"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "post": {
38
+ "requestBody": {
39
+ "content": {
40
+ "application/json": {
41
+ "schema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "name": {
45
+ "type": "string"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "responses": {
53
+ "200": {
54
+ "description": "Default Response",
55
+ "content": {
56
+ "application/json": {
57
+ "schema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "id": {
61
+ "type": "number"
62
+ },
63
+ "name": {
64
+ "type": "string"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ "put": {
74
+ "requestBody": {
75
+ "content": {
76
+ "application/json": {
77
+ "schema": {
78
+ "type": "object",
79
+ "properties": {
80
+ "id": {
81
+ "type": "number"
82
+ },
83
+ "name": {
84
+ "type": "string"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ },
91
+ "responses": {
92
+ "200": {
93
+ "description": "Default Response",
94
+ "content": {
95
+ "application/json": {
96
+ "schema": {
97
+ "type": "object",
98
+ "properties": {
99
+ "id": {
100
+ "type": "number"
101
+ },
102
+ "name": {
103
+ "type": "string"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "/posts/{id}": {
114
+ "get": {
115
+ "responses": {
116
+ "200": {
117
+ "description": "Default Response",
118
+ "content": {
119
+ "application/json": {
120
+ "schema": {
121
+ "type": "object",
122
+ "properties": {
123
+ "id": {
124
+ "type": "number"
125
+ },
126
+ "name": {
127
+ "type": "string"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ },
136
+ "post": {
137
+ "responses": {
138
+ "200": {
139
+ "description": "Default Response",
140
+ "content": {
141
+ "application/json": {
142
+ "schema": {
143
+ "type": "object",
144
+ "properties": {
145
+ "id": {
146
+ "type": "number"
147
+ },
148
+ "name": {
149
+ "type": "string"
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "put": {
159
+ "responses": {
160
+ "200": {
161
+ "description": "Default Response",
162
+ "content": {
163
+ "application/json": {
164
+ "schema": {
165
+ "type": "object",
166
+ "properties": {
167
+ "id": {
168
+ "type": "number"
169
+ },
170
+ "name": {
171
+ "type": "string"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ },
180
+ "delete": {
181
+ "responses": {
182
+ "200": {
183
+ "description": "Default Response",
184
+ "content": {
185
+ "application/json": {
186
+ "schema": {
187
+ "type": "object",
188
+ "properties": {
189
+ "id": {
190
+ "type": "number"
191
+ },
192
+ "name": {
193
+ "type": "string"
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.44.0/service",
3
+ "server": {
4
+ "hostname": "127.0.0.1",
5
+ "port": 0,
6
+ "logger": {
7
+ "level": "info"
8
+ },
9
+ "pluginTimeout": 0
10
+ },
11
+ "service": {
12
+ "openapi": true
13
+ },
14
+ "clients": [
15
+ {
16
+ "schema": "./clients/service-2/schema.json",
17
+ "name": "service2",
18
+ "type": "openapi",
19
+ "serviceId": "service-2"
20
+ },
21
+ {
22
+ "schema": "./external-service.schema.json",
23
+ "name": "external-service-1",
24
+ "type": "openapi",
25
+ "url": "http://external-dependency-1"
26
+ }
27
+ ],
28
+ "watch": false
29
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.44.0/service",
3
+ "server": {
4
+ "hostname": "127.0.0.1",
5
+ "port": 0,
6
+ "logger": {
7
+ "level": "info"
8
+ },
9
+ "pluginTimeout": 0
10
+ },
11
+ "service": {
12
+ "openapi": true
13
+ },
14
+ "watch": false
15
+ }
package/lib/api.js CHANGED
@@ -234,8 +234,18 @@ class RuntimeApi {
234
234
  const status = service.getStatus()
235
235
 
236
236
  const type = service.config?.configType
237
+ const version = service.config?.app?.configManagerConfig.version ?? null
237
238
  const { entrypoint, dependencies, localUrl } = service.appConfig
238
- const serviceDetails = { id, type, status, localUrl, entrypoint, dependencies }
239
+
240
+ const serviceDetails = {
241
+ id,
242
+ type,
243
+ status,
244
+ version,
245
+ localUrl,
246
+ entrypoint,
247
+ dependencies
248
+ }
239
249
 
240
250
  if (entrypoint) {
241
251
  serviceDetails.url = status === 'started' ? service.server.url : null
package/lib/config.js CHANGED
@@ -103,135 +103,87 @@ function missingDependencyErrorMessage (clientName, service, configManager) {
103
103
  }
104
104
 
105
105
  async function parseClientsAndComposer (configManager) {
106
- for (let i = 0; i < configManager.current.services.length; ++i) {
107
- const service = configManager.current.services[i]
106
+ for (const service of configManager.current.services) {
108
107
  const cm = new ConfigManager({ source: service.config })
109
108
  const configString = await cm.load()
110
- const parsed = cm._parser(configString)
111
-
112
- if (Array.isArray(parsed.composer?.services)) {
113
- for (let i = 0; i < parsed.composer.services.length; ++i) {
114
- const dep = parsed.composer.services[i]
115
- /* c8 ignore next 4 - why c8? */
116
- const clientName = dep.id ?? ''
117
- const dependency = configManager.current.serviceMap.get(clientName)
118
-
119
- let isLocal = true
120
- let clientUrl = null
121
-
122
- if (dep.origin !== undefined) {
123
- try {
124
- clientUrl = await cm.replaceEnv(dep.origin)
125
- isLocal = false
126
- /* c8 ignore next 4 */
127
- } catch (err) {
128
- // The MissingValueError is an error coming from pupa: https://github.com/sindresorhus/pupa#missingvalueerror
129
- // All other errors are simply rethrown.
130
- if (err.name !== 'MissingValueError') {
131
- throw err
132
- }
109
+ const serviceConfig = cm._parser(configString)
133
110
 
134
- if (dependency !== undefined && dep.origin === `{${err.key}}`) {
135
- clientUrl = `http://${clientName}.plt.local`
136
- service.localServiceEnvVars.set(err.key, clientUrl)
137
- }
138
- }
139
- }
111
+ async function parseConfigUrl (urlString) {
112
+ if (!urlString) {
113
+ return { url: null, envVar: null }
114
+ }
140
115
 
141
- if (isLocal) {
142
- if (dependency === undefined) {
143
- throw new errors.MissingDependencyError(missingDependencyErrorMessage(clientName, service, configManager))
144
- }
145
- clientUrl = `http://${clientName}.plt.local`
146
- dependency.dependents.push(service.id)
116
+ try {
117
+ const url = await cm.replaceEnv(urlString)
118
+ return { url, envVar: null }
119
+ } catch (err) {
120
+ // The MissingValueError is an error coming from pupa
121
+ // https://github.com/sindresorhus/pupa#missingvalueerror
122
+ // All other errors are simply re-thrown.
123
+ if (err.name !== 'MissingValueError' || urlString !== `{${err.key}}`) {
124
+ throw err
147
125
  }
148
-
149
- service.dependencies.push({
150
- id: clientName,
151
- url: clientUrl,
152
- local: isLocal
153
- })
126
+ return { url: null, envVar: err.key }
154
127
  }
155
128
  }
156
129
 
157
- if (Array.isArray(parsed.clients)) {
158
- const promises = parsed.clients.map((client) => {
159
- // eslint-disable-next-line no-async-promise-executor
160
- return new Promise(async (resolve, reject) => {
161
- let clientName = client.serviceId || ''
162
- let clientUrl
163
- let missingKey
164
- let isLocal = false
130
+ async function addServiceDependency (service, dependencyId, urlString) {
131
+ let { url, envVar } = await parseConfigUrl(urlString)
132
+ if (url !== null) {
133
+ service.dependencies.push({ id: dependencyId, url, local: false })
134
+ return
135
+ }
165
136
 
166
- if (clientName === '' || client.url !== undefined) {
167
- try {
168
- clientUrl = await cm.replaceEnv(client.url)
169
- /* c8 ignore next 2 - unclear why c8 is unhappy here */
170
- } catch (err) {
171
- if (err.name !== 'MissingValueError') {
172
- /* c8 ignore next 3 */
173
- reject(err)
174
- return
175
- }
137
+ const depService = configManager.current.serviceMap.get(dependencyId)
138
+ if (depService === undefined) {
139
+ throw new errors.MissingDependencyError(
140
+ missingDependencyErrorMessage(dependencyId, service, configManager)
141
+ )
142
+ }
176
143
 
177
- missingKey = err.key
178
- }
179
- isLocal = missingKey && client.url === `{${missingKey}}`
180
- /* c8 ignore next 3 */
181
- } else {
182
- /* c8 ignore next 2 */
183
- isLocal = true
184
- }
144
+ url = `http://${dependencyId}.plt.local`
145
+
146
+ if (envVar !== null) {
147
+ service.localServiceEnvVars.set(envVar, url)
148
+ }
185
149
 
186
- /* c8 ignore next 20 - unclear why c8 is unhappy for nearly 20 lines here */
187
- if (!clientName) {
150
+ depService.dependents.push(service.id)
151
+ service.dependencies.push({ id: dependencyId, url, local: true })
152
+ }
153
+
154
+ const composedServices = serviceConfig.composer?.services
155
+ if (Array.isArray(composedServices)) {
156
+ await Promise.all(
157
+ composedServices.map(async (composedService) =>
158
+ addServiceDependency(
159
+ service,
160
+ composedService.id,
161
+ composedService.origin
162
+ )
163
+ )
164
+ )
165
+ }
166
+
167
+ if (Array.isArray(serviceConfig.clients)) {
168
+ await Promise.all(
169
+ serviceConfig.clients.map(async (client) => {
170
+ let clientServiceId = client.serviceId
171
+ if (!clientServiceId) {
188
172
  try {
189
- const clientAbsolutePath = pathResolve(service.path, client.path)
190
- const clientPackageJson = join(clientAbsolutePath, 'package.json')
191
- const clientMetadata = JSON.parse(await readFile(clientPackageJson, 'utf8'))
192
- clientName = clientMetadata.name ?? ''
173
+ const clientPath = pathResolve(service.path, client.path)
174
+ const clientPackageJsonPath = join(clientPath, 'package.json')
175
+ const clientPackageJSONFile = await readFile(clientPackageJsonPath, 'utf8')
176
+ const clientPackageJSON = JSON.parse(clientPackageJSONFile)
177
+ clientServiceId = clientPackageJSON.name ?? ''
193
178
  } catch (err) {
194
- if (client.url !== undefined && client.name !== undefined) {
195
- // We resolve because this is a remote client
196
- resolve()
197
- } else {
198
- reject(err)
179
+ if (client.url === undefined || client.name === undefined) {
180
+ throw err
199
181
  }
200
- return
201
182
  }
202
183
  }
203
-
204
- if (clientUrl === undefined) {
205
- // Combine the service name with the client name to avoid collisions
206
- // if two or more services have a client with the same name pointing
207
- // to different services.
208
- clientUrl = isLocal ? `http://${clientName}.plt.local` : client.url
209
- }
210
-
211
- service.dependencies.push({
212
- id: clientName,
213
- url: clientUrl,
214
- local: isLocal
215
- })
216
-
217
- const dependency = configManager.current.serviceMap.get(clientName)
218
-
219
- /* c8 ignore next 4 */
220
- if (dependency === undefined) {
221
- throw new errors.MissingDependencyError(missingDependencyErrorMessage(clientName, service, configManager))
222
- }
223
-
224
- dependency.dependents.push(service.id)
225
-
226
- if (isLocal) {
227
- service.localServiceEnvVars.set(missingKey, clientUrl)
228
- }
229
-
230
- resolve()
184
+ await addServiceDependency(service, clientServiceId, client.url)
231
185
  })
232
- })
233
-
234
- await Promise.all(promises)
186
+ )
235
187
  }
236
188
  }
237
189
  }
@@ -240,11 +192,16 @@ function topologicalSort (configManager) {
240
192
  const { services } = configManager.current
241
193
  const topo = new Topo.Sorter()
242
194
 
243
- for (let i = 0; i < services.length; ++i) {
244
- const service = services[i]
245
- const dependencyIds = service.dependencies.map(dep => dep.id)
195
+ for (const service of services) {
196
+ const localDependencyIds = service.dependencies
197
+ .filter(dep => dep.local)
198
+ .map(dep => dep.id)
246
199
 
247
- topo.add(service, { group: service.id, after: dependencyIds, manual: true })
200
+ topo.add(service, {
201
+ group: service.id,
202
+ after: localDependencyIds,
203
+ manual: true
204
+ })
248
205
  }
249
206
 
250
207
  configManager.current.services = topo.sort()