@mauroandre/velojs 0.0.60 → 0.0.61

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.
@@ -7,6 +7,14 @@
7
7
  * family as `/_action` and `/_event` — which inherits the middlewares of the
8
8
  * module's route node and serves the module's loader as a stream of snapshots.
9
9
  *
10
+ * The tab-facing twin is the live action: a module declares
11
+ * `liveActions = { actionName: [channel, …] }` and the framework derives
12
+ * `GET /_live/{moduleId}/{actionName}` — a subscription whose args travel in
13
+ * `?_args=` and whose source is the action itself (a parameterized read,
14
+ * re-executed with those args). The subscription enters the SAME groups by
15
+ * (channel, partition): one `emit` by name wakes the loader connections and
16
+ * the live subscriptions of the partition, each re-executing its own source.
17
+ *
10
18
  * The app owns the partition policy in `app/channels.ts`, a name → resolver map:
11
19
  *
12
20
  * ```ts
@@ -15,7 +23,8 @@
15
23
  * };
16
24
  * ```
17
25
  *
18
- * On subscribe the resolver receives `{ user, params }`: `user` is the
26
+ * On subscribe the resolver receives `{ user, params }` (a loader connection)
27
+ * or `{ user, args }` (a live action subscription): `user` is the
19
28
  * materialized principal (`c.get("user")`, the house key — `undefined` when the
20
29
  * app does not materialize one, so a public channel ignores it and an
21
30
  * authenticated one returns `null`, which denies the subscription with 403) and
@@ -56,7 +65,7 @@
56
65
  * dropped and closed instead of receiving data of a group it left.
57
66
  */
58
67
  import type { Context, Hono, MiddlewareHandler } from "hono";
59
- import type { LoaderArgs, RouteModule } from "./types.js";
68
+ import type { ActionArgs, LoaderArgs, RouteModule } from "./types.js";
60
69
  import { type TelemetryStatus, type TelemetryTrace } from "./telemetry.js";
61
70
  /** Result of a partition resolver. `null`/`undefined` denies the subscription. */
62
71
  export type ChannelScopeResult = string | null | undefined;
@@ -76,6 +85,12 @@ export type ChannelScopeResult = string | null | undefined;
76
85
  export interface ChannelScopeContext {
77
86
  user?: any;
78
87
  params?: Record<string, any>;
88
+ /**
89
+ * The declared arguments of a **live action** subscription (`kind: "live"`).
90
+ * The tab-facing twin of `params`: the address the subscription itself
91
+ * declared, not something the route declares.
92
+ */
93
+ args?: Record<string, any> | undefined;
79
94
  }
80
95
  /**
81
96
  * What `onGroupOpen` receives: the partition key of the group that just got its
@@ -86,14 +101,27 @@ export interface ChannelScopeContext {
86
101
  * partitioned by principal, declared in a module with `:params`, mixes
87
102
  * resources in the same group and the hook's params are the first connection's
88
103
  * — arbitrary for the rest; such a channel must not consume them here.
104
+ *
105
+ * `args` is the live-action half of the same address: when the connection that
106
+ * opened the group is a live action subscription, they are the args that
107
+ * subscription declared (with the same arbitrariness caveat when the partition
108
+ * does not derive from them).
89
109
  */
90
110
  export interface ChannelGroupOpenContext {
91
111
  partition: string;
92
112
  params: Record<string, string>;
113
+ /** The declared args of the connection that opened the group (live actions). */
114
+ args?: Record<string, unknown> | undefined;
93
115
  }
94
- /** What `onGroupClose` receives: the partition key of the group being torn down. */
116
+ /**
117
+ * What `onGroupClose` receives: the partition key of the group being torn
118
+ * down and, when the group was opened by a live action subscription, the args
119
+ * of that connection — the same fields `onGroupOpen` saw.
120
+ */
95
121
  export interface ChannelGroupCloseContext {
96
122
  partition: string;
123
+ /** The declared args of the connection that opened the group (live actions). */
124
+ args?: Record<string, unknown> | undefined;
97
125
  }
98
126
  /**
99
127
  * One entry of the app's channel map (`app/channels.ts`).
@@ -206,13 +234,31 @@ export interface ChannelConnection {
206
234
  /** Partition key resolved at subscribe time — never from client input. */
207
235
  partition: string;
208
236
  moduleId: string;
237
+ /**
238
+ * Which surface the connection feeds: `"loader"` (the page's live-loader
239
+ * connection — the default) or `"live"` (a live action subscription, the
240
+ * tab-facing twin).
241
+ */
242
+ kind?: "loader" | "live";
209
243
  /**
210
244
  * The principal materialized at subscribe (`c.get("user")`), captured for
211
245
  * the partition revalidation of every emit — the scope re-runs against it.
212
246
  */
213
247
  principal: unknown;
214
- loader: (args: LoaderArgs) => Promise<unknown>;
215
- /** The subscribing request's context — the loader re-runs against it. */
248
+ /** The module's loader — the data source of a `"loader"` connection. */
249
+ loader?: (args: LoaderArgs) => Promise<unknown>;
250
+ /** The live action — the data source of a `"live"` connection. */
251
+ action?: (args: ActionArgs<any>) => Promise<unknown>;
252
+ /** The action name without the `action_` prefix (`"live"` connections). */
253
+ actionName?: string | undefined;
254
+ /** The subscription's declared args (`"live"` connections) — the tab's address. */
255
+ args?: Record<string, unknown> | undefined;
256
+ /**
257
+ * Identifies one live action subscription across the N group entries it
258
+ * holds (one per declared channel) — the inspector's key.
259
+ */
260
+ liveId?: string | undefined;
261
+ /** The subscribing request's context — the loader/action re-runs against it. */
216
262
  c: Context;
217
263
  params: Record<string, string>;
218
264
  query: Record<string, string>;
@@ -260,25 +306,47 @@ export interface ChannelInspectWindow {
260
306
  channel: string;
261
307
  partition: string;
262
308
  }
309
+ /** One live action subscription, as the inspector reports it. */
310
+ export interface ChannelInspectLiveAction {
311
+ /** The route module holding the action (`metadata.moduleId`). */
312
+ moduleId: string;
313
+ /** The action name, without the `action_` prefix. */
314
+ action: string;
315
+ /** Every (channel, partition) group the subscription entered. */
316
+ channels: {
317
+ channel: string;
318
+ partition: string;
319
+ }[];
320
+ /** The args the subscription declared — its address. */
321
+ args: Record<string, unknown> | null;
322
+ /** ISO timestamp of the subscription's last delivery; `null` when none yet. */
323
+ lastDeliveryAt: string | null;
324
+ }
263
325
  /** The full live state of the channel registry, as `inspectChannels()` sees it. */
264
326
  export interface ChannelInspectReport {
265
327
  channels: ChannelInspectChannel[];
266
328
  /** Open coalescing windows, per (channel, partition). */
267
329
  openCoalesceWindows: ChannelInspectWindow[];
330
+ /** The live action subscriptions, per (moduleId, action, args). */
331
+ liveActions: ChannelInspectLiveAction[];
268
332
  totalConnections: number;
269
333
  }
270
334
  /**
271
335
  * Server-side snapshot of the live channels: per channel, the groups
272
336
  * identified by (moduleId, partition key), each with its connection count and
273
- * the timestamp of its last delivery; the coalescing windows still open; and
274
- * the grand totals. Always available — the app decides how (and with what
275
- * guard) to expose it; the dev server exposes it as `GET /_channel-inspect`.
337
+ * the timestamp of its last delivery; the coalescing windows still open; the
338
+ * live action subscriptions (module, action, args and the groups they hold);
339
+ * and the grand totals — where each live subscription counts once, however
340
+ * many channel groups it entered. Always available — the app decides how (and
341
+ * with what guard) to expose it; the dev server exposes it as
342
+ * `GET /_channel-inspect`.
276
343
  */
277
344
  export declare function inspectChannels(): ChannelInspectReport;
278
345
  /** Test-only. Drops every live connection, group and open window. */
279
346
  export declare function __resetChannels(): void;
280
347
  /**
281
- * Executes the connection's loader with the connection's own principal and
348
+ * Executes the connection's data source (the loader of a page connection, the
349
+ * action of a live subscription) with the connection's own principal and
282
350
  * pushes the result as a snapshot. Per-connection execution (rather than one
283
351
  * execution per partition) is deliberate: every subscriber always receives the
284
352
  * snapshot computed with its own scope, which makes the homogeneous-partition
@@ -286,8 +354,8 @@ export declare function __resetChannels(): void;
286
354
  * amortizes is the number of *rounds*, not the executions inside one — a single
287
355
  * computation serving the whole partition is a later slice.
288
356
  *
289
- * A loader that throws reports the failure on the connection (`error` event)
290
- * instead of dying silently; a loader that returns a `Response` (a redirect
357
+ * A source that throws reports the failure on the connection (`error` event)
358
+ * instead of dying silently; a source that returns a `Response` (a redirect
291
359
  * short-circuit) has no state to send.
292
360
  */
293
361
  export declare function pushSnapshot(conn: ChannelConnection): Promise<TelemetryStatus>;
@@ -375,3 +443,41 @@ export declare function parseRouteParamsQuery(raw: string | undefined | null, de
375
443
  * whose partition no longer holds.
376
444
  */
377
445
  export declare function registerChannelRoute(app: Hono, path: string, channel: string, module: RouteModule, moduleId: string, middlewares: MiddlewareHandler[]): void;
446
+ /** Query key carrying the subscription's declared args. */
447
+ export declare const LIVE_ARGS_QUERY = "_args";
448
+ /**
449
+ * The serialized-args ceiling of a live action subscription, in bytes. Args
450
+ * travel on the URL — the ceiling keeps the address an address, not a payload;
451
+ * a bigger read wants an action call plus channels on the page.
452
+ */
453
+ export declare const LIVE_ARGS_MAX_BYTES = 4096;
454
+ /**
455
+ * Parses and validates the `?_args=` payload of a live action subscription.
456
+ *
457
+ * The client declares the args of the tab it opened; they are the subscription's
458
+ * identity (a change reconnects) and the `body` of every re-execution. An
459
+ * absent payload is the empty object — a live action may take no arguments.
460
+ */
461
+ export declare function parseLiveArgsQuery(raw: string | undefined | null): {
462
+ args: Record<string, unknown>;
463
+ error: string | null;
464
+ };
465
+ /**
466
+ * Registers the internal SSE route of one declared live action:
467
+ * `GET /_live/{moduleId}/{actionName}`, inheriting the route node's
468
+ * middlewares — the subscription is guarded exactly like the action call and
469
+ * the page are.
470
+ *
471
+ * The action runs once on subscribe (the snapshot of connection is the whole
472
+ * loading gesture — no initial POST exists) and once per emit in any of the
473
+ * declared channels, always with the args declared in the subscription as
474
+ * `body`. The subscription enters the groups of every declared channel: an
475
+ * emit in any one delivers.
476
+ *
477
+ * Guards are loud: an action that does not exist in the module, or a channel
478
+ * with no entry in `app/channels.ts`, is an explicit error — never a silent
479
+ * inert route. Args above `LIVE_ARGS_MAX_BYTES` reject the subscription with
480
+ * 400, naming the action and the limit; a scope that resolves to nothing (or
481
+ * throws) denies it with 403.
482
+ */
483
+ export declare function registerLiveActionRoute(app: Hono, path: string, actionName: string, channels: readonly string[], module: RouteModule, moduleId: string, middlewares: MiddlewareHandler[]): void;