alabjs 0.1.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/adapters/cloudflare.d.ts +31 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +30 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/deno.d.ts +22 -0
- package/dist/adapters/deno.d.ts.map +1 -0
- package/dist/adapters/deno.js +21 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/web.d.ts +47 -0
- package/dist/adapters/web.d.ts.map +1 -0
- package/dist/adapters/web.js +212 -0
- package/dist/adapters/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/hooks.d.ts +119 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +220 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/hooks.test.d.ts +2 -0
- package/dist/client/hooks.test.d.ts.map +1 -0
- package/dist/client/hooks.test.js +45 -0
- package/dist/client/hooks.test.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/offline.d.ts +52 -0
- package/dist/client/offline.d.ts.map +1 -0
- package/dist/client/offline.js +90 -0
- package/dist/client/offline.js.map +1 -0
- package/dist/client/provider.d.ts +12 -0
- package/dist/client/provider.d.ts.map +1 -0
- package/dist/client/provider.js +10 -0
- package/dist/client/provider.js.map +1 -0
- package/dist/commands/build.d.ts +18 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +173 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +447 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +92 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/ssg.d.ts +8 -0
- package/dist/commands/ssg.d.ts.map +1 -0
- package/dist/commands/ssg.js +124 -0
- package/dist/commands/ssg.js.map +1 -0
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +26 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/test.d.ts +24 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +87 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +38 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +46 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/Font.d.ts +57 -0
- package/dist/components/Font.d.ts.map +1 -0
- package/dist/components/Font.js +33 -0
- package/dist/components/Font.js.map +1 -0
- package/dist/components/Image.d.ts +74 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +85 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Link.d.ts +23 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +48 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Script.d.ts +37 -0
- package/dist/components/Script.d.ts.map +1 -0
- package/dist/components/Script.js +70 -0
- package/dist/components/Script.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/i18n/i18n.test.d.ts +2 -0
- package/dist/i18n/i18n.test.d.ts.map +1 -0
- package/dist/i18n/i18n.test.js +132 -0
- package/dist/i18n/i18n.test.js.map +1 -0
- package/dist/i18n/index.d.ts +135 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +189 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/router/code-router.d.ts +204 -0
- package/dist/router/code-router.d.ts.map +1 -0
- package/dist/router/code-router.js +258 -0
- package/dist/router/code-router.js.map +1 -0
- package/dist/router/code-router.test.d.ts +2 -0
- package/dist/router/code-router.test.d.ts.map +1 -0
- package/dist/router/code-router.test.js +128 -0
- package/dist/router/code-router.test.js.map +1 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +2 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/manifest.d.ts +12 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/manifest.js +2 -0
- package/dist/router/manifest.js.map +1 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +407 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/cache.d.ts +99 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +161 -0
- package/dist/server/cache.js.map +1 -0
- package/dist/server/cache.test.d.ts +2 -0
- package/dist/server/cache.test.d.ts.map +1 -0
- package/dist/server/cache.test.js +150 -0
- package/dist/server/cache.test.js.map +1 -0
- package/dist/server/csrf.d.ts +28 -0
- package/dist/server/csrf.d.ts.map +1 -0
- package/dist/server/csrf.js +66 -0
- package/dist/server/csrf.js.map +1 -0
- package/dist/server/csrf.test.d.ts +2 -0
- package/dist/server/csrf.test.d.ts.map +1 -0
- package/dist/server/csrf.test.js +154 -0
- package/dist/server/csrf.test.js.map +1 -0
- package/dist/server/image.d.ts +18 -0
- package/dist/server/image.d.ts.map +1 -0
- package/dist/server/image.js +97 -0
- package/dist/server/image.js.map +1 -0
- package/dist/server/index.d.ts +57 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +58 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +53 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +80 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/middleware.test.d.ts +2 -0
- package/dist/server/middleware.test.d.ts.map +1 -0
- package/dist/server/middleware.test.js +125 -0
- package/dist/server/middleware.test.js.map +1 -0
- package/dist/server/revalidate.d.ts +49 -0
- package/dist/server/revalidate.d.ts.map +1 -0
- package/dist/server/revalidate.js +62 -0
- package/dist/server/revalidate.js.map +1 -0
- package/dist/server/revalidate.test.d.ts +2 -0
- package/dist/server/revalidate.test.d.ts.map +1 -0
- package/dist/server/revalidate.test.js +93 -0
- package/dist/server/revalidate.test.js.map +1 -0
- package/dist/server/server-fn.test.d.ts +2 -0
- package/dist/server/server-fn.test.d.ts.map +1 -0
- package/dist/server/server-fn.test.js +105 -0
- package/dist/server/server-fn.test.js.map +1 -0
- package/dist/server/sitemap.d.ts +9 -0
- package/dist/server/sitemap.d.ts.map +1 -0
- package/dist/server/sitemap.js +26 -0
- package/dist/server/sitemap.js.map +1 -0
- package/dist/server/sitemap.test.d.ts +2 -0
- package/dist/server/sitemap.test.d.ts.map +1 -0
- package/dist/server/sitemap.test.js +61 -0
- package/dist/server/sitemap.test.js.map +1 -0
- package/dist/server/sse.d.ts +59 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +91 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/sse.test.d.ts +2 -0
- package/dist/server/sse.test.d.ts.map +1 -0
- package/dist/server/sse.test.js +68 -0
- package/dist/server/sse.test.js.map +1 -0
- package/dist/signals/index.d.ts +101 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +149 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/signals/signals.test.d.ts +2 -0
- package/dist/signals/signals.test.d.ts.map +1 -0
- package/dist/signals/signals.test.js +146 -0
- package/dist/signals/signals.test.js.map +1 -0
- package/dist/ssr/html.d.ts +27 -0
- package/dist/ssr/html.d.ts.map +1 -0
- package/dist/ssr/html.js +107 -0
- package/dist/ssr/html.js.map +1 -0
- package/dist/ssr/html.test.d.ts +2 -0
- package/dist/ssr/html.test.d.ts.map +1 -0
- package/dist/ssr/html.test.js +178 -0
- package/dist/ssr/html.test.js.map +1 -0
- package/dist/ssr/render.d.ts +46 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/render.js +87 -0
- package/dist/ssr/render.js.map +1 -0
- package/dist/ssr/router-dev.d.ts +60 -0
- package/dist/ssr/router-dev.d.ts.map +1 -0
- package/dist/ssr/router-dev.js +205 -0
- package/dist/ssr/router-dev.js.map +1 -0
- package/dist/ssr/router-dev.test.d.ts +2 -0
- package/dist/ssr/router-dev.test.d.ts.map +1 -0
- package/dist/ssr/router-dev.test.js +189 -0
- package/dist/ssr/router-dev.test.js.map +1 -0
- package/dist/test/index.d.ts +93 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +146 -0
- package/dist/test/index.js.map +1 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/napi.d.ts +15 -0
- package/dist/types/napi.d.ts.map +1 -0
- package/dist/types/napi.js +2 -0
- package/dist/types/napi.js.map +1 -0
- package/package.json +107 -0
- package/src/adapters/cloudflare.ts +30 -0
- package/src/adapters/deno.ts +21 -0
- package/src/adapters/web.ts +259 -0
- package/src/cli.ts +68 -0
- package/src/client/hooks.test.ts +54 -0
- package/src/client/hooks.ts +329 -0
- package/src/client/index.ts +5 -0
- package/src/client/offline-sw.ts +191 -0
- package/src/client/offline.ts +114 -0
- package/src/client/provider.tsx +14 -0
- package/src/commands/build.ts +201 -0
- package/src/commands/dev.ts +509 -0
- package/src/commands/info.ts +111 -0
- package/src/commands/ssg.ts +177 -0
- package/src/commands/start.ts +32 -0
- package/src/commands/test.ts +102 -0
- package/src/components/ErrorBoundary.tsx +73 -0
- package/src/components/Font.tsx +100 -0
- package/src/components/Image.tsx +141 -0
- package/src/components/Link.tsx +64 -0
- package/src/components/Script.tsx +97 -0
- package/src/components/index.ts +9 -0
- package/src/i18n/i18n.test.tsx +169 -0
- package/src/i18n/index.tsx +256 -0
- package/src/index.ts +10 -0
- package/src/router/code-router.test.ts +146 -0
- package/src/router/code-router.tsx +459 -0
- package/src/router/index.ts +18 -0
- package/src/router/manifest.ts +13 -0
- package/src/server/app.ts +466 -0
- package/src/server/cache.test.ts +192 -0
- package/src/server/cache.ts +195 -0
- package/src/server/csrf.test.ts +199 -0
- package/src/server/csrf.ts +80 -0
- package/src/server/image.ts +112 -0
- package/src/server/index.ts +144 -0
- package/src/server/middleware.test.ts +151 -0
- package/src/server/middleware.ts +95 -0
- package/src/server/revalidate.test.ts +106 -0
- package/src/server/revalidate.ts +75 -0
- package/src/server/server-fn.test.ts +127 -0
- package/src/server/sitemap.test.ts +68 -0
- package/src/server/sitemap.ts +30 -0
- package/src/server/sse.test.ts +81 -0
- package/src/server/sse.ts +110 -0
- package/src/signals/index.ts +177 -0
- package/src/signals/signals.test.ts +164 -0
- package/src/ssr/html.test.ts +200 -0
- package/src/ssr/html.ts +140 -0
- package/src/ssr/render.ts +144 -0
- package/src/ssr/router-dev.test.ts +230 -0
- package/src/ssr/router-dev.ts +229 -0
- package/src/test/index.ts +206 -0
- package/src/types/compiler.d.ts +25 -0
- package/src/types/index.ts +147 -0
- package/src/types/napi.ts +20 -0
- package/src/types/plugins.d.ts +3 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { use, useReducer, useTransition, useCallback, useState, useEffect, useRef } from "react";
|
|
2
|
+
// Promise cache keyed by URL.
|
|
3
|
+
// • On the SERVER: cleared before each page render (via _clearALabSSRCache) so
|
|
4
|
+
// re-renders after Suspense resolution return the same promise object, which
|
|
5
|
+
// is required for renderToPipeableStream to correctly resolve Suspense.
|
|
6
|
+
// • On the CLIENT: intentionally persists for the session to avoid redundant
|
|
7
|
+
// network round-trips on subsequent re-renders.
|
|
8
|
+
const _promiseCache = new Map();
|
|
9
|
+
/** Clear the server-side promise cache between SSR renders. Called by alab's dev server. */
|
|
10
|
+
export function _clearALabSSRCache() {
|
|
11
|
+
_promiseCache.clear();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Fetch server data with full type inference from a `ServerFn`.
|
|
15
|
+
*
|
|
16
|
+
* Use `import type` to reference the server function — type-only imports
|
|
17
|
+
* are erased at compile time and never cross the server/client boundary.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // app/posts/[id]/page.tsx
|
|
22
|
+
* import type { getPost } from "./page.server"; // ← import type, safe
|
|
23
|
+
* import { useServerData } from "alabjs/client";
|
|
24
|
+
*
|
|
25
|
+
* export default function PostPage({ params }: { params: { id: string } }) {
|
|
26
|
+
* // Return type is inferred from getPost — no manual type annotation needed
|
|
27
|
+
* const post = useServerData<typeof getPost>("getPost", params);
|
|
28
|
+
* post.title; // ✅ typed
|
|
29
|
+
* post.foo; // ✅ TS error — doesn't exist on the return type
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useServerData(fnName, params) {
|
|
34
|
+
const searchParams = params
|
|
35
|
+
? new URLSearchParams(params).toString()
|
|
36
|
+
: "";
|
|
37
|
+
// When running in Node.js (SSR) fetch requires an absolute URL.
|
|
38
|
+
// Alab's dev/prod server sets ALAB_ORIGIN before rendering each page.
|
|
39
|
+
const origin = typeof window !== "undefined"
|
|
40
|
+
? ""
|
|
41
|
+
: (process.env["ALAB_ORIGIN"] ?? "http://localhost:3000");
|
|
42
|
+
const url = `${origin}/_alabjs/data/${fnName}${searchParams ? `?${searchParams}` : ""}`;
|
|
43
|
+
let promise = _promiseCache.get(url);
|
|
44
|
+
if (!promise) {
|
|
45
|
+
promise = fetch(url).then((r) => {
|
|
46
|
+
if (!r.ok)
|
|
47
|
+
throw new Error(`[alabjs] server data fetch failed: ${r.status} ${r.statusText} — ${url}`);
|
|
48
|
+
return r.json();
|
|
49
|
+
});
|
|
50
|
+
_promiseCache.set(url, promise);
|
|
51
|
+
}
|
|
52
|
+
return use(promise);
|
|
53
|
+
}
|
|
54
|
+
function mutationReducer(_state, action) {
|
|
55
|
+
switch (action.type) {
|
|
56
|
+
case "start": return { status: "pending", data: undefined, error: undefined, zodError: undefined };
|
|
57
|
+
case "success": return { status: "success", data: action.data, error: undefined, zodError: undefined };
|
|
58
|
+
case "error": return { status: "error", data: undefined, error: action.error, zodError: undefined };
|
|
59
|
+
case "invalid": return { status: "invalid", data: undefined, error: undefined, zodError: action.zodError };
|
|
60
|
+
case "reset": return { status: "idle", data: undefined, error: undefined, zodError: undefined };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Trigger a server function mutation from the client, with full async state
|
|
65
|
+
* and optional optimistic updates.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```tsx
|
|
69
|
+
* import type { updateTodo } from "./page.server";
|
|
70
|
+
* import { useMutation } from "alabjs/client";
|
|
71
|
+
*
|
|
72
|
+
* // Basic
|
|
73
|
+
* const { mutate, data, isPending, error, zodError, reset } =
|
|
74
|
+
* useMutation<typeof updateTodo>("updateTodo");
|
|
75
|
+
*
|
|
76
|
+
* // With optimistic update
|
|
77
|
+
* const { mutate, optimisticData } = useMutation<typeof updateTodo>("updateTodo", {
|
|
78
|
+
* optimistic: (input) => ({ ...currentTodo, ...input }),
|
|
79
|
+
* onError: (err, rollback) => rollback(),
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function useMutation(fnName, options) {
|
|
84
|
+
const [state, dispatch] = useReducer((mutationReducer), { status: "idle", data: undefined, error: undefined, zodError: undefined });
|
|
85
|
+
// Optimistic value lives in separate state so it can be cleared independently.
|
|
86
|
+
const [optimisticData, setOptimisticData] = useState(undefined);
|
|
87
|
+
const [isPending, startTransition] = useTransition();
|
|
88
|
+
const rollback = useCallback(() => {
|
|
89
|
+
setOptimisticData(undefined);
|
|
90
|
+
dispatch({ type: "reset" });
|
|
91
|
+
}, []);
|
|
92
|
+
const mutate = useCallback((input) => {
|
|
93
|
+
dispatch({ type: "start" });
|
|
94
|
+
if (options?.optimistic) {
|
|
95
|
+
setOptimisticData(options.optimistic(input));
|
|
96
|
+
}
|
|
97
|
+
startTransition(() => {
|
|
98
|
+
void (async () => {
|
|
99
|
+
try {
|
|
100
|
+
const r = await fetch(`/_alabjs/fn/${fnName}`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "content-type": "application/json" },
|
|
103
|
+
body: JSON.stringify(input),
|
|
104
|
+
});
|
|
105
|
+
// Zod validation error from server
|
|
106
|
+
if (r.status === 422) {
|
|
107
|
+
const body = await r.json();
|
|
108
|
+
setOptimisticData(undefined);
|
|
109
|
+
dispatch({ type: "invalid", zodError: body["zodError"] });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!r.ok)
|
|
113
|
+
throw new Error(`[alabjs] mutation failed: ${r.status} ${r.statusText}`);
|
|
114
|
+
const data = await r.json();
|
|
115
|
+
setOptimisticData(undefined);
|
|
116
|
+
dispatch({ type: "success", data });
|
|
117
|
+
options?.onSuccess?.(data);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
121
|
+
setOptimisticData(undefined);
|
|
122
|
+
dispatch({ type: "error", error });
|
|
123
|
+
options?.onError?.(error, rollback);
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
});
|
|
127
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
128
|
+
}, [fnName, rollback]);
|
|
129
|
+
const reset = useCallback(() => {
|
|
130
|
+
setOptimisticData(undefined);
|
|
131
|
+
dispatch({ type: "reset" });
|
|
132
|
+
}, []);
|
|
133
|
+
return {
|
|
134
|
+
mutate,
|
|
135
|
+
data: state.data,
|
|
136
|
+
/** Present when `optimistic` option is set and the request is in flight. */
|
|
137
|
+
optimisticData,
|
|
138
|
+
isPending: isPending || state.status === "pending",
|
|
139
|
+
error: state.error,
|
|
140
|
+
/** Zod validation errors returned by `defineServerFn` schema checks (HTTP 422). */
|
|
141
|
+
zodError: state.zodError,
|
|
142
|
+
isSuccess: state.status === "success",
|
|
143
|
+
isError: state.status === "error",
|
|
144
|
+
isInvalid: state.status === "invalid",
|
|
145
|
+
reset,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Subscribe to a server-sent event stream from a `defineSSEHandler` route.
|
|
150
|
+
*
|
|
151
|
+
* The EventSource is created when the component mounts and closed on unmount.
|
|
152
|
+
* Data is parsed as JSON automatically.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* import { useSSE } from "alabjs/client";
|
|
157
|
+
*
|
|
158
|
+
* export default function PricesPage() {
|
|
159
|
+
* const { data, readyState } = useSSE<{ ticker: string; price: number }>(
|
|
160
|
+
* "/api/prices?ticker=BTC",
|
|
161
|
+
* { event: "price" },
|
|
162
|
+
* );
|
|
163
|
+
*
|
|
164
|
+
* return <div>{readyState === "open" ? data?.price ?? "—" : "connecting…"}</div>;
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export function useSSE(url, options = {}) {
|
|
169
|
+
const { event = "message", enabled = true, onOpen, onError } = options;
|
|
170
|
+
const [data, setData] = useState(undefined);
|
|
171
|
+
const [lastEventId, setLastEventId] = useState("");
|
|
172
|
+
const [readyState, setReadyState] = useState("connecting");
|
|
173
|
+
// Keep callbacks stable across renders without re-subscribing
|
|
174
|
+
const onOpenRef = useRef(onOpen);
|
|
175
|
+
const onErrorRef = useRef(onError);
|
|
176
|
+
onOpenRef.current = onOpen;
|
|
177
|
+
onErrorRef.current = onError;
|
|
178
|
+
const esRef = useRef(null);
|
|
179
|
+
const close = useCallback(() => {
|
|
180
|
+
esRef.current?.close();
|
|
181
|
+
esRef.current = null;
|
|
182
|
+
setReadyState("closed");
|
|
183
|
+
}, []);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!enabled || typeof EventSource === "undefined")
|
|
186
|
+
return;
|
|
187
|
+
const es = new EventSource(url);
|
|
188
|
+
esRef.current = es;
|
|
189
|
+
setReadyState("connecting");
|
|
190
|
+
es.addEventListener("open", () => {
|
|
191
|
+
setReadyState("open");
|
|
192
|
+
onOpenRef.current?.();
|
|
193
|
+
});
|
|
194
|
+
es.addEventListener(event, (e) => {
|
|
195
|
+
setLastEventId(e.lastEventId ?? "");
|
|
196
|
+
try {
|
|
197
|
+
setData(e.data ? JSON.parse(e.data) : undefined);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Server sent data that isn't valid JSON — log and discard rather than
|
|
201
|
+
// silently casting a raw string to T (which would cause runtime type errors).
|
|
202
|
+
console.warn("[alabjs] useSSE: received non-JSON data on", url, "— discarding:", e.data);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
es.addEventListener("error", (e) => {
|
|
206
|
+
onErrorRef.current?.(e);
|
|
207
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
208
|
+
setReadyState("closed");
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return () => {
|
|
212
|
+
es.close();
|
|
213
|
+
esRef.current = null;
|
|
214
|
+
};
|
|
215
|
+
// Re-subscribe if url or event type changes
|
|
216
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
217
|
+
}, [url, event, enabled]);
|
|
218
|
+
return { data, lastEventId, readyState, close };
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/client/hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../../src/client/hooks.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { _clearALabSSRCache } from "./hooks.js";
|
|
3
|
+
// Note: useServerData, useMutation, and useSSE are React hooks that require a
|
|
4
|
+
// component context and browser APIs (fetch, EventSource). Testing the full hooks
|
|
5
|
+
// would require a React test renderer + fetch mocking. Here we test the exported
|
|
6
|
+
// utility and the mutation state machine logic that can be exercised without React.
|
|
7
|
+
describe("_clearALabSSRCache", () => {
|
|
8
|
+
it("is a function", () => {
|
|
9
|
+
expect(typeof _clearALabSSRCache).toBe("function");
|
|
10
|
+
});
|
|
11
|
+
it("can be called without error", () => {
|
|
12
|
+
// This clears the internal promise cache used during SSR.
|
|
13
|
+
// Should not throw even when cache is empty.
|
|
14
|
+
expect(() => _clearALabSSRCache()).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
it("can be called multiple times", () => {
|
|
17
|
+
expect(() => {
|
|
18
|
+
_clearALabSSRCache();
|
|
19
|
+
_clearALabSSRCache();
|
|
20
|
+
_clearALabSSRCache();
|
|
21
|
+
}).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
// ─── Mutation state machine (via module internals) ────────────────────────────
|
|
25
|
+
// The mutationReducer is not exported, but we can test the state machine logic
|
|
26
|
+
// by verifying the expected types and shape of the module exports.
|
|
27
|
+
describe("hooks module exports", () => {
|
|
28
|
+
it("exports useServerData", async () => {
|
|
29
|
+
const mod = await import("./hooks.js");
|
|
30
|
+
expect(typeof mod.useServerData).toBe("function");
|
|
31
|
+
});
|
|
32
|
+
it("exports useMutation", async () => {
|
|
33
|
+
const mod = await import("./hooks.js");
|
|
34
|
+
expect(typeof mod.useMutation).toBe("function");
|
|
35
|
+
});
|
|
36
|
+
it("exports useSSE", async () => {
|
|
37
|
+
const mod = await import("./hooks.js");
|
|
38
|
+
expect(typeof mod.useSSE).toBe("function");
|
|
39
|
+
});
|
|
40
|
+
it("exports _clearALabSSRCache", async () => {
|
|
41
|
+
const mod = await import("./hooks.js");
|
|
42
|
+
expect(typeof mod._clearALabSSRCache).toBe("function");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=hooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.js","sourceRoot":"","sources":["../../src/client/hooks.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,8EAA8E;AAC9E,kFAAkF;AAClF,iFAAiF;AACjF,oFAAoF;AAEpF,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE;QACvB,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,0DAA0D;QAC1D,6CAA6C;QAC7C,MAAM,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,GAAG,EAAE;YACV,kBAAkB,EAAE,CAAC;YACrB,kBAAkB,EAAE,CAAC;YACrB,kBAAkB,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,+EAA+E;AAC/E,mEAAmE;AAEnE,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { useServerData, useMutation, useSSE, _clearALabSSRCache } from "./hooks.js";
|
|
2
|
+
export type { UseSSEOptions, UseSSEResult, SSEReadyState } from "./hooks.js";
|
|
3
|
+
export { useOfflineMutations } from "./offline.js";
|
|
4
|
+
export type { UseOfflineMutationsResult, OfflineMutationResult } from "./offline.js";
|
|
5
|
+
export { AlabProvider } from "./provider.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alab offline support — client-side registration + queue observation.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* // app/layout.tsx
|
|
7
|
+
* import { useOfflineMutations } from "alabjs/client";
|
|
8
|
+
*
|
|
9
|
+
* export default function RootLayout({ children }) {
|
|
10
|
+
* const { isOffline, queuedCount, replay } = useOfflineMutations();
|
|
11
|
+
*
|
|
12
|
+
* return (
|
|
13
|
+
* <>
|
|
14
|
+
* {isOffline && (
|
|
15
|
+
* <div className="offline-banner">
|
|
16
|
+
* You're offline — {queuedCount} mutation(s) will sync when reconnected.
|
|
17
|
+
* <button onClick={replay}>Retry now</button>
|
|
18
|
+
* </div>
|
|
19
|
+
* )}
|
|
20
|
+
* {children}
|
|
21
|
+
* </>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface OfflineMutationResult {
|
|
27
|
+
replayed: {
|
|
28
|
+
fn: string;
|
|
29
|
+
ok: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
export interface UseOfflineMutationsResult {
|
|
33
|
+
/** True when `navigator.onLine` is false. */
|
|
34
|
+
isOffline: boolean;
|
|
35
|
+
/** Number of mutations currently held in the offline queue. */
|
|
36
|
+
queuedCount: number;
|
|
37
|
+
/** Manually trigger a replay of the offline queue. */
|
|
38
|
+
replay: () => void;
|
|
39
|
+
/** History of replayed mutations since mount (cleared on replay start). */
|
|
40
|
+
replayed: OfflineMutationResult["replayed"];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Register the Alab offline service worker and observe queue state.
|
|
44
|
+
*
|
|
45
|
+
* On mount this registers `/_alabjs/offline-sw.js` (emitted by `alab build`).
|
|
46
|
+
* The hook listens for SW messages and browser online/offline events to keep
|
|
47
|
+
* state in sync.
|
|
48
|
+
*
|
|
49
|
+
* Safe to call in SSR — all browser APIs are guarded behind `typeof window`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function useOfflineMutations(): UseOfflineMutationsResult;
|
|
52
|
+
//# sourceMappingURL=offline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.d.ts","sourceRoot":"","sources":["../../src/client/offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,yBAAyB;IACxC,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAID;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,yBAAyB,CA2D/D"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alab offline support — client-side registration + queue observation.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* // app/layout.tsx
|
|
7
|
+
* import { useOfflineMutations } from "alabjs/client";
|
|
8
|
+
*
|
|
9
|
+
* export default function RootLayout({ children }) {
|
|
10
|
+
* const { isOffline, queuedCount, replay } = useOfflineMutations();
|
|
11
|
+
*
|
|
12
|
+
* return (
|
|
13
|
+
* <>
|
|
14
|
+
* {isOffline && (
|
|
15
|
+
* <div className="offline-banner">
|
|
16
|
+
* You're offline — {queuedCount} mutation(s) will sync when reconnected.
|
|
17
|
+
* <button onClick={replay}>Retry now</button>
|
|
18
|
+
* </div>
|
|
19
|
+
* )}
|
|
20
|
+
* {children}
|
|
21
|
+
* </>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { useState, useEffect, useCallback } from "react";
|
|
27
|
+
const SW_PATH = "/_alabjs/offline-sw.js";
|
|
28
|
+
/**
|
|
29
|
+
* Register the Alab offline service worker and observe queue state.
|
|
30
|
+
*
|
|
31
|
+
* On mount this registers `/_alabjs/offline-sw.js` (emitted by `alab build`).
|
|
32
|
+
* The hook listens for SW messages and browser online/offline events to keep
|
|
33
|
+
* state in sync.
|
|
34
|
+
*
|
|
35
|
+
* Safe to call in SSR — all browser APIs are guarded behind `typeof window`.
|
|
36
|
+
*/
|
|
37
|
+
export function useOfflineMutations() {
|
|
38
|
+
const [isOffline, setIsOffline] = useState(typeof navigator !== "undefined" ? !navigator.onLine : false);
|
|
39
|
+
const [queuedCount, setQueuedCount] = useState(0);
|
|
40
|
+
const [replayed, setReplayed] = useState([]);
|
|
41
|
+
// Stable SW controller ref
|
|
42
|
+
const [swReg, setSwReg] = useState(null);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (typeof window === "undefined" || !("serviceWorker" in navigator))
|
|
45
|
+
return;
|
|
46
|
+
// Register SW
|
|
47
|
+
navigator.serviceWorker.register(SW_PATH, { scope: "/" }).then((reg) => {
|
|
48
|
+
setSwReg(reg);
|
|
49
|
+
}).catch(() => { });
|
|
50
|
+
// Online / offline events
|
|
51
|
+
const handleOnline = () => setIsOffline(false);
|
|
52
|
+
const handleOffline = () => setIsOffline(true);
|
|
53
|
+
window.addEventListener("online", handleOnline);
|
|
54
|
+
window.addEventListener("offline", handleOffline);
|
|
55
|
+
// Messages from the SW
|
|
56
|
+
const handleMessage = (event) => {
|
|
57
|
+
const msg = event.data;
|
|
58
|
+
if (!msg)
|
|
59
|
+
return;
|
|
60
|
+
switch (msg["type"]) {
|
|
61
|
+
case "ALAB_QUEUED":
|
|
62
|
+
setQueuedCount(msg["count"]);
|
|
63
|
+
break;
|
|
64
|
+
case "ALAB_REPLAYED":
|
|
65
|
+
setReplayed((prev) => [...prev, { fn: msg["fn"], ok: msg["ok"] }]);
|
|
66
|
+
break;
|
|
67
|
+
case "ALAB_QUEUE_EMPTY":
|
|
68
|
+
setQueuedCount(0);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
navigator.serviceWorker.addEventListener("message", handleMessage);
|
|
73
|
+
return () => {
|
|
74
|
+
window.removeEventListener("online", handleOnline);
|
|
75
|
+
window.removeEventListener("offline", handleOffline);
|
|
76
|
+
navigator.serviceWorker.removeEventListener("message", handleMessage);
|
|
77
|
+
};
|
|
78
|
+
}, []);
|
|
79
|
+
const replay = useCallback(() => {
|
|
80
|
+
setReplayed([]);
|
|
81
|
+
if (swReg?.active) {
|
|
82
|
+
swReg.active.postMessage({ type: "ALAB_REPLAY" });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
navigator.serviceWorker.controller?.postMessage({ type: "ALAB_REPLAY" });
|
|
86
|
+
}
|
|
87
|
+
}, [swReg]);
|
|
88
|
+
return { isOffline, queuedCount, replay, replayed };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=offline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.js","sourceRoot":"","sources":["../../src/client/offline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface AlabProviderProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
fallback?: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Root provider for Alab apps.
|
|
8
|
+
* Wraps the app in a Suspense boundary for `useServerData` hooks.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AlabProvider({ children, fallback }: AlabProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/client/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjD,UAAU,iBAAiB;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAe,EAAE,EAAE,iBAAiB,2CAE5E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Root provider for Alab apps.
|
|
5
|
+
* Wraps the app in a Suspense boundary for `useServerData` hooks.
|
|
6
|
+
*/
|
|
7
|
+
export function AlabProvider({ children, fallback = null }) {
|
|
8
|
+
return _jsx(Suspense, { fallback: fallback, children: children });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/client/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAOjD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAqB;IAC3E,OAAO,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,YAAG,QAAQ,GAAY,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface BuildOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
/** Skip TypeScript type checking (--skip-typecheck flag). */
|
|
4
|
+
skipTypecheck?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Build mode:
|
|
7
|
+
* - `"ssr"` (default) — full SSR + client bundle, requires a Node.js server.
|
|
8
|
+
* - `"spa"` — pure client-side bundle, deployable to any CDN. No Node.js needed.
|
|
9
|
+
* Server functions become direct fetch calls to `/_alabjs/fn/*`; point these at
|
|
10
|
+
* a separate API server or use Cloudflare Workers for the data layer.
|
|
11
|
+
*/
|
|
12
|
+
mode?: "ssr" | "spa";
|
|
13
|
+
/** Open an interactive bundle size treemap after build (`--analyze`). */
|
|
14
|
+
analyze?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function build({ cwd, skipTypecheck, mode, analyze }: BuildOptions): Promise<void>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAKA,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACrB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsED,wBAAsB,KAAK,CAAC,EAAE,GAAG,EAAE,aAAqB,EAAE,IAAY,EAAE,OAAe,EAAE,EAAE,YAAY,iBAmFtG"}
|