@fuzdev/fuz_app 0.29.0 → 0.31.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 (210) hide show
  1. package/dist/actions/CLAUDE.md +630 -0
  2. package/dist/actions/action_rpc.d.ts +29 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +42 -6
  5. package/dist/actions/action_types.d.ts +2 -2
  6. package/dist/actions/cancel.d.ts +12 -13
  7. package/dist/actions/cancel.d.ts.map +1 -1
  8. package/dist/actions/cancel.js +10 -13
  9. package/dist/actions/heartbeat.d.ts +8 -13
  10. package/dist/actions/heartbeat.d.ts.map +1 -1
  11. package/dist/actions/heartbeat.js +5 -8
  12. package/dist/actions/register_action_ws.d.ts +3 -3
  13. package/dist/actions/register_action_ws.js +2 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +4 -4
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +3 -3
  17. package/dist/actions/socket.svelte.d.ts +16 -16
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +15 -15
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/actions/transports_ws_backend.d.ts +15 -0
  22. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  23. package/dist/actions/transports_ws_backend.js +17 -0
  24. package/dist/auth/CLAUDE.md +923 -0
  25. package/dist/auth/account_action_specs.d.ts +216 -0
  26. package/dist/auth/account_action_specs.d.ts.map +1 -0
  27. package/dist/auth/account_action_specs.js +159 -0
  28. package/dist/auth/account_actions.d.ts +51 -0
  29. package/dist/auth/account_actions.d.ts.map +1 -0
  30. package/dist/auth/account_actions.js +119 -0
  31. package/dist/auth/account_queries.d.ts +6 -2
  32. package/dist/auth/account_queries.d.ts.map +1 -1
  33. package/dist/auth/account_queries.js +40 -4
  34. package/dist/auth/account_routes.d.ts +94 -16
  35. package/dist/auth/account_routes.d.ts.map +1 -1
  36. package/dist/auth/account_routes.js +108 -180
  37. package/dist/auth/account_schema.d.ts +85 -30
  38. package/dist/auth/account_schema.d.ts.map +1 -1
  39. package/dist/auth/account_schema.js +40 -8
  40. package/dist/auth/admin_action_specs.d.ts +674 -0
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -0
  42. package/dist/auth/admin_action_specs.js +287 -0
  43. package/dist/auth/admin_actions.d.ts +69 -0
  44. package/dist/auth/admin_actions.d.ts.map +1 -0
  45. package/dist/auth/admin_actions.js +256 -0
  46. package/dist/auth/api_token.d.ts +10 -0
  47. package/dist/auth/api_token.d.ts.map +1 -1
  48. package/dist/auth/api_token.js +9 -0
  49. package/dist/auth/api_token_queries.d.ts +3 -3
  50. package/dist/auth/api_token_queries.js +3 -3
  51. package/dist/auth/app_settings_schema.d.ts +4 -3
  52. package/dist/auth/app_settings_schema.d.ts.map +1 -1
  53. package/dist/auth/app_settings_schema.js +2 -1
  54. package/dist/auth/audit_log_routes.d.ts +14 -6
  55. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  56. package/dist/auth/audit_log_routes.js +22 -79
  57. package/dist/auth/audit_log_schema.d.ts +100 -29
  58. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  59. package/dist/auth/audit_log_schema.js +83 -11
  60. package/dist/auth/bootstrap_routes.d.ts +14 -0
  61. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_routes.js +10 -3
  63. package/dist/auth/cleanup.d.ts +63 -0
  64. package/dist/auth/cleanup.d.ts.map +1 -0
  65. package/dist/auth/cleanup.js +80 -0
  66. package/dist/auth/invite_schema.d.ts +11 -10
  67. package/dist/auth/invite_schema.d.ts.map +1 -1
  68. package/dist/auth/invite_schema.js +4 -3
  69. package/dist/auth/migrations.d.ts +6 -0
  70. package/dist/auth/migrations.d.ts.map +1 -1
  71. package/dist/auth/migrations.js +28 -0
  72. package/dist/auth/permit_offer_action_specs.d.ts +364 -0
  73. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
  74. package/dist/auth/permit_offer_action_specs.js +216 -0
  75. package/dist/auth/permit_offer_actions.d.ts +96 -0
  76. package/dist/auth/permit_offer_actions.d.ts.map +1 -0
  77. package/dist/auth/permit_offer_actions.js +428 -0
  78. package/dist/auth/permit_offer_notifications.d.ts +361 -0
  79. package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
  80. package/dist/auth/permit_offer_notifications.js +179 -0
  81. package/dist/auth/permit_offer_queries.d.ts +165 -0
  82. package/dist/auth/permit_offer_queries.d.ts.map +1 -0
  83. package/dist/auth/permit_offer_queries.js +390 -0
  84. package/dist/auth/permit_offer_schema.d.ts +103 -0
  85. package/dist/auth/permit_offer_schema.d.ts.map +1 -0
  86. package/dist/auth/permit_offer_schema.js +142 -0
  87. package/dist/auth/permit_queries.d.ts +77 -14
  88. package/dist/auth/permit_queries.d.ts.map +1 -1
  89. package/dist/auth/permit_queries.js +119 -24
  90. package/dist/auth/session_queries.d.ts +4 -2
  91. package/dist/auth/session_queries.d.ts.map +1 -1
  92. package/dist/auth/session_queries.js +4 -2
  93. package/dist/auth/signup_routes.d.ts +13 -0
  94. package/dist/auth/signup_routes.d.ts.map +1 -1
  95. package/dist/auth/signup_routes.js +14 -7
  96. package/dist/http/CLAUDE.md +584 -0
  97. package/dist/http/pending_effects.d.ts +29 -0
  98. package/dist/http/pending_effects.d.ts.map +1 -0
  99. package/dist/http/pending_effects.js +31 -0
  100. package/dist/http/route_spec.d.ts.map +1 -1
  101. package/dist/http/route_spec.js +4 -3
  102. package/dist/rate_limiter.d.ts +30 -0
  103. package/dist/rate_limiter.d.ts.map +1 -1
  104. package/dist/rate_limiter.js +25 -2
  105. package/dist/realtime/sse_auth_guard.d.ts +2 -0
  106. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  107. package/dist/realtime/sse_auth_guard.js +5 -3
  108. package/dist/testing/CLAUDE.md +668 -1
  109. package/dist/testing/admin_integration.d.ts +10 -7
  110. package/dist/testing/admin_integration.d.ts.map +1 -1
  111. package/dist/testing/admin_integration.js +382 -482
  112. package/dist/testing/app_server.d.ts +7 -6
  113. package/dist/testing/app_server.d.ts.map +1 -1
  114. package/dist/testing/attack_surface.d.ts +9 -3
  115. package/dist/testing/attack_surface.d.ts.map +1 -1
  116. package/dist/testing/attack_surface.js +4 -4
  117. package/dist/testing/audit_completeness.d.ts +6 -0
  118. package/dist/testing/audit_completeness.d.ts.map +1 -1
  119. package/dist/testing/audit_completeness.js +158 -134
  120. package/dist/testing/auth_apps.d.ts.map +1 -1
  121. package/dist/testing/auth_apps.js +4 -33
  122. package/dist/testing/db.d.ts +1 -1
  123. package/dist/testing/db.d.ts.map +1 -1
  124. package/dist/testing/db.js +2 -0
  125. package/dist/testing/entities.d.ts +35 -13
  126. package/dist/testing/entities.d.ts.map +1 -1
  127. package/dist/testing/entities.js +17 -0
  128. package/dist/testing/integration.d.ts +10 -0
  129. package/dist/testing/integration.d.ts.map +1 -1
  130. package/dist/testing/integration.js +352 -340
  131. package/dist/testing/integration_helpers.d.ts +16 -5
  132. package/dist/testing/integration_helpers.d.ts.map +1 -1
  133. package/dist/testing/integration_helpers.js +24 -4
  134. package/dist/testing/rate_limiting.d.ts +7 -0
  135. package/dist/testing/rate_limiting.d.ts.map +1 -1
  136. package/dist/testing/rate_limiting.js +41 -10
  137. package/dist/testing/rpc_helpers.d.ts +153 -1
  138. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  139. package/dist/testing/rpc_helpers.js +184 -8
  140. package/dist/testing/sse_round_trip.d.ts +8 -0
  141. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  142. package/dist/testing/sse_round_trip.js +10 -3
  143. package/dist/testing/standard.d.ts +9 -1
  144. package/dist/testing/standard.d.ts.map +1 -1
  145. package/dist/testing/standard.js +6 -2
  146. package/dist/testing/surface_invariants.d.ts +7 -3
  147. package/dist/testing/surface_invariants.d.ts.map +1 -1
  148. package/dist/testing/surface_invariants.js +5 -4
  149. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  150. package/dist/testing/ws_round_trip.js +9 -38
  151. package/dist/ui/AccountSessions.svelte +8 -4
  152. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  153. package/dist/ui/AdminAccounts.svelte +61 -33
  154. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  155. package/dist/ui/AdminAuditLog.svelte +3 -2
  156. package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
  157. package/dist/ui/AdminInvites.svelte +3 -2
  158. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  159. package/dist/ui/AdminOverview.svelte +14 -9
  160. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  161. package/dist/ui/AdminPermitHistory.svelte +3 -2
  162. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  163. package/dist/ui/AdminSessions.svelte +29 -25
  164. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  165. package/dist/ui/CLAUDE.md +351 -0
  166. package/dist/ui/OpenSignupToggle.svelte +6 -3
  167. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  168. package/dist/ui/PermitOfferForm.svelte +141 -0
  169. package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
  170. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
  171. package/dist/ui/PermitOfferHistory.svelte +109 -0
  172. package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
  173. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
  174. package/dist/ui/PermitOfferInbox.svelte +121 -0
  175. package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
  176. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
  177. package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
  178. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  179. package/dist/ui/account_sessions_state.svelte.js +39 -16
  180. package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
  181. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  182. package/dist/ui/admin_accounts_state.svelte.js +99 -23
  183. package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
  184. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  185. package/dist/ui/admin_invites_state.svelte.js +38 -26
  186. package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
  187. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  188. package/dist/ui/admin_sessions_state.svelte.js +35 -21
  189. package/dist/ui/app_settings_state.svelte.d.ts +39 -0
  190. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  191. package/dist/ui/app_settings_state.svelte.js +34 -18
  192. package/dist/ui/audit_log_state.svelte.d.ts +40 -3
  193. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  194. package/dist/ui/audit_log_state.svelte.js +36 -42
  195. package/dist/ui/auth_state.svelte.d.ts +4 -3
  196. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  197. package/dist/ui/auth_state.svelte.js +4 -1
  198. package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
  199. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
  200. package/dist/ui/permit_offers_state.svelte.js +197 -0
  201. package/package.json +3 -3
  202. package/dist/auth/admin_routes.d.ts +0 -29
  203. package/dist/auth/admin_routes.d.ts.map +0 -1
  204. package/dist/auth/admin_routes.js +0 -226
  205. package/dist/auth/app_settings_routes.d.ts +0 -27
  206. package/dist/auth/app_settings_routes.d.ts.map +0 -1
  207. package/dist/auth/app_settings_routes.js +0 -66
  208. package/dist/auth/invite_routes.d.ts +0 -18
  209. package/dist/auth/invite_routes.d.ts.map +0 -1
  210. package/dist/auth/invite_routes.js +0 -129
@@ -0,0 +1,121 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Recipient-side pending offer inbox.
4
+ *
5
+ * Renders `PermitOffersState.incoming` (pending, soonest-expiry first)
6
+ * with accept + decline-with-reason controls. Grantor and scope rendering
7
+ * are delegated via optional callback props — consumers plug in display
8
+ * names once they know what a `from_actor_id` / `scope_id` represents
9
+ * in their domain (usernames, classroom names, etc.).
10
+ */
11
+
12
+ import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
13
+ import {SvelteMap} from 'svelte/reactivity';
14
+
15
+ import {permit_offers_state_context} from './permit_offers_state.svelte.js';
16
+ import ConfirmButton from './ConfirmButton.svelte';
17
+ import {format_relative_time, format_datetime_local, truncate_uuid} from './ui_format.js';
18
+ import {PERMIT_OFFER_MESSAGE_LENGTH_MAX} from '../auth/permit_offer_schema.js';
19
+
20
+ const {
21
+ format_actor = truncate_uuid,
22
+ format_scope,
23
+ format_role = (role: string) => role,
24
+ }: {
25
+ /** Display label for `from_actor_id`. Defaults to a truncated uuid. */
26
+ format_actor?: (from_actor_id: string) => string;
27
+ /** Display label for an offer's scope. Defaults to truncated uuid or "global" when `null`. */
28
+ format_scope?: (scope_id: string | null, role: string) => string;
29
+ /** Display label for a role constant. Defaults to identity (role name as stored). */
30
+ format_role?: (role: string) => string;
31
+ } = $props();
32
+
33
+ const permit_offers = permit_offers_state_context.get();
34
+
35
+ const scope_label = (scope_id: string | null, role: string): string => {
36
+ if (format_scope) return format_scope(scope_id, role);
37
+ return scope_id === null ? 'global' : truncate_uuid(scope_id);
38
+ };
39
+
40
+ const decline_reasons: SvelteMap<string, string> = new SvelteMap();
41
+ </script>
42
+
43
+ <section class="permit-offer-inbox">
44
+ <h2>pending offers</h2>
45
+
46
+ {#if permit_offers.error}
47
+ <p class="color_c_50">{permit_offers.error}</p>
48
+ {/if}
49
+
50
+ {#if permit_offers.incoming.length === 0}
51
+ <p class="text_50">No pending offers.</p>
52
+ {:else}
53
+ <ul class="column gap_md">
54
+ {#each permit_offers.incoming as offer (offer.id)}
55
+ <li class="box p_md column gap_sm">
56
+ <div class="row gap_sm align_center">
57
+ <span class="chip color_a">{format_role(offer.role)}</span>
58
+ <span class="text_50 font_size_sm">{scope_label(offer.scope_id, offer.role)}</span>
59
+ <span class="text_50 font_size_sm">from {format_actor(offer.from_actor_id)}</span>
60
+ <span
61
+ class="text_50 font_size_sm ml_auto"
62
+ title={format_datetime_local(offer.expires_at)}
63
+ >
64
+ expires {format_relative_time(offer.expires_at)}
65
+ </span>
66
+ </div>
67
+
68
+ {#if offer.message}
69
+ <p class="mb_0">{offer.message}</p>
70
+ {/if}
71
+
72
+ <div class="row gap_sm">
73
+ <PendingButton
74
+ pending={permit_offers.loading}
75
+ disabled={permit_offers.loading}
76
+ onclick={() => permit_offers.accept(offer.id)}
77
+ class="color_b"
78
+ >
79
+ accept
80
+ </PendingButton>
81
+
82
+ <ConfirmButton
83
+ title="decline offer"
84
+ position="bottom"
85
+ onconfirm={() => {
86
+ const reason = decline_reasons.get(offer.id) ?? '';
87
+ void permit_offers.decline(offer.id, reason || null);
88
+ decline_reasons.delete(offer.id);
89
+ }}
90
+ >
91
+ {#snippet children(_popover, _confirm)}
92
+ decline
93
+ {/snippet}
94
+ {#snippet popover_content(popover, confirm)}
95
+ <div class="column gap_sm p_sm">
96
+ <label>
97
+ <div class="title">reason (optional)</div>
98
+ <textarea
99
+ name="decline-reason"
100
+ maxlength={PERMIT_OFFER_MESSAGE_LENGTH_MAX}
101
+ placeholder="optional reason"
102
+ value={decline_reasons.get(offer.id) ?? ''}
103
+ oninput={(e) =>
104
+ decline_reasons.set(offer.id, (e.target as HTMLTextAreaElement).value)}
105
+ ></textarea>
106
+ </label>
107
+ <div class="row gap_sm">
108
+ <button type="button" class="color_c bg_100" onclick={confirm}>
109
+ confirm decline
110
+ </button>
111
+ <button type="button" onclick={() => popover.hide()}>cancel</button>
112
+ </div>
113
+ </div>
114
+ {/snippet}
115
+ </ConfirmButton>
116
+ </div>
117
+ </li>
118
+ {/each}
119
+ </ul>
120
+ {/if}
121
+ </section>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ /** Display label for `from_actor_id`. Defaults to a truncated uuid. */
3
+ format_actor?: (from_actor_id: string) => string;
4
+ /** Display label for an offer's scope. Defaults to truncated uuid or "global" when `null`. */
5
+ format_scope?: (scope_id: string | null, role: string) => string;
6
+ /** Display label for a role constant. Defaults to identity (role name as stored). */
7
+ format_role?: (role: string) => string;
8
+ };
9
+ declare const PermitOfferInbox: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type PermitOfferInbox = ReturnType<typeof PermitOfferInbox>;
11
+ export default PermitOfferInbox;
12
+ //# sourceMappingURL=PermitOfferInbox.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermitOfferInbox.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/PermitOfferInbox.svelte"],"names":[],"mappings":"AAoBC,KAAK,gBAAgB,GAAI;IACxB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE,qFAAqF;IACrF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AA0FH,QAAA,MAAM,gBAAgB,sDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -1,13 +1,63 @@
1
1
  /**
2
- * Reactive state for managing auth sessions on a settings page.
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; the
4
+ * REST routes that backed this class moved to `account_actions.ts` in the
5
+ * 2026-04-23 RPC migration.
3
6
  *
4
7
  * @module
5
8
  */
6
9
  import { Loadable } from './loadable.svelte.js';
7
- import type { AuthSession } from '../auth/account_schema.js';
10
+ import type { AuthSessionJson } from '../auth/account_schema.js';
11
+ /**
12
+ * Narrow RPC surface consumed by `AccountSessionsState`. Consumers adapt their
13
+ * typed RPC client to this shape. Mirrors the other per-domain `*Rpc`
14
+ * interfaces (`AdminAccountsRpc`, `AuditLogRpc`, `AdminInvitesRpc`).
15
+ *
16
+ * The three methods wrap the corresponding action specs on
17
+ * `account_actions.ts`:
18
+ *
19
+ * - `list` → `account_session_list`
20
+ * - `revoke` → `account_session_revoke` (IDOR-guarded by `account_id` server-side)
21
+ * - `revoke_all` → `account_session_revoke_all`
22
+ */
23
+ export interface AccountSessionsRpc {
24
+ list: () => Promise<{
25
+ sessions: Array<AuthSessionJson>;
26
+ }>;
27
+ revoke: (params: {
28
+ session_id: string;
29
+ }) => Promise<{
30
+ ok: true;
31
+ revoked: boolean;
32
+ }>;
33
+ revoke_all: () => Promise<{
34
+ ok: true;
35
+ count: number;
36
+ }>;
37
+ }
38
+ /**
39
+ * Svelte context carrying the reactive `AccountSessionsRpc` accessor. Mirrors
40
+ * the admin-side RPC contexts. Unset context falls back to `() => null` so
41
+ * components render the usual "rpc adapter not wired" state.
42
+ */
43
+ export declare const account_sessions_rpc_context: {
44
+ get: () => () => AccountSessionsRpc | null;
45
+ set: (value?: (() => AccountSessionsRpc | null) | undefined) => () => AccountSessionsRpc | null;
46
+ };
47
+ export interface AccountSessionsStateOptions {
48
+ /**
49
+ * Reactive accessor for the RPC adapter; returns `null` when unwired.
50
+ * Matches the `get_rpc` pattern on the admin state classes.
51
+ */
52
+ get_rpc?: () => AccountSessionsRpc | null;
53
+ }
8
54
  export declare class AccountSessionsState extends Loadable {
9
- sessions: Array<AuthSession>;
55
+ #private;
56
+ sessions: Array<AuthSessionJson>;
10
57
  readonly active_count: number;
58
+ constructor(options?: AccountSessionsStateOptions);
59
+ /** True when an RPC adapter is wired. `fetch` / `revoke` / `revoke_all` no-op without it. */
60
+ get has_rpc(): boolean;
11
61
  fetch(): Promise<void>;
12
62
  revoke(id: string): Promise<void>;
13
63
  revoke_all(): Promise<void>;
@@ -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;;;;GAIG;AAEH,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAE9C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,2BAA2B,CAAC;AAE3D,qBAAa,oBAAqB,SAAQ,QAAQ;IACjD,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,CAAkB;IAE9C,QAAQ,CAAC,YAAY,SAAkC;IAEjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAWtB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAajC"}
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;;;;;;;GAOG;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,40 +1,63 @@
1
1
  /**
2
- * Reactive state for managing auth sessions on a settings page.
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; the
4
+ * REST routes that backed this class moved to `account_actions.ts` in the
5
+ * 2026-04-23 RPC migration.
3
6
  *
4
7
  * @module
5
8
  */
9
+ import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
6
10
  import { Loadable } from './loadable.svelte.js';
7
- import { parse_response_error, ui_fetch } from './ui_fetch.js';
11
+ /**
12
+ * Svelte context carrying the reactive `AccountSessionsRpc` accessor. Mirrors
13
+ * the admin-side RPC contexts. Unset context falls back to `() => null` so
14
+ * components render the usual "rpc adapter not wired" state.
15
+ */
16
+ export const account_sessions_rpc_context = create_context(() => () => null);
8
17
  export class AccountSessionsState extends Loadable {
18
+ #get_rpc;
9
19
  sessions = $state.raw([]);
10
20
  active_count = $derived(this.sessions.length);
21
+ constructor(options) {
22
+ super();
23
+ this.#get_rpc = options?.get_rpc ?? (() => null);
24
+ }
25
+ /** True when an RPC adapter is wired. `fetch` / `revoke` / `revoke_all` no-op without it. */
26
+ get has_rpc() {
27
+ return this.#get_rpc() !== null;
28
+ }
11
29
  async fetch() {
30
+ const rpc = this.#get_rpc();
31
+ if (!rpc) {
32
+ this.error = 'rpc adapter not wired';
33
+ return;
34
+ }
12
35
  await this.run(async () => {
13
- const response = await ui_fetch('/api/account/sessions');
14
- if (!response.ok) {
15
- throw new Error(await parse_response_error(response, 'Failed to fetch sessions'));
16
- }
17
- const data = await response.json();
18
- this.sessions = data.sessions ?? [];
36
+ const { sessions } = await rpc.list();
37
+ this.sessions = sessions;
19
38
  });
20
39
  }
21
40
  async revoke(id) {
41
+ const rpc = this.#get_rpc();
42
+ if (!rpc) {
43
+ this.error = 'rpc adapter not wired';
44
+ return;
45
+ }
22
46
  await this.run(async () => {
23
- const response = await ui_fetch(`/api/account/sessions/${id}/revoke`, { method: 'POST' });
24
- if (!response.ok) {
25
- throw new Error(await parse_response_error(response, 'Failed to revoke session'));
26
- }
47
+ await rpc.revoke({ session_id: id });
27
48
  });
28
49
  if (!this.error) {
29
50
  await this.fetch();
30
51
  }
31
52
  }
32
53
  async revoke_all() {
54
+ const rpc = this.#get_rpc();
55
+ if (!rpc) {
56
+ this.error = 'rpc adapter not wired';
57
+ return;
58
+ }
33
59
  await this.run(async () => {
34
- const response = await ui_fetch('/api/account/sessions/revoke-all', { method: 'POST' });
35
- if (!response.ok) {
36
- throw new Error(await parse_response_error(response, 'Failed to revoke sessions'));
37
- }
60
+ await rpc.revoke_all();
38
61
  });
39
62
  if (!this.error) {
40
63
  // Current session is now revoked — next API call will 401.
@@ -6,14 +6,130 @@
6
6
  import { SvelteSet } from 'svelte/reactivity';
7
7
  import { Loadable } from './loadable.svelte.js';
8
8
  import type { AdminAccountEntryJson } from '../auth/account_schema.js';
9
+ import type { AdminSessionJson } from '../auth/audit_log_schema.js';
10
+ import type { PermitOfferJson } from '../auth/permit_offer_schema.js';
11
+ /**
12
+ * Narrow RPC surface consumed by `AdminAccountsState`. Consumers adapt their
13
+ * typed RPC client (e.g. a `create_rpc_client` Proxy) to this shape — the
14
+ * state class stays decoupled from the client's `Result` return type so
15
+ * tests can inject plain-function stubs. Mirrors the `PermitOffersRpc`
16
+ * pattern.
17
+ *
18
+ * Every operation flows through RPC: the listing reuses `admin_account_list`,
19
+ * grant reuses `permit_offer_create`, revoke and retract have dedicated
20
+ * actions, and the session / token revoke-all mutations reuse
21
+ * `admin_session_revoke_all` and `admin_token_revoke_all`. Without the
22
+ * adapter the state class cannot fetch, grant, revoke, retract, or
23
+ * revoke-all sessions/tokens.
24
+ */
25
+ export interface AdminAccountsRpc {
26
+ list_accounts: () => Promise<{
27
+ accounts: Array<AdminAccountEntryJson>;
28
+ grantable_roles: Array<string>;
29
+ }>;
30
+ list_sessions: () => Promise<{
31
+ sessions: Array<AdminSessionJson>;
32
+ }>;
33
+ grant_permit: (params: {
34
+ to_account_id: string;
35
+ role: string;
36
+ }) => Promise<{
37
+ offer: PermitOfferJson;
38
+ }>;
39
+ revoke_permit: (params: {
40
+ actor_id: string;
41
+ permit_id: string;
42
+ reason?: string | null;
43
+ }) => Promise<{
44
+ ok: true;
45
+ revoked: true;
46
+ }>;
47
+ retract_offer: (offer_id: string) => Promise<{
48
+ ok: true;
49
+ }>;
50
+ session_revoke_all: (params: {
51
+ account_id: string;
52
+ }) => Promise<{
53
+ ok: true;
54
+ count: number;
55
+ }>;
56
+ token_revoke_all: (params: {
57
+ account_id: string;
58
+ }) => Promise<{
59
+ ok: true;
60
+ count: number;
61
+ }>;
62
+ }
63
+ /**
64
+ * Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
65
+ * provisioner (typically the admin route shell) calls `set(() => rpc)`;
66
+ * consumers read with `const get_rpc = admin_accounts_rpc_context.get();`
67
+ * and either pass the accessor straight to `AdminAccountsState`/
68
+ * `AdminSessionsState` or wrap it with `const rpc = $derived(get_rpc());`
69
+ * for direct RPC calls. Unset context falls back to `() => null` so
70
+ * components mounted without a provisioner surface the usual "rpc adapter
71
+ * not wired" path.
72
+ */
73
+ export declare const admin_accounts_rpc_context: {
74
+ get: () => () => AdminAccountsRpc | null;
75
+ set: (value?: (() => AdminAccountsRpc | null) | undefined) => () => AdminAccountsRpc | null;
76
+ };
77
+ export interface AdminAccountsStateOptions {
78
+ /**
79
+ * Reactive accessor for the RPC adapter; returns `null` when unwired.
80
+ * Matches `PermitOffersStateOptions.account_id` / `actor_id` pattern —
81
+ * lets the component pass a `$props()`-sourced rpc without tripping
82
+ * Svelte's `state_referenced_locally` warning.
83
+ */
84
+ get_rpc?: () => AdminAccountsRpc | null;
85
+ }
9
86
  export declare class AdminAccountsState extends Loadable {
87
+ #private;
10
88
  accounts: Array<AdminAccountEntryJson>;
11
89
  grantable_roles: Array<string>;
12
90
  readonly granting_keys: SvelteSet<string>;
13
91
  readonly revoking_ids: SvelteSet<string>;
92
+ readonly retracting_ids: SvelteSet<string>;
14
93
  readonly account_count: number;
94
+ constructor(options?: AdminAccountsStateOptions);
95
+ /**
96
+ * True when an RPC adapter is wired. UI uses this to gate all controls
97
+ * — fetch, grant, revoke, retract all flow through the same adapter.
98
+ */
99
+ get has_rpc(): boolean;
15
100
  fetch(): Promise<void>;
16
- grant_permit(account_id: string, role: string): Promise<void>;
17
- revoke_permit(account_id: string, permit_id: string): Promise<void>;
101
+ /**
102
+ * Offer the role to the recipient via the `permit_offer_create` RPC.
103
+ * Server returns the pending offer; the recipient must accept before
104
+ * the permit materializes. Returns the offer payload on success so
105
+ * callers can drive follow-up UX (e.g. seed `PermitOffersState.outgoing`).
106
+ *
107
+ * A re-offer from the same admin to the same `(account, role)`
108
+ * refreshes the existing pending row — the returned offer id is stable
109
+ * across those calls.
110
+ *
111
+ * No-op when the rpc adapter is absent; `error` is set to a descriptive
112
+ * message so the UI surfaces the misconfiguration.
113
+ */
114
+ grant_permit(account_id: string, role: string): Promise<PermitOfferJson | undefined>;
115
+ /**
116
+ * Revoke an active permit via the `permit_revoke` RPC.
117
+ *
118
+ * `actor_id` is the natural key — permits are actor-scoped, and the
119
+ * admin UI reads `row.actor.id` straight from the listing, so the state
120
+ * class takes it directly rather than deriving it from `account_id`.
121
+ * The optional `reason` is stamped on `permit.revoked_reason` and
122
+ * surfaced on the revokee's WS notification.
123
+ */
124
+ revoke_permit(actor_id: string, permit_id: string, reason?: string | null): Promise<void>;
125
+ /**
126
+ * Retract a pending offer the admin issued via the `permit_offer_retract`
127
+ * RPC. The action handles auth, audit, and the
128
+ * `permit_offer_retracted` WS notification.
129
+ *
130
+ * After success, refetches the listing so `pending_offers` drops the
131
+ * row and the "+ {role}" button un-hides.
132
+ */
133
+ retract_offer(offer_id: string): Promise<void>;
18
134
  }
19
135
  //# 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":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAE9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AAErE,qBAAa,kBAAmB,SAAQ,QAAQ;IAC/C,QAAQ,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAkB;IACxD,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAkB;IAChD,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IAC5D,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IAE3D,QAAQ,CAAC,aAAa,SAAkC;IAElD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB7D,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkBzE"}
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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"}
@@ -4,55 +4,104 @@
4
4
  * @module
5
5
  */
6
6
  import { SvelteSet } from 'svelte/reactivity';
7
+ import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
7
8
  import { Loadable } from './loadable.svelte.js';
8
- import { parse_response_error, ui_fetch } from './ui_fetch.js';
9
+ /**
10
+ * Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
11
+ * provisioner (typically the admin route shell) calls `set(() => rpc)`;
12
+ * consumers read with `const get_rpc = admin_accounts_rpc_context.get();`
13
+ * and either pass the accessor straight to `AdminAccountsState`/
14
+ * `AdminSessionsState` or wrap it with `const rpc = $derived(get_rpc());`
15
+ * for direct RPC calls. Unset context falls back to `() => null` so
16
+ * components mounted without a provisioner surface the usual "rpc adapter
17
+ * not wired" path.
18
+ */
19
+ export const admin_accounts_rpc_context = create_context(() => () => null);
9
20
  export class AdminAccountsState extends Loadable {
21
+ #get_rpc;
10
22
  accounts = $state.raw([]);
11
23
  grantable_roles = $state.raw([]);
12
24
  granting_keys = new SvelteSet();
13
25
  revoking_ids = new SvelteSet();
26
+ retracting_ids = new SvelteSet();
14
27
  account_count = $derived(this.accounts.length);
28
+ constructor(options) {
29
+ super();
30
+ this.#get_rpc = options?.get_rpc ?? (() => null);
31
+ }
32
+ /**
33
+ * True when an RPC adapter is wired. UI uses this to gate all controls
34
+ * — fetch, grant, revoke, retract all flow through the same adapter.
35
+ */
36
+ get has_rpc() {
37
+ return this.#get_rpc() !== null;
38
+ }
15
39
  async fetch() {
40
+ const rpc = this.#get_rpc();
41
+ if (!rpc) {
42
+ this.error = 'rpc adapter not wired';
43
+ return;
44
+ }
16
45
  await this.run(async () => {
17
- const response = await ui_fetch('/api/admin/accounts');
18
- if (!response.ok) {
19
- throw new Error(await parse_response_error(response, 'Failed to fetch accounts'));
20
- }
21
- const data = await response.json();
22
- this.accounts = data.accounts ?? [];
23
- this.grantable_roles = data.grantable_roles ?? [];
46
+ const { accounts, grantable_roles } = await rpc.list_accounts();
47
+ this.accounts = accounts;
48
+ this.grantable_roles = grantable_roles;
24
49
  });
25
50
  }
51
+ /**
52
+ * Offer the role to the recipient via the `permit_offer_create` RPC.
53
+ * Server returns the pending offer; the recipient must accept before
54
+ * the permit materializes. Returns the offer payload on success so
55
+ * callers can drive follow-up UX (e.g. seed `PermitOffersState.outgoing`).
56
+ *
57
+ * A re-offer from the same admin to the same `(account, role)`
58
+ * refreshes the existing pending row — the returned offer id is stable
59
+ * across those calls.
60
+ *
61
+ * No-op when the rpc adapter is absent; `error` is set to a descriptive
62
+ * message so the UI surfaces the misconfiguration.
63
+ */
26
64
  async grant_permit(account_id, role) {
65
+ const rpc = this.#get_rpc();
66
+ if (!rpc) {
67
+ this.error = 'rpc adapter not wired';
68
+ return undefined;
69
+ }
27
70
  const key = `${account_id}:${role}`;
28
71
  this.granting_keys.add(key);
29
72
  try {
30
- const response = await ui_fetch(`/api/admin/accounts/${account_id}/permits/grant`, {
31
- method: 'POST',
32
- headers: { 'Content-Type': 'application/json' },
33
- body: JSON.stringify({ role }),
34
- });
35
- if (!response.ok) {
36
- this.error = await parse_response_error(response);
37
- return;
38
- }
73
+ const { offer } = await rpc.grant_permit({ to_account_id: account_id, role });
74
+ this.error = null;
39
75
  await this.fetch();
76
+ return offer;
40
77
  }
41
78
  catch (e) {
42
79
  this.error = e instanceof Error ? e.message : 'Failed to grant permit';
80
+ return undefined;
43
81
  }
44
82
  finally {
45
83
  this.granting_keys.delete(key);
46
84
  }
47
85
  }
48
- async revoke_permit(account_id, permit_id) {
86
+ /**
87
+ * Revoke an active permit via the `permit_revoke` RPC.
88
+ *
89
+ * `actor_id` is the natural key — permits are actor-scoped, and the
90
+ * admin UI reads `row.actor.id` straight from the listing, so the state
91
+ * class takes it directly rather than deriving it from `account_id`.
92
+ * The optional `reason` is stamped on `permit.revoked_reason` and
93
+ * surfaced on the revokee's WS notification.
94
+ */
95
+ async revoke_permit(actor_id, permit_id, reason) {
96
+ const rpc = this.#get_rpc();
97
+ if (!rpc) {
98
+ this.error = 'rpc adapter not wired';
99
+ return;
100
+ }
49
101
  this.revoking_ids.add(permit_id);
50
102
  try {
51
- const response = await ui_fetch(`/api/admin/accounts/${account_id}/permits/${permit_id}/revoke`, { method: 'POST' });
52
- if (!response.ok) {
53
- this.error = await parse_response_error(response);
54
- return;
55
- }
103
+ await rpc.revoke_permit({ actor_id, permit_id, reason: reason ?? null });
104
+ this.error = null;
56
105
  await this.fetch();
57
106
  }
58
107
  catch (e) {
@@ -62,4 +111,31 @@ export class AdminAccountsState extends Loadable {
62
111
  this.revoking_ids.delete(permit_id);
63
112
  }
64
113
  }
114
+ /**
115
+ * Retract a pending offer the admin issued via the `permit_offer_retract`
116
+ * RPC. The action handles auth, audit, and the
117
+ * `permit_offer_retracted` WS notification.
118
+ *
119
+ * After success, refetches the listing so `pending_offers` drops the
120
+ * row and the "+ {role}" button un-hides.
121
+ */
122
+ async retract_offer(offer_id) {
123
+ const rpc = this.#get_rpc();
124
+ if (!rpc) {
125
+ this.error = 'rpc adapter not wired';
126
+ return;
127
+ }
128
+ this.retracting_ids.add(offer_id);
129
+ try {
130
+ await rpc.retract_offer(offer_id);
131
+ this.error = null;
132
+ await this.fetch();
133
+ }
134
+ catch (e) {
135
+ this.error = e instanceof Error ? e.message : 'Failed to retract offer';
136
+ }
137
+ finally {
138
+ this.retracting_ids.delete(offer_id);
139
+ }
140
+ }
65
141
  }
@@ -1,17 +1,63 @@
1
1
  /**
2
2
  * Reactive state for admin invite management.
3
3
  *
4
+ * Flows every operation through an injected `AdminInvitesRpc` adapter — the
5
+ * class stays decoupled from the concrete RPC client so tests can inject
6
+ * plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
7
+ *
4
8
  * @module
5
9
  */
6
10
  import { SvelteSet } from 'svelte/reactivity';
7
11
  import { Loadable } from './loadable.svelte.js';
8
- import type { InviteWithUsernamesJson } from '../auth/invite_schema.js';
12
+ import type { InviteJson, InviteWithUsernamesJson } from '../auth/invite_schema.js';
13
+ /**
14
+ * Narrow RPC surface consumed by `AdminInvitesState`. Consumers adapt their
15
+ * typed RPC client to this shape. `error.data.reason` on thrown errors
16
+ * carries the `ERROR_INVITE_*` constant — handled by the caller when
17
+ * user-friendly messages are needed.
18
+ */
19
+ export interface AdminInvitesRpc {
20
+ list: () => Promise<{
21
+ invites: Array<InviteWithUsernamesJson>;
22
+ }>;
23
+ create: (params: {
24
+ email?: string | null;
25
+ username?: string | null;
26
+ }) => Promise<{
27
+ ok: true;
28
+ invite: InviteJson;
29
+ }>;
30
+ delete: (params: {
31
+ invite_id: string;
32
+ }) => Promise<{
33
+ ok: true;
34
+ }>;
35
+ }
36
+ /**
37
+ * Svelte context carrying the reactive `AdminInvitesRpc` accessor. Mirrors
38
+ * `admin_accounts_rpc_context`. Unset context falls back to `() => null`.
39
+ */
40
+ export declare const admin_invites_rpc_context: {
41
+ get: () => () => AdminInvitesRpc | null;
42
+ set: (value?: (() => AdminInvitesRpc | null) | undefined) => () => AdminInvitesRpc | null;
43
+ };
44
+ export interface AdminInvitesStateOptions {
45
+ /**
46
+ * Reactive accessor for the RPC adapter. `null` disables all operations
47
+ * (the state reports a descriptive error when mutations/fetches fire).
48
+ */
49
+ get_rpc?: () => AdminInvitesRpc | null;
50
+ }
9
51
  export declare class AdminInvitesState extends Loadable {
52
+ #private;
10
53
  invites: Array<InviteWithUsernamesJson>;
11
54
  creating: boolean;
12
55
  readonly deleting_ids: SvelteSet<string>;
13
56
  readonly invite_count: number;
14
57
  readonly unclaimed_count: number;
58
+ constructor(options?: AdminInvitesStateOptions);
59
+ /** True when an RPC adapter is wired. All ops require it. */
60
+ get has_rpc(): boolean;
15
61
  fetch(): Promise<void>;
16
62
  create_invite(email?: string, username?: string): Promise<boolean>;
17
63
  delete_invite(id: string): Promise<void>;
@@ -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;;;;GAIG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAE9C,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AAEtE,qBAAa,iBAAkB,SAAQ,QAAQ;IAC9C,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;IAEhF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAWtB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BlE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAe9C"}
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;AAG5C,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,UAAU,EAAE,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AAElF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAA;KAAC,CAAC,CAAC;IAC/D,MAAM,EAAE,CAAC,MAAM,EAAE;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,MAAM,EAAE;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC,CAAC;CAC7D;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,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgB9C"}