what-server 0.10.0 → 0.11.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/README.md CHANGED
@@ -101,6 +101,154 @@ const submitForm = formAction(async (formData) => {
101
101
  });
102
102
  ```
103
103
 
104
+ ## Request Handler & CSRF (on by default)
105
+
106
+ `createRequestHandler` is the full-stack entry: route match → render → action
107
+ dispatch, as a Web-Fetch `(Request) => Response` handler.
108
+
109
+ ```js
110
+ import { createRequestHandler } from 'what-server';
111
+
112
+ const handle = createRequestHandler({ routes }); // CSRF protection is ON — zero config
113
+ ```
114
+
115
+ How the default CSRF wiring works (double-submit cookie):
116
+
117
+ 1. Every HTML response ensures a `what-csrf` cookie (`SameSite=Lax`, readable
118
+ by JS so the fetch client can echo it).
119
+ 2. Uncached HTML renders also embed `<meta name="what-csrf-token">` with the
120
+ same token (ISR-cached pages skip the meta tag so a per-user token is never
121
+ baked into shared cache entries — clients read the cookie instead).
122
+ 3. `POST /__what_action` validates the client-supplied token (`X-CSRF-Token`
123
+ header for fetch clients, `_csrf` form field for plain HTML forms) against
124
+ the cookie. Mismatch or missing token → `403`.
125
+
126
+ Opt out explicitly (e.g. token-authed APIs behind another gateway):
127
+
128
+ ```js
129
+ createRequestHandler({ routes, csrf: false });
130
+ ```
131
+
132
+ Passing your own `actionHandler` also disables the auto-provisioning — a
133
+ custom handler owns its CSRF policy via `createActionHandler({ getCsrfToken })`
134
+ or `{ skipCsrf: true }`.
135
+
136
+ ### Plain HTML form posts (progressive enhancement)
137
+
138
+ `POST /__what_action` accepts `application/x-www-form-urlencoded` bodies, so
139
+ forms work without any client JavaScript:
140
+
141
+ ```html
142
+ <form method="post" action="/__what_action">
143
+ <input type="hidden" name="_action" value="my-action-id">
144
+ <input type="hidden" name="_csrf" value="{token from the what-csrf-token meta tag}">
145
+ <input type="hidden" name="_redirect" value="/thanks">
146
+ <input name="email">
147
+ <button>Subscribe</button>
148
+ </form>
149
+ ```
150
+
151
+ - Action id: `_action` (or `data-action`) hidden field, or `?action=` query param.
152
+ - The action receives **one argument**: the form fields as a plain object
153
+ (reserved `_action` / `_csrf` / `_redirect` fields stripped; repeated field
154
+ names become arrays).
155
+ - Success responds `303 See Other` (POST/redirect/GET) to `_redirect` (must be
156
+ a local path), else the Referer path, else `/`.
157
+ - Failures respond with an HTML error page and a matching status (`403` bad
158
+ CSRF, `404` unknown action, `500` action error).
159
+
160
+ The JSON path is unchanged: fetch clients send `X-What-Action` + JSON
161
+ `{ args }` and get JSON back.
162
+
163
+ ## Deploying
164
+
165
+ All four deploy adapters wrap the same Web-Fetch core (`createRequestHandler`),
166
+ so routes, actions, CSRF, and ISR behave identically everywhere. Each adapter
167
+ below is covered by `test/deploy-readiness.test.js`.
168
+
169
+ ### Node (self-hosted: VPS, Docker, Fly, Railway)
170
+
171
+ ```js
172
+ // server.js
173
+ import { createServer } from 'what-server';
174
+ import { routes } from './routes.js';
175
+
176
+ createServer({ routes }).listen(process.env.PORT || 3000);
177
+ ```
178
+
179
+ Run `node server.js`. That's the deploy. `createServer` accepts the same
180
+ options as `createRequestHandler` (`cache` for ISR, `scheduler` for background
181
+ revalidation — the scheduler is stopped cleanly on SIGTERM/SIGINT). To mount
182
+ inside an existing Express/connect app use `whatMiddleware(options)` (calls
183
+ `next()` on 404), or convert any Web-Fetch handler with `toNodeListener(handler)`.
184
+
185
+ ### Static export (any CDN: Nginx, S3, GitHub Pages, Netlify)
186
+
187
+ ```js
188
+ import { exportStatic } from 'what-server';
189
+ const { pages } = await exportStatic({ routes, outDir: 'dist' });
190
+ ```
191
+
192
+ Renders every `static`/`hybrid` route to `dist/<path>/index.html` plus a
193
+ `__what_data.json` per page (loader data for client-side navigation). Dynamic
194
+ routes need `getStaticPaths`. Upload `dist/` to any static host — no special
195
+ server config required (the layout is plain `path/index.html`). Note: server
196
+ actions and per-user CSRF need a runtime — pair a static export with one of
197
+ the runtime adapters if you use actions.
198
+
199
+ ### Vercel
200
+
201
+ ```js
202
+ // build.mjs — emit a Build Output API v3 directory
203
+ import { buildVercelOutput } from 'what-server';
204
+
205
+ await buildVercelOutput({
206
+ files: { 'index.mjs': bundledHandlerCode }, // your build bundles routes + createVercelHandler into this
207
+ staticDir: 'public', // optional: CDN-served assets
208
+ });
209
+ ```
210
+
211
+ The function entry must export the Web-Fetch handler:
212
+
213
+ ```js
214
+ // (bundled into index.mjs)
215
+ import { createVercelHandler } from 'what-server';
216
+ import { routes } from './routes.js';
217
+ export default createVercelHandler({ routes });
218
+ ```
219
+
220
+ Then deploy the prebuilt output: `vercel deploy --prebuilt`. The emitted
221
+ layout is `config.json` (version 3, filesystem-first routing) +
222
+ `functions/render.func/{.vc-config.json,index.mjs}` + `static/`. ISR maps to
223
+ Vercel's native `s-maxage`/`stale-while-revalidate` headers emitted by the
224
+ cache engine. Calling `buildVercelOutput()` without `files` writes
225
+ `config.json` only (your build step owns `functions/` — backward compatible).
226
+
227
+ ### Cloudflare Workers
228
+
229
+ ```js
230
+ // worker.js
231
+ import { createCloudflareHandler } from 'what-server';
232
+ import { routes } from './routes.js';
233
+ export default createCloudflareHandler({ routes });
234
+ ```
235
+
236
+ ```toml
237
+ # wrangler.toml
238
+ name = "my-what-app"
239
+ main = "worker.js"
240
+ compatibility_date = "2026-01-01"
241
+ ```
242
+
243
+ `wrangler dev` to verify locally, `wrangler deploy` to ship. The handler is a
244
+ standard ES module worker (`{ fetch(request, env, ctx) }`); `env`/`ctx` are
245
+ exposed to loaders/renderers as `request.__env` / `request.__ctx` for KV/D1
246
+ bindings and `ctx.waitUntil`. For ISR across isolates, pass a KV/redis-backed
247
+ `what-isr` store as `cache` (in-memory caches don't survive isolate recycling).
248
+ Manual verification step (workerd is not a repo dependency): run
249
+ `wrangler dev worker.js` and load `/` — module shape and request handling are
250
+ covered by automated tests.
251
+
104
252
  ## Sub-path Exports
105
253
 
106
254
  | Path | Contents |
package/dist/actions.js CHANGED
@@ -8,7 +8,7 @@ async function revalidatePath(path, options) {
8
8
  if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);
9
9
  if (isDev) {
10
10
  console.warn(
11
- `[what] revalidatePath('${path}') had no effect: no cache engine is bound. Create a what-cache engine and bind it in your adapter (setRevalidationHandler).`
11
+ `[what] revalidatePath('${path}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`
12
12
  );
13
13
  }
14
14
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/actions.js", "../src/revalidation-registry.js"],
4
- "sourcesContent": ["// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\nimport { revalidatePath as serverRevalidatePath, revalidateTag as serverRevalidateTag } from './revalidation-registry.js';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(async result => {\n // Server-side cache revalidation: if the action declared revalidate paths\n // or tags, purge them through the bound cache engine (no-op if unbound).\n const opts = action.options || {};\n if (Array.isArray(opts.revalidate)) {\n for (const p of opts.revalidate) await serverRevalidatePath(p);\n }\n if (Array.isArray(opts.revalidateTags)) {\n for (const t of opts.revalidateTags) await serverRevalidateTag(t);\n }\n return { status: 200, body: result };\n })\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n", "// Revalidation registry \u2014 the indirection that lets app code call\n// revalidatePath()/revalidateTag() from `what-framework/server` while the actual\n// cache engine lives in the optional `what-cache` package. The deploy adapter\n// binds the engine at startup via setRevalidationHandler(); until then these are\n// safe no-ops (with a dev hint).\n\nlet _handler = null;\n\nconst isDev = typeof process !== 'undefined' ? process.env?.NODE_ENV !== 'production' : true;\n\n/** Bind a cache engine: setRevalidationHandler({ revalidatePath, revalidateTag }). */\nexport function setRevalidationHandler(handler) {\n _handler = handler;\n}\n\nexport function getRevalidationHandler() {\n return _handler;\n}\n\nexport async function revalidatePath(path, options) {\n if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);\n if (isDev) {\n console.warn(\n `[what] revalidatePath('${path}') had no effect: no cache engine is bound. ` +\n 'Create a what-cache engine and bind it in your adapter (setRevalidationHandler).'\n );\n }\n}\n\nexport async function revalidateTag(tag, options) {\n if (_handler && _handler.revalidateTag) return _handler.revalidateTag(tag, options);\n if (isDev) {\n console.warn(\n `[what] revalidateTag('${tag}') had no effect: no cache engine is bound.`\n );\n }\n}\n"],
4
+ "sourcesContent": ["// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\nimport { revalidatePath as serverRevalidatePath, revalidateTag as serverRevalidateTag } from './revalidation-registry.js';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(async result => {\n // Server-side cache revalidation: if the action declared revalidate paths\n // or tags, purge them through the bound cache engine (no-op if unbound).\n const opts = action.options || {};\n if (Array.isArray(opts.revalidate)) {\n for (const p of opts.revalidate) await serverRevalidatePath(p);\n }\n if (Array.isArray(opts.revalidateTags)) {\n for (const t of opts.revalidateTags) await serverRevalidateTag(t);\n }\n return { status: 200, body: result };\n })\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n", "// Revalidation registry \u2014 the indirection that lets app code call\n// revalidatePath()/revalidateTag() from `what-framework/server` while the actual\n// cache engine lives in the optional `what-isr` package. The deploy adapter\n// binds the engine at startup via setRevalidationHandler(); until then these are\n// safe no-ops (with a dev hint).\n\nlet _handler = null;\n\nconst isDev = typeof process !== 'undefined' ? process.env?.NODE_ENV !== 'production' : true;\n\n/** Bind a cache engine: setRevalidationHandler({ revalidatePath, revalidateTag }). */\nexport function setRevalidationHandler(handler) {\n _handler = handler;\n}\n\nexport function getRevalidationHandler() {\n return _handler;\n}\n\nexport async function revalidatePath(path, options) {\n if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);\n if (isDev) {\n console.warn(\n `[what] revalidatePath('${path}') had no effect: no cache engine is bound. ` +\n 'Create a what-isr engine and bind it in your adapter (setRevalidationHandler).'\n );\n }\n}\n\nexport async function revalidateTag(tag, options) {\n if (_handler && _handler.revalidateTag) return _handler.revalidateTag(tag, options);\n if (isDev) {\n console.warn(\n `[what] revalidateTag('${tag}') had no effect: no cache engine is bound.`\n );\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["action"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{signal as p,batch as v}from"what-core";var h=null,k=!(typeof process<"u");async function S(e,t){if(h&&h.revalidatePath)return h.revalidatePath(e,t);k&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-cache engine and bind it in your adapter (setRevalidationHandler).`)}async function b(e,t){if(h&&h.revalidateTag)return h.revalidateTag(e,t);k&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var A=new Map;function P(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function I(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function R(e,t){if(!e||!t||e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function U(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var T=0;function C(){return`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++T).toString(36)}_${Date.now().toString(36)}`}`}function O(e,t={}){let r=t.id||C(),{onError:n,onSuccess:l,revalidate:o}=t;typeof window>"u"&&A.set(r,{fn:e,options:t});async function a(...i){if(typeof window>"u")return e(...i);let s=t.timeout||3e4,u=new AbortController,c=setTimeout(()=>u.abort(),s);try{let f=P(),d={"Content-Type":"application/json","X-What-Action":r};f&&(d["X-CSRF-Token"]=f);let g=await fetch("/__what_action",{method:"POST",headers:d,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!g.ok){let w=await g.json().catch(()=>({message:"Action failed"}));throw new Error(w.message||"Action failed")}let y=await g.json();if(l&&l(y),o)for(let w of o)_(w);return y}catch(f){if(f.name==="AbortError"){let d=new Error(`Action "${r}" timed out after ${s}ms`);throw d.code="TIMEOUT",n&&n(d),d}throw n&&n(f),f}finally{clearTimeout(c)}}return a._actionId=r,a._isAction=!0,a}function x(e,t={}){let{onSuccess:r,onError:n,resetOnSuccess:l=!0}=t;return async o=>{let a,i;o instanceof Event?(o.preventDefault(),i=o.target,a=new FormData(i)):a=o;let s={},u=!1;for(let[c,f]of a.entries())typeof File<"u"&&f instanceof File&&(u=!0),s[c]?Array.isArray(s[c])?s[c].push(f):s[c]=[s[c],f]:s[c]=f;try{let c=u?await e(s,a):await e(s);return r&&r(c,i),l&&i&&i.reset(),c}catch(c){throw n&&n(c,i),c}}}function F(e){let t=p(!1),r=p(null),n=p(null);async function l(...o){t.set(!0),r.set(null);try{let a=await e(...o);return n.set(a),a}catch(a){throw r.set(a),a}finally{t.set(!1)}}return{trigger:l,isPending:()=>t(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function j(e,t={}){let{resetOnSuccess:r=!0}=t,n={current:null},l=F(x(e,{resetOnSuccess:r}));function o(a){a.preventDefault();let i=new FormData(a.target);return n.current=a.target,l.trigger(i)}return{...l,handleSubmit:o,formRef:n}}function M(e,t){let r=p(e),n=p([]),l=p(e);function o(u){let c=t(r.peek(),u);v(()=>{n.set([...n.peek(),u]),r.set(c)})}function a(u,c){v(()=>{if(n.set(n.peek().filter(f=>f!==u)),c!==void 0){l.set(c);let f=c;for(let d of n.peek())f=t(f,d);r.set(f)}})}function i(u,c){v(()=>{let f=n.peek().filter(y=>y!==u);n.set(f);let d=c!==void 0?c:l.peek();l.set(d);let g=d;for(let y of f)g=t(g,y);r.set(g)})}async function s(u,c){o(u);try{let f=await c();return a(u,f),f}catch(f){throw i(u),f}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:a,rollback:i,withOptimistic:s,set:u=>{r.set(u),l.set(u)}}}var m=new Map;function N(e,t){return m.has(e)||m.set(e,new Set),m.get(e).add(t),()=>{m.get(e)?.delete(t)}}function _(e){let t=m.get(e);if(t)for(let r of t)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function H(e,t,r,n={}){let{csrfToken:l,skipCsrf:o=!1}=n;if(!o){if(!l)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!R(i,l))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=A.get(t);return a?Array.isArray(r)?a.fn(...r).then(async i=>{let s=a.options||{};if(Array.isArray(s.revalidate))for(let u of s.revalidate)await S(u);if(Array.isArray(s.revalidateTags))for(let u of s.revalidateTags)await b(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${t}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function V(){return[...A.keys()]}function X(e,t={}){let{onSuccess:r,onError:n,onSettled:l}=t,o={isPending:p(!1),error:p(null),data:p(null)};async function a(...i){o.isPending.set(!0),o.error.set(null);try{let s=await e(...i);return o.data.set(s),r&&r(s,...i),s}catch(s){throw o.error.set(s),n&&n(s,...i),s}finally{o.isPending.set(!1),l&&l(o.data.peek(),o.error.peek(),...i)}}return{mutate:a,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}export{O as action,U as csrfMetaTag,x as formAction,I as generateCsrfToken,V as getRegisteredActions,H as handleActionRequest,_ as invalidatePath,N as onRevalidate,F as useAction,j as useFormAction,X as useMutation,M as useOptimistic,R as validateCsrfToken};
1
+ import{signal as p,batch as v}from"what-core";var h=null,k=!(typeof process<"u");async function S(e,t){if(h&&h.revalidatePath)return h.revalidatePath(e,t);k&&console.warn(`[what] revalidatePath('${e}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`)}async function b(e,t){if(h&&h.revalidateTag)return h.revalidateTag(e,t);k&&console.warn(`[what] revalidateTag('${e}') had no effect: no cache engine is bound.`)}var A=new Map;function P(){if(typeof document<"u"){let e=document.querySelector('meta[name="what-csrf-token"]');if(e)return e.getAttribute("content");let t=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(t)return decodeURIComponent(t[1])}return null}function I(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function R(e,t){if(!e||!t||e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function U(e){return`<meta name="what-csrf-token" content="${String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}">`}var T=0;function C(){return`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),t=>t.toString(16).padStart(2,"0")).join(""):`c${(++T).toString(36)}_${Date.now().toString(36)}`}`}function O(e,t={}){let r=t.id||C(),{onError:n,onSuccess:l,revalidate:o}=t;typeof window>"u"&&A.set(r,{fn:e,options:t});async function a(...i){if(typeof window>"u")return e(...i);let s=t.timeout||3e4,u=new AbortController,c=setTimeout(()=>u.abort(),s);try{let f=P(),d={"Content-Type":"application/json","X-What-Action":r};f&&(d["X-CSRF-Token"]=f);let g=await fetch("/__what_action",{method:"POST",headers:d,credentials:"same-origin",signal:u.signal,body:JSON.stringify({args:i})});if(!g.ok){let w=await g.json().catch(()=>({message:"Action failed"}));throw new Error(w.message||"Action failed")}let y=await g.json();if(l&&l(y),o)for(let w of o)_(w);return y}catch(f){if(f.name==="AbortError"){let d=new Error(`Action "${r}" timed out after ${s}ms`);throw d.code="TIMEOUT",n&&n(d),d}throw n&&n(f),f}finally{clearTimeout(c)}}return a._actionId=r,a._isAction=!0,a}function x(e,t={}){let{onSuccess:r,onError:n,resetOnSuccess:l=!0}=t;return async o=>{let a,i;o instanceof Event?(o.preventDefault(),i=o.target,a=new FormData(i)):a=o;let s={},u=!1;for(let[c,f]of a.entries())typeof File<"u"&&f instanceof File&&(u=!0),s[c]?Array.isArray(s[c])?s[c].push(f):s[c]=[s[c],f]:s[c]=f;try{let c=u?await e(s,a):await e(s);return r&&r(c,i),l&&i&&i.reset(),c}catch(c){throw n&&n(c,i),c}}}function F(e){let t=p(!1),r=p(null),n=p(null);async function l(...o){t.set(!0),r.set(null);try{let a=await e(...o);return n.set(a),a}catch(a){throw r.set(a),a}finally{t.set(!1)}}return{trigger:l,isPending:()=>t(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function j(e,t={}){let{resetOnSuccess:r=!0}=t,n={current:null},l=F(x(e,{resetOnSuccess:r}));function o(a){a.preventDefault();let i=new FormData(a.target);return n.current=a.target,l.trigger(i)}return{...l,handleSubmit:o,formRef:n}}function M(e,t){let r=p(e),n=p([]),l=p(e);function o(u){let c=t(r.peek(),u);v(()=>{n.set([...n.peek(),u]),r.set(c)})}function a(u,c){v(()=>{if(n.set(n.peek().filter(f=>f!==u)),c!==void 0){l.set(c);let f=c;for(let d of n.peek())f=t(f,d);r.set(f)}})}function i(u,c){v(()=>{let f=n.peek().filter(y=>y!==u);n.set(f);let d=c!==void 0?c:l.peek();l.set(d);let g=d;for(let y of f)g=t(g,y);r.set(g)})}async function s(u,c){o(u);try{let f=await c();return a(u,f),f}catch(f){throw i(u),f}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:a,rollback:i,withOptimistic:s,set:u=>{r.set(u),l.set(u)}}}var m=new Map;function N(e,t){return m.has(e)||m.set(e,new Set),m.get(e).add(t),()=>{m.get(e)?.delete(t)}}function _(e){let t=m.get(e);if(t)for(let r of t)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function H(e,t,r,n={}){let{csrfToken:l,skipCsrf:o=!1}=n;if(!o){if(!l)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let i=e?.headers?.["x-csrf-token"]||e?.headers?.["X-CSRF-Token"];if(!R(i,l))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let a=A.get(t);return a?Array.isArray(r)?a.fn(...r).then(async i=>{let s=a.options||{};if(Array.isArray(s.revalidate))for(let u of s.revalidate)await S(u);if(Array.isArray(s.revalidateTags))for(let u of s.revalidateTags)await b(u);return{status:200,body:i}}).catch(i=>(console.error(`[what] Action "${t}" error:`,i),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function V(){return[...A.keys()]}function X(e,t={}){let{onSuccess:r,onError:n,onSettled:l}=t,o={isPending:p(!1),error:p(null),data:p(null)};async function a(...i){o.isPending.set(!0),o.error.set(null);try{let s=await e(...i);return o.data.set(s),r&&r(s,...i),s}catch(s){throw o.error.set(s),n&&n(s,...i),s}finally{o.isPending.set(!1),l&&l(o.data.peek(),o.error.peek(),...i)}}return{mutate:a,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}export{O as action,U as csrfMetaTag,x as formAction,I as generateCsrfToken,V as getRegisteredActions,H as handleActionRequest,_ as invalidatePath,N as onRevalidate,F as useAction,j as useFormAction,X as useMutation,M as useOptimistic,R as validateCsrfToken};
2
2
  //# sourceMappingURL=actions.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/actions.js", "../src/revalidation-registry.js"],
4
- "sourcesContent": ["// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\nimport { revalidatePath as serverRevalidatePath, revalidateTag as serverRevalidateTag } from './revalidation-registry.js';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(async result => {\n // Server-side cache revalidation: if the action declared revalidate paths\n // or tags, purge them through the bound cache engine (no-op if unbound).\n const opts = action.options || {};\n if (Array.isArray(opts.revalidate)) {\n for (const p of opts.revalidate) await serverRevalidatePath(p);\n }\n if (Array.isArray(opts.revalidateTags)) {\n for (const t of opts.revalidateTags) await serverRevalidateTag(t);\n }\n return { status: 200, body: result };\n })\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n", "// Revalidation registry \u2014 the indirection that lets app code call\n// revalidatePath()/revalidateTag() from `what-framework/server` while the actual\n// cache engine lives in the optional `what-cache` package. The deploy adapter\n// binds the engine at startup via setRevalidationHandler(); until then these are\n// safe no-ops (with a dev hint).\n\nlet _handler = null;\n\nconst isDev = typeof process !== 'undefined' ? process.env?.NODE_ENV !== 'production' : true;\n\n/** Bind a cache engine: setRevalidationHandler({ revalidatePath, revalidateTag }). */\nexport function setRevalidationHandler(handler) {\n _handler = handler;\n}\n\nexport function getRevalidationHandler() {\n return _handler;\n}\n\nexport async function revalidatePath(path, options) {\n if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);\n if (isDev) {\n console.warn(\n `[what] revalidatePath('${path}') had no effect: no cache engine is bound. ` +\n 'Create a what-cache engine and bind it in your adapter (setRevalidationHandler).'\n );\n }\n}\n\nexport async function revalidateTag(tag, options) {\n if (_handler && _handler.revalidateTag) return _handler.revalidateTag(tag, options);\n if (isDev) {\n console.warn(\n `[what] revalidateTag('${tag}') had no effect: no cache engine is bound.`\n );\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\nimport { revalidatePath as serverRevalidatePath, revalidateTag as serverRevalidateTag } from './revalidation-registry.js';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(async result => {\n // Server-side cache revalidation: if the action declared revalidate paths\n // or tags, purge them through the bound cache engine (no-op if unbound).\n const opts = action.options || {};\n if (Array.isArray(opts.revalidate)) {\n for (const p of opts.revalidate) await serverRevalidatePath(p);\n }\n if (Array.isArray(opts.revalidateTags)) {\n for (const t of opts.revalidateTags) await serverRevalidateTag(t);\n }\n return { status: 200, body: result };\n })\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n", "// Revalidation registry \u2014 the indirection that lets app code call\n// revalidatePath()/revalidateTag() from `what-framework/server` while the actual\n// cache engine lives in the optional `what-isr` package. The deploy adapter\n// binds the engine at startup via setRevalidationHandler(); until then these are\n// safe no-ops (with a dev hint).\n\nlet _handler = null;\n\nconst isDev = typeof process !== 'undefined' ? process.env?.NODE_ENV !== 'production' : true;\n\n/** Bind a cache engine: setRevalidationHandler({ revalidatePath, revalidateTag }). */\nexport function setRevalidationHandler(handler) {\n _handler = handler;\n}\n\nexport function getRevalidationHandler() {\n return _handler;\n}\n\nexport async function revalidatePath(path, options) {\n if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);\n if (isDev) {\n console.warn(\n `[what] revalidatePath('${path}') had no effect: no cache engine is bound. ` +\n 'Create a what-isr engine and bind it in your adapter (setRevalidationHandler).'\n );\n }\n}\n\nexport async function revalidateTag(tag, options) {\n if (_handler && _handler.revalidateTag) return _handler.revalidateTag(tag, options);\n if (isDev) {\n console.warn(\n `[what] revalidateTag('${tag}') had no effect: no cache engine is bound.`\n );\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["signal", "batch", "_handler", "isDev", "revalidatePath", "path", "options", "_handler", "isDev", "revalidateTag", "tag", "actionRegistry", "getCsrfToken", "meta", "match", "generateCsrfToken", "arr", "b", "validateCsrfToken", "requestToken", "sessionToken", "result", "i", "csrfMetaTag", "token", "_actionCounter", "generateActionId", "action", "fn", "options", "id", "onError", "onSuccess", "revalidate", "callAction", "args", "timeout", "controller", "timeoutId", "csrfToken", "headers", "response", "error", "path", "invalidatePath", "timeoutError", "formAction", "actionFn", "resetOnSuccess", "formDataOrEvent", "formData", "form", "data", "hasFiles", "key", "value", "useAction", "isPending", "signal", "trigger", "e", "useFormAction", "formRef", "actionState", "handleSubmit", "useOptimistic", "initialValue", "reducer", "pending", "baseValue", "addOptimistic", "optimisticValue", "batch", "resolve", "serverValue", "a", "current", "rollback", "realValue", "newPending", "base", "withOptimistic", "asyncFn", "v", "revalidationCallbacks", "onRevalidate", "callback", "callbacks", "cb", "handleActionRequest", "req", "actionId", "sessionCsrfToken", "skipCsrf", "requestCsrfToken", "opts", "p", "revalidatePath", "t", "revalidateTag", "getRegisteredActions", "useMutation", "mutationFn", "onSettled", "state", "mutate"]
7
7
  }