@oxy-hq/sdk 1.0.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,482 +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
- - `appPath?: string` - Optional app path to load initial app data upon initialization
368
- - `files?: Record<string, string>` - Optional initial files to preload as a mapping of table name to file path
369
- - `onReady?: (sdk: OxySDK) => void` - Called when SDK is initialized
370
- - `onError?: (error: Error) => void` - Called on initialization error
371
- - `loadingFallback?: ReactNode` - Rendered while the SDK is initializing (replaces `children` during load)
372
- - `errorFallback?: ReactNode | ((error: Error) => ReactNode)` - Rendered when initialization fails; can be a node or a render function that receives the error
373
-
374
- ```tsx
375
- <OxyProvider
376
- config={createConfig()}
377
- loadingFallback={<div>Loading SDK...</div>}
378
- errorFallback={(error) => <div>Failed to initialize: {error.message}</div>}
379
- >
380
- <YourApp />
381
- </OxyProvider>
382
- ```
383
-
384
- #### `useOxy()`
385
-
386
- Hook to access SDK, loading state, and errors.
387
-
388
- ```tsx
389
- const { sdk, isLoading, error } = useOxy();
390
- ```
391
-
392
- Returns:
393
-
394
- - `sdk: OxySDK | null` - The SDK instance (null if not ready)
395
- - `isLoading: boolean` - True while initializing
396
- - `error: Error | null` - Initialization error if any
397
-
398
- #### `useOxySDK()`
399
-
400
- Hook that returns SDK directly or throws if not ready. Useful when you know SDK should be initialized.
401
-
402
- ```tsx
403
- const sdk = useOxySDK(); // Throws if not ready
404
- ```
405
-
406
- ### Advanced: OxyClient
407
-
408
- For advanced use cases, access the underlying client via `sdk.getClient()`:
409
-
410
- ```typescript
411
- const client = sdk.getClient();
412
- await client.listApps();
413
- await client.getDisplays("my-app.app.yml");
414
- const blob = await client.getFile("path/to/file.parquet");
415
- ```
416
-
417
- ### Advanced: ParquetReader
418
-
419
- For advanced use cases, access the underlying reader via `sdk.getReader()`:
420
-
421
- ```typescript
422
- const reader = sdk.getReader();
423
- await reader.registerParquet(customBlob, "custom_table");
424
59
  ```
425
60
 
426
- Or use standalone:
427
-
428
- ```typescript
429
- import { ParquetReader } from "@oxy/sdk";
61
+ ## Identity (`oxy-app.json`)
430
62
 
431
- const reader = new ParquetReader();
432
- await reader.registerParquet(blob1, "table1");
433
- 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/`):
434
65
 
435
- const result = await reader.query("SELECT * FROM table1 JOIN table2 ON ...");
436
- await reader.close();
66
+ ```json
67
+ { "schemaVersion": 2, "slug": "store-pulse", "orgSlug": "acme", "name": "Store Pulse" }
437
68
  ```
438
69
 
439
- ## 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.
440
76
 
441
- - `OXY_URL` - Base URL of the Oxy API (required)
442
- - `OXY_API_KEY` - API key for authentication (required)
443
- - `OXY_PROJECT_ID` - Project UUID (required)
444
- - `OXY_BRANCH` - Branch name (optional)
77
+ ## API
445
78
 
446
- ## 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. |
447
89
 
448
- 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.
449
93
 
450
- ## Building and Publishing
451
-
452
- ```bash
453
- # Install dependencies
454
- npm install
94
+ ## Docs
455
95
 
456
- # Build the SDK
457
- npm run build
458
-
459
- # Type check
460
- npm run typecheck
461
-
462
- # Lint
463
- npm run lint
464
-
465
- # Publish to npm (beta)
466
- npm run publish:beta
467
-
468
- # Publish to npm (latest)
469
- npm run publish:latest
470
- ```
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.
471
100
 
472
101
  ## License
473
102
 
474
103
  MIT
475
-
476
- ## Support
477
-
478
- For issues and questions, please visit [GitHub Issues](https://github.com/dataframehq/oxy-internal/issues).
479
-
480
- ### Local development with v0 or cloud service
481
-
482
- - Disable the local network access check [flag](chrome ://flags/#local-network-access-check)