@demokit-ai/next 0.0.1
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/dist/client.cjs +152 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +114 -0
- package/dist/client.d.ts +114 -0
- package/dist/client.js +128 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.cjs +111 -0
- package/dist/middleware.cjs.map +1 -0
- package/dist/middleware.d.cts +47 -0
- package/dist/middleware.d.ts +47 -0
- package/dist/middleware.js +106 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server.cjs +183 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +89 -0
- package/dist/server.d.ts +89 -0
- package/dist/server.js +171 -0
- package/dist/server.js.map +1 -0
- package/dist/types-Cvq5JUP1.d.cts +129 -0
- package/dist/types-Cvq5JUP1.d.ts +129 -0
- package/package.json +94 -0
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var async_hooks = require('async_hooks');
|
|
4
|
+
var core = require('@demokit-ai/core');
|
|
5
|
+
var server = require('next/server');
|
|
6
|
+
|
|
7
|
+
// src/server/context.ts
|
|
8
|
+
var demoContextStorage = new async_hooks.AsyncLocalStorage();
|
|
9
|
+
function getServerDemoContext() {
|
|
10
|
+
return demoContextStorage.getStore() ?? null;
|
|
11
|
+
}
|
|
12
|
+
function isServerDemoMode() {
|
|
13
|
+
const context = getServerDemoContext();
|
|
14
|
+
return context?.enabled ?? false;
|
|
15
|
+
}
|
|
16
|
+
function getServerScenario() {
|
|
17
|
+
const context = getServerDemoContext();
|
|
18
|
+
return context?.scenario ?? null;
|
|
19
|
+
}
|
|
20
|
+
function runWithDemoContext(context, fn) {
|
|
21
|
+
return demoContextStorage.run(context, fn);
|
|
22
|
+
}
|
|
23
|
+
function createServerInterceptor(config) {
|
|
24
|
+
const { fixtures, scenarios = {}, baseUrl } = config;
|
|
25
|
+
const getActiveFixtures = () => {
|
|
26
|
+
const scenario = getServerScenario();
|
|
27
|
+
if (scenario && scenarios[scenario]) {
|
|
28
|
+
return { ...fixtures, ...scenarios[scenario] };
|
|
29
|
+
}
|
|
30
|
+
return fixtures;
|
|
31
|
+
};
|
|
32
|
+
const wrappedFixtures = new Proxy(fixtures, {
|
|
33
|
+
get(target, prop) {
|
|
34
|
+
const activeFixtures = getActiveFixtures();
|
|
35
|
+
return activeFixtures[prop];
|
|
36
|
+
},
|
|
37
|
+
ownKeys() {
|
|
38
|
+
const activeFixtures = getActiveFixtures();
|
|
39
|
+
return Reflect.ownKeys(activeFixtures);
|
|
40
|
+
},
|
|
41
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
42
|
+
const activeFixtures = getActiveFixtures();
|
|
43
|
+
if (prop in activeFixtures) {
|
|
44
|
+
return {
|
|
45
|
+
configurable: true,
|
|
46
|
+
enumerable: true,
|
|
47
|
+
value: activeFixtures[prop]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return core.createDemoInterceptor({
|
|
54
|
+
fixtures: wrappedFixtures,
|
|
55
|
+
baseUrl,
|
|
56
|
+
// Server-side storage is not used (we use cookies/headers)
|
|
57
|
+
storageKey: void 0
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function withDemoCheck(handler, fallback) {
|
|
61
|
+
return async (context) => {
|
|
62
|
+
if (!isServerDemoMode()) {
|
|
63
|
+
if (fallback) {
|
|
64
|
+
return fallback();
|
|
65
|
+
}
|
|
66
|
+
throw new Error("[DemoKit] Demo mode is not enabled");
|
|
67
|
+
}
|
|
68
|
+
return handler(context);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
var DEFAULT_CONFIG = {
|
|
72
|
+
cookieName: "demokit-mode",
|
|
73
|
+
urlParam: "demo",
|
|
74
|
+
headerName: "x-demokit-mode",
|
|
75
|
+
apiPaths: ["/api/"],
|
|
76
|
+
cookieOptions: {
|
|
77
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
78
|
+
// 1 week
|
|
79
|
+
path: "/",
|
|
80
|
+
sameSite: "lax",
|
|
81
|
+
secure: process.env.NODE_ENV === "production"
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function createDemoMiddleware(config = {}) {
|
|
85
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
86
|
+
const { cookieName, urlParam, headerName, apiPaths, cookieOptions } = mergedConfig;
|
|
87
|
+
return function demoMiddleware2(request) {
|
|
88
|
+
const demoParam = request.nextUrl.searchParams.get(urlParam);
|
|
89
|
+
const demoCookie = request.cookies.get(cookieName);
|
|
90
|
+
let response = server.NextResponse.next();
|
|
91
|
+
let isDemoMode = false;
|
|
92
|
+
let scenario = null;
|
|
93
|
+
if (demoParam !== null) {
|
|
94
|
+
if (demoParam === "false" || demoParam === "0" || demoParam === "") {
|
|
95
|
+
response = server.NextResponse.next();
|
|
96
|
+
response.cookies.delete(cookieName);
|
|
97
|
+
isDemoMode = false;
|
|
98
|
+
} else if (demoParam === "true" || demoParam === "1") {
|
|
99
|
+
response = server.NextResponse.next();
|
|
100
|
+
response.cookies.set(cookieName, "true", cookieOptions);
|
|
101
|
+
isDemoMode = true;
|
|
102
|
+
} else {
|
|
103
|
+
response = server.NextResponse.next();
|
|
104
|
+
response.cookies.set(cookieName, demoParam, cookieOptions);
|
|
105
|
+
isDemoMode = true;
|
|
106
|
+
scenario = demoParam;
|
|
107
|
+
}
|
|
108
|
+
} else if (demoCookie) {
|
|
109
|
+
isDemoMode = true;
|
|
110
|
+
scenario = demoCookie.value !== "true" ? demoCookie.value : null;
|
|
111
|
+
}
|
|
112
|
+
if (isDemoMode) {
|
|
113
|
+
const isApiRoute = apiPaths.some(
|
|
114
|
+
(path) => request.nextUrl.pathname.startsWith(path)
|
|
115
|
+
);
|
|
116
|
+
if (isApiRoute) {
|
|
117
|
+
const requestHeaders = new Headers(request.headers);
|
|
118
|
+
requestHeaders.set(headerName, scenario ?? "true");
|
|
119
|
+
response = server.NextResponse.next({
|
|
120
|
+
request: {
|
|
121
|
+
headers: requestHeaders
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (demoParam !== null) {
|
|
125
|
+
if (demoParam === "false" || demoParam === "0" || demoParam === "") {
|
|
126
|
+
response.cookies.delete(cookieName);
|
|
127
|
+
} else {
|
|
128
|
+
response.cookies.set(
|
|
129
|
+
cookieName,
|
|
130
|
+
demoParam === "true" || demoParam === "1" ? "true" : demoParam,
|
|
131
|
+
cookieOptions
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
isDemoMode,
|
|
139
|
+
scenario,
|
|
140
|
+
response
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function demoMiddleware(config = {}) {
|
|
145
|
+
const handler = createDemoMiddleware(config);
|
|
146
|
+
return function middleware(request) {
|
|
147
|
+
return handler(request).response;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function isDemoRequest(request, config = {}) {
|
|
151
|
+
const { cookieName = "demokit-mode", headerName = "x-demokit-mode" } = config;
|
|
152
|
+
if (request.headers.get(headerName)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const cookie = request.cookies.get(cookieName);
|
|
156
|
+
return cookie !== void 0 && cookie.value !== "";
|
|
157
|
+
}
|
|
158
|
+
function getDemoScenario(request, config = {}) {
|
|
159
|
+
const { cookieName = "demokit-mode", headerName = "x-demokit-mode" } = config;
|
|
160
|
+
const headerValue = request.headers.get(headerName);
|
|
161
|
+
if (headerValue && headerValue !== "true") {
|
|
162
|
+
return headerValue;
|
|
163
|
+
}
|
|
164
|
+
const cookie = request.cookies.get(cookieName);
|
|
165
|
+
if (cookie && cookie.value !== "true") {
|
|
166
|
+
return cookie.value;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
exports.createDemoMiddleware = createDemoMiddleware;
|
|
172
|
+
exports.createServerInterceptor = createServerInterceptor;
|
|
173
|
+
exports.demoContextStorage = demoContextStorage;
|
|
174
|
+
exports.demoMiddleware = demoMiddleware;
|
|
175
|
+
exports.getDemoScenario = getDemoScenario;
|
|
176
|
+
exports.getServerDemoContext = getServerDemoContext;
|
|
177
|
+
exports.getServerScenario = getServerScenario;
|
|
178
|
+
exports.isDemoRequest = isDemoRequest;
|
|
179
|
+
exports.isServerDemoMode = isServerDemoMode;
|
|
180
|
+
exports.runWithDemoContext = runWithDemoContext;
|
|
181
|
+
exports.withDemoCheck = withDemoCheck;
|
|
182
|
+
//# sourceMappingURL=server.cjs.map
|
|
183
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/context.ts","../src/server/interceptor.ts","../src/server/middleware.ts"],"names":["AsyncLocalStorage","createDemoInterceptor","demoMiddleware","NextResponse"],"mappings":"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","file":"server.cjs","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n\n/**\n * Server-side demo mode context\n */\nexport interface ServerDemoContext {\n /**\n * Whether demo mode is enabled\n */\n enabled: boolean\n\n /**\n * The current scenario name\n */\n scenario: string | null\n}\n\n/**\n * AsyncLocalStorage for server-side demo mode context\n * This allows Server Components to access demo mode state\n */\nexport const demoContextStorage = new AsyncLocalStorage<ServerDemoContext>()\n\n/**\n * Get the current server-side demo context\n * Returns null if not in a demo context\n */\nexport function getServerDemoContext(): ServerDemoContext | null {\n return demoContextStorage.getStore() ?? null\n}\n\n/**\n * Check if demo mode is enabled on the server\n */\nexport function isServerDemoMode(): boolean {\n const context = getServerDemoContext()\n return context?.enabled ?? false\n}\n\n/**\n * Get the current scenario on the server\n */\nexport function getServerScenario(): string | null {\n const context = getServerDemoContext()\n return context?.scenario ?? null\n}\n\n/**\n * Run a function within a demo context\n */\nexport function runWithDemoContext<T>(\n context: ServerDemoContext,\n fn: () => T\n): T {\n return demoContextStorage.run(context, fn)\n}\n","import {\n createDemoInterceptor,\n type FixtureMap,\n type DemoInterceptor,\n type RequestContext,\n} from '@demokit-ai/core'\nimport { isServerDemoMode, getServerScenario } from './context'\n\n/**\n * Configuration for server-side demo interceptor\n */\nexport interface ServerInterceptorConfig {\n /**\n * Base fixtures (always available)\n */\n fixtures: FixtureMap\n\n /**\n * Scenario-specific fixtures\n */\n scenarios?: Record<string, FixtureMap>\n\n /**\n * Base URL for relative URL parsing\n */\n baseUrl?: string\n}\n\n/**\n * Create a server-side demo interceptor\n *\n * This interceptor works with Server Components and API routes.\n * It uses AsyncLocalStorage to check demo mode state.\n *\n * @example\n * // lib/demo-interceptor.ts\n * import { createServerInterceptor } from '@demokit-ai/next/server'\n *\n * export const demoInterceptor = createServerInterceptor({\n * fixtures: {\n * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],\n * },\n * scenarios: {\n * 'empty': { 'GET /api/users': () => [] },\n * },\n * })\n *\n * // Enable in your app\n * demoInterceptor.enable()\n */\nexport function createServerInterceptor(config: ServerInterceptorConfig): DemoInterceptor {\n const { fixtures, scenarios = {}, baseUrl } = config\n\n // Merge fixtures based on current scenario\n const getActiveFixtures = (): FixtureMap => {\n const scenario = getServerScenario()\n if (scenario && scenarios[scenario]) {\n return { ...fixtures, ...scenarios[scenario] }\n }\n return fixtures\n }\n\n // Wrapper that checks server context before each request\n const wrappedFixtures: FixtureMap = new Proxy(fixtures, {\n get(target, prop: string) {\n const activeFixtures = getActiveFixtures()\n return activeFixtures[prop]\n },\n ownKeys() {\n const activeFixtures = getActiveFixtures()\n return Reflect.ownKeys(activeFixtures)\n },\n getOwnPropertyDescriptor(target, prop) {\n const activeFixtures = getActiveFixtures()\n if (prop in activeFixtures) {\n return {\n configurable: true,\n enumerable: true,\n value: activeFixtures[prop as string],\n }\n }\n return undefined\n },\n })\n\n return createDemoInterceptor({\n fixtures: wrappedFixtures,\n baseUrl,\n // Server-side storage is not used (we use cookies/headers)\n storageKey: undefined,\n })\n}\n\n/**\n * Helper to wrap a fixture handler with demo mode check\n */\nexport function withDemoCheck<T>(\n handler: (context: RequestContext) => T | Promise<T>,\n fallback?: () => T | Promise<T>\n): (context: RequestContext) => T | Promise<T> {\n return async (context) => {\n if (!isServerDemoMode()) {\n if (fallback) {\n return fallback()\n }\n throw new Error('[DemoKit] Demo mode is not enabled')\n }\n return handler(context)\n }\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { DemoMiddlewareConfig, MiddlewareResult } from '../types'\n\nconst DEFAULT_CONFIG: Required<DemoMiddlewareConfig> = {\n cookieName: 'demokit-mode',\n urlParam: 'demo',\n headerName: 'x-demokit-mode',\n apiPaths: ['/api/'],\n cookieOptions: {\n maxAge: 60 * 60 * 24 * 7, // 1 week\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n },\n}\n\n/**\n * Create a demo-aware middleware\n *\n * This middleware:\n * 1. Checks for demo mode URL parameter (?demo=true or ?demo=false)\n * 2. Sets/clears demo mode cookie\n * 3. Adds demo mode header to API requests\n * 4. Supports scenario switching via ?demo=scenario-name\n *\n * @example\n * // middleware.ts\n * import { createDemoMiddleware } from '@demokit-ai/next/middleware'\n *\n * const demoMiddleware = createDemoMiddleware()\n *\n * export function middleware(request: NextRequest) {\n * return demoMiddleware(request)\n * }\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * }\n */\nexport function createDemoMiddleware(config: DemoMiddlewareConfig = {}) {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config }\n const { cookieName, urlParam, headerName, apiPaths, cookieOptions } = mergedConfig\n\n return function demoMiddleware(request: NextRequest): MiddlewareResult {\n // Check for demo mode in URL param\n const demoParam = request.nextUrl.searchParams.get(urlParam)\n\n // Check for existing cookie\n const demoCookie = request.cookies.get(cookieName)\n\n let response = NextResponse.next()\n let isDemoMode = false\n let scenario: string | null = null\n\n // Handle URL param toggle\n if (demoParam !== null) {\n if (demoParam === 'false' || demoParam === '0' || demoParam === '') {\n // Disable demo mode\n response = NextResponse.next()\n response.cookies.delete(cookieName)\n isDemoMode = false\n } else if (demoParam === 'true' || demoParam === '1') {\n // Enable demo mode (no scenario)\n response = NextResponse.next()\n response.cookies.set(cookieName, 'true', cookieOptions)\n isDemoMode = true\n } else {\n // Enable with specific scenario\n response = NextResponse.next()\n response.cookies.set(cookieName, demoParam, cookieOptions)\n isDemoMode = true\n scenario = demoParam\n }\n } else if (demoCookie) {\n // Use existing cookie\n isDemoMode = true\n scenario = demoCookie.value !== 'true' ? demoCookie.value : null\n }\n\n // Add header for API routes\n if (isDemoMode) {\n const isApiRoute = apiPaths.some((path) =>\n request.nextUrl.pathname.startsWith(path)\n )\n\n if (isApiRoute) {\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(headerName, scenario ?? 'true')\n\n response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n })\n\n // Re-set cookie if we created new response\n if (demoParam !== null) {\n if (demoParam === 'false' || demoParam === '0' || demoParam === '') {\n response.cookies.delete(cookieName)\n } else {\n response.cookies.set(\n cookieName,\n demoParam === 'true' || demoParam === '1' ? 'true' : demoParam,\n cookieOptions\n )\n }\n }\n }\n }\n\n return {\n isDemoMode,\n scenario,\n response,\n }\n }\n}\n\n/**\n * Simple middleware wrapper that just returns the response\n *\n * @example\n * import { demoMiddleware } from '@demokit-ai/next/middleware'\n * export const middleware = demoMiddleware()\n */\nexport function demoMiddleware(config: DemoMiddlewareConfig = {}) {\n const handler = createDemoMiddleware(config)\n\n return function middleware(request: NextRequest): NextResponse {\n return handler(request).response\n }\n}\n\n/**\n * Check if a request is in demo mode (from middleware or direct header check)\n */\nexport function isDemoRequest(request: NextRequest, config: DemoMiddlewareConfig = {}): boolean {\n const { cookieName = 'demokit-mode', headerName = 'x-demokit-mode' } = config\n\n // Check header first (set by middleware)\n if (request.headers.get(headerName)) {\n return true\n }\n\n // Check cookie\n const cookie = request.cookies.get(cookieName)\n return cookie !== undefined && cookie.value !== ''\n}\n\n/**\n * Get the demo scenario from a request\n */\nexport function getDemoScenario(request: NextRequest, config: DemoMiddlewareConfig = {}): string | null {\n const { cookieName = 'demokit-mode', headerName = 'x-demokit-mode' } = config\n\n // Check header first\n const headerValue = request.headers.get(headerName)\n if (headerValue && headerValue !== 'true') {\n return headerValue\n }\n\n // Check cookie\n const cookie = request.cookies.get(cookieName)\n if (cookie && cookie.value !== 'true') {\n return cookie.value\n }\n\n return null\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { FixtureMap, DemoInterceptor, RequestContext } from '@demokit-ai/core';
|
|
3
|
+
export { createDemoMiddleware, demoMiddleware, getDemoScenario, isDemoRequest } from './middleware.cjs';
|
|
4
|
+
import 'next/server';
|
|
5
|
+
import './types-Cvq5JUP1.cjs';
|
|
6
|
+
import 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Server-side demo mode context
|
|
10
|
+
*/
|
|
11
|
+
interface ServerDemoContext {
|
|
12
|
+
/**
|
|
13
|
+
* Whether demo mode is enabled
|
|
14
|
+
*/
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The current scenario name
|
|
18
|
+
*/
|
|
19
|
+
scenario: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* AsyncLocalStorage for server-side demo mode context
|
|
23
|
+
* This allows Server Components to access demo mode state
|
|
24
|
+
*/
|
|
25
|
+
declare const demoContextStorage: AsyncLocalStorage<ServerDemoContext>;
|
|
26
|
+
/**
|
|
27
|
+
* Get the current server-side demo context
|
|
28
|
+
* Returns null if not in a demo context
|
|
29
|
+
*/
|
|
30
|
+
declare function getServerDemoContext(): ServerDemoContext | null;
|
|
31
|
+
/**
|
|
32
|
+
* Check if demo mode is enabled on the server
|
|
33
|
+
*/
|
|
34
|
+
declare function isServerDemoMode(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get the current scenario on the server
|
|
37
|
+
*/
|
|
38
|
+
declare function getServerScenario(): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Run a function within a demo context
|
|
41
|
+
*/
|
|
42
|
+
declare function runWithDemoContext<T>(context: ServerDemoContext, fn: () => T): T;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for server-side demo interceptor
|
|
46
|
+
*/
|
|
47
|
+
interface ServerInterceptorConfig {
|
|
48
|
+
/**
|
|
49
|
+
* Base fixtures (always available)
|
|
50
|
+
*/
|
|
51
|
+
fixtures: FixtureMap;
|
|
52
|
+
/**
|
|
53
|
+
* Scenario-specific fixtures
|
|
54
|
+
*/
|
|
55
|
+
scenarios?: Record<string, FixtureMap>;
|
|
56
|
+
/**
|
|
57
|
+
* Base URL for relative URL parsing
|
|
58
|
+
*/
|
|
59
|
+
baseUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a server-side demo interceptor
|
|
63
|
+
*
|
|
64
|
+
* This interceptor works with Server Components and API routes.
|
|
65
|
+
* It uses AsyncLocalStorage to check demo mode state.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // lib/demo-interceptor.ts
|
|
69
|
+
* import { createServerInterceptor } from '@demokit-ai/next/server'
|
|
70
|
+
*
|
|
71
|
+
* export const demoInterceptor = createServerInterceptor({
|
|
72
|
+
* fixtures: {
|
|
73
|
+
* 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
|
|
74
|
+
* },
|
|
75
|
+
* scenarios: {
|
|
76
|
+
* 'empty': { 'GET /api/users': () => [] },
|
|
77
|
+
* },
|
|
78
|
+
* })
|
|
79
|
+
*
|
|
80
|
+
* // Enable in your app
|
|
81
|
+
* demoInterceptor.enable()
|
|
82
|
+
*/
|
|
83
|
+
declare function createServerInterceptor(config: ServerInterceptorConfig): DemoInterceptor;
|
|
84
|
+
/**
|
|
85
|
+
* Helper to wrap a fixture handler with demo mode check
|
|
86
|
+
*/
|
|
87
|
+
declare function withDemoCheck<T>(handler: (context: RequestContext) => T | Promise<T>, fallback?: () => T | Promise<T>): (context: RequestContext) => T | Promise<T>;
|
|
88
|
+
|
|
89
|
+
export { type ServerDemoContext, type ServerInterceptorConfig, createServerInterceptor, demoContextStorage, getServerDemoContext, getServerScenario, isServerDemoMode, runWithDemoContext, withDemoCheck };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { FixtureMap, DemoInterceptor, RequestContext } from '@demokit-ai/core';
|
|
3
|
+
export { createDemoMiddleware, demoMiddleware, getDemoScenario, isDemoRequest } from './middleware.js';
|
|
4
|
+
import 'next/server';
|
|
5
|
+
import './types-Cvq5JUP1.js';
|
|
6
|
+
import 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Server-side demo mode context
|
|
10
|
+
*/
|
|
11
|
+
interface ServerDemoContext {
|
|
12
|
+
/**
|
|
13
|
+
* Whether demo mode is enabled
|
|
14
|
+
*/
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The current scenario name
|
|
18
|
+
*/
|
|
19
|
+
scenario: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* AsyncLocalStorage for server-side demo mode context
|
|
23
|
+
* This allows Server Components to access demo mode state
|
|
24
|
+
*/
|
|
25
|
+
declare const demoContextStorage: AsyncLocalStorage<ServerDemoContext>;
|
|
26
|
+
/**
|
|
27
|
+
* Get the current server-side demo context
|
|
28
|
+
* Returns null if not in a demo context
|
|
29
|
+
*/
|
|
30
|
+
declare function getServerDemoContext(): ServerDemoContext | null;
|
|
31
|
+
/**
|
|
32
|
+
* Check if demo mode is enabled on the server
|
|
33
|
+
*/
|
|
34
|
+
declare function isServerDemoMode(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get the current scenario on the server
|
|
37
|
+
*/
|
|
38
|
+
declare function getServerScenario(): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Run a function within a demo context
|
|
41
|
+
*/
|
|
42
|
+
declare function runWithDemoContext<T>(context: ServerDemoContext, fn: () => T): T;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for server-side demo interceptor
|
|
46
|
+
*/
|
|
47
|
+
interface ServerInterceptorConfig {
|
|
48
|
+
/**
|
|
49
|
+
* Base fixtures (always available)
|
|
50
|
+
*/
|
|
51
|
+
fixtures: FixtureMap;
|
|
52
|
+
/**
|
|
53
|
+
* Scenario-specific fixtures
|
|
54
|
+
*/
|
|
55
|
+
scenarios?: Record<string, FixtureMap>;
|
|
56
|
+
/**
|
|
57
|
+
* Base URL for relative URL parsing
|
|
58
|
+
*/
|
|
59
|
+
baseUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a server-side demo interceptor
|
|
63
|
+
*
|
|
64
|
+
* This interceptor works with Server Components and API routes.
|
|
65
|
+
* It uses AsyncLocalStorage to check demo mode state.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // lib/demo-interceptor.ts
|
|
69
|
+
* import { createServerInterceptor } from '@demokit-ai/next/server'
|
|
70
|
+
*
|
|
71
|
+
* export const demoInterceptor = createServerInterceptor({
|
|
72
|
+
* fixtures: {
|
|
73
|
+
* 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
|
|
74
|
+
* },
|
|
75
|
+
* scenarios: {
|
|
76
|
+
* 'empty': { 'GET /api/users': () => [] },
|
|
77
|
+
* },
|
|
78
|
+
* })
|
|
79
|
+
*
|
|
80
|
+
* // Enable in your app
|
|
81
|
+
* demoInterceptor.enable()
|
|
82
|
+
*/
|
|
83
|
+
declare function createServerInterceptor(config: ServerInterceptorConfig): DemoInterceptor;
|
|
84
|
+
/**
|
|
85
|
+
* Helper to wrap a fixture handler with demo mode check
|
|
86
|
+
*/
|
|
87
|
+
declare function withDemoCheck<T>(handler: (context: RequestContext) => T | Promise<T>, fallback?: () => T | Promise<T>): (context: RequestContext) => T | Promise<T>;
|
|
88
|
+
|
|
89
|
+
export { type ServerDemoContext, type ServerInterceptorConfig, createServerInterceptor, demoContextStorage, getServerDemoContext, getServerScenario, isServerDemoMode, runWithDemoContext, withDemoCheck };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
import { createDemoInterceptor } from '@demokit-ai/core';
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
|
|
5
|
+
// src/server/context.ts
|
|
6
|
+
var demoContextStorage = new AsyncLocalStorage();
|
|
7
|
+
function getServerDemoContext() {
|
|
8
|
+
return demoContextStorage.getStore() ?? null;
|
|
9
|
+
}
|
|
10
|
+
function isServerDemoMode() {
|
|
11
|
+
const context = getServerDemoContext();
|
|
12
|
+
return context?.enabled ?? false;
|
|
13
|
+
}
|
|
14
|
+
function getServerScenario() {
|
|
15
|
+
const context = getServerDemoContext();
|
|
16
|
+
return context?.scenario ?? null;
|
|
17
|
+
}
|
|
18
|
+
function runWithDemoContext(context, fn) {
|
|
19
|
+
return demoContextStorage.run(context, fn);
|
|
20
|
+
}
|
|
21
|
+
function createServerInterceptor(config) {
|
|
22
|
+
const { fixtures, scenarios = {}, baseUrl } = config;
|
|
23
|
+
const getActiveFixtures = () => {
|
|
24
|
+
const scenario = getServerScenario();
|
|
25
|
+
if (scenario && scenarios[scenario]) {
|
|
26
|
+
return { ...fixtures, ...scenarios[scenario] };
|
|
27
|
+
}
|
|
28
|
+
return fixtures;
|
|
29
|
+
};
|
|
30
|
+
const wrappedFixtures = new Proxy(fixtures, {
|
|
31
|
+
get(target, prop) {
|
|
32
|
+
const activeFixtures = getActiveFixtures();
|
|
33
|
+
return activeFixtures[prop];
|
|
34
|
+
},
|
|
35
|
+
ownKeys() {
|
|
36
|
+
const activeFixtures = getActiveFixtures();
|
|
37
|
+
return Reflect.ownKeys(activeFixtures);
|
|
38
|
+
},
|
|
39
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
40
|
+
const activeFixtures = getActiveFixtures();
|
|
41
|
+
if (prop in activeFixtures) {
|
|
42
|
+
return {
|
|
43
|
+
configurable: true,
|
|
44
|
+
enumerable: true,
|
|
45
|
+
value: activeFixtures[prop]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return createDemoInterceptor({
|
|
52
|
+
fixtures: wrappedFixtures,
|
|
53
|
+
baseUrl,
|
|
54
|
+
// Server-side storage is not used (we use cookies/headers)
|
|
55
|
+
storageKey: void 0
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function withDemoCheck(handler, fallback) {
|
|
59
|
+
return async (context) => {
|
|
60
|
+
if (!isServerDemoMode()) {
|
|
61
|
+
if (fallback) {
|
|
62
|
+
return fallback();
|
|
63
|
+
}
|
|
64
|
+
throw new Error("[DemoKit] Demo mode is not enabled");
|
|
65
|
+
}
|
|
66
|
+
return handler(context);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var DEFAULT_CONFIG = {
|
|
70
|
+
cookieName: "demokit-mode",
|
|
71
|
+
urlParam: "demo",
|
|
72
|
+
headerName: "x-demokit-mode",
|
|
73
|
+
apiPaths: ["/api/"],
|
|
74
|
+
cookieOptions: {
|
|
75
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
76
|
+
// 1 week
|
|
77
|
+
path: "/",
|
|
78
|
+
sameSite: "lax",
|
|
79
|
+
secure: process.env.NODE_ENV === "production"
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function createDemoMiddleware(config = {}) {
|
|
83
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
84
|
+
const { cookieName, urlParam, headerName, apiPaths, cookieOptions } = mergedConfig;
|
|
85
|
+
return function demoMiddleware2(request) {
|
|
86
|
+
const demoParam = request.nextUrl.searchParams.get(urlParam);
|
|
87
|
+
const demoCookie = request.cookies.get(cookieName);
|
|
88
|
+
let response = NextResponse.next();
|
|
89
|
+
let isDemoMode = false;
|
|
90
|
+
let scenario = null;
|
|
91
|
+
if (demoParam !== null) {
|
|
92
|
+
if (demoParam === "false" || demoParam === "0" || demoParam === "") {
|
|
93
|
+
response = NextResponse.next();
|
|
94
|
+
response.cookies.delete(cookieName);
|
|
95
|
+
isDemoMode = false;
|
|
96
|
+
} else if (demoParam === "true" || demoParam === "1") {
|
|
97
|
+
response = NextResponse.next();
|
|
98
|
+
response.cookies.set(cookieName, "true", cookieOptions);
|
|
99
|
+
isDemoMode = true;
|
|
100
|
+
} else {
|
|
101
|
+
response = NextResponse.next();
|
|
102
|
+
response.cookies.set(cookieName, demoParam, cookieOptions);
|
|
103
|
+
isDemoMode = true;
|
|
104
|
+
scenario = demoParam;
|
|
105
|
+
}
|
|
106
|
+
} else if (demoCookie) {
|
|
107
|
+
isDemoMode = true;
|
|
108
|
+
scenario = demoCookie.value !== "true" ? demoCookie.value : null;
|
|
109
|
+
}
|
|
110
|
+
if (isDemoMode) {
|
|
111
|
+
const isApiRoute = apiPaths.some(
|
|
112
|
+
(path) => request.nextUrl.pathname.startsWith(path)
|
|
113
|
+
);
|
|
114
|
+
if (isApiRoute) {
|
|
115
|
+
const requestHeaders = new Headers(request.headers);
|
|
116
|
+
requestHeaders.set(headerName, scenario ?? "true");
|
|
117
|
+
response = NextResponse.next({
|
|
118
|
+
request: {
|
|
119
|
+
headers: requestHeaders
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
if (demoParam !== null) {
|
|
123
|
+
if (demoParam === "false" || demoParam === "0" || demoParam === "") {
|
|
124
|
+
response.cookies.delete(cookieName);
|
|
125
|
+
} else {
|
|
126
|
+
response.cookies.set(
|
|
127
|
+
cookieName,
|
|
128
|
+
demoParam === "true" || demoParam === "1" ? "true" : demoParam,
|
|
129
|
+
cookieOptions
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
isDemoMode,
|
|
137
|
+
scenario,
|
|
138
|
+
response
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function demoMiddleware(config = {}) {
|
|
143
|
+
const handler = createDemoMiddleware(config);
|
|
144
|
+
return function middleware(request) {
|
|
145
|
+
return handler(request).response;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function isDemoRequest(request, config = {}) {
|
|
149
|
+
const { cookieName = "demokit-mode", headerName = "x-demokit-mode" } = config;
|
|
150
|
+
if (request.headers.get(headerName)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
const cookie = request.cookies.get(cookieName);
|
|
154
|
+
return cookie !== void 0 && cookie.value !== "";
|
|
155
|
+
}
|
|
156
|
+
function getDemoScenario(request, config = {}) {
|
|
157
|
+
const { cookieName = "demokit-mode", headerName = "x-demokit-mode" } = config;
|
|
158
|
+
const headerValue = request.headers.get(headerName);
|
|
159
|
+
if (headerValue && headerValue !== "true") {
|
|
160
|
+
return headerValue;
|
|
161
|
+
}
|
|
162
|
+
const cookie = request.cookies.get(cookieName);
|
|
163
|
+
if (cookie && cookie.value !== "true") {
|
|
164
|
+
return cookie.value;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { createDemoMiddleware, createServerInterceptor, demoContextStorage, demoMiddleware, getDemoScenario, getServerDemoContext, getServerScenario, isDemoRequest, isServerDemoMode, runWithDemoContext, withDemoCheck };
|
|
170
|
+
//# sourceMappingURL=server.js.map
|
|
171
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/context.ts","../src/server/interceptor.ts","../src/server/middleware.ts"],"names":["demoMiddleware"],"mappings":"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","file":"server.js","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n\n/**\n * Server-side demo mode context\n */\nexport interface ServerDemoContext {\n /**\n * Whether demo mode is enabled\n */\n enabled: boolean\n\n /**\n * The current scenario name\n */\n scenario: string | null\n}\n\n/**\n * AsyncLocalStorage for server-side demo mode context\n * This allows Server Components to access demo mode state\n */\nexport const demoContextStorage = new AsyncLocalStorage<ServerDemoContext>()\n\n/**\n * Get the current server-side demo context\n * Returns null if not in a demo context\n */\nexport function getServerDemoContext(): ServerDemoContext | null {\n return demoContextStorage.getStore() ?? null\n}\n\n/**\n * Check if demo mode is enabled on the server\n */\nexport function isServerDemoMode(): boolean {\n const context = getServerDemoContext()\n return context?.enabled ?? false\n}\n\n/**\n * Get the current scenario on the server\n */\nexport function getServerScenario(): string | null {\n const context = getServerDemoContext()\n return context?.scenario ?? null\n}\n\n/**\n * Run a function within a demo context\n */\nexport function runWithDemoContext<T>(\n context: ServerDemoContext,\n fn: () => T\n): T {\n return demoContextStorage.run(context, fn)\n}\n","import {\n createDemoInterceptor,\n type FixtureMap,\n type DemoInterceptor,\n type RequestContext,\n} from '@demokit-ai/core'\nimport { isServerDemoMode, getServerScenario } from './context'\n\n/**\n * Configuration for server-side demo interceptor\n */\nexport interface ServerInterceptorConfig {\n /**\n * Base fixtures (always available)\n */\n fixtures: FixtureMap\n\n /**\n * Scenario-specific fixtures\n */\n scenarios?: Record<string, FixtureMap>\n\n /**\n * Base URL for relative URL parsing\n */\n baseUrl?: string\n}\n\n/**\n * Create a server-side demo interceptor\n *\n * This interceptor works with Server Components and API routes.\n * It uses AsyncLocalStorage to check demo mode state.\n *\n * @example\n * // lib/demo-interceptor.ts\n * import { createServerInterceptor } from '@demokit-ai/next/server'\n *\n * export const demoInterceptor = createServerInterceptor({\n * fixtures: {\n * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],\n * },\n * scenarios: {\n * 'empty': { 'GET /api/users': () => [] },\n * },\n * })\n *\n * // Enable in your app\n * demoInterceptor.enable()\n */\nexport function createServerInterceptor(config: ServerInterceptorConfig): DemoInterceptor {\n const { fixtures, scenarios = {}, baseUrl } = config\n\n // Merge fixtures based on current scenario\n const getActiveFixtures = (): FixtureMap => {\n const scenario = getServerScenario()\n if (scenario && scenarios[scenario]) {\n return { ...fixtures, ...scenarios[scenario] }\n }\n return fixtures\n }\n\n // Wrapper that checks server context before each request\n const wrappedFixtures: FixtureMap = new Proxy(fixtures, {\n get(target, prop: string) {\n const activeFixtures = getActiveFixtures()\n return activeFixtures[prop]\n },\n ownKeys() {\n const activeFixtures = getActiveFixtures()\n return Reflect.ownKeys(activeFixtures)\n },\n getOwnPropertyDescriptor(target, prop) {\n const activeFixtures = getActiveFixtures()\n if (prop in activeFixtures) {\n return {\n configurable: true,\n enumerable: true,\n value: activeFixtures[prop as string],\n }\n }\n return undefined\n },\n })\n\n return createDemoInterceptor({\n fixtures: wrappedFixtures,\n baseUrl,\n // Server-side storage is not used (we use cookies/headers)\n storageKey: undefined,\n })\n}\n\n/**\n * Helper to wrap a fixture handler with demo mode check\n */\nexport function withDemoCheck<T>(\n handler: (context: RequestContext) => T | Promise<T>,\n fallback?: () => T | Promise<T>\n): (context: RequestContext) => T | Promise<T> {\n return async (context) => {\n if (!isServerDemoMode()) {\n if (fallback) {\n return fallback()\n }\n throw new Error('[DemoKit] Demo mode is not enabled')\n }\n return handler(context)\n }\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { DemoMiddlewareConfig, MiddlewareResult } from '../types'\n\nconst DEFAULT_CONFIG: Required<DemoMiddlewareConfig> = {\n cookieName: 'demokit-mode',\n urlParam: 'demo',\n headerName: 'x-demokit-mode',\n apiPaths: ['/api/'],\n cookieOptions: {\n maxAge: 60 * 60 * 24 * 7, // 1 week\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n },\n}\n\n/**\n * Create a demo-aware middleware\n *\n * This middleware:\n * 1. Checks for demo mode URL parameter (?demo=true or ?demo=false)\n * 2. Sets/clears demo mode cookie\n * 3. Adds demo mode header to API requests\n * 4. Supports scenario switching via ?demo=scenario-name\n *\n * @example\n * // middleware.ts\n * import { createDemoMiddleware } from '@demokit-ai/next/middleware'\n *\n * const demoMiddleware = createDemoMiddleware()\n *\n * export function middleware(request: NextRequest) {\n * return demoMiddleware(request)\n * }\n *\n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * }\n */\nexport function createDemoMiddleware(config: DemoMiddlewareConfig = {}) {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config }\n const { cookieName, urlParam, headerName, apiPaths, cookieOptions } = mergedConfig\n\n return function demoMiddleware(request: NextRequest): MiddlewareResult {\n // Check for demo mode in URL param\n const demoParam = request.nextUrl.searchParams.get(urlParam)\n\n // Check for existing cookie\n const demoCookie = request.cookies.get(cookieName)\n\n let response = NextResponse.next()\n let isDemoMode = false\n let scenario: string | null = null\n\n // Handle URL param toggle\n if (demoParam !== null) {\n if (demoParam === 'false' || demoParam === '0' || demoParam === '') {\n // Disable demo mode\n response = NextResponse.next()\n response.cookies.delete(cookieName)\n isDemoMode = false\n } else if (demoParam === 'true' || demoParam === '1') {\n // Enable demo mode (no scenario)\n response = NextResponse.next()\n response.cookies.set(cookieName, 'true', cookieOptions)\n isDemoMode = true\n } else {\n // Enable with specific scenario\n response = NextResponse.next()\n response.cookies.set(cookieName, demoParam, cookieOptions)\n isDemoMode = true\n scenario = demoParam\n }\n } else if (demoCookie) {\n // Use existing cookie\n isDemoMode = true\n scenario = demoCookie.value !== 'true' ? demoCookie.value : null\n }\n\n // Add header for API routes\n if (isDemoMode) {\n const isApiRoute = apiPaths.some((path) =>\n request.nextUrl.pathname.startsWith(path)\n )\n\n if (isApiRoute) {\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(headerName, scenario ?? 'true')\n\n response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n })\n\n // Re-set cookie if we created new response\n if (demoParam !== null) {\n if (demoParam === 'false' || demoParam === '0' || demoParam === '') {\n response.cookies.delete(cookieName)\n } else {\n response.cookies.set(\n cookieName,\n demoParam === 'true' || demoParam === '1' ? 'true' : demoParam,\n cookieOptions\n )\n }\n }\n }\n }\n\n return {\n isDemoMode,\n scenario,\n response,\n }\n }\n}\n\n/**\n * Simple middleware wrapper that just returns the response\n *\n * @example\n * import { demoMiddleware } from '@demokit-ai/next/middleware'\n * export const middleware = demoMiddleware()\n */\nexport function demoMiddleware(config: DemoMiddlewareConfig = {}) {\n const handler = createDemoMiddleware(config)\n\n return function middleware(request: NextRequest): NextResponse {\n return handler(request).response\n }\n}\n\n/**\n * Check if a request is in demo mode (from middleware or direct header check)\n */\nexport function isDemoRequest(request: NextRequest, config: DemoMiddlewareConfig = {}): boolean {\n const { cookieName = 'demokit-mode', headerName = 'x-demokit-mode' } = config\n\n // Check header first (set by middleware)\n if (request.headers.get(headerName)) {\n return true\n }\n\n // Check cookie\n const cookie = request.cookies.get(cookieName)\n return cookie !== undefined && cookie.value !== ''\n}\n\n/**\n * Get the demo scenario from a request\n */\nexport function getDemoScenario(request: NextRequest, config: DemoMiddlewareConfig = {}): string | null {\n const { cookieName = 'demokit-mode', headerName = 'x-demokit-mode' } = config\n\n // Check header first\n const headerValue = request.headers.get(headerName)\n if (headerValue && headerValue !== 'true') {\n return headerValue\n }\n\n // Check cookie\n const cookie = request.cookies.get(cookieName)\n if (cookie && cookie.value !== 'true') {\n return cookie.value\n }\n\n return null\n}\n"]}
|