@superblocksteam/library 2.0.104 → 2.0.105-next.1

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/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as consoleLogAttributes, t as early_console_buffer_default } from "../early-console-buffer-D4wVuyBf.js";
2
- import { A as useSuperblocksProfiles, B as createManagedPropsList, C as rejectById, D as useSuperblocksContext, E as getAppMode, F as editorBridge, G as getEditStore, H as PropsCategory, I as iframeMessageHandler, L as isEmbeddedBySuperblocksFirstParty, M as generateId, N as sendNotification, O as useSuperblocksDataTags, P as colors$1, R as sendMessageImmediately, S as addNewPromise, T as SuperblocksContextProvider, U as Section, V as Prop, W as createPropertiesPanelDefinition, _ as root_store_default, a as FixWithClarkButton, b as getContextFromTraceHeaders, c as ErrorContent, d as ErrorMessage$1, f as ErrorStack, g as StyledClarkIcon, h as SecondaryButton, i as getWidgetRectAnchorName, j as useSuperblocksUser, k as useSuperblocksGroups, l as ErrorDetails, m as ErrorTitle, n as useJSXContext, o as ActionsContainer, p as ErrorSummary, r as getWidgetAnchorName, s as ErrorContainer, u as ErrorIconContainer, v as startEditorSync, w as resolveById, x as api_hmr_tracker_default, y as createIframeSpan, z as isEditMode } from "../jsx-wrapper-DnM3BCRU.js";
2
+ import { A as useSuperblocksProfiles, B as createManagedPropsList, C as rejectById, D as useSuperblocksContext, E as getAppMode, F as editorBridge, G as getEditStore, H as PropsCategory, I as iframeMessageHandler, L as isEmbeddedBySuperblocksFirstParty, M as generateId, N as sendNotification, O as useSuperblocksDataTags, P as colors$1, R as sendMessageImmediately, S as addNewPromise, T as SuperblocksContextProvider, U as Section, V as Prop, W as createPropertiesPanelDefinition, _ as root_store_default, a as FixWithClarkButton, b as getContextFromTraceHeaders, c as ErrorContent, d as ErrorMessage$1, f as ErrorStack, g as StyledClarkIcon, h as SecondaryButton, i as getWidgetRectAnchorName, j as useSuperblocksUser, k as useSuperblocksGroups, l as ErrorDetails, m as ErrorTitle, n as useJSXContext, o as ActionsContainer, p as ErrorSummary, r as getWidgetAnchorName, s as ErrorContainer, u as ErrorIconContainer, v as startEditorSync, w as resolveById, x as api_hmr_tracker_default, y as createIframeSpan, z as isEditMode } from "../jsx-wrapper-BCVlG9Wg.js";
3
3
  import { n as initTracerProviderWithOrigin } from "../utils-BGEEeYie.js";
4
4
  import { action, autorun, computed, makeAutoObservable, makeObservable, observable, reaction, when } from "mobx";
5
5
  import { UNSAFE_DataRouterContext, generatePath, useLocation, useNavigate, useParams, useRouteError } from "react-router";
@@ -18,6 +18,7 @@ import colors from "tailwindcss/colors";
18
18
  import { Observer, observer } from "mobx-react-lite";
19
19
  import { getIntegrationDeclarations } from "@superblocksteam/sdk-api";
20
20
  import zodToJsonSchema from "zod-to-json-schema";
21
+ import useSWR, { mutate } from "swr";
21
22
  import * as Dialog from "@radix-ui/react-dialog";
22
23
  import posthog from "posthog-js";
23
24
  import { Graph } from "@dagrejs/graphlib";
@@ -965,6 +966,24 @@ function useGetCurrentUserQuery() {
965
966
  }, []);
966
967
  }
967
968
 
969
+ //#endregion
970
+ //#region src/lib/internal-details/lib/registry-loader.ts
971
+ /**
972
+ * Dynamically import the SDK API registry module from the app origin.
973
+ *
974
+ * Uses a cache-busting parameter because the browser's ES module map
975
+ * caches modules by URL — without it, subsequent import() calls return
976
+ * the stale first-load module and newly created APIs are never discovered.
977
+ */
978
+ async function loadRegistryModule() {
979
+ const registryUrl = new URL("/server/apis/index.ts", window.location.origin);
980
+ registryUrl.searchParams.set("t", String(Date.now()));
981
+ return (await import(
982
+ /* @vite-ignore */
983
+ registryUrl.href
984
+ )).default;
985
+ }
986
+
968
987
  //#endregion
969
988
  //#region src/lib/internal-details/lib/utils/zod-to-typescript.ts
970
989
  /**
@@ -1073,6 +1092,7 @@ function extractSdkApiMetadata(name, api, sourceCode) {
1073
1092
  outputSchema,
1074
1093
  isStreaming: false,
1075
1094
  entryPoint,
1095
+ exportName: root_store_default.getApiExportName(name),
1076
1096
  integrations: (api.integrations ?? []).map((integration) => ({
1077
1097
  key: integration.key,
1078
1098
  pluginId: integration.pluginId,
@@ -1154,7 +1174,7 @@ async function discoverAndRegisterSdkApis() {
1154
1174
  if (!root_store_default.sdkApiEnabled) return;
1155
1175
  if (!root_store_default.hasUnresolvedGate()) root_store_default.initDiscoveryGate();
1156
1176
  try {
1157
- const apis = (await import(new URL("/server/apis/index.ts", window.location.origin).href)).default;
1177
+ const apis = await loadRegistryModule();
1158
1178
  const apiNames = Object.keys(apis);
1159
1179
  if (apiNames.length === 0) {
1160
1180
  console.debug("[sdk-api-discovery] No SDK APIs found in registry");
@@ -1181,6 +1201,24 @@ async function discoverAndRegisterSdkApis() {
1181
1201
  }
1182
1202
  });
1183
1203
  const sources = await Promise.all(sourcePromises);
1204
+ const namedImportMap = await buildRegistryNamedImportMap();
1205
+ for (let i = 0; i < apiNames.length; i++) {
1206
+ const source = sources[i];
1207
+ const name = apiNames[i];
1208
+ if (!source) continue;
1209
+ const namedExportName = namedImportMap.get(name);
1210
+ if (namedExportName !== void 0) {
1211
+ root_store_default.setApiExportName(name, namedExportName);
1212
+ continue;
1213
+ }
1214
+ if (/export\s+default\b/.test(source)) continue;
1215
+ const namedExportPattern = /export\s+(?:const|let|var)\s+(\w+)\s*=/g;
1216
+ let match;
1217
+ const exportNames = [];
1218
+ while ((match = namedExportPattern.exec(source)) !== null) if (match[1]) exportNames.push(match[1]);
1219
+ if (exportNames.length === 1) root_store_default.setApiExportName(name, exportNames[0]);
1220
+ else if (exportNames.length > 1) root_store_default.setApiExportName(name, name);
1221
+ }
1184
1222
  const results = await Promise.allSettled(apiNames.map(async (name, i) => {
1185
1223
  const apiModule = apis[name];
1186
1224
  if (!apiModule) throw new Error(`API ${name} not found in registry`);
@@ -1195,6 +1233,47 @@ async function discoverAndRegisterSdkApis() {
1195
1233
  root_store_default.notifyDiscoveryComplete();
1196
1234
  }
1197
1235
  }
1236
+ /**
1237
+ * Fetches `server/apis/index.ts` and builds a map of ALL named imports:
1238
+ * `registryKey → originalExportName`.
1239
+ *
1240
+ * Given a registry line like:
1241
+ * import { GetAllUsers as ListUsers, CreateOrder } from './users.js'
1242
+ *
1243
+ * The map will contain:
1244
+ * ListUsers → GetAllUsers (aliased)
1245
+ * CreateOrder → CreateOrder (non-aliased)
1246
+ *
1247
+ * Also handles mixed default+named imports:
1248
+ * import Default, { GetOrders as ListOrders } from './orders.js'
1249
+ * → ListOrders → GetOrders
1250
+ *
1251
+ * Default imports and type-only imports are excluded.
1252
+ */
1253
+ async function buildRegistryNamedImportMap() {
1254
+ const map = /* @__PURE__ */ new Map();
1255
+ try {
1256
+ const resp = await fetch("/sb-raw-source/server/apis/index.ts");
1257
+ if (!resp.ok) return map;
1258
+ const text = await resp.text();
1259
+ const importBlockPattern = /import\s+(?:type\s+)?(?:\w+\s*,\s*)?\{([^}]+)\}\s*from\s*['"][^'"]+['"]/g;
1260
+ let blockMatch;
1261
+ while ((blockMatch = importBlockPattern.exec(text)) !== null) {
1262
+ const fullStatement = blockMatch[0];
1263
+ if (/^import\s+type\b/.test(fullStatement)) continue;
1264
+ const specifiers = blockMatch[1].split(",");
1265
+ for (const spec of specifiers) {
1266
+ const trimmed = spec.trim();
1267
+ if (!trimmed || /^type\s/.test(trimmed)) continue;
1268
+ const parts = trimmed.split(/\s+as\s+/);
1269
+ const originalName = parts[0].trim();
1270
+ const localName = (parts[1] ?? originalName).trim();
1271
+ if (/^\w+$/.test(originalName) && /^\w+$/.test(localName)) map.set(localName, originalName);
1272
+ }
1273
+ }
1274
+ } catch {}
1275
+ return map;
1276
+ }
1198
1277
  if (import.meta.hot) import.meta.hot.accept("/server/apis/index.ts", () => {
1199
1278
  if (!root_store_default.sdkApiEnabled) return;
1200
1279
  console.debug("[sdk-api-discovery] API registry updated via HMR, re-running discovery");
@@ -1204,6 +1283,360 @@ if (import.meta.hot) import.meta.hot.accept("/server/apis/index.ts", () => {
1204
1283
  discoverAndRegisterSdkApis();
1205
1284
  });
1206
1285
 
1286
+ //#endregion
1287
+ //#region src/lib/internal-details/query-cache.ts
1288
+ /**
1289
+ * query-cache — thin SWR-backed QueryClient.
1290
+ *
1291
+ * Replaces the hand-rolled QueryCache/QueryEntry/QueryClient with SWR's
1292
+ * built-in cache and mutate API. The module-scope `queryClient` singleton
1293
+ * survives React tree teardowns (critical for the iframe sb-init lifecycle)
1294
+ * because SWR's default cache is a module-scope Map.
1295
+ *
1296
+ * Public surface:
1297
+ * - `queryClient.invalidateQueries(prefix)` — cache invalidation
1298
+ * - `queryClient.setQueryData(key, data)` — optimistic updates
1299
+ * - `queryClient.getQueryData(key)` — read cached data
1300
+ * - `queryClient.buildCacheKey(apiName, inputs)` — deterministic key
1301
+ * - `queryClient.cache.clear()` — test helper
1302
+ */
1303
+ const KEY_SEPARATOR = "::";
1304
+ const dataStore = /* @__PURE__ */ new Map();
1305
+ /** Last successful write time per cache key (survives hook remounts; used for `isStale`). */
1306
+ const dataUpdatedAtByKey = /* @__PURE__ */ new Map();
1307
+ /**
1308
+ * Build a deterministic, string cache key from API name + inputs.
1309
+ * Sorts object entries so { b: 2, a: 1 } and { a: 1, b: 2 } produce
1310
+ * the same key.
1311
+ */
1312
+ function buildCacheKey(apiName, inputs) {
1313
+ return `${apiName}${KEY_SEPARATOR}${JSON.stringify(Object.entries(inputs).sort(([a], [b]) => a.localeCompare(b)))}`;
1314
+ }
1315
+ var QueryClient = class {
1316
+ buildCacheKey = buildCacheKey;
1317
+ getQueryData(key) {
1318
+ return dataStore.get(key);
1319
+ }
1320
+ setQueryData(key, updater) {
1321
+ const current = this.getQueryData(key);
1322
+ const next = typeof updater === "function" ? updater(current) : updater;
1323
+ dataStore.set(key, next);
1324
+ dataUpdatedAtByKey.set(key, Date.now());
1325
+ mutate(key, next, { revalidate: false }).catch(() => {});
1326
+ }
1327
+ async invalidateQueries(keyPrefix) {
1328
+ const prefix = keyPrefix.includes(KEY_SEPARATOR) ? keyPrefix : keyPrefix + KEY_SEPARATOR;
1329
+ for (const key of dataStore.keys()) if (key.startsWith(prefix)) {
1330
+ dataStore.delete(key);
1331
+ dataUpdatedAtByKey.delete(key);
1332
+ }
1333
+ await mutate((key) => typeof key === "string" && key.startsWith(prefix));
1334
+ }
1335
+ cache = { clear: () => {
1336
+ dataStore.clear();
1337
+ dataUpdatedAtByKey.clear();
1338
+ mutate(() => true, void 0, { revalidate: false }).catch(() => {});
1339
+ } };
1340
+ };
1341
+ const queryClient = new QueryClient();
1342
+
1343
+ //#endregion
1344
+ //#region src/lib/internal-details/execute-api.ts
1345
+ /**
1346
+ * execute-api — framework-agnostic API execution function.
1347
+ *
1348
+ * Extracts the core SDK/legacy routing logic from useSdkApi so it can be:
1349
+ * 1. Called as a plain Promise outside any React component.
1350
+ * 2. Used as the `queryFn` inside useApiData's QueryEntry.
1351
+ * 3. Exported as a public primitive for utility functions and tests.
1352
+ *
1353
+ * Edit-mode tracking messages (execution-started / completed / failed) are
1354
+ * sent to the parent frame when isEditMode() is true, matching the behaviour
1355
+ * of the original useSdkApi implementation.
1356
+ */
1357
+ /**
1358
+ * Returns true for every error except user-cancelled aborts.
1359
+ * Only `DOMException` with `name === "AbortError"` is treated as non-retryable.
1360
+ */
1361
+ function isRetryableError(error) {
1362
+ if (error instanceof DOMException && error.name === "AbortError") return false;
1363
+ return true;
1364
+ }
1365
+ /** Default exponential backoff: 1s, 2s, 4s, …, capped at 30s. */
1366
+ function defaultRetryDelay(attempt) {
1367
+ return Math.min(1e3 * Math.pow(2, attempt), 3e4);
1368
+ }
1369
+ /**
1370
+ * Execute a Superblocks API as a plain Promise, without any React lifecycle.
1371
+ *
1372
+ * Routes to the SDK path (`executeSdkApiV3`) when the API has an entry point
1373
+ * or when `sdkApiEnabled` is set on rootStore. Falls back to the legacy YAML
1374
+ * path otherwise.
1375
+ *
1376
+ * Sends edit-mode tracking messages to the parent frame when in edit mode,
1377
+ * keeping the Requests Panel in sync even for out-of-React calls.
1378
+ *
1379
+ * @throws {DOMException} with name "AbortError" if cancelled via signal.
1380
+ * @throws The orchestrator error object on API failure.
1381
+ */
1382
+ async function executeApi(apiName, inputs, options) {
1383
+ const inputRecord = inputs ?? {};
1384
+ let sdkApiEnabled = root_store_default.sdkApiEnabled;
1385
+ let hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1386
+ if (!sdkApiEnabled && !hasEntryPoint) {
1387
+ await root_store_default.awaitDiscovery();
1388
+ sdkApiEnabled = root_store_default.sdkApiEnabled;
1389
+ hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1390
+ }
1391
+ if (sdkApiEnabled || hasEntryPoint) {
1392
+ const executionId = options?.executionId ?? generateId();
1393
+ const startTime = Date.now();
1394
+ const editMode = isEditMode();
1395
+ if (editMode) sendMessageImmediately({
1396
+ type: "sdk-api-execution-started",
1397
+ payload: {
1398
+ executionId,
1399
+ apiName,
1400
+ input: inputRecord
1401
+ }
1402
+ });
1403
+ try {
1404
+ const result$1 = await root_store_default.apis.executeSdkApiV3(apiName, inputRecord, { signal: options?.signal });
1405
+ const durationMs = Date.now() - startTime;
1406
+ if (result$1 == null) {
1407
+ const nullError = {
1408
+ code: "NO_RESPONSE",
1409
+ message: `API "${apiName}" returned no response`
1410
+ };
1411
+ if (editMode) sendMessageImmediately({
1412
+ type: "sdk-api-execution-failed",
1413
+ payload: {
1414
+ executionId,
1415
+ apiName,
1416
+ error: nullError,
1417
+ durationMs
1418
+ }
1419
+ });
1420
+ throw new Error(nullError.message);
1421
+ }
1422
+ if (result$1.error?.code === "ABORTED") {
1423
+ if (editMode) sendMessageImmediately({
1424
+ type: "sdk-api-execution-failed",
1425
+ payload: {
1426
+ executionId,
1427
+ apiName,
1428
+ error: result$1.error,
1429
+ durationMs
1430
+ }
1431
+ });
1432
+ throw new DOMException(`API "${apiName}" execution was aborted`, "AbortError");
1433
+ }
1434
+ if (result$1.success) {
1435
+ if (editMode) sendMessageImmediately({
1436
+ type: "sdk-api-execution-completed",
1437
+ payload: {
1438
+ executionId,
1439
+ apiName,
1440
+ output: result$1.output,
1441
+ durationMs,
1442
+ diagnostics: result$1.diagnostics,
1443
+ orchestratorStartMs: result$1.executionStartMs,
1444
+ orchestratorDurationMs: result$1.executionDurationMs,
1445
+ fetchStartMs: result$1.fetchStartMs
1446
+ }
1447
+ });
1448
+ return result$1.output;
1449
+ }
1450
+ const error = result$1.error ?? {
1451
+ code: "UNKNOWN",
1452
+ message: "API execution failed"
1453
+ };
1454
+ if (editMode) sendMessageImmediately({
1455
+ type: "sdk-api-execution-failed",
1456
+ payload: {
1457
+ executionId,
1458
+ apiName,
1459
+ error,
1460
+ durationMs,
1461
+ diagnostics: result$1.diagnostics,
1462
+ orchestratorStartMs: result$1.executionStartMs,
1463
+ orchestratorDurationMs: result$1.executionDurationMs,
1464
+ fetchStartMs: result$1.fetchStartMs
1465
+ }
1466
+ });
1467
+ throw error;
1468
+ } catch (err) {
1469
+ if (editMode && !(err instanceof DOMException && err.name === "AbortError") && !(err instanceof Error && err.message === `API "${apiName}" returned no response`) && !(typeof err === "object" && err !== null && "code" in err)) {
1470
+ const durationMs = Date.now() - startTime;
1471
+ sendMessageImmediately({
1472
+ type: "sdk-api-execution-failed",
1473
+ payload: {
1474
+ executionId,
1475
+ apiName,
1476
+ error: {
1477
+ code: "UNKNOWN",
1478
+ message: err instanceof Error ? err.message : "API execution failed"
1479
+ },
1480
+ durationMs
1481
+ }
1482
+ });
1483
+ }
1484
+ throw err;
1485
+ }
1486
+ }
1487
+ const apiPath = getApiPath(apiName);
1488
+ const result = await root_store_default.apis.runApiByPath({
1489
+ path: apiPath,
1490
+ inputs: inputRecord
1491
+ });
1492
+ if (result == null) throw new Error(`API "${apiName}" returned no response`);
1493
+ if (result.error != null) throw result.error;
1494
+ return result.data;
1495
+ }
1496
+ /**
1497
+ * Creates a typed version of executeApi bound to a specific API registry.
1498
+ *
1499
+ * @example
1500
+ * ```typescript
1501
+ * import type { ApiRegistry } from "../../server/apis/index.js";
1502
+ * import { createTypedExecuteApi } from "@superblocksteam/library";
1503
+ * export const executeApi = createTypedExecuteApi<ApiRegistry>();
1504
+ * ```
1505
+ */
1506
+ function createTypedExecuteApi() {
1507
+ return executeApi;
1508
+ }
1509
+
1510
+ //#endregion
1511
+ //#region src/lib/internal-details/use-api-data.ts
1512
+ /**
1513
+ * useApiData — declarative data-fetching hook backed by SWR.
1514
+ *
1515
+ * Designed for GET-style reads that auto-fetch on mount and when inputs change.
1516
+ * Uses SWR for caching, deduplication, revalidation, and shared state.
1517
+ *
1518
+ * Use this hook for data loading. For mutations and event-driven calls use
1519
+ * `useApi`. For utility functions outside React use `executeApi`.
1520
+ *
1521
+ * @example
1522
+ * ```tsx
1523
+ * const { data, loading, isError, error } = useApiData("GetUsers", { email });
1524
+ * if (loading) return <Skeleton />;
1525
+ * if (isError) return <ErrorBanner error={error} />;
1526
+ * return <UserList users={data.users} />;
1527
+ * ```
1528
+ */
1529
+ const EXEC_ID_PREFIX = "sb-exec-id:";
1530
+ function getOrCreateExecId(cacheKey) {
1531
+ const key = EXEC_ID_PREFIX + cacheKey;
1532
+ try {
1533
+ const id$1 = sessionStorage.getItem(key);
1534
+ if (id$1) return id$1;
1535
+ } catch {}
1536
+ const id = generateId();
1537
+ try {
1538
+ sessionStorage.setItem(key, id);
1539
+ } catch {}
1540
+ return id;
1541
+ }
1542
+ function rotateExecId(cacheKey) {
1543
+ const key = EXEC_ID_PREFIX + cacheKey;
1544
+ const id = generateId();
1545
+ try {
1546
+ sessionStorage.setItem(key, id);
1547
+ } catch {}
1548
+ return id;
1549
+ }
1550
+ function useApiData(apiName, inputs, options = {}) {
1551
+ const { enabled = true, staleTime = 0, gcTime: _gcTime = 3e5, retry = 3, retryDelay = defaultRetryDelay, refetchOnWindowFocus = false, refetchOnReconnect = true, refetchInterval = false, structuralSharing = false } = options;
1552
+ const cacheKey = buildCacheKey(apiName, inputs);
1553
+ const controllerRef = useRef(null);
1554
+ const dataUpdatedAtRef = useRef(0);
1555
+ const fetcher = useCallback(async (_key) => {
1556
+ controllerRef.current?.abort();
1557
+ const controller = new AbortController();
1558
+ controllerRef.current = controller;
1559
+ const executionId = getOrCreateExecId(cacheKey);
1560
+ try {
1561
+ const result = await executeApi(apiName, inputs, {
1562
+ signal: controller.signal,
1563
+ executionId
1564
+ });
1565
+ const now = Date.now();
1566
+ dataUpdatedAtRef.current = now;
1567
+ dataUpdatedAtByKey.set(cacheKey, now);
1568
+ dataStore.set(cacheKey, result);
1569
+ return result;
1570
+ } finally {
1571
+ rotateExecId(cacheKey);
1572
+ }
1573
+ }, [cacheKey]);
1574
+ const swrConfig = {
1575
+ revalidateOnFocus: refetchOnWindowFocus,
1576
+ revalidateOnReconnect: refetchOnReconnect,
1577
+ refreshInterval: refetchInterval || 0,
1578
+ dedupingInterval: staleTime,
1579
+ compare: structuralSharing ? void 0 : (a, b) => a === b,
1580
+ onErrorRetry: (err, _key, _config, revalidate, { retryCount }) => {
1581
+ if (err instanceof DOMException && err.name === "AbortError") return;
1582
+ if (retry === false || retry === 0) return;
1583
+ if (!isRetryableError(err)) return;
1584
+ if (retryCount > retry) return;
1585
+ const delay = retryDelay(retryCount - 1);
1586
+ const timer = setTimeout(() => void revalidate({ retryCount }), delay);
1587
+ controllerRef.current?.signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
1588
+ },
1589
+ shouldRetryOnError: true,
1590
+ revalidateOnMount: staleTime > 0 ? void 0 : true,
1591
+ keepPreviousData: true,
1592
+ fallbackData: options.placeholderData
1593
+ };
1594
+ const { data, error, isValidating, isLoading, mutate: mutate$1 } = useSWR(enabled ? cacheKey : null, fetcher, swrConfig);
1595
+ const effectiveError = error instanceof DOMException && error.name === "AbortError" ? void 0 : error;
1596
+ const hasData = data !== void 0;
1597
+ const hasError = effectiveError !== void 0;
1598
+ const isFetching = isValidating;
1599
+ const status = hasData ? "success" : hasError ? "error" : "pending";
1600
+ const lastUpdatedAt = dataUpdatedAtByKey.get(cacheKey) ?? dataUpdatedAtRef.current;
1601
+ const isStaleNow = hasData && staleTime > 0 && lastUpdatedAt > 0 && Date.now() - lastUpdatedAt >= staleTime;
1602
+ const refetch = useCallback(async () => {
1603
+ controllerRef.current?.abort();
1604
+ controllerRef.current = null;
1605
+ await new Promise((resolve) => setTimeout(resolve, 0));
1606
+ return await mutate$1();
1607
+ }, [mutate$1]);
1608
+ const cancel = useCallback(() => {
1609
+ controllerRef.current?.abort();
1610
+ controllerRef.current = null;
1611
+ }, []);
1612
+ return {
1613
+ data: data ?? options.placeholderData,
1614
+ error: effectiveError ?? void 0,
1615
+ status,
1616
+ fetchStatus: isFetching ? "fetching" : "idle",
1617
+ loading: isLoading && !hasData,
1618
+ fetching: isFetching,
1619
+ isError: status === "error",
1620
+ isSuccess: status === "success",
1621
+ isStale: isStaleNow,
1622
+ refetch,
1623
+ cancel
1624
+ };
1625
+ }
1626
+ /**
1627
+ * Creates a typed version of useApiData bound to a specific API registry.
1628
+ *
1629
+ * @example
1630
+ * ```typescript
1631
+ * import type { ApiRegistry } from "../../server/apis/index.js";
1632
+ * import { useTypedApiData } from "@superblocksteam/library";
1633
+ * export const useApiData = useTypedApiData<ApiRegistry>();
1634
+ * ```
1635
+ */
1636
+ function useTypedApiData() {
1637
+ return useApiData;
1638
+ }
1639
+
1207
1640
  //#endregion
1208
1641
  //#region src/lib/internal-details/use-api.ts
1209
1642
  /**
@@ -1216,6 +1649,11 @@ if (import.meta.hot) import.meta.hot.accept("/server/apis/index.ts", () => {
1216
1649
  * Delegation happens at RUN TIME (when run() is invoked), not render time,
1217
1650
  * so components that mount before bootstrap completes still use the SDK path
1218
1651
  * once the user triggers execution.
1652
+ *
1653
+ * Three primitives are now available:
1654
+ * - `useApi` — imperative mutations (this file)
1655
+ * - `useApiData` — declarative reads with cache (use-api-data.ts)
1656
+ * - `executeApi` — plain Promise outside React (execute-api.ts)
1219
1657
  */
1220
1658
  /**
1221
1659
  * Get the API path for an API by name.
@@ -1314,7 +1752,10 @@ function useSdkApi(apiName) {
1314
1752
  apiName,
1315
1753
  output: result.output,
1316
1754
  durationMs,
1317
- diagnostics: result.diagnostics
1755
+ diagnostics: result.diagnostics,
1756
+ orchestratorStartMs: result.executionStartMs,
1757
+ orchestratorDurationMs: result.executionDurationMs,
1758
+ fetchStartMs: result.fetchStartMs
1318
1759
  }
1319
1760
  });
1320
1761
  return result.output;
@@ -1330,7 +1771,10 @@ function useSdkApi(apiName) {
1330
1771
  apiName,
1331
1772
  error,
1332
1773
  durationMs,
1333
- diagnostics: result.diagnostics
1774
+ diagnostics: result.diagnostics,
1775
+ orchestratorStartMs: result.executionStartMs,
1776
+ orchestratorDurationMs: result.executionDurationMs,
1777
+ fetchStartMs: result.fetchStartMs
1334
1778
  }
1335
1779
  });
1336
1780
  throw error;
@@ -1345,195 +1789,114 @@ function useSdkApi(apiName) {
1345
1789
  };
1346
1790
  }
1347
1791
  /**
1348
- * React hook for calling APIs with automatic type inference.
1349
- *
1350
- * Supports two modes:
1351
- *
1352
- * **Imperative** — call `run()` manually (e.g., on button click):
1353
- * ```ts
1354
- * const { run, loading, data } = useApi("CreateOrder");
1355
- * await run({ item, qty });
1356
- * ```
1357
- *
1358
- * **Declarative** — pass inputs to auto-fetch when they change:
1359
- * ```ts
1360
- * const { loading, data } = useApi("GetUsers", { email, name });
1361
- * // re-fetches automatically when email or name changes
1362
- * ```
1363
- *
1364
- * When the SDK API feature flag (`clark.sdk-api.enabled`) is on, this executes
1365
- * via the orchestrator's v3/execute endpoint from within the library iframe.
1366
- *
1367
- * When the flag is off, this delegates to the legacy YAML-based API system
1368
- * which uses `rootStore.apis.runApiByPath`.
1369
- *
1370
- * IMPORTANT: The path is chosen at RUN TIME (when run() is invoked), not at
1371
- * render time. This ensures that if the component mounts before the bootstrap
1372
- * response arrives (sdkApiEnabled still false), the API will still use the SDK
1373
- * path once bootstrap completes.
1792
+ * Imperative state machine for useApi (no inputs / mutation mode).
1793
+ * Extracted so Rules of Hooks are satisfied when useApi conditionally routes
1794
+ * to the declarative path via useApiData.
1374
1795
  *
1375
- * @param apiName - The name of the API to execute (e.g., "GetUsers")
1376
- * @param inputs - Optional inputs object. When provided, the API is called
1377
- * automatically on mount and whenever the input values change (shallow compare).
1378
- * @returns Object with `run`, `cancel`, `loading`, and `data`
1796
+ * Both useApiImperative and the useApiData call inside useApi are always
1797
+ * invoked on every render React requires unconditional hook calls.
1379
1798
  */
1380
- const INFLIGHT_KEY = "__sb_useApi_inflightFetches";
1381
- const MOUNTS_KEY = "__sb_useApi_activeMounts";
1382
- const EDIT_MODE_FETCH_DELAY_MS = 150;
1383
- function getFetchDelayMs() {
1384
- return isEditMode() ? EDIT_MODE_FETCH_DELAY_MS : 0;
1385
- }
1386
- function getInflightFetches() {
1387
- const w = globalThis;
1388
- if (!w[INFLIGHT_KEY]) w[INFLIGHT_KEY] = /* @__PURE__ */ new Map();
1389
- return w[INFLIGHT_KEY];
1390
- }
1391
- function getActiveMounts() {
1392
- const w = globalThis;
1393
- if (!w[MOUNTS_KEY]) w[MOUNTS_KEY] = /* @__PURE__ */ new Map();
1394
- return w[MOUNTS_KEY];
1395
- }
1396
- function incMounts(key) {
1397
- const mounts = getActiveMounts();
1398
- const prev = mounts.get(key) ?? 0;
1399
- mounts.set(key, prev + 1);
1400
- }
1401
- function decMounts(key) {
1402
- const mounts = getActiveMounts();
1403
- const n = (mounts.get(key) ?? 1) - 1;
1404
- if (n <= 0) mounts.delete(key);
1405
- else mounts.set(key, n);
1406
- }
1407
- function scheduleInflightCleanup(cacheKey) {
1408
- setTimeout(() => {
1409
- if ((getActiveMounts().get(cacheKey) ?? 0) === 0) getInflightFetches().delete(cacheKey);
1410
- }, Math.max(getFetchDelayMs() * 2, 1));
1411
- }
1412
- /** Build a cache key from API name and input fingerprint. */
1413
- function inflightKey(apiName, fingerprint) {
1414
- return `${apiName}::${fingerprint}`;
1415
- }
1416
- function useApi(apiName, inputs) {
1799
+ function useApiImperative(apiName) {
1417
1800
  const { run: sdkRun, cancel: sdkCancel } = useSdkApi(apiName);
1418
1801
  const { run: legacyRun, cancel: legacyCancel } = useApiStateful(apiName);
1419
1802
  const [loading, setLoading] = useState(false);
1420
1803
  const [data, setData] = useState(void 0);
1421
1804
  const [error, setError] = useState(void 0);
1805
+ const [status, setStatus] = useState("idle");
1806
+ const [variables, setVariables] = useState(void 0);
1422
1807
  const runIdRef = useRef(0);
1423
- const runRef = useRef(null);
1424
- const run = useCallback(async (runInputs) => {
1425
- getInflightFetches().delete(inflightKey(apiName, currentFingerprintRef.current ?? ""));
1426
- const thisRun = ++runIdRef.current;
1427
- setLoading(true);
1428
- setError(void 0);
1429
- try {
1430
- let sdkApiEnabled = root_store_default.sdkApiEnabled;
1431
- let hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1432
- if (!sdkApiEnabled && !hasEntryPoint) {
1433
- await root_store_default.awaitDiscovery();
1434
- sdkApiEnabled = root_store_default.sdkApiEnabled;
1435
- hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1436
- }
1437
- const result = sdkApiEnabled || hasEntryPoint ? await sdkRun(runInputs ?? {}) : await legacyRun(runInputs);
1438
- if (thisRun === runIdRef.current) {
1439
- setData(result);
1440
- setError(void 0);
1441
- }
1442
- return result;
1443
- } catch (err) {
1444
- if (thisRun === runIdRef.current) setError(err);
1445
- throw err;
1446
- } finally {
1447
- if (thisRun === runIdRef.current) setLoading(false);
1448
- }
1449
- }, [
1450
- apiName,
1451
- sdkRun,
1452
- legacyRun
1453
- ]);
1454
- runRef.current = run;
1455
- const cancel = useCallback(async () => {
1456
- ++runIdRef.current;
1457
- setLoading(false);
1458
- setError(void 0);
1459
- getInflightFetches().delete(inflightKey(apiName, currentFingerprintRef.current ?? ""));
1460
- if (root_store_default.sdkApiEnabled || !!root_store_default.getApiEntryPoint(apiName)) return sdkCancel();
1461
- return legacyCancel();
1462
- }, [
1463
- apiName,
1464
- sdkCancel,
1465
- legacyCancel
1466
- ]);
1467
- const inputRecord = inputs;
1468
- const inputFingerprint = inputRecord ? JSON.stringify(Object.entries(inputRecord).sort(([a], [b]) => a.localeCompare(b))) : null;
1469
- const currentFingerprintRef = useRef(inputFingerprint);
1470
- currentFingerprintRef.current = inputFingerprint;
1471
- useEffect(() => {
1472
- if (!inputRecord || inputFingerprint === null) return;
1473
- const cacheKey = inflightKey(apiName, inputFingerprint);
1474
- const inflightPromise = getInflightFetches().get(cacheKey);
1475
- if (inflightPromise) {
1476
- incMounts(cacheKey);
1477
- setLoading(true);
1808
+ return {
1809
+ run: useCallback(async (runInputs) => {
1478
1810
  const thisRun = ++runIdRef.current;
1479
- inflightPromise.then((result) => {
1480
- if (thisRun === runIdRef.current && result !== void 0) {
1811
+ setLoading(true);
1812
+ setError(void 0);
1813
+ setStatus("pending");
1814
+ setVariables(runInputs);
1815
+ try {
1816
+ let sdkApiEnabled = root_store_default.sdkApiEnabled;
1817
+ let hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1818
+ if (!sdkApiEnabled && !hasEntryPoint) {
1819
+ await root_store_default.awaitDiscovery();
1820
+ sdkApiEnabled = root_store_default.sdkApiEnabled;
1821
+ hasEntryPoint = !!root_store_default.getApiEntryPoint(apiName);
1822
+ }
1823
+ const result = sdkApiEnabled || hasEntryPoint ? await sdkRun(runInputs ?? {}) : await legacyRun(runInputs);
1824
+ if (thisRun === runIdRef.current) {
1481
1825
  setData(result);
1482
1826
  setError(void 0);
1827
+ setStatus("success");
1828
+ }
1829
+ return result;
1830
+ } catch (thrown) {
1831
+ if (thisRun === runIdRef.current) {
1832
+ setError(thrown);
1833
+ setStatus("error");
1483
1834
  }
1484
- }).catch((err) => {
1485
- if (thisRun === runIdRef.current) setError(err);
1486
- }).finally(() => {
1835
+ throw thrown;
1836
+ } finally {
1487
1837
  if (thisRun === runIdRef.current) setLoading(false);
1488
- });
1489
- return () => {
1490
- ++runIdRef.current;
1491
- setLoading(false);
1492
- decMounts(cacheKey);
1493
- scheduleInflightCleanup(cacheKey);
1494
- };
1495
- }
1496
- incMounts(cacheKey);
1497
- let resolveFetch;
1498
- let rejectFetch;
1499
- const placeholder = new Promise((resolve, reject) => {
1500
- resolveFetch = resolve;
1501
- rejectFetch = reject;
1502
- });
1503
- placeholder.catch(() => {});
1504
- getInflightFetches().set(cacheKey, placeholder);
1505
- let timerFired = false;
1506
- const timer = setTimeout(() => {
1507
- timerFired = true;
1508
- const fetchResult = runRef.current(inputRecord).catch((err) => {
1509
- rejectFetch(err);
1510
- });
1511
- getInflightFetches().set(cacheKey, placeholder);
1512
- fetchResult.then((result) => {
1513
- resolveFetch(result);
1514
- if (result !== void 0) scheduleInflightCleanup(cacheKey);
1515
- else if (getInflightFetches().get(cacheKey) === placeholder) getInflightFetches().delete(cacheKey);
1516
- });
1517
- }, getFetchDelayMs());
1518
- return () => {
1519
- clearTimeout(timer);
1520
- if (!timerFired) {
1521
- if (getInflightFetches().get(cacheKey) === placeholder) getInflightFetches().delete(cacheKey);
1522
- resolveFetch(void 0);
1523
1838
  }
1839
+ }, [
1840
+ apiName,
1841
+ sdkRun,
1842
+ legacyRun
1843
+ ]),
1844
+ cancel: useCallback(async () => {
1524
1845
  ++runIdRef.current;
1525
1846
  setLoading(false);
1526
- decMounts(cacheKey);
1527
- };
1528
- }, [apiName, inputFingerprint]);
1529
- return {
1530
- run,
1531
- cancel,
1847
+ setError(void 0);
1848
+ setStatus("idle");
1849
+ if (root_store_default.sdkApiEnabled || !!root_store_default.getApiEntryPoint(apiName)) return sdkCancel();
1850
+ return legacyCancel();
1851
+ }, [
1852
+ apiName,
1853
+ sdkCancel,
1854
+ legacyCancel
1855
+ ]),
1856
+ reset: useCallback(() => {
1857
+ ++runIdRef.current;
1858
+ setLoading(false);
1859
+ setData(void 0);
1860
+ setError(void 0);
1861
+ setStatus("idle");
1862
+ setVariables(void 0);
1863
+ }, []),
1532
1864
  loading,
1533
1865
  data,
1534
- error
1866
+ error,
1867
+ status,
1868
+ variables
1535
1869
  };
1536
1870
  }
1871
+ function useApi(apiName, inputs) {
1872
+ const isDeclarative = inputs !== void 0;
1873
+ const imperativeResult = useApiImperative(apiName);
1874
+ const declarativeResult = useApiData(apiName, inputs ?? {}, {
1875
+ enabled: isDeclarative,
1876
+ refetchOnWindowFocus: false,
1877
+ refetchOnReconnect: false,
1878
+ retry: false
1879
+ });
1880
+ if (isDeclarative) {
1881
+ const imperativeUsed = imperativeResult.status !== "idle";
1882
+ return {
1883
+ run: imperativeResult.run,
1884
+ cancel: async () => {
1885
+ await imperativeResult.cancel();
1886
+ declarativeResult.cancel();
1887
+ },
1888
+ reset: () => {
1889
+ imperativeResult.reset();
1890
+ },
1891
+ loading: imperativeUsed ? imperativeResult.loading : declarativeResult.fetching || declarativeResult.loading,
1892
+ data: imperativeUsed ? imperativeResult.data : declarativeResult.data,
1893
+ error: imperativeUsed ? imperativeResult.error : declarativeResult.error,
1894
+ status: imperativeUsed ? imperativeResult.status : declarativeResult.status,
1895
+ variables: imperativeUsed ? imperativeResult.variables : inputs
1896
+ };
1897
+ }
1898
+ return imperativeResult;
1899
+ }
1537
1900
  /**
1538
1901
  * Creates a typed version of useApi bound to a specific API registry type.
1539
1902
  *
@@ -4861,7 +5224,10 @@ const EmbedWrapper$2 = (props) => {
4861
5224
  executionId,
4862
5225
  apiName,
4863
5226
  output: result.output,
4864
- durationMs
5227
+ durationMs,
5228
+ orchestratorStartMs: result.executionStartMs,
5229
+ orchestratorDurationMs: result.executionDurationMs,
5230
+ fetchStartMs: result.fetchStartMs
4865
5231
  }
4866
5232
  });
4867
5233
  else sendMessageImmediately({
@@ -4873,7 +5239,10 @@ const EmbedWrapper$2 = (props) => {
4873
5239
  code: "UNKNOWN",
4874
5240
  message: "API execution failed"
4875
5241
  },
4876
- durationMs
5242
+ durationMs,
5243
+ orchestratorStartMs: result.executionStartMs,
5244
+ orchestratorDurationMs: result.executionDurationMs,
5245
+ fetchStartMs: result.fetchStartMs
4877
5246
  }
4878
5247
  });
4879
5248
  }).catch((error$1) => {
@@ -5608,6 +5977,7 @@ async function loadBuildManifest() {
5608
5977
  root_store_default.clearApiIntegrations();
5609
5978
  for (const [name, meta] of Object.entries(sdkApis)) {
5610
5979
  root_store_default.setApiEntryPoint(name, meta.entryPoint);
5980
+ if (meta.exportName) root_store_default.setApiExportName(name, meta.exportName);
5611
5981
  if (meta.integrations) root_store_default.setApiIntegrations(name, meta.integrations);
5612
5982
  }
5613
5983
  } else root_store_default.apis.loadApiManifest(mod.default);
@@ -5768,7 +6138,7 @@ const SbProvider = function SbProvider$1({ name = "codemode", children, classNam
5768
6138
  value: context$1 ?? void 0,
5769
6139
  children: isEmbedded ? /* @__PURE__ */ jsx(EmbedWrapper, { children }) : /* @__PURE__ */ jsx(Auth0Wrapper, { children })
5770
6140
  }), /* @__PURE__ */ jsx(DevTools, {})]
5771
- }) }), /* @__PURE__ */ jsx(interaction_layer_default, {})]
6141
+ }) }), isEditMode() && /* @__PURE__ */ jsx(interaction_layer_default, {})]
5772
6142
  });
5773
6143
  };
5774
6144
  var sb_provider_default = SbProvider;
@@ -6997,5 +7367,5 @@ early_console_buffer_default.getInstance().patchEarly();
6997
7367
  registerHtmlElements();
6998
7368
 
6999
7369
  //#endregion
7000
- export { App, PageNotFound, Prop, Property, PropsCategory, RouteLoadError, Section, Superblocks, sb_provider_default as SuperblocksProvider, createElement, embedStore, getAppMode, logoutIntegrations, registerComponent, tailwindStylesCategory, useApi, useApiStateful, useEmbedEvent, useEmbedProperties, useEmitEmbedEvent, useSuperblocksDataTags, useSuperblocksGroups, useSuperblocksProfiles, useSuperblocksUser, useTypedApi };
7370
+ export { App, PageNotFound, Prop, Property, PropsCategory, RouteLoadError, Section, Superblocks, sb_provider_default as SuperblocksProvider, createElement, createTypedExecuteApi, embedStore, executeApi, getAppMode, logoutIntegrations, queryClient, registerComponent, tailwindStylesCategory, useApi, useApiData, useApiStateful, useEmbedEvent, useEmbedProperties, useEmitEmbedEvent, useSuperblocksDataTags, useSuperblocksGroups, useSuperblocksProfiles, useSuperblocksUser, useTypedApi, useTypedApiData };
7001
7371
  //# sourceMappingURL=index.js.map