@mauroandre/velojs 0.0.55 → 0.0.57
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/README.md +423 -3
- package/agent/AGENTS.md +216 -0
- package/agent/skills/velojs-live-loader/SKILL.md +410 -0
- package/dist/channels.d.ts +370 -0
- package/dist/channels.js +376 -0
- package/dist/client.js +56 -45
- package/dist/components.js +7 -6
- package/dist/events.d.ts +6 -0
- package/dist/events.js +1 -0
- package/dist/hooks.d.ts +26 -21
- package/dist/hooks.js +117 -98
- package/dist/live-loader.d.ts +120 -0
- package/dist/live-loader.js +141 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +144 -118
- package/dist/testing/index.d.ts +5 -1
- package/dist/testing/index.js +495 -412
- package/dist/testing/subscription.d.ts +18 -2
- package/dist/testing/types.d.ts +85 -0
- package/dist/types.d.ts +9 -0
- package/dist/vite.d.ts +15 -0
- package/dist/vite.js +431 -340
- package/package.json +1 -1
|
@@ -2,10 +2,26 @@
|
|
|
2
2
|
* SSE subscription helper — parses the SSE protocol from a streaming Response
|
|
3
3
|
* and exposes assertion-friendly methods for tests.
|
|
4
4
|
*/
|
|
5
|
-
import type { TestSubscription } from "./types.js";
|
|
5
|
+
import type { TestSubscription, NextOptions, ChannelEvent } from "./types.js";
|
|
6
6
|
interface BuildSubscriptionOptions {
|
|
7
7
|
response: Response;
|
|
8
8
|
parseJson?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Deliver `snapshot` events through `events`/`next()` too, not only via the
|
|
11
|
+
* `snapshot` getter. The live loader has no `message` events at all — every
|
|
12
|
+
* update is a snapshot — so its subscription reads them as plain events.
|
|
13
|
+
*/
|
|
14
|
+
snapshotEvents?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Live-loader mode: `slice` events travel through `events`/`next()` like the
|
|
17
|
+
* snapshots (the payload exactly as it went on the wire), and every arrival
|
|
18
|
+
* is recorded discriminated in `nextEvent()` — `{ type: "snapshot" |
|
|
19
|
+
* "slice", data }`. `next()` keeps the slice-1 contract: the raw `data`.
|
|
20
|
+
*/
|
|
21
|
+
channelEvents?: boolean;
|
|
9
22
|
}
|
|
10
|
-
export declare function buildSubscription<TEvent, TSnapshot>(opts: BuildSubscriptionOptions): Promise<TestSubscription<TEvent, TSnapshot
|
|
23
|
+
export declare function buildSubscription<TEvent, TSnapshot>(opts: BuildSubscriptionOptions): Promise<TestSubscription<TEvent, TSnapshot> & {
|
|
24
|
+
/** Next arrival discriminated by the mode that delivered it. */
|
|
25
|
+
nextEvent(opts: NextOptions): Promise<ChannelEvent<TEvent>>;
|
|
26
|
+
}>;
|
|
11
27
|
export {};
|
package/dist/testing/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { Hono, Context } from "hono";
|
|
|
5
5
|
import type { AppRoutes } from "../types.js";
|
|
6
6
|
import type { EventStream } from "../events.js";
|
|
7
7
|
import type { SocketHandler, SocketStub } from "../sockets.js";
|
|
8
|
+
import type { ChannelMap, ChannelRegistryOptions } from "../channels.js";
|
|
8
9
|
export type Cookies = Record<string, string>;
|
|
9
10
|
export type Headers = Record<string, string>;
|
|
10
11
|
export type Query = Record<string, string | string[]>;
|
|
@@ -28,6 +29,20 @@ export interface CreateTestAppOptions {
|
|
|
28
29
|
getSessionCookie?: (input: {
|
|
29
30
|
user: any;
|
|
30
31
|
}) => Promise<Cookies> | Cookies;
|
|
32
|
+
/**
|
|
33
|
+
* The app's live-loader channel map — the same object `app/channels.ts`
|
|
34
|
+
* exports. Registered before the app is created, so the channel routes and
|
|
35
|
+
* `emit()` can resolve partitions. May also be registered from `bootstrap`
|
|
36
|
+
* via `registerChannels()`.
|
|
37
|
+
*/
|
|
38
|
+
channels?: ChannelMap;
|
|
39
|
+
/**
|
|
40
|
+
* Options of the channel registration: the coalescing window of the
|
|
41
|
+
* invalidation mode and the emit log. `createTestApp` registers with
|
|
42
|
+
* `coalesceMs: 0` (immediate, deterministic) unless this option says
|
|
43
|
+
* otherwise; `logEmits` defaults to on, as in production.
|
|
44
|
+
*/
|
|
45
|
+
channelOptions?: ChannelRegistryOptions;
|
|
31
46
|
/**
|
|
32
47
|
* Serve the app over real TCP on this port, in addition to the in-memory
|
|
33
48
|
* API. Needed when an actor outside this process must reach the app over
|
|
@@ -80,6 +95,18 @@ export interface SubscribeOptions extends RequestOptions {
|
|
|
80
95
|
/** Channel ID — sent as `?channel=...`. */
|
|
81
96
|
channel?: string;
|
|
82
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Options of a live-loader channel connection.
|
|
100
|
+
*
|
|
101
|
+
* `params` are the route params the connection declares — the same ones the
|
|
102
|
+
* browser extracts from the URL and sends in `?_route=`. The server-side
|
|
103
|
+
* validation of the declared path runs for real: a key the module's path does
|
|
104
|
+
* not declare rejects the subscription (400), never silently ignores the input.
|
|
105
|
+
*/
|
|
106
|
+
export interface ChannelSubscribeOptions extends SubscribeOptions {
|
|
107
|
+
/** Route params the connection declares (serialized into `?_route=`). */
|
|
108
|
+
params?: Params;
|
|
109
|
+
}
|
|
83
110
|
export interface NextOptions {
|
|
84
111
|
/** Reject if no event arrives within this many ms. */
|
|
85
112
|
timeoutMs: number;
|
|
@@ -124,6 +151,35 @@ export interface TestSubscription<TEvent = any, TSnapshot = any> {
|
|
|
124
151
|
/** Force disconnect. Triggers per-channel source abortSignal server-side. */
|
|
125
152
|
close(): Promise<void>;
|
|
126
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* A live-loader channel subscription. Every update — the snapshot on connect,
|
|
156
|
+
* each emit's snapshot and each slice — arrives through `next()` as the `data`
|
|
157
|
+
* exactly as it went on the wire (a whole snapshot or a raw slice), so `await
|
|
158
|
+
* next({ timeoutMs })` is the clock: no sleep, no retry, no timing assertion.
|
|
159
|
+
*
|
|
160
|
+
* `nextEvent()` is the same arrival stream discriminated as
|
|
161
|
+
* `{ type: "snapshot" | "slice", data }` — use it when the test cares which
|
|
162
|
+
* mode delivered the update. `next()` and `nextEvent()` each advance their own
|
|
163
|
+
* cursor over the same arrivals, so use one or the other within a test.
|
|
164
|
+
*
|
|
165
|
+
* The connection only exists after the connect snapshot: await the first
|
|
166
|
+
* `next()` before emitting, and the server is guaranteed to have registered it.
|
|
167
|
+
*/
|
|
168
|
+
export interface TestChannelSubscription<T = any> extends TestSubscription<T, T> {
|
|
169
|
+
/** Next arrival, discriminated by the mode that delivered it. */
|
|
170
|
+
nextEvent(opts: NextOptions): Promise<ChannelEvent<T>>;
|
|
171
|
+
}
|
|
172
|
+
/** One channel arrival: a whole snapshot or a slice to be merged. */
|
|
173
|
+
export interface ChannelEvent<T = any> {
|
|
174
|
+
type: "snapshot" | "slice";
|
|
175
|
+
data: T;
|
|
176
|
+
}
|
|
177
|
+
/** The module (or moduleId) a channel belongs to, as `app.channel()` accepts it. */
|
|
178
|
+
export type ChannelModuleRef = string | {
|
|
179
|
+
metadata?: {
|
|
180
|
+
moduleId?: string;
|
|
181
|
+
} | undefined;
|
|
182
|
+
};
|
|
127
183
|
export interface MockContextOptions {
|
|
128
184
|
user?: any;
|
|
129
185
|
params?: Params;
|
|
@@ -168,6 +224,35 @@ export interface TestApp {
|
|
|
168
224
|
*/
|
|
169
225
|
loader<T = any>(fn: Function | string, opts?: LoaderRequestOptions): Promise<T | TestResponse>;
|
|
170
226
|
subscribe<TEvent = any, TSnapshot = any>(stream: EventStream<TEvent, TSnapshot> | string, opts?: SubscribeOptions): Promise<TestSubscription<TEvent, TSnapshot>>;
|
|
227
|
+
/**
|
|
228
|
+
* Open a live-loader channel connection for a module's declared channel.
|
|
229
|
+
*
|
|
230
|
+
* The module may be the imported route module (its `metadata.moduleId` is
|
|
231
|
+
* read) or a moduleId string. Cookies (and therefore the principal) come
|
|
232
|
+
* from the options or from `app.as(user)`; the route node's middlewares run
|
|
233
|
+
* for real, so an unauthenticated subscription is denied exactly as in
|
|
234
|
+
* production. `params` are the route params the connection declares — the
|
|
235
|
+
* server validates their keys against the module's path.
|
|
236
|
+
*
|
|
237
|
+
* ```ts
|
|
238
|
+
* const sub = await app.as({ id: 7 }).channel(Expenses, "teamExpenses");
|
|
239
|
+
* await sub.next({ timeoutMs: 1000 }); // snapshot on connect
|
|
240
|
+
* await emit("teamExpenses", { user: { teamId: 7 } });
|
|
241
|
+
* expect(await sub.next({ timeoutMs: 1000 })).toEqual({ teamTotal: 2 });
|
|
242
|
+
*
|
|
243
|
+
* // A route with `:params`: the connection declares them and the loader
|
|
244
|
+
* // re-executes with them.
|
|
245
|
+
* const project = await app.channel(Project, "projectFiles", { params: { id: "7" } });
|
|
246
|
+
*
|
|
247
|
+
* // The same arrivals discriminated: nextEvent() tells a snapshot from a slice.
|
|
248
|
+
* await emit(Expenses, "teamExpenses", { user: { teamId: 7 } }, { teamTotal: 880 });
|
|
249
|
+
* expect(await sub.nextEvent({ timeoutMs: 1000 })).toEqual({
|
|
250
|
+
* type: "slice",
|
|
251
|
+
* data: { teamTotal: 880 },
|
|
252
|
+
* });
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
channel<T = any>(module: ChannelModuleRef, name: string, opts?: ChannelSubscribeOptions): Promise<TestChannelSubscription<T>>;
|
|
171
256
|
/**
|
|
172
257
|
* Open an in-memory session against a `socket_*` handler.
|
|
173
258
|
*
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,15 @@ export interface Metadata {
|
|
|
20
20
|
export interface RouteModule {
|
|
21
21
|
Component: ComponentType<any>;
|
|
22
22
|
loader?: (args: LoaderArgs) => Promise<any>;
|
|
23
|
+
/**
|
|
24
|
+
* Channel names whose state must keep this module's loader data faithful.
|
|
25
|
+
* The opt-in for the live loader: with `channels`, the framework derives an
|
|
26
|
+
* internal SSE route per channel (`/_channel/{moduleId}/{channel}`) and the
|
|
27
|
+
* client opens one connection per (module, channel) after hydration. A
|
|
28
|
+
* module that declares `channels` without a `loader` is an explicit
|
|
29
|
+
* dev/build error. Requires an entry per name in `app/channels.ts`.
|
|
30
|
+
*/
|
|
31
|
+
channels?: readonly string[];
|
|
23
32
|
staticPaths?: () => Promise<Record<string, string>[]>;
|
|
24
33
|
metadata?: Metadata;
|
|
25
34
|
[key: `action_${string}`]: (args: ActionArgs<any>) => Promise<any>;
|
package/dist/vite.d.ts
CHANGED
|
@@ -66,6 +66,21 @@ export declare function buildFullPathMap(code: string): Map<string, PathInfo>;
|
|
|
66
66
|
* Percorre recursivamente a árvore de rotas e coleta moduleName → { fullPath, path }
|
|
67
67
|
*/
|
|
68
68
|
export declare function collectFullPaths(elements: (t.Expression | t.SpreadElement | null)[], parentPath: string, result: Map<string, PathInfo>): void;
|
|
69
|
+
/**
|
|
70
|
+
* Names declared by a module's `channels` export — `null` when the module does
|
|
71
|
+
* not declare the convention (no `channels` array export at all).
|
|
72
|
+
*
|
|
73
|
+
* Only the array form is a channel declaration: `app/channels.ts` also exports
|
|
74
|
+
* a `channels` binding, but an object (the name → resolver map), and must never
|
|
75
|
+
* be read as a module declaring channels.
|
|
76
|
+
*/
|
|
77
|
+
export declare function declaredChannelNames(code: string): string[] | null;
|
|
78
|
+
/**
|
|
79
|
+
* Channel names declared in the app's channel map file (`app/channels.ts`) —
|
|
80
|
+
* the canonical list the framework validates module declarations against.
|
|
81
|
+
* A missing file yields an empty set, which makes every declaration unknown.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readChannelMapNames(file: string): Set<string>;
|
|
69
84
|
/**
|
|
70
85
|
* @hono/vite-dev-server treats `exclude` as all-or-nothing: passing one
|
|
71
86
|
* replaces the defaults entirely. We compose instead — the upstream defaults
|