@altopelago/aeon-aes 0.12.0 → 0.13.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 (59) hide show
  1. package/README.md +31 -2
  2. package/dist/events.d.ts +5 -0
  3. package/dist/events.d.ts.map +1 -1
  4. package/dist/events.js +3 -0
  5. package/dist/events.js.map +1 -1
  6. package/dist/film-internal.d.ts +137 -0
  7. package/dist/film-internal.d.ts.map +1 -0
  8. package/dist/film-internal.js +702 -0
  9. package/dist/film-internal.js.map +1 -0
  10. package/dist/film.d.ts +64 -0
  11. package/dist/film.d.ts.map +1 -0
  12. package/dist/film.js +22 -0
  13. package/dist/film.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/modes.d.ts.map +1 -1
  19. package/dist/modes.js +14 -8
  20. package/dist/modes.js.map +1 -1
  21. package/dist/paths.d.ts +1 -0
  22. package/dist/paths.d.ts.map +1 -1
  23. package/dist/paths.js +33 -29
  24. package/dist/paths.js.map +1 -1
  25. package/dist/portable.d.ts +98 -0
  26. package/dist/portable.d.ts.map +1 -0
  27. package/dist/portable.js +524 -0
  28. package/dist/portable.js.map +1 -0
  29. package/dist/references.d.ts.map +1 -1
  30. package/dist/references.js +9 -4
  31. package/dist/references.js.map +1 -1
  32. package/dist/resolve.d.ts.map +1 -1
  33. package/dist/resolve.js +52 -2
  34. package/dist/resolve.js.map +1 -1
  35. package/dist/sha256.d.ts +3 -0
  36. package/dist/sha256.d.ts.map +1 -0
  37. package/dist/sha256.js +82 -0
  38. package/dist/sha256.js.map +1 -0
  39. package/dist/telex-datatype-internal.d.ts +12 -0
  40. package/dist/telex-datatype-internal.d.ts.map +1 -0
  41. package/dist/telex-datatype-internal.js +323 -0
  42. package/dist/telex-datatype-internal.js.map +1 -0
  43. package/dist/telex-incremental-internal.d.ts +50 -0
  44. package/dist/telex-incremental-internal.d.ts.map +1 -0
  45. package/dist/telex-incremental-internal.js +422 -0
  46. package/dist/telex-incremental-internal.js.map +1 -0
  47. package/dist/telex-internal.d.ts +86 -0
  48. package/dist/telex-internal.d.ts.map +1 -0
  49. package/dist/telex-internal.js +1134 -0
  50. package/dist/telex-internal.js.map +1 -0
  51. package/dist/telex-limits-internal.d.ts +35 -0
  52. package/dist/telex-limits-internal.d.ts.map +1 -0
  53. package/dist/telex-limits-internal.js +70 -0
  54. package/dist/telex-limits-internal.js.map +1 -0
  55. package/dist/telex.d.ts +130 -0
  56. package/dist/telex.d.ts.map +1 -0
  57. package/dist/telex.js +46 -0
  58. package/dist/telex.js.map +1 -0
  59. package/package.json +3 -3
@@ -0,0 +1,1134 @@
1
+ // @ts-nocheck
2
+ // Kept behaviorally aligned with the published Telex v1 reference codec.
3
+ const UTF8_ENCODER = new TextEncoder();
4
+ const ASCII_ONLY = /^[\x00-\x7F]*$/u;
5
+ const PAYLOAD_REQUIRES_ESCAPING = /[\\\u0000-\u001F\u007F\uD800-\uDFFF]/u;
6
+ const utf8ByteLength = (value) => (ASCII_ONLY.test(value) ? value.length : UTF8_ENCODER.encode(value).byteLength);
7
+ import { assertDatatypeDescriptor, formatDatatypeDescriptor, parseDatatypeDescriptor, } from './telex-datatype-internal.js';
8
+ import { normalizeDatatypeLimits, normalizeTelexLimits } from './telex-limits-internal.js';
9
+ export { DEFAULT_TELEX_LIMITS, normalizeTelexLimits } from './telex-limits-internal.js';
10
+ const VERSION_LINE = 'telex.aes=1';
11
+ const PROFILE_FIELD = 'profile';
12
+ const PROJECTION_FIELD = 'projection';
13
+ const FIELD_NAME = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/;
14
+ const BARE_PATH_MEMBER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
15
+ const TELEX_FIELD_ORDER = new Map([
16
+ 'header',
17
+ 'path',
18
+ 'kind',
19
+ 'datatype',
20
+ 'identity',
21
+ 'value',
22
+ 'origin',
23
+ 'span',
24
+ ].map((field, index) => [field, index]));
25
+ const AES_CORE_FIELDS = [
26
+ 'header',
27
+ 'path',
28
+ 'kind',
29
+ 'datatype',
30
+ 'generics',
31
+ 'clarifiers',
32
+ 'identity',
33
+ 'value',
34
+ 'origin',
35
+ 'span',
36
+ ];
37
+ const CORE_FIELDS = new Set(AES_CORE_FIELDS);
38
+ const VALUE_KINDS = new Set([
39
+ 'StringLiteral',
40
+ 'NumberLiteral',
41
+ 'InfinityLiteral',
42
+ 'NaNLiteral',
43
+ 'NullLiteral',
44
+ 'BooleanLiteral',
45
+ 'ToggleLiteral',
46
+ 'HexLiteral',
47
+ 'RadixLiteral',
48
+ 'EncodingLiteral',
49
+ 'SeparatorLiteral',
50
+ 'SansaAddressLiteral',
51
+ 'DateLiteral',
52
+ 'TimeLiteral',
53
+ 'DateTimeLiteral',
54
+ 'WTCDateTimeLiteral',
55
+ 'ObjectNode',
56
+ 'ListNode',
57
+ 'TupleLiteral',
58
+ 'NodeLiteral',
59
+ 'NodeHead',
60
+ 'CloneReference',
61
+ 'PointerReference',
62
+ ]);
63
+ const VALUELESS_KINDS = new Set(['ObjectNode', 'ListNode', 'TupleLiteral', 'NodeLiteral']);
64
+ const INDEX_CONTAINER_KINDS = new Set(['ListNode', 'TupleLiteral', 'NodeHead']);
65
+ const VALUE_CONTAINER_KINDS = new Set(['ObjectNode', 'ListNode', 'TupleLiteral', 'NodeLiteral']);
66
+ const EXACT_VALUES = new Map([
67
+ ['InfinityLiteral', new Set(['Infinity', '-Infinity'])],
68
+ ['NaNLiteral', new Set(['NaN', '-NaN'])],
69
+ ['BooleanLiteral', new Set(['true', 'false'])],
70
+ ['ToggleLiteral', new Set(['yes', 'no', 'on', 'off'])],
71
+ ]);
72
+ export const TELEX_VERSION = '1';
73
+ export const COMPLETE_AES_PROFILE = 'aes.complete.v1';
74
+ export const PARTIAL_AES_PROFILE = 'aes.partial.v1';
75
+ export const AEON_DOCUMENT_PROJECTION = 'aeon.document.v1';
76
+ export class TelexSyntaxError extends Error {
77
+ constructor(message, line, code = 'TELEX_SYNTAX_ERROR', details = {}) {
78
+ super(line === undefined ? message : `Line ${line}: ${message}`);
79
+ this.name = 'TelexSyntaxError';
80
+ this.line = line;
81
+ this.code = code;
82
+ Object.assign(this, details);
83
+ }
84
+ }
85
+ export function parseTelex(input, options = {}) {
86
+ if (typeof input !== 'string') {
87
+ throw new TypeError('Telex input must be a string');
88
+ }
89
+ const limits = normalizeTelexLimits(options);
90
+ assertTelexLimit('max_input_bytes', utf8ByteLength(input), limits.maxInputBytes);
91
+ const lines = input.split('\n');
92
+ for (let index = 0; index < lines.length; index += 1) {
93
+ const physical = lines[index].endsWith('\r') ? lines[index].slice(0, -1) : lines[index];
94
+ assertTelexLimit('max_line_bytes', utf8ByteLength(physical), limits.maxLineBytes, index + 1);
95
+ }
96
+ if (input.startsWith('\uFEFF')) {
97
+ throw new TelexSyntaxError('UTF-8 byte-order marks are not allowed', 1, 'TELEX_BOM');
98
+ }
99
+ if (/\r(?!\n)/u.test(input)) {
100
+ throw new TelexSyntaxError('Bare carriage returns are not allowed', undefined, 'TELEX_BARE_CR');
101
+ }
102
+ const canonicalLineEndings = !input.includes('\r\n');
103
+ const hasFinalLf = input.endsWith('\n');
104
+ if (!canonicalLineEndings) {
105
+ for (let index = 0; index < lines.length; index += 1) {
106
+ if (lines[index].endsWith('\r'))
107
+ lines[index] = lines[index].slice(0, -1);
108
+ }
109
+ }
110
+ if (hasFinalLf)
111
+ lines.pop();
112
+ if (lines[0] !== VERSION_LINE) {
113
+ throw new TelexSyntaxError(`Expected ${VERSION_LINE}`, 1, 'TELEX_INVALID_PREAMBLE');
114
+ }
115
+ if (lines.length === 1) {
116
+ return {
117
+ version: TELEX_VERSION,
118
+ profile: COMPLETE_AES_PROFILE,
119
+ profileExplicit: false,
120
+ projection: null,
121
+ projectionExplicit: false,
122
+ records: [],
123
+ canonical: canonicalLineEndings && hasFinalLf,
124
+ };
125
+ }
126
+ let profile = COMPLETE_AES_PROFILE;
127
+ let profileExplicit = false;
128
+ let projection = null;
129
+ let projectionExplicit = false;
130
+ let headerCanonical = true;
131
+ let eventStart = 1;
132
+ let lastHeaderRank = -1;
133
+ const resourceState = { decodedPayloadBytes: 0 };
134
+ while (eventStart < lines.length && lines[eventStart] !== '') {
135
+ const lineNumber = eventStart + 1;
136
+ const delimiter = lines[eventStart].indexOf('=');
137
+ const field = delimiter < 0 ? '' : lines[eventStart].slice(0, delimiter);
138
+ if (field !== PROFILE_FIELD && field !== PROJECTION_FIELD)
139
+ break;
140
+ const rank = field === PROFILE_FIELD ? 0 : 1;
141
+ if (rank < lastHeaderRank)
142
+ headerCanonical = false;
143
+ lastHeaderRank = rank;
144
+ const decoded = decodePayloadBounded(lines[eventStart].slice(delimiter + 1), lineNumber, limits, resourceState);
145
+ headerCanonical &&= decoded.canonical;
146
+ if (decoded.value.length === 0) {
147
+ const label = field === PROFILE_FIELD ? 'Profile' : 'Projection';
148
+ const code = field === PROFILE_FIELD ? 'TELEX_EMPTY_PROFILE' : 'TELEX_EMPTY_PROJECTION';
149
+ throw new TelexSyntaxError(`${label} identifier must not be empty`, lineNumber, code);
150
+ }
151
+ if (field === PROFILE_FIELD) {
152
+ if (profileExplicit) {
153
+ throw new TelexSyntaxError('Duplicate stream field: profile', lineNumber, 'TELEX_DUPLICATE_STREAM_FIELD');
154
+ }
155
+ profile = decoded.value;
156
+ profileExplicit = true;
157
+ }
158
+ else {
159
+ if (projectionExplicit) {
160
+ throw new TelexSyntaxError('Duplicate stream field: projection', lineNumber, 'TELEX_DUPLICATE_STREAM_FIELD');
161
+ }
162
+ projection = decoded.value;
163
+ projectionExplicit = true;
164
+ }
165
+ eventStart += 1;
166
+ }
167
+ if (eventStart === lines.length) {
168
+ return {
169
+ version: TELEX_VERSION,
170
+ profile,
171
+ profileExplicit,
172
+ projection,
173
+ projectionExplicit,
174
+ records: [],
175
+ canonical: canonicalLineEndings && hasFinalLf && headerCanonical,
176
+ };
177
+ }
178
+ if (lines[eventStart] !== '') {
179
+ throw new TelexSyntaxError('Expected a blank line after the stream header', eventStart + 1, 'TELEX_MISSING_HEADER_SEPARATOR');
180
+ }
181
+ eventStart += 1;
182
+ const records = [];
183
+ const datatypeLimits = normalizeDatatypeLimits(limits);
184
+ let record = null;
185
+ let datatypeLine;
186
+ let datatypeComponentLine;
187
+ let canonical = canonicalLineEndings && hasFinalLf && headerCanonical;
188
+ // Count the required preamble separator so an additional blank is visibly
189
+ // non-canonical.
190
+ let separatorWidth = 1;
191
+ for (let index = eventStart; index < lines.length; index += 1) {
192
+ const lineNumber = index + 1;
193
+ const line = lines[index];
194
+ if (line === '') {
195
+ separatorWidth += 1;
196
+ if (record !== null) {
197
+ assertTelexLimit('max_events', records.length + 1, limits.maxEvents, lineNumber);
198
+ const decoded = decodeWireRecord(record, datatypeLine, datatypeComponentLine, datatypeLimits);
199
+ canonical &&= decoded.canonical && hasCanonicalFieldOrder(record);
200
+ records.push(decoded.record);
201
+ record = null;
202
+ datatypeLine = undefined;
203
+ datatypeComponentLine = undefined;
204
+ }
205
+ continue;
206
+ }
207
+ if (separatorWidth > 1)
208
+ canonical = false;
209
+ separatorWidth = 0;
210
+ if (record === null)
211
+ record = new Map();
212
+ const delimiter = line.indexOf('=');
213
+ if (delimiter < 1) {
214
+ throw new TelexSyntaxError('Expected field=value', lineNumber, 'TELEX_INVALID_FIELD_LINE');
215
+ }
216
+ const field = line.slice(0, delimiter);
217
+ if (!FIELD_NAME.test(field)) {
218
+ throw new TelexSyntaxError(`Invalid field name: ${field}`, lineNumber, 'TELEX_INVALID_FIELD_NAME');
219
+ }
220
+ if (record.has(field)) {
221
+ throw new TelexSyntaxError(`Duplicate field: ${field}`, lineNumber, 'TELEX_DUPLICATE_FIELD');
222
+ }
223
+ assertTelexLimit('max_fields_per_event', record.size + 1, limits.maxFieldsPerEvent, lineNumber);
224
+ const decoded = decodePayloadBounded(line.slice(delimiter + 1), lineNumber, limits, resourceState);
225
+ canonical &&= decoded.canonical;
226
+ record.set(field, decoded.value);
227
+ if (field === 'datatype')
228
+ datatypeLine = lineNumber;
229
+ if (field === 'generics' || field === 'clarifiers')
230
+ datatypeComponentLine ??= lineNumber;
231
+ }
232
+ if (record !== null) {
233
+ assertTelexLimit('max_events', records.length + 1, limits.maxEvents, lines.length);
234
+ const decoded = decodeWireRecord(record, datatypeLine, datatypeComponentLine, datatypeLimits);
235
+ canonical &&= decoded.canonical && hasCanonicalFieldOrder(record);
236
+ records.push(decoded.record);
237
+ }
238
+ if (separatorWidth > 0)
239
+ canonical = false;
240
+ return {
241
+ version: TELEX_VERSION,
242
+ profile,
243
+ profileExplicit,
244
+ projection,
245
+ projectionExplicit,
246
+ records,
247
+ canonical,
248
+ };
249
+ }
250
+ export function encodeTelex(records, options = {}) {
251
+ if (!Array.isArray(records)) {
252
+ throw new TypeError('Telex records must be an array');
253
+ }
254
+ const limits = normalizeTelexLimits(options);
255
+ assertTelexLimit('max_events', records.length, limits.maxEvents);
256
+ const { profile, projection } = options;
257
+ if (profile !== undefined && (typeof profile !== 'string' || profile.length === 0)) {
258
+ throw new TypeError('Telex profile must be a non-empty string');
259
+ }
260
+ if (projection !== undefined && (typeof projection !== 'string' || projection.length === 0)) {
261
+ throw new TypeError('Telex projection must be a non-empty string');
262
+ }
263
+ assertEncodeStructuralLimits(records, profile ?? COMPLETE_AES_PROFILE, projection ?? null, limits);
264
+ const headerLines = [VERSION_LINE];
265
+ assertTelexLimit('max_line_bytes', VERSION_LINE.length, limits.maxLineBytes, 1);
266
+ let decodedPayloadBytes = 0;
267
+ if (profile !== undefined) {
268
+ decodedPayloadBytes = addDecodedPayloadBytes(decodedPayloadBytes, profile, limits);
269
+ const line = `${PROFILE_FIELD}=${encodePayload(profile)}`;
270
+ assertTelexLimit('max_line_bytes', utf8ByteLength(line), limits.maxLineBytes, headerLines.length + 1);
271
+ headerLines.push(line);
272
+ }
273
+ if (projection !== undefined) {
274
+ decodedPayloadBytes = addDecodedPayloadBytes(decodedPayloadBytes, projection, limits);
275
+ const line = `${PROJECTION_FIELD}=${encodePayload(projection)}`;
276
+ assertTelexLimit('max_line_bytes', utf8ByteLength(line), limits.maxLineBytes, headerLines.length + 1);
277
+ headerLines.push(line);
278
+ }
279
+ if (records.length === 0) {
280
+ const output = `${headerLines.join('\n')}\n`;
281
+ assertTelexLimit('max_input_bytes', utf8ByteLength(output), limits.maxInputBytes);
282
+ return output;
283
+ }
284
+ const datatypeLimits = normalizeDatatypeLimits(limits);
285
+ const stanzas = new Array(records.length);
286
+ let lineNumber = headerLines.length + 2;
287
+ for (let recordIndex = 0; recordIndex < records.length; recordIndex += 1) {
288
+ const entries = encodeWireRecord(records[recordIndex], recordIndex, datatypeLimits);
289
+ if (entries.length === 0) {
290
+ throw new TypeError(`Telex record ${recordIndex + 1} must not be empty`);
291
+ }
292
+ assertTelexLimit('max_fields_per_event', entries.length, limits.maxFieldsPerEvent);
293
+ for (const [field, value] of entries) {
294
+ if (!FIELD_NAME.test(field)) {
295
+ throw new TypeError(`Invalid Telex field name: ${field}`);
296
+ }
297
+ if (typeof value !== 'string')
298
+ throw new TypeError(`Telex field ${field} must have a string payload`);
299
+ decodedPayloadBytes = addDecodedPayloadBytes(decodedPayloadBytes, value, limits);
300
+ }
301
+ if (!hasCanonicalEntryOrder(entries))
302
+ entries.sort(compareTelexFields);
303
+ const lines = new Array(entries.length);
304
+ for (let index = 0; index < entries.length; index += 1) {
305
+ const [field, value] = entries[index];
306
+ const line = `${field}=${encodePayload(value)}`;
307
+ assertTelexLimit('max_line_bytes', utf8ByteLength(line), limits.maxLineBytes, lineNumber);
308
+ lines[index] = line;
309
+ lineNumber += 1;
310
+ }
311
+ stanzas[recordIndex] = lines.join('\n');
312
+ lineNumber += 1;
313
+ }
314
+ const output = `${headerLines.join('\n')}\n\n${stanzas.join('\n\n')}\n`;
315
+ assertTelexLimit('max_input_bytes', utf8ByteLength(output), limits.maxInputBytes);
316
+ return output;
317
+ }
318
+ export function canonicalizeTelex(input, limitsOptions = {}) {
319
+ const parsed = parseTelex(input, limitsOptions);
320
+ const options = {
321
+ ...limitsOptions,
322
+ ...(parsed.profileExplicit ? { profile: parsed.profile } : {}),
323
+ ...(parsed.projectionExplicit ? { projection: parsed.projection } : {}),
324
+ };
325
+ return encodeTelex(parsed.records, options);
326
+ }
327
+ /**
328
+ * Parse Telex and report whether every non-root path prefix is present.
329
+ * This is a structural convenience check, not full AES profile validation.
330
+ */
331
+ export function checkTelexCompleteness(input, options = {}) {
332
+ const parsed = parseTelex(input, options);
333
+ return checkPrefixCompleteness(parsed.records, { projection: parsed.projection });
334
+ }
335
+ export function checkPrefixCompleteness(records, options = {}) {
336
+ if (!Array.isArray(records)) {
337
+ throw new TypeError('Telex records must be an array');
338
+ }
339
+ const paths = new Map([['path', new Set()], ['header', new Set()]]);
340
+ for (const [index, record] of records.entries()) {
341
+ const addressField = recordAddressField(record);
342
+ if (addressField === null) {
343
+ throw new TypeError(`Telex record ${index + 1} must have exactly one string address field`);
344
+ }
345
+ if (addressField === 'header' && options.projection !== AEON_DOCUMENT_PROJECTION) {
346
+ throw new TypeError(`Telex record ${index + 1} requires projection '${AEON_DOCUMENT_PROJECTION}'`);
347
+ }
348
+ paths.get(addressField).add(record[addressField]);
349
+ }
350
+ const missing = [];
351
+ const reported = new Set();
352
+ for (const record of records) {
353
+ const addressField = recordAddressField(record);
354
+ const address = record[addressField];
355
+ const prefixes = canonicalPathPrefixes(address);
356
+ for (const prefix of prefixes.slice(0, -1)) {
357
+ const reportKey = `${addressField}\0${prefix}`;
358
+ if (paths.get(addressField).has(prefix) || reported.has(reportKey))
359
+ continue;
360
+ reported.add(reportKey);
361
+ missing.push({
362
+ ...(addressField === 'header' ? { field: 'header' } : {}),
363
+ path: prefix,
364
+ requiredBy: address,
365
+ });
366
+ }
367
+ }
368
+ return { complete: missing.length === 0, missing };
369
+ }
370
+ /**
371
+ * Validate decoded events against the selected AES profile.
372
+ * Syntax errors still throw from parseTelex; semantic failures are diagnostics.
373
+ */
374
+ export function validateTelex(input, options = {}) {
375
+ const parsed = typeof input === 'string' ? parseTelex(input, options) : input;
376
+ if (parsed === null || typeof parsed !== 'object' || !Array.isArray(parsed.records)) {
377
+ throw new TypeError('Expected Telex text or a parsed Telex result');
378
+ }
379
+ return validateTelexRecords(parsed.records, {
380
+ ...options,
381
+ profile: parsed.profile ?? COMPLETE_AES_PROFILE,
382
+ projection: parsed.projection ?? null,
383
+ });
384
+ }
385
+ export function validateTelexRecords(records, options = {}) {
386
+ if (!Array.isArray(records)) {
387
+ throw new TypeError('Telex records must be an array');
388
+ }
389
+ const state = createTelexValidationState(options);
390
+ prepareTelexValidationRecords(state, records, 0);
391
+ return finalizeTelexValidationState(state, records.length);
392
+ }
393
+ /** Internal state shared by one-shot and incremental semantic validation. */
394
+ export function createTelexValidationState(options = {}) {
395
+ const limits = normalizeTelexLimits(options);
396
+ const profile = options.profile ?? COMPLETE_AES_PROFILE;
397
+ const projection = options.projection ?? null;
398
+ if (typeof profile !== 'string' || profile.length === 0) {
399
+ throw new TypeError('AES profile must be a non-empty string');
400
+ }
401
+ if (projection !== null && (typeof projection !== 'string' || projection.length === 0)) {
402
+ throw new TypeError('AES projection must be null or a non-empty string');
403
+ }
404
+ const registeredFields = new Set(options.registeredFields ?? []);
405
+ for (const field of registeredFields) {
406
+ if (typeof field !== 'string' || !FIELD_NAME.test(field) || CORE_FIELDS.has(field)) {
407
+ throw new TypeError(`Invalid registered extension field: ${String(field)}`);
408
+ }
409
+ }
410
+ const diagnostics = [];
411
+ const events = [];
412
+ if (profile !== COMPLETE_AES_PROFILE && profile !== PARTIAL_AES_PROFILE) {
413
+ diagnostics.push(diagnostic('AES_UNSUPPORTED_PROFILE', `Unsupported AES profile: ${profile}`));
414
+ }
415
+ if (projection !== null && projection !== AEON_DOCUMENT_PROJECTION) {
416
+ diagnostics.push(diagnostic('AES_UNSUPPORTED_PROJECTION', `Unsupported AES projection: ${projection}`));
417
+ }
418
+ return {
419
+ profile,
420
+ projection,
421
+ registeredFields,
422
+ limits,
423
+ diagnostics,
424
+ events,
425
+ bodySeen: false,
426
+ finalized: false,
427
+ };
428
+ }
429
+ /** Prepare event-local semantics while retaining final cross-event checks. */
430
+ export function prepareTelexValidationRecords(state, records, firstRecord) {
431
+ if (state.finalized) {
432
+ throw new Error('The Telex semantic accumulator is already complete');
433
+ }
434
+ for (let index = 0; index < records.length; index += 1) {
435
+ const source = records[index];
436
+ const recordIndex = firstRecord + index;
437
+ if (source === null || typeof source !== 'object') {
438
+ state.diagnostics.push(diagnostic('AES_INVALID_EVENT', 'An AES event must be an object or Map', { record: recordIndex }));
439
+ continue;
440
+ }
441
+ const event = source instanceof Map ? Object.fromEntries(source) : source;
442
+ const addressField = recordAddressField(event);
443
+ const address = addressField === null ? undefined : event[addressField];
444
+ const context = { record: recordIndex, ...(typeof address === 'string' ? { path: address } : {}) };
445
+ for (const [field, payload] of Object.entries(event)) {
446
+ if (field !== 'generics' && field !== 'clarifiers' && typeof payload !== 'string') {
447
+ state.diagnostics.push(diagnostic('AES_INVALID_PAYLOAD', `Field '${field}' must have a string payload`, { ...context, field }));
448
+ }
449
+ if (!CORE_FIELDS.has(field) && !state.registeredFields.has(field)) {
450
+ state.diagnostics.push(diagnostic('AES_UNKNOWN_FIELD', `Field '${field}' is not registered by profile '${state.profile}'`, { ...context, field }));
451
+ }
452
+ }
453
+ const hasPath = Object.hasOwn(event, 'path');
454
+ const hasHeader = Object.hasOwn(event, 'header');
455
+ if (!hasPath && !hasHeader) {
456
+ state.diagnostics.push(diagnostic('AES_MISSING_ADDRESS', "AES records require exactly one of 'path' or 'header'", context));
457
+ }
458
+ else if (hasPath && hasHeader) {
459
+ state.diagnostics.push(diagnostic('AES_MULTIPLE_ADDRESSES', "AES records cannot carry both 'path' and 'header'", context));
460
+ }
461
+ if (!Object.hasOwn(event, 'kind')) {
462
+ state.diagnostics.push(diagnostic('AES_MISSING_FIELD', "AES records require 'kind'", { ...context, field: 'kind' }));
463
+ }
464
+ let pathDetails;
465
+ if (typeof address === 'string') {
466
+ try {
467
+ pathDetails = parseCanonicalDataPath(address);
468
+ if (address === '$') {
469
+ throw new TypeError('The root is not an event path');
470
+ }
471
+ validatePathLimits(address, pathDetails, state.limits, state.diagnostics, context, addressField ?? 'path');
472
+ }
473
+ catch (error) {
474
+ state.diagnostics.push(diagnostic(addressField === 'header' ? 'AES_INVALID_HEADER_PATH' : 'AES_INVALID_PATH', error.message, { ...context, field: addressField ?? 'path' }));
475
+ }
476
+ }
477
+ if (addressField === 'header') {
478
+ if (state.projection !== AEON_DOCUMENT_PROJECTION) {
479
+ state.diagnostics.push(diagnostic('AES_HEADER_REQUIRES_PROJECTION', `Header records require projection '${AEON_DOCUMENT_PROJECTION}'`, { ...context, field: 'header' }));
480
+ }
481
+ if (state.bodySeen) {
482
+ state.diagnostics.push(diagnostic('AES_HEADER_ORDER', 'Header records must precede body events', { ...context, field: 'header' }));
483
+ }
484
+ if (pathDetails !== undefined && !isAeonHeaderPath(address, pathDetails)) {
485
+ state.diagnostics.push(diagnostic('AES_INVALID_HEADER_PATH', "Header paths must begin with a quoted 'aeon:' member", { ...context, field: 'header' }));
486
+ pathDetails = undefined;
487
+ }
488
+ }
489
+ else if (addressField === 'path') {
490
+ state.bodySeen = true;
491
+ }
492
+ const knownKind = typeof event.kind === 'string' && VALUE_KINDS.has(event.kind);
493
+ if (typeof event.kind === 'string' && !knownKind) {
494
+ state.diagnostics.push(diagnostic('AES_UNKNOWN_KIND', `Unknown AES value kind: ${event.kind}`, { ...context, field: 'kind' }));
495
+ }
496
+ if (knownKind)
497
+ validateEventValue(event, recordIndex, state.diagnostics, state.limits);
498
+ validateOptionalCoreFields(event, recordIndex, state.diagnostics, state.limits);
499
+ state.events.push({ event, index: recordIndex, addressField, address, pathDetails, knownKind });
500
+ }
501
+ }
502
+ /** Perform whole-stream validation after syntax and record ingestion finish. */
503
+ export function finalizeTelexValidationState(state, recordCount) {
504
+ if (state.finalized) {
505
+ throw new Error('The Telex semantic accumulator is already complete');
506
+ }
507
+ state.finalized = true;
508
+ if (recordCount > state.limits.maxEvents) {
509
+ state.diagnostics.unshift(limitDiagnostic('max_events', recordCount, state.limits.maxEvents));
510
+ }
511
+ const bodyEvents = state.events.filter(({ addressField }) => addressField === 'path');
512
+ const headerEvents = state.events.filter(({ addressField }) => addressField === 'header');
513
+ validateRepresentedStructuralLimits(bodyEvents, state.limits, state.diagnostics);
514
+ validateRepresentedStructuralLimits(headerEvents, state.limits, state.diagnostics);
515
+ if (state.profile === COMPLETE_AES_PROFILE) {
516
+ validateCompleteStream(bodyEvents, state.diagnostics);
517
+ validateReferenceTargets([...bodyEvents, ...(state.projection === AEON_DOCUMENT_PROJECTION ? headerEvents : [])], bodyEvents, state.diagnostics);
518
+ }
519
+ if (state.projection === AEON_DOCUMENT_PROJECTION) {
520
+ validateCompleteStream(headerEvents, state.diagnostics);
521
+ }
522
+ validateIdentityUniqueness(state.profile === COMPLETE_AES_PROFILE
523
+ ? [...bodyEvents, ...(state.projection === AEON_DOCUMENT_PROJECTION ? headerEvents : [])]
524
+ : state.projection === AEON_DOCUMENT_PROJECTION ? headerEvents : [], state.diagnostics);
525
+ return {
526
+ valid: state.diagnostics.length === 0,
527
+ profile: state.profile,
528
+ diagnostics: state.diagnostics,
529
+ };
530
+ }
531
+ function validateEventValue(event, index, diagnostics, limits) {
532
+ const addressField = recordAddressField(event);
533
+ const address = addressField === null ? undefined : event[addressField];
534
+ const context = { record: index, ...(typeof address === 'string' ? { path: address } : {}) };
535
+ const hasValue = Object.hasOwn(event, 'value');
536
+ if (VALUELESS_KINDS.has(event.kind)) {
537
+ if (hasValue) {
538
+ diagnostics.push(diagnostic('AES_UNEXPECTED_VALUE', `Kind '${event.kind}' must not carry 'value'`, { ...context, field: 'value' }));
539
+ }
540
+ return;
541
+ }
542
+ if (!hasValue) {
543
+ diagnostics.push(diagnostic('AES_MISSING_VALUE', `Kind '${event.kind}' requires 'value'`, { ...context, field: 'value' }));
544
+ return;
545
+ }
546
+ if (typeof event.value !== 'string')
547
+ return;
548
+ if (event.kind === 'StringLiteral') {
549
+ validateCodepointLimit('max_string_codepoints', event.value, limits.maxStringCodepoints, diagnostics, context, 'value');
550
+ }
551
+ const exact = EXACT_VALUES.get(event.kind);
552
+ if (exact !== undefined && !exact.has(event.value)) {
553
+ diagnostics.push(diagnostic('AES_INVALID_VALUE', `Invalid '${event.kind}' payload: ${event.value}`, { ...context, field: 'value' }));
554
+ }
555
+ if (event.kind === 'HexLiteral' && !/^[0-9a-f]+$/u.test(event.value)) {
556
+ diagnostics.push(diagnostic('AES_INVALID_VALUE', 'Hex payloads require one or more lowercase hexadecimal digits', { ...context, field: 'value' }));
557
+ }
558
+ if (event.kind === 'NodeHead' && event.value.length === 0) {
559
+ diagnostics.push(diagnostic('AES_INVALID_VALUE', 'Node tags must not be empty', { ...context, field: 'value' }));
560
+ }
561
+ if (event.kind === 'NodeHead') {
562
+ validateCodepointLimit('max_key_segment_codepoints', event.value, limits.maxKeySegmentCodepoints, diagnostics, context, 'value');
563
+ }
564
+ if (event.kind === 'WTCDateTimeLiteral') {
565
+ const separator = event.value.lastIndexOf('&');
566
+ const reference = separator === -1 ? undefined : event.value.slice(separator + 1);
567
+ if (reference?.toLowerCase() === 'local' && reference !== 'local') {
568
+ diagnostics.push(diagnostic('AES_INVALID_VALUE', "The reserved WTC reference must be exact lowercase 'local'", { ...context, field: 'value' }));
569
+ }
570
+ }
571
+ if (event.kind === 'CloneReference' || event.kind === 'PointerReference') {
572
+ try {
573
+ const pathDetails = parseCanonicalDataPath(event.value);
574
+ if (event.value === '$')
575
+ throw new TypeError('The root is not an event path');
576
+ validatePathLimits(event.value, pathDetails, limits, diagnostics, context, 'value');
577
+ }
578
+ catch (error) {
579
+ diagnostics.push(diagnostic('AES_INVALID_REFERENCE', error.message, { ...context, field: 'value' }));
580
+ }
581
+ }
582
+ }
583
+ function validateOptionalCoreFields(event, index, diagnostics, datatypeLimits) {
584
+ const addressField = recordAddressField(event);
585
+ const address = addressField === null ? undefined : event[addressField];
586
+ const context = { record: index, ...(typeof address === 'string' ? { path: address } : {}) };
587
+ for (const field of ['datatype', 'identity']) {
588
+ if (Object.hasOwn(event, field) && event[field] === '') {
589
+ diagnostics.push(diagnostic('AES_EMPTY_FIELD', `Field '${field}' must not be empty when present`, { ...context, field }));
590
+ }
591
+ }
592
+ const hasDatatype = Object.hasOwn(event, 'datatype');
593
+ const hasGenerics = Object.hasOwn(event, 'generics');
594
+ const hasClarifiers = Object.hasOwn(event, 'clarifiers');
595
+ if (!hasDatatype && (hasGenerics || hasClarifiers)) {
596
+ diagnostics.push(diagnostic('AES_DATATYPE_COMPONENTS', "Fields 'generics' and 'clarifiers' require 'datatype'", { ...context, field: hasGenerics ? 'generics' : 'clarifiers' }));
597
+ }
598
+ else if (hasDatatype && (!hasGenerics || !hasClarifiers)) {
599
+ diagnostics.push(diagnostic('AES_DATATYPE_COMPONENTS', "A datatype requires explicit 'generics' and 'clarifiers' arrays", { ...context, field: !hasGenerics ? 'generics' : 'clarifiers' }));
600
+ }
601
+ else if (hasDatatype) {
602
+ try {
603
+ assertDatatypeDescriptor({
604
+ datatype: event.datatype,
605
+ generics: event.generics,
606
+ clarifiers: event.clarifiers,
607
+ }, datatypeLimits);
608
+ }
609
+ catch (error) {
610
+ diagnostics.push(diagnostic(error.code ?? 'AES_INVALID_DATATYPE', error.message, { ...context, field: 'datatype' }));
611
+ }
612
+ validateDatatypeStringLimits(event.generics, datatypeLimits, diagnostics, context);
613
+ validateDatatypeStringLimits(event.clarifiers, datatypeLimits, diagnostics, context);
614
+ }
615
+ if (typeof event.origin === 'string' && !/^sha256:[0-9a-f]{64}$/u.test(event.origin)) {
616
+ diagnostics.push(diagnostic('AES_INVALID_ORIGIN', "Origin must be 'sha256:' followed by 64 lowercase hexadecimal digits", { ...context, field: 'origin' }));
617
+ }
618
+ if (!Object.hasOwn(event, 'span') || typeof event.span !== 'string')
619
+ return;
620
+ if (!Object.hasOwn(event, 'origin')) {
621
+ diagnostics.push(diagnostic('AES_SPAN_REQUIRES_ORIGIN', "Field 'span' requires source identity in 'origin'", { ...context, field: 'span' }));
622
+ }
623
+ const match = event.span.match(/^(0|[1-9][0-9]*):(0|[1-9][0-9]*)$/u);
624
+ if (match === null || BigInt(match[1]) >= BigInt(match[2])) {
625
+ diagnostics.push(diagnostic('AES_INVALID_SPAN', "Span must be canonical 'start-byte:end-byte' with start-byte < end-byte", { ...context, field: 'span' }));
626
+ }
627
+ }
628
+ function validateReferenceTargets(referenceEvents, bodyEvents, diagnostics) {
629
+ const bodyPaths = new Set(bodyEvents
630
+ .filter(({ pathDetails }) => pathDetails !== undefined)
631
+ .map(({ address }) => address));
632
+ for (const { event, index, address } of referenceEvents) {
633
+ if (event.kind !== 'CloneReference' && event.kind !== 'PointerReference')
634
+ continue;
635
+ if (typeof event.value !== 'string' || event.value === '$')
636
+ continue;
637
+ try {
638
+ parseCanonicalDataPath(event.value);
639
+ }
640
+ catch {
641
+ continue;
642
+ }
643
+ if (!bodyPaths.has(event.value)) {
644
+ diagnostics.push(diagnostic('AES_MISSING_REFERENCE_TARGET', `Missing reference target '${event.value}'`, { record: index, path: address, field: 'value', requiredPath: event.value }));
645
+ }
646
+ }
647
+ }
648
+ function validateCompleteStream(events, diagnostics) {
649
+ const byPath = new Map();
650
+ for (const candidate of events) {
651
+ const { index, address, pathDetails } = candidate;
652
+ if (pathDetails !== undefined) {
653
+ if (byPath.has(address)) {
654
+ diagnostics.push(diagnostic('AES_DUPLICATE_PATH', `Duplicate record address '${address}'`, { record: index, path: address, firstRecord: byPath.get(address).index }));
655
+ }
656
+ else {
657
+ byPath.set(address, candidate);
658
+ }
659
+ }
660
+ }
661
+ for (const candidate of events) {
662
+ const { event, index, address, pathDetails } = candidate;
663
+ if (pathDetails === undefined)
664
+ continue;
665
+ if (pathDetails.segments.length === 1) {
666
+ if (pathDetails.segments[0].type !== 'member') {
667
+ diagnostics.push(diagnostic('AES_MISSING_PARENT', "Only a member event can be a direct child of the unrepresented '$' root", { record: index, path: address, requiredPath: '$' }));
668
+ }
669
+ if (event.kind === 'NodeHead') {
670
+ diagnostics.push(invalidNodeHeadPlacement(event, index));
671
+ }
672
+ continue;
673
+ }
674
+ const segment = pathDetails.segments.at(-1);
675
+ const parentPath = pathDetails.prefixes.at(-2);
676
+ const parent = byPath.get(parentPath);
677
+ if (parent === undefined) {
678
+ diagnostics.push(diagnostic('AES_MISSING_PARENT', `Missing parent event '${parentPath}'`, { record: index, path: address, requiredPath: parentPath }));
679
+ continue;
680
+ }
681
+ if (segment.type === 'member' && parent.event.kind !== 'ObjectNode') {
682
+ diagnostics.push(incompatibleParent(event, index, parentPath, parent.event.kind, 'ObjectNode'));
683
+ }
684
+ else if (segment.type === 'index') {
685
+ if (parent.event.kind === 'NodeLiteral') {
686
+ if (event.kind !== 'NodeHead') {
687
+ diagnostics.push(incompatibleParent(event, index, parentPath, 'NodeLiteral', 'NodeHead child'));
688
+ }
689
+ }
690
+ else if (!INDEX_CONTAINER_KINDS.has(parent.event.kind)) {
691
+ diagnostics.push(incompatibleParent(event, index, parentPath, parent.event.kind, 'ListNode, TupleLiteral, NodeLiteral, or NodeHead'));
692
+ }
693
+ }
694
+ if (event.kind === 'NodeHead'
695
+ && (segment.type !== 'index' || parent.event.kind !== 'NodeLiteral')) {
696
+ diagnostics.push(invalidNodeHeadPlacement(event, index));
697
+ }
698
+ }
699
+ }
700
+ function validateIdentityUniqueness(events, diagnostics) {
701
+ const identities = new Map();
702
+ for (const { event, index, address } of events) {
703
+ if (typeof event.identity !== 'string' || event.identity.length === 0)
704
+ continue;
705
+ if (identities.has(event.identity)) {
706
+ diagnostics.push(diagnostic('AES_DUPLICATE_IDENTITY', `Duplicate structural identity '${event.identity}'`, { record: index, path: address, field: 'identity', firstRecord: identities.get(event.identity) }));
707
+ }
708
+ else {
709
+ identities.set(event.identity, index);
710
+ }
711
+ }
712
+ }
713
+ function incompatibleParent(event, index, parentPath, actual, expected) {
714
+ const addressField = recordAddressField(event);
715
+ const address = addressField === null ? undefined : event[addressField];
716
+ return diagnostic('AES_INCOMPATIBLE_PARENT', `Parent '${parentPath}' has kind '${actual}'; expected ${expected}`, { record: index, path: address, requiredPath: parentPath });
717
+ }
718
+ function invalidNodeHeadPlacement(event, index) {
719
+ const addressField = recordAddressField(event);
720
+ const address = addressField === null ? undefined : event[addressField];
721
+ return diagnostic('AES_INVALID_NODE_HEAD', "A 'NodeHead' must be an indexed direct child of a 'NodeLiteral'", { record: index, ...(typeof address === 'string' ? { path: address } : {}) });
722
+ }
723
+ function recordAddressField(record) {
724
+ if (record === null || typeof record !== 'object')
725
+ return null;
726
+ const hasPath = typeof record.path === 'string';
727
+ const hasHeader = typeof record.header === 'string';
728
+ return hasPath === hasHeader ? null : hasHeader ? 'header' : 'path';
729
+ }
730
+ function isAeonHeaderPath(path, details) {
731
+ if (details.segments[0]?.type !== 'member')
732
+ return false;
733
+ const first = details.prefixes[0];
734
+ if (!first?.startsWith('$.['))
735
+ return false;
736
+ try {
737
+ const member = JSON.parse(first.slice(3, -1));
738
+ return typeof member === 'string' && member.startsWith('aeon:') && member.length > 5;
739
+ }
740
+ catch {
741
+ return false;
742
+ }
743
+ }
744
+ function diagnostic(code, message, context = {}) {
745
+ return { code, message, ...context };
746
+ }
747
+ function validatePathLimits(path, details, limits, diagnostics, context, field) {
748
+ if (details.segments.length > limits.maxPathDepth) {
749
+ diagnostics.push(limitDiagnostic('max_path_depth', details.segments.length, limits.maxPathDepth, { ...context, field }));
750
+ }
751
+ const characters = [...path].length;
752
+ if (characters > limits.maxPathCharacters) {
753
+ diagnostics.push(limitDiagnostic('max_path_characters', characters, limits.maxPathCharacters, { ...context, field }));
754
+ }
755
+ let attributeDepth = 0;
756
+ let currentAttributeDepth = 0;
757
+ for (const segment of details.segments) {
758
+ currentAttributeDepth = segment.type === 'attribute' ? currentAttributeDepth + 1 : 0;
759
+ attributeDepth = Math.max(attributeDepth, currentAttributeDepth);
760
+ }
761
+ if (attributeDepth > limits.maxAttributeDepth) {
762
+ diagnostics.push(limitDiagnostic('max_attribute_depth', attributeDepth, limits.maxAttributeDepth, { ...context, field }));
763
+ }
764
+ for (const segment of details.segments) {
765
+ if ((segment.type === 'member' || segment.type === 'attribute') && typeof segment.value === 'string') {
766
+ validateCodepointLimit('max_key_segment_codepoints', segment.value, limits.maxKeySegmentCodepoints, diagnostics, context, field);
767
+ }
768
+ }
769
+ }
770
+ function validateCodepointLimit(counter, value, limit, diagnostics, context, field) {
771
+ const observed = [...value].length;
772
+ if (observed > limit)
773
+ diagnostics.push(limitDiagnostic(counter, observed, limit, { ...context, field }));
774
+ }
775
+ function validateDatatypeStringLimits(items, limits, diagnostics, context) {
776
+ if (!Array.isArray(items))
777
+ return;
778
+ for (const item of items) {
779
+ if (item?.kind === 'StringLiteral' && typeof item.value === 'string') {
780
+ validateCodepointLimit('max_string_codepoints', item.value, limits.maxStringCodepoints, diagnostics, context, 'clarifiers');
781
+ }
782
+ else if (item !== null && typeof item === 'object') {
783
+ validateDatatypeStringLimits(item.generics, limits, diagnostics, context);
784
+ validateDatatypeStringLimits(item.clarifiers, limits, diagnostics, context);
785
+ }
786
+ }
787
+ }
788
+ function validateRepresentedStructuralLimits(events, limits, diagnostics) {
789
+ const byPath = new Map();
790
+ for (const candidate of events) {
791
+ if (candidate.pathDetails !== undefined && typeof candidate.address === 'string' && !byPath.has(candidate.address)) {
792
+ byPath.set(candidate.address, candidate);
793
+ }
794
+ }
795
+ const directItems = new Map();
796
+ for (const candidate of byPath.values()) {
797
+ if (VALUE_CONTAINER_KINDS.has(candidate.event.kind)) {
798
+ let depth = 1;
799
+ for (let prefixIndex = candidate.pathDetails.prefixes.length - 2; prefixIndex >= 0; prefixIndex -= 1) {
800
+ const parent = byPath.get(candidate.pathDetails.prefixes[prefixIndex]);
801
+ if (parent !== undefined && VALUE_CONTAINER_KINDS.has(parent.event.kind))
802
+ depth += 1;
803
+ }
804
+ if (depth > limits.maxValueNestingDepth) {
805
+ diagnostics.push(limitDiagnostic('max_value_nesting_depth', depth, limits.maxValueNestingDepth, {
806
+ record: candidate.index, path: candidate.address, field: candidate.addressField,
807
+ }));
808
+ }
809
+ }
810
+ if (candidate.pathDetails.segments.at(-1)?.type !== 'index' || candidate.pathDetails.prefixes.length < 2)
811
+ continue;
812
+ const parentPath = candidate.pathDetails.prefixes.at(-2);
813
+ directItems.set(parentPath, (directItems.get(parentPath) ?? 0) + 1);
814
+ }
815
+ for (const [path, observed] of directItems) {
816
+ const parent = byPath.get(path);
817
+ const entry = parent?.event.kind === 'ListNode'
818
+ ? ['max_list_items', limits.maxListItems]
819
+ : parent?.event.kind === 'TupleLiteral'
820
+ ? ['max_tuple_items', limits.maxTupleItems]
821
+ : undefined;
822
+ if (entry !== undefined && observed > entry[1]) {
823
+ diagnostics.push(limitDiagnostic(entry[0], observed, entry[1], {
824
+ record: parent.index, path, field: parent.addressField,
825
+ }));
826
+ }
827
+ }
828
+ }
829
+ function limitDiagnostic(counter, observed, limit, context = {}) {
830
+ return diagnostic('AES_LIMIT_EXCEEDED', limitMessage(counter, observed, limit), { ...context, counter, observed, limit });
831
+ }
832
+ export function decodePayloadBounded(payload, lineNumber, limits, state) {
833
+ const decoded = decodePayload(payload, lineNumber);
834
+ state.decodedPayloadBytes = addDecodedPayloadBytes(state.decodedPayloadBytes, decoded.value, limits, lineNumber);
835
+ return decoded;
836
+ }
837
+ function addDecodedPayloadBytes(current, value, limits, lineNumber) {
838
+ const observed = current + utf8ByteLength(value);
839
+ assertTelexLimit('max_decoded_payload_bytes', observed, limits.maxDecodedPayloadBytes, lineNumber);
840
+ return observed;
841
+ }
842
+ export function assertTelexLimit(counter, observed, limit, line) {
843
+ if (observed <= limit)
844
+ return;
845
+ throw new TelexSyntaxError(limitMessage(counter, observed, limit), line, 'TELEX_LIMIT_EXCEEDED', { counter, observed, limit });
846
+ }
847
+ function assertEncodeStructuralLimits(records, profile, projection, limits) {
848
+ const exhaustion = validateTelexRecords(records, { limits, profile, projection })
849
+ .diagnostics.find((diagnostic) => diagnostic.code === 'AES_LIMIT_EXCEEDED');
850
+ if (exhaustion === undefined)
851
+ return;
852
+ assertTelexLimit(exhaustion.counter, exhaustion.observed, exhaustion.limit);
853
+ }
854
+ function limitMessage(counter, observed, limit) {
855
+ return `${counter} observed value ${observed} exceeds configured limit ${limit}`;
856
+ }
857
+ function compareTelexFields([left], [right]) {
858
+ return compareTelexFieldNames(left, right);
859
+ }
860
+ function compareTelexFieldNames(left, right) {
861
+ const leftRank = TELEX_FIELD_ORDER.get(left);
862
+ const rightRank = TELEX_FIELD_ORDER.get(right);
863
+ if (leftRank !== undefined || rightRank !== undefined) {
864
+ if (leftRank === undefined)
865
+ return 1;
866
+ if (rightRank === undefined)
867
+ return -1;
868
+ return leftRank - rightRank;
869
+ }
870
+ return left < right ? -1 : left > right ? 1 : 0;
871
+ }
872
+ export function hasCanonicalFieldOrder(record) {
873
+ const fields = record instanceof Map ? record.keys() : Object.keys(record);
874
+ let previous;
875
+ for (const field of fields) {
876
+ if (previous !== undefined && compareTelexFieldNames(previous, field) > 0)
877
+ return false;
878
+ previous = field;
879
+ }
880
+ return true;
881
+ }
882
+ function hasCanonicalEntryOrder(entries) {
883
+ for (let index = 1; index < entries.length; index += 1) {
884
+ if (compareTelexFieldNames(entries[index - 1][0], entries[index][0]) > 0)
885
+ return false;
886
+ }
887
+ return true;
888
+ }
889
+ export function decodeWireRecord(fields, datatypeLine, datatypeComponentLine, datatypeLimits) {
890
+ const record = {};
891
+ let canonical = true;
892
+ for (const [field, value] of fields) {
893
+ if (field === 'generics' || field === 'clarifiers') {
894
+ throw new TelexSyntaxError(`Logical AES field '${field}' must be encoded through the Telex datatype line`, datatypeComponentLine, 'TELEX_INVALID_DATATYPE');
895
+ }
896
+ if (field !== 'datatype') {
897
+ record[field] = value;
898
+ continue;
899
+ }
900
+ let descriptor;
901
+ try {
902
+ descriptor = parseDatatypeDescriptor(value, datatypeLimits);
903
+ }
904
+ catch (error) {
905
+ throw new TelexSyntaxError(error.message, datatypeLine, error.code ?? 'TELEX_INVALID_DATATYPE', error.counter === undefined
906
+ ? {}
907
+ : { counter: error.counter, observed: error.observed, limit: error.limit });
908
+ }
909
+ canonical &&= formatDatatypeDescriptor(descriptor, datatypeLimits) === value;
910
+ record.datatype = descriptor.datatype;
911
+ record.generics = descriptor.generics;
912
+ record.clarifiers = descriptor.clarifiers;
913
+ }
914
+ return { record, canonical };
915
+ }
916
+ function encodeWireRecord(source, recordIndex, datatypeLimits) {
917
+ if (source === null || typeof source !== 'object' || Array.isArray(source)) {
918
+ throw new TypeError(`Telex record ${recordIndex + 1} must be an object or Map`);
919
+ }
920
+ const logicalEntries = source instanceof Map ? [...source.entries()] : Object.entries(source);
921
+ const hasDatatype = source instanceof Map ? source.has('datatype') : Object.hasOwn(source, 'datatype');
922
+ const hasGenerics = source instanceof Map ? source.has('generics') : Object.hasOwn(source, 'generics');
923
+ const hasClarifiers = source instanceof Map ? source.has('clarifiers') : Object.hasOwn(source, 'clarifiers');
924
+ if (!hasDatatype && (hasGenerics || hasClarifiers)) {
925
+ throw new TypeError(`Telex record ${recordIndex + 1} has datatype components without datatype`);
926
+ }
927
+ if (hasDatatype && (!hasGenerics || !hasClarifiers)) {
928
+ throw new TypeError(`Telex record ${recordIndex + 1} requires generics and clarifiers arrays with datatype`);
929
+ }
930
+ let wireDatatype;
931
+ if (hasDatatype) {
932
+ const descriptor = {
933
+ datatype: source instanceof Map ? source.get('datatype') : source.datatype,
934
+ generics: source instanceof Map ? source.get('generics') : source.generics,
935
+ clarifiers: source instanceof Map ? source.get('clarifiers') : source.clarifiers,
936
+ };
937
+ wireDatatype = formatDatatypeDescriptor(descriptor, datatypeLimits);
938
+ }
939
+ const entries = [];
940
+ for (const [field, value] of logicalEntries) {
941
+ if (field === 'generics' || field === 'clarifiers')
942
+ continue;
943
+ entries.push([field, field === 'datatype' ? wireDatatype : value]);
944
+ }
945
+ return entries;
946
+ }
947
+ function canonicalPathPrefixes(path) {
948
+ return parseCanonicalDataPath(path).prefixes;
949
+ }
950
+ function parseCanonicalDataPath(path) {
951
+ if (!path.startsWith('$')) {
952
+ throw new TypeError(`Expected an absolute canonical path: ${path}`);
953
+ }
954
+ if (path === '$')
955
+ return { prefixes: [], segments: [] };
956
+ const prefixes = [];
957
+ const segments = [];
958
+ let cursor = 1;
959
+ while (cursor < path.length) {
960
+ const start = cursor;
961
+ if (path.startsWith('.@.', cursor)) {
962
+ cursor += 3;
963
+ const member = readMember(path, cursor);
964
+ cursor = member.end;
965
+ segments.push({ type: 'attribute', value: member.value });
966
+ }
967
+ else if (path[cursor] === '.') {
968
+ cursor += 1;
969
+ const member = readMember(path, cursor);
970
+ cursor = member.end;
971
+ segments.push({ type: 'member', value: member.value });
972
+ }
973
+ else if (path[cursor] === '[') {
974
+ const index = path.slice(cursor).match(/^\[(?:0|[1-9][0-9]*)\]/u);
975
+ if (!index)
976
+ throw new TypeError(`Invalid canonical index in path: ${path}`);
977
+ cursor += index[0].length;
978
+ segments.push({ type: 'index', value: Number(index[0].slice(1, -1)) });
979
+ }
980
+ else {
981
+ throw new TypeError(`Invalid canonical path segment in: ${path}`);
982
+ }
983
+ prefixes.push(`${prefixes.at(-1) ?? '$'}${path.slice(start, cursor)}`);
984
+ }
985
+ return { prefixes, segments };
986
+ }
987
+ function readMember(path, cursor) {
988
+ if (path[cursor] === '[') {
989
+ if (path[cursor + 1] !== '"') {
990
+ throw new TypeError(`Expected a quoted canonical member in path: ${path}`);
991
+ }
992
+ let quoteEnd = cursor + 2;
993
+ let escaped = false;
994
+ for (; quoteEnd < path.length; quoteEnd += 1) {
995
+ const character = path[quoteEnd];
996
+ if (escaped) {
997
+ escaped = false;
998
+ }
999
+ else if (character === '\\') {
1000
+ escaped = true;
1001
+ }
1002
+ else if (character === '"') {
1003
+ break;
1004
+ }
1005
+ }
1006
+ if (path[quoteEnd] !== '"' || path[quoteEnd + 1] !== ']') {
1007
+ throw new TypeError(`Unterminated quoted canonical member in path: ${path}`);
1008
+ }
1009
+ const encoded = path.slice(cursor + 1, quoteEnd + 1);
1010
+ let decoded;
1011
+ try {
1012
+ decoded = JSON.parse(encoded);
1013
+ }
1014
+ catch {
1015
+ throw new TypeError(`Invalid quoted canonical member in path: ${path}`);
1016
+ }
1017
+ if (typeof decoded !== 'string'
1018
+ || decoded.length === 0
1019
+ || BARE_PATH_MEMBER.test(decoded)
1020
+ || hasLoneSurrogate(decoded)
1021
+ || JSON.stringify(decoded) !== encoded) {
1022
+ throw new TypeError(`Non-canonical quoted member in path: ${path}`);
1023
+ }
1024
+ return { end: quoteEnd + 2, value: decoded };
1025
+ }
1026
+ const member = path.slice(cursor).match(/^[A-Za-z_][A-Za-z0-9_]*/u);
1027
+ if (!member)
1028
+ throw new TypeError(`Invalid canonical member in path: ${path}`);
1029
+ return { end: cursor + member[0].length, value: member[0] };
1030
+ }
1031
+ function hasLoneSurrogate(value) {
1032
+ for (let index = 0; index < value.length; index += 1) {
1033
+ const unit = value.charCodeAt(index);
1034
+ if (unit >= 0xd800 && unit <= 0xdbff) {
1035
+ const next = value.charCodeAt(index + 1);
1036
+ if (next < 0xdc00 || next > 0xdfff)
1037
+ return true;
1038
+ index += 1;
1039
+ }
1040
+ else if (unit >= 0xdc00 && unit <= 0xdfff) {
1041
+ return true;
1042
+ }
1043
+ }
1044
+ return false;
1045
+ }
1046
+ function decodePayload(payload, lineNumber) {
1047
+ if (!PAYLOAD_REQUIRES_ESCAPING.test(payload)) {
1048
+ return { value: payload, canonical: true };
1049
+ }
1050
+ let value = '';
1051
+ let canonical = true;
1052
+ for (let index = 0; index < payload.length; index += 1) {
1053
+ const character = payload[index];
1054
+ const codePoint = payload.codePointAt(index);
1055
+ if (character !== '\\') {
1056
+ if (codePoint <= 0x1f || codePoint === 0x7f) {
1057
+ throw new TelexSyntaxError('Unescaped control character in payload', lineNumber, 'TELEX_UNESCAPED_CONTROL');
1058
+ }
1059
+ if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
1060
+ throw new TelexSyntaxError('Payload contains a surrogate instead of a Unicode scalar', lineNumber, 'TELEX_INVALID_UNICODE_SCALAR');
1061
+ }
1062
+ value += String.fromCodePoint(codePoint);
1063
+ if (codePoint > 0xffff)
1064
+ index += 1;
1065
+ continue;
1066
+ }
1067
+ const escape = payload[++index];
1068
+ if (escape === undefined) {
1069
+ throw new TelexSyntaxError('Incomplete escape', lineNumber, 'TELEX_INCOMPLETE_ESCAPE');
1070
+ }
1071
+ const short = { '\\': '\\', n: '\n', r: '\r', t: '\t', 0: '\0' }[escape];
1072
+ if (short !== undefined) {
1073
+ value += short;
1074
+ continue;
1075
+ }
1076
+ if (escape !== 'u' || payload[index + 1] !== '{') {
1077
+ throw new TelexSyntaxError(`Unknown escape: \\${escape}`, lineNumber, 'TELEX_UNKNOWN_ESCAPE');
1078
+ }
1079
+ const close = payload.indexOf('}', index + 2);
1080
+ if (close === -1) {
1081
+ throw new TelexSyntaxError('Unterminated Unicode escape', lineNumber, 'TELEX_UNTERMINATED_UNICODE_ESCAPE');
1082
+ }
1083
+ const digits = payload.slice(index + 2, close);
1084
+ if (!/^[0-9A-Fa-f]{1,6}$/u.test(digits)) {
1085
+ throw new TelexSyntaxError('Invalid Unicode escape', lineNumber, 'TELEX_INVALID_UNICODE_ESCAPE');
1086
+ }
1087
+ const scalar = Number.parseInt(digits, 16);
1088
+ if (scalar > 0x10ffff || (scalar >= 0xd800 && scalar <= 0xdfff)) {
1089
+ throw new TelexSyntaxError('Unicode escape is not a scalar value', lineNumber, 'TELEX_INVALID_UNICODE_SCALAR');
1090
+ }
1091
+ if (digits !== scalar.toString(16).toUpperCase())
1092
+ canonical = false;
1093
+ if ({ 0: '\0', 9: '\t', A: '\n', D: '\r' }[digits.toUpperCase()] !== undefined) {
1094
+ canonical = false;
1095
+ }
1096
+ value += String.fromCodePoint(scalar);
1097
+ index = close;
1098
+ }
1099
+ return { value, canonical };
1100
+ }
1101
+ function encodePayload(payload) {
1102
+ if (!PAYLOAD_REQUIRES_ESCAPING.test(payload))
1103
+ return payload;
1104
+ let encoded = '';
1105
+ for (const character of payload) {
1106
+ const scalar = character.codePointAt(0);
1107
+ if (scalar >= 0xd800 && scalar <= 0xdfff) {
1108
+ throw new TypeError('Telex payloads must contain Unicode scalar values');
1109
+ }
1110
+ switch (character) {
1111
+ case '\\':
1112
+ encoded += '\\\\';
1113
+ break;
1114
+ case '\n':
1115
+ encoded += '\\n';
1116
+ break;
1117
+ case '\r':
1118
+ encoded += '\\r';
1119
+ break;
1120
+ case '\t':
1121
+ encoded += '\\t';
1122
+ break;
1123
+ case '\0':
1124
+ encoded += '\\0';
1125
+ break;
1126
+ default:
1127
+ encoded += scalar < 0x20 || scalar === 0x7f
1128
+ ? `\\u{${scalar.toString(16).toUpperCase()}}`
1129
+ : character;
1130
+ }
1131
+ }
1132
+ return encoded;
1133
+ }
1134
+ //# sourceMappingURL=telex-internal.js.map