@floegence/floe-webapp-core 0.67.1 → 0.69.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.
@@ -59,6 +59,8 @@ export interface FloeLayoutConfig {
59
59
  max: number;
60
60
  };
61
61
  defaultActiveTab: string;
62
+ /** Disable when the consumer owns navigation persistence. Defaults to true. */
63
+ persistActiveTab?: boolean;
62
64
  defaultCollapsed: boolean;
63
65
  };
64
66
  terminal: {
@@ -5,16 +5,16 @@ import { useResolvedFloeConfig as v } from "./FloeConfigContext.js";
5
5
  const {
6
6
  Provider: x,
7
7
  use: C,
8
- useOptional: L
8
+ useOptional: A
9
9
  } = u({
10
10
  name: "Layout",
11
11
  init: g
12
12
  });
13
13
  function g() {
14
- const d = v(), i = () => d.config.layout, p = () => typeof window > "u" ? !1 : window.matchMedia(i().mobileQuery).matches, s = d.persist.load(i().storageKey, {}), h = {
14
+ const n = v(), i = () => n.config.layout, p = () => typeof window > "u" ? !1 : window.matchMedia(i().mobileQuery).matches, s = n.persist.load(i().storageKey, {}), h = {
15
15
  sidebar: {
16
16
  width: s.sidebar?.width ?? i().sidebar.defaultWidth,
17
- activeTab: s.sidebar?.activeTab ?? i().sidebar.defaultActiveTab,
17
+ activeTab: (i().sidebar.persistActiveTab !== !1 ? s.sidebar?.activeTab : void 0) ?? i().sidebar.defaultActiveTab,
18
18
  collapsed: s.sidebar?.collapsed ?? i().sidebar.defaultCollapsed
19
19
  },
20
20
  terminal: {
@@ -22,44 +22,46 @@ function g() {
22
22
  height: s.terminal?.height ?? i().terminal.defaultHeight
23
23
  },
24
24
  isMobile: p()
25
- }, [t, o] = c(h), [l, b] = c({
25
+ }, [a, o] = c(h), [l, b] = c({
26
26
  motion: "animated",
27
27
  revision: 0
28
28
  }), m = (e) => Math.max(i().sidebar.clamp.min, Math.min(i().sidebar.clamp.max, e));
29
29
  return f(() => {
30
30
  const e = {
31
31
  sidebar: {
32
- width: t.sidebar.width,
33
- activeTab: t.sidebar.activeTab,
34
- collapsed: t.sidebar.collapsed
32
+ width: a.sidebar.width,
33
+ ...i().sidebar.persistActiveTab !== !1 ? {
34
+ activeTab: a.sidebar.activeTab
35
+ } : {},
36
+ collapsed: a.sidebar.collapsed
35
37
  },
36
38
  terminal: {
37
- opened: t.terminal.opened,
38
- height: t.terminal.height
39
+ opened: a.terminal.opened,
40
+ height: a.terminal.height
39
41
  }
40
42
  };
41
- d.persist.debouncedSave(i().storageKey, e);
43
+ n.persist.debouncedSave(i().storageKey, e);
42
44
  }), {
43
45
  // Sidebar accessors
44
- sidebarWidth: () => t.sidebar.width,
45
- sidebarActiveTab: () => t.sidebar.activeTab,
46
- sidebarCollapsed: () => t.sidebar.collapsed,
46
+ sidebarWidth: () => a.sidebar.width,
47
+ sidebarActiveTab: () => a.sidebar.activeTab,
48
+ sidebarCollapsed: () => a.sidebar.collapsed,
47
49
  sidebarVisibilityMotion: () => l.motion,
48
50
  sidebarVisibilityMotionRevision: () => l.revision,
49
51
  clampSidebarWidth: m,
50
52
  // Sidebar actions
51
- setSidebarWidth: (e) => o(r((a) => {
52
- a.sidebar.width = m(e);
53
+ setSidebarWidth: (e) => o(r((t) => {
54
+ t.sidebar.width = m(e);
53
55
  })),
54
- setSidebarActiveTab: (e, a) => {
55
- o(r((n) => {
56
- n.sidebar.activeTab = e, a?.openSidebar !== !1 && (n.sidebar.collapsed = !1);
57
- })), a?.visibilityMotion === "instant" && b(r((n) => {
58
- n.motion = "instant", n.revision += 1;
56
+ setSidebarActiveTab: (e, t) => {
57
+ o(r((d) => {
58
+ d.sidebar.activeTab = e, t?.openSidebar !== !1 && (d.sidebar.collapsed = !1);
59
+ })), t?.visibilityMotion === "instant" && b(r((d) => {
60
+ d.motion = "instant", d.revision += 1;
59
61
  }));
60
62
  },
61
- setSidebarCollapsed: (e) => o(r((a) => {
62
- a.sidebar.collapsed = e;
63
+ setSidebarCollapsed: (e) => o(r((t) => {
64
+ t.sidebar.collapsed = e;
63
65
  })),
64
66
  toggleSidebarCollapse: () => o(r((e) => {
65
67
  e.sidebar.collapsed = !e.sidebar.collapsed;
@@ -68,17 +70,17 @@ function g() {
68
70
  l.revision === e && b("motion", "animated");
69
71
  },
70
72
  // Terminal accessors
71
- terminalOpened: () => t.terminal.opened,
72
- terminalHeight: () => t.terminal.height,
73
+ terminalOpened: () => a.terminal.opened,
74
+ terminalHeight: () => a.terminal.height,
73
75
  // Terminal actions
74
76
  toggleTerminal: () => o(r((e) => {
75
77
  e.terminal.opened = !e.terminal.opened;
76
78
  })),
77
- setTerminalHeight: (e) => o(r((a) => {
78
- a.terminal.height = Math.max(i().terminal.clamp.min, Math.min(i().terminal.clamp.max, e));
79
+ setTerminalHeight: (e) => o(r((t) => {
80
+ t.terminal.height = Math.max(i().terminal.clamp.min, Math.min(i().terminal.clamp.max, e));
79
81
  })),
80
82
  // Mobile
81
- isMobile: () => t.isMobile,
83
+ isMobile: () => a.isMobile,
82
84
  setIsMobile: (e) => o("isMobile", e)
83
85
  };
84
86
  }
@@ -86,5 +88,5 @@ export {
86
88
  x as LayoutProvider,
87
89
  g as createLayoutService,
88
90
  C as useLayout,
89
- L as useOptionalLayout
91
+ A as useOptionalLayout
90
92
  };
@@ -12,6 +12,8 @@ export interface ResourceCacheStorage {
12
12
  }
13
13
  export interface ResourceSnapshot<T> {
14
14
  readonly data: T | undefined;
15
+ /** True until storage settles, or live data/invalidation supersedes restoration. */
16
+ readonly restoring: boolean;
15
17
  readonly refreshing: boolean;
16
18
  readonly stale: boolean;
17
19
  readonly error: unknown;
@@ -36,6 +38,8 @@ export interface CachedResourceDefinition<T> {
36
38
  }
37
39
  export interface ResourceCache {
38
40
  resource<T>(definition: CachedResourceDefinition<T>): CachedResource<T>;
41
+ /** Abort pending refreshes without retiring snapshots, storage, hydration or subscribers. */
42
+ cancelRefreshes(scope: string): void;
39
43
  clearScope(scope: string): Promise<void>;
40
44
  flush(): Promise<void>;
41
45
  dispose(): void;
@@ -1,175 +1,186 @@
1
- async function N(f, i) {
2
- const g = [...await f.list()].sort((e, t) => e.lastAccessedAt - t.lastAccessedAt);
3
- let d = g.reduce((e, t) => e + t.bytes, 0);
4
- const o = [];
5
- for (const e of g) {
6
- if (d <= Math.max(0, i)) break;
7
- await f.remove(e.key), o.push(e.key), d -= e.bytes;
1
+ async function J(y, l) {
2
+ const m = [...await y.list()].sort((r, s) => r.lastAccessedAt - s.lastAccessedAt);
3
+ let i = m.reduce((r, s) => r + s.bytes, 0);
4
+ const a = [];
5
+ for (const r of m) {
6
+ if (i <= Math.max(0, l)) break;
7
+ await y.remove(r.key), a.push(r.key), i -= r.bytes;
8
8
  }
9
- return o;
9
+ return a;
10
10
  }
11
- function I(f) {
12
- const i = f.storage, g = Math.max(0, f.maxBytes ?? 32 * 1024 * 1024), d = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map();
13
- let e, t = Promise.resolve(), a = !1;
14
- const l = (s) => (t = t.then(s).catch(() => {
15
- }), t), v = async () => {
16
- const s = await N({
17
- list: async () => (await i.list()).map((r) => ({ ...r, lastAccessedAt: Math.max(r.lastAccessedAt, d.get(r.key)?.accessedAt ?? 0) })),
18
- remove: (r) => i.remove(r)
19
- }, g);
20
- for (const r of s) d.get(r)?.evicted();
21
- }, y = async () => {
22
- clearTimeout(e), e = void 0;
23
- const s = [...o];
24
- o.clear(), s.length && l(async () => {
25
- for (const [r, h] of s)
26
- if (h.valid())
11
+ function z(y) {
12
+ const l = y.storage, m = Math.max(0, y.maxBytes ?? 32 * 1024 * 1024), i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
13
+ let r, s = Promise.resolve(), n = !1;
14
+ const d = (t) => (s = s.then(t).catch(() => {
15
+ }), s), f = async () => {
16
+ const t = await J({
17
+ list: async () => (await l.list()).map((e) => ({ ...e, lastAccessedAt: Math.max(e.lastAccessedAt, i.get(e.key)?.accessedAt ?? 0) })),
18
+ remove: (e) => l.remove(e)
19
+ }, m);
20
+ for (const e of t) i.get(e)?.evicted();
21
+ }, p = async () => {
22
+ clearTimeout(r), r = void 0;
23
+ const t = [...a];
24
+ a.clear(), t.length && d(async () => {
25
+ for (const [e, v] of t)
26
+ if (v.valid())
27
27
  try {
28
- await i.set(r, h.value), h.stored();
28
+ await l.set(e, v.value), v.stored();
29
29
  } catch {
30
30
  }
31
- await v();
32
- }), await t;
33
- }, k = (s, r, h, c) => {
34
- o.set(s, { value: r, valid: h, stored: c }), e === void 0 && (e = setTimeout(() => {
35
- y();
36
- }, f.writeDelayMs ?? 100));
31
+ await f();
32
+ }), await s;
33
+ }, x = (t, e, v, o) => {
34
+ a.set(t, { value: e, valid: v, stored: o }), r === void 0 && (r = setTimeout(() => {
35
+ p();
36
+ }, y.writeDelayMs ?? 100));
37
37
  };
38
38
  return {
39
- resource: (s) => {
40
- if (a) throw new Error("Resource cache is disposed");
41
- if (!s.scope || !s.key || !Number.isSafeInteger(s.version)) throw new Error("Invalid resource identity");
42
- const r = JSON.stringify([s.scope, s.key, s.version]), h = d.get(r);
43
- if (h)
44
- return h.accessedAt = Date.now(), h.handle;
45
- let c = { data: void 0, refreshing: !1, stale: !0, error: void 0 };
46
- const D = /* @__PURE__ */ new Set();
47
- let R = 0, p = 0, q, x, b, w;
48
- const m = () => {
49
- for (const n of D) n();
50
- }, E = (n) => {
51
- ++R, c = { data: n, refreshing: c.refreshing, stale: !1, error: void 0 };
39
+ resource: (t) => {
40
+ if (n) throw new Error("Resource cache is disposed");
41
+ if (!t.scope || !t.key || !Number.isSafeInteger(t.version)) throw new Error("Invalid resource identity");
42
+ const e = JSON.stringify([t.scope, t.key, t.version]), v = i.get(e);
43
+ if (v)
44
+ return v.accessedAt = Date.now(), v.handle;
45
+ let o = { data: void 0, restoring: !0, refreshing: !1, stale: !0, error: void 0 };
46
+ const S = /* @__PURE__ */ new Set();
47
+ let D = 0, w = 0, E, R, g, h, M;
48
+ const b = () => {
49
+ for (const c of S) c();
50
+ }, P = (c) => {
51
+ ++D, o = { data: c, restoring: !1, refreshing: o.refreshing, stale: !1, error: void 0 };
52
52
  try {
53
- const u = JSON.stringify({ version: s.version, data: s.persist ? s.persist(n) : n });
54
- q = u, u !== x ? k(r, u, () => q === u, () => {
55
- x = u;
56
- }) : o.delete(r);
53
+ const u = JSON.stringify({ version: t.version, data: t.persist ? t.persist(c) : c });
54
+ E = u, u !== R ? x(e, u, () => E === u, () => {
55
+ R = u;
56
+ }) : a.delete(e);
57
57
  } catch {
58
58
  }
59
- m();
60
- }, B = R, C = i.get(r).then((n) => {
61
- if (!(a || R !== B || n === null))
59
+ b();
60
+ }, C = D, O = l.get(e).then((c) => {
61
+ if (!(n || D !== C || c === null))
62
62
  try {
63
- const u = JSON.parse(n);
64
- if (u.version !== s.version) throw new Error("Unsupported snapshot");
65
- const A = s.decode(u.data);
66
- c = { ...c, data: A, stale: !0 }, q = n, x = n, m();
63
+ const u = JSON.parse(c);
64
+ if (u.version !== t.version) throw new Error("Unsupported snapshot");
65
+ const A = t.decode(u.data);
66
+ o = { ...o, data: A, stale: !0 }, E = c, R = c;
67
67
  } catch {
68
- l(() => i.remove(r));
68
+ d(() => l.remove(e));
69
69
  }
70
70
  }).catch(() => {
71
- }), M = {
72
- snapshot: () => c,
73
- subscribe: (n) => (D.add(n), () => {
74
- D.delete(n);
71
+ }).finally(() => {
72
+ n || !o.restoring || (o = { ...o, restoring: !1 }, b());
73
+ }), q = {
74
+ snapshot: () => o,
75
+ subscribe: (c) => (S.add(c), () => {
76
+ S.delete(c);
75
77
  }),
76
- hydrate: () => C,
77
- refresh: (n) => {
78
- if (w) return w;
79
- const u = ++p, A = new AbortController();
80
- return b = A, c = { ...c, refreshing: !0, error: void 0 }, m(), w = Promise.resolve().then(() => n(A.signal)).then((S) => (!a && u === p && !A.signal.aborted && E(S), S), (S) => {
81
- throw !a && u === p && !A.signal.aborted && (c = { ...c, stale: !0, error: S }, m()), S;
82
- }).finally(() => {
83
- u === p && (w = void 0, b = void 0, c = { ...c, refreshing: !1 }, m());
84
- }), w;
78
+ hydrate: () => O,
79
+ refresh: (c) => {
80
+ if (h) return h;
81
+ const u = ++w, A = new AbortController();
82
+ g = A, o = { ...o, refreshing: !0, error: void 0 }, b();
83
+ const B = new Promise((k, I) => {
84
+ M = () => I(new DOMException("Resource refresh cancelled", "AbortError"));
85
+ }), N = Promise.resolve().then(() => c(A.signal)).then((k) => (!n && u === w && !A.signal.aborted && P(k), k), (k) => {
86
+ throw !n && u === w && !A.signal.aborted && (o = { ...o, stale: !0, error: k }, b()), k;
87
+ });
88
+ return h = Promise.race([N, B]).finally(() => {
89
+ u === w && (h = void 0, g = void 0, M = void 0, o = { ...o, refreshing: !1 }, b());
90
+ }), h;
85
91
  },
86
- set: (n) => {
87
- ++p, b?.abort(), w = void 0, c = { ...c, refreshing: !1 }, E(n);
92
+ set: (c) => {
93
+ ++w, g?.abort(), h = void 0, o = { ...o, refreshing: !1 }, P(c);
88
94
  },
89
- invalidate: (n = !1) => {
90
- ++R, ++p, b?.abort(), b = void 0, w = void 0, q = void 0, x = void 0, o.delete(r), c = { data: n ? void 0 : c.data, refreshing: !1, stale: !0, error: void 0 }, m(), l(() => i.remove(r));
95
+ invalidate: (c = !1) => {
96
+ ++D, ++w, g?.abort(), g = void 0, h = void 0, E = void 0, R = void 0, a.delete(e), o = { data: c ? void 0 : o.data, restoring: !1, refreshing: !1, stale: !0, error: void 0 }, b(), d(() => l.remove(e));
91
97
  }
92
98
  };
93
- return d.set(r, { scope: s.scope, accessedAt: Date.now(), evicted: () => {
94
- x = void 0;
95
- }, handle: M, stop: () => {
96
- ++p, b?.abort(), D.clear();
97
- } }), M;
99
+ return i.set(e, { scope: t.scope, accessedAt: Date.now(), evicted: () => {
100
+ R = void 0;
101
+ }, handle: q, cancelRefresh: () => {
102
+ h && (++w, M?.(), M = void 0, g?.abort(), g = void 0, h = void 0, o = { ...o, refreshing: !1, stale: !0 }, b());
103
+ }, stop: () => {
104
+ ++w, g?.abort(), S.clear();
105
+ } }), q;
98
106
  },
99
- flush: y,
100
- clearScope: async (s) => {
101
- for (const r of d.values()) r.scope === s && r.handle.invalidate(!0);
102
- await l(async () => {
103
- for (const r of await i.list())
107
+ flush: p,
108
+ cancelRefreshes: (t) => {
109
+ for (const e of i.values()) e.scope === t && e.cancelRefresh();
110
+ },
111
+ clearScope: async (t) => {
112
+ for (const e of i.values()) e.scope === t && e.handle.invalidate(!0);
113
+ await d(async () => {
114
+ for (const e of await l.list())
104
115
  try {
105
- JSON.parse(r.key)[0] === s && await i.remove(r.key);
116
+ JSON.parse(e.key)[0] === t && await l.remove(e.key);
106
117
  } catch {
107
118
  }
108
119
  });
109
120
  },
110
121
  dispose: () => {
111
- if (!a) {
112
- a = !0;
113
- for (const s of d.values()) s.stop();
114
- y(), d.clear();
122
+ if (!n) {
123
+ n = !0;
124
+ for (const t of i.values()) t.stop();
125
+ p(), i.clear();
115
126
  }
116
127
  }
117
128
  };
118
129
  }
119
- function J(f = "floe-resource-cache") {
120
- let i;
121
- const g = () => i ??= new Promise((o, e) => {
122
- const t = indexedDB.open(f, 1);
123
- t.onupgradeneeded = () => t.result.createObjectStore("snapshots", { keyPath: "key" }), t.onsuccess = () => {
124
- const a = t.result;
125
- a.onversionchange = () => {
126
- a.close(), i = void 0;
127
- }, o(a);
128
- }, t.onerror = () => {
129
- i = void 0, e(t.error);
130
- }, t.onblocked = () => {
131
- i = void 0, e(new Error("Resource cache database is blocked"));
130
+ function L(y = "floe-resource-cache") {
131
+ let l;
132
+ const m = () => l ??= new Promise((a, r) => {
133
+ const s = indexedDB.open(y, 1);
134
+ s.onupgradeneeded = () => s.result.createObjectStore("snapshots", { keyPath: "key" }), s.onsuccess = () => {
135
+ const n = s.result;
136
+ n.onversionchange = () => {
137
+ n.close(), l = void 0;
138
+ }, a(n);
139
+ }, s.onerror = () => {
140
+ l = void 0, r(s.error);
141
+ }, s.onblocked = () => {
142
+ l = void 0, r(new Error("Resource cache database is blocked"));
132
143
  };
133
- }), d = async (o, e) => {
134
- const t = await g();
135
- return new Promise((a, l) => {
136
- const v = t.transaction("snapshots", o);
137
- let y;
138
- v.oncomplete = () => a(y), v.onerror = () => l(v.error), v.onabort = () => l(v.error ?? new Error("Resource cache transaction aborted")), e(v.objectStore("snapshots"), (k) => {
139
- y = k;
144
+ }), i = async (a, r) => {
145
+ const s = await m();
146
+ return new Promise((n, d) => {
147
+ const f = s.transaction("snapshots", a);
148
+ let p;
149
+ f.oncomplete = () => n(p), f.onerror = () => d(f.error), f.onabort = () => d(f.error ?? new Error("Resource cache transaction aborted")), r(f.objectStore("snapshots"), (x) => {
150
+ p = x;
140
151
  });
141
152
  });
142
153
  };
143
154
  return {
144
- get: (o) => d("readwrite", (e, t) => {
145
- const a = e.get(o);
146
- a.onsuccess = () => {
147
- const l = a.result;
148
- l && e.put({ ...l, lastAccessedAt: Date.now() }), t(l?.value ?? null);
155
+ get: (a) => i("readwrite", (r, s) => {
156
+ const n = r.get(a);
157
+ n.onsuccess = () => {
158
+ const d = n.result;
159
+ d && r.put({ ...d, lastAccessedAt: Date.now() }), s(d?.value ?? null);
149
160
  };
150
161
  }),
151
- set: (o, e) => d("readwrite", (t, a) => {
152
- t.put({ key: o, value: e, bytes: new TextEncoder().encode(e).byteLength, lastAccessedAt: Date.now() }), a(void 0);
162
+ set: (a, r) => i("readwrite", (s, n) => {
163
+ s.put({ key: a, value: r, bytes: new TextEncoder().encode(r).byteLength, lastAccessedAt: Date.now() }), n(void 0);
153
164
  }),
154
- remove: (o) => d("readwrite", (e, t) => {
155
- e.delete(o), t(void 0);
165
+ remove: (a) => i("readwrite", (r, s) => {
166
+ r.delete(a), s(void 0);
156
167
  }),
157
- list: () => d("readonly", (o, e) => {
158
- const t = [], a = o.openCursor();
159
- a.onsuccess = () => {
160
- const l = a.result;
161
- if (!l) {
162
- e(t);
168
+ list: () => i("readonly", (a, r) => {
169
+ const s = [], n = a.openCursor();
170
+ n.onsuccess = () => {
171
+ const d = n.result;
172
+ if (!d) {
173
+ r(s);
163
174
  return;
164
175
  }
165
- const { key: v, bytes: y, lastAccessedAt: k } = l.value;
166
- t.push({ key: v, bytes: y, lastAccessedAt: k }), l.continue();
176
+ const { key: f, bytes: p, lastAccessedAt: x } = d.value;
177
+ s.push({ key: f, bytes: p, lastAccessedAt: x }), d.continue();
167
178
  };
168
179
  })
169
180
  };
170
181
  }
171
182
  export {
172
- J as createIndexedDBResourceCacheStorage,
173
- I as createResourceCache,
174
- N as enforceResourceCacheBudget
183
+ L as createIndexedDBResourceCacheStorage,
184
+ z as createResourceCache,
185
+ J as enforceResourceCacheBudget
175
186
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-core",
3
- "version": "0.67.1",
3
+ "version": "0.69.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",