@astrale-os/kernel-dsl 1.0.0-beta.11 → 1.0.0-beta.12
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/dist/v1/builder/callable/authoring.d.ts +1 -1
- package/dist/v1/builder/class/authoring.d.ts +6 -256
- package/dist/v1/builder/class/handle.d.ts +27 -0
- package/dist/v1/builder/class/handle.js +1 -0
- package/dist/v1/builder/class/input.d.ts +129 -0
- package/dist/v1/builder/class/input.js +1 -0
- package/dist/v1/builder/class/projection.d.ts +129 -0
- package/dist/v1/builder/class/projection.js +1 -0
- package/dist/v1/builder/core/authoring.d.ts +1 -1
- package/dist/v1/builder/index.d.ts +3 -1
- package/dist/v1/builder/lowering/class.d.ts +1 -1
- package/dist/v1/builder/lowering/schema/context.d.ts +2 -1
- package/dist/v1/builder/lowering/value/path.d.ts +1 -1
- package/dist/v1/builder/policy/authoring.d.ts +1 -1
- package/dist/v1/builder/schema/input.d.ts +1 -1
- package/dist/v1/builder/schema/projection/class.d.ts +2 -1
- package/dist/v1/builder/schema/projection/core-input.d.ts +2 -1
- package/dist/v1/builder/schema/projection/identity.d.ts +2 -1
- package/dist/v1/builder/schema/projection/property.d.ts +1 -1
- package/dist/v1/builder/value/path-metadata.d.ts +1 -1
- package/dist/v1/builder/value/path.d.ts +1 -1
- package/dist/v1/language/index.d.ts +1 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/language/validation/diagnostic.d.ts +1 -1
- package/dist/v1/language/value/inclusion/arrays.d.ts +3 -0
- package/dist/v1/language/value/inclusion/arrays.js +85 -0
- package/dist/v1/language/value/inclusion/assertions.d.ts +18 -0
- package/dist/v1/language/value/inclusion/assertions.js +29 -0
- package/dist/v1/language/value/inclusion/combinators.d.ts +3 -0
- package/dist/v1/language/value/inclusion/combinators.js +67 -0
- package/dist/v1/language/value/inclusion/equivalence.d.ts +4 -0
- package/dist/v1/language/value/inclusion/equivalence.js +74 -0
- package/dist/v1/language/value/inclusion/evidence.d.ts +34 -0
- package/dist/v1/language/value/inclusion/evidence.js +138 -0
- package/dist/v1/language/value/inclusion/includes.d.ts +17 -0
- package/dist/v1/language/value/inclusion/includes.js +25 -719
- package/dist/v1/language/value/inclusion/keywords.d.ts +5 -0
- package/dist/v1/language/value/inclusion/keywords.js +31 -0
- package/dist/v1/language/value/inclusion/kinds.d.ts +17 -0
- package/dist/v1/language/value/inclusion/kinds.js +57 -0
- package/dist/v1/language/value/inclusion/location.d.ts +24 -0
- package/dist/v1/language/value/inclusion/location.js +63 -0
- package/dist/v1/language/value/inclusion/numbers.d.ts +3 -0
- package/dist/v1/language/value/inclusion/numbers.js +41 -0
- package/dist/v1/language/value/inclusion/objects.d.ts +3 -0
- package/dist/v1/language/value/inclusion/objects.js +190 -0
- package/dist/v1/language/value/inclusion/strings.d.ts +3 -0
- package/dist/v1/language/value/inclusion/strings.js +29 -0
- package/dist/v1/language/value/inclusion/types.d.ts +3 -0
- package/dist/v1/language/value/inclusion/types.js +11 -0
- package/dist/v1/language/value/index.d.ts +1 -1
- package/dist/v1/language/value/index.js +1 -1
- package/dist/v1/language/value/ordering.d.ts +9 -0
- package/dist/v1/language/value/ordering.js +72 -9
- package/dist/v1/schema/validation/value-api.d.ts +9 -0
- package/dist/v1/schema/validation/value-api.js +23 -1
- package/dist/value/canonical.d.ts +3 -2
- package/dist/value/canonical.js +30 -6
- package/dist/value/decode.d.ts +7 -0
- package/dist/value/decode.js +159 -9
- package/dist/value/index.d.ts +1 -1
- package/dist/value/json.d.ts +1 -1
- package/dist/value/json.js +1 -1
- package/dist/value/types.d.ts +6 -0
- package/dist/value/unicode.d.ts +2 -1
- package/dist/value/unicode.js +2 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** The exact V1 value-schema grammar embedded in DomainSchema declarations. */
|
|
2
|
+
import type { ValueKind } from '@astrale-os/kernel-dsl/value';
|
|
2
3
|
import type { Diagnostic } from '../../language/validation/diagnostic.js';
|
|
3
4
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
4
5
|
import type { OrderingKind } from '../../language/value/ordering.js';
|
|
@@ -13,3 +14,11 @@ export type { OrderingKind } from '../../language/value/ordering.js';
|
|
|
13
14
|
export declare function validate(owner: DomainSchema, contract: JsonSchema, input: unknown, pointer?: string): readonly Diagnostic[];
|
|
14
15
|
/** Return the one portable scalar ordering domain admitted by a property schema, if any. */
|
|
15
16
|
export declare function classifyOrdering(owner: DomainSchema, contract: JsonSchema): OrderingKind | undefined;
|
|
17
|
+
/** The value kind of a property schema: what a graph language may test on the property (Y4). */
|
|
18
|
+
export declare function classifyValueKind(owner: DomainSchema, contract: JsonSchema): ValueKind;
|
|
19
|
+
/**
|
|
20
|
+
* A warning for each class property whose value kind is `json` (an ambiguous union, an object, an
|
|
21
|
+
* array with repetitions): a Query filters it by equality only. Nothing refuses such a property;
|
|
22
|
+
* the CLI shows the warning to its author.
|
|
23
|
+
*/
|
|
24
|
+
export declare function valueKindWarnings(owner: DomainSchema): readonly Diagnostic<'VS_VALUE_KIND_JSON'>[];
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** The exact V1 value-schema grammar embedded in DomainSchema declarations. */
|
|
2
|
+
import { diagnostic } from '../../language/validation/diagnostic.js';
|
|
2
3
|
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
3
|
-
import { classifyOrdering as classifyOrderingWithRegistry } from '../../language/value/ordering.js';
|
|
4
|
+
import { classifyOrdering as classifyOrderingWithRegistry, classifyValueKind as classifyValueKindWithRegistry, } from '../../language/value/ordering.js';
|
|
4
5
|
import { createDomainValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
6
|
+
import { escapePointer } from './policy/context.js';
|
|
5
7
|
const registries = new WeakMap();
|
|
6
8
|
export { VALUE_SCHEMA_DIALECT as DIALECT, VALUE_SCHEMA_VOCABULARY as VOCABULARY, } from '../../language/value/model/schema.js';
|
|
7
9
|
export { blob, isBlobValueSchema as isBlob } from '../../language/value/model/blob.js';
|
|
@@ -14,6 +16,26 @@ export function validate(owner, contract, input, pointer = '') {
|
|
|
14
16
|
export function classifyOrdering(owner, contract) {
|
|
15
17
|
return classifyOrderingWithRegistry(contract, registryFor(owner));
|
|
16
18
|
}
|
|
19
|
+
/** The value kind of a property schema: what a graph language may test on the property (Y4). */
|
|
20
|
+
export function classifyValueKind(owner, contract) {
|
|
21
|
+
return classifyValueKindWithRegistry(contract, registryFor(owner));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A warning for each class property whose value kind is `json` (an ambiguous union, an object, an
|
|
25
|
+
* array with repetitions): a Query filters it by equality only. Nothing refuses such a property;
|
|
26
|
+
* the CLI shows the warning to its author.
|
|
27
|
+
*/
|
|
28
|
+
export function valueKindWarnings(owner) {
|
|
29
|
+
const warnings = [];
|
|
30
|
+
for (const [className, definition] of Object.entries(owner.classes)) {
|
|
31
|
+
for (const [name, property] of Object.entries(definition.properties)) {
|
|
32
|
+
if (classifyValueKind(owner, property.schema) !== 'json')
|
|
33
|
+
continue;
|
|
34
|
+
warnings.push(diagnostic('VS_VALUE_KIND_JSON', `/classes/${escapePointer(className)}/properties/${escapePointer(name)}`, `The value kind of ${className}.${name} is json: a Query filters it by equality only.`));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return warnings;
|
|
38
|
+
}
|
|
17
39
|
function registryFor(owner) {
|
|
18
40
|
let registry = registries.get(owner);
|
|
19
41
|
if (registry === undefined) {
|
|
@@ -6,7 +6,8 @@ export declare function serialize(input: unknown, options?: Options): string;
|
|
|
6
6
|
export declare function serializeAdmitted(input: Value): string;
|
|
7
7
|
export declare function encode(input: unknown, options?: Options): Uint8Array;
|
|
8
8
|
/**
|
|
9
|
-
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength
|
|
10
|
-
* admission-owned immutable root counted its bytes when it was admitted,
|
|
9
|
+
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`, without
|
|
10
|
+
* serializing it: an admission-owned immutable root counted its bytes when it was admitted, and any
|
|
11
|
+
* other container is counted member by member.
|
|
11
12
|
*/
|
|
12
13
|
export declare function byteLength(input: Value): number;
|
package/dist/value/canonical.js
CHANGED
|
@@ -32,16 +32,40 @@ export function encode(input, options) {
|
|
|
32
32
|
return encoder.encode(serialize(input, options));
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength
|
|
36
|
-
* admission-owned immutable root counted its bytes when it was admitted,
|
|
35
|
+
* The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`, without
|
|
36
|
+
* serializing it: an admission-owned immutable root counted its bytes when it was admitted, and any
|
|
37
|
+
* other container is counted member by member.
|
|
37
38
|
*/
|
|
38
39
|
export function byteLength(input) {
|
|
39
40
|
if (input !== null && typeof input === 'object') {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
41
|
+
const owned = immutableSnapshotBytes(input);
|
|
42
|
+
if (owned !== undefined)
|
|
43
|
+
return owned;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
// As serializing it would, a Value beyond the default emitted-byte bound refuses.
|
|
46
|
+
const bytes = measure(input);
|
|
47
|
+
if (bytes > defaultLimits.maxBytes)
|
|
48
|
+
throw new BoundaryError('VALUE_LIMIT', `Canonical JSON exceeds the ${defaultLimits.maxBytes}-byte limit`);
|
|
49
|
+
return bytes;
|
|
50
|
+
}
|
|
51
|
+
/** The canonical JSON bytes of a trusted Value, counted member by member, owned roots at once. */
|
|
52
|
+
function measure(value) {
|
|
53
|
+
if (value === null || typeof value !== 'object')
|
|
54
|
+
return canonicalPrimitiveBytes(value);
|
|
55
|
+
const owned = immutableSnapshotBytes(value);
|
|
56
|
+
if (owned !== undefined)
|
|
57
|
+
return owned;
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
let bytes = 2 + Math.max(0, value.length - 1);
|
|
60
|
+
for (const item of value)
|
|
61
|
+
bytes += measure(item);
|
|
62
|
+
return bytes;
|
|
63
|
+
}
|
|
64
|
+
const keys = Object.keys(value);
|
|
65
|
+
let bytes = 2 + Math.max(0, keys.length - 1) + keys.length;
|
|
66
|
+
for (const key of keys)
|
|
67
|
+
bytes += canonicalPrimitiveBytes(key) + measure(value[key]);
|
|
68
|
+
return bytes;
|
|
45
69
|
}
|
|
46
70
|
function serializeValue(value) {
|
|
47
71
|
if (value === null)
|
package/dist/value/decode.d.ts
CHANGED
|
@@ -2,3 +2,10 @@ import type { Value } from './types.js';
|
|
|
2
2
|
import { type Options } from './limits.js';
|
|
3
3
|
/** Strictly decode one bounded JSON value. */
|
|
4
4
|
export declare function decode(input: string | Uint8Array, options?: Options): Value;
|
|
5
|
+
/**
|
|
6
|
+
* Strictly decode one bounded JSON value as the deeply frozen snapshot `acceptImmutable` returns
|
|
7
|
+
* for it, within the same complete canonical byte budget. Native parsing built every container
|
|
8
|
+
* from scanned text, so none needs host reflection: the scan counts its receipt, and freezing it
|
|
9
|
+
* completes its admission.
|
|
10
|
+
*/
|
|
11
|
+
export declare function decodeImmutable(input: string | Uint8Array, options?: Options): Value;
|
package/dist/value/decode.js
CHANGED
|
@@ -1,16 +1,137 @@
|
|
|
1
1
|
import { printParseErrorCode, visit } from 'jsonc-parser';
|
|
2
2
|
import { DecodeError } from './error.js';
|
|
3
|
+
import { retainImmutableSnapshot } from './immutable.optimization.js';
|
|
3
4
|
import { limitsFor } from './limits.js';
|
|
4
|
-
import { assertDecodedString, assertSourceString } from './unicode.js';
|
|
5
|
+
import { assertDecodedString, assertSourceString, canonicalPrimitiveBytes, utf8Length, } from './unicode.js';
|
|
5
6
|
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
6
7
|
/** Strictly decode one bounded JSON value. */
|
|
7
8
|
export function decode(input, options) {
|
|
9
|
+
return decodeWith(input, limitsFor(options), undefined);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Strictly decode one bounded JSON value as the deeply frozen snapshot `acceptImmutable` returns
|
|
13
|
+
* for it, within the same complete canonical byte budget. Native parsing built every container
|
|
14
|
+
* from scanned text, so none needs host reflection: the scan counts its receipt, and freezing it
|
|
15
|
+
* completes its admission.
|
|
16
|
+
*/
|
|
17
|
+
export function decodeImmutable(input, options) {
|
|
8
18
|
const limits = limitsFor(options);
|
|
19
|
+
const summary = { members: 0, depth: 0, stringBytes: 0, canonicalBytes: 0 };
|
|
20
|
+
// Canonical text may decode to null, so only undefined sends it to the strict scan.
|
|
21
|
+
const canonical = decodeCanonical(input, limits, summary);
|
|
22
|
+
const value = canonical === undefined ? decodeWith(input, limits, summary) : canonical;
|
|
23
|
+
if (value !== null && typeof value === 'object') {
|
|
24
|
+
freeze(value);
|
|
25
|
+
retainImmutableSnapshot(value, summary);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The value `input` parses to when `input` is exactly the text `JSON.stringify` writes for that
|
|
31
|
+
* value, with its receipt counted into `summary`; canonical JSON, as this package serializes it,
|
|
32
|
+
* is such text. That text holds no whitespace, no duplicate member and no number the strict scan
|
|
33
|
+
* refuses, and its byte length is the canonical length, so one walk of the parsed value checks
|
|
34
|
+
* what remains, its strings and limits, as the scan would. Any other input answers undefined, and
|
|
35
|
+
* the strict scan decides it.
|
|
36
|
+
*/
|
|
37
|
+
function decodeCanonical(input, limits, summary) {
|
|
38
|
+
let source;
|
|
39
|
+
let bytes;
|
|
40
|
+
if (typeof input === 'string') {
|
|
41
|
+
source = input;
|
|
42
|
+
bytes = utf8Length(input, limits.maxBytes);
|
|
43
|
+
if (bytes < 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
bytes = input.byteLength;
|
|
48
|
+
try {
|
|
49
|
+
source = decoder.decode(input);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (bytes > limits.maxBytes)
|
|
56
|
+
return undefined;
|
|
57
|
+
let value;
|
|
58
|
+
try {
|
|
59
|
+
value = JSON.parse(source);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
// In ASCII text, a string holds U+0000 or a lone surrogate only through its escape; without one,
|
|
65
|
+
// each string's UTF-8 length is its length.
|
|
66
|
+
const ascii = bytes === source.length && !source.includes('\\u0000') && !source.includes('\\ud');
|
|
67
|
+
const walk = { limits, ascii, members: 0, depth: 0, strings: 0 };
|
|
68
|
+
if (!portable(value, 0, walk) || JSON.stringify(value) !== source)
|
|
69
|
+
return undefined;
|
|
70
|
+
summary.members = walk.members;
|
|
71
|
+
summary.depth = walk.depth;
|
|
72
|
+
summary.stringBytes = walk.strings;
|
|
73
|
+
summary.canonicalBytes = bytes;
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
/** Whether a parsed value's strings and limits hold as the strict scan checks them. */
|
|
77
|
+
function portable(value, depth, walk) {
|
|
78
|
+
if (typeof value === 'string')
|
|
79
|
+
return portableString(value, walk);
|
|
80
|
+
// A number the scan refuses, -0 or one past binary64, never reads back as the text written for it.
|
|
81
|
+
if (value === null || typeof value !== 'object')
|
|
82
|
+
return true;
|
|
83
|
+
if (depth >= walk.limits.maxDepth)
|
|
84
|
+
return false;
|
|
85
|
+
if (depth >= walk.depth)
|
|
86
|
+
walk.depth = depth + 1;
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
walk.members += value.length;
|
|
89
|
+
if (walk.members > walk.limits.maxMembers)
|
|
90
|
+
return false;
|
|
91
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
92
|
+
if (!portable(value[index], depth + 1, walk))
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const keys = Object.keys(value);
|
|
98
|
+
walk.members += keys.length;
|
|
99
|
+
if (walk.members > walk.limits.maxMembers)
|
|
100
|
+
return false;
|
|
101
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
102
|
+
const key = keys[index];
|
|
103
|
+
if (!portableString(key, walk))
|
|
104
|
+
return false;
|
|
105
|
+
if (!portable(value[key], depth + 1, walk))
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
function portableString(value, walk) {
|
|
111
|
+
let bytes = value.length;
|
|
112
|
+
if (!walk.ascii) {
|
|
113
|
+
if (value.includes('\u0000'))
|
|
114
|
+
return false;
|
|
115
|
+
bytes = utf8Length(value, walk.limits.maxStringBytes);
|
|
116
|
+
if (bytes < 0)
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (bytes > walk.limits.maxStringBytes)
|
|
120
|
+
return false;
|
|
121
|
+
if (bytes > walk.strings)
|
|
122
|
+
walk.strings = bytes;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
function decodeWith(input, limits, summary) {
|
|
9
126
|
const source = sourceFor(input, limits);
|
|
10
127
|
if (source.charCodeAt(0) === 0xfeff) {
|
|
11
128
|
throw new DecodeError('JSON_SYNTAX', 'A byte-order mark is not JSON whitespace', 0);
|
|
12
129
|
}
|
|
13
|
-
scan(source, limits);
|
|
130
|
+
scan(source, limits, summary);
|
|
131
|
+
// An immutable snapshot keeps to the canonical byte budget, which a value's text need not.
|
|
132
|
+
if (summary !== undefined && summary.canonicalBytes > limits.maxBytes) {
|
|
133
|
+
throw new DecodeError('JSON_LIMIT', `Canonical JSON exceeds the ${limits.maxBytes}-byte limit`, 0);
|
|
134
|
+
}
|
|
14
135
|
try {
|
|
15
136
|
return JSON.parse(source);
|
|
16
137
|
}
|
|
@@ -18,6 +139,13 @@ export function decode(input, options) {
|
|
|
18
139
|
throw new DecodeError('JSON_SYNTAX', 'Native JSON decoding rejected the strictly scanned input', 0, { cause: error });
|
|
19
140
|
}
|
|
20
141
|
}
|
|
142
|
+
function freeze(value) {
|
|
143
|
+
for (const member of Object.values(value)) {
|
|
144
|
+
if (member !== null && typeof member === 'object')
|
|
145
|
+
freeze(member);
|
|
146
|
+
}
|
|
147
|
+
Object.freeze(value);
|
|
148
|
+
}
|
|
21
149
|
function sourceFor(input, limits) {
|
|
22
150
|
if (typeof input === 'string') {
|
|
23
151
|
assertSourceString(input, limits.maxBytes);
|
|
@@ -33,14 +161,18 @@ function sourceFor(input, limits) {
|
|
|
33
161
|
throw new DecodeError('JSON_UTF8', 'Malformed UTF-8 JSON input', 0, { cause: error });
|
|
34
162
|
}
|
|
35
163
|
}
|
|
36
|
-
function scan(source, limits) {
|
|
37
|
-
const state = { frames: [], limits, members: 0 };
|
|
164
|
+
function scan(source, limits, summary) {
|
|
165
|
+
const state = { frames: [], limits, members: 0, summary };
|
|
38
166
|
const beginContainer = (kind, offset) => {
|
|
39
167
|
countArrayItem(state, offset);
|
|
40
168
|
if (state.frames.length >= limits.maxDepth) {
|
|
41
169
|
throw new DecodeError('JSON_LIMIT', `JSON nesting exceeds the ${limits.maxDepth}-level limit`, offset);
|
|
42
170
|
}
|
|
43
|
-
state.frames.push(kind === 'object' ? { kind, names: new Set() } : { kind });
|
|
171
|
+
state.frames.push(kind === 'object' ? { kind, names: new Set(), entries: 0 } : { kind, entries: 0 });
|
|
172
|
+
if (summary !== undefined) {
|
|
173
|
+
summary.depth = Math.max(summary.depth, state.frames.length);
|
|
174
|
+
summary.canonicalBytes += 2;
|
|
175
|
+
}
|
|
44
176
|
};
|
|
45
177
|
const visitor = {
|
|
46
178
|
onObjectBegin: (offset) => beginContainer('object', offset),
|
|
@@ -48,12 +180,18 @@ function scan(source, limits) {
|
|
|
48
180
|
const frame = state.frames.at(-1);
|
|
49
181
|
if (frame?.kind !== 'object')
|
|
50
182
|
failSyntax('Object member outside an Object', offset);
|
|
51
|
-
assertDecodedString(name, offset, limits.maxStringBytes);
|
|
183
|
+
const bytes = assertDecodedString(name, offset, limits.maxStringBytes);
|
|
52
184
|
if (frame.names.has(name)) {
|
|
53
185
|
throw new DecodeError('JSON_DUPLICATE_MEMBER', `Duplicate object member ${JSON.stringify(name)}`, offset);
|
|
54
186
|
}
|
|
55
187
|
frame.names.add(name);
|
|
56
188
|
addMembers(state, 1, offset);
|
|
189
|
+
if (summary !== undefined) {
|
|
190
|
+
// The name, its colon, and a comma before every member but the first.
|
|
191
|
+
summary.stringBytes = Math.max(summary.stringBytes, bytes);
|
|
192
|
+
summary.canonicalBytes += canonicalPrimitiveBytes(name) + (frame.entries > 0 ? 2 : 1);
|
|
193
|
+
}
|
|
194
|
+
frame.entries += 1;
|
|
57
195
|
},
|
|
58
196
|
onObjectEnd: (offset) => endContainer(state, 'object', offset),
|
|
59
197
|
onArrayBegin: (offset) => beginContainer('array', offset),
|
|
@@ -61,7 +199,9 @@ function scan(source, limits) {
|
|
|
61
199
|
onLiteralValue: (value, offset) => {
|
|
62
200
|
countArrayItem(state, offset);
|
|
63
201
|
if (typeof value === 'string') {
|
|
64
|
-
assertDecodedString(value, offset, limits.maxStringBytes);
|
|
202
|
+
const bytes = assertDecodedString(value, offset, limits.maxStringBytes);
|
|
203
|
+
if (summary !== undefined)
|
|
204
|
+
summary.stringBytes = Math.max(summary.stringBytes, bytes);
|
|
65
205
|
}
|
|
66
206
|
else if (typeof value === 'number') {
|
|
67
207
|
if (!Number.isFinite(value)) {
|
|
@@ -71,6 +211,8 @@ function scan(source, limits) {
|
|
|
71
211
|
throw new DecodeError('JSON_NUMBER', 'JSON number resolves to negative zero', offset);
|
|
72
212
|
}
|
|
73
213
|
}
|
|
214
|
+
if (summary !== undefined)
|
|
215
|
+
summary.canonicalBytes += canonicalPrimitiveBytes(value);
|
|
74
216
|
},
|
|
75
217
|
onError: (error, offset) => {
|
|
76
218
|
failSyntax(`Invalid JSON: ${printParseErrorCode(error)}`, offset);
|
|
@@ -88,10 +230,18 @@ function scan(source, limits) {
|
|
|
88
230
|
throw error;
|
|
89
231
|
throw new DecodeError('JSON_SYNTAX', 'JSON scanner failed', 0, { cause: error });
|
|
90
232
|
}
|
|
233
|
+
if (summary !== undefined)
|
|
234
|
+
summary.members = state.members;
|
|
91
235
|
}
|
|
92
236
|
function countArrayItem(state, offset) {
|
|
93
|
-
|
|
94
|
-
|
|
237
|
+
const frame = state.frames.at(-1);
|
|
238
|
+
if (frame?.kind !== 'array')
|
|
239
|
+
return;
|
|
240
|
+
addMembers(state, 1, offset);
|
|
241
|
+
// A comma before every item but the first.
|
|
242
|
+
if (state.summary !== undefined && frame.entries > 0)
|
|
243
|
+
state.summary.canonicalBytes += 1;
|
|
244
|
+
frame.entries += 1;
|
|
95
245
|
}
|
|
96
246
|
function addMembers(state, count, offset) {
|
|
97
247
|
state.members += count;
|
package/dist/value/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export type { BoundaryErrorCode, DecodeErrorCode } from './error.js';
|
|
|
6
6
|
export { defaultLimits } from './limits.js';
|
|
7
7
|
export type { LimitOverrides, Limits, Options } from './limits.js';
|
|
8
8
|
export * as json from './json.js';
|
|
9
|
-
export type { Array, Object, Primitive, Value } from './types.js';
|
|
9
|
+
export type { Array, Object, Primitive, Value, ValueKind } from './types.js';
|
package/dist/value/json.d.ts
CHANGED
package/dist/value/json.js
CHANGED
package/dist/value/types.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ export type Object = {
|
|
|
4
4
|
readonly [name: string]: Value;
|
|
5
5
|
};
|
|
6
6
|
export type Value = Primitive | Array | Object;
|
|
7
|
+
/**
|
|
8
|
+
* The value kind of a property (page "Fondations Query v7", § Atoms et value kinds, Y4): what a
|
|
9
|
+
* graph language may test on its values. The schema of the property derives it; `null` is a point
|
|
10
|
+
* of every kind, outside every order (R6).
|
|
11
|
+
*/
|
|
12
|
+
export type ValueKind = 'text' | 'number' | 'instant' | 'enum' | 'boolean' | 'set' | 'json';
|
package/dist/value/unicode.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ import type { Primitive } from './types.js';
|
|
|
3
3
|
export declare function canonicalPrimitiveBytes(value: Primitive): number;
|
|
4
4
|
export declare function utf8Length(value: string, max?: number): number;
|
|
5
5
|
export declare function assertHostString(value: string, pointer: string, maxBytes: number): number;
|
|
6
|
-
|
|
6
|
+
/** Its UTF-8 length, once it is a portable string within `maxBytes`. */
|
|
7
|
+
export declare function assertDecodedString(value: string, offset: number, maxBytes: number): number;
|
|
7
8
|
export declare function assertSourceString(value: string, maxBytes: number): void;
|
package/dist/value/unicode.js
CHANGED
|
@@ -63,6 +63,7 @@ export function assertHostString(value, pointer, maxBytes) {
|
|
|
63
63
|
}
|
|
64
64
|
return length;
|
|
65
65
|
}
|
|
66
|
+
/** Its UTF-8 length, once it is a portable string within `maxBytes`. */
|
|
66
67
|
export function assertDecodedString(value, offset, maxBytes) {
|
|
67
68
|
if (value.includes('\u0000')) {
|
|
68
69
|
throw new DecodeError('JSON_STRING', 'JSON string contains U+0000', offset);
|
|
@@ -74,6 +75,7 @@ export function assertDecodedString(value, offset, maxBytes) {
|
|
|
74
75
|
if (length > maxBytes) {
|
|
75
76
|
throw new DecodeError('JSON_LIMIT', `JSON string exceeds the ${maxBytes}-byte UTF-8 limit`, offset);
|
|
76
77
|
}
|
|
78
|
+
return length;
|
|
77
79
|
}
|
|
78
80
|
export function assertSourceString(value, maxBytes) {
|
|
79
81
|
const length = utf8Length(value, maxBytes);
|