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

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.cjs CHANGED
@@ -101,6 +101,7 @@ __export(src_exports, {
101
101
  initSession: () => initSession,
102
102
  initVfsRealtimeWs: () => initVfsRealtimeWs,
103
103
  initializeFsaos: () => initializeFsaos,
104
+ initializeWorkspace: () => initializeWorkspace,
104
105
  interpretSchema: () => interpretSchema,
105
106
  invalidateAllVfs: () => invalidateAllVfs,
106
107
  invalidateChannelMessages: () => invalidateChannelMessages,
@@ -116,6 +117,7 @@ __export(src_exports, {
116
117
  isScopeReady: () => isScopeReady,
117
118
  itemsFilterFromQueryFilter: () => itemsFilterFromQueryFilter,
118
119
  listChildren: () => listChildren,
120
+ loadMounts: () => loadMounts,
119
121
  mount: () => mount2,
120
122
  mountRealtimeScope: () => mountRealtimeScope,
121
123
  normalizeItem: () => normalizeItem,
@@ -127,9 +129,11 @@ __export(src_exports, {
127
129
  readItem: () => readItem,
128
130
  refreshQuery: () => refreshQuery,
129
131
  registerCleanup: () => registerCleanup,
132
+ replaceMounts: () => replaceMounts,
130
133
  resetAllSdkState: () => resetAllSdkState,
131
134
  resolvePrincipalId: () => resolvePrincipalId,
132
135
  resubscribePath: () => resubscribePath,
136
+ selectMount: () => selectMount,
133
137
  serverDeadlinePassed: () => serverDeadlinePassed,
134
138
  serverMsUntil: () => serverMsUntil,
135
139
  serverNow: () => serverNow,
@@ -335,7 +339,7 @@ function notifyAnchorListeners() {
335
339
  }
336
340
  function setAnchor(ref) {
337
341
  const path = typeof ref === "string" ? ref : ref.path;
338
- const changed = currentAnchorPath !== path;
342
+ const changed = currentAnchorPath !== path || currentAnchorRef?.id !== (typeof ref === "string" ? void 0 : ref.id);
339
343
  currentAnchorPath = path;
340
344
  currentAnchorRef = typeof ref === "string" ? null : ref;
341
345
  if (typeof ref !== "string" && !mountSet.some((m) => m.id === ref.id)) {
@@ -372,13 +376,13 @@ function isAnchorReady() {
372
376
  return anchorResolved;
373
377
  }
374
378
  var homeAnchor = null;
375
- function electHomeAnchor(ref) {
379
+ function electHomeAnchor(ref, options = {}) {
376
380
  if (homeAnchor) {
377
381
  console.warn("[SDK] electHomeAnchor called twice; ignoring (logout to re-elect).");
378
382
  return;
379
383
  }
380
384
  homeAnchor = ref;
381
- setAnchor(ref);
385
+ if (options.activate !== false) setAnchor(ref);
382
386
  }
383
387
  function getHomeAnchor() {
384
388
  return homeAnchor;
@@ -408,32 +412,33 @@ function subscribeMounts(listener) {
408
412
  mountListeners.delete(listener);
409
413
  };
410
414
  }
411
- function setMountsState(mounts) {
415
+ function commitMountSelection(mounts, anchor) {
416
+ const mountsChanged = JSON.stringify(mountSet) !== JSON.stringify(mounts);
417
+ const anchorChanged = currentAnchorPath !== anchor.path || currentAnchorRef?.id !== anchor.id;
412
418
  mountSet = mounts;
413
- notifyMountListeners();
414
- }
415
- function addMountState(ref) {
416
- if (mountSet.some((m) => m.id === ref.id)) return;
417
- mountSet = [...mountSet, ref];
418
- notifyMountListeners();
419
- }
420
- function removeMountState(id) {
421
- const removed = mountSet.find((m) => m.id === id);
422
- if (!removed) return;
423
- mountSet = mountSet.filter((m) => m.id !== id);
424
- notifyMountListeners();
425
- if (currentAnchorRef?.id === id && homeAnchor) {
426
- setAnchor(homeAnchor);
419
+ currentAnchorPath = anchor.path;
420
+ currentAnchorRef = anchor;
421
+ if (!anchorResolved) {
422
+ anchorResolved = true;
423
+ resolveAnchorReady();
427
424
  }
425
+ if (mountsChanged) notifyMountListeners();
426
+ if (anchorChanged) notifyAnchorListeners();
428
427
  }
429
428
  var sessionEntry = null;
430
429
  var sessionPromise = null;
430
+ var sessionGeneration = 0;
431
+ function getSessionGeneration() {
432
+ return sessionGeneration;
433
+ }
431
434
  async function initSession() {
432
435
  if (sessionEntry) return sessionEntry;
433
436
  if (sessionPromise) return sessionPromise;
437
+ const generation = sessionGeneration;
434
438
  sessionPromise = (async () => {
435
439
  const hostname = getHostname();
436
440
  const token = await getAccessToken();
441
+ if (generation !== sessionGeneration) throw new Error("Session changed during initialization");
437
442
  const headers = {};
438
443
  if (token) {
439
444
  headers["Authorization"] = `Bearer ${token}`;
@@ -446,6 +451,7 @@ async function initSession() {
446
451
  throw new Error(`Session init failed: ${response.status} ${response.statusText}`);
447
452
  }
448
453
  const json = await response.json();
454
+ if (generation !== sessionGeneration) throw new Error("Session changed during initialization");
449
455
  const entry = json.entry || json;
450
456
  sessionEntry = {
451
457
  scope_id: entry.scope_id,
@@ -459,13 +465,17 @@ async function initSession() {
459
465
  principal_id: entry.principal_id || null
460
466
  };
461
467
  return sessionEntry;
462
- })();
468
+ })().catch((error) => {
469
+ if (generation === sessionGeneration) sessionPromise = null;
470
+ throw error;
471
+ });
463
472
  return sessionPromise;
464
473
  }
465
474
  function getSessionEntry() {
466
475
  return sessionEntry;
467
476
  }
468
477
  function clearSession() {
478
+ sessionGeneration++;
469
479
  sessionEntry = null;
470
480
  sessionPromise = null;
471
481
  currentAnchorPath = null;
@@ -480,6 +490,7 @@ function registerCleanup(fn) {
480
490
  function resetAllSdkState() {
481
491
  clearSession();
482
492
  clearCachedToken();
493
+ resolveAnchorReady();
483
494
  anchorResolved = false;
484
495
  anchorReady = new Promise((resolve) => {
485
496
  resolveAnchorReady = resolve;
@@ -572,24 +583,28 @@ var GatewayRequestError = class extends Error {
572
583
  }
573
584
  };
574
585
  async function gatewayCall(method, params = {}) {
586
+ const generation = getSessionGeneration();
587
+ const requestedAnchor = getAnchor();
575
588
  const hostname = getHostname();
576
589
  const url = `${GATEWAY_URL}/d/${hostname}`;
577
590
  const headers = {
578
591
  "Content-Type": "application/json"
579
592
  };
580
593
  const token = await getAccessToken();
594
+ if (generation !== getSessionGeneration()) throw new Error("Session changed before gateway request");
581
595
  if (token) {
582
596
  headers["Authorization"] = `Bearer ${token}`;
583
597
  }
584
598
  if (!ANCHOR_INDEPENDENT_METHODS.has(method) && !params._scope_path) {
585
599
  await awaitAnchorReady();
586
600
  }
601
+ if (generation !== getSessionGeneration()) throw new Error("Session changed before gateway request");
587
602
  const enrichedParams = { ...params };
588
603
  const session = getSessionEntry();
589
604
  if (!enrichedParams.domain_scope_id && session?.scope_id) {
590
605
  enrichedParams.domain_scope_id = session.scope_id;
591
606
  }
592
- const anchorPath = getAnchor();
607
+ const anchorPath = requestedAnchor ?? getAnchor();
593
608
  if (!enrichedParams._scope_path && anchorPath) {
594
609
  enrichedParams._scope_path = anchorPath;
595
610
  }
@@ -599,6 +614,7 @@ async function gatewayCall(method, params = {}) {
599
614
  body: JSON.stringify({ method, params: enrichedParams })
600
615
  });
601
616
  const responseBody = await response.text();
617
+ if (generation !== getSessionGeneration()) throw new Error("Session changed during gateway request");
602
618
  let json = null;
603
619
  if (responseBody) {
604
620
  try {
@@ -705,6 +721,11 @@ function clearArrangementRefreshes(client2) {
705
721
  pending.delete(client2);
706
722
  }
707
723
  var client = null;
724
+ registerCleanup(() => {
725
+ if (!client) return;
726
+ void client.cancelQueries({ queryKey: ["vfs"] });
727
+ client.removeQueries({ queryKey: ["vfs"] });
728
+ });
708
729
  function setQueryClient(custom) {
709
730
  if (client && client !== custom) {
710
731
  throw new Error(
@@ -1982,60 +2003,40 @@ function invalidateOpenEnvelope(path, strategy) {
1982
2003
  }
1983
2004
  }
1984
2005
 
1985
- // src/mounts.ts
1986
- function declPath(memberSpacePath) {
1987
- return `${memberSpacePath}/settings/mounts`;
1988
- }
1989
- var declItemPath = null;
1990
- async function loadMounts(memberSpacePath, resolveRef) {
1991
- declItemPath = declPath(memberSpacePath);
1992
- try {
1993
- const res = await gatewayCall("read", { path: declItemPath });
1994
- const ids = res?.item?.type_data?.mounts ?? [];
1995
- const refs = ids.map(resolveRef).filter((r) => r !== null);
1996
- const home = getHomeAnchor();
1997
- if (home && !refs.some((r) => r.id === home.id)) {
1998
- refs.unshift(home);
1999
- }
2000
- setMountsState(refs);
2001
- return refs;
2002
- } catch {
2003
- const home = getHomeAnchor();
2004
- const refs = home ? [home] : [];
2005
- setMountsState(refs);
2006
- return refs;
2007
- }
2008
- }
2009
- async function persist() {
2010
- if (!declItemPath) return;
2011
- const ids = getMounts().map((m) => m.id);
2012
- const lastAnchor = getAnchorRef()?.id ?? null;
2013
- try {
2014
- await gatewayCall("update", {
2015
- path: declItemPath,
2016
- type_data: { mounts: ids, last_anchor: lastAnchor }
2017
- });
2018
- } catch (e) {
2019
- try {
2020
- const parent = declItemPath.split("/").slice(0, -1).join("/");
2021
- await gatewayCall("create", {
2022
- parent_path: parent,
2023
- name: "mounts",
2024
- item_type: "file",
2025
- type_data: { mounts: ids, last_anchor: lastAnchor }
2026
- });
2027
- } catch (e2) {
2028
- console.warn("[SDK] mount declaration persist failed:", e2);
2029
- }
2030
- }
2031
- }
2032
- async function mount(ref) {
2033
- addMountState(ref);
2034
- await persist();
2006
+ // src/memberships.ts
2007
+ function membershipToMountRef(m) {
2008
+ return {
2009
+ id: m.accountId,
2010
+ path: m.path,
2011
+ kind: m.isSpace ? "space" : "account",
2012
+ name: m.name,
2013
+ grantedUnderAccountId: m.grantedUnderAccountId ?? void 0
2014
+ };
2035
2015
  }
2036
- async function unmount(id) {
2037
- removeMountState(id);
2038
- await persist();
2016
+ async function fetchMemberships() {
2017
+ const result = await gatewayCall("list-memberships", {});
2018
+ const memberships = result.memberships || [];
2019
+ return memberships.map((m) => ({
2020
+ accountId: m.account_id,
2021
+ path: m.account_path,
2022
+ name: m.account_name,
2023
+ role: m.role || "member",
2024
+ typeData: m.account_type_data || {},
2025
+ joinedAt: m.joined_at,
2026
+ via: m.via,
2027
+ placement: m.placement,
2028
+ subordinateTo: m.subordinate_to ?? null,
2029
+ cascadeAnchorId: m.cascade_anchor_id ?? null,
2030
+ isAccount: m.is_account ?? void 0,
2031
+ isSpace: m.is_space ?? void 0,
2032
+ isOwner: m.is_owner ?? void 0,
2033
+ billingResponsible: m.billing_responsible ?? void 0,
2034
+ grantedVia: m.granted_via ?? null,
2035
+ grantedUnderAccountId: m.granted_under_account_id ?? null,
2036
+ membershipPath: m.membership_path ?? null,
2037
+ personalSpaceId: m.personal_space_id ?? null,
2038
+ personalSpacePath: m.personal_space_path ?? null
2039
+ }));
2039
2040
  }
2040
2041
 
2041
2042
  // src/vfs-realtime-ws.ts
@@ -2045,7 +2046,10 @@ var unsubCcmEvents = null;
2045
2046
  var debounceTimers = /* @__PURE__ */ new Map();
2046
2047
  var DEBOUNCE_MS = 250;
2047
2048
  function initVfsRealtimeWs(accountId) {
2048
- disposeVfsRealtimeWs();
2049
+ if (unsubEvents) {
2050
+ mountRealtimeScope(accountId);
2051
+ return;
2052
+ }
2049
2053
  mountRealtimeScope(accountId);
2050
2054
  unsubEvents = subscribeToEvents(
2051
2055
  "vfs_change",
@@ -2162,6 +2166,147 @@ function applyInvalidation(action, path, data, parentPath) {
2162
2166
  }
2163
2167
  }
2164
2168
 
2169
+ // src/mounts.ts
2170
+ var declaration = null;
2171
+ registerCleanup(() => {
2172
+ declaration = null;
2173
+ });
2174
+ function assertCurrent(owner) {
2175
+ if (owner !== declaration || owner.generation !== getSessionGeneration()) throw new Error("Workspace session changed");
2176
+ }
2177
+ function missing(error) {
2178
+ return error instanceof GatewayRequestError && error.payload?.error === "item_not_found";
2179
+ }
2180
+ function canonical(owner, ref) {
2181
+ const current = owner.resolve(ref.id);
2182
+ if (!current || current.path !== ref.path || current.kind !== ref.kind) {
2183
+ throw new Error("Mount is not an available membership");
2184
+ }
2185
+ return current;
2186
+ }
2187
+ function withHome(refs) {
2188
+ const home = getHomeAnchor();
2189
+ if (!home) throw new Error("Elect a home account before loading mounts");
2190
+ return [home, ...refs.filter((ref, index) => ref.id !== home.id && refs.findIndex((r) => r.id === ref.id) === index)];
2191
+ }
2192
+ function signature(refs, anchor) {
2193
+ return JSON.stringify({ mounts: refs.map((ref) => ref.id), last_anchor: anchor.id });
2194
+ }
2195
+ async function save(owner, refs, anchor) {
2196
+ assertCurrent(owner);
2197
+ const next = signature(refs, anchor);
2198
+ if (owner.saved === next) return;
2199
+ const type_data = JSON.parse(next);
2200
+ await gatewayCall(owner.exists ? "update" : "create", owner.exists ? {
2201
+ _scope_path: owner.scope,
2202
+ path: owner.path,
2203
+ type_data
2204
+ } : {
2205
+ _scope_path: owner.scope,
2206
+ parent_path: owner.path.slice(0, owner.path.lastIndexOf("/")),
2207
+ name: "mounts",
2208
+ item_type: "file",
2209
+ type_data
2210
+ });
2211
+ assertCurrent(owner);
2212
+ owner.exists = true;
2213
+ owner.saved = next;
2214
+ }
2215
+ function publish(refs, anchor) {
2216
+ const previous = getMounts();
2217
+ initVfsRealtimeWs(getHomeAnchor().id);
2218
+ for (const ref of previous) if (!refs.some((r) => r.id === ref.id)) unmountRealtimeScope(ref.id);
2219
+ for (const ref of refs) mountRealtimeScope(ref.id);
2220
+ commitMountSelection(refs, anchor);
2221
+ }
2222
+ async function loadMounts(memberSpacePath, resolveRef, options = {}) {
2223
+ const home = getHomeAnchor();
2224
+ if (!home) throw new Error("Elect a home account before loading mounts");
2225
+ const path = `${memberSpacePath}/settings/mounts`;
2226
+ if (declaration && declaration.path !== path) throw new Error("Reset the session before changing the mount declaration owner");
2227
+ const owner = declaration ??= {
2228
+ generation: getSessionGeneration(),
2229
+ path,
2230
+ scope: home.path,
2231
+ resolve: resolveRef,
2232
+ exists: false,
2233
+ ready: false,
2234
+ saved: null,
2235
+ queue: Promise.resolve()
2236
+ };
2237
+ assertCurrent(owner);
2238
+ if (owner.loading) return owner.loading;
2239
+ if (owner.ready) return getMounts();
2240
+ owner.resolve = resolveRef;
2241
+ owner.loading = (async () => {
2242
+ let data = null;
2243
+ try {
2244
+ const response = await gatewayCall("read", { path, _scope_path: owner.scope });
2245
+ assertCurrent(owner);
2246
+ data = response.type_data ?? response.item?.type_data;
2247
+ if (!data || !Array.isArray(data.mounts) || !data.mounts.every((id) => typeof id === "string")) {
2248
+ throw new Error("Invalid saved mount declaration");
2249
+ }
2250
+ owner.exists = true;
2251
+ } catch (error) {
2252
+ assertCurrent(owner);
2253
+ if (!missing(error)) throw error;
2254
+ owner.exists = false;
2255
+ }
2256
+ const initial = options.initialAnchor ? canonical(owner, options.initialAnchor) : home;
2257
+ const refs = withHome(data ? data.mounts.map(resolveRef).filter((ref) => ref !== null) : [initial]);
2258
+ const anchor = data ? refs.find((ref) => ref.id === data.last_anchor) ?? refs.find((ref) => ref.kind === "space") ?? home : initial;
2259
+ if (data) owner.saved = signature(refs, anchor);
2260
+ else if (options.initialAnchor) await save(owner, refs, anchor);
2261
+ assertCurrent(owner);
2262
+ owner.ready = true;
2263
+ publish(refs, anchor);
2264
+ return refs;
2265
+ })().finally(() => {
2266
+ owner.loading = void 0;
2267
+ });
2268
+ return owner.loading;
2269
+ }
2270
+ function mutate(change) {
2271
+ const owner = declaration;
2272
+ if (!owner?.ready) return Promise.reject(new Error("Workspace mounts are not loaded"));
2273
+ const task = owner.queue.then(async () => {
2274
+ assertCurrent(owner);
2275
+ const { refs, anchor } = change(owner);
2276
+ await save(owner, refs, anchor);
2277
+ assertCurrent(owner);
2278
+ publish(refs, anchor);
2279
+ });
2280
+ owner.queue = task.catch(() => {
2281
+ });
2282
+ return task;
2283
+ }
2284
+ function mount(ref) {
2285
+ return mutate((owner) => ({ refs: withHome([...getMounts(), canonical(owner, ref)]), anchor: getAnchorRef() }));
2286
+ }
2287
+ function unmount(id) {
2288
+ return mutate(() => {
2289
+ if (id === getHomeAnchor().id) throw new Error("The home account cannot be unmounted");
2290
+ const refs = withHome(getMounts().filter((ref) => ref.id !== id));
2291
+ const anchor = refs.find((ref) => ref.id === getAnchorRef()?.id) ?? refs.find((ref) => ref.kind === "space") ?? getHomeAnchor();
2292
+ return { refs, anchor };
2293
+ });
2294
+ }
2295
+ function selectMount(ref) {
2296
+ return mutate((owner) => {
2297
+ const anchor = canonical(owner, ref);
2298
+ return { refs: withHome([...getMounts(), anchor]), anchor };
2299
+ });
2300
+ }
2301
+ function replaceMounts(refs, anchor) {
2302
+ return mutate((owner) => {
2303
+ const next = withHome(refs.map((ref) => canonical(owner, ref)));
2304
+ const selected = canonical(owner, anchor);
2305
+ if (!next.some((ref) => ref.id === selected.id)) throw new Error("Selected mount must be in the working set");
2306
+ return { refs: next, anchor: selected };
2307
+ });
2308
+ }
2309
+
2165
2310
  // src/hooks.ts
2166
2311
  function useScopeKey() {
2167
2312
  return React.useSyncExternalStore(subscribeScope, getScopeVersion, getScopeVersion);
@@ -2528,44 +2673,13 @@ function useScope() {
2528
2673
  return { data, loading, error };
2529
2674
  }
2530
2675
  var EMPTY_ACCOUNTS = [];
2531
- function membershipToMountRef(m) {
2532
- return {
2533
- id: m.accountId,
2534
- path: m.path,
2535
- kind: m.isSpace ? "space" : "account",
2536
- name: m.name,
2537
- grantedUnderAccountId: m.grantedUnderAccountId ?? void 0
2538
- };
2539
- }
2540
2676
  function useAccounts() {
2541
2677
  const { user } = useAuth();
2542
- const autoSelectedRef = React.useRef(false);
2678
+ const autoSelectedRef = React.useRef(null);
2679
+ const [mountError, setMountError] = React.useState(null);
2543
2680
  const query = reactQuery.useQuery({
2544
2681
  queryKey: vfsKeys.memberships(user?.id ?? "anonymous"),
2545
- queryFn: async () => {
2546
- const result = await gatewayCall("list-memberships", {});
2547
- const memberships = result.memberships || [];
2548
- return memberships.map((m) => ({
2549
- accountId: m.account_id,
2550
- path: m.account_path,
2551
- name: m.account_name,
2552
- role: m.role || "member",
2553
- typeData: m.account_type_data || {},
2554
- joinedAt: m.joined_at,
2555
- via: m.via,
2556
- placement: m.placement,
2557
- subordinateTo: m.subordinate_to ?? null,
2558
- cascadeAnchorId: m.cascade_anchor_id ?? null,
2559
- isAccount: m.is_account ?? void 0,
2560
- isSpace: m.is_space ?? void 0,
2561
- isOwner: m.is_owner ?? void 0,
2562
- billingResponsible: m.billing_responsible ?? void 0,
2563
- grantedVia: m.granted_via ?? null,
2564
- grantedUnderAccountId: m.granted_under_account_id ?? null,
2565
- personalSpaceId: m.personal_space_id ?? null,
2566
- personalSpacePath: m.personal_space_path ?? null
2567
- }));
2568
- },
2682
+ queryFn: fetchMemberships,
2569
2683
  enabled: !!user?.id,
2570
2684
  staleTime: 1e3 * 60 * 2
2571
2685
  });
@@ -2587,50 +2701,47 @@ function useAccounts() {
2587
2701
  }, [anchorVersion2, allMemberships]);
2588
2702
  React.useEffect(() => {
2589
2703
  if (!user?.id) {
2590
- autoSelectedRef.current = false;
2704
+ autoSelectedRef.current = null;
2591
2705
  return;
2592
2706
  }
2593
- if (autoSelectedRef.current) return;
2707
+ if (autoSelectedRef.current === user.id) return;
2708
+ setMountError(null);
2594
2709
  if (electionCandidates.length === 1) {
2595
2710
  const only = electionCandidates[0];
2596
- electHomeAnchor(membershipToMountRef(only));
2711
+ if (!getHomeAnchor()) electHomeAnchor(membershipToMountRef(only));
2597
2712
  initVfsRealtimeWs(only.accountId);
2598
2713
  if (only.personalSpacePath) {
2599
2714
  loadMounts(only.personalSpacePath, (id) => {
2600
2715
  const m = allMemberships.find((x) => x.accountId === id);
2601
2716
  return m ? membershipToMountRef(m) : null;
2602
- }).catch(() => {
2603
- });
2717
+ }).catch(setMountError);
2604
2718
  }
2605
- autoSelectedRef.current = true;
2719
+ autoSelectedRef.current = user.id;
2606
2720
  } else if (electionCandidates.length > 1) {
2607
- autoSelectedRef.current = true;
2721
+ autoSelectedRef.current = user.id;
2608
2722
  }
2609
2723
  }, [user?.id, electionCandidates, allMemberships]);
2610
- const switchAccount = React.useCallback((accountPath) => {
2724
+ const switchAccount = React.useCallback(async (accountPath) => {
2611
2725
  const m = allMemberships.find((a) => a.path === accountPath);
2612
- if (!m) return;
2726
+ if (!m) throw new Error("Account membership is unavailable");
2613
2727
  const ref = membershipToMountRef(m);
2614
2728
  if (!getHomeAnchor()) {
2615
2729
  electHomeAnchor(ref);
2616
- if (m.personalSpacePath) {
2617
- loadMounts(m.personalSpacePath, (id) => {
2618
- const mm = allMemberships.find((x) => x.accountId === id);
2619
- return mm ? membershipToMountRef(mm) : null;
2620
- }).catch(() => {
2621
- });
2622
- }
2623
- } else {
2624
- setAnchor(ref);
2625
2730
  }
2731
+ const home = allMemberships.find((a) => a.accountId === getHomeAnchor()?.id);
2732
+ if (!home?.personalSpacePath) throw new Error("The home membership has no mount declaration location");
2733
+ await loadMounts(home.personalSpacePath, (id) => {
2734
+ const member = allMemberships.find((a) => a.accountId === id);
2735
+ return member ? membershipToMountRef(member) : null;
2736
+ });
2737
+ await selectMount(ref);
2738
+ setMountError(null);
2626
2739
  initVfsRealtimeWs(m.accountId);
2627
2740
  }, [allMemberships]);
2628
- const mountScope = React.useCallback((scopePath) => {
2741
+ const mountScope = React.useCallback(async (scopePath) => {
2629
2742
  const m = allMemberships.find((a) => a.path === scopePath);
2630
- if (!m) return;
2631
- mount(membershipToMountRef(m)).catch(() => {
2632
- });
2633
- mountRealtimeScope(scopePath);
2743
+ if (!m) throw new Error("Scope membership is unavailable");
2744
+ await mount(membershipToMountRef(m));
2634
2745
  }, [allMemberships]);
2635
2746
  return {
2636
2747
  accounts: electionCandidates,
@@ -2640,17 +2751,19 @@ function useAccounts() {
2640
2751
  switchAccount,
2641
2752
  mountScope,
2642
2753
  loading: query.isLoading,
2643
- error: query.error
2754
+ error: query.error ?? mountError
2644
2755
  };
2645
2756
  }
2646
2757
  function useMounts() {
2758
+ useScopeKey();
2647
2759
  const version = React.useSyncExternalStore(subscribeMounts, getMountVersion, getMountVersion);
2648
2760
  const mounts = React.useMemo(() => getMounts(), [version]);
2649
2761
  return {
2650
2762
  mounts,
2651
2763
  homeAnchor: getHomeAnchor(),
2652
2764
  anchor: getAnchorRef(),
2653
- setAnchor,
2765
+ setAnchor: selectMount,
2766
+ replaceMounts,
2654
2767
  mount,
2655
2768
  unmount
2656
2769
  };
@@ -3397,6 +3510,7 @@ function normalizeChannel(raw) {
3397
3510
  isPrivate: td.is_private || false,
3398
3511
  isDm: td.is_dm || false,
3399
3512
  messageCount: td.message_count || 0,
3513
+ createdAt: raw.created_at || "",
3400
3514
  updatedAt: raw.updated_at || "",
3401
3515
  chatScopePath: lineage.context_path || "",
3402
3516
  contextId: raw.scope_item_id || "",
@@ -4003,6 +4117,15 @@ function usePersonalItemOrder(key, items) {
4003
4117
  const scopePath = getScope();
4004
4118
  const supported = anchor?.kind === "space";
4005
4119
  const query = useArrangement(supported && scopePath ? { key, scope_id: anchor.id } : null);
4120
+ const orderedItems = React.useMemo(() => {
4121
+ const arranged = query.applyTo([...items]);
4122
+ if (key !== "channel-order") return arranged;
4123
+ const savedIds = new Set(query.order ?? []);
4124
+ const unplaced = arranged.filter((item) => !savedIds.has(item.id));
4125
+ const created = (item) => Date.parse(item.createdAt ?? "") || 0;
4126
+ unplaced.sort((a, b) => created(b) - created(a));
4127
+ return [...unplaced, ...arranged.filter((item) => savedIds.has(item.id))];
4128
+ }, [key, items, query.applyTo, query.order]);
4006
4129
  const client2 = reactQuery.useQueryClient();
4007
4130
  const pending3 = React.useRef(false);
4008
4131
  const ready = Boolean(supported && !query.error && query.arrangement?.item_id && query.resolutionLevel === "member" && query.arrangement.write_target_id === query.arrangement.item_id);
@@ -4029,7 +4152,7 @@ function usePersonalItemOrder(key, items) {
4029
4152
  if (cached?.item_id !== query.arrangement.item_id || JSON.stringify(cached?.type_data?.order ?? []) !== JSON.stringify(query.order ?? [])) {
4030
4153
  throw new Error("The saved order changed. Try dragging again");
4031
4154
  }
4032
- const current = query.applyTo([...items]).map((item) => item.id);
4155
+ const current = orderedItems.map((item) => item.id);
4033
4156
  if (ids.every((id, index2) => current[index2] === id)) return;
4034
4157
  const visible = new Set(visibleIds);
4035
4158
  let index = 0;
@@ -4048,7 +4171,7 @@ function usePersonalItemOrder(key, items) {
4048
4171
  }
4049
4172
  }
4050
4173
  return {
4051
- items: query.applyTo([...items]),
4174
+ items: orderedItems,
4052
4175
  ready,
4053
4176
  supported,
4054
4177
  contextKey: `${generation}:${scopePath}:${key}`,
@@ -4336,34 +4459,109 @@ function startTracking(options = {}) {
4336
4459
  return trackingCleanup;
4337
4460
  }
4338
4461
 
4339
- // src/initialize.ts
4462
+ // src/workspace.ts
4340
4463
  var initialization;
4464
+ registerCleanup(() => {
4465
+ initialization = void 0;
4466
+ });
4467
+ function initializeWorkspace(options) {
4468
+ if (initialization) {
4469
+ if (initialization.accountPath !== options.accountPath) return Promise.reject(new Error("Reset the session before changing accounts"));
4470
+ return initialization.promise;
4471
+ }
4472
+ const generation = getSessionGeneration();
4473
+ const check = () => {
4474
+ if (generation !== getSessionGeneration()) throw new Error("Workspace session changed");
4475
+ };
4476
+ const promise = (async () => {
4477
+ const entry = await initSession();
4478
+ check();
4479
+ if (!entry.principal_id) throw new Error("Workspace initialization requires an authenticated principal");
4480
+ const memberships = await getQueryClient().fetchQuery({
4481
+ queryKey: vfsKeys.memberships(entry.principal_id),
4482
+ queryFn: fetchMemberships,
4483
+ staleTime: 12e4,
4484
+ retry: false
4485
+ });
4486
+ check();
4487
+ const account = memberships.find((m) => m.path === options.accountPath && m.isAccount && !m.isSpace);
4488
+ if (!account) throw new Error("Authenticated account membership is unavailable");
4489
+ const personal = memberships.find((m) => m.isSpace && m.accountId === account.personalSpaceId && m.path === account.personalSpacePath);
4490
+ if (!personal || !account.personalSpacePath) throw new Error("A provisioned default Space membership is required");
4491
+ const home = membershipToMountRef(account);
4492
+ const defaultSpace = membershipToMountRef(personal);
4493
+ const elected = getHomeAnchor();
4494
+ if (elected && elected.id !== home.id) throw new Error("Reset the session before changing accounts");
4495
+ if (!elected) electHomeAnchor(home, { activate: false });
4496
+ await loadMounts(account.personalSpacePath, (id) => {
4497
+ const member = memberships.find((m) => m.accountId === id && (m.isAccount || m.isSpace));
4498
+ return member ? membershipToMountRef(member) : null;
4499
+ }, { initialAnchor: defaultSpace });
4500
+ check();
4501
+ return { home, defaultSpace, memberships };
4502
+ })().catch((error) => {
4503
+ if (generation === getSessionGeneration()) initialization = void 0;
4504
+ throw error;
4505
+ });
4506
+ initialization = { accountPath: options.accountPath, promise };
4507
+ return promise;
4508
+ }
4509
+
4510
+ // src/initialize.ts
4511
+ var initialization2;
4512
+ var stopActiveTracking;
4513
+ var workspaceAccount;
4514
+ registerCleanup(() => {
4515
+ stopActiveTracking?.();
4516
+ stopActiveTracking = void 0;
4517
+ initialization2 = void 0;
4518
+ workspaceAccount = void 0;
4519
+ });
4341
4520
  function initializeFsaos(options = {}) {
4342
- if (initialization) return initialization;
4343
- initialization = (async () => {
4521
+ if (initialization2) {
4522
+ if (options.workspace && options.workspace.accountPath !== workspaceAccount) {
4523
+ return Promise.reject(new Error("Initialize workspace through initializeWorkspace, or dispose the existing runtime first"));
4524
+ }
4525
+ return initialization2;
4526
+ }
4527
+ workspaceAccount = options.workspace?.accountPath;
4528
+ const generation = getSessionGeneration();
4529
+ initialization2 = (async () => {
4344
4530
  const tracking = options.tracking ?? true;
4345
4531
  const stopTracking = tracking === false ? () => {
4346
4532
  } : startTracking(typeof tracking === "object" ? tracking : {});
4533
+ stopActiveTracking = stopTracking;
4347
4534
  try {
4348
4535
  const session = options.session === false ? null : await initSession();
4536
+ const workspace = options.workspace ? await initializeWorkspace(options.workspace) : void 0;
4537
+ if (generation !== getSessionGeneration()) throw new Error("Session changed during initialization");
4349
4538
  let disposed = false;
4350
4539
  const runtime = {
4351
4540
  session,
4541
+ workspace,
4352
4542
  dispose() {
4353
4543
  if (disposed) return;
4354
4544
  disposed = true;
4355
4545
  stopTracking();
4356
- initialization = void 0;
4546
+ if (generation === getSessionGeneration()) {
4547
+ initialization2 = void 0;
4548
+ workspaceAccount = void 0;
4549
+ stopActiveTracking = void 0;
4550
+ }
4357
4551
  }
4358
4552
  };
4359
4553
  return runtime;
4360
4554
  } catch (error) {
4361
4555
  stopTracking();
4362
- initialization = void 0;
4556
+ if (generation === getSessionGeneration()) {
4557
+ initialization2 = void 0;
4558
+ workspaceAccount = void 0;
4559
+ stopActiveTracking = void 0;
4560
+ }
4363
4561
  throw error;
4364
4562
  }
4365
4563
  })();
4366
- return initialization;
4564
+ return initialization2;
4367
4565
  }
4368
4566
 
4369
4567
  // src/execution-control.ts
@@ -5458,6 +5656,7 @@ exports.hasServerClock = hasServerClock;
5458
5656
  exports.initSession = initSession;
5459
5657
  exports.initVfsRealtimeWs = initVfsRealtimeWs;
5460
5658
  exports.initializeFsaos = initializeFsaos;
5659
+ exports.initializeWorkspace = initializeWorkspace;
5461
5660
  exports.interpretSchema = interpretSchema;
5462
5661
  exports.invalidateAllVfs = invalidateAllVfs;
5463
5662
  exports.invalidateChannelMessages = invalidateChannelMessages;
@@ -5473,6 +5672,7 @@ exports.isListableItemsFilter = isListableItemsFilter;
5473
5672
  exports.isScopeReady = isScopeReady;
5474
5673
  exports.itemsFilterFromQueryFilter = itemsFilterFromQueryFilter;
5475
5674
  exports.listChildren = listChildren;
5675
+ exports.loadMounts = loadMounts;
5476
5676
  exports.mount = mount2;
5477
5677
  exports.mountRealtimeScope = mountRealtimeScope;
5478
5678
  exports.normalizeItem = normalizeItem;
@@ -5484,9 +5684,11 @@ exports.queryClient = queryClient;
5484
5684
  exports.readItem = readItem;
5485
5685
  exports.refreshQuery = refreshQuery;
5486
5686
  exports.registerCleanup = registerCleanup;
5687
+ exports.replaceMounts = replaceMounts;
5487
5688
  exports.resetAllSdkState = resetAllSdkState;
5488
5689
  exports.resolvePrincipalId = resolvePrincipalId;
5489
5690
  exports.resubscribePath = resubscribePath;
5691
+ exports.selectMount = selectMount;
5490
5692
  exports.serverDeadlinePassed = serverDeadlinePassed;
5491
5693
  exports.serverMsUntil = serverMsUntil;
5492
5694
  exports.serverNow = serverNow;