@fuzdev/fuz_app 0.60.0 → 0.61.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.
Files changed (57) hide show
  1. package/dist/ui/AccountSessions.svelte +21 -6
  2. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  3. package/dist/ui/AdminAccounts.svelte +32 -25
  4. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  5. package/dist/ui/AdminAuditLog.svelte +3 -3
  6. package/dist/ui/AdminInvites.svelte +20 -15
  7. package/dist/ui/AdminOverview.svelte +19 -21
  8. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  9. package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
  10. package/dist/ui/AdminSessions.svelte +19 -21
  11. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  12. package/dist/ui/AdminSettings.svelte +1 -3
  13. package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
  14. package/dist/ui/CLAUDE.md +123 -69
  15. package/dist/ui/ConfirmButton.svelte +82 -24
  16. package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
  17. package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
  18. package/dist/ui/OpenSignupToggle.svelte +6 -4
  19. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  20. package/dist/ui/RoleGrantOfferForm.svelte +4 -4
  21. package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
  22. package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
  23. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
  24. package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
  25. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  26. package/dist/ui/account_sessions_state.svelte.js +32 -33
  27. package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
  28. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  29. package/dist/ui/admin_accounts_state.svelte.js +58 -76
  30. package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
  31. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  32. package/dist/ui/admin_invites_state.svelte.js +32 -48
  33. package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
  34. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  35. package/dist/ui/admin_sessions_state.svelte.js +30 -47
  36. package/dist/ui/app_settings_state.svelte.d.ts +8 -3
  37. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  38. package/dist/ui/app_settings_state.svelte.js +19 -27
  39. package/dist/ui/async_slot.svelte.d.ts +173 -0
  40. package/dist/ui/async_slot.svelte.d.ts.map +1 -0
  41. package/dist/ui/async_slot.svelte.js +241 -0
  42. package/dist/ui/audit_log_state.svelte.d.ts +8 -2
  43. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  44. package/dist/ui/audit_log_state.svelte.js +19 -18
  45. package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
  46. package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
  47. package/dist/ui/keyed_async_slot.svelte.js +177 -0
  48. package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
  49. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  50. package/dist/ui/role_grant_offers_state.svelte.js +34 -15
  51. package/dist/ui/table_state.svelte.d.ts +10 -7
  52. package/dist/ui/table_state.svelte.d.ts.map +1 -1
  53. package/dist/ui/table_state.svelte.js +11 -8
  54. package/package.json +1 -1
  55. package/dist/ui/loadable.svelte.d.ts +0 -60
  56. package/dist/ui/loadable.svelte.d.ts.map +0 -1
  57. package/dist/ui/loadable.svelte.js +0 -80
@@ -6,18 +6,24 @@
6
6
  * stream continues to use `EventSource` directly — streams aren't an RPC
7
7
  * concern.
8
8
  *
9
+ * Holds two `AsyncSlot`s — `list` (the main event stream) and
10
+ * `role_grant_history` (the dedicated role-grant history endpoint). Data
11
+ * lives on the class so SSE pushes and gap-fill calls update it directly.
12
+ *
9
13
  * @module
10
14
  */
11
15
  import { DEV } from 'esm-env';
12
16
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
13
- import { Loadable } from './loadable.svelte.js';
17
+ import { AsyncSlot } from './async_slot.svelte.js';
14
18
  /**
15
19
  * Svelte context carrying the reactive `AuditLogRpc` accessor. Mirrors
16
20
  * `admin_accounts_rpc_context`. Unset context falls back to `() => null`.
17
21
  */
18
22
  export const audit_log_rpc_context = create_context(() => () => null);
19
- export class AuditLogState extends Loadable {
23
+ export class AuditLogState {
20
24
  #get_rpc;
25
+ list = new AsyncSlot();
26
+ role_grant_history = new AsyncSlot();
21
27
  events = $state.raw([]);
22
28
  role_grant_history_events = $state.raw([]);
23
29
  count = $derived(this.events.length);
@@ -30,7 +36,6 @@ export class AuditLogState extends Loadable {
30
36
  /** Path to the SSE stream endpoint. */
31
37
  #stream_url;
32
38
  constructor(options) {
33
- super();
34
39
  this.#get_rpc = options?.get_rpc ?? (() => null);
35
40
  this.#stream_url = options?.stream_url ?? '/api/admin/audit/stream';
36
41
  }
@@ -38,26 +43,22 @@ export class AuditLogState extends Loadable {
38
43
  get has_rpc() {
39
44
  return this.#get_rpc() !== null;
40
45
  }
41
- async fetch(options) {
46
+ #require_rpc() {
42
47
  const rpc = this.#get_rpc();
43
- if (!rpc) {
44
- this.error = 'rpc adapter not wired';
45
- return;
46
- }
47
- await this.run(async () => {
48
- const { events } = await rpc.list(options);
48
+ if (!rpc)
49
+ throw new Error('rpc adapter not wired');
50
+ return rpc;
51
+ }
52
+ async fetch(options) {
53
+ await this.list.run(async () => {
54
+ const { events } = await this.#require_rpc().list(options);
49
55
  this.events = events;
50
- this.#update_last_seq(this.events);
56
+ this.#update_last_seq(events);
51
57
  });
52
58
  }
53
59
  async fetch_role_grant_history(limit, offset) {
54
- const rpc = this.#get_rpc();
55
- if (!rpc) {
56
- this.error = 'rpc adapter not wired';
57
- return;
58
- }
59
- await this.run(async () => {
60
- const { events } = await rpc.role_grant_history({ limit, offset });
60
+ await this.role_grant_history.run(async () => {
61
+ const { events } = await this.#require_rpc().role_grant_history({ limit, offset });
61
62
  this.role_grant_history_events = events;
62
63
  });
63
64
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Keyed sibling of `AsyncSlot` — fans the per-instance supersession
3
+ * machinery out across an open set of keys.
4
+ *
5
+ * Each key gets its own lazily-created `AsyncSlot`, so concurrent
6
+ * `run(key_a, ...)` and `run(key_b, ...)` calls are independent: a
7
+ * second `run()` on `key_a` aborts only `key_a`'s in-flight call,
8
+ * leaving `key_b` running. The keyed shape replaces the
9
+ * `AsyncSlot` + `SvelteSet<id>` pair that state classes previously
10
+ * carried for per-row in-flight tracking, with two genuine wins:
11
+ *
12
+ * - **Cross-key supersession is correct** — clicking row B while row
13
+ * A is in flight no longer aborts A; each row has its own
14
+ * AbortController.
15
+ * - **Per-key error surfacing** — `error(key)` carries the failure
16
+ * for that key only, instead of the last-error-wins shape of a
17
+ * shared slot.
18
+ *
19
+ * The backing `SvelteMap` keeps entries even after a `run()` resolves
20
+ * — components can read `error(key)` to render an inline per-row
21
+ * failure indicator. Call `delete(key)` to dismiss an entry, or
22
+ * `reset()` to clear everything (e.g. on page leave).
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * class AdminInvitesState {
27
+ * readonly remove = new KeyedAsyncSlot<Uuid>();
28
+ *
29
+ * async submit_delete(id: Uuid): Promise<void> {
30
+ * const ok = await this.remove.run(id, () => this.#rpc().delete({invite_id: id}));
31
+ * if (ok !== undefined) await this.fetch();
32
+ * }
33
+ * }
34
+ *
35
+ * // In a template:
36
+ * // <button disabled={state.remove.loading(row.id)}>
37
+ * // {state.remove.loading(row.id) ? 'deleting…' : 'delete'}
38
+ * // </button>
39
+ * // {#if state.remove.error(row.id)}<p>{state.remove.error(row.id)}</p>{/if}
40
+ * ```
41
+ *
42
+ * @module
43
+ */
44
+ import { AsyncSlot, type AsyncSlotOptions, type RunOptions } from './async_slot.svelte.js';
45
+ /**
46
+ * Constructor options for `KeyedAsyncSlot`. Propagated to every child
47
+ * `AsyncSlot` at lazy creation time.
48
+ *
49
+ * `initial` from {@link AsyncSlotOptions} is deliberately omitted —
50
+ * keyed slots have no per-key seed concept (the entries don't exist
51
+ * until `run()` creates them).
52
+ */
53
+ export type KeyedAsyncSlotOptions<T, E = string> = Omit<AsyncSlotOptions<T, E>, 'initial'>;
54
+ /**
55
+ * Reactive container for many concurrent async operations keyed by `K`.
56
+ *
57
+ * @typeParam K - The key type. Map identity is SameValueZero — branded
58
+ * strings (`Uuid`) work directly. For composite keys, stringify at
59
+ * the call site (e.g. `` `${account_id}:${role}` ``).
60
+ * @typeParam T - The success payload type. Use `void` for write-only
61
+ * actions whose response isn't worth retaining.
62
+ * @typeParam E - The shape of per-key `error(key)`. Defaults to
63
+ * `string` (set by the default `map_error`).
64
+ */
65
+ export declare class KeyedAsyncSlot<K, T = void, E = string> {
66
+ #private;
67
+ constructor(options?: KeyedAsyncSlotOptions<T, E>);
68
+ /** Total number of keys with state (pending OR resolved). Reactive. */
69
+ get size(): number;
70
+ /** Reactive — true once `run(key, ...)` has been called and the entry hasn't been deleted. */
71
+ has(key: K): boolean;
72
+ /**
73
+ * Direct access to the underlying `AsyncSlot` for `key`, or
74
+ * `undefined` if no `run()` has been issued for it yet. Reactive on
75
+ * map population and on the slot's `$state.raw` fields.
76
+ *
77
+ * Prefer the sugar getters ({@link loading}, {@link error}) for
78
+ * templates; reach for `get(key)` when you need `error_data`, `data`,
79
+ * or to call `abort()` / `set()` / `reset()` on the underlying slot.
80
+ */
81
+ get(key: K): AsyncSlot<T, E> | undefined;
82
+ /** Reactive — `false` for keys that have never been used. */
83
+ loading(key: K): boolean;
84
+ /** Reactive — `null` when the key has no entry or hasn't failed. */
85
+ error(key: K): E | null;
86
+ /** Reactive — `false` for keys that have never been used. */
87
+ failed(key: K): boolean;
88
+ /** Reactive — `false` for keys that have never been used. */
89
+ succeeded(key: K): boolean;
90
+ /** Reactive iterator over every key with state. */
91
+ keys(): IterableIterator<K>;
92
+ /** Reactive iterator over every slot. */
93
+ values(): IterableIterator<AsyncSlot<T, E>>;
94
+ /** Reactive iterator over `[key, slot]` pairs. */
95
+ entries(): IterableIterator<[K, AsyncSlot<T, E>]>;
96
+ /**
97
+ * Run an async operation for `key`. Lazily creates an `AsyncSlot`
98
+ * for the key on first use, inheriting the constructor's
99
+ * `map_error` / `preserve_error_on_retry` options.
100
+ *
101
+ * Supersession is scoped to `key`: a second `run(key, ...)` aborts
102
+ * the first's signal AND drops its commit. Calls on different keys
103
+ * are fully independent (each has its own `AbortController`).
104
+ *
105
+ * @returns the resolved value on success; `undefined` on failure,
106
+ * abort, or supersession.
107
+ */
108
+ run(key: K, fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions): Promise<T | undefined>;
109
+ /**
110
+ * Abort the in-flight run for `key`, if any. No-op when the key has
111
+ * no entry. The slot stays in the map at its prior resolved status —
112
+ * call {@link delete} to remove the entry entirely.
113
+ */
114
+ abort(key: K, reason?: unknown): void;
115
+ /**
116
+ * Abort every in-flight run. Resolved entries stay in the map —
117
+ * call {@link reset} to clear them too.
118
+ */
119
+ abort_all(reason?: unknown): void;
120
+ /**
121
+ * Abort the in-flight run for `key` (if any) and remove the entry
122
+ * from the map. After `delete(key)`, `has(key)` returns `false` and
123
+ * the sugar getters report the no-entry defaults — typically how a
124
+ * UI dismisses a per-row error indicator.
125
+ *
126
+ * @returns `true` if the key had an entry.
127
+ *
128
+ * @mutates `this`
129
+ */
130
+ delete(key: K): boolean;
131
+ /**
132
+ * Abort every in-flight run and clear the map. The keyed slot looks
133
+ * like a fresh instance afterwards.
134
+ *
135
+ * @mutates `this`
136
+ */
137
+ reset(): void;
138
+ }
139
+ //# sourceMappingURL=keyed_async_slot.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyed_async_slot.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/keyed_async_slot.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAIH,OAAO,EAAC,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAEzF;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAE3F;;;;;;;;;;GAUG;AACH,qBAAa,cAAc,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,MAAM;;gBAItC,OAAO,GAAE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAM;IAIrD,uEAAuE;IACvE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8FAA8F;IAC9F,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIpB;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS;IAIxC,6DAA6D;IAC7D,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIxB,oEAAoE;IACpE,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI;IAIvB,6DAA6D;IAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIvB,6DAA6D;IAC7D,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAI1B,mDAAmD;IACnD,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAI3B,yCAAyC;IACzC,MAAM,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAI3C,kDAAkD;IAClD,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAIjD;;;;;;;;;;;OAWG;IACG,GAAG,CACR,GAAG,EAAE,CAAC,EACN,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACvC,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IASzB;;;;OAIG;IACH,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI;IAIrC;;;OAGG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI;IAMjC;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAOvB;;;;;OAKG;IACH,KAAK,IAAI,IAAI;CAMb"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Keyed sibling of `AsyncSlot` — fans the per-instance supersession
3
+ * machinery out across an open set of keys.
4
+ *
5
+ * Each key gets its own lazily-created `AsyncSlot`, so concurrent
6
+ * `run(key_a, ...)` and `run(key_b, ...)` calls are independent: a
7
+ * second `run()` on `key_a` aborts only `key_a`'s in-flight call,
8
+ * leaving `key_b` running. The keyed shape replaces the
9
+ * `AsyncSlot` + `SvelteSet<id>` pair that state classes previously
10
+ * carried for per-row in-flight tracking, with two genuine wins:
11
+ *
12
+ * - **Cross-key supersession is correct** — clicking row B while row
13
+ * A is in flight no longer aborts A; each row has its own
14
+ * AbortController.
15
+ * - **Per-key error surfacing** — `error(key)` carries the failure
16
+ * for that key only, instead of the last-error-wins shape of a
17
+ * shared slot.
18
+ *
19
+ * The backing `SvelteMap` keeps entries even after a `run()` resolves
20
+ * — components can read `error(key)` to render an inline per-row
21
+ * failure indicator. Call `delete(key)` to dismiss an entry, or
22
+ * `reset()` to clear everything (e.g. on page leave).
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * class AdminInvitesState {
27
+ * readonly remove = new KeyedAsyncSlot<Uuid>();
28
+ *
29
+ * async submit_delete(id: Uuid): Promise<void> {
30
+ * const ok = await this.remove.run(id, () => this.#rpc().delete({invite_id: id}));
31
+ * if (ok !== undefined) await this.fetch();
32
+ * }
33
+ * }
34
+ *
35
+ * // In a template:
36
+ * // <button disabled={state.remove.loading(row.id)}>
37
+ * // {state.remove.loading(row.id) ? 'deleting…' : 'delete'}
38
+ * // </button>
39
+ * // {#if state.remove.error(row.id)}<p>{state.remove.error(row.id)}</p>{/if}
40
+ * ```
41
+ *
42
+ * @module
43
+ */
44
+ import { SvelteMap } from 'svelte/reactivity';
45
+ import { AsyncSlot } from './async_slot.svelte.js';
46
+ /**
47
+ * Reactive container for many concurrent async operations keyed by `K`.
48
+ *
49
+ * @typeParam K - The key type. Map identity is SameValueZero — branded
50
+ * strings (`Uuid`) work directly. For composite keys, stringify at
51
+ * the call site (e.g. `` `${account_id}:${role}` ``).
52
+ * @typeParam T - The success payload type. Use `void` for write-only
53
+ * actions whose response isn't worth retaining.
54
+ * @typeParam E - The shape of per-key `error(key)`. Defaults to
55
+ * `string` (set by the default `map_error`).
56
+ */
57
+ export class KeyedAsyncSlot {
58
+ #slots = new SvelteMap();
59
+ #options;
60
+ constructor(options = {}) {
61
+ this.#options = options;
62
+ }
63
+ /** Total number of keys with state (pending OR resolved). Reactive. */
64
+ get size() {
65
+ return this.#slots.size;
66
+ }
67
+ /** Reactive — true once `run(key, ...)` has been called and the entry hasn't been deleted. */
68
+ has(key) {
69
+ return this.#slots.has(key);
70
+ }
71
+ /**
72
+ * Direct access to the underlying `AsyncSlot` for `key`, or
73
+ * `undefined` if no `run()` has been issued for it yet. Reactive on
74
+ * map population and on the slot's `$state.raw` fields.
75
+ *
76
+ * Prefer the sugar getters ({@link loading}, {@link error}) for
77
+ * templates; reach for `get(key)` when you need `error_data`, `data`,
78
+ * or to call `abort()` / `set()` / `reset()` on the underlying slot.
79
+ */
80
+ get(key) {
81
+ return this.#slots.get(key);
82
+ }
83
+ /** Reactive — `false` for keys that have never been used. */
84
+ loading(key) {
85
+ return this.#slots.get(key)?.loading ?? false;
86
+ }
87
+ /** Reactive — `null` when the key has no entry or hasn't failed. */
88
+ error(key) {
89
+ return this.#slots.get(key)?.error ?? null;
90
+ }
91
+ /** Reactive — `false` for keys that have never been used. */
92
+ failed(key) {
93
+ return this.#slots.get(key)?.failed ?? false;
94
+ }
95
+ /** Reactive — `false` for keys that have never been used. */
96
+ succeeded(key) {
97
+ return this.#slots.get(key)?.succeeded ?? false;
98
+ }
99
+ /** Reactive iterator over every key with state. */
100
+ keys() {
101
+ return this.#slots.keys();
102
+ }
103
+ /** Reactive iterator over every slot. */
104
+ values() {
105
+ return this.#slots.values();
106
+ }
107
+ /** Reactive iterator over `[key, slot]` pairs. */
108
+ entries() {
109
+ return this.#slots.entries();
110
+ }
111
+ /**
112
+ * Run an async operation for `key`. Lazily creates an `AsyncSlot`
113
+ * for the key on first use, inheriting the constructor's
114
+ * `map_error` / `preserve_error_on_retry` options.
115
+ *
116
+ * Supersession is scoped to `key`: a second `run(key, ...)` aborts
117
+ * the first's signal AND drops its commit. Calls on different keys
118
+ * are fully independent (each has its own `AbortController`).
119
+ *
120
+ * @returns the resolved value on success; `undefined` on failure,
121
+ * abort, or supersession.
122
+ */
123
+ async run(key, fn, options) {
124
+ let slot = this.#slots.get(key);
125
+ if (!slot) {
126
+ slot = new AsyncSlot(this.#options);
127
+ this.#slots.set(key, slot);
128
+ }
129
+ return slot.run(fn, options);
130
+ }
131
+ /**
132
+ * Abort the in-flight run for `key`, if any. No-op when the key has
133
+ * no entry. The slot stays in the map at its prior resolved status —
134
+ * call {@link delete} to remove the entry entirely.
135
+ */
136
+ abort(key, reason) {
137
+ this.#slots.get(key)?.abort(reason);
138
+ }
139
+ /**
140
+ * Abort every in-flight run. Resolved entries stay in the map —
141
+ * call {@link reset} to clear them too.
142
+ */
143
+ abort_all(reason) {
144
+ for (const slot of this.#slots.values()) {
145
+ slot.abort(reason);
146
+ }
147
+ }
148
+ /**
149
+ * Abort the in-flight run for `key` (if any) and remove the entry
150
+ * from the map. After `delete(key)`, `has(key)` returns `false` and
151
+ * the sugar getters report the no-entry defaults — typically how a
152
+ * UI dismisses a per-row error indicator.
153
+ *
154
+ * @returns `true` if the key had an entry.
155
+ *
156
+ * @mutates `this`
157
+ */
158
+ delete(key) {
159
+ const slot = this.#slots.get(key);
160
+ if (!slot)
161
+ return false;
162
+ slot.abort();
163
+ return this.#slots.delete(key);
164
+ }
165
+ /**
166
+ * Abort every in-flight run and clear the map. The keyed slot looks
167
+ * like a fresh instance afterwards.
168
+ *
169
+ * @mutates `this`
170
+ */
171
+ reset() {
172
+ for (const slot of this.#slots.values()) {
173
+ slot.abort();
174
+ }
175
+ this.#slots.clear();
176
+ }
177
+ }
@@ -13,9 +13,18 @@
13
13
  * delivery is pull-only via `subscribe()` — the consumer plumbs their
14
14
  * `FrontendWebsocketClient` / `ActionPeer` receiver to `apply_notification`.
15
15
  *
16
+ * Holds six `AsyncSlot`s — one per RPC verb. The cache `#offers` lives on
17
+ * the class (multiple ops write into it via `#merge_offers` /
18
+ * `#remove_offer`); the `create` slot is typed `AsyncSlot<RoleGrantOfferJson>`
19
+ * so `submit_create` can return the new offer via the slot's
20
+ * supersession-safe `data` path, but the other slots' `data` is unused
21
+ * (no single op owns the cache). Method names use the `submit_*` prefix
22
+ * to avoid slot-name collisions; the `history` view stayed natural by
23
+ * naming the fetch slot `list_history`.
24
+ *
16
25
  * @module
17
26
  */
18
- import { Loadable } from './loadable.svelte.js';
27
+ import { AsyncSlot } from './async_slot.svelte.js';
19
28
  import type { RoleGrantOfferJson } from '../auth/role_grant_offer_schema.js';
20
29
  /**
21
30
  * Svelte context for `RoleGrantOffersState`.
@@ -81,8 +90,31 @@ export interface RoleGrantOffersStateOptions {
81
90
  */
82
91
  actor_id: () => string | null;
83
92
  }
84
- export declare class RoleGrantOffersState extends Loadable {
93
+ export declare class RoleGrantOffersState {
85
94
  #private;
95
+ readonly list: AsyncSlot<void, string>;
96
+ readonly list_history: AsyncSlot<void, string>;
97
+ readonly create: AsyncSlot<{
98
+ id: string & import("zod").$brand<"Uuid">;
99
+ from_actor_id: string & import("zod").$brand<"Uuid">;
100
+ to_account_id: string & import("zod").$brand<"Uuid">;
101
+ to_actor_id: (string & import("zod").$brand<"Uuid">) | null;
102
+ role: string;
103
+ scope_kind: string | null;
104
+ scope_id: (string & import("zod").$brand<"Uuid">) | null;
105
+ message: string | null;
106
+ created_at: string;
107
+ expires_at: string;
108
+ accepted_at: string | null;
109
+ declined_at: string | null;
110
+ decline_reason: string | null;
111
+ retracted_at: string | null;
112
+ superseded_at: string | null;
113
+ resulting_role_grant_id: (string & import("zod").$brand<"Uuid">) | null;
114
+ }, string>;
115
+ readonly accept: AsyncSlot<void, string>;
116
+ readonly decline: AsyncSlot<void, string>;
117
+ readonly retract: AsyncSlot<void, string>;
86
118
  /** Pending offers for the current account, soonest-expiring first. */
87
119
  readonly incoming: Array<RoleGrantOfferJson>;
88
120
  /** Pending offers from the current actor, newest-created first. */
@@ -105,7 +137,7 @@ export declare class RoleGrantOffersState extends Loadable {
105
137
  * `to_account_id`; omit / null for the account-grain default (any actor
106
138
  * on the recipient account may accept).
107
139
  */
108
- create(params: {
140
+ submit_create(params: {
109
141
  to_account_id: string;
110
142
  to_actor_id?: string | null;
111
143
  role: string;
@@ -113,9 +145,9 @@ export declare class RoleGrantOffersState extends Loadable {
113
145
  message?: string | null;
114
146
  }): Promise<RoleGrantOfferJson | undefined>;
115
147
  /** Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded. */
116
- accept(offer_id: string): Promise<void>;
117
- decline(offer_id: string, reason?: string | null): Promise<void>;
118
- retract(offer_id: string): Promise<void>;
148
+ submit_accept(offer_id: string): Promise<void>;
149
+ submit_decline(offer_id: string, reason?: string | null): Promise<void>;
150
+ submit_retract(offer_id: string): Promise<void>;
119
151
  /**
120
152
  * Wire a notification subscription. The handler dispatches each matching
121
153
  * notification into `apply_notification`; the returned disposer unwires.
@@ -129,7 +161,7 @@ export declare class RoleGrantOffersState extends Loadable {
129
161
  * @mutates `this`
130
162
  */
131
163
  apply_notification(notification: RoleGrantOfferNotification): void;
132
- /** Clear the cache and reset loading/error state. */
164
+ /** Clear the cache and reset every slot. */
133
165
  reset(): void;
134
166
  }
135
167
  //# sourceMappingURL=role_grant_offers_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}
@@ -13,10 +13,19 @@
13
13
  * delivery is pull-only via `subscribe()` — the consumer plumbs their
14
14
  * `FrontendWebsocketClient` / `ActionPeer` receiver to `apply_notification`.
15
15
  *
16
+ * Holds six `AsyncSlot`s — one per RPC verb. The cache `#offers` lives on
17
+ * the class (multiple ops write into it via `#merge_offers` /
18
+ * `#remove_offer`); the `create` slot is typed `AsyncSlot<RoleGrantOfferJson>`
19
+ * so `submit_create` can return the new offer via the slot's
20
+ * supersession-safe `data` path, but the other slots' `data` is unused
21
+ * (no single op owns the cache). Method names use the `submit_*` prefix
22
+ * to avoid slot-name collisions; the `history` view stayed natural by
23
+ * naming the fetch slot `list_history`.
24
+ *
16
25
  * @module
17
26
  */
18
27
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
19
- import { Loadable } from './loadable.svelte.js';
28
+ import { AsyncSlot } from './async_slot.svelte.js';
20
29
  import { ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, ROLE_GRANT_REVOKE_NOTIFICATION_METHOD, } from '../auth/role_grant_offer_notifications.js';
21
30
  /**
22
31
  * Svelte context for `RoleGrantOffersState`.
@@ -28,10 +37,16 @@ const is_terminal = (o) => o.accepted_at !== null ||
28
37
  o.declined_at !== null ||
29
38
  o.retracted_at !== null ||
30
39
  o.superseded_at !== null;
31
- export class RoleGrantOffersState extends Loadable {
40
+ export class RoleGrantOffersState {
32
41
  #rpc;
33
42
  #get_account_id;
34
43
  #get_actor_id;
44
+ list = new AsyncSlot();
45
+ list_history = new AsyncSlot();
46
+ create = new AsyncSlot();
47
+ accept = new AsyncSlot();
48
+ decline = new AsyncSlot();
49
+ retract = new AsyncSlot();
35
50
  #offers = $state.raw(new Map());
36
51
  /** Pending offers for the current account, soonest-expiring first. */
37
52
  incoming = $derived.by(() => {
@@ -79,21 +94,20 @@ export class RoleGrantOffersState extends Loadable {
79
94
  });
80
95
  incoming_count = $derived(this.incoming.length);
81
96
  constructor(options) {
82
- super();
83
97
  this.#rpc = options.rpc;
84
98
  this.#get_account_id = options.account_id;
85
99
  this.#get_actor_id = options.actor_id;
86
100
  }
87
101
  /** Seed the cache with the recipient-side pending inbox. */
88
102
  async fetch() {
89
- await this.run(async () => {
103
+ await this.list.run(async () => {
90
104
  const { offers } = await this.#rpc.list();
91
105
  this.#merge_offers(offers);
92
106
  });
93
107
  }
94
108
  /** Seed both-directions history (includes terminal rows). */
95
109
  async fetch_history(options) {
96
- await this.run(async () => {
110
+ await this.list_history.run(async () => {
97
111
  const { offers } = await this.#rpc.history(options);
98
112
  this.#merge_offers(offers);
99
113
  });
@@ -105,16 +119,16 @@ export class RoleGrantOffersState extends Loadable {
105
119
  * `to_account_id`; omit / null for the account-grain default (any actor
106
120
  * on the recipient account may accept).
107
121
  */
108
- async create(params) {
109
- return this.run(async () => {
122
+ async submit_create(params) {
123
+ return this.create.run(async () => {
110
124
  const { offer } = await this.#rpc.create(params);
111
125
  this.#merge_offers([offer]);
112
126
  return offer;
113
127
  });
114
128
  }
115
129
  /** Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded. */
116
- async accept(offer_id) {
117
- await this.run(async () => {
130
+ async submit_accept(offer_id) {
131
+ await this.accept.run(async () => {
118
132
  const result = await this.#rpc.accept(offer_id);
119
133
  this.#merge_offers([result.offer]);
120
134
  // siblings are authoritatively superseded server-side; the
@@ -125,14 +139,14 @@ export class RoleGrantOffersState extends Loadable {
125
139
  }
126
140
  });
127
141
  }
128
- async decline(offer_id, reason) {
129
- await this.run(async () => {
142
+ async submit_decline(offer_id, reason) {
143
+ await this.decline.run(async () => {
130
144
  await this.#rpc.decline(offer_id, reason);
131
145
  this.#remove_offer(offer_id);
132
146
  });
133
147
  }
134
- async retract(offer_id) {
135
- await this.run(async () => {
148
+ async submit_retract(offer_id) {
149
+ await this.retract.run(async () => {
136
150
  await this.#rpc.retract(offer_id);
137
151
  this.#remove_offer(offer_id);
138
152
  });
@@ -178,9 +192,14 @@ export class RoleGrantOffersState extends Loadable {
178
192
  return;
179
193
  }
180
194
  }
181
- /** Clear the cache and reset loading/error state. */
195
+ /** Clear the cache and reset every slot. */
182
196
  reset() {
183
- super.reset();
197
+ this.list.reset();
198
+ this.list_history.reset();
199
+ this.create.reset();
200
+ this.accept.reset();
201
+ this.decline.reset();
202
+ this.retract.reset();
184
203
  this.#offers = new Map();
185
204
  }
186
205
  #merge_offers(offers) {
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Reactive state for database table pagination and data fetching.
3
3
  *
4
- * Extends `Loadable` to manage paginated table data with column metadata,
5
- * row deletion, and derived pagination controls.
4
+ * Holds one `AsyncSlot` `list` (the paginated row fetch). Per-row delete
5
+ * uses plain try/catch + scalar `deleting` / `delete_error` fields (no slot
6
+ * — `delete_error` must persist past `list.run()` retries so the failure
7
+ * message stays visible while the user refetches).
6
8
  *
7
9
  * @example
8
10
  * ```ts
@@ -26,10 +28,10 @@
26
28
  * table.fetch('accounts');
27
29
  * </script>
28
30
  *
29
- * {#if table.loading}
31
+ * {#if table.list.loading}
30
32
  * <p>loading…</p>
31
- * {:else if table.error}
32
- * <p>{table.error}</p>
33
+ * {:else if table.list.error}
34
+ * <p>{table.list.error}</p>
33
35
  * {:else}
34
36
  * <p>showing {table.showing_start}–{table.showing_end} of {table.total}</p>
35
37
  * {/if}
@@ -37,11 +39,12 @@
37
39
  *
38
40
  * @module
39
41
  */
40
- import { Loadable } from './loadable.svelte.js';
42
+ import { AsyncSlot } from './async_slot.svelte.js';
41
43
  import type { ColumnInfo } from '../http/db_routes.js';
42
44
  /** Maximum number of rows that can be fetched in a single page. */
43
45
  export declare const TABLE_LIMIT_MAX = 1000;
44
- export declare class TableState extends Loadable {
46
+ export declare class TableState {
47
+ readonly list: AsyncSlot<void, string>;
45
48
  table_name: string;
46
49
  columns: Array<ColumnInfo>;
47
50
  rows: Array<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"table_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/table_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAG9C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,sBAAsB,CAAC;AAErD,mEAAmE;AACnE,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,qBAAa,UAAW,SAAQ,QAAQ;IACvC,UAAU,EAAE,MAAM,CAAkB;IACpC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAkB;IAC5C,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAkB;IACtD,KAAK,SAAiB;IACtB,MAAM,SAAiB;IACvB,KAAK,SAAmB;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC9C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC3C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAG/C,QAAQ,CAAC,aAAa,SAAoD;IAC1E,QAAQ,CAAC,WAAW,SAAkE;IACtF,QAAQ,CAAC,QAAQ,UAA6B;IAC9C,QAAQ,CAAC,QAAQ,UAAmD;IAEpE;;;;;OAKG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBvE,OAAO,IAAI,IAAI;IAIf,OAAO,IAAI,IAAI;IAIf;;;;;;;OAOG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAoChE"}
1
+ {"version":3,"file":"table_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/table_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAGjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,sBAAsB,CAAC;AAErD,mEAAmE;AACnE,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,qBAAa,UAAU;IACtB,QAAQ,CAAC,IAAI,0BAAyB;IAEtC,UAAU,EAAE,MAAM,CAAkB;IACpC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAkB;IAC5C,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAkB;IACtD,KAAK,SAAiB;IACtB,MAAM,SAAiB;IACvB,KAAK,SAAmB;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC9C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC3C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAG/C,QAAQ,CAAC,aAAa,SAAoD;IAC1E,QAAQ,CAAC,WAAW,SAAkE;IACtF,QAAQ,CAAC,QAAQ,UAA6B;IAC9C,QAAQ,CAAC,QAAQ,UAAmD;IAEpE;;;;;OAKG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBvE,OAAO,IAAI,IAAI;IAIf,OAAO,IAAI,IAAI;IAIf;;;;;;;OAOG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAoChE"}