@toa.io/extensions.exposition 1.0.0-alpha.288 → 1.0.0-alpha.289
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/CHANGELOG.md +11 -0
- package/components/identity.basic/tsconfig.tsbuildinfo +1 -1
- package/components/identity.clients/tsconfig.tsbuildinfo +1 -1
- package/components/identity.credentials/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/tsconfig.tsbuildinfo +1 -1
- package/components/identity.grants/tsconfig.tsbuildinfo +1 -1
- package/components/identity.keys/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/tsconfig.tsbuildinfo +1 -1
- package/components/identity.passkeys/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/tsconfig.tsbuildinfo +1 -1
- package/documentation/access.md +3 -2
- package/documentation/discovery.md +8 -8
- package/documentation/help.md +16 -3
- package/documentation/introspection.md +10 -2
- package/documentation/octets.md +20 -0
- package/features/discovery.feature +4 -2
- package/features/discovery.ui.feature +19 -3
- package/features/help.feature +46 -0
- package/features/octets.feature +59 -0
- package/features/steps/Components.ts +4 -2
- package/features/steps/Database.ts +7 -1
- package/features/steps/Gateway.ts +23 -21
- package/features/steps/Parameters.ts +25 -16
- package/features/steps/Realtime.ts +3 -3
- package/package.json +5 -5
- package/source/Directive.ts +0 -11
- package/source/Discovery/trunk.ts +13 -3
- package/source/Factory.ts +3 -86
- package/source/HTTP/Probe.ts +0 -11
- package/source/HTTP/Server.ts +2 -3
- package/source/HTTP/messages.ts +4 -2
- package/source/Introspection.ts +28 -0
- package/source/const.ts +18 -0
- package/source/deployment.ts +2 -2
- package/source/directives/auth/Anonymous.ts +10 -0
- package/source/directives/help/Family.ts +5 -1
- package/source/directives/help/Help.ts +21 -0
- package/source/directives/octets/Directive.ts +7 -0
- package/source/directives/octets/Octets.ts +8 -0
- package/source/directives/octets/Put.ts +10 -0
- package/source/manifest.ts +1 -1
- package/source/root.ts +2 -2
- package/source/service.ts +94 -0
- package/source/shortcuts.ts +14 -0
- package/transpiled/Directive.d.ts +0 -1
- package/transpiled/Directive.js +0 -11
- package/transpiled/Directive.js.map +1 -1
- package/transpiled/Discovery/trunk.d.ts +5 -2
- package/transpiled/Discovery/trunk.js +12 -4
- package/transpiled/Discovery/trunk.js.map +1 -1
- package/transpiled/Factory.js +3 -61
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/HTTP/Probe.d.ts +0 -10
- package/transpiled/HTTP/Probe.js +0 -10
- package/transpiled/HTTP/Probe.js.map +1 -1
- package/transpiled/HTTP/Server.d.ts +0 -1
- package/transpiled/HTTP/Server.js +2 -2
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/messages.js +4 -2
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/Introspection.d.ts +21 -0
- package/transpiled/Introspection.js +2 -0
- package/transpiled/Introspection.js.map +1 -1
- package/transpiled/const.d.ts +14 -0
- package/transpiled/const.js +14 -0
- package/transpiled/const.js.map +1 -1
- package/transpiled/deployment.js +2 -2
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/auth/Anonymous.d.ts +7 -0
- package/transpiled/directives/auth/Anonymous.js +8 -0
- package/transpiled/directives/auth/Anonymous.js.map +1 -1
- package/transpiled/directives/help/Family.d.ts +1 -1
- package/transpiled/directives/help/Family.js +4 -0
- package/transpiled/directives/help/Family.js.map +1 -1
- package/transpiled/directives/help/Help.d.ts +6 -0
- package/transpiled/directives/help/Help.js +14 -0
- package/transpiled/directives/help/Help.js.map +1 -1
- package/transpiled/directives/octets/Directive.d.ts +6 -0
- package/transpiled/directives/octets/Directive.js.map +1 -1
- package/transpiled/directives/octets/Octets.d.ts +3 -0
- package/transpiled/directives/octets/Octets.js +6 -0
- package/transpiled/directives/octets/Octets.js.map +1 -1
- package/transpiled/directives/octets/Put.d.ts +3 -0
- package/transpiled/directives/octets/Put.js +8 -0
- package/transpiled/directives/octets/Put.js.map +1 -1
- package/transpiled/manifest.js +1 -1
- package/transpiled/root.js +2 -2
- package/transpiled/root.js.map +1 -1
- package/transpiled/service.d.ts +7 -0
- package/transpiled/service.js +74 -0
- package/transpiled/service.js.map +1 -0
- package/transpiled/shortcuts.d.ts +3 -0
- package/transpiled/shortcuts.js +13 -0
- package/transpiled/shortcuts.js.map +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/ui/dist/_app/immutable/assets/0.Bl7vLmIK.css +2 -0
- package/ui/dist/_app/immutable/chunks/{CWJaG9mK.js → CDMRPWCx.js} +2 -2
- package/ui/dist/_app/immutable/chunks/DJTKIUs8.js +1 -0
- package/ui/dist/_app/immutable/chunks/{yDQhitF9.js → z-DVUbQC.js} +1 -1
- package/ui/dist/_app/immutable/entry/{app.WZSMBcGf.js → app.5S2Jtv46.js} +2 -2
- package/ui/dist/_app/immutable/entry/start.Bqi6IBmH.js +1 -0
- package/ui/dist/_app/immutable/nodes/{0.Dl2DqINo.js → 0.D4LwNilH.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{1.CSSnGhVD.js → 1.CSRya9So.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{2.DmolQyNG.js → 2.BtiGmR6I.js} +4 -4
- package/ui/dist/_app/version.json +1 -1
- package/ui/dist/index.html +11 -11
- package/components/identity.bans/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.basic/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.clients/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.federation/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.grants/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.keys/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.otp/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.passkeys/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.roles/migrations/0001-system-properties.yaml +0 -14
- package/components/identity.tokens/migrations/0001-system-properties.yaml +0 -14
- package/ui/dist/_app/immutable/assets/0.BcIGtTfw.css +0 -2
- package/ui/dist/_app/immutable/chunks/BgTrzN6u.js +0 -1
- package/ui/dist/_app/immutable/entry/start._LkoC6Lz.js +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Resource introspection
|
|
2
2
|
|
|
3
3
|
Any resource can be introspected by sending an `OPTIONS` request to the resource's path.
|
|
4
|
-
[
|
|
4
|
+
[Resource discovery](discovery.md) answers every resource at once, at `OPTIONS /.discovery`, and
|
|
5
|
+
serves a page that reads it.
|
|
5
6
|
|
|
6
7
|
What it answers is what the route's directives leave of what the operation declared, for each
|
|
7
8
|
method this identity may reach. A method they may not is not there, nor in `Allow`; a resource
|
|
@@ -13,13 +14,17 @@ described to whoever asks, as it is at `/.rpc` and `/.mcp`.
|
|
|
13
14
|
|
|
14
15
|
What the resource itself is, from [`help:node`](help.md), is answered beside the methods rather
|
|
15
16
|
than inside one. A verb is upper case, so neither key can be mistaken for the other. A resource
|
|
16
|
-
carries `private`, `protected` and `system` where any of its methods does.
|
|
17
|
+
carries `private`, `protected` and `system` where any of its methods does. A method
|
|
18
|
+
[`help:method: null`](help.md) hides is in none of this, and is reached as it always was.
|
|
17
19
|
|
|
18
20
|
Introspection properties:
|
|
19
21
|
|
|
20
22
|
- `title` and `description` what the route states this method is, from
|
|
21
23
|
[`help:method`](help.md). The operation states what it is too, and that is not this: it is
|
|
22
24
|
written without knowledge of any route, and the same operation mounted twice is two methods
|
|
25
|
+
- `anonymous` reaching it takes nothing — [`auth:anonymous`](access.md#anonymous). Said rather
|
|
26
|
+
than left unsaid, because it is also what refuses a caller presenting a credential: a client
|
|
27
|
+
holding one has to know which methods not to present it to
|
|
23
28
|
- `authenticated` reaching it takes being someone, whoever — [`auth:anyone`](access.md#anyone),
|
|
24
29
|
`auth:delegate` and `auth:claims`
|
|
25
30
|
- `private` reaching it is being the identity it is about — [`auth:id`](access.md#id)
|
|
@@ -33,6 +38,9 @@ Introspection properties:
|
|
|
33
38
|
`omit`, `search` — is not among them: it is the same of every queryable resource, and so
|
|
34
39
|
not something one says about itself. A [procedure](rpc.md) carries it, because there it is
|
|
35
40
|
something the caller sends
|
|
41
|
+
- `octets` what sending a file takes, from [`octets:put`](octets.md): the media types it
|
|
42
|
+
`accept`s, the `limit` a larger body is refused by, and `stream` where storing it starts a
|
|
43
|
+
workflow and the reply arrives in parts rather than as one object
|
|
36
44
|
- `input` input schema, restricted by `io:input`, without what the gateway fills itself
|
|
37
45
|
- `output` output schema, restricted by `io:output`; absent where the reply is not sent at all.
|
|
38
46
|
An operation that declares none is described by what its type answers: a transition, an
|
package/documentation/octets.md
CHANGED
|
@@ -190,6 +190,26 @@ status: exception
|
|
|
190
190
|
--cut--
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
### What a client is told
|
|
194
|
+
|
|
195
|
+
`octets:put` says what sending a file takes, where everything else about a method is said —
|
|
196
|
+
[`OPTIONS`](introspection.md) and the [tree](discovery.md):
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
POST:
|
|
200
|
+
octets:
|
|
201
|
+
accept: image/jpeg,image/png
|
|
202
|
+
limit: 64MiB
|
|
203
|
+
stream: true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`accept` is absent where the directive names no type, and anything may be sent. `limit` is
|
|
207
|
+
written as the declaration writes it, and is what a `413` reports. `stream` says a workflow runs
|
|
208
|
+
on what was stored, so the reply arrives as parts rather than as one object — the first part is
|
|
209
|
+
the entry.
|
|
210
|
+
|
|
211
|
+
Reading a file and deleting one say nothing: those requests are what they look like.
|
|
212
|
+
|
|
193
213
|
## `octets:get`
|
|
194
214
|
|
|
195
215
|
Fetches the content of a stored BLOB corresponding to the request path, and returns it as the
|
|
@@ -133,7 +133,8 @@ Feature: Resource discovery
|
|
|
133
133
|
|
|
134
134
|
routes:
|
|
135
135
|
/open:
|
|
136
|
-
GET:
|
|
136
|
+
GET:
|
|
137
|
+
anonymous: true
|
|
137
138
|
"""
|
|
138
139
|
And the reply does not contain:
|
|
139
140
|
"""
|
|
@@ -207,5 +208,6 @@ Feature: Resource discovery
|
|
|
207
208
|
|
|
208
209
|
routes:
|
|
209
210
|
/pots:
|
|
210
|
-
GET:
|
|
211
|
+
GET:
|
|
212
|
+
anonymous: true
|
|
211
213
|
"""
|
|
@@ -156,9 +156,9 @@ Feature: The discovery page
|
|
|
156
156
|
location: /.discovery/
|
|
157
157
|
"""
|
|
158
158
|
|
|
159
|
-
Scenario: A client that
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
Scenario: A client that asks for nothing in particular is sent there too
|
|
160
|
+
Several unfurlers ask for anything at all, and a link to an application would otherwise
|
|
161
|
+
show nothing.
|
|
162
162
|
|
|
163
163
|
When the following request is received:
|
|
164
164
|
"""
|
|
@@ -166,6 +166,22 @@ Feature: The discovery page
|
|
|
166
166
|
host: nex.toa.io
|
|
167
167
|
accept: */*
|
|
168
168
|
"""
|
|
169
|
+
Then the following reply is sent:
|
|
170
|
+
"""
|
|
171
|
+
302 Moved Temporarily
|
|
172
|
+
location: /.discovery/
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
Scenario: A client that asked for something else gets what it asked for
|
|
176
|
+
An `accept` naming what the gateway answers with is a client that wants the resource,
|
|
177
|
+
not the page.
|
|
178
|
+
|
|
179
|
+
When the following request is received:
|
|
180
|
+
"""
|
|
181
|
+
GET / HTTP/1.1
|
|
182
|
+
host: nex.toa.io
|
|
183
|
+
accept: application/json
|
|
184
|
+
"""
|
|
169
185
|
Then the following reply is sent:
|
|
170
186
|
"""
|
|
171
187
|
405 Method Not Allowed
|
package/features/help.feature
CHANGED
|
@@ -464,3 +464,49 @@ Feature: Help
|
|
|
464
464
|
"""
|
|
465
465
|
404 Not Found
|
|
466
466
|
"""
|
|
467
|
+
|
|
468
|
+
Scenario: A method nobody is meant to find
|
|
469
|
+
`help:method: null` takes the method out of every answer and leaves it as callable as it
|
|
470
|
+
was — what a monitor reaches for, and what a person has no business going looking for.
|
|
471
|
+
|
|
472
|
+
Given the annotation:
|
|
473
|
+
"""yaml
|
|
474
|
+
/:
|
|
475
|
+
anonymous: true
|
|
476
|
+
/hello:
|
|
477
|
+
GET:
|
|
478
|
+
dev:stub: hi
|
|
479
|
+
/agent:
|
|
480
|
+
GET:
|
|
481
|
+
help:method: null
|
|
482
|
+
dev:stub: hi
|
|
483
|
+
"""
|
|
484
|
+
When the following request is received:
|
|
485
|
+
"""
|
|
486
|
+
OPTIONS /.discovery HTTP/1.1
|
|
487
|
+
host: nex.toa.io
|
|
488
|
+
accept: application/yaml
|
|
489
|
+
"""
|
|
490
|
+
Then the following reply is sent:
|
|
491
|
+
"""
|
|
492
|
+
200 OK
|
|
493
|
+
|
|
494
|
+
routes:
|
|
495
|
+
/hello:
|
|
496
|
+
GET:
|
|
497
|
+
anonymous: true
|
|
498
|
+
"""
|
|
499
|
+
And the reply does not contain:
|
|
500
|
+
"""
|
|
501
|
+
/hello/agent
|
|
502
|
+
"""
|
|
503
|
+
# and it answers as it always did
|
|
504
|
+
When the following request is received:
|
|
505
|
+
"""
|
|
506
|
+
GET /hello/agent/ HTTP/1.1
|
|
507
|
+
host: nex.toa.io
|
|
508
|
+
"""
|
|
509
|
+
Then the following reply is sent:
|
|
510
|
+
"""
|
|
511
|
+
200 OK
|
|
512
|
+
"""
|
package/features/octets.feature
CHANGED
|
@@ -245,3 +245,62 @@ Feature: Octets directive family
|
|
|
245
245
|
|
|
246
246
|
Trailing slash is redundant
|
|
247
247
|
"""
|
|
248
|
+
|
|
249
|
+
Scenario: What sending a file takes
|
|
250
|
+
A file is not a value, so no schema states what may be sent — `octets:put` does, and a
|
|
251
|
+
client reads it where it reads everything else about the method.
|
|
252
|
+
|
|
253
|
+
When the following request is received:
|
|
254
|
+
"""
|
|
255
|
+
OPTIONS /media/jpeg-or-png/ HTTP/1.1
|
|
256
|
+
host: nex.toa.io
|
|
257
|
+
accept: application/yaml
|
|
258
|
+
"""
|
|
259
|
+
Then the following reply is sent:
|
|
260
|
+
"""
|
|
261
|
+
200 OK
|
|
262
|
+
Allow: POST
|
|
263
|
+
|
|
264
|
+
POST:
|
|
265
|
+
octets:
|
|
266
|
+
accept: image/jpeg,image/png
|
|
267
|
+
limit: 64MiB
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
Scenario: A limit is what a refusal reports
|
|
271
|
+
What a larger body is refused by is said the way the refusal says it.
|
|
272
|
+
|
|
273
|
+
When the following request is received:
|
|
274
|
+
"""
|
|
275
|
+
OPTIONS /limit-1kb/ HTTP/1.1
|
|
276
|
+
host: nex.toa.io
|
|
277
|
+
accept: application/yaml
|
|
278
|
+
"""
|
|
279
|
+
Then the following reply is sent:
|
|
280
|
+
"""
|
|
281
|
+
200 OK
|
|
282
|
+
|
|
283
|
+
POST:
|
|
284
|
+
octets:
|
|
285
|
+
limit: 1kb
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
Scenario: Reading a file takes nothing to say
|
|
289
|
+
Only sending one says anything a schema does not; `octets:get` and `octets:delete` are
|
|
290
|
+
the request they look like.
|
|
291
|
+
|
|
292
|
+
When the following request is received:
|
|
293
|
+
"""
|
|
294
|
+
OPTIONS /media/images/whatever/ HTTP/1.1
|
|
295
|
+
host: nex.toa.io
|
|
296
|
+
accept: application/yaml
|
|
297
|
+
"""
|
|
298
|
+
Then the following reply is sent:
|
|
299
|
+
"""
|
|
300
|
+
200 OK
|
|
301
|
+
Allow: GET
|
|
302
|
+
"""
|
|
303
|
+
And the reply does not contain:
|
|
304
|
+
"""
|
|
305
|
+
octets:
|
|
306
|
+
"""
|
|
@@ -5,6 +5,8 @@ import tsflow from 'cucumber-tsflow'
|
|
|
5
5
|
|
|
6
6
|
import * as boot from '@toa.io/boot'
|
|
7
7
|
import { type Connector } from '@toa.io/core'
|
|
8
|
+
import { environment } from '@toa.io/generic'
|
|
9
|
+
|
|
8
10
|
import { load as parse } from 'js-yaml'
|
|
9
11
|
import { Gateway } from './Gateway.js'
|
|
10
12
|
import { Workspace } from './Workspace.js'
|
|
@@ -68,7 +70,7 @@ export class Components {
|
|
|
68
70
|
/** What the deployment would tell the values service, as the scenario needs it. */
|
|
69
71
|
@given('the configuration values are deployed:')
|
|
70
72
|
public deployValues(yaml: string): void {
|
|
71
|
-
|
|
73
|
+
environment.set('TOA_CONFIGURATION_VALUES', JSON.stringify(parse(yaml)))
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
@given('the `{word}` is stopped')
|
|
@@ -87,7 +89,7 @@ export class Components {
|
|
|
87
89
|
|
|
88
90
|
await Promise.all(promises)
|
|
89
91
|
|
|
90
|
-
delete
|
|
92
|
+
environment.delete('TOA_CONFIGURATION_VALUES')
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
private async runComponent(name: string, manifest?: object): Promise<void> {
|
|
@@ -45,6 +45,10 @@ export class Database {
|
|
|
45
45
|
document.CREATED ??= Date.now()
|
|
46
46
|
document.UPDATED ??= document.CREATED
|
|
47
47
|
|
|
48
|
+
// a table states a timestamp as the entity carries it; the storage holds it as a date
|
|
49
|
+
for (const name of TIMESTAMPS)
|
|
50
|
+
if (typeof document[name] === 'number') document[name] = new Date(document[name])
|
|
51
|
+
|
|
48
52
|
documents.push(document)
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -96,5 +100,7 @@ export class Database {
|
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
const TIMESTAMPS = ['CREATED', 'UPDATED', 'DELETED']
|
|
104
|
+
|
|
99
105
|
/** Every fixture table names an `_id`, which is what a row is replaced by. */
|
|
100
|
-
type Document = Record<string, string | number | boolean | null> & { _id: string }
|
|
106
|
+
type Document = Record<string, string | number | boolean | Date | null> & { _id: string }
|
|
@@ -2,10 +2,12 @@ import tsflow from 'cucumber-tsflow'
|
|
|
2
2
|
|
|
3
3
|
import * as boot from '@toa.io/boot'
|
|
4
4
|
import { type Connector } from '@toa.io/core'
|
|
5
|
+
import { environment } from '@toa.io/generic'
|
|
6
|
+
|
|
5
7
|
import { load as parse } from 'js-yaml'
|
|
6
8
|
import { Factory } from '../../source/index.js'
|
|
7
9
|
import * as syntax from '../../source/RTD/syntax/index.js'
|
|
8
|
-
import { shortcuts } from '../../source/
|
|
10
|
+
import { shortcuts } from '../../source/shortcuts.js'
|
|
9
11
|
import { manifests } from './map.js'
|
|
10
12
|
import { PORT, PROBE } from './Parameters.js'
|
|
11
13
|
import type * as http from '../../source/HTTP/index.js'
|
|
@@ -26,7 +28,7 @@ export class Gateway {
|
|
|
26
28
|
if (annotation['/'] !== undefined) {
|
|
27
29
|
const tree = syntax.parse(annotation['/'], shortcuts)
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
environment.set('TOA_EXPOSITION', JSON.stringify(tree))
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const { debug, authorities, bouncer, ip, oauth, rpc, mcp } = annotation
|
|
@@ -46,7 +48,7 @@ export class Gateway {
|
|
|
46
48
|
|
|
47
49
|
if (mcp !== undefined) properties.mcp = mcp
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
environment.set('TOA_EXPOSITION_PROPERTIES', JSON.stringify(properties))
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -75,8 +77,8 @@ export class Gateway {
|
|
|
75
77
|
tree.routes.push(...node.routes)
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
environment.set('TOA_EXPOSITION', JSON.stringify(tree))
|
|
81
|
+
environment.set('TOA_EXPOSITION_PROPERTIES', JSON.stringify(DEFAULT_PROPERTIES))
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
@given('the `{word}` configuration:')
|
|
@@ -90,7 +92,7 @@ export class Gateway {
|
|
|
90
92
|
// scenario-scoped, as the secrets it may refer to are: a configuration left behind
|
|
91
93
|
// outlives the secrets it points at, and the next scenario cannot resolve them
|
|
92
94
|
this.written.push(key)
|
|
93
|
-
|
|
95
|
+
environment.set(key, JSON.stringify(configuration))
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/** The secrets a scenario's configuration refers to. */
|
|
@@ -100,7 +102,7 @@ export class Gateway {
|
|
|
100
102
|
|
|
101
103
|
for (const [name, value] of Object.entries(secrets)) {
|
|
102
104
|
this.written.push(SECRET + name)
|
|
103
|
-
|
|
105
|
+
environment.set(SECRET + name, value)
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -111,8 +113,11 @@ export class Gateway {
|
|
|
111
113
|
|
|
112
114
|
@given('the Gateway is running')
|
|
113
115
|
public async start(): Promise<void> {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (!environment.has('TOA_EXPOSITION'))
|
|
117
|
+
environment.set('TOA_EXPOSITION', DEFAULT_TREE)
|
|
118
|
+
|
|
119
|
+
if (!environment.has('TOA_EXPOSITION_PROPERTIES'))
|
|
120
|
+
environment.set('TOA_EXPOSITION_PROPERTIES', JSON.stringify(DEFAULT_PROPERTIES))
|
|
116
121
|
|
|
117
122
|
this.writeConfiguration()
|
|
118
123
|
|
|
@@ -141,14 +146,11 @@ export class Gateway {
|
|
|
141
146
|
|
|
142
147
|
/** What the running gateway was built from: a different value is a different deployment. */
|
|
143
148
|
private static signature(): string {
|
|
144
|
-
const configuration = Object.
|
|
145
|
-
.filter((key) => key.startsWith('TOA_CONFIGURATION_'))
|
|
146
|
-
.sort()
|
|
147
|
-
.map((key) => [key, process.env[key]])
|
|
149
|
+
const configuration = Object.entries(environment.entries('TOA_CONFIGURATION_')).sort()
|
|
148
150
|
|
|
149
151
|
return JSON.stringify([
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
environment.get('TOA_EXPOSITION'),
|
|
153
|
+
environment.get('TOA_EXPOSITION_PROPERTIES'),
|
|
152
154
|
process.env.__TESTING_EXPOSITION_BRANCH_TTL,
|
|
153
155
|
configuration
|
|
154
156
|
])
|
|
@@ -158,12 +160,12 @@ export class Gateway {
|
|
|
158
160
|
public async cleanup(): Promise<void> {
|
|
159
161
|
delete process.env.__TESTING_EXPOSITION_BRANCH_TTL
|
|
160
162
|
|
|
161
|
-
for (const key of this.written) delete
|
|
163
|
+
for (const key of this.written) environment.delete(key)
|
|
162
164
|
|
|
163
165
|
this.written = []
|
|
164
166
|
|
|
165
|
-
delete
|
|
166
|
-
delete
|
|
167
|
+
environment.delete('TOA_EXPOSITION')
|
|
168
|
+
environment.delete('TOA_EXPOSITION_PROPERTIES')
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
@afterAll()
|
|
@@ -178,11 +180,11 @@ export class Gateway {
|
|
|
178
180
|
const [name, namespace = 'default'] = id.split('.').reverse()
|
|
179
181
|
const key = `TOA_CONFIGURATION_${namespace.toUpperCase()}_${name.toUpperCase()}`
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
if (!environment.has(key)) environment.set(key, JSON.stringify(configuration))
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
for (const [name, value] of Object.entries(DEFAULT_SECRETS))
|
|
185
|
-
|
|
187
|
+
if (!environment.has(SECRET + name)) environment.set(SECRET + name, value)
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
|
|
@@ -208,7 +210,7 @@ const DEFAULT_PROPERTIES: Partial<http.Options> = {
|
|
|
208
210
|
port: PORT,
|
|
209
211
|
probe: PROBE,
|
|
210
212
|
// `npm run features:h2c` runs the whole suite over cleartext HTTP/2
|
|
211
|
-
protocol:
|
|
213
|
+
protocol: environment.get('TOA_EXPOSITION_PROTOCOL') === 'h2c' ? 'h2c' : 'h1'
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
// the identity components boot inside the gateway, and without a variable each would wait
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
import * as dotenv from 'dotenv'
|
|
3
3
|
import { setDefaultTimeout } from '@cucumber/cucumber'
|
|
4
|
+
import { environment as variables } from '@toa.io/generic'
|
|
4
5
|
|
|
5
6
|
dotenv.config({ path: join(import.meta.dirname, '.env') })
|
|
6
7
|
|
|
@@ -21,24 +22,29 @@ export class Parameters {
|
|
|
21
22
|
|
|
22
23
|
setDefaultTimeout(60 * 1000)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
variables.set('TOA_DEV', '1')
|
|
25
26
|
|
|
26
27
|
// a reply is checked against what the operation declares, so the suite runs Toa under the
|
|
27
28
|
// contract it asks applications to keep
|
|
28
|
-
|
|
29
|
+
if (!variables.has('TOA_ENV')) variables.set('TOA_ENV', 'local')
|
|
29
30
|
|
|
30
31
|
// the gateway answers for itself, as it does in a deployment: telemetry's probe tracks the
|
|
31
32
|
// nested composition, which connects before route discovery settles
|
|
32
|
-
|
|
33
|
+
if (!variables.has('TOA_TELEMETRY_READY'))
|
|
34
|
+
variables.set('TOA_TELEMETRY_READY', JSON.stringify(false))
|
|
33
35
|
|
|
34
36
|
// export traces to the local Tempo (`docker compose up tempo grafana`),
|
|
35
37
|
// unavailability of the endpoint is harmless
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (!variables.has('TOA_TELEMETRY_TRACES'))
|
|
39
|
+
variables.set(
|
|
40
|
+
'TOA_TELEMETRY_TRACES',
|
|
41
|
+
JSON.stringify({
|
|
42
|
+
exporters: {
|
|
43
|
+
console: null,
|
|
44
|
+
otlp: { endpoint: 'http://localhost:31061' }
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
)
|
|
42
48
|
|
|
43
49
|
/*
|
|
44
50
|
* The page the gateway serves at `/.discovery/`, pointed at a fixture: the scenarios are
|
|
@@ -144,10 +150,13 @@ const CLOUDINARY = {
|
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
153
|
+
variables.set(
|
|
154
|
+
'TOA_STORAGES',
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
octets: {
|
|
157
|
+
provider: 'tmp',
|
|
158
|
+
directory: Math.random().toString(36).substring(2)
|
|
159
|
+
},
|
|
160
|
+
...(environment === undefined ? {} : CLOUDINARY)
|
|
161
|
+
})
|
|
162
|
+
)
|
|
@@ -4,7 +4,7 @@ import tsflow from 'cucumber-tsflow'
|
|
|
4
4
|
|
|
5
5
|
import { Factory } from '@toa.io/extensions.realtime'
|
|
6
6
|
import * as boot from '@toa.io/boot'
|
|
7
|
-
import { match, timeout } from '@toa.io/generic'
|
|
7
|
+
import { environment, match, timeout } from '@toa.io/generic'
|
|
8
8
|
import { load as parse } from 'js-yaml'
|
|
9
9
|
import { Agent } from '@toa.io/agent'
|
|
10
10
|
import { Parameters } from './Parameters.js'
|
|
@@ -38,7 +38,7 @@ export class Realtime {
|
|
|
38
38
|
for (const [event, property] of Object.entries(annotation))
|
|
39
39
|
routes.push({ event, properties: [property] })
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
environment.set('TOA_REALTIME', JSON.stringify(routes))
|
|
42
42
|
|
|
43
43
|
const factory = new Factory(boot.host())
|
|
44
44
|
|
|
@@ -93,7 +93,7 @@ export class Realtime {
|
|
|
93
93
|
await this.instance.disconnect()
|
|
94
94
|
|
|
95
95
|
this.instance = null
|
|
96
|
-
|
|
96
|
+
environment.delete('TOA_REALTIME')
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.289",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Exposition",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@node-rs/bcrypt": "1.10.8",
|
|
22
22
|
"@simplewebauthn/server": "14.0.0",
|
|
23
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
24
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
23
|
+
"@toa.io/core": "1.0.0-alpha.289",
|
|
24
|
+
"@toa.io/generic": "1.0.0-alpha.289",
|
|
25
|
+
"@toa.io/schemas": "1.0.0-alpha.289",
|
|
26
26
|
"content-type": "3.0.0",
|
|
27
27
|
"jose": "6.2.10",
|
|
28
28
|
"js-yaml": "5.4.1",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"@types/cors": "2.8.19",
|
|
61
61
|
"@types/negotiator": "0.6.5"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "4fcb482beac5896305d8084cdbc1d5fcf372d108"
|
|
64
64
|
}
|
package/source/Directive.ts
CHANGED
|
@@ -246,14 +246,3 @@ interface Stage {
|
|
|
246
246
|
depart: SpanOptions
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
export const shortcuts: RTD.syntax.Shortcuts = new Map([
|
|
250
|
-
['anonymous', 'auth:anonymous'],
|
|
251
|
-
['anyone', 'auth:anyone'],
|
|
252
|
-
['id', 'auth:id'],
|
|
253
|
-
['role', 'auth:role'],
|
|
254
|
-
['rule', 'auth:rule'],
|
|
255
|
-
['incept', 'auth:incept'],
|
|
256
|
-
['input', 'io:input'],
|
|
257
|
-
['output', 'io:output'],
|
|
258
|
-
['languages', 'map:languages']
|
|
259
|
-
])
|
|
@@ -11,13 +11,23 @@ const HTML = 'text/html'
|
|
|
11
11
|
* and `/` is usually not one of those — a person who typed the address into a browser is
|
|
12
12
|
* looking for something to look at, and the page is the only thing here that is one.
|
|
13
13
|
*
|
|
14
|
-
* A client that
|
|
15
|
-
*
|
|
14
|
+
* A client that asked for something in particular gets what it asked for: only an `accept`
|
|
15
|
+
* that prefers a page, or one that names no preference at all, is sent here. The second is
|
|
16
|
+
* what an unfurler sends — several ask for anything, and a link to an application would
|
|
17
|
+
* otherwise show nothing — and what `curl` sends, which the page serves no worse than the
|
|
18
|
+
* `405` it used to get.
|
|
16
19
|
*/
|
|
17
20
|
export function looking(context: Context): OutgoingMessage | null {
|
|
18
21
|
if (context.request.method !== 'GET' || context.procedural) return null
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
const accept = context.request.headers.accept
|
|
24
|
+
|
|
25
|
+
if (accept !== undefined && accept.trim() !== ANYTHING) {
|
|
26
|
+
if (new Negotiator(context.request).mediaType([...types, HTML]) !== HTML) return null
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
return { status: 302, headers: new Headers({ location: DISCOVERY + '/' }) }
|
|
23
30
|
}
|
|
31
|
+
|
|
32
|
+
/** An `accept` that names nothing, which is the same as naming none. */
|
|
33
|
+
const ANYTHING = '*/*'
|