@nexus_js/server 0.6.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/actions.d.ts +158 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +396 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/context.d.ts +41 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +68 -0
  10. package/dist/context.js.map +1 -0
  11. package/dist/csrf.d.ts +56 -0
  12. package/dist/csrf.d.ts.map +1 -0
  13. package/dist/csrf.js +153 -0
  14. package/dist/csrf.js.map +1 -0
  15. package/dist/dev-assets.d.ts +31 -0
  16. package/dist/dev-assets.d.ts.map +1 -0
  17. package/dist/dev-assets.js +198 -0
  18. package/dist/dev-assets.js.map +1 -0
  19. package/dist/error-boundary.d.ts +87 -0
  20. package/dist/error-boundary.d.ts.map +1 -0
  21. package/dist/error-boundary.js +181 -0
  22. package/dist/error-boundary.js.map +1 -0
  23. package/dist/index.d.ts +44 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +277 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/load-module.d.ts +26 -0
  28. package/dist/load-module.d.ts.map +1 -0
  29. package/dist/load-module.js +288 -0
  30. package/dist/load-module.js.map +1 -0
  31. package/dist/logger.d.ts +63 -0
  32. package/dist/logger.d.ts.map +1 -0
  33. package/dist/logger.js +158 -0
  34. package/dist/logger.js.map +1 -0
  35. package/dist/navigate.d.ts +21 -0
  36. package/dist/navigate.d.ts.map +1 -0
  37. package/dist/navigate.js +45 -0
  38. package/dist/navigate.js.map +1 -0
  39. package/dist/rate-limit.d.ts +71 -0
  40. package/dist/rate-limit.d.ts.map +1 -0
  41. package/dist/rate-limit.js +136 -0
  42. package/dist/rate-limit.js.map +1 -0
  43. package/dist/renderer.d.ts +92 -0
  44. package/dist/renderer.d.ts.map +1 -0
  45. package/dist/renderer.js +386 -0
  46. package/dist/renderer.js.map +1 -0
  47. package/dist/streaming.d.ts +98 -0
  48. package/dist/streaming.d.ts.map +1 -0
  49. package/dist/streaming.js +216 -0
  50. package/dist/streaming.js.map +1 -0
  51. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nexus Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @nexus_js/server
2
+
3
+ Nexus HTTP server — SSR, Server Actions, Edge-ready.
4
+
5
+ ## Documentation
6
+
7
+ All guides, API reference, and examples live on **[nexusjs.dev](https://nexusjs.dev)**.
8
+
9
+ ## Links
10
+
11
+ - **Website:** [https://nexusjs.dev](https://nexusjs.dev)
12
+ - **Repository:** [github.com/bierfor/nexus](https://github.com/bierfor/nexus) (see `packages/server/`)
13
+ - **Issues:** [github.com/bierfor/nexus/issues](https://github.com/bierfor/nexus/issues)
14
+
15
+ ## License
16
+
17
+ MIT © Nexus contributors
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Nexus Server Actions — type-safe, race-condition-safe server mutations.
3
+ *
4
+ * Race Condition Problem:
5
+ * User clicks "Save" three times in rapid succession.
6
+ * Request 1 arrives, starts processing (200ms).
7
+ * Request 2 arrives, starts processing (180ms) — finishes FIRST.
8
+ * Request 3 arrives, starts processing (150ms) — finishes SECOND.
9
+ * Request 1 finishes LAST — overwrites the results of 2 and 3. 💥
10
+ *
11
+ * Solutions implemented:
12
+ *
13
+ * 1. Idempotency key deduplication:
14
+ * Client sends X-Nexus-Idempotency: <uuid> with each action call.
15
+ * If the same key arrives again while the first is in flight,
16
+ * the server returns the SAME response (cached for 30s).
17
+ *
18
+ * 2. Per-island action mutex:
19
+ * Each island tracks its in-flight actions per action name.
20
+ * Configurable behavior: 'cancel' | 'queue' | 'reject' | 'ignore'.
21
+ *
22
+ * 3. AbortController propagation:
23
+ * The signal is passed into the action context. Actions that
24
+ * call external APIs should check ctx.signal.aborted.
25
+ * If the client disconnects, the signal fires automatically.
26
+ *
27
+ * 4. Client-side: $optimistic with built-in race guard.
28
+ * The createOptimistic() pending flag blocks double-submit.
29
+ */
30
+ import type { NexusContext } from './context.js';
31
+ import { type RateLimitConfig } from './rate-limit.js';
32
+ export type ActionFn<TInput = FormData, TOutput = void> = (input: TInput, ctx: NexusContext & {
33
+ signal: AbortSignal;
34
+ }) => Promise<TOutput>;
35
+ export type RaceStrategy = 'cancel' | 'queue' | 'reject' | 'ignore';
36
+ export interface ActionOptions {
37
+ /**
38
+ * How to handle concurrent calls to the same action from the same client.
39
+ * 'cancel' — abort the previous call, run the new one (default for mutations)
40
+ * 'queue' — run calls sequentially in order
41
+ * 'reject' — reject the new call if one is already in flight
42
+ * 'ignore' — let all calls run in parallel (default for idempotent reads)
43
+ */
44
+ race?: RaceStrategy;
45
+ /**
46
+ * Mark as idempotent — same idempotency key returns cached result.
47
+ * Set to true for safe retries (GET-like mutations).
48
+ */
49
+ idempotent?: boolean;
50
+ /**
51
+ * Timeout in ms. Aborts the action if it takes too long.
52
+ * Default: 30000 (30s)
53
+ */
54
+ timeout?: number;
55
+ /**
56
+ * Retry on network failure (not on logic errors).
57
+ * Default: 0
58
+ */
59
+ retries?: number;
60
+ /**
61
+ * Per-action rate limiting.
62
+ * Example: { window: '1m', max: 3 } → 3 calls per minute per IP.
63
+ * Override the key with keyFn: { window: '1m', max: 3, keyFn: r => userId }
64
+ */
65
+ rateLimit?: RateLimitConfig;
66
+ /**
67
+ * Require a valid CSRF action token in the x-nexus-action-token header.
68
+ * Default: true for all state-mutating actions.
69
+ * Set to false for read-only or public actions.
70
+ */
71
+ csrf?: boolean;
72
+ /**
73
+ * A Zod-compatible schema for input validation.
74
+ * If provided, the action will reject requests with invalid input before
75
+ * calling the handler. Prevents SQL injection and type coercion attacks.
76
+ */
77
+ schema?: {
78
+ parse: (data: unknown) => unknown;
79
+ };
80
+ }
81
+ export interface ActionResult<T = unknown> {
82
+ data?: T;
83
+ error?: string;
84
+ status: number;
85
+ /** Echoed back to client for deduplication */
86
+ idempotencyKey?: string;
87
+ /** Server-side execution time in ms */
88
+ duration?: number;
89
+ }
90
+ /**
91
+ * Defines a Server Action with integrated security, rate limiting, and
92
+ * race-condition management. The returned object is registered automatically
93
+ * and ready to be called by the client.
94
+ *
95
+ * Security layers applied (in order):
96
+ * 1. CSRF token validation (x-nexus-action-token header)
97
+ * 2. Rate limiting (sliding window, per-IP or per-user)
98
+ * 3. Input schema validation (Zod or any .parse() compatible schema)
99
+ * 4. AbortController (client disconnect + timeout)
100
+ * 5. Idempotency deduplication
101
+ * 6. Race condition strategy (cancel | queue | reject | ignore)
102
+ *
103
+ * @example
104
+ * export const capture = createAction({
105
+ * rateLimit: { window: '1m', max: 3, keyFn: (req) => req.headers.get('x-user-id') ?? extractIP(req) },
106
+ * schema: z.object({ pokemonId: z.number().int().min(1).max(1010) }),
107
+ * race: 'cancel',
108
+ * async handler(data, ctx) {
109
+ * const { pokemonId } = data;
110
+ * await db.captures.create({ userId: ctx.user.id, pokemonId });
111
+ * },
112
+ * });
113
+ */
114
+ export declare function createAction<TInput = FormData, TOutput = void>(optsOrFn: ActionFn<TInput, TOutput> | (ActionOptions & {
115
+ handler: ActionFn<TInput, TOutput>;
116
+ }), legacyOpts?: ActionOptions): ActionFn<TInput, TOutput>;
117
+ export declare function registerAction(name: string, fn: ActionFn<unknown, unknown>, opts?: ActionOptions): void;
118
+ export declare class ActionError extends Error {
119
+ readonly status: number;
120
+ readonly code?: string | undefined;
121
+ constructor(message: string, status?: number, code?: string | undefined);
122
+ }
123
+ export declare class ActionAbortedError extends ActionError {
124
+ constructor();
125
+ }
126
+ /**
127
+ * Main HTTP handler for /_nexus/action/:name
128
+ * This is where all the race-condition logic runs.
129
+ */
130
+ export declare function handleActionRequest(request: Request): Promise<Response>;
131
+ /**
132
+ * Validates that a request comes from a trusted Nexus client.
133
+ * Checks x-nexus-action header and CSRF token.
134
+ */
135
+ export declare function validateRequest(ctx: NexusContext): Promise<void>;
136
+ export { generateActionToken, validateActionToken, extractSessionId, generateSessionId } from './csrf.js';
137
+ export { createRateLimiter, RateLimitError, parseWindow } from './rate-limit.js';
138
+ export type { RateLimitConfig, RateLimitResult, RateLimiter } from './rate-limit.js';
139
+ /**
140
+ * Client-side AbortController factory.
141
+ * Use this in island code to cancel in-flight action fetches
142
+ * when the user triggers a new one.
143
+ *
144
+ * @example
145
+ * const guard = createActionGuard('save', 'cancel');
146
+ * async function save(formData) {
147
+ * const signal = guard.arm();
148
+ * const result = await callAction('savePost', formData, { signal });
149
+ * if (!guard.aborted) updateUI(result);
150
+ * }
151
+ */
152
+ export declare function createActionGuard(name: string, strategy?: RaceStrategy): {
153
+ arm: () => AbortSignal;
154
+ abort: () => void;
155
+ aborted: boolean;
156
+ pending: boolean;
157
+ };
158
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Nexus Server Actions — type-safe, race-condition-safe server mutations.
3
+ *
4
+ * Race Condition Problem:
5
+ * User clicks "Save" three times in rapid succession.
6
+ * Request 1 arrives, starts processing (200ms).
7
+ * Request 2 arrives, starts processing (180ms) — finishes FIRST.
8
+ * Request 3 arrives, starts processing (150ms) — finishes SECOND.
9
+ * Request 1 finishes LAST — overwrites the results of 2 and 3. 💥
10
+ *
11
+ * Solutions implemented:
12
+ *
13
+ * 1. Idempotency key deduplication:
14
+ * Client sends X-Nexus-Idempotency: <uuid> with each action call.
15
+ * If the same key arrives again while the first is in flight,
16
+ * the server returns the SAME response (cached for 30s).
17
+ *
18
+ * 2. Per-island action mutex:
19
+ * Each island tracks its in-flight actions per action name.
20
+ * Configurable behavior: 'cancel' | 'queue' | 'reject' | 'ignore'.
21
+ *
22
+ * 3. AbortController propagation:
23
+ * The signal is passed into the action context. Actions that
24
+ * call external APIs should check ctx.signal.aborted.
25
+ * If the client disconnects, the signal fires automatically.
26
+ *
27
+ * 4. Client-side: $optimistic with built-in race guard.
28
+ * The createOptimistic() pending flag blocks double-submit.
29
+ */
30
+ import { createContext } from './context.js';
31
+ import { serialize, deserialize } from '@nexus_js/serialize';
32
+ import { validateActionToken, extractSessionId, ACTION_TOKEN_HEADER, } from './csrf.js';
33
+ import { createRateLimiter, registerLimiter, RateLimitError, } from './rate-limit.js';
34
+ const ACTION_PREFIX = '/_nexus/action/';
35
+ const idempotencyCache = new Map();
36
+ const IDEMPOTENCY_TTL = 30_000; // 30 seconds
37
+ // ── In-flight action tracking ─────────────────────────────────────────────────
38
+ // Map: `${islandId}:${actionName}` → AbortController
39
+ const inFlightActions = new Map();
40
+ const actionQueues = new Map();
41
+ const actionRegistry = new Map();
42
+ // ── Public API ────────────────────────────────────────────────────────────────
43
+ /**
44
+ * Defines a Server Action with integrated security, rate limiting, and
45
+ * race-condition management. The returned object is registered automatically
46
+ * and ready to be called by the client.
47
+ *
48
+ * Security layers applied (in order):
49
+ * 1. CSRF token validation (x-nexus-action-token header)
50
+ * 2. Rate limiting (sliding window, per-IP or per-user)
51
+ * 3. Input schema validation (Zod or any .parse() compatible schema)
52
+ * 4. AbortController (client disconnect + timeout)
53
+ * 5. Idempotency deduplication
54
+ * 6. Race condition strategy (cancel | queue | reject | ignore)
55
+ *
56
+ * @example
57
+ * export const capture = createAction({
58
+ * rateLimit: { window: '1m', max: 3, keyFn: (req) => req.headers.get('x-user-id') ?? extractIP(req) },
59
+ * schema: z.object({ pokemonId: z.number().int().min(1).max(1010) }),
60
+ * race: 'cancel',
61
+ * async handler(data, ctx) {
62
+ * const { pokemonId } = data;
63
+ * await db.captures.create({ userId: ctx.user.id, pokemonId });
64
+ * },
65
+ * });
66
+ */
67
+ export function createAction(optsOrFn, legacyOpts = {}) {
68
+ // Support both createAction(fn, opts) and createAction({ handler, ...opts })
69
+ const fn = typeof optsOrFn === 'function' ? optsOrFn : optsOrFn.handler;
70
+ const opts = typeof optsOrFn === 'function' ? legacyOpts : optsOrFn;
71
+ // Wire up rate limiter at definition time (not per-request)
72
+ const limiter = opts.rateLimit ? createRateLimiter(opts.rateLimit) : null;
73
+ return async (input, ctx) => {
74
+ // 1. CSRF validation
75
+ if (opts.csrf !== false) {
76
+ await validateRequest(ctx);
77
+ }
78
+ // 2. Rate limiting
79
+ if (limiter) {
80
+ const result = limiter.check(ctx.request);
81
+ if (!result.allowed) {
82
+ throw new RateLimitError(result);
83
+ }
84
+ }
85
+ // 3. Schema validation
86
+ if (opts.schema) {
87
+ try {
88
+ input = opts.schema.parse(input);
89
+ }
90
+ catch (err) {
91
+ const msg = err instanceof Error ? err.message : 'Input validation failed';
92
+ throw new ActionError(`Invalid input: ${msg}`, 400, 'VALIDATION_ERROR');
93
+ }
94
+ }
95
+ return fn(input, ctx);
96
+ };
97
+ }
98
+ export function registerAction(name, fn, opts = {}) {
99
+ const limiter = opts.rateLimit ? createRateLimiter(opts.rateLimit) : null;
100
+ if (limiter)
101
+ registerLimiter(name, limiter);
102
+ actionRegistry.set(name, { fn, opts, name });
103
+ }
104
+ export class ActionError extends Error {
105
+ status;
106
+ code;
107
+ constructor(message, status = 400, code) {
108
+ super(message);
109
+ this.status = status;
110
+ this.code = code;
111
+ this.name = 'ActionError';
112
+ }
113
+ }
114
+ export class ActionAbortedError extends ActionError {
115
+ constructor() {
116
+ super('Action was superseded by a newer request', 409, 'ABORTED');
117
+ }
118
+ }
119
+ /**
120
+ * Main HTTP handler for /_nexus/action/:name
121
+ * This is where all the race-condition logic runs.
122
+ */
123
+ export async function handleActionRequest(request) {
124
+ const url = new URL(request.url);
125
+ if (!url.pathname.startsWith(ACTION_PREFIX)) {
126
+ return new Response('Not Found', { status: 404 });
127
+ }
128
+ if (request.method !== 'POST') {
129
+ return new Response('Method Not Allowed', {
130
+ status: 405,
131
+ headers: { allow: 'POST' },
132
+ });
133
+ }
134
+ const actionName = url.pathname.slice(ACTION_PREFIX.length);
135
+ const registered = actionRegistry.get(actionName);
136
+ if (!registered) {
137
+ return jsonResponse({ error: `Action "${actionName}" not found`, status: 404 }, 404);
138
+ }
139
+ const { fn, opts } = registered;
140
+ const race = opts.race ?? 'cancel';
141
+ const timeout = opts.timeout ?? 30_000;
142
+ // ── CSRF token validation ──────────────────────────────────────────────────
143
+ if (opts.csrf !== false) {
144
+ const token = request.headers.get(ACTION_TOKEN_HEADER);
145
+ const secret = process.env['NEXUS_SECRET'] ?? 'nexus-dev-secret-change-me';
146
+ const sessionId = extractSessionId(request);
147
+ if (!token) {
148
+ return jsonResponse({
149
+ error: 'Missing action token — possible CSRF attack',
150
+ status: 403,
151
+ code: 'MISSING_CSRF_TOKEN',
152
+ }, 403);
153
+ }
154
+ const validation = validateActionToken(token, sessionId, actionName, secret);
155
+ if (!validation.valid) {
156
+ return jsonResponse({
157
+ error: validation.reason ?? 'Invalid action token',
158
+ status: 403,
159
+ code: validation.replayed ? 'REPLAY_ATTACK' : 'INVALID_CSRF_TOKEN',
160
+ }, 403);
161
+ }
162
+ }
163
+ // ── Rate limiting ──────────────────────────────────────────────────────────
164
+ if (opts.rateLimit) {
165
+ const limiter = createRateLimiter(opts.rateLimit);
166
+ const result = limiter.check(request);
167
+ if (!result.allowed) {
168
+ return new Response(JSON.stringify({
169
+ error: `Rate limit exceeded. Retry in ${result.retryAfter}s.`,
170
+ status: 429,
171
+ code: 'RATE_LIMITED',
172
+ retryAfter: result.retryAfter,
173
+ resetAt: result.resetAt,
174
+ }), {
175
+ status: 429,
176
+ headers: {
177
+ 'content-type': 'application/json',
178
+ ...limiter.headers(result),
179
+ },
180
+ });
181
+ }
182
+ }
183
+ // ── Idempotency check ──────────────────────────────────────────────────────
184
+ const idempotencyKey = request.headers.get('x-nexus-idempotency');
185
+ if (idempotencyKey && opts.idempotent) {
186
+ const cached = idempotencyCache.get(idempotencyKey);
187
+ if (cached && Date.now() < cached.expiresAt) {
188
+ return jsonResponse({ data: cached.result, status: cached.status, idempotencyKey }, cached.status);
189
+ }
190
+ }
191
+ // ── Race condition management ──────────────────────────────────────────────
192
+ const islandId = request.headers.get('x-nexus-island') ?? 'global';
193
+ const raceKey = `${islandId}:${actionName}`;
194
+ if (race === 'reject') {
195
+ if (inFlightActions.has(raceKey)) {
196
+ return jsonResponse({
197
+ error: 'Action already in progress',
198
+ status: 409,
199
+ code: 'CONCURRENT_ACTION',
200
+ }, 409);
201
+ }
202
+ }
203
+ if (race === 'cancel') {
204
+ const existing = inFlightActions.get(raceKey);
205
+ if (existing) {
206
+ existing.abort(new ActionAbortedError());
207
+ }
208
+ }
209
+ if (race === 'queue') {
210
+ await waitInQueue(raceKey);
211
+ }
212
+ // ── AbortController setup ──────────────────────────────────────────────────
213
+ const controller = new AbortController();
214
+ // Chain with client disconnect signal
215
+ request.signal?.addEventListener('abort', () => {
216
+ controller.abort(new Error('Client disconnected'));
217
+ });
218
+ // Timeout
219
+ const timeoutId = setTimeout(() => {
220
+ controller.abort(new Error(`Action timeout after ${timeout}ms`));
221
+ }, timeout);
222
+ inFlightActions.set(raceKey, controller);
223
+ // ── Execute ───────────────────────────────────────────────────────────────
224
+ const ctx = createContext(request);
225
+ const ctxWithSignal = Object.assign(ctx, { signal: controller.signal });
226
+ const startTime = Date.now();
227
+ try {
228
+ // Deserialize input using Nexus transport (preserves Date, Map, Set, etc.)
229
+ const input = await deserializeInput(request);
230
+ let result;
231
+ let attempts = 0;
232
+ const maxAttempts = 1 + (opts.retries ?? 0);
233
+ while (attempts < maxAttempts) {
234
+ try {
235
+ result = await fn(input, ctxWithSignal);
236
+ break;
237
+ }
238
+ catch (err) {
239
+ attempts++;
240
+ if (err instanceof ActionError || err instanceof ActionAbortedError)
241
+ throw err;
242
+ if (attempts >= maxAttempts)
243
+ throw err;
244
+ await delay(100 * attempts); // exponential backoff
245
+ }
246
+ }
247
+ const duration = Date.now() - startTime;
248
+ // Cache idempotent results
249
+ if (idempotencyKey && opts.idempotent) {
250
+ idempotencyCache.set(idempotencyKey, {
251
+ result,
252
+ status: 200,
253
+ expiresAt: Date.now() + IDEMPOTENCY_TTL,
254
+ });
255
+ // Cleanup old entries periodically
256
+ cleanIdempotencyCache();
257
+ }
258
+ // Serialize response with Nexus transport
259
+ const serialized = serialize({ data: result, status: 200, duration, idempotencyKey });
260
+ return new Response(serialized, {
261
+ status: 200,
262
+ headers: {
263
+ 'content-type': 'application/json',
264
+ 'x-nexus-duration': String(duration),
265
+ ...(idempotencyKey ? { 'x-nexus-idempotency': idempotencyKey } : {}),
266
+ },
267
+ });
268
+ }
269
+ catch (err) {
270
+ clearTimeout(timeoutId);
271
+ inFlightActions.delete(raceKey);
272
+ releaseQueue(raceKey);
273
+ if (controller.signal.aborted) {
274
+ return jsonResponse({
275
+ error: 'Action was cancelled',
276
+ status: 409,
277
+ code: 'CANCELLED',
278
+ }, 409);
279
+ }
280
+ if (err instanceof ActionError) {
281
+ return jsonResponse({ error: err.message, status: err.status, code: err.code }, err.status);
282
+ }
283
+ console.error(`[Nexus Action] "${actionName}" failed:`, err);
284
+ return jsonResponse({ error: 'Internal Server Error', status: 500 }, 500);
285
+ }
286
+ finally {
287
+ clearTimeout(timeoutId);
288
+ inFlightActions.delete(raceKey);
289
+ releaseQueue(raceKey);
290
+ }
291
+ }
292
+ /**
293
+ * Validates that a request comes from a trusted Nexus client.
294
+ * Checks x-nexus-action header and CSRF token.
295
+ */
296
+ export async function validateRequest(ctx) {
297
+ const nexusHeader = ctx.request.headers.get('x-nexus-action');
298
+ if (!nexusHeader) {
299
+ throw new ActionError('Missing Nexus action header', 403, 'MISSING_HEADER');
300
+ }
301
+ }
302
+ // Re-export security primitives for use in app code
303
+ export { generateActionToken, validateActionToken, extractSessionId, generateSessionId } from './csrf.js';
304
+ export { createRateLimiter, RateLimitError, parseWindow } from './rate-limit.js';
305
+ // ── Client-side race guard ────────────────────────────────────────────────────
306
+ /**
307
+ * Client-side AbortController factory.
308
+ * Use this in island code to cancel in-flight action fetches
309
+ * when the user triggers a new one.
310
+ *
311
+ * @example
312
+ * const guard = createActionGuard('save', 'cancel');
313
+ * async function save(formData) {
314
+ * const signal = guard.arm();
315
+ * const result = await callAction('savePost', formData, { signal });
316
+ * if (!guard.aborted) updateUI(result);
317
+ * }
318
+ */
319
+ export function createActionGuard(name, strategy = 'cancel') {
320
+ let currentController = null;
321
+ let _pending = false;
322
+ return {
323
+ arm() {
324
+ if (strategy === 'cancel' && currentController) {
325
+ currentController.abort();
326
+ }
327
+ currentController = new AbortController();
328
+ _pending = true;
329
+ currentController.signal.addEventListener('abort', () => { _pending = false; });
330
+ return currentController.signal;
331
+ },
332
+ abort() {
333
+ currentController?.abort();
334
+ _pending = false;
335
+ },
336
+ get aborted() {
337
+ return currentController?.signal.aborted ?? false;
338
+ },
339
+ get pending() {
340
+ return _pending;
341
+ },
342
+ };
343
+ }
344
+ // ── Helpers ───────────────────────────────────────────────────────────────────
345
+ async function deserializeInput(request) {
346
+ const contentType = request.headers.get('content-type') ?? '';
347
+ if (contentType.includes('application/json')) {
348
+ const text = await request.text();
349
+ try {
350
+ return deserialize(text);
351
+ }
352
+ catch {
353
+ return JSON.parse(text);
354
+ }
355
+ }
356
+ if (contentType.includes('multipart/form-data') ||
357
+ contentType.includes('application/x-www-form-urlencoded')) {
358
+ return request.formData();
359
+ }
360
+ return request.text();
361
+ }
362
+ function jsonResponse(body, status) {
363
+ return new Response(JSON.stringify(body), {
364
+ status,
365
+ headers: { 'content-type': 'application/json' },
366
+ });
367
+ }
368
+ async function waitInQueue(key) {
369
+ return new Promise((resolve) => {
370
+ const queue = actionQueues.get(key) ?? [];
371
+ queue.push({ resolve });
372
+ actionQueues.set(key, queue);
373
+ if (queue.length === 1)
374
+ resolve(); // First in queue — go immediately
375
+ });
376
+ }
377
+ function releaseQueue(key) {
378
+ const queue = actionQueues.get(key);
379
+ if (!queue || queue.length === 0)
380
+ return;
381
+ queue.shift(); // Remove the completed action
382
+ const next = queue[0];
383
+ if (next)
384
+ next.resolve(); // Unblock the next queued action
385
+ }
386
+ function cleanIdempotencyCache() {
387
+ const now = Date.now();
388
+ for (const [key, entry] of idempotencyCache.entries()) {
389
+ if (now > entry.expiresAt)
390
+ idempotencyCache.delete(key);
391
+ }
392
+ }
393
+ function delay(ms) {
394
+ return new Promise((resolve) => setTimeout(resolve, ms));
395
+ }
396
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Nexus Request Context — passed to every page, layout and server action.
3
+ * Inspired by SvelteKit's RequestEvent and Next.js's Request/Response pattern.
4
+ */
5
+ export interface NexusContext {
6
+ request: Request;
7
+ params: Record<string, string>;
8
+ url: URL;
9
+ headers: Headers;
10
+ locals: Record<string, unknown>;
11
+ /** Set a response header */
12
+ setHeader: (key: string, value: string) => void;
13
+ /** Set a cookie */
14
+ setCookie: (name: string, value: string, opts?: CookieOptions) => void;
15
+ /** Get a cookie value */
16
+ getCookie: (name: string) => string | undefined;
17
+ /** Redirect — throws, so use `return redirect(...)` pattern */
18
+ redirect: (location: string, status?: 301 | 302 | 303 | 307 | 308) => never;
19
+ /** Return a not-found response */
20
+ notFound: () => never;
21
+ }
22
+ export interface CookieOptions {
23
+ path?: string;
24
+ domain?: string;
25
+ maxAge?: number;
26
+ expires?: Date;
27
+ httpOnly?: boolean;
28
+ secure?: boolean;
29
+ sameSite?: 'Strict' | 'Lax' | 'None';
30
+ }
31
+ /** Internal redirect signal */
32
+ export declare class RedirectSignal {
33
+ readonly location: string;
34
+ readonly status: number;
35
+ constructor(location: string, status: number);
36
+ }
37
+ /** Internal not-found signal */
38
+ export declare class NotFoundSignal {
39
+ }
40
+ export declare function createContext(request: Request, params?: Record<string, string>): NexusContext;
41
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,4BAA4B;IAC5B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,mBAAmB;IACnB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACvE,yBAAyB;IACzB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAK,KAAK,CAAC;IAC5E,kCAAkC;IAClC,QAAQ,EAAE,MAAM,KAAK,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;CACtC;AAED,+BAA+B;AAC/B,qBAAa,cAAc;aAEP,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAEjC;AAED,gCAAgC;AAChC,qBAAa,cAAc;CAAG;AAE9B,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,YAAY,CAwCd"}