@oxy-hq/sdk 2.5.0 → 2.6.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 +1 -1
- package/dist/index.cjs +8 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +206 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +206 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{react-CVdWXcu8.mjs → react-5HGW_0oy.mjs} +24 -24
- package/dist/react-5HGW_0oy.mjs.map +1 -0
- package/dist/{react-BeweGSYF.cjs → react-BAiiXftp.cjs} +29 -29
- package/dist/react-BAiiXftp.cjs.map +1 -0
- package/dist/{react-BnpR8VRJ.d.mts → react-DnBdQ8dG.d.cts} +16 -16
- package/dist/react-DnBdQ8dG.d.cts.map +1 -0
- package/dist/{react-BnpR8VRJ.d.cts → react-DnBdQ8dG.d.mts} +16 -16
- package/dist/react-DnBdQ8dG.d.mts.map +1 -0
- package/dist/shell.cjs +3 -3
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +3 -3
- package/dist/shell.d.mts +3 -3
- package/dist/shell.mjs +3 -3
- package/dist/shell.mjs.map +1 -1
- package/package.json +8 -8
- package/dist/react-BeweGSYF.cjs.map +0 -1
- package/dist/react-BnpR8VRJ.d.cts.map +0 -1
- package/dist/react-BnpR8VRJ.d.mts.map +0 -1
- package/dist/react-CVdWXcu8.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { A as UseSemanticQueryOpts, B as
|
|
2
|
+
import { A as UseSemanticQueryOpts, B as CustomAppErrorReport, C as UseProcedureRunInput, D as UseQueryOpts, E as UseQueryInput, F as useProcedureRun, G as OxyAppFunctionManifest, H as apiErrorFromResponse, I as useQuery, J as _resetCustomAppManifestCacheForTest, K as OxyAppManifest, L as useResolvedManifest, M as useAgentRun, N as useFunction, O as UseQueryResult, P as useOxyApp, R as useSemanticQuery, S as UseFunctionResult, T as UseProcedureRunResult, U as interpretCustomAppError, V as OxyApiError, W as LoadManifestOptions, Y as loadCustomAppManifest, _ as SemanticFilter, a as AppFetcher, b as UseAgentRunInput, c as OxyAppProvider, d as OxyChatProps, f as ProcedureProgress, g as SemanticDateRangeOp, h as SemanticArrayOp, i as AgentSqlArtifact, j as UseSemanticQueryResult, k as UseSemanticQueryInput, l as OxyAppProviderProps, m as ProcedureRunState, n as AgentRunEvent, o as OxyAnswer, p as ProcedureResult, q as ResolvedCustomAppManifest, r as AgentRunState, s as OxyAnswerProps, t as AgentArtifact, u as OxyChat, v as SemanticScalarOp, w as UseProcedureRunOpts, x as UseAgentRunResult, y as SemanticTimeDimension, z as useTrackEvent } from "./react-DnBdQ8dG.mjs";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Configuration for the Oxy SDK
|
|
@@ -432,11 +432,11 @@ declare class AnomaliesClient {
|
|
|
432
432
|
explain(anomalyId: string): Promise<ExplainResult>;
|
|
433
433
|
}
|
|
434
434
|
//#endregion
|
|
435
|
-
//#region src/
|
|
435
|
+
//#region src/custom-app/debug.d.ts
|
|
436
436
|
/** Untyped at the boundary — keep it loose so server-side schema
|
|
437
437
|
* additions don't break older bundles. Stable enough for inspection
|
|
438
438
|
* but not a contract clients should depend on field-by-field. */
|
|
439
|
-
interface
|
|
439
|
+
interface CustomAppDebugSnapshot {
|
|
440
440
|
org_slug: string;
|
|
441
441
|
app_slug: string;
|
|
442
442
|
app: {
|
|
@@ -460,13 +460,13 @@ interface CustomerAppDebugSnapshot {
|
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
462
462
|
* Fetch the server-side diagnostic snapshot for this bundle. Pair with
|
|
463
|
-
* `
|
|
463
|
+
* `loadCustomAppManifest()` — pass its result here. Logs the
|
|
464
464
|
* snapshot through the SDK logger so it appears in the bundle's
|
|
465
465
|
* console at info level.
|
|
466
466
|
*/
|
|
467
|
-
declare function
|
|
467
|
+
declare function getCustomAppDebug(resolved: ResolvedCustomAppManifest): Promise<CustomAppDebugSnapshot>;
|
|
468
468
|
//#endregion
|
|
469
|
-
//#region src/
|
|
469
|
+
//#region src/custom-app/function-context.d.ts
|
|
470
470
|
/**
|
|
471
471
|
* The request passed as the first argument to a function's default export.
|
|
472
472
|
*
|
|
@@ -486,6 +486,24 @@ interface OxyFunctionUser {
|
|
|
486
486
|
id: string;
|
|
487
487
|
email: string;
|
|
488
488
|
orgId: string;
|
|
489
|
+
/**
|
|
490
|
+
* The caller's role **within this app**, derived server-side from app
|
|
491
|
+
* membership (with org-owner / Oxy-staff break-glass). Absent when they hold
|
|
492
|
+
* no membership.
|
|
493
|
+
*
|
|
494
|
+
* This is the value to gate a privileged surface on — it cannot be forged by
|
|
495
|
+
* the client, unlike a query param or a client-side flag:
|
|
496
|
+
*
|
|
497
|
+
* ```ts
|
|
498
|
+
* if (ctx.user.appRole !== "admin") {
|
|
499
|
+
* return Response.json({ error: "forbidden" }, { status: 403 });
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* Note it is deliberately NOT the org role: an app admin administers one app
|
|
504
|
+
* without holding org-Admin (which also carries billing and member management).
|
|
505
|
+
*/
|
|
506
|
+
appRole?: "admin" | "member";
|
|
489
507
|
}
|
|
490
508
|
/** Result of a `ctx.fetch` call. */
|
|
491
509
|
interface OxyFetchResult {
|
|
@@ -539,6 +557,25 @@ interface EmailSendInput {
|
|
|
539
557
|
* background (retried) sends become exactly-once once it does.
|
|
540
558
|
*/
|
|
541
559
|
idempotencyKey?: string;
|
|
560
|
+
/**
|
|
561
|
+
* Files to attach. Max 20 per send, and **10 MiB decoded in total** — SES
|
|
562
|
+
* caps a whole message near 40 MB, so for anything larger store the file with
|
|
563
|
+
* {@link OxyStorageApi} and email a presigned link instead of inlining it.
|
|
564
|
+
*/
|
|
565
|
+
attachments?: EmailAttachment[];
|
|
566
|
+
}
|
|
567
|
+
/** One attachment on {@link EmailSendInput}. */
|
|
568
|
+
interface EmailAttachment {
|
|
569
|
+
/** Filename shown to the recipient. Required; path separators are stripped. */
|
|
570
|
+
filename: string;
|
|
571
|
+
/** File bytes, **base64-encoded** (the only way binary crosses the isolate boundary). */
|
|
572
|
+
content: string;
|
|
573
|
+
/** MIME type; defaults to `application/octet-stream`. */
|
|
574
|
+
contentType?: string;
|
|
575
|
+
/** Render inline (e.g. an image referenced as `cid:<contentId>`) instead of as a download. */
|
|
576
|
+
inline?: boolean;
|
|
577
|
+
/** Content-ID for an inline part, referenced from the HTML body as `cid:<contentId>`. */
|
|
578
|
+
contentId?: string;
|
|
542
579
|
}
|
|
543
580
|
/** Result of a successful `ctx.email.send`. */
|
|
544
581
|
interface EmailSendResult {
|
|
@@ -549,6 +586,164 @@ interface EmailSendResult {
|
|
|
549
586
|
interface OxyEmailApi {
|
|
550
587
|
send(input: EmailSendInput): Promise<EmailSendResult>;
|
|
551
588
|
}
|
|
589
|
+
/** Input to `ctx.storage.getUploadUrl`. */
|
|
590
|
+
interface StorageUploadUrlInput {
|
|
591
|
+
/**
|
|
592
|
+
* Destination path inside the app's silo, e.g. `"uploads/q1-report.pdf"`.
|
|
593
|
+
* Segments are sanitized server-side and cannot escape the silo. Omit to use
|
|
594
|
+
* `filename`, which is placed under `uploads/`.
|
|
595
|
+
*/
|
|
596
|
+
pathname?: string;
|
|
597
|
+
/** Shorthand for `pathname: "uploads/<filename>"`. */
|
|
598
|
+
filename?: string;
|
|
599
|
+
/** MIME type; bound into the presigned PUT signature. Inferred when omitted. */
|
|
600
|
+
contentType?: string;
|
|
601
|
+
/**
|
|
602
|
+
* Exact byte length of the upload, bound into the signature — S3 rejects a
|
|
603
|
+
* body of any other size. Capped by the server's upload ceiling (100 MiB by
|
|
604
|
+
* default).
|
|
605
|
+
*/
|
|
606
|
+
contentLength: number;
|
|
607
|
+
/** Presign lifetime in seconds (default 900; max 604800 — SigV4's own limit). */
|
|
608
|
+
expiresInSeconds?: number;
|
|
609
|
+
}
|
|
610
|
+
/** A minted presigned upload. */
|
|
611
|
+
interface StorageUploadUrl {
|
|
612
|
+
/** Presigned PUT — the browser uploads the file bytes directly to this URL. */
|
|
613
|
+
url: string;
|
|
614
|
+
/**
|
|
615
|
+
* The stored key. Record it (e.g. on a row in your warehouse) — it is how you
|
|
616
|
+
* fetch, list or link to the asset later. A random suffix is added so two
|
|
617
|
+
* people uploading `report.pdf` don't collide.
|
|
618
|
+
*/
|
|
619
|
+
key: string;
|
|
620
|
+
/** ISO-8601 expiry of the presigned URL. */
|
|
621
|
+
expiresAt: string;
|
|
622
|
+
}
|
|
623
|
+
/** A minted presigned download. */
|
|
624
|
+
interface StorageDownloadUrl {
|
|
625
|
+
url: string;
|
|
626
|
+
expiresAt: string;
|
|
627
|
+
}
|
|
628
|
+
/** One asset in the app's silo. */
|
|
629
|
+
interface StorageObject {
|
|
630
|
+
key: string;
|
|
631
|
+
size: number;
|
|
632
|
+
contentType?: string | null;
|
|
633
|
+
/** ISO-8601. */
|
|
634
|
+
lastModified?: string | null;
|
|
635
|
+
}
|
|
636
|
+
/** One page of {@link OxyStorageApi.list}. */
|
|
637
|
+
interface StorageListPage {
|
|
638
|
+
objects: StorageObject[];
|
|
639
|
+
/** Pass back as `cursor` to fetch the next page; `null` when complete. */
|
|
640
|
+
cursor: string | null;
|
|
641
|
+
hasMore: boolean;
|
|
642
|
+
}
|
|
643
|
+
/** Options for {@link OxyStorageApi.put}. */
|
|
644
|
+
interface StoragePutOptions {
|
|
645
|
+
/** MIME type. Inferred from the pathname's extension when omitted. */
|
|
646
|
+
contentType?: string;
|
|
647
|
+
/**
|
|
648
|
+
* How `body` is encoded. `"base64"` is what makes **binary** generated assets
|
|
649
|
+
* (PDF, PNG, Parquet) possible — a UTF-8 string would corrupt them.
|
|
650
|
+
*/
|
|
651
|
+
encoding?: "utf8" | "base64";
|
|
652
|
+
/** Append a short random component before the extension to avoid collisions. */
|
|
653
|
+
addRandomSuffix?: boolean;
|
|
654
|
+
/**
|
|
655
|
+
* Replace an existing asset at this path. Defaults to `false` — writing over
|
|
656
|
+
* an asset by accident is worse than an error, so this is opt-in.
|
|
657
|
+
*/
|
|
658
|
+
allowOverwrite?: boolean;
|
|
659
|
+
/** `Cache-Control: max-age=<seconds>` stored on the object. */
|
|
660
|
+
cacheControlMaxAge?: number;
|
|
661
|
+
}
|
|
662
|
+
/** Result of a `put` (and of `copy`). */
|
|
663
|
+
interface StoragePutResult {
|
|
664
|
+
key: string;
|
|
665
|
+
size: number;
|
|
666
|
+
contentType: string;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* `ctx.storage` — this app's **asset store**, covering both kinds of file an app
|
|
670
|
+
* produces, in one silo (`customer-app-storage/<app_id>/`):
|
|
671
|
+
*
|
|
672
|
+
* - **Uploaded** — a human picks a file; `getUploadUrl` mints a presigned PUT and
|
|
673
|
+
* the browser uploads **straight to S3**, so uploads aren't bounded by the
|
|
674
|
+
* request-body limit and the bytes never pass through your function.
|
|
675
|
+
* - **Generated** — your function produces the file (a rendered PDF, a CSV
|
|
676
|
+
* export, a chart PNG) and writes it with `put`, using
|
|
677
|
+
* `{ encoding: "base64" }` for binary.
|
|
678
|
+
*
|
|
679
|
+
* Gated by the fail-closed `storage.read` / `storage.write` capabilities in
|
|
680
|
+
* `oxy-app.json`. Every asset is private; reads are always presigned and
|
|
681
|
+
* time-boxed. Keys are confined to your app — another app's key is rejected.
|
|
682
|
+
*
|
|
683
|
+
* ```ts
|
|
684
|
+
* // Uploaded: mint a URL, browser PUTs to it, then record `key`.
|
|
685
|
+
* const { url, key } = await ctx.storage.getUploadUrl({
|
|
686
|
+
* filename: "q1-report.pdf", contentType: "application/pdf", contentLength: size,
|
|
687
|
+
* });
|
|
688
|
+
*
|
|
689
|
+
* // Generated: write a CSV your function just built.
|
|
690
|
+
* const { key } = await ctx.storage.put("generated/jan.csv", csv);
|
|
691
|
+
*
|
|
692
|
+
* // Either way: email a link that outlives the request.
|
|
693
|
+
* const { url: link } = await ctx.storage.getDownloadUrl(key, {
|
|
694
|
+
* expiresInSeconds: 604800, download: true,
|
|
695
|
+
* });
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
interface OxyStorageApi {
|
|
699
|
+
/** Mint a presigned PUT for a browser upload (requires `storage.write`). */
|
|
700
|
+
getUploadUrl(input: StorageUploadUrlInput): Promise<StorageUploadUrl>;
|
|
701
|
+
/**
|
|
702
|
+
* Mint a presigned GET (requires `storage.read`). `download: true` forces a
|
|
703
|
+
* save-as via `Content-Disposition`, which is what an emailed link wants.
|
|
704
|
+
*/
|
|
705
|
+
getDownloadUrl(key: string, opts?: {
|
|
706
|
+
expiresInSeconds?: number;
|
|
707
|
+
download?: boolean;
|
|
708
|
+
}): Promise<StorageDownloadUrl>;
|
|
709
|
+
/**
|
|
710
|
+
* Write a generated asset (requires `storage.write`). Capped at 6 MiB — for
|
|
711
|
+
* anything larger, mint a presigned upload URL and stream to it.
|
|
712
|
+
*/
|
|
713
|
+
put(pathname: string, body: string, opts?: StoragePutOptions): Promise<StoragePutResult>;
|
|
714
|
+
/** Read an asset back; `null` when absent (requires `storage.read`). */
|
|
715
|
+
get(key: string, opts?: {
|
|
716
|
+
encoding?: "utf8" | "base64";
|
|
717
|
+
}): Promise<{
|
|
718
|
+
body: string;
|
|
719
|
+
contentType: string | null;
|
|
720
|
+
size: number;
|
|
721
|
+
encoding: string;
|
|
722
|
+
} | null>;
|
|
723
|
+
/** Metadata without the body; `null` when absent (requires `storage.read`). */
|
|
724
|
+
head(key: string): Promise<StorageObject | null>;
|
|
725
|
+
/**
|
|
726
|
+
* One page of assets (requires `storage.read`). Paginated deliberately — pass
|
|
727
|
+
* the returned `cursor` back to walk a large silo without loading it all.
|
|
728
|
+
*/
|
|
729
|
+
list(opts?: {
|
|
730
|
+
prefix?: string;
|
|
731
|
+
limit?: number;
|
|
732
|
+
cursor?: string;
|
|
733
|
+
}): Promise<StorageListPage>;
|
|
734
|
+
/**
|
|
735
|
+
* Delete one or many assets (requires `storage.write`). Idempotent — deleting
|
|
736
|
+
* an absent key is a no-op success. `deleted` is the number of keys **accepted**
|
|
737
|
+
* for deletion (an absent key counts too), not a count of keys that existed.
|
|
738
|
+
*/
|
|
739
|
+
delete(keyOrKeys: string | string[]): Promise<{
|
|
740
|
+
deleted: number;
|
|
741
|
+
}>;
|
|
742
|
+
/** Server-side copy within the app's silo (requires `storage.write`). */
|
|
743
|
+
copy(fromKey: string, toPathname: string, opts?: {
|
|
744
|
+
allowOverwrite?: boolean;
|
|
745
|
+
}): Promise<StoragePutResult>;
|
|
746
|
+
}
|
|
552
747
|
/**
|
|
553
748
|
* The data-plane context passed as the second argument to a function's default
|
|
554
749
|
* export. Mirrors the host-assembled `ctx` (`__buildCtx` in `runtime.rs`);
|
|
@@ -574,14 +769,15 @@ interface OxyFunctionContext {
|
|
|
574
769
|
semantic: OxySemanticApi;
|
|
575
770
|
airway: OxyAirwayApi;
|
|
576
771
|
email: OxyEmailApi;
|
|
772
|
+
storage: OxyStorageApi;
|
|
577
773
|
}
|
|
578
774
|
/** Signature of a function's default export: `export default async (req, ctx) => Response`. */
|
|
579
775
|
type OxyFunctionHandler = (req: OxyFunctionRequest, ctx: OxyFunctionContext) => Promise<Response> | Response;
|
|
580
776
|
//#endregion
|
|
581
|
-
//#region src/
|
|
777
|
+
//#region src/custom-app/inject.d.ts
|
|
582
778
|
/**
|
|
583
779
|
* Shape of `window.__OXY_APP__` written by oxy at serve time.
|
|
584
|
-
* Consumed by `
|
|
780
|
+
* Consumed by `loadCustomAppManifest` as the authoritative identity
|
|
585
781
|
* source (overrides any hints in `oxy-app.json`).
|
|
586
782
|
*/
|
|
587
783
|
interface OxyInjectedAppConfig {
|
|
@@ -607,7 +803,7 @@ declare global {
|
|
|
607
803
|
*/
|
|
608
804
|
declare function readInjectedAppConfig(): OxyInjectedAppConfig | undefined;
|
|
609
805
|
//#endregion
|
|
610
|
-
//#region src/
|
|
806
|
+
//#region src/custom-app/logger.d.ts
|
|
611
807
|
type OxyAppLogLevel = "debug" | "info" | "warn" | "error";
|
|
612
808
|
interface OxyAppLogger {
|
|
613
809
|
log(level: OxyAppLogLevel, msg: string, ctx?: Record<string, unknown>): void;
|
|
@@ -617,5 +813,5 @@ declare function setOxyAppLogger(logger: OxyAppLogger | null): void;
|
|
|
617
813
|
/** Used by the SDK internals; not part of the public surface. */
|
|
618
814
|
declare function getOxyAppLogger(): OxyAppLogger;
|
|
619
815
|
//#endregion
|
|
620
|
-
export { type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, AnomaliesClient, type Anomaly, type AnomalySeverity, type AnomalyStatus, type AppFetcher, type
|
|
816
|
+
export { type AgentArtifact, type AgentRunEvent, type AgentRunState, type AgentSqlArtifact, AnomaliesClient, type Anomaly, type AnomalySeverity, type AnomalyStatus, type AppFetcher, type CustomAppDebugSnapshot, type CustomAppErrorReport, type DimensionOpportunity, type DriverAttribution, type DriverConfidence, type DriverDirection, type DriverForm, type DriverStrength, type EdgeKind, type EmailAttachment, 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 OxyStorageApi, type OxyWarehouseApi, type PredictChange, type PredictImpact, type PredictResult, type ProcedureProgress, type ProcedureResult, type ProcedureRunState, type ResolvedCustomAppManifest, 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 StorageDownloadUrl, type StorageListPage, type StorageObject, type StoragePutOptions, type StoragePutResult, type StorageUploadUrl, type StorageUploadUrlInput, type UseAgentRunInput, type UseAgentRunResult, type UseFunctionResult, type UseProcedureRunInput, type UseProcedureRunOpts, type UseProcedureRunResult, type UseQueryInput, type UseQueryOpts, type UseQueryResult, type UseSemanticQueryInput, type UseSemanticQueryOpts, type UseSemanticQueryResult, _resetCustomAppManifestCacheForTest, apiErrorFromResponse, getCustomAppDebug, getOxyAppLogger, interpretCustomAppError, loadCustomAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useOxyApp, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
|
|
621
817
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config.ts","../src/metricTree.ts","../src/anomalies.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config.ts","../src/metricTree.ts","../src/anomalies.ts","../src/custom-app/debug.ts","../src/custom-app/function-context.ts","../src/custom-app/inject.ts","../src/custom-app/logger.ts"],"mappings":";;;;;;UAGiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;;;EAKA;;;;;;;EAQA;;;;;EAMA;;;;KCjCU;KACA;KACA;KACA;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,MAAM;;EAEN;EACA,WAAW;EACX,UAAU;EACV,YAAY;EACZ;EACA,MAAM;EACN;EACA;EACA;EACA;;UAGe;EACf,OAAO;EACP,OAAO;EACP;;UAKe;EACf;EACA;EACA;EACA;EACA,OAAO;EACP,WAAW;EACX,UAAU;EACV;EACA;;UAGe;EACf;EACA,SAAS;;UAKM;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA,MAAM;EACN;;UAGe;EACf,QAAQ;EACR,SAAS;;KAKC;EACN;EAAmB;;EACnB;EAAmB;EAAmB;;EACtC;EAA6B;EAAmB;;EAChD;EAAuB;EAAmB;EAAe;;UAE9C;EACf,OAAO;EACP;EACA;EACA;;UAGe;EACf,OAAO;EACP;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;;UAGI;EACf;EACA;EACA;EACA;EACA;EACA,MAAM;EACN;EACA;;KAGU;EAEN;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;;UAGW;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,qBAAqB;EACrB;EACA,WAAW;;UAKI;EACf;EACA;EACA;EACA;EACA;;EAEA;;UAGe;EACf;EACA;;EAEA;EACA;EACA,UAAU;EACV;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;;EAEA;EACA,YAAY;EACZ,oBAAoB;EACpB,YAAY;;;;;;;KAUF,eAAa,GAAG,kBAAkB,UAAU,gBAAgB,QAAQ;;;;;;;;;;;;;;;;cAiBnE;mBACM;mBACA;EAEjB,YAAY,QAAQ,WAAW,SAAS;UAKhC;UAIA;;;;;;;;;;;;;EAmBF,QAAQ,gBAAgB,QAAQ;;;;;;;;;;;;;;EAkBhC,eAAe,oBAAoB,QAAQ;;;;;;;;;;;;EAkB3C,QAAQ,SAAS,kBAAkB,QAAQ;;;;;;;;;;;;;;;EAsB3C,QAAQ,SAAS,iBAAiB,QAAQ;;;;;;;;;;;;;;;;;EAwB1C,kBAAkB,SAAS,qBAAqB,QAAQ;;;;KC7VpD;KACA;;;;;;UAOK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;EACV,QAAQ;EACR;;EAEA,gBAAgB;EAChB;EACA;EACA;;UAGe;EACf,SAAS;;EAET;;UAGe;EACf,WAAW;;UAGI;;EAEf;;UAGe;EACf;EACA;EACA;;KAKU,aAAa,GAAG,kBAAkB,UAAU,gBAAgB,QAAQ;;;;;;;;;;;;;;cAenE;mBACM;mBACA;EAEjB,YAAY,QAAQ,WAAW,SAAS;UAKhC;UAIA;;;;;;;;;;EAgBF,KAAK,UAAS,uBAA4B,QAAQ;;;;;;;;;;;;;EAqBlD,KAAK,UAAS,cAAmB,QAAQ;;;;EAWzC,aAAa,mBAAmB,QAAQ,gBAAgB,QAAQ;;;;;EAYhE,QAAQ,oBAAoB,QAAQ;;;;;;;UCpI3B;EACf;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAEF;EACA;;EAEA,UAAU;EACV;EACA,UAAU;IAAQ;IAAc;;;;;;;;;iBASZ,kBACpB,UAAU,4BACT,QAAQ;;;;;;;;;;;UChBM;;EAEf;;;KAMU,iBAAiB;;UAGZ;EACf;EACA;EACA;;;;;;;;;;;;;;;;;;EAkBA;;;UAIe;EACf;EACA;;;UAIe;EACf,OAAO,kBAAkB,eAAe,MAAM,mBAAmB;EACjE,KAAK,kBAAkB,cAAc;EACrC,OACE,kBACA,eACA,MAAM,kBACN,4BACC;;;UAIY;EACf,IAAI,aAAa,gBAAgB;;;UAIlB;EACf,MAAM,MAAM,0BAA0B;;;UAIvB;EACf,IAAI,qBAAqB,YAAY,iCAAiC;IAAU;;;;;;;;;UAWjE;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;EAMA;;;;;;EAMA,cAAc;;;UAIC;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;;UAIe;EACf,KAAK,OAAO,iBAAiB,QAAQ;;;UAMtB;;;;;;EAMf;;EAEA;;EAEA;;;;;;EAMA;;EAEA;;;UAIe;;EAEf;;;;;;EAMA;;EAEA;;;UAIe;EACf;EACA;;;UAIe;EACf;EACA;EACA;;EAEA;;;UAIe;EACf,SAAS;;EAET;EACA;;;UAIe;;EAEf;;;;;EAKA;;EAEA;;;;;EAKA;;EAEA;;;UAIe;EACf;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCe;;EAEf,aAAa,OAAO,wBAAwB,QAAQ;;;;;EAKpD,eACE,aACA;IAAS;IAA2B;MACnC,QAAQ;;;;;EAKX,IAAI,kBAAkB,cAAc,OAAO,oBAAoB,QAAQ;;EAEvE,IACE,aACA;IAAS;MACR;IAAU;IAAc;IAA4B;IAAc;;;EAErE,KAAK,cAAc,QAAQ;;;;;EAK3B,KAAK;IAAS;IAAiB;IAAgB;MAAoB,QAAQ;;;;;;EAM3E,OAAO,+BAA+B;IAAU;;;EAEhD,KACE,iBACA,oBACA;IAAS;MACR,QAAQ;;;;;;;UAUI;;EAEf,MAAM;;EAEN,KAAK;;EAEL,OAAO;;EAEP,MAAM,cAAc,QAAQ;;EAE5B,YACE,aACA;IAAS;MACR,eAAe;;EAElB,MAAM,aAAa,OAAO,cAAc,QAAQ;EAChD,WAAW;EACX,SAAS;EACT,UAAU;EACV,QAAQ;EACR,OAAO;EACP,SAAS;;;KAIC,sBACV,KAAK,oBACL,KAAK,uBACF,QAAQ,YAAY;;;;;;;;UCxVR;EACf;EACA;EACA;EACA;EACA;EACA;;EAEA;;QAGM;YACI;IACR,cAAc;;;;;;;;;iBAUF,yBAAyB;;;KCpB7B;UAEK;EACf,IAAI,OAAO,gBAAgB,aAAa,MAAM;;;iBAMhC,gBAAgB,QAAQ;;iBAKxB,mBAAmB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @oxy/sdk - TypeScript SDK for Oxy data platform
|
|
2
|
-
import { _ as
|
|
2
|
+
import { _ as interpretCustomAppError, a as useFunction, c as useQuery, d as useTrackEvent, f as _resetCustomAppManifestCacheForTest, g as apiErrorFromResponse, h as OxyApiError, i as useAgentRun, l as useResolvedManifest, m as readInjectedAppConfig, n as OxyAppProvider, o as useOxyApp, p as loadCustomAppManifest, r as OxyChat, s as useProcedureRun, t as OxyAnswer, u as useSemanticQuery, v as getOxyAppLogger, y as setOxyAppLogger } from "./react-5HGW_0oy.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/anomalies.ts
|
|
5
5
|
/**
|
|
@@ -82,14 +82,14 @@ var AnomaliesClient = class {
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
//#endregion
|
|
85
|
-
//#region src/
|
|
85
|
+
//#region src/custom-app/debug.ts
|
|
86
86
|
/**
|
|
87
87
|
* Fetch the server-side diagnostic snapshot for this bundle. Pair with
|
|
88
|
-
* `
|
|
88
|
+
* `loadCustomAppManifest()` — pass its result here. Logs the
|
|
89
89
|
* snapshot through the SDK logger so it appears in the bundle's
|
|
90
90
|
* console at info level.
|
|
91
91
|
*/
|
|
92
|
-
async function
|
|
92
|
+
async function getCustomAppDebug(resolved) {
|
|
93
93
|
const log = getOxyAppLogger();
|
|
94
94
|
const { apiBaseUrl, orgSlug, appSlug } = resolved;
|
|
95
95
|
const url = `${apiBaseUrl}/api/customer-apps/${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;
|
|
@@ -233,5 +233,5 @@ var MetricTreeClient = class {
|
|
|
233
233
|
};
|
|
234
234
|
|
|
235
235
|
//#endregion
|
|
236
|
-
export { AnomaliesClient, MetricTreeClient, OxyAnswer, OxyApiError, OxyAppProvider, OxyChat,
|
|
236
|
+
export { AnomaliesClient, MetricTreeClient, OxyAnswer, OxyApiError, OxyAppProvider, OxyChat, _resetCustomAppManifestCacheForTest, apiErrorFromResponse, getCustomAppDebug, getOxyAppLogger, interpretCustomAppError, loadCustomAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useOxyApp, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
|
|
237
237
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/anomalies.ts","../src/customer-app/debug.ts","../src/metricTree.ts"],"sourcesContent":["// Anomaly inbox types + client. Surfaces the `/semantic/anomalies*`\n// endpoints — list, scan, status, explain — so SDK consumers can render\n// the same inbox the Oxy IDE uses.\n\nimport type { OxyConfig } from \"./config\";\nimport type { ExplainResult } from \"./metricTree\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnomalyStatus = \"new\" | \"acknowledged\" | \"dismissed\";\nexport type AnomalySeverity = \"low\" | \"medium\" | \"high\";\n\n/**\n * One row in the anomaly inbox. Detected by `oxy-metric-monitoring` per\n * `.monitor.yml` entry; upserted by repeat scans so unresolved anomalies\n * stay visible without piling up duplicates.\n */\nexport interface Anomaly {\n id: string;\n workspace_id: string;\n measure: string;\n time_dimension: string;\n granularity: string;\n period_start: string;\n period_end: string;\n observed: number;\n expected: number;\n lower_bound: number;\n upper_bound: number;\n z_score: number;\n severity: AnomalySeverity | string;\n status: AnomalyStatus | string;\n label?: string | null;\n /** Cached ExplainResult — populated by `POST /anomalies/:id/explain`. */\n explain_cache?: ExplainResult | null;\n explain_cached_at?: string | null;\n detected_at: string;\n updated_at: string;\n}\n\nexport interface ListAnomaliesOptions {\n status?: AnomalyStatus | string;\n /** Max rows (server caps at 500, defaults to 100). */\n limit?: number;\n}\n\nexport interface ListAnomaliesResponse {\n anomalies: Anomaly[];\n}\n\nexport interface ScanOptions {\n /** Override the reference \"now\" date (YYYY-MM-DD) — useful for demos. */\n as_of?: string;\n}\n\nexport interface ScanResponse {\n monitors_scanned: number;\n monitors_failed: number;\n anomalies_persisted: number;\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for `/semantic/anomalies*`. Construct via `OxyClient.anomalies`\n * rather than instantiating directly — the getter wires the request helper\n * so auth, timeout, and branch propagation come along for free.\n *\n * @example\n * ```typescript\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * for (const a of anomalies) {\n * console.log(a.label ?? a.measure, a.severity, a.z_score.toFixed(2));\n * }\n * ```\n */\nexport class AnomaliesClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/anomalies${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * List anomalies in the inbox, newest first.\n *\n * @example\n * ```typescript\n * // Open / unresolved anomalies only\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * ```\n */\n async list(options: ListAnomaliesOptions = {}): Promise<ListAnomaliesResponse> {\n const extra: Record<string, string> = {};\n if (options.status) extra.status = options.status;\n if (options.limit) extra.limit = String(options.limit);\n // No trailing slash before the query — axum 307-redirects \"/anomalies/\"\n // to \"/anomalies\", and the redirect fails CORS preflight in browsers.\n return this.request<ListAnomaliesResponse>(this.path(this.buildQuery(extra)));\n }\n\n /**\n * Trigger a full scan. Iterates every `.monitor.yml` entry in the\n * workspace, runs the detector, and upserts matching rows into the\n * inbox. Returns counts of scanned / failed / persisted.\n *\n * @example\n * ```typescript\n * // Scan against a known-good reference date (matches the seed dataset)\n * const result = await client.anomalies.scan({ as_of: \"2025-12-15\" });\n * console.log(`${result.anomalies_persisted} anomalies detected`);\n * ```\n */\n async scan(options: ScanOptions = {}): Promise<ScanResponse> {\n const extra: Record<string, string> = {};\n if (options.as_of) extra.as_of = options.as_of;\n return this.request<ScanResponse>(this.path(`/scan${this.buildQuery(extra)}`), {\n method: \"POST\"\n });\n }\n\n /**\n * Update an anomaly's status (acknowledge / dismiss / re-open).\n */\n async updateStatus(anomalyId: string, status: AnomalyStatus): Promise<Anomaly> {\n const query = this.buildQuery();\n return this.request<Anomaly>(this.path(`/${encodeURIComponent(anomalyId)}/status${query}`), {\n method: \"POST\",\n body: JSON.stringify({ status })\n });\n }\n\n /**\n * Run the metric-tree `explain` for an anomaly and cache the result on\n * the row. Subsequent calls return the cached `ExplainResult` instantly.\n */\n async explain(anomalyId: string): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(\n this.path(`/${encodeURIComponent(anomalyId)}/explain${query}`),\n { method: \"POST\" }\n );\n }\n}\n","// Bundle-side accessor for the server's diagnostic snapshot.\n//\n// `GET /api/customer-apps/<org>/<app>/debug` returns a structured\n// snapshot of what oxy currently sees about a registered customer\n// app: the app row, bundle dir resolution, and parsed manifest (or\n// parse error). Useful when a bundle isn't loading what you expected\n// and you want to verify what the server actually sees — without\n// needing terminal access.\n//\n// The `products` field on the snapshot is a legacy artifact carried\n// for server-side compatibility; in v2 the bundle owns its queries\n// via `useQuery` and the field is always empty for v2 manifests.\n\nimport { getOxyAppLogger } from \"./logger\";\nimport type { ResolvedCustomerAppManifest } from \"./manifest\";\n\n/** Untyped at the boundary — keep it loose so server-side schema\n * additions don't break older bundles. Stable enough for inspection\n * but not a contract clients should depend on field-by-field. */\nexport interface CustomerAppDebugSnapshot {\n org_slug: string;\n app_slug: string;\n app: {\n id: string;\n slug: string;\n name: string;\n status: string;\n source_type: string;\n project_id: string;\n branch: string;\n };\n bundle_dir: string | null;\n bundle_dir_exists: boolean;\n /** Raw parsed manifest from the server — kept loose so schema additions don't break older bundles. */\n manifest: Record<string, unknown> | null;\n manifest_error: string | null;\n products: Array<{ name: string; producer: string }>;\n}\n\n/**\n * Fetch the server-side diagnostic snapshot for this bundle. Pair with\n * `loadCustomerAppManifest()` — pass its result here. Logs the\n * snapshot through the SDK logger so it appears in the bundle's\n * console at info level.\n */\nexport async function getCustomerAppDebug(\n resolved: ResolvedCustomerAppManifest\n): Promise<CustomerAppDebugSnapshot> {\n const log = getOxyAppLogger();\n const { apiBaseUrl, orgSlug, appSlug } = resolved;\n const url =\n `${apiBaseUrl}/api/customer-apps/` +\n `${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;\n\n log.log(\"debug\", \"fetching debug snapshot\", { url });\n const res = await fetch(url, { credentials: \"same-origin\" });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch debug snapshot (HTTP ${res.status}): ${detail || res.statusText}`\n );\n }\n const snapshot = (await res.json()) as CustomerAppDebugSnapshot;\n log.log(\"info\", \"debug snapshot\", snapshot as unknown as Record<string, unknown>);\n return snapshot;\n}\n","// Metric-tree types + client. Mirrors `airlayer::engine::metric_tree*`\n// over the `/<project_id>/semantic/metric-tree*` HTTP endpoints. Serde\n// emits snake_case so these field names match the wire format verbatim.\n\nimport type { OxyConfig } from \"./config\";\n\n// ── Tree ──────────────────────────────────────────────────────────────────────\n\nexport type EdgeKind = \"component\" | \"driver\";\nexport type DriverDirection = \"positive\" | \"negative\" | \"unknown\";\nexport type DriverStrength = \"strong\" | \"moderate\" | \"weak\";\nexport type DriverConfidence = \"high\" | \"medium\" | \"low\";\nexport type DriverForm = \"linear\" | \"log-log\" | \"log-linear\" | \"linear-log\";\n\nexport interface MetricNode {\n id: string;\n view: string;\n measure: string;\n label: string;\n description?: string | null;\n measure_type: string;\n is_composite: boolean;\n expr?: string | null;\n}\n\nexport interface MetricEdge {\n from: string;\n to: string;\n kind: EdgeKind;\n /** Sign of a component edge; omitted (defaults to +1) for most edges. */\n sign?: number;\n direction: DriverDirection;\n strength: DriverStrength;\n confidence: DriverConfidence;\n coefficient?: number | null;\n form: DriverForm;\n intercept?: number | null;\n lag?: number | null;\n description?: string | null;\n refs?: string[] | null;\n}\n\nexport interface MetricTree {\n nodes: MetricNode[];\n edges: MetricEdge[];\n root?: string | null;\n}\n\n// ── Sensitivity ──────────────────────────────────────────────────────────────\n\nexport interface SensitivityDriver {\n measure: string;\n path: string[];\n edge_kind: string;\n effective_coefficient?: number | null;\n form?: DriverForm | null;\n direction: DriverDirection;\n strength: DriverStrength;\n lag?: number | null;\n description?: string | null;\n}\n\nexport interface SensitivityResult {\n target: string;\n drivers: SensitivityDriver[];\n}\n\n// ── Predict ──────────────────────────────────────────────────────────────────\n\nexport interface PredictChange {\n measure: string;\n delta: number;\n}\n\nexport interface PredictImpact {\n measure: string;\n estimated_delta: number;\n confidence: string;\n path: string[];\n form: DriverForm;\n lag?: number | null;\n}\n\nexport interface PredictResult {\n inputs: PredictChange[];\n impacts: PredictImpact[];\n}\n\n// ── Explain (RCA) ────────────────────────────────────────────────────────────\n\nexport type SplitKind =\n | { type: \"component\"; child_measure: string }\n | { type: \"dimension\"; dimension: string; value: string }\n | { type: \"uniform_degradation\"; dimension: string; num_elements: number }\n | { type: \"cross_cutting\"; dimension: string; value: string; measures: string[] };\n\nexport interface ExplainSibling {\n split: SplitKind;\n measure: string;\n delta: number;\n root_fraction: number;\n}\n\nexport interface ExplainNode {\n split: SplitKind;\n measure: string;\n filters: unknown[];\n delta: number;\n concentration: number;\n root_fraction: number;\n siblings?: ExplainSibling[];\n dimension_count?: number;\n children?: ExplainNode[];\n}\n\nexport interface DriverAttribution {\n driver_measure: string;\n driver_previous: number;\n driver_current: number;\n driver_delta: number;\n coefficient?: number;\n form: DriverForm;\n estimated_target_impact?: number;\n description?: string;\n}\n\nexport type ExplainWarning =\n | {\n type: \"simpsons_paradox\";\n dimension: string;\n aggregate_delta: number;\n segment_directions: [string, number][];\n }\n | {\n type: \"opposing_offset\";\n component_a: string;\n component_b: string;\n delta_a: number;\n delta_b: number;\n }\n | {\n type: \"non_additive_dimension_split\";\n measure: string;\n measure_type: string;\n dimension: string;\n };\n\nexport interface ExplainConfigOverride {\n deep?: boolean;\n max_depth?: number;\n coverage_threshold?: number;\n}\n\nexport interface ExplainRequest {\n target: string;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n config?: ExplainConfigOverride;\n}\n\nexport interface ExplainResult {\n target: string;\n target_delta: number;\n target_previous: number;\n target_current: number;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n nodes: ExplainNode[];\n coverage: number;\n driver_attribution?: DriverAttribution[];\n alternatives?: unknown[];\n warnings?: ExplainWarning[];\n}\n\n// ── Opportunity ──────────────────────────────────────────────────────────────\n\nexport interface SegmentOpportunity {\n segment: string;\n current_value: number;\n volume: number;\n benchmark: number;\n gap: number;\n /** Match-the-best upside in measure units. */\n upside: number;\n}\n\nexport interface DimensionOpportunity {\n dimension: string;\n cardinality: number;\n /** \"best_peer\" or \"p75\". */\n benchmark_basis: string;\n total_upside: number;\n segments: SegmentOpportunity[];\n other_segments_skipped: number;\n}\n\nexport interface SkippedDimension {\n dimension: string;\n reason: string;\n}\n\nexport interface OpportunityRequest {\n target: string;\n time_dimension: string;\n period: [string, string];\n}\n\nexport interface OpportunityResult {\n target: string;\n period: [string, string];\n overall_value: number;\n /** \"value_share\" (additive) or \"equal\" (ratios). */\n weight_basis: string;\n dimensions: DimensionOpportunity[];\n skipped_dimensions: SkippedDimension[];\n downstream: PredictImpact[];\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\n/**\n * Shape of the inner request helper exposed by `OxyClient`. The metric-tree\n * client reuses it to inherit auth headers, timeout, baseUrl, and project\n * scoping rather than reimplementing fetch end-to-end.\n */\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for the `/semantic/metric-tree*` endpoints. Surfaces the four\n * airlayer metric-tree analyses (tree introspection, sensitivity, predict,\n * explain, opportunity) over typed methods.\n *\n * Construction is internal to {@link OxyClient} — call `client.metricTree`\n * to access an instance rather than building one yourself.\n *\n * @example\n * ```typescript\n * const client = await OxyClient.create({ projectId: \"...\", apiKey: \"...\" });\n * const tree = await client.metricTree.getTree();\n * const drivers = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * ```\n */\nexport class MetricTreeClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/metric-tree${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * Fetch the full metric tree, or the subtree rooted at `root`.\n *\n * @param root - Optional fully-qualified measure id to root the tree at.\n * @returns Nodes (measures) and edges (component / driver relationships).\n *\n * @example\n * ```typescript\n * const tree = await client.metricTree.getTree();\n * const subtree = await client.metricTree.getTree(\"orders.net_revenue\");\n * ```\n */\n async getTree(root?: string): Promise<MetricTree> {\n const query = this.buildQuery(root ? { root } : {});\n return this.request<MetricTree>(this.path(query));\n }\n\n /**\n * Rank the declared drivers of a measure by influence.\n *\n * @param measureId - Fully-qualified measure id (`view.measure`).\n *\n * @example\n * ```typescript\n * const sensitivity = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * for (const driver of sensitivity.drivers) {\n * console.log(driver.measure, driver.direction, driver.strength);\n * }\n * ```\n */\n async getSensitivity(measureId: string): Promise<SensitivityResult> {\n const query = this.buildQuery();\n return this.request<SensitivityResult>(\n this.path(`/${encodeURIComponent(measureId)}/sensitivity${query}`)\n );\n }\n\n /**\n * Propagate hypothetical `(measure, delta)` changes upward through the\n * tree. Returns the estimated impact on every downstream measure.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.predict([\n * { measure: \"marketing_spend.total_spend\", delta: 10000 },\n * ]);\n * ```\n */\n async predict(changes: PredictChange[]): Promise<PredictResult> {\n const query = this.buildQuery();\n return this.request<PredictResult>(this.path(`/predict${query}`), {\n method: \"POST\",\n body: JSON.stringify({ changes })\n });\n }\n\n /**\n * Period-over-period root-cause decomposition. Recursively splits the\n * target measure by components and dimensions until the move concentrates.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.explain({\n * target: \"financials.operating_profit\",\n * time_dimension: \"financials.month\",\n * current_period: [\"2025-09-01\", \"2025-09-30\"],\n * previous_period: [\"2025-08-01\", \"2025-08-31\"],\n * });\n * ```\n */\n async explain(request: ExplainRequest): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(this.path(`/explain${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n\n /**\n * Size the upside opportunity for a measure by finding underperforming\n * segments. Skips high-cardinality dimensions and trims the long tail.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.findOpportunities({\n * target: \"orders.net_revenue\",\n * time_dimension: \"orders.order_date\",\n * period: [\"2025-09-01\", \"2025-09-30\"],\n * });\n * for (const dim of result.dimensions) {\n * console.log(dim.dimension, \"+\", dim.total_upside);\n * }\n * ```\n */\n async findOpportunities(request: OpportunityRequest): Promise<OpportunityResult> {\n const query = this.buildQuery();\n return this.request<OpportunityResult>(this.path(`/opportunity${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/anomalies.ts","../src/custom-app/debug.ts","../src/metricTree.ts"],"sourcesContent":["// Anomaly inbox types + client. Surfaces the `/semantic/anomalies*`\n// endpoints — list, scan, status, explain — so SDK consumers can render\n// the same inbox the Oxy IDE uses.\n\nimport type { OxyConfig } from \"./config\";\nimport type { ExplainResult } from \"./metricTree\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnomalyStatus = \"new\" | \"acknowledged\" | \"dismissed\";\nexport type AnomalySeverity = \"low\" | \"medium\" | \"high\";\n\n/**\n * One row in the anomaly inbox. Detected by `oxy-metric-monitoring` per\n * `.monitor.yml` entry; upserted by repeat scans so unresolved anomalies\n * stay visible without piling up duplicates.\n */\nexport interface Anomaly {\n id: string;\n workspace_id: string;\n measure: string;\n time_dimension: string;\n granularity: string;\n period_start: string;\n period_end: string;\n observed: number;\n expected: number;\n lower_bound: number;\n upper_bound: number;\n z_score: number;\n severity: AnomalySeverity | string;\n status: AnomalyStatus | string;\n label?: string | null;\n /** Cached ExplainResult — populated by `POST /anomalies/:id/explain`. */\n explain_cache?: ExplainResult | null;\n explain_cached_at?: string | null;\n detected_at: string;\n updated_at: string;\n}\n\nexport interface ListAnomaliesOptions {\n status?: AnomalyStatus | string;\n /** Max rows (server caps at 500, defaults to 100). */\n limit?: number;\n}\n\nexport interface ListAnomaliesResponse {\n anomalies: Anomaly[];\n}\n\nexport interface ScanOptions {\n /** Override the reference \"now\" date (YYYY-MM-DD) — useful for demos. */\n as_of?: string;\n}\n\nexport interface ScanResponse {\n monitors_scanned: number;\n monitors_failed: number;\n anomalies_persisted: number;\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for `/semantic/anomalies*`. Construct via `OxyClient.anomalies`\n * rather than instantiating directly — the getter wires the request helper\n * so auth, timeout, and branch propagation come along for free.\n *\n * @example\n * ```typescript\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * for (const a of anomalies) {\n * console.log(a.label ?? a.measure, a.severity, a.z_score.toFixed(2));\n * }\n * ```\n */\nexport class AnomaliesClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/anomalies${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * List anomalies in the inbox, newest first.\n *\n * @example\n * ```typescript\n * // Open / unresolved anomalies only\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * ```\n */\n async list(options: ListAnomaliesOptions = {}): Promise<ListAnomaliesResponse> {\n const extra: Record<string, string> = {};\n if (options.status) extra.status = options.status;\n if (options.limit) extra.limit = String(options.limit);\n // No trailing slash before the query — axum 307-redirects \"/anomalies/\"\n // to \"/anomalies\", and the redirect fails CORS preflight in browsers.\n return this.request<ListAnomaliesResponse>(this.path(this.buildQuery(extra)));\n }\n\n /**\n * Trigger a full scan. Iterates every `.monitor.yml` entry in the\n * workspace, runs the detector, and upserts matching rows into the\n * inbox. Returns counts of scanned / failed / persisted.\n *\n * @example\n * ```typescript\n * // Scan against a known-good reference date (matches the seed dataset)\n * const result = await client.anomalies.scan({ as_of: \"2025-12-15\" });\n * console.log(`${result.anomalies_persisted} anomalies detected`);\n * ```\n */\n async scan(options: ScanOptions = {}): Promise<ScanResponse> {\n const extra: Record<string, string> = {};\n if (options.as_of) extra.as_of = options.as_of;\n return this.request<ScanResponse>(this.path(`/scan${this.buildQuery(extra)}`), {\n method: \"POST\"\n });\n }\n\n /**\n * Update an anomaly's status (acknowledge / dismiss / re-open).\n */\n async updateStatus(anomalyId: string, status: AnomalyStatus): Promise<Anomaly> {\n const query = this.buildQuery();\n return this.request<Anomaly>(this.path(`/${encodeURIComponent(anomalyId)}/status${query}`), {\n method: \"POST\",\n body: JSON.stringify({ status })\n });\n }\n\n /**\n * Run the metric-tree `explain` for an anomaly and cache the result on\n * the row. Subsequent calls return the cached `ExplainResult` instantly.\n */\n async explain(anomalyId: string): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(\n this.path(`/${encodeURIComponent(anomalyId)}/explain${query}`),\n { method: \"POST\" }\n );\n }\n}\n","// Bundle-side accessor for the server's diagnostic snapshot.\n//\n// `GET /api/customer-apps/<org>/<app>/debug` returns a structured\n// snapshot of what oxy currently sees about a registered customer\n// app: the app row, bundle dir resolution, and parsed manifest (or\n// parse error). Useful when a bundle isn't loading what you expected\n// and you want to verify what the server actually sees — without\n// needing terminal access.\n//\n// The `products` field on the snapshot is a legacy artifact carried\n// for server-side compatibility; in v2 the bundle owns its queries\n// via `useQuery` and the field is always empty for v2 manifests.\n\nimport { getOxyAppLogger } from \"./logger\";\nimport type { ResolvedCustomAppManifest } from \"./manifest\";\n\n/** Untyped at the boundary — keep it loose so server-side schema\n * additions don't break older bundles. Stable enough for inspection\n * but not a contract clients should depend on field-by-field. */\nexport interface CustomAppDebugSnapshot {\n org_slug: string;\n app_slug: string;\n app: {\n id: string;\n slug: string;\n name: string;\n status: string;\n source_type: string;\n project_id: string;\n branch: string;\n };\n bundle_dir: string | null;\n bundle_dir_exists: boolean;\n /** Raw parsed manifest from the server — kept loose so schema additions don't break older bundles. */\n manifest: Record<string, unknown> | null;\n manifest_error: string | null;\n products: Array<{ name: string; producer: string }>;\n}\n\n/**\n * Fetch the server-side diagnostic snapshot for this bundle. Pair with\n * `loadCustomAppManifest()` — pass its result here. Logs the\n * snapshot through the SDK logger so it appears in the bundle's\n * console at info level.\n */\nexport async function getCustomAppDebug(\n resolved: ResolvedCustomAppManifest\n): Promise<CustomAppDebugSnapshot> {\n const log = getOxyAppLogger();\n const { apiBaseUrl, orgSlug, appSlug } = resolved;\n const url =\n `${apiBaseUrl}/api/customer-apps/` +\n `${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;\n\n log.log(\"debug\", \"fetching debug snapshot\", { url });\n const res = await fetch(url, { credentials: \"same-origin\" });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch debug snapshot (HTTP ${res.status}): ${detail || res.statusText}`\n );\n }\n const snapshot = (await res.json()) as CustomAppDebugSnapshot;\n log.log(\"info\", \"debug snapshot\", snapshot as unknown as Record<string, unknown>);\n return snapshot;\n}\n","// Metric-tree types + client. Mirrors `airlayer::engine::metric_tree*`\n// over the `/<project_id>/semantic/metric-tree*` HTTP endpoints. Serde\n// emits snake_case so these field names match the wire format verbatim.\n\nimport type { OxyConfig } from \"./config\";\n\n// ── Tree ──────────────────────────────────────────────────────────────────────\n\nexport type EdgeKind = \"component\" | \"driver\";\nexport type DriverDirection = \"positive\" | \"negative\" | \"unknown\";\nexport type DriverStrength = \"strong\" | \"moderate\" | \"weak\";\nexport type DriverConfidence = \"high\" | \"medium\" | \"low\";\nexport type DriverForm = \"linear\" | \"log-log\" | \"log-linear\" | \"linear-log\";\n\nexport interface MetricNode {\n id: string;\n view: string;\n measure: string;\n label: string;\n description?: string | null;\n measure_type: string;\n is_composite: boolean;\n expr?: string | null;\n}\n\nexport interface MetricEdge {\n from: string;\n to: string;\n kind: EdgeKind;\n /** Sign of a component edge; omitted (defaults to +1) for most edges. */\n sign?: number;\n direction: DriverDirection;\n strength: DriverStrength;\n confidence: DriverConfidence;\n coefficient?: number | null;\n form: DriverForm;\n intercept?: number | null;\n lag?: number | null;\n description?: string | null;\n refs?: string[] | null;\n}\n\nexport interface MetricTree {\n nodes: MetricNode[];\n edges: MetricEdge[];\n root?: string | null;\n}\n\n// ── Sensitivity ──────────────────────────────────────────────────────────────\n\nexport interface SensitivityDriver {\n measure: string;\n path: string[];\n edge_kind: string;\n effective_coefficient?: number | null;\n form?: DriverForm | null;\n direction: DriverDirection;\n strength: DriverStrength;\n lag?: number | null;\n description?: string | null;\n}\n\nexport interface SensitivityResult {\n target: string;\n drivers: SensitivityDriver[];\n}\n\n// ── Predict ──────────────────────────────────────────────────────────────────\n\nexport interface PredictChange {\n measure: string;\n delta: number;\n}\n\nexport interface PredictImpact {\n measure: string;\n estimated_delta: number;\n confidence: string;\n path: string[];\n form: DriverForm;\n lag?: number | null;\n}\n\nexport interface PredictResult {\n inputs: PredictChange[];\n impacts: PredictImpact[];\n}\n\n// ── Explain (RCA) ────────────────────────────────────────────────────────────\n\nexport type SplitKind =\n | { type: \"component\"; child_measure: string }\n | { type: \"dimension\"; dimension: string; value: string }\n | { type: \"uniform_degradation\"; dimension: string; num_elements: number }\n | { type: \"cross_cutting\"; dimension: string; value: string; measures: string[] };\n\nexport interface ExplainSibling {\n split: SplitKind;\n measure: string;\n delta: number;\n root_fraction: number;\n}\n\nexport interface ExplainNode {\n split: SplitKind;\n measure: string;\n filters: unknown[];\n delta: number;\n concentration: number;\n root_fraction: number;\n siblings?: ExplainSibling[];\n dimension_count?: number;\n children?: ExplainNode[];\n}\n\nexport interface DriverAttribution {\n driver_measure: string;\n driver_previous: number;\n driver_current: number;\n driver_delta: number;\n coefficient?: number;\n form: DriverForm;\n estimated_target_impact?: number;\n description?: string;\n}\n\nexport type ExplainWarning =\n | {\n type: \"simpsons_paradox\";\n dimension: string;\n aggregate_delta: number;\n segment_directions: [string, number][];\n }\n | {\n type: \"opposing_offset\";\n component_a: string;\n component_b: string;\n delta_a: number;\n delta_b: number;\n }\n | {\n type: \"non_additive_dimension_split\";\n measure: string;\n measure_type: string;\n dimension: string;\n };\n\nexport interface ExplainConfigOverride {\n deep?: boolean;\n max_depth?: number;\n coverage_threshold?: number;\n}\n\nexport interface ExplainRequest {\n target: string;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n config?: ExplainConfigOverride;\n}\n\nexport interface ExplainResult {\n target: string;\n target_delta: number;\n target_previous: number;\n target_current: number;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n nodes: ExplainNode[];\n coverage: number;\n driver_attribution?: DriverAttribution[];\n alternatives?: unknown[];\n warnings?: ExplainWarning[];\n}\n\n// ── Opportunity ──────────────────────────────────────────────────────────────\n\nexport interface SegmentOpportunity {\n segment: string;\n current_value: number;\n volume: number;\n benchmark: number;\n gap: number;\n /** Match-the-best upside in measure units. */\n upside: number;\n}\n\nexport interface DimensionOpportunity {\n dimension: string;\n cardinality: number;\n /** \"best_peer\" or \"p75\". */\n benchmark_basis: string;\n total_upside: number;\n segments: SegmentOpportunity[];\n other_segments_skipped: number;\n}\n\nexport interface SkippedDimension {\n dimension: string;\n reason: string;\n}\n\nexport interface OpportunityRequest {\n target: string;\n time_dimension: string;\n period: [string, string];\n}\n\nexport interface OpportunityResult {\n target: string;\n period: [string, string];\n overall_value: number;\n /** \"value_share\" (additive) or \"equal\" (ratios). */\n weight_basis: string;\n dimensions: DimensionOpportunity[];\n skipped_dimensions: SkippedDimension[];\n downstream: PredictImpact[];\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\n/**\n * Shape of the inner request helper exposed by `OxyClient`. The metric-tree\n * client reuses it to inherit auth headers, timeout, baseUrl, and project\n * scoping rather than reimplementing fetch end-to-end.\n */\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for the `/semantic/metric-tree*` endpoints. Surfaces the four\n * airlayer metric-tree analyses (tree introspection, sensitivity, predict,\n * explain, opportunity) over typed methods.\n *\n * Construction is internal to {@link OxyClient} — call `client.metricTree`\n * to access an instance rather than building one yourself.\n *\n * @example\n * ```typescript\n * const client = await OxyClient.create({ projectId: \"...\", apiKey: \"...\" });\n * const tree = await client.metricTree.getTree();\n * const drivers = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * ```\n */\nexport class MetricTreeClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/metric-tree${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * Fetch the full metric tree, or the subtree rooted at `root`.\n *\n * @param root - Optional fully-qualified measure id to root the tree at.\n * @returns Nodes (measures) and edges (component / driver relationships).\n *\n * @example\n * ```typescript\n * const tree = await client.metricTree.getTree();\n * const subtree = await client.metricTree.getTree(\"orders.net_revenue\");\n * ```\n */\n async getTree(root?: string): Promise<MetricTree> {\n const query = this.buildQuery(root ? { root } : {});\n return this.request<MetricTree>(this.path(query));\n }\n\n /**\n * Rank the declared drivers of a measure by influence.\n *\n * @param measureId - Fully-qualified measure id (`view.measure`).\n *\n * @example\n * ```typescript\n * const sensitivity = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * for (const driver of sensitivity.drivers) {\n * console.log(driver.measure, driver.direction, driver.strength);\n * }\n * ```\n */\n async getSensitivity(measureId: string): Promise<SensitivityResult> {\n const query = this.buildQuery();\n return this.request<SensitivityResult>(\n this.path(`/${encodeURIComponent(measureId)}/sensitivity${query}`)\n );\n }\n\n /**\n * Propagate hypothetical `(measure, delta)` changes upward through the\n * tree. Returns the estimated impact on every downstream measure.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.predict([\n * { measure: \"marketing_spend.total_spend\", delta: 10000 },\n * ]);\n * ```\n */\n async predict(changes: PredictChange[]): Promise<PredictResult> {\n const query = this.buildQuery();\n return this.request<PredictResult>(this.path(`/predict${query}`), {\n method: \"POST\",\n body: JSON.stringify({ changes })\n });\n }\n\n /**\n * Period-over-period root-cause decomposition. Recursively splits the\n * target measure by components and dimensions until the move concentrates.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.explain({\n * target: \"financials.operating_profit\",\n * time_dimension: \"financials.month\",\n * current_period: [\"2025-09-01\", \"2025-09-30\"],\n * previous_period: [\"2025-08-01\", \"2025-08-31\"],\n * });\n * ```\n */\n async explain(request: ExplainRequest): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(this.path(`/explain${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n\n /**\n * Size the upside opportunity for a measure by finding underperforming\n * segments. Skips high-cardinality dimensions and trims the long tail.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.findOpportunities({\n * target: \"orders.net_revenue\",\n * time_dimension: \"orders.order_date\",\n * period: [\"2025-09-01\", \"2025-09-30\"],\n * });\n * for (const dim of result.dimensions) {\n * console.log(dim.dimension, \"+\", dim.total_upside);\n * }\n * ```\n */\n async findOpportunities(request: OpportunityRequest): Promise<OpportunityResult> {\n const query = this.buildQuery();\n return this.request<OpportunityResult>(this.path(`/opportunity${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8EA,IAAa,kBAAb,MAA6B;CAI3B,YAAY,QAAmB,SAAoB;EACjD,KAAK,SAAS;EACd,KAAK,UAAU;CACjB;CAEA,AAAQ,KAAK,QAAwB;EACnC,OAAO,IAAI,KAAK,OAAO,UAAU,qBAAqB;CACxD;CAEA,AAAQ,WAAW,QAAgC,CAAC,GAAW;EAC7D,MAAM,SAAiC,EAAE,GAAG,MAAM;EAClD,IAAI,KAAK,OAAO,QAAQ,OAAO,SAAS,KAAK,OAAO;EACpD,MAAM,KAAK,IAAI,gBAAgB,MAAM,CAAC,CAAC,SAAS;EAChD,OAAO,KAAK,IAAI,OAAO;CACzB;;;;;;;;;;CAWA,MAAM,KAAK,UAAgC,CAAC,GAAmC;EAC7E,MAAM,QAAgC,CAAC;EACvC,IAAI,QAAQ,QAAQ,MAAM,SAAS,QAAQ;EAC3C,IAAI,QAAQ,OAAO,MAAM,QAAQ,OAAO,QAAQ,KAAK;EAGrD,OAAO,KAAK,QAA+B,KAAK,KAAK,KAAK,WAAW,KAAK,CAAC,CAAC;CAC9E;;;;;;;;;;;;;CAcA,MAAM,KAAK,UAAuB,CAAC,GAA0B;EAC3D,MAAM,QAAgC,CAAC;EACvC,IAAI,QAAQ,OAAO,MAAM,QAAQ,QAAQ;EACzC,OAAO,KAAK,QAAsB,KAAK,KAAK,QAAQ,KAAK,WAAW,KAAK,GAAG,GAAG,EAC7E,QAAQ,OACV,CAAC;CACH;;;;CAKA,MAAM,aAAa,WAAmB,QAAyC;EAC7E,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QAAiB,KAAK,KAAK,IAAI,mBAAmB,SAAS,EAAE,SAAS,OAAO,GAAG;GAC1F,QAAQ;GACR,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;EACjC,CAAC;CACH;;;;;CAMA,MAAM,QAAQ,WAA2C;EACvD,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QACV,KAAK,KAAK,IAAI,mBAAmB,SAAS,EAAE,UAAU,OAAO,GAC7D,EAAE,QAAQ,OAAO,CACnB;CACF;AACF;;;;;;;;;;ACjHA,eAAsB,kBACpB,UACiC;CACjC,MAAM,MAAM,gBAAgB;CAC5B,MAAM,EAAE,YAAY,SAAS,YAAY;CACzC,MAAM,MACJ,GAAG,WAAW,qBACX,mBAAmB,OAAO,EAAE,GAAG,mBAAmB,OAAO,EAAE;CAEhE,IAAI,IAAI,SAAS,2BAA2B,EAAE,IAAI,CAAC;CACnD,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,aAAa,cAAc,CAAC;CAC3D,IAAI,CAAC,IAAI,IAAI;EACX,MAAM,SAAS,MAAM,IAAI,KAAK,CAAC,CAAC,YAAY,EAAE;EAC9C,MAAM,IAAI,MACR,wCAAwC,IAAI,OAAO,KAAK,UAAU,IAAI,YACxE;CACF;CACA,MAAM,WAAY,MAAM,IAAI,KAAK;CACjC,IAAI,IAAI,QAAQ,kBAAkB,QAA8C;CAChF,OAAO;AACT;;;;;;;;;;;;;;;;;;;ACmLA,IAAa,mBAAb,MAA8B;CAI5B,YAAY,QAAmB,SAAoB;EACjD,KAAK,SAAS;EACd,KAAK,UAAU;CACjB;CAEA,AAAQ,KAAK,QAAwB;EACnC,OAAO,IAAI,KAAK,OAAO,UAAU,uBAAuB;CAC1D;CAEA,AAAQ,WAAW,QAAgC,CAAC,GAAW;EAC7D,MAAM,SAAiC,EAAE,GAAG,MAAM;EAClD,IAAI,KAAK,OAAO,QAAQ,OAAO,SAAS,KAAK,OAAO;EACpD,MAAM,KAAK,IAAI,gBAAgB,MAAM,CAAC,CAAC,SAAS;EAChD,OAAO,KAAK,IAAI,OAAO;CACzB;;;;;;;;;;;;;CAcA,MAAM,QAAQ,MAAoC;EAChD,MAAM,QAAQ,KAAK,WAAW,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;EAClD,OAAO,KAAK,QAAoB,KAAK,KAAK,KAAK,CAAC;CAClD;;;;;;;;;;;;;;CAeA,MAAM,eAAe,WAA+C;EAClE,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QACV,KAAK,KAAK,IAAI,mBAAmB,SAAS,EAAE,cAAc,OAAO,CACnE;CACF;;;;;;;;;;;;CAaA,MAAM,QAAQ,SAAkD;EAC9D,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QAAuB,KAAK,KAAK,WAAW,OAAO,GAAG;GAChE,QAAQ;GACR,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;EAClC,CAAC;CACH;;;;;;;;;;;;;;;CAgBA,MAAM,QAAQ,SAAiD;EAC7D,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QAAuB,KAAK,KAAK,WAAW,OAAO,GAAG;GAChE,QAAQ;GACR,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;CACH;;;;;;;;;;;;;;;;;CAkBA,MAAM,kBAAkB,SAAyD;EAC/E,MAAM,QAAQ,KAAK,WAAW;EAC9B,OAAO,KAAK,QAA2B,KAAK,KAAK,eAAe,OAAO,GAAG;GACxE,QAAQ;GACR,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;CACH;AACF"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/custom-app/logger.ts
|
|
6
6
|
let activeLogger = createConsoleLogger();
|
|
7
7
|
/** Replace the global logger. Pass `null` to silence everything. */
|
|
8
8
|
function setOxyAppLogger(logger) {
|
|
@@ -42,9 +42,9 @@ function silentLogger() {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
//#endregion
|
|
45
|
-
//#region src/
|
|
45
|
+
//#region src/custom-app/errors.ts
|
|
46
46
|
/**
|
|
47
|
-
* Error thrown by all
|
|
47
|
+
* Error thrown by all custom-app hooks when an API call returns a
|
|
48
48
|
* non-2xx response. Carries the structured `code` + `hint` the server
|
|
49
49
|
* emits so bundle UIs can render an actionable message instead of
|
|
50
50
|
* "404: { ...json... }".
|
|
@@ -91,12 +91,12 @@ async function apiErrorFromResponse(resp) {
|
|
|
91
91
|
}
|
|
92
92
|
const ARCH_DOC = "internal-docs/customer-apps.md";
|
|
93
93
|
/** Interpret a thrown error as a structured report for UI display. */
|
|
94
|
-
function
|
|
94
|
+
function interpretCustomAppError(err) {
|
|
95
95
|
const message = err instanceof Error ? err.message : String(err);
|
|
96
96
|
if (/Failed to load oxy-app\.json.*HTTP 404/.test(message)) return {
|
|
97
97
|
title: "Manifest not found",
|
|
98
98
|
message,
|
|
99
|
-
hint: "The bundle is being served, but oxy-app.json was not. Check that public/oxy-app.json is committed in the
|
|
99
|
+
hint: "The bundle is being served, but oxy-app.json was not. Check that public/oxy-app.json is committed in the custom-app repo and that the build copied it into the static output. If you're using Next.js, anything under public/ is auto-copied to out/.",
|
|
100
100
|
docs: ARCH_DOC
|
|
101
101
|
};
|
|
102
102
|
if (/Failed to load oxy-app\.json/.test(message)) return {
|
|
@@ -132,7 +132,7 @@ function interpretCustomerAppError(err) {
|
|
|
132
132
|
if (/^403:.*SELECT.*WITH/im.test(message)) return {
|
|
133
133
|
title: "Query rejected — read-only endpoint",
|
|
134
134
|
message,
|
|
135
|
-
hint: "This proxy only runs SELECT or WITH queries. Mutations (INSERT/UPDATE/DELETE/DROP) are not allowed from
|
|
135
|
+
hint: "This proxy only runs SELECT or WITH queries. Mutations (INSERT/UPDATE/DELETE/DROP) are not allowed from custom-app bundles.",
|
|
136
136
|
docs: ARCH_DOC
|
|
137
137
|
};
|
|
138
138
|
if (/^403:/m.test(message)) return {
|
|
@@ -168,7 +168,7 @@ function interpretCustomerAppError(err) {
|
|
|
168
168
|
if (/Unexpected token '<'|<!doctype/i.test(message)) return {
|
|
169
169
|
title: "Fetched HTML where JSON was expected",
|
|
170
170
|
message,
|
|
171
|
-
hint: "Most likely the built bundle is stale — built against an old SDK whose endpoints no longer exist on the server. Rebuild the bundle (vite build) with @oxy-hq/sdk@^2.0.0 and reload. If the bundle is current, check that OXY_APP_BASE_PATH matches the path the
|
|
171
|
+
hint: "Most likely the built bundle is stale — built against an old SDK whose endpoints no longer exist on the server. Rebuild the bundle (vite build) with @oxy-hq/sdk@^2.0.0 and reload. If the bundle is current, check that OXY_APP_BASE_PATH matches the path the custom-app row is served at.",
|
|
172
172
|
docs: ARCH_DOC
|
|
173
173
|
};
|
|
174
174
|
return {
|
|
@@ -180,7 +180,7 @@ function interpretCustomerAppError(err) {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
//#endregion
|
|
183
|
-
//#region src/
|
|
183
|
+
//#region src/custom-app/inject.ts
|
|
184
184
|
/**
|
|
185
185
|
* Read the runtime app-config oxy injected at serve time. Returns
|
|
186
186
|
* `undefined` outside the browser or when the global isn't set
|
|
@@ -193,18 +193,18 @@ function readInjectedAppConfig() {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
//#endregion
|
|
196
|
-
//#region src/
|
|
196
|
+
//#region src/custom-app/manifest.ts
|
|
197
197
|
let cached = null;
|
|
198
198
|
/**
|
|
199
199
|
* Load + validate the manifest. Cached after the first call so callers
|
|
200
200
|
* can invoke this from every component without coordinating.
|
|
201
201
|
*/
|
|
202
|
-
function
|
|
202
|
+
function loadCustomAppManifest(options = {}) {
|
|
203
203
|
if (!cached) cached = fetchAndValidate(options);
|
|
204
204
|
return cached;
|
|
205
205
|
}
|
|
206
206
|
/** For tests: reset the cache between runs. */
|
|
207
|
-
function
|
|
207
|
+
function _resetCustomAppManifestCacheForTest() {
|
|
208
208
|
cached = null;
|
|
209
209
|
}
|
|
210
210
|
async function fetchAndValidate(options) {
|
|
@@ -226,7 +226,7 @@ async function fetchAndValidate(options) {
|
|
|
226
226
|
status: res.status,
|
|
227
227
|
statusText: res.statusText
|
|
228
228
|
});
|
|
229
|
-
throw new Error(`Failed to load oxy-app.json from ${manifestUrl} (HTTP ${res.status}). The
|
|
229
|
+
throw new Error(`Failed to load oxy-app.json from ${manifestUrl} (HTTP ${res.status}). The custom-app repo must commit this file alongside the bundle.`);
|
|
230
230
|
}
|
|
231
231
|
const manifest = validateManifest(await res.json(), manifestUrl);
|
|
232
232
|
const resolved = {
|
|
@@ -291,7 +291,7 @@ const FUNCTION_NAME_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
|
291
291
|
/**
|
|
292
292
|
* Validate the optional `functions` map. Each key is a function name;
|
|
293
293
|
* each value declares how the function is invoked. Mirrors the
|
|
294
|
-
* server-side validation in `
|
|
294
|
+
* server-side validation in `custom_apps_publish.rs` so a bad
|
|
295
295
|
* manifest fails at build, not at publish.
|
|
296
296
|
*/
|
|
297
297
|
function validateFunctions(raw) {
|
|
@@ -369,7 +369,7 @@ function isRecord(v) {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
//#endregion
|
|
372
|
-
//#region src/
|
|
372
|
+
//#region src/custom-app/function-invoke.ts
|
|
373
373
|
const inflight$1 = /* @__PURE__ */ new Map();
|
|
374
374
|
/**
|
|
375
375
|
* Dedup key for an invocation: function name + its (stable-serialized) body,
|
|
@@ -395,7 +395,7 @@ function sharedFunctionInvoke(key, run) {
|
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
//#endregion
|
|
398
|
-
//#region src/
|
|
398
|
+
//#region src/custom-app/function-sse.ts
|
|
399
399
|
/**
|
|
400
400
|
* Read a `text/event-stream` function response to completion. Resolves with the
|
|
401
401
|
* decoded result + captured logs, or rejects (with `.logs` attached) on an
|
|
@@ -452,7 +452,7 @@ async function readFunctionSseStream(resp) {
|
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
//#endregion
|
|
455
|
-
//#region src/
|
|
455
|
+
//#region src/custom-app/interpolate.ts
|
|
456
456
|
/**
|
|
457
457
|
* Interpolate `{{ params.X }}` and `{{ params.X | sqlquote }}` placeholders
|
|
458
458
|
* in a SQL template.
|
|
@@ -481,7 +481,7 @@ function interpolateSqlParams(sql, params) {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
//#endregion
|
|
484
|
-
//#region src/
|
|
484
|
+
//#region src/custom-app/markdown.ts
|
|
485
485
|
/**
|
|
486
486
|
* Allowlist for `[text](url)` href values in agent-emitted markdown.
|
|
487
487
|
* Markdown comes from an LLM, which sits across an external trust
|
|
@@ -557,7 +557,7 @@ function isTableStart(lines, idx) {
|
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
//#endregion
|
|
560
|
-
//#region src/
|
|
560
|
+
//#region src/custom-app/query-cache.ts
|
|
561
561
|
const SWR_TTL_MS = 3e4;
|
|
562
562
|
const inflight = /* @__PURE__ */ new Map();
|
|
563
563
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -601,7 +601,7 @@ async function sharedQuery(fetcher, projectId, sql, db, opts = {}) {
|
|
|
601
601
|
}
|
|
602
602
|
|
|
603
603
|
//#endregion
|
|
604
|
-
//#region src/
|
|
604
|
+
//#region src/custom-app/react.tsx
|
|
605
605
|
function defaultFetcher(input, init) {
|
|
606
606
|
return fetch(input, {
|
|
607
607
|
credentials: "include",
|
|
@@ -642,7 +642,7 @@ function OxyAppProvider(props) {
|
|
|
642
642
|
});
|
|
643
643
|
React.useEffect(() => {
|
|
644
644
|
let cancelled = false;
|
|
645
|
-
|
|
645
|
+
loadCustomAppManifest(manifestOptions).then((resolved) => {
|
|
646
646
|
if (!cancelled) setState({
|
|
647
647
|
status: "ready",
|
|
648
648
|
resolved,
|
|
@@ -651,7 +651,7 @@ function OxyAppProvider(props) {
|
|
|
651
651
|
}).catch((e) => {
|
|
652
652
|
if (!cancelled) setState({
|
|
653
653
|
status: "error",
|
|
654
|
-
error:
|
|
654
|
+
error: interpretCustomAppError(e),
|
|
655
655
|
fetcher
|
|
656
656
|
});
|
|
657
657
|
});
|
|
@@ -753,7 +753,7 @@ function useOxyApp() {
|
|
|
753
753
|
}
|
|
754
754
|
/**
|
|
755
755
|
* Execute an ad-hoc SQL query against the project linked to this
|
|
756
|
-
*
|
|
756
|
+
* custom app. The query is specified inline by the caller; no
|
|
757
757
|
* manifest declaration is involved.
|
|
758
758
|
*
|
|
759
759
|
* Re-runs whenever `input` or enabled `params` change. Use the
|
|
@@ -2254,5 +2254,5 @@ const styles = {
|
|
|
2254
2254
|
};
|
|
2255
2255
|
|
|
2256
2256
|
//#endregion
|
|
2257
|
-
export {
|
|
2258
|
-
//# sourceMappingURL=react-
|
|
2257
|
+
export { interpretCustomAppError as _, useFunction as a, useQuery as c, useTrackEvent as d, _resetCustomAppManifestCacheForTest as f, apiErrorFromResponse as g, OxyApiError as h, useAgentRun as i, useResolvedManifest as l, readInjectedAppConfig as m, OxyAppProvider as n, useOxyApp as o, loadCustomAppManifest as p, OxyChat as r, useProcedureRun as s, OxyAnswer as t, useSemanticQuery as u, getOxyAppLogger as v, setOxyAppLogger as y };
|
|
2258
|
+
//# sourceMappingURL=react-5HGW_0oy.mjs.map
|