@fuzdev/fuz_app 0.113.0 → 0.115.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 (129) hide show
  1. package/dist/actions/CLAUDE.md +46 -20
  2. package/dist/actions/action_dispatcher.d.ts +1 -1
  3. package/dist/actions/action_dispatcher.d.ts.map +1 -1
  4. package/dist/actions/action_dispatcher.js +22 -16
  5. package/dist/actions/action_event.d.ts +18 -2
  6. package/dist/actions/action_event.d.ts.map +1 -1
  7. package/dist/actions/action_event.js +63 -19
  8. package/dist/actions/action_event_types.d.ts +22 -0
  9. package/dist/actions/action_event_types.d.ts.map +1 -1
  10. package/dist/actions/action_event_types.js +19 -0
  11. package/dist/actions/compile_action_registry.d.ts +4 -2
  12. package/dist/actions/compile_action_registry.d.ts.map +1 -1
  13. package/dist/actions/compile_action_registry.js +8 -3
  14. package/dist/actions/frontend_rpc_client.d.ts +6 -0
  15. package/dist/actions/frontend_rpc_client.d.ts.map +1 -1
  16. package/dist/actions/frontend_rpc_client.js +2 -1
  17. package/dist/actions/heartbeat.d.ts +8 -4
  18. package/dist/actions/heartbeat.d.ts.map +1 -1
  19. package/dist/actions/heartbeat.js +8 -4
  20. package/dist/actions/peer_ping.d.ts.map +1 -1
  21. package/dist/actions/peer_ping.js +3 -1
  22. package/dist/actions/perform_action.d.ts +1 -1
  23. package/dist/actions/perform_action.d.ts.map +1 -1
  24. package/dist/actions/perform_action.js +6 -4
  25. package/dist/actions/register_action_ws.d.ts +13 -0
  26. package/dist/actions/register_action_ws.d.ts.map +1 -1
  27. package/dist/actions/register_action_ws.js +23 -2
  28. package/dist/actions/rpc_client.d.ts +1 -1
  29. package/dist/actions/rpc_client.d.ts.map +1 -1
  30. package/dist/actions/rpc_client.js +2 -1
  31. package/dist/actions/socket.svelte.d.ts +53 -10
  32. package/dist/actions/socket.svelte.d.ts.map +1 -1
  33. package/dist/actions/socket.svelte.js +84 -23
  34. package/dist/actions/transports.d.ts +24 -0
  35. package/dist/actions/transports.d.ts.map +1 -1
  36. package/dist/actions/transports.js +30 -0
  37. package/dist/actions/transports_http.d.ts +11 -4
  38. package/dist/actions/transports_http.d.ts.map +1 -1
  39. package/dist/actions/transports_http.js +63 -18
  40. package/dist/actions/transports_ws.d.ts +28 -3
  41. package/dist/actions/transports_ws.d.ts.map +1 -1
  42. package/dist/actions/transports_ws.js +33 -4
  43. package/dist/actions/ws_endpoint_spec.d.ts +7 -0
  44. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  45. package/dist/auth/CLAUDE.md +52 -8
  46. package/dist/auth/admin_actions.d.ts.map +1 -1
  47. package/dist/auth/admin_actions.js +4 -3
  48. package/dist/auth/audit_log_schema.d.ts +3 -1
  49. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  50. package/dist/auth/audit_log_schema.js +13 -3
  51. package/dist/auth/cell_action_specs.js +1 -1
  52. package/dist/auth/cell_actions.d.ts.map +1 -1
  53. package/dist/auth/cell_actions.js +33 -4
  54. package/dist/auth/cell_authorize.d.ts +3 -0
  55. package/dist/auth/cell_authorize.d.ts.map +1 -1
  56. package/dist/auth/cell_authorize.js +7 -4
  57. package/dist/auth/request_context.d.ts +12 -3
  58. package/dist/auth/request_context.d.ts.map +1 -1
  59. package/dist/auth/request_context.js +12 -3
  60. package/dist/auth/role_grant_offer_action_specs.d.ts +30 -5
  61. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  62. package/dist/auth/role_grant_offer_action_specs.js +53 -17
  63. package/dist/auth/role_grant_offer_actions.d.ts +23 -12
  64. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  65. package/dist/auth/role_grant_offer_actions.js +132 -38
  66. package/dist/auth/role_grant_offer_queries.d.ts +36 -0
  67. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_queries.js +76 -0
  69. package/dist/auth/role_schema.d.ts +12 -0
  70. package/dist/auth/role_schema.d.ts.map +1 -1
  71. package/dist/auth/role_schema.js +12 -0
  72. package/dist/auth/self_service_role_actions.d.ts +14 -0
  73. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  74. package/dist/auth/self_service_role_actions.js +25 -1
  75. package/dist/http/CLAUDE.md +7 -3
  76. package/dist/http/jsonrpc_helpers.d.ts +0 -5
  77. package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
  78. package/dist/http/jsonrpc_helpers.js +0 -13
  79. package/dist/http/schema_helpers.d.ts +56 -0
  80. package/dist/http/schema_helpers.d.ts.map +1 -1
  81. package/dist/http/schema_helpers.js +91 -0
  82. package/dist/server/app_server.d.ts.map +1 -1
  83. package/dist/server/app_server.js +1 -0
  84. package/dist/server/serve_fact_route.d.ts +3 -1
  85. package/dist/server/serve_fact_route.d.ts.map +1 -1
  86. package/dist/server/serve_fact_route.js +7 -4
  87. package/dist/testing/CLAUDE.md +29 -16
  88. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  89. package/dist/testing/cross_backend/cell_gated_create.js +23 -1
  90. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  91. package/dist/testing/cross_backend/peer_ping_ws.js +18 -0
  92. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/role_grant_participation.js +34 -0
  94. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +12 -1
  95. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +13 -1
  97. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  98. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +9 -7
  99. package/dist/testing/cross_backend/ws_round_trip.d.ts +11 -3
  100. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  101. package/dist/testing/cross_backend/ws_round_trip.js +74 -12
  102. package/dist/testing/entities.d.ts +6 -3
  103. package/dist/testing/entities.d.ts.map +1 -1
  104. package/dist/testing/entities.js +4 -1
  105. package/dist/testing/transports/ws_client.d.ts +6 -0
  106. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  107. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  108. package/dist/testing/transports/ws_transport.js +6 -1
  109. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  110. package/dist/testing/ws_round_trip.js +5 -0
  111. package/dist/ui/BootstrapForm.svelte +14 -10
  112. package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -1
  113. package/dist/ui/CLAUDE.md +19 -7
  114. package/dist/ui/LoginForm.svelte +8 -4
  115. package/dist/ui/LoginForm.svelte.d.ts.map +1 -1
  116. package/dist/ui/RoleGrantOfferForm.svelte +29 -14
  117. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +2 -3
  118. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -1
  119. package/dist/ui/SignupForm.svelte +14 -13
  120. package/dist/ui/form_state.svelte.d.ts +24 -11
  121. package/dist/ui/form_state.svelte.d.ts.map +1 -1
  122. package/dist/ui/form_state.svelte.js +38 -10
  123. package/dist/ui/role_grant_offers_state.svelte.d.ts +25 -15
  124. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  125. package/dist/ui/role_grant_offers_state.svelte.js +7 -1
  126. package/package.json +2 -22
  127. package/dist/actions/request_tracker.svelte.d.ts +0 -77
  128. package/dist/actions/request_tracker.svelte.d.ts.map +0 -1
  129. package/dist/actions/request_tracker.svelte.js +0 -169
@@ -5,16 +5,28 @@
5
5
  * `attempted` state (set on submit attempt). Errors show after a field is blurred or
6
6
  * after a submit attempt, avoiding premature validation while the user is still typing.
7
7
  *
8
- * The `FormState.form` attachment also handles Enter key advancing
9
- * between focusable elements.
8
+ * The `FormState.form` attachment also handles the Enter key: in an input
9
+ * followed by another input it moves focus to that input (past any button
10
+ * between), and in the last input it submits the form (`requestSubmit`) —
11
+ * so Enter in a login form's password field logs in, even when the form's
12
+ * button is `type="button"`, as `PendingButton` is. Hidden inputs don't count, and an Enter that commits
13
+ * IME composition is left alone.
14
+ *
15
+ * While a submit is in flight, make the fields `readonly` rather than `disabled`
16
+ * (and the button `aria-disabled`, guarding the handler against re-entry) —
17
+ * disabling the focused element drops focus to `<body>`. Enter still advances
18
+ * through and submits from `readonly` inputs.
19
+ *
20
+ * The type parameter names the form's fields, so `focus`, `show`, `touch`,
21
+ * and `is_touched` reject a misspelled field at compile time.
10
22
  *
11
23
  * All trackable inputs must have a `name` attribute — an error is thrown in dev
12
24
  * if an input without `name` loses focus.
13
25
  *
14
26
  * @example
15
27
  * ```svelte
16
- * <script>
17
- * const form_state = new FormState();
28
+ * <script lang="ts">
29
+ * const form_state = new FormState<'username'>();
18
30
  * let username = $state.raw('');
19
31
  * const username_valid = $derived(Username.safeParse(username).success);
20
32
  * const can_submit = $derived(username.trim() && username_valid);
@@ -43,7 +55,8 @@
43
55
  import { DEV } from 'esm-env';
44
56
  import { on } from 'svelte/events';
45
57
  import { SvelteSet } from 'svelte/reactivity';
46
- const FOCUSABLE_SELECTOR = 'input:not(:disabled), button:not(:disabled)';
58
+ /** Inputs Enter moves between — disabled and hidden ones are skipped. */
59
+ const ENTER_INPUT_SELECTOR = 'input:not(:disabled):not([type=hidden])';
47
60
  const FORM_INPUT_SELECTOR = 'input, textarea, select';
48
61
  export class FormState {
49
62
  #touched = new SvelteSet();
@@ -56,8 +69,9 @@ export class FormState {
56
69
  return this.#attempted;
57
70
  }
58
71
  /**
59
- * Creates a form attachment that handles Enter key advancing between
60
- * focusable elements and tracks field touched state via delegated `focusout`.
72
+ * Creates a form attachment that handles the Enter key (advance focus to
73
+ * the next input, or submit from the last one) and tracks field touched
74
+ * state via delegated `focusout`.
61
75
  *
62
76
  * Fields are identified by their `name` attribute.
63
77
  *
@@ -73,14 +87,28 @@ export class FormState {
73
87
  const keydown_cleanup = on(form, 'keydown', (e) => {
74
88
  if (e.key !== 'Enter')
75
89
  return;
90
+ // an IME composing text commits it with Enter — not a submit; Safari
91
+ // reports that Enter with `isComposing` false but `keyCode` 229 — the
92
+ // deprecated field is the only signal there
93
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
94
+ if (e.isComposing || e.keyCode === 229)
95
+ return;
76
96
  if (!(e.target instanceof HTMLInputElement))
77
97
  return;
78
- const elements = Array.from(form.querySelectorAll(FOCUSABLE_SELECTOR));
79
- const index = elements.indexOf(e.target);
98
+ const inputs = Array.from(form.querySelectorAll(ENTER_INPUT_SELECTOR));
99
+ const index = inputs.indexOf(e.target);
80
100
  if (index < 0)
81
101
  return;
82
102
  e.preventDefault();
83
- elements[(index + 1) % elements.length].focus();
103
+ // advance to the next input, past any button between (a show-password
104
+ // toggle, say); the last input submits instead
105
+ const next_input = inputs[index + 1];
106
+ if (next_input) {
107
+ next_input.focus();
108
+ }
109
+ else {
110
+ form.requestSubmit();
111
+ }
84
112
  });
85
113
  const focusout_cleanup = on(form, 'focusout', (e) => {
86
114
  const target = e.target;
@@ -26,6 +26,26 @@
26
26
  */
27
27
  import { AsyncSlot } from './async_slot.svelte.js';
28
28
  import type { RoleGrantOfferJson } from '../auth/role_grant_offer_schema.js';
29
+ /**
30
+ * The scope of an offer: global (both absent / `null`) or scoped (both set).
31
+ * `scope_kind` and `scope_id` are paired on the wire — a lone `scope_id` is an
32
+ * `invalid_params` refusal — so the pair is one union rather than two optional
33
+ * fields.
34
+ */
35
+ export type RoleGrantOfferScope = {
36
+ scope_kind?: null;
37
+ scope_id?: null;
38
+ } | {
39
+ scope_kind: string;
40
+ scope_id: string;
41
+ };
42
+ /** Params for creating an offer — `RoleGrantOffersRpc.create` and `submit_create`. */
43
+ export type RoleGrantOfferCreateParams = {
44
+ to_account_id: string;
45
+ to_actor_id?: string | null;
46
+ role: string;
47
+ message?: string | null;
48
+ } & RoleGrantOfferScope;
29
49
  /**
30
50
  * Svelte context for `RoleGrantOffersState`.
31
51
  * Use `role_grant_offers_state_context.set(state)` in the provider and
@@ -52,13 +72,7 @@ export interface RoleGrantOffersRpc {
52
72
  }) => Promise<{
53
73
  offers: Array<RoleGrantOfferJson>;
54
74
  }>;
55
- create: (params: {
56
- to_account_id: string;
57
- to_actor_id?: string | null;
58
- role: string;
59
- scope_id?: string | null;
60
- message?: string | null;
61
- }) => Promise<{
75
+ create: (params: RoleGrantOfferCreateParams) => Promise<{
62
76
  offer: RoleGrantOfferJson;
63
77
  }>;
64
78
  accept: (offer_id: string) => Promise<{
@@ -135,15 +149,11 @@ export declare class RoleGrantOffersState {
135
149
  *
136
150
  * `to_actor_id` (optional) narrows the offer to a specific actor on
137
151
  * `to_account_id`; omit / null for the account-grain default (any actor
138
- * on the recipient account may accept).
152
+ * on the recipient account may accept). A scoped offer passes `scope_kind`
153
+ * and `scope_id` together; an unpaired scope (reachable from untyped
154
+ * callers) fails with `GRANT_SCOPE_PAIRED_MESSAGE` without an RPC.
139
155
  */
140
- submit_create(params: {
141
- to_account_id: string;
142
- to_actor_id?: string | null;
143
- role: string;
144
- scope_id?: string | null;
145
- message?: string | null;
146
- }): Promise<RoleGrantOfferJson | undefined>;
156
+ submit_create(params: RoleGrantOfferCreateParams): Promise<RoleGrantOfferJson | undefined>;
147
157
  /** Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded. */
148
158
  submit_accept(offer_id: string): Promise<void>;
149
159
  submit_decline(offer_id: string, reason?: string | null): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@
27
27
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.ts';
28
28
  import { AsyncSlot } from './async_slot.svelte.js';
29
29
  import { is_iso8601_seconds_live } from '../timestamp.js';
30
+ import { GRANT_SCOPE_PAIRED_MESSAGE, is_grant_scope_paired } from '../auth/role_grant_offer_action_specs.js';
30
31
  import { ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, ROLE_GRANT_REVOKE_NOTIFICATION_METHOD } from '../auth/role_grant_offer_notifications.js';
31
32
  /**
32
33
  * Svelte context for `RoleGrantOffersState`.
@@ -121,10 +122,15 @@ export class RoleGrantOffersState {
121
122
  *
122
123
  * `to_actor_id` (optional) narrows the offer to a specific actor on
123
124
  * `to_account_id`; omit / null for the account-grain default (any actor
124
- * on the recipient account may accept).
125
+ * on the recipient account may accept). A scoped offer passes `scope_kind`
126
+ * and `scope_id` together; an unpaired scope (reachable from untyped
127
+ * callers) fails with `GRANT_SCOPE_PAIRED_MESSAGE` without an RPC.
125
128
  */
126
129
  async submit_create(params) {
127
130
  return this.create.run(async () => {
131
+ if (!is_grant_scope_paired(params)) {
132
+ throw new Error(GRANT_SCOPE_PAIRED_MESSAGE);
133
+ }
128
134
  const { offer } = await this.#rpc.create(params);
129
135
  this.#merge_offers([offer]);
130
136
  return offer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.113.0",
3
+ "version": "0.115.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -75,7 +75,7 @@
75
75
  "@electric-sql/pglite": "^0.4.5",
76
76
  "@fuzdev/blake3-wasm": "^0.2.0",
77
77
  "@fuzdev/fuz_code": "^0.48.0",
78
- "@fuzdev/fuz_css": "^0.63.3",
78
+ "@fuzdev/fuz_css": "^0.64.0",
79
79
  "@fuzdev/fuz_ui": "^0.208.0",
80
80
  "@fuzdev/fuz_util": "^0.69.0",
81
81
  "@fuzdev/gro": "^0.212.0",
@@ -100,8 +100,6 @@
100
100
  "jsdom": "^28.1.0",
101
101
  "magic-string": "^0.30.21",
102
102
  "pg": "^8.20.0",
103
- "prettier": "^3.9.6",
104
- "prettier-plugin-svelte": "^4.1.1",
105
103
  "svelte": "^5.56.4",
106
104
  "svelte-check": "^4.7.1",
107
105
  "svelte-docinfo": "^0.7.0",
@@ -115,24 +113,6 @@
115
113
  "zimmerframe": "^1.1.4",
116
114
  "zod": "^4.3.6"
117
115
  },
118
- "prettier": {
119
- "plugins": [
120
- "prettier-plugin-svelte"
121
- ],
122
- "useTabs": true,
123
- "printWidth": 100,
124
- "singleQuote": true,
125
- "bracketSpacing": true,
126
- "trailingComma": "none",
127
- "overrides": [
128
- {
129
- "files": "package.json",
130
- "options": {
131
- "useTabs": false
132
- }
133
- }
134
- ]
135
- },
136
116
  "sideEffects": [
137
117
  "**/*.css"
138
118
  ],
@@ -1,77 +0,0 @@
1
- /**
2
- * Request tracker — manages pending JSON-RPC requests with timeouts.
3
- *
4
- * Uses SvelteMap for reactive pending request tracking.
5
- *
6
- * @module
7
- */
8
- import { type Deferred, type AsyncStatus } from '@fuzdev/fuz_util/async.ts';
9
- import { SvelteMap } from 'svelte/reactivity';
10
- import { type JsonrpcErrorResponse, type JsonrpcRequestId, type JsonrpcResponseOrError } from '../http/jsonrpc.js';
11
- /** ISO datetime string for request creation timestamps. */
12
- type Datetime = string & {
13
- readonly __brand: 'Datetime';
14
- };
15
- export declare class RequestTrackerItem {
16
- readonly id: JsonrpcRequestId;
17
- readonly deferred: Deferred<JsonrpcResponseOrError>;
18
- readonly created: Datetime;
19
- status: AsyncStatus;
20
- timeout: NodeJS.Timeout | undefined;
21
- constructor(id: JsonrpcRequestId, deferred: Deferred<JsonrpcResponseOrError>, created: Datetime, status: AsyncStatus, timeout: NodeJS.Timeout | undefined);
22
- }
23
- /**
24
- * Reactive pending-request store with per-request timeouts. Used by transports
25
- * that don't delegate request/response correlation to a `WebsocketRpcConnection`.
26
- */
27
- export declare class RequestTracker {
28
- readonly pending_requests: SvelteMap<JsonrpcRequestId, RequestTrackerItem>;
29
- readonly request_timeout_ms: number;
30
- constructor(request_timeout_ms?: number);
31
- /**
32
- * Track a new request keyed by `id`.
33
- *
34
- * @returns deferred resolved on response, or rejected via the timeout
35
- * @mutates this - inserts a `RequestTrackerItem` into `pending_requests`
36
- * and arms a timeout that auto-rejects after `request_timeout_ms`;
37
- * clears any prior timeout for the same id
38
- */
39
- track_request(id: JsonrpcRequestId): Deferred<JsonrpcResponseOrError>;
40
- /**
41
- * Resolve a pending request with its response.
42
- *
43
- * @mutates this - clears the timeout, marks status `'success'`,
44
- * resolves the deferred, and removes the entry from `pending_requests`
45
- */
46
- resolve_request(id: JsonrpcRequestId, response: JsonrpcResponseOrError): void;
47
- /**
48
- * Reject a pending request with `error_message`.
49
- *
50
- * @mutates this - clears the timeout, marks status `'failure'`,
51
- * rejects the deferred with a `ThrownJsonrpcError`, and removes the
52
- * entry from `pending_requests`
53
- */
54
- reject_request(id: JsonrpcRequestId, error_message: JsonrpcErrorResponse): void;
55
- /**
56
- * Handles an incoming JSON-RPC message. Resolves or rejects the associated request.
57
- * Ignores notifications and unknown/invalid messages.
58
- */
59
- handle_message(message: any): void;
60
- /**
61
- * Cancel a pending request without rejecting its deferred — just
62
- * cleanup. The caller's promise stays unsettled; pair with an external
63
- * resolution if needed.
64
- *
65
- * @mutates this - clears the timeout and removes the entry from `pending_requests`
66
- */
67
- cancel_request(id: JsonrpcRequestId): void;
68
- /**
69
- * Cancel all pending requests.
70
- * @param reason - optional reason to include in rejection
71
- * @mutates this - clears every timeout, marks each status `'failure'`,
72
- * rejects each deferred with `internal_error`, and empties `pending_requests`
73
- */
74
- cancel_all_requests(reason?: string): void;
75
- }
76
- export {};
77
- //# sourceMappingURL=request_tracker.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"request_tracker.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/request_tracker.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAEN,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,MAAM,oBAAoB,CAAC;AAG5B,2DAA2D;AAC3D,KAAK,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAM1D,qBAAa,kBAAkB;IAC9B,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,sBAAsB,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,MAAM,EAAE,WAAW,CAAiB;IACpC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAgB;gBAGlD,EAAE,EAAE,gBAAgB,EACpB,QAAQ,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAC1C,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS;CAQpC;AAED;;;GAGG;AACH,qBAAa,cAAc;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAmB;IAC7F,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;gBAExB,kBAAkB,SAAU;IAIxC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,EAAE,gBAAgB,GAAG,QAAQ,CAAC,sBAAsB,CAAC;IA6BrE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAkB7E;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,GAAG,IAAI;IAuB/E;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI;IAiBlC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI;IAe1C;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;CAiB1C"}
@@ -1,169 +0,0 @@
1
- /**
2
- * Request tracker — manages pending JSON-RPC requests with timeouts.
3
- *
4
- * Uses SvelteMap for reactive pending request tracking.
5
- *
6
- * @module
7
- */
8
- import { create_deferred } from '@fuzdev/fuz_util/async.ts';
9
- import { SvelteMap } from 'svelte/reactivity';
10
- import { JSONRPC_INTERNAL_ERROR } from '../http/jsonrpc.js';
11
- import { ThrownJsonrpcError, JSONRPC_ERROR_CODES } from '../http/jsonrpc_errors.js';
12
- const get_datetime_now = () => new Date().toISOString();
13
- // TODO what if this uses a tracker id param that's an opaque UUID but can be used for action association?
14
- // TODO name, like `TrackedRequest`? or is this implicit namespacing and generic name preferred
15
- export class RequestTrackerItem {
16
- id;
17
- deferred;
18
- created;
19
- status = $state.raw();
20
- timeout = $state.raw();
21
- constructor(id, deferred, created, status, timeout) {
22
- this.id = id;
23
- this.deferred = deferred;
24
- this.created = created;
25
- this.status = status;
26
- this.timeout = timeout;
27
- }
28
- }
29
- /**
30
- * Reactive pending-request store with per-request timeouts. Used by transports
31
- * that don't delegate request/response correlation to a `WebsocketRpcConnection`.
32
- */
33
- export class RequestTracker {
34
- pending_requests = new SvelteMap();
35
- request_timeout_ms;
36
- constructor(request_timeout_ms = 120_000) {
37
- this.request_timeout_ms = request_timeout_ms;
38
- }
39
- /**
40
- * Track a new request keyed by `id`.
41
- *
42
- * @returns deferred resolved on response, or rejected via the timeout
43
- * @mutates this - inserts a `RequestTrackerItem` into `pending_requests`
44
- * and arms a timeout that auto-rejects after `request_timeout_ms`;
45
- * clears any prior timeout for the same id
46
- */
47
- track_request(id) {
48
- const deferred = create_deferred();
49
- const created = get_datetime_now();
50
- // If we're tracking a request with the same id, clean up the previous one first
51
- const existing_request = this.pending_requests.get(id);
52
- if (existing_request?.timeout) {
53
- clearTimeout(existing_request.timeout);
54
- }
55
- // Set up a timeout to automatically reject the request after a delay
56
- const timeout = setTimeout(() => {
57
- // Create a proper timeout error message
58
- this.reject_request(id, {
59
- jsonrpc: '2.0',
60
- id,
61
- error: { code: JSONRPC_INTERNAL_ERROR, message: `request timed out: ${id}` }
62
- });
63
- }, this.request_timeout_ms);
64
- // Store the request tracker using the new class
65
- this.pending_requests.set(id, new RequestTrackerItem(id, deferred, created, 'pending', timeout));
66
- return deferred;
67
- }
68
- /**
69
- * Resolve a pending request with its response.
70
- *
71
- * @mutates this - clears the timeout, marks status `'success'`,
72
- * resolves the deferred, and removes the entry from `pending_requests`
73
- */
74
- resolve_request(id, response) {
75
- const request = this.pending_requests.get(id);
76
- if (!request) {
77
- console.warn(`received response for unknown request: ${id}`);
78
- return;
79
- }
80
- // Clear the timeout and resolve the promise
81
- if (request.timeout) {
82
- clearTimeout(request.timeout);
83
- request.timeout = undefined;
84
- }
85
- request.status = 'success';
86
- request.deferred.resolve(response);
87
- this.pending_requests.delete(id);
88
- }
89
- /**
90
- * Reject a pending request with `error_message`.
91
- *
92
- * @mutates this - clears the timeout, marks status `'failure'`,
93
- * rejects the deferred with a `ThrownJsonrpcError`, and removes the
94
- * entry from `pending_requests`
95
- */
96
- reject_request(id, error_message) {
97
- const request = this.pending_requests.get(id);
98
- if (!request) {
99
- console.warn(`received error for unknown request: ${id}`);
100
- return;
101
- }
102
- // Clear the timeout and reject the promise
103
- if (request.timeout) {
104
- clearTimeout(request.timeout);
105
- request.timeout = undefined;
106
- }
107
- request.status = 'failure';
108
- const error = new ThrownJsonrpcError(error_message.error.code, error_message.error.message, error_message.error.data);
109
- request.deferred.reject(error);
110
- this.pending_requests.delete(id);
111
- }
112
- /**
113
- * Handles an incoming JSON-RPC message. Resolves or rejects the associated request.
114
- * Ignores notifications and unknown/invalid messages.
115
- */
116
- handle_message(message) {
117
- if (!message)
118
- return; // ignore invalid values
119
- const { id } = message;
120
- // TODO maybe log a warning/error?
121
- if (id == null)
122
- return; // ignore notifications and errors without ids
123
- // JSON-RPC responses require both an `id` and either a `result` or `error` field, but not both
124
- if ('result' in message) {
125
- this.resolve_request(id, message);
126
- }
127
- else if ('error' in message) {
128
- this.reject_request(id, message);
129
- }
130
- // ignore other messages
131
- }
132
- /**
133
- * Cancel a pending request without rejecting its deferred — just
134
- * cleanup. The caller's promise stays unsettled; pair with an external
135
- * resolution if needed.
136
- *
137
- * @mutates this - clears the timeout and removes the entry from `pending_requests`
138
- */
139
- cancel_request(id) {
140
- const request = this.pending_requests.get(id);
141
- if (!request) {
142
- return;
143
- }
144
- if (request.timeout) {
145
- clearTimeout(request.timeout);
146
- request.timeout = undefined;
147
- }
148
- // We don't reject the promise here, just clean up the tracking
149
- this.pending_requests.delete(id);
150
- }
151
- /**
152
- * Cancel all pending requests.
153
- * @param reason - optional reason to include in rejection
154
- * @mutates this - clears every timeout, marks each status `'failure'`,
155
- * rejects each deferred with `internal_error`, and empties `pending_requests`
156
- */
157
- cancel_all_requests(reason) {
158
- for (const [id, request] of this.pending_requests.entries()) {
159
- if (request.timeout) {
160
- clearTimeout(request.timeout);
161
- request.timeout = undefined;
162
- }
163
- request.status = 'failure';
164
- request.deferred.reject(new ThrownJsonrpcError(JSONRPC_ERROR_CODES.internal_error, // TODO canceled error?
165
- reason || 'request cancelled'));
166
- this.pending_requests.delete(id);
167
- }
168
- }
169
- }