@web-ts-toolkit/json-frame 0.40.0 → 0.41.0

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.
Files changed (6) hide show
  1. package/README.md +75 -16
  2. package/index.d.mts +115 -29
  3. package/index.d.ts +115 -29
  4. package/index.js +661 -292
  5. package/index.mjs +660 -292
  6. package/package.json +1 -1
package/index.js CHANGED
@@ -30,6 +30,7 @@ var index_exports = {};
30
30
  __export(index_exports, {
31
31
  AmbiguousOrientError: () => AmbiguousOrientError,
32
32
  ExportKeyCollisionError: () => ExportKeyCollisionError,
33
+ JSON_FRAME_MAX_DEPTH: () => JSON_FRAME_MAX_DEPTH,
33
34
  JsonFrameError: () => JsonFrameError,
34
35
  JsonFrameOptionError: () => JsonFrameOptionError,
35
36
  JsonFrameParseError: () => JsonFrameParseError,
@@ -40,7 +41,38 @@ __export(index_exports, {
40
41
  module.exports = __toCommonJS(index_exports);
41
42
 
42
43
  // src/errors.ts
44
+ var DIAGNOSTIC_KEY_LIMIT = 5;
43
45
  var freezeCopy = (values) => Object.freeze([...values]);
46
+ var summarizeDiagnosticValue = (value) => {
47
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
48
+ return value;
49
+ }
50
+ if (Array.isArray(value)) {
51
+ return Object.freeze({ kind: "array", length: value.length });
52
+ }
53
+ if (typeof value === "object") {
54
+ const keys = Object.keys(value);
55
+ return Object.freeze({
56
+ kind: "object",
57
+ keyCount: keys.length,
58
+ keys: freezeCopy(keys.slice(0, DIAGNOSTIC_KEY_LIMIT)),
59
+ truncated: keys.length > DIAGNOSTIC_KEY_LIMIT
60
+ });
61
+ }
62
+ if (typeof value === "undefined") {
63
+ return Object.freeze({ kind: "undefined" });
64
+ }
65
+ if (typeof value === "symbol") {
66
+ return Object.freeze({ kind: "symbol" });
67
+ }
68
+ if (typeof value === "bigint") {
69
+ return Object.freeze({ kind: "bigint" });
70
+ }
71
+ if (typeof value === "function") {
72
+ return Object.freeze({ kind: "function" });
73
+ }
74
+ return Object.freeze({ kind: "object", keyCount: 0, keys: freezeCopy([]), truncated: false });
75
+ };
44
76
  var JsonFrameError = class extends Error {
45
77
  constructor(message, context = {}) {
46
78
  super(message);
@@ -50,7 +82,9 @@ var JsonFrameError = class extends Error {
50
82
  this.path = context.path;
51
83
  this.row = context.row;
52
84
  this.column = context.column;
53
- this.value = context.value;
85
+ if ("value" in context) {
86
+ this.value = summarizeDiagnosticValue(context.value);
87
+ }
54
88
  }
55
89
  };
56
90
  var JsonFrameParseError = class extends JsonFrameError {
@@ -99,25 +133,494 @@ var ExportKeyCollisionError = class extends JsonFrameError {
99
133
  }
100
134
  };
101
135
 
136
+ // src/frame/column.ts
137
+ var INT32_MIN = -2147483648;
138
+ var INT32_MAX = 2147483647;
139
+ var freezeArray = (values) => Object.freeze([...values]);
140
+ var freezeColumnInfo = (info) => Object.freeze({ ...info });
141
+ var DATETIME_PATTERN = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?:[T ](?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2})(?:\.(?<fraction>\d{1,9}))?)?$/;
142
+ var columnOperationCounters = {
143
+ materializeColumnCalls: 0,
144
+ materializedCells: 0,
145
+ scalarReads: 0,
146
+ rebuiltCells: 0,
147
+ nullableChecks: 0
148
+ };
149
+ var hasCategoricalMetadata = (field) => field.constraints?.enum !== void 0 || field.ordered === true || field.extDtype === "category";
150
+ var mapTableSchemaFieldType = (field) => {
151
+ if (hasCategoricalMetadata(field)) {
152
+ return "categorical";
153
+ }
154
+ switch (field.type) {
155
+ case "boolean":
156
+ return "boolean";
157
+ case "integer":
158
+ case "year":
159
+ return "integer";
160
+ case "number":
161
+ return "float";
162
+ case "date":
163
+ case "datetime":
164
+ return "datetime";
165
+ case "string":
166
+ return "string";
167
+ case "any":
168
+ return "unknown";
169
+ default:
170
+ return "unknown";
171
+ }
172
+ };
173
+ var inferColumnType = (values) => {
174
+ let nullable = false;
175
+ let sawBoolean = false;
176
+ let sawString = false;
177
+ let sawInteger = false;
178
+ let sawFloat = false;
179
+ let sawStructured = false;
180
+ for (const value of values) {
181
+ if (value === null) {
182
+ nullable = true;
183
+ continue;
184
+ }
185
+ if (typeof value === "boolean") {
186
+ sawBoolean = true;
187
+ continue;
188
+ }
189
+ if (typeof value === "string") {
190
+ sawString = true;
191
+ continue;
192
+ }
193
+ if (typeof value === "number") {
194
+ if (Number.isInteger(value)) {
195
+ sawInteger = true;
196
+ } else {
197
+ sawFloat = true;
198
+ }
199
+ continue;
200
+ }
201
+ sawStructured = true;
202
+ }
203
+ const nonNullKinds = [sawBoolean, sawString, sawInteger || sawFloat, sawStructured].filter(Boolean).length;
204
+ if (nonNullKinds === 0) {
205
+ return freezeColumnInfo({ type: "unknown", nullable });
206
+ }
207
+ if (sawStructured || nonNullKinds > 1 || sawBoolean && (sawString || sawInteger || sawFloat)) {
208
+ return freezeColumnInfo({ type: "mixed", nullable });
209
+ }
210
+ if (sawString) {
211
+ return freezeColumnInfo({ type: "string", nullable });
212
+ }
213
+ if (sawBoolean) {
214
+ return freezeColumnInfo({ type: "boolean", nullable });
215
+ }
216
+ if (sawFloat) {
217
+ return freezeColumnInfo({ type: "float", nullable });
218
+ }
219
+ return freezeColumnInfo({ type: "integer", nullable });
220
+ };
221
+ var isPandasNaiveIsoDatetime = (value) => {
222
+ const match = DATETIME_PATTERN.exec(value);
223
+ if (match?.groups === void 0) {
224
+ return false;
225
+ }
226
+ const year = Number(match.groups.year);
227
+ const month = Number(match.groups.month);
228
+ const day = Number(match.groups.day);
229
+ const hour = match.groups.hour === void 0 ? 0 : Number(match.groups.hour);
230
+ const minute = match.groups.minute === void 0 ? 0 : Number(match.groups.minute);
231
+ const second = match.groups.second === void 0 ? 0 : Number(match.groups.second);
232
+ if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) {
233
+ return false;
234
+ }
235
+ const normalized = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
236
+ return normalized.getUTCFullYear() === year && normalized.getUTCMonth() === month - 1 && normalized.getUTCDate() === day && normalized.getUTCHours() === hour && normalized.getUTCMinutes() === minute && normalized.getUTCSeconds() === second;
237
+ };
238
+ var isScalarCategory = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
239
+ var isJsonContainer = (value) => {
240
+ if (Array.isArray(value)) {
241
+ return true;
242
+ }
243
+ if (value === null || typeof value !== "object") {
244
+ return false;
245
+ }
246
+ const prototype = Object.getPrototypeOf(value);
247
+ return prototype === Object.prototype || prototype === null;
248
+ };
249
+ var isColumnValueCompatible = (type, value) => {
250
+ if (value === null) {
251
+ return true;
252
+ }
253
+ switch (type) {
254
+ case "integer":
255
+ return typeof value === "number" && Number.isInteger(value);
256
+ case "float":
257
+ return typeof value === "number";
258
+ case "string":
259
+ return typeof value === "string";
260
+ case "boolean":
261
+ return typeof value === "boolean";
262
+ case "datetime":
263
+ return typeof value === "string" && isPandasNaiveIsoDatetime(value);
264
+ case "categorical":
265
+ return isScalarCategory(value);
266
+ case "mixed":
267
+ case "unknown":
268
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isJsonContainer(value);
269
+ default: {
270
+ const exhaustive = type;
271
+ return exhaustive;
272
+ }
273
+ }
274
+ };
275
+ var getColumnCellPath = (orient, column, columnIndex, rowIndex, rowLabel) => {
276
+ switch (orient) {
277
+ case "records":
278
+ return `$[${rowIndex}][${JSON.stringify(column)}]`;
279
+ case "values":
280
+ return `$[${rowIndex}][${columnIndex}]`;
281
+ case "split":
282
+ return `$.data[${rowIndex}][${columnIndex}]`;
283
+ case "table":
284
+ return `$.data[${rowIndex}][${JSON.stringify(column)}]`;
285
+ case "index":
286
+ return `$[${JSON.stringify(String(rowLabel))}][${JSON.stringify(column)}]`;
287
+ case "columns":
288
+ return `$[${JSON.stringify(column)}][${JSON.stringify(String(rowLabel))}]`;
289
+ default:
290
+ return `$.data[${rowIndex}][${JSON.stringify(column)}]`;
291
+ }
292
+ };
293
+ var validateColumnValuesForType = ({
294
+ column,
295
+ columnIndex,
296
+ values,
297
+ type,
298
+ orient,
299
+ index
300
+ }) => {
301
+ for (let rowIndex = 0; rowIndex < values.length; rowIndex += 1) {
302
+ const value = values[rowIndex];
303
+ if (isColumnValueCompatible(type, value)) {
304
+ continue;
305
+ }
306
+ throw new JsonFrameValidationError("Column cells are incompatible with the declared logical type.", {
307
+ ...orient === void 0 ? {} : { orient },
308
+ path: getColumnCellPath(orient, column, columnIndex, rowIndex, index?.[rowIndex]),
309
+ row: rowIndex,
310
+ column,
311
+ value
312
+ });
313
+ }
314
+ };
315
+ var packColumn = (values, info, packThreshold) => {
316
+ if (packThreshold === 0 || values.length < packThreshold || info.nullable) {
317
+ return freezeArray(values);
318
+ }
319
+ if (info.type === "integer") {
320
+ const packed = new Int32Array(values.length);
321
+ for (let index = 0; index < values.length; index += 1) {
322
+ const value = values[index];
323
+ if (typeof value !== "number" || !Number.isInteger(value) || value < INT32_MIN || value > INT32_MAX) {
324
+ return freezeArray(values);
325
+ }
326
+ packed[index] = value;
327
+ }
328
+ return packed;
329
+ }
330
+ if (info.type === "float") {
331
+ const packed = new Float64Array(values.length);
332
+ for (let index = 0; index < values.length; index += 1) {
333
+ const value = values[index];
334
+ if (typeof value !== "number") {
335
+ return freezeArray(values);
336
+ }
337
+ packed[index] = value;
338
+ }
339
+ return packed;
340
+ }
341
+ return freezeArray(values);
342
+ };
343
+ var hasNullValue = (values) => {
344
+ columnOperationCounters.nullableChecks += values.length;
345
+ for (const value of values) {
346
+ if (value === null) {
347
+ return true;
348
+ }
349
+ }
350
+ return false;
351
+ };
352
+ var validateExplicitColumnTypes = (columns, columnTypes) => {
353
+ if (columnTypes === void 0) {
354
+ return;
355
+ }
356
+ const knownColumns = new Set(columns);
357
+ for (const column of Object.keys(columnTypes)) {
358
+ if (!knownColumns.has(column)) {
359
+ throw new JsonFrameOptionError(
360
+ "`options.columnTypes` keys must match parsed column names.",
361
+ `columnTypes.${column}`,
362
+ columnTypes[column]
363
+ );
364
+ }
365
+ }
366
+ };
367
+ var resolveSchemaTypeByColumn = (frame) => {
368
+ if (frame.tableSchema === void 0) {
369
+ return /* @__PURE__ */ new Map();
370
+ }
371
+ const schemaTypes = /* @__PURE__ */ new Map();
372
+ for (const field of frame.tableSchema.fields) {
373
+ if (frame.columns.includes(field.name)) {
374
+ schemaTypes.set(field.name, mapTableSchemaFieldType(field));
375
+ }
376
+ }
377
+ return schemaTypes;
378
+ };
379
+ var materializeColumn = (column) => {
380
+ columnOperationCounters.materializeColumnCalls += 1;
381
+ columnOperationCounters.materializedCells += column.length;
382
+ return Array.isArray(column) ? freezeArray(column) : freezeArray(Array.from(column));
383
+ };
384
+ var getStoredColumnValue = (column, position) => {
385
+ columnOperationCounters.scalarReads += 1;
386
+ return column[position];
387
+ };
388
+ var rebuildStoredColumn = (column, positions) => {
389
+ const rebuilt = new Array(positions.length);
390
+ for (let index = 0; index < positions.length; index += 1) {
391
+ rebuilt[index] = getStoredColumnValue(column, positions[index]);
392
+ }
393
+ columnOperationCounters.rebuiltCells += positions.length;
394
+ return rebuilt;
395
+ };
396
+ var materializeFrameData = (columns, data) => {
397
+ const materialized = /* @__PURE__ */ new Map();
398
+ for (const column of columns) {
399
+ const stored = data.get(column);
400
+ if (stored === void 0) {
401
+ throw new JsonFrameValidationError("Stored frame data is missing a declared column.", {
402
+ path: "$",
403
+ column
404
+ });
405
+ }
406
+ materialized.set(column, materializeColumn(stored));
407
+ }
408
+ return materialized;
409
+ };
410
+ var buildFrameState = (frame, options) => {
411
+ validateExplicitColumnTypes(frame.columns, options.columnTypes);
412
+ const schemaTypes = resolveSchemaTypeByColumn(frame);
413
+ const data = /* @__PURE__ */ new Map();
414
+ const columnInfo = /* @__PURE__ */ new Map();
415
+ for (let columnIndex = 0; columnIndex < frame.columns.length; columnIndex += 1) {
416
+ const column = frame.columns[columnIndex];
417
+ const values = frame.data.get(column);
418
+ if (values === void 0) {
419
+ throw new JsonFrameValidationError("Parsed frame data is missing a declared column.", {
420
+ orient: frame.orient,
421
+ column
422
+ });
423
+ }
424
+ const inferred = inferColumnType(values);
425
+ const resolvedType = schemaTypes.get(column) ?? options.columnTypes?.[column] ?? inferred.type;
426
+ if (schemaTypes.has(column) === false && options.columnTypes?.[column] !== void 0) {
427
+ validateColumnValuesForType({
428
+ column,
429
+ columnIndex,
430
+ values,
431
+ type: resolvedType,
432
+ orient: frame.orient,
433
+ index: frame.index
434
+ });
435
+ }
436
+ const info = freezeColumnInfo({ type: resolvedType, nullable: inferred.nullable });
437
+ columnInfo.set(column, info);
438
+ data.set(column, packColumn(values, info, options.packThreshold));
439
+ }
440
+ return {
441
+ columns: freezeArray(frame.columns),
442
+ index: freezeArray(frame.index),
443
+ indexKind: frame.indexKind,
444
+ data,
445
+ columnInfo,
446
+ ...frame.tableSchema === void 0 ? {} : { tableSchema: frame.tableSchema },
447
+ ...frame.tableIndexField === void 0 ? {} : { tableIndexField: frame.tableIndexField }
448
+ };
449
+ };
450
+ var createFrameStateFromRebuiltData = (frame, options) => {
451
+ const data = /* @__PURE__ */ new Map();
452
+ const columnInfo = /* @__PURE__ */ new Map();
453
+ for (const column of frame.columns) {
454
+ const values = frame.data.get(column);
455
+ if (values === void 0) {
456
+ throw new JsonFrameValidationError("Rebuilt frame data is missing a declared column.", {
457
+ path: "$",
458
+ column
459
+ });
460
+ }
461
+ const sourceInfo = frame.columnInfo.get(column);
462
+ if (sourceInfo === void 0) {
463
+ throw new JsonFrameValidationError("Rebuilt frame metadata is missing a declared column.", {
464
+ path: "$",
465
+ column
466
+ });
467
+ }
468
+ const info = freezeColumnInfo({
469
+ type: sourceInfo.type,
470
+ nullable: sourceInfo.nullable && hasNullValue(values)
471
+ });
472
+ columnInfo.set(column, info);
473
+ data.set(column, packColumn(values, info, options.packThreshold));
474
+ }
475
+ return {
476
+ columns: freezeArray(frame.columns),
477
+ index: freezeArray(frame.index),
478
+ indexKind: frame.indexKind,
479
+ data,
480
+ columnInfo,
481
+ ...frame.tableSchema === void 0 ? {} : { tableSchema: frame.tableSchema },
482
+ ...frame.tableIndexField === void 0 ? {} : { tableIndexField: frame.tableIndexField }
483
+ };
484
+ };
485
+ var createFrameState = (frame, options) => buildFrameState(frame, options);
486
+
487
+ // src/json.ts
488
+ var JSON_FRAME_MAX_DEPTH = 1e3;
489
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
490
+ var rootPath = (path) => ({ type: "root", path });
491
+ var isPlainObject = (value) => {
492
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
493
+ return false;
494
+ }
495
+ const prototype = Object.getPrototypeOf(value);
496
+ return prototype === Object.prototype || prototype === null;
497
+ };
498
+ var pathToString = (path) => {
499
+ const segments = [];
500
+ let current = path;
501
+ while (current !== void 0 && current.type !== "root") {
502
+ segments.push(current);
503
+ current = current.parent;
504
+ }
505
+ let formatted = current?.type === "root" ? current.path : "$";
506
+ for (let index = segments.length - 1; index >= 0; index -= 1) {
507
+ const segment = segments[index];
508
+ if (segment.type === "index") {
509
+ formatted += `[${segment.index}]`;
510
+ } else if (segment.type === "property") {
511
+ formatted += IDENTIFIER_PATTERN.test(segment.key) ? `.${segment.key}` : `[${JSON.stringify(segment.key)}]`;
512
+ }
513
+ }
514
+ return formatted;
515
+ };
516
+ var childIndexPath = (parent, index) => ({ parent, type: "index", index });
517
+ var childPropertyPath = (parent, key) => ({ parent, type: "property", key });
518
+ var throwValidation = (message, path, value, orient) => {
519
+ throw new JsonFrameValidationError(message, {
520
+ ...orient === void 0 ? {} : { orient },
521
+ path: pathToString(path),
522
+ value
523
+ });
524
+ };
525
+ var cloneScalar = (value, path, orient) => {
526
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
527
+ return value;
528
+ }
529
+ if (typeof value === "number") {
530
+ if (!Number.isFinite(value)) {
531
+ throwValidation("Numbers must be finite JSON values.", path, value, orient);
532
+ }
533
+ return value;
534
+ }
535
+ if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
536
+ throwValidation("Input must contain only JSON-compatible values.", path, value, orient);
537
+ }
538
+ return void 0;
539
+ };
540
+ var createFrame = (source, path, depth, ancestors, orient) => {
541
+ const scalar = cloneScalar(source, path, orient);
542
+ if (scalar !== void 0) {
543
+ return { value: scalar };
544
+ }
545
+ if (depth > JSON_FRAME_MAX_DEPTH) {
546
+ throwValidation(
547
+ `JSON input exceeds the maximum supported nesting depth of ${JSON_FRAME_MAX_DEPTH}.`,
548
+ path,
549
+ source,
550
+ orient
551
+ );
552
+ }
553
+ if (Array.isArray(source)) {
554
+ if (ancestors.has(source)) {
555
+ throwValidation("Input contains a cyclic array.", path, source, orient);
556
+ }
557
+ const target2 = [];
558
+ ancestors.add(source);
559
+ return { value: target2, frame: { kind: "array", source, target: target2, path, depth, index: 0 } };
560
+ }
561
+ if (!isPlainObject(source)) {
562
+ throwValidation("Input objects must be plain JSON objects or arrays.", path, source, orient);
563
+ }
564
+ const objectSource = source;
565
+ if (ancestors.has(objectSource)) {
566
+ throwValidation("Input contains a cyclic object.", path, objectSource, orient);
567
+ }
568
+ const target = /* @__PURE__ */ Object.create(null);
569
+ ancestors.add(objectSource);
570
+ return {
571
+ value: target,
572
+ frame: { kind: "object", source: objectSource, target, keys: Object.keys(objectSource), path, depth, index: 0 }
573
+ };
574
+ };
575
+ var cloneJsonCompatible = (value, orient, path = "$") => {
576
+ const ancestors = /* @__PURE__ */ new Set();
577
+ const root = createFrame(value, rootPath(path), 0, ancestors, orient);
578
+ const stack = root.frame === void 0 ? [] : [root.frame];
579
+ while (stack.length > 0) {
580
+ const frame = stack[stack.length - 1];
581
+ if (frame.kind === "array") {
582
+ if (frame.index >= frame.source.length) {
583
+ ancestors.delete(frame.source);
584
+ stack.pop();
585
+ continue;
586
+ }
587
+ const index = frame.index;
588
+ frame.index += 1;
589
+ const childPath2 = childIndexPath(frame.path, index);
590
+ if (!(index in frame.source)) {
591
+ throwValidation("Sparse arrays are not valid JSON input.", childPath2, frame.source, orient);
592
+ }
593
+ const child2 = createFrame(frame.source[index], childPath2, frame.depth + 1, ancestors, orient);
594
+ frame.target.push(child2.value);
595
+ if (child2.frame !== void 0) {
596
+ stack.push(child2.frame);
597
+ }
598
+ continue;
599
+ }
600
+ if (frame.index >= frame.keys.length) {
601
+ ancestors.delete(frame.source);
602
+ stack.pop();
603
+ continue;
604
+ }
605
+ const key = frame.keys[frame.index];
606
+ frame.index += 1;
607
+ const childPath = childPropertyPath(frame.path, key);
608
+ const child = createFrame(frame.source[key], childPath, frame.depth + 1, ancestors, orient);
609
+ frame.target[key] = child.value;
610
+ if (child.frame !== void 0) {
611
+ stack.push(child.frame);
612
+ }
613
+ }
614
+ return root.value;
615
+ };
616
+
102
617
  // src/export/payload.ts
103
618
  var createRecord = () => /* @__PURE__ */ Object.create(null);
104
- var cloneJsonValue = (value) => {
105
- if (Array.isArray(value)) {
106
- return value.map((entry) => cloneJsonValue(entry));
107
- }
108
- if (value !== null && typeof value === "object") {
109
- const cloned = createRecord();
110
- for (const [key, entryValue] of Object.entries(value)) {
111
- cloned[key] = cloneJsonValue(entryValue);
112
- }
113
- return cloned;
114
- }
115
- return value;
116
- };
117
- var cloneSchemaField = (field, name = field.name) => {
619
+ var appendPath = (path, key) => `${path}[${JSON.stringify(key)}]`;
620
+ var cloneSchemaField = (field, name = field.name, path = "$.schema.fields[]") => {
118
621
  const cloned = createRecord();
119
622
  for (const [key, value] of Object.entries(field)) {
120
- cloned[key] = key === "name" ? name : value === void 0 ? void 0 : cloneJsonValue(value);
623
+ cloned[key] = key === "name" ? name : value === void 0 ? void 0 : cloneJsonCompatible(value, "table", appendPath(path, key));
121
624
  }
122
625
  return cloned;
123
626
  };
@@ -128,10 +631,12 @@ var cloneSchema = (schema, fields, primaryKey) => {
128
631
  if (key === "fields" || key === "primaryKey") {
129
632
  continue;
130
633
  }
131
- cloned[key] = value === void 0 ? void 0 : cloneJsonValue(value);
634
+ cloned[key] = value === void 0 ? void 0 : cloneJsonCompatible(value, "table", appendPath("$.schema", key));
132
635
  }
133
636
  }
134
- cloned.fields = fields.map((field) => cloneSchemaField(field));
637
+ cloned.fields = fields.map(
638
+ (field, index) => cloneSchemaField(field, field.name, `$.schema.fields[${index}]`)
639
+ );
135
640
  if (primaryKey !== void 0) {
136
641
  cloned.primaryKey = [...primaryKey];
137
642
  }
@@ -145,8 +650,9 @@ var mapColumnTypeToSchemaField = (name, info) => {
145
650
  case "float":
146
651
  return "number";
147
652
  case "string":
148
- case "categorical":
149
653
  return "string";
654
+ case "categorical":
655
+ return "any";
150
656
  case "boolean":
151
657
  return "boolean";
152
658
  case "datetime":
@@ -232,6 +738,22 @@ var resolveIndexFieldName = (state, options) => {
232
738
  }
233
739
  return indexField;
234
740
  };
741
+ var assertUniqueTableIndexLabels = (index, indexField) => {
742
+ const seen = /* @__PURE__ */ new Map();
743
+ for (let rowIndex = 0; rowIndex < index.length; rowIndex += 1) {
744
+ const label = index[rowIndex];
745
+ if (seen.has(label)) {
746
+ throw new JsonFrameValidationError("Cannot export duplicate index labels as a table primary key.", {
747
+ orient: "table",
748
+ path: `$.data[${rowIndex}][${JSON.stringify(indexField)}]`,
749
+ row: rowIndex,
750
+ column: indexField,
751
+ value: label
752
+ });
753
+ }
754
+ seen.set(label, rowIndex);
755
+ }
756
+ };
235
757
  var resolveIndexKeys = (state, orient) => {
236
758
  const seenKeys = /* @__PURE__ */ new Map();
237
759
  const keys = [];
@@ -301,19 +823,32 @@ var exportSplit = (state, materialized) => ({
301
823
  });
302
824
  var exportTable = (state, materialized, columnInfo, options) => {
303
825
  const indexField = resolveIndexFieldName(state, options);
826
+ if (indexField !== void 0) {
827
+ assertUniqueTableIndexLabels(state.index, indexField);
828
+ }
304
829
  const indexTemplate = getIndexFieldTemplate(state);
305
830
  const dataTemplates = getDataFieldTemplates(state);
306
831
  const fields = [];
307
832
  if (indexField !== void 0) {
308
833
  fields.push(
309
- indexTemplate === void 0 ? { name: indexField, type: inferIndexFieldType(state.index) } : cloneSchemaField(indexTemplate, indexField)
834
+ indexTemplate === void 0 ? { name: indexField, type: inferIndexFieldType(state.index) } : cloneSchemaField(indexTemplate, indexField, "$.schema.fields[0]")
310
835
  );
311
836
  }
312
837
  for (let columnIndex = 0; columnIndex < state.columns.length; columnIndex += 1) {
313
838
  const column = state.columns[columnIndex];
314
839
  const template = dataTemplates[columnIndex];
840
+ if (template === void 0) {
841
+ validateColumnValuesForType({
842
+ column,
843
+ columnIndex,
844
+ values: materialized.get(column) ?? [],
845
+ type: columnInfo.get(column).type,
846
+ orient: "table",
847
+ index: state.index
848
+ });
849
+ }
315
850
  fields.push(
316
- template === void 0 ? mapColumnTypeToSchemaField(column, columnInfo.get(column)) : cloneSchemaField(template, column)
851
+ template === void 0 ? mapColumnTypeToSchemaField(column, columnInfo.get(column)) : cloneSchemaField(template, column, `$.schema.fields[${fields.length}]`)
317
852
  );
318
853
  }
319
854
  const data = [];
@@ -333,185 +868,6 @@ var exportTable = (state, materialized, columnInfo, options) => {
333
868
  };
334
869
  };
335
870
 
336
- // src/frame/column.ts
337
- var INT32_MIN = -2147483648;
338
- var INT32_MAX = 2147483647;
339
- var freezeArray = (values) => Object.freeze([...values]);
340
- var freezeColumnInfo = (info) => Object.freeze({ ...info });
341
- var hasCategoricalMetadata = (field) => field.constraints?.enum !== void 0 || field.ordered === true || field.extDtype === "category";
342
- var mapTableSchemaFieldType = (field) => {
343
- if (hasCategoricalMetadata(field)) {
344
- return "categorical";
345
- }
346
- switch (field.type) {
347
- case "boolean":
348
- return "boolean";
349
- case "integer":
350
- case "year":
351
- return "integer";
352
- case "number":
353
- return "float";
354
- case "date":
355
- case "datetime":
356
- return "datetime";
357
- case "string":
358
- return "string";
359
- case "any":
360
- return "unknown";
361
- default:
362
- return "unknown";
363
- }
364
- };
365
- var inferColumnType = (values) => {
366
- let nullable = false;
367
- let sawBoolean = false;
368
- let sawString = false;
369
- let sawInteger = false;
370
- let sawFloat = false;
371
- let sawStructured = false;
372
- for (const value of values) {
373
- if (value === null) {
374
- nullable = true;
375
- continue;
376
- }
377
- if (typeof value === "boolean") {
378
- sawBoolean = true;
379
- continue;
380
- }
381
- if (typeof value === "string") {
382
- sawString = true;
383
- continue;
384
- }
385
- if (typeof value === "number") {
386
- if (Number.isInteger(value)) {
387
- sawInteger = true;
388
- } else {
389
- sawFloat = true;
390
- }
391
- continue;
392
- }
393
- sawStructured = true;
394
- }
395
- const nonNullKinds = [sawBoolean, sawString, sawInteger || sawFloat, sawStructured].filter(Boolean).length;
396
- if (nonNullKinds === 0) {
397
- return freezeColumnInfo({ type: "unknown", nullable });
398
- }
399
- if (sawStructured || nonNullKinds > 1 || sawBoolean && (sawString || sawInteger || sawFloat)) {
400
- return freezeColumnInfo({ type: "mixed", nullable });
401
- }
402
- if (sawString) {
403
- return freezeColumnInfo({ type: "string", nullable });
404
- }
405
- if (sawBoolean) {
406
- return freezeColumnInfo({ type: "boolean", nullable });
407
- }
408
- if (sawFloat) {
409
- return freezeColumnInfo({ type: "float", nullable });
410
- }
411
- return freezeColumnInfo({ type: "integer", nullable });
412
- };
413
- var packColumn = (values, info, packThreshold) => {
414
- if (packThreshold === 0 || values.length < packThreshold || info.nullable) {
415
- return freezeArray(values);
416
- }
417
- if (info.type === "integer") {
418
- const packed = new Int32Array(values.length);
419
- for (let index = 0; index < values.length; index += 1) {
420
- const value = values[index];
421
- if (typeof value !== "number" || !Number.isInteger(value) || value < INT32_MIN || value > INT32_MAX) {
422
- return freezeArray(values);
423
- }
424
- packed[index] = value;
425
- }
426
- return packed;
427
- }
428
- if (info.type === "float") {
429
- const packed = new Float64Array(values.length);
430
- for (let index = 0; index < values.length; index += 1) {
431
- const value = values[index];
432
- if (typeof value !== "number") {
433
- return freezeArray(values);
434
- }
435
- packed[index] = value;
436
- }
437
- return packed;
438
- }
439
- return freezeArray(values);
440
- };
441
- var validateExplicitColumnTypes = (columns, columnTypes) => {
442
- if (columnTypes === void 0) {
443
- return;
444
- }
445
- const knownColumns = new Set(columns);
446
- for (const column of Object.keys(columnTypes)) {
447
- if (!knownColumns.has(column)) {
448
- throw new JsonFrameOptionError(
449
- "`options.columnTypes` keys must match parsed column names.",
450
- `columnTypes.${column}`,
451
- columnTypes[column]
452
- );
453
- }
454
- }
455
- };
456
- var resolveSchemaTypeByColumn = (frame) => {
457
- if (frame.tableSchema === void 0) {
458
- return /* @__PURE__ */ new Map();
459
- }
460
- const schemaTypes = /* @__PURE__ */ new Map();
461
- for (const field of frame.tableSchema.fields) {
462
- if (frame.columns.includes(field.name)) {
463
- schemaTypes.set(field.name, mapTableSchemaFieldType(field));
464
- }
465
- }
466
- return schemaTypes;
467
- };
468
- var materializeColumn = (column) => Array.isArray(column) ? freezeArray(column) : freezeArray(Array.from(column));
469
- var materializeFrameData = (columns, data) => {
470
- const materialized = /* @__PURE__ */ new Map();
471
- for (const column of columns) {
472
- const stored = data.get(column);
473
- if (stored === void 0) {
474
- throw new JsonFrameValidationError("Stored frame data is missing a declared column.", {
475
- path: "$",
476
- column
477
- });
478
- }
479
- materialized.set(column, materializeColumn(stored));
480
- }
481
- return materialized;
482
- };
483
- var buildFrameState = (frame, options) => {
484
- validateExplicitColumnTypes(frame.columns, options.columnTypes);
485
- const schemaTypes = resolveSchemaTypeByColumn(frame);
486
- const data = /* @__PURE__ */ new Map();
487
- const columnInfo = /* @__PURE__ */ new Map();
488
- for (const column of frame.columns) {
489
- const values = frame.data.get(column);
490
- if (values === void 0) {
491
- throw new JsonFrameValidationError("Parsed frame data is missing a declared column.", {
492
- orient: frame.orient,
493
- column
494
- });
495
- }
496
- const inferred = inferColumnType(values);
497
- const resolvedType = schemaTypes.get(column) ?? options.columnTypes?.[column] ?? inferred.type;
498
- const info = freezeColumnInfo({ type: resolvedType, nullable: inferred.nullable });
499
- columnInfo.set(column, info);
500
- data.set(column, packColumn(values, info, options.packThreshold));
501
- }
502
- return {
503
- columns: freezeArray(frame.columns),
504
- index: freezeArray(frame.index),
505
- indexKind: frame.indexKind,
506
- data,
507
- columnInfo,
508
- ...frame.tableSchema === void 0 ? {} : { tableSchema: frame.tableSchema },
509
- ...frame.tableIndexField === void 0 ? {} : { tableIndexField: frame.tableIndexField }
510
- };
511
- };
512
- var createFrameStateFromData = (frame, options) => buildFrameState(frame, options);
513
- var createFrameState = (frame, options) => buildFrameState(frame, options);
514
-
515
871
  // src/frame/DataFrame.ts
516
872
  var hasOwn = Object.prototype.hasOwnProperty;
517
873
  var frameStateByInstance = /* @__PURE__ */ new WeakMap();
@@ -531,7 +887,7 @@ var cloneSchema2 = (schema, fields, primaryKey) => {
531
887
  }
532
888
  return Object.freeze(cloned);
533
889
  };
534
- var _state, _packThreshold, _DataFrame_instances, assertRowPosition_fn, materializedData_fn, createRow_fn, columnTypeOverrides_fn, rebuild_fn, selectSchema_fn, renameSchema_fn, resetIndexSchema_fn;
890
+ var _state, _packThreshold, _DataFrame_instances, assertRowPosition_fn, materializedData_fn, createRow_fn, columnInfoOverrides_fn, rebuild_fn, selectSchema_fn, renameSchema_fn, resetIndexSchema_fn;
535
891
  var DataFrame = class {
536
892
  constructor(state, packThreshold) {
537
893
  __privateAdd(this, _DataFrame_instances);
@@ -593,18 +949,22 @@ var DataFrame = class {
593
949
  }
594
950
  row(position) {
595
951
  __privateMethod(this, _DataFrame_instances, assertRowPosition_fn).call(this, position);
596
- return __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, __privateMethod(this, _DataFrame_instances, materializedData_fn).call(this), position);
952
+ return __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, position);
597
953
  }
598
954
  rows() {
599
- const materialized = __privateMethod(this, _DataFrame_instances, materializedData_fn).call(this);
600
- const rows = __privateGet(this, _state).index.map((_, position) => __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, materialized, position));
955
+ const rows = __privateGet(this, _state).index.map((_, position) => __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, position));
601
956
  return freezeArray2(rows);
602
957
  }
603
958
  filter(predicate) {
604
- const materialized = __privateMethod(this, _DataFrame_instances, materializedData_fn).call(this);
959
+ if (typeof predicate !== "function") {
960
+ throw new JsonFrameValidationError("`filter` predicate must be a function.", {
961
+ path: "$.predicate",
962
+ value: predicate
963
+ });
964
+ }
605
965
  const keptPositions = [];
606
966
  for (let position = 0; position < this.length; position += 1) {
607
- if (predicate(__privateMethod(this, _DataFrame_instances, createRow_fn).call(this, materialized, position), __privateGet(this, _state).index[position], position)) {
967
+ if (predicate(__privateMethod(this, _DataFrame_instances, createRow_fn).call(this, position), __privateGet(this, _state).index[position], position)) {
608
968
  keptPositions.push(position);
609
969
  }
610
970
  }
@@ -621,11 +981,16 @@ var DataFrame = class {
621
981
  });
622
982
  }
623
983
  sort(compare) {
624
- const materialized = __privateMethod(this, _DataFrame_instances, materializedData_fn).call(this);
984
+ if (typeof compare !== "function") {
985
+ throw new JsonFrameValidationError("`sort` compare must be a function.", {
986
+ path: "$.compare",
987
+ value: compare
988
+ });
989
+ }
625
990
  const ordering = __privateGet(this, _state).index.map((indexLabel, position) => ({
626
991
  indexLabel,
627
992
  position,
628
- row: __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, materialized, position)
993
+ row: __privateMethod(this, _DataFrame_instances, createRow_fn).call(this, position)
629
994
  }));
630
995
  ordering.sort((left, right) => {
631
996
  const result = compare(left.row, right.row, left.indexLabel, right.indexLabel, left.position, right.position);
@@ -673,7 +1038,26 @@ var DataFrame = class {
673
1038
  });
674
1039
  }
675
1040
  rename(mapping) {
676
- const nextColumns = __privateGet(this, _state).columns.map((column) => hasOwn.call(mapping, column) ? mapping[column] : column);
1041
+ if (mapping === null || typeof mapping !== "object" || Array.isArray(mapping)) {
1042
+ throw new JsonFrameValidationError("`rename` mapping must be a non-array object.", {
1043
+ path: "$.mapping",
1044
+ value: mapping
1045
+ });
1046
+ }
1047
+ const nextColumns = __privateGet(this, _state).columns.map((column) => {
1048
+ if (!hasOwn.call(mapping, column)) {
1049
+ return column;
1050
+ }
1051
+ const renamedColumn = mapping[column];
1052
+ if (typeof renamedColumn !== "string") {
1053
+ throw new JsonFrameValidationError("`rename` mapping values must be strings for existing columns.", {
1054
+ path: `$.mapping[${JSON.stringify(column)}]`,
1055
+ column,
1056
+ value: renamedColumn
1057
+ });
1058
+ }
1059
+ return renamedColumn;
1060
+ });
677
1061
  const seen = /* @__PURE__ */ new Set();
678
1062
  for (let position = 0; position < nextColumns.length; position += 1) {
679
1063
  const column = nextColumns[position];
@@ -719,19 +1103,35 @@ assertRowPosition_fn = function(position) {
719
1103
  materializedData_fn = function() {
720
1104
  return materializeFrameData(__privateGet(this, _state).columns, __privateGet(this, _state).data);
721
1105
  };
722
- createRow_fn = function(materialized, position) {
1106
+ createRow_fn = function(position) {
723
1107
  const row = /* @__PURE__ */ Object.create(null);
724
1108
  for (const column of __privateGet(this, _state).columns) {
725
- row[column] = materialized.get(column)[position];
1109
+ const stored = __privateGet(this, _state).data.get(column);
1110
+ if (stored === void 0) {
1111
+ throw new JsonFrameValidationError("Stored frame data is missing a declared column.", {
1112
+ path: "$",
1113
+ column
1114
+ });
1115
+ }
1116
+ row[column] = getStoredColumnValue(stored, position);
726
1117
  }
727
1118
  return Object.freeze(row);
728
1119
  };
729
- columnTypeOverrides_fn = function(columns, sourceColumns) {
730
- return Object.freeze(
731
- Object.fromEntries(
732
- columns.map((column, position) => [column, __privateGet(this, _state).columnInfo.get(sourceColumns[position]).type])
733
- )
734
- );
1120
+ columnInfoOverrides_fn = function(columns, sourceColumns) {
1121
+ const columnInfo = /* @__PURE__ */ new Map();
1122
+ for (let position = 0; position < columns.length; position += 1) {
1123
+ const column = columns[position];
1124
+ const sourceColumn = sourceColumns[position];
1125
+ const sourceInfo = __privateGet(this, _state).columnInfo.get(sourceColumn);
1126
+ if (sourceInfo === void 0) {
1127
+ throw new JsonFrameValidationError("Stored frame metadata is missing a declared column.", {
1128
+ path: "$",
1129
+ column: sourceColumn
1130
+ });
1131
+ }
1132
+ columnInfo.set(column, sourceInfo);
1133
+ }
1134
+ return columnInfo;
735
1135
  };
736
1136
  rebuild_fn = function({
737
1137
  columns,
@@ -741,31 +1141,31 @@ rebuild_fn = function({
741
1141
  indexKind,
742
1142
  schema
743
1143
  }) {
744
- const materialized = __privateMethod(this, _DataFrame_instances, materializedData_fn).call(this);
745
1144
  const nextData = /* @__PURE__ */ new Map();
1145
+ const nextColumnInfo = __privateMethod(this, _DataFrame_instances, columnInfoOverrides_fn).call(this, columns, sourceColumns);
746
1146
  for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
747
1147
  const column = columns[columnIndex];
748
- const sourceColumn = materialized.get(sourceColumns[columnIndex]);
749
- if (sourceColumn === void 0) {
1148
+ const sourceStoredColumn = __privateGet(this, _state).data.get(sourceColumns[columnIndex]);
1149
+ if (sourceStoredColumn === void 0) {
750
1150
  throw new JsonFrameValidationError("Stored frame data is missing a declared column.", {
751
1151
  path: "$",
752
1152
  column
753
1153
  });
754
1154
  }
755
- nextData.set(column, freezeArray2(positions.map((position) => sourceColumn[position])));
1155
+ nextData.set(column, rebuildStoredColumn(sourceStoredColumn, positions));
756
1156
  }
757
- const state = createFrameStateFromData(
1157
+ const state = createFrameStateFromRebuiltData(
758
1158
  {
759
1159
  columns,
760
1160
  index,
761
1161
  indexKind,
762
1162
  data: nextData,
1163
+ columnInfo: nextColumnInfo,
763
1164
  ...schema.tableSchema === void 0 ? {} : { tableSchema: schema.tableSchema },
764
1165
  ...schema.tableIndexField === void 0 ? {} : { tableIndexField: schema.tableIndexField }
765
1166
  },
766
1167
  {
767
- packThreshold: __privateGet(this, _packThreshold),
768
- columnTypes: __privateMethod(this, _DataFrame_instances, columnTypeOverrides_fn).call(this, columns, sourceColumns)
1168
+ packThreshold: __privateGet(this, _packThreshold)
769
1169
  }
770
1170
  );
771
1171
  return createDataFrame(state, __privateGet(this, _packThreshold));
@@ -933,11 +1333,11 @@ var normalizeFromOrientOptions = (options) => {
933
1333
 
934
1334
  // src/parse/parse.ts
935
1335
  var hasOwn2 = Object.prototype.hasOwnProperty;
936
- var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1336
+ var IDENTIFIER_PATTERN2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
937
1337
  var freezeArray3 = (values) => Object.freeze([...values]);
938
- var appendPropertyPath = (path, key) => IDENTIFIER_PATTERN.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
1338
+ var appendPropertyPath = (path, key) => IDENTIFIER_PATTERN2.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
939
1339
  var appendIndexPath = (path, index) => `${path}[${index}]`;
940
- var isPlainObject = (value) => {
1340
+ var isPlainObject2 = (value) => {
941
1341
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
942
1342
  return false;
943
1343
  }
@@ -957,6 +1357,18 @@ var assertSupportedIndexLabel = (value, orient, path) => {
957
1357
  value
958
1358
  });
959
1359
  };
1360
+ var assertUniqueTablePrimaryKeyLabel = (seen, label, row, column, path) => {
1361
+ if (seen.has(label)) {
1362
+ throw new JsonFrameValidationError("Table primary-key values must be unique.", {
1363
+ orient: "table",
1364
+ path,
1365
+ row,
1366
+ column,
1367
+ value: label
1368
+ });
1369
+ }
1370
+ seen.set(label, row);
1371
+ };
960
1372
  var assertUniqueStringColumns = (values, orient, path) => {
961
1373
  const columns = [];
962
1374
  const seen = /* @__PURE__ */ new Set();
@@ -1014,52 +1426,6 @@ var finalizeFrame = ({
1014
1426
  ...tableIndexField === void 0 ? {} : { tableIndexField }
1015
1427
  };
1016
1428
  };
1017
- var validateJsonCompatible = (value, path = "$", stack = /* @__PURE__ */ new Set()) => {
1018
- if (value === null || typeof value === "string" || typeof value === "boolean") {
1019
- return value;
1020
- }
1021
- if (typeof value === "number") {
1022
- if (!Number.isFinite(value)) {
1023
- throw new JsonFrameValidationError("Numbers must be finite JSON values.", { path, value });
1024
- }
1025
- return value;
1026
- }
1027
- if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
1028
- throw new JsonFrameValidationError("Input must contain only JSON-compatible values.", { path, value });
1029
- }
1030
- if (Array.isArray(value)) {
1031
- if (stack.has(value)) {
1032
- throw new JsonFrameValidationError("Input contains a cyclic array.", { path, value });
1033
- }
1034
- stack.add(value);
1035
- const normalized2 = [];
1036
- for (let index = 0; index < value.length; index += 1) {
1037
- if (!(index in value)) {
1038
- stack.delete(value);
1039
- throw new JsonFrameValidationError("Sparse arrays are not valid JSON input.", {
1040
- path: appendIndexPath(path, index),
1041
- value
1042
- });
1043
- }
1044
- normalized2.push(validateJsonCompatible(value[index], appendIndexPath(path, index), stack));
1045
- }
1046
- stack.delete(value);
1047
- return normalized2;
1048
- }
1049
- if (!isPlainObject(value)) {
1050
- throw new JsonFrameValidationError("Input objects must be plain JSON objects or arrays.", { path, value });
1051
- }
1052
- if (stack.has(value)) {
1053
- throw new JsonFrameValidationError("Input contains a cyclic object.", { path, value });
1054
- }
1055
- stack.add(value);
1056
- const normalized = /* @__PURE__ */ Object.create(null);
1057
- for (const [key, entryValue] of Object.entries(value)) {
1058
- normalized[key] = validateJsonCompatible(entryValue, appendPropertyPath(path, key), stack);
1059
- }
1060
- stack.delete(value);
1061
- return normalized;
1062
- };
1063
1429
  var detectOrient = (value) => {
1064
1430
  if (Array.isArray(value)) {
1065
1431
  if (value.length === 0) {
@@ -1071,7 +1437,7 @@ var detectOrient = (value) => {
1071
1437
  if (value.every((entry) => Array.isArray(entry))) {
1072
1438
  return "values";
1073
1439
  }
1074
- if (value.every((entry) => isPlainObject(entry))) {
1440
+ if (value.every((entry) => isPlainObject2(entry))) {
1075
1441
  return "records";
1076
1442
  }
1077
1443
  throw new JsonFrameValidationError("Auto detection requires a non-empty array of row objects or row arrays.", {
@@ -1079,20 +1445,20 @@ var detectOrient = (value) => {
1079
1445
  value
1080
1446
  });
1081
1447
  }
1082
- if (!isPlainObject(value)) {
1448
+ if (!isPlainObject2(value)) {
1083
1449
  throw new JsonFrameValidationError("Auto detection requires a JSON array or object payload.", {
1084
1450
  path: "$",
1085
1451
  value
1086
1452
  });
1087
1453
  }
1088
- if (isPlainObject(value.schema) && Array.isArray(value.data)) {
1454
+ if (isPlainObject2(value.schema) && Array.isArray(value.data)) {
1089
1455
  return "table";
1090
1456
  }
1091
1457
  if (Array.isArray(value.columns) && Array.isArray(value.data) && (value.index === void 0 || Array.isArray(value.index))) {
1092
1458
  return "split";
1093
1459
  }
1094
1460
  const keys = Object.keys(value);
1095
- if (keys.length === 0 || keys.every((key) => isPlainObject(value[key]))) {
1461
+ if (keys.length === 0 || keys.every((key) => isPlainObject2(value[key]))) {
1096
1462
  throw new AmbiguousOrientError("Auto detection cannot distinguish between index and columns payloads.", [
1097
1463
  "index",
1098
1464
  "columns"
@@ -1114,7 +1480,7 @@ var parseRecords = (value, orient) => {
1114
1480
  for (let rowIndex = 0; rowIndex < value.length; rowIndex += 1) {
1115
1481
  const row = value[rowIndex];
1116
1482
  const rowPath = appendIndexPath("$", rowIndex);
1117
- if (!isPlainObject(row)) {
1483
+ if (!isPlainObject2(row)) {
1118
1484
  throw new JsonFrameValidationError("`records` rows must be JSON objects.", {
1119
1485
  orient,
1120
1486
  path: rowPath,
@@ -1143,7 +1509,7 @@ var parseRecords = (value, orient) => {
1143
1509
  });
1144
1510
  };
1145
1511
  var parseIndex = (value, orient) => {
1146
- if (!isPlainObject(value)) {
1512
+ if (!isPlainObject2(value)) {
1147
1513
  throw new JsonFrameValidationError("`index` payloads must be objects keyed by index label.", {
1148
1514
  orient,
1149
1515
  path: "$",
@@ -1158,7 +1524,7 @@ var parseIndex = (value, orient) => {
1158
1524
  const indexLabel = index[rowIndex];
1159
1525
  const row = value[indexLabel];
1160
1526
  const rowPath = appendPropertyPath("$", indexLabel);
1161
- if (!isPlainObject(row)) {
1527
+ if (!isPlainObject2(row)) {
1162
1528
  throw new JsonFrameValidationError("`index` payload rows must be JSON objects.", {
1163
1529
  orient,
1164
1530
  path: rowPath,
@@ -1186,7 +1552,7 @@ var parseIndex = (value, orient) => {
1186
1552
  });
1187
1553
  };
1188
1554
  var parseColumns = (value, orient) => {
1189
- if (!isPlainObject(value)) {
1555
+ if (!isPlainObject2(value)) {
1190
1556
  throw new JsonFrameValidationError("`columns` payloads must be objects keyed by column label.", {
1191
1557
  orient,
1192
1558
  path: "$",
@@ -1208,7 +1574,7 @@ var parseColumns = (value, orient) => {
1208
1574
  for (const column of columns) {
1209
1575
  const columnValue = value[column];
1210
1576
  const columnPath = appendPropertyPath("$", column);
1211
- if (!isPlainObject(columnValue)) {
1577
+ if (!isPlainObject2(columnValue)) {
1212
1578
  throw new JsonFrameValidationError("`columns` payload values must be objects keyed by index label.", {
1213
1579
  orient,
1214
1580
  path: columnPath,
@@ -1287,7 +1653,7 @@ var parseValues = (value, options, orient) => {
1287
1653
  });
1288
1654
  };
1289
1655
  var parseSplit = (value, orient) => {
1290
- if (!isPlainObject(value)) {
1656
+ if (!isPlainObject2(value)) {
1291
1657
  throw new JsonFrameValidationError("`split` payloads must be objects.", { orient, path: "$", value });
1292
1658
  }
1293
1659
  if (!Array.isArray(value.columns)) {
@@ -1362,7 +1728,7 @@ var parseSplit = (value, orient) => {
1362
1728
  });
1363
1729
  };
1364
1730
  var cloneConstraints = (value, orient, path) => {
1365
- if (!isPlainObject(value)) {
1731
+ if (!isPlainObject2(value)) {
1366
1732
  throw new JsonFrameValidationError("Table Schema field constraints must be an object when present.", {
1367
1733
  orient,
1368
1734
  path,
@@ -1404,7 +1770,7 @@ var cloneConstraints = (value, orient, path) => {
1404
1770
  var cloneTableField = (value, index) => {
1405
1771
  const orient = "table";
1406
1772
  const path = appendIndexPath("$.schema.fields", index);
1407
- if (!isPlainObject(value)) {
1773
+ if (!isPlainObject2(value)) {
1408
1774
  throw new JsonFrameValidationError("Table Schema fields must be objects.", {
1409
1775
  orient,
1410
1776
  path,
@@ -1451,7 +1817,7 @@ var cloneTableField = (value, index) => {
1451
1817
  };
1452
1818
  var cloneTableSchema = (value) => {
1453
1819
  const orient = "table";
1454
- if (!isPlainObject(value)) {
1820
+ if (!isPlainObject2(value)) {
1455
1821
  throw new JsonFrameValidationError("`table.schema` must be an object.", {
1456
1822
  orient,
1457
1823
  path: "$.schema",
@@ -1527,7 +1893,7 @@ var cloneTableSchema = (value) => {
1527
1893
  return Object.freeze(cloned);
1528
1894
  };
1529
1895
  var parseTable = (value, orient) => {
1530
- if (!isPlainObject(value)) {
1896
+ if (!isPlainObject2(value)) {
1531
1897
  throw new JsonFrameValidationError("`table` payloads must be objects.", { orient, path: "$", value });
1532
1898
  }
1533
1899
  const schema = cloneTableSchema(value.schema);
@@ -1552,10 +1918,11 @@ var parseTable = (value, orient) => {
1552
1918
  const fieldNames = new Set(schema.fields.map((field) => field.name));
1553
1919
  const index = [];
1554
1920
  const indexKind = primaryKey === void 0 ? "synthetic" : "source";
1921
+ const seenPrimaryKeyLabels = /* @__PURE__ */ new Map();
1555
1922
  for (let rowIndex = 0; rowIndex < value.data.length; rowIndex += 1) {
1556
1923
  const row = value.data[rowIndex];
1557
1924
  const rowPath = appendIndexPath("$.data", rowIndex);
1558
- if (!isPlainObject(row)) {
1925
+ if (!isPlainObject2(row)) {
1559
1926
  throw new JsonFrameValidationError("`table.data` rows must be objects.", {
1560
1927
  orient,
1561
1928
  path: rowPath,
@@ -1585,9 +1952,10 @@ var parseTable = (value, orient) => {
1585
1952
  value: row
1586
1953
  });
1587
1954
  } else {
1588
- index.push(
1589
- assertSupportedIndexLabel(row[primaryKey], orient, appendPropertyPath(rowPath, primaryKey))
1590
- );
1955
+ const primaryKeyPath = appendPropertyPath(rowPath, primaryKey);
1956
+ const indexLabel = assertSupportedIndexLabel(row[primaryKey], orient, primaryKeyPath);
1957
+ assertUniqueTablePrimaryKeyLabel(seenPrimaryKeyLabels, indexLabel, rowIndex, primaryKey, primaryKeyPath);
1958
+ index.push(indexLabel);
1591
1959
  }
1592
1960
  for (const column of columns) {
1593
1961
  data.get(column).push(hasOwn2.call(row, column) ? row[column] : null);
@@ -1617,7 +1985,7 @@ var parseInput = (input, options) => {
1617
1985
  throw error;
1618
1986
  }
1619
1987
  })();
1620
- const jsonValue = validateJsonCompatible(parsedValue);
1988
+ const jsonValue = cloneJsonCompatible(parsedValue);
1621
1989
  const orient = options.orient === "auto" ? detectOrient(jsonValue) : options.orient;
1622
1990
  switch (orient) {
1623
1991
  case "records":
@@ -1649,6 +2017,7 @@ function fromOrient(input, options) {
1649
2017
  0 && (module.exports = {
1650
2018
  AmbiguousOrientError,
1651
2019
  ExportKeyCollisionError,
2020
+ JSON_FRAME_MAX_DEPTH,
1652
2021
  JsonFrameError,
1653
2022
  JsonFrameOptionError,
1654
2023
  JsonFrameParseError,