@tschk/moonshine-next 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # `@tschk/moonshine-next`
2
+
3
+ Next.js adapter for Moonshine. Re-exports real Next.js libraries on subpaths
4
+ plus moonshine signal bridges.
5
+
6
+ ```ts
7
+ import { createSignal, useSignal } from "@tschk/moonshine-next";
8
+ import { useRouter, usePathnameSignal } from "@tschk/moonshine-next/navigation";
9
+ import Link from "@tschk/moonshine-next/link";
10
+ import Image from "@tschk/moonshine-next/image";
11
+ import { headers } from "@tschk/moonshine-next/headers";
12
+ import { useFragmentShader } from "@tschk/moonshine-next/shaders";
13
+ import { moonshineRoute, moonshineJson } from "@tschk/moonshine-next/server";
14
+ ```
15
+
16
+ | Subpath | Host library |
17
+ | ---------------- | -------------------------------------------------- |
18
+ | `.` / `./client` | moonshine signals + resources (`"use client"`) |
19
+ | `./navigation` | `next/navigation` + pathname/search signals |
20
+ | `./link` | `next/link` |
21
+ | `./image` | `next/image` |
22
+ | `./headers` | `next/headers` (server only) |
23
+ | `./dynamic` | `next/dynamic` |
24
+ | `./shaders` | moonshine WebGL helpers |
25
+ | `./server` | `moonshineRoute` / `Json` / `Html` + pages helpers |
26
+
27
+ Peer: `next` >= 15, `react` >= 18, `react-dom` >= 18.
28
+
29
+ Does **not** export `MoonshineRouter` — use the Next.js App Router.
30
+
31
+ See [Moonshine README](../../README.md).
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@tschk/moonshine-next",
3
+ "version": "0.3.1",
4
+ "description": "Next.js adapter — moonshine signals + next/navigation|link|image|headers|shaders",
5
+ "license": "ISC",
6
+ "type": "module",
7
+ "main": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": "./src/index.ts",
11
+ "./client": "./src/client.ts",
12
+ "./server": "./src/server.ts",
13
+ "./navigation": "./src/navigation.ts",
14
+ "./link": "./src/link.ts",
15
+ "./image": "./src/image.ts",
16
+ "./headers": "./src/headers.ts",
17
+ "./dynamic": "./src/dynamic.ts",
18
+ "./shaders": "./src/shaders.ts"
19
+ },
20
+ "dependencies": {
21
+ "@tschk/moonshine": "^0.3.1",
22
+ "@tschk/moonshine-shaders": "^0.3.1"
23
+ },
24
+ "peerDependencies": {
25
+ "next": ">=15",
26
+ "react": ">=18",
27
+ "react-dom": ">=18"
28
+ },
29
+ "files": [
30
+ "src",
31
+ "README.md"
32
+ ],
33
+ "keywords": [
34
+ "moonshine",
35
+ "next",
36
+ "adapter"
37
+ ],
38
+ "scripts": {
39
+ "format": "bunx prettier --check .",
40
+ "lint": "bunx oxlint .",
41
+ "typecheck": "exit 0",
42
+ "test": "bun test"
43
+ }
44
+ }
package/src/client.ts ADDED
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Next Client Component surface: moonshine signals + resources.
5
+ * Stack libraries live on subpaths: `/navigation`, `/link`, `/image`, `/shaders`.
6
+ */
7
+ export * from "@tschk/moonshine/host-react";
8
+
9
+ export {
10
+ createIslandSignal,
11
+ createResource,
12
+ useResource,
13
+ } from "@tschk/moonshine/host-react";
package/src/dynamic.ts ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+
3
+ /**
4
+ * next/dynamic re-export for lazy client islands.
5
+ */
6
+ export { default } from "next/dynamic";
package/src/headers.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * next/headers re-export (Server Components / Route Handlers only).
3
+ *
4
+ * ```ts
5
+ * import { headers, cookies } from "@tschk/moonshine-next/headers";
6
+ * ```
7
+ */
8
+ export { cookies, draftMode, headers } from "next/headers";
package/src/image.ts ADDED
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ /**
4
+ * next/image re-export for moonshine apps.
5
+ *
6
+ * ```ts
7
+ * import Image from "@tschk/moonshine-next/image";
8
+ * ```
9
+ */
10
+ export { default } from "next/image";
11
+ export type { ImageProps } from "next/image";
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ /**
4
+ * @tschk/moonshine-next
5
+ *
6
+ * Default export = **client** surface. For Route Handlers:
7
+ * import { moonshineRoute, moonshineJson } from "@tschk/moonshine-next/server"
8
+ *
9
+ * Does not export MoonshineRouter — use the App Router.
10
+ */
11
+ export * from "./client";
package/src/link.ts ADDED
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ /**
4
+ * next/link re-export for moonshine apps.
5
+ *
6
+ * ```ts
7
+ * import Link from "@tschk/moonshine-next/link";
8
+ * ```
9
+ */
10
+ export { default } from "next/link";
11
+ export type { LinkProps } from "next/link";
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ /**
4
+ * next/navigation + moonshine signal bridges.
5
+ *
6
+ * ```ts
7
+ * import { usePathname, usePathnameSignal, useRouter } from "@tschk/moonshine-next/navigation";
8
+ * ```
9
+ */
10
+ import { useEffect, useMemo, useRef } from "react";
11
+ import {
12
+ useParams as useNextParams,
13
+ usePathname as useNextPathname,
14
+ useRouter as useNextRouter,
15
+ useSearchParams as useNextSearchParams,
16
+ notFound,
17
+ permanentRedirect,
18
+ redirect,
19
+ useSelectedLayoutSegment,
20
+ useSelectedLayoutSegments,
21
+ } from "next/navigation";
22
+ import { createSignal, useSignal, type Signal } from "@tschk/moonshine/react";
23
+
24
+ export {
25
+ useNextRouter as useRouter,
26
+ useNextPathname as usePathname,
27
+ useNextSearchParams as useSearchParams,
28
+ useNextParams as useParams,
29
+ redirect,
30
+ permanentRedirect,
31
+ notFound,
32
+ useSelectedLayoutSegment,
33
+ useSelectedLayoutSegments,
34
+ };
35
+
36
+ /** Pathname as a live moonshine signal (updates on App Router navigation). */
37
+ export function usePathnameSignal(): Signal<string> {
38
+ const pathname = useNextPathname();
39
+ const signal = useMemo(() => createSignal(pathname), []);
40
+ const ref = useRef(signal);
41
+ ref.current = signal;
42
+ useEffect(() => {
43
+ ref.current.set(pathname);
44
+ }, [pathname]);
45
+ return signal;
46
+ }
47
+
48
+ /** Search string `?a=1` as signal. */
49
+ export function useSearchSignal(): Signal<string> {
50
+ const sp = useNextSearchParams();
51
+ const value = sp?.toString() ? `?${sp.toString()}` : "";
52
+ const signal = useMemo(() => createSignal(value), []);
53
+ useEffect(() => {
54
+ signal.set(value);
55
+ }, [value, signal]);
56
+ return signal;
57
+ }
58
+
59
+ /** Read a single search param as signal. */
60
+ export function useSearchParamSignal(
61
+ name: string,
62
+ fallback = "",
63
+ ): Signal<string> {
64
+ const sp = useNextSearchParams();
65
+ const value = sp?.get(name) ?? fallback;
66
+ const signal = useMemo(() => createSignal(value), []);
67
+ useEffect(() => {
68
+ signal.set(value);
69
+ }, [value, signal]);
70
+ return signal;
71
+ }
72
+
73
+ /** Convenience: useSignal(usePathnameSignal()). */
74
+ export function useReactivePathname(): string {
75
+ return useSignal(usePathnameSignal());
76
+ }
package/src/server.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Next.js server surface — Route Handlers / Server Actions helpers.
3
+ * No client hooks.
4
+ */
5
+ export {
6
+ createMoonshineServer,
7
+ definePage,
8
+ handleMoonshineRequest,
9
+ resolvePage,
10
+ resolveStaticPath,
11
+ toMoonshineRequest,
12
+ tryServeStatic,
13
+ } from "@tschk/moonshine/server";
14
+ export type {
15
+ MoonshinePageModule,
16
+ MoonshineRequest,
17
+ MoonshineServer,
18
+ MoonshineServerOptions,
19
+ } from "@tschk/moonshine/server";
20
+
21
+ /** JSON Response helper for Route Handlers. */
22
+ export function moonshineJson(
23
+ data: unknown,
24
+ init: ResponseInit = {},
25
+ ): Response {
26
+ const headers = new Headers(init.headers);
27
+ if (!headers.has("content-type")) {
28
+ headers.set("content-type", "application/json; charset=utf-8");
29
+ }
30
+ return new Response(JSON.stringify(data), { ...init, headers });
31
+ }
32
+
33
+ /** HTML Response helper. */
34
+ export function moonshineHtml(html: string, init: ResponseInit = {}): Response {
35
+ const headers = new Headers(init.headers);
36
+ if (!headers.has("content-type")) {
37
+ headers.set("content-type", "text/html; charset=utf-8");
38
+ }
39
+ return new Response(html, { ...init, headers });
40
+ }
41
+
42
+ /**
43
+ * Wrap an async handler for `app/api/.../route.ts`.
44
+ *
45
+ * ```ts
46
+ * export const GET = moonshineRoute(async (req) => moonshineJson({ ok: true }));
47
+ * ```
48
+ */
49
+ export function moonshineRoute(
50
+ handler: (request: Request) => Response | Promise<Response>,
51
+ ): (request: Request) => Promise<Response> {
52
+ return async (request) => {
53
+ try {
54
+ return await handler(request);
55
+ } catch (e) {
56
+ const message = e instanceof Error ? e.message : String(e);
57
+ return moonshineJson({ error: message }, { status: 500 });
58
+ }
59
+ };
60
+ }
package/src/shaders.ts ADDED
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WebGL fragment helpers (moonshine shaders) under the Next adapter path.
5
+ *
6
+ * ```ts
7
+ * import { useFragmentShader } from "@tschk/moonshine-next/shaders";
8
+ * ```
9
+ */
10
+ export {
11
+ createFullscreenFragment,
12
+ useFragmentShader,
13
+ wrapFragmentSource,
14
+ } from "@tschk/moonshine-shaders";
15
+ export type {
16
+ FragmentShaderHandle,
17
+ UseFragmentShaderOptions,
18
+ } from "@tschk/moonshine-shaders";
package/src/shims.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ /** Ambient types so adapter builds without installing Next in every workspace. */
2
+ declare module "next/navigation" {
3
+ export function useRouter(): {
4
+ push(href: string): void;
5
+ replace(href: string): void;
6
+ back(): void;
7
+ forward(): void;
8
+ refresh(): void;
9
+ prefetch(href: string): void;
10
+ };
11
+ export function usePathname(): string;
12
+ export function useSearchParams(): URLSearchParams;
13
+ export function useParams<
14
+ T extends Record<string, string | string[]> = Record<string, string>,
15
+ >(): T;
16
+ export function redirect(url: string): never;
17
+ export function notFound(): never;
18
+ export function permanentRedirect(url: string): never;
19
+ export function useSelectedLayoutSegment(): string | null;
20
+ export function useSelectedLayoutSegments(): string[];
21
+ }
22
+
23
+ declare module "next/link" {
24
+ import type {
25
+ ComponentType,
26
+ ReactNode,
27
+ CSSProperties,
28
+ MouseEventHandler,
29
+ } from "react";
30
+ export type LinkProps = {
31
+ href: string;
32
+ children?: ReactNode;
33
+ className?: string;
34
+ style?: CSSProperties;
35
+ replace?: boolean;
36
+ scroll?: boolean;
37
+ prefetch?: boolean;
38
+ onClick?: MouseEventHandler<HTMLAnchorElement>;
39
+ [key: string]: unknown;
40
+ };
41
+ const Link: ComponentType<LinkProps>;
42
+ export default Link;
43
+ }
44
+
45
+ declare module "next/image" {
46
+ import type { ComponentType, CSSProperties } from "react";
47
+ export type ImageProps = {
48
+ src: string | { src: string; height: number; width: number };
49
+ alt: string;
50
+ width?: number;
51
+ height?: number;
52
+ fill?: boolean;
53
+ className?: string;
54
+ style?: CSSProperties;
55
+ priority?: boolean;
56
+ quality?: number;
57
+ sizes?: string;
58
+ [key: string]: unknown;
59
+ };
60
+ const Image: ComponentType<ImageProps>;
61
+ export default Image;
62
+ }
63
+
64
+ declare module "next/headers" {
65
+ export function headers(): Headers;
66
+ export function cookies(): {
67
+ get(name: string): { name: string; value: string } | undefined;
68
+ getAll(): Array<{ name: string; value: string }>;
69
+ has(name: string): boolean;
70
+ set(name: string, value: string): void;
71
+ delete(name: string): void;
72
+ };
73
+ export function draftMode(): {
74
+ isEnabled: boolean;
75
+ enable(): void;
76
+ disable(): void;
77
+ };
78
+ }
79
+
80
+ declare module "next/dynamic" {
81
+ import type { ComponentType } from "react";
82
+ export default function dynamic<P = object>(
83
+ loader: () => Promise<{ default: ComponentType<P> } | ComponentType<P>>,
84
+ options?: { ssr?: boolean; loading?: ComponentType },
85
+ ): ComponentType<P>;
86
+ }