@gscdump/engine 0.4.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/adapters/duckdb-node.d.mts +19 -0
  4. package/dist/adapters/duckdb-node.mjs +78 -0
  5. package/dist/adapters/filesystem.d.mts +206 -0
  6. package/dist/adapters/filesystem.mjs +320 -0
  7. package/dist/adapters/http.d.mts +227 -0
  8. package/dist/adapters/http.mjs +119 -0
  9. package/dist/adapters/hyparquet.d.mts +107 -0
  10. package/dist/adapters/hyparquet.mjs +250 -0
  11. package/dist/adapters/inspection-sqlite-browser.d.mts +9 -0
  12. package/dist/adapters/inspection-sqlite-browser.mjs +42 -0
  13. package/dist/adapters/inspection-sqlite-node.d.mts +9 -0
  14. package/dist/adapters/inspection-sqlite-node.mjs +32 -0
  15. package/dist/adapters/node-harness.d.mts +334 -0
  16. package/dist/adapters/node-harness.mjs +1907 -0
  17. package/dist/adapters/r2-manifest.d.mts +227 -0
  18. package/dist/adapters/r2-manifest.mjs +355 -0
  19. package/dist/adapters/r2.d.mts +93 -0
  20. package/dist/adapters/r2.mjs +65 -0
  21. package/dist/arrow-utils.d.mts +14 -0
  22. package/dist/arrow-utils.mjs +8 -0
  23. package/dist/contracts.d.mts +436 -0
  24. package/dist/contracts.mjs +1 -0
  25. package/dist/entities.d.mts +238 -0
  26. package/dist/entities.mjs +359 -0
  27. package/dist/index.d.mts +1849 -0
  28. package/dist/index.mjs +1976 -0
  29. package/dist/ingest.d.mts +96 -0
  30. package/dist/ingest.mjs +187 -0
  31. package/dist/planner.d.mts +16 -0
  32. package/dist/planner.mjs +321 -0
  33. package/dist/resolver/index.d.mts +207 -0
  34. package/dist/resolver/index.mjs +869 -0
  35. package/dist/rollups.d.mts +207 -0
  36. package/dist/rollups.mjs +553 -0
  37. package/dist/schema.d.mts +1258 -0
  38. package/dist/schema.mjs +139 -0
  39. package/dist/scope.d.mts +38 -0
  40. package/dist/scope.mjs +28 -0
  41. package/dist/snapshot.d.mts +14 -0
  42. package/dist/snapshot.mjs +1 -0
  43. package/dist/sql-bind.d.mts +19 -0
  44. package/dist/sql-bind.mjs +92 -0
  45. package/dist/sql-fragments.d.mts +21 -0
  46. package/dist/sql-fragments.mjs +13 -0
  47. package/package.json +168 -0
@@ -0,0 +1,139 @@
1
+ import { date, doublePrecision, getTableConfig, integer, pgTable, varchar } from "drizzle-orm/pg-core";
2
+ function metricCols() {
3
+ return {
4
+ clicks: integer("clicks").notNull(),
5
+ impressions: integer("impressions").notNull(),
6
+ sum_position: doublePrecision("sum_position").notNull()
7
+ };
8
+ }
9
+ const dateCol = () => date("date").notNull();
10
+ const pages = pgTable("pages", {
11
+ url: varchar("url").notNull(),
12
+ date: dateCol(),
13
+ ...metricCols()
14
+ });
15
+ const keywords = pgTable("keywords", {
16
+ query: varchar("query").notNull(),
17
+ query_canonical: varchar("query_canonical"),
18
+ date: dateCol(),
19
+ ...metricCols()
20
+ });
21
+ const countries = pgTable("countries", {
22
+ country: varchar("country").notNull(),
23
+ date: dateCol(),
24
+ ...metricCols()
25
+ });
26
+ const devices = pgTable("devices", {
27
+ device: varchar("device").notNull(),
28
+ date: dateCol(),
29
+ ...metricCols()
30
+ });
31
+ const page_keywords = pgTable("page_keywords", {
32
+ url: varchar("url").notNull(),
33
+ query: varchar("query").notNull(),
34
+ query_canonical: varchar("query_canonical"),
35
+ date: dateCol(),
36
+ ...metricCols()
37
+ });
38
+ const search_appearance = pgTable("search_appearance", {
39
+ searchAppearance: varchar("searchAppearance").notNull(),
40
+ date: dateCol(),
41
+ ...metricCols()
42
+ });
43
+ const drizzleSchema = {
44
+ pages,
45
+ keywords,
46
+ countries,
47
+ devices,
48
+ page_keywords,
49
+ search_appearance
50
+ };
51
+ const TABLE_METADATA = {
52
+ pages: {
53
+ sortKey: ["date", "url"],
54
+ version: 1
55
+ },
56
+ keywords: {
57
+ sortKey: ["date", "query"],
58
+ version: 2
59
+ },
60
+ countries: {
61
+ sortKey: ["date", "country"],
62
+ version: 1
63
+ },
64
+ devices: {
65
+ sortKey: ["date", "device"],
66
+ version: 1
67
+ },
68
+ page_keywords: {
69
+ sortKey: [
70
+ "date",
71
+ "url",
72
+ "query"
73
+ ],
74
+ version: 2
75
+ },
76
+ search_appearance: {
77
+ sortKey: ["date", "searchAppearance"],
78
+ version: 1
79
+ }
80
+ };
81
+ function pgSqlTypeToColumnType(sqlType) {
82
+ const t = sqlType.toLowerCase();
83
+ if (t.startsWith("varchar") || t === "text" || t.startsWith("char")) return "VARCHAR";
84
+ if (t === "date" || t.startsWith("timestamp")) return "DATE";
85
+ if (t.startsWith("double") || t === "real" || t.startsWith("numeric") || t.startsWith("decimal")) return "DOUBLE";
86
+ if (t === "bigint" || t === "int8") return "BIGINT";
87
+ if (t === "integer" || t === "int" || t === "int4" || t === "smallint" || t === "int2") return "INTEGER";
88
+ throw new Error(`unmapped pg type '${sqlType}' — extend pgSqlTypeToColumnType in @gscdump/engine/schema`);
89
+ }
90
+ function tableSchemaFrom(tableName) {
91
+ const columns = getTableConfig(drizzleSchema[tableName]).columns.map((col) => ({
92
+ name: col.name,
93
+ type: pgSqlTypeToColumnType(col.getSQLType()),
94
+ nullable: !col.notNull
95
+ }));
96
+ const meta = TABLE_METADATA[tableName];
97
+ return {
98
+ name: tableName,
99
+ columns,
100
+ sortKey: meta.sortKey,
101
+ version: meta.version
102
+ };
103
+ }
104
+ const METRIC_TABLES = [
105
+ "pages",
106
+ "keywords",
107
+ "countries",
108
+ "devices",
109
+ "page_keywords",
110
+ "search_appearance"
111
+ ];
112
+ const SCHEMAS = Object.fromEntries(METRIC_TABLES.map((t) => [t, tableSchemaFrom(t)]));
113
+ function currentSchemaVersion(table) {
114
+ return SCHEMAS[table].version;
115
+ }
116
+ function schemaFor(table) {
117
+ return SCHEMAS[table];
118
+ }
119
+ function allTables() {
120
+ return METRIC_TABLES;
121
+ }
122
+ function inferTable(dimensions) {
123
+ const dims = new Set(dimensions);
124
+ const hasPage = dims.has("page");
125
+ const hasQuery = dims.has("query");
126
+ if (hasPage && hasQuery) return "page_keywords";
127
+ if (hasQuery) return "keywords";
128
+ if (hasPage) return "pages";
129
+ if (dims.has("country")) return "countries";
130
+ if (dims.has("device")) return "devices";
131
+ if (dims.has("searchAppearance")) return "search_appearance";
132
+ return "keywords";
133
+ }
134
+ function dimensionToColumn(dim, _table) {
135
+ if (dim === "page") return "url";
136
+ if (dim === "queryCanonical") return "query_canonical";
137
+ return dim;
138
+ }
139
+ export { SCHEMAS, TABLE_METADATA, allTables, countries, currentSchemaVersion, devices, dimensionToColumn, drizzleSchema, inferTable, keywords, page_keywords, pages, schemaFor, search_appearance };
@@ -0,0 +1,38 @@
1
+ import { SQL } from "drizzle-orm";
2
+ /**
3
+ * Structural subset of `ResolvedWindow` from `@gscdump/analysis/period`.
4
+ * Inlined here so engine doesn't take a downstream dependency on analysis;
5
+ * any object with `start`/`end` strings (and optional `days`) satisfies it.
6
+ */
7
+ interface ResolvedWindow {
8
+ start: string;
9
+ end: string;
10
+ days?: number;
11
+ }
12
+ interface ScopedRunnerOptions {
13
+ siteId?: string;
14
+ window?: ResolvedWindow;
15
+ /** Inclusive lower bound for `date`. Ignored if `window` is supplied. */
16
+ startDate?: string;
17
+ /** Inclusive upper bound for `date`. Ignored if `window` is supplied. */
18
+ endDate?: string;
19
+ }
20
+ interface TableScope {
21
+ wherePredicates: SQL[];
22
+ window?: ResolvedWindow;
23
+ siteId?: string;
24
+ }
25
+ declare function buildTableScope(table: Record<string, any>, opts: ScopedRunnerOptions): TableScope;
26
+ declare function mergeScope(scope: TableScope, ...extra: SQL[]): SQL | undefined;
27
+ /**
28
+ * Bind `buildTableScope` + `mergeScope` to a specific drizzle schema. Engine
29
+ * adapters (`engine-sqlite`, `engine-wasm`) call this once at module load and
30
+ * re-export the returned `scopeFor` / `mergeScope` so consumers get a typed
31
+ * `keyof Schema` table parameter without each adapter re-implementing the
32
+ * pass-through wrapper.
33
+ */
34
+ declare function createScopedHelpers<S extends Record<string, Record<string, any>>>(schema: S): {
35
+ scopeFor: (table: keyof S, opts: ScopedRunnerOptions) => TableScope;
36
+ mergeScope: typeof mergeScope;
37
+ };
38
+ export { ResolvedWindow, ScopedRunnerOptions, TableScope, buildTableScope, createScopedHelpers, mergeScope };
package/dist/scope.mjs ADDED
@@ -0,0 +1,28 @@
1
+ import { and, eq, gte, lte } from "drizzle-orm";
2
+ function buildTableScope(table, opts) {
3
+ const predicates = [];
4
+ if (opts.siteId && "site_id" in table) predicates.push(eq(table.site_id, opts.siteId));
5
+ if ("date" in table) {
6
+ const start = opts.window?.start ?? opts.startDate;
7
+ const end = opts.window?.end ?? opts.endDate;
8
+ if (start) predicates.push(gte(table.date, start));
9
+ if (end) predicates.push(lte(table.date, end));
10
+ }
11
+ return {
12
+ wherePredicates: predicates,
13
+ window: opts.window,
14
+ siteId: opts.siteId
15
+ };
16
+ }
17
+ function mergeScope(scope, ...extra) {
18
+ const all = [...scope.wherePredicates, ...extra].filter(Boolean);
19
+ if (all.length === 0) return void 0;
20
+ return and(...all);
21
+ }
22
+ function createScopedHelpers(schema) {
23
+ return {
24
+ scopeFor: (table, opts) => buildTableScope(schema[table], opts),
25
+ mergeScope
26
+ };
27
+ }
28
+ export { buildTableScope, createScopedHelpers, mergeScope };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Describes a hot/cold snapshot set. Produced by the snapshot builder,
3
+ * consumed by `attachSnapshotIndex`. Filenames are derived from `cold`
4
+ * via `cold-${yearMonth}.duckdb`; hot is always `hot.duckdb` when
5
+ * `hot: true`.
6
+ */
7
+ interface SnapshotIndex {
8
+ version: 1;
9
+ builtAt: string;
10
+ cold: string[];
11
+ hot: boolean;
12
+ hotDays: number;
13
+ }
14
+ export { SnapshotIndex };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ /** Escape single quotes for inlining inside a SQL string literal (SQL-standard `''` escaping). */
2
+ declare function sqlEscape(s: string): string;
3
+ declare function formatLiteral(value: unknown): string;
4
+ /**
5
+ * Replace `?` and `$N` placeholders with inline SQL literals. Single-quoted
6
+ * string regions and SQL comments (`-- line`, `/* block *\/`) are left
7
+ * untouched — a `?` or `$1` inside `'foo?bar'` or a comment is not a
8
+ * placeholder. SQL-standard `''` escape handling; no `\`-escape or
9
+ * dialect-specific identifier quoting.
10
+ *
11
+ * `?` placeholders bind sequentially against `params`. `$N` (Postgres-style)
12
+ * binds explicitly to `params[N-1]`. The two styles must not be mixed in the
13
+ * same query.
14
+ *
15
+ * Throws when placeholder count and params length disagree, or when a `$N`
16
+ * index is out of range.
17
+ */
18
+ declare function bindLiterals(sql: string, params: readonly unknown[]): string;
19
+ export { bindLiterals, formatLiteral, sqlEscape };
@@ -0,0 +1,92 @@
1
+ function containsDisallowedControlChars(value) {
2
+ for (let i = 0; i < value.length; i++) {
3
+ const code = value.charCodeAt(i);
4
+ if (code >= 0 && code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31) return true;
5
+ }
6
+ return false;
7
+ }
8
+ function sqlEscape(s) {
9
+ return s.replace(/'/g, "''");
10
+ }
11
+ function formatLiteral(value) {
12
+ if (value == null) return "NULL";
13
+ if (typeof value === "number") {
14
+ if (!Number.isFinite(value)) throw new Error(`cannot inline non-finite number: ${value}`);
15
+ return String(value);
16
+ }
17
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
18
+ if (typeof value === "bigint") return value.toString();
19
+ if (value instanceof Date) return `'${value.toISOString()}'`;
20
+ if (typeof value === "string") {
21
+ if (containsDisallowedControlChars(value)) throw new Error("string literal contains disallowed control characters");
22
+ return `'${value.replace(/'/g, "''")}'`;
23
+ }
24
+ throw new Error(`cannot inline value of type ${typeof value}`);
25
+ }
26
+ function bindLiterals(sql, params) {
27
+ if (params.length === 0) return sql;
28
+ let out = "";
29
+ let i = 0;
30
+ let qmarkIdx = 0;
31
+ const usedDollar = /* @__PURE__ */ new Set();
32
+ let inString = false;
33
+ while (i < sql.length) {
34
+ const c = sql[i];
35
+ if (inString) {
36
+ out += c;
37
+ if (c === "'") {
38
+ if (sql[i + 1] === "'") {
39
+ out += "'";
40
+ i += 2;
41
+ continue;
42
+ }
43
+ inString = false;
44
+ }
45
+ i++;
46
+ continue;
47
+ }
48
+ if (c === "-" && sql[i + 1] === "-") {
49
+ const nl = sql.indexOf("\n", i + 2);
50
+ const end = nl === -1 ? sql.length : nl;
51
+ out += sql.slice(i, end);
52
+ i = end;
53
+ continue;
54
+ }
55
+ if (c === "/" && sql[i + 1] === "*") {
56
+ const close = sql.indexOf("*/", i + 2);
57
+ const end = close === -1 ? sql.length : close + 2;
58
+ out += sql.slice(i, end);
59
+ i = end;
60
+ continue;
61
+ }
62
+ if (c === "'") {
63
+ inString = true;
64
+ out += c;
65
+ i++;
66
+ continue;
67
+ }
68
+ if (c === "?") {
69
+ if (qmarkIdx >= params.length) throw new Error(`bindLiterals: more '?' placeholders than params (have ${params.length})`);
70
+ out += formatLiteral(params[qmarkIdx++]);
71
+ i++;
72
+ continue;
73
+ }
74
+ if (c === "$" && sql[i + 1] && sql[i + 1] >= "0" && sql[i + 1] <= "9") {
75
+ let j = i + 1;
76
+ while (j < sql.length && sql[j] >= "0" && sql[j] <= "9") j++;
77
+ const n = Number(sql.slice(i + 1, j));
78
+ if (n < 1 || n > params.length) throw new Error(`bindLiterals: $${n} out of range (have ${params.length} params)`);
79
+ usedDollar.add(n - 1);
80
+ out += formatLiteral(params[n - 1]);
81
+ i = j;
82
+ continue;
83
+ }
84
+ out += c;
85
+ i++;
86
+ }
87
+ if (qmarkIdx > 0 && usedDollar.size > 0) throw new Error("bindLiterals: cannot mix '?' and '$N' placeholders in the same query");
88
+ const used = qmarkIdx > 0 ? qmarkIdx : usedDollar.size;
89
+ if (used !== params.length) throw new Error(`bindLiterals: ${params.length - used} params unused`);
90
+ return out;
91
+ }
92
+ export { bindLiterals, formatLiteral, sqlEscape };
@@ -0,0 +1,21 @@
1
+ import { Metric } from "gscdump/query";
2
+ /**
3
+ * Standard SQL `LIKE` escape. Backslash is the explicit ESCAPE char so
4
+ * literal `%`, `_`, and `\` survive a `contains` predicate unchanged.
5
+ */
6
+ declare function escapeLike(value: string): string;
7
+ /**
8
+ * Per-metric raw SQL aggregate expression.
9
+ *
10
+ * - `clicks` / `impressions` cast to DOUBLE so DuckDB SUM doesn't return
11
+ * BIGINT (which loses through JSON / Workers RPC).
12
+ * - `position` reverses the ingestion offset (`sum_position = position - 1`
13
+ * summed; divide by impressions and add 1 back).
14
+ */
15
+ declare const METRIC_EXPR: Record<Metric, string>;
16
+ /**
17
+ * Top-level page predicate: matches paths with at most one `/`. Parameterized
18
+ * on the resolved column expression so drizzle can pass a column ref.
19
+ */
20
+ declare function topLevelPagePredicateSql(pathExpr: string): string;
21
+ export { METRIC_EXPR, escapeLike, topLevelPagePredicateSql };
@@ -0,0 +1,13 @@
1
+ function escapeLike(value) {
2
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
3
+ }
4
+ const METRIC_EXPR = {
5
+ clicks: "CAST(SUM(clicks) AS DOUBLE)",
6
+ impressions: "CAST(SUM(impressions) AS DOUBLE)",
7
+ ctr: "CAST(SUM(clicks) AS DOUBLE) / NULLIF(SUM(impressions), 0)",
8
+ position: "SUM(sum_position) / NULLIF(SUM(impressions), 0) + 1"
9
+ };
10
+ function topLevelPagePredicateSql(pathExpr) {
11
+ return `LENGTH(${pathExpr}) - LENGTH(REPLACE(${pathExpr}, '/', '')) <= 1`;
12
+ }
13
+ export { METRIC_EXPR, escapeLike, topLevelPagePredicateSql };
package/package.json ADDED
@@ -0,0 +1,168 @@
1
+ {
2
+ "name": "@gscdump/engine",
3
+ "type": "module",
4
+ "version": "0.4.0",
5
+ "description": "Append-only Parquet/DuckDB storage engine + planner + adapters for the gscdump pipeline. Node + edge runtimes; opt-in heavy peers.",
6
+ "author": {
7
+ "name": "Harlan Wilton",
8
+ "email": "harlan@harlanzw.com",
9
+ "url": "https://harlanzw.com/"
10
+ },
11
+ "license": "MIT",
12
+ "funding": "https://github.com/sponsors/harlan-zw",
13
+ "homepage": "https://github.com/harlan-zw/gscdump/tree/main/packages/engine#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/harlan-zw/gscdump.git",
17
+ "directory": "packages/engine"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/harlan-zw/gscdump/issues"
21
+ },
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.mts",
26
+ "import": "./dist/index.mjs"
27
+ },
28
+ "./contracts": {
29
+ "types": "./dist/contracts.d.mts",
30
+ "import": "./dist/contracts.mjs"
31
+ },
32
+ "./snapshot": {
33
+ "types": "./dist/snapshot.d.mts",
34
+ "import": "./dist/snapshot.mjs"
35
+ },
36
+ "./planner": {
37
+ "types": "./dist/planner.d.mts",
38
+ "import": "./dist/planner.mjs"
39
+ },
40
+ "./schema": {
41
+ "types": "./dist/schema.d.mts",
42
+ "import": "./dist/schema.mjs"
43
+ },
44
+ "./ingest": {
45
+ "types": "./dist/ingest.d.mts",
46
+ "import": "./dist/ingest.mjs"
47
+ },
48
+ "./sql": {
49
+ "types": "./dist/sql-bind.d.mts",
50
+ "import": "./dist/sql-bind.mjs"
51
+ },
52
+ "./sql-fragments": {
53
+ "types": "./dist/sql-fragments.d.mts",
54
+ "import": "./dist/sql-fragments.mjs"
55
+ },
56
+ "./rollups": {
57
+ "types": "./dist/rollups.d.mts",
58
+ "import": "./dist/rollups.mjs"
59
+ },
60
+ "./entities": {
61
+ "types": "./dist/entities.d.mts",
62
+ "import": "./dist/entities.mjs"
63
+ },
64
+ "./node": {
65
+ "types": "./dist/adapters/duckdb-node.d.mts",
66
+ "import": "./dist/adapters/duckdb-node.mjs"
67
+ },
68
+ "./node-harness": {
69
+ "types": "./dist/adapters/node-harness.d.mts",
70
+ "import": "./dist/adapters/node-harness.mjs"
71
+ },
72
+ "./filesystem": {
73
+ "types": "./dist/adapters/filesystem.d.mts",
74
+ "import": "./dist/adapters/filesystem.mjs"
75
+ },
76
+ "./http": {
77
+ "types": "./dist/adapters/http.d.mts",
78
+ "import": "./dist/adapters/http.mjs"
79
+ },
80
+ "./hyparquet": {
81
+ "types": "./dist/adapters/hyparquet.d.mts",
82
+ "import": "./dist/adapters/hyparquet.mjs"
83
+ },
84
+ "./r2": {
85
+ "types": "./dist/adapters/r2.d.mts",
86
+ "import": "./dist/adapters/r2.mjs"
87
+ },
88
+ "./r2-manifest": {
89
+ "types": "./dist/adapters/r2-manifest.d.mts",
90
+ "import": "./dist/adapters/r2-manifest.mjs"
91
+ },
92
+ "./resolver": {
93
+ "types": "./dist/resolver/index.d.mts",
94
+ "import": "./dist/resolver/index.mjs"
95
+ },
96
+ "./scope": {
97
+ "types": "./dist/scope.d.mts",
98
+ "import": "./dist/scope.mjs"
99
+ },
100
+ "./arrow": {
101
+ "types": "./dist/arrow-utils.d.mts",
102
+ "import": "./dist/arrow-utils.mjs"
103
+ },
104
+ "./inspection-sqlite-node": {
105
+ "types": "./dist/adapters/inspection-sqlite-node.d.mts",
106
+ "import": "./dist/adapters/inspection-sqlite-node.mjs"
107
+ },
108
+ "./inspection-sqlite-browser": {
109
+ "types": "./dist/adapters/inspection-sqlite-browser.d.mts",
110
+ "import": "./dist/adapters/inspection-sqlite-browser.mjs"
111
+ }
112
+ },
113
+ "main": "./dist/index.mjs",
114
+ "types": "./dist/index.d.mts",
115
+ "files": [
116
+ "dist"
117
+ ],
118
+ "engines": {
119
+ "node": ">=18"
120
+ },
121
+ "peerDependencies": {
122
+ "@duckdb/duckdb-wasm": "^1.32.0",
123
+ "better-sqlite3": "^12.9.0",
124
+ "hyparquet": "^1.25.1",
125
+ "hyparquet-writer": "^0.13.0",
126
+ "wa-sqlite": "^1.0.0"
127
+ },
128
+ "peerDependenciesMeta": {
129
+ "@duckdb/duckdb-wasm": {
130
+ "optional": true
131
+ },
132
+ "better-sqlite3": {
133
+ "optional": true
134
+ },
135
+ "hyparquet": {
136
+ "optional": true
137
+ },
138
+ "hyparquet-writer": {
139
+ "optional": true
140
+ },
141
+ "wa-sqlite": {
142
+ "optional": true
143
+ }
144
+ },
145
+ "dependencies": {
146
+ "drizzle-orm": "^0.45.2",
147
+ "proper-lockfile": "^4.1.2",
148
+ "gscdump": "0.4.0"
149
+ },
150
+ "devDependencies": {
151
+ "@duckdb/duckdb-wasm": "^1.32.0",
152
+ "@types/proper-lockfile": "^4.1.4",
153
+ "aws4fetch": "^1.0.20",
154
+ "better-sqlite3": "^12.9.0",
155
+ "hyparquet": "^1.25.1",
156
+ "hyparquet-writer": "^0.13.0",
157
+ "tsx": "^4.19.2",
158
+ "vitest": "^4.1.5"
159
+ },
160
+ "scripts": {
161
+ "dev": "obuild --stub",
162
+ "build": "obuild",
163
+ "typecheck": "tsc --noEmit",
164
+ "test": "vitest",
165
+ "r2-harness": "tsx scripts/r2-contention-harness.ts",
166
+ "backfill-audit": "tsx scripts/backfill-audit.ts"
167
+ }
168
+ }