@sixb/ducklake 0.1.4 → 0.1.6

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/dist/index.js CHANGED
@@ -1,52 +1,145 @@
1
1
  // @bun
2
- // src/internal/ducklake-connection-manager.ts
3
- import { LakeStorageError as LakeStorageError4 } from "@sixb/core/lake-storage";
2
+ // src/internal/ducklake-change-reader.ts
3
+ import { randomUUID } from "crypto";
4
+ import {
5
+ LakeStorageError as LakeStorageError7,
6
+ resolveDatasetChangeColumns
7
+ } from "@sixb/core/lake-storage";
4
8
 
5
- // src/internal/catalog-key.ts
6
- import { resolve } from "path";
7
- function localCatalogCoordinationKey(options) {
8
- switch (options.catalog.type) {
9
- case "duckdb":
10
- case "sqlite":
11
- if (options.catalog.path === ":memory:") {
12
- return;
13
- }
14
- return `${options.catalog.type}:${resolve(options.catalog.path)}`;
15
- case "custom":
16
- return `custom:${options.catalog.uri}`;
17
- case "postgres":
18
- return;
9
+ // src/internal/duckdb-row.ts
10
+ import { LakeStorageError } from "@sixb/core/lake-storage";
11
+ function getString(row, key) {
12
+ const value = row[key];
13
+ if (typeof value !== "string") {
14
+ throw new LakeStorageError(`[SixbDuckLake] Expected DuckDB column '${key}' to be a string, got ${typeof value}.`);
19
15
  }
16
+ return value;
17
+ }
18
+ function getOptionalString(row, key) {
19
+ const value = row[key];
20
+ if (value === null || value === undefined) {
21
+ return;
22
+ }
23
+ if (typeof value !== "string") {
24
+ throw new LakeStorageError(`[SixbDuckLake] Expected DuckDB column '${key}' to be a string, got ${typeof value}.`);
25
+ }
26
+ return value;
27
+ }
28
+ function getBoolean(row, key) {
29
+ const value = row[key];
30
+ if (typeof value !== "boolean") {
31
+ throw new LakeStorageError(`[SixbDuckLake] Expected DuckDB column '${key}' to be a boolean, got ${typeof value}.`);
32
+ }
33
+ return value;
34
+ }
35
+ function getBigIntLike(row, key) {
36
+ const value = row[key];
37
+ if (typeof value === "bigint") {
38
+ return value;
39
+ }
40
+ if (typeof value === "number" && Number.isInteger(value)) {
41
+ return BigInt(value);
42
+ }
43
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
44
+ return BigInt(value);
45
+ }
46
+ throw new LakeStorageError(`[SixbDuckLake] Expected DuckDB column '${key}' to be an integer, got ${typeof value}.`);
47
+ }
48
+ function getOptionalBigIntLike(row, key) {
49
+ const value = row[key];
50
+ if (value === null || value === undefined) {
51
+ return;
52
+ }
53
+ return getBigIntLike(row, key);
54
+ }
55
+ function getDate(row, key) {
56
+ const value = row[key];
57
+ if (value instanceof Date) {
58
+ return value;
59
+ }
60
+ if (typeof value === "string") {
61
+ return new Date(value);
62
+ }
63
+ throw new LakeStorageError(`[SixbDuckLake] Expected DuckDB column '${key}' to be a date, got ${typeof value}.`);
20
64
  }
21
65
 
22
- // src/internal/duckdb-runtime.ts
23
- import {
24
- DuckDBInstance
25
- } from "@duckdb/node-api";
26
- import { LakeStorageError as LakeStorageError3 } from "@sixb/core/lake-storage";
27
-
28
- // src/internal/duckdb-value-converter.ts
29
- import {
30
- DuckDBDecimalValue,
31
- DuckDBTypeId,
32
- JSDuckDBValueConverter
33
- } from "@duckdb/node-api";
34
- import { LakeStorageError } from "@sixb/core/lake-storage";
35
- var sixbDuckDbValueConverter = (value, type, converter) => {
36
- if (value === null) {
66
+ // src/internal/names.ts
67
+ import { LakeStorageError as LakeStorageError2 } from "@sixb/core/lake-storage";
68
+ var DATASET_TABLE_PREFIX = "sixb__ds__";
69
+ var textEncoder = new TextEncoder;
70
+ var textDecoder = new TextDecoder("utf-8", { fatal: true });
71
+ function isInlineByte(byte) {
72
+ return byte >= 97 && byte <= 122 || byte >= 48 && byte <= 57;
73
+ }
74
+ function isHexPair(value) {
75
+ return /^[0-9a-f]{2}$/.test(value);
76
+ }
77
+ function encodeDatasetId(datasetId) {
78
+ if (datasetId.trim().length === 0) {
79
+ throw new LakeStorageError2("[SixbDuckLake] Dataset id must not be empty.");
80
+ }
81
+ let encoded = "";
82
+ for (const byte of textEncoder.encode(datasetId)) {
83
+ if (isInlineByte(byte)) {
84
+ encoded += String.fromCharCode(byte);
85
+ } else if (byte === 46) {
86
+ encoded += "__";
87
+ } else {
88
+ encoded += `_${byte.toString(16).padStart(2, "0")}`;
89
+ }
90
+ }
91
+ return encoded;
92
+ }
93
+ function decodeDatasetId(encoded) {
94
+ if (encoded.length === 0) {
37
95
  return null;
38
96
  }
39
- if (type.typeId === DuckDBTypeId.DECIMAL) {
40
- if (!(value instanceof DuckDBDecimalValue)) {
41
- throw new LakeStorageError("[SixbDuckLake] DuckDB returned an invalid DECIMAL value.");
97
+ const bytes = [];
98
+ let index = 0;
99
+ while (index < encoded.length) {
100
+ const char = encoded[index];
101
+ const byte = char.charCodeAt(0);
102
+ if (isInlineByte(byte)) {
103
+ bytes.push(byte);
104
+ index += 1;
105
+ continue;
42
106
  }
43
- return value.toString();
107
+ if (char !== "_") {
108
+ return null;
109
+ }
110
+ if (encoded[index + 1] === "_") {
111
+ bytes.push(46);
112
+ index += 2;
113
+ continue;
114
+ }
115
+ const hex = encoded.slice(index + 1, index + 3);
116
+ if (!isHexPair(hex)) {
117
+ return null;
118
+ }
119
+ bytes.push(Number.parseInt(hex, 16));
120
+ index += 3;
44
121
  }
45
- return JSDuckDBValueConverter(value, type, converter);
46
- };
122
+ try {
123
+ return textDecoder.decode(new Uint8Array(bytes));
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+ function encodeDatasetTableName(datasetId) {
129
+ return `${DATASET_TABLE_PREFIX}${encodeDatasetId(datasetId)}`;
130
+ }
131
+ function encodeDatasetSequenceTableName(datasetId) {
132
+ return `sixb__seq__${encodeDatasetTableName(datasetId)}`;
133
+ }
134
+ function decodeDatasetTableName(tableName) {
135
+ if (!tableName.startsWith(DATASET_TABLE_PREFIX)) {
136
+ return null;
137
+ }
138
+ return decodeDatasetId(tableName.slice(DATASET_TABLE_PREFIX.length));
139
+ }
47
140
 
48
141
  // src/internal/sql.ts
49
- import { LakeStorageError as LakeStorageError2 } from "@sixb/core/lake-storage";
142
+ import { LakeStorageError as LakeStorageError3 } from "@sixb/core/lake-storage";
50
143
  var DEFAULT_ALIAS = "sixb_lake";
51
144
  function duckLakeAlias(options) {
52
145
  return options.alias ?? DEFAULT_ALIAS;
@@ -70,7 +163,7 @@ function duckLakeMetadataTableName(options, tableName) {
70
163
  }
71
164
  function quoteIdentifier(identifier) {
72
165
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
73
- throw new LakeStorageError2(`[SixbDuckLake] Invalid SQL identifier '${identifier}'.`);
166
+ throw new LakeStorageError3(`[SixbDuckLake] Invalid SQL identifier '${identifier}'.`);
74
167
  }
75
168
  return `"${identifier}"`;
76
169
  }
@@ -279,7 +372,7 @@ function optionalIdentifierParameter(name, value) {
279
372
  return [];
280
373
  }
281
374
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
282
- throw new LakeStorageError2(`[SixbDuckLake] Invalid SQL identifier '${value}'.`);
375
+ throw new LakeStorageError3(`[SixbDuckLake] Invalid SQL identifier '${value}'.`);
283
376
  }
284
377
  return [`${name} ${value}`];
285
378
  }
@@ -288,23 +381,441 @@ function appendPostgresConnectionParameter(parameters, names, name, value) {
288
381
  return;
289
382
  }
290
383
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
291
- throw new LakeStorageError2(`[SixbDuckLake] Invalid PostgreSQL catalog parameter '${name}'.`);
384
+ throw new LakeStorageError3(`[SixbDuckLake] Invalid PostgreSQL catalog parameter '${name}'.`);
292
385
  }
293
386
  const normalizedName = name.toLowerCase();
294
387
  if (names.has(normalizedName)) {
295
- throw new LakeStorageError2(`[SixbDuckLake] Duplicate PostgreSQL catalog parameter '${name}'.`);
388
+ throw new LakeStorageError3(`[SixbDuckLake] Duplicate PostgreSQL catalog parameter '${name}'.`);
389
+ }
390
+ names.add(normalizedName);
391
+ parameters.push(`${name}=${quotePostgresConnectionValue(String(value))}`);
392
+ }
393
+ function quotePostgresConnectionValue(value) {
394
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
395
+ }
396
+ function pathScheme(path) {
397
+ const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(path);
398
+ return match ? match[1].toLowerCase() : null;
399
+ }
400
+
401
+ // src/internal/ducklake-dataset-table-ref.ts
402
+ async function resolveDatasetTableRef(options, runtime, datasetId) {
403
+ const tableName = encodeDatasetTableName(datasetId);
404
+ const ducklakeTable = duckLakeMetadataTableName(options, "ducklake_table");
405
+ const [row] = await runtime.query(`
406
+ SELECT table_id
407
+ FROM ${ducklakeTable}
408
+ WHERE table_name = ${quoteSqlString(tableName)}
409
+ AND end_snapshot IS NULL
410
+ LIMIT 1
411
+ `);
412
+ return row === undefined ? null : {
413
+ datasetId,
414
+ tableName,
415
+ tableId: getBigIntLike(row, "table_id")
416
+ };
417
+ }
418
+
419
+ // src/internal/schema.ts
420
+ import { LakeStorageError as LakeStorageError5 } from "@sixb/core/lake-storage";
421
+
422
+ // src/internal/duckdb-column-types.ts
423
+ function normalizeDuckDbTypeSql(typeSql) {
424
+ return typeSql.trim().toUpperCase().replace(/\s+/g, " ").replace(/\s*([(),])\s*/g, "$1");
425
+ }
426
+ function defineDuckDbColumnType(sql, aliases = []) {
427
+ const acceptedTypes = new Set([sql, ...aliases].map(normalizeDuckDbTypeSql));
428
+ return {
429
+ sql,
430
+ matches: (typeSql) => acceptedTypes.has(normalizeDuckDbTypeSql(typeSql))
431
+ };
432
+ }
433
+ function defineDuckDbDecimalType(precision, scale) {
434
+ return {
435
+ ...defineDuckDbColumnType(`DECIMAL(${precision}, ${scale})`),
436
+ precision,
437
+ scale
438
+ };
439
+ }
440
+ var DUCKDB_COLUMN_TYPES = {
441
+ string: defineDuckDbColumnType("VARCHAR"),
442
+ boolean: defineDuckDbColumnType("BOOLEAN"),
443
+ int64: defineDuckDbColumnType("BIGINT", ["INT64"]),
444
+ float64: defineDuckDbColumnType("DOUBLE", ["FLOAT64"]),
445
+ decimal: defineDuckDbDecimalType(38, 9),
446
+ date: defineDuckDbColumnType("DATE"),
447
+ timestamp: defineDuckDbColumnType("TIMESTAMPTZ", ["TIMESTAMP WITH TIME ZONE"]),
448
+ json: defineDuckDbColumnType("JSON"),
449
+ fileRef: defineDuckDbColumnType("STRUCT(blobId VARCHAR, digest VARCHAR, sizeBytes BIGINT, fileName VARCHAR, mediaType VARCHAR, logicalPath VARCHAR)")
450
+ };
451
+ function findDatasetColumnTypeForDuckDbSql(typeSql) {
452
+ for (const type of Object.keys(DUCKDB_COLUMN_TYPES)) {
453
+ if (DUCKDB_COLUMN_TYPES[type].matches(typeSql)) {
454
+ return type;
455
+ }
456
+ }
457
+ return;
458
+ }
459
+
460
+ // src/internal/duckdb-decimal.ts
461
+ import { normalizeDecimalValue } from "@sixb/core";
462
+ import { LakeStorageError as LakeStorageError4 } from "@sixb/core/lake-storage";
463
+ var decimalType = DUCKDB_COLUMN_TYPES.decimal;
464
+ var maxIntegerDigits = decimalType.precision - decimalType.scale;
465
+ function normalizeDuckDbDecimalValue(value, columnName) {
466
+ if (typeof value !== "string") {
467
+ throw new LakeStorageError4(`[SixbDuckLake] Decimal column '${columnName}' must be an exact decimal string.`);
468
+ }
469
+ let normalized;
470
+ try {
471
+ normalized = normalizeDecimalValue(value);
472
+ } catch {
473
+ throw new LakeStorageError4(`[SixbDuckLake] Decimal column '${columnName}' must be an exact decimal string.`);
474
+ }
475
+ const unsigned = normalized.startsWith("-") ? normalized.slice(1) : normalized;
476
+ const [integer, fraction = ""] = unsigned.split(".");
477
+ if (integer.length > maxIntegerDigits || fraction.length > decimalType.scale) {
478
+ throw new LakeStorageError4(`[SixbDuckLake] Decimal column '${columnName}' cannot be represented exactly as ${decimalType.sql}; expected at most ${maxIntegerDigits} integer digits and ${decimalType.scale} fractional digits.`);
479
+ }
480
+ return normalized;
481
+ }
482
+
483
+ // src/internal/schema.ts
484
+ function datasetColumnTypeToDuckDbSql(type) {
485
+ return DUCKDB_COLUMN_TYPES[type].sql;
486
+ }
487
+ function duckDbTypeToDatasetColumnType(typeSql) {
488
+ const type = findDatasetColumnTypeForDuckDbSql(typeSql);
489
+ if (type) {
490
+ return type;
491
+ }
492
+ throw new LakeStorageError5(`[SixbDuckLake] DuckDB column type '${typeSql}' cannot be mapped to a Sixb dataset column type.`);
493
+ }
494
+ function datasetColumnToDuckDbSql(column) {
495
+ const nullableSql = column.nullable ? "" : " NOT NULL";
496
+ return `${quoteIdentifier(column.name)} ${datasetColumnTypeToDuckDbSql(column.type)}${nullableSql}`;
497
+ }
498
+ function datasetSchemaToDuckDbColumnsSql(schema) {
499
+ return schema.columns.map((column) => datasetColumnToDuckDbSql(column)).join(", ");
500
+ }
501
+ function datasetSchemaToDuckDbNullableColumnsSql(schema) {
502
+ return schema.columns.map((column) => `${quoteIdentifier(column.name)} ${datasetColumnTypeToDuckDbSql(column.type)}`).join(", ");
503
+ }
504
+ function datasetSchemaColumnNamesSql(schema) {
505
+ return schema.columns.map((column) => quoteIdentifier(column.name)).join(", ");
506
+ }
507
+ function normalizeReadValue(value, column) {
508
+ if (value === null || value === undefined) {
509
+ return null;
510
+ }
511
+ switch (column.type) {
512
+ case "int64":
513
+ return typeof value === "bigint" ? value.toString() : value;
514
+ case "decimal":
515
+ return normalizeDuckDbDecimalValue(value, column.name);
516
+ case "date":
517
+ return value instanceof Date ? value.toISOString().slice(0, 10) : String(value);
518
+ case "timestamp":
519
+ return value instanceof Date ? value.toISOString() : String(value);
520
+ case "json":
521
+ return typeof value === "string" ? JSON.parse(value) : value;
522
+ case "fileRef":
523
+ return normalizeFileRef(value);
524
+ default:
525
+ return value;
526
+ }
527
+ }
528
+ function normalizeFileRef(value) {
529
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
530
+ throw new LakeStorageError5("[SixbDuckLake] DuckDB fileRef value must be a struct.");
531
+ }
532
+ const record = value;
533
+ return {
534
+ blobId: getStringField(record, "blobId"),
535
+ digest: getStringField(record, "digest"),
536
+ sizeBytes: getIntegerField(record, "sizeBytes"),
537
+ fileName: getOptionalStringField(record, "fileName"),
538
+ mediaType: getOptionalStringField(record, "mediaType"),
539
+ logicalPath: getOptionalStringField(record, "logicalPath")
540
+ };
541
+ }
542
+ function getStringField(record, key) {
543
+ const value = record[key];
544
+ if (typeof value !== "string") {
545
+ throw new LakeStorageError5(`[SixbDuckLake] DuckDB fileRef field '${key}' must be a string.`);
546
+ }
547
+ return value;
548
+ }
549
+ function getOptionalStringField(record, key) {
550
+ const value = record[key];
551
+ if (value === null || value === undefined) {
552
+ return;
553
+ }
554
+ if (typeof value !== "string") {
555
+ throw new LakeStorageError5(`[SixbDuckLake] DuckDB fileRef field '${key}' must be a string.`);
556
+ }
557
+ return value;
558
+ }
559
+ function getIntegerField(record, key) {
560
+ const value = record[key];
561
+ if (typeof value === "number" && Number.isInteger(value)) {
562
+ return value;
563
+ }
564
+ if (typeof value === "bigint") {
565
+ return Number(value);
566
+ }
567
+ throw new LakeStorageError5(`[SixbDuckLake] DuckDB fileRef field '${key}' must be an integer.`);
568
+ }
569
+
570
+ // src/internal/versions.ts
571
+ import { LakeStorageError as LakeStorageError6 } from "@sixb/core/lake-storage";
572
+ function parseVersionId(versionId) {
573
+ if (!versionId.startsWith("ducklake:")) {
574
+ throw new LakeStorageError6(`[SixbDuckLake] Invalid DuckLake version id '${versionId}'.`);
575
+ }
576
+ const snapshotId = versionId.slice("ducklake:".length);
577
+ if (!/^\d+$/.test(snapshotId)) {
578
+ throw new LakeStorageError6(`[SixbDuckLake] Invalid DuckLake version id '${versionId}'.`);
579
+ }
580
+ return snapshotId;
581
+ }
582
+ function toVersionId(snapshotId) {
583
+ return `ducklake:${snapshotId}`;
584
+ }
585
+ function parseCommitMetadata(value) {
586
+ if (typeof value !== "string") {
587
+ return;
588
+ }
589
+ let parsed;
590
+ try {
591
+ parsed = JSON.parse(value);
592
+ } catch {
593
+ return;
594
+ }
595
+ if (!isRecord(parsed) || !isRecord(parsed.sixb) || parsed.sixb.kind !== "datasetVersion" || typeof parsed.sixb.datasetId !== "string") {
596
+ return;
597
+ }
598
+ const mode = parsed.sixb.mode;
599
+ const commitId = parsed.sixb.commitId;
600
+ const rowCount = parsed.sixb.rowCount;
601
+ const validatedPrimaryKeyColumns = parsed.sixb.validatedPrimaryKeyColumns;
602
+ return {
603
+ kind: "datasetVersion",
604
+ datasetId: parsed.sixb.datasetId,
605
+ ...typeof commitId === "string" ? { commitId } : {},
606
+ ...mode === "snapshot" || mode === "append" || mode === "merge" || mode === "schema" ? { mode } : {},
607
+ ...isDatasetProducer(parsed.sixb.producer) ? { producer: parsed.sixb.producer } : {},
608
+ ...isDatasetVersionRefs(parsed.sixb.inputs) ? { inputs: parsed.sixb.inputs } : {},
609
+ ...typeof commitId === "string" && isRowCount(rowCount) ? { rowCount } : {},
610
+ ...typeof commitId === "string" && isValidatedPrimaryKeyColumns(validatedPrimaryKeyColumns) ? { validatedPrimaryKeyColumns } : {},
611
+ ...isSchemaChangeMetadata(parsed.sixb.schemaChange) ? { schemaChange: parsed.sixb.schemaChange } : {}
612
+ };
613
+ }
614
+ function parseInlineDataChange(changesMade, tableId) {
615
+ const tableIdText = tableId.toString();
616
+ let hasDataChange = false;
617
+ let hasDeleteChange = false;
618
+ for (const change of changesMade.split(",")) {
619
+ const [kind, changedTableId] = change.split(":");
620
+ if (changedTableId !== tableIdText) {
621
+ continue;
622
+ }
623
+ const normalizedKind = kind.toLowerCase();
624
+ if (normalizedKind.includes("insert") || normalizedKind.includes("delete")) {
625
+ hasDataChange = true;
626
+ }
627
+ if (normalizedKind.includes("delete")) {
628
+ hasDeleteChange = true;
629
+ }
630
+ }
631
+ return { hasDataChange, hasDeleteChange };
632
+ }
633
+ function isRecord(value) {
634
+ return typeof value === "object" && value !== null && !Array.isArray(value);
635
+ }
636
+ function isDatasetProducer(value) {
637
+ return isRecord(value) && (value.kind === "sync" || value.kind === "pipeline" || value.kind === "ingest") && optionalString(value.id) && optionalString(value.runId);
638
+ }
639
+ function isDatasetVersionRefs(value) {
640
+ return Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.datasetId === "string" && typeof item.versionId === "string");
641
+ }
642
+ function isSchemaChangeMetadata(value) {
643
+ return isRecord(value) && (value.addColumns === undefined || Array.isArray(value.addColumns) && value.addColumns.every((columnName) => typeof columnName === "string"));
644
+ }
645
+ function optionalString(value) {
646
+ return value === undefined || typeof value === "string";
647
+ }
648
+ function isRowCount(value) {
649
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
650
+ }
651
+ function isValidatedPrimaryKeyColumns(value) {
652
+ return Array.isArray(value) && value.length > 0 && value.every((column) => typeof column === "string" && column.trim().length > 0) && new Set(value).size === value.length;
653
+ }
654
+
655
+ // src/internal/ducklake-change-reader.ts
656
+ var PAGE_ROWS = 5000;
657
+
658
+ class DuckLakeChangeReader {
659
+ options;
660
+ connections;
661
+ snapshots;
662
+ constructor(options, connections, snapshots) {
663
+ this.options = options;
664
+ this.connections = connections;
665
+ this.snapshots = snapshots;
666
+ }
667
+ async readChanges(input) {
668
+ input.signal?.throwIfAborted();
669
+ const before = await this.snapshots.getVersion(input.datasetId, input.fromVersionId);
670
+ const after = await this.snapshots.getVersion(input.datasetId, input.toVersionId);
671
+ const columns = resolveDatasetChangeColumns(input, before, after);
672
+ if (!columns)
673
+ return null;
674
+ const lease = await this.connections.acquireAttachedRuntime();
675
+ const runtime = lease.runtime;
676
+ const temporary = quoteIdentifier(`sixb_delta_${randomUUID().replaceAll("-", "")}`);
677
+ let retained = false;
678
+ let created = false;
679
+ try {
680
+ const ref = await resolveDatasetTableRef(this.options, runtime, input.datasetId);
681
+ if (!ref)
682
+ return null;
683
+ const table = qualifiedTableName(this.options, ref.tableName);
684
+ const oldRelation = `${table} AT (VERSION => ${parseVersionId(input.fromVersionId)})`;
685
+ const newRelation = `${table} AT (VERSION => ${parseVersionId(input.toVersionId)})`;
686
+ const previousCount = await uniqueRowCount(runtime, oldRelation, input.keyColumns);
687
+ const nextCount = await uniqueRowCount(runtime, newRelation, input.keyColumns);
688
+ if (previousCount === null || nextCount === null)
689
+ return null;
690
+ input.signal?.throwIfAborted();
691
+ const key = quoteIdentifier(input.keyColumns[0]);
692
+ const values = columns.flatMap((column, index) => [
693
+ `b.${quoteIdentifier(column.name)} AS b${index}`,
694
+ `a.${quoteIdentifier(column.name)} AS a${index}`
695
+ ]);
696
+ const join = input.keyColumns.map((name) => `b.${quoteIdentifier(name)} = a.${quoteIdentifier(name)}`).join(" AND ");
697
+ const changed = columns.map((column) => `b.${quoteIdentifier(column.name)} IS DISTINCT FROM a.${quoteIdentifier(column.name)}`).join(" OR ");
698
+ await runtime.run(`CREATE TEMP TABLE ${temporary} AS
699
+ SELECT b.${key} IS NOT NULL AS before_present, a.${key} IS NOT NULL AS after_present,
700
+ ${values.join(", ")}
701
+ FROM (SELECT * FROM ${oldRelation}) b
702
+ FULL OUTER JOIN (SELECT * FROM ${newRelation}) a ON ${join}
703
+ WHERE b.${key} IS NULL OR a.${key} IS NULL OR ${changed}`);
704
+ created = true;
705
+ const [count] = await runtime.query(`SELECT count(*) AS count FROM ${temporary}`);
706
+ const changeCount = safeCount(count, "count");
707
+ input.signal?.throwIfAborted();
708
+ let closePromise;
709
+ const close = () => {
710
+ closePromise ??= runtime.run(`DROP TABLE IF EXISTS ${temporary}`);
711
+ return closePromise;
712
+ };
713
+ const changes = async function* () {
714
+ try {
715
+ let cursor = -1n;
716
+ let delivered = 0;
717
+ while (delivered < changeCount) {
718
+ input.signal?.throwIfAborted();
719
+ if (closePromise)
720
+ throw new LakeStorageError7("[SixbDuckLake] Change reader is closed.");
721
+ const page = await runtime.query(`SELECT rowid AS position, * FROM ${temporary}
722
+ WHERE rowid > ${cursor} ORDER BY rowid LIMIT ${PAGE_ROWS}`);
723
+ if (page.length === 0)
724
+ throw new LakeStorageError7("[SixbDuckLake] Change read ended before its declared count.");
725
+ for (const row of page) {
726
+ input.signal?.throwIfAborted();
727
+ delivered++;
728
+ yield {
729
+ before: row.before_present === true ? rowImage(row, "b", columns) : null,
730
+ after: row.after_present === true ? rowImage(row, "a", columns) : null
731
+ };
732
+ }
733
+ cursor = getBigIntLike(page[page.length - 1], "position");
734
+ }
735
+ } finally {
736
+ await close();
737
+ }
738
+ }();
739
+ retained = true;
740
+ return { fromRowCount: previousCount, toRowCount: nextCount, changeCount, changes, close };
741
+ } finally {
742
+ try {
743
+ if (!retained && created)
744
+ await runtime.run(`DROP TABLE IF EXISTS ${temporary}`);
745
+ } finally {
746
+ await lease.release();
747
+ }
748
+ }
749
+ }
750
+ }
751
+ async function uniqueRowCount(runtime, relation, keys) {
752
+ const names = keys.map(quoteIdentifier);
753
+ const invalid = names.map((name) => `${name} IS NULL OR trim(${name}) = ''`).join(" OR ");
754
+ const [row] = await runtime.query(`SELECT count(*) AS total,
755
+ count(DISTINCT (${names.join(", ")})) AS unique_keys,
756
+ count(*) FILTER (WHERE ${invalid}) AS invalid_keys FROM ${relation}`);
757
+ const count = safeCount(row, "total");
758
+ return safeCount(row, "unique_keys") === count && safeCount(row, "invalid_keys") === 0 ? count : null;
759
+ }
760
+ function safeCount(row, name) {
761
+ const count = Number(getBigIntLike(row, name));
762
+ if (!Number.isSafeInteger(count) || count < 0)
763
+ throw new LakeStorageError7("[SixbDuckLake] Change count exceeds the supported integer range.");
764
+ return count;
765
+ }
766
+ function rowImage(row, prefix, columns) {
767
+ return Object.fromEntries(columns.map((column, index) => [
768
+ column.name,
769
+ normalizeReadValue(row[`${prefix}${index}`], column)
770
+ ]));
771
+ }
772
+
773
+ // src/internal/ducklake-connection-manager.ts
774
+ import { LakeStorageError as LakeStorageError10 } from "@sixb/core/lake-storage";
775
+
776
+ // src/internal/catalog-key.ts
777
+ import { resolve } from "path";
778
+ function localCatalogCoordinationKey(options) {
779
+ switch (options.catalog.type) {
780
+ case "duckdb":
781
+ case "sqlite":
782
+ if (options.catalog.path === ":memory:") {
783
+ return;
784
+ }
785
+ return `${options.catalog.type}:${resolve(options.catalog.path)}`;
786
+ case "custom":
787
+ return `custom:${options.catalog.uri}`;
788
+ case "postgres":
789
+ return;
296
790
  }
297
- names.add(normalizedName);
298
- parameters.push(`${name}=${quotePostgresConnectionValue(String(value))}`);
299
- }
300
- function quotePostgresConnectionValue(value) {
301
- return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
302
- }
303
- function pathScheme(path) {
304
- const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(path);
305
- return match ? match[1].toLowerCase() : null;
306
791
  }
307
792
 
793
+ // src/internal/duckdb-runtime.ts
794
+ import {
795
+ DuckDBInstance
796
+ } from "@duckdb/node-api";
797
+ import { LakeStorageError as LakeStorageError9 } from "@sixb/core/lake-storage";
798
+
799
+ // src/internal/duckdb-value-converter.ts
800
+ import {
801
+ DuckDBDecimalValue,
802
+ DuckDBTypeId,
803
+ JSDuckDBValueConverter
804
+ } from "@duckdb/node-api";
805
+ import { LakeStorageError as LakeStorageError8 } from "@sixb/core/lake-storage";
806
+ var sixbDuckDbValueConverter = (value, type, converter) => {
807
+ if (value === null) {
808
+ return null;
809
+ }
810
+ if (type.typeId === DuckDBTypeId.DECIMAL) {
811
+ if (!(value instanceof DuckDBDecimalValue)) {
812
+ throw new LakeStorageError8("[SixbDuckLake] DuckDB returned an invalid DECIMAL value.");
813
+ }
814
+ return value.toString();
815
+ }
816
+ return JSDuckDBValueConverter(value, type, converter);
817
+ };
818
+
308
819
  // src/internal/duckdb-runtime.ts
309
820
  class NodeDuckDbRuntime {
310
821
  instance;
@@ -337,8 +848,8 @@ class NodeDuckDbRuntime {
337
848
  async* streamRows(sql, values) {
338
849
  this.assertAcceptingOperations();
339
850
  let releaseOperation;
340
- const operationFinished = new Promise((resolve2) => {
341
- releaseOperation = resolve2;
851
+ const operationFinished = new Promise((resolve) => {
852
+ releaseOperation = resolve;
342
853
  });
343
854
  const waitForTurn = this.operations.then(() => {
344
855
  this.assertNotClosed();
@@ -392,12 +903,12 @@ class NodeDuckDbRuntime {
392
903
  }
393
904
  assertAcceptingOperations() {
394
905
  if (this.closing || this.closed) {
395
- throw new LakeStorageError3("[SixbDuckLake] DuckDB runtime is closed.");
906
+ throw new LakeStorageError9("[SixbDuckLake] DuckDB runtime is closed.");
396
907
  }
397
908
  }
398
909
  assertNotClosed() {
399
910
  if (this.closed) {
400
- throw new LakeStorageError3("[SixbDuckLake] DuckDB runtime is closed.");
911
+ throw new LakeStorageError9("[SixbDuckLake] DuckDB runtime is closed.");
401
912
  }
402
913
  }
403
914
  }
@@ -509,7 +1020,7 @@ class DuckLakeConnectionManager {
509
1020
  }
510
1021
  assertOpen() {
511
1022
  if (this.closed) {
512
- throw new LakeStorageError4("[SixbDuckLake] DuckLakeStorage is closed.");
1023
+ throw new LakeStorageError10("[SixbDuckLake] DuckLakeStorage is closed.");
513
1024
  }
514
1025
  }
515
1026
  async runtime() {
@@ -793,8 +1304,8 @@ class DuckLakeConnectionManager {
793
1304
  }
794
1305
  const previous = this.localAttachmentLock.catch(() => {});
795
1306
  let release;
796
- const current = new Promise((resolve2) => {
797
- release = resolve2;
1307
+ const current = new Promise((resolve) => {
1308
+ release = resolve;
798
1309
  });
799
1310
  this.localAttachmentLock = previous.then(() => current);
800
1311
  await previous;
@@ -805,264 +1316,54 @@ class DuckLakeConnectionManager {
805
1316
  }
806
1317
  released = true;
807
1318
  release();
808
- };
809
- }
810
- async closeRuntimePromise(runtimePromise, options) {
811
- if (runtimePromise === undefined) {
812
- return;
813
- }
814
- let runtime;
815
- try {
816
- runtime = await runtimePromise;
817
- } catch {
818
- return;
819
- }
820
- await this.closeRuntime(runtime, options);
821
- }
822
- async closeRuntime(runtime, options) {
823
- if (options.detach) {
824
- try {
825
- await runtime.run(`DETACH ${quoteIdentifier(duckLakeAlias(this.options))}`);
826
- } catch {}
827
- }
828
- await runtime.close();
829
- }
830
- }
831
- var localCatalogGenerations = new Map;
832
- function currentLocalCatalogGeneration(options) {
833
- const key = localCatalogCoordinationKey(options);
834
- return key === undefined ? 0 : localCatalogGenerations.get(key) ?? 0;
835
- }
836
- function incrementLocalCatalogGeneration(options) {
837
- const key = localCatalogCoordinationKey(options);
838
- if (key === undefined) {
839
- return 0;
840
- }
841
- const nextGeneration = (localCatalogGenerations.get(key) ?? 0) + 1;
842
- localCatalogGenerations.set(key, nextGeneration);
843
- return nextGeneration;
844
- }
845
-
846
- // src/internal/ducklake-dataset-catalog.ts
847
- import { randomUUID } from "crypto";
848
- import {
849
- assertDatasetDefinition,
850
- LakeStorageError as LakeStorageError11,
851
- planDatasetDefinitionUpdate
852
- } from "@sixb/core/lake-storage";
853
-
854
- // src/internal/duckdb-row.ts
855
- import { LakeStorageError as LakeStorageError5 } from "@sixb/core/lake-storage";
856
- function getString(row, key) {
857
- const value = row[key];
858
- if (typeof value !== "string") {
859
- throw new LakeStorageError5(`[SixbDuckLake] Expected DuckDB column '${key}' to be a string, got ${typeof value}.`);
860
- }
861
- return value;
862
- }
863
- function getOptionalString(row, key) {
864
- const value = row[key];
865
- if (value === null || value === undefined) {
866
- return;
867
- }
868
- if (typeof value !== "string") {
869
- throw new LakeStorageError5(`[SixbDuckLake] Expected DuckDB column '${key}' to be a string, got ${typeof value}.`);
870
- }
871
- return value;
872
- }
873
- function getBoolean(row, key) {
874
- const value = row[key];
875
- if (typeof value !== "boolean") {
876
- throw new LakeStorageError5(`[SixbDuckLake] Expected DuckDB column '${key}' to be a boolean, got ${typeof value}.`);
877
- }
878
- return value;
879
- }
880
- function getBigIntLike(row, key) {
881
- const value = row[key];
882
- if (typeof value === "bigint") {
883
- return value;
884
- }
885
- if (typeof value === "number" && Number.isInteger(value)) {
886
- return BigInt(value);
887
- }
888
- if (typeof value === "string" && /^-?\d+$/.test(value)) {
889
- return BigInt(value);
890
- }
891
- throw new LakeStorageError5(`[SixbDuckLake] Expected DuckDB column '${key}' to be an integer, got ${typeof value}.`);
892
- }
893
- function getOptionalBigIntLike(row, key) {
894
- const value = row[key];
895
- if (value === null || value === undefined) {
896
- return;
897
- }
898
- return getBigIntLike(row, key);
899
- }
900
- function getDate(row, key) {
901
- const value = row[key];
902
- if (value instanceof Date) {
903
- return value;
904
- }
905
- if (typeof value === "string") {
906
- return new Date(value);
907
- }
908
- throw new LakeStorageError5(`[SixbDuckLake] Expected DuckDB column '${key}' to be a date, got ${typeof value}.`);
909
- }
910
-
911
- // src/internal/ducklake-catalog-schema.ts
912
- import { LakeStorageError as LakeStorageError8 } from "@sixb/core/lake-storage";
913
-
914
- // src/internal/schema.ts
915
- import { LakeStorageError as LakeStorageError7 } from "@sixb/core/lake-storage";
916
-
917
- // src/internal/duckdb-column-types.ts
918
- function normalizeDuckDbTypeSql(typeSql) {
919
- return typeSql.trim().toUpperCase().replace(/\s+/g, " ").replace(/\s*([(),])\s*/g, "$1");
920
- }
921
- function defineDuckDbColumnType(sql, aliases = []) {
922
- const acceptedTypes = new Set([sql, ...aliases].map(normalizeDuckDbTypeSql));
923
- return {
924
- sql,
925
- matches: (typeSql) => acceptedTypes.has(normalizeDuckDbTypeSql(typeSql))
926
- };
927
- }
928
- function defineDuckDbDecimalType(precision, scale) {
929
- return {
930
- ...defineDuckDbColumnType(`DECIMAL(${precision}, ${scale})`),
931
- precision,
932
- scale
933
- };
934
- }
935
- var DUCKDB_COLUMN_TYPES = {
936
- string: defineDuckDbColumnType("VARCHAR"),
937
- boolean: defineDuckDbColumnType("BOOLEAN"),
938
- int64: defineDuckDbColumnType("BIGINT", ["INT64"]),
939
- float64: defineDuckDbColumnType("DOUBLE", ["FLOAT64"]),
940
- decimal: defineDuckDbDecimalType(38, 9),
941
- date: defineDuckDbColumnType("DATE"),
942
- timestamp: defineDuckDbColumnType("TIMESTAMPTZ", ["TIMESTAMP WITH TIME ZONE"]),
943
- json: defineDuckDbColumnType("JSON"),
944
- fileRef: defineDuckDbColumnType("STRUCT(blobId VARCHAR, digest VARCHAR, sizeBytes BIGINT, fileName VARCHAR, mediaType VARCHAR, logicalPath VARCHAR)")
945
- };
946
- function findDatasetColumnTypeForDuckDbSql(typeSql) {
947
- for (const type of Object.keys(DUCKDB_COLUMN_TYPES)) {
948
- if (DUCKDB_COLUMN_TYPES[type].matches(typeSql)) {
949
- return type;
950
- }
951
- }
952
- return;
953
- }
954
-
955
- // src/internal/duckdb-decimal.ts
956
- import { normalizeDecimalValue } from "@sixb/core";
957
- import { LakeStorageError as LakeStorageError6 } from "@sixb/core/lake-storage";
958
- var decimalType = DUCKDB_COLUMN_TYPES.decimal;
959
- var maxIntegerDigits = decimalType.precision - decimalType.scale;
960
- function normalizeDuckDbDecimalValue(value, columnName) {
961
- if (typeof value !== "string") {
962
- throw new LakeStorageError6(`[SixbDuckLake] Decimal column '${columnName}' must be an exact decimal string.`);
963
- }
964
- let normalized;
965
- try {
966
- normalized = normalizeDecimalValue(value);
967
- } catch {
968
- throw new LakeStorageError6(`[SixbDuckLake] Decimal column '${columnName}' must be an exact decimal string.`);
969
- }
970
- const unsigned = normalized.startsWith("-") ? normalized.slice(1) : normalized;
971
- const [integer, fraction = ""] = unsigned.split(".");
972
- if (integer.length > maxIntegerDigits || fraction.length > decimalType.scale) {
973
- throw new LakeStorageError6(`[SixbDuckLake] Decimal column '${columnName}' cannot be represented exactly as ${decimalType.sql}; expected at most ${maxIntegerDigits} integer digits and ${decimalType.scale} fractional digits.`);
974
- }
975
- return normalized;
976
- }
977
-
978
- // src/internal/schema.ts
979
- function datasetColumnTypeToDuckDbSql(type) {
980
- return DUCKDB_COLUMN_TYPES[type].sql;
981
- }
982
- function duckDbTypeToDatasetColumnType(typeSql) {
983
- const type = findDatasetColumnTypeForDuckDbSql(typeSql);
984
- if (type) {
985
- return type;
986
- }
987
- throw new LakeStorageError7(`[SixbDuckLake] DuckDB column type '${typeSql}' cannot be mapped to a Sixb dataset column type.`);
988
- }
989
- function datasetColumnToDuckDbSql(column) {
990
- const nullableSql = column.nullable ? "" : " NOT NULL";
991
- return `${quoteIdentifier(column.name)} ${datasetColumnTypeToDuckDbSql(column.type)}${nullableSql}`;
992
- }
993
- function datasetSchemaToDuckDbColumnsSql(schema) {
994
- return schema.columns.map((column) => datasetColumnToDuckDbSql(column)).join(", ");
995
- }
996
- function datasetSchemaToDuckDbNullableColumnsSql(schema) {
997
- return schema.columns.map((column) => `${quoteIdentifier(column.name)} ${datasetColumnTypeToDuckDbSql(column.type)}`).join(", ");
998
- }
999
- function datasetSchemaColumnNamesSql(schema) {
1000
- return schema.columns.map((column) => quoteIdentifier(column.name)).join(", ");
1001
- }
1002
- function normalizeReadValue(value, column) {
1003
- if (value === null || value === undefined) {
1004
- return null;
1005
- }
1006
- switch (column.type) {
1007
- case "int64":
1008
- return typeof value === "bigint" ? value.toString() : value;
1009
- case "decimal":
1010
- return normalizeDuckDbDecimalValue(value, column.name);
1011
- case "date":
1012
- return value instanceof Date ? value.toISOString().slice(0, 10) : String(value);
1013
- case "timestamp":
1014
- return value instanceof Date ? value.toISOString() : String(value);
1015
- case "json":
1016
- return typeof value === "string" ? JSON.parse(value) : value;
1017
- case "fileRef":
1018
- return normalizeFileRef(value);
1019
- default:
1020
- return value;
1021
- }
1022
- }
1023
- function normalizeFileRef(value) {
1024
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
1025
- throw new LakeStorageError7("[SixbDuckLake] DuckDB fileRef value must be a struct.");
1026
- }
1027
- const record = value;
1028
- return {
1029
- blobId: getStringField(record, "blobId"),
1030
- digest: getStringField(record, "digest"),
1031
- sizeBytes: getIntegerField(record, "sizeBytes"),
1032
- fileName: getOptionalStringField(record, "fileName"),
1033
- mediaType: getOptionalStringField(record, "mediaType"),
1034
- logicalPath: getOptionalStringField(record, "logicalPath")
1035
- };
1036
- }
1037
- function getStringField(record, key) {
1038
- const value = record[key];
1039
- if (typeof value !== "string") {
1040
- throw new LakeStorageError7(`[SixbDuckLake] DuckDB fileRef field '${key}' must be a string.`);
1319
+ };
1041
1320
  }
1042
- return value;
1043
- }
1044
- function getOptionalStringField(record, key) {
1045
- const value = record[key];
1046
- if (value === null || value === undefined) {
1047
- return;
1321
+ async closeRuntimePromise(runtimePromise, options) {
1322
+ if (runtimePromise === undefined) {
1323
+ return;
1324
+ }
1325
+ let runtime;
1326
+ try {
1327
+ runtime = await runtimePromise;
1328
+ } catch {
1329
+ return;
1330
+ }
1331
+ await this.closeRuntime(runtime, options);
1048
1332
  }
1049
- if (typeof value !== "string") {
1050
- throw new LakeStorageError7(`[SixbDuckLake] DuckDB fileRef field '${key}' must be a string.`);
1333
+ async closeRuntime(runtime, options) {
1334
+ if (options.detach) {
1335
+ try {
1336
+ await runtime.run(`DETACH ${quoteIdentifier(duckLakeAlias(this.options))}`);
1337
+ } catch {}
1338
+ }
1339
+ await runtime.close();
1051
1340
  }
1052
- return value;
1053
1341
  }
1054
- function getIntegerField(record, key) {
1055
- const value = record[key];
1056
- if (typeof value === "number" && Number.isInteger(value)) {
1057
- return value;
1058
- }
1059
- if (typeof value === "bigint") {
1060
- return Number(value);
1342
+ var localCatalogGenerations = new Map;
1343
+ function currentLocalCatalogGeneration(options) {
1344
+ const key = localCatalogCoordinationKey(options);
1345
+ return key === undefined ? 0 : localCatalogGenerations.get(key) ?? 0;
1346
+ }
1347
+ function incrementLocalCatalogGeneration(options) {
1348
+ const key = localCatalogCoordinationKey(options);
1349
+ if (key === undefined) {
1350
+ return 0;
1061
1351
  }
1062
- throw new LakeStorageError7(`[SixbDuckLake] DuckDB fileRef field '${key}' must be an integer.`);
1352
+ const nextGeneration = (localCatalogGenerations.get(key) ?? 0) + 1;
1353
+ localCatalogGenerations.set(key, nextGeneration);
1354
+ return nextGeneration;
1063
1355
  }
1064
1356
 
1357
+ // src/internal/ducklake-dataset-catalog.ts
1358
+ import { randomUUID as randomUUID2 } from "crypto";
1359
+ import {
1360
+ assertDatasetDefinition,
1361
+ LakeStorageError as LakeStorageError13,
1362
+ planDatasetDefinitionUpdate
1363
+ } from "@sixb/core/lake-storage";
1364
+
1065
1365
  // src/internal/ducklake-catalog-schema.ts
1366
+ import { LakeStorageError as LakeStorageError11 } from "@sixb/core/lake-storage";
1066
1367
  var SIXB_PRIMARY_KEY_COMMENT_NAMESPACE = "sixb:primary-key:";
1067
1368
  var DATASET_SEQUENCE_COLUMN_COMMENT = "sixb.sequenceBy.v1";
1068
1369
  var SIXB_PRIMARY_KEY_COMMENT_PREFIX = `${SIXB_PRIMARY_KEY_COMMENT_NAMESPACE}v1:`;
@@ -1113,14 +1414,14 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
1113
1414
  throwInvalidPrimaryKeyMetadata(tableName, column, comment);
1114
1415
  }
1115
1416
  if (seenOrdinals.has(ordinal)) {
1116
- throw new LakeStorageError8(`[SixbDuckLake] Dataset table '${tableName}' has duplicate primary-key ordinal '${ordinal}'.`);
1417
+ throw new LakeStorageError11(`[SixbDuckLake] Dataset table '${tableName}' has duplicate primary-key ordinal '${ordinal}'.`);
1117
1418
  }
1118
1419
  seenOrdinals.add(ordinal);
1119
1420
  if (duckDbTypeToDatasetColumnType(column.columnType) !== "string") {
1120
- throw new LakeStorageError8(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must map to type 'string'.`);
1421
+ throw new LakeStorageError11(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must map to type 'string'.`);
1121
1422
  }
1122
1423
  if (column.nullsAllowed) {
1123
- throw new LakeStorageError8(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must not be nullable.`);
1424
+ throw new LakeStorageError11(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must not be nullable.`);
1124
1425
  }
1125
1426
  keyedColumns.push({ column, ordinal });
1126
1427
  }
@@ -1130,7 +1431,7 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
1130
1431
  keyedColumns.sort((left, right) => left.ordinal - right.ordinal);
1131
1432
  for (const [expectedOrdinal, keyedColumn] of keyedColumns.entries()) {
1132
1433
  if (keyedColumn.ordinal !== expectedOrdinal) {
1133
- throw new LakeStorageError8(`[SixbDuckLake] Dataset table '${tableName}' primary-key ordinals must be contiguous from 0.`);
1434
+ throw new LakeStorageError11(`[SixbDuckLake] Dataset table '${tableName}' primary-key ordinals must be contiguous from 0.`);
1134
1435
  }
1135
1436
  }
1136
1437
  const columnNames = keyedColumns.map(({ column }) => column.columnName);
@@ -1140,7 +1441,7 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
1140
1441
  return columnNames;
1141
1442
  }
1142
1443
  function throwInvalidPrimaryKeyMetadata(tableName, column, comment) {
1143
- throw new LakeStorageError8(`[SixbDuckLake] Dataset table '${tableName}' has malformed primary-key metadata '${comment}' on column '${column.columnName}'.`);
1444
+ throw new LakeStorageError11(`[SixbDuckLake] Dataset table '${tableName}' has malformed primary-key metadata '${comment}' on column '${column.columnName}'.`);
1144
1445
  }
1145
1446
  function duckLakeCatalogColumnToDefinition(tableName, column, childrenByParentId) {
1146
1447
  const children = [...childrenByParentId.get(column.columnId) ?? []].sort(compareDuckLakeCatalogColumnOrder);
@@ -1157,7 +1458,7 @@ function duckLakeCatalogColumnTypeToDatasetType(tableName, column, children) {
1157
1458
  if (isFileRefStruct(children)) {
1158
1459
  return "fileRef";
1159
1460
  }
1160
- throw new LakeStorageError8(`[SixbDuckLake] DuckDB column type '${formatDuckLakeCatalogColumnType(column, children)}' in dataset table '${tableName}' cannot be mapped to a Sixb dataset column type.`);
1461
+ throw new LakeStorageError11(`[SixbDuckLake] DuckDB column type '${formatDuckLakeCatalogColumnType(column, children)}' in dataset table '${tableName}' cannot be mapped to a Sixb dataset column type.`);
1161
1462
  }
1162
1463
  function compareDuckLakeCatalogColumnOrder(left, right) {
1163
1464
  return Number(left.columnOrder - right.columnOrder);
@@ -1179,84 +1480,7 @@ function formatDuckLakeCatalogColumnType(column, children) {
1179
1480
  }
1180
1481
 
1181
1482
  // src/internal/ducklake-current-dataset-definitions.ts
1182
- import { LakeStorageError as LakeStorageError10 } from "@sixb/core/lake-storage";
1183
-
1184
- // src/internal/names.ts
1185
- import { LakeStorageError as LakeStorageError9 } from "@sixb/core/lake-storage";
1186
- var DATASET_TABLE_PREFIX = "sixb__ds__";
1187
- var textEncoder = new TextEncoder;
1188
- var textDecoder = new TextDecoder("utf-8", { fatal: true });
1189
- function isInlineByte(byte) {
1190
- return byte >= 97 && byte <= 122 || byte >= 48 && byte <= 57;
1191
- }
1192
- function isHexPair(value) {
1193
- return /^[0-9a-f]{2}$/.test(value);
1194
- }
1195
- function encodeDatasetId(datasetId) {
1196
- if (datasetId.trim().length === 0) {
1197
- throw new LakeStorageError9("[SixbDuckLake] Dataset id must not be empty.");
1198
- }
1199
- let encoded = "";
1200
- for (const byte of textEncoder.encode(datasetId)) {
1201
- if (isInlineByte(byte)) {
1202
- encoded += String.fromCharCode(byte);
1203
- } else if (byte === 46) {
1204
- encoded += "__";
1205
- } else {
1206
- encoded += `_${byte.toString(16).padStart(2, "0")}`;
1207
- }
1208
- }
1209
- return encoded;
1210
- }
1211
- function decodeDatasetId(encoded) {
1212
- if (encoded.length === 0) {
1213
- return null;
1214
- }
1215
- const bytes = [];
1216
- let index = 0;
1217
- while (index < encoded.length) {
1218
- const char = encoded[index];
1219
- const byte = char.charCodeAt(0);
1220
- if (isInlineByte(byte)) {
1221
- bytes.push(byte);
1222
- index += 1;
1223
- continue;
1224
- }
1225
- if (char !== "_") {
1226
- return null;
1227
- }
1228
- if (encoded[index + 1] === "_") {
1229
- bytes.push(46);
1230
- index += 2;
1231
- continue;
1232
- }
1233
- const hex = encoded.slice(index + 1, index + 3);
1234
- if (!isHexPair(hex)) {
1235
- return null;
1236
- }
1237
- bytes.push(Number.parseInt(hex, 16));
1238
- index += 3;
1239
- }
1240
- try {
1241
- return textDecoder.decode(new Uint8Array(bytes));
1242
- } catch {
1243
- return null;
1244
- }
1245
- }
1246
- function encodeDatasetTableName(datasetId) {
1247
- return `${DATASET_TABLE_PREFIX}${encodeDatasetId(datasetId)}`;
1248
- }
1249
- function encodeDatasetSequenceTableName(datasetId) {
1250
- return `sixb__seq__${encodeDatasetTableName(datasetId)}`;
1251
- }
1252
- function decodeDatasetTableName(tableName) {
1253
- if (!tableName.startsWith(DATASET_TABLE_PREFIX)) {
1254
- return null;
1255
- }
1256
- return decodeDatasetId(tableName.slice(DATASET_TABLE_PREFIX.length));
1257
- }
1258
-
1259
- // src/internal/ducklake-current-dataset-definitions.ts
1483
+ import { LakeStorageError as LakeStorageError12 } from "@sixb/core/lake-storage";
1260
1484
  async function readCurrentDatasetDefinitions(input) {
1261
1485
  if (input.datasetIds?.length === 0) {
1262
1486
  return new Map;
@@ -1383,7 +1607,7 @@ async function readCurrentDatasetPartitions(input, tableNames) {
1383
1607
  transform: getString(row, "transform")
1384
1608
  };
1385
1609
  if (partition.transform !== "identity") {
1386
- throw new LakeStorageError10(`[SixbDuckLake] Dataset table '${partition.tableName}' uses unsupported DuckLake partition transform '${partition.transform}'.`);
1610
+ throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${partition.tableName}' uses unsupported DuckLake partition transform '${partition.transform}'.`);
1387
1611
  }
1388
1612
  return partition;
1389
1613
  });
@@ -1485,7 +1709,7 @@ class DuckLakeDatasetCatalog {
1485
1709
  if (failures.length > 0) {
1486
1710
  const details = failures.join(`
1487
1711
  `);
1488
- throw new LakeStorageError11(`[SixbLake] Lake dataset definition check failed for ${failures.length} dataset(s).
1712
+ throw new LakeStorageError13(`[SixbLake] Lake dataset definition check failed for ${failures.length} dataset(s).
1489
1713
  ${details}`);
1490
1714
  }
1491
1715
  }
@@ -1521,12 +1745,12 @@ ${details}`);
1521
1745
  assertDuckLakeSnapshotId(snapshotId);
1522
1746
  const columns = await this.readDatasetColumnsAtSnapshot(runtime, tableName, tableId, snapshotId);
1523
1747
  if (columns.length === 0) {
1524
- throw new LakeStorageError11(`[SixbDuckLake] Could not reconstruct schema for dataset '${datasetId}' at DuckLake snapshot '${snapshotId}'.`);
1748
+ throw new LakeStorageError13(`[SixbDuckLake] Could not reconstruct schema for dataset '${datasetId}' at DuckLake snapshot '${snapshotId}'.`);
1525
1749
  }
1526
1750
  return duckLakeCatalogColumnsToDatasetSchema(tableName, columns);
1527
1751
  }
1528
1752
  assertSchema(definition) {
1529
- assertDatasetDefinition(definition, (message) => new LakeStorageError11(`[SixbDuckLake] ${message}`));
1753
+ assertDatasetDefinition(definition, (message) => new LakeStorageError13(`[SixbDuckLake] ${message}`));
1530
1754
  }
1531
1755
  async applyExistingDatasetUpdate(plan) {
1532
1756
  const tableName = encodeDatasetTableName(plan.definition.id);
@@ -1573,7 +1797,7 @@ ${details}`);
1573
1797
  await runtime.run(`ALTER TABLE ${qualifiedName} SET PARTITIONED BY (${definition.partitionBy.map((columnName) => quoteIdentifier(columnName)).join(", ")})`);
1574
1798
  } catch (error) {
1575
1799
  const message = error instanceof Error ? error.message : String(error);
1576
- throw new LakeStorageError11(`[SixbDuckLake] Dataset '${definition.id}' cannot apply partitionBy because DuckLake rejected ALTER TABLE SET PARTITIONED BY: ${message}`);
1800
+ throw new LakeStorageError13(`[SixbDuckLake] Dataset '${definition.id}' cannot apply partitionBy because DuckLake rejected ALTER TABLE SET PARTITIONED BY: ${message}`);
1577
1801
  }
1578
1802
  }
1579
1803
  async applyPrimaryKey(runtime, qualifiedName, definition) {
@@ -1590,7 +1814,7 @@ ${details}`);
1590
1814
  sixb: {
1591
1815
  kind: "datasetVersion",
1592
1816
  datasetId,
1593
- commitId: randomUUID(),
1817
+ commitId: randomUUID2(),
1594
1818
  mode: "schema",
1595
1819
  schemaChange: {
1596
1820
  addColumns: plan.columns.map((column) => column.name)
@@ -1632,12 +1856,12 @@ ${details}`);
1632
1856
  }
1633
1857
  function assertDuckLakeSnapshotId(snapshotId) {
1634
1858
  if (!/^\d+$/.test(snapshotId)) {
1635
- throw new LakeStorageError11(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
1859
+ throw new LakeStorageError13(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
1636
1860
  }
1637
1861
  }
1638
1862
 
1639
1863
  // src/internal/ducklake-maintenance.ts
1640
- import { LakeStorageError as LakeStorageError12 } from "@sixb/core/lake-storage";
1864
+ import { LakeStorageError as LakeStorageError14 } from "@sixb/core/lake-storage";
1641
1865
  var DEFAULT_RETENTION = "7 days";
1642
1866
 
1643
1867
  class DuckLakeMaintenance {
@@ -1653,7 +1877,7 @@ class DuckLakeMaintenance {
1653
1877
  const deleteOlderThan = options.deleteOlderThan ?? expireOlderThan;
1654
1878
  const dryRun = options.dryRun ?? false;
1655
1879
  if (this.options.readOnly && !dryRun) {
1656
- throw new LakeStorageError12("[SixbDuckLake] DuckLake maintenance cannot run in read-only mode.");
1880
+ throw new LakeStorageError14("[SixbDuckLake] DuckLake maintenance cannot run in read-only mode.");
1657
1881
  }
1658
1882
  return this.connections.withExclusiveAttached(async (runtime) => {
1659
1883
  const counts = await runMaintenanceProcedures(runtime, this.options, {
@@ -1690,112 +1914,7 @@ function maintenanceProcedureSql(procedure, aliasSql, olderThan, dryRun) {
1690
1914
  }
1691
1915
 
1692
1916
  // src/internal/ducklake-row-reader.ts
1693
- import { LakeStorageError as LakeStorageError14 } from "@sixb/core/lake-storage";
1694
-
1695
- // src/internal/ducklake-dataset-table-ref.ts
1696
- async function resolveDatasetTableRef(options, runtime, datasetId) {
1697
- const tableName = encodeDatasetTableName(datasetId);
1698
- const ducklakeTable = duckLakeMetadataTableName(options, "ducklake_table");
1699
- const [row] = await runtime.query(`
1700
- SELECT table_id
1701
- FROM ${ducklakeTable}
1702
- WHERE table_name = ${quoteSqlString(tableName)}
1703
- AND end_snapshot IS NULL
1704
- LIMIT 1
1705
- `);
1706
- return row === undefined ? null : {
1707
- datasetId,
1708
- tableName,
1709
- tableId: getBigIntLike(row, "table_id")
1710
- };
1711
- }
1712
-
1713
- // src/internal/versions.ts
1714
- import { LakeStorageError as LakeStorageError13 } from "@sixb/core/lake-storage";
1715
- function parseVersionId(versionId) {
1716
- if (!versionId.startsWith("ducklake:")) {
1717
- throw new LakeStorageError13(`[SixbDuckLake] Invalid DuckLake version id '${versionId}'.`);
1718
- }
1719
- const snapshotId = versionId.slice("ducklake:".length);
1720
- if (!/^\d+$/.test(snapshotId)) {
1721
- throw new LakeStorageError13(`[SixbDuckLake] Invalid DuckLake version id '${versionId}'.`);
1722
- }
1723
- return snapshotId;
1724
- }
1725
- function toVersionId(snapshotId) {
1726
- return `ducklake:${snapshotId}`;
1727
- }
1728
- function parseCommitMetadata(value) {
1729
- if (typeof value !== "string") {
1730
- return;
1731
- }
1732
- let parsed;
1733
- try {
1734
- parsed = JSON.parse(value);
1735
- } catch {
1736
- return;
1737
- }
1738
- if (!isRecord(parsed) || !isRecord(parsed.sixb) || parsed.sixb.kind !== "datasetVersion" || typeof parsed.sixb.datasetId !== "string") {
1739
- return;
1740
- }
1741
- const mode = parsed.sixb.mode;
1742
- const commitId = parsed.sixb.commitId;
1743
- const rowCount = parsed.sixb.rowCount;
1744
- const validatedPrimaryKeyColumns = parsed.sixb.validatedPrimaryKeyColumns;
1745
- return {
1746
- kind: "datasetVersion",
1747
- datasetId: parsed.sixb.datasetId,
1748
- ...typeof commitId === "string" ? { commitId } : {},
1749
- ...mode === "snapshot" || mode === "append" || mode === "merge" || mode === "schema" ? { mode } : {},
1750
- ...isDatasetProducer(parsed.sixb.producer) ? { producer: parsed.sixb.producer } : {},
1751
- ...isDatasetVersionRefs(parsed.sixb.inputs) ? { inputs: parsed.sixb.inputs } : {},
1752
- ...typeof commitId === "string" && isRowCount(rowCount) ? { rowCount } : {},
1753
- ...typeof commitId === "string" && isValidatedPrimaryKeyColumns(validatedPrimaryKeyColumns) ? { validatedPrimaryKeyColumns } : {},
1754
- ...isSchemaChangeMetadata(parsed.sixb.schemaChange) ? { schemaChange: parsed.sixb.schemaChange } : {}
1755
- };
1756
- }
1757
- function parseInlineDataChange(changesMade, tableId) {
1758
- const tableIdText = tableId.toString();
1759
- let hasDataChange = false;
1760
- let hasDeleteChange = false;
1761
- for (const change of changesMade.split(",")) {
1762
- const [kind, changedTableId] = change.split(":");
1763
- if (changedTableId !== tableIdText) {
1764
- continue;
1765
- }
1766
- const normalizedKind = kind.toLowerCase();
1767
- if (normalizedKind.includes("insert") || normalizedKind.includes("delete")) {
1768
- hasDataChange = true;
1769
- }
1770
- if (normalizedKind.includes("delete")) {
1771
- hasDeleteChange = true;
1772
- }
1773
- }
1774
- return { hasDataChange, hasDeleteChange };
1775
- }
1776
- function isRecord(value) {
1777
- return typeof value === "object" && value !== null && !Array.isArray(value);
1778
- }
1779
- function isDatasetProducer(value) {
1780
- return isRecord(value) && (value.kind === "sync" || value.kind === "pipeline" || value.kind === "ingest") && optionalString(value.id) && optionalString(value.runId);
1781
- }
1782
- function isDatasetVersionRefs(value) {
1783
- return Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.datasetId === "string" && typeof item.versionId === "string");
1784
- }
1785
- function isSchemaChangeMetadata(value) {
1786
- return isRecord(value) && (value.addColumns === undefined || Array.isArray(value.addColumns) && value.addColumns.every((columnName) => typeof columnName === "string"));
1787
- }
1788
- function optionalString(value) {
1789
- return value === undefined || typeof value === "string";
1790
- }
1791
- function isRowCount(value) {
1792
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1793
- }
1794
- function isValidatedPrimaryKeyColumns(value) {
1795
- return Array.isArray(value) && value.length > 0 && value.every((column) => typeof column === "string" && column.trim().length > 0) && new Set(value).size === value.length;
1796
- }
1797
-
1798
- // src/internal/ducklake-row-reader.ts
1917
+ import { LakeStorageError as LakeStorageError15 } from "@sixb/core/lake-storage";
1799
1918
  var READ_PAGE_ROWS = 5000;
1800
1919
  var PHYSICAL_ROW_ID_ALIAS = "__sixb_physical_row_id";
1801
1920
 
@@ -1815,7 +1934,7 @@ class DuckLakeRowReader {
1815
1934
  const runtime = lease.runtime;
1816
1935
  const tableRef = await resolveDatasetTableRef(this.options, runtime, input.datasetId);
1817
1936
  if (!tableRef) {
1818
- throw new LakeStorageError14(`[SixbDuckLake] Unknown dataset '${input.datasetId}'.`);
1937
+ throw new LakeStorageError15(`[SixbDuckLake] Unknown dataset '${input.datasetId}'.`);
1819
1938
  }
1820
1939
  const version = await this.resolveVersion(runtime, tableRef, input.versionId);
1821
1940
  const selectedColumns = this.resolveReadColumns(tableRef.datasetId, version.schema, input.columns);
@@ -1891,18 +2010,18 @@ class DuckLakeRowReader {
1891
2010
  return columns.map((columnName) => {
1892
2011
  const column = columnsByName.get(columnName);
1893
2012
  if (!column) {
1894
- throw new LakeStorageError14(`[SixbDuckLake] Dataset '${datasetId}' does not have column '${columnName}' at the requested version.`);
2013
+ throw new LakeStorageError15(`[SixbDuckLake] Dataset '${datasetId}' does not have column '${columnName}' at the requested version.`);
1895
2014
  }
1896
2015
  return column;
1897
2016
  });
1898
2017
  }
1899
2018
  throwNoCommittedVersion(datasetId) {
1900
- throw new LakeStorageError14(`[SixbDuckLake] No committed version found for dataset '${datasetId}'.`);
2019
+ throw new LakeStorageError15(`[SixbDuckLake] No committed version found for dataset '${datasetId}'.`);
1901
2020
  }
1902
2021
  }
1903
2022
 
1904
2023
  // src/internal/ducklake-snapshot-reader.ts
1905
- import { LakeStorageError as LakeStorageError15 } from "@sixb/core/lake-storage";
2024
+ import { LakeStorageError as LakeStorageError16 } from "@sixb/core/lake-storage";
1906
2025
  var SNAPSHOT_ROW_BATCH_SIZE = 128;
1907
2026
  var CATALOG_SNAPSHOT_SCAN_LIMIT = 512;
1908
2027
 
@@ -2115,7 +2234,7 @@ class DuckLakeSnapshotReader {
2115
2234
  }
2116
2235
  assertNoMetadataConflict(snapshotId, datasetId, metadata) {
2117
2236
  if (metadata !== undefined && metadata.datasetId !== datasetId) {
2118
- throw new LakeStorageError15(`[SixbDuckLake] DuckLake snapshot '${snapshotId}' changed dataset '${datasetId}' but Sixb commit metadata references dataset '${metadata.datasetId}'.`);
2237
+ throw new LakeStorageError16(`[SixbDuckLake] DuckLake snapshot '${snapshotId}' changed dataset '${datasetId}' but Sixb commit metadata references dataset '${metadata.datasetId}'.`);
2119
2238
  }
2120
2239
  }
2121
2240
  async getLatestVersionForDefinition(runtime, dataset) {
@@ -2395,7 +2514,7 @@ class DuckLakeSnapshotReader {
2395
2514
  }
2396
2515
  function assertDuckLakeSnapshotId2(snapshotId) {
2397
2516
  if (!/^\d+$/.test(snapshotId)) {
2398
- throw new LakeStorageError15(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
2517
+ throw new LakeStorageError16(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
2399
2518
  }
2400
2519
  }
2401
2520
  function fileChangeKey(snapshotId, tableId) {
@@ -2403,19 +2522,19 @@ function fileChangeKey(snapshotId, tableId) {
2403
2522
  }
2404
2523
 
2405
2524
  // src/internal/ducklake-sql-executor.ts
2406
- import { randomUUID as randomUUID3 } from "crypto";
2525
+ import { randomUUID as randomUUID4 } from "crypto";
2407
2526
  import {
2408
- LakeStorageError as LakeStorageError18
2527
+ LakeStorageError as LakeStorageError19
2409
2528
  } from "@sixb/core/lake-storage";
2410
2529
 
2411
2530
  // src/internal/dataset-row-commit.ts
2412
2531
  import {
2413
2532
  getDatasetPrimaryKeyColumns,
2414
- LakeStorageError as LakeStorageError16
2533
+ LakeStorageError as LakeStorageError17
2415
2534
  } from "@sixb/core/lake-storage";
2416
2535
  function assertDatasetWriteMode(mode, operation) {
2417
2536
  if (mode !== "snapshot" && mode !== "append") {
2418
- throw new LakeStorageError16(`[SixbDuckLake] Invalid ${operation} mode '${String(mode)}'. Expected 'snapshot' or 'append'.`);
2537
+ throw new LakeStorageError17(`[SixbDuckLake] Invalid ${operation} mode '${String(mode)}'. Expected 'snapshot' or 'append'.`);
2419
2538
  }
2420
2539
  }
2421
2540
  async function applyDatasetRowsFromRelation(input) {
@@ -2475,7 +2594,7 @@ async function applyDatasetRowsFromRelation(input) {
2475
2594
  async function inspectUniqueKeyedRelation(input) {
2476
2595
  const primaryKeyColumns = getDatasetPrimaryKeyColumns(input.dataset);
2477
2596
  if (primaryKeyColumns === null) {
2478
- throw new LakeStorageError16(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before rows can be keyed.`);
2597
+ throw new LakeStorageError17(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before rows can be keyed.`);
2479
2598
  }
2480
2599
  const keysSql = primaryKeyColumns.map((column) => quoteIdentifier(column)).join(", ");
2481
2600
  const [row] = await input.runtime.query(`
@@ -2490,7 +2609,7 @@ async function inspectUniqueKeyedRelation(input) {
2490
2609
  const rowCount = row === undefined ? 0 : Number(getBigIntLike(row, "row_count"));
2491
2610
  const maxKeyCount = row === undefined ? 0 : Number(getBigIntLike(row, "max_key_count"));
2492
2611
  if (maxKeyCount > 1) {
2493
- throw new LakeStorageError16(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
2612
+ throw new LakeStorageError17(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
2494
2613
  }
2495
2614
  return rowCount;
2496
2615
  }
@@ -2507,7 +2626,7 @@ async function assertNoPrimaryKeyOverlap(input) {
2507
2626
  LIMIT 1
2508
2627
  `);
2509
2628
  if (collision !== undefined) {
2510
- throw new LakeStorageError16(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
2629
+ throw new LakeStorageError17(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
2511
2630
  }
2512
2631
  }
2513
2632
  async function fingerprintRelation(runtime, relationSql, columnsSql) {
@@ -2539,9 +2658,9 @@ async function countRows(runtime, relationSql) {
2539
2658
  }
2540
2659
 
2541
2660
  // src/internal/sql-transform-relations.ts
2542
- import { randomUUID as randomUUID2 } from "crypto";
2661
+ import { randomUUID as randomUUID3 } from "crypto";
2543
2662
  import {
2544
- LakeStorageError as LakeStorageError17
2663
+ LakeStorageError as LakeStorageError18
2545
2664
  } from "@sixb/core/lake-storage";
2546
2665
  var RELATION_PLACEHOLDER_PREFIX = "__sixb_sql_transform_relation_";
2547
2666
  var RELATION_PLACEHOLDER_PATTERN = /__sixb_sql_transform_relation_[0-9a-f]{32}_[0-9]+__/g;
@@ -2559,12 +2678,12 @@ function renderDuckLakeSqlTransformSql(input) {
2559
2678
  const context = createDuckLakeSqlTransformRelationContext(input);
2560
2679
  const sql = input.sql(context.relations);
2561
2680
  if (typeof sql !== "string") {
2562
- throw new LakeStorageError17("[SixbDuckLake] SQL transform body must return a SQL string.");
2681
+ throw new LakeStorageError18("[SixbDuckLake] SQL transform body must return a SQL string.");
2563
2682
  }
2564
2683
  return context.render(sql);
2565
2684
  }
2566
2685
  function createDuckLakeSqlTransformRelationContext(input) {
2567
- const token = randomUUID2().replaceAll("-", "");
2686
+ const token = randomUUID3().replaceAll("-", "");
2568
2687
  const relations = Object.create(null);
2569
2688
  const replacements = new Map;
2570
2689
  let index = 0;
@@ -2597,7 +2716,7 @@ function renderDuckLakeSourceRelation(options, source) {
2597
2716
  return `(SELECT * FROM ${qualifiedTableName(options, tableName)} AT (VERSION => ${snapshotId}))`;
2598
2717
  }
2599
2718
  function throwUnresolvedPlaceholder() {
2600
- throw new LakeStorageError17("[SixbDuckLake] SQL transform contains an unresolved relation placeholder.");
2719
+ throw new LakeStorageError18("[SixbDuckLake] SQL transform contains an unresolved relation placeholder.");
2601
2720
  }
2602
2721
 
2603
2722
  // src/internal/ducklake-sql-executor.ts
@@ -2657,8 +2776,8 @@ class DuckLakeSqlExecutor {
2657
2776
  commitMessage: input.commitMessage ?? `execute SQL transform for dataset ${target.id}`,
2658
2777
  producer: input.producer,
2659
2778
  inputs: sources.inputs,
2660
- applyChanges: (runtime2, context) => this.applySqlTransform({
2661
- runtime: runtime2,
2779
+ applyChanges: (runtime, context) => this.applySqlTransform({
2780
+ runtime,
2662
2781
  target,
2663
2782
  mode: input.mode,
2664
2783
  sql,
@@ -2691,14 +2810,14 @@ class DuckLakeSqlExecutor {
2691
2810
  async resolveSourceDataset(runtime, dataset) {
2692
2811
  const definition = await this.datasets.getDatasetOnRuntime(runtime, dataset.id);
2693
2812
  if (!definition) {
2694
- throw new LakeStorageError18(`[SixbDuckLake] Unknown SQL transform source dataset '${dataset.id}'.`);
2813
+ throw new LakeStorageError19(`[SixbDuckLake] Unknown SQL transform source dataset '${dataset.id}'.`);
2695
2814
  }
2696
2815
  return definition;
2697
2816
  }
2698
2817
  async resolveTargetDataset(runtime, dataset) {
2699
2818
  const definition = await this.datasets.getDatasetOnRuntime(runtime, dataset.id);
2700
2819
  if (!definition) {
2701
- throw new LakeStorageError18(`[SixbDuckLake] Unknown SQL transform target dataset '${dataset.id}'.`);
2820
+ throw new LakeStorageError19(`[SixbDuckLake] Unknown SQL transform target dataset '${dataset.id}'.`);
2702
2821
  }
2703
2822
  this.datasets.assertSchema(definition);
2704
2823
  return definition;
@@ -2748,7 +2867,7 @@ class DuckLakeSqlExecutor {
2748
2867
  return checkedCasts;
2749
2868
  }
2750
2869
  async applySqlTransform(input) {
2751
- const tempTableName = `sixb_sql_transform_${randomUUID3().replaceAll("-", "")}`;
2870
+ const tempTableName = `sixb_sql_transform_${randomUUID4().replaceAll("-", "")}`;
2752
2871
  const tempTable = quoteIdentifier(tempTableName);
2753
2872
  await input.runtime.run(`CREATE TEMP TABLE ${tempTable} AS SELECT * FROM (${input.sql}) AS sixb_sql_transform_result`);
2754
2873
  let result;
@@ -2819,33 +2938,33 @@ function normalizePreviewValue(value) {
2819
2938
  return value;
2820
2939
  }
2821
2940
  function throwNoCommittedSourceVersion(datasetId) {
2822
- throw new LakeStorageError18(`[SixbDuckLake] No committed version found for SQL transform source dataset '${datasetId}'.`);
2941
+ throw new LakeStorageError19(`[SixbDuckLake] No committed version found for SQL transform source dataset '${datasetId}'.`);
2823
2942
  }
2824
2943
  function throwResultSchemaMismatch(datasetId, detail) {
2825
- throw new LakeStorageError18(`[SixbDuckLake] SQL transform result schema does not match target dataset '${datasetId}': ${detail}.`);
2944
+ throw new LakeStorageError19(`[SixbDuckLake] SQL transform result schema does not match target dataset '${datasetId}': ${detail}.`);
2826
2945
  }
2827
2946
 
2828
2947
  // src/internal/ducklake-write-coordinator.ts
2829
- import { randomUUID as randomUUID6 } from "crypto";
2948
+ import { randomUUID as randomUUID7 } from "crypto";
2830
2949
  import {
2831
2950
  assertUnsequencedDatasetWrite,
2832
2951
  getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns4,
2833
2952
  hasDatasetInputChanges,
2834
2953
  LakeConcurrencyError,
2835
- LakeStorageError as LakeStorageError22,
2954
+ LakeStorageError as LakeStorageError23,
2836
2955
  retryDatasetMergeCommit
2837
2956
  } from "@sixb/core/lake-storage";
2838
2957
 
2839
2958
  // src/internal/dataset-row-merge.ts
2840
2959
  import {
2841
2960
  getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns2,
2842
- LakeStorageError as LakeStorageError19,
2961
+ LakeStorageError as LakeStorageError20,
2843
2962
  reconcileDatasetSequences
2844
2963
  } from "@sixb/core/lake-storage";
2845
2964
  async function applyDatasetMergeFromRelation(input) {
2846
2965
  const primaryKeyColumns = getDatasetPrimaryKeyColumns2(input.dataset);
2847
2966
  if (primaryKeyColumns === null) {
2848
- throw new LakeStorageError19(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before it can be merged.`);
2967
+ throw new LakeStorageError20(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before it can be merged.`);
2849
2968
  }
2850
2969
  const stagingTable = quoteIdentifier(input.stagingTableName);
2851
2970
  const targetTable = qualifiedTableName(input.options, encodeDatasetTableName(input.dataset.id));
@@ -3006,7 +3125,7 @@ function finalChangesSelectSql(input, primaryKeyColumns) {
3006
3125
  async function countMergeEffects(input, targetTable, finalChanges, primaryKeyColumns) {
3007
3126
  const matchColumn = primaryKeyColumns[0];
3008
3127
  if (matchColumn === undefined) {
3009
- throw new LakeStorageError19(`[SixbDuckLake] Dataset '${input.dataset.id}' primaryKey must contain at least one column.`);
3128
+ throw new LakeStorageError20(`[SixbDuckLake] Dataset '${input.dataset.id}' primaryKey must contain at least one column.`);
3010
3129
  }
3011
3130
  const keyMatchSql = primaryKeyMatchSql(primaryKeyColumns, "target", "source");
3012
3131
  const targetMatch = `target.${quoteIdentifier(matchColumn)} IS NOT NULL`;
@@ -3055,12 +3174,12 @@ function sameColumns(left, right) {
3055
3174
  }
3056
3175
 
3057
3176
  // src/internal/ducklake-merge-session.ts
3058
- import { randomUUID as randomUUID4 } from "crypto";
3177
+ import { randomUUID as randomUUID5 } from "crypto";
3059
3178
  import {
3060
3179
  cloneDatasetMergeChange,
3061
3180
  datasetSequenceChange,
3062
3181
  getDatasetMergeChangeValidationError,
3063
- LakeStorageError as LakeStorageError20
3182
+ LakeStorageError as LakeStorageError21
3064
3183
  } from "@sixb/core/lake-storage";
3065
3184
 
3066
3185
  // src/internal/row-appender.ts
@@ -3119,7 +3238,7 @@ function fileRefToDuckDbStruct(fileRef) {
3119
3238
  // src/internal/ducklake-merge-session.ts
3120
3239
  var MERGE_CHANGE_BATCH_SIZE = 1000;
3121
3240
  async function createDuckLakeMergeSession(input) {
3122
- const suffix = randomUUID4().replaceAll("-", "");
3241
+ const suffix = randomUUID5().replaceAll("-", "");
3123
3242
  const stagingTableName = `sixb_merge_${suffix}`;
3124
3243
  const sequenceColumnName = `sixb_merge_sequence_${suffix}`;
3125
3244
  const kindColumnName = `sixb_merge_kind_${suffix}`;
@@ -3161,7 +3280,7 @@ class DuckLakeMergeSession {
3161
3280
  for await (const change of changes) {
3162
3281
  const validationError = getDatasetMergeChangeValidationError(change, this.input.dataset);
3163
3282
  if (validationError) {
3164
- throw new LakeStorageError20(`[SixbDuckLake] ${validationError}`);
3283
+ throw new LakeStorageError21(`[SixbDuckLake] ${validationError}`);
3165
3284
  }
3166
3285
  batch.push({
3167
3286
  sequence: this.nextSequence,
@@ -3224,7 +3343,7 @@ class DuckLakeMergeSession {
3224
3343
  }
3225
3344
  assertOpen() {
3226
3345
  if (this.closed) {
3227
- throw new LakeStorageError20("[SixbDuckLake] Merge session is already closed.");
3346
+ throw new LakeStorageError21("[SixbDuckLake] Merge session is already closed.");
3228
3347
  }
3229
3348
  }
3230
3349
  async cleanup() {
@@ -3239,16 +3358,16 @@ class DuckLakeMergeSession {
3239
3358
  }
3240
3359
 
3241
3360
  // src/internal/ducklake-write-session.ts
3242
- import { randomUUID as randomUUID5 } from "crypto";
3361
+ import { randomUUID as randomUUID6 } from "crypto";
3243
3362
  import { getDatasetRowValidationError } from "@sixb/core";
3244
3363
  import {
3245
3364
  encodeDatasetPrimaryKey,
3246
3365
  getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns3,
3247
- LakeStorageError as LakeStorageError21
3366
+ LakeStorageError as LakeStorageError22
3248
3367
  } from "@sixb/core/lake-storage";
3249
3368
  var WRITE_ROW_BATCH_SIZE = 1000;
3250
3369
  async function createDuckLakeWriteSession(input) {
3251
- const stagingTableName = `sixb_write_${randomUUID5().replaceAll("-", "")}`;
3370
+ const stagingTableName = `sixb_write_${randomUUID6().replaceAll("-", "")}`;
3252
3371
  await input.runtime.run(`CREATE TEMP TABLE ${quoteIdentifier(stagingTableName)} (${datasetSchemaToDuckDbColumnsSql(input.write.dataset.schema)})`);
3253
3372
  return new DuckLakeWriteSession(input.commitWrite, input.runtime, input.write, stagingTableName);
3254
3373
  }
@@ -3275,13 +3394,13 @@ class DuckLakeWriteSession {
3275
3394
  for await (const row of rows) {
3276
3395
  const validationError = getDatasetRowValidationError(row, this.input.dataset);
3277
3396
  if (validationError) {
3278
- throw new LakeStorageError21(`[SixbDuckLake] ${validationError}`);
3397
+ throw new LakeStorageError22(`[SixbDuckLake] ${validationError}`);
3279
3398
  }
3280
3399
  const clonedRow = structuredClone(row);
3281
3400
  if (getDatasetPrimaryKeyColumns3(this.input.dataset) !== null) {
3282
3401
  const primaryKey = encodeDatasetPrimaryKey(this.input.dataset, clonedRow);
3283
3402
  if (this.stagedPrimaryKeys.has(primaryKey) || batchPrimaryKeys.has(primaryKey)) {
3284
- throw new LakeStorageError21(`[SixbDuckLake] Dataset '${this.input.dataset.id}' ${this.input.mode ?? "snapshot"} source contains duplicate primary key.`);
3403
+ throw new LakeStorageError22(`[SixbDuckLake] Dataset '${this.input.dataset.id}' ${this.input.mode ?? "snapshot"} source contains duplicate primary key.`);
3285
3404
  }
3286
3405
  batchPrimaryKeys.add(primaryKey);
3287
3406
  }
@@ -3338,7 +3457,7 @@ class DuckLakeWriteSession {
3338
3457
  }
3339
3458
  assertOpen() {
3340
3459
  if (this.closed) {
3341
- throw new LakeStorageError21("[SixbDuckLake] Write session is already closed.");
3460
+ throw new LakeStorageError22("[SixbDuckLake] Write session is already closed.");
3342
3461
  }
3343
3462
  }
3344
3463
  async cleanup() {
@@ -3371,7 +3490,7 @@ class DuckLakeWriteCoordinator {
3371
3490
  this.connections.assertOpen();
3372
3491
  const definition = await this.datasets.getDataset(input.dataset.id);
3373
3492
  if (!definition) {
3374
- throw new LakeStorageError22(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
3493
+ throw new LakeStorageError23(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
3375
3494
  }
3376
3495
  this.datasets.assertSchema(definition);
3377
3496
  assertUnsequencedDatasetWrite(definition);
@@ -3392,13 +3511,13 @@ class DuckLakeWriteCoordinator {
3392
3511
  this.connections.assertOpen();
3393
3512
  const definition = await this.datasets.getDataset(input.dataset.id);
3394
3513
  if (!definition) {
3395
- throw new LakeStorageError22(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
3514
+ throw new LakeStorageError23(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
3396
3515
  }
3397
3516
  if (getDatasetPrimaryKeyColumns4(definition) === null) {
3398
- throw new LakeStorageError22(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
3517
+ throw new LakeStorageError23(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
3399
3518
  }
3400
3519
  this.datasets.assertSchema(definition);
3401
- const latestVersion = await this.connections.withAttachedRuntime((runtime2) => this.snapshots.getLatestVersionForDefinition(runtime2, definition));
3520
+ const latestVersion = await this.connections.withAttachedRuntime((runtime) => this.snapshots.getLatestVersionForDefinition(runtime, definition));
3402
3521
  if (input.expectedLatestVersionId !== undefined && latestVersion?.versionId !== input.expectedLatestVersionId) {
3403
3522
  throw new LakeConcurrencyError(`[SixbDuckLake] Optimistic merge start failed for dataset '${definition.id}': expected latest version '${input.expectedLatestVersionId}', found '${latestVersion?.versionId ?? "none"}'.`);
3404
3523
  }
@@ -3413,7 +3532,7 @@ class DuckLakeWriteCoordinator {
3413
3532
  async commitWrite(input) {
3414
3533
  const definition = await this.datasets.getDataset(input.write.dataset.id);
3415
3534
  if (!definition) {
3416
- throw new LakeStorageError22(`[SixbDuckLake] Unknown dataset '${input.write.dataset.id}'.`);
3535
+ throw new LakeStorageError23(`[SixbDuckLake] Unknown dataset '${input.write.dataset.id}'.`);
3417
3536
  }
3418
3537
  const mode = input.write.mode ?? "snapshot";
3419
3538
  assertDatasetWriteMode(mode, "write");
@@ -3434,10 +3553,10 @@ class DuckLakeWriteCoordinator {
3434
3553
  async commitMerge(input) {
3435
3554
  const definition = await this.datasets.getDataset(input.merge.dataset.id);
3436
3555
  if (!definition) {
3437
- throw new LakeStorageError22(`[SixbDuckLake] Unknown dataset '${input.merge.dataset.id}'.`);
3556
+ throw new LakeStorageError23(`[SixbDuckLake] Unknown dataset '${input.merge.dataset.id}'.`);
3438
3557
  }
3439
3558
  if (getDatasetPrimaryKeyColumns4(definition) === null) {
3440
- throw new LakeStorageError22(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
3559
+ throw new LakeStorageError23(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
3441
3560
  }
3442
3561
  return retryDatasetMergeCommit(input.merge, input.commit, (rebase) => this.withCommitRuntime(async (runtime) => {
3443
3562
  input.commit?.signal?.throwIfAborted();
@@ -3465,7 +3584,7 @@ class DuckLakeWriteCoordinator {
3465
3584
  validatedPrimaryKeyColumns: context.validatedPrimaryKeyColumns
3466
3585
  });
3467
3586
  if (result.sourceRowCount !== input.changesWritten) {
3468
- throw new LakeStorageError22(`[SixbDuckLake] Staged merge for dataset '${definition.id}' accepted ${input.changesWritten} change(s), but DuckLake saw ${result.sourceRowCount} source change(s) at commit time.`);
3587
+ throw new LakeStorageError23(`[SixbDuckLake] Staged merge for dataset '${definition.id}' accepted ${input.changesWritten} change(s), but DuckLake saw ${result.sourceRowCount} source change(s) at commit time.`);
3469
3588
  }
3470
3589
  return result;
3471
3590
  }
@@ -3487,7 +3606,7 @@ class DuckLakeWriteCoordinator {
3487
3606
  disableRetries: input.mode === "append" && getDatasetPrimaryKeyColumns4(input.dataset) !== null
3488
3607
  });
3489
3608
  if (result.version === null) {
3490
- throw new LakeStorageError22(`[SixbDuckLake] No DuckLake changes were committed for dataset '${input.dataset.id}', and no previous version exists.`);
3609
+ throw new LakeStorageError23(`[SixbDuckLake] No DuckLake changes were committed for dataset '${input.dataset.id}', and no previous version exists.`);
3491
3610
  }
3492
3611
  return { ...result.version, outcome: result.outcome };
3493
3612
  }
@@ -3536,7 +3655,7 @@ class DuckLakeWriteCoordinator {
3536
3655
  });
3537
3656
  input.signal?.throwIfAborted();
3538
3657
  await this.ensureNoOpHasSnapshot(input, changeResult, latestVersion);
3539
- const commitId = randomUUID6();
3658
+ const commitId = randomUUID7();
3540
3659
  await this.setCommitMetadata(input, commitId, commitRowCount(input, changeResult, latestVersion));
3541
3660
  await input.runtime.run("COMMIT");
3542
3661
  committed = true;
@@ -3569,7 +3688,7 @@ class DuckLakeWriteCoordinator {
3569
3688
  if (version) {
3570
3689
  return { outcome: "created", version };
3571
3690
  }
3572
- throw new LakeStorageError22(`[SixbDuckLake] DuckLake committed snapshot '${ownSnapshotId}' for dataset '${input.dataset.id}', but Sixb could not hydrate it as a dataset version.`);
3691
+ throw new LakeStorageError23(`[SixbDuckLake] DuckLake committed snapshot '${ownSnapshotId}' for dataset '${input.dataset.id}', but Sixb could not hydrate it as a dataset version.`);
3573
3692
  }
3574
3693
  if (!input.changeResult.dataChangeExpected) {
3575
3694
  const version = await this.versionForNoOpCommit(input.runtime, input.dataset, input.allowInitialNoOp ?? false);
@@ -3587,9 +3706,9 @@ class DuckLakeWriteCoordinator {
3587
3706
  };
3588
3707
  }
3589
3708
  if (input.committedWriteSnapshotId === input.previousWriteSnapshotId) {
3590
- throw new LakeStorageError22(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed without producing a snapshot for this non-empty write.`);
3709
+ throw new LakeStorageError23(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed without producing a snapshot for this non-empty write.`);
3591
3710
  }
3592
- throw new LakeStorageError22(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed, but Sixb could not find a matching snapshot for this write.`);
3711
+ throw new LakeStorageError23(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed, but Sixb could not find a matching snapshot for this write.`);
3593
3712
  }
3594
3713
  async applyStagedRows(input) {
3595
3714
  const mode = input.write.mode ?? "snapshot";
@@ -3602,7 +3721,7 @@ class DuckLakeWriteCoordinator {
3602
3721
  previousRowCount: input.previousRowCount
3603
3722
  });
3604
3723
  if (result.sourceRowCount !== input.rowsWritten) {
3605
- throw new LakeStorageError22(`[SixbDuckLake] Staged write for dataset '${input.write.dataset.id}' accepted ${input.rowsWritten} row(s), but DuckLake saw ${result.sourceRowCount} source row(s) at commit time.`);
3724
+ throw new LakeStorageError23(`[SixbDuckLake] Staged write for dataset '${input.write.dataset.id}' accepted ${input.rowsWritten} row(s), but DuckLake saw ${result.sourceRowCount} source row(s) at commit time.`);
3606
3725
  }
3607
3726
  return result;
3608
3727
  }
@@ -3640,7 +3759,7 @@ class DuckLakeWriteCoordinator {
3640
3759
  if (allowInitialNoOp) {
3641
3760
  return null;
3642
3761
  }
3643
- throw new LakeStorageError22(`[SixbDuckLake] No DuckLake changes were committed for dataset '${definition.id}', and no previous version exists.`);
3762
+ throw new LakeStorageError23(`[SixbDuckLake] No DuckLake changes were committed for dataset '${definition.id}', and no previous version exists.`);
3644
3763
  }
3645
3764
  async rollbackTransaction(runtime) {
3646
3765
  try {
@@ -3714,7 +3833,7 @@ function commitRowCount(input, changeResult, latestVersion) {
3714
3833
  }
3715
3834
  function assertDuckLakeSnapshotId3(snapshotId) {
3716
3835
  if (!/^\d+$/.test(snapshotId)) {
3717
- throw new LakeStorageError22(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
3836
+ throw new LakeStorageError23(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
3718
3837
  }
3719
3838
  }
3720
3839
  var localCatalogCommitLocks = new Map;
@@ -3726,8 +3845,8 @@ async function withCatalogCommitLock(options, run) {
3726
3845
  const previous = localCatalogCommitLocks.get(key) ?? Promise.resolve();
3727
3846
  const ready = previous.catch(() => {});
3728
3847
  let release;
3729
- const next = new Promise((resolve2) => {
3730
- release = resolve2;
3848
+ const next = new Promise((resolve) => {
3849
+ release = resolve;
3731
3850
  });
3732
3851
  const current = ready.then(() => next);
3733
3852
  localCatalogCommitLocks.set(key, current);
@@ -3750,6 +3869,7 @@ class DuckLakeStorage {
3750
3869
  datasets;
3751
3870
  maintenance;
3752
3871
  rows;
3872
+ changes;
3753
3873
  snapshotReader;
3754
3874
  writes;
3755
3875
  constructor(options) {
@@ -3768,6 +3888,7 @@ class DuckLakeStorage {
3768
3888
  this.maintenance = new DuckLakeMaintenance(normalizedOptions, this.connections);
3769
3889
  this.snapshotReader = new DuckLakeSnapshotReader(normalizedOptions, this.connections, this.datasets);
3770
3890
  this.rows = new DuckLakeRowReader(normalizedOptions, this.connections, this.snapshotReader);
3891
+ this.changes = new DuckLakeChangeReader(normalizedOptions, this.connections, this.snapshotReader);
3771
3892
  this.writes = new DuckLakeWriteCoordinator(normalizedOptions, this.connections, this.datasets, this.snapshotReader);
3772
3893
  this.sql = new DuckLakeSqlExecutor(normalizedOptions, this.connections, this.datasets, this.snapshotReader, this.writes);
3773
3894
  }
@@ -3804,6 +3925,9 @@ class DuckLakeStorage {
3804
3925
  readRows(input) {
3805
3926
  return this.rows.readRows(input);
3806
3927
  }
3928
+ readChanges(input) {
3929
+ return this.changes.readChanges(input);
3930
+ }
3807
3931
  async runMaintenance(options) {
3808
3932
  return this.maintenance.runMaintenance(options);
3809
3933
  }
@@ -3815,4 +3939,4 @@ export {
3815
3939
  DuckLakeStorage
3816
3940
  };
3817
3941
 
3818
- //# debugId=2A2E6355256FAE5864756E2164756E21
3942
+ //# debugId=555594FFA13F16F264756E2164756E21