@xml-auditfile/auditfile 0.0.0-stage → 0.0.2-alpha.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/LICENSE +201 -0
- package/README.md +183 -2
- package/package.json +62 -4
- package/src/auditfile.js +186 -0
- package/src/definitions/dialect.js +40 -0
- package/src/definitions/element-definition.js +27 -0
- package/src/definitions/localized-text.js +41 -0
- package/src/definitions/manifest.js +160 -0
- package/src/definitions/names.js +42 -0
- package/src/definitions/resources.js +29 -0
- package/src/definitions/view-definition.js +160 -0
- package/src/diagnostics/collector.js +139 -0
- package/src/diagnostics/diagnostic.js +53 -0
- package/src/diagnostics/report.js +56 -0
- package/src/generated/spec.js +74 -0
- package/src/index.js +20 -0
- package/src/internal/composite-key.js +2 -0
- package/src/internal/decimal.js +193 -0
- package/src/internal/encoding.js +118 -0
- package/src/internal/json.js +106 -0
- package/src/internal/sha256.js +147 -0
- package/src/internal/streams.js +232 -0
- package/src/internal/xml-reader.js +247 -0
- package/src/memory/memory-sink.js +68 -0
- package/src/reading/mapping-plan.js +91 -0
- package/src/reading/read-options.js +136 -0
- package/src/reading/read-session.js +446 -0
- package/src/reading/row-loader.js +362 -0
- package/src/reading/row.js +123 -0
- package/src/reading/source.js +146 -0
- package/src/reading/value-converter.js +136 -0
- package/src/schema/schema.js +256 -0
- package/src/sinks/sink.js +53 -0
- package/src/validation/reference-checker.js +146 -0
- package/src/validation/xaf-rules.js +167 -0
- package/src/versioning/version-resolver.js +119 -0
- package/src/versioning/version.js +217 -0
- package/src/views/view-result.js +20 -0
- package/src/views/view-runner.js +449 -0
- package/src/xsd/datatypes.js +141 -0
- package/src/xsd/regex.js +121 -0
- package/src/xsd/schema-set.js +555 -0
- package/src/xsd/validator.js +484 -0
- package/types/auditfile.d.ts +57 -0
- package/types/definitions/dialect.d.ts +27 -0
- package/types/definitions/element-definition.d.ts +16 -0
- package/types/definitions/localized-text.d.ts +25 -0
- package/types/definitions/manifest.d.ts +118 -0
- package/types/definitions/names.d.ts +25 -0
- package/types/definitions/resources.d.ts +9 -0
- package/types/definitions/view-definition.d.ts +85 -0
- package/types/diagnostics/collector.d.ts +38 -0
- package/types/diagnostics/diagnostic.d.ts +37 -0
- package/types/diagnostics/report.d.ts +32 -0
- package/types/generated/spec.d.ts +6 -0
- package/types/index.d.ts +18 -0
- package/types/internal/composite-key.d.ts +2 -0
- package/types/internal/decimal.d.ts +55 -0
- package/types/internal/encoding.d.ts +43 -0
- package/types/internal/json.d.ts +17 -0
- package/types/internal/sha256.d.ts +17 -0
- package/types/internal/streams.d.ts +35 -0
- package/types/internal/xml-reader.d.ts +50 -0
- package/types/memory/memory-sink.d.ts +29 -0
- package/types/reading/mapping-plan.d.ts +45 -0
- package/types/reading/read-options.d.ts +69 -0
- package/types/reading/read-session.d.ts +28 -0
- package/types/reading/row-loader.d.ts +18 -0
- package/types/reading/row.d.ts +53 -0
- package/types/reading/source.d.ts +73 -0
- package/types/reading/value-converter.d.ts +15 -0
- package/types/schema/schema.d.ts +116 -0
- package/types/sinks/sink.d.ts +59 -0
- package/types/validation/reference-checker.d.ts +11 -0
- package/types/validation/xaf-rules.d.ts +13 -0
- package/types/versioning/version-resolver.d.ts +25 -0
- package/types/versioning/version.d.ts +71 -0
- package/types/views/view-result.d.ts +13 -0
- package/types/views/view-runner.d.ts +1 -0
- package/types/xsd/datatypes.d.ts +40 -0
- package/types/xsd/regex.d.ts +6 -0
- package/types/xsd/schema-set.d.ts +20 -0
- package/types/xsd/validator.d.ts +48 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Streaming reader for Dutch tax audit files (XAF and related), following auditfile-spec.
|
|
2
|
+
|
|
3
|
+
export { Auditfile, AuditfileReader } from './auditfile.js';
|
|
4
|
+
export { Dialect, DialectCorrection } from './definitions/dialect.js';
|
|
5
|
+
export { ElementDefinition } from './definitions/element-definition.js';
|
|
6
|
+
export { LocalizedText, TextLanguage } from './definitions/localized-text.js';
|
|
7
|
+
export { AuditfileManifest, ColumnType, ManifestColumn, ManifestKey, ManifestReference, ManifestTable } from './definitions/manifest.js';
|
|
8
|
+
export { DialectAction, ViewAggregate, ViewOperator } from './definitions/names.js';
|
|
9
|
+
export { ViewColumn, ViewDefinition, ViewJoin, ViewJoinCondition, ViewParameter, ViewValue } from './definitions/view-definition.js';
|
|
10
|
+
export { compareSeverity, Diagnostic, DiagnosticSeverity } from './diagnostics/diagnostic.js';
|
|
11
|
+
export { AuditfileReport } from './diagnostics/report.js';
|
|
12
|
+
export { Decimal } from './internal/decimal.js';
|
|
13
|
+
export { MemorySink } from './memory/memory-sink.js';
|
|
14
|
+
export { ReadMode, ReadProgress } from './reading/read-options.js';
|
|
15
|
+
export { AuditfileRow } from './reading/row.js';
|
|
16
|
+
export { AuditfileSource, PartInfo, SourcePart } from './reading/source.js';
|
|
17
|
+
export { AuditfileSchema, SchemaColumn, SchemaTable, VersionSource } from './schema/schema.js';
|
|
18
|
+
export { ImportContext, SinkCapabilities } from './sinks/sink.js';
|
|
19
|
+
export { AuditfileVersion } from './versioning/version.js';
|
|
20
|
+
export { ViewResult } from './views/view-result.js';
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const PATTERN = /^([+-]?)(\d*)(?:\.(\d*))?$/;
|
|
2
|
+
|
|
3
|
+
// The range of .NET decimal, so every implementation of the reader accepts the same values.
|
|
4
|
+
const MAX_SCALE = 28;
|
|
5
|
+
const MAX_COEFFICIENT = 79228162514264337593543950335n;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An exact decimal number: a BigInt coefficient and a scale (digits after the point).
|
|
9
|
+
* Amounts are never floating point, so totals and balances are exact.
|
|
10
|
+
* The scale is kept as written: `Decimal.parse('100.00').toString()` is `'100.00'`.
|
|
11
|
+
*/
|
|
12
|
+
export class Decimal {
|
|
13
|
+
#coefficient;
|
|
14
|
+
#scale;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {bigint} coefficient
|
|
18
|
+
* @param {number} [scale]
|
|
19
|
+
*/
|
|
20
|
+
constructor(coefficient, scale = 0) {
|
|
21
|
+
if (typeof coefficient !== 'bigint') {
|
|
22
|
+
throw new TypeError('The coefficient of a Decimal must be a bigint.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!Number.isInteger(scale) || scale < 0) {
|
|
26
|
+
throw new RangeError('The scale of a Decimal must be a non-negative integer.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.#coefficient = coefficient;
|
|
30
|
+
this.#scale = scale;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Zero. */
|
|
34
|
+
static ZERO = new Decimal(0n, 0);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parses XSD notation: an optional sign, digits and an optional decimal point. No exponent, no grouping.
|
|
38
|
+
* Returns null when the text is not valid or outside the range of a .NET decimal.
|
|
39
|
+
* @param {string} text
|
|
40
|
+
* @returns {Decimal | null}
|
|
41
|
+
*/
|
|
42
|
+
static parse(text) {
|
|
43
|
+
const match = PATTERN.exec(text);
|
|
44
|
+
if (match === null) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const [, sign, whole, fraction = ''] = match;
|
|
49
|
+
if (whole.length === 0 && fraction.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (fraction.length > MAX_SCALE) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const digits = (whole + fraction).replace(/^0+/, '');
|
|
58
|
+
if (digits.length > 29) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const magnitude = digits.length === 0 ? 0n : BigInt(digits);
|
|
63
|
+
if (magnitude > MAX_COEFFICIENT) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Decimal(sign === '-' ? -magnitude : magnitude, fraction.length);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Converts a number, bigint, numeric string or Decimal.
|
|
72
|
+
* @param {Decimal | number | bigint | string} value
|
|
73
|
+
* @returns {Decimal}
|
|
74
|
+
*/
|
|
75
|
+
static from(value) {
|
|
76
|
+
if (value instanceof Decimal) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === 'bigint') {
|
|
81
|
+
return new Decimal(value, 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof value === 'number') {
|
|
85
|
+
if (!Number.isFinite(value)) {
|
|
86
|
+
throw new RangeError(`${value} is not a finite number.`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Number.isInteger(value)) {
|
|
90
|
+
if (!Number.isSafeInteger(value)) {
|
|
91
|
+
throw new RangeError(`${value} is not a safe integer; pass a string or bigint instead.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return new Decimal(BigInt(value), 0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
99
|
+
const parsed = Decimal.parse(String(value).trim());
|
|
100
|
+
if (parsed !== null) {
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new TypeError(`'${String(value)}' is not a valid decimal.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The unscaled value. */
|
|
109
|
+
get coefficient() {
|
|
110
|
+
return this.#coefficient;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Digits after the decimal point. */
|
|
114
|
+
get scale() {
|
|
115
|
+
return this.#scale;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @param {Decimal} other */
|
|
119
|
+
add(other) {
|
|
120
|
+
const scale = Math.max(this.#scale, other.#scale);
|
|
121
|
+
return new Decimal(this.#rescaled(scale) + other.#rescaled(scale), scale);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @param {Decimal} other */
|
|
125
|
+
subtract(other) {
|
|
126
|
+
return this.add(other.negate());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
negate() {
|
|
130
|
+
return new Decimal(-this.#coefficient, this.#scale);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Compares by value: `1.0` equals `1.00`.
|
|
135
|
+
* @param {Decimal} other
|
|
136
|
+
* @returns {-1 | 0 | 1}
|
|
137
|
+
*/
|
|
138
|
+
compareTo(other) {
|
|
139
|
+
const scale = Math.max(this.#scale, other.#scale);
|
|
140
|
+
const a = this.#rescaled(scale);
|
|
141
|
+
const b = other.#rescaled(scale);
|
|
142
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @param {unknown} other */
|
|
146
|
+
equals(other) {
|
|
147
|
+
return other instanceof Decimal && this.compareTo(other) === 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** True when the value has no fractional part. */
|
|
151
|
+
get isInteger() {
|
|
152
|
+
return this.#coefficient % 10n ** BigInt(this.#scale) === 0n;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The value with trailing fractional zeros removed: `1.50` becomes `1.5`. */
|
|
156
|
+
normalize() {
|
|
157
|
+
let coefficient = this.#coefficient;
|
|
158
|
+
let scale = this.#scale;
|
|
159
|
+
while (scale > 0 && coefficient % 10n === 0n) {
|
|
160
|
+
coefficient /= 10n;
|
|
161
|
+
scale--;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return new Decimal(coefficient, scale);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Nearest JavaScript number; may lose precision. */
|
|
168
|
+
toNumber() {
|
|
169
|
+
return Number(this.toString());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Invariant notation with the scale as written, e.g. `-123.40`. */
|
|
173
|
+
toString() {
|
|
174
|
+
const negative = this.#coefficient < 0n;
|
|
175
|
+
const digits = (negative ? -this.#coefficient : this.#coefficient).toString().padStart(this.#scale + 1, '0');
|
|
176
|
+
const whole = digits.slice(0, digits.length - this.#scale);
|
|
177
|
+
const fraction = digits.slice(digits.length - this.#scale);
|
|
178
|
+
return (negative ? '-' : '') + whole + (this.#scale > 0 ? `.${fraction}` : '');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Serializes as a string so no precision is lost. */
|
|
182
|
+
toJSON() {
|
|
183
|
+
return this.toString();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
187
|
+
return `Decimal(${this.toString()})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#rescaled(scale) {
|
|
191
|
+
return this.#coefficient * 10n ** BigInt(scale - this.#scale);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The encoding of a document or part, from its byte order mark or XML declaration (UTF-8 when neither says otherwise),
|
|
3
|
+
* following the detection rules of XML 1.0 appendix F.
|
|
4
|
+
*/
|
|
5
|
+
export class PartEncoding {
|
|
6
|
+
/** @internal */
|
|
7
|
+
constructor(name, bomLength, unitSize, bigEndian) {
|
|
8
|
+
/** Name as declared, e.g. "utf-8", "utf-16le", "ISO-8859-1". */
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.bomLength = bomLength;
|
|
11
|
+
/** Bytes per code unit: 1 for UTF-8 and single-byte encodings, 2 for UTF-16. */
|
|
12
|
+
this.unitSize = unitSize;
|
|
13
|
+
this.bigEndian = bigEndian;
|
|
14
|
+
Object.freeze(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Upper-case name for messages, e.g. "UTF-8". */
|
|
18
|
+
get label() {
|
|
19
|
+
return this.name.toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when both decode the same way; labels such as "latin1" and "ISO-8859-1" are the same encoding. */
|
|
23
|
+
sameAs(other) {
|
|
24
|
+
return canonical(this.name) === canonical(other.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {Uint8Array} bytes The first bytes.
|
|
29
|
+
* @returns {PartEncoding}
|
|
30
|
+
*/
|
|
31
|
+
static detect(bytes) {
|
|
32
|
+
const startsWith = (...start) => bytes.length >= start.length && start.every((b, i) => bytes[i] === b);
|
|
33
|
+
if (startsWith(0xef, 0xbb, 0xbf)) {
|
|
34
|
+
return new PartEncoding('utf-8', 3, 1, false);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (startsWith(0xff, 0xfe) || startsWith(0x3c, 0x00, 0x3f, 0x00)) {
|
|
38
|
+
return new PartEncoding('utf-16le', startsWith(0xff, 0xfe) ? 2 : 0, 2, false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (startsWith(0xfe, 0xff) || startsWith(0x00, 0x3c, 0x00, 0x3f)) {
|
|
42
|
+
return new PartEncoding('utf-16be', startsWith(0xfe, 0xff) ? 2 : 0, 2, true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const head = String.fromCharCode(...bytes.subarray(0, Math.min(bytes.length, 512)));
|
|
46
|
+
const declaration = /^<\?xml\s[^>]*?encoding\s*=\s*(["'])([A-Za-z][A-Za-z0-9._-]*)\1/.exec(head);
|
|
47
|
+
return new PartEncoding(declaration === null ? 'utf-8' : declaration[2], 0, 1, false);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Number of bytes of a leading byte order mark and XML declaration.
|
|
52
|
+
* @param {Uint8Array} bytes
|
|
53
|
+
* @param {number} length Bytes to look at.
|
|
54
|
+
*/
|
|
55
|
+
skipDeclaration(bytes, length) {
|
|
56
|
+
const position = this.bomLength;
|
|
57
|
+
if (!this.#matches(bytes, length, position, '<?xml')) {
|
|
58
|
+
return position;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (let i = position + 5 * this.unitSize; i + 2 * this.unitSize <= length; i += this.unitSize) {
|
|
62
|
+
if (this.#matches(bytes, length, i, '?>')) {
|
|
63
|
+
return i + 2 * this.unitSize;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return position;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#matches(bytes, length, position, text) {
|
|
71
|
+
for (let c = 0; c < text.length; c++) {
|
|
72
|
+
const at = position + c * this.unitSize;
|
|
73
|
+
if (at + this.unitSize > length) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const low = this.unitSize === 1 || !this.bigEndian ? bytes[at] : bytes[at + 1];
|
|
78
|
+
const high = this.unitSize === 1 ? 0 : this.bigEndian ? bytes[at] : bytes[at + 1];
|
|
79
|
+
if (low !== text.charCodeAt(c) || high !== 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The WHATWG name of an encoding label, or the label itself when no decoder knows it. */
|
|
89
|
+
function canonical(name) {
|
|
90
|
+
try {
|
|
91
|
+
return new TextDecoder(name).encoding;
|
|
92
|
+
} catch {
|
|
93
|
+
return name.toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The declared encoding is not supported (XAF-X004). */
|
|
98
|
+
export class XmlEncodingError extends Error {
|
|
99
|
+
constructor(encoding, message = `Encoding '${encoding}' is not supported.`) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = 'XmlEncodingError';
|
|
102
|
+
this.encoding = encoding;
|
|
103
|
+
/** @type {number | null} The part, when one part differs from the first. */
|
|
104
|
+
this.part = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A continuation part in another encoding than the first part. The parts are decoded as one document,
|
|
110
|
+
* so it would be read as garbled text without any error; it is refused instead (XAF-X004).
|
|
111
|
+
*/
|
|
112
|
+
export class PartEncodingError extends XmlEncodingError {
|
|
113
|
+
constructor(part, encoding, first) {
|
|
114
|
+
super(`${encoding.label} (part 1: ${first.label})`, `Part ${part} is encoded as ${encoding.label}, but part 1 as ${first.label}.`);
|
|
115
|
+
this.name = 'PartEncodingError';
|
|
116
|
+
this.part = part;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Typed access to parsed JSON. Every accessor checks the type, so malformed input fails with a clear message
|
|
2
|
+
// instead of producing undefined values further on.
|
|
3
|
+
|
|
4
|
+
/** @param {unknown} value */
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} text
|
|
11
|
+
* @param {string} name
|
|
12
|
+
*/
|
|
13
|
+
export function parseObject(text, name) {
|
|
14
|
+
let value;
|
|
15
|
+
try {
|
|
16
|
+
value = JSON.parse(text);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new SyntaxError(`${name} is not valid JSON: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!isObject(value)) {
|
|
22
|
+
throw new SyntaxError(`${name} is not a JSON object.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function has(json, key) {
|
|
29
|
+
return Object.prototype.hasOwnProperty.call(json, key) && json[key] !== null && json[key] !== undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function string(json, key) {
|
|
33
|
+
const value = has(json, key) ? json[key] : undefined;
|
|
34
|
+
if (typeof value !== 'string') {
|
|
35
|
+
throw new SyntaxError(`JSON: '${key}' must be a string.`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function optionalString(json, key) {
|
|
42
|
+
return has(json, key) ? string(json, key) : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function int(json, key) {
|
|
46
|
+
const value = has(json, key) ? json[key] : undefined;
|
|
47
|
+
if (!Number.isSafeInteger(value)) {
|
|
48
|
+
throw new SyntaxError(`JSON: '${key}' must be an integer.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function optionalInt(json, key) {
|
|
55
|
+
return has(json, key) ? int(json, key) : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function bool(json, key) {
|
|
59
|
+
return has(json, key) && json[key] === true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function object(json, key) {
|
|
63
|
+
if (!has(json, key)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!isObject(json[key])) {
|
|
68
|
+
throw new SyntaxError(`JSON: '${key}' must be an object.`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return json[key];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function array(json, key) {
|
|
75
|
+
if (!has(json, key)) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!Array.isArray(json[key])) {
|
|
80
|
+
throw new SyntaxError(`JSON: '${key}' must be an array.`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return json[key];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function objects(json, key) {
|
|
87
|
+
return array(json, key).map((item) => {
|
|
88
|
+
if (!isObject(item)) {
|
|
89
|
+
throw new SyntaxError(`JSON: '${key}' must contain objects.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return item;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function strings(json, key) {
|
|
97
|
+
return array(json, key).map((item) => {
|
|
98
|
+
if (typeof item !== 'string') {
|
|
99
|
+
throw new SyntaxError(`JSON: '${key}' must contain strings.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return item;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { isObject };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Incremental SHA-256 (FIPS 180-4) without platform APIs, so the reader runs in Node and in the browser alike.
|
|
2
|
+
// In Node the built-in implementation is used instead, through process.getBuiltinModule so bundlers see no import.
|
|
3
|
+
|
|
4
|
+
const nodeCrypto = globalThis.process?.getBuiltinModule?.('node:crypto') ?? null;
|
|
5
|
+
|
|
6
|
+
const K = new Uint32Array([
|
|
7
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
8
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
9
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
10
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
11
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
12
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
13
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
14
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export class Sha256 {
|
|
18
|
+
#state = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
19
|
+
#block = new Uint8Array(64);
|
|
20
|
+
#blockLength = 0;
|
|
21
|
+
#length = 0;
|
|
22
|
+
#words = new Uint32Array(64);
|
|
23
|
+
#finished = false;
|
|
24
|
+
|
|
25
|
+
/** @param {Uint8Array} data */
|
|
26
|
+
update(data) {
|
|
27
|
+
if (this.#finished) {
|
|
28
|
+
throw new Error('The hash has already been finished.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.#length += data.length;
|
|
32
|
+
let offset = 0;
|
|
33
|
+
if (this.#blockLength > 0) {
|
|
34
|
+
const take = Math.min(64 - this.#blockLength, data.length);
|
|
35
|
+
this.#block.set(data.subarray(0, take), this.#blockLength);
|
|
36
|
+
this.#blockLength += take;
|
|
37
|
+
offset = take;
|
|
38
|
+
if (this.#blockLength < 64) {
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.#compress(this.#block, 0);
|
|
43
|
+
this.#blockLength = 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (; offset + 64 <= data.length; offset += 64) {
|
|
47
|
+
this.#compress(data, offset);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (offset < data.length) {
|
|
51
|
+
this.#block.set(data.subarray(offset), 0);
|
|
52
|
+
this.#blockLength = data.length - offset;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Finishes the hash and returns it as lowercase hex. */
|
|
59
|
+
digestHex() {
|
|
60
|
+
if (this.#finished) {
|
|
61
|
+
throw new Error('The hash has already been finished.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const bits = this.#length * 8;
|
|
65
|
+
const padding = new Uint8Array((this.#blockLength < 56 ? 56 : 120) - this.#blockLength + 8);
|
|
66
|
+
padding[0] = 0x80;
|
|
67
|
+
const view = new DataView(padding.buffer);
|
|
68
|
+
view.setUint32(padding.length - 8, Math.floor(bits / 0x100000000));
|
|
69
|
+
view.setUint32(padding.length - 4, bits >>> 0);
|
|
70
|
+
this.#length -= padding.length;
|
|
71
|
+
this.update(padding);
|
|
72
|
+
this.#finished = true;
|
|
73
|
+
|
|
74
|
+
let hex = '';
|
|
75
|
+
for (const word of this.#state) {
|
|
76
|
+
hex += word.toString(16).padStart(8, '0');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return hex;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @param {Uint8Array} data */
|
|
83
|
+
static hex(data) {
|
|
84
|
+
return Sha256.create().update(data).digestHex();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The fastest available implementation: Node's when present, this one otherwise.
|
|
89
|
+
* @returns {{ update(data: Uint8Array): unknown, digestHex(): string }}
|
|
90
|
+
*/
|
|
91
|
+
static create() {
|
|
92
|
+
if (nodeCrypto === null) {
|
|
93
|
+
return new Sha256();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const hash = nodeCrypto.createHash('sha256');
|
|
97
|
+
return {
|
|
98
|
+
update(data) {
|
|
99
|
+
hash.update(data);
|
|
100
|
+
return this;
|
|
101
|
+
},
|
|
102
|
+
digestHex: () => hash.digest('hex'),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#compress(data, offset) {
|
|
107
|
+
const w = this.#words;
|
|
108
|
+
for (let i = 0; i < 16; i++) {
|
|
109
|
+
const j = offset + i * 4;
|
|
110
|
+
w[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | data[j + 3];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (let i = 16; i < 64; i++) {
|
|
114
|
+
const a = w[i - 15];
|
|
115
|
+
const b = w[i - 2];
|
|
116
|
+
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
|
|
117
|
+
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
|
|
118
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const s = this.#state;
|
|
122
|
+
let [a, b, c, d, e, f, g, h] = s;
|
|
123
|
+
for (let i = 0; i < 64; i++) {
|
|
124
|
+
const s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
125
|
+
const t1 = (h + s1 + ((e & f) ^ (~e & g)) + K[i] + w[i]) | 0;
|
|
126
|
+
const s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
127
|
+
const t2 = (s0 + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
|
128
|
+
h = g;
|
|
129
|
+
g = f;
|
|
130
|
+
f = e;
|
|
131
|
+
e = (d + t1) | 0;
|
|
132
|
+
d = c;
|
|
133
|
+
c = b;
|
|
134
|
+
b = a;
|
|
135
|
+
a = (t1 + t2) | 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
s[0] += a;
|
|
139
|
+
s[1] += b;
|
|
140
|
+
s[2] += c;
|
|
141
|
+
s[3] += d;
|
|
142
|
+
s[4] += e;
|
|
143
|
+
s[5] += f;
|
|
144
|
+
s[6] += g;
|
|
145
|
+
s[7] += h;
|
|
146
|
+
}
|
|
147
|
+
}
|