mailschema 0.1.0 → 0.1.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/README.md +13 -3
- package/{cli.mjs → bin/mailschema.js} +37 -9
- package/dist/content-review-0.1.schema.json +52 -0
- package/dist/contribution.schema.json +19 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/map-0.1.schema.json +275 -0
- package/dist/map.d.ts +6 -0
- package/dist/map.js +35 -0
- package/dist/model.d.ts +1 -0
- package/dist/validation.js +6 -3
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# MailSchema
|
|
2
2
|
|
|
3
|
-
JSON Schemas
|
|
3
|
+
JSON Schemas and validation tools for Mail Action Protocol 0.1 and the MailSchema Registry.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Validate MAP descriptions, requests, results and problems, or prepare a Registry contribution. The package is local and performs no network requests.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install mailschema
|
|
9
9
|
npx mailschema check contribution.json
|
|
10
10
|
npx mailschema schema > contribution.schema.json
|
|
11
|
+
npx mailschema check description.json --map
|
|
12
|
+
npx mailschema check request.json --content-review
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
Node.js 22 or newer. The JavaScript API is ESM; the CLI reads a local JSON file of at most 256 KiB and does not upload or modify it.
|
|
@@ -22,10 +24,18 @@ else assertContribution(candidate);
|
|
|
22
24
|
const schema = getContributionSchema(); // Independent copy, JSON Schema Draft 2020-12.
|
|
23
25
|
```
|
|
24
26
|
|
|
27
|
+
```js
|
|
28
|
+
import { assertMapDocument, assertContentReviewRequest, getMapSchema } from 'mailschema';
|
|
29
|
+
|
|
30
|
+
assertMapDocument(description);
|
|
31
|
+
assertContentReviewRequest(request);
|
|
32
|
+
const mapSchema = getMapSchema();
|
|
33
|
+
```
|
|
34
|
+
|
|
25
35
|
Use `assertTypeRecord(value)` or `mailschema check record.json --record` for expanded Registry records. `getRecordSchema()` returns the equivalent schema. The raw contribution schema is also exported as `mailschema/contribution.schema.json`.
|
|
26
36
|
|
|
27
37
|
For a structurally valid contribution, `referenceErrors(contribution, catalog)` checks references against a supplied catalogue: current amendment bases, existing type names, exact implementation records, versions, profiles and operations. The catalogue contains `types` and `snapshots` arrays of `{ record, digest }` entries. It is never fetched automatically.
|
|
28
38
|
|
|
29
39
|
`Contribution`, `TypeRecord`, `TypeDefinition`, `Implementation`, `Party` and `CatalogView` are exported TypeScript types. Successful validation does not verify a contributor's identity, establish product compatibility or accept a submission. Full repository review also checks competing amendments and contribution identifiers.
|
|
30
40
|
|
|
31
|
-
Package version `0.
|
|
41
|
+
Raw schemas are exported as `mailschema/map-0.1.schema.json`, `mailschema/content-review-0.1.schema.json` and `mailschema/contribution.schema.json`. Package version `0.2.0` is independent of the MAP profile version. MIT licensed.
|
|
@@ -3,15 +3,19 @@ import { open } from 'node:fs/promises';
|
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import {
|
|
5
5
|
assertContribution,
|
|
6
|
+
assertContentReviewRequest,
|
|
7
|
+
assertMapDocument,
|
|
6
8
|
assertTypeRecord,
|
|
9
|
+
getContentReviewSchema,
|
|
7
10
|
getContributionSchema,
|
|
11
|
+
getMapSchema,
|
|
8
12
|
getRecordSchema,
|
|
9
|
-
} from '
|
|
13
|
+
} from '../dist/index.js';
|
|
10
14
|
|
|
11
15
|
const help = `MailSchema contribution tools
|
|
12
16
|
|
|
13
|
-
mailschema check <file.json> [--record]
|
|
14
|
-
mailschema schema [--record]
|
|
17
|
+
mailschema check <file.json> [--record | --map | --content-review]
|
|
18
|
+
mailschema schema [--record | --map | --content-review]
|
|
15
19
|
|
|
16
20
|
Checks JSON structure and required fields locally. Registry references and
|
|
17
21
|
editorial acceptance are separate checks. No files are uploaded or changed.`;
|
|
@@ -19,13 +23,37 @@ editorial acceptance are separate checks. No files are uploaded or changed.`;
|
|
|
19
23
|
try {
|
|
20
24
|
const { values, positionals } = parseArgs({
|
|
21
25
|
allowPositionals: true,
|
|
22
|
-
options: {
|
|
26
|
+
options: {
|
|
27
|
+
record: { type: 'boolean' },
|
|
28
|
+
map: { type: 'boolean' },
|
|
29
|
+
'content-review': { type: 'boolean' },
|
|
30
|
+
help: { type: 'boolean', short: 'h' },
|
|
31
|
+
},
|
|
23
32
|
});
|
|
24
33
|
const [command, path, ...extra] = positionals;
|
|
34
|
+
const formats = [values.record, values.map, values['content-review']].filter(Boolean);
|
|
35
|
+
if (formats.length > 1) throw new Error('Choose only one document format.');
|
|
36
|
+
const selected = values['content-review']
|
|
37
|
+
? 'Content Review request'
|
|
38
|
+
: values.map
|
|
39
|
+
? 'MAP document'
|
|
40
|
+
: values.record
|
|
41
|
+
? 'type record'
|
|
42
|
+
: 'contribution';
|
|
25
43
|
if (values.help || !command) console.log(help);
|
|
26
44
|
else if (command === 'schema' && !path && !extra.length)
|
|
27
45
|
console.log(
|
|
28
|
-
JSON.stringify(
|
|
46
|
+
JSON.stringify(
|
|
47
|
+
values['content-review']
|
|
48
|
+
? getContentReviewSchema()
|
|
49
|
+
: values.map
|
|
50
|
+
? getMapSchema()
|
|
51
|
+
: values.record
|
|
52
|
+
? getRecordSchema()
|
|
53
|
+
: getContributionSchema(),
|
|
54
|
+
null,
|
|
55
|
+
2,
|
|
56
|
+
),
|
|
29
57
|
);
|
|
30
58
|
else if (command === 'check' && path && !extra.length) {
|
|
31
59
|
const file = await open(path, 'r');
|
|
@@ -41,11 +69,11 @@ try {
|
|
|
41
69
|
} finally {
|
|
42
70
|
await file.close();
|
|
43
71
|
}
|
|
44
|
-
if (values
|
|
72
|
+
if (values['content-review']) assertContentReviewRequest(value);
|
|
73
|
+
else if (values.map) assertMapDocument(value);
|
|
74
|
+
else if (values.record) assertTypeRecord(value);
|
|
45
75
|
else assertContribution(value);
|
|
46
|
-
console.log(
|
|
47
|
-
'Valid MailSchema ' + (values.record ? 'type record' : 'contribution') + ' structure.',
|
|
48
|
-
);
|
|
76
|
+
console.log(`Valid MailSchema ${selected}.`);
|
|
49
77
|
} else throw new Error(help);
|
|
50
78
|
} catch (error) {
|
|
51
79
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mailschema.org/schemas/content-review-0.1.schema.json",
|
|
4
|
+
"title": "Content Review 0.1 MAP request",
|
|
5
|
+
"allOf": [
|
|
6
|
+
{ "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" },
|
|
7
|
+
{
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"type": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"id": { "const": "https://mailschema.org/types/content-review" },
|
|
14
|
+
"version": { "const": "0.1" },
|
|
15
|
+
"recordDigest": {
|
|
16
|
+
"const": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"oneOf": [
|
|
24
|
+
{
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"operation": { "const": "request-changes" },
|
|
28
|
+
"input": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"properties": {
|
|
32
|
+
"feedback": { "type": "string", "minLength": 1, "maxLength": 12000 }
|
|
33
|
+
},
|
|
34
|
+
"required": ["feedback"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"operation": { "const": "approve" },
|
|
42
|
+
"input": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"maxProperties": 0
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -69,6 +69,11 @@
|
|
|
69
69
|
"type": "object",
|
|
70
70
|
"additionalProperties": false,
|
|
71
71
|
"properties": {
|
|
72
|
+
"id": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
75
|
+
"maxLength": 100
|
|
76
|
+
},
|
|
72
77
|
"name": {
|
|
73
78
|
"type": "string",
|
|
74
79
|
"minLength": 1,
|
|
@@ -82,7 +87,7 @@
|
|
|
82
87
|
"pattern": "\\S"
|
|
83
88
|
}
|
|
84
89
|
},
|
|
85
|
-
"required": ["name", "description"]
|
|
90
|
+
"required": ["id", "name", "description"]
|
|
86
91
|
},
|
|
87
92
|
"minItems": 1,
|
|
88
93
|
"maxItems": 100
|
|
@@ -188,6 +193,11 @@
|
|
|
188
193
|
"type": "object",
|
|
189
194
|
"additionalProperties": false,
|
|
190
195
|
"properties": {
|
|
196
|
+
"id": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
199
|
+
"maxLength": 100
|
|
200
|
+
},
|
|
191
201
|
"name": {
|
|
192
202
|
"type": "string",
|
|
193
203
|
"minLength": 1,
|
|
@@ -332,6 +342,11 @@
|
|
|
332
342
|
"type": "object",
|
|
333
343
|
"additionalProperties": false,
|
|
334
344
|
"properties": {
|
|
345
|
+
"id": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
348
|
+
"maxLength": 100
|
|
349
|
+
},
|
|
335
350
|
"name": {
|
|
336
351
|
"type": "string",
|
|
337
352
|
"minLength": 1,
|
|
@@ -345,7 +360,7 @@
|
|
|
345
360
|
"pattern": "\\S"
|
|
346
361
|
}
|
|
347
362
|
},
|
|
348
|
-
"required": ["name", "description"]
|
|
363
|
+
"required": ["id", "name", "description"]
|
|
349
364
|
},
|
|
350
365
|
"minItems": 1,
|
|
351
366
|
"maxItems": 100
|
|
@@ -790,9 +805,8 @@
|
|
|
790
805
|
"type": "array",
|
|
791
806
|
"items": {
|
|
792
807
|
"type": "string",
|
|
793
|
-
"
|
|
794
|
-
"maxLength":
|
|
795
|
-
"pattern": "\\S"
|
|
808
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
809
|
+
"maxLength": 100
|
|
796
810
|
},
|
|
797
811
|
"minItems": 1,
|
|
798
812
|
"maxItems": 100
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { contributionErrors, assertContribution, assertTypeRecord, referenceErro
|
|
|
2
2
|
export type { CatalogView } from './validation.js';
|
|
3
3
|
export { typeStages } from './model.js';
|
|
4
4
|
export type { Party, TypeDefinition, TypeRecord, Contribution, Implementation } from './model.js';
|
|
5
|
+
export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, } from './map.js';
|
|
5
6
|
/** Return an independent copy of the contribution JSON Schema (Draft 2020-12). */
|
|
6
7
|
export declare function getContributionSchema(): Record<string, unknown>;
|
|
7
8
|
/** Return the schema for an expanded Registry record, including attribution and history. */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Schemas and validation tools for MAP documents and MailSchema Registry contributions. */
|
|
2
2
|
import schema from './contribution.schema.json' with { type: 'json' };
|
|
3
3
|
export { contributionErrors, assertContribution, assertTypeRecord, referenceErrors, } from './validation.js';
|
|
4
4
|
export { typeStages } from './model.js';
|
|
5
|
+
export { mapErrors, assertMapDocument, contentReviewRequestErrors, assertContentReviewRequest, getMapSchema, getContentReviewSchema, } from './map.js';
|
|
5
6
|
/** Return an independent copy of the contribution JSON Schema (Draft 2020-12). */
|
|
6
7
|
export function getContributionSchema() {
|
|
7
8
|
return structuredClone(schema);
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mailschema.org/schemas/map-0.1.schema.json",
|
|
4
|
+
"title": "Mail Action Protocol 0.1",
|
|
5
|
+
"description": "Description, request, result and problem documents for the MAP 0.1 authenticated HTTPS profile.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/$defs/description" },
|
|
8
|
+
{ "$ref": "#/$defs/request" },
|
|
9
|
+
{ "$ref": "#/$defs/result" },
|
|
10
|
+
{ "$ref": "#/$defs/problem" }
|
|
11
|
+
],
|
|
12
|
+
"$defs": {
|
|
13
|
+
"profile": {
|
|
14
|
+
"const": "https://mailschema.org/profiles/map/0.1"
|
|
15
|
+
},
|
|
16
|
+
"identifier": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"format": "uri",
|
|
19
|
+
"maxLength": 2048
|
|
20
|
+
},
|
|
21
|
+
"uuidUrn": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
|
24
|
+
},
|
|
25
|
+
"digest": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"pattern": "^sha-256:[a-f0-9]{64}$"
|
|
28
|
+
},
|
|
29
|
+
"recordDigest": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"pattern": "^sha-256:[a-f0-9]{64}$"
|
|
32
|
+
},
|
|
33
|
+
"typeReference": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"properties": {
|
|
37
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
38
|
+
"version": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$",
|
|
41
|
+
"maxLength": 80
|
|
42
|
+
},
|
|
43
|
+
"recordDigest": { "$ref": "#/$defs/recordDigest" }
|
|
44
|
+
},
|
|
45
|
+
"required": ["id", "version", "recordDigest"]
|
|
46
|
+
},
|
|
47
|
+
"target": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"properties": {
|
|
51
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
52
|
+
"revision": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
53
|
+
"title": { "type": "string", "minLength": 1, "maxLength": 500 },
|
|
54
|
+
"digest": { "$ref": "#/$defs/digest" }
|
|
55
|
+
},
|
|
56
|
+
"required": ["id", "revision", "digest"]
|
|
57
|
+
},
|
|
58
|
+
"operation": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"additionalProperties": false,
|
|
61
|
+
"properties": {
|
|
62
|
+
"id": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
65
|
+
"maxLength": 100
|
|
66
|
+
},
|
|
67
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
68
|
+
"description": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
69
|
+
"inputSchema": { "$ref": "#/$defs/identifier" }
|
|
70
|
+
},
|
|
71
|
+
"required": ["id", "name", "description", "inputSchema"]
|
|
72
|
+
},
|
|
73
|
+
"description": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"properties": {
|
|
77
|
+
"@context": {
|
|
78
|
+
"const": "https://mailschema.org/contexts/map-0.1.jsonld"
|
|
79
|
+
},
|
|
80
|
+
"@type": { "const": "MailAction" },
|
|
81
|
+
"@id": { "$ref": "#/$defs/uuidUrn" },
|
|
82
|
+
"profile": { "$ref": "#/$defs/profile" },
|
|
83
|
+
"type": { "$ref": "#/$defs/typeReference" },
|
|
84
|
+
"describedAt": { "type": "string", "format": "date-time" },
|
|
85
|
+
"expiresAt": { "type": "string", "format": "date-time" },
|
|
86
|
+
"service": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
91
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
92
|
+
"execution": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"additionalProperties": false,
|
|
95
|
+
"properties": {
|
|
96
|
+
"url": { "$ref": "#/$defs/identifier" },
|
|
97
|
+
"method": { "const": "POST" },
|
|
98
|
+
"requestMediaType": { "const": "application/json" },
|
|
99
|
+
"resultMediaType": { "const": "application/json" },
|
|
100
|
+
"resultUrlTemplate": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"format": "uri-template",
|
|
103
|
+
"pattern": "^https://.*\\{requestId\\}.*$",
|
|
104
|
+
"maxLength": 2048
|
|
105
|
+
},
|
|
106
|
+
"resultRetentionSeconds": {
|
|
107
|
+
"type": "integer",
|
|
108
|
+
"minimum": 300,
|
|
109
|
+
"maximum": 31536000
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"required": [
|
|
113
|
+
"url",
|
|
114
|
+
"method",
|
|
115
|
+
"requestMediaType",
|
|
116
|
+
"resultMediaType",
|
|
117
|
+
"resultUrlTemplate",
|
|
118
|
+
"resultRetentionSeconds"
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
"humanUrl": { "$ref": "#/$defs/identifier" },
|
|
122
|
+
"authorization": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"properties": {
|
|
126
|
+
"kind": { "const": "service-configured" },
|
|
127
|
+
"schemes": {
|
|
128
|
+
"type": "array",
|
|
129
|
+
"items": { "enum": ["oauth2", "bearer", "session"] },
|
|
130
|
+
"minItems": 1,
|
|
131
|
+
"uniqueItems": true
|
|
132
|
+
},
|
|
133
|
+
"audience": { "type": "string", "minLength": 1, "maxLength": 500 }
|
|
134
|
+
},
|
|
135
|
+
"required": ["kind", "schemes"]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": ["id", "name", "execution", "humanUrl", "authorization"]
|
|
139
|
+
},
|
|
140
|
+
"target": { "$ref": "#/$defs/target" },
|
|
141
|
+
"operations": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"items": { "$ref": "#/$defs/operation" },
|
|
144
|
+
"minItems": 1,
|
|
145
|
+
"maxItems": 100
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"required": [
|
|
149
|
+
"@context",
|
|
150
|
+
"@type",
|
|
151
|
+
"@id",
|
|
152
|
+
"profile",
|
|
153
|
+
"type",
|
|
154
|
+
"describedAt",
|
|
155
|
+
"expiresAt",
|
|
156
|
+
"service",
|
|
157
|
+
"target",
|
|
158
|
+
"operations"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"request": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"properties": {
|
|
165
|
+
"kind": { "const": "MapRequest" },
|
|
166
|
+
"profile": { "$ref": "#/$defs/profile" },
|
|
167
|
+
"requestId": { "$ref": "#/$defs/uuidUrn" },
|
|
168
|
+
"interactionId": { "$ref": "#/$defs/uuidUrn" },
|
|
169
|
+
"requestedAt": { "type": "string", "format": "date-time" },
|
|
170
|
+
"type": { "$ref": "#/$defs/typeReference" },
|
|
171
|
+
"operation": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
174
|
+
"maxLength": 100
|
|
175
|
+
},
|
|
176
|
+
"target": { "$ref": "#/$defs/target" },
|
|
177
|
+
"input": { "type": "object", "maxProperties": 100 }
|
|
178
|
+
},
|
|
179
|
+
"required": [
|
|
180
|
+
"kind",
|
|
181
|
+
"profile",
|
|
182
|
+
"requestId",
|
|
183
|
+
"interactionId",
|
|
184
|
+
"requestedAt",
|
|
185
|
+
"type",
|
|
186
|
+
"operation",
|
|
187
|
+
"target",
|
|
188
|
+
"input"
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
"result": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"additionalProperties": false,
|
|
194
|
+
"properties": {
|
|
195
|
+
"kind": { "const": "MapResult" },
|
|
196
|
+
"profile": { "$ref": "#/$defs/profile" },
|
|
197
|
+
"requestId": { "$ref": "#/$defs/uuidUrn" },
|
|
198
|
+
"interactionId": { "$ref": "#/$defs/uuidUrn" },
|
|
199
|
+
"operation": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
202
|
+
"maxLength": 100
|
|
203
|
+
},
|
|
204
|
+
"state": { "enum": ["accepted", "completed", "pending", "approval-required"] },
|
|
205
|
+
"target": { "$ref": "#/$defs/target" },
|
|
206
|
+
"recordedAt": { "type": "string", "format": "date-time" },
|
|
207
|
+
"resultUrl": { "$ref": "#/$defs/identifier" },
|
|
208
|
+
"output": { "type": "object", "maxProperties": 100 }
|
|
209
|
+
},
|
|
210
|
+
"required": [
|
|
211
|
+
"kind",
|
|
212
|
+
"profile",
|
|
213
|
+
"requestId",
|
|
214
|
+
"interactionId",
|
|
215
|
+
"operation",
|
|
216
|
+
"state",
|
|
217
|
+
"target",
|
|
218
|
+
"recordedAt",
|
|
219
|
+
"resultUrl",
|
|
220
|
+
"output"
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
"problem": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"additionalProperties": false,
|
|
226
|
+
"properties": {
|
|
227
|
+
"type": {
|
|
228
|
+
"enum": [
|
|
229
|
+
"https://mailschema.org/problems/refused",
|
|
230
|
+
"https://mailschema.org/problems/stale-target",
|
|
231
|
+
"https://mailschema.org/problems/expired-interaction",
|
|
232
|
+
"https://mailschema.org/problems/invalid-request",
|
|
233
|
+
"https://mailschema.org/problems/unsupported-profile",
|
|
234
|
+
"https://mailschema.org/problems/unsupported-type",
|
|
235
|
+
"https://mailschema.org/problems/unsupported-operation",
|
|
236
|
+
"https://mailschema.org/problems/idempotency-conflict",
|
|
237
|
+
"https://mailschema.org/problems/request-in-progress"
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
"title": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
241
|
+
"status": { "type": "integer", "minimum": 400, "maximum": 599 },
|
|
242
|
+
"detail": { "type": "string", "minLength": 1, "maxLength": 4000 },
|
|
243
|
+
"instance": { "$ref": "#/$defs/identifier" },
|
|
244
|
+
"profile": { "$ref": "#/$defs/profile" },
|
|
245
|
+
"requestId": { "$ref": "#/$defs/uuidUrn" },
|
|
246
|
+
"interactionId": { "$ref": "#/$defs/uuidUrn" },
|
|
247
|
+
"code": {
|
|
248
|
+
"enum": [
|
|
249
|
+
"refused",
|
|
250
|
+
"stale-target",
|
|
251
|
+
"expired-interaction",
|
|
252
|
+
"invalid-request",
|
|
253
|
+
"unsupported-profile",
|
|
254
|
+
"unsupported-type",
|
|
255
|
+
"unsupported-operation",
|
|
256
|
+
"idempotency-conflict",
|
|
257
|
+
"request-in-progress"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
"target": { "$ref": "#/$defs/target" }
|
|
261
|
+
},
|
|
262
|
+
"required": [
|
|
263
|
+
"type",
|
|
264
|
+
"title",
|
|
265
|
+
"status",
|
|
266
|
+
"detail",
|
|
267
|
+
"instance",
|
|
268
|
+
"profile",
|
|
269
|
+
"requestId",
|
|
270
|
+
"interactionId",
|
|
271
|
+
"code"
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
package/dist/map.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function mapErrors(value: unknown): string[];
|
|
2
|
+
export declare function assertMapDocument(value: unknown): void;
|
|
3
|
+
export declare function contentReviewRequestErrors(value: unknown): string[];
|
|
4
|
+
export declare function assertContentReviewRequest(value: unknown): void;
|
|
5
|
+
export declare function getMapSchema(): Record<string, unknown>;
|
|
6
|
+
export declare function getContentReviewSchema(): Record<string, unknown>;
|
package/dist/map.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
import mapSchema from './map-0.1.schema.json' with { type: 'json' };
|
|
4
|
+
import contentReviewSchema from './content-review-0.1.schema.json' with { type: 'json' };
|
|
5
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
6
|
+
addFormats(ajv);
|
|
7
|
+
const mapDocument = ajv.compile(mapSchema);
|
|
8
|
+
const contentReviewRequest = ajv.compile(contentReviewSchema);
|
|
9
|
+
function errors(validate, value) {
|
|
10
|
+
if (validate(value))
|
|
11
|
+
return [];
|
|
12
|
+
return (validate.errors || []).map((error) => `${error.instancePath || '/'} ${error.message}${error.keyword === 'required' ? `: ${error.params.missingProperty}` : ''}`);
|
|
13
|
+
}
|
|
14
|
+
export function mapErrors(value) {
|
|
15
|
+
return errors(mapDocument, value);
|
|
16
|
+
}
|
|
17
|
+
export function assertMapDocument(value) {
|
|
18
|
+
const found = mapErrors(value);
|
|
19
|
+
if (found.length)
|
|
20
|
+
throw new Error(`Invalid MAP 0.1 document:\n${found.join('\n')}`);
|
|
21
|
+
}
|
|
22
|
+
export function contentReviewRequestErrors(value) {
|
|
23
|
+
return errors(contentReviewRequest, value);
|
|
24
|
+
}
|
|
25
|
+
export function assertContentReviewRequest(value) {
|
|
26
|
+
const found = contentReviewRequestErrors(value);
|
|
27
|
+
if (found.length)
|
|
28
|
+
throw new Error(`Invalid Content Review 0.1 request:\n${found.join('\n')}`);
|
|
29
|
+
}
|
|
30
|
+
export function getMapSchema() {
|
|
31
|
+
return structuredClone(mapSchema);
|
|
32
|
+
}
|
|
33
|
+
export function getContentReviewSchema() {
|
|
34
|
+
return structuredClone(contentReviewSchema);
|
|
35
|
+
}
|
package/dist/model.d.ts
CHANGED
package/dist/validation.js
CHANGED
|
@@ -39,7 +39,7 @@ export function referenceErrors(input, catalog) {
|
|
|
39
39
|
];
|
|
40
40
|
if (target.profile !== input.profile)
|
|
41
41
|
errors.push('Execution profile does not match the targeted record.');
|
|
42
|
-
const operations = new Set(target.operations.map((operation) => operation.
|
|
42
|
+
const operations = new Set(target.operations.map((operation) => operation.id));
|
|
43
43
|
if (new Set(input.operations).size !== input.operations.length ||
|
|
44
44
|
input.operations.some((operation) => !operations.has(operation)))
|
|
45
45
|
errors.push('Unknown or duplicate operation in the implementation declaration.');
|
|
@@ -53,8 +53,11 @@ export function referenceErrors(input, catalog) {
|
|
|
53
53
|
if (catalog.types.some((item) => item.record.slug !== input.record.slug &&
|
|
54
54
|
item.record.name.toLocaleLowerCase() === input.record.name.toLocaleLowerCase()))
|
|
55
55
|
errors.push('Another type already uses this name.');
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
const operationIds = input.record.operations.map((operation) => operation.id);
|
|
57
|
+
const operationNames = input.record.operations.map((operation) => operation.name.toLocaleLowerCase());
|
|
58
|
+
if (new Set(operationIds).size !== operationIds.length)
|
|
59
|
+
errors.push('Operation identifiers must be unique within a type.');
|
|
60
|
+
if (new Set(operationNames).size !== operationNames.length)
|
|
58
61
|
errors.push('Operation names must be unique within a type.');
|
|
59
62
|
}
|
|
60
63
|
return errors;
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailschema",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Schemas and validation tools for Mail Action Protocol and the MailSchema Registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "MailSchema contributors",
|
|
8
|
+
"homepage": "https://mailschema.org/tools/",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/mailschema/mailschema.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mailschema/mailschema/issues"
|
|
15
|
+
},
|
|
8
16
|
"engines": {
|
|
9
17
|
"node": ">=22"
|
|
10
18
|
},
|
|
@@ -14,15 +22,17 @@
|
|
|
14
22
|
"import": "./dist/index.js"
|
|
15
23
|
},
|
|
16
24
|
"./contribution.schema.json": "./dist/contribution.schema.json",
|
|
25
|
+
"./map-0.1.schema.json": "./dist/map-0.1.schema.json",
|
|
26
|
+
"./content-review-0.1.schema.json": "./dist/content-review-0.1.schema.json",
|
|
17
27
|
"./package.json": "./package.json"
|
|
18
28
|
},
|
|
19
29
|
"types": "./dist/index.d.ts",
|
|
20
30
|
"bin": {
|
|
21
|
-
"mailschema": "./
|
|
31
|
+
"mailschema": "./bin/mailschema.js"
|
|
22
32
|
},
|
|
23
33
|
"files": [
|
|
24
34
|
"dist",
|
|
25
|
-
"
|
|
35
|
+
"bin",
|
|
26
36
|
"README.md",
|
|
27
37
|
"LICENSE"
|
|
28
38
|
],
|