koguma 0.4.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,70 @@
1
+ /**
2
+ * React hooks for Koguma.
3
+ *
4
+ * Usage:
5
+ * import { useEntry, useEntries } from "koguma/react";
6
+ * const { data, loading, error } = useEntry<FourDirectionsPage>("fourDirectionsPage", "single");
7
+ * const { data: cards } = useEntries<FeatureCard>("featureCard");
8
+ */
9
+ import { useEffect, useState } from "react";
10
+ import { getClient } from "../client/index.ts";
11
+
12
+ export interface UseEntryResult<T> {
13
+ data: T | null;
14
+ loading: boolean;
15
+ error: string | null;
16
+ }
17
+
18
+ export interface UseEntriesResult<T> {
19
+ data: T[];
20
+ loading: boolean;
21
+ error: string | null;
22
+ }
23
+
24
+ // Module-level cache: { "featureCard": Promise<T[]>, "fourDirectionsPage:single": Promise<T> }
25
+ const cache = new Map<string, Promise<unknown>>();
26
+
27
+ /** Fetch and cache a single entry */
28
+ export function useEntry<T = Record<string, unknown>>(
29
+ contentType: string,
30
+ id: string,
31
+ ): UseEntryResult<T> {
32
+ const [data, setData] = useState<T | null>(null);
33
+ const [loading, setLoading] = useState(true);
34
+ const [error, setError] = useState<string | null>(null);
35
+
36
+ useEffect(() => {
37
+ const key = `${contentType}:${id}`;
38
+ if (!cache.has(key)) {
39
+ cache.set(key, getClient().get<T>(contentType, id));
40
+ }
41
+ (cache.get(key) as Promise<T>)
42
+ .then(setData)
43
+ .catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
44
+ .finally(() => setLoading(false));
45
+ }, [contentType, id]);
46
+
47
+ return { data, loading, error };
48
+ }
49
+
50
+ /** Fetch and cache all entries of a type */
51
+ export function useEntries<T = Record<string, unknown>>(
52
+ contentType: string,
53
+ ): UseEntriesResult<T> {
54
+ const [data, setData] = useState<T[]>([]);
55
+ const [loading, setLoading] = useState(true);
56
+ const [error, setError] = useState<string | null>(null);
57
+
58
+ useEffect(() => {
59
+ const key = `list:${contentType}`;
60
+ if (!cache.has(key)) {
61
+ cache.set(key, getClient().list<T>(contentType));
62
+ }
63
+ (cache.get(key) as Promise<T[]>)
64
+ .then(setData)
65
+ .catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
66
+ .finally(() => setLoading(false));
67
+ }, [contentType]);
68
+
69
+ return { data, loading, error };
70
+ }
package/src/worker.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * createWorker — the main entry point for Koguma.
3
+ *
4
+ * This is what the developer imports in their worker.ts:
5
+ *
6
+ * import { createWorker } from "koguma/worker";
7
+ * import config from "./site.config";
8
+ * export default createWorker(config);
9
+ */
10
+ import { Hono } from "hono";
11
+ import type { KogumaConfig } from "./config/define.ts";
12
+ import { createApiRouter } from "./api/router.ts";
13
+ import { adminHtml } from "./admin/dashboard.ts";
14
+
15
+ type Env = Record<string, unknown>;
16
+
17
+
18
+ export function createWorker(config: KogumaConfig) {
19
+ const app = new Hono();
20
+
21
+ // Mount the API
22
+ const api = createApiRouter(config);
23
+ app.route("/", api);
24
+
25
+ // Admin SPA — catch-all for /admin* routes
26
+ app.get("/admin", (c) => {
27
+ return c.html(adminHtml(config.siteName));
28
+ });
29
+ app.get("/admin/*", (c) => {
30
+ return c.html(adminHtml(config.siteName));
31
+ });
32
+
33
+ return {
34
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
35
+ const url = new URL(request.url);
36
+
37
+ // API and admin routes handled by Hono
38
+ if (url.pathname.startsWith("/api/") || url.pathname.startsWith("/admin")) {
39
+ return app.fetch(request, env, ctx);
40
+ }
41
+
42
+ // Everything else — serve static assets (Vite build)
43
+ const assets = env.ASSETS as { fetch: typeof fetch } | undefined;
44
+ if (assets) {
45
+ return assets.fetch(request);
46
+ }
47
+
48
+ return new Response("Not found", { status: 404 });
49
+ },
50
+ };
51
+ }