@mauroandre/velojs 0.0.54 → 0.0.56
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 +332 -3
- package/agent/AGENTS.md +149 -0
- package/agent/skills/velojs-getting-started/SKILL.md +2 -0
- package/agent/skills/velojs-live-loader/SKILL.md +329 -0
- package/agent/skills/velojs-production-deploy/SKILL.md +6 -0
- package/agent/skills/velojs-server-api/SKILL.md +1 -0
- package/agent/skills/velojs-type-reference/SKILL.md +1 -0
- package/dist/channels.d.ts +289 -0
- package/dist/channels.js +283 -0
- package/dist/client.js +55 -45
- package/dist/components.js +7 -6
- package/dist/config.d.ts +5 -3
- 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 +87 -0
- package/dist/live-loader.js +131 -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 +507 -427
- package/dist/testing/subscription.d.ts +18 -2
- package/dist/testing/types.d.ts +68 -0
- package/dist/types.d.ts +9 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.js +458 -359
- 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
|
|
@@ -124,6 +139,35 @@ export interface TestSubscription<TEvent = any, TSnapshot = any> {
|
|
|
124
139
|
/** Force disconnect. Triggers per-channel source abortSignal server-side. */
|
|
125
140
|
close(): Promise<void>;
|
|
126
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* A live-loader channel subscription. Every update — the snapshot on connect,
|
|
144
|
+
* each emit's snapshot and each slice — arrives through `next()` as the `data`
|
|
145
|
+
* exactly as it went on the wire (a whole snapshot or a raw slice), so `await
|
|
146
|
+
* next({ timeoutMs })` is the clock: no sleep, no retry, no timing assertion.
|
|
147
|
+
*
|
|
148
|
+
* `nextEvent()` is the same arrival stream discriminated as
|
|
149
|
+
* `{ type: "snapshot" | "slice", data }` — use it when the test cares which
|
|
150
|
+
* mode delivered the update. `next()` and `nextEvent()` each advance their own
|
|
151
|
+
* cursor over the same arrivals, so use one or the other within a test.
|
|
152
|
+
*
|
|
153
|
+
* The connection only exists after the connect snapshot: await the first
|
|
154
|
+
* `next()` before emitting, and the server is guaranteed to have registered it.
|
|
155
|
+
*/
|
|
156
|
+
export interface TestChannelSubscription<T = any> extends TestSubscription<T, T> {
|
|
157
|
+
/** Next arrival, discriminated by the mode that delivered it. */
|
|
158
|
+
nextEvent(opts: NextOptions): Promise<ChannelEvent<T>>;
|
|
159
|
+
}
|
|
160
|
+
/** One channel arrival: a whole snapshot or a slice to be merged. */
|
|
161
|
+
export interface ChannelEvent<T = any> {
|
|
162
|
+
type: "snapshot" | "slice";
|
|
163
|
+
data: T;
|
|
164
|
+
}
|
|
165
|
+
/** The module (or moduleId) a channel belongs to, as `app.channel()` accepts it. */
|
|
166
|
+
export type ChannelModuleRef = string | {
|
|
167
|
+
metadata?: {
|
|
168
|
+
moduleId?: string;
|
|
169
|
+
} | undefined;
|
|
170
|
+
};
|
|
127
171
|
export interface MockContextOptions {
|
|
128
172
|
user?: any;
|
|
129
173
|
params?: Params;
|
|
@@ -168,6 +212,30 @@ export interface TestApp {
|
|
|
168
212
|
*/
|
|
169
213
|
loader<T = any>(fn: Function | string, opts?: LoaderRequestOptions): Promise<T | TestResponse>;
|
|
170
214
|
subscribe<TEvent = any, TSnapshot = any>(stream: EventStream<TEvent, TSnapshot> | string, opts?: SubscribeOptions): Promise<TestSubscription<TEvent, TSnapshot>>;
|
|
215
|
+
/**
|
|
216
|
+
* Open a live-loader channel connection for a module's declared channel.
|
|
217
|
+
*
|
|
218
|
+
* The module may be the imported route module (its `metadata.moduleId` is
|
|
219
|
+
* read) or a moduleId string. Cookies (and therefore the principal) come
|
|
220
|
+
* from the options or from `app.as(user)`; the route node's middlewares run
|
|
221
|
+
* for real, so an unauthenticated subscription is denied exactly as in
|
|
222
|
+
* production.
|
|
223
|
+
*
|
|
224
|
+
* ```ts
|
|
225
|
+
* const sub = await app.as({ id: 7 }).channel(Expenses, "teamExpenses");
|
|
226
|
+
* await sub.next({ timeoutMs: 1000 }); // snapshot on connect
|
|
227
|
+
* await emit("teamExpenses", { teamId: 7 });
|
|
228
|
+
* expect(await sub.next({ timeoutMs: 1000 })).toEqual({ teamTotal: 2 });
|
|
229
|
+
*
|
|
230
|
+
* // The same arrivals discriminated: nextEvent() tells a snapshot from a slice.
|
|
231
|
+
* await emit(Expenses, "teamExpenses", { teamId: 7 }, { teamTotal: 880 });
|
|
232
|
+
* expect(await sub.nextEvent({ timeoutMs: 1000 })).toEqual({
|
|
233
|
+
* type: "slice",
|
|
234
|
+
* data: { teamTotal: 880 },
|
|
235
|
+
* });
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
channel<T = any>(module: ChannelModuleRef, name: string, opts?: SubscribeOptions): Promise<TestChannelSubscription<T>>;
|
|
171
239
|
/**
|
|
172
240
|
* Open an in-memory session against a `socket_*` handler.
|
|
173
241
|
*
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PluginOption } from "vite";
|
|
2
2
|
import type { VeloConfig } from "./config.js";
|
|
3
3
|
import * as t from "@babel/types";
|
|
4
4
|
export declare function injectMetadata(code: string, moduleId: string, fullPath?: string, path?: string): string;
|
|
@@ -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
|