@toa.io/extensions.exposition 0.22.1 → 0.24.0-alpha.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.
- package/components/identity.basic/source/authenticate.ts +3 -2
- package/components/identity.basic/source/transit.ts +4 -3
- package/components/octets.storage/manifest.toa.yaml +26 -0
- package/components/octets.storage/operations/delete.js +7 -0
- package/components/octets.storage/operations/fetch.js +46 -0
- package/components/octets.storage/operations/get.js +7 -0
- package/components/octets.storage/operations/list.js +7 -0
- package/components/octets.storage/operations/permute.js +7 -0
- package/components/octets.storage/operations/store.js +11 -0
- package/cucumber.js +0 -1
- package/documentation/access.md +2 -3
- package/documentation/cache.md +42 -0
- package/documentation/octets.md +196 -0
- package/documentation/protocol.md +49 -5
- package/documentation/tree.md +1 -5
- package/features/access.feature +1 -0
- package/features/cache.feature +160 -0
- package/features/errors.feature +18 -0
- package/features/identity.basic.feature +2 -0
- package/features/octets.feature +295 -0
- package/features/octets.workflows.feature +114 -0
- package/features/routes.feature +40 -0
- package/features/steps/HTTP.ts +54 -6
- package/features/steps/Parameters.ts +5 -2
- package/features/steps/Workspace.ts +8 -5
- package/features/steps/components/octets.tester/manifest.toa.yaml +15 -0
- package/features/steps/components/octets.tester/operations/bar.js +12 -0
- package/features/steps/components/octets.tester/operations/baz.js +11 -0
- package/features/steps/components/octets.tester/operations/diversify.js +14 -0
- package/features/steps/components/octets.tester/operations/err.js +16 -0
- package/features/steps/components/octets.tester/operations/foo.js +7 -0
- package/features/steps/components/octets.tester/operations/lenna.png +0 -0
- package/features/steps/components/pots/manifest.toa.yaml +1 -1
- package/features/streams.feature +5 -1
- package/package.json +16 -9
- package/readme.md +8 -5
- package/schemas/octets/context.cos.yaml +1 -0
- package/schemas/octets/delete.cos.yaml +1 -0
- package/schemas/octets/fetch.cos.yaml +3 -0
- package/schemas/octets/list.cos.yaml +1 -0
- package/schemas/octets/permute.cos.yaml +1 -0
- package/schemas/octets/store.cos.yaml +3 -0
- package/source/Gateway.ts +9 -4
- package/source/HTTP/Server.fixtures.ts +2 -6
- package/source/HTTP/Server.test.ts +9 -31
- package/source/HTTP/Server.ts +55 -28
- package/source/HTTP/exceptions.ts +2 -12
- package/source/HTTP/formats/index.ts +7 -4
- package/source/HTTP/formats/json.ts +3 -0
- package/source/HTTP/formats/msgpack.ts +3 -0
- package/source/HTTP/formats/text.ts +3 -0
- package/source/HTTP/formats/yaml.ts +3 -0
- package/source/HTTP/messages.test.ts +3 -49
- package/source/HTTP/messages.ts +60 -35
- package/source/RTD/Route.ts +1 -1
- package/source/RTD/segment.ts +2 -1
- package/source/RTD/syntax/parse.ts +2 -1
- package/source/Remotes.ts +8 -0
- package/source/Tenant.ts +5 -0
- package/source/directives/auth/Family.ts +26 -22
- package/source/directives/auth/Rule.ts +1 -1
- package/source/directives/cache/Control.ts +59 -0
- package/source/directives/cache/Exact.ts +7 -0
- package/source/directives/cache/Family.ts +36 -0
- package/source/directives/cache/index.ts +3 -0
- package/source/directives/cache/types.ts +9 -0
- package/source/directives/index.ts +3 -1
- package/source/directives/octets/Context.ts +18 -0
- package/source/directives/octets/Delete.ts +32 -0
- package/source/directives/octets/Family.ts +68 -0
- package/source/directives/octets/Fetch.ts +85 -0
- package/source/directives/octets/List.ts +32 -0
- package/source/directives/octets/Permute.ts +37 -0
- package/source/directives/octets/Store.ts +158 -0
- package/source/directives/octets/index.ts +3 -0
- package/source/directives/octets/schemas.ts +12 -0
- package/source/directives/octets/types.ts +13 -0
- package/transpiled/Annotation.d.ts +7 -0
- package/transpiled/Annotation.js +3 -0
- package/transpiled/Annotation.js.map +1 -0
- package/transpiled/Branch.d.ts +7 -0
- package/transpiled/Branch.js +3 -0
- package/transpiled/Branch.js.map +1 -0
- package/transpiled/Composition.d.ts +14 -0
- package/transpiled/Composition.js +43 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Context.d.ts +5 -0
- package/transpiled/Context.js +3 -0
- package/transpiled/Context.js.map +1 -0
- package/transpiled/Directive.d.ts +32 -0
- package/transpiled/Directive.js +76 -0
- package/transpiled/Directive.js.map +1 -0
- package/transpiled/Endpoint.d.ts +20 -0
- package/transpiled/Endpoint.js +45 -0
- package/transpiled/Endpoint.js.map +1 -0
- package/transpiled/Factory.d.ts +10 -0
- package/transpiled/Factory.js +66 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Gateway.d.ts +19 -0
- package/transpiled/Gateway.js +92 -0
- package/transpiled/Gateway.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +22 -0
- package/transpiled/HTTP/Server.fixtures.d.ts +11 -0
- package/transpiled/HTTP/Server.fixtures.js +32 -0
- package/transpiled/HTTP/Server.fixtures.js.map +1 -0
- package/transpiled/HTTP/Server.js +131 -0
- package/transpiled/HTTP/Server.js.map +1 -0
- package/transpiled/HTTP/exceptions.d.ts +34 -0
- package/transpiled/HTTP/exceptions.js +71 -0
- package/transpiled/HTTP/exceptions.js.map +1 -0
- package/transpiled/HTTP/formats/index.d.ts +10 -0
- package/transpiled/HTTP/formats/index.js +38 -0
- package/transpiled/HTTP/formats/index.js.map +1 -0
- package/transpiled/HTTP/formats/json.d.ts +6 -0
- package/transpiled/HTTP/formats/json.js +17 -0
- package/transpiled/HTTP/formats/json.js.map +1 -0
- package/transpiled/HTTP/formats/msgpack.d.ts +6 -0
- package/transpiled/HTTP/formats/msgpack.js +38 -0
- package/transpiled/HTTP/formats/msgpack.js.map +1 -0
- package/transpiled/HTTP/formats/text.d.ts +6 -0
- package/transpiled/HTTP/formats/text.js +15 -0
- package/transpiled/HTTP/formats/text.js.map +1 -0
- package/transpiled/HTTP/formats/yaml.d.ts +6 -0
- package/transpiled/HTTP/formats/yaml.js +41 -0
- package/transpiled/HTTP/formats/yaml.js.map +1 -0
- package/transpiled/HTTP/index.d.ts +3 -0
- package/transpiled/HTTP/index.js +20 -0
- package/transpiled/HTTP/index.js.map +1 -0
- package/transpiled/HTTP/messages.d.ts +28 -0
- package/transpiled/HTTP/messages.js +70 -0
- package/transpiled/HTTP/messages.js.map +1 -0
- package/transpiled/Mapping.d.ts +8 -0
- package/transpiled/Mapping.js +38 -0
- package/transpiled/Mapping.js.map +1 -0
- package/transpiled/Query.d.ts +13 -0
- package/transpiled/Query.js +107 -0
- package/transpiled/Query.js.map +1 -0
- package/transpiled/RTD/Context.d.ts +11 -0
- package/transpiled/RTD/Context.js +3 -0
- package/transpiled/RTD/Context.js.map +1 -0
- package/transpiled/RTD/Directives.d.ts +7 -0
- package/transpiled/RTD/Directives.js +3 -0
- package/transpiled/RTD/Directives.js.map +1 -0
- package/transpiled/RTD/Endpoint.d.ts +9 -0
- package/transpiled/RTD/Endpoint.js +3 -0
- package/transpiled/RTD/Endpoint.js.map +1 -0
- package/transpiled/RTD/Match.d.ts +11 -0
- package/transpiled/RTD/Match.js +3 -0
- package/transpiled/RTD/Match.js.map +1 -0
- package/transpiled/RTD/Method.d.ts +9 -0
- package/transpiled/RTD/Method.js +16 -0
- package/transpiled/RTD/Method.js.map +1 -0
- package/transpiled/RTD/Node.d.ts +21 -0
- package/transpiled/RTD/Node.js +61 -0
- package/transpiled/RTD/Node.js.map +1 -0
- package/transpiled/RTD/Route.d.ts +14 -0
- package/transpiled/RTD/Route.js +49 -0
- package/transpiled/RTD/Route.js.map +1 -0
- package/transpiled/RTD/Tree.d.ts +14 -0
- package/transpiled/RTD/Tree.js +40 -0
- package/transpiled/RTD/Tree.js.map +1 -0
- package/transpiled/RTD/factory.d.ts +6 -0
- package/transpiled/RTD/factory.js +36 -0
- package/transpiled/RTD/factory.js.map +1 -0
- package/transpiled/RTD/index.d.ts +8 -0
- package/transpiled/RTD/index.js +38 -0
- package/transpiled/RTD/index.js.map +1 -0
- package/transpiled/RTD/segment.d.ts +8 -0
- package/transpiled/RTD/segment.js +25 -0
- package/transpiled/RTD/segment.js.map +1 -0
- package/transpiled/RTD/syntax/index.d.ts +2 -0
- package/transpiled/RTD/syntax/index.js +19 -0
- package/transpiled/RTD/syntax/index.js.map +1 -0
- package/transpiled/RTD/syntax/parse.d.ts +4 -0
- package/transpiled/RTD/syntax/parse.js +128 -0
- package/transpiled/RTD/syntax/parse.js.map +1 -0
- package/transpiled/RTD/syntax/types.d.ts +41 -0
- package/transpiled/RTD/syntax/types.js +5 -0
- package/transpiled/RTD/syntax/types.js.map +1 -0
- package/transpiled/Remotes.d.ts +9 -0
- package/transpiled/Remotes.js +25 -0
- package/transpiled/Remotes.js.map +1 -0
- package/transpiled/Tenant.d.ts +13 -0
- package/transpiled/Tenant.js +34 -0
- package/transpiled/Tenant.js.map +1 -0
- package/transpiled/deployment.d.ts +3 -0
- package/transpiled/deployment.js +67 -0
- package/transpiled/deployment.js.map +1 -0
- package/transpiled/directives/auth/Anonymous.d.ts +6 -0
- package/transpiled/directives/auth/Anonymous.js +17 -0
- package/transpiled/directives/auth/Anonymous.js.map +1 -0
- package/transpiled/directives/auth/Echo.d.ts +6 -0
- package/transpiled/directives/auth/Echo.js +13 -0
- package/transpiled/directives/auth/Echo.js.map +1 -0
- package/transpiled/directives/auth/Family.d.ts +20 -0
- package/transpiled/directives/auth/Family.js +118 -0
- package/transpiled/directives/auth/Family.js.map +1 -0
- package/transpiled/directives/auth/Id.d.ts +7 -0
- package/transpiled/directives/auth/Id.js +17 -0
- package/transpiled/directives/auth/Id.js.map +1 -0
- package/transpiled/directives/auth/Incept.d.ts +10 -0
- package/transpiled/directives/auth/Incept.js +58 -0
- package/transpiled/directives/auth/Incept.js.map +1 -0
- package/transpiled/directives/auth/Role.d.ts +11 -0
- package/transpiled/directives/auth/Role.js +44 -0
- package/transpiled/directives/auth/Role.js.map +1 -0
- package/transpiled/directives/auth/Rule.d.ts +9 -0
- package/transpiled/directives/auth/Rule.js +22 -0
- package/transpiled/directives/auth/Rule.js.map +1 -0
- package/transpiled/directives/auth/Scheme.d.ts +7 -0
- package/transpiled/directives/auth/Scheme.js +47 -0
- package/transpiled/directives/auth/Scheme.js.map +1 -0
- package/transpiled/directives/auth/index.d.ts +2 -0
- package/transpiled/directives/auth/index.js +7 -0
- package/transpiled/directives/auth/index.js.map +1 -0
- package/transpiled/directives/auth/schemes.d.ts +3 -0
- package/transpiled/directives/auth/schemes.js +9 -0
- package/transpiled/directives/auth/schemes.js.map +1 -0
- package/transpiled/directives/auth/split.d.ts +2 -0
- package/transpiled/directives/auth/split.js +38 -0
- package/transpiled/directives/auth/split.js.map +1 -0
- package/transpiled/directives/auth/types.d.ts +31 -0
- package/transpiled/directives/auth/types.js +3 -0
- package/transpiled/directives/auth/types.js.map +1 -0
- package/transpiled/directives/cache/Control.d.ts +9 -0
- package/transpiled/directives/cache/Control.js +42 -0
- package/transpiled/directives/cache/Control.js.map +1 -0
- package/transpiled/directives/cache/Exact.d.ts +4 -0
- package/transpiled/directives/cache/Exact.js +11 -0
- package/transpiled/directives/cache/Exact.js.map +1 -0
- package/transpiled/directives/cache/Family.d.ts +12 -0
- package/transpiled/directives/cache/Family.js +26 -0
- package/transpiled/directives/cache/Family.js.map +1 -0
- package/transpiled/directives/cache/index.d.ts +2 -0
- package/transpiled/directives/cache/index.js +7 -0
- package/transpiled/directives/cache/index.js.map +1 -0
- package/transpiled/directives/cache/types.d.ts +7 -0
- package/transpiled/directives/cache/types.js +3 -0
- package/transpiled/directives/cache/types.js.map +1 -0
- package/transpiled/directives/dev/Family.d.ts +10 -0
- package/transpiled/directives/dev/Family.js +27 -0
- package/transpiled/directives/dev/Family.js.map +1 -0
- package/transpiled/directives/dev/Stub.d.ts +7 -0
- package/transpiled/directives/dev/Stub.js +14 -0
- package/transpiled/directives/dev/Stub.js.map +1 -0
- package/transpiled/directives/dev/Throw.d.ts +7 -0
- package/transpiled/directives/dev/Throw.js +14 -0
- package/transpiled/directives/dev/Throw.js.map +1 -0
- package/transpiled/directives/dev/index.d.ts +2 -0
- package/transpiled/directives/dev/index.js +7 -0
- package/transpiled/directives/dev/index.js.map +1 -0
- package/transpiled/directives/dev/types.d.ts +4 -0
- package/transpiled/directives/dev/types.js +3 -0
- package/transpiled/directives/dev/types.js.map +1 -0
- package/transpiled/directives/index.d.ts +2 -0
- package/transpiled/directives/index.js +12 -0
- package/transpiled/directives/index.js.map +1 -0
- package/transpiled/directives/octets/Context.d.ts +8 -0
- package/transpiled/directives/octets/Context.js +40 -0
- package/transpiled/directives/octets/Context.js.map +1 -0
- package/transpiled/directives/octets/Delete.d.ts +10 -0
- package/transpiled/directives/octets/Delete.js +47 -0
- package/transpiled/directives/octets/Delete.js.map +1 -0
- package/transpiled/directives/octets/Family.d.ts +12 -0
- package/transpiled/directives/octets/Family.js +49 -0
- package/transpiled/directives/octets/Family.js.map +1 -0
- package/transpiled/directives/octets/Fetch.d.ts +18 -0
- package/transpiled/directives/octets/Fetch.js +77 -0
- package/transpiled/directives/octets/Fetch.js.map +1 -0
- package/transpiled/directives/octets/List.d.ts +10 -0
- package/transpiled/directives/octets/List.js +47 -0
- package/transpiled/directives/octets/List.js.map +1 -0
- package/transpiled/directives/octets/Permute.d.ts +10 -0
- package/transpiled/directives/octets/Permute.js +51 -0
- package/transpiled/directives/octets/Permute.js.map +1 -0
- package/transpiled/directives/octets/Store.d.ts +33 -0
- package/transpiled/directives/octets/Store.js +124 -0
- package/transpiled/directives/octets/Store.js.map +1 -0
- package/transpiled/directives/octets/index.d.ts +2 -0
- package/transpiled/directives/octets/index.js +7 -0
- package/transpiled/directives/octets/index.js.map +1 -0
- package/transpiled/directives/octets/schemas.d.ts +6 -0
- package/transpiled/directives/octets/schemas.js +17 -0
- package/transpiled/directives/octets/schemas.js.map +1 -0
- package/transpiled/directives/octets/types.d.ts +9 -0
- package/transpiled/directives/octets/types.js +3 -0
- package/transpiled/directives/octets/types.js.map +1 -0
- package/transpiled/discovery.d.ts +1 -0
- package/transpiled/discovery.js +3 -0
- package/transpiled/discovery.js.map +1 -0
- package/transpiled/exceptions.d.ts +2 -0
- package/transpiled/exceptions.js +39 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/index.d.ts +5 -0
- package/transpiled/index.js +12 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +3 -0
- package/transpiled/manifest.js +61 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/root.d.ts +2 -0
- package/transpiled/root.js +39 -0
- package/transpiled/root.js.map +1 -0
- package/transpiled/schemas.d.ts +3 -0
- package/transpiled/schemas.js +14 -0
- package/transpiled/schemas.js.map +1 -0
- package/transpiled/tsconfig.tsbuildinfo +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { atob } from 'buffer'
|
|
2
2
|
import { compare } from 'bcryptjs'
|
|
3
3
|
import { type Query, type Maybe } from '@toa.io/types'
|
|
4
|
+
import { Err } from 'error-value'
|
|
4
5
|
import { type Context } from './types'
|
|
5
6
|
|
|
6
7
|
export async function computation (input: string, context: Context): Promise<Maybe<Output>> {
|
|
@@ -21,8 +22,8 @@ export async function computation (input: string, context: Context): Promise<May
|
|
|
21
22
|
else return ERR_PASSWORD_MISMATCH
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const ERR_NOT_FOUND =
|
|
25
|
-
const ERR_PASSWORD_MISMATCH =
|
|
25
|
+
const ERR_NOT_FOUND = Err('NOT_FOUND')
|
|
26
|
+
const ERR_PASSWORD_MISMATCH = Err('PASSWORD_MISMATCH')
|
|
26
27
|
|
|
27
28
|
interface Output {
|
|
28
29
|
identity: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { genSalt, hash } from 'bcryptjs'
|
|
2
2
|
import { type Maybe, type Operation } from '@toa.io/types'
|
|
3
|
+
import { Err } from 'error-value'
|
|
3
4
|
import { type Context, type Entity, type TransitInput, type TransitOutput } from './types'
|
|
4
5
|
|
|
5
6
|
export class Transition implements Operation {
|
|
@@ -60,8 +61,8 @@ function invalid (value: string, expressions: RegExp[]): boolean {
|
|
|
60
61
|
return expressions.some((expression) => !expression.test(value))
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
const ERR_PRINCIPAL_LOCKED =
|
|
64
|
-
const ERR_INVALID_USERNAME =
|
|
65
|
-
const ERR_INVALID_PASSWORD =
|
|
64
|
+
const ERR_PRINCIPAL_LOCKED = Err('PRINCIPAL_LOCKED', 'Principal username cannot be changed.')
|
|
65
|
+
const ERR_INVALID_USERNAME = Err('INVALID_USERNAME', 'Username is not meeting the requirements.')
|
|
66
|
+
const ERR_INVALID_PASSWORD = Err('INVALID_PASSWORD', 'Password is not meeting the requirements.')
|
|
66
67
|
|
|
67
68
|
type Tokens = Context['remote']['identity']['tokens']
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
namespace: octets
|
|
2
|
+
name: storage
|
|
3
|
+
|
|
4
|
+
storages: ~
|
|
5
|
+
|
|
6
|
+
operations:
|
|
7
|
+
store:
|
|
8
|
+
bindings: ~
|
|
9
|
+
input:
|
|
10
|
+
storage*: string
|
|
11
|
+
request*: ~
|
|
12
|
+
accept: string
|
|
13
|
+
fetch: &simple
|
|
14
|
+
bindings: ~
|
|
15
|
+
input:
|
|
16
|
+
storage*: string
|
|
17
|
+
path*: string
|
|
18
|
+
get: *simple
|
|
19
|
+
list: *simple
|
|
20
|
+
delete: *simple
|
|
21
|
+
permute:
|
|
22
|
+
bindings: ~
|
|
23
|
+
input:
|
|
24
|
+
storage*: string
|
|
25
|
+
path*: string
|
|
26
|
+
list*: [string]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { posix } = require('node:path')
|
|
4
|
+
const { Err } = require('error-value')
|
|
5
|
+
|
|
6
|
+
async function fetch (input, context) {
|
|
7
|
+
const storage = context.storages[input.storage]
|
|
8
|
+
const basename = posix.basename(input.path)
|
|
9
|
+
const path = posix.dirname(input.path)
|
|
10
|
+
const [id, suffix] = split(basename)
|
|
11
|
+
const entry = await storage.get(posix.join(path, id))
|
|
12
|
+
|
|
13
|
+
if (entry instanceof Error)
|
|
14
|
+
return entry
|
|
15
|
+
|
|
16
|
+
let variant
|
|
17
|
+
|
|
18
|
+
if (suffix !== undefined) {
|
|
19
|
+
variant = entry.variants.find((variant) => variant.name === suffix)
|
|
20
|
+
|
|
21
|
+
if (variant === undefined)
|
|
22
|
+
return NOT_FOUND
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const stream = await storage.fetch(input.path)
|
|
26
|
+
|
|
27
|
+
if (stream instanceof Error)
|
|
28
|
+
return stream
|
|
29
|
+
|
|
30
|
+
const { type, size } = variant ?? entry
|
|
31
|
+
|
|
32
|
+
return { stream, checksum: entry.id, type, size }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function split (basename) {
|
|
36
|
+
const dot = basename.indexOf('.')
|
|
37
|
+
|
|
38
|
+
if (dot === -1)
|
|
39
|
+
return [basename, undefined]
|
|
40
|
+
else
|
|
41
|
+
return [basename.slice(0, dot), basename.slice(dot + 1)]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const NOT_FOUND = Err('NOT_FOUND')
|
|
45
|
+
|
|
46
|
+
exports.effect = fetch
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function store (input, context) {
|
|
4
|
+
const { storage, request } = input
|
|
5
|
+
const path = request.path
|
|
6
|
+
const claim = request.headers['content-type']
|
|
7
|
+
|
|
8
|
+
return context.storages[storage].put(path, request, { claim, accept: input.accept })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.effect = store
|
package/cucumber.js
CHANGED
package/documentation/access.md
CHANGED
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
The Authorization is implemented as a set of [RTD Directives](tree.md#directives).
|
|
15
15
|
|
|
16
|
-
Directives are executed in a predetermined order until one of them grants access to a resource.
|
|
17
|
-
none of the
|
|
18
|
-
directives grants access, then the Authorization interrupts request processing and responds with an
|
|
16
|
+
Directives are executed in a predetermined order until one of them grants access to a resource.
|
|
17
|
+
If none of the directives grants access, then the Authorization interrupts request processing and responds with an
|
|
19
18
|
authorization error.
|
|
20
19
|
|
|
21
20
|
> The Authorization directive provider is named `authorization`,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Caching
|
|
2
|
+
|
|
3
|
+
Directive family `cache` implements the
|
|
4
|
+
HTTP [Cache-Control](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9).
|
|
5
|
+
|
|
6
|
+
## `cache:control`
|
|
7
|
+
|
|
8
|
+
Sets the value of the `Cache-Control` header
|
|
9
|
+
for [successful responses](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2)
|
|
10
|
+
to [safe HTTP methods](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP).
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
/:
|
|
14
|
+
GET:
|
|
15
|
+
cache:control: max-age=60000
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Implicit modifications
|
|
19
|
+
|
|
20
|
+
In terms of security, the following implicit modifications are made to the `Cache-Control` header:
|
|
21
|
+
|
|
22
|
+
- If it contains the `public` directive without `no-cache` and the request is authenticated,
|
|
23
|
+
the `no-cache` directive is added.
|
|
24
|
+
This is done to prevent the storage of authentication tokens in shared caches.
|
|
25
|
+
- If it does not contain the `private` directive and the request is authenticated, the `private`
|
|
26
|
+
directive is added.
|
|
27
|
+
This is to prevent the storage of private data in shared caches.
|
|
28
|
+
|
|
29
|
+
## `cache:exact`
|
|
30
|
+
|
|
31
|
+
Same as `cache:control` without implicit modifications.
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
/:
|
|
35
|
+
GET:
|
|
36
|
+
cache:exact: public, max-age=60000
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## References
|
|
40
|
+
|
|
41
|
+
- HTTP 14.9.1 [What is cacheable](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9.1)
|
|
42
|
+
- See also [features](/extensions/exposition/features/cache.feature)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# BLOBs
|
|
2
|
+
|
|
3
|
+
The `octets` directive family implements operations with BLOBs, using
|
|
4
|
+
the [Storages extention](/extensions/storages).
|
|
5
|
+
The most common use case is to handle file uploads, downloads, and processing.
|
|
6
|
+
|
|
7
|
+
## `octets:context`
|
|
8
|
+
|
|
9
|
+
Sets the [storage name](/extensions/storages/readme.md#annotation) to be used for the `octets`
|
|
10
|
+
directives under the current RTD Node.
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
/images:
|
|
14
|
+
octets:context: images
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## `octets:store`
|
|
18
|
+
|
|
19
|
+
Stores the content of the request body into a storage, under the request path with
|
|
20
|
+
specified `content-type`.
|
|
21
|
+
|
|
22
|
+
If request's `content-type` is not acceptable, or if the request body does not pass
|
|
23
|
+
the [validation](/extensions/storages/readme.md#async-putpath-string-stream-readable-type-typecontrol-maybeentry),
|
|
24
|
+
the request is rejected with a `415 Unsupported Media Type` response.
|
|
25
|
+
|
|
26
|
+
The value of the directive is an object with the following properties:
|
|
27
|
+
|
|
28
|
+
- `accept`: a media type or an array of media types that are acceptable.
|
|
29
|
+
If the `accept` property is not specified, any media type is acceptable (which is the default).
|
|
30
|
+
- `workflow`: [workflow](#workflows) to be executed once the content is successfully stored.
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
/images:
|
|
34
|
+
octets:context: images
|
|
35
|
+
POST:
|
|
36
|
+
octets:store:
|
|
37
|
+
accept:
|
|
38
|
+
- image/jpeg
|
|
39
|
+
- image/png
|
|
40
|
+
- video/*
|
|
41
|
+
workflow:
|
|
42
|
+
resize: images.resize
|
|
43
|
+
analyze: images.analyze
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Workflows
|
|
47
|
+
|
|
48
|
+
A workflow is a list of endpoints to be called.
|
|
49
|
+
The following input will be passed to each endpoint:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
storage: string
|
|
53
|
+
path: string
|
|
54
|
+
entry: Entry
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
See [Entry](/extensions/storages/readme.md#entry) and an
|
|
58
|
+
example [workflow step processor](../features/steps/components/octets.tester).
|
|
59
|
+
|
|
60
|
+
A _workflow unit_ is an object with keys referencing the workflow step identifier, and an endpoint
|
|
61
|
+
as value.
|
|
62
|
+
Steps within a workflow unit are executed in parallel.
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
octets:store:
|
|
66
|
+
workflow:
|
|
67
|
+
resize: images.resize
|
|
68
|
+
analyze: images.analyze
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A workflow can be a single unit, or an array of units.
|
|
72
|
+
If it's an array, the workflow units are executed in sequence.
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
octets:store:
|
|
76
|
+
workflow:
|
|
77
|
+
- optimize: images.optimize # executed first
|
|
78
|
+
- resize: images.resize # executed second
|
|
79
|
+
analyze: images.analyze # executed in parallel with `resize`
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If one of the workflow units returns an error, the execution of the workflow is interrupted.
|
|
83
|
+
|
|
84
|
+
### Response
|
|
85
|
+
|
|
86
|
+
The response of the `octets:store` directive is the created Entry.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
201 Created
|
|
90
|
+
content-type: application/yaml
|
|
91
|
+
|
|
92
|
+
id: eecd837c
|
|
93
|
+
type: image/jpeg
|
|
94
|
+
created: 1698004822358
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If the `octets:store` directive contains a `workflow`, the response
|
|
98
|
+
is [multipart](protocol.md#multipart-types).
|
|
99
|
+
The first part represents the created Entry, which is sent immediately after the BLOB is stored,
|
|
100
|
+
while subsequent parts are results from the workflow endpoints, sent as soon as they are available.
|
|
101
|
+
|
|
102
|
+
In case a workflow endpoint returns an `Error`, the error part is sent, and the response is closed.
|
|
103
|
+
Error's properties are added to the error part, among with the `step` identifier.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
201 Created
|
|
107
|
+
content-type: multipart/yaml; boundary=cut
|
|
108
|
+
|
|
109
|
+
--cut
|
|
110
|
+
id: eecd837c
|
|
111
|
+
type: image/jpeg
|
|
112
|
+
created: 1698004822358
|
|
113
|
+
--cut
|
|
114
|
+
optimize: null
|
|
115
|
+
--cut
|
|
116
|
+
error:
|
|
117
|
+
step: resize
|
|
118
|
+
code: TOO_SMALL
|
|
119
|
+
message: Image is too small
|
|
120
|
+
--cut--
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## `octets:fetch`
|
|
124
|
+
|
|
125
|
+
Fetches the content of a stored BLOB corresponding to the request path, and returns it as the
|
|
126
|
+
response body with the corresponding `content-type`, `content-length`
|
|
127
|
+
and `etag` ([conditional GET](https://datatracker.ietf.org/doc/html/rfc2616#section-9.3) is
|
|
128
|
+
also supported).
|
|
129
|
+
The `accept` request header is disregarded.
|
|
130
|
+
|
|
131
|
+
The value of the directive is an object with the following properties:
|
|
132
|
+
|
|
133
|
+
- `meta`: `boolean` indicating whether an Entry is accessible.
|
|
134
|
+
Defaults to `false`.
|
|
135
|
+
- `blob`: `boolean` indicating whether the original BLOB is accessible,
|
|
136
|
+
[BLOB variant](/extensions/storages/readme.md#async-fetchpath-string-maybereadable) must be
|
|
137
|
+
specified in the path otherwise.
|
|
138
|
+
Defaults to `true`.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
/images:
|
|
142
|
+
octets:context: images
|
|
143
|
+
/*:
|
|
144
|
+
GET:
|
|
145
|
+
octets:fetch:
|
|
146
|
+
blob: false # prevent access to the original BLOB
|
|
147
|
+
meta: true # allow access to an Entry
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
To access an Entry, the request path must be suffixed with `:meta`:
|
|
151
|
+
|
|
152
|
+
```http
|
|
153
|
+
GET /images/eecd837c:meta HTTP/1.1
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `octets:fetch: ~` declaration is equivalent to defaults.
|
|
157
|
+
|
|
158
|
+
## `octets:list`
|
|
159
|
+
|
|
160
|
+
Lists the entries stored under the request path.
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
/images:
|
|
164
|
+
octets:context: images
|
|
165
|
+
GET:
|
|
166
|
+
octets:list: ~
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Responds with a list of entry identifiers.
|
|
170
|
+
|
|
171
|
+
## `octets:delete`
|
|
172
|
+
|
|
173
|
+
Delete the entry corresponding to the request path.
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
/images:
|
|
177
|
+
octets:context: images
|
|
178
|
+
DELETE:
|
|
179
|
+
octets:delete: ~
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## `octets:permute`
|
|
183
|
+
|
|
184
|
+
Performs
|
|
185
|
+
a [permutation](/extensions/storages/readme.md#async-permutepath-string-ids-string-maybevoid) on the
|
|
186
|
+
entries
|
|
187
|
+
under the request path.
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
/images:
|
|
191
|
+
octets:context: images
|
|
192
|
+
PUT:
|
|
193
|
+
octets:permute: ~
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The request body must be a list of entry identifiers.
|
|
@@ -4,15 +4,59 @@
|
|
|
4
4
|
|
|
5
5
|
The following media types are supported for both requests and responses:
|
|
6
6
|
|
|
7
|
-
- `application/json`
|
|
8
|
-
- `application/yaml` using [js-yaml](https://github.com/nodeca/js-yaml)
|
|
9
7
|
- `application/msgpack` using [msgpackr](https://github.com/kriszyp/msgpackr)
|
|
8
|
+
- `application/yaml` using [js-yaml](https://github.com/nodeca/js-yaml)
|
|
9
|
+
- `application/json`
|
|
10
10
|
- `text/plain`
|
|
11
11
|
|
|
12
12
|
The response format is determined by content negotiation
|
|
13
13
|
using [negotiator](https://github.com/jshttp/negotiator).
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```http
|
|
16
|
+
GET / HTTP/1.1
|
|
17
|
+
accept: application/yaml
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
200 OK
|
|
22
|
+
content-type: application/yaml
|
|
23
|
+
|
|
24
|
+
foo: bar
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Multipart types
|
|
28
|
+
|
|
29
|
+
Multipart responses are endoded using content negotiation,
|
|
30
|
+
and the `content-type` of the response is set to one of the custom `multipart/` subtypes, corresponding to the type of
|
|
31
|
+
the parts:
|
|
32
|
+
|
|
33
|
+
| Response type | Part type |
|
|
34
|
+
|---------------------|-----------------------|
|
|
35
|
+
| `multipart/msgpack` | `application/msgpack` |
|
|
36
|
+
| `multipart/yaml` | `application/yaml` |
|
|
37
|
+
| `multipart/json` | `application/json` |
|
|
38
|
+
| `multipart/text` | `text/plain` |
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
GET /stream/ HTTP/1.1
|
|
44
|
+
accept: application/yaml
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
200 OK
|
|
49
|
+
content-type: multipart/yaml; boundary=cut
|
|
50
|
+
|
|
51
|
+
--cut
|
|
52
|
+
foo: bar
|
|
53
|
+
--cut
|
|
54
|
+
baz: qux
|
|
55
|
+
--cut--
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See also:
|
|
16
59
|
|
|
17
|
-
|
|
18
|
-
|
|
60
|
+
- [Multipart Content-Type](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html) at W3C
|
|
61
|
+
- [Content-Type: multipart](https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa493937(v=exchg.140))
|
|
62
|
+
at Microsoft
|
package/documentation/tree.md
CHANGED
|
@@ -124,7 +124,7 @@ Intermediate Nodes must not have Methods as they are unreachable.
|
|
|
124
124
|
|
|
125
125
|
## Directives
|
|
126
126
|
|
|
127
|
-
RTD Directives are declared using RTD node or Method keys following the `{
|
|
127
|
+
RTD Directives are declared using RTD node or Method keys following the `{family}:{directive}` pattern and can be used
|
|
128
128
|
to add or modify the behavior of request processing. Directive declarations are applied to the RTD node where they are
|
|
129
129
|
declared and to all nested nodes.
|
|
130
130
|
|
|
@@ -151,10 +151,6 @@ When it is necessary to avoid directive nesting, a Route can be declared adjacen
|
|
|
151
151
|
In this example, the Route `/posts/:user-id/:post-id/` has only the `authorization:role` directive
|
|
152
152
|
applied.
|
|
153
153
|
|
|
154
|
-
> Directives can be declared without the `{provider}:` prefix unless there are multiple directives
|
|
155
|
-
> with the same name
|
|
156
|
-
> across different providers.
|
|
157
|
-
|
|
158
154
|
Another way to avoid nesting is to declare an _isolated_ Node as follows:
|
|
159
155
|
|
|
160
156
|
```yaml
|
package/features/access.feature
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Feature: Caching
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given the `identity.basic` database contains:
|
|
5
|
+
# developer:secret
|
|
6
|
+
# user:12345
|
|
7
|
+
| _id | username | password |
|
|
8
|
+
| b70a7dbca6b14a2eaac8a9eb4b2ff4db | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
|
|
9
|
+
Given the `identity.roles` database contains:
|
|
10
|
+
| _id | identity | role |
|
|
11
|
+
| 775a648d054e4ce1a65f8f17e5b51803 | b70a7dbca6b14a2eaac8a9eb4b2ff4db | developer |
|
|
12
|
+
|
|
13
|
+
Scenario: Caching successful response
|
|
14
|
+
Given the annotation:
|
|
15
|
+
"""yaml
|
|
16
|
+
/:
|
|
17
|
+
anonymous: true
|
|
18
|
+
GET:
|
|
19
|
+
cache:control: max-age=60000
|
|
20
|
+
dev:stub: hello
|
|
21
|
+
"""
|
|
22
|
+
When the following request is received:
|
|
23
|
+
"""
|
|
24
|
+
GET / HTTP/1.1
|
|
25
|
+
accept: text/plain
|
|
26
|
+
"""
|
|
27
|
+
Then the following reply is sent:
|
|
28
|
+
"""
|
|
29
|
+
200 OK
|
|
30
|
+
content-type: text/plain
|
|
31
|
+
cache-control: max-age=60000
|
|
32
|
+
|
|
33
|
+
hello
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
Scenario: Nested cache directives
|
|
37
|
+
Given the annotation:
|
|
38
|
+
"""yaml
|
|
39
|
+
/:
|
|
40
|
+
cache:control: max-age=30000
|
|
41
|
+
GET:
|
|
42
|
+
anonymous: true
|
|
43
|
+
dev:stub: hello
|
|
44
|
+
/foo:
|
|
45
|
+
auth:role: developer
|
|
46
|
+
GET:
|
|
47
|
+
dev:stub: hello
|
|
48
|
+
/bar:
|
|
49
|
+
auth:role: developer
|
|
50
|
+
cache:control: max-age=60000, public
|
|
51
|
+
GET:
|
|
52
|
+
dev:stub: hello
|
|
53
|
+
"""
|
|
54
|
+
When the following request is received:
|
|
55
|
+
"""
|
|
56
|
+
GET / HTTP/1.1
|
|
57
|
+
accept: text/plain
|
|
58
|
+
"""
|
|
59
|
+
Then the following reply is sent:
|
|
60
|
+
"""
|
|
61
|
+
200 OK
|
|
62
|
+
content-type: text/plain
|
|
63
|
+
cache-control: max-age=30000
|
|
64
|
+
|
|
65
|
+
hello
|
|
66
|
+
"""
|
|
67
|
+
When the following request is received:
|
|
68
|
+
"""
|
|
69
|
+
GET /foo/ HTTP/1.1
|
|
70
|
+
accept: text/plain
|
|
71
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
72
|
+
"""
|
|
73
|
+
Then the following reply is sent:
|
|
74
|
+
"""
|
|
75
|
+
200 OK
|
|
76
|
+
content-type: text/plain
|
|
77
|
+
cache-control: private, max-age=30000
|
|
78
|
+
|
|
79
|
+
hello
|
|
80
|
+
"""
|
|
81
|
+
When the following request is received:
|
|
82
|
+
"""
|
|
83
|
+
GET /bar/ HTTP/1.1
|
|
84
|
+
accept: text/plain
|
|
85
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
86
|
+
"""
|
|
87
|
+
Then the following reply is sent:
|
|
88
|
+
"""
|
|
89
|
+
200 OK
|
|
90
|
+
content-type: text/plain
|
|
91
|
+
cache-control: no-cache, max-age=60000, public
|
|
92
|
+
|
|
93
|
+
hello
|
|
94
|
+
"""
|
|
95
|
+
And the reply does not contain:
|
|
96
|
+
"""
|
|
97
|
+
cache-control: private, max-age=30000
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
Scenario: Cache-control is not added when request is unsafe
|
|
101
|
+
Given the annotation:
|
|
102
|
+
"""yaml
|
|
103
|
+
/:
|
|
104
|
+
anonymous: true
|
|
105
|
+
cache:control: max-age=60000
|
|
106
|
+
POST:
|
|
107
|
+
dev:stub: hello
|
|
108
|
+
"""
|
|
109
|
+
When the following request is received:
|
|
110
|
+
"""
|
|
111
|
+
POST / HTTP/1.1
|
|
112
|
+
accept: application/yaml
|
|
113
|
+
"""
|
|
114
|
+
Then the reply does not contain:
|
|
115
|
+
"""
|
|
116
|
+
cache-control: max-age=60000
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
Scenario: Cache-control is added without implicit modifications
|
|
120
|
+
Given the annotation:
|
|
121
|
+
"""yaml
|
|
122
|
+
/:
|
|
123
|
+
auth:role: developer
|
|
124
|
+
cache:exact: max-age=60000, public
|
|
125
|
+
GET:
|
|
126
|
+
dev:stub: hello
|
|
127
|
+
"""
|
|
128
|
+
When the following request is received:
|
|
129
|
+
"""
|
|
130
|
+
GET / HTTP/1.1
|
|
131
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
132
|
+
accept: text/plain
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
Then the following reply is sent:
|
|
136
|
+
"""
|
|
137
|
+
200 OK
|
|
138
|
+
content-type: text/plain
|
|
139
|
+
cache-control: max-age=60000, public
|
|
140
|
+
|
|
141
|
+
hello
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
Scenario: Response without caching
|
|
145
|
+
Given the annotation:
|
|
146
|
+
"""yaml
|
|
147
|
+
/:
|
|
148
|
+
anonymous: true
|
|
149
|
+
GET:
|
|
150
|
+
dev:stub: hello
|
|
151
|
+
"""
|
|
152
|
+
When the following request is received:
|
|
153
|
+
"""
|
|
154
|
+
GET / HTTP/1.1
|
|
155
|
+
accept: text/plain
|
|
156
|
+
"""
|
|
157
|
+
Then the reply does not contain:
|
|
158
|
+
"""
|
|
159
|
+
cache-control:
|
|
160
|
+
"""
|