@orkestrel/table 0.0.2 → 0.0.4

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,35 @@ 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. */
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. */
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, in UTF-16 code units, of a schema name or column key. */
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, in UTF-16 code units, of any single retained string. */
19
19
  var STRING_LIMIT = 65536;
20
- /** The maximum total length, in UTF-16 code units, of every string one schema retains. */
20
+ /** Names the maximum total length, in UTF-16 code units, of every string one schema retains. */
21
21
  var TEXT_LIMIT = 1048576;
22
- /** The maximum total number of records, arrays, and leaves one schema retains. */
22
+ /** Names the maximum total number of records, arrays, and leaves one schema retains. */
23
23
  var NODE_LIMIT = 16384;
24
24
  //#endregion
25
25
  //#region src/core/errors.ts
26
- /** An error raised by the table domain. */
26
+ /** Represents an error raised by the table domain. */
27
27
  var TableError = class extends Error {
28
- /** The machine-readable reason for this failure. */
28
+ /** Holds the machine-readable reason for this failure. */
29
29
  code;
30
- /** Structured values that locate or explain this failure. */
30
+ /** Holds structured values that locate or explain this failure. */
31
31
  context;
32
32
  /**
33
- * Create a table error.
33
+ * Creates a table error.
34
34
  *
35
35
  * @param code - The machine-readable reason.
36
36
  * @param message - The human-readable failure text.
@@ -44,10 +44,10 @@ var TableError = class extends Error {
44
44
  }
45
45
  };
46
46
  /**
47
- * Determine whether an unknown value is a table error.
47
+ * Determines whether an unknown value is a table error.
48
48
  *
49
49
  * @param input - The value to inspect.
50
- * @returns Whether the value is a {@link TableError} instance.
50
+ * @returns True if the value is a {@link TableError} instance; false otherwise.
51
51
  */
52
52
  function isTableError(input) {
53
53
  return input instanceof TableError;
@@ -55,7 +55,7 @@ function isTableError(input) {
55
55
  //#endregion
56
56
  //#region src/core/helpers.ts
57
57
  /**
58
- * Find one column by key.
58
+ * Finds one column by key.
59
59
  *
60
60
  * @param schema - The schema whose columns to search.
61
61
  * @param key - The column key to find.
@@ -65,7 +65,7 @@ function extractColumn(schema, key) {
65
65
  return schema.columns.find((column) => column.key === key);
66
66
  }
67
67
  /**
68
- * Read one row's declared identity.
68
+ * Reads one row's declared identity.
69
69
  *
70
70
  * @param schema - The schema that names the identity column.
71
71
  * @param row - The row whose identity to read.
@@ -77,7 +77,7 @@ function extractKey(schema, row) {
77
77
  return (0, _orkestrel_contract.isString)(key) && key.length > 0 ? key : void 0;
78
78
  }
79
79
  /**
80
- * Compute one atomic 0/1/N membership change over known keys.
80
+ * Computes one atomic 0/1/N membership change over known keys.
81
81
  *
82
82
  * @param known - Every key the caller may change.
83
83
  * @param current - The current key set.
@@ -96,11 +96,55 @@ function computeKeys(known, current, input, include) {
96
96
  return next.size !== current.size || [...next].some((key) => !current.has(key)) ? next : current;
97
97
  }
98
98
  /**
99
- * Check whether a value has the shape required by one column cell.
99
+ * Merges lens terms into a column-keyed list, replacing the entry that names the same column.
100
+ *
101
+ * @param current - The list as it stands.
102
+ * @param requested - The terms to write, in the order they are written.
103
+ * @returns A frozen list holding one owned entry per column, in the order the columns first
104
+ * appeared.
105
+ */
106
+ function mergeTerms(current, requested) {
107
+ const next = [...current];
108
+ for (const term of requested) {
109
+ const owned = Object.freeze({ ...term });
110
+ const index = next.findIndex((candidate) => candidate.column === term.column);
111
+ if (index === -1) next.push(owned);
112
+ else next[index] = owned;
113
+ }
114
+ return Object.freeze(next);
115
+ }
116
+ /**
117
+ * Removes every lens term naming one of the given columns.
118
+ *
119
+ * @param current - The list as it stands.
120
+ * @param columns - The column keys to drop.
121
+ * @returns A frozen list holding the entries no named column matched, in their original order.
122
+ */
123
+ function removeTerms(current, columns) {
124
+ const removed = new Set(columns);
125
+ return Object.freeze(current.filter((term) => !removed.has(term.column)));
126
+ }
127
+ /**
128
+ * Checks whether two lens lists hold the same terms in the same order.
129
+ *
130
+ * @param left - The first list.
131
+ * @param right - The second list.
132
+ * @param equal - Decide whether two terms naming one column carry the same operands.
133
+ * @returns True if the lists are the same length and every position names the same column and
134
+ * carries the same operands; false otherwise.
135
+ */
136
+ function matchesTerms(left, right, equal) {
137
+ return left.length === right.length && left.every((term, index) => {
138
+ const other = right[index];
139
+ return other !== void 0 && term.column === other.column && equal(term, other);
140
+ });
141
+ }
142
+ /**
143
+ * Checks whether a value has the shape required by one column cell.
100
144
  *
101
145
  * @param column - The column that owns the cell.
102
146
  * @param value - The unknown value to inspect.
103
- * @returns Whether the column can hold the value.
147
+ * @returns True if the column can hold the value; false otherwise.
104
148
  */
105
149
  function matchesCell(column, value) {
106
150
  if ((0, _orkestrel_contract.isString)(value) && value.length > 65536) return false;
@@ -112,7 +156,7 @@ function matchesCell(column, value) {
112
156
  }
113
157
  }
114
158
  /**
115
- * Compare two cells in ascending order according to one column.
159
+ * Compares two cells in ascending order according to one column.
116
160
  *
117
161
  * @param column - The column that fixes the comparison.
118
162
  * @param left - The first cell, or absence.
@@ -138,11 +182,11 @@ function compareCells(column, left, right) {
138
182
  }
139
183
  }
140
184
  /**
141
- * Check whether one column admits a filter and all its operands.
185
+ * Checks whether one column admits a filter and all its operands.
142
186
  *
143
187
  * @param column - The column that fixes the accepted operators and cell shapes.
144
188
  * @param filter - The filter to inspect.
145
- * @returns Whether the filter belongs to the column and the column can apply it.
189
+ * @returns True if the filter belongs to the column and the column can apply it; false otherwise.
146
190
  */
147
191
  function admitsFilter(column, filter) {
148
192
  if (filter.column !== column.key) return false;
@@ -153,12 +197,12 @@ function admitsFilter(column, filter) {
153
197
  }
154
198
  }
155
199
  /**
156
- * Test one cell against a filter according to its column.
200
+ * Tests one cell against a filter according to its column.
157
201
  *
158
202
  * @param column - The column that fixes the accepted operators.
159
203
  * @param cell - The cell to test, or absence.
160
204
  * @param filter - The filter to apply.
161
- * @returns Whether the filter accepts the cell.
205
+ * @returns True if the filter accepts the cell; false otherwise.
162
206
  */
163
207
  function matchesFilter(column, cell, filter) {
164
208
  if (cell === void 0 || !admitsFilter(column, filter) || !matchesCell(column, cell)) return false;
@@ -169,7 +213,7 @@ function matchesFilter(column, cell, filter) {
169
213
  }
170
214
  }
171
215
  /**
172
- * Keep the rows accepted by every filter.
216
+ * Keeps the rows accepted by every filter.
173
217
  *
174
218
  * @param schema - The schema that declares the filtered columns.
175
219
  * @param rows - The rows to filter.
@@ -187,7 +231,7 @@ function filterRows(schema, rows, filters, matchers) {
187
231
  })));
188
232
  }
189
233
  /**
190
- * Order rows stably by a sequence of terms.
234
+ * Orders rows stably by a sequence of terms.
191
235
  *
192
236
  * @param schema - The schema that declares the sorted columns.
193
237
  * @param rows - The rows to order.
@@ -215,7 +259,7 @@ function sortRows(schema, rows, orders, comparators) {
215
259
  return Object.freeze(indexed.map((entry) => entry.row));
216
260
  }
217
261
  /**
218
- * Audit a structurally valid schema for domain and budget faults.
262
+ * Audits a structurally valid schema for domain and budget faults.
219
263
  *
220
264
  * @param schema - The table schema to audit.
221
265
  * @returns Frozen human-readable diagnostics, or an empty list when the schema is sound.
@@ -314,7 +358,7 @@ function auditTable(schema) {
314
358
  return Object.freeze(faults);
315
359
  }
316
360
  /**
317
- * Project a schema into declaration-ordered JSON.
361
+ * Projects a schema into declaration-ordered JSON.
318
362
  *
319
363
  * @param schema - The schema to project.
320
364
  * @returns A deeply owned JSON record with absent members omitted.
@@ -353,7 +397,7 @@ function serializeTable(schema) {
353
397
  }
354
398
  }
355
399
  /**
356
- * Project rows into schema-column-ordered JSON.
400
+ * Projects rows into schema-column-ordered JSON.
357
401
  *
358
402
  * @param schema - The schema that fixes cell order.
359
403
  * @param rows - The rows to project.
@@ -381,19 +425,19 @@ function serializeRows(schema, rows) {
381
425
  //#endregion
382
426
  //#region src/core/validators.ts
383
427
  /**
384
- * Determine whether an unknown value has a table cell shape.
428
+ * Determines whether an unknown value has a table cell shape.
385
429
  *
386
430
  * @param input - The value to inspect.
387
- * @returns Whether the value is a string, finite number, or boolean.
431
+ * @returns True if the value is a string, finite number, or boolean; false otherwise.
388
432
  */
389
433
  function isTableCell(input) {
390
434
  return (0, _orkestrel_contract.unionOf)(_orkestrel_contract.isString, _orkestrel_contract.isFiniteNumber, _orkestrel_contract.isBoolean)(input);
391
435
  }
392
436
  /**
393
- * Determine whether an unknown value is a record of table cells.
437
+ * Determines whether an unknown value is a record of table cells.
394
438
  *
395
439
  * @param input - The value to inspect.
396
- * @returns Whether every own key is a string and every value is a table cell.
440
+ * @returns True if every own key is a string and every value is a table cell; false otherwise.
397
441
  */
398
442
  function isTableRow(input) {
399
443
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -403,19 +447,19 @@ function isTableRow(input) {
403
447
  return outcome.success && outcome.value;
404
448
  }
405
449
  /**
406
- * Determine whether an unknown value is a declared column cell.
450
+ * Determines whether an unknown value is a declared column cell.
407
451
  *
408
452
  * @param input - The value to inspect.
409
- * @returns Whether the value is one of the four column cells.
453
+ * @returns True if the value is a declared column cell; false otherwise.
410
454
  */
411
455
  function isColumnCell(input) {
412
456
  return COLUMN_CELLS.some((cell) => cell === input);
413
457
  }
414
458
  /**
415
- * Determine whether an unknown value is one exact column choice record.
459
+ * Determines whether an unknown value is one exact column choice record.
416
460
  *
417
461
  * @param input - The value to inspect.
418
- * @returns Whether the value is a column choice.
462
+ * @returns True if the value is a column choice; false otherwise.
419
463
  */
420
464
  function isColumnChoice(input) {
421
465
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -429,10 +473,10 @@ function isColumnChoice(input) {
429
473
  return outcome.success && outcome.value;
430
474
  }
431
475
  /**
432
- * Determine whether an unknown value is one exact discriminated table column.
476
+ * Determines whether an unknown value is one exact discriminated table column.
433
477
  *
434
478
  * @param input - The value to inspect.
435
- * @returns Whether the value is a structurally valid table column.
479
+ * @returns True if the value is a structurally valid table column; false otherwise.
436
480
  */
437
481
  function isTableColumn(input) {
438
482
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -471,10 +515,10 @@ function isTableColumn(input) {
471
515
  return outcome.success && outcome.value;
472
516
  }
473
517
  /**
474
- * Determine whether an unknown value has one exact structural table-schema shape.
518
+ * Determines whether an unknown value has one exact structural table-schema shape.
475
519
  *
476
520
  * @param input - The value to inspect.
477
- * @returns Whether the value has the exact structure of a table schema.
521
+ * @returns True if the value has the exact structure of a table schema; false otherwise.
478
522
  */
479
523
  function isStructuralTableSchema(input) {
480
524
  const outcome = (0, _orkestrel_contract.attempt)(() => {
@@ -494,10 +538,11 @@ function isStructuralTableSchema(input) {
494
538
  return outcome.success && outcome.value;
495
539
  }
496
540
  /**
497
- * Determine whether an unknown value is one semantically sound table schema.
541
+ * Determines whether an unknown value is one semantically sound table schema.
498
542
  *
499
543
  * @param input - The value to inspect.
500
- * @returns Whether the value has valid structure, domain relationships, and budgets.
544
+ * @returns True if the value has valid structure, domain relationships, and
545
+ * budgets; false otherwise.
501
546
  */
502
547
  function isTableSchema(input) {
503
548
  const outcome = (0, _orkestrel_contract.attempt)(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
@@ -506,7 +551,7 @@ function isTableSchema(input) {
506
551
  //#endregion
507
552
  //#region src/core/cloners.ts
508
553
  /**
509
- * Clone one row into an owned frozen snapshot.
554
+ * Clones one row into an owned frozen snapshot.
510
555
  *
511
556
  * @param row - The row to own.
512
557
  * @returns A frozen copy of the row's cells.
@@ -515,7 +560,7 @@ function cloneRow(row) {
515
560
  return Object.freeze({ ...row });
516
561
  }
517
562
  /**
518
- * Clone a table schema into an owned frozen snapshot.
563
+ * Clones a table schema into an owned frozen snapshot.
519
564
  *
520
565
  * @param schema - The schema to own.
521
566
  * @returns A frozen schema with every nested column, choice, list, and metadata record owned.
@@ -546,7 +591,7 @@ function cloneSchema(schema) {
546
591
  //#endregion
547
592
  //#region src/core/parsers.ts
548
593
  /**
549
- * Parse unknown wire data into an owned, semantically sound table schema.
594
+ * Parses unknown wire data into an owned, semantically sound table schema.
550
595
  *
551
596
  * @param input - The unknown schema value to parse.
552
597
  * @returns An owned table schema, or `undefined` on refusal.
@@ -560,7 +605,7 @@ function parseTable(input) {
560
605
  return outcome.success ? outcome.value : void 0;
561
606
  }
562
607
  /**
563
- * Parse unknown wire rows against one table schema.
608
+ * Parses unknown wire rows against one table schema.
564
609
  *
565
610
  * @param schema - The schema that declares the accepted keys and cell shapes.
566
611
  * @param input - The unknown row-list value to parse.
@@ -606,63 +651,94 @@ function parseRows(schema, input) {
606
651
  return outcome.success ? outcome.value : void 0;
607
652
  }
608
653
  //#endregion
609
- //#region src/core/tables/ExpansionManager.ts
610
- /** The keys of the rows somebody has opened. */
611
- var ExpansionManager = class {
654
+ //#region src/core/tables/KeyManager.ts
655
+ /** Manages the key set one table axis holds, and the event it announces when that set moves. */
656
+ var KeyManager = class {
612
657
  #emitter;
658
+ #event;
613
659
  #gate;
614
660
  #rows;
615
661
  #read;
616
662
  #write;
617
663
  /**
618
- * Create an expansion manager over one table's private stores.
664
+ * Creates a key-set shell over one table's private store.
619
665
  *
620
666
  * @param emitter - The table's event emitter.
667
+ * @param event - The event this axis announces when its key set moves.
621
668
  * @param gate - The table lifecycle gate.
622
669
  * @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.
670
+ * @param read - A read of the held keys.
671
+ * @param write - The held-key commit boundary.
625
672
  */
626
- constructor(emitter, gate, rows, read, write) {
673
+ constructor(emitter, event, gate, rows, read, write) {
627
674
  this.#emitter = emitter;
675
+ this.#event = event;
628
676
  this.#gate = gate;
629
677
  this.#rows = rows;
630
678
  this.#read = read;
631
679
  this.#write = write;
632
680
  }
633
- /** The keys of the rows opened right now. */
681
+ /** Returns the keys held right now. */
634
682
  get keys() {
635
683
  return new Set(this.#read());
636
684
  }
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) {
685
+ /**
686
+ * Applies one atomic 0/1/N membership change, announcing it only when the set moves.
687
+ *
688
+ * @param input - Every known key, one key, or a key list.
689
+ * @param include - Decides the next membership from each key's membership at that step.
690
+ * @returns `undefined` for the no-argument form, `false` when any requested key names no row
691
+ * the table holds, and `true` otherwise.
692
+ */
693
+ change(input, include) {
649
694
  this.#gate();
650
- return this.#change(input, (included) => !included) === true;
651
- }
652
- #change(input, include) {
653
695
  const previous = this.#read();
654
696
  const next = computeKeys(this.#rows(), previous, input, include);
655
697
  if (next === void 0) return false;
656
698
  if (next !== previous) {
657
699
  this.#write(next);
658
- this.#emitter.emit("expand", new Set(next));
700
+ this.#emitter.emit(this.#event, new Set(next));
659
701
  }
660
702
  return input === void 0 ? void 0 : true;
661
703
  }
662
704
  };
663
705
  //#endregion
706
+ //#region src/core/tables/ExpansionManager.ts
707
+ /** Manages the keys of the rows somebody has opened. */
708
+ var ExpansionManager = class {
709
+ #keys;
710
+ /**
711
+ * Creates an expansion manager over one table's private stores.
712
+ *
713
+ * @param emitter - The table's event emitter.
714
+ * @param gate - The table lifecycle gate.
715
+ * @param rows - A read of every row key.
716
+ * @param read - A read of the expanded keys.
717
+ * @param write - The expanded-key commit boundary.
718
+ */
719
+ constructor(emitter, gate, rows, read, write) {
720
+ this.#keys = new KeyManager(emitter, "expand", gate, rows, read, write);
721
+ }
722
+ /** Returns the keys of the rows opened right now. */
723
+ get keys() {
724
+ return this.#keys.keys;
725
+ }
726
+ /** Opens one or more rows. */
727
+ expand(input) {
728
+ return this.#keys.change(input, () => true);
729
+ }
730
+ /** Closes one or more rows. */
731
+ clear(input) {
732
+ return this.#keys.change(input, () => false);
733
+ }
734
+ /** Turns one or more rows around independently. */
735
+ toggle(input) {
736
+ return this.#keys.change(input, (included) => !included) === true;
737
+ }
738
+ };
739
+ //#endregion
664
740
  //#region src/core/tables/FilterManager.ts
665
- /** The filters one table applies with and-only composition. */
741
+ /** Manages the filters one table applies with and-only composition. */
666
742
  var FilterManager = class {
667
743
  #schema;
668
744
  #emitter;
@@ -671,7 +747,7 @@ var FilterManager = class {
671
747
  #write;
672
748
  #clamp;
673
749
  /**
674
- * Create a filter manager over one table's private filter store.
750
+ * Creates a filter manager over one table's private filter store.
675
751
  *
676
752
  * @param schema - The table schema.
677
753
  * @param emitter - The table's event emitter.
@@ -688,42 +764,35 @@ var FilterManager = class {
688
764
  this.#write = write;
689
765
  this.#clamp = clamp;
690
766
  }
691
- /** Find one column's filter. */
767
+ /** Finds one column's filter. */
692
768
  filter(column) {
693
769
  const filter = this.#read().find((candidate) => candidate.column === column);
694
770
  return filter === void 0 ? void 0 : Object.freeze({ ...filter });
695
771
  }
696
- /** Read every filter as an owned frozen snapshot. */
772
+ /** Reads every filter as an owned frozen snapshot. */
697
773
  filters() {
698
774
  return Object.freeze(this.#read().map((filter) => Object.freeze({ ...filter })));
699
775
  }
700
- /** Filter one column or several. */
776
+ /** Filters one column or several. */
701
777
  set(input) {
702
778
  this.#gate();
703
779
  const requested = Array.isArray(input) ? input : [input];
704
780
  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));
781
+ const next = mergeTerms(this.#read(), requested);
782
+ if (matchesTerms(next, this.#read(), (filter, other) => this.#operands(filter, other))) return;
783
+ this.#write(next);
714
784
  const page = this.#clamp();
715
785
  this.#emitter.emit("filter", this.filters());
716
786
  if (page !== void 0) this.#emitter.emit("paginate", page);
717
787
  }
718
- /** Stop filtering by one or more columns. */
788
+ /** Stops filtering by one or more columns. */
719
789
  remove(input) {
720
790
  this.#gate();
721
791
  const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
722
792
  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));
793
+ const next = removeTerms(this.#read(), columns);
725
794
  if (next.length !== this.#read().length) {
726
- this.#write(Object.freeze(next));
795
+ this.#write(next);
727
796
  const page = this.#clamp();
728
797
  this.#emitter.emit("filter", this.filters());
729
798
  if (page !== void 0) this.#emitter.emit("paginate", page);
@@ -735,19 +804,16 @@ var FilterManager = class {
735
804
  if (column === void 0) throw new TableError("COLUMN", `The schema declares no column named "${filter.column}"`, { column: filter.column });
736
805
  if (!admitsFilter(column, filter)) throw new TableError("CELL", `Column "${filter.column}" cannot apply that filter`, { column: filter.column });
737
806
  }
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
- });
807
+ #operands(left, right) {
808
+ if (left.operator !== right.operator) return false;
809
+ if (left.operator === "contains" && right.operator === "contains") return left.text === right.text;
810
+ if (left.operator === "between" && right.operator === "between") return left.minimum === right.minimum && left.maximum === right.maximum;
811
+ return left.operator === "equals" && right.operator === "equals" && left.value === right.value;
746
812
  }
747
813
  };
748
814
  //#endregion
749
815
  //#region src/core/tables/PaginationManager.ts
750
- /** The page arithmetic over one table's filtered rows. */
816
+ /** Manages the page arithmetic over one table's filtered rows. */
751
817
  var PaginationManager = class {
752
818
  #emitter;
753
819
  #gate;
@@ -757,7 +823,7 @@ var PaginationManager = class {
757
823
  #readLimit;
758
824
  #writeLimit;
759
825
  /**
760
- * Create a pagination manager over one table's private stores.
826
+ * Creates a pagination manager over one table's private stores.
761
827
  *
762
828
  * @param emitter - The table's event emitter.
763
829
  * @param gate - The table lifecycle gate.
@@ -778,33 +844,33 @@ var PaginationManager = class {
778
844
  const limit = this.#readLimit();
779
845
  if (limit !== void 0) this.#writeLimit(this.#normalize(limit));
780
846
  }
781
- /** The page shown, counted from one. */
847
+ /** Returns the page shown, counted from one. */
782
848
  get page() {
783
849
  return this.#readLimit() === void 0 ? 1 : this.#readPage();
784
850
  }
785
- /** The number of rows one page holds. */
851
+ /** Returns the number of rows one page holds. */
786
852
  get limit() {
787
853
  return this.#readLimit();
788
854
  }
789
- /** The number of filtered rows skipped before this page. */
855
+ /** Returns the number of filtered rows skipped before this page. */
790
856
  get offset() {
791
857
  const limit = this.#readLimit();
792
858
  return limit === void 0 ? 0 : (this.#readPage() - 1) * limit;
793
859
  }
794
- /** The number of pages filled by the filtered rows. */
860
+ /** Returns the number of pages filled by the filtered rows. */
795
861
  get count() {
796
862
  const limit = this.#readLimit();
797
863
  return limit === void 0 ? 1 : Math.max(1, Math.ceil(this.#rows() / limit));
798
864
  }
799
- /** Show another page, clamped to the pages that exist. */
865
+ /** Shows another page, clamped to the pages that exist. */
800
866
  move(page) {
801
867
  this.#gate();
802
- const next = this.#readLimit() === void 0 ? 1 : Math.min(this.count, this.#normalize(page));
868
+ const next = this.#readLimit() === void 0 || Number.isNaN(page) ? 1 : Math.min(this.count, Math.max(1, Math.trunc(page)));
803
869
  if (next === this.#readPage()) return;
804
870
  this.#writePage(next);
805
871
  this.#emitter.emit("paginate", next);
806
872
  }
807
- /** Change the page size while keeping the first row previously shown. */
873
+ /** Changes the page size while keeping the first row previously shown. */
808
874
  resize(limit) {
809
875
  this.#gate();
810
876
  const previous = this.#readLimit();
@@ -822,7 +888,7 @@ var PaginationManager = class {
822
888
  };
823
889
  //#endregion
824
890
  //#region src/core/tables/RowManager.ts
825
- /** The rows one table holds in its own order. */
891
+ /** Manages the rows one table holds in its own order. */
826
892
  var RowManager = class {
827
893
  #schema;
828
894
  #emitter;
@@ -831,7 +897,7 @@ var RowManager = class {
831
897
  #write;
832
898
  #settle;
833
899
  /**
834
- * Create a row manager over one table's private row store.
900
+ * Creates a row manager over one table's private row store.
835
901
  *
836
902
  * @param schema - The table schema.
837
903
  * @param emitter - The table's event emitter.
@@ -851,16 +917,16 @@ var RowManager = class {
851
917
  const seeded = this.#prepare(rows, /* @__PURE__ */ new Set());
852
918
  if (seeded.length > 0) this.#write(Object.freeze(seeded));
853
919
  }
854
- /** Find one row by key as an owned frozen snapshot. */
920
+ /** Finds one row by key as an owned frozen snapshot. */
855
921
  row(key) {
856
922
  const row = this.#read().find((candidate) => extractKey(this.#schema, candidate) === key);
857
923
  return row === void 0 ? void 0 : cloneRow(row);
858
924
  }
859
- /** Read every row as owned frozen snapshots in table order. */
925
+ /** Reads every row as owned frozen snapshots in table order. */
860
926
  rows() {
861
927
  return Object.freeze(this.#read().map((row) => cloneRow(row)));
862
928
  }
863
- /** Append one row or several. */
929
+ /** Appends one row or several. */
864
930
  add(input) {
865
931
  this.#gate();
866
932
  const rows = Array.isArray(input) ? input : [input];
@@ -879,7 +945,7 @@ var RowManager = class {
879
945
  }
880
946
  });
881
947
  }
882
- /** Merge one row or several into the rows their keys name. */
948
+ /** Merges one row or several into the rows their keys name. */
883
949
  update(input) {
884
950
  this.#gate();
885
951
  const updates = (Array.isArray(input) ? input : [input]).map((row) => cloneRow(row));
@@ -918,13 +984,13 @@ var RowManager = class {
918
984
  });
919
985
  return true;
920
986
  }
921
- /** Move one row to a clamped index in table order. */
987
+ /** Moves one row to a clamped index in table order. */
922
988
  move(key, index) {
923
989
  this.#gate();
924
990
  const current = this.#read();
925
991
  const origin = current.findIndex((row) => extractKey(this.#schema, row) === key);
926
992
  if (origin === -1) return false;
927
- const target = Math.min(current.length - 1, Number.isFinite(index) ? Math.max(0, Math.trunc(index)) : 0);
993
+ const target = Math.min(current.length - 1, Number.isNaN(index) ? 0 : Math.max(0, Math.trunc(index)));
928
994
  if (origin === target) return true;
929
995
  const row = current[origin];
930
996
  if (row === void 0) return false;
@@ -935,7 +1001,7 @@ var RowManager = class {
935
1001
  this.#settle([], () => this.#emitter.emit("write", key));
936
1002
  return true;
937
1003
  }
938
- /** Remove one or more rows. */
1004
+ /** Removes one or more rows. */
939
1005
  remove(input) {
940
1006
  this.#gate();
941
1007
  const current = this.#read();
@@ -996,15 +1062,11 @@ var RowManager = class {
996
1062
  };
997
1063
  //#endregion
998
1064
  //#region src/core/tables/SelectionManager.ts
999
- /** The keys of the rows somebody has picked. */
1065
+ /** Manages the keys of the rows somebody has picked. */
1000
1066
  var SelectionManager = class {
1001
- #emitter;
1002
- #gate;
1003
- #rows;
1004
- #read;
1005
- #write;
1067
+ #keys;
1006
1068
  /**
1007
- * Create a selection manager over one table's private stores.
1069
+ * Creates a selection manager over one table's private stores.
1008
1070
  *
1009
1071
  * @param emitter - The table's event emitter.
1010
1072
  * @param gate - The table lifecycle gate.
@@ -1013,45 +1075,28 @@ var SelectionManager = class {
1013
1075
  * @param write - The selected-key commit boundary.
1014
1076
  */
1015
1077
  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;
1078
+ this.#keys = new KeyManager(emitter, "select", gate, rows, read, write);
1021
1079
  }
1022
- /** The keys of the rows picked right now. */
1080
+ /** Returns the keys of the rows picked right now. */
1023
1081
  get keys() {
1024
- return new Set(this.#read());
1082
+ return this.#keys.keys;
1025
1083
  }
1026
- /** Pick one or more rows. */
1084
+ /** Picks one or more rows. */
1027
1085
  select(input) {
1028
- this.#gate();
1029
- return this.#change(input, () => true);
1086
+ return this.#keys.change(input, () => true);
1030
1087
  }
1031
- /** Drop one or more picks. */
1088
+ /** Drops one or more picks. */
1032
1089
  clear(input) {
1033
- this.#gate();
1034
- return this.#change(input, () => false);
1090
+ return this.#keys.change(input, () => false);
1035
1091
  }
1036
- /** Turn one or more rows around independently. */
1092
+ /** Turns one or more rows around independently. */
1037
1093
  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;
1094
+ return this.#keys.change(input, (included) => !included) === true;
1050
1095
  }
1051
1096
  };
1052
1097
  //#endregion
1053
1098
  //#region src/core/tables/SortManager.ts
1054
- /** The ordered sort terms of one table. */
1099
+ /** Manages the ordered sort terms of one table. */
1055
1100
  var SortManager = class {
1056
1101
  #schema;
1057
1102
  #emitter;
@@ -1059,7 +1104,7 @@ var SortManager = class {
1059
1104
  #read;
1060
1105
  #write;
1061
1106
  /**
1062
- * Create a sort manager over one table's private term store.
1107
+ * Creates a sort manager over one table's private term store.
1063
1108
  *
1064
1109
  * @param schema - The table schema.
1065
1110
  * @param emitter - The table's event emitter.
@@ -1074,41 +1119,33 @@ var SortManager = class {
1074
1119
  this.#read = read;
1075
1120
  this.#write = write;
1076
1121
  }
1077
- /** Find one column's sort term. */
1122
+ /** Finds one column's sort term. */
1078
1123
  order(column) {
1079
1124
  const order = this.#read().find((candidate) => candidate.column === column);
1080
1125
  return order === void 0 ? void 0 : Object.freeze({ ...order });
1081
1126
  }
1082
- /** Read every sort term as an owned frozen snapshot. */
1127
+ /** Reads every sort term as an owned frozen snapshot. */
1083
1128
  orders() {
1084
1129
  return Object.freeze(this.#read().map((order) => Object.freeze({ ...order })));
1085
1130
  }
1086
- /** Sort by one column or several. */
1131
+ /** Sorts by one column or several. */
1087
1132
  set(input) {
1088
1133
  this.#gate();
1089
1134
  const requested = Array.isArray(input) ? input : [input];
1090
1135
  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);
1136
+ const next = mergeTerms(this.#read(), requested);
1137
+ if (matchesTerms(next, this.#read(), (order, other) => order.direction === other.direction)) return;
1138
+ this.#write(next);
1101
1139
  this.#emitter.emit("sort", this.orders());
1102
1140
  }
1103
- /** Stop sorting by one or more columns. */
1141
+ /** Stops sorting by one or more columns. */
1104
1142
  remove(input) {
1105
1143
  this.#gate();
1106
1144
  const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
1107
1145
  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));
1146
+ const next = removeTerms(this.#read(), columns);
1110
1147
  if (next.length !== this.#read().length) {
1111
- this.#write(Object.freeze(next));
1148
+ this.#write(next);
1112
1149
  this.#emitter.emit("sort", this.orders());
1113
1150
  }
1114
1151
  return input === void 0 ? void 0 : true;
@@ -1116,16 +1153,10 @@ var SortManager = class {
1116
1153
  #require(column) {
1117
1154
  if (extractColumn(this.#schema, column) === void 0) throw new TableError("COLUMN", `The schema declares no column named "${column}"`, { column });
1118
1155
  }
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
1156
  };
1126
1157
  //#endregion
1127
1158
  //#region src/core/Table.ts
1128
- /** A schema, its rows, and the lens through which they are read. */
1159
+ /** Holds a schema, its rows, and the lens through which they are read. */
1129
1160
  var Table = class {
1130
1161
  #emitter;
1131
1162
  #schema;
@@ -1147,7 +1178,7 @@ var Table = class {
1147
1178
  #expansion;
1148
1179
  #pagination;
1149
1180
  /**
1150
- * Open a table against a schema.
1181
+ * Opens a table against a schema.
1151
1182
  *
1152
1183
  * @param schema - The table declaration to own.
1153
1184
  * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
@@ -1155,9 +1186,13 @@ var Table = class {
1155
1186
  * identity is unusable or repeated, and `CELL` when a seeded cell is invalid.
1156
1187
  */
1157
1188
  constructor(schema, options) {
1158
- const problems = isStructuralTableSchema(schema) ? auditTable(schema) : ["The schema is not a table schema"];
1189
+ const unusable = "The schema is not a table schema";
1190
+ const owned = isStructuralTableSchema(schema) ? (0, _orkestrel_contract.attempt)(() => cloneSchema(schema)) : void 0;
1191
+ if (owned !== void 0 && !owned.success && isTableError(owned.error)) throw owned.error;
1192
+ if (owned === void 0 || !owned.success) throw new TableError("SCHEMA", `The table schema is unusable: ${unusable}`, { problems: [unusable] });
1193
+ const problems = isStructuralTableSchema(owned.value) ? auditTable(owned.value) : [unusable];
1159
1194
  if (problems.length > 0) throw new TableError("SCHEMA", `The table schema is unusable: ${problems.join("; ")}`, { problems: [...problems] });
1160
- this.#schema = cloneSchema(schema);
1195
+ this.#schema = owned.value;
1161
1196
  this.#comparators = options?.comparators === void 0 ? void 0 : Object.freeze({ ...options.comparators });
1162
1197
  this.#matchers = options?.matchers === void 0 ? void 0 : Object.freeze({ ...options.matchers });
1163
1198
  this.#limit = options?.limit;
@@ -1187,54 +1222,54 @@ var Table = class {
1187
1222
  this.#rowStore = rows;
1188
1223
  }, (removed, announce) => this.#settle(removed, announce), options?.rows);
1189
1224
  }
1190
- /** The table's event emitter. */
1225
+ /** Holds the table's event emitter. */
1191
1226
  get emitter() {
1192
1227
  return this.#emitter;
1193
1228
  }
1194
- /** The owned frozen schema. */
1229
+ /** Holds the owned frozen schema. */
1195
1230
  get schema() {
1196
1231
  return this.#schema;
1197
1232
  }
1198
- /** The rows the table holds. */
1233
+ /** Manages the rows the table holds. */
1199
1234
  get rows() {
1200
1235
  return this.#rows;
1201
1236
  }
1202
- /** The ordered sort terms. */
1237
+ /** Manages the ordered sort terms. */
1203
1238
  get sort() {
1204
1239
  return this.#sort;
1205
1240
  }
1206
- /** The filters applied with and-only composition. */
1241
+ /** Manages the filters applied with and-only composition. */
1207
1242
  get filter() {
1208
1243
  return this.#filter;
1209
1244
  }
1210
- /** The selected row keys. */
1245
+ /** Manages the selected row keys. */
1211
1246
  get selection() {
1212
1247
  return this.#selection;
1213
1248
  }
1214
- /** The expanded row keys. */
1249
+ /** Manages the expanded row keys. */
1215
1250
  get expansion() {
1216
1251
  return this.#expansion;
1217
1252
  }
1218
- /** The page arithmetic. */
1253
+ /** Manages the page arithmetic. */
1219
1254
  get pagination() {
1220
1255
  return this.#pagination;
1221
1256
  }
1222
- /** The filtered, sorted, and paged rows as owned frozen snapshots. */
1257
+ /** Returns the filtered, sorted, and paged rows as owned frozen snapshots. */
1223
1258
  get view() {
1224
1259
  const ordered = sortRows(this.#schema, this.#filtered(), this.#orderStore, this.#comparators);
1225
1260
  const limit = this.#limit;
1226
1261
  const page = limit === void 0 ? ordered : ordered.slice(this.#pagination.offset, this.#pagination.offset + limit);
1227
1262
  return Object.freeze(page.map((row) => cloneRow(row)));
1228
1263
  }
1229
- /** The number of rows admitted by the filters. */
1264
+ /** Returns the number of rows admitted by the filters. */
1230
1265
  get count() {
1231
1266
  return this.#filtered().length;
1232
1267
  }
1233
- /** Whether the table has been torn down. */
1268
+ /** Reports whether the table has been torn down. */
1234
1269
  get destroyed() {
1235
1270
  return this.#destroyed;
1236
1271
  }
1237
- /** Reset every moving axis to its opening state. */
1272
+ /** Resets every moving axis to its opening state. */
1238
1273
  clear() {
1239
1274
  this.#gate();
1240
1275
  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 +1282,7 @@ var Table = class {
1247
1282
  this.#limit = this.#initialLimit;
1248
1283
  this.#emitter.emit("clear");
1249
1284
  }
1250
- /** Tear the table down while leaving every getter readable. */
1285
+ /** Tears the table down while leaving every getter readable. */
1251
1286
  destroy() {
1252
1287
  if (this.#destroyed) return;
1253
1288
  this.#destroyed = true;
@@ -1289,7 +1324,7 @@ var Table = class {
1289
1324
  //#endregion
1290
1325
  //#region src/core/factories.ts
1291
1326
  /**
1292
- * Open a table against a schema.
1327
+ * Opens a table against a schema.
1293
1328
  *
1294
1329
  * @param schema - The table declaration to own.
1295
1330
  * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
@@ -1309,15 +1344,9 @@ function createTable(schema, options) {
1309
1344
  exports.CHOICE_LIMIT = CHOICE_LIMIT;
1310
1345
  exports.COLUMN_CELLS = COLUMN_CELLS;
1311
1346
  exports.COLUMN_LIMIT = COLUMN_LIMIT;
1312
- exports.ExpansionManager = ExpansionManager;
1313
- exports.FilterManager = FilterManager;
1314
1347
  exports.NAME_LIMIT = NAME_LIMIT;
1315
1348
  exports.NODE_LIMIT = NODE_LIMIT;
1316
- exports.PaginationManager = PaginationManager;
1317
- exports.RowManager = RowManager;
1318
1349
  exports.STRING_LIMIT = STRING_LIMIT;
1319
- exports.SelectionManager = SelectionManager;
1320
- exports.SortManager = SortManager;
1321
1350
  exports.TEXT_LIMIT = TEXT_LIMIT;
1322
1351
  exports.Table = Table;
1323
1352
  exports.TableError = TableError;
@@ -1341,8 +1370,11 @@ exports.isTableRow = isTableRow;
1341
1370
  exports.isTableSchema = isTableSchema;
1342
1371
  exports.matchesCell = matchesCell;
1343
1372
  exports.matchesFilter = matchesFilter;
1373
+ exports.matchesTerms = matchesTerms;
1374
+ exports.mergeTerms = mergeTerms;
1344
1375
  exports.parseRows = parseRows;
1345
1376
  exports.parseTable = parseTable;
1377
+ exports.removeTerms = removeTerms;
1346
1378
  exports.serializeRows = serializeRows;
1347
1379
  exports.serializeTable = serializeTable;
1348
1380
  exports.sortRows = sortRows;