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