@astrale-os/kernel-dsl 1.0.0-beta.10 → 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.
Files changed (70) hide show
  1. package/dist/v1/addressing/class-key.js +3 -3
  2. package/dist/v1/addressing/key.d.ts +4 -1
  3. package/dist/v1/addressing/key.js +21 -2
  4. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  5. package/dist/v1/builder/class/authoring.d.ts +6 -256
  6. package/dist/v1/builder/class/handle.d.ts +27 -0
  7. package/dist/v1/builder/class/handle.js +1 -0
  8. package/dist/v1/builder/class/input.d.ts +129 -0
  9. package/dist/v1/builder/class/input.js +1 -0
  10. package/dist/v1/builder/class/projection.d.ts +129 -0
  11. package/dist/v1/builder/class/projection.js +1 -0
  12. package/dist/v1/builder/core/authoring.d.ts +1 -1
  13. package/dist/v1/builder/index.d.ts +3 -1
  14. package/dist/v1/builder/lowering/class.d.ts +1 -1
  15. package/dist/v1/builder/lowering/schema/context.d.ts +2 -1
  16. package/dist/v1/builder/lowering/value/path.d.ts +1 -1
  17. package/dist/v1/builder/policy/authoring.d.ts +1 -1
  18. package/dist/v1/builder/schema/input.d.ts +1 -1
  19. package/dist/v1/builder/schema/projection/class.d.ts +2 -1
  20. package/dist/v1/builder/schema/projection/core-input.d.ts +2 -1
  21. package/dist/v1/builder/schema/projection/identity.d.ts +2 -1
  22. package/dist/v1/builder/schema/projection/property.d.ts +1 -1
  23. package/dist/v1/builder/value/path-metadata.d.ts +1 -1
  24. package/dist/v1/builder/value/path.d.ts +1 -1
  25. package/dist/v1/language/index.d.ts +1 -0
  26. package/dist/v1/language/index.js +1 -0
  27. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  28. package/dist/v1/language/value/inclusion/arrays.d.ts +3 -0
  29. package/dist/v1/language/value/inclusion/arrays.js +85 -0
  30. package/dist/v1/language/value/inclusion/assertions.d.ts +18 -0
  31. package/dist/v1/language/value/inclusion/assertions.js +29 -0
  32. package/dist/v1/language/value/inclusion/combinators.d.ts +3 -0
  33. package/dist/v1/language/value/inclusion/combinators.js +67 -0
  34. package/dist/v1/language/value/inclusion/equivalence.d.ts +4 -0
  35. package/dist/v1/language/value/inclusion/equivalence.js +74 -0
  36. package/dist/v1/language/value/inclusion/evidence.d.ts +34 -0
  37. package/dist/v1/language/value/inclusion/evidence.js +138 -0
  38. package/dist/v1/language/value/inclusion/includes.d.ts +17 -0
  39. package/dist/v1/language/value/inclusion/includes.js +25 -719
  40. package/dist/v1/language/value/inclusion/keywords.d.ts +5 -0
  41. package/dist/v1/language/value/inclusion/keywords.js +31 -0
  42. package/dist/v1/language/value/inclusion/kinds.d.ts +17 -0
  43. package/dist/v1/language/value/inclusion/kinds.js +57 -0
  44. package/dist/v1/language/value/inclusion/location.d.ts +24 -0
  45. package/dist/v1/language/value/inclusion/location.js +63 -0
  46. package/dist/v1/language/value/inclusion/numbers.d.ts +3 -0
  47. package/dist/v1/language/value/inclusion/numbers.js +41 -0
  48. package/dist/v1/language/value/inclusion/objects.d.ts +3 -0
  49. package/dist/v1/language/value/inclusion/objects.js +190 -0
  50. package/dist/v1/language/value/inclusion/strings.d.ts +3 -0
  51. package/dist/v1/language/value/inclusion/strings.js +29 -0
  52. package/dist/v1/language/value/inclusion/types.d.ts +3 -0
  53. package/dist/v1/language/value/inclusion/types.js +11 -0
  54. package/dist/v1/language/value/index.d.ts +1 -1
  55. package/dist/v1/language/value/index.js +1 -1
  56. package/dist/v1/language/value/ordering.d.ts +9 -0
  57. package/dist/v1/language/value/ordering.js +72 -9
  58. package/dist/v1/schema/validation/value-api.d.ts +9 -0
  59. package/dist/v1/schema/validation/value-api.js +23 -1
  60. package/dist/value/canonical.d.ts +3 -2
  61. package/dist/value/canonical.js +30 -6
  62. package/dist/value/decode.d.ts +7 -0
  63. package/dist/value/decode.js +159 -9
  64. package/dist/value/index.d.ts +1 -1
  65. package/dist/value/json.d.ts +1 -1
  66. package/dist/value/json.js +1 -1
  67. package/dist/value/types.d.ts +6 -0
  68. package/dist/value/unicode.d.ts +2 -1
  69. package/dist/value/unicode.js +2 -0
  70. 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`: an
10
- * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
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;
@@ -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`: an
36
- * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
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 bytes = immutableSnapshotBytes(input);
41
- if (bytes !== undefined)
42
- return bytes;
41
+ const owned = immutableSnapshotBytes(input);
42
+ if (owned !== undefined)
43
+ return owned;
43
44
  }
44
- return utf8Length(serializeAdmitted(input));
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)
@@ -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;
@@ -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
- if (state.frames.at(-1)?.kind === 'array')
94
- addMembers(state, 1, offset);
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;
@@ -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';
@@ -1,3 +1,3 @@
1
1
  export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
- export { decode } from './decode.js';
2
+ export { decode, decodeImmutable } from './decode.js';
3
3
  export { DecodeError } from './error.js';
@@ -1,3 +1,3 @@
1
1
  export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
- export { decode } from './decode.js';
2
+ export { decode, decodeImmutable } from './decode.js';
3
3
  export { DecodeError } from './error.js';
@@ -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';
@@ -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
- export declare function assertDecodedString(value: string, offset: number, maxBytes: number): void;
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;
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "1.0.0-beta.10",
3
+ "version": "1.0.0-beta.12",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {