@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.
- package/dist/actions/CLAUDE.md +5 -5
- package/dist/actions/action_codegen.d.ts +1 -1
- package/dist/actions/action_codegen.js +2 -2
- package/dist/actions/action_event_helpers.d.ts +3 -3
- package/dist/actions/action_event_helpers.js +8 -8
- package/dist/actions/action_event_types.d.ts +3 -3
- package/dist/actions/action_event_types.js +3 -3
- package/dist/actions/transports_ws_auth_guard.d.ts +2 -2
- package/dist/actions/transports_ws_auth_guard.js +3 -3
- package/dist/auth/CLAUDE.md +157 -15
- package/dist/auth/actor_lookup_action_specs.d.ts +127 -0
- package/dist/auth/actor_lookup_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_lookup_action_specs.js +93 -0
- package/dist/auth/actor_lookup_actions.d.ts +19 -0
- package/dist/auth/actor_lookup_actions.d.ts.map +1 -0
- package/dist/auth/actor_lookup_actions.js +32 -0
- package/dist/auth/actor_lookup_queries.d.ts +44 -0
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -0
- package/dist/auth/actor_lookup_queries.js +42 -0
- package/dist/auth/actor_search_action_specs.d.ts +166 -0
- package/dist/auth/actor_search_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_search_action_specs.js +139 -0
- package/dist/auth/actor_search_actions.d.ts +31 -0
- package/dist/auth/actor_search_actions.d.ts.map +1 -0
- package/dist/auth/actor_search_actions.js +61 -0
- package/dist/auth/actor_search_queries.d.ts +75 -0
- package/dist/auth/actor_search_queries.d.ts.map +1 -0
- package/dist/auth/actor_search_queries.js +91 -0
- package/dist/auth/admin_actions.js +2 -2
- package/dist/auth/all_action_spec_registries.d.ts +55 -0
- package/dist/auth/all_action_spec_registries.d.ts.map +1 -0
- package/dist/auth/all_action_spec_registries.js +59 -0
- package/dist/auth/audit_emitter.d.ts +1 -1
- package/dist/auth/audit_emitter.js +2 -2
- package/dist/auth/audit_log_queries.d.ts +1 -1
- package/dist/auth/audit_log_queries.js +3 -3
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -5
- package/dist/auth/audit_log_schema.js +7 -7
- package/dist/auth/auth_ddl.d.ts +7 -0
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +8 -0
- package/dist/auth/credential_type_schema.d.ts +1 -1
- package/dist/auth/credential_type_schema.js +3 -3
- package/dist/auth/grant_path_schema.d.ts +1 -1
- package/dist/auth/grant_path_schema.js +3 -3
- package/dist/auth/migrations.d.ts +4 -4
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +7 -6
- package/dist/auth/role_grant_offer_actions.js +2 -2
- package/dist/auth/role_grant_offer_notifications.d.ts +2 -2
- package/dist/auth/role_grant_offer_notifications.js +2 -2
- package/dist/auth/role_grant_queries.d.ts +21 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +31 -0
- package/dist/auth/role_schema.d.ts +2 -2
- package/dist/auth/role_schema.js +3 -3
- package/dist/auth/self_service_role_actions.d.ts +1 -1
- package/dist/auth/self_service_role_actions.js +2 -2
- package/dist/auth/session_cookie.d.ts +1 -1
- package/dist/auth/session_cookie.js +1 -1
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.js +5 -5
- package/dist/rate_limiter.d.ts +5 -5
- package/dist/rate_limiter.js +6 -6
- package/dist/realtime/sse_auth_guard.d.ts +3 -3
- package/dist/realtime/sse_auth_guard.js +4 -4
- package/dist/server/app_backend.d.ts +3 -3
- package/dist/server/app_backend.js +4 -4
- package/dist/server/app_server.d.ts +1 -1
- package/dist/server/app_server.js +10 -10
- package/dist/testing/CLAUDE.md +22 -12
- package/dist/testing/admin_integration.js +4 -4
- package/dist/testing/app_server.d.ts +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/attack_surface.d.ts +4 -4
- package/dist/testing/attack_surface.js +6 -6
- package/dist/testing/audit_completeness.js +4 -4
- package/dist/testing/data_exposure.d.ts +2 -2
- package/dist/testing/data_exposure.js +7 -7
- package/dist/testing/db.d.ts +8 -8
- package/dist/testing/db.js +11 -11
- package/dist/testing/integration.js +4 -4
- package/dist/testing/integration_helpers.d.ts +6 -6
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/round_trip.js +2 -2
- package/dist/testing/rpc_round_trip.js +2 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +23 -2
- package/dist/testing/sse_round_trip.js +2 -2
- package/dist/testing/surface_invariants.d.ts +4 -4
- package/dist/testing/surface_invariants.js +5 -5
- package/dist/ui/AccountSessions.svelte +21 -6
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +32 -25
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -3
- package/dist/ui/AdminInvites.svelte +20 -15
- package/dist/ui/AdminOverview.svelte +19 -21
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
- package/dist/ui/AdminSessions.svelte +19 -21
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -3
- package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +123 -69
- package/dist/ui/ConfirmButton.svelte +82 -24
- package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
- package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
- package/dist/ui/OpenSignupToggle.svelte +6 -4
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferForm.svelte +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
- package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +32 -33
- package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +58 -76
- package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +32 -48
- package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +30 -47
- package/dist/ui/app_settings_state.svelte.d.ts +8 -3
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +19 -27
- package/dist/ui/async_slot.svelte.d.ts +173 -0
- package/dist/ui/async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/async_slot.svelte.js +241 -0
- package/dist/ui/audit_log_state.svelte.d.ts +8 -2
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +19 -18
- package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
- package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/keyed_async_slot.svelte.js +177 -0
- package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +34 -15
- package/dist/ui/table_state.svelte.d.ts +10 -7
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +11 -8
- package/package.json +1 -1
- package/dist/ui/loadable.svelte.d.ts +0 -60
- package/dist/ui/loadable.svelte.d.ts.map +0 -1
- package/dist/ui/loadable.svelte.js +0 -80
|
@@ -6,71 +6,54 @@
|
|
|
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 {
|
|
12
|
-
import {
|
|
13
|
-
export class AdminSessionsState
|
|
17
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
18
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
19
|
+
export class AdminSessionsState {
|
|
14
20
|
#get_rpc;
|
|
21
|
+
list = new AsyncSlot();
|
|
22
|
+
revoke_sessions = new KeyedAsyncSlot();
|
|
23
|
+
revoke_tokens = new KeyedAsyncSlot();
|
|
15
24
|
sessions = $state.raw([]);
|
|
16
|
-
revoking_account_ids = new SvelteSet();
|
|
17
|
-
revoking_token_account_ids = new SvelteSet();
|
|
18
25
|
active_count = $derived(this.sessions.length);
|
|
19
26
|
constructor(options) {
|
|
20
|
-
super();
|
|
21
27
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
22
28
|
}
|
|
23
29
|
/** True when an RPC adapter is wired. `fetch` and the revoke controls no-op without it. */
|
|
24
30
|
get has_rpc() {
|
|
25
31
|
return this.#get_rpc() !== null;
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
#require_rpc() {
|
|
28
34
|
const rpc = this.#get_rpc();
|
|
29
|
-
if (!rpc)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (!rpc)
|
|
36
|
+
throw new Error('rpc adapter not wired');
|
|
37
|
+
return rpc;
|
|
38
|
+
}
|
|
39
|
+
async fetch() {
|
|
40
|
+
await this.list.run(async () => {
|
|
41
|
+
const { sessions } = await this.#require_rpc().list_sessions();
|
|
35
42
|
this.sessions = sessions;
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
|
-
async
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
this.revoking_account_ids.add(account_id);
|
|
45
|
-
try {
|
|
46
|
-
await rpc.session_revoke_all({ account_id });
|
|
47
|
-
this.error = null;
|
|
45
|
+
async submit_revoke_sessions(account_id) {
|
|
46
|
+
await this.revoke_sessions.run(account_id, async () => {
|
|
47
|
+
await this.#require_rpc().session_revoke_all({ account_id });
|
|
48
|
+
});
|
|
49
|
+
if (this.revoke_sessions.succeeded(account_id))
|
|
48
50
|
await this.fetch();
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
this.error = e instanceof Error ? e.message : 'Failed to revoke sessions';
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
this.revoking_account_ids.delete(account_id);
|
|
55
|
-
}
|
|
56
51
|
}
|
|
57
|
-
async
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
this.revoking_token_account_ids.add(account_id);
|
|
64
|
-
try {
|
|
65
|
-
await rpc.token_revoke_all({ account_id });
|
|
66
|
-
this.error = null;
|
|
52
|
+
async submit_revoke_tokens(account_id) {
|
|
53
|
+
await this.revoke_tokens.run(account_id, async () => {
|
|
54
|
+
await this.#require_rpc().token_revoke_all({ account_id });
|
|
55
|
+
});
|
|
56
|
+
if (this.revoke_tokens.succeeded(account_id))
|
|
67
57
|
await this.fetch();
|
|
68
|
-
}
|
|
69
|
-
catch (e) {
|
|
70
|
-
this.error = e instanceof Error ? e.message : 'Failed to revoke tokens';
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
this.revoking_token_account_ids.delete(account_id);
|
|
74
|
-
}
|
|
75
58
|
}
|
|
76
59
|
}
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* `AdminInvitesRpc` / `AuditLogRpc`. Tests can inject plain-function stubs
|
|
6
6
|
* and consumers adapt their typed RPC client to the same shape.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (the initial fetch) and `update` (the
|
|
9
|
+
* `app_settings_update` write). Slots track status/error; the canonical
|
|
10
|
+
* `settings` lives on the class so consumers don't unwrap `slot.data`.
|
|
11
|
+
*
|
|
8
12
|
* @module
|
|
9
13
|
*/
|
|
10
|
-
import {
|
|
14
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
11
15
|
import type { AppSettingsWithUsernameJson } from '../auth/app_settings_schema.js';
|
|
12
16
|
import type { AppSettingsGetOutput, AppSettingsUpdateInput, AppSettingsUpdateOutput } from '../auth/admin_action_specs.js';
|
|
13
17
|
/**
|
|
@@ -35,10 +39,11 @@ export interface AppSettingsStateOptions {
|
|
|
35
39
|
*/
|
|
36
40
|
get_rpc?: () => AppSettingsRpc | null;
|
|
37
41
|
}
|
|
38
|
-
export declare class AppSettingsState
|
|
42
|
+
export declare class AppSettingsState {
|
|
39
43
|
#private;
|
|
44
|
+
readonly list: AsyncSlot<void, string>;
|
|
45
|
+
readonly update: AsyncSlot<void, string>;
|
|
40
46
|
settings: AppSettingsWithUsernameJson | null;
|
|
41
|
-
updating: boolean;
|
|
42
47
|
constructor(options?: AppSettingsStateOptions);
|
|
43
48
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
44
49
|
get has_rpc(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_settings_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/app_settings_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"app_settings_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/app_settings_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EACX,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACzC,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC7E;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;qBAAwB,cAAc,GAAG,IAAI;yBAArB,cAAc,GAAG,IAAI,wBAArB,cAAc,GAAG,IAAI;CAEjF,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;CACtC;AAED,qBAAa,gBAAgB;;IAG5B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,MAAM,0BAAyB;IAExC,QAAQ,EAAE,2BAA2B,GAAG,IAAI,CAAoB;gBAEpD,OAAO,CAAC,EAAE,uBAAuB;IAI7C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAMvD"}
|
|
@@ -5,56 +5,48 @@
|
|
|
5
5
|
* `AdminInvitesRpc` / `AuditLogRpc`. Tests can inject plain-function stubs
|
|
6
6
|
* and consumers adapt their typed RPC client to the same shape.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (the initial fetch) and `update` (the
|
|
9
|
+
* `app_settings_update` write). Slots track status/error; the canonical
|
|
10
|
+
* `settings` lives on the class so consumers don't unwrap `slot.data`.
|
|
11
|
+
*
|
|
8
12
|
* @module
|
|
9
13
|
*/
|
|
10
14
|
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
11
|
-
import {
|
|
15
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
12
16
|
/**
|
|
13
17
|
* Svelte context carrying the reactive `AppSettingsRpc` accessor. Mirrors
|
|
14
18
|
* `admin_accounts_rpc_context`. Unset context falls back to `() => null` so
|
|
15
19
|
* `OpenSignupToggle` mounted outside a provisioner hides gracefully.
|
|
16
20
|
*/
|
|
17
21
|
export const app_settings_rpc_context = create_context(() => () => null);
|
|
18
|
-
export class AppSettingsState
|
|
22
|
+
export class AppSettingsState {
|
|
19
23
|
#get_rpc;
|
|
24
|
+
list = new AsyncSlot();
|
|
25
|
+
update = new AsyncSlot();
|
|
20
26
|
settings = $state.raw(null);
|
|
21
|
-
updating = $state.raw(false);
|
|
22
27
|
constructor(options) {
|
|
23
|
-
super();
|
|
24
28
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
25
29
|
}
|
|
26
30
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
27
31
|
get has_rpc() {
|
|
28
32
|
return this.#get_rpc() !== null;
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
#require_rpc() {
|
|
31
35
|
const rpc = this.#get_rpc();
|
|
32
|
-
if (!rpc)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (!rpc)
|
|
37
|
+
throw new Error('rpc adapter not wired');
|
|
38
|
+
return rpc;
|
|
39
|
+
}
|
|
40
|
+
async fetch() {
|
|
41
|
+
await this.list.run(async () => {
|
|
42
|
+
const { settings } = await this.#require_rpc().get();
|
|
38
43
|
this.settings = settings;
|
|
39
44
|
});
|
|
40
45
|
}
|
|
41
46
|
async update_open_signup(value) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.error = 'rpc adapter not wired';
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
this.updating = true;
|
|
48
|
-
this.error = null;
|
|
49
|
-
try {
|
|
50
|
-
const { settings } = await rpc.update({ open_signup: value });
|
|
47
|
+
await this.update.run(async () => {
|
|
48
|
+
const { settings } = await this.#require_rpc().update({ open_signup: value });
|
|
51
49
|
this.settings = settings;
|
|
52
|
-
}
|
|
53
|
-
catch (e) {
|
|
54
|
-
this.error = e instanceof Error ? e.message : 'Failed to update settings';
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
this.updating = false;
|
|
58
|
-
}
|
|
50
|
+
});
|
|
59
51
|
}
|
|
60
52
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composable async-operation slot for Svelte 5 reactive state classes.
|
|
3
|
+
*
|
|
4
|
+
* A state class HOLDS one or more `AsyncSlot`s via composition — one slot
|
|
5
|
+
* per distinct async operation (e.g. `list` + `create` + `revoke`). Each
|
|
6
|
+
* slot tracks the status, payload, and error of its operation
|
|
7
|
+
* independently, so state classes with multiple write paths don't accumulate
|
|
8
|
+
* ad-hoc `creating` / `updating` fields beside a single shared
|
|
9
|
+
* `loading` / `error` pair.
|
|
10
|
+
*
|
|
11
|
+
* Core surface:
|
|
12
|
+
*
|
|
13
|
+
* - **Explicit four-value `status`** — `AsyncStatus` from
|
|
14
|
+
* `@fuzdev/fuz_util/async.js`: `'initial' | 'pending' | 'success' |
|
|
15
|
+
* 'failure'`. `loading: false, error: null` would be ambiguous
|
|
16
|
+
* between "never tried" and "succeeded once and now resting"; the
|
|
17
|
+
* four-value status removes the need for a per-class `submitted` /
|
|
18
|
+
* `hydrated` flag.
|
|
19
|
+
* - **Owns `data: T | undefined`** — the success payload persists
|
|
20
|
+
* across retries (stale-while-revalidate). The sentinel is
|
|
21
|
+
* `undefined` (not `null`) so `null` stays available as a legitimate
|
|
22
|
+
* success value for nullable `T`s. Pass `T = void` for write-only
|
|
23
|
+
* actions whose response isn't worth keeping.
|
|
24
|
+
* - **Supersession via internal `AbortController`** — a second `run()`
|
|
25
|
+
* aborts the first, and superseded results are silently discarded
|
|
26
|
+
* without writing to state. Removes the "in-flight call resolves
|
|
27
|
+
* after the locator advanced" race that locator-style state classes
|
|
28
|
+
* would otherwise need to compensate for.
|
|
29
|
+
* - **`AbortSignal` threaded to the callback** — RPC clients that accept
|
|
30
|
+
* a signal (or `fetch`) get cancellation for free; callers can also
|
|
31
|
+
* pass an external `signal` via {@link RunOptions} to bind the slot's
|
|
32
|
+
* lifetime to a component / page.
|
|
33
|
+
* - **`preserve_error_on_retry`** — opt-in to keeping the previous error
|
|
34
|
+
* visible while a retry is pending (default clears at the start of
|
|
35
|
+
* each `run()`).
|
|
36
|
+
* - **Per-slot `map_error`** — set once in the constructor
|
|
37
|
+
* (`{map_error: to_rpc_error_message}`); every `run()` gets the right
|
|
38
|
+
* normalization without re-passing per call.
|
|
39
|
+
* - **Public `run()`** — slots are composed, not subclassed, so call
|
|
40
|
+
* sites can invoke `state.list.run(...)` directly.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* class CellsState {
|
|
45
|
+
* readonly list = new AsyncSlot<{cells: ReadonlyArray<CellJson>}>();
|
|
46
|
+
* readonly create = new AsyncSlot<{cell: CellJson}>({map_error: to_rpc_error_message});
|
|
47
|
+
*
|
|
48
|
+
* async fetch() {
|
|
49
|
+
* await this.list.run((signal) => this.#api.cell_list({}, {signal}));
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* async submit_new(input: CellCreateInput) {
|
|
53
|
+
* const result = await this.create.run(() => this.#api.cell_create(input));
|
|
54
|
+
* if (result) await this.fetch();
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @module
|
|
60
|
+
*/
|
|
61
|
+
import type { AsyncStatus } from '@fuzdev/fuz_util/async.js';
|
|
62
|
+
export interface AsyncSlotOptions<T, E = string> {
|
|
63
|
+
/**
|
|
64
|
+
* Seed `data` and put the slot in `'success'` before any `run()`. Useful
|
|
65
|
+
* when the page already has the resource in hand (SSR hydration, a
|
|
66
|
+
* mutation response, hand-off from a parent slot).
|
|
67
|
+
*/
|
|
68
|
+
initial?: T;
|
|
69
|
+
/**
|
|
70
|
+
* Convert a caught throw into the error value stored in
|
|
71
|
+
* {@link AsyncSlot.error}. Default extracts `Error.message` (falling
|
|
72
|
+
* back to `'Request failed'` for non-Error throws). Pass
|
|
73
|
+
* `to_rpc_error_message` to unwrap JSON-RPC `data.reason` codes.
|
|
74
|
+
*/
|
|
75
|
+
map_error?: (e: unknown) => E;
|
|
76
|
+
/**
|
|
77
|
+
* When `true`, the previous `error` / `error_data` survive the start
|
|
78
|
+
* of a new `run()` until the next success (or another failure
|
|
79
|
+
* overwrites them). Useful for retry UX that wants to keep the
|
|
80
|
+
* failure message visible alongside an inline spinner. Default `false`
|
|
81
|
+
* — `run()` clears the error at the start so the pending state reads
|
|
82
|
+
* "no current error."
|
|
83
|
+
*/
|
|
84
|
+
preserve_error_on_retry?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface RunOptions {
|
|
87
|
+
/**
|
|
88
|
+
* External signal chained into the slot's internal controller. Aborts
|
|
89
|
+
* the in-flight run when fired (alongside automatic supersession by
|
|
90
|
+
* the next `run()` and manual {@link AsyncSlot.abort} calls).
|
|
91
|
+
*/
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reactive container for a single async operation.
|
|
96
|
+
*
|
|
97
|
+
* @typeParam T - The success payload type. Use `void` for write-only
|
|
98
|
+
* actions whose response isn't worth retaining.
|
|
99
|
+
* @typeParam E - The shape of {@link AsyncSlot.error}. Defaults to
|
|
100
|
+
* `string` (set by the default `map_error`). Narrow to a structured
|
|
101
|
+
* type by providing a `map_error` that returns it.
|
|
102
|
+
*/
|
|
103
|
+
export declare class AsyncSlot<T = void, E = string> {
|
|
104
|
+
#private;
|
|
105
|
+
status: AsyncStatus;
|
|
106
|
+
data: T | undefined;
|
|
107
|
+
error: E | null;
|
|
108
|
+
/** The raw caught value from the last failed `run()`, for programmatic inspection. */
|
|
109
|
+
error_data: unknown;
|
|
110
|
+
/** Convenience derived: `status === 'initial'`. */
|
|
111
|
+
readonly initial: boolean;
|
|
112
|
+
/** Convenience derived: `status === 'pending'`. */
|
|
113
|
+
readonly loading: boolean;
|
|
114
|
+
/** Convenience derived: `status === 'success'`. */
|
|
115
|
+
readonly succeeded: boolean;
|
|
116
|
+
/** Convenience derived: `status === 'failure'`. */
|
|
117
|
+
readonly failed: boolean;
|
|
118
|
+
constructor(options?: AsyncSlotOptions<T, E>);
|
|
119
|
+
/**
|
|
120
|
+
* Run an async operation. The callback receives an `AbortSignal` it
|
|
121
|
+
* can forward to fetch / RPC clients that support cancellation; the
|
|
122
|
+
* slot also discards superseded results internally even if the
|
|
123
|
+
* callback ignores the signal.
|
|
124
|
+
*
|
|
125
|
+
* Supersession rule: a second `run()` aborts the first's signal AND
|
|
126
|
+
* silently drops its commit if it resolves anyway. So
|
|
127
|
+
* back-to-back-to-back `run()` calls leave only the last call's
|
|
128
|
+
* result in `data`.
|
|
129
|
+
*
|
|
130
|
+
* Abort rule: a `run()` that throws because of its own signal (manual
|
|
131
|
+
* `abort()`, external `options.signal`, OR supersession by another
|
|
132
|
+
* `run()`) does NOT promote to `'failure'`. Manual / external aborts
|
|
133
|
+
* revert status to the previous resolved state (`'initial'` if no
|
|
134
|
+
* `run()` has ever succeeded, `'success'` otherwise). Supersession is
|
|
135
|
+
* handled by the bail-on-mismatch check, leaving the second run's
|
|
136
|
+
* `'pending'` standing.
|
|
137
|
+
*
|
|
138
|
+
* @returns the resolved value on success; `undefined` on failure,
|
|
139
|
+
* abort, or supersession
|
|
140
|
+
*/
|
|
141
|
+
run(fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions): Promise<T | undefined>;
|
|
142
|
+
/**
|
|
143
|
+
* Manually abort the in-flight run, if any. Reverts `status`
|
|
144
|
+
* synchronously to the prior resolved state — `'initial'` if no
|
|
145
|
+
* `run()` (or `set()`) has ever succeeded on this slot, `'success'`
|
|
146
|
+
* otherwise. The aborted run's eventual resolution / rejection is
|
|
147
|
+
* dropped without writing to state (the run's `Promise` resolves to
|
|
148
|
+
* `undefined`).
|
|
149
|
+
*/
|
|
150
|
+
abort(reason?: unknown): void;
|
|
151
|
+
/**
|
|
152
|
+
* Replace `data` directly and mark the slot `'success'`. For
|
|
153
|
+
* post-mutation hydration where the calling RPC already returned the
|
|
154
|
+
* canonical row (parallels `CellState.set_cell`).
|
|
155
|
+
*
|
|
156
|
+
* Aborts any in-flight `run()` first — without this, the in-flight
|
|
157
|
+
* callback could resolve after `set()` and overwrite the explicit
|
|
158
|
+
* value (the bail-on-mismatch check only fires when `#controller`
|
|
159
|
+
* was rotated).
|
|
160
|
+
*
|
|
161
|
+
* @mutates `this`
|
|
162
|
+
*/
|
|
163
|
+
set(data: T): void;
|
|
164
|
+
/**
|
|
165
|
+
* Reset to `'initial'`, clear `data` / `error` / `error_data`, and
|
|
166
|
+
* abort any in-flight run. After `reset()` the slot looks like a
|
|
167
|
+
* fresh instance with no `initial` option.
|
|
168
|
+
*
|
|
169
|
+
* @mutates `this`
|
|
170
|
+
*/
|
|
171
|
+
reset(): void;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=async_slot.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async_slot.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/async_slot.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,2BAA2B,CAAC;AAE3D,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM;IAC9C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAC9B;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,qBAAa,SAAS,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,MAAM;;IAC1C,MAAM,EAAE,WAAW,CAAyB;IAC5C,IAAI,EAAE,CAAC,GAAG,SAAS,CAAwC;IAC3D,KAAK,EAAE,CAAC,GAAG,IAAI,CAAoB;IACnC,sFAAsF;IACtF,UAAU,EAAE,OAAO,CAAoB;IAEvC,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAuC;IAChE,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAuC;IAChE,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAuC;IAClE,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAuC;gBAcnD,OAAO,GAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAM;IAUhD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,GAAG,CACR,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACvC,OAAO,GAAE,UAAe,GACtB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA8DzB;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI;IAM7B;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IASlB;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAQb"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composable async-operation slot for Svelte 5 reactive state classes.
|
|
3
|
+
*
|
|
4
|
+
* A state class HOLDS one or more `AsyncSlot`s via composition — one slot
|
|
5
|
+
* per distinct async operation (e.g. `list` + `create` + `revoke`). Each
|
|
6
|
+
* slot tracks the status, payload, and error of its operation
|
|
7
|
+
* independently, so state classes with multiple write paths don't accumulate
|
|
8
|
+
* ad-hoc `creating` / `updating` fields beside a single shared
|
|
9
|
+
* `loading` / `error` pair.
|
|
10
|
+
*
|
|
11
|
+
* Core surface:
|
|
12
|
+
*
|
|
13
|
+
* - **Explicit four-value `status`** — `AsyncStatus` from
|
|
14
|
+
* `@fuzdev/fuz_util/async.js`: `'initial' | 'pending' | 'success' |
|
|
15
|
+
* 'failure'`. `loading: false, error: null` would be ambiguous
|
|
16
|
+
* between "never tried" and "succeeded once and now resting"; the
|
|
17
|
+
* four-value status removes the need for a per-class `submitted` /
|
|
18
|
+
* `hydrated` flag.
|
|
19
|
+
* - **Owns `data: T | undefined`** — the success payload persists
|
|
20
|
+
* across retries (stale-while-revalidate). The sentinel is
|
|
21
|
+
* `undefined` (not `null`) so `null` stays available as a legitimate
|
|
22
|
+
* success value for nullable `T`s. Pass `T = void` for write-only
|
|
23
|
+
* actions whose response isn't worth keeping.
|
|
24
|
+
* - **Supersession via internal `AbortController`** — a second `run()`
|
|
25
|
+
* aborts the first, and superseded results are silently discarded
|
|
26
|
+
* without writing to state. Removes the "in-flight call resolves
|
|
27
|
+
* after the locator advanced" race that locator-style state classes
|
|
28
|
+
* would otherwise need to compensate for.
|
|
29
|
+
* - **`AbortSignal` threaded to the callback** — RPC clients that accept
|
|
30
|
+
* a signal (or `fetch`) get cancellation for free; callers can also
|
|
31
|
+
* pass an external `signal` via {@link RunOptions} to bind the slot's
|
|
32
|
+
* lifetime to a component / page.
|
|
33
|
+
* - **`preserve_error_on_retry`** — opt-in to keeping the previous error
|
|
34
|
+
* visible while a retry is pending (default clears at the start of
|
|
35
|
+
* each `run()`).
|
|
36
|
+
* - **Per-slot `map_error`** — set once in the constructor
|
|
37
|
+
* (`{map_error: to_rpc_error_message}`); every `run()` gets the right
|
|
38
|
+
* normalization without re-passing per call.
|
|
39
|
+
* - **Public `run()`** — slots are composed, not subclassed, so call
|
|
40
|
+
* sites can invoke `state.list.run(...)` directly.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* class CellsState {
|
|
45
|
+
* readonly list = new AsyncSlot<{cells: ReadonlyArray<CellJson>}>();
|
|
46
|
+
* readonly create = new AsyncSlot<{cell: CellJson}>({map_error: to_rpc_error_message});
|
|
47
|
+
*
|
|
48
|
+
* async fetch() {
|
|
49
|
+
* await this.list.run((signal) => this.#api.cell_list({}, {signal}));
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* async submit_new(input: CellCreateInput) {
|
|
53
|
+
* const result = await this.create.run(() => this.#api.cell_create(input));
|
|
54
|
+
* if (result) await this.fetch();
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @module
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Reactive container for a single async operation.
|
|
63
|
+
*
|
|
64
|
+
* @typeParam T - The success payload type. Use `void` for write-only
|
|
65
|
+
* actions whose response isn't worth retaining.
|
|
66
|
+
* @typeParam E - The shape of {@link AsyncSlot.error}. Defaults to
|
|
67
|
+
* `string` (set by the default `map_error`). Narrow to a structured
|
|
68
|
+
* type by providing a `map_error` that returns it.
|
|
69
|
+
*/
|
|
70
|
+
export class AsyncSlot {
|
|
71
|
+
status = $state.raw('initial');
|
|
72
|
+
data = $state.raw(undefined);
|
|
73
|
+
error = $state.raw(null);
|
|
74
|
+
/** The raw caught value from the last failed `run()`, for programmatic inspection. */
|
|
75
|
+
error_data = $state.raw(null);
|
|
76
|
+
/** Convenience derived: `status === 'initial'`. */
|
|
77
|
+
initial = $derived(this.status === 'initial');
|
|
78
|
+
/** Convenience derived: `status === 'pending'`. */
|
|
79
|
+
loading = $derived(this.status === 'pending');
|
|
80
|
+
/** Convenience derived: `status === 'success'`. */
|
|
81
|
+
succeeded = $derived(this.status === 'success');
|
|
82
|
+
/** Convenience derived: `status === 'failure'`. */
|
|
83
|
+
failed = $derived(this.status === 'failure');
|
|
84
|
+
#controller = null;
|
|
85
|
+
/**
|
|
86
|
+
* Tracks whether any `run()` or `set()` has ever produced a success
|
|
87
|
+
* result. Used by {@link abort} to revert to `'success'` (vs `'initial'`)
|
|
88
|
+
* — explicit flag instead of inspecting `data` so the discriminator
|
|
89
|
+
* stays correct for `T = void` (where success-`data` is `undefined`)
|
|
90
|
+
* and for nullable `T`s where `null` is a legitimate success value.
|
|
91
|
+
*/
|
|
92
|
+
#has_succeeded = false;
|
|
93
|
+
#map_error;
|
|
94
|
+
#preserve_error;
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
if (options.initial !== undefined) {
|
|
97
|
+
this.data = options.initial;
|
|
98
|
+
this.status = 'success';
|
|
99
|
+
this.#has_succeeded = true;
|
|
100
|
+
}
|
|
101
|
+
this.#map_error = options.map_error ?? default_map_error;
|
|
102
|
+
this.#preserve_error = options.preserve_error_on_retry ?? false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run an async operation. The callback receives an `AbortSignal` it
|
|
106
|
+
* can forward to fetch / RPC clients that support cancellation; the
|
|
107
|
+
* slot also discards superseded results internally even if the
|
|
108
|
+
* callback ignores the signal.
|
|
109
|
+
*
|
|
110
|
+
* Supersession rule: a second `run()` aborts the first's signal AND
|
|
111
|
+
* silently drops its commit if it resolves anyway. So
|
|
112
|
+
* back-to-back-to-back `run()` calls leave only the last call's
|
|
113
|
+
* result in `data`.
|
|
114
|
+
*
|
|
115
|
+
* Abort rule: a `run()` that throws because of its own signal (manual
|
|
116
|
+
* `abort()`, external `options.signal`, OR supersession by another
|
|
117
|
+
* `run()`) does NOT promote to `'failure'`. Manual / external aborts
|
|
118
|
+
* revert status to the previous resolved state (`'initial'` if no
|
|
119
|
+
* `run()` has ever succeeded, `'success'` otherwise). Supersession is
|
|
120
|
+
* handled by the bail-on-mismatch check, leaving the second run's
|
|
121
|
+
* `'pending'` standing.
|
|
122
|
+
*
|
|
123
|
+
* @returns the resolved value on success; `undefined` on failure,
|
|
124
|
+
* abort, or supersession
|
|
125
|
+
*/
|
|
126
|
+
async run(fn, options = {}) {
|
|
127
|
+
this.#controller?.abort();
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
this.#controller = controller;
|
|
130
|
+
const external = options.signal;
|
|
131
|
+
let external_handler;
|
|
132
|
+
if (external) {
|
|
133
|
+
if (external.aborted) {
|
|
134
|
+
this.abort(external.reason);
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
// Route external abort through the slot's own abort() so the
|
|
138
|
+
// controller-null + status-revert + signal-fire happens
|
|
139
|
+
// atomically (same path as manual abort). Listener is
|
|
140
|
+
// removed in the finally so successful / failing runs don't
|
|
141
|
+
// leak listeners on long-lived external signals.
|
|
142
|
+
external_handler = () => {
|
|
143
|
+
if (this.#controller === controller)
|
|
144
|
+
this.abort(external.reason);
|
|
145
|
+
};
|
|
146
|
+
external.addEventListener('abort', external_handler, { once: true });
|
|
147
|
+
}
|
|
148
|
+
this.status = 'pending';
|
|
149
|
+
if (!this.#preserve_error) {
|
|
150
|
+
this.error = null;
|
|
151
|
+
this.error_data = null;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const result = await fn(controller.signal);
|
|
155
|
+
// Bail if this run was superseded or manually aborted —
|
|
156
|
+
// `abort()` nulls `#controller`, so the mismatch fires in
|
|
157
|
+
// both cases. A callback that ignored its signal and
|
|
158
|
+
// resolved anyway has its result dropped silently.
|
|
159
|
+
if (this.#controller !== controller)
|
|
160
|
+
return undefined;
|
|
161
|
+
this.data = result;
|
|
162
|
+
this.error = null;
|
|
163
|
+
this.error_data = null;
|
|
164
|
+
this.status = 'success';
|
|
165
|
+
this.#has_succeeded = true;
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
if (this.#controller !== controller)
|
|
170
|
+
return undefined;
|
|
171
|
+
this.error = this.#map_error(e);
|
|
172
|
+
this.error_data = e;
|
|
173
|
+
this.status = 'failure';
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
if (external_handler)
|
|
178
|
+
external?.removeEventListener('abort', external_handler);
|
|
179
|
+
if (this.#controller === controller)
|
|
180
|
+
this.#controller = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Abort the in-flight run (if any) and null out the controller field.
|
|
185
|
+
* Shared by {@link abort}, {@link set}, and {@link reset}.
|
|
186
|
+
*/
|
|
187
|
+
#clear_controller(reason) {
|
|
188
|
+
this.#controller?.abort(reason);
|
|
189
|
+
this.#controller = null;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Manually abort the in-flight run, if any. Reverts `status`
|
|
193
|
+
* synchronously to the prior resolved state — `'initial'` if no
|
|
194
|
+
* `run()` (or `set()`) has ever succeeded on this slot, `'success'`
|
|
195
|
+
* otherwise. The aborted run's eventual resolution / rejection is
|
|
196
|
+
* dropped without writing to state (the run's `Promise` resolves to
|
|
197
|
+
* `undefined`).
|
|
198
|
+
*/
|
|
199
|
+
abort(reason) {
|
|
200
|
+
if (!this.#controller)
|
|
201
|
+
return;
|
|
202
|
+
this.#clear_controller(reason);
|
|
203
|
+
this.status = this.#has_succeeded ? 'success' : 'initial';
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Replace `data` directly and mark the slot `'success'`. For
|
|
207
|
+
* post-mutation hydration where the calling RPC already returned the
|
|
208
|
+
* canonical row (parallels `CellState.set_cell`).
|
|
209
|
+
*
|
|
210
|
+
* Aborts any in-flight `run()` first — without this, the in-flight
|
|
211
|
+
* callback could resolve after `set()` and overwrite the explicit
|
|
212
|
+
* value (the bail-on-mismatch check only fires when `#controller`
|
|
213
|
+
* was rotated).
|
|
214
|
+
*
|
|
215
|
+
* @mutates `this`
|
|
216
|
+
*/
|
|
217
|
+
set(data) {
|
|
218
|
+
this.#clear_controller();
|
|
219
|
+
this.data = data;
|
|
220
|
+
this.status = 'success';
|
|
221
|
+
this.#has_succeeded = true;
|
|
222
|
+
this.error = null;
|
|
223
|
+
this.error_data = null;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Reset to `'initial'`, clear `data` / `error` / `error_data`, and
|
|
227
|
+
* abort any in-flight run. After `reset()` the slot looks like a
|
|
228
|
+
* fresh instance with no `initial` option.
|
|
229
|
+
*
|
|
230
|
+
* @mutates `this`
|
|
231
|
+
*/
|
|
232
|
+
reset() {
|
|
233
|
+
this.#clear_controller();
|
|
234
|
+
this.status = 'initial';
|
|
235
|
+
this.data = undefined;
|
|
236
|
+
this.error = null;
|
|
237
|
+
this.error_data = null;
|
|
238
|
+
this.#has_succeeded = false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const default_map_error = (e) => e instanceof Error ? e.message : 'Request failed';
|
|
@@ -6,9 +6,13 @@
|
|
|
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
|
-
import {
|
|
15
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
12
16
|
import type { AuditLogEventWithUsernamesJson, RoleGrantHistoryEventJson } from '../auth/audit_log_schema.js';
|
|
13
17
|
import type { AuditLogListInput, AuditLogListOutput, AuditLogRoleGrantHistoryInput, AuditLogRoleGrantHistoryOutput } from '../auth/admin_action_specs.js';
|
|
14
18
|
/**
|
|
@@ -38,8 +42,10 @@ export interface AuditLogStateOptions {
|
|
|
38
42
|
/** SSE stream URL. Defaults to the shipped admin audit-log stream route. */
|
|
39
43
|
stream_url?: string;
|
|
40
44
|
}
|
|
41
|
-
export declare class AuditLogState
|
|
45
|
+
export declare class AuditLogState {
|
|
42
46
|
#private;
|
|
47
|
+
readonly list: AsyncSlot<void, string>;
|
|
48
|
+
readonly role_grant_history: AsyncSlot<void, string>;
|
|
43
49
|
events: Array<AuditLogEventWithUsernamesJson>;
|
|
44
50
|
role_grant_history_events: Array<RoleGrantHistoryEventJson>;
|
|
45
51
|
readonly count: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/audit_log_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit_log_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/audit_log_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,EAEX,8BAA8B,EAC9B,yBAAyB,EACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACX,iBAAiB,EACjB,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,MAAM,+BAA+B,CAAC;AAGvC;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjE,kBAAkB,EAAE,CACnB,KAAK,CAAC,EAAE,6BAA6B,KACjC,OAAO,CAAC,8BAA8B,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB;qBAAwB,WAAW,GAAG,IAAI;yBAAlB,WAAW,GAAG,IAAI,wBAAlB,WAAW,GAAG,IAAI;CAAmB,CAAC;AAEhG,MAAM,WAAW,oBAAoB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACnC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;;IAGzB,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,kBAAkB,0BAAyB;IAEpD,MAAM,EAAE,KAAK,CAAC,8BAA8B,CAAC,CAAkB;IAC/D,yBAAyB,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAkB;IAE7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAgC;IAEtD,qDAAqD;IACrD,SAAS,UAAqB;gBAWlB,OAAO,CAAC,EAAE,oBAAoB;IAK1C,8FAA8F;IAC9F,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD,wBAAwB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9E;;;;;;;;OAQG;IACH,SAAS,IAAI,MAAM,IAAI;IA0CvB;;;;OAIG;IACH,UAAU,IAAI,IAAI;CAiClB"}
|