thatcher 1.0.41 → 1.0.43

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 (122) hide show
  1. package/README.md +27 -0
  2. package/package.json +2 -5
  3. package/src/app/api/audit/dashboard/route.js +2 -2
  4. package/src/app/api/audit/logs/route.js +2 -2
  5. package/src/app/api/audit/permissions/[id]/route.js +2 -2
  6. package/src/app/api/audit/permissions/route.js +3 -3
  7. package/src/app/api/audit/permissions/stats/route.js +2 -2
  8. package/src/app/api/audit/route.js +2 -3
  9. package/src/app/api/audit/stats/route.js +2 -2
  10. package/src/app/api/auth/google/callback/route.js +1 -1
  11. package/src/app/api/auth/google/route.js +1 -1
  12. package/src/app/api/auth/login/route.js +1 -1
  13. package/src/app/api/auth/logout/route.js +1 -1
  14. package/src/app/api/auth/me/route.js +1 -1
  15. package/src/app/api/auth/mwr-bridge/route.js +1 -1
  16. package/src/app/api/auth/password-reset/route.js +1 -1
  17. package/src/app/api/csrf-token/route.js +1 -1
  18. package/src/app/api/debug/[[...path]]/route.js +2 -2
  19. package/src/app/api/debug/config/route.js +1 -1
  20. package/src/app/api/debug/hooks/route.js +1 -1
  21. package/src/app/api/debug/plugins/route.js +1 -1
  22. package/src/app/api/debug/sqlite/route.js +1 -1
  23. package/src/app/api/debug/sync/route.js +1 -1
  24. package/src/app/api/debug/workflow/route.js +1 -1
  25. package/src/app/api/domains/[domain]/route.js +1 -2
  26. package/src/app/api/domains/route.js +1 -1
  27. package/src/app/api/email/allocate/batch/route.js +1 -1
  28. package/src/app/api/email/allocate/route.js +1 -1
  29. package/src/app/api/email/receive/route.js +1 -1
  30. package/src/app/api/email/send/route.js +1 -1
  31. package/src/app/api/email/unallocated/route.js +1 -1
  32. package/src/app/api/files/[id]/route.js +1 -1
  33. package/src/app/api/health/route.js +1 -2
  34. package/src/app/api/metrics/route.js +1 -2
  35. package/src/engine.js +3 -3
  36. package/src/engine.server.js +3 -3
  37. package/src/index.js +35 -10
  38. package/src/lib/action-factory.js +1 -1
  39. package/src/lib/api-helpers.js +1 -1
  40. package/src/lib/api.js +3 -4
  41. package/src/lib/audit-logger-enhanced.js +1 -1
  42. package/src/lib/auth-middleware.js +1 -1
  43. package/src/lib/auth-route-helpers.js +1 -1
  44. package/src/lib/{busybase-adapter.js → busybase/adapter.js} +3 -3
  45. package/src/lib/{busybase-audit-reads.js → busybase/audit-reads.js} +1 -1
  46. package/src/lib/{busybase-audit.js → busybase/audit.js} +2 -2
  47. package/src/lib/{busybase-lucia-adapter.js → busybase/lucia-adapter.js} +1 -1
  48. package/src/lib/{busybase-store.js → busybase/store.js} +3 -3
  49. package/src/lib/config-generator-engine.js +13 -1
  50. package/src/lib/crud-action-helpers.js +1 -1
  51. package/src/lib/crud-handlers.js +4 -4
  52. package/src/lib/email-sender.js +1 -1
  53. package/src/lib/errors/index.js +3 -0
  54. package/src/lib/{error-recovery.js → errors/recovery.js} +120 -4
  55. package/src/lib/{error-handler.js → errors/types.js} +1 -1
  56. package/src/lib/errors/wrap.js +225 -0
  57. package/src/lib/events-engine.js +1 -1
  58. package/src/lib/hot-reload/index.js +34 -0
  59. package/src/lib/index.js +5 -5
  60. package/src/lib/keyed-cache.js +112 -0
  61. package/src/lib/monitor.js +285 -0
  62. package/src/lib/next-shim.js +294 -0
  63. package/src/lib/observability-init.js +213 -0
  64. package/src/lib/perf.js +468 -0
  65. package/src/lib/query-cache.js +40 -46
  66. package/src/lib/realtime-server.js +18 -0
  67. package/src/lib/render-cache.js +14 -28
  68. package/src/lib/{request-tracing.js → request-trace.js} +57 -2
  69. package/src/lib/response-formatter.js +18 -0
  70. package/src/lib/route-helpers.js +1 -1
  71. package/src/lib/state-protocol.js +13 -0
  72. package/src/lib/state-transport-client.js +6 -0
  73. package/src/lib/state-transport-reconnect.js +5 -0
  74. package/src/lib/state-transport-server.js +8 -0
  75. package/src/lib/utils.js +1 -1
  76. package/src/lib/{validate.js → validation/entity-validators.js} +9 -8
  77. package/src/lib/validation/index.js +1 -1
  78. package/src/lib/validation/security-validators.js +1 -1
  79. package/src/lib/validation-middleware.js +2 -2
  80. package/src/lib/workflow-engine.js +23 -3
  81. package/src/lib/xstate-workflow-engine.js +18 -2
  82. package/src/services/collaborator-role.service.js +2 -2
  83. package/src/services/notification-engine.js +5 -5
  84. package/src/services/permission.service.js +1 -1
  85. package/src/ui/format-helpers.js +1 -4
  86. package/src/ui/highlight-threading-renderer.js +1 -1
  87. package/src/ui/page-handler-admin.js +6 -5
  88. package/src/ui/page-handler-helpers.js +2 -2
  89. package/src/ui/page-handler-reviews.js +4 -4
  90. package/src/ui/page-handler-rfi.js +1 -1
  91. package/src/ui/page-handler.js +2 -2
  92. package/src/ui/render-helpers.js +1 -1
  93. package/src/ui/renderer.js +1 -1
  94. package/src/ui/{review-comparison-renderer.js → review/comparison.js} +1 -1
  95. package/src/ui/{review-detail-renderer.js → review/detail-renderer.js} +3 -3
  96. package/src/ui/review/index.js +32 -0
  97. package/src/ui/{review-mwr-renderer.js → review/mwr.js} +1 -1
  98. package/src/ui/{review-renderer.js → review/renderer.js} +1 -1
  99. package/src/ui/{settings-renderer.js → settings/home.js} +7 -34
  100. package/src/ui/{settings-renderer-advanced.js → settings/review.js} +117 -56
  101. package/src/ui/settings/shared.js +45 -0
  102. package/src/ui/{settings-renderer-teams.js → settings/teams.js} +1 -2
  103. package/src/ui/settings/templates.js +116 -0
  104. package/src/lib/api-error-wrapper.js +0 -125
  105. package/src/lib/db-monitor.js +0 -127
  106. package/src/lib/error-resilience.js +0 -132
  107. package/src/lib/monitoring-init.js +0 -67
  108. package/src/lib/next-compat.js +0 -94
  109. package/src/lib/next-polyfills.js +0 -135
  110. package/src/lib/observability-bootstrap.js +0 -116
  111. package/src/lib/perf-monitor.js +0 -94
  112. package/src/lib/perf-profiler.js +0 -233
  113. package/src/lib/query-perf.js +0 -117
  114. package/src/lib/request-tracker.js +0 -43
  115. package/src/lib/resource-monitor.js +0 -120
  116. package/src/lib/validators.js +0 -67
  117. package/src/lib/with-error-handler.js +0 -31
  118. package/src/ui/settings-renderer-advanced2.js +0 -159
  119. /package/src/ui/{review-detail-panels.js → review/detail-panels.js} +0 -0
  120. /package/src/ui/{review-detail-script.js → review/detail-script.js} +0 -0
  121. /package/src/ui/{review-widgets.js → review/widgets.js} +0 -0
  122. /package/src/ui/{review-zone-nav.js → review/zone-nav.js} +0 -0
@@ -0,0 +1,285 @@
1
+ // Merged from db-monitor.js + resource-monitor.js into one pluggable-probe
2
+ // monitor rather than two hardcoded monitor types. A "probe" is anything
3
+ // shaped like { name, start(), stop(), collect() } registered into the
4
+ // shared registry below; the db and resource probes that used to be two
5
+ // separate files are now just the two probes registered by default. Every
6
+ // original exported function name is preserved unchanged for call-site
7
+ // compatibility (getDatabaseStats, clearDatabaseStats, wrapDatabase,
8
+ // getCPUUsage, getMemoryUsage, getDiskUsage, collectResources,
9
+ // startMonitoring, stopMonitoring, getCurrentResources).
10
+
11
+ import os from 'os';
12
+ import fs from 'fs';
13
+ import { recordDatabase, recordResource } from './metrics-collector.js';
14
+ import { createLogger } from './logger.js';
15
+
16
+ const log = createLogger('[Monitor]');
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Pluggable probe registry
20
+ // ---------------------------------------------------------------------------
21
+ const probes = new Map();
22
+
23
+ function registerProbe(name, probe) {
24
+ probes.set(name, probe);
25
+ return probe;
26
+ }
27
+
28
+ function getProbe(name) {
29
+ return probes.get(name);
30
+ }
31
+
32
+ function listProbes() {
33
+ return Array.from(probes.keys());
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // db probe — from db-monitor.js
38
+ // ---------------------------------------------------------------------------
39
+ const dbStats = {
40
+ connections: 0,
41
+ activeQueries: 0,
42
+ locks: 0,
43
+ slowQueries: []
44
+ };
45
+
46
+ function recordSlowQuery(sql, duration) {
47
+ dbStats.slowQueries.push({
48
+ sql: sql.substring(0, 200),
49
+ duration,
50
+ timestamp: Date.now()
51
+ });
52
+
53
+ if (dbStats.slowQueries.length > 100) {
54
+ dbStats.slowQueries.shift();
55
+ }
56
+ }
57
+
58
+ export function wrapDatabase(db) {
59
+ const originalPrepare = db.prepare.bind(db);
60
+
61
+ db.prepare = function(sql) {
62
+ const stmt = originalPrepare(sql);
63
+ const originalRun = stmt.run.bind(stmt);
64
+ const originalGet = stmt.get.bind(stmt);
65
+ const originalAll = stmt.all.bind(stmt);
66
+
67
+ stmt.run = function(...args) {
68
+ const start = process.hrtime.bigint();
69
+ dbStats.activeQueries++;
70
+
71
+ try {
72
+ const result = originalRun(...args);
73
+ const duration = Number(process.hrtime.bigint() - start) / 1000000;
74
+
75
+ recordDatabase('run', duration, sql);
76
+ if (duration > 100) {
77
+ recordSlowQuery(sql, duration);
78
+ }
79
+
80
+ return result;
81
+ } catch (err) {
82
+ recordDatabase('error', 0, sql);
83
+ throw err;
84
+ } finally {
85
+ dbStats.activeQueries--;
86
+ }
87
+ };
88
+
89
+ stmt.get = function(...args) {
90
+ const start = process.hrtime.bigint();
91
+ dbStats.activeQueries++;
92
+
93
+ try {
94
+ const result = originalGet(...args);
95
+ const duration = Number(process.hrtime.bigint() - start) / 1000000;
96
+
97
+ recordDatabase('get', duration, sql);
98
+ if (duration > 100) {
99
+ recordSlowQuery(sql, duration);
100
+ }
101
+
102
+ return result;
103
+ } catch (err) {
104
+ recordDatabase('error', 0, sql);
105
+ throw err;
106
+ } finally {
107
+ dbStats.activeQueries--;
108
+ }
109
+ };
110
+
111
+ stmt.all = function(...args) {
112
+ const start = process.hrtime.bigint();
113
+ dbStats.activeQueries++;
114
+
115
+ try {
116
+ const result = originalAll(...args);
117
+ const duration = Number(process.hrtime.bigint() - start) / 1000000;
118
+
119
+ recordDatabase('all', duration, sql);
120
+ if (duration > 100) {
121
+ recordSlowQuery(sql, duration);
122
+ }
123
+
124
+ return result;
125
+ } catch (err) {
126
+ recordDatabase('error', 0, sql);
127
+ throw err;
128
+ } finally {
129
+ dbStats.activeQueries--;
130
+ }
131
+ };
132
+
133
+ return stmt;
134
+ };
135
+
136
+ return db;
137
+ }
138
+
139
+ export function getDatabaseStats() {
140
+ return {
141
+ connections: dbStats.connections,
142
+ activeQueries: dbStats.activeQueries,
143
+ locks: dbStats.locks,
144
+ slowQueries: dbStats.slowQueries.slice(-10)
145
+ };
146
+ }
147
+
148
+ export function clearDatabaseStats() {
149
+ dbStats.slowQueries.length = 0;
150
+ }
151
+
152
+ registerProbe('db', {
153
+ name: 'db',
154
+ start: () => {},
155
+ stop: () => {},
156
+ collect: getDatabaseStats,
157
+ clear: clearDatabaseStats,
158
+ });
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // resource probe — from resource-monitor.js
162
+ // ---------------------------------------------------------------------------
163
+ let monitoringInterval = null;
164
+ let dbPath = null;
165
+
166
+ export function getCPUUsage() {
167
+ const cpus = os.cpus();
168
+ let totalIdle = 0;
169
+ let totalTick = 0;
170
+
171
+ cpus.forEach(cpu => {
172
+ for (const type in cpu.times) {
173
+ totalTick += cpu.times[type];
174
+ }
175
+ totalIdle += cpu.times.idle;
176
+ });
177
+
178
+ const idle = totalIdle / cpus.length;
179
+ const total = totalTick / cpus.length;
180
+ const usage = 1 - (idle / total);
181
+
182
+ return Math.max(0, Math.min(1, usage));
183
+ }
184
+
185
+ export function getMemoryUsage() {
186
+ const totalMem = os.totalmem();
187
+ const freeMem = os.freemem();
188
+ const usedMem = totalMem - freeMem;
189
+
190
+ return {
191
+ total: totalMem,
192
+ used: usedMem,
193
+ free: freeMem,
194
+ percentage: usedMem / totalMem
195
+ };
196
+ }
197
+
198
+ export function getDiskUsage() {
199
+ if (!dbPath) return null;
200
+
201
+ try {
202
+ const stats = fs.statSync(dbPath);
203
+ const totalSpace = 1024 * 1024 * 1024 * 100;
204
+ const usedSpace = stats.size;
205
+
206
+ return {
207
+ total: totalSpace,
208
+ used: usedSpace,
209
+ percentage: usedSpace / totalSpace
210
+ };
211
+ } catch (err) {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ export function collectResources() {
217
+ try {
218
+ const cpu = getCPUUsage();
219
+ const memory = getMemoryUsage();
220
+ const disk = getDiskUsage();
221
+
222
+ recordResource(
223
+ cpu,
224
+ memory.percentage,
225
+ disk?.percentage
226
+ );
227
+
228
+ return { cpu, memory, disk };
229
+ } catch (err) {
230
+ log.error('collection error:', { message: err?.message || String(err) });
231
+ return null;
232
+ }
233
+ }
234
+
235
+ export function startMonitoring(interval = 5000, databasePath = null) {
236
+ if (monitoringInterval) {
237
+ stopMonitoring();
238
+ }
239
+
240
+ dbPath = databasePath;
241
+
242
+ monitoringInterval = setInterval(() => {
243
+ collectResources();
244
+ }, interval);
245
+ }
246
+
247
+ export function stopMonitoring() {
248
+ if (monitoringInterval) {
249
+ clearInterval(monitoringInterval);
250
+ monitoringInterval = null;
251
+ }
252
+ }
253
+
254
+ export function getCurrentResources() {
255
+ return collectResources();
256
+ }
257
+
258
+ registerProbe('resource', {
259
+ name: 'resource',
260
+ start: startMonitoring,
261
+ stop: stopMonitoring,
262
+ collect: getCurrentResources,
263
+ });
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // Probe registry surface (new — the pluggable-probe pattern the merge adds)
267
+ // ---------------------------------------------------------------------------
268
+ export { registerProbe, getProbe, listProbes };
269
+
270
+ if (typeof globalThis !== 'undefined') {
271
+ globalThis.__dbMonitor = {
272
+ getDatabaseStats,
273
+ clearDatabaseStats
274
+ };
275
+ globalThis.__resources = {
276
+ getCurrentResources,
277
+ startMonitoring,
278
+ stopMonitoring
279
+ };
280
+ globalThis.__monitor = {
281
+ registerProbe,
282
+ getProbe,
283
+ listProbes,
284
+ };
285
+ }
@@ -0,0 +1,294 @@
1
+ // next-shim.js
2
+ //
3
+ // Merged compatibility layer standing in for parts of the real Next.js API
4
+ // surface, so thatcher's route handlers (src/app/api/**/route.js) and
5
+ // server.js can run outside actual Next.js (e.g. under plain Node/Express).
6
+ //
7
+ // This is the union of the former src/lib/next-compat.js and
8
+ // src/lib/next-polyfills.js, combined verbatim (no behavior changes).
9
+ //
10
+ // Supports:
11
+ // - NextRequest: constructed from a raw Node IncomingMessage + parsed body
12
+ // + url. `.method`, `.headers` (Proxy giving both `.get(name)` /
13
+ // `.has(name)` fetch-style access AND raw `headers['x']` index access,
14
+ // case-insensitive via lowercasing), `.url`, `.body`. `.json()` returns
15
+ // the already-parsed body; `.text()` returns it as a string (or
16
+ // JSON.stringify's it if not already a string).
17
+ // - NextResponse: `.body`, `.status` (default 200), `.headers` (a Map).
18
+ // `.json()` returns `.body`. Static `NextResponse.json(body, init)`
19
+ // constructs one. Static `NextResponse.redirect(url, status = 307)`
20
+ // constructs a redirect response with a `Location` header.
21
+ // - readBody(req): buffers a raw Node request stream into a JSON-parsed
22
+ // object (falls back to the raw string on parse failure), with a
23
+ // 10 MB body-size cap that destroys the request and rejects if
24
+ // exceeded.
25
+ // - normalizeHeaderName(key): maps a lowercased header name to its
26
+ // canonical mixed-case form for a small fixed set of common headers
27
+ // (content-type, content-length, set-cookie, cache-control, expires,
28
+ // etag, last-modified, location, date, connection); passes through
29
+ // unrecognized keys unchanged.
30
+ // - registerGlobals(): assigns NextRequest/NextResponse onto globalThis.
31
+ // - cookies() (async, server-side only -- throws if `window` is defined):
32
+ // reads the request's `Cookie` header (via AsyncLocalStorage-scoped
33
+ // request, falling back to the last value passed to
34
+ // setCurrentRequest()) into a name->value map, and returns an object
35
+ // with `.get(name)` (returns `{name, value}` or undefined), `.set(name,
36
+ // value, options)` / `.delete(name)` (append a Set-Cookie header onto
37
+ // the AsyncLocalStorage-scoped response via `res.setHeader`, supporting
38
+ // `path`/`maxAge`/`expires`/`secure`/`httpOnly`/`sameSite` options), and
39
+ // `.getAll()` / `.has(name)`.
40
+ // - headers(): a STUB read-only headers accessor -- `.get()` always
41
+ // returns null, `.has()` always returns false, `.getSetCookie()` /
42
+ // `.entries()` always return an empty array. It does NOT read the
43
+ // actual request headers (unlike `cookies()`, which does).
44
+ // - setCurrentRequest(req) / setCurrentResponse(res): set a module-level
45
+ // fallback request/response used by cookies() when no
46
+ // AsyncLocalStorage context is active.
47
+ // - runWithContext(req, res, fn): runs `fn` inside an AsyncLocalStorage
48
+ // context carrying `{req, res}`, so cookies() can resolve the current
49
+ // request/response without them being threaded through every call.
50
+ // - revalidatePath() / revalidateTag(): both no-op stubs.
51
+ // - redirect(path, status = 302): server-side throws an Error tagged
52
+ // `error.type = 'redirect'` with `.location`/`.status` (for a caller to
53
+ // catch and translate into an actual HTTP redirect); client-side
54
+ // (`typeof window !== 'undefined'`) sets `window.location.href`
55
+ // directly instead of throwing.
56
+ // - notFound(): server-side throws a plain `Error('notFound()')`;
57
+ // client-side sets `window.location.href = '/404'`.
58
+ //
59
+ // Does NOT support:
60
+ // - Streaming request/response bodies (readBody fully buffers before
61
+ // resolving; NextResponse.body is a plain in-memory value, not a
62
+ // ReadableStream).
63
+ // - `NextRequest.nextUrl` (parsed URL object with `.searchParams` etc.) --
64
+ // only a raw `.url` string is exposed.
65
+ // - `NextRequest.cookies` as a request-bound property -- cookie access is
66
+ // only available via the separate top-level `cookies()` function.
67
+ // - Any real Web Headers object -- `NextRequest.headers` is a Proxy over
68
+ // a plain object, and `NextResponse.headers` is a bare Map, neither
69
+ // implements the full Headers interface (no `.append()`, `.forEach()`,
70
+ // `.delete()`, iteration protocol, etc).
71
+ // - Edge runtime APIs (no `NextFetchEvent`, no `waitUntil`, no Edge
72
+ // `fetch` overrides).
73
+ // - Middleware matchers / `middleware.js` config conventions.
74
+ // - `headers()` returning real request headers -- it is a hardcoded stub
75
+ // that never reflects the actual incoming request.
76
+ // - Response streaming helpers (`NextResponse.next()`, rewriting, etc.)
77
+ // -- only `.json()` and `.redirect()` static constructors exist.
78
+ // - Any multipart/form-data or non-JSON body parsing in readBody --
79
+ // non-JSON bodies resolve as a raw string.
80
+
81
+ import { AsyncLocalStorage } from 'async_hooks';
82
+
83
+ const MAX_BODY_SIZE = 10 * 1024 * 1024;
84
+
85
+ // Wrap node's raw (plain-object, lowercased) headers so both the Fetch-style
86
+ // `headers.get('x')` API and direct `headers['x']` index access work. Node
87
+ // already lowercases header names; `.get()` lowercases its argument to match.
88
+ function wrapHeaders(raw) {
89
+ const h = raw || {};
90
+ return new Proxy(h, {
91
+ get(target, prop) {
92
+ if (prop === 'get') return (name) => target[String(name).toLowerCase()] ?? null;
93
+ if (prop === 'has') return (name) => String(name).toLowerCase() in target;
94
+ return target[prop];
95
+ },
96
+ });
97
+ }
98
+
99
+ export class NextRequest {
100
+ constructor(req, body, url) {
101
+ this.method = req.method;
102
+ this.headers = wrapHeaders(req.headers);
103
+ this.url = url;
104
+ this.body = body;
105
+ }
106
+
107
+ async json() {
108
+ return this.body;
109
+ }
110
+
111
+ async text() {
112
+ return typeof this.body === 'string' ? this.body : JSON.stringify(this.body);
113
+ }
114
+ }
115
+
116
+ export class NextResponse {
117
+ constructor(body, init = {}) {
118
+ this.body = body;
119
+ this.status = init.status || 200;
120
+ this.headers = new Map(Object.entries(init.headers || {}));
121
+ }
122
+
123
+ async json() {
124
+ return this.body;
125
+ }
126
+
127
+ static json(body, init = {}) {
128
+ return new NextResponse(body, init);
129
+ }
130
+
131
+ static redirect(url, status = 307) {
132
+ return new NextResponse(null, {
133
+ status,
134
+ headers: { Location: url }
135
+ });
136
+ }
137
+ }
138
+
139
+ export async function readBody(req) {
140
+ return new Promise((resolve, reject) => {
141
+ let data = '';
142
+ let size = 0;
143
+ req.on('data', (chunk) => {
144
+ size += chunk.length;
145
+ if (size > MAX_BODY_SIZE) {
146
+ req.destroy();
147
+ reject(new Error('Request body too large'));
148
+ return;
149
+ }
150
+ data += chunk;
151
+ });
152
+ req.on('end', () => {
153
+ try {
154
+ resolve(data ? JSON.parse(data) : {});
155
+ } catch {
156
+ resolve(data);
157
+ }
158
+ });
159
+ req.on('error', (err) => reject(err));
160
+ });
161
+ }
162
+
163
+ const HEADER_MAP = {
164
+ 'content-type': 'Content-Type',
165
+ 'content-length': 'Content-Length',
166
+ 'set-cookie': 'Set-Cookie',
167
+ 'cache-control': 'Cache-Control',
168
+ 'expires': 'Expires',
169
+ 'etag': 'ETag',
170
+ 'last-modified': 'Last-Modified',
171
+ 'location': 'Location',
172
+ 'date': 'Date',
173
+ 'connection': 'Connection',
174
+ };
175
+
176
+ export function normalizeHeaderName(key) {
177
+ return HEADER_MAP[key.toLowerCase()] || key;
178
+ }
179
+
180
+ export function registerGlobals() {
181
+ globalThis.NextRequest = NextRequest;
182
+ globalThis.NextResponse = NextResponse;
183
+ }
184
+
185
+ const requestContext = new AsyncLocalStorage();
186
+
187
+ let fallbackRequest = null;
188
+ let fallbackResponse = null;
189
+
190
+ export function setCurrentRequest(req) {
191
+ fallbackRequest = req;
192
+ }
193
+
194
+ export function setCurrentResponse(res) {
195
+ fallbackResponse = res;
196
+ }
197
+
198
+ export function runWithContext(req, res, fn) {
199
+ return requestContext.run({ req, res }, fn);
200
+ }
201
+
202
+ function getRequest() {
203
+ const store = requestContext.getStore();
204
+ return store?.req || fallbackRequest;
205
+ }
206
+
207
+ function getResponse() {
208
+ const store = requestContext.getStore();
209
+ return store?.res || fallbackResponse;
210
+ }
211
+
212
+ export async function cookies() {
213
+ if (typeof window !== 'undefined') {
214
+ throw new Error('cookies() should only be called on the server side');
215
+ }
216
+
217
+ const req = getRequest();
218
+ const res = getResponse();
219
+ const cookieHeader = req?.headers?.cookie || '';
220
+ const cookieMap = {};
221
+
222
+ if (cookieHeader) {
223
+ cookieHeader.split(';').forEach(cookie => {
224
+ const [name, value] = cookie.split('=').map(s => s.trim());
225
+ if (name) cookieMap[name] = decodeURIComponent(value || '');
226
+ });
227
+ }
228
+
229
+ return {
230
+ get: (name) => {
231
+ const value = cookieMap[name];
232
+ return value ? { name, value } : undefined;
233
+ },
234
+ set: (name, value, options = {}) => {
235
+ if (!res) return;
236
+ let setCookieValue = `${name}=${encodeURIComponent(value)}`;
237
+ if (options.path) setCookieValue += `; Path=${options.path}`;
238
+ if (options.maxAge) setCookieValue += `; Max-Age=${options.maxAge}`;
239
+ if (options.expires) setCookieValue += `; Expires=${options.expires}`;
240
+ if (options.secure) setCookieValue += '; Secure';
241
+ if (options.httpOnly) setCookieValue += '; HttpOnly';
242
+ if (options.sameSite) setCookieValue += `; SameSite=${options.sameSite}`;
243
+ const existing = res.getHeader('Set-Cookie') || [];
244
+ const setCookies = Array.isArray(existing) ? existing : [existing];
245
+ res.setHeader('Set-Cookie', [...setCookies, setCookieValue]);
246
+ },
247
+ delete: (name) => {
248
+ if (!res) return;
249
+ const setCookieValue = `${name}=; Path=/; Max-Age=0`;
250
+ const existing = res.getHeader('Set-Cookie') || [];
251
+ const setCookies = Array.isArray(existing) ? existing : [existing];
252
+ res.setHeader('Set-Cookie', [...setCookies, setCookieValue]);
253
+ },
254
+ getAll: () => {
255
+ return Object.entries(cookieMap).map(([name, value]) => ({ name, value }));
256
+ },
257
+ has: (name) => {
258
+ return name in cookieMap;
259
+ },
260
+ };
261
+ }
262
+
263
+ export function headers() {
264
+ return {
265
+ get: (name) => null,
266
+ getSetCookie: () => [],
267
+ has: (name) => false,
268
+ entries: () => [],
269
+ };
270
+ }
271
+
272
+ export function revalidatePath() {}
273
+
274
+ export function revalidateTag() {}
275
+
276
+ export function redirect(path, status = 302) {
277
+ if (typeof window !== 'undefined') {
278
+ window.location.href = path;
279
+ } else {
280
+ const error = new Error(`Redirect to ${path}`);
281
+ error.type = 'redirect';
282
+ error.location = path;
283
+ error.status = status;
284
+ throw error;
285
+ }
286
+ }
287
+
288
+ export function notFound() {
289
+ if (typeof window !== 'undefined') {
290
+ window.location.href = '/404';
291
+ } else {
292
+ throw new Error('notFound()');
293
+ }
294
+ }