@agilesyndrome/cf-genai-base 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/CONTRACT.md ADDED
@@ -0,0 +1,44 @@
1
+ # Shared site contract
2
+
3
+ Every site built from this foundation follows the same edge contract.
4
+
5
+ ## Worker entrypoint
6
+
7
+ `createWorker({ fetch, auth?, scheduled?, security? })` owns the Worker lifecycle.
8
+ The site router owns pages, APIs, D1 queries, and R2 object keys. `scheduled`
9
+ is optional and must use `ctx.waitUntil` for background work.
10
+
11
+ ## Routes
12
+
13
+ - `GET /health` returns `{ ok, version, build_number }` and is cache-disabled.
14
+ - `GET /api/me` returns `{ user: null | { sub, email, name, ...roles } }`.
15
+ - `/auth/login`, `/auth/callback`, and `/auth/logout` are reserved for auth.
16
+ - Public APIs must be explicitly listed in auth configuration.
17
+ - Mutating `/api/*` requests require a same-origin `Origin` header.
18
+
19
+ ## Environment and bindings
20
+
21
+ Required OIDC secrets for the standard auth plugin:
22
+
23
+ - `OIDC_ISSUER`
24
+ - `OIDC_CLIENT_ID`
25
+ - `OIDC_CLIENT_SECRET`
26
+ - `AUTH_SESSION_SECRET`
27
+
28
+ Standard bindings:
29
+
30
+ - `DB`: primary D1 database for durable application records.
31
+ - `ASSETS`: static asset binding when the site has a frontend bundle.
32
+ - `R2_*`: optional R2 buckets for files or photos; use a descriptive suffix.
33
+
34
+ Build metadata is optional: `BUILD_SHA` and `BUILD_NUMBER`.
35
+
36
+ D1 migrations are committed with the site, applied by Wrangler, and are the
37
+ source of truth for schema changes. R2 stores binary data; metadata and access
38
+ control remain in D1.
39
+
40
+ ## User and role contract
41
+
42
+ Auth returns a stable `sub`, normalized lowercase `email`, and display `name`.
43
+ Applications may add roles or an internal D1 user id in `onLogin`; authorization
44
+ must remain in the application router rather than in the shared auth package.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agile Syndrome
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # `@easleyowl/cf-genai-base`
2
+
3
+ Opinionated startup boilerplate for small Cloudflare Workers.
4
+
5
+ The base is deliberately small: a site still owns its router, HTML, D1
6
+ queries, R2 keys, and scheduled jobs. `createWorker` composes an optional auth
7
+ handler, normalizes uncaught failures, and applies baseline response headers.
8
+ Use D1 bindings for durable application data and R2 bindings for binary assets;
9
+ do not put either into module-level state.
10
+
11
+ ```js
12
+ import { createWorker, healthResponse } from "@easleyowl/cf-genai-base";
13
+
14
+ export default createWorker({
15
+ auth: (request, env) => auth.handle(request, env),
16
+ fetch: async (request, env) => {
17
+ if (new URL(request.url).pathname === "/health") return healthResponse(env);
18
+ return router(request, env);
19
+ },
20
+ });
21
+ ```
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@agilesyndrome/cf-genai-base",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.js"
7
+ },
8
+ "description": "Lean Worker lifecycle and security helpers for Cloudflare sites.",
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "README.md",
16
+ "CONTRACT.md",
17
+ "LICENSE"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/agilesyndrome/cf-genai-base.git"
22
+ },
23
+ "homepage": "https://github.com/agilesyndrome/cf-genai-base#readme"
24
+ }
package/src/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Small, opinionated Worker composition layer shared by the Easley Owl sites.
3
+ * Site code owns routing and data access; this owns lifecycle and the edges.
4
+ */
5
+ export function createWorker({ fetch, scheduled, auth, security = true }) {
6
+ return {
7
+ async fetch(request, env, ctx) {
8
+ try {
9
+ const authResponse = auth ? await auth(request, env, ctx) : null;
10
+ const response = authResponse || await fetch(request, env, ctx);
11
+ return security ? secureResponse(response) : response;
12
+ } catch (error) {
13
+ console.error("[worker] request failed", error);
14
+ return secureResponse(Response.json({ error: "Internal server error" }, { status: 500 }));
15
+ }
16
+ },
17
+ ...(scheduled ? { scheduled } : {}),
18
+ };
19
+ }
20
+
21
+ export function secureResponse(response) {
22
+ const headers = new Headers(response.headers);
23
+ headers.set("X-Content-Type-Options", "nosniff");
24
+ headers.set("X-Frame-Options", "DENY");
25
+ headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
26
+ headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(), usb=()");
27
+ headers.set("Cross-Origin-Opener-Policy", "same-origin");
28
+ headers.set("Strict-Transport-Security", "max-age=31536000");
29
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
30
+ }
31
+
32
+ export function methodNotAllowed(allow = "GET") {
33
+ return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
34
+ }
35
+
36
+ export function healthResponse(env, details = {}) {
37
+ return Response.json({
38
+ ok: true,
39
+ version: String(env.BUILD_SHA || "unknown").slice(0, 7),
40
+ build_number: env.BUILD_NUMBER ? String(env.BUILD_NUMBER) : null,
41
+ ...details,
42
+ }, { headers: { "Cache-Control": "no-store" } });
43
+ }