@oxy-hq/sdk 0.1.6 → 0.2.1
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/README.md +281 -171
- package/dist/index.cjs +523 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +416 -33
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +416 -33
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +519 -58
- package/dist/index.mjs.map +1 -1
- package/dist/postMessage-B1J0jDRN.cjs.map +1 -1
- package/dist/postMessage-BxdgtX8j.mjs.map +1 -1
- package/package.json +11 -6
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
// @oxy/sdk - TypeScript SDK for Oxy data platform
|
|
2
2
|
import { a as PostMessageAuthInvalidOriginError, c as PostMessageAuthTimeoutError, n as isInIframe, o as PostMessageAuthInvalidResponseError, r as requestAuthFromParent, s as PostMessageAuthNotInIframeError } from "./postMessage-BxdgtX8j.mjs";
|
|
3
3
|
import * as duckdb from "@duckdb/duckdb-wasm";
|
|
4
|
+
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
4
5
|
|
|
5
6
|
//#region src/config.ts
|
|
6
7
|
/**
|
|
8
|
+
* Safely get environment variable in both Node.js and browser environments
|
|
9
|
+
*/
|
|
10
|
+
function getEnvVar(name) {
|
|
11
|
+
if (typeof process !== "undefined" && process.env) return process.env[name];
|
|
12
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
13
|
+
const viteValue = import.meta.env[`VITE_${name}`];
|
|
14
|
+
if (viteValue !== void 0) return viteValue;
|
|
15
|
+
return import.meta.env[name];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
7
19
|
* Creates an Oxy configuration from environment variables
|
|
8
20
|
*
|
|
9
21
|
* Environment variables:
|
|
@@ -17,16 +29,16 @@ import * as duckdb from "@duckdb/duckdb-wasm";
|
|
|
17
29
|
* @throws Error if required environment variables are missing
|
|
18
30
|
*/
|
|
19
31
|
function createConfig(overrides) {
|
|
20
|
-
const baseUrl = overrides?.baseUrl ||
|
|
21
|
-
const apiKey = overrides?.apiKey ||
|
|
22
|
-
const projectId = overrides?.projectId ||
|
|
32
|
+
const baseUrl = overrides?.baseUrl || getEnvVar("OXY_URL");
|
|
33
|
+
const apiKey = overrides?.apiKey || getEnvVar("OXY_API_KEY");
|
|
34
|
+
const projectId = overrides?.projectId || getEnvVar("OXY_PROJECT_ID");
|
|
23
35
|
if (!baseUrl) throw new Error("OXY_URL environment variable or baseUrl config is required");
|
|
24
36
|
if (!projectId) throw new Error("OXY_PROJECT_ID environment variable or projectId config is required");
|
|
25
37
|
return {
|
|
26
38
|
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
27
39
|
apiKey,
|
|
28
40
|
projectId,
|
|
29
|
-
branch: overrides?.branch ||
|
|
41
|
+
branch: overrides?.branch || getEnvVar("OXY_BRANCH"),
|
|
30
42
|
timeout: overrides?.timeout || 3e4,
|
|
31
43
|
parentOrigin: overrides?.parentOrigin,
|
|
32
44
|
disableAutoAuth: overrides?.disableAutoAuth
|
|
@@ -67,9 +79,9 @@ function createConfig(overrides) {
|
|
|
67
79
|
*/
|
|
68
80
|
async function createConfigAsync(overrides) {
|
|
69
81
|
const { isInIframe: isInIframe$1 } = await import("./postMessage-D5wWgwcO.mjs");
|
|
70
|
-
let baseUrl = overrides?.baseUrl ||
|
|
71
|
-
let apiKey = overrides?.apiKey ||
|
|
72
|
-
let projectId = overrides?.projectId ||
|
|
82
|
+
let baseUrl = overrides?.baseUrl || getEnvVar("OXY_URL");
|
|
83
|
+
let apiKey = overrides?.apiKey || getEnvVar("OXY_API_KEY");
|
|
84
|
+
let projectId = overrides?.projectId || getEnvVar("OXY_PROJECT_ID");
|
|
73
85
|
const disableAutoAuth = overrides?.disableAutoAuth ?? false;
|
|
74
86
|
const parentOrigin = overrides?.parentOrigin;
|
|
75
87
|
if (!disableAutoAuth && isInIframe$1() && !apiKey) if (!parentOrigin) logWarningAboutMissingParentOrigin();
|
|
@@ -106,7 +118,7 @@ function createFinalConfig(baseUrl, apiKey, projectId, overrides) {
|
|
|
106
118
|
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
107
119
|
apiKey,
|
|
108
120
|
projectId,
|
|
109
|
-
branch: overrides?.branch ||
|
|
121
|
+
branch: overrides?.branch || getEnvVar("OXY_BRANCH"),
|
|
110
122
|
timeout: overrides?.timeout || 3e4,
|
|
111
123
|
parentOrigin: overrides?.parentOrigin,
|
|
112
124
|
disableAutoAuth: overrides?.disableAutoAuth
|
|
@@ -150,41 +162,76 @@ async function getConnection() {
|
|
|
150
162
|
return connection;
|
|
151
163
|
}
|
|
152
164
|
/**
|
|
153
|
-
* ParquetReader provides methods to read and query Parquet files
|
|
165
|
+
* ParquetReader provides methods to read and query Parquet files.
|
|
166
|
+
* Supports registering multiple Parquet files with different table names.
|
|
154
167
|
*/
|
|
155
168
|
var ParquetReader = class {
|
|
156
|
-
constructor(
|
|
157
|
-
this.
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
constructor() {
|
|
170
|
+
this.tableMap = /* @__PURE__ */ new Map();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Generate a unique internal table name to prevent conflicts
|
|
174
|
+
*/
|
|
175
|
+
generateInternalTableName(tableName) {
|
|
176
|
+
return `${tableName}_${`${Date.now()}_${Math.random().toString(36).substring(2, 9)}`}`;
|
|
160
177
|
}
|
|
161
178
|
/**
|
|
162
|
-
* Register a Parquet file from a Blob
|
|
179
|
+
* Register a Parquet file from a Blob with a specific table name
|
|
163
180
|
*
|
|
164
181
|
* @param blob - Parquet file as Blob
|
|
182
|
+
* @param tableName - Name to use for the table in queries (required)
|
|
165
183
|
*
|
|
166
184
|
* @example
|
|
167
185
|
* ```typescript
|
|
168
186
|
* const blob = await client.getFile('data/sales.parquet');
|
|
169
|
-
* const reader = new ParquetReader(
|
|
170
|
-
* await reader.registerParquet(blob);
|
|
187
|
+
* const reader = new ParquetReader();
|
|
188
|
+
* await reader.registerParquet(blob, 'sales');
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* // Register multiple files
|
|
194
|
+
* const reader = new ParquetReader();
|
|
195
|
+
* await reader.registerParquet(salesBlob, 'sales');
|
|
196
|
+
* await reader.registerParquet(customersBlob, 'customers');
|
|
197
|
+
* const result = await reader.query('SELECT * FROM sales JOIN customers ON sales.customer_id = customers.id');
|
|
171
198
|
* ```
|
|
172
199
|
*/
|
|
173
|
-
async registerParquet(blob) {
|
|
200
|
+
async registerParquet(blob, tableName) {
|
|
201
|
+
const internalTableName = this.generateInternalTableName(tableName);
|
|
174
202
|
await enqueueOperation(async () => {
|
|
175
203
|
const conn = await getConnection();
|
|
176
204
|
const db = await initializeDuckDB();
|
|
177
205
|
const arrayBuffer = await blob.arrayBuffer();
|
|
178
206
|
const uint8Array = new Uint8Array(arrayBuffer);
|
|
179
|
-
await db.registerFileBuffer(`${
|
|
207
|
+
await db.registerFileBuffer(`${internalTableName}.parquet`, uint8Array);
|
|
180
208
|
try {
|
|
181
|
-
await conn.query(`DROP TABLE IF EXISTS ${
|
|
209
|
+
await conn.query(`DROP TABLE IF EXISTS ${internalTableName}`);
|
|
182
210
|
} catch {}
|
|
183
|
-
await conn.query(`CREATE TABLE ${
|
|
184
|
-
this.
|
|
211
|
+
await conn.query(`CREATE TABLE ${internalTableName} AS SELECT * FROM '${internalTableName}.parquet'`);
|
|
212
|
+
this.tableMap.set(tableName, internalTableName);
|
|
185
213
|
});
|
|
186
214
|
}
|
|
187
215
|
/**
|
|
216
|
+
* Register multiple Parquet files at once
|
|
217
|
+
*
|
|
218
|
+
* @param files - Array of objects containing blob and tableName
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* const reader = new ParquetReader();
|
|
223
|
+
* await reader.registerMultipleParquet([
|
|
224
|
+
* { blob: salesBlob, tableName: 'sales' },
|
|
225
|
+
* { blob: customersBlob, tableName: 'customers' },
|
|
226
|
+
* { blob: productsBlob, tableName: 'products' }
|
|
227
|
+
* ]);
|
|
228
|
+
* const result = await reader.query('SELECT * FROM sales JOIN customers ON sales.customer_id = customers.id');
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
async registerMultipleParquet(files) {
|
|
232
|
+
for (const file of files) await this.registerParquet(file.blob, file.tableName);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
188
235
|
* Execute a SQL query against the registered Parquet data
|
|
189
236
|
*
|
|
190
237
|
* @param sql - SQL query string
|
|
@@ -196,12 +243,25 @@ var ParquetReader = class {
|
|
|
196
243
|
* console.log(result.columns);
|
|
197
244
|
* console.log(result.rows);
|
|
198
245
|
* ```
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* // Query multiple tables
|
|
250
|
+
* await reader.registerParquet(salesBlob, 'sales');
|
|
251
|
+
* await reader.registerParquet(customersBlob, 'customers');
|
|
252
|
+
* const result = await reader.query(`
|
|
253
|
+
* SELECT s.*, c.name
|
|
254
|
+
* FROM sales s
|
|
255
|
+
* JOIN customers c ON s.customer_id = c.id
|
|
256
|
+
* `);
|
|
257
|
+
* ```
|
|
199
258
|
*/
|
|
200
259
|
async query(sql) {
|
|
201
|
-
if (
|
|
260
|
+
if (this.tableMap.size === 0) throw new Error("No Parquet files registered. Call registerParquet() first.");
|
|
202
261
|
return enqueueOperation(async () => {
|
|
203
262
|
const conn = await getConnection();
|
|
204
|
-
|
|
263
|
+
let rewrittenSql = sql;
|
|
264
|
+
for (const [userTableName, internalTableName] of this.tableMap.entries()) rewrittenSql = rewrittenSql.replace(new RegExp(`\\b${userTableName}\\b`, "g"), internalTableName);
|
|
205
265
|
const result = await conn.query(rewrittenSql);
|
|
206
266
|
const columns = result.schema.fields.map((field) => field.name);
|
|
207
267
|
const rows = [];
|
|
@@ -221,64 +281,69 @@ var ParquetReader = class {
|
|
|
221
281
|
});
|
|
222
282
|
}
|
|
223
283
|
/**
|
|
224
|
-
* Get all data from
|
|
284
|
+
* Get all data from a registered table
|
|
225
285
|
*
|
|
286
|
+
* @param tableName - Name of the table to query
|
|
226
287
|
* @param limit - Maximum number of rows to return (default: all)
|
|
227
288
|
* @returns Query result
|
|
228
289
|
*
|
|
229
290
|
* @example
|
|
230
291
|
* ```typescript
|
|
231
|
-
* const allData = await reader.getAll();
|
|
232
|
-
* const first100 = await reader.getAll(100);
|
|
292
|
+
* const allData = await reader.getAll('sales');
|
|
293
|
+
* const first100 = await reader.getAll('sales', 100);
|
|
233
294
|
* ```
|
|
234
295
|
*/
|
|
235
|
-
async getAll(limit) {
|
|
296
|
+
async getAll(tableName, limit) {
|
|
236
297
|
const limitClause = limit ? ` LIMIT ${limit}` : "";
|
|
237
|
-
return this.query(`SELECT * FROM ${
|
|
298
|
+
return this.query(`SELECT * FROM ${tableName}${limitClause}`);
|
|
238
299
|
}
|
|
239
300
|
/**
|
|
240
301
|
* Get table schema information
|
|
241
302
|
*
|
|
303
|
+
* @param tableName - Name of the table to describe
|
|
242
304
|
* @returns Schema information
|
|
243
305
|
*
|
|
244
306
|
* @example
|
|
245
307
|
* ```typescript
|
|
246
|
-
* const schema = await reader.getSchema();
|
|
308
|
+
* const schema = await reader.getSchema('sales');
|
|
247
309
|
* console.log(schema.columns); // ['id', 'name', 'sales']
|
|
248
310
|
* console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]
|
|
249
311
|
* ```
|
|
250
312
|
*/
|
|
251
|
-
async getSchema() {
|
|
252
|
-
return this.query(`DESCRIBE ${
|
|
313
|
+
async getSchema(tableName) {
|
|
314
|
+
return this.query(`DESCRIBE ${tableName}`);
|
|
253
315
|
}
|
|
254
316
|
/**
|
|
255
|
-
* Get row count
|
|
317
|
+
* Get row count for a table
|
|
256
318
|
*
|
|
319
|
+
* @param tableName - Name of the table to count
|
|
257
320
|
* @returns Number of rows in the table
|
|
258
321
|
*
|
|
259
322
|
* @example
|
|
260
323
|
* ```typescript
|
|
261
|
-
* const count = await reader.count();
|
|
324
|
+
* const count = await reader.count('sales');
|
|
262
325
|
* console.log(`Total rows: ${count}`);
|
|
263
326
|
* ```
|
|
264
327
|
*/
|
|
265
|
-
async count() {
|
|
266
|
-
return (await this.query(`SELECT COUNT(*) as count FROM ${
|
|
328
|
+
async count(tableName) {
|
|
329
|
+
return (await this.query(`SELECT COUNT(*) as count FROM ${tableName}`)).rows[0][0];
|
|
267
330
|
}
|
|
268
331
|
/**
|
|
269
|
-
* Close and cleanup resources
|
|
332
|
+
* Close and cleanup all registered resources
|
|
270
333
|
*/
|
|
271
334
|
async close() {
|
|
272
|
-
if (this.
|
|
335
|
+
if (this.tableMap.size > 0) await enqueueOperation(async () => {
|
|
273
336
|
const conn = await getConnection();
|
|
274
337
|
const db = await initializeDuckDB();
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
338
|
+
for (const [, internalTableName] of this.tableMap.entries()) {
|
|
339
|
+
try {
|
|
340
|
+
await conn.query(`DROP TABLE IF EXISTS ${internalTableName}`);
|
|
341
|
+
} catch {}
|
|
342
|
+
try {
|
|
343
|
+
await db.dropFile(`${internalTableName}.parquet`);
|
|
344
|
+
} catch {}
|
|
345
|
+
}
|
|
346
|
+
this.tableMap.clear();
|
|
282
347
|
});
|
|
283
348
|
}
|
|
284
349
|
};
|
|
@@ -286,21 +351,21 @@ var ParquetReader = class {
|
|
|
286
351
|
* Helper function to quickly read a Parquet blob and execute a query
|
|
287
352
|
*
|
|
288
353
|
* @param blob - Parquet file as Blob
|
|
289
|
-
* @param
|
|
354
|
+
* @param tableName - Name to use for the table in queries (default: 'data')
|
|
355
|
+
* @param sql - SQL query to execute (optional, defaults to SELECT * FROM tableName)
|
|
290
356
|
* @returns Query result
|
|
291
357
|
*
|
|
292
358
|
* @example
|
|
293
359
|
* ```typescript
|
|
294
360
|
* const blob = await client.getFile('data/sales.parquet');
|
|
295
|
-
* const result = await queryParquet(blob, 'SELECT product, SUM(amount) as total FROM
|
|
361
|
+
* const result = await queryParquet(blob, 'sales', 'SELECT product, SUM(amount) as total FROM sales GROUP BY product');
|
|
296
362
|
* console.log(result);
|
|
297
363
|
* ```
|
|
298
364
|
*/
|
|
299
|
-
async function queryParquet(blob, sql) {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const query = sql || `SELECT * FROM ${uniqueId}`;
|
|
365
|
+
async function queryParquet(blob, tableName = "data", sql) {
|
|
366
|
+
const reader = new ParquetReader();
|
|
367
|
+
await reader.registerParquet(blob, tableName);
|
|
368
|
+
const query = sql || `SELECT * FROM ${tableName}`;
|
|
304
369
|
const result = await reader.query(query);
|
|
305
370
|
await reader.close();
|
|
306
371
|
return result;
|
|
@@ -309,20 +374,21 @@ async function queryParquet(blob, sql) {
|
|
|
309
374
|
* Helper function to read Parquet file and get all data
|
|
310
375
|
*
|
|
311
376
|
* @param blob - Parquet file as Blob
|
|
377
|
+
* @param tableName - Name to use for the table (default: 'data')
|
|
312
378
|
* @param limit - Maximum number of rows (optional)
|
|
313
379
|
* @returns Query result
|
|
314
380
|
*
|
|
315
381
|
* @example
|
|
316
382
|
* ```typescript
|
|
317
383
|
* const blob = await client.getFile('data/sales.parquet');
|
|
318
|
-
* const data = await readParquet(blob, 1000);
|
|
384
|
+
* const data = await readParquet(blob, 'sales', 1000);
|
|
319
385
|
* console.log(`Loaded ${data.rowCount} rows`);
|
|
320
386
|
* ```
|
|
321
387
|
*/
|
|
322
|
-
async function readParquet(blob, limit) {
|
|
323
|
-
const reader = new ParquetReader(
|
|
324
|
-
await reader.registerParquet(blob);
|
|
325
|
-
const result = await reader.getAll(limit);
|
|
388
|
+
async function readParquet(blob, tableName = "data", limit) {
|
|
389
|
+
const reader = new ParquetReader();
|
|
390
|
+
await reader.registerParquet(blob, tableName);
|
|
391
|
+
const result = await reader.getAll(tableName, limit);
|
|
326
392
|
await reader.close();
|
|
327
393
|
return result;
|
|
328
394
|
}
|
|
@@ -562,7 +628,7 @@ var OxyClient = class OxyClient {
|
|
|
562
628
|
* ```
|
|
563
629
|
*/
|
|
564
630
|
async getTableData(filePath, limit = 100) {
|
|
565
|
-
const result = await readParquet(await this.getFile(filePath), limit);
|
|
631
|
+
const result = await readParquet(await this.getFile(filePath), "data", limit);
|
|
566
632
|
return {
|
|
567
633
|
columns: result.columns,
|
|
568
634
|
rows: result.rows,
|
|
@@ -572,5 +638,400 @@ var OxyClient = class OxyClient {
|
|
|
572
638
|
};
|
|
573
639
|
|
|
574
640
|
//#endregion
|
|
575
|
-
|
|
641
|
+
//#region src/sdk.ts
|
|
642
|
+
/**
|
|
643
|
+
* OxySDK provides a unified interface for fetching data from Oxy and querying it with SQL.
|
|
644
|
+
* It combines OxyClient (for API calls) and ParquetReader (for SQL queries) into a single,
|
|
645
|
+
* easy-to-use interface.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* // Create SDK instance
|
|
650
|
+
* const sdk = new OxySDK({ apiKey: 'your-key', projectId: 'your-project' });
|
|
651
|
+
*
|
|
652
|
+
* // Load a parquet file and query it
|
|
653
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
654
|
+
* const result = await sdk.query('SELECT * FROM sales WHERE amount > 1000');
|
|
655
|
+
* console.log(result.rows);
|
|
656
|
+
*
|
|
657
|
+
* // Clean up when done
|
|
658
|
+
* await sdk.close();
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
var OxySDK = class OxySDK {
|
|
662
|
+
constructor(config) {
|
|
663
|
+
this.client = new OxyClient(config);
|
|
664
|
+
this.reader = new ParquetReader();
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Creates an OxySDK instance asynchronously with support for postMessage authentication
|
|
668
|
+
*
|
|
669
|
+
* @param config - Optional configuration overrides
|
|
670
|
+
* @returns Promise resolving to OxySDK instance
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```typescript
|
|
674
|
+
* // In an iframe - automatic postMessage auth
|
|
675
|
+
* const sdk = await OxySDK.create({
|
|
676
|
+
* parentOrigin: 'https://app.example.com',
|
|
677
|
+
* projectId: 'my-project-id'
|
|
678
|
+
* });
|
|
679
|
+
* ```
|
|
680
|
+
*/
|
|
681
|
+
static async create(config) {
|
|
682
|
+
return new OxySDK(await createConfigAsync(config));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Load a Parquet file from Oxy and register it for SQL queries
|
|
686
|
+
*
|
|
687
|
+
* @param filePath - Path to the parquet file in the app state directory
|
|
688
|
+
* @param tableName - Name to use for the table in SQL queries
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* ```typescript
|
|
692
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
693
|
+
* await sdk.loadFile('data/customers.parquet', 'customers');
|
|
694
|
+
*
|
|
695
|
+
* const result = await sdk.query(`
|
|
696
|
+
* SELECT s.*, c.name
|
|
697
|
+
* FROM sales s
|
|
698
|
+
* JOIN customers c ON s.customer_id = c.id
|
|
699
|
+
* `);
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
async loadFile(filePath, tableName) {
|
|
703
|
+
const blob = await this.client.getFile(filePath);
|
|
704
|
+
await this.reader.registerParquet(blob, tableName);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Load multiple Parquet files at once
|
|
708
|
+
*
|
|
709
|
+
* @param files - Array of file paths and table names
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* await sdk.loadFiles([
|
|
714
|
+
* { filePath: 'data/sales.parquet', tableName: 'sales' },
|
|
715
|
+
* { filePath: 'data/customers.parquet', tableName: 'customers' },
|
|
716
|
+
* { filePath: 'data/products.parquet', tableName: 'products' }
|
|
717
|
+
* ]);
|
|
718
|
+
*
|
|
719
|
+
* const result = await sdk.query('SELECT * FROM sales');
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
722
|
+
async loadFiles(files) {
|
|
723
|
+
for (const file of files) await this.loadFile(file.filePath, file.tableName);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Load all data from an app's data container
|
|
727
|
+
*
|
|
728
|
+
* This fetches the app's data and registers all parquet files using their container keys as table names.
|
|
729
|
+
*
|
|
730
|
+
* @param appPath - Path to the app file
|
|
731
|
+
* @returns DataContainer with file references
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```typescript
|
|
735
|
+
* // If app has data: { sales: { file_path: 'data/sales.parquet' } }
|
|
736
|
+
* const data = await sdk.loadAppData('dashboard.app.yml');
|
|
737
|
+
* // Now you can query the 'sales' table
|
|
738
|
+
* const result = await sdk.query('SELECT * FROM sales LIMIT 10');
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
async loadAppData(appPath) {
|
|
742
|
+
const appDataResponse = await this.client.getAppData(appPath);
|
|
743
|
+
if (appDataResponse.error) throw new Error(`Failed to load app data: ${appDataResponse.error}`);
|
|
744
|
+
if (!appDataResponse.data) return null;
|
|
745
|
+
const loadPromises = Object.entries(appDataResponse.data).map(async ([tableName, fileRef]) => {
|
|
746
|
+
await this.loadFile(fileRef.file_path, tableName);
|
|
747
|
+
});
|
|
748
|
+
await Promise.all(loadPromises);
|
|
749
|
+
return appDataResponse.data;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Execute a SQL query against loaded data
|
|
753
|
+
*
|
|
754
|
+
* @param sql - SQL query to execute
|
|
755
|
+
* @returns Query result with columns and rows
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```typescript
|
|
759
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
760
|
+
*
|
|
761
|
+
* const result = await sdk.query('SELECT product, SUM(amount) as total FROM sales GROUP BY product');
|
|
762
|
+
* console.log(result.columns); // ['product', 'total']
|
|
763
|
+
* console.log(result.rows); // [['Product A', 1000], ['Product B', 2000]]
|
|
764
|
+
* console.log(result.rowCount); // 2
|
|
765
|
+
* ```
|
|
766
|
+
*/
|
|
767
|
+
async query(sql) {
|
|
768
|
+
return this.reader.query(sql);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Get all data from a loaded table
|
|
772
|
+
*
|
|
773
|
+
* @param tableName - Name of the table
|
|
774
|
+
* @param limit - Maximum number of rows (optional)
|
|
775
|
+
* @returns Query result
|
|
776
|
+
*
|
|
777
|
+
* @example
|
|
778
|
+
* ```typescript
|
|
779
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
780
|
+
* const allData = await sdk.getAll('sales');
|
|
781
|
+
* const first100 = await sdk.getAll('sales', 100);
|
|
782
|
+
* ```
|
|
783
|
+
*/
|
|
784
|
+
async getAll(tableName, limit) {
|
|
785
|
+
return this.reader.getAll(tableName, limit);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get schema information for a loaded table
|
|
789
|
+
*
|
|
790
|
+
* @param tableName - Name of the table
|
|
791
|
+
* @returns Schema information
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```typescript
|
|
795
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
796
|
+
* const schema = await sdk.getSchema('sales');
|
|
797
|
+
* console.log(schema.columns); // ['column_name', 'column_type', ...]
|
|
798
|
+
* console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
async getSchema(tableName) {
|
|
802
|
+
return this.reader.getSchema(tableName);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Get row count for a loaded table
|
|
806
|
+
*
|
|
807
|
+
* @param tableName - Name of the table
|
|
808
|
+
* @returns Number of rows
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* ```typescript
|
|
812
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
813
|
+
* const count = await sdk.count('sales');
|
|
814
|
+
* console.log(`Total rows: ${count}`);
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
817
|
+
async count(tableName) {
|
|
818
|
+
return this.reader.count(tableName);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get direct access to the underlying OxyClient
|
|
822
|
+
*
|
|
823
|
+
* Useful for advanced operations like listing apps, getting displays, etc.
|
|
824
|
+
*
|
|
825
|
+
* @returns The OxyClient instance
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```typescript
|
|
829
|
+
* const apps = await sdk.getClient().listApps();
|
|
830
|
+
* const displays = await sdk.getClient().getDisplays('my-app.app.yml');
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
833
|
+
getClient() {
|
|
834
|
+
return this.client;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Get direct access to the underlying ParquetReader
|
|
838
|
+
*
|
|
839
|
+
* Useful for advanced operations like registering blobs directly.
|
|
840
|
+
*
|
|
841
|
+
* @returns The ParquetReader instance
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```typescript
|
|
845
|
+
* const myBlob = new Blob([parquetData]);
|
|
846
|
+
* await sdk.getReader().registerParquet(myBlob, 'mydata');
|
|
847
|
+
* ```
|
|
848
|
+
*/
|
|
849
|
+
getReader() {
|
|
850
|
+
return this.reader;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Close and cleanup all resources
|
|
854
|
+
*
|
|
855
|
+
* This clears all loaded data and releases resources. Call this when you're done with the SDK.
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```typescript
|
|
859
|
+
* const sdk = new OxySDK({ apiKey: 'key', projectId: 'project' });
|
|
860
|
+
* await sdk.loadFile('data/sales.parquet', 'sales');
|
|
861
|
+
* const result = await sdk.query('SELECT * FROM sales');
|
|
862
|
+
* await sdk.close(); // Clean up
|
|
863
|
+
* ```
|
|
864
|
+
*/
|
|
865
|
+
async close() {
|
|
866
|
+
await this.reader.close();
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/react.tsx
|
|
872
|
+
/**
|
|
873
|
+
* React context for OxySDK
|
|
874
|
+
*/
|
|
875
|
+
const OxyContext = createContext(void 0);
|
|
876
|
+
/**
|
|
877
|
+
* Provider component that initializes and provides OxySDK to child components
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```tsx
|
|
881
|
+
* // Synchronous initialization with config
|
|
882
|
+
* function App() {
|
|
883
|
+
* return (
|
|
884
|
+
* <OxyProvider config={{
|
|
885
|
+
* apiKey: 'your-key',
|
|
886
|
+
* projectId: 'your-project',
|
|
887
|
+
* baseUrl: 'https://api.oxy.tech'
|
|
888
|
+
* }}>
|
|
889
|
+
* <Dashboard />
|
|
890
|
+
* </OxyProvider>
|
|
891
|
+
* );
|
|
892
|
+
* }
|
|
893
|
+
* ```
|
|
894
|
+
*
|
|
895
|
+
* @example
|
|
896
|
+
* ```tsx
|
|
897
|
+
* // Async initialization (for iframe/postMessage auth)
|
|
898
|
+
* function App() {
|
|
899
|
+
* return (
|
|
900
|
+
* <OxyProvider
|
|
901
|
+
* useAsync
|
|
902
|
+
* config={{ parentOrigin: 'https://app.example.com' }}
|
|
903
|
+
* >
|
|
904
|
+
* <Dashboard />
|
|
905
|
+
* </OxyProvider>
|
|
906
|
+
* );
|
|
907
|
+
* }
|
|
908
|
+
* ```
|
|
909
|
+
*
|
|
910
|
+
* @example
|
|
911
|
+
* ```tsx
|
|
912
|
+
* // With environment variables
|
|
913
|
+
* import { createConfig } from '@oxy/sdk';
|
|
914
|
+
*
|
|
915
|
+
* function App() {
|
|
916
|
+
* return (
|
|
917
|
+
* <OxyProvider config={createConfig()}>
|
|
918
|
+
* <Dashboard />
|
|
919
|
+
* </OxyProvider>
|
|
920
|
+
* );
|
|
921
|
+
* }
|
|
922
|
+
* ```
|
|
923
|
+
*/
|
|
924
|
+
function OxyProvider({ children, config, useAsync = false, onReady, onError }) {
|
|
925
|
+
const [sdk, setSdk] = useState(null);
|
|
926
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
927
|
+
const [error, setError] = useState(null);
|
|
928
|
+
useEffect(() => {
|
|
929
|
+
let mounted = true;
|
|
930
|
+
let sdkInstance = null;
|
|
931
|
+
async function initializeSDK() {
|
|
932
|
+
try {
|
|
933
|
+
setIsLoading(true);
|
|
934
|
+
setError(null);
|
|
935
|
+
if (useAsync) sdkInstance = await OxySDK.create(config);
|
|
936
|
+
else {
|
|
937
|
+
if (!config) throw new Error("Config is required when useAsync is false. Either provide config or set useAsync=true.");
|
|
938
|
+
sdkInstance = new OxySDK(config);
|
|
939
|
+
}
|
|
940
|
+
if (mounted) {
|
|
941
|
+
setSdk(sdkInstance);
|
|
942
|
+
setIsLoading(false);
|
|
943
|
+
onReady?.(sdkInstance);
|
|
944
|
+
}
|
|
945
|
+
} catch (err) {
|
|
946
|
+
const error$1 = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to initialize SDK");
|
|
947
|
+
if (mounted) {
|
|
948
|
+
setError(error$1);
|
|
949
|
+
setIsLoading(false);
|
|
950
|
+
onError?.(error$1);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
initializeSDK();
|
|
955
|
+
return () => {
|
|
956
|
+
mounted = false;
|
|
957
|
+
if (sdkInstance) sdkInstance.close().catch(console.error);
|
|
958
|
+
};
|
|
959
|
+
}, [
|
|
960
|
+
config,
|
|
961
|
+
useAsync,
|
|
962
|
+
onReady,
|
|
963
|
+
onError
|
|
964
|
+
]);
|
|
965
|
+
return /* @__PURE__ */ React.createElement(OxyContext.Provider, { value: {
|
|
966
|
+
sdk,
|
|
967
|
+
isLoading,
|
|
968
|
+
error
|
|
969
|
+
} }, children);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Hook to access OxySDK from child components
|
|
973
|
+
*
|
|
974
|
+
* @throws {Error} If used outside of OxyProvider
|
|
975
|
+
* @returns {OxyContextValue} The SDK instance, loading state, and error
|
|
976
|
+
*
|
|
977
|
+
* @example
|
|
978
|
+
* ```tsx
|
|
979
|
+
* function Dashboard() {
|
|
980
|
+
* const { sdk, isLoading, error } = useOxy();
|
|
981
|
+
*
|
|
982
|
+
* useEffect(() => {
|
|
983
|
+
* if (sdk) {
|
|
984
|
+
* sdk.loadAppData('dashboard.app.yml')
|
|
985
|
+
* .then(() => sdk.query('SELECT * FROM my_table'))
|
|
986
|
+
* .then(result => console.log(result));
|
|
987
|
+
* }
|
|
988
|
+
* }, [sdk]);
|
|
989
|
+
*
|
|
990
|
+
* if (isLoading) return <div>Loading SDK...</div>;
|
|
991
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
992
|
+
* if (!sdk) return null;
|
|
993
|
+
*
|
|
994
|
+
* return <div>Dashboard</div>;
|
|
995
|
+
* }
|
|
996
|
+
* ```
|
|
997
|
+
*/
|
|
998
|
+
function useOxy() {
|
|
999
|
+
const context = useContext(OxyContext);
|
|
1000
|
+
if (context === void 0) throw new Error("useOxy must be used within an OxyProvider");
|
|
1001
|
+
return context;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Hook to access OxySDK that throws if not ready
|
|
1005
|
+
*
|
|
1006
|
+
* This is a convenience hook that returns the SDK directly or throws an error if not initialized.
|
|
1007
|
+
* Use this when you know the SDK should be ready.
|
|
1008
|
+
*
|
|
1009
|
+
* @throws {Error} If used outside of OxyProvider or if SDK is not initialized
|
|
1010
|
+
* @returns {OxySDK} The SDK instance
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* ```tsx
|
|
1014
|
+
* function DataTable() {
|
|
1015
|
+
* const sdk = useOxySDK();
|
|
1016
|
+
* const [data, setData] = useState(null);
|
|
1017
|
+
*
|
|
1018
|
+
* useEffect(() => {
|
|
1019
|
+
* sdk.loadFile('data.parquet', 'data')
|
|
1020
|
+
* .then(() => sdk.query('SELECT * FROM data LIMIT 100'))
|
|
1021
|
+
* .then(setData);
|
|
1022
|
+
* }, [sdk]);
|
|
1023
|
+
*
|
|
1024
|
+
* return <table>...</table>;
|
|
1025
|
+
* }
|
|
1026
|
+
* ```
|
|
1027
|
+
*/
|
|
1028
|
+
function useOxySDK() {
|
|
1029
|
+
const { sdk, isLoading, error } = useOxy();
|
|
1030
|
+
if (error) throw error;
|
|
1031
|
+
if (isLoading || !sdk) throw new Error("OxySDK is not yet initialized");
|
|
1032
|
+
return sdk;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
//#endregion
|
|
1036
|
+
export { OxyClient, OxyProvider, OxySDK, ParquetReader, PostMessageAuthInvalidOriginError, PostMessageAuthInvalidResponseError, PostMessageAuthNotInIframeError, PostMessageAuthTimeoutError, createConfig, createConfigAsync, initializeDuckDB, isInIframe, queryParquet, readParquet, requestAuthFromParent, useOxy, useOxySDK };
|
|
576
1037
|
//# sourceMappingURL=index.mjs.map
|