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 CHANGED
@@ -1,13 +1,15 @@
1
1
  # MailSchema
2
2
 
3
- JSON Schemas, TypeScript definitions and validation tools for the MailSchema Registry.
3
+ JSON Schemas and validation tools for Mail Action Protocol 0.1 and the MailSchema Registry.
4
4
 
5
- Prepare a new interaction type, amend an existing definition, or declare an implementation's supported operations. This package checks the Registry contribution format. It does not implement email delivery, authorization or the draft Mail Action Protocol wire format.
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.1.0` is independent of any specification version. MIT licensed.
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 './dist/index.js';
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: { record: { type: 'boolean' }, help: { type: 'boolean', short: 'h' } },
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(values.record ? getRecordSchema() : getContributionSchema(), null, 2),
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.record) assertTypeRecord(value);
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
- "minLength": 1,
794
- "maxLength": 240,
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
- /** Tools for preparing MailSchema Registry contributions. */
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
@@ -26,6 +26,7 @@ export interface TypeDefinition {
26
26
  overview: string;
27
27
  target: string;
28
28
  operations: {
29
+ id: string;
29
30
  name: string;
30
31
  description: string;
31
32
  }[];
@@ -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.name));
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 operations = input.record.operations.map((operation) => operation.name.toLocaleLowerCase());
57
- if (new Set(operations).size !== operations.length)
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.0",
4
- "description": "JSON Schemas, TypeScript definitions and validation tools for MailSchema Registry contributions",
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": "./cli.mjs"
31
+ "mailschema": "./bin/mailschema.js"
22
32
  },
23
33
  "files": [
24
34
  "dist",
25
- "cli.mjs",
35
+ "bin",
26
36
  "README.md",
27
37
  "LICENSE"
28
38
  ],