@oxy-hq/sdk 2.1.0 → 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/email.cjs +14 -0
- package/dist/email.cjs.map +1 -0
- package/dist/email.d.cts +8 -0
- package/dist/email.d.cts.map +1 -0
- package/dist/email.d.mts +8 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +13 -0
- package/dist/email.mjs.map +1 -0
- package/dist/index.cjs +132 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +176 -25
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +176 -25
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +132 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -8
package/dist/index.mjs
CHANGED
|
@@ -147,6 +147,52 @@ async function getCustomerAppDebug(resolved) {
|
|
|
147
147
|
|
|
148
148
|
//#endregion
|
|
149
149
|
//#region src/customer-app/errors.ts
|
|
150
|
+
/**
|
|
151
|
+
* Error thrown by all customer-app hooks when an API call returns a
|
|
152
|
+
* non-2xx response. Carries the structured `code` + `hint` the server
|
|
153
|
+
* emits so bundle UIs can render an actionable message instead of
|
|
154
|
+
* "404: { ...json... }".
|
|
155
|
+
*/
|
|
156
|
+
var OxyApiError = class extends Error {
|
|
157
|
+
constructor(opts) {
|
|
158
|
+
const base = opts.message || `HTTP ${opts.status}`;
|
|
159
|
+
const code = opts.code ? ` [${opts.code}]` : "";
|
|
160
|
+
const hint = opts.hint ? `\n\n${opts.hint}` : "";
|
|
161
|
+
super(`${base}${code}${hint}`);
|
|
162
|
+
this.name = "OxyApiError";
|
|
163
|
+
this.status = opts.status;
|
|
164
|
+
this.code = opts.code ?? null;
|
|
165
|
+
this.hint = opts.hint ?? null;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Read a non-2xx response from oxy and return an `OxyApiError`.
|
|
170
|
+
* Parses the JSON envelope when present; falls back to raw text
|
|
171
|
+
* (truncated to 240 chars so a runaway HTML error page doesn't
|
|
172
|
+
* dominate the bundle UI).
|
|
173
|
+
*/
|
|
174
|
+
async function apiErrorFromResponse(resp) {
|
|
175
|
+
let body;
|
|
176
|
+
let raw = "";
|
|
177
|
+
try {
|
|
178
|
+
raw = await resp.text();
|
|
179
|
+
body = raw ? JSON.parse(raw) : void 0;
|
|
180
|
+
} catch {}
|
|
181
|
+
if (body && typeof body === "object") {
|
|
182
|
+
const b = body;
|
|
183
|
+
return new OxyApiError({
|
|
184
|
+
status: resp.status,
|
|
185
|
+
message: typeof b.message === "string" ? b.message : `HTTP ${resp.status}`,
|
|
186
|
+
code: typeof b.code === "string" ? b.code : null,
|
|
187
|
+
hint: typeof b.hint === "string" ? b.hint : null
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const snippet = raw.length > 240 ? `${raw.slice(0, 237)}…` : raw;
|
|
191
|
+
return new OxyApiError({
|
|
192
|
+
status: resp.status,
|
|
193
|
+
message: snippet || `HTTP ${resp.status}`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
150
196
|
const ARCH_DOC = "internal-docs/customer-apps.md";
|
|
151
197
|
/** Interpret a thrown error as a structured report for UI display. */
|
|
152
198
|
function interpretCustomerAppError(err) {
|
|
@@ -393,6 +439,24 @@ function validateFunctions(raw) {
|
|
|
393
439
|
fn.cache = { ttlSeconds: ttl };
|
|
394
440
|
}
|
|
395
441
|
}
|
|
442
|
+
if (value.retries !== void 0) {
|
|
443
|
+
const r = value.retries;
|
|
444
|
+
if (!isRecord(r)) throw new Error(`oxy-app.json: function "${fnName}" \`retries\` must be an object`);
|
|
445
|
+
const retries = {};
|
|
446
|
+
for (const key of [
|
|
447
|
+
"maxAttempts",
|
|
448
|
+
"minTimeoutMs",
|
|
449
|
+
"maxTimeoutMs"
|
|
450
|
+
]) {
|
|
451
|
+
const n = r[key];
|
|
452
|
+
if (n !== void 0) {
|
|
453
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) throw new Error(`oxy-app.json: function "${fnName}" \`retries.${key}\` must be a positive integer`);
|
|
454
|
+
retries[key] = n;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
fn.retries = retries;
|
|
458
|
+
}
|
|
459
|
+
if (value.inputExample !== void 0) fn.inputExample = value.inputExample;
|
|
396
460
|
const hasSchedule = fn.schedule !== void 0;
|
|
397
461
|
const hasAirway = fn.airwayStep !== void 0;
|
|
398
462
|
if (!(fn.route ?? !(hasSchedule || hasAirway)) && !hasSchedule && !hasAirway) throw new Error(`oxy-app.json: function "${fnName}" must enable at least one of route/schedule/airwayStep`);
|
|
@@ -406,7 +470,7 @@ function isRecord(v) {
|
|
|
406
470
|
|
|
407
471
|
//#endregion
|
|
408
472
|
//#region src/customer-app/function-invoke.ts
|
|
409
|
-
const inflight = /* @__PURE__ */ new Map();
|
|
473
|
+
const inflight$1 = /* @__PURE__ */ new Map();
|
|
410
474
|
/**
|
|
411
475
|
* Dedup key for an invocation: function name + its (stable-serialized) body,
|
|
412
476
|
* joined by a newline. Function names are `[a-z][a-z0-9-]*` (no newline), so
|
|
@@ -421,12 +485,12 @@ function functionInvokeKey(name, body) {
|
|
|
421
485
|
* this dedups concurrency only, it does NOT memoize the result.
|
|
422
486
|
*/
|
|
423
487
|
function sharedFunctionInvoke(key, run) {
|
|
424
|
-
const existing = inflight.get(key);
|
|
488
|
+
const existing = inflight$1.get(key);
|
|
425
489
|
if (existing) return existing;
|
|
426
490
|
const p = run().finally(() => {
|
|
427
|
-
inflight.delete(key);
|
|
491
|
+
inflight$1.delete(key);
|
|
428
492
|
});
|
|
429
|
-
inflight.set(key, p);
|
|
493
|
+
inflight$1.set(key, p);
|
|
430
494
|
return p;
|
|
431
495
|
}
|
|
432
496
|
|
|
@@ -551,6 +615,50 @@ function isSafeLinkHref(raw) {
|
|
|
551
615
|
return lower.startsWith("http://") || lower.startsWith("https://") || lower.startsWith("mailto:");
|
|
552
616
|
}
|
|
553
617
|
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/customer-app/query-cache.ts
|
|
620
|
+
const SWR_TTL_MS = 3e4;
|
|
621
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
622
|
+
const cache = /* @__PURE__ */ new Map();
|
|
623
|
+
function queryKey(projectId, db, sql) {
|
|
624
|
+
return `${projectId} ${db ?? ""} ${sql}`;
|
|
625
|
+
}
|
|
626
|
+
function getCached(projectId, sql, db) {
|
|
627
|
+
const e = cache.get(queryKey(projectId, db, sql));
|
|
628
|
+
return e && Date.now() - e.at < SWR_TTL_MS ? e.data : void 0;
|
|
629
|
+
}
|
|
630
|
+
/** Fetch with in-flight dedup + cache. `force` bypasses the fresh-cache
|
|
631
|
+
* short-circuit (used by refetch) but still dedupes a concurrent in-flight. */
|
|
632
|
+
async function sharedQuery(fetcher, projectId, sql, db, opts = {}) {
|
|
633
|
+
const key = queryKey(projectId, db, sql);
|
|
634
|
+
if (!opts.force) {
|
|
635
|
+
const fresh = getCached(projectId, sql, db);
|
|
636
|
+
if (fresh) return fresh;
|
|
637
|
+
}
|
|
638
|
+
const existing = inflight.get(key);
|
|
639
|
+
if (existing) return existing;
|
|
640
|
+
const body = JSON.stringify({
|
|
641
|
+
sql,
|
|
642
|
+
...db ? { database: db } : {}
|
|
643
|
+
});
|
|
644
|
+
const p = (async () => {
|
|
645
|
+
const resp = await fetcher(`/api/projects/${projectId}/query`, {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: { "content-type": "application/json" },
|
|
648
|
+
body
|
|
649
|
+
});
|
|
650
|
+
if (!resp.ok) throw await apiErrorFromResponse(resp);
|
|
651
|
+
const data = await resp.json();
|
|
652
|
+
cache.set(key, {
|
|
653
|
+
at: Date.now(),
|
|
654
|
+
data
|
|
655
|
+
});
|
|
656
|
+
return data;
|
|
657
|
+
})().finally(() => inflight.delete(key));
|
|
658
|
+
inflight.set(key, p);
|
|
659
|
+
return p;
|
|
660
|
+
}
|
|
661
|
+
|
|
554
662
|
//#endregion
|
|
555
663
|
//#region src/customer-app/react.tsx
|
|
556
664
|
function defaultFetcher(input, init) {
|
|
@@ -655,58 +763,6 @@ function warnBetaOnce(name) {
|
|
|
655
763
|
if (typeof console !== "undefined" && typeof console.warn === "function") console.warn(`[@oxy-hq/sdk] \`${name}\` is in beta — interface and behavior may change. See https://github.com/oxy-hq/customer-apps for caveats and the migration guide.`);
|
|
656
764
|
}
|
|
657
765
|
/**
|
|
658
|
-
* Error thrown by all customer-app hooks when an API call returns a
|
|
659
|
-
* non-2xx response. Carries the structured `code` + `hint` the server
|
|
660
|
-
* emits so bundle UIs can render an actionable message instead of
|
|
661
|
-
* "404: { ...json... }".
|
|
662
|
-
*
|
|
663
|
-
* The server contract is documented in
|
|
664
|
-
* `crates/app/src/server/api/projects/agent_ask.rs` and
|
|
665
|
-
* `procedure_run.rs` — both emit `{ message, code?, hint? }` as JSON.
|
|
666
|
-
* Hooks that previously wrapped the raw text in `new Error()` now
|
|
667
|
-
* throw this type instead.
|
|
668
|
-
*/
|
|
669
|
-
var OxyApiError = class extends Error {
|
|
670
|
-
constructor(opts) {
|
|
671
|
-
const base = opts.message || `HTTP ${opts.status}`;
|
|
672
|
-
const code = opts.code ? ` [${opts.code}]` : "";
|
|
673
|
-
const hint = opts.hint ? `\n\n${opts.hint}` : "";
|
|
674
|
-
super(`${base}${code}${hint}`);
|
|
675
|
-
this.name = "OxyApiError";
|
|
676
|
-
this.status = opts.status;
|
|
677
|
-
this.code = opts.code ?? null;
|
|
678
|
-
this.hint = opts.hint ?? null;
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
/**
|
|
682
|
-
* Read a non-2xx response from oxy and return an `OxyApiError`.
|
|
683
|
-
* Parses the JSON envelope when present; falls back to raw text
|
|
684
|
-
* (truncated to 240 chars so a runaway HTML error page doesn't
|
|
685
|
-
* dominate the bundle UI).
|
|
686
|
-
*/
|
|
687
|
-
async function apiErrorFromResponse(resp) {
|
|
688
|
-
let body;
|
|
689
|
-
let raw = "";
|
|
690
|
-
try {
|
|
691
|
-
raw = await resp.text();
|
|
692
|
-
body = raw ? JSON.parse(raw) : void 0;
|
|
693
|
-
} catch {}
|
|
694
|
-
if (body && typeof body === "object") {
|
|
695
|
-
const b = body;
|
|
696
|
-
return new OxyApiError({
|
|
697
|
-
status: resp.status,
|
|
698
|
-
message: typeof b.message === "string" ? b.message : `HTTP ${resp.status}`,
|
|
699
|
-
code: typeof b.code === "string" ? b.code : null,
|
|
700
|
-
hint: typeof b.hint === "string" ? b.hint : null
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
const snippet = raw.length > 240 ? `${raw.slice(0, 237)}…` : raw;
|
|
704
|
-
return new OxyApiError({
|
|
705
|
-
status: resp.status,
|
|
706
|
-
message: snippet || `HTTP ${resp.status}`
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
766
|
* Read the resolved manifest from context. Throws if called outside
|
|
711
767
|
* `<OxyAppProvider>` — that's a programmer error worth surfacing
|
|
712
768
|
* loudly, not silently swallowing.
|
|
@@ -760,36 +816,35 @@ function useQuery(input, opts = {}) {
|
|
|
760
816
|
} : s);
|
|
761
817
|
return;
|
|
762
818
|
}
|
|
763
|
-
const ctrl = new AbortController();
|
|
764
819
|
let cancelled = false;
|
|
820
|
+
const cached = getCached(projectId, sqlWithParams, input.database);
|
|
821
|
+
if (cached && nonce === 0) {
|
|
822
|
+
const { columns, rows } = cached;
|
|
823
|
+
const objects = rows.map((r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])));
|
|
824
|
+
setState({
|
|
825
|
+
rows: objects,
|
|
826
|
+
columns,
|
|
827
|
+
loading: false,
|
|
828
|
+
error: null
|
|
829
|
+
});
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
765
832
|
setState((s) => ({
|
|
766
833
|
...s,
|
|
767
834
|
loading: true,
|
|
768
835
|
error: null
|
|
769
836
|
}));
|
|
770
|
-
|
|
771
|
-
sql: sqlWithParams,
|
|
772
|
-
...input.database ? { database: input.database } : {}
|
|
773
|
-
});
|
|
774
|
-
fetcher(`/api/projects/${projectId}/query`, {
|
|
775
|
-
method: "POST",
|
|
776
|
-
headers: { "content-type": "application/json" },
|
|
777
|
-
body,
|
|
778
|
-
signal: ctrl.signal
|
|
779
|
-
}).then(async (resp) => {
|
|
780
|
-
if (!resp.ok) throw await apiErrorFromResponse(resp);
|
|
781
|
-
return resp.json();
|
|
782
|
-
}).then(({ columns, rows }) => {
|
|
837
|
+
sharedQuery(fetcher, projectId, sqlWithParams, input.database, { force: nonce > 0 }).then(({ columns, rows }) => {
|
|
783
838
|
if (cancelled) return;
|
|
839
|
+
const objects = rows.map((r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])));
|
|
784
840
|
setState({
|
|
785
|
-
rows:
|
|
841
|
+
rows: objects,
|
|
786
842
|
columns,
|
|
787
843
|
loading: false,
|
|
788
844
|
error: null
|
|
789
845
|
});
|
|
790
846
|
}).catch((err) => {
|
|
791
847
|
if (cancelled) return;
|
|
792
|
-
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
793
848
|
setState((s) => ({
|
|
794
849
|
...s,
|
|
795
850
|
loading: false,
|
|
@@ -798,7 +853,6 @@ function useQuery(input, opts = {}) {
|
|
|
798
853
|
});
|
|
799
854
|
return () => {
|
|
800
855
|
cancelled = true;
|
|
801
|
-
ctrl.abort();
|
|
802
856
|
};
|
|
803
857
|
}, [
|
|
804
858
|
enabled,
|
|
@@ -939,7 +993,8 @@ function useSemanticQuery(input, opts = {}) {
|
|
|
939
993
|
filters: input.filters ?? [],
|
|
940
994
|
...input.limit != null ? { limit: input.limit } : {}
|
|
941
995
|
});
|
|
942
|
-
|
|
996
|
+
const url = `/api/projects/${projectId}/semantic-query${debug ? "?debug=1" : ""}`;
|
|
997
|
+
fetcher(url, {
|
|
943
998
|
method: "POST",
|
|
944
999
|
headers: { "content-type": "application/json" },
|
|
945
1000
|
body,
|
|
@@ -949,8 +1004,9 @@ function useSemanticQuery(input, opts = {}) {
|
|
|
949
1004
|
return resp.json();
|
|
950
1005
|
}).then(({ columns, rows, truncated, sql }) => {
|
|
951
1006
|
if (cancelled) return;
|
|
1007
|
+
const objects = rows.map((r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])));
|
|
952
1008
|
setState({
|
|
953
|
-
rows:
|
|
1009
|
+
rows: objects,
|
|
954
1010
|
columns,
|
|
955
1011
|
truncated,
|
|
956
1012
|
sql: sql ?? null,
|
|
@@ -2277,5 +2333,5 @@ var MetricTreeClient = class {
|
|
|
2277
2333
|
};
|
|
2278
2334
|
|
|
2279
2335
|
//#endregion
|
|
2280
|
-
export { AnomaliesClient, MetricTreeClient, OxyAnswer, OxyApiError, OxyAppProvider, OxyChat, _resetCustomerAppManifestCacheForTest, getCustomerAppDebug, getOxyAppLogger, interpretCustomerAppError, loadCustomerAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
|
|
2336
|
+
export { AnomaliesClient, MetricTreeClient, OxyAnswer, OxyApiError, OxyAppProvider, OxyChat, _resetCustomerAppManifestCacheForTest, apiErrorFromResponse, getCustomerAppDebug, getOxyAppLogger, interpretCustomerAppError, loadCustomerAppManifest, readInjectedAppConfig, setOxyAppLogger, useAgentRun, useFunction, useProcedureRun, useQuery, useResolvedManifest, useSemanticQuery, useTrackEvent };
|
|
2281
2337
|
//# sourceMappingURL=index.mjs.map
|