@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/dist/scopes.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { joinPaths } from "./utils/joinPaths.js";
|
|
2
|
+
import { ActionAuth } from "./actions/actions.js";
|
|
3
|
+
import { ActionMeta } from "./actions/meta.js";
|
|
4
|
+
import { HTTP } from './registry.js';
|
|
5
|
+
export class Scope {
|
|
6
|
+
#path;
|
|
7
|
+
#serverTiming = false;
|
|
8
|
+
#registry;
|
|
9
|
+
#writer;
|
|
10
|
+
#http;
|
|
11
|
+
#children = [];
|
|
12
|
+
#public = true;
|
|
13
|
+
#propergateMeta;
|
|
14
|
+
constructor({ path, serverTiming, registry, writer, propergateMeta, }) {
|
|
15
|
+
this.#path = path;
|
|
16
|
+
this.#serverTiming = serverTiming;
|
|
17
|
+
this.#registry = registry;
|
|
18
|
+
this.#writer = writer;
|
|
19
|
+
this.#http = new HTTP(this);
|
|
20
|
+
this.#propergateMeta = propergateMeta;
|
|
21
|
+
}
|
|
22
|
+
get path() {
|
|
23
|
+
return this.#path;
|
|
24
|
+
}
|
|
25
|
+
get registry() {
|
|
26
|
+
return this.#registry;
|
|
27
|
+
}
|
|
28
|
+
get http() {
|
|
29
|
+
return this.#http;
|
|
30
|
+
}
|
|
31
|
+
get actions() {
|
|
32
|
+
return this.#children
|
|
33
|
+
.filter((meta) => {
|
|
34
|
+
if (meta.action == null) {
|
|
35
|
+
console.warn(`Action ${meta.method}: ${meta.path} not fully implemented before processing`);
|
|
36
|
+
}
|
|
37
|
+
return meta.action != null;
|
|
38
|
+
})
|
|
39
|
+
.map((meta) => meta.action);
|
|
40
|
+
}
|
|
41
|
+
get handlers() {
|
|
42
|
+
return this.actions.flatMap((action) => action.handlers);
|
|
43
|
+
}
|
|
44
|
+
public() {
|
|
45
|
+
this.#public = true;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
private() {
|
|
49
|
+
this.#public = false;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates any HTTP method.
|
|
54
|
+
*
|
|
55
|
+
* @param method The HTTP method.
|
|
56
|
+
* @param name Name for the action being produced.
|
|
57
|
+
* @param path Path the action responds to.
|
|
58
|
+
*/
|
|
59
|
+
method(method, name, path) {
|
|
60
|
+
const meta = new ActionMeta(this.#registry.rootIRI, method.toUpperCase(), name, path, this.#registry, this.#writer, this);
|
|
61
|
+
meta.serverTiming = this.#serverTiming;
|
|
62
|
+
this.#children.push(meta);
|
|
63
|
+
this.#propergateMeta(meta);
|
|
64
|
+
return new ActionAuth(meta);
|
|
65
|
+
}
|
|
66
|
+
url() {
|
|
67
|
+
return joinPaths(this.#registry.rootIRI, this.#path);
|
|
68
|
+
}
|
|
69
|
+
finalize() {
|
|
70
|
+
const partials = {
|
|
71
|
+
'@id': this.url(),
|
|
72
|
+
'@container': '@type',
|
|
73
|
+
};
|
|
74
|
+
for (let index = 0; index < this.#children.length; index++) {
|
|
75
|
+
const meta = this.#children[index];
|
|
76
|
+
const action = meta.action;
|
|
77
|
+
if (action == null || action.type == null) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const partial = action.jsonldPartial();
|
|
81
|
+
if (partial == null) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
partials[partial['@type']] = partial;
|
|
85
|
+
}
|
|
86
|
+
if (this.#public) {
|
|
87
|
+
this.#registry.http.get('scope', this.#path)
|
|
88
|
+
.public()
|
|
89
|
+
.handle('application/ld+json', (ctx) => {
|
|
90
|
+
ctx.body = JSON.stringify(partials);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
this.#registry.http.get('scope', this.#path)
|
|
95
|
+
.public()
|
|
96
|
+
.handle('application/ld+json', (ctx) => {
|
|
97
|
+
ctx.body = JSON.stringify(partials);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
for (let index = 0; index < this.#children.length; index++) {
|
|
101
|
+
const action = this.#children[index].action;
|
|
102
|
+
if (action == null || action.type == null) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (this.#public) {
|
|
106
|
+
this.#registry.http.get('scope-action', joinPaths(this.url(), action.name))
|
|
107
|
+
.public()
|
|
108
|
+
.handle('application/ld+json', async (ctx) => {
|
|
109
|
+
ctx.body = JSON.stringify(await action.jsonld());
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.#registry.http.get('scope-action', joinPaths(this.url(), action.name))
|
|
114
|
+
.private()
|
|
115
|
+
.handle('application/ld+json', async (ctx) => {
|
|
116
|
+
ctx.body = JSON.stringify(await action.jsonld());
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { Registry } from "./registry.js";
|
|
4
|
+
import { makeTypeDef, makeTypeDefs } from "./makeTypeDefs.js";
|
|
5
|
+
const typeDefs = makeTypeDefs([
|
|
6
|
+
makeTypeDef('GetThing', 'https://schema.example.com/'),
|
|
7
|
+
makeTypeDef('name', 'https://schema.org/'),
|
|
8
|
+
makeTypeDef('description', 'https://schema.org/'),
|
|
9
|
+
]);
|
|
10
|
+
const registry = new Registry({
|
|
11
|
+
rootIRI: 'https://example.com',
|
|
12
|
+
});
|
|
13
|
+
const scope = registry.scope('/actions')
|
|
14
|
+
.public();
|
|
15
|
+
scope.http.get('get-thing', '/thing')
|
|
16
|
+
.public()
|
|
17
|
+
.define({
|
|
18
|
+
typeDef: typeDefs.GetThing,
|
|
19
|
+
spec: {
|
|
20
|
+
name: {
|
|
21
|
+
typeDef: typeDefs.name,
|
|
22
|
+
valueMinLength: 3,
|
|
23
|
+
valueRequired: true,
|
|
24
|
+
},
|
|
25
|
+
description: {
|
|
26
|
+
type: 'https://schema.org/description',
|
|
27
|
+
valueRequired: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
registry.finalize();
|
|
32
|
+
test('It responds with actions partials as a jsonld type map', async () => {
|
|
33
|
+
const res = await registry.handleRequest(new Request('https://example.com/actions'));
|
|
34
|
+
const body = await res.json();
|
|
35
|
+
assert.deepEqual(body[typeDefs.GetThing.type], {
|
|
36
|
+
'@type': typeDefs.GetThing.type,
|
|
37
|
+
'@id': 'https://example.com/actions/get-thing',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
test('It responds with actions partials as a jsonld type map', async () => {
|
|
41
|
+
const res = await registry.handleRequest(new Request('https://example.com/actions/get-thing'));
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
// compacted representation is returned
|
|
44
|
+
assert.equal(body['@id'], 'https://example.com/actions/get-thing');
|
|
45
|
+
assert.equal(body['@type'], typeDefs.GetThing.term);
|
|
46
|
+
assert.deepEqual(body[`${typeDefs.name.term}-input`], {
|
|
47
|
+
'@type': 'https://schema.org/PropertyValueSpecification',
|
|
48
|
+
valueRequired: true,
|
|
49
|
+
valueMinLength: 3,
|
|
50
|
+
});
|
|
51
|
+
assert.deepEqual(body[`${typeDefs.description.term}-input`], {
|
|
52
|
+
'@type': 'https://schema.org/PropertyValueSpecification',
|
|
53
|
+
valueRequired: false,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fileTransformer(value: File | string): Promise<File | Blob>;
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function alwaysArray<T>(value: T | Array<T>): Array<T>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JSONLDContext, ContextVersion, TypeDef } from "../jsonld.js";
|
|
2
|
+
export declare function contextBuilder({ vocab, version, protect, idTerm, aliases, typeDefs: argsTypeDefs, }: {
|
|
3
|
+
idTerm?: string;
|
|
4
|
+
vocab?: string;
|
|
5
|
+
version?: ContextVersion;
|
|
6
|
+
protect?: boolean;
|
|
7
|
+
aliases?: Record<string, string>;
|
|
8
|
+
typeDefs?: Record<string, TypeDef> | Array<TypeDef>;
|
|
9
|
+
}): JSONLDContext;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isNil } from "./isNil.js";
|
|
2
|
+
import { isPopulatedObject } from "./isPopulatedObject.js";
|
|
3
|
+
export function contextBuilder({ vocab, version = 1.1, protect = false, idTerm, aliases, typeDefs: argsTypeDefs, }) {
|
|
4
|
+
let typeDefs = null;
|
|
5
|
+
if (Array.isArray(argsTypeDefs)) {
|
|
6
|
+
typeDefs = argsTypeDefs;
|
|
7
|
+
}
|
|
8
|
+
else if (!isNil(argsTypeDefs)) {
|
|
9
|
+
typeDefs = Object.values(argsTypeDefs);
|
|
10
|
+
}
|
|
11
|
+
const inverseAliases = {};
|
|
12
|
+
const shadowedAliases = [];
|
|
13
|
+
const context = {};
|
|
14
|
+
if (version === 1.1) {
|
|
15
|
+
context['@version'] = version;
|
|
16
|
+
}
|
|
17
|
+
if (vocab != null) {
|
|
18
|
+
context['@vocab'] = vocab;
|
|
19
|
+
}
|
|
20
|
+
if (protect) {
|
|
21
|
+
context['@protected'] = true;
|
|
22
|
+
}
|
|
23
|
+
if (idTerm != null) {
|
|
24
|
+
context[idTerm] = '@id';
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(typeDefs)) {
|
|
27
|
+
for (const typeDef of typeDefs) {
|
|
28
|
+
if (typeDef.term.startsWith('@')) {
|
|
29
|
+
throw new Error(`Terms cannot start with @. Recieved ${typeDef.term}`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof aliases !== 'undefined' && Object.hasOwn(aliases, typeDef.term)) {
|
|
32
|
+
shadowedAliases.push(typeDef.term);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (typeof aliases !== 'undefined') {
|
|
37
|
+
for (const [alias, iri] of Object.entries(aliases)) {
|
|
38
|
+
if (iri === vocab) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!shadowedAliases.includes(alias)) {
|
|
42
|
+
context[alias] = iri;
|
|
43
|
+
inverseAliases[iri] = alias;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(typeDefs)) {
|
|
48
|
+
for (const typeDef of typeDefs) {
|
|
49
|
+
if (idTerm === typeDef.term) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
let type = typeDef.type;
|
|
53
|
+
const matchesVocab = typeDef.type.startsWith(vocab);
|
|
54
|
+
if (vocab != null && matchesVocab) {
|
|
55
|
+
type = typeDef.type.replace(vocab, '');
|
|
56
|
+
}
|
|
57
|
+
else if (typeof aliases !== 'undefined') {
|
|
58
|
+
for (const [alias, value] of Object.entries(aliases)) {
|
|
59
|
+
if (typeDef.type.startsWith(value)) {
|
|
60
|
+
type = typeDef.type.replace(value, `${alias}:`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!matchesVocab && isPopulatedObject(typeDef.contextDefinition)) {
|
|
65
|
+
context[typeDef.term] = {
|
|
66
|
+
'@id': type,
|
|
67
|
+
...typeDef.contextDefinition,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
else if (isPopulatedObject(typeDef.contextDefinition)) {
|
|
71
|
+
context[typeDef.term] = {
|
|
72
|
+
'@id': type,
|
|
73
|
+
...typeDef.contextDefinition,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
else if (!matchesVocab) {
|
|
77
|
+
context[typeDef.term] = type;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return context;
|
|
82
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JSONLDContext } from "../jsonld.js";
|
|
2
|
+
import type { ActionSpec } from '../actions/spec.js';
|
|
3
|
+
export declare function getActionContext({ spec, vocab, aliases, }: {
|
|
4
|
+
vocab?: string;
|
|
5
|
+
aliases?: Record<string, string>;
|
|
6
|
+
spec: ActionSpec;
|
|
7
|
+
}): JSONLDContext;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { makeTypeDef, makeTypeDefs } from "../makeTypeDefs.js";
|
|
2
|
+
import { contextBuilder } from "./contextBuilder.js";
|
|
3
|
+
const defaultTypeDefs = makeTypeDefs([
|
|
4
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'Entrypoint' }),
|
|
5
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'target' }),
|
|
6
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'httpMethod' }),
|
|
7
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'urlTemplate' }),
|
|
8
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'PropertyValueSpecification' }),
|
|
9
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'maxValue' }),
|
|
10
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'minValue' }),
|
|
11
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'readonlyValue' }),
|
|
12
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'stepValue' }),
|
|
13
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'valueName' }),
|
|
14
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'valueRequired' }),
|
|
15
|
+
makeTypeDef({ schema: 'https://schema.org/', term: 'multipleValues' }),
|
|
16
|
+
]);
|
|
17
|
+
export function getActionContext({ spec, vocab, aliases, }) {
|
|
18
|
+
const typeDefs = Object.values(defaultTypeDefs);
|
|
19
|
+
function searchAndAssignContextValues(term, propertySpec) {
|
|
20
|
+
if (propertySpec.typeDef != null) {
|
|
21
|
+
typeDefs.push(propertySpec.typeDef);
|
|
22
|
+
typeDefs.push(makeTypeDef({
|
|
23
|
+
term: `${term}-input`,
|
|
24
|
+
type: `${propertySpec.typeDef.type}-input`,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
else if (propertySpec.type === 'string') {
|
|
28
|
+
typeDefs.push(makeTypeDef({ term, type: propertySpec.type }));
|
|
29
|
+
typeDefs.push(makeTypeDef({
|
|
30
|
+
term: `${term}-input`,
|
|
31
|
+
type: `${propertySpec.type}-input`,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
if (propertySpec.properties != null) {
|
|
35
|
+
for (const [term, childPropertySpec] of Object.entries(propertySpec.properties)) {
|
|
36
|
+
searchAndAssignContextValues(term, childPropertySpec);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const [term, propertySpec] of Object.entries(spec)) {
|
|
41
|
+
searchAndAssignContextValues(term, propertySpec);
|
|
42
|
+
}
|
|
43
|
+
return contextBuilder({
|
|
44
|
+
vocab,
|
|
45
|
+
aliases,
|
|
46
|
+
typeDefs,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isNil } from "./isNil.js";
|
|
2
|
+
import { isObject } from "./isObject.js";
|
|
3
|
+
// deno-lint-ignore no-explicit-any
|
|
4
|
+
export async function getPropertyValueSpecifications(spec) {
|
|
5
|
+
async function splitSpecAndValue(propertySpec) {
|
|
6
|
+
let options = null;
|
|
7
|
+
const specValue = {};
|
|
8
|
+
const inputSpec = {
|
|
9
|
+
'@type': 'https://schema.org/PropertyValueSpecification',
|
|
10
|
+
defaultValue: propertySpec.defaultValue,
|
|
11
|
+
maxValue: propertySpec.maxValue,
|
|
12
|
+
minValue: propertySpec.minValue,
|
|
13
|
+
readonlyValue: propertySpec.readonlyValue,
|
|
14
|
+
stepValue: propertySpec.stepValue,
|
|
15
|
+
valueName: propertySpec.valueName,
|
|
16
|
+
valueRequired: propertySpec.valueRequired,
|
|
17
|
+
multipleValues: propertySpec.multipleValues,
|
|
18
|
+
valueMinLength: propertySpec.valueMinLength,
|
|
19
|
+
valueMaxLength: propertySpec.valueMaxLength,
|
|
20
|
+
valuePatern: propertySpec.valuePattern,
|
|
21
|
+
};
|
|
22
|
+
if (typeof propertySpec.options === 'function') {
|
|
23
|
+
options = await propertySpec.options();
|
|
24
|
+
}
|
|
25
|
+
else if (!isNil(propertySpec.options)) {
|
|
26
|
+
options = propertySpec.options;
|
|
27
|
+
}
|
|
28
|
+
if (!isObject(propertySpec.properties)) {
|
|
29
|
+
return [inputSpec, options];
|
|
30
|
+
}
|
|
31
|
+
for (const [term, childPropertySpec] of Object.entries(propertySpec.properties)) {
|
|
32
|
+
const [childInputSpec, childSpecValue] = await splitSpecAndValue(childPropertySpec);
|
|
33
|
+
specValue[`${term}-input`] = childInputSpec;
|
|
34
|
+
if (childSpecValue != null) {
|
|
35
|
+
specValue[term] = childSpecValue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [inputSpec, specValue];
|
|
39
|
+
}
|
|
40
|
+
const specValue = {};
|
|
41
|
+
for (const [term, propertySpec] of Object.entries(spec)) {
|
|
42
|
+
const [childInputSpec, childSpecValue] = await splitSpecAndValue(propertySpec);
|
|
43
|
+
specValue[`${term}-input`] = childInputSpec;
|
|
44
|
+
if (childSpecValue != null) {
|
|
45
|
+
specValue[term] = childSpecValue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return specValue;
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JSONValue } from "../jsonld.js";
|
|
2
|
+
import type { ContextState, ActionSpec } from "../actions/spec.js";
|
|
3
|
+
import type { ImplementedAction } from "../actions/types.js";
|
|
4
|
+
export type BodyValue = Record<string, JSONValue>;
|
|
5
|
+
export type RequestBodyResult = {
|
|
6
|
+
bodyValues: BodyValue;
|
|
7
|
+
};
|
|
8
|
+
export declare function getRequestBodyValues<State extends ContextState = ContextState, Spec extends ActionSpec<ContextState> = ActionSpec<ContextState>>({ req, action, }: {
|
|
9
|
+
req: Request;
|
|
10
|
+
action: ImplementedAction<State, Spec>;
|
|
11
|
+
}): Promise<RequestBodyResult>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ProblemDetailsError } from "../errors.js";
|
|
2
|
+
import jsonld from 'jsonld';
|
|
3
|
+
export async function getRequestBodyValues({ req, action, }) {
|
|
4
|
+
let bodyValues = {};
|
|
5
|
+
const contentType = req.headers.get('content-type');
|
|
6
|
+
const mappedTypes = Object.entries(action.spec)
|
|
7
|
+
.reduce((acc, [term, propertySpec]) => {
|
|
8
|
+
return {
|
|
9
|
+
...acc,
|
|
10
|
+
[propertySpec.typeDef.term]: {
|
|
11
|
+
term,
|
|
12
|
+
propertySpec,
|
|
13
|
+
},
|
|
14
|
+
[propertySpec.type || propertySpec.typeDef?.type]: {
|
|
15
|
+
term,
|
|
16
|
+
propertySpec,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}, {});
|
|
20
|
+
if (contentType?.startsWith('multipart/form-data')) {
|
|
21
|
+
// multipart should be sent using expanded types which get normalized
|
|
22
|
+
// into the compact terms for each type.
|
|
23
|
+
// otherwise json requests would need to also need to use
|
|
24
|
+
// expanded terms.
|
|
25
|
+
const formData = await req.formData();
|
|
26
|
+
for (const [name, part] of formData.entries()) {
|
|
27
|
+
if (typeof name !== 'string') {
|
|
28
|
+
throw new ProblemDetailsError(400, {
|
|
29
|
+
title: 'Unnamed parameter in request multipart body',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
let term;
|
|
33
|
+
let propertySpec;
|
|
34
|
+
if (typeof part !== 'string' && (part.type && !part.type.startsWith('text/plain'))) {
|
|
35
|
+
term = mappedTypes[name].term;
|
|
36
|
+
propertySpec = mappedTypes[name].propertySpec;
|
|
37
|
+
if (!term || !propertySpec) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (propertySpec.dataType !== 'file') {
|
|
41
|
+
throw new ProblemDetailsError(400, {
|
|
42
|
+
title: `Unexpected content '${name}' in request multipart body`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//bodyValues[term] = part;
|
|
46
|
+
bodyValues[term] = null;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (typeof part === 'string' || part.type === 'text/plain' || part.type == null) {
|
|
50
|
+
term = mappedTypes[name].term;
|
|
51
|
+
propertySpec = mappedTypes[name].propertySpec;
|
|
52
|
+
}
|
|
53
|
+
else if (part.name) {
|
|
54
|
+
term = mappedTypes[name].term;
|
|
55
|
+
propertySpec = mappedTypes[name].propertySpec;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new ProblemDetailsError(400, {
|
|
59
|
+
title: `Unexpected content '${name}' in request multipart body`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (!term || !propertySpec) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const textValue = typeof part === 'string' ? part : await part.text();
|
|
66
|
+
if (!textValue) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (propertySpec.dataType === 'number' && /\d+(\.\d+)?/.test(textValue)) {
|
|
70
|
+
bodyValues[term] = Number(textValue);
|
|
71
|
+
}
|
|
72
|
+
else if (propertySpec.dataType === 'boolean') {
|
|
73
|
+
bodyValues[term] = textValue === 'true';
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
bodyValues[term] = textValue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (contentType?.startsWith('application/json')) {
|
|
81
|
+
try {
|
|
82
|
+
bodyValues = await req.json();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new ProblemDetailsError(400, {
|
|
86
|
+
title: 'Failed to parse JSON body',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (contentType?.startsWith('application/ld+json')) {
|
|
91
|
+
let source;
|
|
92
|
+
let expanded;
|
|
93
|
+
let compacted;
|
|
94
|
+
try {
|
|
95
|
+
source = await req.json();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new ProblemDetailsError(400, {
|
|
99
|
+
title: 'Failed to parse JSON body',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
expanded = await jsonld.expand(source);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw new ProblemDetailsError(400, {
|
|
107
|
+
title: 'Failed to expand JSON-LD body',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
compacted = await jsonld.compact(expanded, action.context);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new ProblemDetailsError(400, {
|
|
115
|
+
title: 'Failed to compact JSON-LD body',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
delete compacted['@context'];
|
|
119
|
+
bodyValues = compacted;
|
|
120
|
+
}
|
|
121
|
+
return { bodyValues };
|
|
122
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImplementedAction } from "../actions/types.js";
|
|
2
|
+
import type { ActionSpec, ContextState, FileSingleSpec, FileMultiSpec, BooleanSingleSpec, BooleanMultiSpec, NumberSingleSpec, NumberMultiSpec, StringSingleSpec, StringMultiSpec, ParsedIRIValues } from "../actions/spec.js";
|
|
3
|
+
export type IRIValue<Spec extends ActionSpec<ContextState> = ActionSpec<ContextState>> = {
|
|
4
|
+
[Term in keyof Spec]: (Spec[Term] extends FileSingleSpec | FileMultiSpec ? never : Spec[Term]['valueName'] extends string ? (Spec[Term] extends BooleanSingleSpec ? boolean : Spec[Term] extends BooleanMultiSpec ? boolean[] : Spec[Term] extends NumberSingleSpec ? number : Spec[Term] extends NumberMultiSpec ? number[] : Spec[Term] extends StringSingleSpec ? string : Spec[Term] extends StringMultiSpec ? string[] : never) : never);
|
|
5
|
+
};
|
|
6
|
+
export type RequestIRIResult<Spec extends ActionSpec<ContextState> = ActionSpec<ContextState>> = {
|
|
7
|
+
pathValues: ParsedIRIValues;
|
|
8
|
+
queryValues: ParsedIRIValues;
|
|
9
|
+
iriValues: IRIValue<Spec>;
|
|
10
|
+
};
|
|
11
|
+
export declare function getRequestIRIValues<State extends ContextState = ContextState, Spec extends ActionSpec<ContextState> = ActionSpec<ContextState>>({ iri, action, }: {
|
|
12
|
+
iri: string;
|
|
13
|
+
action: ImplementedAction<State, Spec>;
|
|
14
|
+
}): RequestIRIResult<Spec>;
|