@demokit-ai/next 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/chunk-YLBNVWMI.js +109 -0
- package/dist/chunk-YLBNVWMI.js.map +1 -0
- package/dist/client.cjs +161 -55
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +8 -3
- package/dist/client.d.ts +8 -3
- package/dist/client.js +115 -20
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +47 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +40 -13
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +11 -104
- package/dist/middleware.js.map +1 -1
- package/dist/server.cjs +62 -24
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +24 -104
- package/dist/server.js.map +1 -1
- package/dist/{types-Cvq5JUP1.d.cts → types-Brt8EaFz.d.cts} +38 -1
- package/dist/{types-Cvq5JUP1.d.ts → types-Brt8EaFz.d.ts} +38 -1
- package/package.json +23 -36
- package/README.md +0 -193
package/dist/server.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
createDemoMiddleware,
|
|
3
|
+
demoMiddleware,
|
|
4
|
+
getDemoScenario,
|
|
5
|
+
isDemoRequest
|
|
6
|
+
} from "./chunk-YLBNVWMI.js";
|
|
4
7
|
|
|
5
8
|
// src/server/context.ts
|
|
9
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
10
|
var demoContextStorage = new AsyncLocalStorage();
|
|
7
11
|
function getServerDemoContext() {
|
|
8
12
|
return demoContextStorage.getStore() ?? null;
|
|
@@ -18,6 +22,11 @@ function getServerScenario() {
|
|
|
18
22
|
function runWithDemoContext(context, fn) {
|
|
19
23
|
return demoContextStorage.run(context, fn);
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
// src/server/interceptor.ts
|
|
27
|
+
import {
|
|
28
|
+
createDemoInterceptor
|
|
29
|
+
} from "@demokit-ai/core";
|
|
21
30
|
function createServerInterceptor(config) {
|
|
22
31
|
const { fixtures, scenarios = {}, baseUrl } = config;
|
|
23
32
|
const getActiveFixtures = () => {
|
|
@@ -66,106 +75,17 @@ function withDemoCheck(handler, fallback) {
|
|
|
66
75
|
return handler(context);
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
export {
|
|
79
|
+
createDemoMiddleware,
|
|
80
|
+
createServerInterceptor,
|
|
81
|
+
demoContextStorage,
|
|
82
|
+
demoMiddleware,
|
|
83
|
+
getDemoScenario,
|
|
84
|
+
getServerDemoContext,
|
|
85
|
+
getServerScenario,
|
|
86
|
+
isDemoRequest,
|
|
87
|
+
isServerDemoMode,
|
|
88
|
+
runWithDemoContext,
|
|
89
|
+
withDemoCheck
|
|
81
90
|
};
|
|
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
91
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/context.ts","../src/server/interceptor.ts"],"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"],"mappings":";;;;;;;;AAAA,SAAS,yBAAyB;AAqB3B,IAAM,qBAAqB,IAAI,kBAAqC;AAMpE,SAAS,uBAAiD;AAC/D,SAAO,mBAAmB,SAAS,KAAK;AAC1C;AAKO,SAAS,mBAA4B;AAC1C,QAAM,UAAU,qBAAqB;AACrC,SAAO,SAAS,WAAW;AAC7B;AAKO,SAAS,oBAAmC;AACjD,QAAM,UAAU,qBAAqB;AACrC,SAAO,SAAS,YAAY;AAC9B;AAKO,SAAS,mBACd,SACA,IACG;AACH,SAAO,mBAAmB,IAAI,SAAS,EAAE;AAC3C;;;ACvDA;AAAA,EACE;AAAA,OAIK;AA6CA,SAAS,wBAAwB,QAAkD;AACxF,QAAM,EAAE,UAAU,YAAY,CAAC,GAAG,QAAQ,IAAI;AAG9C,QAAM,oBAAoB,MAAkB;AAC1C,UAAM,WAAW,kBAAkB;AACnC,QAAI,YAAY,UAAU,QAAQ,GAAG;AACnC,aAAO,EAAE,GAAG,UAAU,GAAG,UAAU,QAAQ,EAAE;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAGA,QAAM,kBAA8B,IAAI,MAAM,UAAU;AAAA,IACtD,IAAI,QAAQ,MAAc;AACxB,YAAM,iBAAiB,kBAAkB;AACzC,aAAO,eAAe,IAAI;AAAA,IAC5B;AAAA,IACA,UAAU;AACR,YAAM,iBAAiB,kBAAkB;AACzC,aAAO,QAAQ,QAAQ,cAAc;AAAA,IACvC;AAAA,IACA,yBAAyB,QAAQ,MAAM;AACrC,YAAM,iBAAiB,kBAAkB;AACzC,UAAI,QAAQ,gBAAgB;AAC1B,eAAO;AAAA,UACL,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,OAAO,eAAe,IAAc;AAAA,QACtC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,SAAO,sBAAsB;AAAA,IAC3B,UAAU;AAAA,IACV;AAAA;AAAA,IAEA,YAAY;AAAA,EACd,CAAC;AACH;AAKO,SAAS,cACd,SACA,UAC6C;AAC7C,SAAO,OAAO,YAAY;AACxB,QAAI,CAAC,iBAAiB,GAAG;AACvB,UAAI,UAAU;AACZ,eAAO,SAAS;AAAA,MAClB;AACA,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,WAAO,QAAQ,OAAO;AAAA,EACxB;AACF;","names":[]}
|
|
@@ -48,6 +48,11 @@ interface DemoKitNextProviderProps extends DemoKitNextConfig {
|
|
|
48
48
|
* If not provided, will read from cookie/localStorage
|
|
49
49
|
*/
|
|
50
50
|
initialEnabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Remote source configuration for fetching fixtures from DemoKit Cloud
|
|
53
|
+
* Create using createRemoteSource()
|
|
54
|
+
*/
|
|
55
|
+
source?: RemoteSourceConfig;
|
|
51
56
|
}
|
|
52
57
|
/**
|
|
53
58
|
* Configuration for createDemoMiddleware
|
|
@@ -125,5 +130,37 @@ type DefineFixtures<T extends Record<string, FixtureHandler>> = T;
|
|
|
125
130
|
* Helper type for defining scenarios with type safety
|
|
126
131
|
*/
|
|
127
132
|
type DefineScenarios<T extends Record<string, FixtureMap>> = T;
|
|
133
|
+
/**
|
|
134
|
+
* Configuration for remote fixture source (DemoKit Cloud)
|
|
135
|
+
*/
|
|
136
|
+
interface RemoteSourceConfig {
|
|
137
|
+
/**
|
|
138
|
+
* DemoKit Cloud API URL (versioned base URL)
|
|
139
|
+
* The SDK will append `/fixtures` to this URL.
|
|
140
|
+
* @example 'https://demokit-cloud.kasava.dev/api'
|
|
141
|
+
* @default 'https://api.demokit.cloud/api'
|
|
142
|
+
*/
|
|
143
|
+
apiUrl: string;
|
|
144
|
+
/**
|
|
145
|
+
* DemoKit Cloud API key
|
|
146
|
+
* Format: dk_live_xxx
|
|
147
|
+
*/
|
|
148
|
+
apiKey: string;
|
|
149
|
+
/**
|
|
150
|
+
* Request timeout in milliseconds
|
|
151
|
+
* @default 10000
|
|
152
|
+
*/
|
|
153
|
+
timeout?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Whether to retry failed requests
|
|
156
|
+
* @default true
|
|
157
|
+
*/
|
|
158
|
+
retry?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Maximum number of retries
|
|
161
|
+
* @default 3
|
|
162
|
+
*/
|
|
163
|
+
maxRetries?: number;
|
|
164
|
+
}
|
|
128
165
|
|
|
129
|
-
export type { DemoScenario as D, MiddlewareResult as M, DemoKitNextConfig as a, DemoKitNextProviderProps as b, DemoMiddlewareConfig as c, DefineFixtures as d, DefineScenarios as e };
|
|
166
|
+
export type { DemoScenario as D, MiddlewareResult as M, RemoteSourceConfig as R, DemoKitNextConfig as a, DemoKitNextProviderProps as b, DemoMiddlewareConfig as c, DefineFixtures as d, DefineScenarios as e };
|
|
@@ -48,6 +48,11 @@ interface DemoKitNextProviderProps extends DemoKitNextConfig {
|
|
|
48
48
|
* If not provided, will read from cookie/localStorage
|
|
49
49
|
*/
|
|
50
50
|
initialEnabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Remote source configuration for fetching fixtures from DemoKit Cloud
|
|
53
|
+
* Create using createRemoteSource()
|
|
54
|
+
*/
|
|
55
|
+
source?: RemoteSourceConfig;
|
|
51
56
|
}
|
|
52
57
|
/**
|
|
53
58
|
* Configuration for createDemoMiddleware
|
|
@@ -125,5 +130,37 @@ type DefineFixtures<T extends Record<string, FixtureHandler>> = T;
|
|
|
125
130
|
* Helper type for defining scenarios with type safety
|
|
126
131
|
*/
|
|
127
132
|
type DefineScenarios<T extends Record<string, FixtureMap>> = T;
|
|
133
|
+
/**
|
|
134
|
+
* Configuration for remote fixture source (DemoKit Cloud)
|
|
135
|
+
*/
|
|
136
|
+
interface RemoteSourceConfig {
|
|
137
|
+
/**
|
|
138
|
+
* DemoKit Cloud API URL (versioned base URL)
|
|
139
|
+
* The SDK will append `/fixtures` to this URL.
|
|
140
|
+
* @example 'https://demokit-cloud.kasava.dev/api'
|
|
141
|
+
* @default 'https://api.demokit.cloud/api'
|
|
142
|
+
*/
|
|
143
|
+
apiUrl: string;
|
|
144
|
+
/**
|
|
145
|
+
* DemoKit Cloud API key
|
|
146
|
+
* Format: dk_live_xxx
|
|
147
|
+
*/
|
|
148
|
+
apiKey: string;
|
|
149
|
+
/**
|
|
150
|
+
* Request timeout in milliseconds
|
|
151
|
+
* @default 10000
|
|
152
|
+
*/
|
|
153
|
+
timeout?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Whether to retry failed requests
|
|
156
|
+
* @default true
|
|
157
|
+
*/
|
|
158
|
+
retry?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Maximum number of retries
|
|
161
|
+
* @default 3
|
|
162
|
+
*/
|
|
163
|
+
maxRetries?: number;
|
|
164
|
+
}
|
|
128
165
|
|
|
129
|
-
export type { DemoScenario as D, MiddlewareResult as M, DemoKitNextConfig as a, DemoKitNextProviderProps as b, DemoMiddlewareConfig as c, DefineFixtures as d, DefineScenarios as e };
|
|
166
|
+
export type { DemoScenario as D, MiddlewareResult as M, RemoteSourceConfig as R, DemoKitNextConfig as a, DemoKitNextProviderProps as b, DemoMiddlewareConfig as c, DefineFixtures as d, DefineScenarios as e };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demokit-ai/next",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Next.js adapter for DemoKit -
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Next.js adapter for DemoKit - SSR support and middleware",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -51,44 +51,31 @@
|
|
|
51
51
|
"files": [
|
|
52
52
|
"dist"
|
|
53
53
|
],
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "tsup",
|
|
56
|
-
"dev": "tsup --watch",
|
|
57
|
-
"test": "vitest",
|
|
58
|
-
"typecheck": "tsc --noEmit"
|
|
59
|
-
},
|
|
60
54
|
"dependencies": {
|
|
61
|
-
"@demokit-ai/
|
|
62
|
-
"@demokit-ai/
|
|
55
|
+
"@demokit-ai/react": "0.4.0",
|
|
56
|
+
"@demokit-ai/core": "0.4.0"
|
|
63
57
|
},
|
|
64
58
|
"devDependencies": {
|
|
65
|
-
"@types/
|
|
66
|
-
"@types/react
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"tsup": "^8.3.5",
|
|
71
|
-
"typescript": "^5.7.2",
|
|
72
|
-
"vitest": "^2.1.8"
|
|
59
|
+
"@types/node": "^22.0.0",
|
|
60
|
+
"@types/react": "^19.2.14",
|
|
61
|
+
"@types/react-dom": "^19.2.3",
|
|
62
|
+
"tsup": "^8.5.1",
|
|
63
|
+
"typescript": "^6.0.2"
|
|
73
64
|
},
|
|
74
65
|
"peerDependencies": {
|
|
75
|
-
"next": ">=
|
|
76
|
-
"react": ">=
|
|
77
|
-
"react-dom": ">=
|
|
66
|
+
"next": ">=15.0.0",
|
|
67
|
+
"react": ">=18.0.0",
|
|
68
|
+
"react-dom": ">=18.0.0"
|
|
78
69
|
},
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"mock",
|
|
82
|
-
"next.js",
|
|
83
|
-
"app-router",
|
|
84
|
-
"middleware",
|
|
85
|
-
"fixtures"
|
|
86
|
-
],
|
|
87
|
-
"license": "MIT",
|
|
88
|
-
"repository": {
|
|
89
|
-
"type": "git",
|
|
90
|
-
"url": "https://github.com/your-org/demokit",
|
|
91
|
-
"directory": "packages/next"
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=18"
|
|
92
72
|
},
|
|
93
|
-
"
|
|
94
|
-
|
|
73
|
+
"license": "Apache-2.0",
|
|
74
|
+
"sideEffects": false,
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsup",
|
|
77
|
+
"dev": "tsup --watch --no-dts",
|
|
78
|
+
"typecheck": "tsc --noEmit",
|
|
79
|
+
"clean": "rm -rf dist"
|
|
80
|
+
}
|
|
81
|
+
}
|
package/README.md
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# @demokit-ai/next
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
Next.js adapter for DemoKit - App Router, middleware, and API route mocking for demo mode.
|
|
7
|
-
|
|
8
|
-
## Installation
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install @demokit-ai/next @demokit-ai/core @demokit-ai/react
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Features
|
|
15
|
-
|
|
16
|
-
- Demo-aware middleware for URL parameter and cookie handling
|
|
17
|
-
- Server-side demo context with AsyncLocalStorage
|
|
18
|
-
- Client-side hooks for demo mode state
|
|
19
|
-
- API route interceptors for fixture responses
|
|
20
|
-
- Scenario-based demo configurations
|
|
21
|
-
- Full TypeScript support
|
|
22
|
-
|
|
23
|
-
## Usage
|
|
24
|
-
|
|
25
|
-
### Middleware Setup
|
|
26
|
-
|
|
27
|
-
Add demo mode detection to your middleware:
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
// middleware.ts
|
|
31
|
-
import { createDemoMiddleware } from '@demokit-ai/next/middleware'
|
|
32
|
-
|
|
33
|
-
export const middleware = createDemoMiddleware({
|
|
34
|
-
cookieName: 'demo-mode',
|
|
35
|
-
paramName: 'demo',
|
|
36
|
-
scenarios: {
|
|
37
|
-
default: { users: 'standard' },
|
|
38
|
-
enterprise: { users: 'enterprise', features: 'all' },
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export const config = {
|
|
43
|
-
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Server Components
|
|
48
|
-
|
|
49
|
-
Access demo mode in Server Components:
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
// app/users/page.tsx
|
|
53
|
-
import { getServerDemoContext, isServerDemoMode } from '@demokit-ai/next/server'
|
|
54
|
-
|
|
55
|
-
export default async function UsersPage() {
|
|
56
|
-
const isDemo = isServerDemoMode()
|
|
57
|
-
const context = getServerDemoContext()
|
|
58
|
-
|
|
59
|
-
if (isDemo) {
|
|
60
|
-
return <UserList users={context.fixtures.users} />
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const users = await fetchRealUsers()
|
|
64
|
-
return <UserList users={users} />
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Client Components
|
|
69
|
-
|
|
70
|
-
Use the client-side hook:
|
|
71
|
-
|
|
72
|
-
```tsx
|
|
73
|
-
'use client'
|
|
74
|
-
import { useNextDemoMode } from '@demokit-ai/next/client'
|
|
75
|
-
|
|
76
|
-
export function DemoToggle() {
|
|
77
|
-
const { enabled, toggle, scenario, setScenario } = useNextDemoMode()
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<div>
|
|
81
|
-
<button onClick={toggle}>
|
|
82
|
-
Demo: {enabled ? 'ON' : 'OFF'}
|
|
83
|
-
</button>
|
|
84
|
-
{enabled && (
|
|
85
|
-
<select value={scenario} onChange={(e) => setScenario(e.target.value)}>
|
|
86
|
-
<option value="default">Default</option>
|
|
87
|
-
<option value="enterprise">Enterprise</option>
|
|
88
|
-
</select>
|
|
89
|
-
)}
|
|
90
|
-
</div>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### API Route Interception
|
|
96
|
-
|
|
97
|
-
Create demo-aware API routes:
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
// app/api/users/route.ts
|
|
101
|
-
import { createServerInterceptor } from '@demokit-ai/next/server'
|
|
102
|
-
|
|
103
|
-
const interceptor = createServerInterceptor({
|
|
104
|
-
fixtures: {
|
|
105
|
-
GET: [
|
|
106
|
-
{ id: '1', name: 'Demo User' },
|
|
107
|
-
{ id: '2', name: 'Another User' },
|
|
108
|
-
],
|
|
109
|
-
POST: (request) => ({
|
|
110
|
-
id: '3',
|
|
111
|
-
name: request.body.name,
|
|
112
|
-
created: true,
|
|
113
|
-
}),
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
export async function GET(request: Request) {
|
|
118
|
-
return interceptor(request, async () => {
|
|
119
|
-
// Real implementation
|
|
120
|
-
const users = await db.users.findMany()
|
|
121
|
-
return Response.json(users)
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Configuration
|
|
127
|
-
|
|
128
|
-
Define demo scenarios and fixtures:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
// demo.config.ts
|
|
132
|
-
import { createDemoConfig, defineFixtures, defineScenarios } from '@demokit-ai/next'
|
|
133
|
-
|
|
134
|
-
export const demoConfig = createDemoConfig({
|
|
135
|
-
fixtures: defineFixtures({
|
|
136
|
-
users: [
|
|
137
|
-
{ id: '1', name: 'Demo User', plan: 'free' },
|
|
138
|
-
{ id: '2', name: 'Pro User', plan: 'pro' },
|
|
139
|
-
],
|
|
140
|
-
products: [
|
|
141
|
-
{ id: '1', name: 'Widget', price: 9.99 },
|
|
142
|
-
],
|
|
143
|
-
}),
|
|
144
|
-
scenarios: defineScenarios({
|
|
145
|
-
default: {},
|
|
146
|
-
enterprise: {
|
|
147
|
-
users: [
|
|
148
|
-
{ id: '1', name: 'Enterprise User', plan: 'enterprise' },
|
|
149
|
-
],
|
|
150
|
-
},
|
|
151
|
-
}),
|
|
152
|
-
})
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## API Reference
|
|
156
|
-
|
|
157
|
-
### Middleware
|
|
158
|
-
|
|
159
|
-
#### `createDemoMiddleware(options)`
|
|
160
|
-
|
|
161
|
-
Options:
|
|
162
|
-
- `cookieName` - Cookie name for demo state (default: 'demo-mode')
|
|
163
|
-
- `paramName` - URL parameter for demo activation (default: 'demo')
|
|
164
|
-
- `scenarios` - Scenario configurations
|
|
165
|
-
- `defaultScenario` - Default scenario name
|
|
166
|
-
|
|
167
|
-
### Server
|
|
168
|
-
|
|
169
|
-
#### `isServerDemoMode()`
|
|
170
|
-
|
|
171
|
-
Returns `true` if demo mode is active in the current request.
|
|
172
|
-
|
|
173
|
-
#### `getServerDemoContext()`
|
|
174
|
-
|
|
175
|
-
Returns the demo context including fixtures and scenario.
|
|
176
|
-
|
|
177
|
-
#### `createServerInterceptor(options)`
|
|
178
|
-
|
|
179
|
-
Creates an interceptor for API routes.
|
|
180
|
-
|
|
181
|
-
### Client
|
|
182
|
-
|
|
183
|
-
#### `useNextDemoMode()`
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
- `enabled` - Demo mode state
|
|
187
|
-
- `toggle()` - Toggle demo mode
|
|
188
|
-
- `scenario` - Current scenario
|
|
189
|
-
- `setScenario(name)` - Switch scenario
|
|
190
|
-
|
|
191
|
-
## License
|
|
192
|
-
|
|
193
|
-
MIT
|