@datumesh/verify 0.0.0-stage → 0.2.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 (73) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +56 -2
  3. package/dist/cjs/ed25519.d.ts +2 -0
  4. package/dist/cjs/ed25519.js +82 -0
  5. package/dist/cjs/envelope.d.ts +38 -0
  6. package/dist/cjs/envelope.js +243 -0
  7. package/dist/cjs/errors.d.ts +14 -0
  8. package/dist/cjs/errors.js +65 -0
  9. package/dist/cjs/hash.d.ts +6 -0
  10. package/dist/cjs/hash.js +27 -0
  11. package/dist/cjs/index.d.ts +37 -0
  12. package/dist/cjs/index.js +101 -0
  13. package/dist/cjs/jcs.d.ts +5 -0
  14. package/dist/cjs/jcs.js +25 -0
  15. package/dist/cjs/json.d.ts +29 -0
  16. package/dist/cjs/json.js +325 -0
  17. package/dist/cjs/keys.d.ts +73 -0
  18. package/dist/cjs/keys.js +257 -0
  19. package/dist/cjs/lexical.d.ts +69 -0
  20. package/dist/cjs/lexical.js +231 -0
  21. package/dist/cjs/package.json +3 -0
  22. package/dist/cjs/unicode-data.d.ts +8 -0
  23. package/dist/cjs/unicode-data.js +14 -0
  24. package/dist/cjs/unicode.d.ts +12 -0
  25. package/dist/cjs/unicode.js +192 -0
  26. package/dist/cjs/units-data.d.ts +4 -0
  27. package/dist/cjs/units-data.js +11 -0
  28. package/dist/cjs/units.d.ts +24 -0
  29. package/dist/cjs/units.js +131 -0
  30. package/dist/cjs/uri.d.ts +43 -0
  31. package/dist/cjs/uri.js +209 -0
  32. package/dist/cjs/utf8.d.ts +10 -0
  33. package/dist/cjs/utf8.js +101 -0
  34. package/dist/cjs/validate.d.ts +25 -0
  35. package/dist/cjs/validate.js +408 -0
  36. package/dist/cjs/verify.d.ts +57 -0
  37. package/dist/cjs/verify.js +232 -0
  38. package/dist/esm/ed25519.d.ts +2 -0
  39. package/dist/esm/ed25519.js +79 -0
  40. package/dist/esm/envelope.d.ts +38 -0
  41. package/dist/esm/envelope.js +240 -0
  42. package/dist/esm/errors.d.ts +14 -0
  43. package/dist/esm/errors.js +60 -0
  44. package/dist/esm/hash.d.ts +6 -0
  45. package/dist/esm/hash.js +21 -0
  46. package/dist/esm/index.d.ts +37 -0
  47. package/dist/esm/index.js +79 -0
  48. package/dist/esm/jcs.d.ts +5 -0
  49. package/dist/esm/jcs.js +18 -0
  50. package/dist/esm/json.d.ts +29 -0
  51. package/dist/esm/json.js +313 -0
  52. package/dist/esm/keys.d.ts +73 -0
  53. package/dist/esm/keys.js +249 -0
  54. package/dist/esm/lexical.d.ts +69 -0
  55. package/dist/esm/lexical.js +209 -0
  56. package/dist/esm/package.json +3 -0
  57. package/dist/esm/unicode-data.d.ts +8 -0
  58. package/dist/esm/unicode-data.js +11 -0
  59. package/dist/esm/unicode.d.ts +12 -0
  60. package/dist/esm/unicode.js +184 -0
  61. package/dist/esm/units-data.d.ts +4 -0
  62. package/dist/esm/units-data.js +8 -0
  63. package/dist/esm/units.d.ts +24 -0
  64. package/dist/esm/units.js +123 -0
  65. package/dist/esm/uri.d.ts +43 -0
  66. package/dist/esm/uri.js +204 -0
  67. package/dist/esm/utf8.d.ts +10 -0
  68. package/dist/esm/utf8.js +96 -0
  69. package/dist/esm/validate.d.ts +25 -0
  70. package/dist/esm/validate.js +397 -0
  71. package/dist/esm/verify.d.ts +57 -0
  72. package/dist/esm/verify.js +227 -0
  73. package/package.json +54 -4
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatumeshError = exports.ERROR_CODES = exports.SPEC_VERSION = void 0;
4
+ exports.fail = fail;
5
+ /** The spec revision this library conforms to. */
6
+ exports.SPEC_VERSION = 'spec-0.2.0';
7
+ /** Every error code defined by SPEC.md 10.2, in the order of that table. */
8
+ exports.ERROR_CODES = [
9
+ 'E_PARSE_JSON',
10
+ 'E_PARSE_DUPLICATE_KEY',
11
+ 'E_PARSE_URI',
12
+ 'E_SCHEMA',
13
+ 'E_CANON_NAME',
14
+ 'E_CANON_ARG_NAME',
15
+ 'E_CANON_ARG_DUPLICATE',
16
+ 'E_CANON_ARG_ORDER',
17
+ 'E_CANON_PERCENT',
18
+ 'E_CANON_TYPE',
19
+ 'E_CANON_NUMBER',
20
+ 'E_CANON_UNIT',
21
+ 'E_CANON_DATE',
22
+ 'E_CANON_INSTANT',
23
+ 'E_CANON_STRING',
24
+ 'E_CANON_BLOB',
25
+ 'E_CANON_HEX',
26
+ 'E_CANON_LICENCE',
27
+ 'E_CANON_SET',
28
+ 'E_CANON_LIMIT',
29
+ 'E_SLICE',
30
+ 'E_PUBLISHER_MISMATCH',
31
+ 'E_VALID_MISMATCH',
32
+ 'E_TIME_ORDER',
33
+ 'E_ARG_UNKNOWN',
34
+ 'E_ARG_MISSING',
35
+ 'E_KEY_UNAVAILABLE',
36
+ 'E_KEY_DOC',
37
+ 'E_KEY_UNKNOWN',
38
+ 'E_KEY_NOT_VALID',
39
+ 'E_SIGNATURE',
40
+ 'E_FN_UNAVAILABLE',
41
+ 'E_FN_HASH_MISMATCH',
42
+ 'E_ARG_TYPE',
43
+ 'E_RETURN_TYPE',
44
+ 'E_INPUT_UNAVAILABLE',
45
+ 'E_INPUT_INVALID',
46
+ 'E_INPUT_UNDECLARED',
47
+ 'E_VALUE_MISMATCH',
48
+ 'E_BLOB_MISMATCH',
49
+ 'E_ENVELOPE',
50
+ ];
51
+ /** A Datumesh rule was broken. `code` is a SPEC.md 10.2 code; `path` locates the failure. */
52
+ class DatumeshError extends Error {
53
+ constructor(code, detail, path = null) {
54
+ super(`${code}${path ? ` at ${path}` : ''}: ${detail}`);
55
+ this.name = 'DatumeshError';
56
+ this.code = code;
57
+ this.path = path;
58
+ this.detail = detail;
59
+ }
60
+ }
61
+ exports.DatumeshError = DatumeshError;
62
+ /** Shorthand used throughout: throw a DatumeshError. */
63
+ function fail(code, detail, path = null) {
64
+ throw new DatumeshError(code, detail, path);
65
+ }
@@ -0,0 +1,6 @@
1
+ export declare function toHex(b: Uint8Array): string;
2
+ export declare function fromHex(s: string): Uint8Array;
3
+ /** SHA-256 in lowercase hex. */
4
+ export declare function sha256Hex(b: Uint8Array): string;
5
+ /** SPEC 9.1: first 16 hex digits of SHA-256 over the raw 32-byte public key. */
6
+ export declare function keyIdOf(publicKey: Uint8Array): string;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toHex = toHex;
4
+ exports.fromHex = fromHex;
5
+ exports.sha256Hex = sha256Hex;
6
+ exports.keyIdOf = keyIdOf;
7
+ const sha2_1 = require("@noble/hashes/sha2");
8
+ function toHex(b) {
9
+ let s = '';
10
+ for (let i = 0; i < b.length; i++)
11
+ s += b[i].toString(16).padStart(2, '0');
12
+ return s;
13
+ }
14
+ function fromHex(s) {
15
+ const out = new Uint8Array(s.length / 2);
16
+ for (let i = 0; i < out.length; i++)
17
+ out[i] = parseInt(s.slice(2 * i, 2 * i + 2), 16);
18
+ return out;
19
+ }
20
+ /** SHA-256 in lowercase hex. */
21
+ function sha256Hex(b) {
22
+ return toHex((0, sha2_1.sha256)(b));
23
+ }
24
+ /** SPEC 9.1: first 16 hex digits of SHA-256 over the raw 32-byte public key. */
25
+ function keyIdOf(publicKey) {
26
+ return sha256Hex(publicKey).slice(0, 16);
27
+ }
@@ -0,0 +1,37 @@
1
+ import { type Kind } from './validate.js';
2
+ export { DatumeshError, ERROR_CODES, SPEC_VERSION, type ErrorCode } from './errors.js';
3
+ export { verify, type VerifyResult, type Recompute } from './verify.js';
4
+ export { checkEnvelope, type EnvelopeResult, type EnvelopeRequest, type Outcome, type Trust } from './envelope.js';
5
+ export { parseUri, formatUri, httpsUrl, type CellRef, type TypedScalar, type Signature, type Unit, type UnitRef } from './uri.js';
6
+ export { keysFromResponse, keysFromDocument, fetchKeys, FETCH_TIMEOUT_MS, type KeysResult, type KeyEntry, type KeyMap, type KeySource } from './keys.js';
7
+ export type { Kind } from './validate.js';
8
+ export type { JsonValue } from './json.js';
9
+ /** Library version. */
10
+ export declare const VERSION = "0.2.0";
11
+ /**
12
+ * Validate `input` as `kind` and return its RFC 8785 canonical bytes. `input` is JSON text,
13
+ * UTF-8 bytes, or an already-parsed value. `kind` is "scalar", "cell", "ref", "function" or
14
+ * "receipt" (inferred from the members when omitted), or "jcs" for plain RFC 8785 with only the
15
+ * strict-parsing rules of SPEC 6.1. A receipt is returned whole, `sig` included, and is checked
16
+ * through SPEC 10.1 step 4. Throws DatumeshError.
17
+ */
18
+ export declare function canonicalize(input: unknown, kind?: Kind | null): Uint8Array;
19
+ /** Cell id (SPEC 4.4): SHA-256 of the cell's canonical bytes, lowercase hex. Throws DatumeshError. */
20
+ export declare function cellHash(cell: unknown): string;
21
+ /** Function hash (SPEC 7): SHA-256 of the descriptor's canonical bytes. Throws DatumeshError. */
22
+ export declare function functionHash(descriptor: unknown): string;
23
+ /** Receipt hash (SPEC 8.3): SHA-256 of the canonical receipt without `sig`. Throws DatumeshError. */
24
+ export declare function receiptHash(receipt: unknown): string;
25
+ /** The signed payload of a receipt (SPEC 8.3): canonical bytes without `sig`. Throws DatumeshError. */
26
+ export declare function receiptPayload(receipt: unknown): Uint8Array;
27
+ /**
28
+ * The unit of a cell's value (SPEC 7.1, units.md section 3): the descriptor's return unit with
29
+ * the cell's argument values substituted, e.g. "USD/EUR"; null when the function does not
30
+ * return a number. `descriptor` must be the cell's function (E_FN_HASH_MISMATCH otherwise).
31
+ * Both are JSON text, bytes or parsed objects. Throws DatumeshError like canonicalize, and
32
+ * E_ARG_MISSING / E_SLICE / E_ARG_TYPE / E_CANON_UNIT when a referenced argument cannot give a
33
+ * unit.
34
+ */
35
+ export declare function resolvedUnit(descriptor: unknown, cell: unknown): string | null;
36
+ /** Key id of an Ed25519 public key given as 64 lowercase hex digits (SPEC 9.1). Throws DatumeshError. */
37
+ export declare function keyId(publicKeyHex: string): string;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = exports.FETCH_TIMEOUT_MS = exports.fetchKeys = exports.keysFromDocument = exports.keysFromResponse = exports.httpsUrl = exports.formatUri = exports.parseUri = exports.checkEnvelope = exports.verify = exports.SPEC_VERSION = exports.ERROR_CODES = exports.DatumeshError = void 0;
4
+ exports.canonicalize = canonicalize;
5
+ exports.cellHash = cellHash;
6
+ exports.functionHash = functionHash;
7
+ exports.receiptHash = receiptHash;
8
+ exports.receiptPayload = receiptPayload;
9
+ exports.resolvedUnit = resolvedUnit;
10
+ exports.keyId = keyId;
11
+ /**
12
+ * @datumesh/verify: verify Datumesh receipts (SPEC.md, revision spec-0.2.0).
13
+ *
14
+ * Everything here is synchronous and offline except fetchKeys, the only function that uses the
15
+ * network.
16
+ */
17
+ const errors_js_1 = require("./errors.js");
18
+ const hash_js_1 = require("./hash.js");
19
+ const json_js_1 = require("./json.js");
20
+ const jcs_js_1 = require("./jcs.js");
21
+ const lexical_js_1 = require("./lexical.js");
22
+ const validate_js_1 = require("./validate.js");
23
+ var errors_js_2 = require("./errors.js");
24
+ Object.defineProperty(exports, "DatumeshError", { enumerable: true, get: function () { return errors_js_2.DatumeshError; } });
25
+ Object.defineProperty(exports, "ERROR_CODES", { enumerable: true, get: function () { return errors_js_2.ERROR_CODES; } });
26
+ Object.defineProperty(exports, "SPEC_VERSION", { enumerable: true, get: function () { return errors_js_2.SPEC_VERSION; } });
27
+ var verify_js_1 = require("./verify.js");
28
+ Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return verify_js_1.verify; } });
29
+ var envelope_js_1 = require("./envelope.js");
30
+ Object.defineProperty(exports, "checkEnvelope", { enumerable: true, get: function () { return envelope_js_1.checkEnvelope; } });
31
+ var uri_js_1 = require("./uri.js");
32
+ Object.defineProperty(exports, "parseUri", { enumerable: true, get: function () { return uri_js_1.parseUri; } });
33
+ Object.defineProperty(exports, "formatUri", { enumerable: true, get: function () { return uri_js_1.formatUri; } });
34
+ Object.defineProperty(exports, "httpsUrl", { enumerable: true, get: function () { return uri_js_1.httpsUrl; } });
35
+ var keys_js_1 = require("./keys.js");
36
+ Object.defineProperty(exports, "keysFromResponse", { enumerable: true, get: function () { return keys_js_1.keysFromResponse; } });
37
+ Object.defineProperty(exports, "keysFromDocument", { enumerable: true, get: function () { return keys_js_1.keysFromDocument; } });
38
+ Object.defineProperty(exports, "fetchKeys", { enumerable: true, get: function () { return keys_js_1.fetchKeys; } });
39
+ Object.defineProperty(exports, "FETCH_TIMEOUT_MS", { enumerable: true, get: function () { return keys_js_1.FETCH_TIMEOUT_MS; } });
40
+ /** Library version. */
41
+ exports.VERSION = '0.2.0';
42
+ function parseAndValidate(input, kind) {
43
+ if (kind !== undefined && kind !== null && !validate_js_1.KINDS.includes(kind))
44
+ throw new TypeError(`unknown kind ${JSON.stringify(kind)}; expected one of ${validate_js_1.KINDS.join(', ')}`);
45
+ const value = (0, json_js_1.toJson)(input);
46
+ const k = kind ?? (0, validate_js_1.inferKind)(value);
47
+ (0, validate_js_1.validate)(value, k);
48
+ return { value, kind: k };
49
+ }
50
+ /**
51
+ * Validate `input` as `kind` and return its RFC 8785 canonical bytes. `input` is JSON text,
52
+ * UTF-8 bytes, or an already-parsed value. `kind` is "scalar", "cell", "ref", "function" or
53
+ * "receipt" (inferred from the members when omitted), or "jcs" for plain RFC 8785 with only the
54
+ * strict-parsing rules of SPEC 6.1. A receipt is returned whole, `sig` included, and is checked
55
+ * through SPEC 10.1 step 4. Throws DatumeshError.
56
+ */
57
+ function canonicalize(input, kind) {
58
+ return (0, jcs_js_1.jcsBytes)(parseAndValidate(input, kind).value);
59
+ }
60
+ /** Cell id (SPEC 4.4): SHA-256 of the cell's canonical bytes, lowercase hex. Throws DatumeshError. */
61
+ function cellHash(cell) {
62
+ return (0, hash_js_1.sha256Hex)((0, jcs_js_1.jcsBytes)(parseAndValidate(cell, 'cell').value));
63
+ }
64
+ /** Function hash (SPEC 7): SHA-256 of the descriptor's canonical bytes. Throws DatumeshError. */
65
+ function functionHash(descriptor) {
66
+ return (0, hash_js_1.sha256Hex)((0, jcs_js_1.jcsBytes)(parseAndValidate(descriptor, 'function').value));
67
+ }
68
+ /** Receipt hash (SPEC 8.3): SHA-256 of the canonical receipt without `sig`. Throws DatumeshError. */
69
+ function receiptHash(receipt) {
70
+ return (0, hash_js_1.sha256Hex)(receiptPayload(receipt));
71
+ }
72
+ /** The signed payload of a receipt (SPEC 8.3): canonical bytes without `sig`. Throws DatumeshError. */
73
+ function receiptPayload(receipt) {
74
+ const r = parseAndValidate(receipt, 'receipt').value;
75
+ const o = (0, json_js_1.newObject)();
76
+ for (const k of Object.keys(r))
77
+ if (k !== 'sig')
78
+ o[k] = r[k];
79
+ return (0, jcs_js_1.jcsBytes)(o);
80
+ }
81
+ /**
82
+ * The unit of a cell's value (SPEC 7.1, units.md section 3): the descriptor's return unit with
83
+ * the cell's argument values substituted, e.g. "USD/EUR"; null when the function does not
84
+ * return a number. `descriptor` must be the cell's function (E_FN_HASH_MISMATCH otherwise).
85
+ * Both are JSON text, bytes or parsed objects. Throws DatumeshError like canonicalize, and
86
+ * E_ARG_MISSING / E_SLICE / E_ARG_TYPE / E_CANON_UNIT when a referenced argument cannot give a
87
+ * unit.
88
+ */
89
+ function resolvedUnit(descriptor, cell) {
90
+ if (descriptor === undefined || cell === undefined)
91
+ throw new TypeError('resolvedUnit(descriptor, cell) needs both arguments');
92
+ const d = (0, json_js_1.toJson)(descriptor);
93
+ const c = (0, json_js_1.toJson)(cell);
94
+ return (0, validate_js_1.cellUnit)(d, c, (x) => (0, hash_js_1.sha256Hex)((0, jcs_js_1.jcsBytes)(x)));
95
+ }
96
+ /** Key id of an Ed25519 public key given as 64 lowercase hex digits (SPEC 9.1). Throws DatumeshError. */
97
+ function keyId(publicKeyHex) {
98
+ if (typeof publicKeyHex !== 'string' || !(0, lexical_js_1.isHex)(publicKeyHex, 64))
99
+ (0, errors_js_1.fail)('E_CANON_HEX', 'expected 64 lowercase hex digits', '$');
100
+ return (0, hash_js_1.keyIdOf)((0, hash_js_1.fromHex)(publicKeyHex));
101
+ }
@@ -0,0 +1,5 @@
1
+ import type { JsonValue } from './json.js';
2
+ /** RFC 8785 canonical text of a JSON value. */
3
+ export declare function jcsText(v: JsonValue): string;
4
+ /** RFC 8785 canonical bytes (UTF-8) of a JSON value. */
5
+ export declare function jcsBytes(v: JsonValue): Uint8Array;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.jcsText = jcsText;
7
+ exports.jcsBytes = jcsBytes;
8
+ // RFC 8785 JSON Canonicalization Scheme. Serialisation is delegated to the `canonicalize`
9
+ // package (the RFC authors' implementation), which relies on ECMAScript JSON.stringify for
10
+ // strings and numbers and on the default (UTF-16 code unit) sort for member names, exactly as
11
+ // RFC 8785 specifies. It is only ever given values produced by ./json.ts (plain data, finite
12
+ // numbers, well-formed strings), so its toJSON/undefined handling never comes into play.
13
+ const canonicalize_1 = __importDefault(require("canonicalize"));
14
+ const utf8_js_1 = require("./utf8.js");
15
+ /** RFC 8785 canonical text of a JSON value. */
16
+ function jcsText(v) {
17
+ const s = (0, canonicalize_1.default)(v);
18
+ if (typeof s !== 'string')
19
+ throw new Error('canonicalize returned no text');
20
+ return s;
21
+ }
22
+ /** RFC 8785 canonical bytes (UTF-8) of a JSON value. */
23
+ function jcsBytes(v) {
24
+ return (0, utf8_js_1.utf8Encode)(jcsText(v));
25
+ }
@@ -0,0 +1,29 @@
1
+ export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
2
+ export interface JsonObject {
3
+ [key: string]: JsonValue;
4
+ }
5
+ /**
6
+ * Nesting deeper than this is rejected with E_PARSE_JSON. SPEC.md sets no depth limit; this is
7
+ * an implementation limit (Datumesh objects nest at most five levels) that keeps parsing and
8
+ * canonicalisation from exhausting the call stack.
9
+ */
10
+ export declare const MAX_DEPTH = 1000;
11
+ export declare function isObject(v: unknown): v is JsonObject;
12
+ export declare function newObject(): JsonObject;
13
+ export declare function has(o: JsonObject, k: string): boolean;
14
+ /** Member names in JCS order (UTF-16 code units, which is JavaScript's default sort). */
15
+ export declare function sortedKeys(o: JsonObject): string[];
16
+ export declare function memberPath(path: string, name: string): string;
17
+ /** Parse JSON text strictly. */
18
+ export declare function parseJsonText(text: string, numbers?: Map<string, string>): JsonValue;
19
+ /** Parse UTF-8 JSON bytes strictly: no BOM, no invalid UTF-8. */
20
+ export declare function parseJsonBytes(bytes: Uint8Array, numbers?: Map<string, string>): JsonValue;
21
+ /**
22
+ * Check an already-parsed JavaScript value against the JSON data model and return a private
23
+ * deep copy (plain data with null-prototype objects), so later checks cannot be affected by
24
+ * getters, prototypes or mutation. Duplicate members cannot be detected here: pass JSON text
25
+ * or bytes for that.
26
+ */
27
+ export declare function normalizeValue(v: unknown, path?: string, depth?: number): JsonValue;
28
+ /** JSON text (string), UTF-8 bytes (Uint8Array) or an already-parsed value. */
29
+ export declare function toJson(input: unknown, numbers?: Map<string, string>): JsonValue;
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_DEPTH = void 0;
4
+ exports.isObject = isObject;
5
+ exports.newObject = newObject;
6
+ exports.has = has;
7
+ exports.sortedKeys = sortedKeys;
8
+ exports.memberPath = memberPath;
9
+ exports.parseJsonText = parseJsonText;
10
+ exports.parseJsonBytes = parseJsonBytes;
11
+ exports.normalizeValue = normalizeValue;
12
+ exports.toJson = toJson;
13
+ // Strict JSON parsing (SPEC.md 6.1, I-JSON / RFC 7493). JSON.parse is not used: it accepts
14
+ // duplicate members (last wins) and lone surrogates, both of which must be rejected.
15
+ const errors_js_1 = require("./errors.js");
16
+ const unicode_js_1 = require("./unicode.js");
17
+ const utf8_js_1 = require("./utf8.js");
18
+ /**
19
+ * Nesting deeper than this is rejected with E_PARSE_JSON. SPEC.md sets no depth limit; this is
20
+ * an implementation limit (Datumesh objects nest at most five levels) that keeps parsing and
21
+ * canonicalisation from exhausting the call stack.
22
+ */
23
+ exports.MAX_DEPTH = 1000;
24
+ function isObject(v) {
25
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
26
+ }
27
+ function newObject() {
28
+ return Object.create(null);
29
+ }
30
+ function has(o, k) {
31
+ return Object.prototype.hasOwnProperty.call(o, k);
32
+ }
33
+ /** Member names in JCS order (UTF-16 code units, which is JavaScript's default sort). */
34
+ function sortedKeys(o) {
35
+ return Object.keys(o).sort();
36
+ }
37
+ function memberPath(path, name) {
38
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? `${path}.${name}` : `${path}[${JSON.stringify(name)}]`;
39
+ }
40
+ class Parser {
41
+ constructor(s,
42
+ /** When given, receives the source text of every number, by JSON path. */
43
+ numbers) {
44
+ this.s = s;
45
+ this.numbers = numbers;
46
+ this.i = 0;
47
+ /** Path of the first string holding an escaped lone surrogate; reported after parsing. */
48
+ this.surrogatePath = null;
49
+ }
50
+ err(msg) {
51
+ (0, errors_js_1.fail)('E_PARSE_JSON', `${msg} at offset ${this.i}`, '$');
52
+ }
53
+ ws() {
54
+ const s = this.s;
55
+ while (this.i < s.length) {
56
+ const c = s.charCodeAt(this.i);
57
+ if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
58
+ this.i++;
59
+ else
60
+ break;
61
+ }
62
+ }
63
+ parseDocument() {
64
+ this.ws();
65
+ const v = this.value('$', 0);
66
+ this.ws();
67
+ if (this.i !== this.s.length)
68
+ this.err('unexpected data after the JSON value');
69
+ return v;
70
+ }
71
+ value(path, depth) {
72
+ if (depth > exports.MAX_DEPTH)
73
+ this.err(`nesting deeper than ${exports.MAX_DEPTH} levels (implementation limit)`);
74
+ const s = this.s;
75
+ const c = s[this.i];
76
+ if (c === '{')
77
+ return this.object(path, depth);
78
+ if (c === '[')
79
+ return this.array(path, depth);
80
+ if (c === '"')
81
+ return this.string(path);
82
+ if (c === 't' && s.startsWith('true', this.i)) {
83
+ this.i += 4;
84
+ return true;
85
+ }
86
+ if (c === 'f' && s.startsWith('false', this.i)) {
87
+ this.i += 5;
88
+ return false;
89
+ }
90
+ if (c === 'n' && s.startsWith('null', this.i)) {
91
+ this.i += 4;
92
+ return null;
93
+ }
94
+ if (c === '-' || (c >= '0' && c <= '9'))
95
+ return this.number(path);
96
+ if (c === undefined)
97
+ this.err('unexpected end of input');
98
+ if (s.startsWith('NaN', this.i) || s.startsWith('Infinity', this.i) || s.startsWith('-Infinity', this.i)) {
99
+ this.err('NaN and Infinity are not JSON');
100
+ }
101
+ this.err(`unexpected character ${JSON.stringify(c)}`);
102
+ }
103
+ object(path, depth) {
104
+ this.i++; // {
105
+ const o = newObject();
106
+ let dup = null;
107
+ this.ws();
108
+ if (this.s[this.i] === '}') {
109
+ this.i++;
110
+ return o;
111
+ }
112
+ for (;;) {
113
+ this.ws();
114
+ if (this.s[this.i] !== '"')
115
+ this.err('expected a member name');
116
+ const k = this.string(path);
117
+ this.ws();
118
+ if (this.s[this.i] !== ':')
119
+ this.err("expected ':'");
120
+ this.i++;
121
+ this.ws();
122
+ const v = this.value(memberPath(path, k), depth + 1);
123
+ if (has(o, k)) {
124
+ if (dup === null)
125
+ dup = k;
126
+ }
127
+ else
128
+ o[k] = v;
129
+ this.ws();
130
+ const c = this.s[this.i];
131
+ if (c === ',') {
132
+ this.i++;
133
+ continue;
134
+ }
135
+ if (c === '}') {
136
+ this.i++;
137
+ break;
138
+ }
139
+ this.err("expected ',' or '}'");
140
+ }
141
+ // Like an object-pairs hook, duplicates are reported once the object is complete.
142
+ if (dup !== null)
143
+ (0, errors_js_1.fail)('E_PARSE_DUPLICATE_KEY', `duplicate member ${JSON.stringify(dup)}`, path);
144
+ return o;
145
+ }
146
+ array(path, depth) {
147
+ this.i++; // [
148
+ const a = [];
149
+ this.ws();
150
+ if (this.s[this.i] === ']') {
151
+ this.i++;
152
+ return a;
153
+ }
154
+ for (;;) {
155
+ this.ws();
156
+ a.push(this.value(`${path}[${a.length}]`, depth + 1));
157
+ this.ws();
158
+ const c = this.s[this.i];
159
+ if (c === ',') {
160
+ this.i++;
161
+ continue;
162
+ }
163
+ if (c === ']') {
164
+ this.i++;
165
+ return a;
166
+ }
167
+ this.err("expected ',' or ']'");
168
+ }
169
+ }
170
+ string(path) {
171
+ const s = this.s;
172
+ this.i++; // opening quote
173
+ let out = '';
174
+ let start = this.i;
175
+ for (;;) {
176
+ if (this.i >= s.length)
177
+ this.err('unterminated string');
178
+ const c = s.charCodeAt(this.i);
179
+ if (c === 0x22) {
180
+ out += s.slice(start, this.i);
181
+ this.i++;
182
+ break;
183
+ }
184
+ if (c < 0x20)
185
+ this.err('control character in string');
186
+ if (c === 0x5c) {
187
+ out += s.slice(start, this.i);
188
+ const e = s[this.i + 1];
189
+ let ch;
190
+ switch (e) {
191
+ case '"':
192
+ ch = '"';
193
+ break;
194
+ case '\\':
195
+ ch = '\\';
196
+ break;
197
+ case '/':
198
+ ch = '/';
199
+ break;
200
+ case 'b':
201
+ ch = '\b';
202
+ break;
203
+ case 'f':
204
+ ch = '\f';
205
+ break;
206
+ case 'n':
207
+ ch = '\n';
208
+ break;
209
+ case 'r':
210
+ ch = '\r';
211
+ break;
212
+ case 't':
213
+ ch = '\t';
214
+ break;
215
+ case 'u': {
216
+ const h = s.slice(this.i + 2, this.i + 6);
217
+ if (!/^[0-9A-Fa-f]{4}$/.test(h))
218
+ this.err('bad \\u escape');
219
+ ch = String.fromCharCode(parseInt(h, 16));
220
+ this.i += 4;
221
+ break;
222
+ }
223
+ default:
224
+ this.err('bad escape');
225
+ }
226
+ out += ch;
227
+ this.i += 2;
228
+ start = this.i;
229
+ continue;
230
+ }
231
+ this.i++;
232
+ }
233
+ if (this.surrogatePath === null && (0, unicode_js_1.hasLoneSurrogate)(out))
234
+ this.surrogatePath = path;
235
+ return out;
236
+ }
237
+ number(path) {
238
+ const s = this.s;
239
+ const m = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
240
+ m.lastIndex = this.i;
241
+ const r = m.exec(s);
242
+ if (!r || r[0] === '-' || r[0] === '')
243
+ this.err('bad number');
244
+ const text = r[0];
245
+ // A digit right after "0" (e.g. 01) is not JSON.
246
+ const next = s[this.i + text.length];
247
+ if (next !== undefined && /[0-9.eE+-]/.test(next))
248
+ this.err('bad number');
249
+ this.i += text.length;
250
+ const n = Number(text);
251
+ if (!Number.isFinite(n))
252
+ (0, errors_js_1.fail)('E_PARSE_JSON', `number out of IEEE 754 range: ${text.slice(0, 40)}`, '$');
253
+ this.numbers?.set(path, text);
254
+ return n;
255
+ }
256
+ }
257
+ /** Parse JSON text strictly. */
258
+ function parseJsonText(text, numbers) {
259
+ if (text.charCodeAt(0) === 0xfeff)
260
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'byte order mark', '$');
261
+ if ((0, unicode_js_1.hasLoneSurrogate)(text))
262
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'lone surrogate in the text', '$');
263
+ const p = new Parser(text, numbers);
264
+ const v = p.parseDocument();
265
+ if (p.surrogatePath !== null)
266
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'lone surrogate', p.surrogatePath);
267
+ return v;
268
+ }
269
+ /** Parse UTF-8 JSON bytes strictly: no BOM, no invalid UTF-8. */
270
+ function parseJsonBytes(bytes, numbers) {
271
+ if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
272
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'byte order mark', '$');
273
+ }
274
+ const text = (0, utf8_js_1.utf8Decode)(bytes);
275
+ if (text === null)
276
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'invalid UTF-8', '$');
277
+ return parseJsonText(text, numbers);
278
+ }
279
+ /**
280
+ * Check an already-parsed JavaScript value against the JSON data model and return a private
281
+ * deep copy (plain data with null-prototype objects), so later checks cannot be affected by
282
+ * getters, prototypes or mutation. Duplicate members cannot be detected here: pass JSON text
283
+ * or bytes for that.
284
+ */
285
+ function normalizeValue(v, path = '$', depth = 0) {
286
+ if (depth > exports.MAX_DEPTH)
287
+ (0, errors_js_1.fail)('E_PARSE_JSON', `nesting deeper than ${exports.MAX_DEPTH} levels (or a cycle)`, path);
288
+ if (v === null || typeof v === 'boolean')
289
+ return v;
290
+ if (typeof v === 'number') {
291
+ if (!Number.isFinite(v))
292
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'NaN and Infinity are not JSON', path);
293
+ return v;
294
+ }
295
+ if (typeof v === 'string') {
296
+ if ((0, unicode_js_1.hasLoneSurrogate)(v))
297
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'lone surrogate', path);
298
+ return v;
299
+ }
300
+ if (Array.isArray(v))
301
+ return v.map((x, i) => normalizeValue(x, `${path}[${i}]`, depth + 1));
302
+ if (typeof v === 'object') {
303
+ const proto = Object.getPrototypeOf(v);
304
+ if (proto !== Object.prototype && proto !== null)
305
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'not a plain JSON object', path);
306
+ const o = newObject();
307
+ for (const k of Object.keys(v)) {
308
+ if ((0, unicode_js_1.hasLoneSurrogate)(k))
309
+ (0, errors_js_1.fail)('E_PARSE_JSON', 'lone surrogate in a member name', path);
310
+ o[k] = normalizeValue(v[k], memberPath(path, k), depth + 1);
311
+ }
312
+ return o;
313
+ }
314
+ (0, errors_js_1.fail)('E_PARSE_JSON', `${typeof v} is not a JSON value`, path);
315
+ }
316
+ /** JSON text (string), UTF-8 bytes (Uint8Array) or an already-parsed value. */
317
+ function toJson(input, numbers) {
318
+ if (typeof input === 'string')
319
+ return parseJsonText(input, numbers);
320
+ if (input instanceof Uint8Array)
321
+ return parseJsonBytes(input, numbers);
322
+ if (input instanceof ArrayBuffer)
323
+ return parseJsonBytes(new Uint8Array(input), numbers);
324
+ return normalizeValue(input);
325
+ }