@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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { isObject } from './utils/isObject.js';
|
|
2
|
+
import { preferredMediaTypes } from './utils/preferredMediaTypes.js';
|
|
3
|
+
// deno-lint-ignore no-explicit-any
|
|
4
|
+
export function isFileData(value) {
|
|
5
|
+
if (typeof value === 'string' && value.startsWith('data:')) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
else if (value instanceof File) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
export function isObjectArraySpec(spec) {
|
|
14
|
+
return isObject(spec.properties) && Boolean(spec.multipleValues);
|
|
15
|
+
}
|
|
16
|
+
export function isObjectSpec(spec) {
|
|
17
|
+
return isObject(spec.properties) && !spec.multipleValues;
|
|
18
|
+
}
|
|
19
|
+
export function isArraySpec(spec) {
|
|
20
|
+
return !isObject(spec.properties) && Boolean(spec.multipleValues);
|
|
21
|
+
}
|
|
22
|
+
export function failsRequiredRequirement(value, specValue) {
|
|
23
|
+
return specValue.valueRequired && (typeof value === 'undefined' || value === null);
|
|
24
|
+
}
|
|
25
|
+
export function failsTypeRequirement(value, specValue) {
|
|
26
|
+
const dataType = specValue.dataType;
|
|
27
|
+
if (dataType == null) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
else if (specValue.dataType === 'file' && isFileData(value)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
else if (Array.isArray(value)) {
|
|
34
|
+
for (let index = 0; index < value.length; index++) {
|
|
35
|
+
const item = value[index];
|
|
36
|
+
// deno-lint-ignore valid-typeof
|
|
37
|
+
if (typeof item !== dataType) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
// deno-lint-ignore valid-typeof
|
|
44
|
+
return typeof value !== specValue.dataType;
|
|
45
|
+
}
|
|
46
|
+
export function failsContentTypeRequirement(value, specValue) {
|
|
47
|
+
if (specValue.type !== 'file' ||
|
|
48
|
+
specValue.contentType == null ||
|
|
49
|
+
!isFileData(value)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
let contentType;
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
contentType = value.replace(/^data\:/, '').split(';')[0];
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
contentType = value.type;
|
|
58
|
+
}
|
|
59
|
+
if (contentType == null) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
else if (typeof specValue.contentType === 'string') {
|
|
63
|
+
return !preferredMediaTypes(contentType, [specValue.contentType]);
|
|
64
|
+
}
|
|
65
|
+
return !preferredMediaTypes(contentType, specValue.contentType);
|
|
66
|
+
}
|
|
67
|
+
export function failsMinValue(value, specValue) {
|
|
68
|
+
if (!specValue.valueRequired && value == null) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (typeof specValue.minValue !== 'number') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
else if (typeof value !== 'number') {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return value < specValue.minValue;
|
|
78
|
+
}
|
|
79
|
+
export function failsMaxValue(value, specValue) {
|
|
80
|
+
if (!specValue.valueRequired && value == null) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (typeof specValue.maxValue !== 'number') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
else if (typeof value !== 'number') {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return value > specValue.maxValue;
|
|
90
|
+
}
|
|
91
|
+
export function failsStepValue(value, specValue) {
|
|
92
|
+
if (typeof specValue.stepValue !== 'number') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
else if (typeof value !== 'number') {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return value % specValue.stepValue !== 0;
|
|
99
|
+
}
|
|
100
|
+
export function failsPatternValue(value, specValue) {
|
|
101
|
+
if (typeof specValue.valuePattern !== 'string') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
else if (typeof value !== 'string') {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const regexp = new RegExp(specValue.valuePattern);
|
|
108
|
+
return !regexp.test(value);
|
|
109
|
+
}
|
|
110
|
+
export function failValueMinLength(value, specValue) {
|
|
111
|
+
if (typeof specValue.valueMinLength !== 'number') {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (typeof value === 'string' || Array.isArray(value)) {
|
|
115
|
+
return value.length < specValue.valueMinLength;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
export function failValueMaxLength(value, specValue) {
|
|
120
|
+
if (typeof specValue.valueMaxLength !== 'number') {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === 'string' || Array.isArray(value)) {
|
|
124
|
+
return value.length > specValue.valueMaxLength;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
export function failsValidator(value, specValue) {
|
|
129
|
+
if (typeof specValue.validator !== 'function') {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const validator = specValue.validator;
|
|
133
|
+
return !validator(value);
|
|
134
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { Accept, ContentTypeCache } from "./accept.js";
|
|
4
|
+
|
|
5
|
+
const cache = new ContentTypeCache([
|
|
6
|
+
'text/html',
|
|
7
|
+
'application/ld+json',
|
|
8
|
+
'application/json',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
test('Matches with no accepts header', () => {
|
|
12
|
+
const accept = Accept.from(new Request('https://example.com'));
|
|
13
|
+
|
|
14
|
+
assert(accept.negotiate(cache) === 'text/html');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('Matches with specific accepts header', () => {
|
|
18
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
19
|
+
headers: {
|
|
20
|
+
accept: 'application/ld+json',
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
assert(accept.negotiate(cache) === 'application/ld+json');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Matches with glob header', () => {
|
|
28
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
29
|
+
headers: {
|
|
30
|
+
accept: '*/*',
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
assert(accept.negotiate(cache) === 'text/html');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Matches with glob subtype header', () => {
|
|
38
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
39
|
+
headers: {
|
|
40
|
+
accept: 'application/*',
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
assert(accept.negotiate(cache) === 'application/ld+json');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Matches q prioritizing in header', () => {
|
|
48
|
+
const accept = Accept.from(new Request('https://example.com', {
|
|
49
|
+
headers: {
|
|
50
|
+
accept: 'text/html; q=0.5, application/json; q=1, text/cvs; q=.7',
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
assert(accept.negotiate(cache) === 'application/json');
|
|
55
|
+
});
|
package/lib/accept.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a content type cache usually from the set of content type
|
|
5
|
+
* options supported by an action or action set.
|
|
6
|
+
*/
|
|
7
|
+
export class ContentTypeCache {
|
|
8
|
+
default: string;
|
|
9
|
+
set: Set<string>;
|
|
10
|
+
map: Map<string, string> = new Map();
|
|
11
|
+
contentTypes: string[];
|
|
12
|
+
|
|
13
|
+
constructor(contentTypes: string[]) {
|
|
14
|
+
this.default = contentTypes[0];
|
|
15
|
+
this.contentTypes = contentTypes;
|
|
16
|
+
this.set = new Set(contentTypes);
|
|
17
|
+
|
|
18
|
+
this.set.add('*/*');
|
|
19
|
+
this.map.set('*/*', contentTypes[0]);
|
|
20
|
+
|
|
21
|
+
for (let index = 0; index < contentTypes.length; index++) {
|
|
22
|
+
const contentType = contentTypes[index];
|
|
23
|
+
const type = contentType.replace(/[^\/]*$/, '*');
|
|
24
|
+
|
|
25
|
+
this.map.set(contentType, contentType);
|
|
26
|
+
|
|
27
|
+
if (!this.map.has(type)) {
|
|
28
|
+
this.set.add(type);
|
|
29
|
+
this.map.set(type, contentType);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get [Symbol.toStringTag]() {
|
|
35
|
+
return `[ContentTypeCache ${this.contentTypes.join(' ')}]`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* This accept object is created from a request before any content negotiation
|
|
41
|
+
* begins allowing all subsequent checks to re-use the same caches header values.
|
|
42
|
+
*
|
|
43
|
+
* @todo Implement support for all accept headers.
|
|
44
|
+
*
|
|
45
|
+
* @param accept - The value of the request's accept header.
|
|
46
|
+
* @param acceptLanguage - The value of the request's accept-language header.
|
|
47
|
+
* @param acceptEncoding - The value of the request's accept-encoding header.
|
|
48
|
+
*/
|
|
49
|
+
export class Accept {
|
|
50
|
+
acceptRe = /(?<ct>[^,;\s]+)(;\s?q=(?<q>(\d(\.\d+)|(.\d))))?/g;
|
|
51
|
+
accept: string[] = [];
|
|
52
|
+
acceptCache: Set<string> = new Set();
|
|
53
|
+
//#acceptLanguage: string[] = [];
|
|
54
|
+
//#acceptLanguageCache: Set<string> = new Set();
|
|
55
|
+
//#acceptEncoding: string[] = [];
|
|
56
|
+
//#acceptEncodingCache: Set<string> = new Set();
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
accept: string | null,
|
|
60
|
+
_acceptLanguage: string | null,
|
|
61
|
+
_acceptEncoding: string | null,
|
|
62
|
+
) {
|
|
63
|
+
[this.accept, this.acceptCache] = this.#process(accept);
|
|
64
|
+
//[this.#acceptLanguage, this.#acceptLanguageCache] = this.#process(acceptLanguage);
|
|
65
|
+
//[this.#acceptEncoding, this.#acceptEncodingCache] = this.#process(acceptEncoding);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates an accept instance from a request or response instance
|
|
70
|
+
*/
|
|
71
|
+
static from(req: Request): Accept {
|
|
72
|
+
const accept = req.headers.get('Accept');
|
|
73
|
+
const acceptLanguage = req.headers.get('Accept-Language');
|
|
74
|
+
const acceptEncoding = req.headers.get('Accept-Encoding');
|
|
75
|
+
|
|
76
|
+
console.log('ACCEPT', accept);
|
|
77
|
+
|
|
78
|
+
return new Accept(
|
|
79
|
+
accept,
|
|
80
|
+
acceptLanguage,
|
|
81
|
+
acceptEncoding,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
debug() {
|
|
86
|
+
return this.accept.join(' ');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Negotiates against the cached set of content type options.
|
|
91
|
+
*
|
|
92
|
+
* @param contentType Content type cache built for an action.
|
|
93
|
+
*/
|
|
94
|
+
negotiate(contentType: ContentTypeCache): null | string {
|
|
95
|
+
if (this.accept.length === 0) {
|
|
96
|
+
return contentType.default;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// TODO: check might be over-optimizing.
|
|
100
|
+
const intersection = this.acceptCache.intersection(contentType.set);
|
|
101
|
+
|
|
102
|
+
if (intersection.size === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let index = 0; index < this.accept.length; index++) {
|
|
107
|
+
const match = contentType.map.get(this.accept[index]);
|
|
108
|
+
|
|
109
|
+
if (match != null) {
|
|
110
|
+
return match;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#process(header: string | null): [string[], Set<string>] {
|
|
118
|
+
if (header == null) {
|
|
119
|
+
return [[], new Set('*/*')];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let match: RegExpExecArray | null;
|
|
123
|
+
|
|
124
|
+
const items: Array<{ ct: string, q: number }> = [];
|
|
125
|
+
const cache = new Set<string>();
|
|
126
|
+
|
|
127
|
+
while ((match = this.acceptRe.exec(header))) {
|
|
128
|
+
const ct = match.groups?.ct as string;
|
|
129
|
+
const q = Number(match.groups?.q ?? 1);
|
|
130
|
+
|
|
131
|
+
cache.add(ct);
|
|
132
|
+
items.push({ ct, q });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
items.sort((a, b) => b.q - a.q);
|
|
136
|
+
|
|
137
|
+
return [
|
|
138
|
+
items.map(({ ct }) => ct),
|
|
139
|
+
cache,
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get [Symbol.toStringTag]() {
|
|
144
|
+
return `[Accept ${this.accept.join(' ')}]`
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ContentTypeCache, type Accept } from "../accept.js";
|
|
2
|
+
import { makeURLPattern } from "../utils/makeURLPattern.js";
|
|
3
|
+
import type { ActionMeta } from "./meta.js";
|
|
4
|
+
import type { ImplementedAction } from "./types.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export type UnsupportedContentTypeMatch = {
|
|
8
|
+
type: 'unsupported-content-type';
|
|
9
|
+
contentTypes: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ActionAcceptMatch = {
|
|
13
|
+
type: 'match';
|
|
14
|
+
action: ImplementedAction;
|
|
15
|
+
contentType?: string;
|
|
16
|
+
language?: string;
|
|
17
|
+
encoding?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ActionMatchResult =
|
|
21
|
+
| UnsupportedContentTypeMatch
|
|
22
|
+
| ActionAcceptMatch
|
|
23
|
+
;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A set of actions grouped by having equal methods and equivilent paths.
|
|
27
|
+
*/
|
|
28
|
+
export class ActionSet {
|
|
29
|
+
#rootIRI: string;
|
|
30
|
+
#method: string;
|
|
31
|
+
#urlPattern: URLPattern;
|
|
32
|
+
#contentTypeActionMap: Map<string, ImplementedAction>;
|
|
33
|
+
#ctc: ContentTypeCache;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
rootIRI: string,
|
|
37
|
+
method: string,
|
|
38
|
+
path: string,
|
|
39
|
+
meta: ActionMeta[],
|
|
40
|
+
) {
|
|
41
|
+
this.#rootIRI = rootIRI;
|
|
42
|
+
this.#method = method;
|
|
43
|
+
|
|
44
|
+
this.#urlPattern = makeURLPattern(path, rootIRI);
|
|
45
|
+
|
|
46
|
+
[this.#contentTypeActionMap, this.#ctc] = this.#process(meta);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
matches(method: string, path: string, accept: Accept): null | ActionMatchResult {
|
|
50
|
+
if (method !== this.#method) {
|
|
51
|
+
return null;
|
|
52
|
+
} else if (!this.#urlPattern.test(path, this.#rootIRI)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const contentType = accept.negotiate(this.#ctc);
|
|
57
|
+
const action = this.#contentTypeActionMap.get(contentType as string);
|
|
58
|
+
|
|
59
|
+
if (contentType != null && action != null) {
|
|
60
|
+
return {
|
|
61
|
+
type: 'match',
|
|
62
|
+
action,
|
|
63
|
+
contentType,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#process(meta: ActionMeta[]): [Map<string, ImplementedAction>, ContentTypeCache] {
|
|
71
|
+
const contentTypes: string[] = [];
|
|
72
|
+
const contentTypeActionMap: Map<string, ImplementedAction> = new Map();
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < meta.length; i++) {
|
|
75
|
+
const action = meta[i].action as ImplementedAction;
|
|
76
|
+
|
|
77
|
+
for (let j = 0; j < action.contentTypes.length; j++) {
|
|
78
|
+
const contentType = action.contentTypes[j];
|
|
79
|
+
|
|
80
|
+
contentTypes.push(contentType);
|
|
81
|
+
contentTypeActionMap.set(contentType, action);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [contentTypeActionMap, new ContentTypeCache(contentTypes)];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|