@orkestrel/table 0.0.1

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.
@@ -0,0 +1,1350 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _orkestrel_contract = require("@orkestrel/contract");
3
+ let _orkestrel_emitter = require("@orkestrel/emitter");
4
+ //#region src/core/constants.ts
5
+ /** Every column cell, in the order declared by the public contract. */
6
+ var COLUMN_CELLS = Object.freeze([
7
+ "text",
8
+ "number",
9
+ "flag",
10
+ "choice"
11
+ ]);
12
+ /** The maximum number of columns one schema may declare. */
13
+ var COLUMN_LIMIT = 256;
14
+ /** The maximum number of choices one `choice` column may offer. */
15
+ var CHOICE_LIMIT = 1024;
16
+ /** The maximum length, in UTF-16 code units, of a schema name or column key. */
17
+ var NAME_LIMIT = 128;
18
+ /** The maximum length, in UTF-16 code units, of any single retained string. */
19
+ var STRING_LIMIT = 65536;
20
+ /** The maximum total length, in UTF-16 code units, of every string one schema retains. */
21
+ var TEXT_LIMIT = 1048576;
22
+ /** The maximum total number of records, arrays, and leaves one schema retains. */
23
+ var NODE_LIMIT = 16384;
24
+ //#endregion
25
+ //#region src/core/errors.ts
26
+ /** An error raised by the table domain. */
27
+ var TableError = class extends Error {
28
+ /** The machine-readable reason for this failure. */
29
+ code;
30
+ /** Structured values that locate or explain this failure. */
31
+ context;
32
+ /**
33
+ * Create a table error.
34
+ *
35
+ * @param code - The machine-readable reason.
36
+ * @param message - The human-readable failure text.
37
+ * @param context - Optional structured failure details.
38
+ */
39
+ constructor(code, message, context) {
40
+ super(message);
41
+ this.name = "TableError";
42
+ this.code = code;
43
+ if (context !== void 0) this.context = context;
44
+ }
45
+ };
46
+ /**
47
+ * Determine whether an unknown value is a table error.
48
+ *
49
+ * @param input - The value to inspect.
50
+ * @returns Whether the value is a {@link TableError} instance.
51
+ */
52
+ function isTableError(input) {
53
+ return input instanceof TableError;
54
+ }
55
+ //#endregion
56
+ //#region src/core/helpers.ts
57
+ /**
58
+ * Find one column by key.
59
+ *
60
+ * @param schema - The schema whose columns to search.
61
+ * @param key - The column key to find.
62
+ * @returns The declared column, or `undefined` when no column has that key.
63
+ */
64
+ function extractColumn(schema, key) {
65
+ return schema.columns.find((column) => column.key === key);
66
+ }
67
+ /**
68
+ * Read one row's declared identity.
69
+ *
70
+ * @param schema - The schema that names the identity column.
71
+ * @param row - The row whose identity to read.
72
+ * @returns The non-empty string identity, or `undefined` when it is unusable.
73
+ */
74
+ function extractKey(schema, row) {
75
+ if (!Object.hasOwn(row, schema.key)) return void 0;
76
+ const key = row[schema.key];
77
+ return (0, _orkestrel_contract.isString)(key) && key.length > 0 ? key : void 0;
78
+ }
79
+ /**
80
+ * Compute one atomic 0/1/N membership change over known keys.
81
+ *
82
+ * @param known - Every key the caller may change.
83
+ * @param current - The current key set.
84
+ * @param input - Every known key, one key, or a key list.
85
+ * @param include - Decide the next membership from each key's membership at that step.
86
+ * @returns `undefined` when any requested key is unknown, the current set for a no-op, or the next
87
+ * set when membership changes.
88
+ */
89
+ function computeKeys(known, current, input, include) {
90
+ const requested = input === void 0 ? known : Array.isArray(input) ? input : [input];
91
+ const population = new Set(known);
92
+ if (requested.some((key) => !population.has(key))) return void 0;
93
+ const next = new Set(current);
94
+ for (const key of requested) if (include(next.has(key))) next.add(key);
95
+ else next.delete(key);
96
+ return next.size !== current.size || [...next].some((key) => !current.has(key)) ? next : current;
97
+ }
98
+ /**
99
+ * Check whether a value has the shape required by one column cell.
100
+ *
101
+ * @param column - The column that owns the cell.
102
+ * @param value - The unknown value to inspect.
103
+ * @returns Whether the column can hold the value.
104
+ */
105
+ function matchesCell(column, value) {
106
+ if ((0, _orkestrel_contract.isString)(value) && value.length > 65536) return false;
107
+ switch (column.cell) {
108
+ case "text": return (0, _orkestrel_contract.isString)(value);
109
+ case "number": return (0, _orkestrel_contract.isFiniteNumber)(value);
110
+ case "flag": return (0, _orkestrel_contract.isBoolean)(value);
111
+ case "choice": return (0, _orkestrel_contract.isString)(value) && column.choices.some((choice) => choice.value === value);
112
+ }
113
+ }
114
+ /**
115
+ * Compare two cells in ascending order according to one column.
116
+ *
117
+ * @param column - The column that fixes the comparison.
118
+ * @param left - The first cell, or absence.
119
+ * @param right - The second cell, or absence.
120
+ * @returns A negative number, positive number, or zero in sort-comparator form.
121
+ */
122
+ function compareCells(column, left, right) {
123
+ if (left === void 0) return right === void 0 ? 0 : -1;
124
+ if (right === void 0) return 1;
125
+ switch (column.cell) {
126
+ case "text":
127
+ if (!(0, _orkestrel_contract.isString)(left) || !(0, _orkestrel_contract.isString)(right)) return 0;
128
+ return left < right ? -1 : left > right ? 1 : 0;
129
+ case "number":
130
+ if (!(0, _orkestrel_contract.isFiniteNumber)(left) || !(0, _orkestrel_contract.isFiniteNumber)(right)) return 0;
131
+ return left - right;
132
+ case "flag":
133
+ if (!(0, _orkestrel_contract.isBoolean)(left) || !(0, _orkestrel_contract.isBoolean)(right)) return 0;
134
+ return left === right ? 0 : left ? 1 : -1;
135
+ case "choice":
136
+ if (!(0, _orkestrel_contract.isString)(left) || !(0, _orkestrel_contract.isString)(right)) return 0;
137
+ return column.choices.findIndex((choice) => choice.value === left) - column.choices.findIndex((choice) => choice.value === right);
138
+ }
139
+ }
140
+ /**
141
+ * Check whether one column admits a filter and all its operands.
142
+ *
143
+ * @param column - The column that fixes the accepted operators and cell shapes.
144
+ * @param filter - The filter to inspect.
145
+ * @returns Whether the filter belongs to the column and the column can apply it.
146
+ */
147
+ function admitsFilter(column, filter) {
148
+ if (filter.column !== column.key) return false;
149
+ switch (filter.operator) {
150
+ case "contains": return (column.cell === "text" || column.cell === "choice") && filter.text.length <= 65536;
151
+ case "between": return (column.cell === "text" || column.cell === "number") && matchesCell(column, filter.minimum) && matchesCell(column, filter.maximum);
152
+ case "equals": return matchesCell(column, filter.value);
153
+ }
154
+ }
155
+ /**
156
+ * Test one cell against a filter according to its column.
157
+ *
158
+ * @param column - The column that fixes the accepted operators.
159
+ * @param cell - The cell to test, or absence.
160
+ * @param filter - The filter to apply.
161
+ * @returns Whether the filter accepts the cell.
162
+ */
163
+ function matchesFilter(column, cell, filter) {
164
+ if (cell === void 0 || !admitsFilter(column, filter) || !matchesCell(column, cell)) return false;
165
+ switch (filter.operator) {
166
+ case "contains": return (0, _orkestrel_contract.isString)(cell) && cell.includes(filter.text);
167
+ case "between": return compareCells(column, cell, filter.minimum) >= 0 && compareCells(column, cell, filter.maximum) <= 0;
168
+ case "equals": return cell === filter.value;
169
+ }
170
+ }
171
+ /**
172
+ * Keep the rows accepted by every filter.
173
+ *
174
+ * @param schema - The schema that declares the filtered columns.
175
+ * @param rows - The rows to filter.
176
+ * @param filters - The filters to apply with and-only composition.
177
+ * @param matchers - Optional per-column replacements for the default matcher.
178
+ * @returns A frozen copy of the accepted rows in their original order.
179
+ */
180
+ function filterRows(schema, rows, filters, matchers) {
181
+ return Object.freeze(rows.filter((row) => filters.every((filter) => {
182
+ const column = extractColumn(schema, filter.column);
183
+ if (column === void 0) return false;
184
+ const matcher = matchers !== void 0 && Object.hasOwn(matchers, column.key) ? matchers[column.key] : void 0;
185
+ const cell = Object.hasOwn(row, column.key) ? row[column.key] : void 0;
186
+ return matcher === void 0 ? matchesFilter(column, cell, filter) : matcher(cell, filter);
187
+ })));
188
+ }
189
+ /**
190
+ * Order rows stably by a sequence of terms.
191
+ *
192
+ * @param schema - The schema that declares the sorted columns.
193
+ * @param rows - The rows to order.
194
+ * @param orders - The ordered sort terms.
195
+ * @param comparators - Optional per-column replacements for the default comparator.
196
+ * @returns A frozen sorted copy that leaves the input untouched.
197
+ */
198
+ function sortRows(schema, rows, orders, comparators) {
199
+ const indexed = rows.map((row, index) => ({
200
+ row,
201
+ index
202
+ }));
203
+ indexed.sort((left, right) => {
204
+ for (const order of orders) {
205
+ const column = extractColumn(schema, order.column);
206
+ if (column === void 0) continue;
207
+ const comparator = comparators !== void 0 && Object.hasOwn(comparators, column.key) ? comparators[column.key] : void 0;
208
+ const leftCell = Object.hasOwn(left.row, column.key) ? left.row[column.key] : void 0;
209
+ const rightCell = Object.hasOwn(right.row, column.key) ? right.row[column.key] : void 0;
210
+ const compared = comparator === void 0 ? compareCells(column, leftCell, rightCell) : comparator(leftCell, rightCell);
211
+ if (compared !== 0 && !Number.isNaN(compared)) return order.direction === "ascending" ? compared : -compared;
212
+ }
213
+ return left.index - right.index;
214
+ });
215
+ return Object.freeze(indexed.map((entry) => entry.row));
216
+ }
217
+ /**
218
+ * Audit a structurally valid schema for domain and budget faults.
219
+ *
220
+ * @param schema - The table schema to audit.
221
+ * @returns Frozen human-readable diagnostics, or an empty list when the schema is sound.
222
+ */
223
+ function auditTable(schema) {
224
+ const faults = [];
225
+ const columns = /* @__PURE__ */ new Set();
226
+ let choiceExceeded;
227
+ let nameExceeded = schema.name !== void 0 && schema.name.length > 128;
228
+ if (schema.columns.length > 256) faults.push(`schema declares more than 256 columns`);
229
+ const columnCount = Math.min(schema.columns.length, 257);
230
+ for (let index = 0; index < columnCount; index += 1) {
231
+ const column = schema.columns[index];
232
+ if (column === void 0) continue;
233
+ if (column.key.length > 128) nameExceeded = true;
234
+ if (choiceExceeded === void 0 && column.cell === "choice" && column.choices.length > 1024) choiceExceeded = column.key;
235
+ }
236
+ if (choiceExceeded !== void 0) faults.push(`column "${choiceExceeded}" offers more than ${CHOICE_LIMIT} choices`);
237
+ if (nameExceeded) faults.push(`schema contains a name longer than 128`);
238
+ const pending = [schema];
239
+ const metadata = [false];
240
+ let position = 0;
241
+ let stringExceeded = false;
242
+ let textExceeded = false;
243
+ let nodeExceeded = false;
244
+ let text = 0;
245
+ while (position < pending.length) {
246
+ const node = pending[position];
247
+ const inMeta = metadata[position] === true;
248
+ position += 1;
249
+ if ((0, _orkestrel_contract.isString)(node)) {
250
+ if (node.length > 65536) stringExceeded = true;
251
+ text = Math.min(TEXT_LIMIT + 1, text + node.length);
252
+ if (text > 1048576) textExceeded = true;
253
+ continue;
254
+ }
255
+ if ((0, _orkestrel_contract.isArray)(node)) {
256
+ const read = (0, _orkestrel_contract.readArrayEntries)(node);
257
+ if (!read.success || !read.value.dense) continue;
258
+ for (const entry of read.value.entries) {
259
+ if (pending.length >= 16384) {
260
+ nodeExceeded = true;
261
+ continue;
262
+ }
263
+ pending.push(entry);
264
+ metadata.push(inMeta);
265
+ }
266
+ continue;
267
+ }
268
+ if (!(0, _orkestrel_contract.isRecord)(node)) continue;
269
+ const keys = (0, _orkestrel_contract.attempt)(() => Object.keys(node));
270
+ if (!keys.success) continue;
271
+ for (const key of keys.value) {
272
+ if (inMeta) {
273
+ if (key.length > 65536) stringExceeded = true;
274
+ text = Math.min(TEXT_LIMIT + 1, text + key.length);
275
+ if (text > 1048576) textExceeded = true;
276
+ }
277
+ const value = (0, _orkestrel_contract.attempt)(() => node[key]);
278
+ if (!value.success || value.value === void 0) continue;
279
+ if (pending.length >= 16384) {
280
+ nodeExceeded = true;
281
+ continue;
282
+ }
283
+ pending.push(value.value);
284
+ metadata.push(inMeta || key === "meta");
285
+ }
286
+ }
287
+ if (stringExceeded) faults.push(`schema contains a string longer than ${STRING_LIMIT}`);
288
+ if (textExceeded) faults.push(`schema retains more than ${TEXT_LIMIT} string code units`);
289
+ if (nodeExceeded) faults.push(`schema retains more than ${NODE_LIMIT} nodes`);
290
+ for (let index = 0; index < columnCount; index += 1) {
291
+ const column = schema.columns[index];
292
+ if (column === void 0) continue;
293
+ if (!nodeExceeded && column.meta !== void 0) {
294
+ if (!(0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(column.meta)).success) faults.push(`column "${column.key}" has metadata that cannot be owned`);
295
+ }
296
+ if (column.key.length === 0) faults.push("column \"\" has an empty key");
297
+ if (columns.has(column.key)) faults.push(`column "${column.key}" is declared more than once`);
298
+ columns.add(column.key);
299
+ if (column.cell === "choice") {
300
+ const choices = /* @__PURE__ */ new Set();
301
+ const choiceCount = Math.min(column.choices.length, CHOICE_LIMIT + 1);
302
+ for (let choiceIndex = 0; choiceIndex < choiceCount; choiceIndex += 1) {
303
+ const choice = column.choices[choiceIndex];
304
+ if (choice === void 0) continue;
305
+ if (choices.has(choice.value)) faults.push(`column "${column.key}" offers choice "${choice.value}" more than once`);
306
+ choices.add(choice.value);
307
+ }
308
+ if (column.choices.length === 0) faults.push(`column "${column.key}" offers no choices`);
309
+ }
310
+ }
311
+ const key = extractColumn(schema, schema.key);
312
+ if (key === void 0) faults.push(`schema key "${schema.key}" names no declared column`);
313
+ else if (key.cell === "number" || key.cell === "flag") faults.push(`schema key "${schema.key}" names a ${key.cell} column, which holds no identity`);
314
+ return Object.freeze(faults);
315
+ }
316
+ /**
317
+ * Project a schema into declaration-ordered JSON.
318
+ *
319
+ * @param schema - The schema to project.
320
+ * @returns A deeply owned JSON record with absent members omitted.
321
+ * @throws A {@link TableError} coded `SCHEMA` when metadata cannot be owned.
322
+ */
323
+ function serializeTable(schema) {
324
+ const output = {};
325
+ if (schema.name !== void 0) output.name = schema.name;
326
+ if (schema.label !== void 0) output.label = schema.label;
327
+ if (schema.help !== void 0) output.help = schema.help;
328
+ output.key = schema.key;
329
+ output.columns = schema.columns.map((column) => {
330
+ const entry = {
331
+ cell: column.cell,
332
+ key: column.key
333
+ };
334
+ if (column.label !== void 0) entry.label = column.label;
335
+ if (column.help !== void 0) entry.help = column.help;
336
+ if (column.hidden !== void 0) entry.hidden = column.hidden;
337
+ if (column.meta !== void 0) entry.meta = column.meta;
338
+ if (column.cell === "choice") entry.choices = column.choices.map((choice) => {
339
+ const option = {
340
+ value: choice.value,
341
+ label: choice.label
342
+ };
343
+ if (choice.help !== void 0) option.help = choice.help;
344
+ return option;
345
+ });
346
+ return entry;
347
+ });
348
+ try {
349
+ return (0, _orkestrel_contract.cloneJSONRecord)(output);
350
+ } catch (error) {
351
+ if (!(0, _orkestrel_contract.isContractError)(error)) throw error;
352
+ throw new TableError("SCHEMA", "schema contains metadata that cannot be owned");
353
+ }
354
+ }
355
+ /**
356
+ * Project rows into schema-column-ordered JSON.
357
+ *
358
+ * @param schema - The schema that fixes cell order.
359
+ * @param rows - The rows to project.
360
+ * @returns A frozen list of owned JSON records with absent cells omitted.
361
+ */
362
+ function serializeRows(schema, rows) {
363
+ const output = [];
364
+ for (const row of rows) {
365
+ const entry = {};
366
+ for (const column of schema.columns) {
367
+ if (!Object.hasOwn(row, column.key)) continue;
368
+ const value = row[column.key];
369
+ if (value === void 0) continue;
370
+ Object.defineProperty(entry, column.key, {
371
+ value,
372
+ enumerable: true,
373
+ configurable: true,
374
+ writable: true
375
+ });
376
+ }
377
+ output.push((0, _orkestrel_contract.cloneJSONRecord)(entry));
378
+ }
379
+ return Object.freeze(output);
380
+ }
381
+ //#endregion
382
+ //#region src/core/validators.ts
383
+ /**
384
+ * Determine whether an unknown value has a table cell shape.
385
+ *
386
+ * @param input - The value to inspect.
387
+ * @returns Whether the value is a string, finite number, or boolean.
388
+ */
389
+ function isTableCell(input) {
390
+ return (0, _orkestrel_contract.unionOf)(_orkestrel_contract.isString, _orkestrel_contract.isFiniteNumber, _orkestrel_contract.isBoolean)(input);
391
+ }
392
+ /**
393
+ * Determine whether an unknown value is a record of table cells.
394
+ *
395
+ * @param input - The value to inspect.
396
+ * @returns Whether every own key is a string and every value is a table cell.
397
+ */
398
+ function isTableRow(input) {
399
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
400
+ if (!(0, _orkestrel_contract.isRecord)(input)) return false;
401
+ return Reflect.ownKeys(input).every((key) => (0, _orkestrel_contract.isString)(key) && Object.hasOwn(input, key) && isTableCell(input[key]));
402
+ });
403
+ return outcome.success && outcome.value;
404
+ }
405
+ /**
406
+ * Determine whether an unknown value is a declared column cell.
407
+ *
408
+ * @param input - The value to inspect.
409
+ * @returns Whether the value is one of the four column cells.
410
+ */
411
+ function isColumnCell(input) {
412
+ return COLUMN_CELLS.some((cell) => cell === input);
413
+ }
414
+ /**
415
+ * Determine whether an unknown value is one exact column choice record.
416
+ *
417
+ * @param input - The value to inspect.
418
+ * @returns Whether the value is a column choice.
419
+ */
420
+ function isColumnChoice(input) {
421
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
422
+ if (!(0, _orkestrel_contract.isRecord)(input) || !Reflect.ownKeys(input).every((key) => (0, _orkestrel_contract.isString)(key))) return false;
423
+ return (0, _orkestrel_contract.recordOf)({
424
+ value: _orkestrel_contract.isString,
425
+ label: _orkestrel_contract.isString,
426
+ help: _orkestrel_contract.isString
427
+ }, ["help"])(input);
428
+ });
429
+ return outcome.success && outcome.value;
430
+ }
431
+ /**
432
+ * Determine whether an unknown value is one exact discriminated table column.
433
+ *
434
+ * @param input - The value to inspect.
435
+ * @returns Whether the value is a structurally valid table column.
436
+ */
437
+ function isTableColumn(input) {
438
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
439
+ if (!(0, _orkestrel_contract.isRecord)(input) || !Object.hasOwn(input, "cell") || !Object.hasOwn(input, "key")) return false;
440
+ const cell = input.cell;
441
+ if (!isColumnCell(cell)) return false;
442
+ if (!Reflect.ownKeys(input).every((key) => {
443
+ if (!(0, _orkestrel_contract.isString)(key)) return false;
444
+ if ([
445
+ "cell",
446
+ "key",
447
+ "label",
448
+ "help",
449
+ "hidden",
450
+ "meta"
451
+ ].includes(key)) return true;
452
+ return cell === "choice" && key === "choices";
453
+ })) return false;
454
+ const key = input.key;
455
+ const hasLabel = Object.hasOwn(input, "label");
456
+ const label = hasLabel ? input.label : void 0;
457
+ const hasHelp = Object.hasOwn(input, "help");
458
+ const help = hasHelp ? input.help : void 0;
459
+ const hasHidden = Object.hasOwn(input, "hidden");
460
+ const hidden = hasHidden ? input.hidden : void 0;
461
+ const hasMeta = Object.hasOwn(input, "meta");
462
+ const meta = hasMeta ? input.meta : void 0;
463
+ if (hasMeta) {
464
+ if (!(0, _orkestrel_contract.isBoundedJSONRecord)(meta)) return false;
465
+ if (!(0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(meta)).success) return false;
466
+ }
467
+ if (!(0, _orkestrel_contract.isString)(key) || hasLabel && !(0, _orkestrel_contract.isString)(label) || hasHelp && !(0, _orkestrel_contract.isString)(help) || hasHidden && !(0, _orkestrel_contract.isBoolean)(hidden)) return false;
468
+ if (cell !== "choice") return !Object.hasOwn(input, "choices");
469
+ return Object.hasOwn(input, "choices") && (0, _orkestrel_contract.arrayOf)(isColumnChoice)(input.choices);
470
+ });
471
+ return outcome.success && outcome.value;
472
+ }
473
+ /**
474
+ * Determine whether an unknown value has one exact structural table-schema shape.
475
+ *
476
+ * @param input - The value to inspect.
477
+ * @returns Whether the value has the exact structure of a table schema.
478
+ */
479
+ function isStructuralTableSchema(input) {
480
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
481
+ if (!(0, _orkestrel_contract.isRecord)(input) || !Reflect.ownKeys(input).every((key) => (0, _orkestrel_contract.isString)(key))) return false;
482
+ return (0, _orkestrel_contract.recordOf)({
483
+ name: _orkestrel_contract.isString,
484
+ label: _orkestrel_contract.isString,
485
+ help: _orkestrel_contract.isString,
486
+ key: _orkestrel_contract.isString,
487
+ columns: (0, _orkestrel_contract.arrayOf)(isTableColumn)
488
+ }, [
489
+ "name",
490
+ "label",
491
+ "help"
492
+ ])(input);
493
+ });
494
+ return outcome.success && outcome.value;
495
+ }
496
+ /**
497
+ * Determine whether an unknown value is one semantically sound table schema.
498
+ *
499
+ * @param input - The value to inspect.
500
+ * @returns Whether the value has valid structure, domain relationships, and budgets.
501
+ */
502
+ function isTableSchema(input) {
503
+ const outcome = (0, _orkestrel_contract.attempt)(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
504
+ return outcome.success && outcome.value;
505
+ }
506
+ //#endregion
507
+ //#region src/core/cloners.ts
508
+ /**
509
+ * Clone one row into an owned frozen snapshot.
510
+ *
511
+ * @param row - The row to own.
512
+ * @returns A frozen copy of the row's cells.
513
+ */
514
+ function cloneRow(row) {
515
+ return Object.freeze({ ...row });
516
+ }
517
+ /**
518
+ * Clone a table schema into an owned frozen snapshot.
519
+ *
520
+ * @param schema - The schema to own.
521
+ * @returns A frozen schema with every nested column, choice, list, and metadata record owned.
522
+ */
523
+ function cloneSchema(schema) {
524
+ return Object.freeze({
525
+ ...schema,
526
+ columns: Object.freeze(schema.columns.map((column) => {
527
+ let meta = {};
528
+ if (column.meta !== void 0) try {
529
+ meta = { meta: (0, _orkestrel_contract.cloneJSONRecord)(column.meta) };
530
+ } catch (error) {
531
+ if (!(0, _orkestrel_contract.isContractError)(error)) throw error;
532
+ throw new TableError("SCHEMA", `column "${column.key}" has metadata that cannot be owned`, { column: column.key });
533
+ }
534
+ if (column.cell === "choice") return Object.freeze({
535
+ ...column,
536
+ ...meta,
537
+ choices: Object.freeze(column.choices.map((choice) => Object.freeze({ ...choice })))
538
+ });
539
+ return Object.freeze({
540
+ ...column,
541
+ ...meta
542
+ });
543
+ }))
544
+ });
545
+ }
546
+ //#endregion
547
+ //#region src/core/parsers.ts
548
+ /**
549
+ * Parse unknown wire data into an owned, semantically sound table schema.
550
+ *
551
+ * @param input - The unknown schema value to parse.
552
+ * @returns An owned table schema, or `undefined` on refusal.
553
+ */
554
+ function parseTable(input) {
555
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
556
+ if (!isTableSchema(input)) return void 0;
557
+ const projected = serializeTable(input);
558
+ return isTableSchema(projected) ? projected : void 0;
559
+ });
560
+ return outcome.success ? outcome.value : void 0;
561
+ }
562
+ /**
563
+ * Parse unknown wire rows against one table schema.
564
+ *
565
+ * @param schema - The schema that declares the accepted keys and cell shapes.
566
+ * @param input - The unknown row-list value to parse.
567
+ * @returns Frozen owned rows, or `undefined` when any row is refused.
568
+ */
569
+ function parseRows(schema, input) {
570
+ const outcome = (0, _orkestrel_contract.attempt)(() => {
571
+ if (!isTableSchema(schema) || !(0, _orkestrel_contract.isArray)(input)) return;
572
+ const read = (0, _orkestrel_contract.readArrayEntries)(input);
573
+ if (!read.success || !read.value.dense) return void 0;
574
+ const keys = /* @__PURE__ */ new Set();
575
+ const rows = [];
576
+ for (const candidate of read.value.entries) {
577
+ if (!(0, _orkestrel_contract.isRecord)(candidate)) return void 0;
578
+ const row = {};
579
+ for (const key of Reflect.ownKeys(candidate)) {
580
+ if (!(0, _orkestrel_contract.isString)(key) || !Object.hasOwn(candidate, key)) return void 0;
581
+ const column = extractColumn(schema, key);
582
+ if (column === void 0) return void 0;
583
+ const inputCell = candidate[key];
584
+ let cell = inputCell;
585
+ if (column.cell === "number" && (0, _orkestrel_contract.isString)(inputCell)) {
586
+ if (inputCell.length > 65536) return void 0;
587
+ cell = (0, _orkestrel_contract.parseNumber)(inputCell);
588
+ } else if (column.cell === "flag" && inputCell === "true") cell = true;
589
+ else if (column.cell === "flag" && inputCell === "false") cell = false;
590
+ if (!matchesCell(column, cell)) return void 0;
591
+ Object.defineProperty(row, key, {
592
+ value: cell,
593
+ enumerable: true,
594
+ configurable: true,
595
+ writable: true
596
+ });
597
+ }
598
+ const owned = cloneRow(row);
599
+ const key = extractKey(schema, owned);
600
+ if (key === void 0 || keys.has(key)) return void 0;
601
+ keys.add(key);
602
+ rows.push(owned);
603
+ }
604
+ return Object.freeze(rows);
605
+ });
606
+ return outcome.success ? outcome.value : void 0;
607
+ }
608
+ //#endregion
609
+ //#region src/core/tables/ExpansionManager.ts
610
+ /** The keys of the rows somebody has opened. */
611
+ var ExpansionManager = class {
612
+ #emitter;
613
+ #gate;
614
+ #rows;
615
+ #read;
616
+ #write;
617
+ /**
618
+ * Create an expansion manager over one table's private stores.
619
+ *
620
+ * @param emitter - The table's event emitter.
621
+ * @param gate - The table lifecycle gate.
622
+ * @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.
625
+ */
626
+ constructor(emitter, gate, rows, read, write) {
627
+ this.#emitter = emitter;
628
+ this.#gate = gate;
629
+ this.#rows = rows;
630
+ this.#read = read;
631
+ this.#write = write;
632
+ }
633
+ /** The keys of the rows opened right now. */
634
+ get keys() {
635
+ return new Set(this.#read());
636
+ }
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) {
649
+ this.#gate();
650
+ return this.#change(input, (included) => !included) === true;
651
+ }
652
+ #change(input, include) {
653
+ const previous = this.#read();
654
+ const next = computeKeys(this.#rows(), previous, input, include);
655
+ if (next === void 0) return false;
656
+ if (next !== previous) {
657
+ this.#write(next);
658
+ this.#emitter.emit("expand", new Set(next));
659
+ }
660
+ return input === void 0 ? void 0 : true;
661
+ }
662
+ };
663
+ //#endregion
664
+ //#region src/core/tables/FilterManager.ts
665
+ /** The filters one table applies with and-only composition. */
666
+ var FilterManager = class {
667
+ #schema;
668
+ #emitter;
669
+ #gate;
670
+ #read;
671
+ #write;
672
+ #clamp;
673
+ /**
674
+ * Create a filter manager over one table's private filter store.
675
+ *
676
+ * @param schema - The table schema.
677
+ * @param emitter - The table's event emitter.
678
+ * @param gate - The table lifecycle gate.
679
+ * @param read - A read of the current filters.
680
+ * @param write - The filter commit boundary.
681
+ * @param clamp - The pagination clamp commit after a filter commit.
682
+ */
683
+ constructor(schema, emitter, gate, read, write, clamp) {
684
+ this.#schema = schema;
685
+ this.#emitter = emitter;
686
+ this.#gate = gate;
687
+ this.#read = read;
688
+ this.#write = write;
689
+ this.#clamp = clamp;
690
+ }
691
+ /** Find one column's filter. */
692
+ filter(column) {
693
+ const filter = this.#read().find((candidate) => candidate.column === column);
694
+ return filter === void 0 ? void 0 : Object.freeze({ ...filter });
695
+ }
696
+ /** Read every filter as an owned frozen snapshot. */
697
+ filters() {
698
+ return Object.freeze(this.#read().map((filter) => Object.freeze({ ...filter })));
699
+ }
700
+ /** Filter one column or several. */
701
+ set(input) {
702
+ this.#gate();
703
+ const requested = Array.isArray(input) ? input : [input];
704
+ 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));
714
+ const page = this.#clamp();
715
+ this.#emitter.emit("filter", this.filters());
716
+ if (page !== void 0) this.#emitter.emit("paginate", page);
717
+ }
718
+ /** Stop filtering by one or more columns. */
719
+ remove(input) {
720
+ this.#gate();
721
+ const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
722
+ 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));
725
+ if (next.length !== this.#read().length) {
726
+ this.#write(Object.freeze(next));
727
+ const page = this.#clamp();
728
+ this.#emitter.emit("filter", this.filters());
729
+ if (page !== void 0) this.#emitter.emit("paginate", page);
730
+ }
731
+ return input === void 0 ? void 0 : true;
732
+ }
733
+ #validate(filter) {
734
+ const column = extractColumn(this.#schema, filter.column);
735
+ if (column === void 0) throw new TableError("COLUMN", `The schema declares no column named "${filter.column}"`, { column: filter.column });
736
+ if (!admitsFilter(column, filter)) throw new TableError("CELL", `Column "${filter.column}" cannot apply that filter`, { column: filter.column });
737
+ }
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
+ });
746
+ }
747
+ };
748
+ //#endregion
749
+ //#region src/core/tables/PaginationManager.ts
750
+ /** The page arithmetic over one table's filtered rows. */
751
+ var PaginationManager = class {
752
+ #emitter;
753
+ #gate;
754
+ #rows;
755
+ #readPage;
756
+ #writePage;
757
+ #readLimit;
758
+ #writeLimit;
759
+ /**
760
+ * Create a pagination manager over one table's private stores.
761
+ *
762
+ * @param emitter - The table's event emitter.
763
+ * @param gate - The table lifecycle gate.
764
+ * @param rows - A read of the filtered row count.
765
+ * @param readPage - A read of the current page.
766
+ * @param writePage - The page commit boundary.
767
+ * @param readLimit - A read of the current page size.
768
+ * @param writeLimit - The page-size commit boundary.
769
+ */
770
+ constructor(emitter, gate, rows, readPage, writePage, readLimit, writeLimit) {
771
+ this.#emitter = emitter;
772
+ this.#gate = gate;
773
+ this.#rows = rows;
774
+ this.#readPage = readPage;
775
+ this.#writePage = writePage;
776
+ this.#readLimit = readLimit;
777
+ this.#writeLimit = writeLimit;
778
+ const limit = this.#readLimit();
779
+ if (limit !== void 0) this.#writeLimit(this.#normalize(limit));
780
+ }
781
+ /** The page shown, counted from one. */
782
+ get page() {
783
+ return this.#readLimit() === void 0 ? 1 : this.#readPage();
784
+ }
785
+ /** The number of rows one page holds. */
786
+ get limit() {
787
+ return this.#readLimit();
788
+ }
789
+ /** The number of filtered rows skipped before this page. */
790
+ get offset() {
791
+ const limit = this.#readLimit();
792
+ return limit === void 0 ? 0 : (this.#readPage() - 1) * limit;
793
+ }
794
+ /** The number of pages filled by the filtered rows. */
795
+ get count() {
796
+ const limit = this.#readLimit();
797
+ return limit === void 0 ? 1 : Math.max(1, Math.ceil(this.#rows() / limit));
798
+ }
799
+ /** Show another page, clamped to the pages that exist. */
800
+ move(page) {
801
+ this.#gate();
802
+ const next = this.#readLimit() === void 0 ? 1 : Math.min(this.count, this.#normalize(page));
803
+ if (next === this.#readPage()) return;
804
+ this.#writePage(next);
805
+ this.#emitter.emit("paginate", next);
806
+ }
807
+ /** Change the page size while keeping the first row previously shown. */
808
+ resize(limit) {
809
+ this.#gate();
810
+ const previous = this.#readLimit();
811
+ const nextLimit = limit === void 0 ? void 0 : this.#normalize(limit);
812
+ if (previous === nextLimit) return;
813
+ const anchor = previous === void 0 ? 0 : (this.#readPage() - 1) * previous;
814
+ this.#writeLimit(nextLimit);
815
+ const nextPage = nextLimit === void 0 ? 1 : Math.min(Math.max(1, Math.floor(anchor / nextLimit) + 1), this.count);
816
+ this.#writePage(nextPage);
817
+ this.#emitter.emit("paginate", nextPage);
818
+ }
819
+ #normalize(value) {
820
+ return Number.isFinite(value) ? Math.max(1, Math.trunc(value)) : 1;
821
+ }
822
+ };
823
+ //#endregion
824
+ //#region src/core/tables/RowManager.ts
825
+ /** The rows one table holds in its own order. */
826
+ var RowManager = class {
827
+ #schema;
828
+ #emitter;
829
+ #gate;
830
+ #read;
831
+ #write;
832
+ #settle;
833
+ /**
834
+ * Create a row manager over one table's private row store.
835
+ *
836
+ * @param schema - The table schema.
837
+ * @param emitter - The table's event emitter.
838
+ * @param gate - The table lifecycle gate.
839
+ * @param read - A read of the current rows.
840
+ * @param write - The row commit boundary.
841
+ * @param settle - Commit dependent state, then order row and dependent announcements.
842
+ * @param rows - Rows to seed without announcements.
843
+ */
844
+ constructor(schema, emitter, gate, read, write, settle, rows = []) {
845
+ this.#schema = schema;
846
+ this.#emitter = emitter;
847
+ this.#gate = gate;
848
+ this.#read = read;
849
+ this.#write = write;
850
+ this.#settle = settle;
851
+ const seeded = this.#prepare(rows, /* @__PURE__ */ new Set());
852
+ if (seeded.length > 0) this.#write(Object.freeze(seeded));
853
+ }
854
+ /** Find one row by key as an owned frozen snapshot. */
855
+ row(key) {
856
+ const row = this.#read().find((candidate) => extractKey(this.#schema, candidate) === key);
857
+ return row === void 0 ? void 0 : cloneRow(row);
858
+ }
859
+ /** Read every row as owned frozen snapshots in table order. */
860
+ rows() {
861
+ return Object.freeze(this.#read().map((row) => cloneRow(row)));
862
+ }
863
+ /** Append one row or several. */
864
+ add(input) {
865
+ this.#gate();
866
+ const rows = Array.isArray(input) ? input : [input];
867
+ const keys = /* @__PURE__ */ new Set();
868
+ for (const row of this.#read()) {
869
+ const key = extractKey(this.#schema, row);
870
+ if (key !== void 0) keys.add(key);
871
+ }
872
+ const added = this.#prepare(rows, keys);
873
+ if (added.length === 0) return;
874
+ this.#write(Object.freeze([...this.#read(), ...added]));
875
+ this.#settle([], () => {
876
+ for (const row of added) {
877
+ const key = extractKey(this.#schema, row);
878
+ if (key !== void 0) this.#emitter.emit("write", key);
879
+ }
880
+ });
881
+ }
882
+ /** Merge one row or several into the rows their keys name. */
883
+ update(input) {
884
+ this.#gate();
885
+ const updates = (Array.isArray(input) ? input : [input]).map((row) => cloneRow(row));
886
+ const current = this.#read();
887
+ const locations = [];
888
+ for (const update of updates) {
889
+ this.#validate(update);
890
+ const key = extractKey(this.#schema, update);
891
+ if (key === void 0) this.#failKey("A row has no usable identity");
892
+ const index = current.findIndex((row) => extractKey(this.#schema, row) === key);
893
+ if (index === -1) return false;
894
+ locations.push(index);
895
+ }
896
+ const next = [...current];
897
+ const moved = [];
898
+ for (let index = 0; index < updates.length; index += 1) {
899
+ const update = updates[index];
900
+ const location = locations[index];
901
+ if (update === void 0 || location === void 0) continue;
902
+ const previous = next[location];
903
+ if (previous === void 0) continue;
904
+ const merged = cloneRow({
905
+ ...previous,
906
+ ...update
907
+ });
908
+ if (!this.#same(previous, merged)) {
909
+ next[location] = merged;
910
+ const key = extractKey(this.#schema, merged);
911
+ if (key !== void 0) moved.push(key);
912
+ }
913
+ }
914
+ if (moved.length === 0) return true;
915
+ this.#write(Object.freeze(next));
916
+ this.#settle([], () => {
917
+ for (const key of moved) this.#emitter.emit("write", key);
918
+ });
919
+ return true;
920
+ }
921
+ /** Move one row to a clamped index in table order. */
922
+ move(key, index) {
923
+ this.#gate();
924
+ const current = this.#read();
925
+ const origin = current.findIndex((row) => extractKey(this.#schema, row) === key);
926
+ if (origin === -1) return false;
927
+ const target = Math.min(current.length - 1, Number.isFinite(index) ? Math.max(0, Math.trunc(index)) : 0);
928
+ if (origin === target) return true;
929
+ const row = current[origin];
930
+ if (row === void 0) return false;
931
+ const next = [...current];
932
+ next.splice(origin, 1);
933
+ next.splice(target, 0, row);
934
+ this.#write(Object.freeze(next));
935
+ this.#settle([], () => this.#emitter.emit("write", key));
936
+ return true;
937
+ }
938
+ /** Remove one or more rows. */
939
+ remove(input) {
940
+ this.#gate();
941
+ const current = this.#read();
942
+ const requested = input === void 0 ? current.flatMap((row) => {
943
+ const key = extractKey(this.#schema, row);
944
+ return key === void 0 ? [] : [key];
945
+ }) : Array.isArray(input) ? input : [input];
946
+ const keys = new Set(requested);
947
+ const known = new Set(current.flatMap((row) => {
948
+ const key = extractKey(this.#schema, row);
949
+ return key === void 0 ? [] : [key];
950
+ }));
951
+ if ([...keys].some((key) => !known.has(key))) return false;
952
+ if (keys.size === 0) return input === void 0 ? void 0 : true;
953
+ const removed = current.flatMap((row) => {
954
+ const key = extractKey(this.#schema, row);
955
+ return key !== void 0 && keys.has(key) ? [key] : [];
956
+ });
957
+ this.#write(Object.freeze(current.filter((row) => {
958
+ const key = extractKey(this.#schema, row);
959
+ return key === void 0 || !keys.has(key);
960
+ })));
961
+ this.#settle(removed, () => {
962
+ for (const key of removed) this.#emitter.emit("remove", key);
963
+ });
964
+ return input === void 0 ? void 0 : true;
965
+ }
966
+ #prepare(rows, existing) {
967
+ const owned = [];
968
+ for (const row of rows) {
969
+ const snapshot = cloneRow(row);
970
+ this.#validate(snapshot);
971
+ const key = extractKey(this.#schema, snapshot);
972
+ if (key === void 0) this.#failKey("A row has no usable identity");
973
+ if (existing.has(key)) this.#failKey(`Row key "${key}" is already taken`, key);
974
+ existing.add(key);
975
+ owned.push(snapshot);
976
+ }
977
+ return owned;
978
+ }
979
+ #validate(row) {
980
+ if (extractKey(this.#schema, row) === void 0) this.#failKey("A row has no usable identity");
981
+ if (!isTableRow(row)) throw new TableError("CELL", "A row contains a value no table cell can hold");
982
+ for (const key of Object.keys(row)) {
983
+ const column = extractColumn(this.#schema, key);
984
+ if (column === void 0 || !matchesCell(column, row[key])) throw new TableError("CELL", `Column "${key}" cannot hold that cell`, { column: key });
985
+ }
986
+ }
987
+ #failKey(message, key) {
988
+ if (key === void 0) throw new TableError("KEY", message);
989
+ throw new TableError("KEY", message, { key });
990
+ }
991
+ #same(left, right) {
992
+ const leftKeys = Object.keys(left);
993
+ const rightKeys = Object.keys(right);
994
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && left[key] === right[key]);
995
+ }
996
+ };
997
+ //#endregion
998
+ //#region src/core/tables/SelectionManager.ts
999
+ /** The keys of the rows somebody has picked. */
1000
+ var SelectionManager = class {
1001
+ #emitter;
1002
+ #gate;
1003
+ #rows;
1004
+ #read;
1005
+ #write;
1006
+ /**
1007
+ * Create a selection manager over one table's private stores.
1008
+ *
1009
+ * @param emitter - The table's event emitter.
1010
+ * @param gate - The table lifecycle gate.
1011
+ * @param rows - A read of every row key.
1012
+ * @param read - A read of the selected keys.
1013
+ * @param write - The selected-key commit boundary.
1014
+ */
1015
+ 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;
1021
+ }
1022
+ /** The keys of the rows picked right now. */
1023
+ get keys() {
1024
+ return new Set(this.#read());
1025
+ }
1026
+ /** Pick one or more rows. */
1027
+ select(input) {
1028
+ this.#gate();
1029
+ return this.#change(input, () => true);
1030
+ }
1031
+ /** Drop one or more picks. */
1032
+ clear(input) {
1033
+ this.#gate();
1034
+ return this.#change(input, () => false);
1035
+ }
1036
+ /** Turn one or more rows around independently. */
1037
+ 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;
1050
+ }
1051
+ };
1052
+ //#endregion
1053
+ //#region src/core/tables/SortManager.ts
1054
+ /** The ordered sort terms of one table. */
1055
+ var SortManager = class {
1056
+ #schema;
1057
+ #emitter;
1058
+ #gate;
1059
+ #read;
1060
+ #write;
1061
+ /**
1062
+ * Create a sort manager over one table's private term store.
1063
+ *
1064
+ * @param schema - The table schema.
1065
+ * @param emitter - The table's event emitter.
1066
+ * @param gate - The table lifecycle gate.
1067
+ * @param read - A read of the current terms.
1068
+ * @param write - The term commit boundary.
1069
+ */
1070
+ constructor(schema, emitter, gate, read, write) {
1071
+ this.#schema = schema;
1072
+ this.#emitter = emitter;
1073
+ this.#gate = gate;
1074
+ this.#read = read;
1075
+ this.#write = write;
1076
+ }
1077
+ /** Find one column's sort term. */
1078
+ order(column) {
1079
+ const order = this.#read().find((candidate) => candidate.column === column);
1080
+ return order === void 0 ? void 0 : Object.freeze({ ...order });
1081
+ }
1082
+ /** Read every sort term as an owned frozen snapshot. */
1083
+ orders() {
1084
+ return Object.freeze(this.#read().map((order) => Object.freeze({ ...order })));
1085
+ }
1086
+ /** Sort by one column or several. */
1087
+ set(input) {
1088
+ this.#gate();
1089
+ const requested = Array.isArray(input) ? input : [input];
1090
+ 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);
1101
+ this.#emitter.emit("sort", this.orders());
1102
+ }
1103
+ /** Stop sorting by one or more columns. */
1104
+ remove(input) {
1105
+ this.#gate();
1106
+ const columns = input === void 0 ? this.#schema.columns.map((column) => column.key) : Array.isArray(input) ? input : [input];
1107
+ 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));
1110
+ if (next.length !== this.#read().length) {
1111
+ this.#write(Object.freeze(next));
1112
+ this.#emitter.emit("sort", this.orders());
1113
+ }
1114
+ return input === void 0 ? void 0 : true;
1115
+ }
1116
+ #require(column) {
1117
+ if (extractColumn(this.#schema, column) === void 0) throw new TableError("COLUMN", `The schema declares no column named "${column}"`, { column });
1118
+ }
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
+ };
1126
+ //#endregion
1127
+ //#region src/core/Table.ts
1128
+ /** A schema, its rows, and the lens through which they are read. */
1129
+ var Table = class {
1130
+ #emitter;
1131
+ #schema;
1132
+ #comparators;
1133
+ #matchers;
1134
+ #initialLimit;
1135
+ #rowStore = Object.freeze([]);
1136
+ #orderStore = Object.freeze([]);
1137
+ #filterStore = Object.freeze([]);
1138
+ #selected = /* @__PURE__ */ new Set();
1139
+ #expanded = /* @__PURE__ */ new Set();
1140
+ #page = 1;
1141
+ #limit;
1142
+ #destroyed = false;
1143
+ #rows;
1144
+ #sort;
1145
+ #filter;
1146
+ #selection;
1147
+ #expansion;
1148
+ #pagination;
1149
+ /**
1150
+ * Open a table against a schema.
1151
+ *
1152
+ * @param schema - The table declaration to own.
1153
+ * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
1154
+ * @throws A {@link TableError} coded `SCHEMA` when the schema is unusable, `KEY` when a seeded
1155
+ * identity is unusable or repeated, and `CELL` when a seeded cell is invalid.
1156
+ */
1157
+ constructor(schema, options) {
1158
+ const problems = isStructuralTableSchema(schema) ? auditTable(schema) : ["The schema is not a table schema"];
1159
+ if (problems.length > 0) throw new TableError("SCHEMA", `The table schema is unusable: ${problems.join("; ")}`, { problems: [...problems] });
1160
+ this.#schema = cloneSchema(schema);
1161
+ this.#comparators = options?.comparators === void 0 ? void 0 : Object.freeze({ ...options.comparators });
1162
+ this.#matchers = options?.matchers === void 0 ? void 0 : Object.freeze({ ...options.matchers });
1163
+ this.#limit = options?.limit;
1164
+ this.#emitter = new _orkestrel_emitter.Emitter({
1165
+ ...options?.on === void 0 ? {} : { on: options.on },
1166
+ ...options?.error === void 0 ? {} : { error: options.error }
1167
+ });
1168
+ this.#selection = new SelectionManager(this.#emitter, () => this.#gate(), () => this.#keys(), () => this.#selected, (keys) => {
1169
+ this.#selected = keys;
1170
+ });
1171
+ this.#expansion = new ExpansionManager(this.#emitter, () => this.#gate(), () => this.#keys(), () => this.#expanded, (keys) => {
1172
+ this.#expanded = keys;
1173
+ });
1174
+ this.#pagination = new PaginationManager(this.#emitter, () => this.#gate(), () => this.count, () => this.#page, (page) => {
1175
+ this.#page = page;
1176
+ }, () => this.#limit, (limit) => {
1177
+ this.#limit = limit;
1178
+ });
1179
+ this.#initialLimit = this.#limit;
1180
+ this.#sort = new SortManager(this.#schema, this.#emitter, () => this.#gate(), () => this.#orderStore, (orders) => {
1181
+ this.#orderStore = orders;
1182
+ });
1183
+ this.#filter = new FilterManager(this.#schema, this.#emitter, () => this.#gate(), () => this.#filterStore, (filters) => {
1184
+ this.#filterStore = filters;
1185
+ }, () => this.#clamp());
1186
+ this.#rows = new RowManager(this.#schema, this.#emitter, () => this.#gate(), () => this.#rowStore, (rows) => {
1187
+ this.#rowStore = rows;
1188
+ }, (removed, announce) => this.#settle(removed, announce), options?.rows);
1189
+ }
1190
+ /** The table's event emitter. */
1191
+ get emitter() {
1192
+ return this.#emitter;
1193
+ }
1194
+ /** The owned frozen schema. */
1195
+ get schema() {
1196
+ return this.#schema;
1197
+ }
1198
+ /** The rows the table holds. */
1199
+ get rows() {
1200
+ return this.#rows;
1201
+ }
1202
+ /** The ordered sort terms. */
1203
+ get sort() {
1204
+ return this.#sort;
1205
+ }
1206
+ /** The filters applied with and-only composition. */
1207
+ get filter() {
1208
+ return this.#filter;
1209
+ }
1210
+ /** The selected row keys. */
1211
+ get selection() {
1212
+ return this.#selection;
1213
+ }
1214
+ /** The expanded row keys. */
1215
+ get expansion() {
1216
+ return this.#expansion;
1217
+ }
1218
+ /** The page arithmetic. */
1219
+ get pagination() {
1220
+ return this.#pagination;
1221
+ }
1222
+ /** The filtered, sorted, and paged rows as owned frozen snapshots. */
1223
+ get view() {
1224
+ const ordered = sortRows(this.#schema, this.#filtered(), this.#orderStore, this.#comparators);
1225
+ const limit = this.#limit;
1226
+ const page = limit === void 0 ? ordered : ordered.slice(this.#pagination.offset, this.#pagination.offset + limit);
1227
+ return Object.freeze(page.map((row) => cloneRow(row)));
1228
+ }
1229
+ /** The number of rows admitted by the filters. */
1230
+ get count() {
1231
+ return this.#filtered().length;
1232
+ }
1233
+ /** Whether the table has been torn down. */
1234
+ get destroyed() {
1235
+ return this.#destroyed;
1236
+ }
1237
+ /** Reset every moving axis to its opening state. */
1238
+ clear() {
1239
+ this.#gate();
1240
+ 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;
1241
+ this.#rowStore = Object.freeze([]);
1242
+ this.#orderStore = Object.freeze([]);
1243
+ this.#filterStore = Object.freeze([]);
1244
+ this.#selected = /* @__PURE__ */ new Set();
1245
+ this.#expanded = /* @__PURE__ */ new Set();
1246
+ this.#page = 1;
1247
+ this.#limit = this.#initialLimit;
1248
+ this.#emitter.emit("clear");
1249
+ }
1250
+ /** Tear the table down while leaving every getter readable. */
1251
+ destroy() {
1252
+ if (this.#destroyed) return;
1253
+ this.#destroyed = true;
1254
+ this.#emitter.destroy();
1255
+ }
1256
+ #filtered() {
1257
+ return filterRows(this.#schema, this.#rowStore, this.#filterStore, this.#matchers);
1258
+ }
1259
+ #keys() {
1260
+ return this.#rowStore.flatMap((row) => {
1261
+ const key = extractKey(this.#schema, row);
1262
+ return key === void 0 ? [] : [key];
1263
+ });
1264
+ }
1265
+ #settle(removed, announce) {
1266
+ const keys = new Set(removed);
1267
+ const selected = new Set([...this.#selected].filter((key) => !keys.has(key)));
1268
+ const expanded = new Set([...this.#expanded].filter((key) => !keys.has(key)));
1269
+ const selectedChanged = selected.size !== this.#selected.size;
1270
+ const expandedChanged = expanded.size !== this.#expanded.size;
1271
+ if (selectedChanged) this.#selected = selected;
1272
+ if (expandedChanged) this.#expanded = expanded;
1273
+ const page = this.#clamp();
1274
+ announce();
1275
+ if (selectedChanged) this.#emitter.emit("select", new Set(selected));
1276
+ if (expandedChanged) this.#emitter.emit("expand", new Set(expanded));
1277
+ if (page !== void 0) this.#emitter.emit("paginate", page);
1278
+ }
1279
+ #clamp() {
1280
+ const page = Math.min(this.#page, this.#pagination.count);
1281
+ if (page === this.#page) return void 0;
1282
+ this.#page = page;
1283
+ return page;
1284
+ }
1285
+ #gate() {
1286
+ if (this.#destroyed) throw new TableError("DESTROYED", "The table was destroyed and cannot change");
1287
+ }
1288
+ };
1289
+ //#endregion
1290
+ //#region src/core/factories.ts
1291
+ /**
1292
+ * Open a table against a schema.
1293
+ *
1294
+ * @param schema - The table declaration to own.
1295
+ * @param options - Initial rows, lens overrides, pagination, and emitter wiring.
1296
+ * @returns A live table interface.
1297
+ * @throws A {@link TableError} coded `SCHEMA` when the schema is unusable, `KEY` when a seeded
1298
+ * identity is unusable or repeated, and `CELL` when a seeded cell is invalid.
1299
+ * @example
1300
+ * ```ts
1301
+ * const table = createTable({ key: 'id', columns: [{ cell: 'text', key: 'id' }] })
1302
+ * table.rows.add({ id: '1' })
1303
+ * ```
1304
+ */
1305
+ function createTable(schema, options) {
1306
+ return new Table(schema, options);
1307
+ }
1308
+ //#endregion
1309
+ exports.CHOICE_LIMIT = CHOICE_LIMIT;
1310
+ exports.COLUMN_CELLS = COLUMN_CELLS;
1311
+ exports.COLUMN_LIMIT = COLUMN_LIMIT;
1312
+ exports.ExpansionManager = ExpansionManager;
1313
+ exports.FilterManager = FilterManager;
1314
+ exports.NAME_LIMIT = NAME_LIMIT;
1315
+ exports.NODE_LIMIT = NODE_LIMIT;
1316
+ exports.PaginationManager = PaginationManager;
1317
+ exports.RowManager = RowManager;
1318
+ exports.STRING_LIMIT = STRING_LIMIT;
1319
+ exports.SelectionManager = SelectionManager;
1320
+ exports.SortManager = SortManager;
1321
+ exports.TEXT_LIMIT = TEXT_LIMIT;
1322
+ exports.Table = Table;
1323
+ exports.TableError = TableError;
1324
+ exports.admitsFilter = admitsFilter;
1325
+ exports.auditTable = auditTable;
1326
+ exports.cloneRow = cloneRow;
1327
+ exports.cloneSchema = cloneSchema;
1328
+ exports.compareCells = compareCells;
1329
+ exports.computeKeys = computeKeys;
1330
+ exports.createTable = createTable;
1331
+ exports.extractColumn = extractColumn;
1332
+ exports.extractKey = extractKey;
1333
+ exports.filterRows = filterRows;
1334
+ exports.isColumnCell = isColumnCell;
1335
+ exports.isColumnChoice = isColumnChoice;
1336
+ exports.isStructuralTableSchema = isStructuralTableSchema;
1337
+ exports.isTableCell = isTableCell;
1338
+ exports.isTableColumn = isTableColumn;
1339
+ exports.isTableError = isTableError;
1340
+ exports.isTableRow = isTableRow;
1341
+ exports.isTableSchema = isTableSchema;
1342
+ exports.matchesCell = matchesCell;
1343
+ exports.matchesFilter = matchesFilter;
1344
+ exports.parseRows = parseRows;
1345
+ exports.parseTable = parseTable;
1346
+ exports.serializeRows = serializeRows;
1347
+ exports.serializeTable = serializeTable;
1348
+ exports.sortRows = sortRows;
1349
+
1350
+ //# sourceMappingURL=index.cjs.map