@gscdump/cloudflare 0.37.2 → 0.37.4
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 +13 -2
- package/dist/index.mjs +99 -76
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParquetCodec, QueryExecutor, createStorageEngine } from "@gscdump/engine";
|
|
1
|
+
import { ParquetCodec, QueryExecutor, Row, createStorageEngine } from "@gscdump/engine";
|
|
2
2
|
import { H3Event } from "h3";
|
|
3
3
|
interface AnalyticsEnv {
|
|
4
4
|
/** R2 bucket holding parquet + rollup + entity data. Required in origin mode. */
|
|
@@ -62,6 +62,10 @@ interface AnalyticsEngineHooks {
|
|
|
62
62
|
/** Called once per R2 PUT, with the byte size of the payload. */
|
|
63
63
|
onR2Write?: (byteLength: number) => void;
|
|
64
64
|
}
|
|
65
|
+
interface AnalyticsEngineRuntime {
|
|
66
|
+
getEngine: (env: AnalyticsEnv, db: any, hooks?: AnalyticsEngineHooks) => ReturnType<typeof createStorageEngine> | null;
|
|
67
|
+
}
|
|
68
|
+
declare function createAnalyticsEngineRuntime(): AnalyticsEngineRuntime;
|
|
65
69
|
declare function getAnalyticsEngine(env: AnalyticsEnv, db: any, hooks?: AnalyticsEngineHooks): ReturnType<typeof createStorageEngine> | null;
|
|
66
70
|
interface InflightDedupe<T> {
|
|
67
71
|
dedupe: (key: string, run: () => Promise<T>) => Promise<T>;
|
|
@@ -80,10 +84,17 @@ declare function getHostedR2QueryKey(input: HostedR2QueryKeyInput): string;
|
|
|
80
84
|
declare function signSizeHint(env: AnalyticsEnv, key: string, bytes: number): Promise<string>;
|
|
81
85
|
declare function verifySizeHint(env: AnalyticsEnv, key: string, bytes: number, providedHex: string): Promise<boolean>;
|
|
82
86
|
declare function createDucklingsCodec(_env: AnalyticsEnv): ParquetCodec;
|
|
87
|
+
interface DucklingsRowCache {
|
|
88
|
+
clear: () => void;
|
|
89
|
+
get: (key: string) => Row[] | undefined;
|
|
90
|
+
put: (key: string, rows: Row[]) => void;
|
|
91
|
+
}
|
|
92
|
+
declare function createDucklingsRowCache(maxBytes?: number): DucklingsRowCache;
|
|
83
93
|
interface DucklingsExecutorOptions {
|
|
84
94
|
ipcChunkBytes?: number;
|
|
85
95
|
ipcDirectCallBytes?: number;
|
|
86
96
|
ipcTotalBytes?: number;
|
|
97
|
+
rowCache?: DucklingsRowCache;
|
|
87
98
|
}
|
|
88
99
|
declare function createDucklingsExecutor(env: AnalyticsEnv, opts?: DucklingsExecutorOptions): QueryExecutor;
|
|
89
|
-
export { type AnalyticsEngineHooks, type AnalyticsEnv, type HostedR2QueryKeyInput, type InflightDedupe, createDucklingsCodec, createDucklingsExecutor, createInflightDedupe, getAnalyticsEngine, getHostedR2QueryKey, signSizeHint, useAnalyticsEnv, verifySizeHint };
|
|
100
|
+
export { type AnalyticsEngineHooks, type AnalyticsEngineRuntime, type AnalyticsEnv, type DucklingsRowCache, type HostedR2QueryKeyInput, type InflightDedupe, createAnalyticsEngineRuntime, createDucklingsCodec, createDucklingsExecutor, createDucklingsRowCache, createInflightDedupe, getAnalyticsEngine, getHostedR2QueryKey, signSizeHint, useAnalyticsEnv, verifySizeHint };
|
package/dist/index.mjs
CHANGED
|
@@ -119,55 +119,54 @@ function assertWorkerReadBudget(opts) {
|
|
|
119
119
|
if (totalBytes > maxBytes) throw new Error(`createDucklingsExecutor: planned read spans ${totalBytes} bytes, exceeding the ${maxBytes} byte Worker budget. Narrow the date range or route through a background/windowed query.`);
|
|
120
120
|
}
|
|
121
121
|
const ROW_CACHE_MAX_BYTES = 16 * 1024 * 1024;
|
|
122
|
-
let rowCacheBytes = 0;
|
|
123
|
-
const rowCache = /* @__PURE__ */ new Map();
|
|
124
122
|
function estimateRowsBytes(rows) {
|
|
125
123
|
if (rows.length === 0) return 0;
|
|
126
124
|
const cols = Object.keys(rows[0]).length;
|
|
127
125
|
return rows.length * cols * 64;
|
|
128
126
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (value === null || value === void 0) continue;
|
|
136
|
-
hasValue = true;
|
|
137
|
-
if (typeof value === "string") {
|
|
138
|
-
hasString = true;
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
if (typeof value === "bigint") {
|
|
142
|
-
hasBigInt = true;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (typeof value === "number") {
|
|
146
|
-
if (!Number.isInteger(value)) hasFloat = true;
|
|
147
|
-
if (value > 2147483647 || value < -2147483648) hasBigInt = true;
|
|
148
|
-
}
|
|
127
|
+
function addInferredValue(inference, value) {
|
|
128
|
+
if (value === null || value === void 0) return;
|
|
129
|
+
inference.hasValue = true;
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
inference.hasString = true;
|
|
132
|
+
return;
|
|
149
133
|
}
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
134
|
+
if (typeof value === "bigint") {
|
|
135
|
+
inference.hasBigInt = true;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "number") {
|
|
139
|
+
if (!Number.isInteger(value)) inference.hasFloat = true;
|
|
140
|
+
if (value > 2147483647 || value < -2147483648) inference.hasBigInt = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function inferredColumnType(inference) {
|
|
144
|
+
if (!inference.hasValue || inference.hasString) return "VARCHAR";
|
|
145
|
+
if (inference.hasFloat) return "DOUBLE";
|
|
146
|
+
return inference.hasBigInt ? "BIGINT" : "INTEGER";
|
|
153
147
|
}
|
|
154
148
|
function chunkSchemaColumns(rows, schemaColumns) {
|
|
155
149
|
const columns = schemaColumns ? [...schemaColumns] : [];
|
|
156
150
|
const seen = new Set(columns.map((c) => c.name));
|
|
157
|
-
const
|
|
151
|
+
const extraColumns = /* @__PURE__ */ new Map();
|
|
158
152
|
for (const row of rows) for (const key in row) {
|
|
159
153
|
if (seen.has(key)) continue;
|
|
160
|
-
let
|
|
161
|
-
if (!
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
let inference = extraColumns.get(key);
|
|
155
|
+
if (!inference) {
|
|
156
|
+
inference = {
|
|
157
|
+
hasValue: false,
|
|
158
|
+
hasString: false,
|
|
159
|
+
hasFloat: false,
|
|
160
|
+
hasBigInt: false
|
|
161
|
+
};
|
|
162
|
+
extraColumns.set(key, inference);
|
|
164
163
|
}
|
|
165
|
-
|
|
164
|
+
addInferredValue(inference, row[key]);
|
|
166
165
|
}
|
|
167
|
-
if (
|
|
168
|
-
for (const [name,
|
|
166
|
+
if (extraColumns.size === 0) return schemaColumns ? columns : void 0;
|
|
167
|
+
for (const [name, inference] of extraColumns) columns.push({
|
|
169
168
|
name,
|
|
170
|
-
type:
|
|
169
|
+
type: inferredColumnType(inference),
|
|
171
170
|
nullable: true
|
|
172
171
|
});
|
|
173
172
|
return columns;
|
|
@@ -208,30 +207,46 @@ function rowsToArrowIPCChunks(rows, schemaColumns, opts = {}) {
|
|
|
208
207
|
}
|
|
209
208
|
return chunks;
|
|
210
209
|
}
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
210
|
+
function createDucklingsRowCache(maxBytes = ROW_CACHE_MAX_BYTES) {
|
|
211
|
+
let totalBytes = 0;
|
|
212
|
+
const entries = /* @__PURE__ */ new Map();
|
|
213
|
+
return {
|
|
214
|
+
clear() {
|
|
215
|
+
entries.clear();
|
|
216
|
+
totalBytes = 0;
|
|
217
|
+
},
|
|
218
|
+
get(key) {
|
|
219
|
+
const hit = entries.get(key);
|
|
220
|
+
if (!hit) return void 0;
|
|
221
|
+
entries.delete(key);
|
|
222
|
+
entries.set(key, hit);
|
|
223
|
+
return hit.rows;
|
|
224
|
+
},
|
|
225
|
+
put(key, rows) {
|
|
226
|
+
const bytes = estimateRowsBytes(rows);
|
|
227
|
+
if (bytes > maxBytes) return;
|
|
228
|
+
const existing = entries.get(key);
|
|
229
|
+
if (existing) {
|
|
230
|
+
entries.delete(key);
|
|
231
|
+
totalBytes -= existing.bytes;
|
|
232
|
+
}
|
|
233
|
+
while (totalBytes + bytes > maxBytes) {
|
|
234
|
+
const oldest = entries.keys().next().value;
|
|
235
|
+
if (oldest === void 0) break;
|
|
236
|
+
const evicted = entries.get(oldest);
|
|
237
|
+
entries.delete(oldest);
|
|
238
|
+
totalBytes -= evicted.bytes;
|
|
239
|
+
}
|
|
240
|
+
entries.set(key, {
|
|
241
|
+
rows,
|
|
242
|
+
bytes
|
|
243
|
+
});
|
|
244
|
+
totalBytes += bytes;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
233
247
|
}
|
|
234
248
|
function createDucklingsExecutor(env, opts = {}) {
|
|
249
|
+
const rowCache = opts.rowCache ?? createDucklingsRowCache();
|
|
235
250
|
return { async execute({ sql, params, fileKeys, placeholderTables, pushdownFilters, dataSource, signal, table }) {
|
|
236
251
|
signal?.throwIfAborted();
|
|
237
252
|
const svc = resolveSvc(env);
|
|
@@ -259,14 +274,14 @@ function createDucklingsExecutor(env, opts = {}) {
|
|
|
259
274
|
const filter = pushdownFilters?.[placeholder];
|
|
260
275
|
const perFile = await mapLimit(keys, WORKER_R2_DECODE_CONCURRENCY, async (key) => {
|
|
261
276
|
if (!filter) {
|
|
262
|
-
const cached =
|
|
277
|
+
const cached = rowCache.get(key);
|
|
263
278
|
if (cached) return cached;
|
|
264
279
|
}
|
|
265
280
|
signal?.throwIfAborted();
|
|
266
281
|
const bytes = await dataSource.read(key, void 0, signal);
|
|
267
282
|
signal?.throwIfAborted();
|
|
268
283
|
const rows = await decodeParquetToRows(bytes, filter ? { filter } : {});
|
|
269
|
-
if (!filter)
|
|
284
|
+
if (!filter) rowCache.put(key, rows);
|
|
270
285
|
return rows;
|
|
271
286
|
});
|
|
272
287
|
const merged = [];
|
|
@@ -331,24 +346,32 @@ function createDucklingsExecutor(env, opts = {}) {
|
|
|
331
346
|
};
|
|
332
347
|
} };
|
|
333
348
|
}
|
|
349
|
+
function createAnalyticsEngineRuntime() {
|
|
350
|
+
const rowCache = createDucklingsRowCache();
|
|
351
|
+
return { getEngine(env, db, hooks = {}) {
|
|
352
|
+
if (!env.R2_DATA) return null;
|
|
353
|
+
const baseDataSource = createR2DataSource({
|
|
354
|
+
bucket: env.R2_DATA,
|
|
355
|
+
bucketName: env.R2_BUCKET_NAME
|
|
356
|
+
});
|
|
357
|
+
return createStorageEngine({
|
|
358
|
+
dataSource: hooks.onR2Write ? {
|
|
359
|
+
...baseDataSource,
|
|
360
|
+
async write(key, bytes) {
|
|
361
|
+
hooks.onR2Write(bytes.byteLength);
|
|
362
|
+
return baseDataSource.write(key, bytes);
|
|
363
|
+
}
|
|
364
|
+
} : baseDataSource,
|
|
365
|
+
manifestStore: createD1ManifestStore(db),
|
|
366
|
+
codec: createDucklingsCodec(env),
|
|
367
|
+
executor: createDucklingsExecutor(env, { rowCache })
|
|
368
|
+
});
|
|
369
|
+
} };
|
|
370
|
+
}
|
|
371
|
+
let sharedRuntime;
|
|
334
372
|
function getAnalyticsEngine(env, db, hooks = {}) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
bucket: env.R2_DATA,
|
|
338
|
-
bucketName: env.R2_BUCKET_NAME
|
|
339
|
-
});
|
|
340
|
-
return createStorageEngine({
|
|
341
|
-
dataSource: hooks.onR2Write ? {
|
|
342
|
-
...baseDataSource,
|
|
343
|
-
async write(key, bytes) {
|
|
344
|
-
hooks.onR2Write(bytes.byteLength);
|
|
345
|
-
return baseDataSource.write(key, bytes);
|
|
346
|
-
}
|
|
347
|
-
} : baseDataSource,
|
|
348
|
-
manifestStore: createD1ManifestStore(db),
|
|
349
|
-
codec: createDucklingsCodec(env),
|
|
350
|
-
executor: createDucklingsExecutor(env)
|
|
351
|
-
});
|
|
373
|
+
sharedRuntime ??= createAnalyticsEngineRuntime();
|
|
374
|
+
return sharedRuntime.getEngine(env, db, hooks);
|
|
352
375
|
}
|
|
353
376
|
function useAnalyticsEnv(event) {
|
|
354
377
|
const fromCtx = event.context.analyticsEnv;
|
|
@@ -436,4 +459,4 @@ async function verifySizeHint(env, key, bytes, providedHex) {
|
|
|
436
459
|
for (let i = 0; i < SIG_HEX_LEN; i++) diff |= expected.charCodeAt(i) ^ providedHex.charCodeAt(i);
|
|
437
460
|
return diff === 0;
|
|
438
461
|
}
|
|
439
|
-
export { createDucklingsCodec, createDucklingsExecutor, createInflightDedupe, getAnalyticsEngine, getHostedR2QueryKey, signSizeHint, useAnalyticsEnv, verifySizeHint };
|
|
462
|
+
export { createAnalyticsEngineRuntime, createDucklingsCodec, createDucklingsExecutor, createDucklingsRowCache, createInflightDedupe, getAnalyticsEngine, getHostedR2QueryKey, signSizeHint, useAnalyticsEnv, verifySizeHint };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cloudflare",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.37.
|
|
4
|
+
"version": "0.37.4",
|
|
5
5
|
"description": "Cloudflare-Workers-flavored helpers for the gscdump analytics stack: AnalyticsEnv binding contract, R2 SigV4 presigner, size-hint HMAC, DuckDB Workers shims, engine factory.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@uwdata/flechette": "^2.5.0",
|
|
48
48
|
"aws4fetch": "^1.0.20",
|
|
49
|
-
"@gscdump/contracts": "0.37.
|
|
50
|
-
"gscdump": "0.37.
|
|
51
|
-
"@gscdump/engine
|
|
52
|
-
"
|
|
49
|
+
"@gscdump/contracts": "0.37.4",
|
|
50
|
+
"@gscdump/engine-sqlite": "0.37.4",
|
|
51
|
+
"@gscdump/engine": "0.37.4",
|
|
52
|
+
"gscdump": "0.37.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@cloudflare/vitest-pool-workers": "^0.18.0",
|