mailschema 0.2.2 → 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.
Files changed (52) hide show
  1. package/README.md +25 -119
  2. package/bin/mailschema.js +40 -53
  3. package/dist/context.jsonld +50 -0
  4. package/dist/contract.schema.json +206 -0
  5. package/dist/core/artifacts.d.ts +11 -36
  6. package/dist/core/artifacts.js +21 -69
  7. package/dist/core/bundled.d.ts +4 -4
  8. package/dist/core/bundled.js +7 -7
  9. package/dist/core/contract.d.ts +59 -56
  10. package/dist/core/contract.js +224 -298
  11. package/dist/core/description.d.ts +45 -0
  12. package/dist/core/description.js +47 -0
  13. package/dist/core/implementation.d.ts +32 -0
  14. package/dist/core/implementation.js +25 -0
  15. package/dist/core/index.d.ts +7 -6
  16. package/dist/core/index.js +9 -9
  17. package/dist/core/json.d.ts +23 -0
  18. package/dist/core/{document.js → json.js} +61 -51
  19. package/dist/core/patterns.d.ts +12 -0
  20. package/dist/core/patterns.js +165 -0
  21. package/dist/core/sha256.d.ts +2 -0
  22. package/dist/core/sha256.js +59 -0
  23. package/dist/core.schema.json +210 -0
  24. package/dist/implementation.schema.json +173 -0
  25. package/dist/index.d.ts +4 -9
  26. package/dist/index.js +2 -14
  27. package/dist/profile.json +27 -0
  28. package/package.json +9 -10
  29. package/dist/contribution.schema.json +0 -874
  30. package/dist/core/binding.d.ts +0 -24
  31. package/dist/core/binding.js +0 -50
  32. package/dist/core/document.d.ts +0 -24
  33. package/dist/core/documents.d.ts +0 -68
  34. package/dist/core/documents.js +0 -145
  35. package/dist/core/limits.d.ts +0 -19
  36. package/dist/core/limits.js +0 -55
  37. package/dist/core/pointer.d.ts +0 -12
  38. package/dist/core/pointer.js +0 -48
  39. package/dist/core/schemas.d.ts +0 -21
  40. package/dist/core/schemas.js +0 -125
  41. package/dist/core/types.d.ts +0 -127
  42. package/dist/core/types.js +0 -2
  43. package/dist/forms-0.1.schema.json +0 -329
  44. package/dist/map-0.2.jsonld +0 -91
  45. package/dist/map-0.2.schema.json +0 -1033
  46. package/dist/map.d.ts +0 -8
  47. package/dist/map.js +0 -18
  48. package/dist/model.d.ts +0 -95
  49. package/dist/model.js +0 -5
  50. package/dist/type-contract-0.2.schema.json +0 -179
  51. package/dist/validation.d.ts +0 -15
  52. package/dist/validation.js +0 -64
@@ -1,78 +1,30 @@
1
- // The bundled core artifacts and the validators built from them.
1
+ // MAP 0.3's identifiers, limits and schema validation.
2
2
  import Ajv2020 from 'ajv/dist/2020.js';
3
- import { contractFormat, coreSchema, formsSchema } from "./bundled.js";
4
- export const MAP_PROFILE = 'https://mailschema.org/profiles/map/0.2';
5
- export const CORE_SCHEMA = 'https://mailschema.org/schemas/map-0.2.schema.json';
6
- export const FORMS_SCHEMA = 'https://mailschema.org/schemas/forms-0.1.schema.json';
7
- export const CONTRACT_FORMAT = 'https://mailschema.org/schemas/type-contract-0.2.schema.json';
8
- export const CONTEXT = 'https://mailschema.org/contexts/map-0.2.jsonld';
9
- export const JSON_SCHEMA_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
10
- /** The schemas a contract may depend on without supplying them itself. */
11
- export const BUNDLED = new Map([
12
- [CORE_SCHEMA, coreSchema],
13
- [FORMS_SCHEMA, formsSchema],
14
- ]);
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
15
  /**
16
- * JSON Schema 2020-12 as MAP reads it. Lexical forms are the core schema's patterns, so
17
- * `format` stays an annotation: format checkers differ between validators. References
18
- * resolve only against schemas already added, never over the network.
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
19
  * @internal
20
20
  */
21
21
  export function createValidator() {
22
- const ajv = new Ajv2020({
23
- allErrors: true,
24
- strict: true,
25
- strictRequired: false,
26
- validateFormats: false,
27
- });
28
- // HTML autofill field names label form fields; they are annotations, not assertions.
29
- ajv.addKeyword({ keyword: 'autocomplete', schemaType: 'string' });
30
- // Only JSON Schema 2020-12 vocabulary keywords. Ajv also reads OpenAPI's `nullable`,
31
- // which admits a null other validators refuse, and the earlier `dependencies` and
32
- // `definitions`; strict mode now refuses them as unknown.
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`.
33
25
  for (const keyword of ['nullable', 'dependencies', 'definitions'])
34
26
  ajv.removeKeyword(keyword);
35
- for (const schema of BUNDLED.values())
36
- ajv.addSchema(schema);
37
27
  return ajv;
38
28
  }
39
- const core = createValidator();
40
- /** A core or form fields definition by name, for the shared lexical vectors. */
41
- export const definition = (schema, name) => core.getSchema(`${schema}#/$defs/${name}`);
42
- /** Errors as `pointer message` lines, as the other MailSchema packages report them. */
43
- export function errorList(errors, prefix = '') {
44
- return (errors ?? [])
45
- .slice(0, 100)
46
- .map((error) => `${prefix + error.instancePath || '/'} ${error.message}${error.keyword === 'required' ? `: ${error.params.missingProperty}` : ''}`);
47
- }
48
- const errorsOf = (validate, value) => validate(value) ? [] : errorList(validate.errors);
49
- const DOCUMENT = core.getSchema(CORE_SCHEMA);
50
- const DEFINITIONS = Object.fromEntries(['description', 'request', 'result', 'problem'].map((name) => [
51
- name,
52
- definition(CORE_SCHEMA, name),
53
- ]));
54
- const DATE_TIME = new RegExp(coreSchema.$defs.dateTime.pattern, 'u');
55
- const REQUEST_ID = definition(CORE_SCHEMA, 'uuidUrn');
56
- const CONTRACT = core.compile(contractFormat);
57
- /** Checks a type contract against the contract format. */
58
- export const contractFormatErrors = (contract) => errorsOf(CONTRACT, contract);
59
- /** Checks any MAP 0.2 description, request, result or problem against the core schema. */
60
- export const mapErrors = (document) => errorsOf(DOCUMENT, document);
61
- /**
62
- * Checks a description against the core description definition only. A description of a
63
- * type the caller has no contract for can be checked this far.
64
- */
65
- export const descriptionErrors = (description) => errorsOf(DEFINITIONS.description, description);
66
- /** Checks a request against the core request definition. Its identifier stays free if it fails. */
67
- export const requestErrors = (request) => errorsOf(DEFINITIONS.request, request);
68
- /** Checks a result against the core result definition only. */
69
- export const resultErrors = (result) => errorsOf(DEFINITIONS.result, result);
70
- /** Checks a problem against the core problem definition, including its type, status and code. */
71
- export const problemErrors = (problem) => errorsOf(DEFINITIONS.problem, problem);
72
- /** Whether a value is a request identifier: the core UUID URN form. Only one names a result resource. */
73
- export const isRequestId = (value) => REQUEST_ID(value);
74
- /**
75
- * Whether a deadline, a core date-time plus `afterSeconds`, has been reached. Anything but
76
- * a core date-time counts as reached, so a mistake fails closed.
77
- */
78
- export const reached = (now, deadline, afterSeconds = 0) => !(DATE_TIME.test(deadline) && now.getTime() < Date.parse(deadline) + afterSeconds * 1000);
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}`);
@@ -1,4 +1,4 @@
1
- import coreSchema from '../map-0.2.schema.json';
2
- import formsSchema from '../forms-0.1.schema.json';
3
- import contractFormat from '../type-contract-0.2.schema.json';
4
- export { coreSchema, formsSchema, contractFormat };
1
+ import coreSchema from '../core.schema.json';
2
+ import contractSchema from '../contract.schema.json';
3
+ import implementationSchema from '../implementation.schema.json';
4
+ export { coreSchema, contractSchema, implementationSchema };
@@ -1,7 +1,7 @@
1
- // The core artifacts, by their canonical files. Package preparation points these three
2
- // imports at the package's own copies; no other line differs between the repository and
3
- // the npm package.
4
- import coreSchema from '../map-0.2.schema.json' with { type: 'json' };
5
- import formsSchema from '../forms-0.1.schema.json' with { type: 'json' };
6
- import contractFormat from '../type-contract-0.2.schema.json' with { type: 'json' };
7
- export { coreSchema, formsSchema, contractFormat };
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 };
@@ -1,66 +1,69 @@
1
- import type { ProblemCode } from './documents.ts';
2
- import type { InputError, JsonObject, MapDescription, MapRequest, TypeContract, TypeReference } from './types.ts';
3
- /** A type contract that fails its own checks or its pinned digest. */
4
- export declare class InvalidContract extends Error {
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
+ };
5
16
  }
6
- /** Reasons the core assigns to the approval lifecycle. */
7
- export declare const APPROVAL_REASONS: readonly ["declined", "stale-target", "expired", "superseded"];
8
- /** A MAP problem a request earns before the service's own state is consulted. */
9
- export type RequestProblem = {
10
- code: ProblemCode;
11
- title: string;
12
- detail: string;
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
+ };
13
42
  };
14
- /**
15
- * A type contract an implementation vendors, verified against the digest it was pinned by
16
- * and compiled once. It checks the descriptions, requests, inputs and results of that exact
17
- * type. The Registry has already applied the contract rules, such as portable patterns, to
18
- * the contract that digest names; loading refuses anything that would otherwise fail when a
19
- * request arrives.
20
- */
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. */
21
46
  export declare class Contract {
22
47
  #private;
23
- readonly document: TypeContract;
48
+ readonly document: ContractDocument;
49
+ /** `sha-256:` and the SHA-256 of the contract's RFC 8785 form. */
24
50
  readonly digest: string;
25
- readonly requestSchema: JsonObject;
26
- /**
27
- * `digest` is the contract digest the implementation pinned; `dependencies` supplies, by
28
- * URL, any pinned schema the package does not bundle.
29
- */
30
- constructor(contract: unknown, requestSchema: unknown, { digest: pinned, dependencies, }: {
31
- digest: string;
32
- dependencies?: Record<string, unknown>;
33
- });
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;
34
55
  get id(): string;
35
56
  get version(): string;
36
- /** The type reference a description and a request of this contract name exactly. */
37
- get typeReference(): TypeReference;
38
- operation(id: string): import("./types.ts").ContractOperation | undefined;
39
- /** Whether completing the operation decides the interaction. */
40
- isDecision(id: string): boolean;
41
- /**
42
- * Every rule a description must satisfy beyond the core schema, for the service that issues
43
- * it and the client that receives it.
44
- */
45
- descriptionErrors(description: unknown): string[];
46
- /**
47
- * The checks a service makes on a request once it has resolved the description it issued,
48
- * compared the description digest and established the caller: the exact type, an offered
49
- * operation the authority permits, and expiry. Undefined when they pass. The service then
50
- * applies its own state (a decided interaction, a stale target) and `inputErrors`, in that
51
- * order, before any effect.
52
- */
53
- requestProblem(description: MapDescription, request: MapRequest, { now }: {
54
- now: Date;
55
- }): RequestProblem | undefined;
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[];
56
62
  /**
57
- * Input problems for one request, each with a detail and a JSON Pointer into its input: the
58
- * operation's input schema, then its field bindings against the description's details. Type
59
- * rules a contract cannot express are the caller's.
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.
60
67
  */
61
- inputErrors(description: MapDescription, request: MapRequest): InputError[];
62
- /** A request against the core request definition and this contract's request schema. */
63
- requestErrors(request: unknown): string[];
64
- /** The core result definition, then the output schema and reason the operation declares. */
65
- resultErrors(result: unknown): string[];
68
+ descriptionErrors(description: Description): string[];
66
69
  }