@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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/accept.d.ts +41 -0
  4. package/dist/accept.js +110 -0
  5. package/dist/accept.test.d.ts +1 -0
  6. package/dist/accept.test.js +44 -0
  7. package/dist/action.test.d.ts +1 -0
  8. package/dist/action.test.js +1 -0
  9. package/dist/actions/actionSets.d.ts +23 -0
  10. package/dist/actions/actionSets.js +49 -0
  11. package/dist/actions/actions.d.ts +163 -0
  12. package/dist/actions/actions.js +436 -0
  13. package/dist/actions/context.d.ts +78 -0
  14. package/dist/actions/context.js +112 -0
  15. package/dist/actions/meta.d.ts +49 -0
  16. package/dist/actions/meta.js +177 -0
  17. package/dist/actions/path.d.ts +21 -0
  18. package/dist/actions/path.js +83 -0
  19. package/dist/actions/path.test.d.ts +1 -0
  20. package/dist/actions/path.test.js +9 -0
  21. package/dist/actions/spec.d.ts +214 -0
  22. package/dist/actions/spec.js +1 -0
  23. package/dist/actions/types.d.ts +112 -0
  24. package/dist/actions/types.js +2 -0
  25. package/dist/actions/writer.d.ts +27 -0
  26. package/dist/actions/writer.js +140 -0
  27. package/dist/actions/writer.test.d.ts +1 -0
  28. package/dist/actions/writer.test.js +42 -0
  29. package/dist/auth/types.d.ts +14 -0
  30. package/dist/auth/types.js +1 -0
  31. package/dist/cache/cache.d.ts +30 -0
  32. package/dist/cache/cache.js +220 -0
  33. package/dist/cache/etag.d.ts +17 -0
  34. package/dist/cache/etag.js +83 -0
  35. package/dist/cache/etag.test.d.ts +1 -0
  36. package/dist/cache/etag.test.js +91 -0
  37. package/dist/cache/memory.d.ts +12 -0
  38. package/dist/cache/memory.js +36 -0
  39. package/dist/cache/types.d.ts +175 -0
  40. package/dist/cache/types.js +4 -0
  41. package/dist/errors.d.ts +11 -0
  42. package/dist/errors.js +54 -0
  43. package/dist/jsonld.d.ts +43 -0
  44. package/dist/jsonld.js +1 -0
  45. package/dist/makeTypeDefs.d.ts +27 -0
  46. package/dist/makeTypeDefs.js +70 -0
  47. package/dist/merge.d.ts +61 -0
  48. package/dist/merge.js +1 -0
  49. package/dist/mod.d.ts +14 -0
  50. package/dist/mod.js +14 -0
  51. package/dist/processAction.d.ts +15 -0
  52. package/dist/processAction.js +512 -0
  53. package/dist/registry.d.ts +88 -0
  54. package/dist/registry.js +314 -0
  55. package/dist/registry.test.d.ts +1 -0
  56. package/dist/registry.test.js +133 -0
  57. package/dist/request.d.ts +29 -0
  58. package/dist/request.js +118 -0
  59. package/dist/scopes.d.ts +35 -0
  60. package/dist/scopes.js +121 -0
  61. package/dist/scopes.test.d.ts +1 -0
  62. package/dist/scopes.test.js +55 -0
  63. package/dist/transformers/fileTransformer.d.ts +1 -0
  64. package/dist/transformers/fileTransformer.js +8 -0
  65. package/dist/types.d.ts +12 -0
  66. package/dist/types.js +1 -0
  67. package/dist/utils/alwaysArray.d.ts +1 -0
  68. package/dist/utils/alwaysArray.js +9 -0
  69. package/dist/utils/contextBuilder.d.ts +9 -0
  70. package/dist/utils/contextBuilder.js +82 -0
  71. package/dist/utils/getActionContext.d.ts +7 -0
  72. package/dist/utils/getActionContext.js +48 -0
  73. package/dist/utils/getInternalName.d.ts +6 -0
  74. package/dist/utils/getInternalName.js +7 -0
  75. package/dist/utils/getParamLocation.d.ts +2 -0
  76. package/dist/utils/getParamLocation.js +6 -0
  77. package/dist/utils/getPropertyValueSpecifications.d.ts +2 -0
  78. package/dist/utils/getPropertyValueSpecifications.js +49 -0
  79. package/dist/utils/getRequestBodyValues.d.ts +11 -0
  80. package/dist/utils/getRequestBodyValues.js +122 -0
  81. package/dist/utils/getRequestIRIValues.d.ts +14 -0
  82. package/dist/utils/getRequestIRIValues.js +133 -0
  83. package/dist/utils/isBodyInit.d.ts +1 -0
  84. package/dist/utils/isBodyInit.js +21 -0
  85. package/dist/utils/isNil.d.ts +1 -0
  86. package/dist/utils/isNil.js +4 -0
  87. package/dist/utils/isObject.d.ts +6 -0
  88. package/dist/utils/isObject.js +6 -0
  89. package/dist/utils/isPopulatedObject.d.ts +5 -0
  90. package/dist/utils/isPopulatedObject.js +8 -0
  91. package/dist/utils/isPopulatedString.d.ts +1 -0
  92. package/dist/utils/isPopulatedString.js +4 -0
  93. package/dist/utils/joinPaths.d.ts +1 -0
  94. package/dist/utils/joinPaths.js +31 -0
  95. package/dist/utils/makeAppendProblemDetails.d.ts +14 -0
  96. package/dist/utils/makeAppendProblemDetails.js +26 -0
  97. package/dist/utils/makeURLPattern.d.ts +5 -0
  98. package/dist/utils/makeURLPattern.js +12 -0
  99. package/dist/utils/normalizeURL.d.ts +4 -0
  100. package/dist/utils/normalizeURL.js +11 -0
  101. package/dist/utils/parseSearchParams.d.ts +3 -0
  102. package/dist/utils/parseSearchParams.js +24 -0
  103. package/dist/utils/preferredMediaTypes.d.ts +42 -0
  104. package/dist/utils/preferredMediaTypes.js +149 -0
  105. package/dist/utils/urlToIRI.d.ts +1 -0
  106. package/dist/utils/urlToIRI.js +8 -0
  107. package/dist/utils/validateSpecValue.d.ts +1 -0
  108. package/dist/utils/validateSpecValue.js +1 -0
  109. package/dist/validators.d.ts +16 -0
  110. package/dist/validators.js +134 -0
  111. package/lib/accept.test.ts +55 -0
  112. package/lib/accept.ts +147 -0
  113. package/lib/action.test.ts +2 -0
  114. package/lib/actions/actionSets.ts +88 -0
  115. package/lib/actions/actions.ts +795 -0
  116. package/lib/actions/context.ts +170 -0
  117. package/lib/actions/meta.ts +251 -0
  118. package/lib/actions/path.test.ts +15 -0
  119. package/lib/actions/path.ts +99 -0
  120. package/lib/actions/spec.ts +545 -0
  121. package/lib/actions/types.ts +146 -0
  122. package/lib/actions/writer.test.ts +57 -0
  123. package/lib/actions/writer.ts +176 -0
  124. package/lib/auth/types.ts +22 -0
  125. package/lib/cache/cache.ts +291 -0
  126. package/lib/cache/etag.test.ts +122 -0
  127. package/lib/cache/etag.ts +106 -0
  128. package/lib/cache/memory.ts +52 -0
  129. package/lib/cache/types.ts +240 -0
  130. package/lib/errors.ts +66 -0
  131. package/lib/jsonld.ts +67 -0
  132. package/lib/makeTypeDefs.ts +138 -0
  133. package/lib/merge.ts +86 -0
  134. package/lib/mod.ts +14 -0
  135. package/lib/processAction.ts +690 -0
  136. package/lib/registry.test.ts +174 -0
  137. package/lib/registry.ts +455 -0
  138. package/lib/request.ts +153 -0
  139. package/lib/scopes.test.ts +70 -0
  140. package/lib/scopes.ts +178 -0
  141. package/lib/transformers/fileTransformer.ts +10 -0
  142. package/lib/types.ts +13 -0
  143. package/lib/utils/alwaysArray.ts +10 -0
  144. package/lib/utils/contextBuilder.ts +111 -0
  145. package/lib/utils/getActionContext.ts +76 -0
  146. package/lib/utils/getInternalName.ts +15 -0
  147. package/lib/utils/getParamLocation.ts +14 -0
  148. package/lib/utils/getPropertyValueSpecifications.ts +76 -0
  149. package/lib/utils/getRequestBodyValues.ts +155 -0
  150. package/lib/utils/getRequestIRIValues.ts +201 -0
  151. package/lib/utils/isBodyInit.ts +22 -0
  152. package/lib/utils/isNil.ts +4 -0
  153. package/lib/utils/isObject.ts +8 -0
  154. package/lib/utils/isPopulatedObject.ts +9 -0
  155. package/lib/utils/isPopulatedString.ts +4 -0
  156. package/lib/utils/joinPaths.ts +36 -0
  157. package/lib/utils/makeAppendProblemDetails.ts +57 -0
  158. package/lib/utils/makeURLPattern.ts +18 -0
  159. package/lib/utils/normalizeURL.ts +15 -0
  160. package/lib/utils/parseSearchParams.ts +36 -0
  161. package/lib/utils/preferredMediaTypes.ts +220 -0
  162. package/lib/utils/urlToIRI.ts +11 -0
  163. package/lib/utils/validateSpecValue.ts +0 -0
  164. package/lib/validators.ts +186 -0
  165. 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
@@ -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
+ }