@oxy-hq/sdk 0.1.0 → 0.1.2

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 CHANGED
@@ -28,7 +28,47 @@ npm install @duckdb/duckdb-wasm
28
28
 
29
29
  ## Quick Start
30
30
 
31
- ### Configuration
31
+ ### Option 1: Client-Side Usage (Iframe with PostMessage)
32
+
33
+ Perfect for embedding dashboards in iframes (e.g., v0.dev, sandboxed environments):
34
+
35
+ ```typescript
36
+ import { OxyClient } from '@oxy/sdk';
37
+
38
+ // SDK automatically requests API key from parent window
39
+ const client = await OxyClient.create({
40
+ parentOrigin: 'https://app.example.com', // Parent window origin
41
+ projectId: 'your-project-uuid',
42
+ baseUrl: 'https://api.oxy.tech'
43
+ });
44
+
45
+ // Use the client
46
+ const apps = await client.listApps();
47
+ console.log('Available apps:', apps);
48
+ ```
49
+
50
+ **Parent window setup** (one-time):
51
+ ```typescript
52
+ // In parent window that hosts the iframe
53
+ window.addEventListener('message', (event) => {
54
+ if (event.data.type !== 'OXY_AUTH_REQUEST') return;
55
+
56
+ // Validate iframe origin
57
+ if (event.origin !== 'https://your-iframe-app.com') return;
58
+
59
+ // Send user's API key to iframe
60
+ event.source.postMessage({
61
+ type: 'OXY_AUTH_RESPONSE',
62
+ version: '1.0',
63
+ requestId: event.data.requestId,
64
+ apiKey: getUserApiKey(), // Your auth logic
65
+ projectId: 'your-project-uuid',
66
+ baseUrl: 'https://api.oxy.tech'
67
+ }, event.origin);
68
+ });
69
+ ```
70
+
71
+ ### Option 2: Traditional Usage (Environment Variables)
32
72
 
33
73
  Set up environment variables:
34
74
 
@@ -39,7 +79,7 @@ export OXY_PROJECT_ID="your-project-uuid"
39
79
  export OXY_BRANCH="main" # optional
40
80
  ```
41
81
 
42
- ### Basic Usage
82
+ Basic usage:
43
83
 
44
84
  ```typescript
45
85
  import { OxyClient, createConfig } from '@oxy/sdk';
package/dist/index.cjs CHANGED
@@ -26,9 +26,107 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  }) : target, mod));
27
27
 
28
28
  //#endregion
29
+ const require_postMessage = require('./postMessage-CtL0W-Zt.cjs');
29
30
  let _duckdb_duckdb_wasm = require("@duckdb/duckdb-wasm");
30
31
  _duckdb_duckdb_wasm = __toESM(_duckdb_duckdb_wasm);
31
32
 
33
+ //#region src/config.ts
34
+ /**
35
+ * Creates an Oxy configuration from environment variables
36
+ *
37
+ * Environment variables:
38
+ * - OXY_URL: Base URL of the Oxy API
39
+ * - OXY_API_KEY: API key for authentication
40
+ * - OXY_PROJECT_ID: Project ID (UUID)
41
+ * - OXY_BRANCH: (Optional) Branch name
42
+ *
43
+ * @param overrides - Optional configuration overrides
44
+ * @returns OxyConfig object
45
+ * @throws Error if required environment variables are missing
46
+ */
47
+ function createConfig(overrides) {
48
+ const baseUrl = overrides?.baseUrl || process.env.OXY_URL;
49
+ const apiKey = overrides?.apiKey || process.env.OXY_API_KEY;
50
+ const projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;
51
+ if (!baseUrl) throw new Error("OXY_URL environment variable or baseUrl config is required");
52
+ if (!projectId) throw new Error("OXY_PROJECT_ID environment variable or projectId config is required");
53
+ return {
54
+ baseUrl: baseUrl.replace(/\/$/, ""),
55
+ apiKey,
56
+ projectId,
57
+ branch: overrides?.branch || process.env.OXY_BRANCH,
58
+ timeout: overrides?.timeout || 3e4,
59
+ parentOrigin: overrides?.parentOrigin,
60
+ disableAutoAuth: overrides?.disableAutoAuth
61
+ };
62
+ }
63
+ /**
64
+ * Creates an Oxy configuration asynchronously with support for postMessage authentication
65
+ *
66
+ * This is the recommended method for iframe scenarios where authentication
67
+ * needs to be obtained from the parent window via postMessage.
68
+ *
69
+ * When running in an iframe without an API key, this function will:
70
+ * 1. Detect the iframe context
71
+ * 2. Send an authentication request to the parent window
72
+ * 3. Wait for the parent to respond with credentials
73
+ * 4. Return the configured client
74
+ *
75
+ * Environment variables (fallback):
76
+ * - OXY_URL: Base URL of the Oxy API
77
+ * - OXY_API_KEY: API key for authentication
78
+ * - OXY_PROJECT_ID: Project ID (UUID)
79
+ * - OXY_BRANCH: (Optional) Branch name
80
+ *
81
+ * @param overrides - Optional configuration overrides
82
+ * @returns Promise resolving to OxyConfig object
83
+ * @throws Error if required configuration is missing
84
+ * @throws PostMessageAuthTimeoutError if parent doesn't respond
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // Automatic iframe detection and authentication
89
+ * const config = await createConfigAsync({
90
+ * parentOrigin: 'https://app.example.com',
91
+ * projectId: 'my-project-id',
92
+ * baseUrl: 'https://api.oxy.tech'
93
+ * });
94
+ * ```
95
+ */
96
+ async function createConfigAsync(overrides) {
97
+ const { isInIframe: isInIframe$1, requestAuthFromParent: requestAuthFromParent$1 } = await Promise.resolve().then(() => require("./postMessage-DacyukaO.cjs"));
98
+ let baseUrl = overrides?.baseUrl || process.env.OXY_URL;
99
+ let apiKey = overrides?.apiKey || process.env.OXY_API_KEY;
100
+ let projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;
101
+ const disableAutoAuth = overrides?.disableAutoAuth ?? false;
102
+ const parentOrigin = overrides?.parentOrigin;
103
+ if (!disableAutoAuth && isInIframe$1() && !apiKey) if (!parentOrigin) console.warn("[Oxy SDK] Running in iframe without API key and no parentOrigin specified. PostMessage authentication will be skipped. Provide parentOrigin config to enable automatic authentication.");
104
+ else try {
105
+ const authResult = await requestAuthFromParent$1({
106
+ parentOrigin,
107
+ timeout: overrides?.timeout || 5e3
108
+ });
109
+ apiKey = authResult.apiKey;
110
+ if (authResult.projectId) projectId = authResult.projectId;
111
+ if (authResult.baseUrl) baseUrl = authResult.baseUrl;
112
+ console.log("[Oxy SDK] Successfully authenticated via postMessage");
113
+ } catch (error) {
114
+ console.error("[Oxy SDK] Failed to authenticate via postMessage:", error.message);
115
+ }
116
+ if (!baseUrl) throw new Error("OXY_URL environment variable or baseUrl config is required");
117
+ if (!projectId) throw new Error("OXY_PROJECT_ID environment variable or projectId config is required");
118
+ return {
119
+ baseUrl: baseUrl.replace(/\/$/, ""),
120
+ apiKey,
121
+ projectId,
122
+ branch: overrides?.branch || process.env.OXY_BRANCH,
123
+ timeout: overrides?.timeout || 3e4,
124
+ parentOrigin,
125
+ disableAutoAuth
126
+ };
127
+ }
128
+
129
+ //#endregion
32
130
  //#region src/parquet.ts
33
131
  let dbInstance = null;
34
132
  let connection = null;
@@ -231,11 +329,38 @@ async function readParquet(blob, limit) {
231
329
  /**
232
330
  * Oxy API Client for interacting with Oxy data
233
331
  */
234
- var OxyClient = class {
332
+ var OxyClient = class OxyClient {
235
333
  constructor(config) {
236
334
  this.config = config;
237
335
  }
238
336
  /**
337
+ * Creates an OxyClient instance asynchronously with support for postMessage authentication
338
+ *
339
+ * This is the recommended method when using the SDK in an iframe that needs to
340
+ * obtain authentication from the parent window via postMessage.
341
+ *
342
+ * @param config - Optional configuration overrides
343
+ * @returns Promise resolving to OxyClient instance
344
+ * @throws Error if required configuration is missing
345
+ * @throws PostMessageAuthTimeoutError if parent doesn't respond
346
+ *
347
+ * @example
348
+ * ```typescript
349
+ * // In an iframe - automatic postMessage auth
350
+ * const client = await OxyClient.create({
351
+ * parentOrigin: 'https://app.example.com',
352
+ * projectId: 'my-project-id',
353
+ * baseUrl: 'https://api.oxy.tech'
354
+ * });
355
+ *
356
+ * // Use the client normally
357
+ * const apps = await client.listApps();
358
+ * ```
359
+ */
360
+ static async create(config) {
361
+ return new OxyClient(await createConfigAsync(config));
362
+ }
363
+ /**
239
364
  * Encodes a file path to base64 for use in API URLs
240
365
  */
241
366
  encodePathBase64(path) {
@@ -443,40 +568,17 @@ var OxyClient = class {
443
568
  }
444
569
  };
445
570
 
446
- //#endregion
447
- //#region src/config.ts
448
- /**
449
- * Creates an Oxy configuration from environment variables
450
- *
451
- * Environment variables:
452
- * - OXY_URL: Base URL of the Oxy API
453
- * - OXY_API_KEY: API key for authentication
454
- * - OXY_PROJECT_ID: Project ID (UUID)
455
- * - OXY_BRANCH: (Optional) Branch name
456
- *
457
- * @param overrides - Optional configuration overrides
458
- * @returns OxyConfig object
459
- * @throws Error if required environment variables are missing
460
- */
461
- function createConfig(overrides) {
462
- const baseUrl = overrides?.baseUrl || process.env.OXY_URL;
463
- const apiKey = overrides?.apiKey || process.env.OXY_API_KEY;
464
- const projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;
465
- if (!baseUrl) throw new Error("OXY_URL environment variable or baseUrl config is required");
466
- if (!projectId) throw new Error("OXY_PROJECT_ID environment variable or projectId config is required");
467
- return {
468
- baseUrl: baseUrl.replace(/\/$/, ""),
469
- apiKey,
470
- projectId,
471
- branch: overrides?.branch || process.env.OXY_BRANCH,
472
- timeout: overrides?.timeout || 3e4
473
- };
474
- }
475
-
476
571
  //#endregion
477
572
  exports.OxyClient = OxyClient;
478
573
  exports.ParquetReader = ParquetReader;
574
+ exports.PostMessageAuthInvalidOriginError = require_postMessage.PostMessageAuthInvalidOriginError;
575
+ exports.PostMessageAuthInvalidResponseError = require_postMessage.PostMessageAuthInvalidResponseError;
576
+ exports.PostMessageAuthNotInIframeError = require_postMessage.PostMessageAuthNotInIframeError;
577
+ exports.PostMessageAuthTimeoutError = require_postMessage.PostMessageAuthTimeoutError;
479
578
  exports.createConfig = createConfig;
579
+ exports.createConfigAsync = createConfigAsync;
580
+ exports.isInIframe = require_postMessage.isInIframe;
480
581
  exports.queryParquet = queryParquet;
481
582
  exports.readParquet = readParquet;
583
+ exports.requestAuthFromParent = require_postMessage.requestAuthFromParent;
482
584
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["duckdb"],"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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"}
1
+ {"version":3,"file":"index.cjs","names":["isInIframe","requestAuthFromParent","duckdb"],"sources":["../src/config.ts","../src/parquet.ts","../src/client.ts"],"sourcesContent":["/**\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 * Parent window origin for postMessage authentication (iframe scenarios)\n * Required when using postMessage auth for security.\n * Example: 'https://app.example.com'\n * Use '*' only in development!\n */\n parentOrigin?: string;\n\n /**\n * Disable automatic postMessage authentication even if in iframe\n * Set to true if you want to provide API key manually in iframe context\n */\n disableAutoAuth?: boolean;\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 parentOrigin: overrides?.parentOrigin,\n disableAutoAuth: overrides?.disableAutoAuth,\n };\n}\n\n/**\n * Creates an Oxy configuration asynchronously with support for postMessage authentication\n *\n * This is the recommended method for iframe scenarios where authentication\n * needs to be obtained from the parent window via postMessage.\n *\n * When running in an iframe without an API key, this function will:\n * 1. Detect the iframe context\n * 2. Send an authentication request to the parent window\n * 3. Wait for the parent to respond with credentials\n * 4. Return the configured client\n *\n * Environment variables (fallback):\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 Promise resolving to OxyConfig object\n * @throws Error if required configuration is missing\n * @throws PostMessageAuthTimeoutError if parent doesn't respond\n *\n * @example\n * ```typescript\n * // Automatic iframe detection and authentication\n * const config = await createConfigAsync({\n * parentOrigin: 'https://app.example.com',\n * projectId: 'my-project-id',\n * baseUrl: 'https://api.oxy.tech'\n * });\n * ```\n */\nexport async function createConfigAsync(\n overrides?: Partial<OxyConfig>\n): Promise<OxyConfig> {\n // Import postMessage utilities (dynamic to avoid circular deps)\n const { isInIframe, requestAuthFromParent } = await import('./auth/postMessage');\n\n // Start with environment variables and overrides\n let baseUrl = overrides?.baseUrl || process.env.OXY_URL;\n let apiKey = overrides?.apiKey || process.env.OXY_API_KEY;\n let projectId = overrides?.projectId || process.env.OXY_PROJECT_ID;\n\n const disableAutoAuth = overrides?.disableAutoAuth ?? false;\n const parentOrigin = overrides?.parentOrigin;\n\n // Automatic iframe detection and authentication\n if (!disableAutoAuth && isInIframe() && !apiKey) {\n if (!parentOrigin) {\n console.warn(\n '[Oxy SDK] Running in iframe without API key and no parentOrigin specified. ' +\n 'PostMessage authentication will be skipped. ' +\n 'Provide parentOrigin config to enable automatic authentication.'\n );\n } else {\n try {\n const authResult = await requestAuthFromParent({\n parentOrigin,\n timeout: overrides?.timeout || 5000,\n });\n\n apiKey = authResult.apiKey;\n if (authResult.projectId) {\n projectId = authResult.projectId;\n }\n if (authResult.baseUrl) {\n baseUrl = authResult.baseUrl;\n }\n\n console.log('[Oxy SDK] Successfully authenticated via postMessage');\n } catch (error) {\n console.error(\n '[Oxy SDK] Failed to authenticate via postMessage:',\n (error as Error).message\n );\n // Fall through to use environment variables if available\n }\n }\n }\n\n // Validation\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 parentOrigin,\n disableAutoAuth,\n };\n}\n","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, createConfigAsync } 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 * Creates an OxyClient instance asynchronously with support for postMessage authentication\n *\n * This is the recommended method when using the SDK in an iframe that needs to\n * obtain authentication from the parent window via postMessage.\n *\n * @param config - Optional configuration overrides\n * @returns Promise resolving to OxyClient instance\n * @throws Error if required configuration is missing\n * @throws PostMessageAuthTimeoutError if parent doesn't respond\n *\n * @example\n * ```typescript\n * // In an iframe - automatic postMessage auth\n * const client = await OxyClient.create({\n * parentOrigin: 'https://app.example.com',\n * projectId: 'my-project-id',\n * baseUrl: 'https://api.oxy.tech'\n * });\n *\n * // Use the client normally\n * const apps = await client.listApps();\n * ```\n */\n static async create(config?: Partial<OxyConfig>): Promise<OxyClient> {\n const resolvedConfig = await createConfigAsync(config);\n return new OxyClient(resolvedConfig);\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"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -24,6 +24,18 @@ interface OxyConfig {
24
24
  * Request timeout in milliseconds (default: 30000)
25
25
  */
26
26
  timeout?: number;
27
+ /**
28
+ * Parent window origin for postMessage authentication (iframe scenarios)
29
+ * Required when using postMessage auth for security.
30
+ * Example: 'https://app.example.com'
31
+ * Use '*' only in development!
32
+ */
33
+ parentOrigin?: string;
34
+ /**
35
+ * Disable automatic postMessage authentication even if in iframe
36
+ * Set to true if you want to provide API key manually in iframe context
37
+ */
38
+ disableAutoAuth?: boolean;
27
39
  }
28
40
  /**
29
41
  * Creates an Oxy configuration from environment variables
@@ -39,6 +51,40 @@ interface OxyConfig {
39
51
  * @throws Error if required environment variables are missing
40
52
  */
41
53
  declare function createConfig(overrides?: Partial<OxyConfig>): OxyConfig;
54
+ /**
55
+ * Creates an Oxy configuration asynchronously with support for postMessage authentication
56
+ *
57
+ * This is the recommended method for iframe scenarios where authentication
58
+ * needs to be obtained from the parent window via postMessage.
59
+ *
60
+ * When running in an iframe without an API key, this function will:
61
+ * 1. Detect the iframe context
62
+ * 2. Send an authentication request to the parent window
63
+ * 3. Wait for the parent to respond with credentials
64
+ * 4. Return the configured client
65
+ *
66
+ * Environment variables (fallback):
67
+ * - OXY_URL: Base URL of the Oxy API
68
+ * - OXY_API_KEY: API key for authentication
69
+ * - OXY_PROJECT_ID: Project ID (UUID)
70
+ * - OXY_BRANCH: (Optional) Branch name
71
+ *
72
+ * @param overrides - Optional configuration overrides
73
+ * @returns Promise resolving to OxyConfig object
74
+ * @throws Error if required configuration is missing
75
+ * @throws PostMessageAuthTimeoutError if parent doesn't respond
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * // Automatic iframe detection and authentication
80
+ * const config = await createConfigAsync({
81
+ * parentOrigin: 'https://app.example.com',
82
+ * projectId: 'my-project-id',
83
+ * baseUrl: 'https://api.oxy.tech'
84
+ * });
85
+ * ```
86
+ */
87
+ declare function createConfigAsync(overrides?: Partial<OxyConfig>): Promise<OxyConfig>;
42
88
  //#endregion
43
89
  //#region src/types.d.ts
44
90
  /**
@@ -99,6 +145,76 @@ interface QueryResult$1 {
99
145
  rows: any[][];
100
146
  rowCount: number;
101
147
  }
148
+ /**
149
+ * PostMessage authentication protocol types
150
+ */
151
+ /**
152
+ * Request message sent from iframe to parent window
153
+ */
154
+ interface OxyAuthRequestMessage {
155
+ type: 'OXY_AUTH_REQUEST';
156
+ version: '1.0';
157
+ timestamp: number;
158
+ requestId: string;
159
+ }
160
+ /**
161
+ * Response message sent from parent window to iframe
162
+ */
163
+ interface OxyAuthResponseMessage {
164
+ type: 'OXY_AUTH_RESPONSE';
165
+ version: '1.0';
166
+ requestId: string;
167
+ apiKey: string;
168
+ projectId?: string;
169
+ baseUrl?: string;
170
+ }
171
+ /**
172
+ * Options for postMessage authentication
173
+ */
174
+ interface PostMessageAuthOptions {
175
+ /** Required parent window origin for security (e.g., 'https://app.example.com'). Use '*' only in development! */
176
+ parentOrigin?: string;
177
+ /** Timeout in milliseconds (default: 5000) */
178
+ timeout?: number;
179
+ /** Number of retry attempts (default: 0) */
180
+ retries?: number;
181
+ }
182
+ /**
183
+ * Result from successful postMessage authentication
184
+ */
185
+ interface PostMessageAuthResult {
186
+ apiKey: string;
187
+ projectId?: string;
188
+ baseUrl?: string;
189
+ source: 'postmessage';
190
+ }
191
+ /**
192
+ * Custom error classes for postMessage authentication
193
+ */
194
+ /**
195
+ * Error thrown when postMessage authentication times out
196
+ */
197
+ declare class PostMessageAuthTimeoutError extends Error {
198
+ constructor(timeout: number);
199
+ }
200
+ /**
201
+ * Error thrown when authentication response comes from unauthorized origin
202
+ */
203
+ declare class PostMessageAuthInvalidOriginError extends Error {
204
+ constructor(expected: string, actual: string);
205
+ }
206
+ /**
207
+ * Error thrown when postMessage authentication is attempted outside iframe context
208
+ */
209
+ declare class PostMessageAuthNotInIframeError extends Error {
210
+ constructor();
211
+ }
212
+ /**
213
+ * Error thrown when authentication response is malformed or invalid
214
+ */
215
+ declare class PostMessageAuthInvalidResponseError extends Error {
216
+ constructor(reason: string);
217
+ }
102
218
  //#endregion
103
219
  //#region src/client.d.ts
104
220
  /**
@@ -107,6 +223,31 @@ interface QueryResult$1 {
107
223
  declare class OxyClient {
108
224
  private config;
109
225
  constructor(config: OxyConfig);
226
+ /**
227
+ * Creates an OxyClient instance asynchronously with support for postMessage authentication
228
+ *
229
+ * This is the recommended method when using the SDK in an iframe that needs to
230
+ * obtain authentication from the parent window via postMessage.
231
+ *
232
+ * @param config - Optional configuration overrides
233
+ * @returns Promise resolving to OxyClient instance
234
+ * @throws Error if required configuration is missing
235
+ * @throws PostMessageAuthTimeoutError if parent doesn't respond
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * // In an iframe - automatic postMessage auth
240
+ * const client = await OxyClient.create({
241
+ * parentOrigin: 'https://app.example.com',
242
+ * projectId: 'my-project-id',
243
+ * baseUrl: 'https://api.oxy.tech'
244
+ * });
245
+ *
246
+ * // Use the client normally
247
+ * const apps = await client.listApps();
248
+ * ```
249
+ */
250
+ static create(config?: Partial<OxyConfig>): Promise<OxyClient>;
110
251
  /**
111
252
  * Encodes a file path to base64 for use in API URLs
112
253
  */
@@ -363,5 +504,36 @@ declare function queryParquet(blob: Blob, sql?: string): Promise<QueryResult>;
363
504
  */
364
505
  declare function readParquet(blob: Blob, limit?: number): Promise<QueryResult>;
365
506
  //#endregion
366
- export { type AppDataResponse, type AppItem, type DataContainer, type DisplayData, type DisplayWithError, type FileReference, OxyClient, type OxyConfig, type QueryResult as ParquetQueryResult, ParquetReader, type QueryResult$1 as QueryResult, type TableData, createConfig, queryParquet, readParquet };
507
+ //#region src/auth/postMessage.d.ts
508
+ /**
509
+ * Check if the current context is running inside an iframe
510
+ *
511
+ * @returns true if running in an iframe, false otherwise (including Node.js)
512
+ */
513
+ declare function isInIframe(): boolean;
514
+ /**
515
+ * Request authentication from parent window via postMessage
516
+ *
517
+ * This is the main entry point for iframe-based authentication.
518
+ * It sends a request to the parent window and waits for a response.
519
+ *
520
+ * @param options - Configuration options for the auth request
521
+ * @returns Promise that resolves with authentication credentials
522
+ * @throws {PostMessageAuthNotInIframeError} If not in an iframe
523
+ * @throws {PostMessageAuthTimeoutError} If parent doesn't respond in time
524
+ * @throws {PostMessageAuthInvalidOriginError} If response from wrong origin
525
+ * @throws {PostMessageAuthInvalidResponseError} If response is malformed
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * const auth = await requestAuthFromParent({
530
+ * parentOrigin: 'https://app.example.com',
531
+ * timeout: 5000
532
+ * });
533
+ * console.log('Received API key:', auth.apiKey);
534
+ * ```
535
+ */
536
+ declare function requestAuthFromParent(options?: PostMessageAuthOptions): Promise<PostMessageAuthResult>;
537
+ //#endregion
538
+ export { type AppDataResponse, type AppItem, type DataContainer, type DisplayData, type DisplayWithError, type FileReference, type OxyAuthRequestMessage, type OxyAuthResponseMessage, OxyClient, type OxyConfig, type QueryResult as ParquetQueryResult, ParquetReader, PostMessageAuthInvalidOriginError, PostMessageAuthInvalidResponseError, PostMessageAuthNotInIframeError, type PostMessageAuthOptions, type PostMessageAuthResult, PostMessageAuthTimeoutError, type QueryResult$1 as QueryResult, type TableData, createConfig, createConfigAsync, isInIframe, queryParquet, readParquet, requestAuthFromParent };
367
539
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/config.ts","../src/types.ts","../src/client.ts","../src/parquet.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/config.ts","../src/types.ts","../src/client.ts","../src/parquet.ts","../src/auth/postMessage.ts"],"sourcesContent":[],"mappings":"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"}