@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/dist/server.js CHANGED
@@ -1,8 +1,12 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
2
- import { createDemoInterceptor } from '@demokit-ai/core';
3
- import { NextResponse } from 'next/server';
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
- 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
- }
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
@@ -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.2.0",
4
- "description": "Next.js adapter for DemoKit - App Router, middleware, and API route mocking",
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/core": "*",
62
- "@demokit-ai/react": "*"
55
+ "@demokit-ai/react": "0.4.0",
56
+ "@demokit-ai/core": "0.4.0"
63
57
  },
64
58
  "devDependencies": {
65
- "@types/react": "^19.0.0",
66
- "@types/react-dom": "^19.0.0",
67
- "next": "^15.1.0",
68
- "react": "^19.0.0",
69
- "react-dom": "^19.0.0",
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": ">=13.0.0",
76
- "react": ">=17.0.0",
77
- "react-dom": ">=17.0.0"
66
+ "next": ">=15.0.0",
67
+ "react": ">=18.0.0",
68
+ "react-dom": ">=18.0.0"
78
69
  },
79
- "keywords": [
80
- "demo",
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
- "sideEffects": false
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
- ![Tests](https://img.shields.io/badge/tests-130%20passing-brightgreen)
4
- ![Coverage](https://img.shields.io/badge/coverage-7%25-red)
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