@ai4b-team/fsaos-gateway-sdk 3.34.2 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1085,6 +1085,10 @@ interface ItemsFilter {
1085
1085
  type?: string;
1086
1086
  /** Scope path boundary. Defaults to session scope. */
1087
1087
  scope?: string;
1088
+ /** Explicit mounted Space UUIDs, filtered by the kernel before pagination.
1089
+ * [] returns no work. Omitted preserves legacy scope behavior. This is a
1090
+ * read selection, never a write target or permission grant. */
1091
+ spaceIds?: string[];
1088
1092
  /** Direct parent_id filter. */
1089
1093
  parent_id?: string;
1090
1094
  /** Name ILIKE filter (partial match). */
@@ -1131,7 +1135,7 @@ declare function isListableItemsFilter(filter: ItemsFilter | null | undefined):
1131
1135
  * icon, ...) are ignored. `extra` adds what the screen owns: scope, page size.
1132
1136
  * Returns null when the stored filter names nothing to list.
1133
1137
  */
1134
- declare function itemsFilterFromQueryFilter(typeData: Record<string, unknown> | null | undefined, extra?: Pick<ItemsFilter, 'scope' | 'limit' | 'name' | 'parent_id'>): ItemsFilter | null;
1138
+ declare function itemsFilterFromQueryFilter(typeData: Record<string, unknown> | null | undefined, extra?: Pick<ItemsFilter, 'scope' | 'spaceIds' | 'limit' | 'name' | 'parent_id'>): ItemsFilter | null;
1135
1139
  /**
1136
1140
  * Fetch items with structured filters: one type, or OR-groups across types.
1137
1141
  * Calls kernel_query (gateway 'query' method). The kernel filters before it
@@ -1654,7 +1658,88 @@ declare function useSearch(query: string | undefined | null, itemTypes?: string[
1654
1658
  /** Multi-signal search (exact + trigram + keyword + semantic vector) via kernel resolve. */
1655
1659
  declare function useResolve(params: ResolveParams | null | undefined): _tanstack_react_query.UseQueryResult<ResolveResponse, Error>;
1656
1660
  /** Fetch items with structured filters: one type, or OR-groups across types. */
1657
- declare function useItems(filter: ItemsFilter | null | undefined): _tanstack_react_query.UseQueryResult<VFSItem[], Error>;
1661
+ declare function useItems(filter: ItemsFilter | null | undefined): _tanstack_react_query.QueryObserverRefetchErrorResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverSuccessResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverLoadingErrorResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverPendingResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverPlaceholderResult<VFSItem[], Error> | {
1662
+ data: undefined;
1663
+ error: Error;
1664
+ isError: true;
1665
+ isPending: false;
1666
+ isLoading: false;
1667
+ isLoadingError: false;
1668
+ isRefetchError: true;
1669
+ isSuccess: false;
1670
+ isPlaceholderData: false;
1671
+ status: "error";
1672
+ dataUpdatedAt: number;
1673
+ errorUpdatedAt: number;
1674
+ failureCount: number;
1675
+ failureReason: Error | null;
1676
+ errorUpdateCount: number;
1677
+ isFetched: boolean;
1678
+ isFetchedAfterMount: boolean;
1679
+ isFetching: boolean;
1680
+ isInitialLoading: boolean;
1681
+ isPaused: boolean;
1682
+ isRefetching: boolean;
1683
+ isStale: boolean;
1684
+ isEnabled: boolean;
1685
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1686
+ fetchStatus: _tanstack_react_query.FetchStatus;
1687
+ promise: Promise<VFSItem[]>;
1688
+ } | {
1689
+ data: undefined;
1690
+ error: null;
1691
+ isError: false;
1692
+ isPending: false;
1693
+ isLoading: false;
1694
+ isLoadingError: false;
1695
+ isRefetchError: false;
1696
+ isSuccess: true;
1697
+ isPlaceholderData: false;
1698
+ status: "success";
1699
+ dataUpdatedAt: number;
1700
+ errorUpdatedAt: number;
1701
+ failureCount: number;
1702
+ failureReason: Error | null;
1703
+ errorUpdateCount: number;
1704
+ isFetched: boolean;
1705
+ isFetchedAfterMount: boolean;
1706
+ isFetching: boolean;
1707
+ isInitialLoading: boolean;
1708
+ isPaused: boolean;
1709
+ isRefetching: boolean;
1710
+ isStale: boolean;
1711
+ isEnabled: boolean;
1712
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1713
+ fetchStatus: _tanstack_react_query.FetchStatus;
1714
+ promise: Promise<VFSItem[]>;
1715
+ } | {
1716
+ data: undefined;
1717
+ isError: false;
1718
+ error: null;
1719
+ isPending: false;
1720
+ isLoading: false;
1721
+ isLoadingError: false;
1722
+ isRefetchError: false;
1723
+ isSuccess: true;
1724
+ isPlaceholderData: true;
1725
+ status: "success";
1726
+ dataUpdatedAt: number;
1727
+ errorUpdatedAt: number;
1728
+ failureCount: number;
1729
+ failureReason: Error | null;
1730
+ errorUpdateCount: number;
1731
+ isFetched: boolean;
1732
+ isFetchedAfterMount: boolean;
1733
+ isFetching: boolean;
1734
+ isInitialLoading: boolean;
1735
+ isPaused: boolean;
1736
+ isRefetching: boolean;
1737
+ isStale: boolean;
1738
+ isEnabled: boolean;
1739
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1740
+ fetchStatus: _tanstack_react_query.FetchStatus;
1741
+ promise: Promise<VFSItem[]>;
1742
+ };
1658
1743
  interface InfiniteItemsOptions {
1659
1744
  /** Keep this cached listing fresh through the SDK's existing realtime transport. No polling. Defaults to false. */
1660
1745
  realtime?: boolean;
@@ -1967,6 +2052,8 @@ declare function useAccounts(): AccountsState;
1967
2052
  /** The working set (mounts) as reactive state, plus mount/unmount verbs. */
1968
2053
  declare function useMounts(): {
1969
2054
  mounts: MountRef[];
2055
+ selectedSpaces: MountRef[];
2056
+ readSpaceIds: string[];
1970
2057
  homeAnchor: MountRef | null;
1971
2058
  anchor: MountRef | null;
1972
2059
  setAnchor: typeof selectMount;
package/dist/index.d.ts CHANGED
@@ -1085,6 +1085,10 @@ interface ItemsFilter {
1085
1085
  type?: string;
1086
1086
  /** Scope path boundary. Defaults to session scope. */
1087
1087
  scope?: string;
1088
+ /** Explicit mounted Space UUIDs, filtered by the kernel before pagination.
1089
+ * [] returns no work. Omitted preserves legacy scope behavior. This is a
1090
+ * read selection, never a write target or permission grant. */
1091
+ spaceIds?: string[];
1088
1092
  /** Direct parent_id filter. */
1089
1093
  parent_id?: string;
1090
1094
  /** Name ILIKE filter (partial match). */
@@ -1131,7 +1135,7 @@ declare function isListableItemsFilter(filter: ItemsFilter | null | undefined):
1131
1135
  * icon, ...) are ignored. `extra` adds what the screen owns: scope, page size.
1132
1136
  * Returns null when the stored filter names nothing to list.
1133
1137
  */
1134
- declare function itemsFilterFromQueryFilter(typeData: Record<string, unknown> | null | undefined, extra?: Pick<ItemsFilter, 'scope' | 'limit' | 'name' | 'parent_id'>): ItemsFilter | null;
1138
+ declare function itemsFilterFromQueryFilter(typeData: Record<string, unknown> | null | undefined, extra?: Pick<ItemsFilter, 'scope' | 'spaceIds' | 'limit' | 'name' | 'parent_id'>): ItemsFilter | null;
1135
1139
  /**
1136
1140
  * Fetch items with structured filters: one type, or OR-groups across types.
1137
1141
  * Calls kernel_query (gateway 'query' method). The kernel filters before it
@@ -1654,7 +1658,88 @@ declare function useSearch(query: string | undefined | null, itemTypes?: string[
1654
1658
  /** Multi-signal search (exact + trigram + keyword + semantic vector) via kernel resolve. */
1655
1659
  declare function useResolve(params: ResolveParams | null | undefined): _tanstack_react_query.UseQueryResult<ResolveResponse, Error>;
1656
1660
  /** Fetch items with structured filters: one type, or OR-groups across types. */
1657
- declare function useItems(filter: ItemsFilter | null | undefined): _tanstack_react_query.UseQueryResult<VFSItem[], Error>;
1661
+ declare function useItems(filter: ItemsFilter | null | undefined): _tanstack_react_query.QueryObserverRefetchErrorResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverSuccessResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverLoadingErrorResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverPendingResult<VFSItem[], Error> | _tanstack_react_query.QueryObserverPlaceholderResult<VFSItem[], Error> | {
1662
+ data: undefined;
1663
+ error: Error;
1664
+ isError: true;
1665
+ isPending: false;
1666
+ isLoading: false;
1667
+ isLoadingError: false;
1668
+ isRefetchError: true;
1669
+ isSuccess: false;
1670
+ isPlaceholderData: false;
1671
+ status: "error";
1672
+ dataUpdatedAt: number;
1673
+ errorUpdatedAt: number;
1674
+ failureCount: number;
1675
+ failureReason: Error | null;
1676
+ errorUpdateCount: number;
1677
+ isFetched: boolean;
1678
+ isFetchedAfterMount: boolean;
1679
+ isFetching: boolean;
1680
+ isInitialLoading: boolean;
1681
+ isPaused: boolean;
1682
+ isRefetching: boolean;
1683
+ isStale: boolean;
1684
+ isEnabled: boolean;
1685
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1686
+ fetchStatus: _tanstack_react_query.FetchStatus;
1687
+ promise: Promise<VFSItem[]>;
1688
+ } | {
1689
+ data: undefined;
1690
+ error: null;
1691
+ isError: false;
1692
+ isPending: false;
1693
+ isLoading: false;
1694
+ isLoadingError: false;
1695
+ isRefetchError: false;
1696
+ isSuccess: true;
1697
+ isPlaceholderData: false;
1698
+ status: "success";
1699
+ dataUpdatedAt: number;
1700
+ errorUpdatedAt: number;
1701
+ failureCount: number;
1702
+ failureReason: Error | null;
1703
+ errorUpdateCount: number;
1704
+ isFetched: boolean;
1705
+ isFetchedAfterMount: boolean;
1706
+ isFetching: boolean;
1707
+ isInitialLoading: boolean;
1708
+ isPaused: boolean;
1709
+ isRefetching: boolean;
1710
+ isStale: boolean;
1711
+ isEnabled: boolean;
1712
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1713
+ fetchStatus: _tanstack_react_query.FetchStatus;
1714
+ promise: Promise<VFSItem[]>;
1715
+ } | {
1716
+ data: undefined;
1717
+ isError: false;
1718
+ error: null;
1719
+ isPending: false;
1720
+ isLoading: false;
1721
+ isLoadingError: false;
1722
+ isRefetchError: false;
1723
+ isSuccess: true;
1724
+ isPlaceholderData: true;
1725
+ status: "success";
1726
+ dataUpdatedAt: number;
1727
+ errorUpdatedAt: number;
1728
+ failureCount: number;
1729
+ failureReason: Error | null;
1730
+ errorUpdateCount: number;
1731
+ isFetched: boolean;
1732
+ isFetchedAfterMount: boolean;
1733
+ isFetching: boolean;
1734
+ isInitialLoading: boolean;
1735
+ isPaused: boolean;
1736
+ isRefetching: boolean;
1737
+ isStale: boolean;
1738
+ isEnabled: boolean;
1739
+ refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<VFSItem[], Error>>;
1740
+ fetchStatus: _tanstack_react_query.FetchStatus;
1741
+ promise: Promise<VFSItem[]>;
1742
+ };
1658
1743
  interface InfiniteItemsOptions {
1659
1744
  /** Keep this cached listing fresh through the SDK's existing realtime transport. No polling. Defaults to false. */
1660
1745
  realtime?: boolean;
@@ -1967,6 +2052,8 @@ declare function useAccounts(): AccountsState;
1967
2052
  /** The working set (mounts) as reactive state, plus mount/unmount verbs. */
1968
2053
  declare function useMounts(): {
1969
2054
  mounts: MountRef[];
2055
+ selectedSpaces: MountRef[];
2056
+ readSpaceIds: string[];
1970
2057
  homeAnchor: MountRef | null;
1971
2058
  anchor: MountRef | null;
1972
2059
  setAnchor: typeof selectMount;
package/dist/index.js CHANGED
@@ -757,6 +757,10 @@ function listingTypes(filter) {
757
757
  }
758
758
  function affectsListing(query, filter, event) {
759
759
  if (containsChangedItem(query, event)) return true;
760
+ if (filter.spaceIds !== void 0) {
761
+ if (!filter.spaceIds.length) return false;
762
+ if (event.space_item_id !== void 0 && !filter.spaceIds.includes(event.space_item_id) && !filter.spaceIds.includes(event.old_space_item_id)) return false;
763
+ }
760
764
  const scope = filter.scope;
761
765
  const scoped = typeof scope === "string" && scope !== "__unscoped__";
762
766
  const hasPath = typeof event.path === "string" || typeof event.old_path === "string";
@@ -800,6 +804,29 @@ function clearInfiniteItemsRefreshes(client2) {
800
804
  pending2.delete(client2);
801
805
  }
802
806
 
807
+ // src/items-selection.ts
808
+ var SelectedSpaceContractError = class extends Error {
809
+ constructor() {
810
+ super("The server did not confirm the selected-Space listing contract");
811
+ }
812
+ };
813
+ function assertItemsSelection(requested, returned) {
814
+ if (!Array.isArray(returned) || returned.some((id) => typeof id !== "string") || JSON.stringify([...new Set(returned)].sort()) !== JSON.stringify(requested)) {
815
+ throw new SelectedSpaceContractError();
816
+ }
817
+ }
818
+ function normalizeItemsSelection(filter) {
819
+ if (!filter || filter.spaceIds === void 0) return filter;
820
+ return {
821
+ ...filter,
822
+ spaceIds: [...new Set(filter.spaceIds.map((id) => id.toLowerCase()))].sort(),
823
+ ...filter.limit !== void 0 ? { limit: Math.max(1, Math.min(filter.limit, 500)) } : {}
824
+ };
825
+ }
826
+ function unavailableItemsSelection(error) {
827
+ return error instanceof SelectedSpaceContractError || error instanceof EnforcementDeniedError || error instanceof GatewayRequestError && (error.payload?.error === "SPACE_SELECTION_UNAVAILABLE" || error.payload?.error === "QUERY_PARAMS_INVALID" || error.status === 401 || error.status === 403);
828
+ }
829
+
803
830
  // src/vfs-keys.ts
804
831
  function sk() {
805
832
  return getScope() ?? "__unscoped__";
@@ -822,7 +849,7 @@ var vfsKeys = {
822
849
  /** Search results (scope-aware — search is scoped). */
823
850
  search: (query, types) => ["vfs", sk(), "search", query, ...types ?? []],
824
851
  /** Items query (type-based listing with filters, scope-aware). */
825
- items: (type, filters) => ["vfs", sk(), "items", type, JSON.stringify(filters ?? {})],
852
+ items: (type, filters) => ["vfs", sk(), "items", type, JSON.stringify(normalizeItemsSelection(filters ?? {}))],
826
853
  /** All items queries for current scope (for broad invalidation). */
827
854
  allItems: () => ["vfs", sk(), "items"],
828
855
  /** Recursive tree at a path + depth (scope-aware). */
@@ -1712,12 +1739,15 @@ function itemsFilterFromQueryFilter(typeData, extra = {}) {
1712
1739
  return { ...filter, ...extra };
1713
1740
  }
1714
1741
  async function fetchItems(filter, options = {}) {
1742
+ filter = normalizeItemsSelection(filter);
1743
+ if (filter.spaceIds?.length === 0) return { items: [], total_hint: 0 };
1715
1744
  const params = {};
1716
1745
  if (filter.type) params.item_type = filter.type;
1717
1746
  if (filter.scope) {
1718
1747
  params.scope_path = filter.scope;
1719
1748
  }
1720
1749
  const filters = {};
1750
+ if (filter.spaceIds !== void 0) filters.space_ids = filter.spaceIds;
1721
1751
  if (filter.name) filters.name = filter.name;
1722
1752
  if (filter.tag) filters.tag = filter.tag;
1723
1753
  if (filter.parent_id) filters.parent_id = filter.parent_id;
@@ -1744,6 +1774,9 @@ async function fetchItems(filter, options = {}) {
1744
1774
  }
1745
1775
  try {
1746
1776
  const response = await gatewayCall("query", params);
1777
+ if (filter.spaceIds !== void 0) {
1778
+ assertItemsSelection(filter.spaceIds, response.selected_space_ids ?? response.content?.selected_space_ids);
1779
+ }
1747
1780
  const items = extractItems(response);
1748
1781
  for (const item of items) {
1749
1782
  getQueryClient().setQueryData(vfsKeys.item(item.path), item);
@@ -1751,7 +1784,7 @@ async function fetchItems(filter, options = {}) {
1751
1784
  return { items, total_hint: response.total ?? response.content?.total ?? items.length };
1752
1785
  } catch (err) {
1753
1786
  console.error(`[gateway] Failed to fetch items of type ${filter.type ?? "(any_of)"}:`, err);
1754
- if (options.throwOnError) throw err;
1787
+ if (options.throwOnError || filter.spaceIds !== void 0) throw err;
1755
1788
  return { items: [], total_hint: 0 };
1756
1789
  }
1757
1790
  }
@@ -2365,22 +2398,28 @@ function useResolve(params) {
2365
2398
  }
2366
2399
  function useItems(filter) {
2367
2400
  useScopeKey();
2368
- const stableFilter = useMemo(() => filter, [JSON.stringify(filter)]);
2369
- return useQuery({
2401
+ const selected = normalizeItemsSelection(filter);
2402
+ const stableFilter = useMemo(() => selected, [JSON.stringify(selected)]);
2403
+ const query = useQuery({
2370
2404
  queryKey: vfsKeys.items(
2371
2405
  stableFilter?.type ?? "",
2372
2406
  stableFilter
2373
2407
  ),
2374
2408
  queryFn: () => fetchItems(stableFilter),
2375
2409
  enabled: isListableItemsFilter(stableFilter),
2410
+ ...stableFilter?.spaceIds !== void 0 ? {
2411
+ retry: (attempt, error) => !unavailableItemsSelection(error) && attempt < 1
2412
+ } : {},
2376
2413
  select: (result) => result.items
2377
2414
  });
2415
+ return stableFilter?.spaceIds !== void 0 && unavailableItemsSelection(query.error) ? { ...query, data: void 0 } : query;
2378
2416
  }
2379
2417
  function useInfiniteItems(filter, options = {}) {
2380
2418
  const scopeKey = useScopeKey();
2381
2419
  const { realtime = false } = options;
2382
- const PAGE_SIZE = filter?.limit ?? 20;
2383
- const stableFilter = useMemo(() => filter, [JSON.stringify(filter)]);
2420
+ const selected = normalizeItemsSelection(filter);
2421
+ const PAGE_SIZE = selected?.limit ?? 20;
2422
+ const stableFilter = useMemo(() => selected, [JSON.stringify(selected)]);
2384
2423
  const stableKey = useMemo(() => ["vfs", getScope() ?? "__unscoped__", "infinite-items", stableFilter?.type ?? "", JSON.stringify(stableFilter ?? {})], [scopeKey, stableFilter]);
2385
2424
  const query = useInfiniteQuery({
2386
2425
  queryKey: stableKey,
@@ -2393,16 +2432,21 @@ function useInfiniteItems(filter, options = {}) {
2393
2432
  },
2394
2433
  initialPageParam: 0,
2395
2434
  getNextPageParam: (lastPage, _allPages, lastPageParam) => {
2435
+ if (stableFilter?.spaceIds !== void 0 && lastPage.total_hint !== void 0 && lastPageParam + lastPage.items.length >= lastPage.total_hint) return void 0;
2396
2436
  if (lastPage.items.length < PAGE_SIZE) return void 0;
2397
2437
  return lastPageParam + lastPage.items.length;
2398
2438
  },
2399
2439
  enabled: isListableItemsFilter(stableFilter),
2440
+ ...stableFilter?.spaceIds !== void 0 ? {
2441
+ retry: (attempt, error) => !unavailableItemsSelection(error) && attempt < 1
2442
+ } : {},
2400
2443
  // Realtime marks the cache stale only on relevant changes or reconnect.
2401
2444
  // Expanding a channel or handing it to another observer reuses its pages.
2402
2445
  staleTime: realtime ? Infinity : void 0,
2403
2446
  meta: realtime && stableFilter ? { infiniteItemsRealtime: { filter: stableFilter } } : {}
2404
2447
  });
2405
2448
  const allItems = useMemo(() => {
2449
+ if (stableFilter?.spaceIds !== void 0 && unavailableItemsSelection(query.error)) return [];
2406
2450
  if (!query.data?.pages) return [];
2407
2451
  const seen = /* @__PURE__ */ new Set();
2408
2452
  const flat = [];
@@ -2415,17 +2459,18 @@ function useInfiniteItems(filter, options = {}) {
2415
2459
  }
2416
2460
  }
2417
2461
  return flat;
2418
- }, [query.data?.pages]);
2462
+ }, [query.data?.pages, query.error, stableFilter]);
2463
+ const unavailable = stableFilter?.spaceIds !== void 0 && unavailableItemsSelection(query.error);
2419
2464
  return {
2420
2465
  allItems,
2421
2466
  refetch: query.refetch,
2422
2467
  fetchNextPage: query.fetchNextPage,
2423
2468
  isFetchingNextPage: query.isFetchingNextPage,
2424
- isExhausted: !query.hasNextPage,
2469
+ isExhausted: unavailable || !query.hasNextPage,
2425
2470
  isLoading: query.isLoading,
2426
2471
  isFetching: query.isFetching,
2427
2472
  error: query.error,
2428
- totalHint: query.data?.pages?.[0]?.total_hint
2473
+ totalHint: unavailable ? void 0 : query.data?.pages?.[0]?.total_hint
2429
2474
  };
2430
2475
  }
2431
2476
  function useInfiniteChildren(path) {
@@ -2733,10 +2778,15 @@ function useMounts() {
2733
2778
  useScopeKey();
2734
2779
  const version = useSyncExternalStore(subscribeMounts, getMountVersion, getMountVersion);
2735
2780
  const mounts = useMemo(() => getMounts(), [version]);
2781
+ const anchor = getAnchorRef();
2782
+ const selectedSpaces = useMemo(() => mounts.filter((ref) => ref.kind === "space" && (anchor?.kind === "account" || ref.id === anchor?.id)), [mounts, anchor]);
2783
+ const readSpaceIds = useMemo(() => selectedSpaces.map((ref) => ref.id).sort(), [selectedSpaces]);
2736
2784
  return {
2737
2785
  mounts,
2786
+ selectedSpaces,
2787
+ readSpaceIds,
2738
2788
  homeAnchor: getHomeAnchor(),
2739
- anchor: getAnchorRef(),
2789
+ anchor,
2740
2790
  setAnchor: selectMount,
2741
2791
  replaceMounts,
2742
2792
  mount,