@orkestrel/table 0.0.3 → 0.0.5

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.
@@ -2,35 +2,40 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
4
  //#region src/core/constants.ts
5
- /** Every column cell, in the order declared by the public contract. */
5
+ /** Lists every column cell, in the order declared by the public contract. */
6
6
  var COLUMN_CELLS = Object.freeze([
7
7
  "text",
8
8
  "number",
9
9
  "flag",
10
10
  "choice"
11
11
  ]);
12
- /** The maximum number of columns one schema may declare. */
12
+ /** Names the maximum number of columns one schema may declare: 256. */
13
13
  var COLUMN_LIMIT = 256;
14
- /** The maximum number of choices one `choice` column may offer. */
14
+ /** Names the maximum number of choices one `choice` column may offer: 1024. */
15
15
  var CHOICE_LIMIT = 1024;
16
- /** The maximum length, in UTF-16 code units, of a schema name or column key. */
16
+ /** Names the maximum length of a schema name or column key: 128 UTF-16 code units. */
17
17
  var NAME_LIMIT = 128;
18
- /** The maximum length, in UTF-16 code units, of any single retained string. */
18
+ /** Names the maximum length of any single retained string: 65536 UTF-16 code units. */
19
19
  var STRING_LIMIT = 65536;
20
- /** The maximum total length, in UTF-16 code units, of every string one schema retains. */
20
+ /**
21
+ * Names the maximum total length of every string one schema retains: 1048576 UTF-16 code units.
22
+ */
21
23
  var TEXT_LIMIT = 1048576;
22
- /** The maximum total number of records, arrays, and leaves one schema retains. */
24
+ /** Names the maximum total number of records, arrays, and leaves one schema retains: 16384. */
23
25
  var NODE_LIMIT = 16384;
24
26
  //#endregion
25
27
  //#region src/core/errors.ts
26
- /** An error raised by the table domain. */
28
+ /**
29
+ * Represents an error raised by the table domain — a machine-readable `code` and optional
30
+ * structured `context`.
31
+ */
27
32
  var TableError = class extends Error {
28
- /** The machine-readable reason for this failure. */
33
+ /** Holds the machine-readable reason for this failure. */
29
34
  code;
30
- /** Structured values that locate or explain this failure. */
35
+ /** Holds structured values that locate or explain this failure. */
31
36
  context;
32
37
  /**
33
- * Create a table error.
38
+ * Creates a table error.
34
39
  *
35
40
  * @param code - The machine-readable reason.
36
41
  * @param message - The human-readable failure text.
@@ -44,10 +49,11 @@ var TableError = class extends Error {
44
49
  }
45
50
  };
46
51
  /**
47
- * Determine whether an unknown value is a table error.
52
+ * Determines whether a caught value is a table error, so a `catch` branches on `code` without an
53
+ * assertion.
48
54
  *
49
55
  * @param input - The value to inspect.
50
- * @returns Whether the value is a {@link TableError} instance.
56
+ * @returns True if the value is a {@link TableError} instance; false otherwise.
51
57
  */
52
58
  function isTableError(input) {
53
59
  return input instanceof TableError;
@@ -55,7 +61,7 @@ function isTableError(input) {
55
61
  //#endregion
56
62
  //#region src/core/helpers.ts
57
63
  /**
58
- * Find one column by key.
64
+ * Finds one column by key; `undefined` when the schema declares no such column.
59
65
  *
60
66
  * @param schema - The schema whose columns to search.
61
67
  * @param key - The column key to find.
@@ -65,7 +71,8 @@ function extractColumn(schema, key) {
65
71
  return schema.columns.find((column) => column.key === key);
66
72
  }
67
73
  /**
68
- * Read one row's declared identity.
74
+ * Reads one row's declared identity; `undefined` when its key cell is missing, empty, or not a
75
+ * string.
69
76
  *
70
77
  * @param schema - The schema that names the identity column.
71
78
  * @param row - The row whose identity to read.
@@ -77,7 +84,8 @@ function extractKey(schema, row) {
77
84
  return (0, _orkestrel_contract.isString)(key) && key.length > 0 ? key : void 0;
78
85
  }
79
86
  /**
80
- * Compute one atomic 0/1/N membership change over known keys.
87
+ * Computes one atomic 0/1/N membership change over the keys a caller may address — the engine
88
+ * selection and expansion share.
81
89
  *
82
90
  * @param known - Every key the caller may change.
83
91
  * @param current - The current key set.
@@ -96,11 +104,59 @@ function computeKeys(known, current, input, include) {
96
104
  return next.size !== current.size || [...next].some((key) => !current.has(key)) ? next : current;
97
105
  }
98
106
  /**
99
- * Check whether a value has the shape required by one column cell.
107
+ * Merges lens terms into a column-keyed list, replacing the entry that names the same column
108
+ * the `set` write.
109
+ *
110
+ * @param current - The list as it stands.
111
+ * @param requested - The terms to write, in the order they are written.
112
+ * @returns A frozen list holding one owned entry per column, in the order the columns first
113
+ * appeared.
114
+ */
115
+ function mergeTerms(current, requested) {
116
+ const next = [...current];
117
+ for (const term of requested) {
118
+ const owned = Object.freeze({ ...term });
119
+ const index = next.findIndex((candidate) => candidate.column === term.column);
120
+ if (index === -1) next.push(owned);
121
+ else next[index] = owned;
122
+ }
123
+ return Object.freeze(next);
124
+ }
125
+ /**
126
+ * Removes every lens term naming one of the given columns — the drop `sort.remove` and
127
+ * `filter.remove` share.
128
+ *
129
+ * @param current - The list as it stands.
130
+ * @param columns - The column keys to drop.
131
+ * @returns A frozen list holding the entries no named column matched, in their original order.
132
+ */
133
+ function removeTerms(current, columns) {
134
+ const removed = new Set(columns);
135
+ return Object.freeze(current.filter((term) => !removed.has(term.column)));
136
+ }
137
+ /**
138
+ * Checks whether two lens lists hold the same terms in the same order, with the supplied test
139
+ * deciding the operands.
140
+ *
141
+ * @param left - The first list.
142
+ * @param right - The second list.
143
+ * @param equal - Decide whether two terms naming one column carry the same operands.
144
+ * @returns True if the lists are the same length and every position names the same column and
145
+ * carries the same operands; false otherwise.
146
+ */
147
+ function matchesTerms(left, right, equal) {
148
+ return left.length === right.length && left.every((term, index) => {
149
+ const other = right[index];
150
+ return other !== void 0 && term.column === other.column && equal(term, other);
151
+ });
152
+ }
153
+ /**
154
+ * Checks whether one column can hold a value — the shape gate every write and every seed passes
155
+ * through.
100
156
  *
101
157
  * @param column - The column that owns the cell.
102
158
  * @param value - The unknown value to inspect.
103
- * @returns Whether the column can hold the value.
159
+ * @returns True if the column can hold the value; false otherwise.
104
160
  */
105
161
  function matchesCell(column, value) {
106
162
  if ((0, _orkestrel_contract.isString)(value) && value.length > 65536) return false;
@@ -112,7 +168,7 @@ function matchesCell(column, value) {
112
168
  }
113
169
  }
114
170
  /**
115
- * Compare two cells in ascending order according to one column.
171
+ * Compares two of one column's cells the way its `cell` fixes, describing ascending order.
116
172
  *
117
173
  * @param column - The column that fixes the comparison.
118
174
  * @param left - The first cell, or absence.
@@ -138,11 +194,12 @@ function compareCells(column, left, right) {
138
194
  }
139
195
  }
140
196
  /**
141
- * Check whether one column admits a filter and all its operands.
197
+ * Checks whether one column admits a filter and every operand it carries — the gate `filter.set`
198
+ * and {@link matchesFilter} share.
142
199
  *
143
200
  * @param column - The column that fixes the accepted operators and cell shapes.
144
201
  * @param filter - The filter to inspect.
145
- * @returns Whether the filter belongs to the column and the column can apply it.
202
+ * @returns True if the filter belongs to the column and the column can apply it; false otherwise.
146
203
  */
147
204
  function admitsFilter(column, filter) {
148
205
  if (filter.column !== column.key) return false;
@@ -153,12 +210,12 @@ function admitsFilter(column, filter) {
153
210
  }
154
211
  }
155
212
  /**
156
- * Test one cell against a filter according to its column.
213
+ * Tests one of a column's cells against one filter the way its `cell` fixes.
157
214
  *
158
215
  * @param column - The column that fixes the accepted operators.
159
216
  * @param cell - The cell to test, or absence.
160
217
  * @param filter - The filter to apply.
161
- * @returns Whether the filter accepts the cell.
218
+ * @returns True if the filter accepts the cell; false otherwise.
162
219
  */
163
220
  function matchesFilter(column, cell, filter) {
164
221
  if (cell === void 0 || !admitsFilter(column, filter) || !matchesCell(column, cell)) return false;
@@ -169,7 +226,8 @@ function matchesFilter(column, cell, filter) {
169
226
  }
170
227
  }
171
228
  /**
172
- * Keep the rows accepted by every filter.
229
+ * Keeps the rows every filter accepts, in the order given; a supplied {@link CellMatcher}
230
+ * replaces the default per column.
173
231
  *
174
232
  * @param schema - The schema that declares the filtered columns.
175
233
  * @param rows - The rows to filter.
@@ -187,7 +245,8 @@ function filterRows(schema, rows, filters, matchers) {
187
245
  })));
188
246
  }
189
247
  /**
190
- * Order rows stably by a sequence of terms.
248
+ * Orders rows by the terms given, stably; a supplied {@link CellComparator} replaces the default
249
+ * per column.
191
250
  *
192
251
  * @param schema - The schema that declares the sorted columns.
193
252
  * @param rows - The rows to order.
@@ -215,7 +274,8 @@ function sortRows(schema, rows, orders, comparators) {
215
274
  return Object.freeze(indexed.map((entry) => entry.row));
216
275
  }
217
276
  /**
218
- * Audit a structurally valid schema for domain and budget faults.
277
+ * Audits a structurally valid schema for domain faults and budget breaches, returning human
278
+ * diagnostics.
219
279
  *
220
280
  * @param schema - The table schema to audit.
221
281
  * @returns Frozen human-readable diagnostics, or an empty list when the schema is sound.
@@ -314,7 +374,8 @@ function auditTable(schema) {
314
374
  return Object.freeze(faults);
315
375
  }
316
376
  /**
317
- * Project a schema into declaration-ordered JSON.
377
+ * Projects a schema into JSON in declaration order, dropping every absent member; raises `SCHEMA`
378
+ * for a `meta` it cannot own.
318
379
  *
319
380
  * @param schema - The schema to project.
320
381
  * @returns A deeply owned JSON record with absent members omitted.
@@ -353,7 +414,8 @@ function serializeTable(schema) {
353
414
  }
354
415
  }
355
416
  /**
356
- * Project rows into schema-column-ordered JSON.
417
+ * Projects rows into JSON with each row's cells in the schema's column order, dropping every
418
+ * absent cell.
357
419
  *
358
420
  * @param schema - The schema that fixes cell order.
359
421
  * @param rows - The rows to project.
@@ -381,19 +443,21 @@ function serializeRows(schema, rows) {
381
443
  //#endregion
382
444
  //#region src/core/validators.ts
383
445
  /**
384
- * Determine whether an unknown value has a table cell shape.
446
+ * Determines whether an unknown value has a table cell shape — a string, a finite number, or a
447
+ * boolean.
385
448
  *
386
449
  * @param input - The value to inspect.
387
- * @returns Whether the value is a string, finite number, or boolean.
450
+ * @returns True if the value is a string, finite number, or boolean; false otherwise.
388
451
  */
389
452
  function isTableCell(input) {
390
453
  return (0, _orkestrel_contract.unionOf)(_orkestrel_contract.isString, _orkestrel_contract.isFiniteNumber, _orkestrel_contract.isBoolean)(input);
391
454
  }
392
455
  /**
393
- * Determine whether an unknown value is a record of table cells.
456
+ * Determines whether an unknown value is a record whose every own key is a string and every value
457
+ * a {@link TableCell}.
394
458
  *
395
459
  * @param input - The value to inspect.
396
- * @returns Whether every own key is a string and every value is a table cell.
460
+ * @returns True if every own key is a string and every value is a table cell; false otherwise.
397
461
  */
398
462
  function isTableRow(input) {
399
463
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -403,19 +467,20 @@ function isTableRow(input) {
403
467
  return outcome.success && outcome.value;
404
468
  }
405
469
  /**
406
- * Determine whether an unknown value is a declared column cell.
470
+ * Determines whether an unknown value is a declared column cell.
407
471
  *
408
472
  * @param input - The value to inspect.
409
- * @returns Whether the value is one of the four column cells.
473
+ * @returns True if the value is a declared column cell; false otherwise.
410
474
  */
411
475
  function isColumnCell(input) {
412
476
  return COLUMN_CELLS.some((cell) => cell === input);
413
477
  }
414
478
  /**
415
- * Determine whether an unknown value is one exact column choice record.
479
+ * Determines whether an unknown value is one exact {@link ColumnChoice} record; an unknown member
480
+ * refuses it.
416
481
  *
417
482
  * @param input - The value to inspect.
418
- * @returns Whether the value is a column choice.
483
+ * @returns True if the value is a column choice; false otherwise.
419
484
  */
420
485
  function isColumnChoice(input) {
421
486
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -429,10 +494,11 @@ function isColumnChoice(input) {
429
494
  return outcome.success && outcome.value;
430
495
  }
431
496
  /**
432
- * Determine whether an unknown value is one exact discriminated table column.
497
+ * Determines whether an unknown value is one exact discriminated {@link TableColumn}, checked
498
+ * against its cell's own options.
433
499
  *
434
500
  * @param input - The value to inspect.
435
- * @returns Whether the value is a structurally valid table column.
501
+ * @returns True if the value is a structurally valid table column; false otherwise.
436
502
  */
437
503
  function isTableColumn(input) {
438
504
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -471,10 +537,11 @@ function isTableColumn(input) {
471
537
  return outcome.success && outcome.value;
472
538
  }
473
539
  /**
474
- * Determine whether an unknown value has one exact structural table-schema shape.
540
+ * Determines whether an unknown value has the exact shape of a {@link TableSchema} — the shape
541
+ * alone, with no domain check.
475
542
  *
476
543
  * @param input - The value to inspect.
477
- * @returns Whether the value has the exact structure of a table schema.
544
+ * @returns True if the value has the exact structure of a table schema; false otherwise.
478
545
  */
479
546
  function isStructuralTableSchema(input) {
480
547
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -494,10 +561,12 @@ function isStructuralTableSchema(input) {
494
561
  return outcome.success && outcome.value;
495
562
  }
496
563
  /**
497
- * Determine whether an unknown value is one semantically sound table schema.
564
+ * Determines whether an unknown value is a {@link TableSchema} a table can be opened against —
565
+ * the exact shape, and an audit that finds nothing.
498
566
  *
499
567
  * @param input - The value to inspect.
500
- * @returns Whether the value has valid structure, domain relationships, and budgets.
568
+ * @returns True if the value has valid structure, domain relationships, and
569
+ * budgets; false otherwise.
501
570
  */
502
571
  function isTableSchema(input) {
503
572
  const outcome = (0, _orkestrel_contract.attempt)(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
@@ -506,7 +575,7 @@ function isTableSchema(input) {
506
575
  //#endregion
507
576
  //#region src/core/cloners.ts
508
577
  /**
509
- * Clone one row into an owned frozen snapshot.
578
+ * Clones one row into an owned frozen snapshot.
510
579
  *
511
580
  * @param row - The row to own.
512
581
  * @returns A frozen copy of the row's cells.
@@ -515,7 +584,8 @@ function cloneRow(row) {
515
584
  return Object.freeze({ ...row });
516
585
  }
517
586
  /**
518
- * Clone a table schema into an owned frozen snapshot.
587
+ * Clones a whole schema into an owned frozen snapshot, freezing every nested column, choice list,
588
+ * choice, and `meta`; raises `SCHEMA` for a `meta` it cannot own.
519
589
  *
520
590
  * @param schema - The schema to own.
521
591
  * @returns A frozen schema with every nested column, choice, list, and metadata record owned.
@@ -546,7 +616,7 @@ function cloneSchema(schema) {
546
616
  //#endregion
547
617
  //#region src/core/parsers.ts
548
618
  /**
549
- * Parse unknown wire data into an owned, semantically sound table schema.
619
+ * Parses unknown wire data into an owned, structurally valid, semantically sound table schema.
550
620
  *
551
621
  * @param input - The unknown schema value to parse.
552
622
  * @returns An owned table schema, or `undefined` on refusal.
@@ -560,7 +630,8 @@ function parseTable(input) {
560
630
  return outcome.success ? outcome.value : void 0;
561
631
  }
562
632
  /**
563
- * Parse unknown wire rows against one table schema.
633
+ * Parses unknown wire data into owned rows against one table schema, coercing a numeric string
634
+ * and `'true'` / `'false'`.
564
635
  *
565
636
  * @param schema - The schema that declares the accepted keys and cell shapes.
566
637
  * @param input - The unknown row-list value to parse.
@@ -606,63 +677,94 @@ function parseRows(schema, input) {
606
677
  return outcome.success ? outcome.value : void 0;
607
678
  }
608
679
  //#endregion
609
- //#region src/core/tables/ExpansionManager.ts
610
- /** The keys of the rows somebody has opened. */
611
- var ExpansionManager = class {
680
+ //#region src/core/tables/KeyManager.ts
681
+ /** Manages the key set one table axis holds, and the event it announces when that set moves. */
682
+ var KeyManager = class {
612
683
  #emitter;
684
+ #event;
613
685
  #gate;
614
686
  #rows;
615
687
  #read;
616
688
  #write;
617
689
  /**
618
- * Create an expansion manager over one table's private stores.
690
+ * Creates a key-set shell over one table's private store.
619
691
  *
620
692
  * @param emitter - The table's event emitter.
693
+ * @param event - The event this axis announces when its key set moves.
621
694
  * @param gate - The table lifecycle gate.
622
695
  * @param rows - A read of every row key.
623
- * @param read - A read of the expanded keys.
624
- * @param write - The expanded-key commit boundary.
696
+ * @param read - A read of the held keys.
697
+ * @param write - The held-key commit boundary.
625
698
  */
626
- constructor(emitter, gate, rows, read, write) {
699
+ constructor(emitter, event, gate, rows, read, write) {
627
700
  this.#emitter = emitter;
701
+ this.#event = event;
628
702
  this.#gate = gate;
629
703
  this.#rows = rows;
630
704
  this.#read = read;
631
705
  this.#write = write;
632
706
  }
633
- /** The keys of the rows opened right now. */
707
+ /** Returns the keys held right now. */
634
708
  get keys() {
635
709
  return new Set(this.#read());
636
710
  }
637
- /** Open one or more rows. */
638
- expand(input) {
639
- this.#gate();
640
- return this.#change(input, () => true);
641
- }
642
- /** Close one or more rows. */
643
- clear(input) {
644
- this.#gate();
645
- return this.#change(input, () => false);
646
- }
647
- /** Turn one or more rows around independently. */
648
- toggle(input) {
711
+ /**
712
+ * Applies one atomic 0/1/N membership change, announcing it only when the set moves.
713
+ *
714
+ * @param input - Every known key, one key, or a key list.
715
+ * @param include - Decides the next membership from each key's membership at that step.
716
+ * @returns `undefined` for the no-argument form, `false` when any requested key names no row
717
+ * the table holds, and `true` otherwise.
718
+ */
719
+ change(input, include) {
649
720
  this.#gate();
650
- return this.#change(input, (included) => !included) === true;
651
- }
652
- #change(input, include) {
653
721
  const previous = this.#read();
654
722
  const next = computeKeys(this.#rows(), previous, input, include);
655
723
  if (next === void 0) return false;
656
724
  if (next !== previous) {
657
725
  this.#write(next);
658
- this.#emitter.emit("expand", new Set(next));
726
+ this.#emitter.emit(this.#event, new Set(next));
659
727
  }
660
728
  return input === void 0 ? void 0 : true;
661
729
  }
662
730
  };
663
731
  //#endregion
732
+ //#region src/core/tables/ExpansionManager.ts
733
+ /** Manages the keys of the rows somebody has opened. */
734
+ var ExpansionManager = class {
735
+ #keys;
736
+ /**
737
+ * Creates an expansion manager over one table's private stores.
738
+ *
739
+ * @param emitter - The table's event emitter.
740
+ * @param gate - The table lifecycle gate.
741
+ * @param rows - A read of every row key.
742
+ * @param read - A read of the expanded keys.
743
+ * @param write - The expanded-key commit boundary.
744
+ */
745
+ constructor(emitter, gate, rows, read, write) {
746
+ this.#keys = new KeyManager(emitter, "expand", gate, rows, read, write);
747
+ }
748
+ /** Returns the keys of the rows opened right now. */
749
+ get keys() {
750
+ return this.#keys.keys;
751
+ }
752
+ /** Opens one or more rows. */
753
+ expand(input) {
754
+ return this.#keys.change(input, () => true);
755
+ }
756
+ /** Closes one or more rows. */
757
+ clear(input) {
758
+ return this.#keys.change(input, () => false);
759
+ }
760
+ /** Turns one or more rows around independently. */
761
+ toggle(input) {
762
+ return this.#keys.change(input, (included) => !included) === true;
763
+ }
764
+ };
765
+ //#endregion
664
766
  //#region src/core/tables/FilterManager.ts
665
- /** The filters one table applies with and-only composition. */
767
+ /** Manages the filters one table applies with and-only composition. */
666
768
  var FilterManager = class {
667
769
  #schema;
668
770
  #emitter;
@@ -671,7 +773,7 @@ var FilterManager = class {
671
773
  #write;
672
774
  #clamp;
673
775
  /**
674
- * Create a filter manager over one table's private filter store.
776
+ * Creates a filter manager over one table's private filter store.
675
777
  *
676
778
  * @param schema - The table schema.
677
779
  * @param emitter - The table's event emitter.
@@ -688,42 +790,35 @@ var FilterManager = class {
688
790
  this.#write = write;
689
791
  this.#clamp = clamp;
690
792
  }
691
- /** Find one column's filter. */
793
+ /** Finds one column's filter. */
692
794
  filter(column) {
693
795
  const filter = this.#read().find((candidate) => candidate.column === column);
694
796
  return filter === void 0 ? void 0 : Object.freeze({ ...filter });
695
797
  }
696
- /** Read every filter as an owned frozen snapshot. */
798
+ /** Reads every filter as an owned frozen snapshot. */
697
799
  filters() {
698
800
  return Object.freeze(this.#read().map((filter) => Object.freeze({ ...filter })));
699
801
  }
700
- /** Filter one column or several. */
802
+ /** Filters one column or several. */
701
803
  set(input) {
702
804
  this.#gate();
703
805
  const requested = Array.isArray(input) ? input : [input];
704
806
  for (const filter of requested) this.#validate(filter);
705
- const next = [...this.#read()];
706
- for (const filter of requested) {
707
- const owned = Object.freeze({ ...filter });
708
- const index = next.findIndex((candidate) => candidate.column === filter.column);
709
- if (index === -1) next.push(owned);
710
- else next[index] = owned;
711
- }
712
- if (this.#same(next, this.#read())) return;
713
- this.#write(Object.freeze(next));
807
+ const next = mergeTerms(this.#read(), requested);
808
+ if (matchesTerms(next, this.#read(), (filter, other) => this.#operands(filter, other))) return;
809
+ this.#write(next);
714
810
  const page = this.#clamp();
715
811
  this.#emitter.emit("filter", this.filters());
716
812
  if (page !== void 0) this.#emitter.emit("paginate", page);
717
813
  }
718
- /** Stop filtering by one or more columns. */
814
+ /** Stops filtering by one or more columns. */
719
815
  remove(input) {
720
816
  this.#gate();
721
817
  const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
722
818
  for (const column of columns) if (extractColumn(this.#schema, column) === void 0) return false;
723
- const removed = new Set(columns);
724
- const next = this.#read().filter((filter) => !removed.has(filter.column));
819
+ const next = removeTerms(this.#read(), columns);
725
820
  if (next.length !== this.#read().length) {
726
- this.#write(Object.freeze(next));
821
+ this.#write(next);
727
822
  const page = this.#clamp();
728
823
  this.#emitter.emit("filter", this.filters());
729
824
  if (page !== void 0) this.#emitter.emit("paginate", page);
@@ -735,19 +830,16 @@ var FilterManager = class {
735
830
  if (column === void 0) throw new TableError("COLUMN", `The schema declares no column named "${filter.column}"`, { column: filter.column });
736
831
  if (!admitsFilter(column, filter)) throw new TableError("CELL", `Column "${filter.column}" cannot apply that filter`, { column: filter.column });
737
832
  }
738
- #same(left, right) {
739
- return left.length === right.length && left.every((filter, index) => {
740
- const other = right[index];
741
- if (other === void 0 || filter.column !== other.column || filter.operator !== other.operator) return false;
742
- if (filter.operator === "contains" && other.operator === "contains") return filter.text === other.text;
743
- if (filter.operator === "between" && other.operator === "between") return filter.minimum === other.minimum && filter.maximum === other.maximum;
744
- return filter.operator === "equals" && other.operator === "equals" && filter.value === other.value;
745
- });
833
+ #operands(left, right) {
834
+ if (left.operator !== right.operator) return false;
835
+ if (left.operator === "contains" && right.operator === "contains") return left.text === right.text;
836
+ if (left.operator === "between" && right.operator === "between") return left.minimum === right.minimum && left.maximum === right.maximum;
837
+ return left.operator === "equals" && right.operator === "equals" && left.value === right.value;
746
838
  }
747
839
  };
748
840
  //#endregion
749
841
  //#region src/core/tables/PaginationManager.ts
750
- /** The page arithmetic over one table's filtered rows. */
842
+ /** Manages the page arithmetic over one table's filtered rows. */
751
843
  var PaginationManager = class {
752
844
  #emitter;
753
845
  #gate;
@@ -757,7 +849,7 @@ var PaginationManager = class {
757
849
  #readLimit;
758
850
  #writeLimit;
759
851
  /**
760
- * Create a pagination manager over one table's private stores.
852
+ * Creates a pagination manager over one table's private stores.
761
853
  *
762
854
  * @param emitter - The table's event emitter.
763
855
  * @param gate - The table lifecycle gate.
@@ -778,33 +870,33 @@ var PaginationManager = class {
778
870
  const limit = this.#readLimit();
779
871
  if (limit !== void 0) this.#writeLimit(this.#normalize(limit));
780
872
  }
781
- /** The page shown, counted from one. */
873
+ /** Returns the page shown, counted from one. */
782
874
  get page() {
783
875
  return this.#readLimit() === void 0 ? 1 : this.#readPage();
784
876
  }
785
- /** The number of rows one page holds. */
877
+ /** Returns the number of rows one page holds. */
786
878
  get limit() {
787
879
  return this.#readLimit();
788
880
  }
789
- /** The number of filtered rows skipped before this page. */
881
+ /** Returns the number of filtered rows skipped before this page. */
790
882
  get offset() {
791
883
  const limit = this.#readLimit();
792
884
  return limit === void 0 ? 0 : (this.#readPage() - 1) * limit;
793
885
  }
794
- /** The number of pages filled by the filtered rows. */
886
+ /** Returns the number of pages filled by the filtered rows. */
795
887
  get count() {
796
888
  const limit = this.#readLimit();
797
889
  return limit === void 0 ? 1 : Math.max(1, Math.ceil(this.#rows() / limit));
798
890
  }
799
- /** Show another page, clamped to the pages that exist. */
891
+ /** Shows another page, clamped to the pages that exist. */
800
892
  move(page) {
801
893
  this.#gate();
802
- const next = this.#readLimit() === void 0 ? 1 : Math.min(this.count, this.#normalize(page));
894
+ const next = this.#readLimit() === void 0 || Number.isNaN(page) ? 1 : Math.min(this.count, Math.max(1, Math.trunc(page)));
803
895
  if (next === this.#readPage()) return;
804
896
  this.#writePage(next);
805
897
  this.#emitter.emit("paginate", next);
806
898
  }
807
- /** Change the page size while keeping the first row previously shown. */
899
+ /** Changes the page size while keeping the first row previously shown. */
808
900
  resize(limit) {
809
901
  this.#gate();
810
902
  const previous = this.#readLimit();
@@ -822,7 +914,7 @@ var PaginationManager = class {
822
914
  };
823
915
  //#endregion
824
916
  //#region src/core/tables/RowManager.ts
825
- /** The rows one table holds in its own order. */
917
+ /** Manages the rows one table holds in its own order. */
826
918
  var RowManager = class {
827
919
  #schema;
828
920
  #emitter;
@@ -831,7 +923,7 @@ var RowManager = class {
831
923
  #write;
832
924
  #settle;
833
925
  /**
834
- * Create a row manager over one table's private row store.
926
+ * Creates a row manager over one table's private row store.
835
927
  *
836
928
  * @param schema - The table schema.
837
929
  * @param emitter - The table's event emitter.
@@ -851,16 +943,16 @@ var RowManager = class {
851
943
  const seeded = this.#prepare(rows, /* @__PURE__ */ new Set());
852
944
  if (seeded.length > 0) this.#write(Object.freeze(seeded));
853
945
  }
854
- /** Find one row by key as an owned frozen snapshot. */
946
+ /** Finds one row by key as an owned frozen snapshot. */
855
947
  row(key) {
856
948
  const row = this.#read().find((candidate) => extractKey(this.#schema, candidate) === key);
857
949
  return row === void 0 ? void 0 : cloneRow(row);
858
950
  }
859
- /** Read every row as owned frozen snapshots in table order. */
951
+ /** Reads every row as owned frozen snapshots in table order. */
860
952
  rows() {
861
953
  return Object.freeze(this.#read().map((row) => cloneRow(row)));
862
954
  }
863
- /** Append one row or several. */
955
+ /** Appends one row or several. */
864
956
  add(input) {
865
957
  this.#gate();
866
958
  const rows = Array.isArray(input) ? input : [input];
@@ -879,7 +971,7 @@ var RowManager = class {
879
971
  }
880
972
  });
881
973
  }
882
- /** Merge one row or several into the rows their keys name. */
974
+ /** Merges one row or several into the rows their keys name. */
883
975
  update(input) {
884
976
  this.#gate();
885
977
  const updates = (Array.isArray(input) ? input : [input]).map((row) => cloneRow(row));
@@ -918,13 +1010,13 @@ var RowManager = class {
918
1010
  });
919
1011
  return true;
920
1012
  }
921
- /** Move one row to a clamped index in table order. */
1013
+ /** Moves one row to a clamped index in table order. */
922
1014
  move(key, index) {
923
1015
  this.#gate();
924
1016
  const current = this.#read();
925
1017
  const origin = current.findIndex((row) => extractKey(this.#schema, row) === key);
926
1018
  if (origin === -1) return false;
927
- const target = Math.min(current.length - 1, Number.isFinite(index) ? Math.max(0, Math.trunc(index)) : 0);
1019
+ const target = Math.min(current.length - 1, Number.isNaN(index) ? 0 : Math.max(0, Math.trunc(index)));
928
1020
  if (origin === target) return true;
929
1021
  const row = current[origin];
930
1022
  if (row === void 0) return false;
@@ -935,7 +1027,7 @@ var RowManager = class {
935
1027
  this.#settle([], () => this.#emitter.emit("write", key));
936
1028
  return true;
937
1029
  }
938
- /** Remove one or more rows. */
1030
+ /** Removes one or more rows. */
939
1031
  remove(input) {
940
1032
  this.#gate();
941
1033
  const current = this.#read();
@@ -996,15 +1088,11 @@ var RowManager = class {
996
1088
  };
997
1089
  //#endregion
998
1090
  //#region src/core/tables/SelectionManager.ts
999
- /** The keys of the rows somebody has picked. */
1091
+ /** Manages the keys of the rows somebody has picked. */
1000
1092
  var SelectionManager = class {
1001
- #emitter;
1002
- #gate;
1003
- #rows;
1004
- #read;
1005
- #write;
1093
+ #keys;
1006
1094
  /**
1007
- * Create a selection manager over one table's private stores.
1095
+ * Creates a selection manager over one table's private stores.
1008
1096
  *
1009
1097
  * @param emitter - The table's event emitter.
1010
1098
  * @param gate - The table lifecycle gate.
@@ -1013,45 +1101,28 @@ var SelectionManager = class {
1013
1101
  * @param write - The selected-key commit boundary.
1014
1102
  */
1015
1103
  constructor(emitter, gate, rows, read, write) {
1016
- this.#emitter = emitter;
1017
- this.#gate = gate;
1018
- this.#rows = rows;
1019
- this.#read = read;
1020
- this.#write = write;
1104
+ this.#keys = new KeyManager(emitter, "select", gate, rows, read, write);
1021
1105
  }
1022
- /** The keys of the rows picked right now. */
1106
+ /** Returns the keys of the rows picked right now. */
1023
1107
  get keys() {
1024
- return new Set(this.#read());
1108
+ return this.#keys.keys;
1025
1109
  }
1026
- /** Pick one or more rows. */
1110
+ /** Picks one or more rows. */
1027
1111
  select(input) {
1028
- this.#gate();
1029
- return this.#change(input, () => true);
1112
+ return this.#keys.change(input, () => true);
1030
1113
  }
1031
- /** Drop one or more picks. */
1114
+ /** Drops one or more picks. */
1032
1115
  clear(input) {
1033
- this.#gate();
1034
- return this.#change(input, () => false);
1116
+ return this.#keys.change(input, () => false);
1035
1117
  }
1036
- /** Turn one or more rows around independently. */
1118
+ /** Turns one or more rows around independently. */
1037
1119
  toggle(input) {
1038
- this.#gate();
1039
- return this.#change(input, (included) => !included) === true;
1040
- }
1041
- #change(input, include) {
1042
- const previous = this.#read();
1043
- const next = computeKeys(this.#rows(), previous, input, include);
1044
- if (next === void 0) return false;
1045
- if (next !== previous) {
1046
- this.#write(next);
1047
- this.#emitter.emit("select", new Set(next));
1048
- }
1049
- return input === void 0 ? void 0 : true;
1120
+ return this.#keys.change(input, (included) => !included) === true;
1050
1121
  }
1051
1122
  };
1052
1123
  //#endregion
1053
1124
  //#region src/core/tables/SortManager.ts
1054
- /** The ordered sort terms of one table. */
1125
+ /** Manages the ordered sort terms of one table. */
1055
1126
  var SortManager = class {
1056
1127
  #schema;
1057
1128
  #emitter;
@@ -1059,7 +1130,7 @@ var SortManager = class {
1059
1130
  #read;
1060
1131
  #write;
1061
1132
  /**
1062
- * Create a sort manager over one table's private term store.
1133
+ * Creates a sort manager over one table's private term store.
1063
1134
  *
1064
1135
  * @param schema - The table schema.
1065
1136
  * @param emitter - The table's event emitter.
@@ -1074,41 +1145,33 @@ var SortManager = class {
1074
1145
  this.#read = read;
1075
1146
  this.#write = write;
1076
1147
  }
1077
- /** Find one column's sort term. */
1148
+ /** Finds one column's sort term. */
1078
1149
  order(column) {
1079
1150
  const order = this.#read().find((candidate) => candidate.column === column);
1080
1151
  return order === void 0 ? void 0 : Object.freeze({ ...order });
1081
1152
  }
1082
- /** Read every sort term as an owned frozen snapshot. */
1153
+ /** Reads every sort term as an owned frozen snapshot. */
1083
1154
  orders() {
1084
1155
  return Object.freeze(this.#read().map((order) => Object.freeze({ ...order })));
1085
1156
  }
1086
- /** Sort by one column or several. */
1157
+ /** Sorts by one column or several. */
1087
1158
  set(input) {
1088
1159
  this.#gate();
1089
1160
  const requested = Array.isArray(input) ? input : [input];
1090
1161
  for (const order of requested) this.#require(order.column);
1091
- const next = [...this.#read()];
1092
- for (const order of requested) {
1093
- const owned = Object.freeze({ ...order });
1094
- const index = next.findIndex((candidate) => candidate.column === order.column);
1095
- if (index === -1) next.push(owned);
1096
- else next[index] = owned;
1097
- }
1098
- if (this.#same(next, this.#read())) return;
1099
- const committed = Object.freeze(next);
1100
- this.#write(committed);
1162
+ const next = mergeTerms(this.#read(), requested);
1163
+ if (matchesTerms(next, this.#read(), (order, other) => order.direction === other.direction)) return;
1164
+ this.#write(next);
1101
1165
  this.#emitter.emit("sort", this.orders());
1102
1166
  }
1103
- /** Stop sorting by one or more columns. */
1167
+ /** Stops sorting by one or more columns. */
1104
1168
  remove(input) {
1105
1169
  this.#gate();
1106
1170
  const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
1107
1171
  for (const column of columns) if (extractColumn(this.#schema, column) === void 0) return false;
1108
- const removed = new Set(columns);
1109
- const next = this.#read().filter((order) => !removed.has(order.column));
1172
+ const next = removeTerms(this.#read(), columns);
1110
1173
  if (next.length !== this.#read().length) {
1111
- this.#write(Object.freeze(next));
1174
+ this.#write(next);
1112
1175
  this.#emitter.emit("sort", this.orders());
1113
1176
  }
1114
1177
  return input === void 0 ? void 0 : true;
@@ -1116,16 +1179,13 @@ var SortManager = class {
1116
1179
  #require(column) {
1117
1180
  if (extractColumn(this.#schema, column) === void 0) throw new TableError("COLUMN", `The schema declares no column named "${column}"`, { column });
1118
1181
  }
1119
- #same(left, right) {
1120
- return left.length === right.length && left.every((order, index) => {
1121
- const other = right[index];
1122
- return other !== void 0 && order.column === other.column && order.direction === other.direction;
1123
- });
1124
- }
1125
1182
  };
1126
1183
  //#endregion
1127
1184
  //#region src/core/Table.ts
1128
- /** A schema, its rows, and the lens through which they are read. */
1185
+ /**
1186
+ * Holds a schema, its rows, and the lens through which they are read, implementing
1187
+ * {@link TableInterface} exactly.
1188
+ */
1129
1189
  var Table = class {
1130
1190
  #emitter;
1131
1191
  #schema;
@@ -1147,7 +1207,7 @@ var Table = class {
1147
1207
  #expansion;
1148
1208
  #pagination;
1149
1209
  /**
1150
- * Open a table against a schema.
1210
+ * Opens a table against a schema.
1151
1211
  *
1152
1212
  * @param schema - The table declaration to own.
1153
1213
  * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
@@ -1155,9 +1215,13 @@ var Table = class {
1155
1215
  * identity is unusable or repeated, and `CELL` when a seeded cell is invalid.
1156
1216
  */
1157
1217
  constructor(schema, options) {
1158
- const problems = isStructuralTableSchema(schema) ? auditTable(schema) : ["The schema is not a table schema"];
1218
+ const unusable = "The schema is not a table schema";
1219
+ const owned = isStructuralTableSchema(schema) ? (0, _orkestrel_contract.attempt)(() => cloneSchema(schema)) : void 0;
1220
+ if (owned !== void 0 && !owned.success && isTableError(owned.error)) throw owned.error;
1221
+ if (owned === void 0 || !owned.success) throw new TableError("SCHEMA", `The table schema is unusable: ${unusable}`, { problems: [unusable] });
1222
+ const problems = isStructuralTableSchema(owned.value) ? auditTable(owned.value) : [unusable];
1159
1223
  if (problems.length > 0) throw new TableError("SCHEMA", `The table schema is unusable: ${problems.join("; ")}`, { problems: [...problems] });
1160
- this.#schema = cloneSchema(schema);
1224
+ this.#schema = owned.value;
1161
1225
  this.#comparators = options?.comparators === void 0 ? void 0 : Object.freeze({ ...options.comparators });
1162
1226
  this.#matchers = options?.matchers === void 0 ? void 0 : Object.freeze({ ...options.matchers });
1163
1227
  this.#limit = options?.limit;
@@ -1187,54 +1251,54 @@ var Table = class {
1187
1251
  this.#rowStore = rows;
1188
1252
  }, (removed, announce) => this.#settle(removed, announce), options?.rows);
1189
1253
  }
1190
- /** The table's event emitter. */
1254
+ /** Holds the table's event emitter. */
1191
1255
  get emitter() {
1192
1256
  return this.#emitter;
1193
1257
  }
1194
- /** The owned frozen schema. */
1258
+ /** Holds the owned frozen schema. */
1195
1259
  get schema() {
1196
1260
  return this.#schema;
1197
1261
  }
1198
- /** The rows the table holds. */
1262
+ /** Manages the rows the table holds. */
1199
1263
  get rows() {
1200
1264
  return this.#rows;
1201
1265
  }
1202
- /** The ordered sort terms. */
1266
+ /** Manages the ordered sort terms. */
1203
1267
  get sort() {
1204
1268
  return this.#sort;
1205
1269
  }
1206
- /** The filters applied with and-only composition. */
1270
+ /** Manages the filters applied with and-only composition. */
1207
1271
  get filter() {
1208
1272
  return this.#filter;
1209
1273
  }
1210
- /** The selected row keys. */
1274
+ /** Manages the selected row keys. */
1211
1275
  get selection() {
1212
1276
  return this.#selection;
1213
1277
  }
1214
- /** The expanded row keys. */
1278
+ /** Manages the expanded row keys. */
1215
1279
  get expansion() {
1216
1280
  return this.#expansion;
1217
1281
  }
1218
- /** The page arithmetic. */
1282
+ /** Manages the page arithmetic. */
1219
1283
  get pagination() {
1220
1284
  return this.#pagination;
1221
1285
  }
1222
- /** The filtered, sorted, and paged rows as owned frozen snapshots. */
1286
+ /** Returns the filtered, sorted, and paged rows as owned frozen snapshots. */
1223
1287
  get view() {
1224
1288
  const ordered = sortRows(this.#schema, this.#filtered(), this.#orderStore, this.#comparators);
1225
1289
  const limit = this.#limit;
1226
1290
  const page = limit === void 0 ? ordered : ordered.slice(this.#pagination.offset, this.#pagination.offset + limit);
1227
1291
  return Object.freeze(page.map((row) => cloneRow(row)));
1228
1292
  }
1229
- /** The number of rows admitted by the filters. */
1293
+ /** Returns the number of rows admitted by the filters. */
1230
1294
  get count() {
1231
1295
  return this.#filtered().length;
1232
1296
  }
1233
- /** Whether the table has been torn down. */
1297
+ /** Reports whether the table has been torn down. */
1234
1298
  get destroyed() {
1235
1299
  return this.#destroyed;
1236
1300
  }
1237
- /** Reset every moving axis to its opening state. */
1301
+ /** Resets every moving axis to its opening state. */
1238
1302
  clear() {
1239
1303
  this.#gate();
1240
1304
  if (!(this.#rowStore.length > 0 || this.#orderStore.length > 0 || this.#filterStore.length > 0 || this.#selected.size > 0 || this.#expanded.size > 0 || this.#page !== 1 || this.#limit !== this.#initialLimit)) return;
@@ -1247,7 +1311,7 @@ var Table = class {
1247
1311
  this.#limit = this.#initialLimit;
1248
1312
  this.#emitter.emit("clear");
1249
1313
  }
1250
- /** Tear the table down while leaving every getter readable. */
1314
+ /** Tears the table down while leaving every getter readable. */
1251
1315
  destroy() {
1252
1316
  if (this.#destroyed) return;
1253
1317
  this.#destroyed = true;
@@ -1289,17 +1353,43 @@ var Table = class {
1289
1353
  //#endregion
1290
1354
  //#region src/core/factories.ts
1291
1355
  /**
1292
- * Open a table against a schema.
1356
+ * Opens a table against a schema. The schema is copied, and the copy is what the table declares.
1293
1357
  *
1294
1358
  * @param schema - The table declaration to own.
1295
1359
  * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
1296
1360
  * @returns A live table interface.
1297
1361
  * @throws A {@link TableError} coded `SCHEMA` when the schema is unusable, `KEY` when a seeded
1298
1362
  * identity is unusable or repeated, and `CELL` when a seeded cell is invalid.
1299
- * @example
1363
+ * @example Open a table
1300
1364
  * ```ts
1301
- * const table = createTable({ key: 'id', columns: [{ cell: 'text', key: 'id' }] })
1302
- * table.rows.add({ id: '1' })
1365
+ * import { createTable } from '@orkestrel/table'
1366
+ *
1367
+ * const table = createTable(
1368
+ * {
1369
+ * label: 'People',
1370
+ * key: 'id',
1371
+ * columns: [
1372
+ * { cell: 'text', key: 'id', label: 'Reference' },
1373
+ * { cell: 'text', key: 'name', label: 'Name' },
1374
+ * { cell: 'number', key: 'age', label: 'Age' },
1375
+ * ],
1376
+ * },
1377
+ * {
1378
+ * rows: [
1379
+ * { id: '1', name: 'Ada', age: 36 },
1380
+ * { id: '2', name: 'Grace', age: 45 },
1381
+ * { id: '3', name: 'Alan', age: 41 },
1382
+ * ],
1383
+ * limit: 2,
1384
+ * },
1385
+ * )
1386
+ *
1387
+ * table.filter.set({ column: 'name', operator: 'contains', text: 'a' })
1388
+ * table.sort.set({ column: 'age', direction: 'descending' })
1389
+ *
1390
+ * table.count // 3 — every name holds a lowercase 'a'
1391
+ * table.pagination.count // 2 — two pages of two
1392
+ * table.view.map((row) => row.name) // ['Grace', 'Alan'] — page one, oldest first
1303
1393
  * ```
1304
1394
  */
1305
1395
  function createTable(schema, options) {
@@ -1309,15 +1399,9 @@ function createTable(schema, options) {
1309
1399
  exports.CHOICE_LIMIT = CHOICE_LIMIT;
1310
1400
  exports.COLUMN_CELLS = COLUMN_CELLS;
1311
1401
  exports.COLUMN_LIMIT = COLUMN_LIMIT;
1312
- exports.ExpansionManager = ExpansionManager;
1313
- exports.FilterManager = FilterManager;
1314
1402
  exports.NAME_LIMIT = NAME_LIMIT;
1315
1403
  exports.NODE_LIMIT = NODE_LIMIT;
1316
- exports.PaginationManager = PaginationManager;
1317
- exports.RowManager = RowManager;
1318
1404
  exports.STRING_LIMIT = STRING_LIMIT;
1319
- exports.SelectionManager = SelectionManager;
1320
- exports.SortManager = SortManager;
1321
1405
  exports.TEXT_LIMIT = TEXT_LIMIT;
1322
1406
  exports.Table = Table;
1323
1407
  exports.TableError = TableError;
@@ -1341,8 +1425,11 @@ exports.isTableRow = isTableRow;
1341
1425
  exports.isTableSchema = isTableSchema;
1342
1426
  exports.matchesCell = matchesCell;
1343
1427
  exports.matchesFilter = matchesFilter;
1428
+ exports.matchesTerms = matchesTerms;
1429
+ exports.mergeTerms = mergeTerms;
1344
1430
  exports.parseRows = parseRows;
1345
1431
  exports.parseTable = parseTable;
1432
+ exports.removeTerms = removeTerms;
1346
1433
  exports.serializeRows = serializeRows;
1347
1434
  exports.serializeTable = serializeTable;
1348
1435
  exports.sortRows = sortRows;