@oxy-hq/sdk 0.3.0 → 2.0.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/README.md CHANGED
@@ -1,474 +1,103 @@
1
- # Oxy TypeScript SDK
1
+ # @oxy-hq/sdk
2
2
 
3
- Official TypeScript/JavaScript SDK for interacting with the Oxy data platform.
3
+ React SDK for building **customer-app bundles** on the [Oxy](https://oxy.tech)
4
+ platform. A bundle is a normal Vite + React app that reads from its linked oxy
5
+ project — raw SQL, the semantic layer, agents, and procedures — through a
6
+ small set of hooks, plus a couple of drop-in components.
4
7
 
5
- ## Features
8
+ > **v2 is a complete rewrite.** The v1 stack (`OxyClient` / `OxySDK`, the
9
+ > Parquet/DuckDB-WASM reader, postMessage auth) is gone. Bundles now talk to
10
+ > `/api/projects/:id/*` exclusively. See `CHANGELOG.md`.
6
11
 
7
- - 🚀 **Simple API** - Easy-to-use client for fetching app data
8
- - 📊 **Parquet Support** - Read and query Parquet files using DuckDB-WASM
9
- - 🔒 **Type-Safe** - Full TypeScript support with comprehensive type definitions
10
- - 🌐 **Universal** - Works in both Node.js and browser environments
11
- - ⚡ **Fast** - Optimized for performance with efficient data handling
12
-
13
- ## Installation
14
-
15
- ```bash
16
- npm install @oxy/sdk
17
- # or
18
- yarn add @oxy/sdk
19
- # or
20
- pnpm add @oxy/sdk
21
- ```
22
-
23
- For Parquet file support, also install DuckDB-WASM:
24
-
25
- ```bash
26
- npm install @duckdb/duckdb-wasm
27
- ```
28
-
29
- ## Quick Start
30
-
31
- ### Basic Usage
32
-
33
- ```typescript
34
- import { OxySDK } from "@oxy/sdk";
35
-
36
- // Create SDK instance
37
- const sdk = new OxySDK({
38
- apiKey: "your-api-key",
39
- projectId: "your-project-id",
40
- baseUrl: "https://api.oxy.tech",
41
- });
42
-
43
- // Load parquet files and query them
44
- await sdk.loadFile("data/sales.parquet", "sales");
45
- await sdk.loadFile("data/customers.parquet", "customers");
46
-
47
- // Query with SQL - supports joins across multiple tables
48
- const result = await sdk.query(`
49
- SELECT s.product, s.amount, c.name as customer_name
50
- FROM sales s
51
- JOIN customers c ON s.customer_id = c.id
52
- WHERE s.amount > 1000
53
- ORDER BY s.amount DESC
54
- `);
55
-
56
- console.log(result.rows);
57
- await sdk.close();
58
- ```
59
-
60
- ### Load App Data Automatically
61
-
62
- ```typescript
63
- import { OxySDK, createConfig } from "@oxy/sdk";
64
-
65
- const sdk = new OxySDK(createConfig());
66
-
67
- // Loads all data from the app and registers tables
68
- await sdk.loadAppData("dashboard.app.yml");
69
-
70
- // Query the loaded tables
71
- const result = await sdk.query("SELECT * FROM my_table LIMIT 10");
72
- ```
73
-
74
- ### Iframe Usage (PostMessage Authentication)
75
-
76
- For embedding in iframes (e.g., v0.dev, sandboxed environments):
77
-
78
- ```typescript
79
- import { OxySDK } from "@oxy/sdk";
80
-
81
- // SDK automatically requests API key from parent window
82
- const sdk = await OxySDK.create({
83
- parentOrigin: "https://app.example.com",
84
- projectId: "your-project-uuid",
85
- });
86
-
87
- await sdk.loadAppData("dashboard.app.yml");
88
- const result = await sdk.query("SELECT * FROM my_table LIMIT 10");
89
- ```
90
-
91
- **Parent window setup:**
92
-
93
- ```typescript
94
- window.addEventListener("message", (event) => {
95
- if (event.data.type !== "OXY_AUTH_REQUEST") return;
96
- if (event.origin !== "https://your-iframe-app.com") return;
97
-
98
- event.source.postMessage(
99
- {
100
- type: "OXY_AUTH_RESPONSE",
101
- version: "1.0",
102
- requestId: event.data.requestId,
103
- apiKey: getUserApiKey(),
104
- projectId: "your-project-uuid",
105
- baseUrl: "https://api.oxy.tech",
106
- },
107
- event.origin,
108
- );
109
- });
110
- ```
111
-
112
- ### Environment Variables
12
+ ## Install
113
13
 
114
14
  ```bash
115
- export OXY_URL="https://api.oxy.tech"
116
- export OXY_API_KEY="your-api-key"
117
- export OXY_PROJECT_ID="your-project-uuid"
118
- export OXY_BRANCH="main" # optional
15
+ pnpm add @oxy-hq/sdk @oxy-hq/vite-plugin
119
16
  ```
120
17
 
121
- Use `createConfig()` to load from environment:
18
+ `react` (^19) is a peer dependency. `@oxy-hq/vite-plugin` wires the served
19
+ base path, copies `oxy-app.json` into the build, and injects the dev identity
20
+ shim — drop it into `vite.config.ts`:
122
21
 
123
- ```typescript
124
- import { OxySDK, createConfig } from "@oxy/sdk";
22
+ ```ts
23
+ import oxyApp from "@oxy-hq/vite-plugin";
24
+ import react from "@vitejs/plugin-react";
25
+ import { defineConfig } from "vite";
125
26
 
126
- const sdk = new OxySDK(createConfig());
27
+ export default defineConfig({ plugins: [react(), oxyApp()] });
127
28
  ```
128
29
 
129
- ## React Integration
30
+ ## Quick start
130
31
 
131
- ### Using React Context (Recommended)
132
-
133
- The SDK provides `OxyProvider` and `useOxy` hooks for easy integration:
32
+ Wrap your tree in `<OxyAppProvider>` (it resolves the app's identity), then
33
+ read data with hooks:
134
34
 
135
35
  ```tsx
136
- import { OxyProvider, useOxy, createConfig } from "@oxy/sdk";
137
- import { useEffect, useState } from "react";
138
-
139
- // Wrap your app with OxyProvider
140
- function App() {
141
- return (
142
- <OxyProvider config={createConfig()}>
143
- <Dashboard />
144
- </OxyProvider>
145
- );
146
- }
36
+ import { OxyAppProvider, useQuery, OxyChat } from "@oxy-hq/sdk";
147
37
 
148
- // Access SDK in child components
149
38
  function Dashboard() {
150
- const { sdk, isLoading, error } = useOxy();
151
- const [data, setData] = useState(null);
152
-
153
- useEffect(() => {
154
- if (sdk) {
155
- sdk
156
- .loadAppData("dashboard.app.yml")
157
- .then(() => sdk.query("SELECT * FROM my_table LIMIT 100"))
158
- .then(setData);
159
- }
160
- }, [sdk]);
161
-
162
- if (isLoading) return <div>Initializing SDK...</div>;
163
- if (error) return <div>Error: {error.message}</div>;
164
- if (!data) return <div>Loading data...</div>;
165
-
39
+ const { rows, isLoading, error } = useQuery({
40
+ sql: "SELECT Store, SUM(Weekly_Sales) AS sales FROM oxymart GROUP BY 1 ORDER BY 2 DESC LIMIT 5"
41
+ });
42
+ if (isLoading) return <p>Loading…</p>;
43
+ if (error) return <p>{error.message}</p>;
166
44
  return (
167
- <div>
168
- <h1>Dashboard</h1>
169
- <table>
170
- <thead>
171
- <tr>
172
- {data.columns.map((col) => (
173
- <th key={col}>{col}</th>
174
- ))}
175
- </tr>
176
- </thead>
177
- <tbody>
178
- {data.rows.map((row, i) => (
179
- <tr key={i}>
180
- {row.map((cell, j) => (
181
- <td key={j}>{String(cell)}</td>
182
- ))}
183
- </tr>
184
- ))}
185
- </tbody>
186
- </table>
187
- </div>
45
+ <>
46
+ <table>{rows.map((r) => <tr key={r.Store}><td>{r.Store}</td><td>{r.sales}</td></tr>)}</table>
47
+ <OxyChat agentId="analytics" />
48
+ </>
188
49
  );
189
50
  }
190
- ```
191
-
192
- ### Iframe with PostMessage Auth
193
51
 
194
- ```tsx
195
- import { OxyProvider, useOxySDK } from "@oxy/sdk";
196
-
197
- function App() {
52
+ export function App() {
198
53
  return (
199
- <OxyProvider useAsync config={{ parentOrigin: "https://app.example.com" }}>
54
+ <OxyAppProvider fallback={<p>Loading…</p>}>
200
55
  <Dashboard />
201
- </OxyProvider>
56
+ </OxyAppProvider>
202
57
  );
203
58
  }
204
-
205
- function Dashboard() {
206
- const sdk = useOxySDK(); // Throws if not ready
207
- const [data, setData] = useState(null);
208
-
209
- useEffect(() => {
210
- sdk
211
- .loadFile("data/sales.parquet", "sales")
212
- .then(() => sdk.query("SELECT * FROM sales LIMIT 100"))
213
- .then(setData);
214
- }, [sdk]);
215
-
216
- return <div>{/* render data */}</div>;
217
- }
218
- ```
219
-
220
- ### Without Context (Alternative)
221
-
222
- ```typescript
223
- import { OxySDK, createConfig } from '@oxy/sdk';
224
- import { useEffect, useState } from 'react';
225
-
226
- const sdk = new OxySDK(createConfig());
227
-
228
- function Dashboard() {
229
- const [data, setData] = useState(null);
230
-
231
- useEffect(() => {
232
- sdk.loadAppData('dashboard.app.yml')
233
- .then(() => sdk.query('SELECT * FROM my_table LIMIT 100'))
234
- .then(setData);
235
- }, []);
236
-
237
- return <div>{/* render data */}</div>;
238
- }
239
- ```
240
-
241
- ## Use with v0 and Sandbox Services
242
-
243
- **For AI Assistants (v0.dev, Cursor, etc.):** See [.v0/rules.md](.v0/rules.md) and [.cursorrules](.cursorrules) for integration guidelines.
244
-
245
- ```typescript
246
- import { OxySDK, createConfig } from "@oxy/sdk";
247
-
248
- const sdk = new OxySDK(createConfig());
249
-
250
- export async function getDashboardData() {
251
- await sdk.loadAppData("dashboard.app.yml");
252
-
253
- return await sdk.query(`
254
- SELECT s.*, c.name as customer_name
255
- FROM sales s
256
- LEFT JOIN customers c ON s.customer_id = c.id
257
- ORDER BY s.date DESC
258
- LIMIT 100
259
- `);
260
- }
261
-
262
- export function getChartUrl() {
263
- return sdk.getClient().getFileUrl("charts/sales-overview.png");
264
- }
265
- ```
266
-
267
- ## API Reference
268
-
269
- ### OxySDK (Unified Interface)
270
-
271
- The `OxySDK` class combines `OxyClient` and `ParquetReader` into a single, easy-to-use interface.
272
-
273
- #### `constructor(config: OxyConfig)`
274
-
275
- Creates a new SDK instance.
276
-
277
- #### `static async create(config?: Partial<OxyConfig>): Promise<OxySDK>`
278
-
279
- Creates an SDK instance with async configuration (supports postMessage auth).
280
-
281
- ```typescript
282
- const sdk = await OxySDK.create({
283
- parentOrigin: "https://app.example.com",
284
- projectId: "your-project-id",
285
- });
286
- ```
287
-
288
- #### `async loadFile(filePath: string, tableName: string): Promise<void>`
289
-
290
- Loads a Parquet file from Oxy and registers it for SQL queries.
291
-
292
- ```typescript
293
- await sdk.loadFile("data/sales.parquet", "sales");
294
- ```
295
-
296
- #### `async loadFiles(files: Array<{filePath: string, tableName: string}>): Promise<void>`
297
-
298
- Loads multiple Parquet files at once.
299
-
300
- ```typescript
301
- await sdk.loadFiles([
302
- { filePath: "data/sales.parquet", tableName: "sales" },
303
- { filePath: "data/customers.parquet", tableName: "customers" },
304
- ]);
305
- ```
306
-
307
- #### `async loadAppData(appPath: string): Promise<DataContainer | null>`
308
-
309
- Loads all data from an app's data container. Uses container keys as table names.
310
-
311
- ```typescript
312
- const data = await sdk.loadAppData("dashboard.app.yml");
313
- // Now query the tables using their container keys
314
- const result = await sdk.query("SELECT * FROM my_table");
315
- ```
316
-
317
- #### `async query(sql: string): Promise<QueryResult>`
318
-
319
- Executes a SQL query against loaded data.
320
-
321
- ```typescript
322
- const result = await sdk.query("SELECT * FROM sales WHERE amount > 1000");
323
- ```
324
-
325
- #### `async getAll(tableName: string, limit?: number): Promise<QueryResult>`
326
-
327
- Gets all data from a loaded table.
328
-
329
- ```typescript
330
- const data = await sdk.getAll("sales", 100);
331
- ```
332
-
333
- #### `async getSchema(tableName: string): Promise<QueryResult>`
334
-
335
- Gets schema information for a loaded table.
336
-
337
- #### `async count(tableName: string): Promise<number>`
338
-
339
- Gets row count for a loaded table.
340
-
341
- #### `getClient(): OxyClient`
342
-
343
- Returns the underlying `OxyClient` for advanced operations.
344
-
345
- ```typescript
346
- const apps = await sdk.getClient().listApps();
347
- ```
348
-
349
- #### `getReader(): ParquetReader`
350
-
351
- Returns the underlying `ParquetReader` for advanced operations.
352
-
353
- #### `async close(): Promise<void>`
354
-
355
- Closes and cleans up all resources.
356
-
357
- ### React Hooks
358
-
359
- #### `OxyProvider`
360
-
361
- Provider component that initializes and provides OxySDK to child components.
362
-
363
- **Props:**
364
-
365
- - `config?: Partial<OxyConfig>` - SDK configuration
366
- - `useAsync?: boolean` - If true, uses async initialization (supports postMessage auth)
367
- - `onReady?: (sdk: OxySDK) => void` - Called when SDK is initialized
368
- - `onError?: (error: Error) => void` - Called on initialization error
369
-
370
- ```tsx
371
- <OxyProvider config={createConfig()}>
372
- <YourApp />
373
- </OxyProvider>
374
- ```
375
-
376
- #### `useOxy()`
377
-
378
- Hook to access SDK, loading state, and errors.
379
-
380
- ```tsx
381
- const { sdk, isLoading, error } = useOxy();
382
- ```
383
-
384
- Returns:
385
-
386
- - `sdk: OxySDK | null` - The SDK instance (null if not ready)
387
- - `isLoading: boolean` - True while initializing
388
- - `error: Error | null` - Initialization error if any
389
-
390
- #### `useOxySDK()`
391
-
392
- Hook that returns SDK directly or throws if not ready. Useful when you know SDK should be initialized.
393
-
394
- ```tsx
395
- const sdk = useOxySDK(); // Throws if not ready
396
- ```
397
-
398
- ### Advanced: OxyClient
399
-
400
- For advanced use cases, access the underlying client via `sdk.getClient()`:
401
-
402
- ```typescript
403
- const client = sdk.getClient();
404
- await client.listApps();
405
- await client.getDisplays("my-app.app.yml");
406
- const blob = await client.getFile("path/to/file.parquet");
407
- ```
408
-
409
- ### Advanced: ParquetReader
410
-
411
- For advanced use cases, access the underlying reader via `sdk.getReader()`:
412
-
413
- ```typescript
414
- const reader = sdk.getReader();
415
- await reader.registerParquet(customBlob, "custom_table");
416
59
  ```
417
60
 
418
- Or use standalone:
419
-
420
- ```typescript
421
- import { ParquetReader } from "@oxy/sdk";
61
+ ## Identity (`oxy-app.json`)
422
62
 
423
- const reader = new ParquetReader();
424
- await reader.registerParquet(blob1, "table1");
425
- await reader.registerParquet(blob2, "table2");
63
+ Every bundle ships an identity-only manifest at its project root (next to
64
+ `vite.config.ts`, **not** under `public/`):
426
65
 
427
- const result = await reader.query("SELECT * FROM table1 JOIN table2 ON ...");
428
- await reader.close();
66
+ ```json
67
+ { "schemaVersion": 2, "slug": "store-pulse", "orgSlug": "acme", "name": "Store Pulse" }
429
68
  ```
430
69
 
431
- ## Environment Variables
70
+ When oxy serves the bundle it injects the authoritative identity as
71
+ `window.__OXY_APP__`; `OxyAppProvider` reads injection first and the manifest
72
+ second. There is **no API key in the bundle** — requests are authorized by the
73
+ viewer's oxy session (same-origin cookie) or, in cross-origin local dev, a
74
+ bearer token the dev proxy attaches. A bundle can't read data its viewer
75
+ couldn't already read.
432
76
 
433
- - `OXY_URL` - Base URL of the Oxy API (required)
434
- - `OXY_API_KEY` - API key for authentication (required)
435
- - `OXY_PROJECT_ID` - Project UUID (required)
436
- - `OXY_BRANCH` - Branch name (optional)
77
+ ## API
437
78
 
438
- ## Examples
79
+ | Export | What it does |
80
+ | --- | --- |
81
+ | `OxyAppProvider` | Resolves identity, provides it via context. `fallback` renders while loading; `errorFallback` gets a structured error report. |
82
+ | `useQuery({ sql })` | Inline SQL → rows. `SELECT`/`WITH` only, 10k-row cap. |
83
+ | `useSemanticQuery({ topic, dimensions, measures, … })` | Semantic-layer query compiled by airlayer. |
84
+ | `useAgentRun({ agentId })` | `.ask(question)` starts an analytics agent run; streams events over SSE; `.cancel()`. |
85
+ | `useProcedureRun({ procedureId })` | Start a long-running procedure, poll, cancel (beta). |
86
+ | `<OxyChat agentId="…" />` | Drop-in chat UI over `useAgentRun`. |
87
+ | `<OxyAnswer … />` | Renders markdown + SQL artifacts + thread link. URL schemes are allowlisted (rejects `javascript:` etc.). |
88
+ | `OxyApiError` | Structured `{ message, code? }` server-error envelope. |
439
89
 
440
- See the [examples](./examples) directory for more detailed examples:
90
+ Hooks fail loudly if called outside `<OxyAppProvider>`. The default fetcher
91
+ sends `credentials: "include"` so same-origin (served-by-oxy) calls carry the
92
+ session cookie automatically.
441
93
 
442
- ## Building and Publishing
443
-
444
- ```bash
445
- # Install dependencies
446
- npm install
94
+ ## Docs
447
95
 
448
- # Build the SDK
449
- npm run build
450
-
451
- # Type check
452
- npm run typecheck
453
-
454
- # Lint
455
- npm run lint
456
-
457
- # Publish to npm (beta)
458
- npm run publish:beta
459
-
460
- # Publish to npm (latest)
461
- npm run publish:latest
462
- ```
96
+ - Hands-on dev + deploy guide: `docs/local-development.md` in the
97
+ [`oxy-hq/customer-apps`](https://github.com/oxy-hq/customer-apps) repo.
98
+ - SDK flow reference: `docs/sdk-flow.md` in that repo.
99
+ - Platform internals: `internal-docs/customer-apps.md` in oxygen-internal.
463
100
 
464
101
  ## License
465
102
 
466
103
  MIT
467
-
468
- ## Support
469
-
470
- For issues and questions, please visit [GitHub Issues](https://github.com/dataframehq/oxy-internal/issues).
471
-
472
- ### Local development with v0 or cloud service
473
-
474
- - Disable the local network access check [flag](chrome ://flags/#local-network-access-check)