@fuzdev/fuz_app 0.59.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 (151) hide show
  1. package/dist/actions/CLAUDE.md +5 -5
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +2 -2
  4. package/dist/actions/action_event_helpers.d.ts +3 -3
  5. package/dist/actions/action_event_helpers.js +8 -8
  6. package/dist/actions/action_event_types.d.ts +3 -3
  7. package/dist/actions/action_event_types.js +3 -3
  8. package/dist/actions/transports_ws_auth_guard.d.ts +2 -2
  9. package/dist/actions/transports_ws_auth_guard.js +3 -3
  10. package/dist/auth/CLAUDE.md +157 -15
  11. package/dist/auth/actor_lookup_action_specs.d.ts +127 -0
  12. package/dist/auth/actor_lookup_action_specs.d.ts.map +1 -0
  13. package/dist/auth/actor_lookup_action_specs.js +93 -0
  14. package/dist/auth/actor_lookup_actions.d.ts +19 -0
  15. package/dist/auth/actor_lookup_actions.d.ts.map +1 -0
  16. package/dist/auth/actor_lookup_actions.js +32 -0
  17. package/dist/auth/actor_lookup_queries.d.ts +44 -0
  18. package/dist/auth/actor_lookup_queries.d.ts.map +1 -0
  19. package/dist/auth/actor_lookup_queries.js +42 -0
  20. package/dist/auth/actor_search_action_specs.d.ts +166 -0
  21. package/dist/auth/actor_search_action_specs.d.ts.map +1 -0
  22. package/dist/auth/actor_search_action_specs.js +139 -0
  23. package/dist/auth/actor_search_actions.d.ts +31 -0
  24. package/dist/auth/actor_search_actions.d.ts.map +1 -0
  25. package/dist/auth/actor_search_actions.js +61 -0
  26. package/dist/auth/actor_search_queries.d.ts +75 -0
  27. package/dist/auth/actor_search_queries.d.ts.map +1 -0
  28. package/dist/auth/actor_search_queries.js +91 -0
  29. package/dist/auth/admin_actions.js +2 -2
  30. package/dist/auth/all_action_spec_registries.d.ts +55 -0
  31. package/dist/auth/all_action_spec_registries.d.ts.map +1 -0
  32. package/dist/auth/all_action_spec_registries.js +59 -0
  33. package/dist/auth/audit_emitter.d.ts +1 -1
  34. package/dist/auth/audit_emitter.js +2 -2
  35. package/dist/auth/audit_log_queries.d.ts +1 -1
  36. package/dist/auth/audit_log_queries.js +3 -3
  37. package/dist/auth/audit_log_routes.d.ts +1 -1
  38. package/dist/auth/audit_log_routes.js +1 -1
  39. package/dist/auth/audit_log_schema.d.ts +5 -5
  40. package/dist/auth/audit_log_schema.js +7 -7
  41. package/dist/auth/auth_ddl.d.ts +7 -0
  42. package/dist/auth/auth_ddl.d.ts.map +1 -1
  43. package/dist/auth/auth_ddl.js +8 -0
  44. package/dist/auth/credential_type_schema.d.ts +1 -1
  45. package/dist/auth/credential_type_schema.js +3 -3
  46. package/dist/auth/grant_path_schema.d.ts +1 -1
  47. package/dist/auth/grant_path_schema.js +3 -3
  48. package/dist/auth/migrations.d.ts +4 -4
  49. package/dist/auth/migrations.d.ts.map +1 -1
  50. package/dist/auth/migrations.js +7 -6
  51. package/dist/auth/role_grant_offer_actions.js +2 -2
  52. package/dist/auth/role_grant_offer_notifications.d.ts +2 -2
  53. package/dist/auth/role_grant_offer_notifications.js +2 -2
  54. package/dist/auth/role_grant_queries.d.ts +21 -0
  55. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  56. package/dist/auth/role_grant_queries.js +31 -0
  57. package/dist/auth/role_schema.d.ts +2 -2
  58. package/dist/auth/role_schema.js +3 -3
  59. package/dist/auth/self_service_role_actions.d.ts +1 -1
  60. package/dist/auth/self_service_role_actions.js +2 -2
  61. package/dist/auth/session_cookie.d.ts +1 -1
  62. package/dist/auth/session_cookie.js +1 -1
  63. package/dist/auth/session_middleware.d.ts +1 -1
  64. package/dist/auth/session_middleware.js +5 -5
  65. package/dist/rate_limiter.d.ts +5 -5
  66. package/dist/rate_limiter.js +6 -6
  67. package/dist/realtime/sse_auth_guard.d.ts +3 -3
  68. package/dist/realtime/sse_auth_guard.js +4 -4
  69. package/dist/server/app_backend.d.ts +3 -3
  70. package/dist/server/app_backend.js +4 -4
  71. package/dist/server/app_server.d.ts +1 -1
  72. package/dist/server/app_server.js +10 -10
  73. package/dist/testing/CLAUDE.md +22 -12
  74. package/dist/testing/admin_integration.js +4 -4
  75. package/dist/testing/app_server.d.ts +1 -1
  76. package/dist/testing/app_server.js +2 -2
  77. package/dist/testing/attack_surface.d.ts +4 -4
  78. package/dist/testing/attack_surface.js +6 -6
  79. package/dist/testing/audit_completeness.js +4 -4
  80. package/dist/testing/data_exposure.d.ts +2 -2
  81. package/dist/testing/data_exposure.js +7 -7
  82. package/dist/testing/db.d.ts +8 -8
  83. package/dist/testing/db.js +11 -11
  84. package/dist/testing/integration.js +4 -4
  85. package/dist/testing/integration_helpers.d.ts +6 -6
  86. package/dist/testing/integration_helpers.js +7 -7
  87. package/dist/testing/rate_limiting.js +4 -4
  88. package/dist/testing/round_trip.js +2 -2
  89. package/dist/testing/rpc_round_trip.js +2 -2
  90. package/dist/testing/schema_generators.d.ts.map +1 -1
  91. package/dist/testing/schema_generators.js +23 -2
  92. package/dist/testing/sse_round_trip.js +2 -2
  93. package/dist/testing/surface_invariants.d.ts +4 -4
  94. package/dist/testing/surface_invariants.js +5 -5
  95. package/dist/ui/AccountSessions.svelte +21 -6
  96. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  97. package/dist/ui/AdminAccounts.svelte +32 -25
  98. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  99. package/dist/ui/AdminAuditLog.svelte +3 -3
  100. package/dist/ui/AdminInvites.svelte +20 -15
  101. package/dist/ui/AdminOverview.svelte +19 -21
  102. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  103. package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
  104. package/dist/ui/AdminSessions.svelte +19 -21
  105. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  106. package/dist/ui/AdminSettings.svelte +1 -3
  107. package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
  108. package/dist/ui/CLAUDE.md +123 -69
  109. package/dist/ui/ConfirmButton.svelte +82 -24
  110. package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
  111. package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
  112. package/dist/ui/OpenSignupToggle.svelte +6 -4
  113. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  114. package/dist/ui/RoleGrantOfferForm.svelte +4 -4
  115. package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
  116. package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
  117. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
  118. package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
  119. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  120. package/dist/ui/account_sessions_state.svelte.js +32 -33
  121. package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
  122. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  123. package/dist/ui/admin_accounts_state.svelte.js +58 -76
  124. package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
  125. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  126. package/dist/ui/admin_invites_state.svelte.js +32 -48
  127. package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
  128. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  129. package/dist/ui/admin_sessions_state.svelte.js +30 -47
  130. package/dist/ui/app_settings_state.svelte.d.ts +8 -3
  131. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  132. package/dist/ui/app_settings_state.svelte.js +19 -27
  133. package/dist/ui/async_slot.svelte.d.ts +173 -0
  134. package/dist/ui/async_slot.svelte.d.ts.map +1 -0
  135. package/dist/ui/async_slot.svelte.js +241 -0
  136. package/dist/ui/audit_log_state.svelte.d.ts +8 -2
  137. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  138. package/dist/ui/audit_log_state.svelte.js +19 -18
  139. package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
  140. package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
  141. package/dist/ui/keyed_async_slot.svelte.js +177 -0
  142. package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
  143. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  144. package/dist/ui/role_grant_offers_state.svelte.js +34 -15
  145. package/dist/ui/table_state.svelte.d.ts +10 -7
  146. package/dist/ui/table_state.svelte.d.ts.map +1 -1
  147. package/dist/ui/table_state.svelte.js +11 -8
  148. package/package.json +1 -1
  149. package/dist/ui/loadable.svelte.d.ts +0 -60
  150. package/dist/ui/loadable.svelte.d.ts.map +0 -1
  151. package/dist/ui/loadable.svelte.js +0 -80
@@ -1 +1 @@
1
- {"version":3,"file":"account_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/account_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;KAAC,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,MAAM,EAAE;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IAChF,UAAU,EAAE,MAAM,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;qBAAwB,kBAAkB,GAAG,IAAI;yBAAzB,kBAAkB,GAAG,IAAI,wBAAzB,kBAAkB,GAAG,IAAI;CAEzF,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC3C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,kBAAkB,GAAG,IAAI,CAAC;CAC1C;AAED,qBAAa,oBAAqB,SAAQ,QAAQ;;IAGjD,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAkB;IAElD,QAAQ,CAAC,YAAY,SAAkC;gBAE3C,OAAO,CAAC,EAAE,2BAA2B;IAKjD,6FAA6F;IAC7F,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAejC"}
1
+ {"version":3,"file":"account_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/account_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;KAAC,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,MAAM,EAAE;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IAChF,UAAU,EAAE,MAAM,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;qBAAwB,kBAAkB,GAAG,IAAI;yBAAzB,kBAAkB,GAAG,IAAI,wBAAzB,kBAAkB,GAAG,IAAI;CAEzF,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC3C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,kBAAkB,GAAG,IAAI,CAAC;CAC1C;AAED,qBAAa,oBAAoB;;IAGhC,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,MAAM,uCAAsC;IACrD,QAAQ,CAAC,UAAU,0BAAyB;IAE5C,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAkB;IAElD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAkC;gBAEnD,OAAO,CAAC,EAAE,2BAA2B;IAIjD,2GAA2G;IAC3G,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;CAUxC"}
@@ -1,66 +1,65 @@
1
1
  /**
2
2
  * Reactive state for managing the authenticated account's auth sessions on a
3
- * settings page. Reads and mutations flow through a narrow RPC adapter
4
- * backed by `auth/account_actions.ts`.
3
+ * settings page. Reads and mutations flow through a narrow RPC adapter backed
4
+ * by `auth/account_actions.ts`.
5
+ *
6
+ * Holds two `AsyncSlot`s — `list` for the fetch, `revoke_all` for the bulk
7
+ * revoke — plus one `KeyedAsyncSlot<string, void>` (`revoke`) keyed by
8
+ * `session_id` for per-row revoke (independent supersession across
9
+ * concurrent rows; per-row error surfacing). Method names use the
10
+ * `submit_*` prefix to avoid slot-name collisions.
5
11
  *
6
12
  * @module
7
13
  */
8
14
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
9
- import { Loadable } from './loadable.svelte.js';
15
+ import { AsyncSlot } from './async_slot.svelte.js';
16
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
10
17
  /**
11
18
  * Svelte context carrying the reactive `AccountSessionsRpc` accessor. Mirrors
12
19
  * the admin-side RPC contexts. Unset context falls back to `() => null` so
13
20
  * components render the usual "rpc adapter not wired" state.
14
21
  */
15
22
  export const account_sessions_rpc_context = create_context(() => () => null);
16
- export class AccountSessionsState extends Loadable {
23
+ export class AccountSessionsState {
17
24
  #get_rpc;
25
+ list = new AsyncSlot();
26
+ revoke = new KeyedAsyncSlot();
27
+ revoke_all = new AsyncSlot();
18
28
  sessions = $state.raw([]);
19
29
  active_count = $derived(this.sessions.length);
20
30
  constructor(options) {
21
- super();
22
31
  this.#get_rpc = options?.get_rpc ?? (() => null);
23
32
  }
24
- /** True when an RPC adapter is wired. `fetch` / `revoke` / `revoke_all` no-op without it. */
33
+ /** True when an RPC adapter is wired. `fetch` / `submit_revoke` / `submit_revoke_all` no-op without it. */
25
34
  get has_rpc() {
26
35
  return this.#get_rpc() !== null;
27
36
  }
28
- async fetch() {
37
+ #require_rpc() {
29
38
  const rpc = this.#get_rpc();
30
- if (!rpc) {
31
- this.error = 'rpc adapter not wired';
32
- return;
33
- }
34
- await this.run(async () => {
35
- const { sessions } = await rpc.list();
39
+ if (!rpc)
40
+ throw new Error('rpc adapter not wired');
41
+ return rpc;
42
+ }
43
+ async fetch() {
44
+ await this.list.run(async () => {
45
+ const { sessions } = await this.#require_rpc().list();
36
46
  this.sessions = sessions;
37
47
  });
38
48
  }
39
- async revoke(id) {
40
- const rpc = this.#get_rpc();
41
- if (!rpc) {
42
- this.error = 'rpc adapter not wired';
43
- return;
44
- }
45
- await this.run(async () => {
46
- await rpc.revoke({ session_id: id });
49
+ async submit_revoke(id) {
50
+ await this.revoke.run(id, async () => {
51
+ await this.#require_rpc().revoke({ session_id: id });
47
52
  });
48
- if (!this.error) {
53
+ if (this.revoke.succeeded(id))
49
54
  await this.fetch();
50
- }
51
55
  }
52
- async revoke_all() {
53
- const rpc = this.#get_rpc();
54
- if (!rpc) {
55
- this.error = 'rpc adapter not wired';
56
- return;
57
- }
58
- await this.run(async () => {
59
- await rpc.revoke_all();
56
+ async submit_revoke_all() {
57
+ await this.revoke_all.run(async () => {
58
+ await this.#require_rpc().revoke_all();
60
59
  });
61
- if (!this.error) {
60
+ if (this.revoke_all.succeeded) {
62
61
  // Current session is now revoked — next API call will 401.
63
- // Clear local state so the UI shows the login page.
62
+ // Clear the local sessions cache so the UI shows the login page.
64
63
  this.sessions = [];
65
64
  }
66
65
  }
@@ -1,11 +1,20 @@
1
1
  /**
2
2
  * Reactive state for admin account management.
3
3
  *
4
+ * Holds one fetch `AsyncSlot` (`list`) plus three `KeyedAsyncSlot`s —
5
+ * `grant` (offer creation, keyed by `account_id:role` or
6
+ * `account_id:role:to_actor_id`), `revoke` (role_grant revoke, keyed
7
+ * by `role_grant_id`), `retract` (offer retraction, keyed by
8
+ * `offer_id`). Per-row supersession is correct (clicking row B no
9
+ * longer aborts row A) and `error(key)` surfaces failure per-row.
10
+ * Method names use the `submit_*` prefix to avoid slot-name
11
+ * collisions.
12
+ *
4
13
  * @module
5
14
  */
6
- import { SvelteSet } from 'svelte/reactivity';
7
15
  import type { Uuid } from '@fuzdev/fuz_util/id.js';
8
- import { Loadable } from './loadable.svelte.js';
16
+ import { AsyncSlot } from './async_slot.svelte.js';
17
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
9
18
  import type { AdminAccountEntryJson } from '../auth/account_schema.js';
10
19
  import type { RoleName } from '../auth/role_schema.js';
11
20
  import type { RoleGrantOfferJson } from '../auth/role_grant_offer_schema.js';
@@ -62,13 +71,38 @@ export interface AdminAccountsStateOptions {
62
71
  */
63
72
  get_rpc?: () => AdminAccountsRpc | null;
64
73
  }
65
- export declare class AdminAccountsState extends Loadable {
74
+ /**
75
+ * Compose the `grant` keyed-slot key for an offer. Account-grain offers
76
+ * key on `${account_id}:${role}`; actor-targeted offers add the actor
77
+ * suffix so the two variants can be in flight simultaneously without
78
+ * colliding on per-row spinners.
79
+ */
80
+ export declare const grant_key: (account_id: Uuid, role: RoleName, to_actor_id?: Uuid | null) => string;
81
+ export declare class AdminAccountsState {
66
82
  #private;
83
+ readonly list: AsyncSlot<void, string>;
84
+ readonly grant: KeyedAsyncSlot<string, {
85
+ id: string & import("zod").$brand<"Uuid">;
86
+ from_actor_id: string & import("zod").$brand<"Uuid">;
87
+ to_account_id: string & import("zod").$brand<"Uuid">;
88
+ to_actor_id: (string & import("zod").$brand<"Uuid">) | null;
89
+ role: string;
90
+ scope_kind: string | null;
91
+ scope_id: (string & import("zod").$brand<"Uuid">) | null;
92
+ message: string | null;
93
+ created_at: string;
94
+ expires_at: string;
95
+ accepted_at: string | null;
96
+ declined_at: string | null;
97
+ decline_reason: string | null;
98
+ retracted_at: string | null;
99
+ superseded_at: string | null;
100
+ resulting_role_grant_id: (string & import("zod").$brand<"Uuid">) | null;
101
+ }, string>;
102
+ readonly revoke: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
103
+ readonly retract: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
67
104
  accounts: Array<AdminAccountEntryJson>;
68
105
  grantable_roles: Array<RoleName>;
69
- readonly granting_keys: SvelteSet<string>;
70
- readonly revoking_ids: SvelteSet<string>;
71
- readonly retracting_ids: SvelteSet<string>;
72
106
  readonly account_count: number;
73
107
  constructor(options?: AdminAccountsStateOptions);
74
108
  /**
@@ -88,16 +122,13 @@ export declare class AdminAccountsState extends Loadable {
88
122
  * across those calls.
89
123
  *
90
124
  * `to_actor_id` (optional) narrows the offer to a specific actor on
91
- * `account_id`; the in-flight `granting_keys` entry stays at
92
- * `account_id:role` for the account-grain default (so existing
93
- * consumers reading the 2-segment key keep working) and becomes
94
- * `account_id:role:to_actor_id` when actor-targeted, so the two
95
- * variants can be in flight without colliding on the per-row spinner.
96
- *
97
- * No-op when the rpc adapter is absent; `error` is set to a descriptive
98
- * message so the UI surfaces the misconfiguration.
125
+ * `account_id`; the keyed-slot key stays at `account_id:role` for the
126
+ * account-grain default (so existing consumers keep working) and
127
+ * becomes `account_id:role:to_actor_id` when actor-targeted, so the
128
+ * two variants can be in flight without colliding on the per-row
129
+ * spinner.
99
130
  */
100
- create_role_grant(account_id: Uuid, role: RoleName, to_actor_id?: Uuid | null): Promise<RoleGrantOfferJson | undefined>;
131
+ submit_grant(account_id: Uuid, role: RoleName, to_actor_id?: Uuid | null): Promise<RoleGrantOfferJson | undefined>;
101
132
  /**
102
133
  * Revoke an active role_grant via the `role_grant_revoke` RPC.
103
134
  *
@@ -107,7 +138,7 @@ export declare class AdminAccountsState extends Loadable {
107
138
  * The optional `reason` is stamped on `role_grant.revoked_reason` and
108
139
  * surfaced on the revokee's WS notification.
109
140
  */
110
- revoke_role_grant(actor_id: Uuid, role_grant_id: Uuid, reason?: string | null): Promise<void>;
141
+ submit_revoke(actor_id: Uuid, role_grant_id: Uuid, reason?: string | null): Promise<void>;
111
142
  /**
112
143
  * Retract a pending offer the admin issued via the `role_grant_offer_retract`
113
144
  * RPC. The action handles auth, audit, and the
@@ -116,6 +147,6 @@ export declare class AdminAccountsState extends Loadable {
116
147
  * After success, refetches the listing so `pending_offers` drops the
117
148
  * row and the "+ {role}" button un-hides.
118
149
  */
119
- retract_offer(offer_id: Uuid): Promise<void>;
150
+ submit_retract(offer_id: Uuid): Promise<void>;
120
151
  }
121
152
  //# sourceMappingURL=admin_accounts_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin_accounts_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_accounts_state.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"admin_accounts_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_accounts_state.svelte.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,20 @@
1
1
  /**
2
2
  * Reactive state for admin account management.
3
3
  *
4
+ * Holds one fetch `AsyncSlot` (`list`) plus three `KeyedAsyncSlot`s —
5
+ * `grant` (offer creation, keyed by `account_id:role` or
6
+ * `account_id:role:to_actor_id`), `revoke` (role_grant revoke, keyed
7
+ * by `role_grant_id`), `retract` (offer retraction, keyed by
8
+ * `offer_id`). Per-row supersession is correct (clicking row B no
9
+ * longer aborts row A) and `error(key)` surfaces failure per-row.
10
+ * Method names use the `submit_*` prefix to avoid slot-name
11
+ * collisions.
12
+ *
4
13
  * @module
5
14
  */
6
- import { SvelteSet } from 'svelte/reactivity';
7
15
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
8
- import { Loadable } from './loadable.svelte.js';
16
+ import { AsyncSlot } from './async_slot.svelte.js';
17
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
9
18
  /**
10
19
  * Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
11
20
  * provisioner (typically the admin route shell) calls `set(() => rpc)`;
@@ -17,16 +26,23 @@ import { Loadable } from './loadable.svelte.js';
17
26
  * not wired" path.
18
27
  */
19
28
  export const admin_accounts_rpc_context = create_context(() => () => null);
20
- export class AdminAccountsState extends Loadable {
29
+ /**
30
+ * Compose the `grant` keyed-slot key for an offer. Account-grain offers
31
+ * key on `${account_id}:${role}`; actor-targeted offers add the actor
32
+ * suffix so the two variants can be in flight simultaneously without
33
+ * colliding on per-row spinners.
34
+ */
35
+ export const grant_key = (account_id, role, to_actor_id) => to_actor_id ? `${account_id}:${role}:${to_actor_id}` : `${account_id}:${role}`;
36
+ export class AdminAccountsState {
21
37
  #get_rpc;
38
+ list = new AsyncSlot();
39
+ grant = new KeyedAsyncSlot();
40
+ revoke = new KeyedAsyncSlot();
41
+ retract = new KeyedAsyncSlot();
22
42
  accounts = $state.raw([]);
23
43
  grantable_roles = $state.raw([]);
24
- granting_keys = new SvelteSet();
25
- revoking_ids = new SvelteSet();
26
- retracting_ids = new SvelteSet();
27
44
  account_count = $derived(this.accounts.length);
28
45
  constructor(options) {
29
- super();
30
46
  this.#get_rpc = options?.get_rpc ?? (() => null);
31
47
  }
32
48
  /**
@@ -36,14 +52,15 @@ export class AdminAccountsState extends Loadable {
36
52
  get has_rpc() {
37
53
  return this.#get_rpc() !== null;
38
54
  }
39
- async fetch() {
55
+ #require_rpc() {
40
56
  const rpc = this.#get_rpc();
41
- if (!rpc) {
42
- this.error = 'rpc adapter not wired';
43
- return;
44
- }
45
- await this.run(async () => {
46
- const { accounts, grantable_roles } = await rpc.list_accounts();
57
+ if (!rpc)
58
+ throw new Error('rpc adapter not wired');
59
+ return rpc;
60
+ }
61
+ async fetch() {
62
+ await this.list.run(async () => {
63
+ const { accounts, grantable_roles } = await this.#require_rpc().list_accounts();
47
64
  this.accounts = accounts;
48
65
  this.grantable_roles = grantable_roles;
49
66
  });
@@ -59,40 +76,25 @@ export class AdminAccountsState extends Loadable {
59
76
  * across those calls.
60
77
  *
61
78
  * `to_actor_id` (optional) narrows the offer to a specific actor on
62
- * `account_id`; the in-flight `granting_keys` entry stays at
63
- * `account_id:role` for the account-grain default (so existing
64
- * consumers reading the 2-segment key keep working) and becomes
65
- * `account_id:role:to_actor_id` when actor-targeted, so the two
66
- * variants can be in flight without colliding on the per-row spinner.
67
- *
68
- * No-op when the rpc adapter is absent; `error` is set to a descriptive
69
- * message so the UI surfaces the misconfiguration.
79
+ * `account_id`; the keyed-slot key stays at `account_id:role` for the
80
+ * account-grain default (so existing consumers keep working) and
81
+ * becomes `account_id:role:to_actor_id` when actor-targeted, so the
82
+ * two variants can be in flight without colliding on the per-row
83
+ * spinner.
70
84
  */
71
- async create_role_grant(account_id, role, to_actor_id) {
72
- const rpc = this.#get_rpc();
73
- if (!rpc) {
74
- this.error = 'rpc adapter not wired';
75
- return undefined;
76
- }
77
- const key = to_actor_id ? `${account_id}:${role}:${to_actor_id}` : `${account_id}:${role}`;
78
- this.granting_keys.add(key);
79
- try {
80
- const { offer } = await rpc.create_role_grant({
85
+ async submit_grant(account_id, role, to_actor_id) {
86
+ const key = grant_key(account_id, role, to_actor_id);
87
+ const offer = await this.grant.run(key, async () => {
88
+ const result = await this.#require_rpc().create_role_grant({
81
89
  to_account_id: account_id,
82
90
  role,
83
91
  ...(to_actor_id ? { to_actor_id } : {}),
84
92
  });
85
- this.error = null;
93
+ return result.offer;
94
+ });
95
+ if (offer)
86
96
  await this.fetch();
87
- return offer;
88
- }
89
- catch (e) {
90
- this.error = e instanceof Error ? e.message : 'Failed to grant role_grant';
91
- return undefined;
92
- }
93
- finally {
94
- this.granting_keys.delete(key);
95
- }
97
+ return offer;
96
98
  }
97
99
  /**
98
100
  * Revoke an active role_grant via the `role_grant_revoke` RPC.
@@ -103,24 +105,16 @@ export class AdminAccountsState extends Loadable {
103
105
  * The optional `reason` is stamped on `role_grant.revoked_reason` and
104
106
  * surfaced on the revokee's WS notification.
105
107
  */
106
- async revoke_role_grant(actor_id, role_grant_id, reason) {
107
- const rpc = this.#get_rpc();
108
- if (!rpc) {
109
- this.error = 'rpc adapter not wired';
110
- return;
111
- }
112
- this.revoking_ids.add(role_grant_id);
113
- try {
114
- await rpc.revoke_role_grant({ actor_id, role_grant_id, reason: reason ?? null });
115
- this.error = null;
108
+ async submit_revoke(actor_id, role_grant_id, reason) {
109
+ await this.revoke.run(role_grant_id, async () => {
110
+ await this.#require_rpc().revoke_role_grant({
111
+ actor_id,
112
+ role_grant_id,
113
+ reason: reason ?? null,
114
+ });
115
+ });
116
+ if (this.revoke.succeeded(role_grant_id))
116
117
  await this.fetch();
117
- }
118
- catch (e) {
119
- this.error = e instanceof Error ? e.message : 'Failed to revoke role_grant';
120
- }
121
- finally {
122
- this.revoking_ids.delete(role_grant_id);
123
- }
124
118
  }
125
119
  /**
126
120
  * Retract a pending offer the admin issued via the `role_grant_offer_retract`
@@ -130,23 +124,11 @@ export class AdminAccountsState extends Loadable {
130
124
  * After success, refetches the listing so `pending_offers` drops the
131
125
  * row and the "+ {role}" button un-hides.
132
126
  */
133
- async retract_offer(offer_id) {
134
- const rpc = this.#get_rpc();
135
- if (!rpc) {
136
- this.error = 'rpc adapter not wired';
137
- return;
138
- }
139
- this.retracting_ids.add(offer_id);
140
- try {
141
- await rpc.retract_offer(offer_id);
142
- this.error = null;
127
+ async submit_retract(offer_id) {
128
+ await this.retract.run(offer_id, async () => {
129
+ await this.#require_rpc().retract_offer(offer_id);
130
+ });
131
+ if (this.retract.succeeded(offer_id))
143
132
  await this.fetch();
144
- }
145
- catch (e) {
146
- this.error = e instanceof Error ? e.message : 'Failed to retract offer';
147
- }
148
- finally {
149
- this.retracting_ids.delete(offer_id);
150
- }
151
133
  }
152
134
  }
@@ -5,11 +5,17 @@
5
5
  * class stays decoupled from the concrete RPC client so tests can inject
6
6
  * plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
7
7
  *
8
+ * Holds two `AsyncSlot`s — `list` (fetch) and `create` (singular write) —
9
+ * plus one `KeyedAsyncSlot<Uuid>` (`remove`) for the per-row delete with
10
+ * correct per-row supersession and per-row error surfacing. Method names
11
+ * use the `submit_*` prefix to avoid slot-name collisions (`delete` is
12
+ * reserved at top-level positions; renamed for symmetry).
13
+ *
8
14
  * @module
9
15
  */
10
- import { SvelteSet } from 'svelte/reactivity';
11
16
  import type { Uuid } from '@fuzdev/fuz_util/id.js';
12
- import { Loadable } from './loadable.svelte.js';
17
+ import { AsyncSlot } from './async_slot.svelte.js';
18
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
13
19
  import type { InviteWithUsernamesJson } from '../auth/invite_schema.js';
14
20
  import type { InviteCreateInput, InviteCreateOutput, InviteDeleteInput, InviteDeleteOutput, InviteListOutput } from '../auth/admin_action_specs.js';
15
21
  /**
@@ -39,18 +45,19 @@ export interface AdminInvitesStateOptions {
39
45
  */
40
46
  get_rpc?: () => AdminInvitesRpc | null;
41
47
  }
42
- export declare class AdminInvitesState extends Loadable {
48
+ export declare class AdminInvitesState {
43
49
  #private;
50
+ readonly list: AsyncSlot<void, string>;
51
+ readonly create: AsyncSlot<void, string>;
52
+ readonly remove: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
44
53
  invites: Array<InviteWithUsernamesJson>;
45
- creating: boolean;
46
- readonly deleting_ids: SvelteSet<string>;
47
54
  readonly invite_count: number;
48
55
  readonly unclaimed_count: number;
49
56
  constructor(options?: AdminInvitesStateOptions);
50
57
  /** True when an RPC adapter is wired. All ops require it. */
51
58
  get has_rpc(): boolean;
52
59
  fetch(): Promise<void>;
53
- create_invite(email?: string, username?: string): Promise<boolean>;
54
- delete_invite(id: Uuid): Promise<void>;
60
+ submit_create(email?: string, username?: string): Promise<boolean>;
61
+ submit_delete(id: Uuid): Promise<void>;
55
62
  }
56
63
  //# sourceMappingURL=admin_invites_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAE5C,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EACX,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB;qBAAwB,eAAe,GAAG,IAAI;yBAAtB,eAAe,GAAG,IAAI,wBAAtB,eAAe,GAAG,IAAI;CAEnF,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CACvC;AAED,qBAAa,iBAAkB,SAAQ,QAAQ;;IAG9C,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAkB;IACzD,QAAQ,UAAqB;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IAE3D,QAAQ,CAAC,YAAY,SAAiC;IACtD,QAAQ,CAAC,eAAe,SAA8D;gBAE1E,OAAO,CAAC,EAAE,wBAAwB;IAK9C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBlE,aAAa,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAgB5C"}
1
+ {"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EACX,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB;qBAAwB,eAAe,GAAG,IAAI;yBAAtB,eAAe,GAAG,IAAI,wBAAtB,eAAe,GAAG,IAAI;CAEnF,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CACvC;AAED,qBAAa,iBAAiB;;IAG7B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,MAAM,0BAAyB;IACxC,QAAQ,CAAC,MAAM,sEAAoC;IAEnD,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAkB;IAEzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAiC;IAC9D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAA8D;gBAElF,OAAO,CAAC,EAAE,wBAAwB;IAI9C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASlE,aAAa,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAM5C"}
@@ -5,79 +5,63 @@
5
5
  * class stays decoupled from the concrete RPC client so tests can inject
6
6
  * plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
7
7
  *
8
+ * Holds two `AsyncSlot`s — `list` (fetch) and `create` (singular write) —
9
+ * plus one `KeyedAsyncSlot<Uuid>` (`remove`) for the per-row delete with
10
+ * correct per-row supersession and per-row error surfacing. Method names
11
+ * use the `submit_*` prefix to avoid slot-name collisions (`delete` is
12
+ * reserved at top-level positions; renamed for symmetry).
13
+ *
8
14
  * @module
9
15
  */
10
- import { SvelteSet } from 'svelte/reactivity';
11
16
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
12
- import { Loadable } from './loadable.svelte.js';
17
+ import { AsyncSlot } from './async_slot.svelte.js';
18
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
13
19
  /**
14
20
  * Svelte context carrying the reactive `AdminInvitesRpc` accessor. Mirrors
15
21
  * `admin_accounts_rpc_context`. Unset context falls back to `() => null`.
16
22
  */
17
23
  export const admin_invites_rpc_context = create_context(() => () => null);
18
- export class AdminInvitesState extends Loadable {
24
+ export class AdminInvitesState {
19
25
  #get_rpc;
26
+ list = new AsyncSlot();
27
+ create = new AsyncSlot();
28
+ remove = new KeyedAsyncSlot();
20
29
  invites = $state.raw([]);
21
- creating = $state.raw(false);
22
- deleting_ids = new SvelteSet();
23
30
  invite_count = $derived(this.invites.length);
24
31
  unclaimed_count = $derived(this.invites.filter((i) => !i.claimed_at).length);
25
32
  constructor(options) {
26
- super();
27
33
  this.#get_rpc = options?.get_rpc ?? (() => null);
28
34
  }
29
35
  /** True when an RPC adapter is wired. All ops require it. */
30
36
  get has_rpc() {
31
37
  return this.#get_rpc() !== null;
32
38
  }
33
- async fetch() {
39
+ #require_rpc() {
34
40
  const rpc = this.#get_rpc();
35
- if (!rpc) {
36
- this.error = 'rpc adapter not wired';
37
- return;
38
- }
39
- await this.run(async () => {
40
- const { invites } = await rpc.list();
41
+ if (!rpc)
42
+ throw new Error('rpc adapter not wired');
43
+ return rpc;
44
+ }
45
+ async fetch() {
46
+ await this.list.run(async () => {
47
+ const { invites } = await this.#require_rpc().list();
41
48
  this.invites = invites;
42
49
  });
43
50
  }
44
- async create_invite(email, username) {
45
- const rpc = this.#get_rpc();
46
- if (!rpc) {
47
- this.error = 'rpc adapter not wired';
48
- return false;
49
- }
50
- this.creating = true;
51
- this.error = null;
52
- try {
53
- await rpc.create({ email: email ?? null, username: username ?? null });
54
- await this.fetch();
55
- return true;
56
- }
57
- catch (e) {
58
- this.error = e instanceof Error ? e.message : 'Failed to create invite';
51
+ async submit_create(email, username) {
52
+ await this.create.run(async () => {
53
+ await this.#require_rpc().create({ email: email ?? null, username: username ?? null });
54
+ });
55
+ if (!this.create.succeeded)
59
56
  return false;
60
- }
61
- finally {
62
- this.creating = false;
63
- }
57
+ await this.fetch();
58
+ return true;
64
59
  }
65
- async delete_invite(id) {
66
- const rpc = this.#get_rpc();
67
- if (!rpc) {
68
- this.error = 'rpc adapter not wired';
69
- return;
70
- }
71
- this.deleting_ids.add(id);
72
- try {
73
- await rpc.delete({ invite_id: id });
60
+ async submit_delete(id) {
61
+ await this.remove.run(id, async () => {
62
+ await this.#require_rpc().delete({ invite_id: id });
63
+ });
64
+ if (this.remove.succeeded(id))
74
65
  await this.fetch();
75
- }
76
- catch (e) {
77
- this.error = e instanceof Error ? e.message : 'Failed to delete invite';
78
- }
79
- finally {
80
- this.deleting_ids.delete(id);
81
- }
82
66
  }
83
67
  }
@@ -6,18 +6,24 @@
6
6
  * `token_revoke_all`); the listing wraps the `admin_session_list` RPC
7
7
  * method.
8
8
  *
9
+ * Holds one fetch `AsyncSlot` (`list`) plus two `KeyedAsyncSlot`s keyed by
10
+ * `account_id` — `revoke_sessions` and `revoke_tokens`. Per-account
11
+ * concurrent revokes are independent (clicking row B does not abort row A)
12
+ * and per-row errors surface via `revoke_sessions.error(account_id)` /
13
+ * `revoke_tokens.error(account_id)`.
14
+ *
9
15
  * @module
10
16
  */
11
- import { SvelteSet } from 'svelte/reactivity';
12
17
  import type { Uuid } from '@fuzdev/fuz_util/id.js';
13
- import { Loadable } from './loadable.svelte.js';
18
+ import { AsyncSlot } from './async_slot.svelte.js';
19
+ import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
14
20
  import type { AdminAccountsRpc } from './admin_accounts_state.svelte.js';
15
21
  import type { AdminSessionJson } from '../auth/audit_log_schema.js';
16
22
  /**
17
23
  * Options for `AdminSessionsState`.
18
24
  *
19
25
  * The RPC adapter drives every operation (listing + the two revoke-all
20
- * mutations). Without it, `fetch` and the revoke controls no-op with
26
+ * mutations). Without it, the slots' `run()` calls fail with
21
27
  * `'rpc adapter not wired'` on `error`.
22
28
  */
23
29
  export interface AdminSessionsStateOptions {
@@ -29,17 +35,18 @@ export interface AdminSessionsStateOptions {
29
35
  */
30
36
  get_rpc?: () => AdminAccountsRpc | null;
31
37
  }
32
- export declare class AdminSessionsState extends Loadable {
38
+ export declare class AdminSessionsState {
33
39
  #private;
40
+ readonly list: AsyncSlot<void, string>;
41
+ readonly revoke_sessions: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
42
+ readonly revoke_tokens: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
34
43
  sessions: Array<AdminSessionJson>;
35
- readonly revoking_account_ids: SvelteSet<string>;
36
- readonly revoking_token_account_ids: SvelteSet<string>;
37
44
  readonly active_count: number;
38
45
  constructor(options?: AdminSessionsStateOptions);
39
46
  /** True when an RPC adapter is wired. `fetch` and the revoke controls no-op without it. */
40
47
  get has_rpc(): boolean;
41
48
  fetch(): Promise<void>;
42
- revoke_all_for_account(account_id: Uuid): Promise<void>;
43
- revoke_all_tokens_for_account(account_id: Uuid): Promise<void>;
49
+ submit_revoke_sessions(account_id: Uuid): Promise<void>;
50
+ submit_revoke_tokens(account_id: Uuid): Promise<void>;
44
51
  }
45
52
  //# sourceMappingURL=admin_sessions_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,kBAAmB,SAAQ,QAAQ;;IAG/C,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAkB;IACnD,QAAQ,CAAC,oBAAoB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IACnE,QAAQ,CAAC,0BAA0B,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IAEzE,QAAQ,CAAC,YAAY,SAAkC;gBAE3C,OAAO,CAAC,EAAE,yBAAyB;IAK/C,2FAA2F;IAC3F,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,sBAAsB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,6BAA6B,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAiBpE"}
1
+ {"version":3,"file":"admin_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,kBAAkB;;IAG9B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,eAAe,sEAAoC;IAC5D,QAAQ,CAAC,aAAa,sEAAoC;IAE1D,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAkB;IAEnD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAkC;gBAEnD,OAAO,CAAC,EAAE,yBAAyB;IAI/C,2FAA2F;IAC3F,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,sBAAsB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,oBAAoB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAM3D"}