@limlabs/rex 0.0.1-test.1 → 0.3.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.
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ interface HtmlProps extends React.HTMLAttributes<HTMLHtmlElement> {
3
+ children?: React.ReactNode;
4
+ }
5
+ interface HeadProps {
6
+ children?: React.ReactNode;
7
+ }
8
+ export declare function Html({ children, ...props }: HtmlProps): React.ReactElement;
9
+ export declare function Head({ children }: HeadProps): React.ReactElement;
10
+ export declare function Main(): React.ReactElement;
11
+ export declare function NextScript(): null;
12
+ export default function Document(): React.ReactElement;
13
+ export {};
14
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC;IAC/D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,UAAU,SAAS;IACjB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CAE1E;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CAEhE;AAED,wBAAgB,IAAI,IAAI,KAAK,CAAC,YAAY,CAEzC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,IAAI,KAAK,CAAC,YAAY,CAYrD"}
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export function Html({ children, ...props }) {
3
+ return React.createElement('html', props, children);
4
+ }
5
+ export function Head({ children }) {
6
+ return React.createElement('head', null, children);
7
+ }
8
+ export function Main() {
9
+ return React.createElement('div', { id: '__rex' });
10
+ }
11
+ export function NextScript() {
12
+ return null;
13
+ }
14
+ export default function Document() {
15
+ return React.createElement(Html, null, React.createElement(Head, null), React.createElement('body', null, React.createElement(Main, null), React.createElement(NextScript, null)));
16
+ }
17
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAa;IACpD,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAa;IAC1C,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ;IAC9B,OAAO,KAAK,CAAC,aAAa,CACxB,IAAI,EACJ,IAAI,EACJ,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAC/B,KAAK,CAAC,aAAa,CACjB,MAAM,EACN,IAAI,EACJ,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAC/B,KAAK,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CACtC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface ImageProps {
3
+ src: string;
4
+ width?: number;
5
+ height?: number;
6
+ alt?: string;
7
+ quality?: number;
8
+ priority?: boolean;
9
+ className?: string;
10
+ }
11
+ export default function Image(props: ImageProps): React.ReactElement;
12
+ export {};
13
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAAC,YAAY,CAgBnE"}
package/dist/image.js ADDED
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ function buildSrc(src, w, q) {
3
+ return '/_rex/image?url=' + encodeURIComponent(src) + '&w=' + w + '&q=' + q;
4
+ }
5
+ export default function Image(props) {
6
+ const { src, width, height, alt, quality = 75, priority = false, className } = props;
7
+ const imgProps = {
8
+ alt: alt ?? '',
9
+ src: buildSrc(src, width ?? 1920, quality),
10
+ loading: priority ? 'eager' : 'lazy',
11
+ decoding: 'async',
12
+ style: { display: 'block', maxWidth: '100%', height: 'auto' },
13
+ };
14
+ if (width !== undefined)
15
+ imgProps.width = width;
16
+ if (height !== undefined)
17
+ imgProps.height = height;
18
+ if (className)
19
+ imgProps.className = className;
20
+ return React.createElement('img', imgProps);
21
+ }
22
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,SAAS,QAAQ,CAAC,GAAW,EAAE,CAAS,EAAE,CAAS;IACjD,OAAO,kBAAkB,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAiB;IAC7C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAErF,MAAM,QAAQ,GAA8C;QAC1D,GAAG,EAAE,GAAG,IAAI,EAAE;QACd,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;QAC1C,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QACpC,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;KAC9D,CAAC;IAEF,IAAI,KAAK,KAAK,SAAS;QAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;IAChD,IAAI,MAAM,KAAK,SAAS;QAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;IACnD,IAAI,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC;IAE9C,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { Html, Head, Main, NextScript } from './document.js';
2
+ export { default as Link } from './link.js';
3
+ export { useRouter, navigateTo } from './router.js';
4
+ export { default as Image } from './image.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Rex framework exports
2
+ export { Html, Head, Main, NextScript } from './document.js';
3
+ export { default as Link } from './link.js';
4
+ export { useRouter, navigateTo } from './router.js';
5
+ export { default as Image } from './image.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC"}
package/dist/link.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
3
+ href: string;
4
+ }
5
+ /**
6
+ * rex/link - Client-side navigation link.
7
+ * Renders an <a> tag that intercepts clicks for SPA navigation.
8
+ */
9
+ export default function Link({ href, children, target, ...rest }: LinkProps): React.ReactElement;
10
+ export {};
11
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,SAAU,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CA2B/F"}
package/dist/link.js ADDED
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { navigateTo } from './router.js';
3
+ /**
4
+ * rex/link - Client-side navigation link.
5
+ * Renders an <a> tag that intercepts clicks for SPA navigation.
6
+ */
7
+ export default function Link({ href, children, target, ...rest }) {
8
+ function handleClick(e) {
9
+ if (e.metaKey ||
10
+ e.ctrlKey ||
11
+ e.shiftKey ||
12
+ e.altKey ||
13
+ target === '_blank' ||
14
+ (href && (href.startsWith('http://') || href.startsWith('https://')))) {
15
+ return;
16
+ }
17
+ e.preventDefault();
18
+ navigateTo(href);
19
+ }
20
+ return React.createElement('a', {
21
+ href,
22
+ onClick: handleClick,
23
+ target,
24
+ ...rest,
25
+ }, children);
26
+ }
27
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.js","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzC;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAa;IACzE,SAAS,WAAW,CAAC,CAAsC;QACzD,IACE,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,MAAM;YACR,MAAM,KAAK,QAAQ;YACnB,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EACrE,CAAC;YACD,OAAO;QACT,CAAC;QAED,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC,aAAa,CACxB,GAAG,EACH;QACE,IAAI;QACJ,OAAO,EAAE,WAAW;QACpB,MAAM;QACN,GAAG,IAAI;KACR,EACD,QAAQ,CACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ export interface RouterEvents {
2
+ on(event: string, handler: (...args: unknown[]) => void): void;
3
+ off(event: string, handler: (...args: unknown[]) => void): void;
4
+ emit(event: string, ...args: unknown[]): void;
5
+ }
6
+ export interface RexRouter {
7
+ pathname: string;
8
+ asPath: string;
9
+ query: Record<string, string>;
10
+ route: string;
11
+ push(url: string): void;
12
+ replace(url: string): void;
13
+ back(): void;
14
+ forward(): void;
15
+ reload(): void;
16
+ prefetch(url: string): void;
17
+ events: RouterEvents;
18
+ isReady: boolean;
19
+ }
20
+ /**
21
+ * Navigate to a new path via client-side routing.
22
+ */
23
+ export declare function navigateTo(path: string): void;
24
+ /**
25
+ * React hook that returns the current router instance.
26
+ */
27
+ export declare function useRouter(): RexRouter;
28
+ export default useRouter;
29
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAChB,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AA0BD;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO7C;AAaD;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAmCrC;AAED,eAAe,SAAS,CAAC"}
package/dist/router.js ADDED
@@ -0,0 +1,65 @@
1
+ function getRouter() {
2
+ return window.__REX_ROUTER ?? null;
3
+ }
4
+ /**
5
+ * Navigate to a new path via client-side routing.
6
+ */
7
+ export function navigateTo(path) {
8
+ const r = getRouter();
9
+ if (r) {
10
+ r.push(path);
11
+ }
12
+ else {
13
+ window.location.href = path;
14
+ }
15
+ }
16
+ function parseQuery(search) {
17
+ const query = {};
18
+ if (!search || search.length <= 1)
19
+ return query;
20
+ const pairs = search.substring(1).split('&');
21
+ for (const pair of pairs) {
22
+ const [key, value] = pair.split('=');
23
+ query[decodeURIComponent(key)] = decodeURIComponent(value ?? '');
24
+ }
25
+ return query;
26
+ }
27
+ /**
28
+ * React hook that returns the current router instance.
29
+ */
30
+ export function useRouter() {
31
+ const r = getRouter();
32
+ const noop = () => { };
33
+ if (r?.state) {
34
+ return {
35
+ pathname: r.state.pathname,
36
+ asPath: r.state.asPath,
37
+ query: r.state.query,
38
+ route: r.state.route,
39
+ push: r.push,
40
+ replace: r.replace,
41
+ back: r.back,
42
+ forward: r.forward,
43
+ reload: r.reload,
44
+ prefetch: r.prefetch,
45
+ events: r.events,
46
+ isReady: true,
47
+ };
48
+ }
49
+ return {
50
+ pathname: window.location.pathname,
51
+ asPath: window.location.pathname + window.location.search,
52
+ query: parseQuery(window.location.search),
53
+ route: window.location.pathname,
54
+ push: (url) => { window.location.href = url; },
55
+ replace: (url) => { window.location.replace(url); },
56
+ back: () => { history.back(); },
57
+ forward: () => { history.forward(); },
58
+ reload: () => { window.location.reload(); },
59
+ prefetch: noop,
60
+ events: { on: noop, off: noop, emit: noop },
61
+ isReady: false,
62
+ };
63
+ }
64
+ export default useRouter;
65
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ export interface RexApiRequest {
2
+ /** HTTP method (e.g. "GET", "POST"). */
3
+ method: string;
4
+ /** The request URL path. */
5
+ url: string;
6
+ /** Request headers as a plain object. */
7
+ headers: Record<string, string>;
8
+ /** Parsed query parameters. */
9
+ query: Record<string, string>;
10
+ /** Request body (parsed JSON or raw string). */
11
+ body: unknown;
12
+ /** Request cookies as a plain object. */
13
+ cookies: Record<string, string>;
14
+ }
15
+ export interface RexApiResponse<T = unknown> {
16
+ /** Set the HTTP status code. */
17
+ status(code: number): RexApiResponse<T>;
18
+ /** Set a response header. */
19
+ setHeader(name: string, value: string): RexApiResponse<T>;
20
+ /** Send a JSON response. */
21
+ json(data: T): RexApiResponse<T>;
22
+ /** Send a string or object response. */
23
+ send(body: string | T): RexApiResponse<T>;
24
+ /** End the response. */
25
+ end(body?: string): RexApiResponse<T>;
26
+ /** Redirect to a URL, optionally with a status code. */
27
+ redirect(url: string): RexApiResponse<T>;
28
+ redirect(status: number, url: string): RexApiResponse<T>;
29
+ }
30
+ /** @deprecated Use `RexApiRequest` instead. */
31
+ export type NextApiRequest = RexApiRequest;
32
+ /** @deprecated Use `RexApiResponse` instead. */
33
+ export type NextApiResponse<T = unknown> = RexApiResponse<T>;
34
+ export interface RexOptions {
35
+ /** Path to the project root directory (containing pages/). */
36
+ root: string;
37
+ /** Whether to run in dev mode (enables HMR, error overlays). */
38
+ dev?: boolean;
39
+ }
40
+ export interface RouteMatch {
41
+ /** The route pattern, e.g. "/blog/:slug" */
42
+ pattern: string;
43
+ /** The module name, e.g. "blog/[slug]" */
44
+ moduleName: string;
45
+ /** Matched params, e.g. { slug: "hello" } */
46
+ params: Record<string, string>;
47
+ }
48
+ export interface PageResult {
49
+ /** Full HTML document. */
50
+ html: string;
51
+ /** HTTP status code. */
52
+ status: number;
53
+ /** Response headers. */
54
+ headers: Array<{
55
+ key: string;
56
+ value: string;
57
+ }>;
58
+ }
59
+ export interface RexInstance {
60
+ /** Whether this instance is running in dev mode. */
61
+ readonly isDev: boolean;
62
+ /** The current build ID. */
63
+ readonly buildId: string;
64
+ /** The path to the static files directory (client JS/CSS bundles). */
65
+ readonly staticDir: string;
66
+ /**
67
+ * Match a URL path against the route trie.
68
+ * Returns the matched route info with params, or null if no match.
69
+ */
70
+ matchRoute(path: string): RouteMatch | null;
71
+ /**
72
+ * Run getServerSideProps for a given path and return the result.
73
+ */
74
+ getServerSideProps(path: string): Promise<Record<string, unknown>>;
75
+ /**
76
+ * Render a page to an HTML string with the given props.
77
+ */
78
+ renderToString(path: string, props: Record<string, unknown>): Promise<string>;
79
+ /**
80
+ * Render a full page (GSSP + SSR + document assembly).
81
+ * Returns HTML, status code, and headers.
82
+ */
83
+ renderPage(path: string): Promise<PageResult>;
84
+ /**
85
+ * Get a request handler function compatible with the Web Fetch API.
86
+ * Returns `(req: Request) => Promise<Response>`.
87
+ */
88
+ getRequestHandler(): (req: Request) => Promise<Response>;
89
+ /**
90
+ * Shut down the Rex instance, releasing V8 isolates and other resources.
91
+ */
92
+ close(): Promise<void>;
93
+ }
94
+ /**
95
+ * Create a new Rex application instance.
96
+ *
97
+ * Scans the pages directory, builds bundles, initializes the V8 isolate pool,
98
+ * and returns a ready-to-use RexInstance.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { createRex } from '@limlabs/rex/server'
103
+ *
104
+ * const rex = await createRex({ root: './my-app' })
105
+ * const handle = rex.getRequestHandler()
106
+ *
107
+ * Bun.serve({ fetch: handle })
108
+ * ```
109
+ */
110
+ export declare const createRex: (options: RexOptions) => Promise<RexInstance>;
111
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,gDAAgD;IAChD,IAAI,EAAE,OAAO,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,gCAAgC;IAChC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IACxC,6BAA6B;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1D,4BAA4B;IAC5B,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IACjC,wCAAwC;IACxC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1C,wBAAwB;IACxB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IACtC,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;CAC1D;AAED,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC;AAC3C,gDAAgD;AAChD,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4BAA4B;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAE5C;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAEnE;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9E;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE9C;;;OAGG;IACH,iBAAiB,IAAI,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEzD;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAwCD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS,YArDD,UAAU,KAAG,OAAO,CAAC,WAAW,CAqDX,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,49 @@
1
+ import { createRequire } from 'node:module';
2
+ import { arch, platform } from 'node:process';
3
+ function loadNativeBinding() {
4
+ const require = createRequire(import.meta.url);
5
+ const platformPackage = `@limlabs/rex-${platform}-${arch}`;
6
+ // Try platform-specific package first (for published npm packages)
7
+ try {
8
+ return require(platformPackage);
9
+ }
10
+ catch {
11
+ // Fall through to local .node file (development)
12
+ }
13
+ // Try local .node file (built with napi build)
14
+ try {
15
+ return require('../rex-napi.node');
16
+ }
17
+ catch {
18
+ // Fall through
19
+ }
20
+ // Try the build output from cargo
21
+ try {
22
+ return require('../../crates/rex_napi/rex-napi.node');
23
+ }
24
+ catch {
25
+ // Fall through
26
+ }
27
+ throw new Error(`Failed to load Rex native binding. ` +
28
+ `Tried: ${platformPackage}, ../rex-napi.node, ../../crates/rex_napi/rex-napi.node. ` +
29
+ `Make sure the native module is built (cd crates/rex_napi && npm run build-debug).`);
30
+ }
31
+ const binding = loadNativeBinding();
32
+ /**
33
+ * Create a new Rex application instance.
34
+ *
35
+ * Scans the pages directory, builds bundles, initializes the V8 isolate pool,
36
+ * and returns a ready-to-use RexInstance.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { createRex } from '@limlabs/rex/server'
41
+ *
42
+ * const rex = await createRex({ root: './my-app' })
43
+ * const handle = rex.getRequestHandler()
44
+ *
45
+ * Bun.serve({ fetch: handle })
46
+ * ```
47
+ */
48
+ export const createRex = binding.createRex;
49
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AA+G9C,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,gBAAgB,QAAQ,IAAI,IAAI,EAAE,CAAC;IAE3D,mEAAmE;IACnE,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,eAAe,CAAkB,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IAED,+CAA+C;IAC/C,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,kBAAkB,CAAkB,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,kCAAkC;IAClC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,qCAAqC,CAAkB,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,qCAAqC;QACrC,UAAU,eAAe,2DAA2D;QACpF,mFAAmF,CACpF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;AAEpC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,16 +1,53 @@
1
1
  {
2
2
  "name": "@limlabs/rex",
3
- "version": "0.0.1-test.1",
3
+ "version": "0.3.0",
4
4
  "description": "Rex - Next.js Pages Router reimplemented in Rust",
5
- "main": "src/index.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "exports": {
7
- ".": "./src/index.js",
8
- "./document": "./src/document.js",
9
- "./link": "./src/link.js",
10
- "./router": "./src/router.js"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./server": {
13
+ "types": "./dist/server.d.ts",
14
+ "import": "./dist/server.js"
15
+ },
16
+ "./document": {
17
+ "types": "./dist/document.d.ts",
18
+ "import": "./dist/document.js"
19
+ },
20
+ "./link": {
21
+ "types": "./dist/link.d.ts",
22
+ "import": "./dist/link.js"
23
+ },
24
+ "./router": {
25
+ "types": "./dist/router.d.ts",
26
+ "import": "./dist/router.js"
27
+ },
28
+ "./image": {
29
+ "types": "./dist/image.d.ts",
30
+ "import": "./dist/image.js"
31
+ },
32
+ "./middleware": {
33
+ "types": "./src/middleware.d.ts",
34
+ "import": "../../runtime/server/middleware.js"
35
+ }
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.0.0",
43
+ "@types/react": "^19.0.0",
44
+ "typescript": "^5.7.0"
45
+ },
46
+ "dependencies": {
47
+ "oxlint": "^1.0.0"
11
48
  },
12
49
  "peerDependencies": {
13
- "react": "^18.0.0",
14
- "react-dom": "^18.0.0"
50
+ "react": "^18.0.0 || ^19.0.0",
51
+ "react-dom": "^18.0.0 || ^19.0.0"
15
52
  }
16
53
  }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+
3
+ interface HtmlProps extends React.HTMLAttributes<HTMLHtmlElement> {
4
+ children?: React.ReactNode;
5
+ }
6
+
7
+ interface HeadProps {
8
+ children?: React.ReactNode;
9
+ }
10
+
11
+ export function Html({ children, ...props }: HtmlProps): React.ReactElement {
12
+ return React.createElement('html', props, children);
13
+ }
14
+
15
+ export function Head({ children }: HeadProps): React.ReactElement {
16
+ return React.createElement('head', null, children);
17
+ }
18
+
19
+ export function Main(): React.ReactElement {
20
+ return React.createElement('div', { id: '__rex' });
21
+ }
22
+
23
+ export function NextScript(): null {
24
+ return null;
25
+ }
26
+
27
+ export default function Document(): React.ReactElement {
28
+ return React.createElement(
29
+ Html,
30
+ null,
31
+ React.createElement(Head, null),
32
+ React.createElement(
33
+ 'body',
34
+ null,
35
+ React.createElement(Main, null),
36
+ React.createElement(NextScript, null)
37
+ )
38
+ );
39
+ }
package/src/image.tsx ADDED
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+
3
+ interface ImageProps {
4
+ src: string;
5
+ width?: number;
6
+ height?: number;
7
+ alt?: string;
8
+ quality?: number;
9
+ priority?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ function buildSrc(src: string, w: number, q: number): string {
14
+ return '/_rex/image?url=' + encodeURIComponent(src) + '&w=' + w + '&q=' + q;
15
+ }
16
+
17
+ export default function Image(props: ImageProps): React.ReactElement {
18
+ const { src, width, height, alt, quality = 75, priority = false, className } = props;
19
+
20
+ const imgProps: React.ImgHTMLAttributes<HTMLImageElement> = {
21
+ alt: alt ?? '',
22
+ src: buildSrc(src, width ?? 1920, quality),
23
+ loading: priority ? 'eager' : 'lazy',
24
+ decoding: 'async',
25
+ style: { display: 'block', maxWidth: '100%', height: 'auto' },
26
+ };
27
+
28
+ if (width !== undefined) imgProps.width = width;
29
+ if (height !== undefined) imgProps.height = height;
30
+ if (className) imgProps.className = className;
31
+
32
+ return React.createElement('img', imgProps);
33
+ }
@@ -2,3 +2,4 @@
2
2
  export { Html, Head, Main, NextScript } from './document.js';
3
3
  export { default as Link } from './link.js';
4
4
  export { useRouter, navigateTo } from './router.js';
5
+ export { default as Image } from './image.js';
@@ -1,16 +1,16 @@
1
1
  import React from 'react';
2
2
  import { navigateTo } from './router.js';
3
3
 
4
+ interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
5
+ href: string;
6
+ }
7
+
4
8
  /**
5
- * rex/link - Client-side navigation link
9
+ * rex/link - Client-side navigation link.
6
10
  * Renders an <a> tag that intercepts clicks for SPA navigation.
7
11
  */
8
- export default function Link({ href, children, target, rel, className, style, ...rest }) {
9
- function handleClick(e) {
10
- // Don't intercept if:
11
- // - modifier key is held (user wants new tab)
12
- // - target is set (external link behavior)
13
- // - href is external
12
+ export default function Link({ href, children, target, ...rest }: LinkProps): React.ReactElement {
13
+ function handleClick(e: React.MouseEvent<HTMLAnchorElement>): void {
14
14
  if (
15
15
  e.metaKey ||
16
16
  e.ctrlKey ||
@@ -29,12 +29,9 @@ export default function Link({ href, children, target, rel, className, style, ..
29
29
  return React.createElement(
30
30
  'a',
31
31
  {
32
- href: href,
32
+ href,
33
33
  onClick: handleClick,
34
- target: target,
35
- rel: rel,
36
- className: className,
37
- style: style,
34
+ target,
38
35
  ...rest,
39
36
  },
40
37
  children
@@ -0,0 +1,84 @@
1
+ export interface NextURL {
2
+ /** The full URL string. */
3
+ href: string;
4
+ /** The pathname portion of the URL (e.g. "/blog/hello"). */
5
+ pathname: string;
6
+ /** The search/query string (e.g. "?foo=bar"). */
7
+ search: string;
8
+ /** String representation of the URL. */
9
+ toString(): string;
10
+ }
11
+
12
+ export interface NextRequest {
13
+ /** HTTP method (e.g. "GET", "POST"). */
14
+ method: string;
15
+ /** The full request URL string. */
16
+ url: string;
17
+ /** Parsed URL object with pathname, search, etc. */
18
+ nextUrl: NextURL;
19
+ /** Request headers as a plain object. */
20
+ headers: Record<string, string>;
21
+ /** Request cookies as a plain object. */
22
+ cookies: Record<string, string>;
23
+ }
24
+
25
+ export interface NextResponseInit {
26
+ /** Headers to add to the response. */
27
+ headers?: Record<string, string>;
28
+ /** Headers/options to modify the downstream request. */
29
+ request?: {
30
+ headers?: Record<string, string>;
31
+ };
32
+ }
33
+
34
+ export declare class NextResponse {
35
+ /**
36
+ * Continue to the next handler, optionally modifying response or request headers.
37
+ */
38
+ static next(init?: NextResponseInit): NextResponse;
39
+
40
+ /**
41
+ * Redirect the request to a different URL.
42
+ * @param url - The target URL (string or URL object).
43
+ * @param status - HTTP status code (default: 307).
44
+ */
45
+ static redirect(url: string | URL, status?: number): NextResponse;
46
+
47
+ /**
48
+ * Rewrite the request to a different URL without changing the browser URL.
49
+ * @param url - The target URL (string or URL object).
50
+ */
51
+ static rewrite(url: string | URL): NextResponse;
52
+ }
53
+
54
+ export interface MiddlewareConfig {
55
+ /**
56
+ * Route patterns to match. Supports:
57
+ * - Exact paths: "/about"
58
+ * - Dynamic segments: "/blog/:slug"
59
+ * - Catch-all: "/api/:path*"
60
+ *
61
+ * If omitted, middleware runs on all routes.
62
+ */
63
+ matcher?: string[];
64
+ }
65
+
66
+ /**
67
+ * Middleware function signature.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { NextResponse } from '@limlabs/rex/middleware'
72
+ * import type { MiddlewareFunction } from '@limlabs/rex/middleware'
73
+ *
74
+ * export const middleware: MiddlewareFunction = (request) => {
75
+ * if (request.nextUrl.pathname === '/old') {
76
+ * return NextResponse.redirect(new URL('/new', request.url))
77
+ * }
78
+ * return NextResponse.next()
79
+ * }
80
+ *
81
+ * export const config = { matcher: ['/old', '/dashboard/:path*'] }
82
+ * ```
83
+ */
84
+ export type MiddlewareFunction = (request: NextRequest) => NextResponse;
package/src/router.ts ADDED
@@ -0,0 +1,109 @@
1
+ export interface RouterEvents {
2
+ on(event: string, handler: (...args: unknown[]) => void): void;
3
+ off(event: string, handler: (...args: unknown[]) => void): void;
4
+ emit(event: string, ...args: unknown[]): void;
5
+ }
6
+
7
+ export interface RexRouter {
8
+ pathname: string;
9
+ asPath: string;
10
+ query: Record<string, string>;
11
+ route: string;
12
+ push(url: string): void;
13
+ replace(url: string): void;
14
+ back(): void;
15
+ forward(): void;
16
+ reload(): void;
17
+ prefetch(url: string): void;
18
+ events: RouterEvents;
19
+ isReady: boolean;
20
+ }
21
+
22
+ interface InternalRouter {
23
+ push(path: string): void;
24
+ replace(path: string): void;
25
+ back(): void;
26
+ forward(): void;
27
+ reload(): void;
28
+ prefetch(url: string): void;
29
+ events: RouterEvents;
30
+ state: {
31
+ pathname: string;
32
+ asPath: string;
33
+ query: Record<string, string>;
34
+ route: string;
35
+ };
36
+ }
37
+
38
+ declare const window: Window & {
39
+ __REX_ROUTER?: InternalRouter;
40
+ };
41
+
42
+ function getRouter(): InternalRouter | null {
43
+ return window.__REX_ROUTER ?? null;
44
+ }
45
+
46
+ /**
47
+ * Navigate to a new path via client-side routing.
48
+ */
49
+ export function navigateTo(path: string): void {
50
+ const r = getRouter();
51
+ if (r) {
52
+ r.push(path);
53
+ } else {
54
+ window.location.href = path;
55
+ }
56
+ }
57
+
58
+ function parseQuery(search: string): Record<string, string> {
59
+ const query: Record<string, string> = {};
60
+ if (!search || search.length <= 1) return query;
61
+ const pairs = search.substring(1).split('&');
62
+ for (const pair of pairs) {
63
+ const [key, value] = pair.split('=');
64
+ query[decodeURIComponent(key)] = decodeURIComponent(value ?? '');
65
+ }
66
+ return query;
67
+ }
68
+
69
+ /**
70
+ * React hook that returns the current router instance.
71
+ */
72
+ export function useRouter(): RexRouter {
73
+ const r = getRouter();
74
+ const noop = (): void => {};
75
+
76
+ if (r?.state) {
77
+ return {
78
+ pathname: r.state.pathname,
79
+ asPath: r.state.asPath,
80
+ query: r.state.query,
81
+ route: r.state.route,
82
+ push: r.push,
83
+ replace: r.replace,
84
+ back: r.back,
85
+ forward: r.forward,
86
+ reload: r.reload,
87
+ prefetch: r.prefetch,
88
+ events: r.events,
89
+ isReady: true,
90
+ };
91
+ }
92
+
93
+ return {
94
+ pathname: window.location.pathname,
95
+ asPath: window.location.pathname + window.location.search,
96
+ query: parseQuery(window.location.search),
97
+ route: window.location.pathname,
98
+ push: (url: string) => { window.location.href = url; },
99
+ replace: (url: string) => { window.location.replace(url); },
100
+ back: () => { history.back(); },
101
+ forward: () => { history.forward(); },
102
+ reload: () => { window.location.reload(); },
103
+ prefetch: noop,
104
+ events: { on: noop, off: noop, emit: noop },
105
+ isReady: false,
106
+ };
107
+ }
108
+
109
+ export default useRouter;
package/src/server.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { createRequire } from 'node:module';
2
+ import { arch, platform } from 'node:process';
3
+
4
+ // --- Public types ---
5
+
6
+ export interface RexApiRequest {
7
+ /** HTTP method (e.g. "GET", "POST"). */
8
+ method: string;
9
+ /** The request URL path. */
10
+ url: string;
11
+ /** Request headers as a plain object. */
12
+ headers: Record<string, string>;
13
+ /** Parsed query parameters. */
14
+ query: Record<string, string>;
15
+ /** Request body (parsed JSON or raw string). */
16
+ body: unknown;
17
+ /** Request cookies as a plain object. */
18
+ cookies: Record<string, string>;
19
+ }
20
+
21
+ export interface RexApiResponse<T = unknown> {
22
+ /** Set the HTTP status code. */
23
+ status(code: number): RexApiResponse<T>;
24
+ /** Set a response header. */
25
+ setHeader(name: string, value: string): RexApiResponse<T>;
26
+ /** Send a JSON response. */
27
+ json(data: T): RexApiResponse<T>;
28
+ /** Send a string or object response. */
29
+ send(body: string | T): RexApiResponse<T>;
30
+ /** End the response. */
31
+ end(body?: string): RexApiResponse<T>;
32
+ /** Redirect to a URL, optionally with a status code. */
33
+ redirect(url: string): RexApiResponse<T>;
34
+ redirect(status: number, url: string): RexApiResponse<T>;
35
+ }
36
+
37
+ /** @deprecated Use `RexApiRequest` instead. */
38
+ export type NextApiRequest = RexApiRequest;
39
+ /** @deprecated Use `RexApiResponse` instead. */
40
+ export type NextApiResponse<T = unknown> = RexApiResponse<T>;
41
+
42
+ export interface RexOptions {
43
+ /** Path to the project root directory (containing pages/). */
44
+ root: string;
45
+ /** Whether to run in dev mode (enables HMR, error overlays). */
46
+ dev?: boolean;
47
+ }
48
+
49
+ export interface RouteMatch {
50
+ /** The route pattern, e.g. "/blog/:slug" */
51
+ pattern: string;
52
+ /** The module name, e.g. "blog/[slug]" */
53
+ moduleName: string;
54
+ /** Matched params, e.g. { slug: "hello" } */
55
+ params: Record<string, string>;
56
+ }
57
+
58
+ export interface PageResult {
59
+ /** Full HTML document. */
60
+ html: string;
61
+ /** HTTP status code. */
62
+ status: number;
63
+ /** Response headers. */
64
+ headers: Array<{ key: string; value: string }>;
65
+ }
66
+
67
+ export interface RexInstance {
68
+ /** Whether this instance is running in dev mode. */
69
+ readonly isDev: boolean;
70
+ /** The current build ID. */
71
+ readonly buildId: string;
72
+ /** The path to the static files directory (client JS/CSS bundles). */
73
+ readonly staticDir: string;
74
+
75
+ /**
76
+ * Match a URL path against the route trie.
77
+ * Returns the matched route info with params, or null if no match.
78
+ */
79
+ matchRoute(path: string): RouteMatch | null;
80
+
81
+ /**
82
+ * Run getServerSideProps for a given path and return the result.
83
+ */
84
+ getServerSideProps(path: string): Promise<Record<string, unknown>>;
85
+
86
+ /**
87
+ * Render a page to an HTML string with the given props.
88
+ */
89
+ renderToString(path: string, props: Record<string, unknown>): Promise<string>;
90
+
91
+ /**
92
+ * Render a full page (GSSP + SSR + document assembly).
93
+ * Returns HTML, status code, and headers.
94
+ */
95
+ renderPage(path: string): Promise<PageResult>;
96
+
97
+ /**
98
+ * Get a request handler function compatible with the Web Fetch API.
99
+ * Returns `(req: Request) => Promise<Response>`.
100
+ */
101
+ getRequestHandler(): (req: Request) => Promise<Response>;
102
+
103
+ /**
104
+ * Shut down the Rex instance, releasing V8 isolates and other resources.
105
+ */
106
+ close(): Promise<void>;
107
+ }
108
+
109
+ interface NativeBinding {
110
+ createRex(options: RexOptions): Promise<RexInstance>;
111
+ }
112
+
113
+ function loadNativeBinding(): NativeBinding {
114
+ const require = createRequire(import.meta.url);
115
+ const platformPackage = `@limlabs/rex-${platform}-${arch}`;
116
+
117
+ // Try platform-specific package first (for published npm packages)
118
+ try {
119
+ return require(platformPackage) as NativeBinding;
120
+ } catch {
121
+ // Fall through to local .node file (development)
122
+ }
123
+
124
+ // Try local .node file (built with napi build)
125
+ try {
126
+ return require('../rex-napi.node') as NativeBinding;
127
+ } catch {
128
+ // Fall through
129
+ }
130
+
131
+ // Try the build output from cargo
132
+ try {
133
+ return require('../../crates/rex_napi/rex-napi.node') as NativeBinding;
134
+ } catch {
135
+ // Fall through
136
+ }
137
+
138
+ throw new Error(
139
+ `Failed to load Rex native binding. ` +
140
+ `Tried: ${platformPackage}, ../rex-napi.node, ../../crates/rex_napi/rex-napi.node. ` +
141
+ `Make sure the native module is built (cd crates/rex_napi && npm run build-debug).`
142
+ );
143
+ }
144
+
145
+ const binding = loadNativeBinding();
146
+
147
+ /**
148
+ * Create a new Rex application instance.
149
+ *
150
+ * Scans the pages directory, builds bundles, initializes the V8 isolate pool,
151
+ * and returns a ready-to-use RexInstance.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { createRex } from '@limlabs/rex/server'
156
+ *
157
+ * const rex = await createRex({ root: './my-app' })
158
+ * const handle = rex.getRequestHandler()
159
+ *
160
+ * Bun.serve({ fetch: handle })
161
+ * ```
162
+ */
163
+ export const createRex = binding.createRex;
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "dist",
15
+ "rootDir": "src"
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/src/document.js DELETED
@@ -1,40 +0,0 @@
1
- import React from 'react';
2
-
3
- /**
4
- * rex/document exports
5
- * These components are meaningful during SSR and provide structure for the HTML document.
6
- * On the client side, they are essentially pass-throughs.
7
- */
8
-
9
- export function Html({ children, ...props }) {
10
- return React.createElement('html', props, children);
11
- }
12
-
13
- export function Head({ children }) {
14
- return React.createElement('head', null, children);
15
- }
16
-
17
- export function Main() {
18
- // During SSR, this is replaced with the actual page content
19
- return React.createElement('div', { id: '__rex' });
20
- }
21
-
22
- export function NextScript() {
23
- // During SSR, this is replaced with the actual script tags
24
- return null;
25
- }
26
-
27
- // Default document component
28
- export default function Document() {
29
- return React.createElement(
30
- Html,
31
- null,
32
- React.createElement(Head, null),
33
- React.createElement(
34
- 'body',
35
- null,
36
- React.createElement(Main, null),
37
- React.createElement(NextScript, null)
38
- )
39
- );
40
- }
package/src/router.js DELETED
@@ -1,114 +0,0 @@
1
- /**
2
- * rex/router - Client-side router
3
- * Handles SPA navigation by fetching page data and re-rendering.
4
- */
5
-
6
- let _initialized = false;
7
- let _currentPath = null;
8
- let _buildId = null;
9
-
10
- /**
11
- * Initialize the client-side router.
12
- * Called automatically on page load.
13
- */
14
- export function initializeRouter() {
15
- if (_initialized) return;
16
- _initialized = true;
17
-
18
- _currentPath = window.location.pathname;
19
- _buildId = window.__REX_BUILD_ID__ || '';
20
-
21
- // Listen for browser back/forward
22
- window.addEventListener('popstate', function(event) {
23
- var path = window.location.pathname;
24
- if (path !== _currentPath) {
25
- _currentPath = path;
26
- loadPage(path, false);
27
- }
28
- });
29
- }
30
-
31
- /**
32
- * Navigate to a new path via client-side routing.
33
- */
34
- export function navigateTo(path) {
35
- if (path === _currentPath) return;
36
-
37
- _currentPath = path;
38
- window.history.pushState(null, '', path);
39
- loadPage(path, true);
40
- }
41
-
42
- /**
43
- * Get the current route information (React hook style, but simplified).
44
- */
45
- export function useRouter() {
46
- return {
47
- pathname: window.location.pathname,
48
- query: parseQuery(window.location.search),
49
- push: navigateTo,
50
- back: function() { window.history.back(); },
51
- };
52
- }
53
-
54
- async function loadPage(path, isNavigation) {
55
- try {
56
- // Fetch page data
57
- var dataPath = path === '/' ? '/index' : path;
58
- var dataUrl = '/_rex/data/' + _buildId + dataPath + '.json';
59
-
60
- var response = await fetch(dataUrl);
61
-
62
- if (response.status === 404) {
63
- // Build ID mismatch or route not found - full reload
64
- window.location.href = path;
65
- return;
66
- }
67
-
68
- if (!response.ok) {
69
- throw new Error('Failed to fetch page data: ' + response.status);
70
- }
71
-
72
- var data = await response.json();
73
-
74
- if (data.redirect) {
75
- navigateTo(data.redirect.destination);
76
- return;
77
- }
78
-
79
- if (data.notFound) {
80
- // Show 404
81
- window.location.href = path;
82
- return;
83
- }
84
-
85
- // For the prototype, we do a full page reload
86
- // A full implementation would dynamically import the page module
87
- // and re-render using window.__REX_ROOT__.render()
88
- window.location.href = path;
89
-
90
- } catch (err) {
91
- console.error('[Rex Router] Navigation error:', err);
92
- window.location.href = path;
93
- }
94
- }
95
-
96
- function parseQuery(search) {
97
- var query = {};
98
- if (!search || search.length <= 1) return query;
99
- var pairs = search.substring(1).split('&');
100
- for (var i = 0; i < pairs.length; i++) {
101
- var pair = pairs[i].split('=');
102
- query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
103
- }
104
- return query;
105
- }
106
-
107
- // Auto-initialize when loaded in browser
108
- if (typeof window !== 'undefined') {
109
- if (document.readyState === 'loading') {
110
- document.addEventListener('DOMContentLoaded', initializeRouter);
111
- } else {
112
- initializeRouter();
113
- }
114
- }