@mauroandre/velojs 0.0.3 → 0.0.5

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/src/client.tsx DELETED
@@ -1,79 +0,0 @@
1
- import { hydrate } from "preact";
2
- import { Router, Route, Switch } from "wouter-preact";
3
- import type { VNode } from "preact";
4
- import type { RouteNode, AppRoutes } from "./types.js";
5
-
6
- // ============================================
7
- // CLIENT OPTIONS
8
- // ============================================
9
-
10
- export interface StartClientOptions {
11
- routes: AppRoutes;
12
- }
13
-
14
- // ============================================
15
- // BUILD ROUTES - Gera rotas do wouter recursivamente
16
- // ============================================
17
-
18
- const buildRoutes = (nodes: RouteNode[], nested: boolean = false): VNode[] => {
19
- return nodes.map((node, index) => {
20
- const Component = node.module.Component;
21
-
22
- // Leaf node - rota final
23
- if (!node.children) {
24
- return (
25
- <Route key={index} path={node.path || ""}>
26
- <Component />
27
- </Route>
28
- );
29
- }
30
-
31
- // Node com children - os children são sempre relativos com nest
32
- const childRoutes = buildRoutes(node.children, !!node.path);
33
-
34
- // isRoot - pula o componente, renderiza só os children
35
- if (node.isRoot) {
36
- return <Switch key={index}>{childRoutes}</Switch>;
37
- }
38
-
39
- // Sem path - wrapper puro (sem Route)
40
- if (!node.path) {
41
- return (
42
- <Component key={index}>
43
- <Switch>{childRoutes}</Switch>
44
- </Component>
45
- );
46
- }
47
-
48
- // Layout com path + nest
49
- return (
50
- <Route key={index} path={node.path} nest>
51
- <Component>
52
- <Switch>{childRoutes}</Switch>
53
- </Component>
54
- </Route>
55
- );
56
- });
57
- };
58
-
59
- // ============================================
60
- // CLIENT ROUTES - Componente de rotas
61
- // ============================================
62
-
63
- const ClientRoutes = ({ routes }: { routes: AppRoutes }) => {
64
- const routeTree = buildRoutes(routes);
65
- return <Router>{routeTree}</Router>;
66
- };
67
-
68
- // ============================================
69
- // START CLIENT - Entry point principal
70
- // ============================================
71
-
72
- export const startClient = (options: StartClientOptions) => {
73
- const { routes } = options;
74
- const body = document.querySelector("body");
75
-
76
- if (body) {
77
- hydrate(<ClientRoutes routes={routes} />, body);
78
- }
79
- };
@@ -1,155 +0,0 @@
1
- /**
2
- * VeloJS Components
3
- * Components that can be used in the app for script/style injection
4
- */
5
-
6
- import { Link as WouterLink } from "wouter-preact";
7
- import type { ComponentChildren } from "preact";
8
-
9
- // ============================================
10
- // SCRIPTS COMPONENT
11
- // ============================================
12
-
13
- interface ScriptsProps {
14
- /**
15
- * Base path for static assets in production
16
- * @default ""
17
- */
18
- basePath?: string;
19
-
20
- /**
21
- * Path to the favicon file relative to the public directory
22
- * Set to false to disable favicon injection
23
- * @default "/favicon.ico"
24
- */
25
- favicon?: string | false;
26
- }
27
-
28
- /**
29
- * Injects the necessary scripts and styles for VeloJS.
30
- * In dev mode: injects Vite HMR client and velo client script
31
- * In production: injects compiled CSS and JS
32
- *
33
- * @example
34
- * ```tsx
35
- * <head>
36
- * <Scripts />
37
- * </head>
38
- * ```
39
- */
40
- export function Scripts({ basePath, favicon = "/favicon.ico" }: ScriptsProps = {}) {
41
- const isDev = import.meta.env.DEV;
42
- basePath = basePath || process.env.STATIC_BASE_URL || "";
43
-
44
- const faviconTag = favicon !== false && (
45
- <link rel="icon" href={`${basePath}${favicon}`} type="image/x-icon" />
46
- );
47
-
48
- if (isDev) {
49
- return (
50
- <>
51
- {faviconTag}
52
- <script type="module" src="/@vite/client"></script>
53
- <script type="module" src="/__velo_client.js"></script>
54
- </>
55
- );
56
- }
57
-
58
- return (
59
- <>
60
- {faviconTag}
61
- <link rel="stylesheet" href={`${basePath}/client.css`} />
62
- <script type="module" src={`${basePath}/client.js`}></script>
63
- </>
64
- );
65
- }
66
-
67
- // ============================================
68
- // LINK COMPONENT
69
- // ============================================
70
-
71
- import type { ComponentProps } from "preact";
72
- import type { RouteModule } from "./types.js";
73
-
74
- // Props do Link do wouter, mas com "to" estendido
75
- type WouterLinkProps = ComponentProps<typeof WouterLink>;
76
- type LinkProps = Omit<WouterLinkProps, "to" | "href"> & {
77
- /**
78
- * Destination - can be a string path or a module with metadata
79
- */
80
- to: string | RouteModule;
81
-
82
- /**
83
- * URL parameters to substitute in the path
84
- * e.g., { id: "123" } replaces :id with 123
85
- */
86
- params?: Record<string, string>;
87
-
88
- /**
89
- * Query string parameters appended to the URL
90
- * e.g., { company: "abc" } appends ?company=abc
91
- */
92
- search?: Record<string, string> | undefined;
93
-
94
- /**
95
- * When true, ignores current URL params and uses fullPath as-is
96
- * By default, params are extracted from current URL and substituted
97
- * @default false
98
- */
99
- absolute?: boolean;
100
- };
101
-
102
- /**
103
- * Substitutes :param placeholders in a path with actual values
104
- */
105
- export function substituteParams(
106
- path: string,
107
- params: Record<string, string>
108
- ): string {
109
- let result = path;
110
- for (const [key, value] of Object.entries(params)) {
111
- result = result.replace(`:${key}`, value);
112
- }
113
- return result;
114
- }
115
-
116
- /**
117
- * Link component for navigation.
118
- * Accepts either a string path or a route module.
119
- *
120
- * @example
121
- * ```tsx
122
- * // With string path
123
- * <Link to="/login">Login</Link>
124
- *
125
- * // With route module (relative - uses path, works with nest)
126
- * <Link to={McpPage}>MCP</Link>
127
- *
128
- * // With route module (absolute - uses fullPath)
129
- * <Link to={LoginPage} absolute>Login</Link>
130
- *
131
- * // With explicit params
132
- * <Link to={UserPage} params={{ id: "123" }}>View User</Link>
133
- * ```
134
- */
135
- export function Link({ to, params, search, absolute, ...rest }: LinkProps) {
136
- const isModule = typeof to !== "string";
137
-
138
- // Default: path (relative), absolute: fullPath
139
- const basePath = isModule
140
- ? (absolute ? to.metadata?.fullPath : to.metadata?.path) ?? "/"
141
- : to;
142
-
143
- // Substitute params if provided
144
- const finalPath = params ? substituteParams(basePath, params) : basePath;
145
-
146
- // Absolute module paths: prefix with ~ for wouter absolute navigation
147
- const routePath = isModule && absolute ? `~${finalPath}` : finalPath;
148
-
149
- // Append query string if search params provided
150
- const queryString = search
151
- ? `?${new URLSearchParams(search).toString()}`
152
- : "";
153
-
154
- return <WouterLink to={`${routePath}${queryString}`} {...rest} />;
155
- }
package/src/cookie.ts DELETED
@@ -1,7 +0,0 @@
1
- export {
2
- getCookie,
3
- getSignedCookie,
4
- setCookie,
5
- setSignedCookie,
6
- deleteCookie,
7
- } from "hono/cookie";
package/src/hooks.tsx DELETED
@@ -1,266 +0,0 @@
1
- import {
2
- signal,
3
- useSignal,
4
- type Signal,
5
- } from "@preact/signals";
6
- import { useEffect } from "preact/hooks";
7
- import {
8
- useParams as wouterUseParams,
9
- useLocation as wouterUseLocation,
10
- } from "wouter-preact";
11
-
12
- /**
13
- * Força o signal a notificar mudanças após mutação de propriedades aninhadas
14
- *
15
- * Uso:
16
- * ```tsx
17
- * word.isChecked = !word.isChecked; // muta
18
- * touch(pathAppliedData); // notifica
19
- * ```
20
- */
21
- export function touch<T>(sig: Signal<T | null>): void {
22
- if (sig.value !== null && typeof sig.value === "object") {
23
- sig.value = { ...sig.value } as T;
24
- }
25
- }
26
-
27
- /**
28
- * Hidrata dados do loader fora de componentes (SSR only)
29
- * Apenas lê do __PAGE_DATA__, nunca faz fetch
30
- *
31
- * Use para dados globais/compartilhados carregados no Layout.
32
- * Para dados de página com suporte a navegação SPA, use useLoader() dentro do Component.
33
- *
34
- * Uso:
35
- * ```tsx
36
- * // No Layout - carrega dados globais
37
- * export const { data: globalData } = Loader<GlobalType>();
38
- *
39
- * // No componente do Layout
40
- * export const Component = ({ children }) => <div>{globalData.value?.name}{children}</div>;
41
- *
42
- * // Em outros módulos - importa o dado do Layout
43
- * import { globalData } from "./Layout.js";
44
- * ```
45
- *
46
- * @param moduleId - Injetado automaticamente pelo veloPlugin
47
- */
48
- export function Loader<T>(moduleId?: string): {
49
- data: Signal<T | null>;
50
- loading: Signal<boolean>;
51
- } {
52
- const loading = signal(false);
53
-
54
- // Servidor: getter que sempre lê do AsyncLocalStorage (sem cache)
55
- if (typeof window === "undefined") {
56
- const data = {
57
- get value(): T | null {
58
- if (!moduleId) return null;
59
- const storage = (globalThis as any).__veloServerData;
60
- const serverData = storage?.getStore?.();
61
- return (serverData?.[moduleId] as T) ?? null;
62
- },
63
- };
64
- return { data: data as Signal<T | null>, loading };
65
- }
66
-
67
- // Cliente: apenas hidrata do __PAGE_DATA__, nunca faz fetch
68
- // (Loader roda uma única vez no import do módulo, não é responsável por SPA)
69
- const pageData = (window as any).__PAGE_DATA__;
70
- const initialData =
71
- moduleId && pageData?.[moduleId] ? (pageData[moduleId] as T) : null;
72
-
73
- return { data: signal<T | null>(initialData), loading };
74
- }
75
-
76
- /**
77
- * Hook para acessar dados do loader dentro de componentes
78
- * Suporta SSR hydration e navegação SPA (faz fetch se necessário)
79
- *
80
- * Uso:
81
- * ```tsx
82
- * export const Component = () => {
83
- * const { data, loading } = useLoader<MyType>();
84
- * return <div>{data.value?.name}</div>;
85
- * };
86
- *
87
- * // Com deps (ex: recarregar ao trocar de rota)
88
- * const params = useParams();
89
- * const { data } = useLoader<MyType>([params.id]);
90
- * ```
91
- *
92
- * @param moduleId - Injetado automaticamente pelo veloPlugin
93
- * @param deps - Array de dependências (triggers re-fetch quando mudam)
94
- */
95
- export function useLoader<T>(): { data: Signal<T | null>; loading: Signal<boolean>; refetch: () => void };
96
- export function useLoader<T>(deps: any[]): { data: Signal<T | null>; loading: Signal<boolean>; refetch: () => void };
97
- export function useLoader<T>(moduleId: string, deps?: any[]): { data: Signal<T | null>; loading: Signal<boolean>; refetch: () => void };
98
- export function useLoader<T>(
99
- moduleIdOrDeps?: string | any[],
100
- deps?: any[],
101
- ): {
102
- data: Signal<T | null>;
103
- loading: Signal<boolean>;
104
- refetch: () => void;
105
- } {
106
- // Resolve args: usuário chama useLoader(deps), vite transforma em useLoader(moduleId, deps)
107
- let moduleId: string | undefined;
108
- let resolvedDeps: any[] | undefined;
109
- if (Array.isArray(moduleIdOrDeps)) {
110
- resolvedDeps = moduleIdOrDeps;
111
- } else {
112
- moduleId = moduleIdOrDeps;
113
- resolvedDeps = deps;
114
- }
115
-
116
- // Servidor: pega dados do AsyncLocalStorage (isolado por request)
117
- let initialData: T | null = null;
118
-
119
- if (typeof window === "undefined" && moduleId) {
120
- const storage = (globalThis as any).__veloServerData;
121
- const serverData = storage?.getStore?.();
122
- if (serverData?.[moduleId]) {
123
- initialData = serverData[moduleId] as T;
124
- }
125
- }
126
-
127
- // Cliente: tenta hidratar do __PAGE_DATA__
128
- if (typeof window !== "undefined" && moduleId) {
129
- const pageData = (window as any).__PAGE_DATA__;
130
- if (pageData?.[moduleId]) {
131
- initialData = pageData[moduleId] as T;
132
- delete pageData[moduleId];
133
- }
134
- }
135
-
136
- const data = useSignal<T | null>(initialData);
137
- const loading = useSignal(false);
138
-
139
- const fetchData = () => {
140
- if (typeof window === "undefined" || !moduleId) return;
141
- const currentPath = window.location.pathname;
142
- const searchParams = new URLSearchParams(window.location.search);
143
- searchParams.set("_data", "1");
144
- loading.value = true;
145
-
146
- fetch(`${currentPath}?${searchParams.toString()}`)
147
- .then((res) => res.json())
148
- .then((json: Record<string, unknown>) => {
149
- data.value = json[moduleId] as T;
150
- loading.value = false;
151
- })
152
- .catch((err) => {
153
- console.error("Erro ao carregar dados:", err);
154
- loading.value = false;
155
- });
156
- };
157
-
158
- // Se não tem dado, faz fetch (navegação SPA)
159
- // Na hidratação initialData !== null, então pula o fetch
160
- // Quando deps mudam (ex: troca de rota), initialData será null e dispara fetch
161
- useEffect(() => {
162
- if (initialData !== null) return;
163
- fetchData();
164
- }, resolvedDeps ?? []);
165
-
166
- return { data, loading, refetch: fetchData };
167
- }
168
-
169
- /**
170
- * Hook para navegação programática
171
- *
172
- * Uso:
173
- * ```tsx
174
- * const navigate = useNavigate();
175
- * navigate("/outra-pagina");
176
- * ```
177
- */
178
- export function useNavigate() {
179
- const [, navigate] = wouterUseLocation();
180
- return navigate;
181
- }
182
-
183
- /**
184
- * Hook para acessar parâmetros da rota (funciona em SSR e cliente)
185
- *
186
- * Uso:
187
- * ```tsx
188
- * // Rota: /teste/:pathAppliedId/avaliacao/:assessmentRatingIndex
189
- * const params = useParams();
190
- * console.log(params.pathAppliedId, params.assessmentRatingIndex);
191
- * ```
192
- */
193
- export function useParams<
194
- T extends Record<string, string> = Record<string, string>,
195
- >(): T {
196
- // Servidor: lê do AsyncLocalStorage
197
- if (typeof window === "undefined") {
198
- const storage = (globalThis as any).__veloServerData;
199
- const serverData = storage?.getStore?.();
200
- return (serverData?.__params as T) ?? ({} as T);
201
- }
202
-
203
- // Cliente: sempre usa wouter para ter params atualizados durante navegação SPA
204
- return wouterUseParams() as T;
205
- }
206
-
207
- /**
208
- * Hook para acessar query string da rota (funciona em SSR e cliente)
209
- *
210
- * Uso:
211
- * ```tsx
212
- * // URL: /teste?foo=bar&baz=123
213
- * const query = useQuery();
214
- * console.log(query.foo, query.baz);
215
- * ```
216
- */
217
- export function useQuery<
218
- T extends Record<string, string> = Record<string, string>,
219
- >(): T {
220
- // Servidor: lê do AsyncLocalStorage
221
- if (typeof window === "undefined") {
222
- const storage = (globalThis as any).__veloServerData;
223
- const serverData = storage?.getStore?.();
224
- return (serverData?.__query as T) ?? ({} as T);
225
- }
226
-
227
- // Cliente: lê do __PAGE_DATA__ ou parse da URL
228
- const pageData = (window as any).__PAGE_DATA__;
229
- if (pageData?.__query) {
230
- return pageData.__query as T;
231
- }
232
-
233
- // Fallback: parse da URL atual
234
- const searchParams = new URLSearchParams(window.location.search);
235
- const query: Record<string, string> = {};
236
- searchParams.forEach((value, key) => {
237
- query[key] = value;
238
- });
239
- return query as T;
240
- }
241
-
242
- /**
243
- * Hook para acessar o pathname completo da URL (funciona em SSR e cliente)
244
- * Diferente do useLocation do wouter que retorna path relativo ao contexto nest,
245
- * este hook sempre retorna o pathname absoluto.
246
- *
247
- * Uso:
248
- * ```tsx
249
- * const pathname = usePathname();
250
- * // Em /admin/colaboradores sempre retorna "/admin/colaboradores"
251
- * // (não "/" como useLocation retornaria dentro do contexto /admin)
252
- * ```
253
- */
254
- export function usePathname(): string {
255
- // Servidor: lê do AsyncLocalStorage
256
- if (typeof window === "undefined") {
257
- const storage = (globalThis as any).__veloServerData;
258
- const serverData = storage?.getStore?.();
259
- return (serverData?.__pathname as string) ?? "/";
260
- }
261
-
262
- // Cliente: usa useLocation do wouter para reatividade,
263
- // mas retorna window.location.pathname para o path absoluto
264
- wouterUseLocation(); // trigger re-render on navigation
265
- return window.location.pathname;
266
- }