@orkestrel/table 0.0.12 → 0.0.13
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/src/browser/index.d.ts +2 -0
- package/dist/src/browser/index.js +31 -29
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +115 -89
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +4 -0
- package/dist/src/core/index.d.ts +4 -0
- package/dist/src/core/index.js +116 -90
- package/dist/src/core/index.js.map +1 -1
- package/package.json +16 -16
package/dist/src/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { arrayOf, attempt, cloneJSONRecord, isArray, isBoolean, isBoundedJSONRecord, isContractError, isFiniteNumber,
|
|
1
|
+
import { GUARD_DEPTH_LIMIT, INTRINSICS, arrayOf, attempt, cloneJSONRecord, holds, isArray, isBoolean, isBoundedJSONRecord, isContractError, isFiniteNumber, isRecord, isString, parseNumber, readArrayEntries, recordOf, unionOf } from "@orkestrel/contract";
|
|
2
2
|
import { Emitter } from "@orkestrel/emitter";
|
|
3
|
-
|
|
3
|
+
// src/core/constants.ts
|
|
4
4
|
/** Lists every column cell, in the order declared by the public contract. */
|
|
5
5
|
var COLUMN_CELLS = Object.freeze([
|
|
6
6
|
"text",
|
|
@@ -22,8 +22,7 @@ var STRING_LIMIT = 65536;
|
|
|
22
22
|
var TEXT_LIMIT = 1048576;
|
|
23
23
|
/** Names the maximum total number of records, arrays, and leaves one schema retains: 16384. */
|
|
24
24
|
var NODE_LIMIT = 16384;
|
|
25
|
-
|
|
26
|
-
//#region src/core/errors.ts
|
|
25
|
+
// src/core/errors.ts
|
|
27
26
|
/**
|
|
28
27
|
* Represents an error raised by the table domain — a machine-readable `code` and optional
|
|
29
28
|
* structured `context`.
|
|
@@ -42,6 +41,7 @@ var TableError = class extends Error {
|
|
|
42
41
|
*/
|
|
43
42
|
constructor(code, message, context) {
|
|
44
43
|
super(message);
|
|
44
|
+
INTRINSICS.define(this, Symbol.for("@orkestrel/table.TableError"), { value: this });
|
|
45
45
|
this.name = "TableError";
|
|
46
46
|
this.code = code;
|
|
47
47
|
if (context !== void 0) this.context = context;
|
|
@@ -51,14 +51,45 @@ var TableError = class extends Error {
|
|
|
51
51
|
* Determines whether a caught value is a table error, so a `catch` branches on `code` without an
|
|
52
52
|
* assertion.
|
|
53
53
|
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Recognizes self-branded errors across copies and subclasses and refuses transparent proxies.
|
|
56
|
+
* Validates the declared code and optional JSON context.
|
|
57
|
+
*
|
|
54
58
|
* @param input - The value to inspect.
|
|
55
59
|
* @returns True if the value is a {@link TableError} instance; false otherwise.
|
|
56
60
|
*/
|
|
57
61
|
function isTableError(input) {
|
|
58
|
-
return
|
|
62
|
+
return holds(() => {
|
|
63
|
+
if (!(input instanceof Error) || INTRINSICS.prototype(input) === Error.prototype) return false;
|
|
64
|
+
if (INTRINSICS.describe(input, Symbol.for("@orkestrel/table.TableError"))?.value !== input) return false;
|
|
65
|
+
const code = INTRINSICS.reflect.read(input, "code");
|
|
66
|
+
const context = INTRINSICS.reflect.read(input, "context");
|
|
67
|
+
if (code !== "SCHEMA" && code !== "COLUMN" && code !== "KEY" && code !== "CELL" && code !== "DESTROYED") return false;
|
|
68
|
+
if (context === void 0) return true;
|
|
69
|
+
if (!isRecord(context)) return false;
|
|
70
|
+
const pending = [{
|
|
71
|
+
value: context,
|
|
72
|
+
ancestors: []
|
|
73
|
+
}];
|
|
74
|
+
for (const { value, ancestors } of pending) {
|
|
75
|
+
if (value === null || [
|
|
76
|
+
"string",
|
|
77
|
+
"number",
|
|
78
|
+
"boolean"
|
|
79
|
+
].includes(typeof value)) continue;
|
|
80
|
+
if (!Array.isArray(value) && !isRecord(value)) return false;
|
|
81
|
+
if (ancestors.length >= GUARD_DEPTH_LIMIT || ancestors.includes(value)) return false;
|
|
82
|
+
const path = [...ancestors, value];
|
|
83
|
+
const children = Array.isArray(value) ? Array.from({ length: value.length }, (_, index) => value[index]) : Object.values(value);
|
|
84
|
+
for (const child of children) pending.push({
|
|
85
|
+
value: child,
|
|
86
|
+
ancestors: path
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
59
91
|
}
|
|
60
|
-
|
|
61
|
-
//#region src/core/helpers.ts
|
|
92
|
+
// src/core/helpers.ts
|
|
62
93
|
/**
|
|
63
94
|
* Finds one column by key; `undefined` when the schema declares no such column.
|
|
64
95
|
*
|
|
@@ -281,88 +312,102 @@ function sortRows(schema, rows, orders, comparators) {
|
|
|
281
312
|
*/
|
|
282
313
|
function auditTable(schema) {
|
|
283
314
|
const faults = [];
|
|
284
|
-
const columns =
|
|
285
|
-
|
|
286
|
-
let nameExceeded = schema.name !== void 0 && schema.name.length > 128;
|
|
315
|
+
const columns = [];
|
|
316
|
+
const longName = schema.name !== void 0 && schema.name.length > 128;
|
|
287
317
|
if (schema.columns.length > 256) faults.push(`schema declares more than 256 columns`);
|
|
288
318
|
const columnCount = Math.min(schema.columns.length, 257);
|
|
289
|
-
|
|
319
|
+
const indices = Array.from({ length: columnCount }, (_, index) => index);
|
|
320
|
+
const [choiceExceeded, nameExceeded] = indices.reduce(([choice, name], index) => {
|
|
290
321
|
const column = schema.columns[index];
|
|
291
|
-
if (column === void 0)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
322
|
+
if (column === void 0) return [choice, name];
|
|
323
|
+
const exceeded = column.key.length > 128;
|
|
324
|
+
return [choice === void 0 && column.cell === "choice" && column.choices.length > 1024 ? column.key : choice, name || exceeded];
|
|
325
|
+
}, [void 0, longName]);
|
|
295
326
|
if (choiceExceeded !== void 0) faults.push(`column "${choiceExceeded}" offers more than ${CHOICE_LIMIT} choices`);
|
|
296
327
|
if (nameExceeded) faults.push(`schema contains a name longer than 128`);
|
|
297
328
|
const pending = [schema];
|
|
298
329
|
const metadata = [false];
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
let textExceeded = false;
|
|
302
|
-
let nodeExceeded = false;
|
|
303
|
-
let text = 0;
|
|
304
|
-
while (position < pending.length) {
|
|
305
|
-
const node = pending[position];
|
|
330
|
+
const measurements = [];
|
|
331
|
+
for (const [position, node] of pending.entries()) {
|
|
306
332
|
const inMeta = metadata[position] === true;
|
|
307
|
-
position += 1;
|
|
308
333
|
if (isString(node)) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
334
|
+
measurements.push([
|
|
335
|
+
node.length,
|
|
336
|
+
node.length > STRING_LIMIT,
|
|
337
|
+
false
|
|
338
|
+
]);
|
|
312
339
|
continue;
|
|
313
340
|
}
|
|
314
341
|
if (isArray(node)) {
|
|
315
342
|
const read = readArrayEntries(node);
|
|
316
343
|
if (!read.success || !read.value.dense) continue;
|
|
317
|
-
|
|
318
|
-
if (pending.length >= 16384)
|
|
319
|
-
nodeExceeded = true;
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
344
|
+
const exceeded = read.value.entries.reduce((refused, entry) => {
|
|
345
|
+
if (pending.length >= 16384) return true;
|
|
322
346
|
pending.push(entry);
|
|
323
347
|
metadata.push(inMeta);
|
|
324
|
-
|
|
348
|
+
return refused;
|
|
349
|
+
}, false);
|
|
350
|
+
measurements.push([
|
|
351
|
+
0,
|
|
352
|
+
false,
|
|
353
|
+
exceeded
|
|
354
|
+
]);
|
|
325
355
|
continue;
|
|
326
356
|
}
|
|
327
357
|
if (!isRecord(node)) continue;
|
|
328
358
|
const keys = attempt(() => Object.keys(node));
|
|
329
359
|
if (!keys.success) continue;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
text = Math.min(TEXT_LIMIT + 1, text + key.length);
|
|
334
|
-
if (text > 1048576) textExceeded = true;
|
|
335
|
-
}
|
|
360
|
+
const measured = keys.value.reduce(([text, string, refused], key) => {
|
|
361
|
+
const length = text + (inMeta ? key.length : 0);
|
|
362
|
+
const oversized = string || inMeta && key.length > 65536;
|
|
336
363
|
const value = attempt(() => node[key]);
|
|
337
|
-
if (!value.success || value.value === void 0)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
364
|
+
if (!value.success || value.value === void 0) return [
|
|
365
|
+
length,
|
|
366
|
+
oversized,
|
|
367
|
+
refused
|
|
368
|
+
];
|
|
369
|
+
if (pending.length >= 16384) return [
|
|
370
|
+
length,
|
|
371
|
+
oversized,
|
|
372
|
+
true
|
|
373
|
+
];
|
|
342
374
|
pending.push(value.value);
|
|
343
375
|
metadata.push(inMeta || key === "meta");
|
|
344
|
-
|
|
345
|
-
|
|
376
|
+
return [
|
|
377
|
+
length,
|
|
378
|
+
oversized,
|
|
379
|
+
refused
|
|
380
|
+
];
|
|
381
|
+
}, [
|
|
382
|
+
0,
|
|
383
|
+
false,
|
|
384
|
+
false
|
|
385
|
+
]);
|
|
386
|
+
measurements.push(measured);
|
|
387
|
+
}
|
|
388
|
+
const stringExceeded = measurements.some((entry) => entry[1]);
|
|
389
|
+
const textExceeded = measurements.reduce((total, entry) => total + entry[0], 0) > TEXT_LIMIT;
|
|
390
|
+
const nodeExceeded = measurements.some((entry) => entry[2]);
|
|
346
391
|
if (stringExceeded) faults.push(`schema contains a string longer than ${STRING_LIMIT}`);
|
|
347
392
|
if (textExceeded) faults.push(`schema retains more than ${TEXT_LIMIT} string code units`);
|
|
348
393
|
if (nodeExceeded) faults.push(`schema retains more than ${NODE_LIMIT} nodes`);
|
|
349
|
-
for (
|
|
394
|
+
for (const index of indices) {
|
|
350
395
|
const column = schema.columns[index];
|
|
351
396
|
if (column === void 0) continue;
|
|
352
397
|
if (!nodeExceeded && column.meta !== void 0) {
|
|
353
398
|
if (!attempt(() => cloneJSONRecord(column.meta)).success) faults.push(`column "${column.key}" has metadata that cannot be owned`);
|
|
354
399
|
}
|
|
355
400
|
if (column.key.length === 0) faults.push("column \"\" has an empty key");
|
|
356
|
-
if (columns.
|
|
357
|
-
columns.
|
|
401
|
+
if (columns.includes(column.key)) faults.push(`column "${column.key}" is declared more than once`);
|
|
402
|
+
columns.push(column.key);
|
|
358
403
|
if (column.cell === "choice") {
|
|
359
|
-
const choices =
|
|
404
|
+
const choices = [];
|
|
360
405
|
const choiceCount = Math.min(column.choices.length, CHOICE_LIMIT + 1);
|
|
361
|
-
for (
|
|
406
|
+
for (const choiceIndex of Array.from({ length: choiceCount }, (_, position) => position)) {
|
|
362
407
|
const choice = column.choices[choiceIndex];
|
|
363
408
|
if (choice === void 0) continue;
|
|
364
|
-
if (choices.
|
|
365
|
-
choices.
|
|
409
|
+
if (choices.includes(choice.value)) faults.push(`column "${column.key}" offers choice "${choice.value}" more than once`);
|
|
410
|
+
choices.push(choice.value);
|
|
366
411
|
}
|
|
367
412
|
if (column.choices.length === 0) faults.push(`column "${column.key}" offers no choices`);
|
|
368
413
|
}
|
|
@@ -447,8 +492,7 @@ function serializeRows(schema, rows) {
|
|
|
447
492
|
function matchesTableRow(left, right) {
|
|
448
493
|
return left !== void 0 && Object.keys(left).length === Object.keys(right).length && Object.keys(right).every((key) => Object.hasOwn(left, key) && left[key] === right[key]);
|
|
449
494
|
}
|
|
450
|
-
|
|
451
|
-
//#region src/core/validators.ts
|
|
495
|
+
// src/core/validators.ts
|
|
452
496
|
/**
|
|
453
497
|
* Determines whether an unknown value has a table cell shape — a string, a finite number, or a
|
|
454
498
|
* boolean.
|
|
@@ -579,8 +623,7 @@ function isTableSchema(input) {
|
|
|
579
623
|
const outcome = attempt(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
|
|
580
624
|
return outcome.success && outcome.value;
|
|
581
625
|
}
|
|
582
|
-
|
|
583
|
-
//#region src/core/cloners.ts
|
|
626
|
+
// src/core/cloners.ts
|
|
584
627
|
/**
|
|
585
628
|
* Clones one row into an owned frozen snapshot.
|
|
586
629
|
*
|
|
@@ -601,13 +644,12 @@ function cloneSchema(schema) {
|
|
|
601
644
|
return Object.freeze({
|
|
602
645
|
...schema,
|
|
603
646
|
columns: Object.freeze(schema.columns.map((column) => {
|
|
604
|
-
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
} catch (error) {
|
|
608
|
-
if (!isContractError(error)) throw error;
|
|
647
|
+
const owned = column.meta === void 0 ? void 0 : attempt(() => cloneJSONRecord(column.meta));
|
|
648
|
+
if (owned !== void 0 && !owned.success) {
|
|
649
|
+
if (!isContractError(owned.error)) throw owned.error;
|
|
609
650
|
throw new TableError("SCHEMA", `column "${column.key}" has metadata that cannot be owned`, { column: column.key });
|
|
610
651
|
}
|
|
652
|
+
const meta = owned === void 0 ? {} : { meta: owned.value };
|
|
611
653
|
if (column.cell === "choice") return Object.freeze({
|
|
612
654
|
...column,
|
|
613
655
|
...meta,
|
|
@@ -620,8 +662,7 @@ function cloneSchema(schema) {
|
|
|
620
662
|
}))
|
|
621
663
|
});
|
|
622
664
|
}
|
|
623
|
-
|
|
624
|
-
//#region src/core/parsers.ts
|
|
665
|
+
// src/core/parsers.ts
|
|
625
666
|
/**
|
|
626
667
|
* Parses unknown wire data into an owned, structurally valid, semantically sound table schema.
|
|
627
668
|
*
|
|
@@ -659,12 +700,8 @@ function parseRows(schema, input) {
|
|
|
659
700
|
const column = extractColumn(schema, key);
|
|
660
701
|
if (column === void 0) return void 0;
|
|
661
702
|
const inputCell = candidate[key];
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (inputCell.length > 65536) return void 0;
|
|
665
|
-
cell = parseNumber(inputCell);
|
|
666
|
-
} else if (column.cell === "flag" && inputCell === "true") cell = true;
|
|
667
|
-
else if (column.cell === "flag" && inputCell === "false") cell = false;
|
|
703
|
+
if (column.cell === "number" && isString(inputCell) && inputCell.length > 65536) return void 0;
|
|
704
|
+
const cell = column.cell === "number" && isString(inputCell) ? parseNumber(inputCell) : column.cell === "flag" && inputCell === "true" ? true : column.cell === "flag" && inputCell === "false" ? false : inputCell;
|
|
668
705
|
if (!matchesCell(column, cell)) return void 0;
|
|
669
706
|
Object.defineProperty(row, key, {
|
|
670
707
|
value: cell,
|
|
@@ -683,8 +720,7 @@ function parseRows(schema, input) {
|
|
|
683
720
|
});
|
|
684
721
|
return outcome.success ? outcome.value : void 0;
|
|
685
722
|
}
|
|
686
|
-
|
|
687
|
-
//#region src/core/tables/KeyManager.ts
|
|
723
|
+
// src/core/tables/KeyManager.ts
|
|
688
724
|
/** Manages the key set one table axis holds, and the event it announces when that set moves. */
|
|
689
725
|
var KeyManager = class {
|
|
690
726
|
#emitter;
|
|
@@ -735,8 +771,7 @@ var KeyManager = class {
|
|
|
735
771
|
return input === void 0 ? void 0 : true;
|
|
736
772
|
}
|
|
737
773
|
};
|
|
738
|
-
|
|
739
|
-
//#region src/core/tables/ExpansionManager.ts
|
|
774
|
+
// src/core/tables/ExpansionManager.ts
|
|
740
775
|
/** Manages the keys of the rows somebody has opened. */
|
|
741
776
|
var ExpansionManager = class {
|
|
742
777
|
#keys;
|
|
@@ -769,8 +804,7 @@ var ExpansionManager = class {
|
|
|
769
804
|
return this.#keys.change(input, (included) => !included) === true;
|
|
770
805
|
}
|
|
771
806
|
};
|
|
772
|
-
|
|
773
|
-
//#region src/core/tables/FilterManager.ts
|
|
807
|
+
// src/core/tables/FilterManager.ts
|
|
774
808
|
/** Manages the filters one table applies with and-only composition. */
|
|
775
809
|
var FilterManager = class {
|
|
776
810
|
#schema;
|
|
@@ -844,8 +878,7 @@ var FilterManager = class {
|
|
|
844
878
|
return left.operator === "equals" && right.operator === "equals" && left.value === right.value;
|
|
845
879
|
}
|
|
846
880
|
};
|
|
847
|
-
|
|
848
|
-
//#region src/core/tables/PaginationManager.ts
|
|
881
|
+
// src/core/tables/PaginationManager.ts
|
|
849
882
|
/** Manages the page arithmetic over one table's filtered rows. */
|
|
850
883
|
var PaginationManager = class {
|
|
851
884
|
#emitter;
|
|
@@ -919,8 +952,7 @@ var PaginationManager = class {
|
|
|
919
952
|
return isFiniteNumber(value) ? Math.max(1, Math.trunc(value)) : 1;
|
|
920
953
|
}
|
|
921
954
|
};
|
|
922
|
-
|
|
923
|
-
//#region src/core/tables/RowManager.ts
|
|
955
|
+
// src/core/tables/RowManager.ts
|
|
924
956
|
/** Manages the rows one table holds in its own order. */
|
|
925
957
|
var RowManager = class {
|
|
926
958
|
#schema;
|
|
@@ -994,8 +1026,7 @@ var RowManager = class {
|
|
|
994
1026
|
}
|
|
995
1027
|
const next = [...current];
|
|
996
1028
|
const moved = [];
|
|
997
|
-
for (
|
|
998
|
-
const update = updates[index];
|
|
1029
|
+
for (const [index, update] of updates.entries()) {
|
|
999
1030
|
const location = locations[index];
|
|
1000
1031
|
if (update === void 0 || location === void 0) continue;
|
|
1001
1032
|
const previous = next[location];
|
|
@@ -1121,8 +1152,7 @@ var RowManager = class {
|
|
|
1121
1152
|
throw new TableError("KEY", message, { key });
|
|
1122
1153
|
}
|
|
1123
1154
|
};
|
|
1124
|
-
|
|
1125
|
-
//#region src/core/tables/SelectionManager.ts
|
|
1155
|
+
// src/core/tables/SelectionManager.ts
|
|
1126
1156
|
/** Manages the keys of the rows somebody has picked. */
|
|
1127
1157
|
var SelectionManager = class {
|
|
1128
1158
|
#keys;
|
|
@@ -1155,8 +1185,7 @@ var SelectionManager = class {
|
|
|
1155
1185
|
return this.#keys.change(input, (included) => !included) === true;
|
|
1156
1186
|
}
|
|
1157
1187
|
};
|
|
1158
|
-
|
|
1159
|
-
//#region src/core/tables/SortManager.ts
|
|
1188
|
+
// src/core/tables/SortManager.ts
|
|
1160
1189
|
/** Manages the ordered sort terms of one table. */
|
|
1161
1190
|
var SortManager = class {
|
|
1162
1191
|
#schema;
|
|
@@ -1215,8 +1244,7 @@ var SortManager = class {
|
|
|
1215
1244
|
if (extractColumn(this.#schema, column) === void 0) throw new TableError("COLUMN", `The schema declares no column named "${column}"`, { column });
|
|
1216
1245
|
}
|
|
1217
1246
|
};
|
|
1218
|
-
|
|
1219
|
-
//#region src/core/Table.ts
|
|
1247
|
+
// src/core/Table.ts
|
|
1220
1248
|
/**
|
|
1221
1249
|
* Holds a schema, its rows, and the lens through which they are read, implementing
|
|
1222
1250
|
* {@link TableInterface} exactly.
|
|
@@ -1385,8 +1413,7 @@ var Table = class {
|
|
|
1385
1413
|
if (this.#destroyed) throw new TableError("DESTROYED", "The table was destroyed and cannot change");
|
|
1386
1414
|
}
|
|
1387
1415
|
};
|
|
1388
|
-
|
|
1389
|
-
//#region src/core/factories.ts
|
|
1416
|
+
// src/core/factories.ts
|
|
1390
1417
|
/**
|
|
1391
1418
|
* Opens a table against a schema. The schema is copied, and the copy is what the table declares.
|
|
1392
1419
|
*
|
|
@@ -1430,7 +1457,6 @@ var Table = class {
|
|
|
1430
1457
|
function createTable(schema, options) {
|
|
1431
1458
|
return new Table(schema, options);
|
|
1432
1459
|
}
|
|
1433
|
-
//#endregion
|
|
1434
1460
|
export { CHOICE_LIMIT, COLUMN_CELLS, COLUMN_LIMIT, NAME_LIMIT, NODE_LIMIT, STRING_LIMIT, TEXT_LIMIT, Table, TableError, admitsFilter, auditTable, cloneRow, cloneSchema, compareCells, computeKeys, createTable, extractColumn, extractKey, filterRows, isColumnCell, isColumnChoice, isStructuralTableSchema, isTableCell, isTableColumn, isTableError, isTableRow, isTableSchema, matchesCell, matchesFilter, matchesTableRow, matchesTerms, mergeTerms, parseRows, parseTable, removeTerms, serializeRows, serializeTable, sortRows };
|
|
1435
1461
|
|
|
1436
1462
|
//# sourceMappingURL=index.js.map
|