@gscdump/engine-duckdb-wasm 0.25.4 → 0.25.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.d.mts CHANGED
@@ -105,6 +105,17 @@ interface OpfsParquetTable {
105
105
  /** Iceberg table name — becomes the DuckDB view name. */
106
106
  table: string;
107
107
  files: OpfsParquetFile[];
108
+ /**
109
+ * Recent-window overlay parquet (the non-stable tail the lake excludes). When
110
+ * present it is materialised into OPFS alongside `files` and the view unions
111
+ * it with an anti-join dedup: the lake (`files`) serves every day it has, the
112
+ * overlay serves ONLY days the lake lacks. So a stale overlay whose days have
113
+ * since landed in the lake can't double-count, and a day that stabilised but
114
+ * isn't yet in the lake still serves from the overlay. Its `contentHash` must
115
+ * change when the overlay bytes change (it is overwritten in place) so the
116
+ * cache re-downloads.
117
+ */
118
+ overlay?: OpfsParquetFile;
108
119
  }
109
120
  interface AttachOpfsTablesOptions {
110
121
  db: AsyncDuckDB;
package/dist/index.mjs CHANGED
@@ -540,8 +540,19 @@ async function materialiseFile(root, name, staleSweepPrefix, file, fetchImpl, fe
540
540
  outcome: "downloaded"
541
541
  };
542
542
  }
543
+ function quoteList(files) {
544
+ return files.map((f) => `'${f.replace(/'/g, "''")}'`).join(", ");
545
+ }
546
+ function lakeSelect(files) {
547
+ return `SELECT * REPLACE (CAST(date AS DATE) AS date) FROM read_parquet([${quoteList(files)}], union_by_name = true)`;
548
+ }
543
549
  function readParquetViewSql$1(schema, table, files) {
544
- return `CREATE OR REPLACE VIEW ${schema}.${table} AS SELECT * REPLACE (CAST(date AS DATE) AS date) FROM read_parquet([${files.map((f) => `'${f.replace(/'/g, "''")}'`).join(", ")}], union_by_name = true)`;
550
+ return `CREATE OR REPLACE VIEW ${schema}.${table} AS ${lakeSelect(files)}`;
551
+ }
552
+ function readParquetViewWithOverlaySql(schema, table, lakeFiles, overlayFile) {
553
+ const overlay = `SELECT * REPLACE (CAST(date AS DATE) AS date) FROM read_parquet(['${overlayFile.replace(/'/g, "''")}'], union_by_name = true)`;
554
+ if (lakeFiles.length === 0) return `CREATE OR REPLACE VIEW ${schema}.${table} AS ${overlay}`;
555
+ return `CREATE OR REPLACE VIEW ${schema}.${table} AS ${lakeSelect(lakeFiles)} UNION ALL BY NAME ${overlay} WHERE CAST(date AS DATE) NOT IN (SELECT DISTINCT CAST(date AS DATE) FROM read_parquet([${quoteList(lakeFiles)}], union_by_name = true))`;
545
556
  }
546
557
  async function runWithConcurrency$1(items, concurrency, fn) {
547
558
  let next = 0;
@@ -564,12 +575,22 @@ async function attachOpfsParquetTables(options) {
564
575
  await requestPersistentStorage();
565
576
  const root = await getOpfsRoot();
566
577
  const flat = [];
567
- for (const t of tables) for (let i = 0; i < t.files.length; i++) flat.push({
568
- table: t.table,
569
- file: t.files[i],
570
- fileIndex: i
571
- });
578
+ for (const t of tables) {
579
+ for (let i = 0; i < t.files.length; i++) flat.push({
580
+ table: t.table,
581
+ file: t.files[i],
582
+ fileIndex: i
583
+ });
584
+ if (t.overlay) flat.push({
585
+ table: t.table,
586
+ file: t.overlay,
587
+ fileIndex: t.files.length,
588
+ overlay: true
589
+ });
590
+ }
572
591
  const total = flat.length;
592
+ const overlayNames = /* @__PURE__ */ new Map();
593
+ const expectedCount = new Map(tables.map((t) => [t.table, t.files.length + (t.overlay ? 1 : 0)]));
573
594
  const { DuckDBDataProtocol } = await import("@duckdb/duckdb-wasm");
574
595
  const registry = getOpfsRegistry(db, DuckDBDataProtocol.BROWSER_FSACCESS);
575
596
  const tableFiles = /* @__PURE__ */ new Map();
@@ -615,6 +636,7 @@ async function attachOpfsParquetTables(options) {
615
636
  handle: result.handle
616
637
  });
617
638
  tableFiles.set(item.table, list);
639
+ if (item.overlay) overlayNames.set(item.table, name);
618
640
  bytesAttached += item.file.bytes;
619
641
  onFileProgress?.({
620
642
  table: item.table,
@@ -639,14 +661,16 @@ async function attachOpfsParquetTables(options) {
639
661
  continue;
640
662
  }
641
663
  const files = tableFiles.get(t.table) ?? [];
642
- if (files.length !== t.files.length) {
664
+ if (files.length !== (expectedCount.get(t.table) ?? t.files.length)) {
643
665
  degraded.add(t.table);
644
666
  await releaseTable(t.table);
645
667
  continue;
646
668
  }
669
+ const overlayName = overlayNames.get(t.table);
670
+ const lakeNames = overlayName ? files.map((f) => f.name).filter((n) => n !== overlayName) : files.map((f) => f.name);
647
671
  try {
648
672
  signal?.throwIfAborted();
649
- await conn.query(readParquetViewSql$1(schema, t.table, files.map((f) => f.name)));
673
+ await conn.query(overlayName ? readParquetViewWithOverlaySql(schema, t.table, lakeNames, overlayName) : readParquetViewSql$1(schema, t.table, lakeNames));
650
674
  } catch (err) {
651
675
  if (isAbortError$1(err)) {
652
676
  await detachOpfs(registry, conn, schema, attached, [...acquiredNames]).catch(() => {});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine-duckdb-wasm",
3
3
  "type": "module",
4
- "version": "0.25.4",
4
+ "version": "0.25.6",
5
5
  "description": "DuckDB-WASM engine adapter for @gscdump/analysis — typed browser analytics against parquet via R2.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "drizzle-orm": "1.0.0-rc.3",
48
- "@gscdump/engine": "0.25.4",
49
- "@gscdump/sdk": "0.25.4",
50
- "gscdump": "0.25.4"
48
+ "@gscdump/engine": "0.25.6",
49
+ "@gscdump/sdk": "0.25.6",
50
+ "gscdump": "0.25.6"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@duckdb/duckdb-wasm": "^1.32.0",