@oxy-hq/sdk 2.0.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,522 @@
1
1
 
2
2
  import * as React from "react";
3
-
3
+ //#region src/config.d.ts
4
+ /**
5
+ * Configuration for the Oxy SDK
6
+ */
7
+ interface OxyConfig {
8
+ /**
9
+ * Base URL of the Oxy API (e.g., 'https://api.oxy.tech' or 'http://localhost:3000')
10
+ */
11
+ baseUrl: string;
12
+ /**
13
+ * API key for authentication (optional for local development)
14
+ */
15
+ apiKey?: string;
16
+ /**
17
+ * Project ID (UUID)
18
+ */
19
+ projectId: string;
20
+ /**
21
+ * Optional branch name (defaults to current branch if not specified)
22
+ */
23
+ branch?: string;
24
+ /**
25
+ * Request timeout in milliseconds (default: 30000)
26
+ */
27
+ timeout?: number;
28
+ /**
29
+ * Parent window origin for postMessage authentication (iframe scenarios)
30
+ * Required when using postMessage auth for security.
31
+ * Example: 'https://app.example.com'
32
+ * Use '*' only in development!
33
+ */
34
+ parentOrigin?: string;
35
+ /**
36
+ * Disable automatic postMessage authentication even if in iframe
37
+ * Set to true if you want to provide API key manually in iframe context
38
+ */
39
+ disableAutoAuth?: boolean;
40
+ }
41
+ //#endregion
42
+ //#region src/metricTree.d.ts
43
+ type EdgeKind = "component" | "driver";
44
+ type DriverDirection = "positive" | "negative" | "unknown";
45
+ type DriverStrength = "strong" | "moderate" | "weak";
46
+ type DriverConfidence = "high" | "medium" | "low";
47
+ type DriverForm = "linear" | "log-log" | "log-linear" | "linear-log";
48
+ interface MetricNode {
49
+ id: string;
50
+ view: string;
51
+ measure: string;
52
+ label: string;
53
+ description?: string | null;
54
+ measure_type: string;
55
+ is_composite: boolean;
56
+ expr?: string | null;
57
+ }
58
+ interface MetricEdge {
59
+ from: string;
60
+ to: string;
61
+ kind: EdgeKind;
62
+ /** Sign of a component edge; omitted (defaults to +1) for most edges. */
63
+ sign?: number;
64
+ direction: DriverDirection;
65
+ strength: DriverStrength;
66
+ confidence: DriverConfidence;
67
+ coefficient?: number | null;
68
+ form: DriverForm;
69
+ intercept?: number | null;
70
+ lag?: number | null;
71
+ description?: string | null;
72
+ refs?: string[] | null;
73
+ }
74
+ interface MetricTree {
75
+ nodes: MetricNode[];
76
+ edges: MetricEdge[];
77
+ root?: string | null;
78
+ }
79
+ interface SensitivityDriver {
80
+ measure: string;
81
+ path: string[];
82
+ edge_kind: string;
83
+ effective_coefficient?: number | null;
84
+ form?: DriverForm | null;
85
+ direction: DriverDirection;
86
+ strength: DriverStrength;
87
+ lag?: number | null;
88
+ description?: string | null;
89
+ }
90
+ interface SensitivityResult {
91
+ target: string;
92
+ drivers: SensitivityDriver[];
93
+ }
94
+ interface PredictChange {
95
+ measure: string;
96
+ delta: number;
97
+ }
98
+ interface PredictImpact {
99
+ measure: string;
100
+ estimated_delta: number;
101
+ confidence: string;
102
+ path: string[];
103
+ form: DriverForm;
104
+ lag?: number | null;
105
+ }
106
+ interface PredictResult {
107
+ inputs: PredictChange[];
108
+ impacts: PredictImpact[];
109
+ }
110
+ type SplitKind = {
111
+ type: "component";
112
+ child_measure: string;
113
+ } | {
114
+ type: "dimension";
115
+ dimension: string;
116
+ value: string;
117
+ } | {
118
+ type: "uniform_degradation";
119
+ dimension: string;
120
+ num_elements: number;
121
+ } | {
122
+ type: "cross_cutting";
123
+ dimension: string;
124
+ value: string;
125
+ measures: string[];
126
+ };
127
+ interface ExplainSibling {
128
+ split: SplitKind;
129
+ measure: string;
130
+ delta: number;
131
+ root_fraction: number;
132
+ }
133
+ interface ExplainNode {
134
+ split: SplitKind;
135
+ measure: string;
136
+ filters: unknown[];
137
+ delta: number;
138
+ concentration: number;
139
+ root_fraction: number;
140
+ siblings?: ExplainSibling[];
141
+ dimension_count?: number;
142
+ children?: ExplainNode[];
143
+ }
144
+ interface DriverAttribution {
145
+ driver_measure: string;
146
+ driver_previous: number;
147
+ driver_current: number;
148
+ driver_delta: number;
149
+ coefficient?: number;
150
+ form: DriverForm;
151
+ estimated_target_impact?: number;
152
+ description?: string;
153
+ }
154
+ type ExplainWarning = {
155
+ type: "simpsons_paradox";
156
+ dimension: string;
157
+ aggregate_delta: number;
158
+ segment_directions: [string, number][];
159
+ } | {
160
+ type: "opposing_offset";
161
+ component_a: string;
162
+ component_b: string;
163
+ delta_a: number;
164
+ delta_b: number;
165
+ } | {
166
+ type: "non_additive_dimension_split";
167
+ measure: string;
168
+ measure_type: string;
169
+ dimension: string;
170
+ };
171
+ interface ExplainConfigOverride {
172
+ deep?: boolean;
173
+ max_depth?: number;
174
+ coverage_threshold?: number;
175
+ }
176
+ interface ExplainRequest {
177
+ target: string;
178
+ time_dimension: string;
179
+ current_period: [string, string];
180
+ previous_period: [string, string];
181
+ config?: ExplainConfigOverride;
182
+ }
183
+ interface ExplainResult {
184
+ target: string;
185
+ target_delta: number;
186
+ target_previous: number;
187
+ target_current: number;
188
+ time_dimension: string;
189
+ current_period: [string, string];
190
+ previous_period: [string, string];
191
+ nodes: ExplainNode[];
192
+ coverage: number;
193
+ driver_attribution?: DriverAttribution[];
194
+ alternatives?: unknown[];
195
+ warnings?: ExplainWarning[];
196
+ }
197
+ interface SegmentOpportunity {
198
+ segment: string;
199
+ current_value: number;
200
+ volume: number;
201
+ benchmark: number;
202
+ gap: number;
203
+ /** Match-the-best upside in measure units. */
204
+ upside: number;
205
+ }
206
+ interface DimensionOpportunity {
207
+ dimension: string;
208
+ cardinality: number;
209
+ /** "best_peer" or "p75". */
210
+ benchmark_basis: string;
211
+ total_upside: number;
212
+ segments: SegmentOpportunity[];
213
+ other_segments_skipped: number;
214
+ }
215
+ interface SkippedDimension {
216
+ dimension: string;
217
+ reason: string;
218
+ }
219
+ interface OpportunityRequest {
220
+ target: string;
221
+ time_dimension: string;
222
+ period: [string, string];
223
+ }
224
+ interface OpportunityResult {
225
+ target: string;
226
+ period: [string, string];
227
+ overall_value: number;
228
+ /** "value_share" (additive) or "equal" (ratios). */
229
+ weight_basis: string;
230
+ dimensions: DimensionOpportunity[];
231
+ skipped_dimensions: SkippedDimension[];
232
+ downstream: PredictImpact[];
233
+ }
234
+ /**
235
+ * Shape of the inner request helper exposed by `OxyClient`. The metric-tree
236
+ * client reuses it to inherit auth headers, timeout, baseUrl, and project
237
+ * scoping rather than reimplementing fetch end-to-end.
238
+ */
239
+ type RequestFn$1 = <T>(endpoint: string, options?: RequestInit) => Promise<T>;
240
+ /**
241
+ * Client for the `/semantic/metric-tree*` endpoints. Surfaces the four
242
+ * airlayer metric-tree analyses (tree introspection, sensitivity, predict,
243
+ * explain, opportunity) over typed methods.
244
+ *
245
+ * Construction is internal to {@link OxyClient} — call `client.metricTree`
246
+ * to access an instance rather than building one yourself.
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const client = await OxyClient.create({ projectId: "...", apiKey: "..." });
251
+ * const tree = await client.metricTree.getTree();
252
+ * const drivers = await client.metricTree.getSensitivity("orders.net_revenue");
253
+ * ```
254
+ */
255
+ declare class MetricTreeClient {
256
+ private readonly request;
257
+ private readonly config;
258
+ constructor(config: OxyConfig, request: RequestFn$1);
259
+ private path;
260
+ private buildQuery;
261
+ /**
262
+ * Fetch the full metric tree, or the subtree rooted at `root`.
263
+ *
264
+ * @param root - Optional fully-qualified measure id to root the tree at.
265
+ * @returns Nodes (measures) and edges (component / driver relationships).
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * const tree = await client.metricTree.getTree();
270
+ * const subtree = await client.metricTree.getTree("orders.net_revenue");
271
+ * ```
272
+ */
273
+ getTree(root?: string): Promise<MetricTree>;
274
+ /**
275
+ * Rank the declared drivers of a measure by influence.
276
+ *
277
+ * @param measureId - Fully-qualified measure id (`view.measure`).
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * const sensitivity = await client.metricTree.getSensitivity("orders.net_revenue");
282
+ * for (const driver of sensitivity.drivers) {
283
+ * console.log(driver.measure, driver.direction, driver.strength);
284
+ * }
285
+ * ```
286
+ */
287
+ getSensitivity(measureId: string): Promise<SensitivityResult>;
288
+ /**
289
+ * Propagate hypothetical `(measure, delta)` changes upward through the
290
+ * tree. Returns the estimated impact on every downstream measure.
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * const result = await client.metricTree.predict([
295
+ * { measure: "marketing_spend.total_spend", delta: 10000 },
296
+ * ]);
297
+ * ```
298
+ */
299
+ predict(changes: PredictChange[]): Promise<PredictResult>;
300
+ /**
301
+ * Period-over-period root-cause decomposition. Recursively splits the
302
+ * target measure by components and dimensions until the move concentrates.
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * const result = await client.metricTree.explain({
307
+ * target: "financials.operating_profit",
308
+ * time_dimension: "financials.month",
309
+ * current_period: ["2025-09-01", "2025-09-30"],
310
+ * previous_period: ["2025-08-01", "2025-08-31"],
311
+ * });
312
+ * ```
313
+ */
314
+ explain(request: ExplainRequest): Promise<ExplainResult>;
315
+ /**
316
+ * Size the upside opportunity for a measure by finding underperforming
317
+ * segments. Skips high-cardinality dimensions and trims the long tail.
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * const result = await client.metricTree.findOpportunities({
322
+ * target: "orders.net_revenue",
323
+ * time_dimension: "orders.order_date",
324
+ * period: ["2025-09-01", "2025-09-30"],
325
+ * });
326
+ * for (const dim of result.dimensions) {
327
+ * console.log(dim.dimension, "+", dim.total_upside);
328
+ * }
329
+ * ```
330
+ */
331
+ findOpportunities(request: OpportunityRequest): Promise<OpportunityResult>;
332
+ }
333
+ //#endregion
334
+ //#region src/anomalies.d.ts
335
+ type AnomalyStatus = "new" | "acknowledged" | "dismissed";
336
+ type AnomalySeverity = "low" | "medium" | "high";
337
+ /**
338
+ * One row in the anomaly inbox. Detected by `oxy-metric-monitoring` per
339
+ * `.monitor.yml` entry; upserted by repeat scans so unresolved anomalies
340
+ * stay visible without piling up duplicates.
341
+ */
342
+ interface Anomaly {
343
+ id: string;
344
+ workspace_id: string;
345
+ measure: string;
346
+ time_dimension: string;
347
+ granularity: string;
348
+ period_start: string;
349
+ period_end: string;
350
+ observed: number;
351
+ expected: number;
352
+ lower_bound: number;
353
+ upper_bound: number;
354
+ z_score: number;
355
+ severity: AnomalySeverity | string;
356
+ status: AnomalyStatus | string;
357
+ label?: string | null;
358
+ /** Cached ExplainResult — populated by `POST /anomalies/:id/explain`. */
359
+ explain_cache?: ExplainResult | null;
360
+ explain_cached_at?: string | null;
361
+ detected_at: string;
362
+ updated_at: string;
363
+ }
364
+ interface ListAnomaliesOptions {
365
+ status?: AnomalyStatus | string;
366
+ /** Max rows (server caps at 500, defaults to 100). */
367
+ limit?: number;
368
+ }
369
+ interface ListAnomaliesResponse {
370
+ anomalies: Anomaly[];
371
+ }
372
+ interface ScanOptions {
373
+ /** Override the reference "now" date (YYYY-MM-DD) — useful for demos. */
374
+ as_of?: string;
375
+ }
376
+ interface ScanResponse {
377
+ monitors_scanned: number;
378
+ monitors_failed: number;
379
+ anomalies_persisted: number;
380
+ }
381
+ type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;
382
+ /**
383
+ * Client for `/semantic/anomalies*`. Construct via `OxyClient.anomalies`
384
+ * rather than instantiating directly — the getter wires the request helper
385
+ * so auth, timeout, and branch propagation come along for free.
386
+ *
387
+ * @example
388
+ * ```typescript
389
+ * const { anomalies } = await client.anomalies.list({ status: "new" });
390
+ * for (const a of anomalies) {
391
+ * console.log(a.label ?? a.measure, a.severity, a.z_score.toFixed(2));
392
+ * }
393
+ * ```
394
+ */
395
+ declare class AnomaliesClient {
396
+ private readonly request;
397
+ private readonly config;
398
+ constructor(config: OxyConfig, request: RequestFn);
399
+ private path;
400
+ private buildQuery;
401
+ /**
402
+ * List anomalies in the inbox, newest first.
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * // Open / unresolved anomalies only
407
+ * const { anomalies } = await client.anomalies.list({ status: "new" });
408
+ * ```
409
+ */
410
+ list(options?: ListAnomaliesOptions): Promise<ListAnomaliesResponse>;
411
+ /**
412
+ * Trigger a full scan. Iterates every `.monitor.yml` entry in the
413
+ * workspace, runs the detector, and upserts matching rows into the
414
+ * inbox. Returns counts of scanned / failed / persisted.
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * // Scan against a known-good reference date (matches the seed dataset)
419
+ * const result = await client.anomalies.scan({ as_of: "2025-12-15" });
420
+ * console.log(`${result.anomalies_persisted} anomalies detected`);
421
+ * ```
422
+ */
423
+ scan(options?: ScanOptions): Promise<ScanResponse>;
424
+ /**
425
+ * Update an anomaly's status (acknowledge / dismiss / re-open).
426
+ */
427
+ updateStatus(anomalyId: string, status: AnomalyStatus): Promise<Anomaly>;
428
+ /**
429
+ * Run the metric-tree `explain` for an anomaly and cache the result on
430
+ * the row. Subsequent calls return the cached `ExplainResult` instantly.
431
+ */
432
+ explain(anomalyId: string): Promise<ExplainResult>;
433
+ }
434
+ //#endregion
4
435
  //#region src/customer-app/manifest.d.ts
436
+ /**
437
+ * Declaration of a single Oxy Function shipped in the bundle's
438
+ * `functions/` dir. See `internal-docs/2026-06-12-customer-apps-functions-design.md`.
439
+ *
440
+ * All fields optional except that at least one invocation surface
441
+ * (`route`, `schedule`, or `airwayStep`) must be active. Absent =
442
+ * `route: true` (HTTP-invocable via `useFunction`).
443
+ */
444
+ interface OxyAppFunctionManifest {
445
+ /** Source entry, relative to the app dir. Default: `functions/<name>.ts`. */
446
+ entry?: string;
447
+ /** Cron expression. When set, the function fires on this schedule. */
448
+ schedule?: string;
449
+ /** IANA timezone for `schedule`. Default: `UTC`. */
450
+ timezone?: string;
451
+ /** Expose `POST .../fn/<name>` (called via `useFunction`). Default: true. */
452
+ route?: boolean;
453
+ /** Wire the function in as an Airway pipeline transform step. */
454
+ airwayStep?: {
455
+ pipeline: string;
456
+ resource: string;
457
+ };
458
+ /** Wall-clock timeout. Default 30, max 300. */
459
+ timeoutSeconds?: number;
460
+ /**
461
+ * Opt-in result caching for route invocations. Omit (the default) to never
462
+ * cache — the safe choice for a side-effectful function (writes, external
463
+ * POSTs, ELT). Set `ttlSeconds` ONLY for read-only / idempotent functions:
464
+ * results are then cached per (build, function, user, request body) for that
465
+ * window, and a repeat `useFunction().invoke(sameBody)` returns the cached
466
+ * result without re-running. A `?refresh` query bypasses it.
467
+ */
468
+ cache?: {
469
+ ttlSeconds?: number;
470
+ };
471
+ /**
472
+ * Databases this function's `ctx.warehouse.*` writes may target. Omit (or
473
+ * leave empty) and the function may NOT write to any database — writes are
474
+ * fail-closed and rejected before any connection is opened. Declare a
475
+ * destination here ONLY for a function that legitimately writes to it; a
476
+ * read-only function omits it. This scopes writes away from the project's
477
+ * source warehouse.
478
+ */
479
+ destinations?: string[];
480
+ /**
481
+ * Capability to write app-scoped secrets via `ctx.secrets.set` (fail-closed:
482
+ * omit → writes rejected). Only the app's own `apps/<app-id>/` namespace is
483
+ * writable. Declare for a function that persists state — e.g. a scheduled
484
+ * token-refresher that writes the rotated token back to Oxy Secrets.
485
+ */
486
+ secrets?: {
487
+ write?: boolean;
488
+ };
489
+ /**
490
+ * Capability to send email via `ctx.email.send` (fail-closed: omit → the
491
+ * host rejects `ctx.email.send` before any provider call). Declare for a
492
+ * function that emails the app's users — e.g. a `notify` route that sends a
493
+ * welcome message, or a scheduled digest. The sender mailbox is
494
+ * platform-controlled; a function may set `replyTo` but never `from`.
495
+ */
496
+ email?: {
497
+ send?: boolean;
498
+ };
499
+ /**
500
+ * Retry policy for **background** runs (a `schedule` fire or a manual job
501
+ * trigger). Omit → a job run is attempted once. Route (HTTP) invocations are
502
+ * request-scoped and never retried. `maxAttempts` counts the first try
503
+ * (`maxAttempts: 3` = up to 2 retries); backoff is exponential (doubling)
504
+ * between `minTimeoutMs` and `maxTimeoutMs`. Maps to the durable queue's
505
+ * retry policy — a transient failure re-runs the whole isolate.
506
+ */
507
+ retries?: {
508
+ maxAttempts?: number;
509
+ minTimeoutMs?: number;
510
+ maxTimeoutMs?: number;
511
+ };
512
+ /**
513
+ * Example input params for the function — a sample JSON body the admin "Run
514
+ * now" surface prefills so an operator knows what to pass (the function reads
515
+ * it as its `req` body, same as a route invocation). Advisory only; not
516
+ * enforced at runtime.
517
+ */
518
+ inputExample?: unknown;
519
+ }
5
520
  /** Wire shape of `oxy-app.json` (v2 only). */
6
521
  interface OxyAppManifest {
7
522
  /** Must be 2. v1 manifests are no longer supported. */
@@ -31,6 +546,12 @@ interface OxyAppManifest {
31
546
  * `/api/projects/:id/query` URL.
32
547
  */
33
548
  projectId?: string;
549
+ /**
550
+ * Optional map of Oxy Functions (server-side handlers) shipped in the
551
+ * bundle's `functions/` dir, keyed by function name. Omit for a pure
552
+ * static bundle (today's default). See the functions design doc.
553
+ */
554
+ functions?: Record<string, OxyAppFunctionManifest>;
34
555
  }
35
556
  /**
36
557
  * Manifest + runtime-injected identity needed to call oxy. Callers
@@ -119,6 +640,30 @@ interface CustomerAppDebugSnapshot {
119
640
  declare function getCustomerAppDebug(resolved: ResolvedCustomerAppManifest): Promise<CustomerAppDebugSnapshot>;
120
641
  //#endregion
121
642
  //#region src/customer-app/errors.d.ts
643
+ /**
644
+ * Error thrown by all customer-app hooks when an API call returns a
645
+ * non-2xx response. Carries the structured `code` + `hint` the server
646
+ * emits so bundle UIs can render an actionable message instead of
647
+ * "404: { ...json... }".
648
+ */
649
+ declare class OxyApiError extends Error {
650
+ readonly status: number;
651
+ readonly code: string | null;
652
+ readonly hint: string | null;
653
+ constructor(opts: {
654
+ status: number;
655
+ message: string;
656
+ code?: string | null;
657
+ hint?: string | null;
658
+ });
659
+ }
660
+ /**
661
+ * Read a non-2xx response from oxy and return an `OxyApiError`.
662
+ * Parses the JSON envelope when present; falls back to raw text
663
+ * (truncated to 240 chars so a runaway HTML error page doesn't
664
+ * dominate the bundle UI).
665
+ */
666
+ declare function apiErrorFromResponse(resp: Response): Promise<OxyApiError>;
122
667
  interface CustomerAppErrorReport {
123
668
  title: string;
124
669
  message: string;
@@ -128,6 +673,118 @@ interface CustomerAppErrorReport {
128
673
  /** Interpret a thrown error as a structured report for UI display. */
129
674
  declare function interpretCustomerAppError(err: unknown): CustomerAppErrorReport;
130
675
  //#endregion
676
+ //#region src/customer-app/function-context.d.ts
677
+ /**
678
+ * The request passed as the first argument to a function's default export.
679
+ *
680
+ * The host hands the isolate the raw request body as a string (see
681
+ * `req_json` in `runtime.rs`); parse it yourself, e.g.
682
+ * `JSON.parse(req.body || "{}")`. This is intentionally *not* a full Web
683
+ * `Request` — there is no `.json()` / headers object in v1.
684
+ */
685
+ interface OxyFunctionRequest {
686
+ /** Raw request body as received (JSON string for a JSON POST). */
687
+ body: string;
688
+ }
689
+ /** A single row from a `ctx.query` / `ctx.queryStream` result. */
690
+ type OxyFunctionRow = Record<string, unknown>;
691
+ /** Identity of the invoking user (route) or the system identity (schedule/airway). */
692
+ interface OxyFunctionUser {
693
+ id: string;
694
+ email: string;
695
+ orgId: string;
696
+ }
697
+ /** Result of a `ctx.fetch` call. */
698
+ interface OxyFetchResult {
699
+ status: number;
700
+ body: string;
701
+ }
702
+ /** `ctx.warehouse.*` — writes to one of the app's configured destination databases. */
703
+ interface OxyWarehouseApi {
704
+ insert(database: string, table: string, rows: OxyFunctionRow[]): Promise<unknown>;
705
+ exec(database: string, sql: string): Promise<unknown>;
706
+ upsert(database: string, table: string, rows: OxyFunctionRow[], conflictColumns: string[]): Promise<unknown>;
707
+ }
708
+ /** `ctx.secrets` — write app-scoped secrets (gated by the `secrets.write` capability). */
709
+ interface OxySecretsApi {
710
+ set(key: string, value: string): Promise<void>;
711
+ }
712
+ /** `ctx.semantic` — airlayer-compiled semantic queries (inherits the pre-agg fast path). */
713
+ interface OxySemanticApi {
714
+ query(spec: Record<string, unknown>): Promise<unknown>;
715
+ }
716
+ /** `ctx.airway` — seed/await an Airway ELT pipeline run. */
717
+ interface OxyAirwayApi {
718
+ run(pipelineRef: string, variables?: Record<string, unknown> | null): Promise<{
719
+ runId: string;
720
+ }>;
721
+ }
722
+ /**
723
+ * Input to `ctx.email.send`. Platform-injected: the sender mailbox (`from`) is
724
+ * platform-controlled and **not** an accepted field — passing it is a typed
725
+ * error. Provide `html` and/or `text` as the body (render a template to HTML
726
+ * with `render` from `@oxy-hq/sdk/email`).
727
+ */
728
+ interface EmailSendInput {
729
+ /** Recipient address(es). Required. */
730
+ to: string | string[];
731
+ /** CC address(es). */
732
+ cc?: string | string[];
733
+ /** BCC address(es). */
734
+ bcc?: string | string[];
735
+ /** Reply-To address — the only sender-identity field an author may set. */
736
+ replyTo?: string;
737
+ /** Subject line. Required. */
738
+ subject: string;
739
+ /** HTML body. Provide at least one of `html` / `text`. */
740
+ html?: string;
741
+ /** Plain-text body. Provide at least one of `html` / `text`. */
742
+ text?: string;
743
+ /**
744
+ * Optional idempotency key (≤256 chars). Accepted and validated in v1 but a
745
+ * no-op until the persisted idempotency table lands — adopt it now so
746
+ * background (retried) sends become exactly-once once it does.
747
+ */
748
+ idempotencyKey?: string;
749
+ }
750
+ /** Result of a successful `ctx.email.send`. */
751
+ interface EmailSendResult {
752
+ /** Provider (SES) message id of the sent message. */
753
+ messageId: string;
754
+ }
755
+ /** `ctx.email` — send email (gated by the `email.send` capability). */
756
+ interface OxyEmailApi {
757
+ send(input: EmailSendInput): Promise<EmailSendResult>;
758
+ }
759
+ /**
760
+ * The data-plane context passed as the second argument to a function's default
761
+ * export. Mirrors the host-assembled `ctx` (`__buildCtx` in `runtime.rs`);
762
+ * every member is a host-provided async function bridged to a Rust backend.
763
+ */
764
+ interface OxyFunctionContext {
765
+ /** Invoking user (route) or system identity (schedule/airway). */
766
+ user: OxyFunctionUser;
767
+ /** Read-only view of the app's configured secrets (project-scoped). */
768
+ env: Record<string, string>;
769
+ /** Structured per-invocation logging (captured + surfaced with the response). */
770
+ log(...args: unknown[]): void;
771
+ /** Read-only SQL (SELECT/WITH only), function-scoped row cap. Resolves to the rows. */
772
+ query(sql: string): Promise<OxyFunctionRow[]>;
773
+ /** Read-only SQL with a higher row cap, yielded to the caller in batches. */
774
+ queryStream(sql: string, opts?: {
775
+ batchSize?: number;
776
+ }): AsyncGenerator<OxyFunctionRow[], void, unknown>;
777
+ /** SSRF-allowlisted outbound HTTP with a response-size cap. */
778
+ fetch(url: string, init?: RequestInit): Promise<OxyFetchResult>;
779
+ warehouse: OxyWarehouseApi;
780
+ secrets: OxySecretsApi;
781
+ semantic: OxySemanticApi;
782
+ airway: OxyAirwayApi;
783
+ email: OxyEmailApi;
784
+ }
785
+ /** Signature of a function's default export: `export default async (req, ctx) => Response`. */
786
+ type OxyFunctionHandler = (req: OxyFunctionRequest, ctx: OxyFunctionContext) => Promise<Response> | Response;
787
+ //#endregion
131
788
  //#region src/customer-app/inject.d.ts
132
789
  /**
133
790
  * Shape of `window.__OXY_APP__` written by oxy at serve time.
@@ -167,6 +824,13 @@ declare function setOxyAppLogger(logger: OxyAppLogger | null): void;
167
824
  /** Used by the SDK internals; not part of the public surface. */
168
825
  declare function getOxyAppLogger(): OxyAppLogger;
169
826
  //#endregion
827
+ //#region src/customer-app/function-sse.d.ts
828
+ /** A captured `console.*` / `ctx.log` line from a function run. */
829
+ interface FunctionLog {
830
+ level: string;
831
+ message: string;
832
+ }
833
+ //#endregion
170
834
  //#region src/customer-app/react.d.ts
171
835
  /**
172
836
  * Credentialed fetch wrapper stored in context so `useQuery` can share
@@ -206,29 +870,6 @@ interface OxyAppProviderProps {
206
870
  * render after the manifest is ready (or the error fallback fires).
207
871
  */
208
872
  declare function OxyAppProvider(props: OxyAppProviderProps): React.JSX.Element;
209
- /**
210
- * Error thrown by all customer-app hooks when an API call returns a
211
- * non-2xx response. Carries the structured `code` + `hint` the server
212
- * emits so bundle UIs can render an actionable message instead of
213
- * "404: { ...json... }".
214
- *
215
- * The server contract is documented in
216
- * `crates/app/src/server/api/projects/agent_ask.rs` and
217
- * `procedure_run.rs` — both emit `{ message, code?, hint? }` as JSON.
218
- * Hooks that previously wrapped the raw text in `new Error()` now
219
- * throw this type instead.
220
- */
221
- declare class OxyApiError extends Error {
222
- readonly status: number;
223
- readonly code: string | null;
224
- readonly hint: string | null;
225
- constructor(opts: {
226
- status: number;
227
- message: string;
228
- code?: string | null;
229
- hint?: string | null;
230
- });
231
- }
232
873
  /**
233
874
  * Read the resolved manifest from context. Throws if called outside
234
875
  * `<OxyAppProvider>` — that's a programmer error worth surfacing
@@ -261,6 +902,42 @@ interface UseQueryResult<Row = Record<string, unknown>> {
261
902
  * available (e.g. a user-supplied filter value).
262
903
  */
263
904
  declare function useQuery<Row = Record<string, unknown>>(input: UseQueryInput, opts?: UseQueryOpts): UseQueryResult<Row>;
905
+ interface UseFunctionResult<Data = unknown> {
906
+ /**
907
+ * Invoke the function with an optional JSON body. Resolves to the parsed
908
+ * result. Pass `{ idempotencyKey }` to make a side-effectful invocation
909
+ * exactly-once: a retry with the same key replays the stored result instead
910
+ * of re-executing. Send a fresh key per logical action (e.g. a UUID per
911
+ * journal entry).
912
+ */
913
+ invoke: (body?: unknown, opts?: {
914
+ idempotencyKey?: string;
915
+ }) => Promise<Data>;
916
+ /** Last successful result, or null before the first invoke. */
917
+ data: Data | null;
918
+ /** True while an invocation is in flight. */
919
+ isLoading: boolean;
920
+ /** Last invocation error, or null. On error this carries `.logs` too. */
921
+ error: Error | null;
922
+ /**
923
+ * `console.*` / `ctx.log` output from the last invoke (success or error), so
924
+ * a developer can see what the function printed without opening the oxy
925
+ * server logs. Empty for a cache hit or idempotent replay — no run happened,
926
+ * so there is nothing to log.
927
+ */
928
+ logs: FunctionLog[];
929
+ }
930
+ /**
931
+ * Imperative hook for invoking an Oxy Function by name.
932
+ *
933
+ * ```tsx
934
+ * const refresh = useFunction("refresh-sales");
935
+ * <button disabled={refresh.isLoading} onClick={() => refresh.invoke({ full: true })}>
936
+ * Refresh
937
+ * </button>
938
+ * ```
939
+ */
940
+ declare function useFunction<Data = unknown>(name: string): UseFunctionResult<Data>;
264
941
  /** Scalar filter operators (compared against a single value). */
265
942
  type SemanticScalarOp = "eq" | "neq" | "lt" | "lte" | "gt" | "gte";
266
943
  /** Array filter operators (compared against a list). */
@@ -457,6 +1134,35 @@ interface UseAgentRunResult {
457
1134
  error: Error | null;
458
1135
  }
459
1136
  declare function useAgentRun(input: UseAgentRunInput): UseAgentRunResult;
1137
+ /**
1138
+ * Engineer-tagged usage event. Free-form `event_name` (≤ 64 chars,
1139
+ * `[a-z][a-z0-9-]*` validated server-side) + optional JSON `payload`
1140
+ * (object, ≤ 4 KiB serialized). Surfaces in the admin Activity tab
1141
+ * grouped by name, with drill-down into recent occurrences.
1142
+ *
1143
+ * The handler returned by [`useTrackEvent`] is **fire-and-forget**:
1144
+ * it enqueues the event into an in-memory batch flushed every second
1145
+ * (and on `pagehide` so a navigation away doesn't drop the tail).
1146
+ * No await semantics — call it inline from a click handler without
1147
+ * awaiting it. Server-side validation errors are logged to the
1148
+ * console; the call site doesn't need to handle them.
1149
+ *
1150
+ * Example:
1151
+ * ```tsx
1152
+ * const track = useTrackEvent();
1153
+ * <button
1154
+ * onClick={() => {
1155
+ * track("export-clicked", { format: "csv", rowCount });
1156
+ * doExport();
1157
+ * }}
1158
+ * >Export</button>
1159
+ * ```
1160
+ *
1161
+ * Rate-limited at 60/min per (user, app) on the server. A burst that
1162
+ * trips the limit drops the excess events with a console warning;
1163
+ * within-limit events are unaffected.
1164
+ */
1165
+ declare function useTrackEvent(): (name: string, payload?: Record<string, unknown>) => void;
460
1166
  interface OxyAnswerProps {
461
1167
  /** Markdown answer text from `useAgentRun().answer`. */
462
1168
  answer: string | null;
@@ -530,5 +1236,5 @@ interface OxyChatProps {
530
1236
  */
531
1237
  declare function OxyChat(props: OxyChatProps): React.JSX.Element;
532
1238
  //#endregion
533
- export { type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, type AppFetcher, type CustomerAppDebugSnapshot, type CustomerAppErrorReport, type LoadManifestOptions, OxyAnswer, type OxyAnswerProps, OxyApiError, type OxyAppLogLevel, type OxyAppLogger, type OxyAppManifest, OxyAppProvider, type OxyAppProviderProps, OxyChat, type OxyChatProps, type OxyInjectedAppConfig, type ProcedureProgress, type ProcedureResult, type ProcedureRunState, type ResolvedCustomerAppManifest, type SemanticArrayOp, type SemanticDateRangeOp, type SemanticFilter, type SemanticScalarOp, type SemanticTimeDimension, type UseAgentRunInput, type UseAgentRunResult, type UseProcedureRunInput, type UseProcedureRunOpts, type UseProcedureRunResult, type UseQueryInput, type UseQueryOpts, type UseQueryResult, type UseSemanticQueryInput, type UseSemanticQueryOpts, type UseSemanticQueryResult, _resetCustomerAppManifestCacheForTest, getCustomerAppDebug, getOxyAppLogger, interpretCustomerAppError, loadCustomerAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery };
1239
+ export { type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, AnomaliesClient, type Anomaly, type AnomalySeverity, type AnomalyStatus, type AppFetcher, type CustomerAppDebugSnapshot, type CustomerAppErrorReport, type DimensionOpportunity, type DriverAttribution, type DriverConfidence, type DriverDirection, type DriverForm, type DriverStrength, type EdgeKind, type EmailSendInput, type EmailSendResult, type ExplainConfigOverride, type ExplainNode, type ExplainRequest, type ExplainResult, type ExplainSibling, type ExplainWarning, type ListAnomaliesOptions, type ListAnomaliesResponse, type LoadManifestOptions, type MetricEdge, type MetricNode, type MetricTree, MetricTreeClient, type OpportunityRequest, type OpportunityResult, type OxyAirwayApi, OxyAnswer, type OxyAnswerProps, OxyApiError, type OxyAppFunctionManifest, type OxyAppLogLevel, type OxyAppLogger, type OxyAppManifest, OxyAppProvider, type OxyAppProviderProps, OxyChat, type OxyChatProps, type OxyEmailApi, type OxyFetchResult, type OxyFunctionContext, type OxyFunctionHandler, type OxyFunctionRequest, type OxyFunctionRow, type OxyFunctionUser, type OxyInjectedAppConfig, type OxySecretsApi, type OxySemanticApi, type OxyWarehouseApi, type PredictChange, type PredictImpact, type PredictResult, type ProcedureProgress, type ProcedureResult, type ProcedureRunState, type ResolvedCustomerAppManifest, type ScanOptions, type ScanResponse, type SegmentOpportunity, type SemanticArrayOp, type SemanticDateRangeOp, type SemanticFilter, type SemanticScalarOp, type SemanticTimeDimension, type SensitivityDriver, type SensitivityResult, type SkippedDimension, type SplitKind, type UseAgentRunInput, type UseAgentRunResult, type UseFunctionResult, type UseProcedureRunInput, type UseProcedureRunOpts, type UseProcedureRunResult, type UseQueryInput, type UseQueryOpts, type UseQueryResult, type UseSemanticQueryInput, type UseSemanticQueryOpts, type UseSemanticQueryResult, _resetCustomerAppManifestCacheForTest, apiErrorFromResponse, getCustomerAppDebug, getOxyAppLogger, interpretCustomerAppError, loadCustomerAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
534
1240
  //# sourceMappingURL=index.d.cts.map