@occultist/occultist 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/accept.d.ts +41 -0
- package/dist/accept.js +110 -0
- package/dist/accept.test.d.ts +1 -0
- package/dist/accept.test.js +44 -0
- package/dist/action.test.d.ts +1 -0
- package/dist/action.test.js +1 -0
- package/dist/actions/actionSets.d.ts +23 -0
- package/dist/actions/actionSets.js +49 -0
- package/dist/actions/actions.d.ts +163 -0
- package/dist/actions/actions.js +436 -0
- package/dist/actions/context.d.ts +78 -0
- package/dist/actions/context.js +112 -0
- package/dist/actions/meta.d.ts +49 -0
- package/dist/actions/meta.js +177 -0
- package/dist/actions/path.d.ts +21 -0
- package/dist/actions/path.js +83 -0
- package/dist/actions/path.test.d.ts +1 -0
- package/dist/actions/path.test.js +9 -0
- package/dist/actions/spec.d.ts +214 -0
- package/dist/actions/spec.js +1 -0
- package/dist/actions/types.d.ts +112 -0
- package/dist/actions/types.js +2 -0
- package/dist/actions/writer.d.ts +27 -0
- package/dist/actions/writer.js +140 -0
- package/dist/actions/writer.test.d.ts +1 -0
- package/dist/actions/writer.test.js +42 -0
- package/dist/auth/types.d.ts +14 -0
- package/dist/auth/types.js +1 -0
- package/dist/cache/cache.d.ts +30 -0
- package/dist/cache/cache.js +220 -0
- package/dist/cache/etag.d.ts +17 -0
- package/dist/cache/etag.js +83 -0
- package/dist/cache/etag.test.d.ts +1 -0
- package/dist/cache/etag.test.js +91 -0
- package/dist/cache/memory.d.ts +12 -0
- package/dist/cache/memory.js +36 -0
- package/dist/cache/types.d.ts +175 -0
- package/dist/cache/types.js +4 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +54 -0
- package/dist/jsonld.d.ts +43 -0
- package/dist/jsonld.js +1 -0
- package/dist/makeTypeDefs.d.ts +27 -0
- package/dist/makeTypeDefs.js +70 -0
- package/dist/merge.d.ts +61 -0
- package/dist/merge.js +1 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +14 -0
- package/dist/processAction.d.ts +15 -0
- package/dist/processAction.js +512 -0
- package/dist/registry.d.ts +88 -0
- package/dist/registry.js +314 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +133 -0
- package/dist/request.d.ts +29 -0
- package/dist/request.js +118 -0
- package/dist/scopes.d.ts +35 -0
- package/dist/scopes.js +121 -0
- package/dist/scopes.test.d.ts +1 -0
- package/dist/scopes.test.js +55 -0
- package/dist/transformers/fileTransformer.d.ts +1 -0
- package/dist/transformers/fileTransformer.js +8 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/dist/utils/alwaysArray.d.ts +1 -0
- package/dist/utils/alwaysArray.js +9 -0
- package/dist/utils/contextBuilder.d.ts +9 -0
- package/dist/utils/contextBuilder.js +82 -0
- package/dist/utils/getActionContext.d.ts +7 -0
- package/dist/utils/getActionContext.js +48 -0
- package/dist/utils/getInternalName.d.ts +6 -0
- package/dist/utils/getInternalName.js +7 -0
- package/dist/utils/getParamLocation.d.ts +2 -0
- package/dist/utils/getParamLocation.js +6 -0
- package/dist/utils/getPropertyValueSpecifications.d.ts +2 -0
- package/dist/utils/getPropertyValueSpecifications.js +49 -0
- package/dist/utils/getRequestBodyValues.d.ts +11 -0
- package/dist/utils/getRequestBodyValues.js +122 -0
- package/dist/utils/getRequestIRIValues.d.ts +14 -0
- package/dist/utils/getRequestIRIValues.js +133 -0
- package/dist/utils/isBodyInit.d.ts +1 -0
- package/dist/utils/isBodyInit.js +21 -0
- package/dist/utils/isNil.d.ts +1 -0
- package/dist/utils/isNil.js +4 -0
- package/dist/utils/isObject.d.ts +6 -0
- package/dist/utils/isObject.js +6 -0
- package/dist/utils/isPopulatedObject.d.ts +5 -0
- package/dist/utils/isPopulatedObject.js +8 -0
- package/dist/utils/isPopulatedString.d.ts +1 -0
- package/dist/utils/isPopulatedString.js +4 -0
- package/dist/utils/joinPaths.d.ts +1 -0
- package/dist/utils/joinPaths.js +31 -0
- package/dist/utils/makeAppendProblemDetails.d.ts +14 -0
- package/dist/utils/makeAppendProblemDetails.js +26 -0
- package/dist/utils/makeURLPattern.d.ts +5 -0
- package/dist/utils/makeURLPattern.js +12 -0
- package/dist/utils/normalizeURL.d.ts +4 -0
- package/dist/utils/normalizeURL.js +11 -0
- package/dist/utils/parseSearchParams.d.ts +3 -0
- package/dist/utils/parseSearchParams.js +24 -0
- package/dist/utils/preferredMediaTypes.d.ts +42 -0
- package/dist/utils/preferredMediaTypes.js +149 -0
- package/dist/utils/urlToIRI.d.ts +1 -0
- package/dist/utils/urlToIRI.js +8 -0
- package/dist/utils/validateSpecValue.d.ts +1 -0
- package/dist/utils/validateSpecValue.js +1 -0
- package/dist/validators.d.ts +16 -0
- package/dist/validators.js +134 -0
- package/lib/accept.test.ts +55 -0
- package/lib/accept.ts +147 -0
- package/lib/action.test.ts +2 -0
- package/lib/actions/actionSets.ts +88 -0
- package/lib/actions/actions.ts +795 -0
- package/lib/actions/context.ts +170 -0
- package/lib/actions/meta.ts +251 -0
- package/lib/actions/path.test.ts +15 -0
- package/lib/actions/path.ts +99 -0
- package/lib/actions/spec.ts +545 -0
- package/lib/actions/types.ts +146 -0
- package/lib/actions/writer.test.ts +57 -0
- package/lib/actions/writer.ts +176 -0
- package/lib/auth/types.ts +22 -0
- package/lib/cache/cache.ts +291 -0
- package/lib/cache/etag.test.ts +122 -0
- package/lib/cache/etag.ts +106 -0
- package/lib/cache/memory.ts +52 -0
- package/lib/cache/types.ts +240 -0
- package/lib/errors.ts +66 -0
- package/lib/jsonld.ts +67 -0
- package/lib/makeTypeDefs.ts +138 -0
- package/lib/merge.ts +86 -0
- package/lib/mod.ts +14 -0
- package/lib/processAction.ts +690 -0
- package/lib/registry.test.ts +174 -0
- package/lib/registry.ts +455 -0
- package/lib/request.ts +153 -0
- package/lib/scopes.test.ts +70 -0
- package/lib/scopes.ts +178 -0
- package/lib/transformers/fileTransformer.ts +10 -0
- package/lib/types.ts +13 -0
- package/lib/utils/alwaysArray.ts +10 -0
- package/lib/utils/contextBuilder.ts +111 -0
- package/lib/utils/getActionContext.ts +76 -0
- package/lib/utils/getInternalName.ts +15 -0
- package/lib/utils/getParamLocation.ts +14 -0
- package/lib/utils/getPropertyValueSpecifications.ts +76 -0
- package/lib/utils/getRequestBodyValues.ts +155 -0
- package/lib/utils/getRequestIRIValues.ts +201 -0
- package/lib/utils/isBodyInit.ts +22 -0
- package/lib/utils/isNil.ts +4 -0
- package/lib/utils/isObject.ts +8 -0
- package/lib/utils/isPopulatedObject.ts +9 -0
- package/lib/utils/isPopulatedString.ts +4 -0
- package/lib/utils/joinPaths.ts +36 -0
- package/lib/utils/makeAppendProblemDetails.ts +57 -0
- package/lib/utils/makeURLPattern.ts +18 -0
- package/lib/utils/normalizeURL.ts +15 -0
- package/lib/utils/parseSearchParams.ts +36 -0
- package/lib/utils/preferredMediaTypes.ts +220 -0
- package/lib/utils/urlToIRI.ts +11 -0
- package/lib/utils/validateSpecValue.ts +0 -0
- package/lib/validators.ts +186 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matthew Quinn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Occultist
|
|
2
|
+
|
|
3
|
+
**Occultist** is an under-development, Koa-inspired backend web framework for Node or Deno,
|
|
4
|
+
adding structure to less-supported HTTP features.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### Auth middleware
|
|
10
|
+
All endpoints require that they are marked public or private simplifying future
|
|
11
|
+
auditing processes. When marked public they and can optionally have auth middleware
|
|
12
|
+
provided to identify the requester. Private endpoints require a auth middleware is
|
|
13
|
+
provided.
|
|
14
|
+
|
|
15
|
+
### Request url and body input processing
|
|
16
|
+
Request bodies, route parameters and query string values can be fully described to
|
|
17
|
+
Occultist allowing it to unpack multipart/form-data, application/json requests
|
|
18
|
+
producing a single typescript typed `ctx.payload` object with all inputs merged together.
|
|
19
|
+
Failed requests automatically support responding with
|
|
20
|
+
[application/problem+json](https://www.rfc-editor.org/rfc/rfc9457.html) responses.
|
|
21
|
+
|
|
22
|
+
### Content negotiation
|
|
23
|
+
Endpoints can have multiple handlers defined responding with different content
|
|
24
|
+
types. Occultist automatically routes the request to the correct handler based
|
|
25
|
+
off the request's accept header, or the first handler if no accept handler is set.
|
|
26
|
+
|
|
27
|
+
### Caching middleware
|
|
28
|
+
Use caching providers to store representations using the provided auth information
|
|
29
|
+
request's URL's parameters and resulting content type provided by the other special
|
|
30
|
+
case middlewares.
|
|
31
|
+
|
|
32
|
+
### Advanced features
|
|
33
|
+
Occultist is being built to complement the also under-development frontend framework
|
|
34
|
+
[Octiron](https://github.com/occultist-dev/octiron) which is built to consume JSON-ld
|
|
35
|
+
APIs and build complex forms using actions in the [schema.org/Action](https://schema.org/Action)
|
|
36
|
+
style. This relationship will be better explained as the two frameworks stablize.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Occultist is in flux at the moment but you can try it now. Endpoints can be created
|
|
40
|
+
with most url and body processing features working for application/json requests and
|
|
41
|
+
content negotiation works for the response's content. The auth and cache features
|
|
42
|
+
described here are yet to be implemented.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
```
|
|
47
|
+
npm install @occultist/occultist
|
|
48
|
+
deno add jsr:@occultist/occultist
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Example
|
|
53
|
+
```typescript
|
|
54
|
+
import { Registry } from '@occultist/occultist';
|
|
55
|
+
|
|
56
|
+
// TODO
|
|
57
|
+
const auth = new AuthProvider();
|
|
58
|
+
// TODO
|
|
59
|
+
const cache = new CacheProvider();
|
|
60
|
+
|
|
61
|
+
const registry = new Registry({
|
|
62
|
+
root: 'https://example.com',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
registry.http.get('list-cats', '/cats')
|
|
66
|
+
// Endpoints are marked public and can optionally have
|
|
67
|
+
// auth middleware identify the requester, or they are
|
|
68
|
+
// marked private and the auth check is required.
|
|
69
|
+
.public(auth.optional())
|
|
70
|
+
|
|
71
|
+
// resulting representation cache keys would vary based
|
|
72
|
+
// on the an auth key that is unique to the user that the above
|
|
73
|
+
// middleware provides, other parameters can further vary the
|
|
74
|
+
// cache and control http cache headers.
|
|
75
|
+
.cache(cache.etag())
|
|
76
|
+
|
|
77
|
+
// define handlers to respond with supported content types.
|
|
78
|
+
.handle('text/html', (ctx) => {
|
|
79
|
+
ctx.body = `
|
|
80
|
+
<!doctype>
|
|
81
|
+
<html>
|
|
82
|
+
<body>
|
|
83
|
+
<h1>Hello, World!</h1>
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
|
86
|
+
`;
|
|
87
|
+
})
|
|
88
|
+
.handle('application/json', (ctx) => {
|
|
89
|
+
ctx.body = `{
|
|
90
|
+
"message": "Hello, World!",
|
|
91
|
+
}`;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// The same method and path combination can be re-used for endpoints
|
|
95
|
+
// which have different middleware requirements. The accept header
|
|
96
|
+
// can be used by requests to pull an alternative representation.
|
|
97
|
+
registry.http.get('get-cat', '/cats')
|
|
98
|
+
.public()
|
|
99
|
+
.handle('application/xml', (ctx) => { ... })
|
|
100
|
+
|
|
101
|
+
registry.http.post('create-cat', '/cats')
|
|
102
|
+
.private(auth.hasPermission('create-cats'))
|
|
103
|
+
// With a body payload defined any requests with
|
|
104
|
+
// application/json or multipart/form-data bodies
|
|
105
|
+
// are automatically pre-processed into the `ctx.payload`.
|
|
106
|
+
.define({
|
|
107
|
+
spec: {
|
|
108
|
+
name: {
|
|
109
|
+
dataType: 'string',
|
|
110
|
+
minValueLength: 2,
|
|
111
|
+
valueRequired: true,
|
|
112
|
+
},
|
|
113
|
+
hasStripes: {
|
|
114
|
+
dataType: 'boolean',
|
|
115
|
+
valueRequired: true,
|
|
116
|
+
},
|
|
117
|
+
image: {
|
|
118
|
+
// you would want to use form-data for a large file upload
|
|
119
|
+
// but data uris can be sent via json
|
|
120
|
+
dataType: 'blob',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
.handle('text/html', async (ctx) => {
|
|
125
|
+
const cat = await storage.createCat({
|
|
126
|
+
name: ctx.payload.name,
|
|
127
|
+
hasStripes: ctx.payload.hasStripes,
|
|
128
|
+
image: ctx.payload.image,
|
|
129
|
+
});
|
|
130
|
+
ctx.status = 303;
|
|
131
|
+
ctx.headers.set('Location', `https://example.com/cats/${cat.id}`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// required before requests are handled
|
|
135
|
+
registry.finalize();
|
|
136
|
+
|
|
137
|
+
const server = createServer();
|
|
138
|
+
|
|
139
|
+
// for Node, Deno and probably Bun.
|
|
140
|
+
server.on('request', (req, res) => registry.handleRequest(req, res));
|
|
141
|
+
server.listen(3000);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Advanced features
|
package/dist/accept.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a content type cache usually from the set of content type
|
|
3
|
+
* options supported by an action or action set.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ContentTypeCache {
|
|
6
|
+
default: string;
|
|
7
|
+
set: Set<string>;
|
|
8
|
+
map: Map<string, string>;
|
|
9
|
+
contentTypes: string[];
|
|
10
|
+
constructor(contentTypes: string[]);
|
|
11
|
+
get [Symbol.toStringTag](): string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* This accept object is created from a request before any content negotiation
|
|
15
|
+
* begins allowing all subsequent checks to re-use the same caches header values.
|
|
16
|
+
*
|
|
17
|
+
* @todo Implement support for all accept headers.
|
|
18
|
+
*
|
|
19
|
+
* @param accept - The value of the request's accept header.
|
|
20
|
+
* @param acceptLanguage - The value of the request's accept-language header.
|
|
21
|
+
* @param acceptEncoding - The value of the request's accept-encoding header.
|
|
22
|
+
*/
|
|
23
|
+
export declare class Accept {
|
|
24
|
+
#private;
|
|
25
|
+
acceptRe: RegExp;
|
|
26
|
+
accept: string[];
|
|
27
|
+
acceptCache: Set<string>;
|
|
28
|
+
constructor(accept: string | null, _acceptLanguage: string | null, _acceptEncoding: string | null);
|
|
29
|
+
/**
|
|
30
|
+
* Creates an accept instance from a request or response instance
|
|
31
|
+
*/
|
|
32
|
+
static from(req: Request): Accept;
|
|
33
|
+
debug(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Negotiates against the cached set of content type options.
|
|
36
|
+
*
|
|
37
|
+
* @param contentType Content type cache built for an action.
|
|
38
|
+
*/
|
|
39
|
+
negotiate(contentType: ContentTypeCache): null | string;
|
|
40
|
+
get [Symbol.toStringTag](): string;
|
|
41
|
+
}
|
package/dist/accept.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a content type cache usually from the set of content type
|
|
3
|
+
* options supported by an action or action set.
|
|
4
|
+
*/
|
|
5
|
+
export class ContentTypeCache {
|
|
6
|
+
default;
|
|
7
|
+
set;
|
|
8
|
+
map = new Map();
|
|
9
|
+
contentTypes;
|
|
10
|
+
constructor(contentTypes) {
|
|
11
|
+
this.default = contentTypes[0];
|
|
12
|
+
this.contentTypes = contentTypes;
|
|
13
|
+
this.set = new Set(contentTypes);
|
|
14
|
+
this.set.add('*/*');
|
|
15
|
+
this.map.set('*/*', contentTypes[0]);
|
|
16
|
+
for (let index = 0; index < contentTypes.length; index++) {
|
|
17
|
+
const contentType = contentTypes[index];
|
|
18
|
+
const type = contentType.replace(/[^\/]*$/, '*');
|
|
19
|
+
this.map.set(contentType, contentType);
|
|
20
|
+
if (!this.map.has(type)) {
|
|
21
|
+
this.set.add(type);
|
|
22
|
+
this.map.set(type, contentType);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
get [Symbol.toStringTag]() {
|
|
27
|
+
return `[ContentTypeCache ${this.contentTypes.join(' ')}]`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* This accept object is created from a request before any content negotiation
|
|
32
|
+
* begins allowing all subsequent checks to re-use the same caches header values.
|
|
33
|
+
*
|
|
34
|
+
* @todo Implement support for all accept headers.
|
|
35
|
+
*
|
|
36
|
+
* @param accept - The value of the request's accept header.
|
|
37
|
+
* @param acceptLanguage - The value of the request's accept-language header.
|
|
38
|
+
* @param acceptEncoding - The value of the request's accept-encoding header.
|
|
39
|
+
*/
|
|
40
|
+
export class Accept {
|
|
41
|
+
acceptRe = /(?<ct>[^,;\s]+)(;\s?q=(?<q>(\d(\.\d+)|(.\d))))?/g;
|
|
42
|
+
accept = [];
|
|
43
|
+
acceptCache = new Set();
|
|
44
|
+
//#acceptLanguage: string[] = [];
|
|
45
|
+
//#acceptLanguageCache: Set<string> = new Set();
|
|
46
|
+
//#acceptEncoding: string[] = [];
|
|
47
|
+
//#acceptEncodingCache: Set<string> = new Set();
|
|
48
|
+
constructor(accept, _acceptLanguage, _acceptEncoding) {
|
|
49
|
+
[this.accept, this.acceptCache] = this.#process(accept);
|
|
50
|
+
//[this.#acceptLanguage, this.#acceptLanguageCache] = this.#process(acceptLanguage);
|
|
51
|
+
//[this.#acceptEncoding, this.#acceptEncodingCache] = this.#process(acceptEncoding);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Creates an accept instance from a request or response instance
|
|
55
|
+
*/
|
|
56
|
+
static from(req) {
|
|
57
|
+
const accept = req.headers.get('Accept');
|
|
58
|
+
const acceptLanguage = req.headers.get('Accept-Language');
|
|
59
|
+
const acceptEncoding = req.headers.get('Accept-Encoding');
|
|
60
|
+
console.log('ACCEPT', accept);
|
|
61
|
+
return new Accept(accept, acceptLanguage, acceptEncoding);
|
|
62
|
+
}
|
|
63
|
+
debug() {
|
|
64
|
+
return this.accept.join(' ');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Negotiates against the cached set of content type options.
|
|
68
|
+
*
|
|
69
|
+
* @param contentType Content type cache built for an action.
|
|
70
|
+
*/
|
|
71
|
+
negotiate(contentType) {
|
|
72
|
+
if (this.accept.length === 0) {
|
|
73
|
+
return contentType.default;
|
|
74
|
+
}
|
|
75
|
+
// TODO: check might be over-optimizing.
|
|
76
|
+
const intersection = this.acceptCache.intersection(contentType.set);
|
|
77
|
+
if (intersection.size === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
for (let index = 0; index < this.accept.length; index++) {
|
|
81
|
+
const match = contentType.map.get(this.accept[index]);
|
|
82
|
+
if (match != null) {
|
|
83
|
+
return match;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
#process(header) {
|
|
89
|
+
if (header == null) {
|
|
90
|
+
return [[], new Set('*/*')];
|
|
91
|
+
}
|
|
92
|
+
let match;
|
|
93
|
+
const items = [];
|
|
94
|
+
const cache = new Set();
|
|
95
|
+
while ((match = this.acceptRe.exec(header))) {
|
|
96
|
+
const ct = match.groups?.ct;
|
|
97
|
+
const q = Number(match.groups?.q ?? 1);
|
|
98
|
+
cache.add(ct);
|
|
99
|
+
items.push({ ct, q });
|
|
100
|
+
}
|
|
101
|
+
items.sort((a, b) => b.q - a.q);
|
|
102
|
+
return [
|
|
103
|
+
items.map(({ ct }) => ct),
|
|
104
|
+
cache,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
get [Symbol.toStringTag]() {
|
|
108
|
+
return `[Accept ${this.accept.join(' ')}]`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { Accept, ContentTypeCache } from "./accept.js";
|
|
4
|
+
const cache = new ContentTypeCache([
|
|
5
|
+
'text/html',
|
|
6
|
+
'application/ld+json',
|
|
7
|
+
'application/json',
|
|
8
|
+
]);
|
|
9
|
+
test('Matches with no accepts header', () => {
|
|
10
|
+
const accept = Accept.from(new Request('https://example.com'));
|
|
11
|
+
assert(accept.negotiate(cache) === 'text/html');
|
|
12
|
+
});
|
|
13
|
+
test('Matches with specific accepts header', () => {
|
|
14
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
15
|
+
headers: {
|
|
16
|
+
accept: 'application/ld+json',
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
assert(accept.negotiate(cache) === 'application/ld+json');
|
|
20
|
+
});
|
|
21
|
+
test('Matches with glob header', () => {
|
|
22
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
23
|
+
headers: {
|
|
24
|
+
accept: '*/*',
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
assert(accept.negotiate(cache) === 'text/html');
|
|
28
|
+
});
|
|
29
|
+
test('Matches with glob subtype header', () => {
|
|
30
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
31
|
+
headers: {
|
|
32
|
+
accept: 'application/*',
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
assert(accept.negotiate(cache) === 'application/ld+json');
|
|
36
|
+
});
|
|
37
|
+
test('Matches q prioritizing in header', () => {
|
|
38
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
39
|
+
headers: {
|
|
40
|
+
accept: 'text/html; q=0.5, application/json; q=1, text/cvs; q=.7',
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
assert(accept.negotiate(cache) === 'application/json');
|
|
44
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Accept } from "../accept.js";
|
|
2
|
+
import type { ActionMeta } from "./meta.js";
|
|
3
|
+
import type { ImplementedAction } from "./types.js";
|
|
4
|
+
export type UnsupportedContentTypeMatch = {
|
|
5
|
+
type: 'unsupported-content-type';
|
|
6
|
+
contentTypes: string[];
|
|
7
|
+
};
|
|
8
|
+
export type ActionAcceptMatch = {
|
|
9
|
+
type: 'match';
|
|
10
|
+
action: ImplementedAction;
|
|
11
|
+
contentType?: string;
|
|
12
|
+
language?: string;
|
|
13
|
+
encoding?: string;
|
|
14
|
+
};
|
|
15
|
+
export type ActionMatchResult = UnsupportedContentTypeMatch | ActionAcceptMatch;
|
|
16
|
+
/**
|
|
17
|
+
* A set of actions grouped by having equal methods and equivilent paths.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ActionSet {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(rootIRI: string, method: string, path: string, meta: ActionMeta[]);
|
|
22
|
+
matches(method: string, path: string, accept: Accept): null | ActionMatchResult;
|
|
23
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ContentTypeCache } from "../accept.js";
|
|
2
|
+
import { makeURLPattern } from "../utils/makeURLPattern.js";
|
|
3
|
+
/**
|
|
4
|
+
* A set of actions grouped by having equal methods and equivilent paths.
|
|
5
|
+
*/
|
|
6
|
+
export class ActionSet {
|
|
7
|
+
#rootIRI;
|
|
8
|
+
#method;
|
|
9
|
+
#urlPattern;
|
|
10
|
+
#contentTypeActionMap;
|
|
11
|
+
#ctc;
|
|
12
|
+
constructor(rootIRI, method, path, meta) {
|
|
13
|
+
this.#rootIRI = rootIRI;
|
|
14
|
+
this.#method = method;
|
|
15
|
+
this.#urlPattern = makeURLPattern(path, rootIRI);
|
|
16
|
+
[this.#contentTypeActionMap, this.#ctc] = this.#process(meta);
|
|
17
|
+
}
|
|
18
|
+
matches(method, path, accept) {
|
|
19
|
+
if (method !== this.#method) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
else if (!this.#urlPattern.test(path, this.#rootIRI)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const contentType = accept.negotiate(this.#ctc);
|
|
26
|
+
const action = this.#contentTypeActionMap.get(contentType);
|
|
27
|
+
if (contentType != null && action != null) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'match',
|
|
30
|
+
action,
|
|
31
|
+
contentType,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
#process(meta) {
|
|
37
|
+
const contentTypes = [];
|
|
38
|
+
const contentTypeActionMap = new Map();
|
|
39
|
+
for (let i = 0; i < meta.length; i++) {
|
|
40
|
+
const action = meta[i].action;
|
|
41
|
+
for (let j = 0; j < action.contentTypes.length; j++) {
|
|
42
|
+
const contentType = action.contentTypes[j];
|
|
43
|
+
contentTypes.push(contentType);
|
|
44
|
+
contentTypeActionMap.set(contentType, action);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [contentTypeActionMap, new ContentTypeCache(contentTypes)];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { CacheInstanceArgs } from '../cache/types.js';
|
|
2
|
+
import type { JSONLDContext, JSONObject, TypeDef } from "../jsonld.js";
|
|
3
|
+
import type { Registry } from '../registry.js';
|
|
4
|
+
import type { Scope } from "../scopes.js";
|
|
5
|
+
import { type ActionMeta } from "./meta.js";
|
|
6
|
+
import type { ActionSpec, ContextState } from "./spec.js";
|
|
7
|
+
import type { HandleRequestArgs, HandlerFn, HandlerMeta, HandlerObj, HandlerValue, HintArgs, ImplementedAction } from './types.js';
|
|
8
|
+
import { ResponseTypes } from './writer.js';
|
|
9
|
+
export type DefineArgs<Term extends string = string, Spec extends ActionSpec = ActionSpec> = {
|
|
10
|
+
typeDef?: TypeDef<Term>;
|
|
11
|
+
spec?: Spec;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A handler definition which can be pulled from a registry, scope or action
|
|
15
|
+
* after an action is defined.
|
|
16
|
+
*/
|
|
17
|
+
export declare class HandlerDefinition<State extends ContextState = ContextState, Spec extends ActionSpec = ActionSpec> {
|
|
18
|
+
name: string;
|
|
19
|
+
contentType: string;
|
|
20
|
+
handler: HandlerFn | HandlerValue;
|
|
21
|
+
meta: HandlerMeta;
|
|
22
|
+
action: ImplementedAction<State, Spec>;
|
|
23
|
+
cache: ReadonlyArray<CacheInstanceArgs>;
|
|
24
|
+
constructor(name: string, contentType: string, handler: HandlerFn | HandlerValue, meta: HandlerMeta, action: ImplementedAction<State, Spec>, actionMeta: ActionMeta);
|
|
25
|
+
get [Symbol.toStringTag](): string;
|
|
26
|
+
}
|
|
27
|
+
export interface Handleable<State extends ContextState = ContextState, Spec extends ActionSpec = ActionSpec> {
|
|
28
|
+
/**
|
|
29
|
+
* Defines the final handler for this content type.
|
|
30
|
+
*
|
|
31
|
+
* An action can have multiple handlers defined
|
|
32
|
+
* each for a different set of content types.
|
|
33
|
+
*/
|
|
34
|
+
handle(contentType: string | string[], handler: HandlerValue | HandlerFn<State, Spec>): FinalizedAction<State, Spec>;
|
|
35
|
+
handle(args: HandlerObj<State, Spec>): FinalizedAction<State, Spec>;
|
|
36
|
+
}
|
|
37
|
+
export declare class FinalizedAction<State extends ContextState = ContextState, Spec extends ActionSpec = ActionSpec> implements Handleable<State, Spec>, ImplementedAction<State, Spec> {
|
|
38
|
+
#private;
|
|
39
|
+
constructor(typeDef: TypeDef | undefined, spec: Spec, meta: ActionMeta<State, Spec>, handlerArgs: HandlerObj<State, Spec>);
|
|
40
|
+
static fromHandlers<State extends ContextState = ContextState, Spec extends ActionSpec = ActionSpec>(typeDef: TypeDef | undefined, spec: Spec, meta: ActionMeta<State, Spec>, contextType: string | string[], handler: HandlerValue | HandlerFn<State, Spec>): FinalizedAction<State, Spec>;
|
|
41
|
+
static fromHandlers<State extends ContextState = ContextState, Spec extends ActionSpec = ActionSpec>(typeDef: TypeDef | undefined, spec: Spec, meta: ActionMeta<State, Spec>, handlerArgs: HandlerObj<State, Spec>): FinalizedAction<State, Spec>;
|
|
42
|
+
static toJSONLD(action: ImplementedAction, scope: Scope): Promise<JSONObject | null>;
|
|
43
|
+
get public(): boolean;
|
|
44
|
+
get method(): string;
|
|
45
|
+
get term(): string | undefined;
|
|
46
|
+
get type(): string | undefined;
|
|
47
|
+
get typeDef(): TypeDef | undefined;
|
|
48
|
+
get name(): string;
|
|
49
|
+
get template(): string;
|
|
50
|
+
get pattern(): URLPattern;
|
|
51
|
+
get spec(): Spec;
|
|
52
|
+
get scope(): Scope | undefined;
|
|
53
|
+
get registry(): Registry;
|
|
54
|
+
get handlers(): HandlerDefinition<State, Spec>[];
|
|
55
|
+
get contentTypes(): string[];
|
|
56
|
+
get context(): JSONObject;
|
|
57
|
+
url(): string;
|
|
58
|
+
jsonld(): Promise<JSONObject | null>;
|
|
59
|
+
jsonldPartial(): {
|
|
60
|
+
'@type': string;
|
|
61
|
+
'@id': string;
|
|
62
|
+
} | null;
|
|
63
|
+
handle(contentType: string | string[], handler: HandlerFn<State, Spec> | HandlerValue): FinalizedAction<State, Spec>;
|
|
64
|
+
handle(args: HandlerObj<State, Spec>): FinalizedAction<State, Spec>;
|
|
65
|
+
handleRequest(args: HandleRequestArgs): Promise<ResponseTypes>;
|
|
66
|
+
}
|
|
67
|
+
export interface Applicable<ActionType> {
|
|
68
|
+
use(): ActionType;
|
|
69
|
+
}
|
|
70
|
+
export declare class DefinedAction<State extends ContextState = ContextState, Term extends string = string, Spec extends ActionSpec = ActionSpec> implements Applicable<DefinedAction<State, Term, Spec>>, Handleable<State, Spec>, ImplementedAction<State, Spec> {
|
|
71
|
+
#private;
|
|
72
|
+
constructor(typeDef: TypeDef | undefined, spec: Spec, meta: ActionMeta<State, Spec>);
|
|
73
|
+
get public(): boolean;
|
|
74
|
+
get method(): string;
|
|
75
|
+
get term(): string | undefined;
|
|
76
|
+
get type(): string | undefined;
|
|
77
|
+
get typeDef(): TypeDef | undefined;
|
|
78
|
+
get name(): string;
|
|
79
|
+
get template(): string;
|
|
80
|
+
get pattern(): URLPattern;
|
|
81
|
+
get path(): string;
|
|
82
|
+
get spec(): Spec;
|
|
83
|
+
get scope(): Scope | undefined;
|
|
84
|
+
get registry(): Registry;
|
|
85
|
+
get handlers(): HandlerDefinition<State, Spec>[];
|
|
86
|
+
get contentTypes(): string[];
|
|
87
|
+
url(): string;
|
|
88
|
+
get context(): JSONLDContext;
|
|
89
|
+
jsonld(): Promise<JSONObject | null>;
|
|
90
|
+
jsonldPartial(): {
|
|
91
|
+
'@type': string;
|
|
92
|
+
'@id': string;
|
|
93
|
+
} | null;
|
|
94
|
+
/**
|
|
95
|
+
* Defines a cache handling rule for this action.
|
|
96
|
+
*
|
|
97
|
+
* Defining caching rules after the `action.define()` method is safer
|
|
98
|
+
* if validating and transforming the action payload might cause
|
|
99
|
+
* auth sensitive checks to be run which might reject the request.
|
|
100
|
+
*/
|
|
101
|
+
cache(args: CacheInstanceArgs): DefinedAction<State, string, Spec>;
|
|
102
|
+
meta(): DefinedAction<State, string, Spec>;
|
|
103
|
+
use(): DefinedAction<State, string, Spec>;
|
|
104
|
+
handle(contentType: string | string[], handler: HandlerValue | HandlerFn<State, Spec>): FinalizedAction<State, Spec>;
|
|
105
|
+
handle(args: HandlerObj<State, Spec>): FinalizedAction<State, Spec>;
|
|
106
|
+
handleRequest(args: HandleRequestArgs): Promise<ResponseTypes>;
|
|
107
|
+
}
|
|
108
|
+
export declare class Action<State extends ContextState = ContextState> implements Applicable<Action>, Handleable<State>, ImplementedAction<State> {
|
|
109
|
+
#private;
|
|
110
|
+
constructor(meta: ActionMeta<State>);
|
|
111
|
+
get public(): boolean;
|
|
112
|
+
get method(): string;
|
|
113
|
+
get term(): string | undefined;
|
|
114
|
+
get type(): string | undefined;
|
|
115
|
+
get typeDef(): TypeDef | undefined;
|
|
116
|
+
get name(): string;
|
|
117
|
+
get template(): string;
|
|
118
|
+
get pattern(): URLPattern;
|
|
119
|
+
get path(): string;
|
|
120
|
+
get spec(): ActionSpec;
|
|
121
|
+
get scope(): Scope | undefined;
|
|
122
|
+
get registry(): Registry;
|
|
123
|
+
get handlers(): HandlerDefinition[];
|
|
124
|
+
get contentTypes(): string[];
|
|
125
|
+
get context(): JSONObject;
|
|
126
|
+
url(): string;
|
|
127
|
+
jsonld(): Promise<null>;
|
|
128
|
+
jsonldPartial(): {
|
|
129
|
+
'@type': string;
|
|
130
|
+
'@id': string;
|
|
131
|
+
} | null;
|
|
132
|
+
use(): Action<State>;
|
|
133
|
+
define<Term extends string = string, Spec extends ActionSpec = ActionSpec>(args: DefineArgs<Term, Spec>): DefinedAction<State, Term, Spec>;
|
|
134
|
+
handle(contentType: string | string[], handler: HandlerValue | HandlerFn<State>): FinalizedAction<State>;
|
|
135
|
+
handle(args: HandlerObj<State>): FinalizedAction<State>;
|
|
136
|
+
handleRequest(args: HandleRequestArgs): Promise<ResponseTypes>;
|
|
137
|
+
}
|
|
138
|
+
export declare class PreAction<State extends ContextState = ContextState> implements Applicable<Action>, Handleable<State> {
|
|
139
|
+
#private;
|
|
140
|
+
constructor(meta: ActionMeta<State>);
|
|
141
|
+
use(): Action<State>;
|
|
142
|
+
define<Term extends string = string, Spec extends ActionSpec = ActionSpec>(args: DefineArgs<Term, Spec>): DefinedAction<State, Term, Spec>;
|
|
143
|
+
handle(contentType: string | string[], handler: HandlerValue | HandlerFn<State>): FinalizedAction<State>;
|
|
144
|
+
handle(args: HandlerObj<State>): FinalizedAction<State>;
|
|
145
|
+
}
|
|
146
|
+
export declare class Endpoint<State extends ContextState = ContextState> implements Applicable<Action>, Handleable<State> {
|
|
147
|
+
#private;
|
|
148
|
+
constructor(meta: ActionMeta<State>);
|
|
149
|
+
hint(hints: HintArgs): Endpoint<State>;
|
|
150
|
+
compress(): Endpoint<State>;
|
|
151
|
+
cache(args: CacheInstanceArgs): this;
|
|
152
|
+
etag(): this;
|
|
153
|
+
use(): Action<State>;
|
|
154
|
+
define<Term extends string = string, Spec extends ActionSpec = ActionSpec>(args: DefineArgs<Term, Spec>): DefinedAction<State, Term, Spec>;
|
|
155
|
+
handle(contentType: string | string[], handler: HandlerValue | HandlerFn<State>): FinalizedAction<State>;
|
|
156
|
+
handle(args: HandlerObj<State>): FinalizedAction<State>;
|
|
157
|
+
}
|
|
158
|
+
export declare class ActionAuth<State extends ContextState = ContextState> {
|
|
159
|
+
#private;
|
|
160
|
+
constructor(meta: ActionMeta<State>);
|
|
161
|
+
public(): Endpoint<State>;
|
|
162
|
+
private(): Endpoint<State>;
|
|
163
|
+
}
|