@orkify/cli 1.0.0-beta.5 → 1.0.0-beta.6

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 (90) hide show
  1. package/README.md +10 -5
  2. package/package.json +8 -31
  3. package/packages/cache/README.md +0 -114
  4. package/packages/cache/dist/CacheClient.d.ts +0 -26
  5. package/packages/cache/dist/CacheClient.d.ts.map +0 -1
  6. package/packages/cache/dist/CacheClient.js +0 -174
  7. package/packages/cache/dist/CacheClient.js.map +0 -1
  8. package/packages/cache/dist/CacheFileStore.d.ts +0 -45
  9. package/packages/cache/dist/CacheFileStore.d.ts.map +0 -1
  10. package/packages/cache/dist/CacheFileStore.js +0 -446
  11. package/packages/cache/dist/CacheFileStore.js.map +0 -1
  12. package/packages/cache/dist/CachePersistence.d.ts +0 -9
  13. package/packages/cache/dist/CachePersistence.d.ts.map +0 -1
  14. package/packages/cache/dist/CachePersistence.js +0 -67
  15. package/packages/cache/dist/CachePersistence.js.map +0 -1
  16. package/packages/cache/dist/CachePrimary.d.ts +0 -25
  17. package/packages/cache/dist/CachePrimary.d.ts.map +0 -1
  18. package/packages/cache/dist/CachePrimary.js +0 -155
  19. package/packages/cache/dist/CachePrimary.js.map +0 -1
  20. package/packages/cache/dist/CacheStore.d.ts +0 -50
  21. package/packages/cache/dist/CacheStore.d.ts.map +0 -1
  22. package/packages/cache/dist/CacheStore.js +0 -271
  23. package/packages/cache/dist/CacheStore.js.map +0 -1
  24. package/packages/cache/dist/constants.d.ts +0 -6
  25. package/packages/cache/dist/constants.d.ts.map +0 -1
  26. package/packages/cache/dist/constants.js +0 -9
  27. package/packages/cache/dist/constants.js.map +0 -1
  28. package/packages/cache/dist/index.d.ts +0 -16
  29. package/packages/cache/dist/index.d.ts.map +0 -1
  30. package/packages/cache/dist/index.js +0 -86
  31. package/packages/cache/dist/index.js.map +0 -1
  32. package/packages/cache/dist/serialize.d.ts +0 -9
  33. package/packages/cache/dist/serialize.d.ts.map +0 -1
  34. package/packages/cache/dist/serialize.js +0 -40
  35. package/packages/cache/dist/serialize.js.map +0 -1
  36. package/packages/cache/dist/types.d.ts +0 -123
  37. package/packages/cache/dist/types.d.ts.map +0 -1
  38. package/packages/cache/dist/types.js +0 -2
  39. package/packages/cache/dist/types.js.map +0 -1
  40. package/packages/cache/package.json +0 -27
  41. package/packages/cache/src/CacheClient.ts +0 -227
  42. package/packages/cache/src/CacheFileStore.ts +0 -528
  43. package/packages/cache/src/CachePersistence.ts +0 -89
  44. package/packages/cache/src/CachePrimary.ts +0 -172
  45. package/packages/cache/src/CacheStore.ts +0 -308
  46. package/packages/cache/src/constants.ts +0 -10
  47. package/packages/cache/src/index.ts +0 -100
  48. package/packages/cache/src/serialize.ts +0 -49
  49. package/packages/cache/src/types.ts +0 -156
  50. package/packages/cache/tsconfig.json +0 -18
  51. package/packages/cache/tsconfig.tsbuildinfo +0 -1
  52. package/packages/next/README.md +0 -166
  53. package/packages/next/dist/error-capture.d.ts +0 -34
  54. package/packages/next/dist/error-capture.d.ts.map +0 -1
  55. package/packages/next/dist/error-capture.js +0 -130
  56. package/packages/next/dist/error-capture.js.map +0 -1
  57. package/packages/next/dist/error-handler.d.ts +0 -10
  58. package/packages/next/dist/error-handler.d.ts.map +0 -1
  59. package/packages/next/dist/error-handler.js +0 -186
  60. package/packages/next/dist/error-handler.js.map +0 -1
  61. package/packages/next/dist/isr-cache.d.ts +0 -9
  62. package/packages/next/dist/isr-cache.d.ts.map +0 -1
  63. package/packages/next/dist/isr-cache.js +0 -86
  64. package/packages/next/dist/isr-cache.js.map +0 -1
  65. package/packages/next/dist/stream.d.ts +0 -5
  66. package/packages/next/dist/stream.d.ts.map +0 -1
  67. package/packages/next/dist/stream.js +0 -22
  68. package/packages/next/dist/stream.js.map +0 -1
  69. package/packages/next/dist/types.d.ts +0 -33
  70. package/packages/next/dist/types.d.ts.map +0 -1
  71. package/packages/next/dist/types.js +0 -6
  72. package/packages/next/dist/types.js.map +0 -1
  73. package/packages/next/dist/use-cache.d.ts +0 -4
  74. package/packages/next/dist/use-cache.d.ts.map +0 -1
  75. package/packages/next/dist/use-cache.js +0 -86
  76. package/packages/next/dist/use-cache.js.map +0 -1
  77. package/packages/next/dist/utils.d.ts +0 -32
  78. package/packages/next/dist/utils.d.ts.map +0 -1
  79. package/packages/next/dist/utils.js +0 -88
  80. package/packages/next/dist/utils.js.map +0 -1
  81. package/packages/next/package.json +0 -52
  82. package/packages/next/src/error-capture.ts +0 -177
  83. package/packages/next/src/error-handler.ts +0 -221
  84. package/packages/next/src/isr-cache.ts +0 -100
  85. package/packages/next/src/stream.ts +0 -23
  86. package/packages/next/src/types.ts +0 -33
  87. package/packages/next/src/use-cache.ts +0 -99
  88. package/packages/next/src/utils.ts +0 -102
  89. package/packages/next/tsconfig.json +0 -19
  90. package/packages/next/tsconfig.tsbuildinfo +0 -1
@@ -1,52 +0,0 @@
1
- {
2
- "name": "@orkify/next",
3
- "version": "1.0.0-beta.1",
4
- "private": true,
5
- "description": "Next.js integration for orkify — cache handlers and browser error tracking",
6
- "type": "module",
7
- "exports": {
8
- "./use-cache": {
9
- "types": "./src/use-cache.ts",
10
- "import": "./dist/use-cache.js",
11
- "default": "./dist/use-cache.js"
12
- },
13
- "./isr-cache": {
14
- "types": "./src/isr-cache.ts",
15
- "import": "./dist/isr-cache.js",
16
- "default": "./dist/isr-cache.js"
17
- },
18
- "./error-capture": {
19
- "types": "./src/error-capture.ts",
20
- "import": "./dist/error-capture.js",
21
- "default": "./dist/error-capture.js"
22
- },
23
- "./error-handler": {
24
- "types": "./src/error-handler.ts",
25
- "import": "./dist/error-handler.js",
26
- "default": "./dist/error-handler.js"
27
- },
28
- "./utils": {
29
- "types": "./src/utils.ts",
30
- "import": "./dist/utils.js",
31
- "default": "./dist/utils.js"
32
- }
33
- },
34
- "scripts": {
35
- "build": "tsc"
36
- },
37
- "dependencies": {
38
- "@orkify/cache": "file:../cache"
39
- },
40
- "peerDependencies": {
41
- "react": ">=18",
42
- "zod": ">=3"
43
- },
44
- "peerDependenciesMeta": {
45
- "react": {
46
- "optional": true
47
- },
48
- "zod": {
49
- "optional": true
50
- }
51
- }
52
- }
@@ -1,177 +0,0 @@
1
- 'use client';
2
-
3
- // Minimal DOM types — this module runs in the browser via 'use client'.
4
- // We don't add "DOM" to tsconfig.lib to avoid polluting Node.js code.
5
-
6
- declare const window: {
7
- addEventListener(type: string, listener: (event: never) => void): void;
8
- removeEventListener(type: string, listener: (event: never) => void): void;
9
- };
10
- declare const location: undefined | { href: string };
11
- declare const navigator: undefined | { userAgent: string };
12
- interface ErrorEvent {
13
- error: unknown;
14
- message: string;
15
- }
16
- interface PromiseRejectionEvent {
17
- reason: unknown;
18
- }
19
-
20
- import { useEffect } from 'react';
21
-
22
- interface OrkifyErrorCaptureProps {
23
- /** API route endpoint. Default: `/orkify/errors` */
24
- endpoint?: string;
25
- /** Max errors to report per page load. Default: 10 */
26
- maxErrors?: number;
27
- }
28
-
29
- /** Simple hash for client-side dedup (not cryptographic). */
30
- function simpleHash(str: string): string {
31
- let h = 0;
32
- for (let i = 0; i < str.length; i++) {
33
- h = ((h << 5) - h + str.charCodeAt(i)) | 0;
34
- }
35
- return h.toString(36);
36
- }
37
-
38
- /**
39
- * Normalize a Firefox/Safari `fn@file:line:col` stack to V8 `at fn (file:line:col)` format.
40
- * Chrome/Edge stacks (already V8 format) pass through unchanged.
41
- */
42
- export function normalizeStack(stack: string): string {
43
- return stack
44
- .split('\n')
45
- .map((line) => {
46
- // Already V8 format: " at fn (...)" or " at ..."
47
- if (/^\s*at\s+/.test(line)) return line;
48
-
49
- // Firefox/Safari: "fn@file:line:col" or "@file:line:col"
50
- const m = line.match(/^([^@]*)@(.+):(\d+):(\d+)$/);
51
- if (m) {
52
- const fn = m[1];
53
- const loc = `${m[2]}:${m[3]}:${m[4]}`;
54
- return fn ? ` at ${fn} (${loc})` : ` at ${loc}`;
55
- }
56
-
57
- return line;
58
- })
59
- .join('\n');
60
- }
61
-
62
- // Module-level state for dedup and rate limiting
63
- const recentHashes = new Map<string, number>();
64
- let errorCount = 0;
65
- let configuredEndpoint = '/orkify/errors';
66
- let configuredMax = 10;
67
-
68
- /** Flush expired dedup entries (older than 5 seconds). */
69
- function flushExpired(): void {
70
- const cutoff = Date.now() - 5_000;
71
- for (const [hash, ts] of recentHashes) {
72
- if (ts < cutoff) recentHashes.delete(hash);
73
- }
74
- }
75
-
76
- /** Send an error report to the server endpoint. */
77
- function sendError(
78
- name: string,
79
- message: string,
80
- stack: string,
81
- errorType: 'browser:error' | 'browser:unhandledRejection'
82
- ): void {
83
- if (errorCount >= configuredMax) return;
84
-
85
- const normalizedStack = normalizeStack(stack);
86
- const hash = simpleHash(normalizedStack);
87
-
88
- // Dedup: skip if same stack seen in last 5 seconds
89
- flushExpired();
90
- if (recentHashes.has(hash)) return;
91
- recentHashes.set(hash, Date.now());
92
- errorCount++;
93
-
94
- void fetch(configuredEndpoint, {
95
- method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
97
- body: JSON.stringify({
98
- name,
99
- message,
100
- stack: normalizedStack,
101
- errorType,
102
- url: typeof location !== 'undefined' ? location.href : '',
103
- userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
104
- timestamp: Date.now(),
105
- }),
106
- keepalive: true,
107
- }).catch(() => {
108
- // Silently ignore — never crash the app for error reporting
109
- });
110
- }
111
-
112
- /**
113
- * Report an error manually. Use this from React Error Boundaries
114
- * (including Next.js `error.tsx`) where errors don't bubble to `window.onerror`.
115
- *
116
- * ```tsx
117
- * import { reportError } from '@orkify/next/error-capture';
118
- * useEffect(() => { reportError(error); }, [error]);
119
- * ```
120
- */
121
- export function reportError(error: unknown): void {
122
- if (!(error instanceof Error)) return;
123
- sendError(error.name || 'Error', error.message || '', error.stack || '', 'browser:error');
124
- }
125
-
126
- /**
127
- * Drop-in component that captures browser errors and reports them to orkify.
128
- * Add to your root layout:
129
- *
130
- * ```tsx
131
- * import { OrkifyErrorCapture } from '@orkify/next/error-capture';
132
- *
133
- * <OrkifyErrorCapture />
134
- * ```
135
- */
136
- export function OrkifyErrorCapture({
137
- endpoint = '/orkify/errors',
138
- maxErrors = 10,
139
- }: OrkifyErrorCaptureProps): null {
140
- useEffect(() => {
141
- configuredEndpoint = endpoint;
142
- configuredMax = maxErrors;
143
-
144
- const onError = (event: ErrorEvent): void => {
145
- const err = event.error;
146
- if (err instanceof Error) {
147
- sendError(err.name || 'Error', err.message || '', err.stack || '', 'browser:error');
148
- } else {
149
- sendError('Error', String(event.message || err), '', 'browser:error');
150
- }
151
- };
152
-
153
- const onRejection = (event: PromiseRejectionEvent): void => {
154
- const reason = event.reason;
155
- if (reason instanceof Error) {
156
- sendError(
157
- reason.name || 'UnhandledRejection',
158
- reason.message || '',
159
- reason.stack || '',
160
- 'browser:unhandledRejection'
161
- );
162
- } else {
163
- sendError('UnhandledRejection', String(reason), '', 'browser:unhandledRejection');
164
- }
165
- };
166
-
167
- window.addEventListener('error', onError);
168
- window.addEventListener('unhandledrejection', onRejection);
169
-
170
- return () => {
171
- window.removeEventListener('error', onError);
172
- window.removeEventListener('unhandledrejection', onRejection);
173
- };
174
- }, [endpoint, maxErrors]);
175
-
176
- return null;
177
- }
@@ -1,221 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { z } from 'zod';
3
- import { extractContext, parseBrowserFrames, type StackFrame } from './utils.js';
4
-
5
- // ── Rate Limiter ────────────────────────────────────────────────────────
6
-
7
- const MAX_ERRORS_PER_WINDOW = 10;
8
- const WINDOW_MS = 10_000;
9
-
10
- interface RateBucket {
11
- count: number;
12
- resetAt: number;
13
- }
14
-
15
- const rateLimits = new Map<string, RateBucket>();
16
-
17
- /** Periodic cleanup to prevent unbounded Map growth. */
18
- let cleanupTimer: null | ReturnType<typeof setInterval> = null;
19
-
20
- function ensureCleanupTimer(): void {
21
- if (cleanupTimer) return;
22
- cleanupTimer = setInterval(() => {
23
- const now = Date.now();
24
- for (const [key, bucket] of rateLimits) {
25
- if (bucket.resetAt <= now) rateLimits.delete(key);
26
- }
27
- if (rateLimits.size === 0 && cleanupTimer) {
28
- clearInterval(cleanupTimer);
29
- cleanupTimer = null;
30
- }
31
- }, 30_000);
32
- cleanupTimer.unref();
33
- }
34
-
35
- function isRateLimited(ip: string): boolean {
36
- const now = Date.now();
37
- const bucket = rateLimits.get(ip);
38
-
39
- if (!bucket || bucket.resetAt <= now) {
40
- rateLimits.set(ip, { count: 1, resetAt: now + WINDOW_MS });
41
- ensureCleanupTimer();
42
- return false;
43
- }
44
-
45
- bucket.count++;
46
- return bucket.count > MAX_ERRORS_PER_WINDOW;
47
- }
48
-
49
- // ── Validation ──────────────────────────────────────────────────────────
50
-
51
- const MAX_BODY_SIZE = 65_536; // 64 KB
52
- const MAX_STACK_LINES = 100;
53
-
54
- const browserErrorSchema = z.object({
55
- name: z.string().max(256),
56
- message: z.string().max(4096),
57
- stack: z.string().max(32_768),
58
- errorType: z.enum(['browser:error', 'browser:unhandledRejection']),
59
- url: z.string().max(2048),
60
- userAgent: z.string().max(512),
61
- timestamp: z.number(),
62
- });
63
-
64
- // ── Source Context Builder ──────────────────────────────────────────────
65
-
66
- interface SourceContextFrame {
67
- file: string;
68
- line: number;
69
- column: number;
70
- pre: string[];
71
- target: string;
72
- post: string[];
73
- }
74
-
75
- function buildSourceContext(frames: StackFrame[]): SourceContextFrame[] {
76
- const result: SourceContextFrame[] = [];
77
-
78
- for (const frame of frames) {
79
- if (!existsSync(frame.file)) continue;
80
-
81
- try {
82
- const source = readFileSync(frame.file, 'utf8');
83
- const context = extractContext(source, frame.line);
84
- if (!context) continue;
85
-
86
- result.push({
87
- file: frame.file,
88
- line: frame.line,
89
- column: frame.column,
90
- pre: context.pre,
91
- target: context.target,
92
- post: context.post,
93
- });
94
- } catch {
95
- continue;
96
- }
97
- }
98
-
99
- return result;
100
- }
101
-
102
- // ── Request Handler ─────────────────────────────────────────────────────
103
-
104
- function getClientIp(request: Request): string {
105
- // Cloudflare: most reliable, cannot be spoofed by the client
106
- const cfIp = request.headers.get('cf-connecting-ip');
107
- if (cfIp) return cfIp;
108
- // Standard proxy headers
109
- const xff = request.headers.get('x-forwarded-for');
110
- if (xff) return xff.split(',')[0].trim();
111
- const realIp = request.headers.get('x-real-ip');
112
- if (realIp) return realIp;
113
- return '127.0.0.1';
114
- }
115
-
116
- /**
117
- * Next.js API route handler for browser error reporting.
118
- *
119
- * Create `app/orkify/errors/route.ts`:
120
- * ```ts
121
- * export { POST } from '@orkify/next/error-handler';
122
- * ```
123
- */
124
- export async function POST(request: Request): Promise<Response> {
125
- try {
126
- // 1. Validate origin — browsers always send Origin on POST requests.
127
- // This blocks cross-origin abuse and non-browser clients (curl/bots).
128
- // Behind reverse proxies (nginx, Cloudflare), Host may be internal
129
- // while Origin is public. X-Forwarded-Host carries the original Host.
130
- const origin = request.headers.get('origin');
131
- if (!origin) {
132
- return Response.json({ ok: false }, { status: 403 });
133
- }
134
- const effectiveHost = request.headers.get('x-forwarded-host') || request.headers.get('host');
135
- if (effectiveHost) {
136
- try {
137
- const originHost = new URL(origin).host;
138
- if (originHost !== effectiveHost) {
139
- return Response.json({ ok: false }, { status: 403 });
140
- }
141
- } catch {
142
- return Response.json({ ok: false }, { status: 403 });
143
- }
144
- }
145
-
146
- // 2. Check content length
147
- const contentLength = request.headers.get('content-length');
148
- if (contentLength && parseInt(contentLength, 10) > MAX_BODY_SIZE) {
149
- return Response.json({ ok: false }, { status: 413 });
150
- }
151
-
152
- // 3. Rate limit
153
- const ip = getClientIp(request);
154
- if (isRateLimited(ip)) {
155
- return Response.json({ ok: false }, { status: 429 });
156
- }
157
-
158
- // 4. Parse and validate body
159
- const raw = await request.text();
160
- if (raw.length > MAX_BODY_SIZE) {
161
- return Response.json({ ok: false }, { status: 413 });
162
- }
163
-
164
- let body: unknown;
165
- try {
166
- body = JSON.parse(raw);
167
- } catch {
168
- return Response.json({ ok: false }, { status: 400 });
169
- }
170
-
171
- const parsed = browserErrorSchema.safeParse(body);
172
- if (!parsed.success) {
173
- return Response.json({ ok: false }, { status: 400 });
174
- }
175
-
176
- const data = parsed.data;
177
-
178
- // 5. Truncate stack to max lines
179
- const stackLines = data.stack.split('\n');
180
- const truncatedStack =
181
- stackLines.length > MAX_STACK_LINES
182
- ? stackLines.slice(0, MAX_STACK_LINES).join('\n')
183
- : data.stack;
184
-
185
- // 6. Parse browser stack → frames, map URLs to file paths
186
- const cwd = process.cwd();
187
- const frames = parseBrowserFrames(truncatedStack, cwd);
188
-
189
- // 7. Build source context from bundled files on disk
190
- const sourceContext = frames.length > 0 ? buildSourceContext(frames) : null;
191
- const topFrame = frames[0] ?? null;
192
-
193
- // 8. Relay to daemon via IPC
194
- if (typeof process.send === 'function') {
195
- process.send({
196
- __orkify: true,
197
- type: 'error',
198
- data: {
199
- errorType: data.errorType,
200
- name: data.name,
201
- message: data.message,
202
- stack: truncatedStack,
203
- fingerprint: '', // Daemon recomputes this
204
- sourceContext: sourceContext && sourceContext.length > 0 ? sourceContext : null,
205
- topFrame,
206
- diagnostics: null,
207
- timestamp: data.timestamp,
208
- nodeVersion: '',
209
- pid: 0,
210
- url: data.url,
211
- userAgent: data.userAgent,
212
- },
213
- });
214
- }
215
-
216
- return Response.json({ ok: true });
217
- } catch {
218
- // Never crash the app for error reporting
219
- return Response.json({ ok: true });
220
- }
221
- }
@@ -1,100 +0,0 @@
1
- import { cache } from '@orkify/cache';
2
-
3
- /**
4
- * ISR / route cache handler for Next.js (`cacheHandler` config).
5
- * Next.js instantiates this with `new`, so it must be a class.
6
- *
7
- * Next.js expects `get()` to return `{ value, lastModified }` (the
8
- * `CacheHandlerValue` interface), so we wrap/unwrap around the raw
9
- * `IncrementalCacheValue` when storing in orkify/cache.
10
- *
11
- * During `next build`, this handler is a no-op — data stored in-memory
12
- * would be lost when the build process exits, causing invariant errors
13
- * at runtime when Next.js expects cached pages that no longer exist.
14
- * Pre-rendered pages are still served from disk (`.next/server/app/`).
15
- *
16
- * Headers objects (used by APP_ROUTE responses) don't survive V8
17
- * structured clone (IPC in cluster mode). We convert them to plain
18
- * objects on set so they work with both IPC and bracket-notation
19
- * access in Next.js internals.
20
- *
21
- * Cache tags are extracted from the `x-next-cache-tags` header (set by
22
- * Next.js for APP_PAGE/APP_ROUTE) and registered in orkify's tag index
23
- * so `revalidateTag()` → `cache.invalidateTag()` can find entries.
24
- */
25
- const IS_BUILD = process.env.NEXT_PHASE === 'phase-production-build';
26
- const NEXT_CACHE_TAGS_HEADER = 'x-next-cache-tags';
27
-
28
- interface StoredEntry {
29
- lastModified: number;
30
- value: null | Record<string, unknown>;
31
- }
32
-
33
- /** Convert Headers to a plain object. Non-Headers pass through. */
34
- function headersToPlainObject(headers: unknown): Record<string, string> | undefined {
35
- if (!headers) return undefined;
36
- if (headers instanceof Headers) {
37
- return Object.fromEntries([...(headers as Headers).entries()]);
38
- }
39
- if (Array.isArray(headers)) {
40
- // entries array from a previous version — normalize to plain object
41
- return Object.fromEntries(headers);
42
- }
43
- return headers as Record<string, string>;
44
- }
45
-
46
- /** Extract cache tags from the x-next-cache-tags header value. */
47
- function extractTags(headers: Record<string, string> | undefined): string[] | undefined {
48
- const tagHeader = headers?.[NEXT_CACHE_TAGS_HEADER];
49
- if (typeof tagHeader === 'string' && tagHeader.length > 0) {
50
- return tagHeader.split(',');
51
- }
52
- return undefined;
53
- }
54
-
55
- export default class OrkifyCacheHandler {
56
- async get(key: string): Promise<null | unknown> {
57
- if (IS_BUILD) return null;
58
-
59
- const stored = await cache.getAsync<StoredEntry>(key);
60
- if (!stored) return null;
61
-
62
- return stored;
63
- }
64
-
65
- async set(key: string, data: unknown, ctx?: { tags?: string[] }): Promise<void> {
66
- if (IS_BUILD) return;
67
-
68
- let value = data as null | Record<string, unknown>;
69
-
70
- // Normalize Headers objects to plain objects for IPC and bracket-notation access
71
- if (value && 'headers' in value) {
72
- const plainHeaders = headersToPlainObject(value.headers);
73
- if (plainHeaders !== value.headers) {
74
- value = { ...value, headers: plainHeaders };
75
- }
76
- }
77
-
78
- // Gather tags: merge explicit ctx.tags with x-next-cache-tags header (deduplicated)
79
- const headerTags = value ? extractTags(value.headers as Record<string, string>) : undefined;
80
- const mergedTags =
81
- ctx?.tags && headerTags
82
- ? [...new Set([...ctx.tags, ...headerTags])]
83
- : (ctx?.tags ?? headerTags);
84
- const tags = mergedTags && mergedTags.length > 0 ? mergedTags : undefined;
85
-
86
- const entry: StoredEntry = { value, lastModified: Date.now() };
87
- cache.set(key, entry, tags ? { tags } : undefined);
88
- }
89
-
90
- async revalidateTag(tag: string | string[]): Promise<void> {
91
- const tags = Array.isArray(tag) ? tag : [tag];
92
- for (const t of tags) {
93
- cache.invalidateTag(t);
94
- }
95
- }
96
-
97
- resetRequestCache(): void {
98
- // No-op — shared cache, not per-request
99
- }
100
- }
@@ -1,23 +0,0 @@
1
- /** Consume a ReadableStream into a single Buffer. */
2
- export async function streamToBuffer(stream: ReadableStream<Uint8Array>): Promise<Buffer> {
3
- const chunks: Uint8Array[] = [];
4
- const reader = stream.getReader();
5
-
6
- for (;;) {
7
- const { done, value } = await reader.read();
8
- if (done) break;
9
- chunks.push(value);
10
- }
11
-
12
- return Buffer.concat(chunks);
13
- }
14
-
15
- /** Create a single-chunk ReadableStream from a Buffer. */
16
- export function bufferToStream(buffer: Buffer): ReadableStream<Uint8Array> {
17
- return new ReadableStream({
18
- start(controller) {
19
- controller.enqueue(new Uint8Array(buffer));
20
- controller.close();
21
- },
22
- });
23
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Next.js cache handler interfaces, mirrored here so orkify doesn't
3
- * depend on `next` as a package dependency.
4
- */
5
-
6
- /** What Next.js passes to / expects from the 'use cache' handler. */
7
- export interface NextCacheEntry {
8
- expire: number; // seconds, hard max lifetime
9
- revalidate: number; // seconds, revalidation interval
10
- stale: number; // seconds
11
- tags: string[];
12
- timestamp: number; // ms, when created
13
- value: ReadableStream<Uint8Array>;
14
- }
15
-
16
- /** 'use cache' handler — 5 methods. */
17
- export interface NextCacheHandler {
18
- get(cacheKey: string, softTags: string[]): Promise<NextCacheEntry | undefined>;
19
- getExpiration(tags: string[]): Promise<number>;
20
- refreshTags(): Promise<void>;
21
- set(cacheKey: string, pendingEntry: Promise<NextCacheEntry>): Promise<void>;
22
- updateTags(tags: string[], durations?: { expire?: number }): Promise<void>;
23
- }
24
-
25
- /** What we actually store in orkify/cache (Buffer instead of stream). */
26
- export interface StoredCacheEntry {
27
- buffer: Buffer;
28
- expire: number;
29
- revalidate: number;
30
- stale: number;
31
- tags: string[];
32
- timestamp: number;
33
- }