mailschema 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -36
- package/bin/mailschema.js +40 -53
- package/dist/context.jsonld +50 -0
- package/dist/contract.schema.json +206 -0
- package/dist/core/artifacts.d.ts +11 -0
- package/dist/core/artifacts.js +30 -0
- package/dist/core/bundled.d.ts +4 -0
- package/dist/core/bundled.js +7 -0
- package/dist/core/contract.d.ts +69 -0
- package/dist/core/contract.js +253 -0
- package/dist/core/description.d.ts +45 -0
- package/dist/core/description.js +47 -0
- package/dist/core/implementation.d.ts +32 -0
- package/dist/core/implementation.js +25 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +9 -0
- package/dist/core/json.d.ts +23 -0
- package/dist/core/json.js +200 -0
- package/dist/core/patterns.d.ts +12 -0
- package/dist/core/patterns.js +165 -0
- package/dist/core/sha256.d.ts +2 -0
- package/dist/core/sha256.js +59 -0
- package/dist/core.schema.json +210 -0
- package/dist/implementation.schema.json +173 -0
- package/dist/index.d.ts +5 -9
- package/dist/index.js +5 -13
- package/dist/profile.json +27 -0
- package/package.json +9 -10
- package/dist/contribution.schema.json +0 -874
- package/dist/forms-0.1.schema.json +0 -329
- package/dist/map-0.2.jsonld +0 -91
- package/dist/map-0.2.schema.json +0 -1033
- package/dist/map.d.ts +0 -10
- package/dist/map.js +0 -20
- package/dist/model.d.ts +0 -95
- package/dist/model.js +0 -5
- package/dist/type-contract-0.2.schema.json +0 -179
- package/dist/validation.d.ts +0 -15
- package/dist/validation.js +0 -64
package/README.md
CHANGED
|
@@ -1,54 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mailschema
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
[](https://github.com/mailschema/javascript/actions/workflows/test.yml)
|
|
5
|
-
|
|
6
|
-
The Mail Action Protocol 0.2 core artifacts, and local validation of MailSchema Registry contributions, with no network requests.
|
|
7
|
-
|
|
8
|
-
[Specification](https://mailschema.org/specification) · [Registry](https://mailschema.org/registry) · [Tools](https://mailschema.org/tools) · [Source](https://github.com/mailschema/javascript)
|
|
9
|
-
|
|
10
|
-
## Install
|
|
3
|
+
[Mail Action Protocol](https://mailschema.org) 0.3 for JavaScript. It parses and checks MAP descriptions, type contracts and Registry implementation records, and computes the RFC 8785 digests that identify contracts. The same code runs the MailSchema specification's own checks. It has no Node.js-only dependencies, so it also runs in browsers and workers.
|
|
11
4
|
|
|
12
5
|
```sh
|
|
13
|
-
npm install mailschema
|
|
6
|
+
npm install mailschema@0.3.0
|
|
14
7
|
```
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## MAP 0.2 core artifacts
|
|
9
|
+
## Check a description against its contract
|
|
19
10
|
|
|
20
11
|
```js
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
getContractFormatSchema,
|
|
24
|
-
getFormsSchema,
|
|
25
|
-
getMapContext,
|
|
26
|
-
getMapSchema,
|
|
27
|
-
} from 'mailschema';
|
|
28
|
-
```
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
13
|
+
import { Contract, parseDescription } from 'mailschema';
|
|
29
14
|
|
|
30
|
-
|
|
15
|
+
const contract = Contract.parse(await readFile('campaign-send-approval-0.1.json'));
|
|
16
|
+
const description = parseDescription(await readFile('description.json'));
|
|
17
|
+
const errors = contract.descriptionErrors(description);
|
|
18
|
+
if (errors.length) throw new Error(errors.join('\n'));
|
|
19
|
+
```
|
|
31
20
|
|
|
32
|
-
|
|
21
|
+
`parseDescription` reads the bytes as MAP JSON (strict I-JSON within 64 KiB) and checks the core rules, or throws `InvalidDocument` with every reason. Obtain contracts from a catalogue you configure and pin them by digest; a message never chooses one.
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
import { assertContribution, contributionErrors, referenceErrors } from 'mailschema';
|
|
23
|
+
## API
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
- `parse(input, maxBytes)`, `canonicalize(value)`, `digest(value)`: MAP JSON and RFC 8785 digests.
|
|
26
|
+
- `descriptionErrors(value)`, `parseDescription(input)`: the core description rules.
|
|
27
|
+
- `new Contract(value)`, `Contract.parse(input)`: a checked contract with `digest`, `operation(id)`, `detailsErrors(details)`, `inputErrors(operationId, input)` and `descriptionErrors(description)`.
|
|
28
|
+
- `contractErrors(value)`: why a value is not a contract, including schema keywords, formats and patterns every implementation can enforce alike.
|
|
29
|
+
- `implementationErrors(value)`, `parseImplementation(input)`: Registry implementation records.
|
|
30
|
+
- `PROFILE`, `CONTEXT`, `DESCRIPTION_MEDIA_TYPE`, `EFFECTS`, `CAPABILITY_KINDS` and the size limits.
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
```
|
|
32
|
+
The profile record, context and schemas are exported byte for byte: `mailschema/profile.json`, `mailschema/context.jsonld`, `mailschema/core.schema.json`, `mailschema/contract.schema.json` and `mailschema/implementation.schema.json`.
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
## Command line
|
|
45
35
|
|
|
46
36
|
```sh
|
|
47
|
-
npx mailschema
|
|
48
|
-
npx mailschema
|
|
49
|
-
npx mailschema
|
|
37
|
+
npx mailschema@0.3.0 contract campaign-send-approval-0.1.json
|
|
38
|
+
npx mailschema@0.3.0 description description.json --contract campaign-send-approval-0.1.json
|
|
39
|
+
npx mailschema@0.3.0 implementation record.json
|
|
50
40
|
```
|
|
51
41
|
|
|
52
|
-
|
|
42
|
+
Checking does not authenticate a message, establish a service's authority or perform an action. Those belong to the implementation, under the [specification](https://mailschema.org/specification/core).
|
|
53
43
|
|
|
54
|
-
|
|
44
|
+
Source: [mailschema/javascript](https://github.com/mailschema/javascript). License: MIT.
|
package/bin/mailschema.js
CHANGED
|
@@ -1,72 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
getMapContext,
|
|
11
|
-
getMapSchema,
|
|
12
|
-
getRecordSchema,
|
|
5
|
+
Contract,
|
|
6
|
+
CONTRACT_MAX_BYTES,
|
|
7
|
+
InvalidDocument,
|
|
8
|
+
parseDescription,
|
|
9
|
+
parseImplementation,
|
|
13
10
|
} from '../dist/index.js';
|
|
14
11
|
|
|
15
|
-
const help = `
|
|
12
|
+
const help = `Mail Action Protocol 0.3 checks
|
|
16
13
|
|
|
17
|
-
mailschema
|
|
18
|
-
mailschema
|
|
14
|
+
mailschema contract <contract.json>
|
|
15
|
+
mailschema description <description.json> [--contract <contract.json>]
|
|
16
|
+
mailschema implementation <record.json>
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
a
|
|
18
|
+
contract checks a type contract and prints its digest. description checks a
|
|
19
|
+
description and, with --contract, that it uses that contract as it allows.
|
|
20
|
+
implementation checks a Registry implementation record. Nothing is uploaded
|
|
21
|
+
or changed.`;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
async function read(path) {
|
|
24
|
+
const info = await stat(path);
|
|
25
|
+
if (!info.isFile() || info.size > CONTRACT_MAX_BYTES)
|
|
26
|
+
throw new Error(`Expected a JSON file of at most ${CONTRACT_MAX_BYTES} bytes.`);
|
|
27
|
+
return new Uint8Array(await readFile(path));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function report(errors) {
|
|
31
|
+
if (!errors.length) return;
|
|
32
|
+
throw new InvalidDocument('Not valid.', errors);
|
|
33
|
+
}
|
|
31
34
|
|
|
32
35
|
try {
|
|
33
36
|
const { values, positionals } = parseArgs({
|
|
34
37
|
allowPositionals: true,
|
|
35
|
-
options: {
|
|
36
|
-
...Object.fromEntries(Object.keys(schemas).map((name) => [name, { type: 'boolean' }])),
|
|
37
|
-
help: { type: 'boolean', short: 'h' },
|
|
38
|
-
},
|
|
38
|
+
options: { contract: { type: 'string' }, help: { type: 'boolean', short: 'h' } },
|
|
39
39
|
});
|
|
40
40
|
const [command, path, ...extra] = positionals;
|
|
41
|
-
const chosen = Object.keys(schemas).filter((name) => values[name]);
|
|
42
|
-
if (chosen.length > 1) throw new Error('Choose only one schema.');
|
|
43
41
|
if (values.help || !command) console.log(help);
|
|
44
|
-
else if (
|
|
45
|
-
|
|
46
|
-
else if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw new Error('Expected a JSON file no larger than 256 KiB.');
|
|
58
|
-
const bytes = Buffer.alloc(256 * 1024 + 1);
|
|
59
|
-
const { bytesRead } = await file.read(bytes, 0, bytes.length, 0);
|
|
60
|
-
if (bytesRead > 256 * 1024) throw new Error('JSON file exceeds 256 KiB.');
|
|
61
|
-
value = JSON.parse(bytes.subarray(0, bytesRead).toString('utf8'));
|
|
62
|
-
} finally {
|
|
63
|
-
await file.close();
|
|
64
|
-
}
|
|
65
|
-
if (values.record) assertTypeRecord(value);
|
|
66
|
-
else assertContribution(value);
|
|
67
|
-
console.log(`Valid MailSchema ${values.record ? 'type record' : 'contribution'}.`);
|
|
42
|
+
else if (!path || extra.length || (values.contract && command !== 'description'))
|
|
43
|
+
throw new Error(help);
|
|
44
|
+
else if (command === 'contract') {
|
|
45
|
+
const contract = Contract.parse(await read(path));
|
|
46
|
+
console.log(`Valid type contract ${contract.id} ${contract.version}\n${contract.digest}`);
|
|
47
|
+
} else if (command === 'description') {
|
|
48
|
+
const description = parseDescription(await read(path));
|
|
49
|
+
if (values.contract)
|
|
50
|
+
report(Contract.parse(await read(values.contract)).descriptionErrors(description));
|
|
51
|
+
console.log(`Valid MAP 0.3 description ${description['@id']}`);
|
|
52
|
+
} else if (command === 'implementation') {
|
|
53
|
+
const record = parseImplementation(await read(path));
|
|
54
|
+
console.log(`Valid implementation record for ${record.service}`);
|
|
68
55
|
} else throw new Error(help);
|
|
69
56
|
} catch (error) {
|
|
70
|
-
console.error(error instanceof
|
|
57
|
+
console.error(error instanceof InvalidDocument ? error.errors.join('\n') : error.message);
|
|
71
58
|
process.exitCode = 1;
|
|
72
59
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"@version": 1.1,
|
|
4
|
+
"@protected": true,
|
|
5
|
+
"@vocab": "https://mailschema.org/vocab/map/0.3#",
|
|
6
|
+
"MailAction": "https://mailschema.org/vocab/map/0.3#MailAction",
|
|
7
|
+
"profile": {
|
|
8
|
+
"@id": "profile",
|
|
9
|
+
"@type": "@id"
|
|
10
|
+
},
|
|
11
|
+
"type": {
|
|
12
|
+
"@id": "type",
|
|
13
|
+
"@type": "@json"
|
|
14
|
+
},
|
|
15
|
+
"service": {
|
|
16
|
+
"@id": "service",
|
|
17
|
+
"@type": "@json"
|
|
18
|
+
},
|
|
19
|
+
"subject": {
|
|
20
|
+
"@id": "subject",
|
|
21
|
+
"@type": "@json"
|
|
22
|
+
},
|
|
23
|
+
"terms": {
|
|
24
|
+
"@id": "terms",
|
|
25
|
+
"@type": "@json"
|
|
26
|
+
},
|
|
27
|
+
"details": {
|
|
28
|
+
"@id": "details",
|
|
29
|
+
"@type": "@json"
|
|
30
|
+
},
|
|
31
|
+
"operations": {
|
|
32
|
+
"@id": "operations",
|
|
33
|
+
"@type": "@json"
|
|
34
|
+
},
|
|
35
|
+
"human": {
|
|
36
|
+
"@id": "human",
|
|
37
|
+
"@type": "@json"
|
|
38
|
+
},
|
|
39
|
+
"recipient": "recipient",
|
|
40
|
+
"inLanguage": "inLanguage",
|
|
41
|
+
"issuedAt": {
|
|
42
|
+
"@id": "issuedAt",
|
|
43
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
44
|
+
},
|
|
45
|
+
"expiresAt": {
|
|
46
|
+
"@id": "expiresAt",
|
|
47
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mailschema.org/artifacts/map-0.3/schemas/contract.schema.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": [
|
|
7
|
+
"id",
|
|
8
|
+
"version",
|
|
9
|
+
"profile",
|
|
10
|
+
"name",
|
|
11
|
+
"summary",
|
|
12
|
+
"requirements",
|
|
13
|
+
"detailsSchema",
|
|
14
|
+
"operations"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"id": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"minLength": 1,
|
|
20
|
+
"format": "uri",
|
|
21
|
+
"pattern": "^https://",
|
|
22
|
+
"maxLength": 2048
|
|
23
|
+
},
|
|
24
|
+
"version": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1,
|
|
27
|
+
"maxLength": 64
|
|
28
|
+
},
|
|
29
|
+
"profile": {
|
|
30
|
+
"const": "https://mailschema.org/profiles/map/0.3"
|
|
31
|
+
},
|
|
32
|
+
"name": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1,
|
|
35
|
+
"maxLength": 2048
|
|
36
|
+
},
|
|
37
|
+
"summary": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1,
|
|
40
|
+
"maxLength": 2048
|
|
41
|
+
},
|
|
42
|
+
"requirements": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1,
|
|
47
|
+
"maxLength": 2048
|
|
48
|
+
},
|
|
49
|
+
"minItems": 1,
|
|
50
|
+
"maxItems": 64
|
|
51
|
+
},
|
|
52
|
+
"detailsSchema": {
|
|
53
|
+
"type": "object"
|
|
54
|
+
},
|
|
55
|
+
"operations": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": false,
|
|
60
|
+
"required": [
|
|
61
|
+
"id",
|
|
62
|
+
"name",
|
|
63
|
+
"semantics",
|
|
64
|
+
"effects",
|
|
65
|
+
"bindings",
|
|
66
|
+
"actors",
|
|
67
|
+
"exactTerms",
|
|
68
|
+
"inputSchema",
|
|
69
|
+
"outcomes"
|
|
70
|
+
],
|
|
71
|
+
"properties": {
|
|
72
|
+
"id": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"minLength": 1,
|
|
75
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
76
|
+
},
|
|
77
|
+
"name": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1,
|
|
80
|
+
"maxLength": 2048
|
|
81
|
+
},
|
|
82
|
+
"semantics": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"minLength": 1,
|
|
85
|
+
"maxLength": 2048
|
|
86
|
+
},
|
|
87
|
+
"effects": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"minLength": 1,
|
|
92
|
+
"format": "uri",
|
|
93
|
+
"pattern": "^https://"
|
|
94
|
+
},
|
|
95
|
+
"minItems": 1,
|
|
96
|
+
"maxItems": 32,
|
|
97
|
+
"uniqueItems": true
|
|
98
|
+
},
|
|
99
|
+
"bindings": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": {
|
|
102
|
+
"enum": [
|
|
103
|
+
"credential",
|
|
104
|
+
"capability"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"minItems": 1,
|
|
108
|
+
"maxItems": 2,
|
|
109
|
+
"uniqueItems": true
|
|
110
|
+
},
|
|
111
|
+
"actors": {
|
|
112
|
+
"type": "array",
|
|
113
|
+
"items": {
|
|
114
|
+
"enum": [
|
|
115
|
+
"human",
|
|
116
|
+
"agent"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"minItems": 1,
|
|
120
|
+
"maxItems": 2,
|
|
121
|
+
"uniqueItems": true
|
|
122
|
+
},
|
|
123
|
+
"exactTerms": {
|
|
124
|
+
"type": "boolean"
|
|
125
|
+
},
|
|
126
|
+
"inputSchema": {
|
|
127
|
+
"type": [
|
|
128
|
+
"object",
|
|
129
|
+
"null"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"outcomes": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"minProperties": 1,
|
|
135
|
+
"propertyNames": {
|
|
136
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
137
|
+
},
|
|
138
|
+
"additionalProperties": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1,
|
|
141
|
+
"maxLength": 2048
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"capability": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"additionalProperties": false,
|
|
147
|
+
"required": [
|
|
148
|
+
"kind",
|
|
149
|
+
"maxLifetimeSeconds"
|
|
150
|
+
],
|
|
151
|
+
"properties": {
|
|
152
|
+
"kind": {
|
|
153
|
+
"enum": [
|
|
154
|
+
"refusal",
|
|
155
|
+
"protective-report",
|
|
156
|
+
"address-confirmation"
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
"maxLifetimeSeconds": {
|
|
160
|
+
"type": "integer",
|
|
161
|
+
"minimum": 1,
|
|
162
|
+
"maximum": 604800
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"allOf": [
|
|
168
|
+
{
|
|
169
|
+
"if": {
|
|
170
|
+
"properties": {
|
|
171
|
+
"bindings": {
|
|
172
|
+
"contains": {
|
|
173
|
+
"const": "capability"
|
|
174
|
+
},
|
|
175
|
+
"type": "array"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"then": {
|
|
180
|
+
"required": [
|
|
181
|
+
"capability"
|
|
182
|
+
],
|
|
183
|
+
"properties": {
|
|
184
|
+
"inputSchema": {
|
|
185
|
+
"type": "null"
|
|
186
|
+
},
|
|
187
|
+
"exactTerms": {
|
|
188
|
+
"const": true
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"else": {
|
|
193
|
+
"not": {
|
|
194
|
+
"required": [
|
|
195
|
+
"capability"
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
"minItems": 1,
|
|
203
|
+
"maxItems": 32
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { coreSchema, contractSchema, implementationSchema } from './bundled.ts';
|
|
2
|
+
export declare const PROFILE = "https://mailschema.org/profiles/map/0.3";
|
|
3
|
+
export declare const CONTEXT = "https://mailschema.org/contexts/map-0.3.jsonld";
|
|
4
|
+
/** The Content-Type of the MIME part that carries a description. */
|
|
5
|
+
export declare const DESCRIPTION_MEDIA_TYPE = "application/ld+json; profile=\"https://mailschema.org/profiles/map/0.3\"";
|
|
6
|
+
export declare const DESCRIPTION_MAX_BYTES = 65536;
|
|
7
|
+
export declare const CONTRACT_MAX_BYTES = 262144;
|
|
8
|
+
/** The namespace of the initial effect vocabulary. */
|
|
9
|
+
export declare const EFFECTS = "https://mailschema.org/effects/";
|
|
10
|
+
/** The formats MAP schemas may assert. Every implementation checks exactly these. */
|
|
11
|
+
export declare const FORMATS: readonly ["date", "date-time", "email", "uri"];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// MAP 0.3's identifiers, limits and schema validation.
|
|
2
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
3
|
+
import addFormats from 'ajv-formats';
|
|
4
|
+
export { coreSchema, contractSchema, implementationSchema } from "./bundled.js";
|
|
5
|
+
export const PROFILE = 'https://mailschema.org/profiles/map/0.3';
|
|
6
|
+
export const CONTEXT = 'https://mailschema.org/contexts/map-0.3.jsonld';
|
|
7
|
+
/** The Content-Type of the MIME part that carries a description. */
|
|
8
|
+
export const DESCRIPTION_MEDIA_TYPE = `application/ld+json; profile="${PROFILE}"`;
|
|
9
|
+
export const DESCRIPTION_MAX_BYTES = 65536;
|
|
10
|
+
export const CONTRACT_MAX_BYTES = 262144;
|
|
11
|
+
/** The namespace of the initial effect vocabulary. */
|
|
12
|
+
export const EFFECTS = 'https://mailschema.org/effects/';
|
|
13
|
+
/** The formats MAP schemas may assert. Every implementation checks exactly these. */
|
|
14
|
+
export const FORMATS = ['date', 'date-time', 'email', 'uri'];
|
|
15
|
+
/**
|
|
16
|
+
* JSON Schema 2020-12 as MAP reads it: the listed formats asserted, and references resolved
|
|
17
|
+
* only within the schema, never over the network. MAP's own rules decide which keywords and
|
|
18
|
+
* formats a contract may use; the validator adds no checks of its own beyond the vocabulary.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export function createValidator() {
|
|
22
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
23
|
+
addFormats(ajv, [...FORMATS]);
|
|
24
|
+
// Ajv also reads OpenAPI's `nullable` and the earlier `dependencies` and `definitions`.
|
|
25
|
+
for (const keyword of ['nullable', 'dependencies', 'definitions'])
|
|
26
|
+
ajv.removeKeyword(keyword);
|
|
27
|
+
return ajv;
|
|
28
|
+
}
|
|
29
|
+
/** Each validation error as its JSON Pointer and message. @internal */
|
|
30
|
+
export const schemaErrors = (errors, prefix = '') => (errors ?? []).map((error) => `${prefix + error.instancePath || '/'}: ${error.message}`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The schemas the 0.3 profile record binds, by their canonical files. Package preparation
|
|
2
|
+
// points these three imports at the package's own copies; no other line differs between the
|
|
3
|
+
// repository and the npm package.
|
|
4
|
+
import coreSchema from '../core.schema.json' with { type: 'json' };
|
|
5
|
+
import contractSchema from '../contract.schema.json' with { type: 'json' };
|
|
6
|
+
import implementationSchema from '../implementation.schema.json' with { type: 'json' };
|
|
7
|
+
export { coreSchema, contractSchema, implementationSchema };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Description } from './description.ts';
|
|
2
|
+
export interface Operation {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
semantics: string;
|
|
6
|
+
effects: string[];
|
|
7
|
+
bindings: ('credential' | 'capability')[];
|
|
8
|
+
actors: ('human' | 'agent')[];
|
|
9
|
+
exactTerms: boolean;
|
|
10
|
+
inputSchema: Record<string, unknown> | null;
|
|
11
|
+
outcomes: Record<string, string>;
|
|
12
|
+
capability?: {
|
|
13
|
+
kind: CapabilityKind;
|
|
14
|
+
maxLifetimeSeconds: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface ContractDocument {
|
|
18
|
+
id: string;
|
|
19
|
+
version: string;
|
|
20
|
+
profile: string;
|
|
21
|
+
name: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
requirements: string[];
|
|
24
|
+
detailsSchema: Record<string, unknown>;
|
|
25
|
+
operations: Operation[];
|
|
26
|
+
}
|
|
27
|
+
export type CapabilityKind = keyof typeof CAPABILITY_KINDS;
|
|
28
|
+
/** The capability binding's kinds: the exact effects each declares and its longest lifetime. */
|
|
29
|
+
export declare const CAPABILITY_KINDS: {
|
|
30
|
+
readonly refusal: {
|
|
31
|
+
readonly effects: readonly ["refusal", "state"];
|
|
32
|
+
readonly maxLifetimeSeconds: 604800;
|
|
33
|
+
};
|
|
34
|
+
readonly 'protective-report': {
|
|
35
|
+
readonly effects: readonly ["protection", "state"];
|
|
36
|
+
readonly maxLifetimeSeconds: 259200;
|
|
37
|
+
};
|
|
38
|
+
readonly 'address-confirmation': {
|
|
39
|
+
readonly effects: readonly ["assertion", "state"];
|
|
40
|
+
readonly maxLifetimeSeconds: 86400;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** Why the value is not a MAP 0.3 type contract, or no reasons. */
|
|
44
|
+
export declare function contractErrors(value: unknown): string[];
|
|
45
|
+
/** A valid contract with its digest and compiled schemas. */
|
|
46
|
+
export declare class Contract {
|
|
47
|
+
#private;
|
|
48
|
+
readonly document: ContractDocument;
|
|
49
|
+
/** `sha-256:` and the SHA-256 of the contract's RFC 8785 form. */
|
|
50
|
+
readonly digest: string;
|
|
51
|
+
/** The contract, or InvalidDocument with every reason the value is not one. */
|
|
52
|
+
constructor(value: unknown);
|
|
53
|
+
/** The contract in the bytes or text, read as MAP JSON within the contract limit. */
|
|
54
|
+
static parse(input: string | Uint8Array): Contract;
|
|
55
|
+
get id(): string;
|
|
56
|
+
get version(): string;
|
|
57
|
+
operation(id: string): Operation | undefined;
|
|
58
|
+
/** Why the details do not satisfy the contract's details schema, or no reasons. */
|
|
59
|
+
detailsErrors(details: unknown): string[];
|
|
60
|
+
/** Why the input is not acceptable for the operation, or no reasons. */
|
|
61
|
+
inputErrors(operationId: string, input: unknown): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Why a valid description does not use this contract as it allows, or no reasons: it names
|
|
64
|
+
* this contract by identifier, version and digest; it offers only declared operations; a
|
|
65
|
+
* capability appears only where the contract permits one, at the service's origin and
|
|
66
|
+
* within the kind's lifetime; and its details satisfy the details schema.
|
|
67
|
+
*/
|
|
68
|
+
descriptionErrors(description: Description): string[];
|
|
69
|
+
}
|