@oxy-hq/sdk 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,450 @@
1
+ // @oxy/sdk - TypeScript SDK for Oxy data platform
2
+ import * as duckdb from "@duckdb/duckdb-wasm";
3
+
4
+ //#region src/parquet.ts
5
+ let dbInstance = null;
6
+ let connection = null;
7
+ /**
8
+ * Initialize DuckDB-WASM instance
9
+ */
10
+ async function initializeDuckDB() {
11
+ if (dbInstance) return dbInstance;
12
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
13
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
14
+ const worker_url = URL.createObjectURL(new Blob([`importScripts("${bundle.mainWorker}");`], { type: "text/javascript" }));
15
+ const worker = new Worker(worker_url);
16
+ const logger = new duckdb.ConsoleLogger();
17
+ dbInstance = new duckdb.AsyncDuckDB(logger, worker);
18
+ await dbInstance.instantiate(bundle.mainModule, bundle.pthreadWorker);
19
+ URL.revokeObjectURL(worker_url);
20
+ return dbInstance;
21
+ }
22
+ /**
23
+ * Get or create a DuckDB connection
24
+ */
25
+ async function getConnection() {
26
+ if (connection) return connection;
27
+ connection = await (await initializeDuckDB()).connect();
28
+ return connection;
29
+ }
30
+ /**
31
+ * ParquetReader provides methods to read and query Parquet files
32
+ */
33
+ var ParquetReader = class {
34
+ constructor(tableName = "data") {
35
+ this.registered = false;
36
+ this.tableName = tableName;
37
+ }
38
+ /**
39
+ * Register a Parquet file from a Blob
40
+ *
41
+ * @param blob - Parquet file as Blob
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const blob = await client.getFile('data/sales.parquet');
46
+ * const reader = new ParquetReader('sales');
47
+ * await reader.registerParquet(blob);
48
+ * ```
49
+ */
50
+ async registerParquet(blob) {
51
+ const conn = await getConnection();
52
+ const db = await initializeDuckDB();
53
+ const arrayBuffer = await blob.arrayBuffer();
54
+ const uint8Array = new Uint8Array(arrayBuffer);
55
+ await db.registerFileBuffer(`${this.tableName}.parquet`, uint8Array);
56
+ try {
57
+ await conn.query(`DROP TABLE IF EXISTS ${this.tableName}`);
58
+ } catch (e) {}
59
+ await conn.query(`CREATE TABLE ${this.tableName} AS SELECT * FROM '${this.tableName}.parquet'`);
60
+ this.registered = true;
61
+ }
62
+ /**
63
+ * Execute a SQL query against the registered Parquet data
64
+ *
65
+ * @param sql - SQL query string
66
+ * @returns Query result with columns and rows
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const result = await reader.query('SELECT * FROM sales LIMIT 10');
71
+ * console.log(result.columns);
72
+ * console.log(result.rows);
73
+ * ```
74
+ */
75
+ async query(sql) {
76
+ if (!this.registered) throw new Error("Parquet file not registered. Call registerParquet() first.");
77
+ const result = await (await getConnection()).query(sql);
78
+ const columns = result.schema.fields.map((field) => field.name);
79
+ const rows = [];
80
+ for (let i = 0; i < result.numRows; i++) {
81
+ const row = [];
82
+ for (let j = 0; j < result.numCols; j++) {
83
+ const col = result.getChildAt(j);
84
+ row.push(col?.get(i));
85
+ }
86
+ rows.push(row);
87
+ }
88
+ return {
89
+ columns,
90
+ rows,
91
+ rowCount: result.numRows
92
+ };
93
+ }
94
+ /**
95
+ * Get all data from the registered table
96
+ *
97
+ * @param limit - Maximum number of rows to return (default: all)
98
+ * @returns Query result
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const allData = await reader.getAll();
103
+ * const first100 = await reader.getAll(100);
104
+ * ```
105
+ */
106
+ async getAll(limit) {
107
+ const limitClause = limit ? ` LIMIT ${limit}` : "";
108
+ return this.query(`SELECT * FROM ${this.tableName}${limitClause}`);
109
+ }
110
+ /**
111
+ * Get table schema information
112
+ *
113
+ * @returns Schema information
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const schema = await reader.getSchema();
118
+ * console.log(schema.columns); // ['id', 'name', 'sales']
119
+ * console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]
120
+ * ```
121
+ */
122
+ async getSchema() {
123
+ return this.query(`DESCRIBE ${this.tableName}`);
124
+ }
125
+ /**
126
+ * Get row count
127
+ *
128
+ * @returns Number of rows in the table
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const count = await reader.count();
133
+ * console.log(`Total rows: ${count}`);
134
+ * ```
135
+ */
136
+ async count() {
137
+ return (await this.query(`SELECT COUNT(*) as count FROM ${this.tableName}`)).rows[0][0];
138
+ }
139
+ /**
140
+ * Close and cleanup resources
141
+ */
142
+ async close() {
143
+ if (this.registered) {
144
+ const conn = await getConnection();
145
+ const db = await initializeDuckDB();
146
+ try {
147
+ await conn.query(`DROP TABLE IF EXISTS ${this.tableName}`);
148
+ } catch (e) {}
149
+ try {
150
+ await db.dropFile(`${this.tableName}.parquet`);
151
+ } catch (e) {}
152
+ this.registered = false;
153
+ }
154
+ }
155
+ };
156
+ /**
157
+ * Helper function to quickly read a Parquet blob and execute a query
158
+ *
159
+ * @param blob - Parquet file as Blob
160
+ * @param sql - SQL query to execute (optional, defaults to SELECT *)
161
+ * @returns Query result
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const blob = await client.getFile('data/sales.parquet');
166
+ * const result = await queryParquet(blob, 'SELECT product, SUM(amount) as total FROM data GROUP BY product');
167
+ * console.log(result);
168
+ * ```
169
+ */
170
+ async function queryParquet(blob, sql) {
171
+ const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
172
+ const reader = new ParquetReader(uniqueId);
173
+ await reader.registerParquet(blob);
174
+ const query = sql || `SELECT * FROM ${uniqueId}`;
175
+ const result = await reader.query(query);
176
+ await reader.close();
177
+ return result;
178
+ }
179
+ /**
180
+ * Helper function to read Parquet file and get all data
181
+ *
182
+ * @param blob - Parquet file as Blob
183
+ * @param limit - Maximum number of rows (optional)
184
+ * @returns Query result
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const blob = await client.getFile('data/sales.parquet');
189
+ * const data = await readParquet(blob, 1000);
190
+ * console.log(`Loaded ${data.rowCount} rows`);
191
+ * ```
192
+ */
193
+ async function readParquet(blob, limit) {
194
+ const reader = new ParquetReader(`temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`);
195
+ await reader.registerParquet(blob);
196
+ const result = await reader.getAll(limit);
197
+ await reader.close();
198
+ return result;
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/client.ts
203
+ /**
204
+ * Oxy API Client for interacting with Oxy data
205
+ */
206
+ var OxyClient = class {
207
+ constructor(config) {
208
+ this.config = config;
209
+ }
210
+ /**
211
+ * Encodes a file path to base64 for use in API URLs
212
+ */
213
+ encodePathBase64(path) {
214
+ if (typeof Buffer !== "undefined") return Buffer.from(path).toString("base64");
215
+ else return btoa(path);
216
+ }
217
+ /**
218
+ * Makes an authenticated HTTP request to the Oxy API
219
+ */
220
+ async request(endpoint, options = {}) {
221
+ const url = `${this.config.baseUrl}${endpoint}`;
222
+ const headers = {
223
+ "Content-Type": "application/json",
224
+ ...options.headers || {}
225
+ };
226
+ if (this.config.apiKey) headers["X-API-Key"] = `${this.config.apiKey}`;
227
+ const controller = new AbortController();
228
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout || 3e4);
229
+ try {
230
+ const response = await fetch(url, {
231
+ ...options,
232
+ headers,
233
+ signal: controller.signal
234
+ });
235
+ clearTimeout(timeoutId);
236
+ if (!response.ok) {
237
+ const errorText = await response.text().catch(() => "Unknown error");
238
+ throw {
239
+ message: `API request failed: ${response.statusText}`,
240
+ status: response.status,
241
+ details: errorText
242
+ };
243
+ }
244
+ if ((typeof options.headers === "object" && options.headers !== null ? options.headers["Accept"] : void 0) === "application/octet-stream") return response.blob();
245
+ return response.json();
246
+ } catch (error) {
247
+ clearTimeout(timeoutId);
248
+ if (error.name === "AbortError") throw new Error(`Request timeout after ${this.config.timeout || 3e4}ms`);
249
+ throw error;
250
+ }
251
+ }
252
+ /**
253
+ * Builds query parameters including optional branch
254
+ */
255
+ buildQueryParams(additionalParams = {}) {
256
+ const params = { ...additionalParams };
257
+ if (this.config.branch) params.branch = this.config.branch;
258
+ const queryString = new URLSearchParams(params).toString();
259
+ return queryString ? `?${queryString}` : "";
260
+ }
261
+ /**
262
+ * Lists all apps in the project
263
+ *
264
+ * @returns Array of app items
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * const apps = await client.listApps();
269
+ * console.log('Available apps:', apps);
270
+ * ```
271
+ */
272
+ async listApps() {
273
+ const query = this.buildQueryParams();
274
+ return this.request(`/${this.config.projectId}/app${query}`);
275
+ }
276
+ /**
277
+ * Gets data for a specific app
278
+ *
279
+ * @param appPath - Relative path to the app file (e.g., 'my-app.app.yml')
280
+ * @returns App data response
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * const data = await client.getAppData('dashboard.app.yml');
285
+ * if (data.error) {
286
+ * console.error('Error:', data.error);
287
+ * } else {
288
+ * console.log('App data:', data.data);
289
+ * }
290
+ * ```
291
+ */
292
+ async getAppData(appPath) {
293
+ const pathb64 = this.encodePathBase64(appPath);
294
+ const query = this.buildQueryParams();
295
+ return this.request(`/${this.config.projectId}/app/${pathb64}${query}`);
296
+ }
297
+ /**
298
+ * Runs an app and returns fresh data (bypasses cache)
299
+ *
300
+ * @param appPath - Relative path to the app file
301
+ * @returns App data response
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * const data = await client.runApp('dashboard.app.yml');
306
+ * console.log('Fresh app data:', data.data);
307
+ * ```
308
+ */
309
+ async runApp(appPath) {
310
+ const pathb64 = this.encodePathBase64(appPath);
311
+ const query = this.buildQueryParams();
312
+ return this.request(`/${this.config.projectId}/app/${pathb64}/run${query}`, { method: "POST" });
313
+ }
314
+ /**
315
+ * Gets display configurations for an app
316
+ *
317
+ * @param appPath - Relative path to the app file
318
+ * @returns Display configurations with potential errors
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * const displays = await client.getDisplays('dashboard.app.yml');
323
+ * displays.displays.forEach(d => {
324
+ * if (d.error) {
325
+ * console.error('Display error:', d.error);
326
+ * } else {
327
+ * console.log('Display:', d.display);
328
+ * }
329
+ * });
330
+ * ```
331
+ */
332
+ async getDisplays(appPath) {
333
+ const pathb64 = this.encodePathBase64(appPath);
334
+ const query = this.buildQueryParams();
335
+ return this.request(`/${this.config.projectId}/app/${pathb64}/displays${query}`);
336
+ }
337
+ /**
338
+ * Gets a file from the app state directory (e.g., generated charts, images)
339
+ *
340
+ * This is useful for retrieving generated assets like charts, images, or other
341
+ * files produced by app workflows and stored in the state directory.
342
+ *
343
+ * @param filePath - Relative path to the file in state directory
344
+ * @returns Blob containing the file data
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * // Get a generated chart image
349
+ * const blob = await client.getFile('charts/sales-chart.png');
350
+ * const imageUrl = URL.createObjectURL(blob);
351
+ *
352
+ * // Use in an img tag
353
+ * document.querySelector('img').src = imageUrl;
354
+ * ```
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * // Download a file
359
+ * const blob = await client.getFile('exports/data.csv');
360
+ * const a = document.createElement('a');
361
+ * a.href = URL.createObjectURL(blob);
362
+ * a.download = 'data.csv';
363
+ * a.click();
364
+ * ```
365
+ */
366
+ async getFile(filePath) {
367
+ const pathb64 = this.encodePathBase64(filePath);
368
+ const query = this.buildQueryParams();
369
+ return this.request(`/${this.config.projectId}/app/file/${pathb64}${query}`, { headers: { "Accept": "application/octet-stream" } });
370
+ }
371
+ /**
372
+ * Gets a file URL for direct browser access
373
+ *
374
+ * This returns a URL that can be used directly in img tags, fetch calls, etc.
375
+ * The URL includes authentication via query parameters.
376
+ *
377
+ * @param filePath - Relative path to the file in state directory
378
+ * @returns Full URL to the file
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const imageUrl = client.getFileUrl('charts/sales-chart.png');
383
+ *
384
+ * // Use directly in img tag (in environments where query-based auth is supported)
385
+ * document.querySelector('img').src = imageUrl;
386
+ * ```
387
+ */
388
+ getFileUrl(filePath) {
389
+ const pathb64 = this.encodePathBase64(filePath);
390
+ const query = this.buildQueryParams();
391
+ return `${this.config.baseUrl}/${this.config.projectId}/app/file/${pathb64}${query}`;
392
+ }
393
+ /**
394
+ * Fetches a parquet file and parses it into table data
395
+ *
396
+ * @param filePath - Relative path to the parquet file
397
+ * @param limit - Maximum number of rows to return (default: 100)
398
+ * @returns TableData with columns and rows
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * const tableData = await client.getTableData('data/sales.parquet', 50);
403
+ * console.log(tableData.columns);
404
+ * console.log(tableData.rows);
405
+ * console.log(`Total rows: ${tableData.total_rows}`);
406
+ * ```
407
+ */
408
+ async getTableData(filePath, limit = 100) {
409
+ const result = await readParquet(await this.getFile(filePath), limit);
410
+ return {
411
+ columns: result.columns,
412
+ rows: result.rows,
413
+ total_rows: result.rowCount
414
+ };
415
+ }
416
+ };
417
+
418
+ //#endregion
419
+ //#region src/config.ts
420
+ /**
421
+ * Creates an Oxy configuration from environment variables
422
+ *
423
+ * Environment variables:
424
+ * - OXY_URL: Base URL of the Oxy API
425
+ * - OXY_API_KEY: API key for authentication
426
+ * - OXY_PROJECT_ID: Project ID (UUID)
427
+ * - OXY_BRANCH: (Optional) Branch name
428
+ *
429
+ * @param overrides - Optional configuration overrides
430
+ * @returns OxyConfig object
431
+ * @throws Error if required environment variables are missing
432
+ */
433
+ function createConfig(overrides) {
434
+ const baseUrl = overrides?.baseUrl || process.env.OXY_URL;
435
+ const apiKey = overrides?.apiKey || process.env.OXY_API_KEY;
436
+ const projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;
437
+ if (!baseUrl) throw new Error("OXY_URL environment variable or baseUrl config is required");
438
+ if (!projectId) throw new Error("OXY_PROJECT_ID environment variable or projectId config is required");
439
+ return {
440
+ baseUrl: baseUrl.replace(/\/$/, ""),
441
+ apiKey,
442
+ projectId,
443
+ branch: overrides?.branch || process.env.OXY_BRANCH,
444
+ timeout: overrides?.timeout || 3e4
445
+ };
446
+ }
447
+
448
+ //#endregion
449
+ export { OxyClient, ParquetReader, createConfig, queryParquet, readParquet };
450
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/parquet.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import * as duckdb from '@duckdb/duckdb-wasm';\n\nlet dbInstance: duckdb.AsyncDuckDB | null = null;\nlet connection: duckdb.AsyncDuckDBConnection | null = null;\n\n/**\n * Initialize DuckDB-WASM instance\n */\nasync function initializeDuckDB(): Promise<duckdb.AsyncDuckDB> {\n if (dbInstance) {\n return dbInstance;\n }\n\n const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();\n\n // Select a bundle based on browser features\n const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);\n\n const worker_url = URL.createObjectURL(\n new Blob([`importScripts(\"${bundle.mainWorker}\");`], {\n type: 'text/javascript',\n })\n );\n\n const worker = new Worker(worker_url);\n const logger = new duckdb.ConsoleLogger();\n\n dbInstance = new duckdb.AsyncDuckDB(logger, worker);\n await dbInstance.instantiate(bundle.mainModule, bundle.pthreadWorker);\n URL.revokeObjectURL(worker_url);\n\n return dbInstance;\n}\n\n/**\n * Get or create a DuckDB connection\n */\nasync function getConnection(): Promise<duckdb.AsyncDuckDBConnection> {\n if (connection) {\n return connection;\n }\n\n const db = await initializeDuckDB();\n connection = await db.connect();\n return connection;\n}\n\n/**\n * Query result interface\n */\nexport interface QueryResult {\n columns: string[];\n rows: any[][];\n rowCount: number;\n}\n\n/**\n * ParquetReader provides methods to read and query Parquet files\n */\nexport class ParquetReader {\n private tableName: string;\n private registered: boolean = false;\n\n constructor(tableName: string = 'data') {\n this.tableName = tableName;\n }\n\n /**\n * Register a Parquet file from a Blob\n *\n * @param blob - Parquet file as Blob\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const reader = new ParquetReader('sales');\n * await reader.registerParquet(blob);\n * ```\n */\n async registerParquet(blob: Blob): Promise<void> {\n const conn = await getConnection();\n const db = await initializeDuckDB();\n\n // Convert blob to Uint8Array\n const arrayBuffer = await blob.arrayBuffer();\n const uint8Array = new Uint8Array(arrayBuffer);\n\n // Register the file with DuckDB\n await db.registerFileBuffer(\n `${this.tableName}.parquet`,\n uint8Array\n );\n\n // Drop table if it exists\n try {\n await conn.query(`DROP TABLE IF EXISTS ${this.tableName}`);\n } catch (e) {\n // Ignore error if table doesn't exist\n }\n\n // Create table from parquet\n await conn.query(\n `CREATE TABLE ${this.tableName} AS SELECT * FROM '${this.tableName}.parquet'`\n );\n\n this.registered = true;\n }\n\n /**\n * Execute a SQL query against the registered Parquet data\n *\n * @param sql - SQL query string\n * @returns Query result with columns and rows\n *\n * @example\n * ```typescript\n * const result = await reader.query('SELECT * FROM sales LIMIT 10');\n * console.log(result.columns);\n * console.log(result.rows);\n * ```\n */\n async query(sql: string): Promise<QueryResult> {\n if (!this.registered) {\n throw new Error('Parquet file not registered. Call registerParquet() first.');\n }\n\n const conn = await getConnection();\n const result = await conn.query(sql);\n\n const columns = result.schema.fields.map((field) => field.name);\n const rows: any[][] = [];\n\n // Convert Arrow table to rows\n for (let i = 0; i < result.numRows; i++) {\n const row: any[] = [];\n for (let j = 0; j < result.numCols; j++) {\n const col = result.getChildAt(j);\n row.push(col?.get(i));\n }\n rows.push(row);\n }\n\n return {\n columns,\n rows,\n rowCount: result.numRows,\n };\n }\n\n /**\n * Get all data from the registered table\n *\n * @param limit - Maximum number of rows to return (default: all)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const allData = await reader.getAll();\n * const first100 = await reader.getAll(100);\n * ```\n */\n async getAll(limit?: number): Promise<QueryResult> {\n const limitClause = limit ? ` LIMIT ${limit}` : '';\n return this.query(`SELECT * FROM ${this.tableName}${limitClause}`);\n }\n\n /**\n * Get table schema information\n *\n * @returns Schema information\n *\n * @example\n * ```typescript\n * const schema = await reader.getSchema();\n * console.log(schema.columns); // ['id', 'name', 'sales']\n * console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]\n * ```\n */\n async getSchema(): Promise<QueryResult> {\n return this.query(`DESCRIBE ${this.tableName}`);\n }\n\n /**\n * Get row count\n *\n * @returns Number of rows in the table\n *\n * @example\n * ```typescript\n * const count = await reader.count();\n * console.log(`Total rows: ${count}`);\n * ```\n */\n async count(): Promise<number> {\n const result = await this.query(`SELECT COUNT(*) as count FROM ${this.tableName}`);\n return result.rows[0][0];\n }\n\n /**\n * Close and cleanup resources\n */\n async close(): Promise<void> {\n if (this.registered) {\n const conn = await getConnection();\n const db = await initializeDuckDB();\n\n // Drop the table\n try {\n await conn.query(`DROP TABLE IF EXISTS ${this.tableName}`);\n } catch (e) {\n // Ignore error\n }\n\n // Drop the registered file buffer\n try {\n await db.dropFile(`${this.tableName}.parquet`);\n } catch (e) {\n // Ignore error if file doesn't exist\n }\n\n this.registered = false;\n }\n }\n}\n\n/**\n * Helper function to quickly read a Parquet blob and execute a query\n *\n * @param blob - Parquet file as Blob\n * @param sql - SQL query to execute (optional, defaults to SELECT *)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const result = await queryParquet(blob, 'SELECT product, SUM(amount) as total FROM data GROUP BY product');\n * console.log(result);\n * ```\n */\nexport async function queryParquet(\n blob: Blob,\n sql?: string\n): Promise<QueryResult> {\n // Generate unique table name to avoid conflicts\n const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n const reader = new ParquetReader(uniqueId);\n\n await reader.registerParquet(blob);\n\n const query = sql || `SELECT * FROM ${uniqueId}`;\n const result = await reader.query(query);\n\n await reader.close();\n return result;\n}\n\n/**\n * Helper function to read Parquet file and get all data\n *\n * @param blob - Parquet file as Blob\n * @param limit - Maximum number of rows (optional)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const data = await readParquet(blob, 1000);\n * console.log(`Loaded ${data.rowCount} rows`);\n * ```\n */\nexport async function readParquet(\n blob: Blob,\n limit?: number\n): Promise<QueryResult> {\n // Generate unique table name to avoid conflicts\n const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n const reader = new ParquetReader(uniqueId);\n\n await reader.registerParquet(blob);\n\n const result = await reader.getAll(limit);\n\n await reader.close();\n return result;\n}\n","import { OxyConfig } from './config';\nimport {\n AppItem,\n AppDataResponse,\n GetDisplaysResponse,\n ApiError,\n TableData,\n} from './types';\nimport { readParquet } from './parquet';\n\n/**\n * Oxy API Client for interacting with Oxy data\n */\nexport class OxyClient {\n private config: OxyConfig;\n\n constructor(config: OxyConfig) {\n this.config = config;\n }\n\n /**\n * Encodes a file path to base64 for use in API URLs\n */\n private encodePathBase64(path: string): string {\n if (typeof Buffer !== 'undefined') {\n // Node.js environment\n return Buffer.from(path).toString('base64');\n } else {\n // Browser environment\n return btoa(path);\n }\n }\n\n /**\n * Makes an authenticated HTTP request to the Oxy API\n */\n private async request<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<T> {\n const url = `${this.config.baseUrl}${endpoint}`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...((options.headers as Record<string, string>) || {}),\n };\n\n // Only add Authorization header if API key is provided (optional for local dev)\n if (this.config.apiKey) {\n headers['X-API-Key'] = `${this.config.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout || 30000);\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n const error: ApiError = {\n message: `API request failed: ${response.statusText}`,\n status: response.status,\n details: errorText,\n };\n throw error;\n }\n\n // Handle binary responses\n const acceptHeader =\n typeof options.headers === 'object' && options.headers !== null\n ? (options.headers as Record<string, string>)['Accept']\n : undefined;\n if (acceptHeader === 'application/octet-stream') {\n return response.blob() as Promise<T>;\n }\n\n return response.json();\n } catch (error: any) {\n clearTimeout(timeoutId);\n\n if (error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.config.timeout || 30000}ms`);\n }\n\n throw error;\n }\n }\n\n /**\n * Builds query parameters including optional branch\n */\n private buildQueryParams(additionalParams: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...additionalParams };\n\n if (this.config.branch) {\n params.branch = this.config.branch;\n }\n\n const searchParams = new URLSearchParams(params);\n const queryString = searchParams.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n /**\n * Lists all apps in the project\n *\n * @returns Array of app items\n *\n * @example\n * ```typescript\n * const apps = await client.listApps();\n * console.log('Available apps:', apps);\n * ```\n */\n async listApps(): Promise<AppItem[]> {\n const query = this.buildQueryParams();\n return this.request<AppItem[]>(`/${this.config.projectId}/app${query}`);\n }\n\n /**\n * Gets data for a specific app\n *\n * @param appPath - Relative path to the app file (e.g., 'my-app.app.yml')\n * @returns App data response\n *\n * @example\n * ```typescript\n * const data = await client.getAppData('dashboard.app.yml');\n * if (data.error) {\n * console.error('Error:', data.error);\n * } else {\n * console.log('App data:', data.data);\n * }\n * ```\n */\n async getAppData(appPath: string): Promise<AppDataResponse> {\n const pathb64 = this.encodePathBase64(appPath);\n const query = this.buildQueryParams();\n return this.request<AppDataResponse>(\n `/${this.config.projectId}/app/${pathb64}${query}`\n );\n }\n\n /**\n * Runs an app and returns fresh data (bypasses cache)\n *\n * @param appPath - Relative path to the app file\n * @returns App data response\n *\n * @example\n * ```typescript\n * const data = await client.runApp('dashboard.app.yml');\n * console.log('Fresh app data:', data.data);\n * ```\n */\n async runApp(appPath: string): Promise<AppDataResponse> {\n const pathb64 = this.encodePathBase64(appPath);\n const query = this.buildQueryParams();\n return this.request<AppDataResponse>(\n `/${this.config.projectId}/app/${pathb64}/run${query}`,\n { method: 'POST' }\n );\n }\n\n /**\n * Gets display configurations for an app\n *\n * @param appPath - Relative path to the app file\n * @returns Display configurations with potential errors\n *\n * @example\n * ```typescript\n * const displays = await client.getDisplays('dashboard.app.yml');\n * displays.displays.forEach(d => {\n * if (d.error) {\n * console.error('Display error:', d.error);\n * } else {\n * console.log('Display:', d.display);\n * }\n * });\n * ```\n */\n async getDisplays(appPath: string): Promise<GetDisplaysResponse> {\n const pathb64 = this.encodePathBase64(appPath);\n const query = this.buildQueryParams();\n return this.request<GetDisplaysResponse>(\n `/${this.config.projectId}/app/${pathb64}/displays${query}`\n );\n }\n\n /**\n * Gets a file from the app state directory (e.g., generated charts, images)\n *\n * This is useful for retrieving generated assets like charts, images, or other\n * files produced by app workflows and stored in the state directory.\n *\n * @param filePath - Relative path to the file in state directory\n * @returns Blob containing the file data\n *\n * @example\n * ```typescript\n * // Get a generated chart image\n * const blob = await client.getFile('charts/sales-chart.png');\n * const imageUrl = URL.createObjectURL(blob);\n *\n * // Use in an img tag\n * document.querySelector('img').src = imageUrl;\n * ```\n *\n * @example\n * ```typescript\n * // Download a file\n * const blob = await client.getFile('exports/data.csv');\n * const a = document.createElement('a');\n * a.href = URL.createObjectURL(blob);\n * a.download = 'data.csv';\n * a.click();\n * ```\n */\n async getFile(filePath: string): Promise<Blob> {\n const pathb64 = this.encodePathBase64(filePath);\n const query = this.buildQueryParams();\n return this.request<Blob>(\n `/${this.config.projectId}/app/file/${pathb64}${query}`,\n {\n headers: {\n 'Accept': 'application/octet-stream',\n },\n }\n );\n }\n\n /**\n * Gets a file URL for direct browser access\n *\n * This returns a URL that can be used directly in img tags, fetch calls, etc.\n * The URL includes authentication via query parameters.\n *\n * @param filePath - Relative path to the file in state directory\n * @returns Full URL to the file\n *\n * @example\n * ```typescript\n * const imageUrl = client.getFileUrl('charts/sales-chart.png');\n *\n * // Use directly in img tag (in environments where query-based auth is supported)\n * document.querySelector('img').src = imageUrl;\n * ```\n */\n getFileUrl(filePath: string): string {\n const pathb64 = this.encodePathBase64(filePath);\n const query = this.buildQueryParams();\n return `${this.config.baseUrl}/${this.config.projectId}/app/file/${pathb64}${query}`;\n }\n\n /**\n * Fetches a parquet file and parses it into table data\n *\n * @param filePath - Relative path to the parquet file\n * @param limit - Maximum number of rows to return (default: 100)\n * @returns TableData with columns and rows\n *\n * @example\n * ```typescript\n * const tableData = await client.getTableData('data/sales.parquet', 50);\n * console.log(tableData.columns);\n * console.log(tableData.rows);\n * console.log(`Total rows: ${tableData.total_rows}`);\n * ```\n */\n async getTableData(filePath: string, limit: number = 100): Promise<TableData> {\n const blob = await this.getFile(filePath);\n const result = await readParquet(blob, limit);\n\n return {\n columns: result.columns,\n rows: result.rows,\n total_rows: result.rowCount,\n };\n }\n}\n","/**\n * Configuration for the Oxy SDK\n */\nexport interface OxyConfig {\n /**\n * Base URL of the Oxy API (e.g., 'https://api.oxy.tech' or 'http://localhost:3000')\n */\n baseUrl: string;\n\n /**\n * API key for authentication (optional for local development)\n */\n apiKey?: string;\n\n /**\n * Project ID (UUID)\n */\n projectId: string;\n\n /**\n * Optional branch name (defaults to current branch if not specified)\n */\n branch?: string;\n\n /**\n * Request timeout in milliseconds (default: 30000)\n */\n timeout?: number;\n}\n\n/**\n * Creates an Oxy configuration from environment variables\n *\n * Environment variables:\n * - OXY_URL: Base URL of the Oxy API\n * - OXY_API_KEY: API key for authentication\n * - OXY_PROJECT_ID: Project ID (UUID)\n * - OXY_BRANCH: (Optional) Branch name\n *\n * @param overrides - Optional configuration overrides\n * @returns OxyConfig object\n * @throws Error if required environment variables are missing\n */\nexport function createConfig(overrides?: Partial<OxyConfig>): OxyConfig {\n const baseUrl = overrides?.baseUrl || process.env.OXY_URL;\n const apiKey = overrides?.apiKey || process.env.OXY_API_KEY;\n const projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;\n\n if (!baseUrl) {\n throw new Error('OXY_URL environment variable or baseUrl config is required');\n }\n\n if (!projectId) {\n throw new Error('OXY_PROJECT_ID environment variable or projectId config is required');\n }\n\n return {\n baseUrl: baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n apiKey,\n projectId,\n branch: overrides?.branch || process.env.OXY_BRANCH,\n timeout: overrides?.timeout || 30000,\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@oxy-hq/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for interacting with Oxy data platform",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "require": "./dist/index.js",
13
+ "import": "./dist/index.mjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsdown",
23
+ "build:watch": "tsdown --watch",
24
+ "dev": "tsdown --watch",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "eslint src --ext .ts",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "prepublishOnly": "npm run build",
30
+ "publish:beta": "npm publish --tag beta",
31
+ "publish:latest": "npm publish --tag latest",
32
+ "demo:react": "cd examples/react-vite-demo && pnpm install --ignore-workspace && pnpm dev",
33
+ "demo:setup": "cd examples/react-vite-demo && pnpm install --ignore-workspace",
34
+ "example:basic": "tsx examples/basic-usage.ts",
35
+ "example:parquet": "tsx examples/parquet-usage.ts",
36
+ "example:v0": "tsx examples/v0-integration.ts"
37
+ },
38
+ "keywords": [
39
+ "oxy",
40
+ "data",
41
+ "analytics",
42
+ "sdk",
43
+ "parquet",
44
+ "duckdb"
45
+ ],
46
+ "author": "Oxy Team",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/dataframehq/oxy-internal.git",
51
+ "directory": "sdk/typescript"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/dataframehq/oxy-internal/issues"
55
+ },
56
+ "homepage": "https://oxy.tech",
57
+ "peerDependencies": {
58
+ "@duckdb/duckdb-wasm": "^1.29.0"
59
+ },
60
+ "dependencies": {},
61
+ "devDependencies": {
62
+ "@duckdb/duckdb-wasm": "^1.29.0",
63
+ "@types/node": "^20.11.0",
64
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
65
+ "@typescript-eslint/parser": "^6.19.0",
66
+ "eslint": "^8.56.0",
67
+ "tsdown": "^0.19.0-beta.2",
68
+ "typescript": "^5.3.3",
69
+ "vitest": "^1.2.0"
70
+ },
71
+ "engines": {
72
+ "node": ">=18.0.0"
73
+ }
74
+ }