@orkestrel/table 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/browser/index.d.ts +8 -773
- package/dist/src/browser/index.js +34 -446
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +118 -92
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +11 -7
- package/dist/src/core/index.d.ts +11 -7
- package/dist/src/core/index.js +119 -93
- package/dist/src/core/index.js.map +1 -1
- package/package.json +18 -18
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INTRINSICS, attempt, holds, parseJSON, parseNumber } from "@orkestrel/contract";
|
|
2
|
+
import { extractKey, matchesTableRow, parseRows, parseTable } from "../core/index.js";
|
|
2
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
3
|
-
|
|
4
|
+
// src/browser/errors.ts
|
|
4
5
|
/** Represents a native table binding failure with a machine-readable code. */
|
|
5
6
|
var TableBindingError = class extends Error {
|
|
6
7
|
code;
|
|
@@ -10,435 +11,24 @@ var TableBindingError = class extends Error {
|
|
|
10
11
|
*/
|
|
11
12
|
constructor(code, message) {
|
|
12
13
|
super(message);
|
|
14
|
+
INTRINSICS.define(this, Symbol.for("@orkestrel/table.TableBindingError"), { value: this });
|
|
13
15
|
this.name = "TableBindingError";
|
|
14
16
|
this.code = code;
|
|
15
17
|
}
|
|
16
18
|
};
|
|
17
19
|
/** Checks whether a caught value is a native table binding error.
|
|
20
|
+
* @remarks
|
|
21
|
+
* Recognizes self-branded errors across copies and subclasses and refuses transparent proxies.
|
|
18
22
|
* @param value - The caught value.
|
|
19
23
|
* @returns Whether the value is a binding error.
|
|
20
24
|
*/
|
|
21
25
|
function isTableBindingError(value) {
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//#region src/core/errors.ts
|
|
26
|
-
/**
|
|
27
|
-
* Represents an error raised by the table domain — a machine-readable `code` and optional
|
|
28
|
-
* structured `context`.
|
|
29
|
-
*/
|
|
30
|
-
var TableError = class extends Error {
|
|
31
|
-
/** Holds the machine-readable reason for this failure. */
|
|
32
|
-
code;
|
|
33
|
-
/** Holds structured values that locate or explain this failure. */
|
|
34
|
-
context;
|
|
35
|
-
/**
|
|
36
|
-
* Creates a table error.
|
|
37
|
-
*
|
|
38
|
-
* @param code - The machine-readable reason.
|
|
39
|
-
* @param message - The human-readable failure text.
|
|
40
|
-
* @param context - Optional structured failure details.
|
|
41
|
-
*/
|
|
42
|
-
constructor(code, message, context) {
|
|
43
|
-
super(message);
|
|
44
|
-
this.name = "TableError";
|
|
45
|
-
this.code = code;
|
|
46
|
-
if (context !== void 0) this.context = context;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region src/core/cloners.ts
|
|
51
|
-
/**
|
|
52
|
-
* Clones one row into an owned frozen snapshot.
|
|
53
|
-
*
|
|
54
|
-
* @param row - The row to own.
|
|
55
|
-
* @returns A frozen copy of the row's cells.
|
|
56
|
-
*/
|
|
57
|
-
function cloneRow(row) {
|
|
58
|
-
return Object.freeze({ ...row });
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region src/core/constants.ts
|
|
62
|
-
/** Lists every column cell, in the order declared by the public contract. */
|
|
63
|
-
var COLUMN_CELLS = Object.freeze([
|
|
64
|
-
"text",
|
|
65
|
-
"number",
|
|
66
|
-
"flag",
|
|
67
|
-
"choice"
|
|
68
|
-
]);
|
|
69
|
-
/** Names the maximum number of choices one `choice` column may offer: 1024. */
|
|
70
|
-
var CHOICE_LIMIT = 1024;
|
|
71
|
-
/** Names the maximum length of any single retained string: 65536 UTF-16 code units. */
|
|
72
|
-
var STRING_LIMIT = 65536;
|
|
73
|
-
/**
|
|
74
|
-
* Names the maximum total length of every string one schema retains: 1048576 UTF-16 code units.
|
|
75
|
-
*/
|
|
76
|
-
var TEXT_LIMIT = 1048576;
|
|
77
|
-
/** Names the maximum total number of records, arrays, and leaves one schema retains: 16384. */
|
|
78
|
-
var NODE_LIMIT = 16384;
|
|
79
|
-
//#endregion
|
|
80
|
-
//#region src/core/helpers.ts
|
|
81
|
-
/**
|
|
82
|
-
* Finds one column by key; `undefined` when the schema declares no such column.
|
|
83
|
-
*
|
|
84
|
-
* @param schema - The schema whose columns to search.
|
|
85
|
-
* @param key - The column key to find.
|
|
86
|
-
* @returns The declared column, or `undefined` when no column has that key.
|
|
87
|
-
*/
|
|
88
|
-
function extractColumn(schema, key) {
|
|
89
|
-
return schema.columns.find((column) => column.key === key);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Reads one row's declared identity; `undefined` when its key cell is missing, empty, or not a
|
|
93
|
-
* string.
|
|
94
|
-
*
|
|
95
|
-
* @param schema - The schema that names the identity column.
|
|
96
|
-
* @param row - The row whose identity to read.
|
|
97
|
-
* @returns The non-empty string identity, or `undefined` when it is unusable.
|
|
98
|
-
*/
|
|
99
|
-
function extractKey(schema, row) {
|
|
100
|
-
if (!Object.hasOwn(row, schema.key)) return void 0;
|
|
101
|
-
const key = row[schema.key];
|
|
102
|
-
return isString(key) && key.length > 0 ? key : void 0;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Checks whether one column can hold a value — the shape gate every write and every seed passes
|
|
106
|
-
* through.
|
|
107
|
-
*
|
|
108
|
-
* @param column - The column that owns the cell.
|
|
109
|
-
* @param value - The unknown value to inspect.
|
|
110
|
-
* @returns True if the column can hold the value; false otherwise.
|
|
111
|
-
*/
|
|
112
|
-
function matchesCell(column, value) {
|
|
113
|
-
if (isString(value) && value.length > 65536) return false;
|
|
114
|
-
switch (column.cell) {
|
|
115
|
-
case "text": return isString(value);
|
|
116
|
-
case "number": return isFiniteNumber(value);
|
|
117
|
-
case "flag": return isBoolean(value);
|
|
118
|
-
case "choice": return isString(value) && column.choices.some((choice) => choice.value === value);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Audits a structurally valid schema for domain faults and budget breaches, returning human
|
|
123
|
-
* diagnostics.
|
|
124
|
-
*
|
|
125
|
-
* @param schema - The table schema to audit.
|
|
126
|
-
* @returns Frozen human-readable diagnostics, or an empty list when the schema is sound.
|
|
127
|
-
*/
|
|
128
|
-
function auditTable(schema) {
|
|
129
|
-
const faults = [];
|
|
130
|
-
const columns = /* @__PURE__ */ new Set();
|
|
131
|
-
let choiceExceeded;
|
|
132
|
-
let nameExceeded = schema.name !== void 0 && schema.name.length > 128;
|
|
133
|
-
if (schema.columns.length > 256) faults.push(`schema declares more than 256 columns`);
|
|
134
|
-
const columnCount = Math.min(schema.columns.length, 257);
|
|
135
|
-
for (let index = 0; index < columnCount; index += 1) {
|
|
136
|
-
const column = schema.columns[index];
|
|
137
|
-
if (column === void 0) continue;
|
|
138
|
-
if (column.key.length > 128) nameExceeded = true;
|
|
139
|
-
if (choiceExceeded === void 0 && column.cell === "choice" && column.choices.length > 1024) choiceExceeded = column.key;
|
|
140
|
-
}
|
|
141
|
-
if (choiceExceeded !== void 0) faults.push(`column "${choiceExceeded}" offers more than ${CHOICE_LIMIT} choices`);
|
|
142
|
-
if (nameExceeded) faults.push(`schema contains a name longer than 128`);
|
|
143
|
-
const pending = [schema];
|
|
144
|
-
const metadata = [false];
|
|
145
|
-
let position = 0;
|
|
146
|
-
let stringExceeded = false;
|
|
147
|
-
let textExceeded = false;
|
|
148
|
-
let nodeExceeded = false;
|
|
149
|
-
let text = 0;
|
|
150
|
-
while (position < pending.length) {
|
|
151
|
-
const node = pending[position];
|
|
152
|
-
const inMeta = metadata[position] === true;
|
|
153
|
-
position += 1;
|
|
154
|
-
if (isString(node)) {
|
|
155
|
-
if (node.length > 65536) stringExceeded = true;
|
|
156
|
-
text = Math.min(TEXT_LIMIT + 1, text + node.length);
|
|
157
|
-
if (text > 1048576) textExceeded = true;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (isArray(node)) {
|
|
161
|
-
const read = readArrayEntries(node);
|
|
162
|
-
if (!read.success || !read.value.dense) continue;
|
|
163
|
-
for (const entry of read.value.entries) {
|
|
164
|
-
if (pending.length >= 16384) {
|
|
165
|
-
nodeExceeded = true;
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
pending.push(entry);
|
|
169
|
-
metadata.push(inMeta);
|
|
170
|
-
}
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (!isRecord(node)) continue;
|
|
174
|
-
const keys = attempt(() => Object.keys(node));
|
|
175
|
-
if (!keys.success) continue;
|
|
176
|
-
for (const key of keys.value) {
|
|
177
|
-
if (inMeta) {
|
|
178
|
-
if (key.length > 65536) stringExceeded = true;
|
|
179
|
-
text = Math.min(TEXT_LIMIT + 1, text + key.length);
|
|
180
|
-
if (text > 1048576) textExceeded = true;
|
|
181
|
-
}
|
|
182
|
-
const value = attempt(() => node[key]);
|
|
183
|
-
if (!value.success || value.value === void 0) continue;
|
|
184
|
-
if (pending.length >= 16384) {
|
|
185
|
-
nodeExceeded = true;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
pending.push(value.value);
|
|
189
|
-
metadata.push(inMeta || key === "meta");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (stringExceeded) faults.push(`schema contains a string longer than ${STRING_LIMIT}`);
|
|
193
|
-
if (textExceeded) faults.push(`schema retains more than ${TEXT_LIMIT} string code units`);
|
|
194
|
-
if (nodeExceeded) faults.push(`schema retains more than ${NODE_LIMIT} nodes`);
|
|
195
|
-
for (let index = 0; index < columnCount; index += 1) {
|
|
196
|
-
const column = schema.columns[index];
|
|
197
|
-
if (column === void 0) continue;
|
|
198
|
-
if (!nodeExceeded && column.meta !== void 0) {
|
|
199
|
-
if (!attempt(() => cloneJSONRecord(column.meta)).success) faults.push(`column "${column.key}" has metadata that cannot be owned`);
|
|
200
|
-
}
|
|
201
|
-
if (column.key.length === 0) faults.push("column \"\" has an empty key");
|
|
202
|
-
if (columns.has(column.key)) faults.push(`column "${column.key}" is declared more than once`);
|
|
203
|
-
columns.add(column.key);
|
|
204
|
-
if (column.cell === "choice") {
|
|
205
|
-
const choices = /* @__PURE__ */ new Set();
|
|
206
|
-
const choiceCount = Math.min(column.choices.length, CHOICE_LIMIT + 1);
|
|
207
|
-
for (let choiceIndex = 0; choiceIndex < choiceCount; choiceIndex += 1) {
|
|
208
|
-
const choice = column.choices[choiceIndex];
|
|
209
|
-
if (choice === void 0) continue;
|
|
210
|
-
if (choices.has(choice.value)) faults.push(`column "${column.key}" offers choice "${choice.value}" more than once`);
|
|
211
|
-
choices.add(choice.value);
|
|
212
|
-
}
|
|
213
|
-
if (column.choices.length === 0) faults.push(`column "${column.key}" offers no choices`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const key = extractColumn(schema, schema.key);
|
|
217
|
-
if (key === void 0) faults.push(`schema key "${schema.key}" names no declared column`);
|
|
218
|
-
else if (key.cell === "number" || key.cell === "flag") faults.push(`schema key "${schema.key}" names a ${key.cell} column, which holds no identity`);
|
|
219
|
-
return Object.freeze(faults);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Projects a schema into JSON in declaration order, dropping every absent member; raises `SCHEMA`
|
|
223
|
-
* for a `meta` it cannot own.
|
|
224
|
-
*
|
|
225
|
-
* @param schema - The schema to project.
|
|
226
|
-
* @returns A deeply owned JSON record with absent members omitted.
|
|
227
|
-
* @throws A {@link TableError} coded `SCHEMA` when metadata cannot be owned.
|
|
228
|
-
*/
|
|
229
|
-
function serializeTable(schema) {
|
|
230
|
-
const output = {};
|
|
231
|
-
if (schema.name !== void 0) output.name = schema.name;
|
|
232
|
-
if (schema.label !== void 0) output.label = schema.label;
|
|
233
|
-
if (schema.help !== void 0) output.help = schema.help;
|
|
234
|
-
output.key = schema.key;
|
|
235
|
-
output.columns = schema.columns.map((column) => {
|
|
236
|
-
const entry = {
|
|
237
|
-
cell: column.cell,
|
|
238
|
-
key: column.key
|
|
239
|
-
};
|
|
240
|
-
if (column.label !== void 0) entry.label = column.label;
|
|
241
|
-
if (column.help !== void 0) entry.help = column.help;
|
|
242
|
-
if (column.hidden !== void 0) entry.hidden = column.hidden;
|
|
243
|
-
if (column.meta !== void 0) entry.meta = column.meta;
|
|
244
|
-
if (column.cell === "choice") entry.choices = column.choices.map((choice) => {
|
|
245
|
-
const option = {
|
|
246
|
-
value: choice.value,
|
|
247
|
-
label: choice.label
|
|
248
|
-
};
|
|
249
|
-
if (choice.help !== void 0) option.help = choice.help;
|
|
250
|
-
return option;
|
|
251
|
-
});
|
|
252
|
-
return entry;
|
|
253
|
-
});
|
|
254
|
-
try {
|
|
255
|
-
return cloneJSONRecord(output);
|
|
256
|
-
} catch (error) {
|
|
257
|
-
if (!isContractError(error)) throw error;
|
|
258
|
-
throw new TableError("SCHEMA", "schema contains metadata that cannot be owned");
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/** Compares complete row values independently of their snapshot identities.
|
|
262
|
-
* @param left - The previous row, if present.
|
|
263
|
-
* @param right - The next row.
|
|
264
|
-
* @returns Whether both rows carry exactly the same cells.
|
|
265
|
-
*/
|
|
266
|
-
function matchesTableRow(left, right) {
|
|
267
|
-
return left !== void 0 && Object.keys(left).length === Object.keys(right).length && Object.keys(right).every((key) => Object.hasOwn(left, key) && left[key] === right[key]);
|
|
268
|
-
}
|
|
269
|
-
//#endregion
|
|
270
|
-
//#region src/core/validators.ts
|
|
271
|
-
/**
|
|
272
|
-
* Determines whether an unknown value is a declared column cell.
|
|
273
|
-
*
|
|
274
|
-
* @param input - The value to inspect.
|
|
275
|
-
* @returns True if the value is a declared column cell; false otherwise.
|
|
276
|
-
*/
|
|
277
|
-
function isColumnCell(input) {
|
|
278
|
-
return COLUMN_CELLS.some((cell) => cell === input);
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Determines whether an unknown value is one exact {@link ColumnChoice} record; an unknown member
|
|
282
|
-
* refuses it.
|
|
283
|
-
*
|
|
284
|
-
* @param input - The value to inspect.
|
|
285
|
-
* @returns True if the value is a column choice; false otherwise.
|
|
286
|
-
*/
|
|
287
|
-
function isColumnChoice(input) {
|
|
288
|
-
const outcome = attempt(() => {
|
|
289
|
-
if (!isRecord(input) || !Reflect.ownKeys(input).every((key) => isString(key))) return false;
|
|
290
|
-
return recordOf({
|
|
291
|
-
value: isString,
|
|
292
|
-
label: isString,
|
|
293
|
-
help: isString
|
|
294
|
-
}, ["help"])(input);
|
|
295
|
-
});
|
|
296
|
-
return outcome.success && outcome.value;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Determines whether an unknown value is one exact discriminated {@link TableColumn}, checked
|
|
300
|
-
* against its cell's own options.
|
|
301
|
-
*
|
|
302
|
-
* @param input - The value to inspect.
|
|
303
|
-
* @returns True if the value is a structurally valid table column; false otherwise.
|
|
304
|
-
*/
|
|
305
|
-
function isTableColumn(input) {
|
|
306
|
-
const outcome = attempt(() => {
|
|
307
|
-
if (!isRecord(input) || !Object.hasOwn(input, "cell") || !Object.hasOwn(input, "key")) return false;
|
|
308
|
-
const cell = input.cell;
|
|
309
|
-
if (!isColumnCell(cell)) return false;
|
|
310
|
-
if (!Reflect.ownKeys(input).every((key) => {
|
|
311
|
-
if (!isString(key)) return false;
|
|
312
|
-
if ([
|
|
313
|
-
"cell",
|
|
314
|
-
"key",
|
|
315
|
-
"label",
|
|
316
|
-
"help",
|
|
317
|
-
"hidden",
|
|
318
|
-
"meta"
|
|
319
|
-
].includes(key)) return true;
|
|
320
|
-
return cell === "choice" && key === "choices";
|
|
321
|
-
})) return false;
|
|
322
|
-
const key = input.key;
|
|
323
|
-
const hasLabel = Object.hasOwn(input, "label");
|
|
324
|
-
const label = hasLabel ? input.label : void 0;
|
|
325
|
-
const hasHelp = Object.hasOwn(input, "help");
|
|
326
|
-
const help = hasHelp ? input.help : void 0;
|
|
327
|
-
const hasHidden = Object.hasOwn(input, "hidden");
|
|
328
|
-
const hidden = hasHidden ? input.hidden : void 0;
|
|
329
|
-
const hasMeta = Object.hasOwn(input, "meta");
|
|
330
|
-
const meta = hasMeta ? input.meta : void 0;
|
|
331
|
-
if (hasMeta) {
|
|
332
|
-
if (!isBoundedJSONRecord(meta)) return false;
|
|
333
|
-
if (!attempt(() => cloneJSONRecord(meta)).success) return false;
|
|
334
|
-
}
|
|
335
|
-
if (!isString(key) || hasLabel && !isString(label) || hasHelp && !isString(help) || hasHidden && !isBoolean(hidden)) return false;
|
|
336
|
-
if (cell !== "choice") return !Object.hasOwn(input, "choices");
|
|
337
|
-
return Object.hasOwn(input, "choices") && arrayOf(isColumnChoice)(input.choices);
|
|
338
|
-
});
|
|
339
|
-
return outcome.success && outcome.value;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Determines whether an unknown value has the exact shape of a {@link TableSchema} — the shape
|
|
343
|
-
* alone, with no domain check.
|
|
344
|
-
*
|
|
345
|
-
* @param input - The value to inspect.
|
|
346
|
-
* @returns True if the value has the exact structure of a table schema; false otherwise.
|
|
347
|
-
*/
|
|
348
|
-
function isStructuralTableSchema(input) {
|
|
349
|
-
const outcome = attempt(() => {
|
|
350
|
-
if (!isRecord(input) || !Reflect.ownKeys(input).every((key) => isString(key))) return false;
|
|
351
|
-
return recordOf({
|
|
352
|
-
name: isString,
|
|
353
|
-
label: isString,
|
|
354
|
-
help: isString,
|
|
355
|
-
key: isString,
|
|
356
|
-
columns: arrayOf(isTableColumn)
|
|
357
|
-
}, [
|
|
358
|
-
"name",
|
|
359
|
-
"label",
|
|
360
|
-
"help"
|
|
361
|
-
])(input);
|
|
362
|
-
});
|
|
363
|
-
return outcome.success && outcome.value;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Determines whether an unknown value is a {@link TableSchema} a table can be opened against —
|
|
367
|
-
* the exact shape, and an audit that finds nothing.
|
|
368
|
-
*
|
|
369
|
-
* @param input - The value to inspect.
|
|
370
|
-
* @returns True if the value has valid structure, domain relationships, and
|
|
371
|
-
* budgets; false otherwise.
|
|
372
|
-
*/
|
|
373
|
-
function isTableSchema(input) {
|
|
374
|
-
const outcome = attempt(() => isStructuralTableSchema(input) && auditTable(input).length === 0);
|
|
375
|
-
return outcome.success && outcome.value;
|
|
376
|
-
}
|
|
377
|
-
//#endregion
|
|
378
|
-
//#region src/core/parsers.ts
|
|
379
|
-
/**
|
|
380
|
-
* Parses unknown wire data into an owned, structurally valid, semantically sound table schema.
|
|
381
|
-
*
|
|
382
|
-
* @param input - The unknown schema value to parse.
|
|
383
|
-
* @returns An owned table schema, or `undefined` on refusal.
|
|
384
|
-
*/
|
|
385
|
-
function parseTable(input) {
|
|
386
|
-
const outcome = attempt(() => {
|
|
387
|
-
if (!isTableSchema(input)) return void 0;
|
|
388
|
-
const projected = serializeTable(input);
|
|
389
|
-
return isTableSchema(projected) ? projected : void 0;
|
|
26
|
+
return holds(() => {
|
|
27
|
+
if (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
28
|
+
return INTRINSICS.describe(value, Symbol.for("@orkestrel/table.TableBindingError"))?.value === value && INTRINSICS.reflect.read(value, "code") === "STRUCTURE";
|
|
390
29
|
});
|
|
391
|
-
return outcome.success ? outcome.value : void 0;
|
|
392
30
|
}
|
|
393
|
-
|
|
394
|
-
* Parses unknown wire data into owned rows against one table schema, coercing a numeric string
|
|
395
|
-
* and `'true'` / `'false'`.
|
|
396
|
-
*
|
|
397
|
-
* @param schema - The schema that declares the accepted keys and cell shapes.
|
|
398
|
-
* @param input - The unknown row-list value to parse.
|
|
399
|
-
* @returns Frozen owned rows, or `undefined` when any row is refused.
|
|
400
|
-
*/
|
|
401
|
-
function parseRows(schema, input) {
|
|
402
|
-
const outcome = attempt(() => {
|
|
403
|
-
if (!isTableSchema(schema) || !isArray(input)) return;
|
|
404
|
-
const read = readArrayEntries(input);
|
|
405
|
-
if (!read.success || !read.value.dense) return void 0;
|
|
406
|
-
const keys = /* @__PURE__ */ new Set();
|
|
407
|
-
const rows = [];
|
|
408
|
-
for (const candidate of read.value.entries) {
|
|
409
|
-
if (!isRecord(candidate)) return void 0;
|
|
410
|
-
const row = {};
|
|
411
|
-
for (const key of Reflect.ownKeys(candidate)) {
|
|
412
|
-
if (!isString(key) || !Object.hasOwn(candidate, key)) return void 0;
|
|
413
|
-
const column = extractColumn(schema, key);
|
|
414
|
-
if (column === void 0) return void 0;
|
|
415
|
-
const inputCell = candidate[key];
|
|
416
|
-
let cell = inputCell;
|
|
417
|
-
if (column.cell === "number" && isString(inputCell)) {
|
|
418
|
-
if (inputCell.length > 65536) return void 0;
|
|
419
|
-
cell = parseNumber(inputCell);
|
|
420
|
-
} else if (column.cell === "flag" && inputCell === "true") cell = true;
|
|
421
|
-
else if (column.cell === "flag" && inputCell === "false") cell = false;
|
|
422
|
-
if (!matchesCell(column, cell)) return void 0;
|
|
423
|
-
Object.defineProperty(row, key, {
|
|
424
|
-
value: cell,
|
|
425
|
-
enumerable: true,
|
|
426
|
-
configurable: true,
|
|
427
|
-
writable: true
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
const owned = cloneRow(row);
|
|
431
|
-
const key = extractKey(schema, owned);
|
|
432
|
-
if (key === void 0 || keys.has(key)) return void 0;
|
|
433
|
-
keys.add(key);
|
|
434
|
-
rows.push(owned);
|
|
435
|
-
}
|
|
436
|
-
return Object.freeze(rows);
|
|
437
|
-
});
|
|
438
|
-
return outcome.success ? outcome.value : void 0;
|
|
439
|
-
}
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/browser/helpers.ts
|
|
31
|
+
// src/browser/helpers.ts
|
|
442
32
|
/** Reads declared header metadata and explicit row keys into a validated core projection.
|
|
443
33
|
* @param element - A native table with data-table-key naming its identity column.
|
|
444
34
|
* @returns The schema and rows, validated together before any mutation.
|
|
@@ -486,7 +76,7 @@ function readTableSchema(element) {
|
|
|
486
76
|
* @throws A binding error for duplicate keys, undeclared cells, or malformed values.
|
|
487
77
|
*/
|
|
488
78
|
function readTableRows(schema, elements) {
|
|
489
|
-
const
|
|
79
|
+
const candidates = elements.map((element) => {
|
|
490
80
|
const cells = { [schema.key]: element.getAttribute("data-table-key") };
|
|
491
81
|
const seen = /* @__PURE__ */ new Set();
|
|
492
82
|
for (const cell of element.cells) {
|
|
@@ -504,12 +94,12 @@ function readTableRows(schema, elements) {
|
|
|
504
94
|
});
|
|
505
95
|
}
|
|
506
96
|
return cells;
|
|
507
|
-
})
|
|
97
|
+
});
|
|
98
|
+
const rows = parseRows(schema, candidates);
|
|
508
99
|
if (rows === void 0) throw new TableBindingError("STRUCTURE", "Native row keys or cells are invalid");
|
|
509
100
|
return rows;
|
|
510
101
|
}
|
|
511
|
-
|
|
512
|
-
//#region src/browser/TableBinding.ts
|
|
102
|
+
// src/browser/TableBinding.ts
|
|
513
103
|
/** Retains keyed native rows while projecting every core table manager. */
|
|
514
104
|
var TableBinding = class {
|
|
515
105
|
#element;
|
|
@@ -749,13 +339,7 @@ var TableBinding = class {
|
|
|
749
339
|
for (const row of view) {
|
|
750
340
|
const key = extractKey(this.#document.schema, row);
|
|
751
341
|
if (key === void 0) continue;
|
|
752
|
-
|
|
753
|
-
if (node === void 0) {
|
|
754
|
-
if (this.#options.row === void 0) throw new TableBindingError("STRUCTURE", "An authored document row has no native row");
|
|
755
|
-
node = this.#element.ownerDocument.createElement("tr");
|
|
756
|
-
node.setAttribute("data-table-key", key);
|
|
757
|
-
this.#rows.set(key, node);
|
|
758
|
-
}
|
|
342
|
+
const node = this.#rows.get(key) ?? this.#createRow(key);
|
|
759
343
|
if (this.#options.row !== void 0 && !matchesTableRow(this.#values.get(key), row)) {
|
|
760
344
|
this.#options.row(node, row);
|
|
761
345
|
if (this.destroyed) return;
|
|
@@ -781,6 +365,20 @@ var TableBinding = class {
|
|
|
781
365
|
this.#observe();
|
|
782
366
|
if (this.#records.length > 0) this.#enqueue();
|
|
783
367
|
}
|
|
368
|
+
#createRow(key) {
|
|
369
|
+
if (this.#options.row === void 0) throw new TableBindingError("STRUCTURE", "An authored document row has no native row");
|
|
370
|
+
const node = this.#element.ownerDocument.createElement("tr");
|
|
371
|
+
node.setAttribute("data-table-key", key);
|
|
372
|
+
this.#rows.set(key, node);
|
|
373
|
+
return node;
|
|
374
|
+
}
|
|
375
|
+
#createDetail(key) {
|
|
376
|
+
const detail = this.#element.ownerDocument.createElement("tr");
|
|
377
|
+
detail.id = `table-detail-${crypto.randomUUID()}`;
|
|
378
|
+
detail.insertCell().colSpan = Math.max(1, this.#rows.get(key)?.cells.length ?? this.#document.schema.columns.length);
|
|
379
|
+
this.#details.set(key, detail);
|
|
380
|
+
return detail;
|
|
381
|
+
}
|
|
784
382
|
#restoreFocus(focused, hidden = false) {
|
|
785
383
|
if (focused !== void 0 && this.#element.ownerDocument.activeElement !== focused && (this.#element.ownerDocument.activeElement === this.#element.ownerDocument.body || this.#element.ownerDocument.activeElement === null)) {
|
|
786
384
|
if (focused.isConnected) focused.focus({ preventScroll: true });
|
|
@@ -794,13 +392,7 @@ var TableBinding = class {
|
|
|
794
392
|
}
|
|
795
393
|
#detail(key, row) {
|
|
796
394
|
if (this.#options.detail === void 0 || !this.#document.expansion.keys.has(key)) return void 0;
|
|
797
|
-
|
|
798
|
-
if (detail === void 0) {
|
|
799
|
-
detail = this.#element.ownerDocument.createElement("tr");
|
|
800
|
-
detail.id = `table-detail-${crypto.randomUUID()}`;
|
|
801
|
-
detail.insertCell().colSpan = Math.max(1, this.#rows.get(key)?.cells.length ?? this.#document.schema.columns.length);
|
|
802
|
-
this.#details.set(key, detail);
|
|
803
|
-
}
|
|
395
|
+
const detail = this.#details.get(key) ?? this.#createDetail(key);
|
|
804
396
|
if (!matchesTableRow(this.#detailValues.get(key), row)) {
|
|
805
397
|
this.#options.detail(detail, row);
|
|
806
398
|
if (!this.destroyed) this.#detailValues.set(key, row);
|
|
@@ -816,11 +408,9 @@ var TableBinding = class {
|
|
|
816
408
|
this.#observe();
|
|
817
409
|
}
|
|
818
410
|
#attribute(element, name, value) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
this.#attributes.set(element, attributes);
|
|
823
|
-
}
|
|
411
|
+
if (!this.#attributes.has(element)) this.#attributes.set(element, /* @__PURE__ */ new Map());
|
|
412
|
+
const attributes = this.#attributes.get(element);
|
|
413
|
+
if (attributes === void 0) return;
|
|
824
414
|
const entry = attributes.get(name);
|
|
825
415
|
if (entry === void 0) attributes.set(name, {
|
|
826
416
|
original: element.getAttribute(name),
|
|
@@ -1064,8 +654,7 @@ var TableBinding = class {
|
|
|
1064
654
|
});
|
|
1065
655
|
}
|
|
1066
656
|
};
|
|
1067
|
-
|
|
1068
|
-
//#region src/browser/factories.ts
|
|
657
|
+
// src/browser/factories.ts
|
|
1069
658
|
/** Creates a native table binding with one retained row owner.
|
|
1070
659
|
* @param element - The native table to bind.
|
|
1071
660
|
* @param document - The borrowed core document.
|
|
@@ -1075,7 +664,6 @@ var TableBinding = class {
|
|
|
1075
664
|
function createTableBinding(element, document, options) {
|
|
1076
665
|
return new TableBinding(element, document, options);
|
|
1077
666
|
}
|
|
1078
|
-
//#endregion
|
|
1079
667
|
export { TableBinding, TableBindingError, createTableBinding, isTableBindingError, readTable, readTableRows, readTableSchema };
|
|
1080
668
|
|
|
1081
669
|
//# sourceMappingURL=index.js.map
|