@voltro/client 0.55.0 → 0.57.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/form.js CHANGED
@@ -1,2 +1,2 @@
1
- import { C as e, S as t, _ as n, a as r, b as i, g as a, i as o, n as s, o as c, r as l, t as u, v as d, w as f, x as p, y as m } from "./formData-DtlF8t8h.js";
1
+ import { C as e, S as t, _ as n, a as r, b as i, g as a, i as o, n as s, o as c, r as l, t as u, v as d, w as f, x as p, y as m } from "./formData-He_L4ozH.js";
2
2
  export { u as FORM_FLASH_SCRIPT_ID, s as FORM_KEY_FIELD, l as FORM_PATH_PREFIX, o as FORM_REDIRECT_FIELD, a as decodeFieldIssues, n as documentLocale, r as formActionPath, c as formDataToInput, p as formField, t as formSections, d as resolveValidationMessage, m as restrictToSchema, e as schemaToColumns, f as schemaToFields, i as validateFields };
@@ -187,17 +187,20 @@ var r = (e) => ({ jsonSchema: { "x-voltro": e } }), i = "#/$defs/", a = (e) => e
187
187
  "validation.invalidFileType": "Dieser Dateityp ist nicht erlaubt",
188
188
  "validation.fileTooLarge": "Die Datei ist zu groß"
189
189
  }
190
- }, b = (e, t) => e.replace(/\{(\w+)\}/g, (e, n) => {
191
- let r = t?.[n];
192
- return r === void 0 ? e : String(r);
193
- }), x = () => {
190
+ }, b = (e, t) => {
191
+ let n = !1, r = e.replace(/\{(\w+)\}/g, (e, r) => {
192
+ let i = t?.[r];
193
+ return i === void 0 ? (n = !0, e) : String(i);
194
+ });
195
+ return n ? void 0 : r;
196
+ }, x = () => {
194
197
  let e = globalThis.document?.documentElement?.lang ?? "";
195
198
  return e === "" ? "en" : e;
196
199
  }, S = (e, t) => {
197
200
  let n = t?.messages?.(e.id, e.params, e.fallback);
198
201
  if (n !== void 0) return n;
199
202
  let r = y[(t?.locale ?? x()).toLowerCase().split("-")[0] ?? "en"]?.[e.id] ?? y.en?.[e.id];
200
- return r === void 0 ? e.fallback : b(r, e.params);
203
+ return (r === void 0 ? void 0 : b(r, e.params)) ?? e.fallback;
201
204
  }, C = (e) => {
202
205
  let t = /^([a-z][\w-]*(?:\.[\w-]+)+)\|(\{.*\})$/.exec(e);
203
206
  if (t !== null && t[1] !== void 0 && t[2] !== void 0) try {
package/dist/index.d.ts CHANGED
@@ -960,6 +960,15 @@ export declare interface EventValidation {
960
960
  readonly errors: Readonly<Record<string, string>>;
961
961
  }
962
962
 
963
+ /** One currently-failing tag. */
964
+ export declare interface FailedCall {
965
+ readonly tag: string;
966
+ readonly source: 'mutation' | 'action' | 'subscription';
967
+ readonly error: unknown;
968
+ /** When it failed, so a UI can say "since 12:04" rather than only "broken". */
969
+ readonly at: number;
970
+ }
971
+
963
972
  export declare interface FieldDescriptor {
964
973
  /** Property name on the input object. */
965
974
  readonly name: string;
@@ -1283,15 +1292,40 @@ export declare interface FrameworkRuntimes {
1283
1292
  readonly get: (name: string) => ApiHandle;
1284
1293
  /** Enumerate the names of currently-installed apis (for debug). */
1285
1294
  readonly names: ReadonlyArray<string>;
1295
+ /**
1296
+ * "Something in this tree needs `apiName`."
1297
+ *
1298
+ * Under `web.api.connect: 'lazy'` the provider does not open a socket until
1299
+ * this is called, so a page that reads no data never opens one. Under the
1300
+ * default `'eager'` it is a no-op — every api connects at mount, as before.
1301
+ *
1302
+ * It exists because the alternative was worse in a specific, measured way:
1303
+ * `interactive: 'full'` is the DEFAULT render mode, and the client opened a
1304
+ * WebSocket on every such page whether or not it subscribed. A pre-rendered
1305
+ * documentation page with no data at all opened one (measured in a real
1306
+ * browser: 0 sockets on `interactive: 'none'` / `'islands'`, one on `'full'`).
1307
+ * And an open socket keeps a dormancy-managed instance awake — `isIdleNow`
1308
+ * returns false while `connectedClients() > 0` — so one browser tab on a
1309
+ * pricing page prevented scale-to-zero indefinitely.
1310
+ *
1311
+ * Optional so a foreign host embedding `FrameworkRuntimesContext` by hand
1312
+ * keeps working; absent means "already connected", which is the safe reading.
1313
+ */
1314
+ readonly demand?: (apiName: string) => void;
1286
1315
  }
1287
1316
 
1288
1317
  export declare const FrameworkRuntimesContext: Context<FrameworkRuntimes | null>;
1289
1318
 
1290
- export declare const FrameworkRuntimesProvider: ({ apis, children, }: FrameworkRuntimesProviderProps) => ReactNode;
1319
+ export declare const FrameworkRuntimesProvider: ({ apis, children, demand, }: FrameworkRuntimesProviderProps) => ReactNode;
1291
1320
 
1292
1321
  export declare interface FrameworkRuntimesProviderProps {
1293
1322
  readonly apis: ReadonlyMap<string, ApiHandle>;
1294
1323
  readonly children: ReactNode;
1324
+ /** Passed straight through to the context so `useFrameworkApi` can say that
1325
+ * something in the tree needs an api. The web boot supplies it under
1326
+ * `web.api.connect: 'lazy'`; a foreign host may omit it, which reads as
1327
+ * "already connected". */
1328
+ readonly demand?: (apiName: string) => void;
1295
1329
  }
1296
1330
 
1297
1331
  /** Browser-safe unique idempotency key: prefer `crypto.randomUUID`, fall back to
@@ -2052,7 +2086,17 @@ export declare interface ResumableAgentStreamState<E> {
2052
2086
  /** The UNWRAPPED inner events received so far, in order (deduped by seq). */
2053
2087
  readonly events: ReadonlyArray<E>;
2054
2088
  readonly status: ResumableStreamStatus;
2055
- /** How many times the transport dropped + auto-reconnected this run. */
2089
+ /**
2090
+ * How many times the transport dropped + auto-reconnected this run — the
2091
+ * RUN TOTAL, which is NOT the number `maxReconnects` bounds.
2092
+ *
2093
+ * `maxReconnects` is a ceiling on CONSECUTIVE reconnects that made no
2094
+ * progress, and the counter behind it resets whenever a reconnect delivers an
2095
+ * event. So this total can exceed it on a long, healthy stream, and rendering
2096
+ * "attempt {reconnects} of {maxReconnects}" puts `5/2` on the screen — which
2097
+ * is what a deployment saw. The two names sit next to each other and read as a
2098
+ * pair; they are not one, and there is no third field that is.
2099
+ */
2056
2100
  readonly reconnects: number;
2057
2101
  /** Set when status === 'error'. */
2058
2102
  readonly error: unknown | undefined;
@@ -2455,10 +2499,50 @@ export declare class RpcErrorBus {
2455
2499
  on(listener: RpcErrorListener): () => void;
2456
2500
  /** Emit. Pipeline code (useMutation / subscriptionCache) calls this. */
2457
2501
  emit(event: RpcError): void;
2502
+ /**
2503
+ * Subscribe to SUCCESSES — the channel that makes a consecutive-failure
2504
+ * ceiling mean what it says.
2505
+ *
2506
+ * ── Why the bus grew a second channel ──────────────────────────────────
2507
+ *
2508
+ * `wireAuthRefresh` bounds how often it may rebuild the transport on
2509
+ * `Unauthenticated`, and `maxConsecutive` bounds the TOTAL — after that many
2510
+ * refreshes *with no successful call in between*, it stops and leaves the
2511
+ * error to the app, because at that point the credential is not stale, it is
2512
+ * refused. "With no successful call in between" was carried by
2513
+ * `AuthRefreshHandle.noteSuccess()`, a method the HOST had to remember to
2514
+ * call, and the only host never called it.
2515
+ *
2516
+ * What that turns the ceiling into is the part worth writing down: not a
2517
+ * loop, but a LIFETIME budget. A tab that rotates its token three times over
2518
+ * a few hours exhausts it and then never refreshes again for as long as it
2519
+ * stays open — every later expiry rejects every subscription it opens, each
2520
+ * rejected entry is terminal for its transport, and the screen waits on a
2521
+ * spinner that has nothing left to wait for.
2522
+ *
2523
+ * A host that must remember something will eventually not, which is the
2524
+ * shape this repo keeps meeting. So the signal comes from the pipeline that
2525
+ * already knows — the same one that emits the errors — and no host has to
2526
+ * wire anything. `noteSuccess()` stays for a host with a better signal of its
2527
+ * own; it is no longer what the policy depends on.
2528
+ */
2529
+ onSuccess(listener: RpcSuccessListener): () => void;
2530
+ /** Emit a success. Called by the same pipeline code that calls `emit`. */
2531
+ emitSuccess(event: RpcSuccess): void;
2458
2532
  }
2459
2533
 
2460
2534
  export declare type RpcErrorListener = (event: RpcError) => void;
2461
2535
 
2536
+ /** A call that SUCCEEDED. Carries only its source and tag — there is no payload
2537
+ * here on purpose: this channel exists so cross-cutting policy can tell
2538
+ * "working again" from "still broken", not so listeners can read data. */
2539
+ export declare interface RpcSuccess {
2540
+ readonly source: 'mutation' | 'action' | 'subscription';
2541
+ readonly tag: string;
2542
+ }
2543
+
2544
+ export declare type RpcSuccessListener = (event: RpcSuccess) => void;
2545
+
2462
2546
  /**
2463
2547
  * Run a sequence. Resolves with a discriminated result rather than rejecting —
2464
2548
  * the same "handled" semantics `useMutation`/`useAction` use, so the call site
@@ -3218,6 +3302,14 @@ export declare interface SubscriptionFailed extends SubscriptionMeta {
3218
3302
  readonly error: unknown;
3219
3303
  }
3220
3304
 
3305
+ export declare interface SubscriptionHealth {
3306
+ /** Every tag whose last outcome on this api was a failure, newest first. */
3307
+ readonly failed: ReadonlyArray<FailedCall>;
3308
+ /** `false` as soon as anything is failing — the positive form, so a component
3309
+ * reads `if (!healthy)` instead of `if (failed.length > 0)`. */
3310
+ readonly healthy: boolean;
3311
+ }
3312
+
3221
3313
  /**
3222
3314
  * Deliberately NOT subscribed — `skip: true`.
3223
3315
  *
@@ -3715,6 +3807,13 @@ export declare type UploadStatus = 'idle' | 'uploading' | 'success' | 'error';
3715
3807
  * `<PermissionProvider>`. The hook form of `decideAccess`. */
3716
3808
  export declare const useAccessDecision: (guards: ReadonlyArray<ManifestGuard> | undefined) => AccessDecision;
3717
3809
 
3810
+ /**
3811
+ * Invoke a server `defineAction` procedure by tag. Unlike `useMutation`,
3812
+ * an action has no optimistic / cache surface — it's a unary call whose
3813
+ * effects (sends, captures, side-effecting writes the framework must NOT
3814
+ * preview locally) only become visible when the server pushes a delta on
3815
+ * an independent subscription.
3816
+ */
3718
3817
  export declare const useAction: <Input = unknown, Output = unknown>(apiName: string, rpcTag: string) => ActionState<Input, Output>;
3719
3818
 
3720
3819
  export declare const useAgent: (apiName: string, rpcTag: string) => AgentControls;
@@ -3858,7 +3957,26 @@ export declare interface UseEventOptions {
3858
3957
  readonly apiName?: string;
3859
3958
  }
3860
3959
 
3861
- export declare const useFormBinding: <Input extends Record<string, unknown> = Record<string, unknown>, Output = unknown>(apiName: string, mutationTag: string, options: UseFormBindingOptions<Input>) => FormBinding<Input, Output>;
3960
+ export declare const useFormBinding: <Input extends Record<string, unknown> = Record<string, unknown>, Output = unknown>(apiName: string,
3961
+ /**
3962
+ * The mutation to submit to — a tag, or a FUNCTION of the current values.
3963
+ *
3964
+ * The function form exists for the case where the form decides which mutation
3965
+ * it is: a calendar entry becomes a recurring SERIES the moment "repeats" is
3966
+ * ticked, and the series mutation takes eleven more fields. Deriving the tag
3967
+ * outside the binding is not available — the values belong to the binding and
3968
+ * do not exist before it — and re-mounting with a different tag resets the
3969
+ * engine, which throws away everything the user has typed.
3970
+ *
3971
+ * It does not: the engine is constructed once and never rebuilt, so the tag
3972
+ * may change on any render and the values, touched state and errors all
3973
+ * survive it. What changes with the tag is the SCHEMA the binding validates
3974
+ * against, which is the point.
3975
+ *
3976
+ * On the first render there are no values yet, so the function is called with
3977
+ * the seeded defaults — the same thing the user is looking at.
3978
+ */
3979
+ mutationTag: string | ((values: Partial<Input>) => string), options: UseFormBindingOptions<Input>) => FormBinding<Input, Output>;
3862
3980
 
3863
3981
  /** The enclosing binding, if a {@link FormBindingProvider} is mounted. */
3864
3982
  export declare const useFormBindingContext: () => FormBinding<Record<string, unknown>, unknown> | undefined;
@@ -4153,6 +4271,19 @@ export declare function useSubscription<T = unknown>(apiName: string, rpcTag: st
4153
4271
  readonly skip?: boolean;
4154
4272
  }): SubscriptionState<T> | SubscriptionIdle;
4155
4273
 
4274
+ /**
4275
+ * The api's currently-refused calls.
4276
+ *
4277
+ * A tag enters the list when a call for it fails and LEAVES it when a call for
4278
+ * the same tag succeeds — keyed by tag rather than counted, because the
4279
+ * question a screen asks is "is this working now", and two failures of one tag
4280
+ * are one broken thing.
4281
+ *
4282
+ * const { healthy, failed } = useSubscriptionHealth('app')
4283
+ * if (!healthy) return <Banner tags={failed.map((f) => f.tag)} />
4284
+ */
4285
+ export declare const useSubscriptionHealth: (apiName: string) => SubscriptionHealth;
4286
+
4156
4287
  /** The column shape a `<DataTable query=…>` will render — from the query's
4157
4288
  * output Schema. */
4158
4289
  export declare const useTableSkeleton: (apiName: string, queryTag: string) => ReadonlyArray<FieldDescriptor>;