@demokit-ai/remix 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/fixtures-BP3SzTkj.d.cts +355 -0
- package/dist/fixtures-BP3SzTkj.d.ts +355 -0
- package/dist/index.cjs +385 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +195 -0
- package/dist/index.d.ts +195 -0
- package/dist/index.js +346 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +307 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +162 -0
- package/dist/server.d.ts +162 -0
- package/dist/server.js +268 -0
- package/dist/server.js.map +1 -0
- package/package.json +78 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
|
|
2
|
+
import { C as CreateDemoLoaderOptions, a as CreateDemoActionOptions, D as DemoRemixProviderProps, b as DemoRemixState, L as LoaderFixtureHandler, A as ActionFixtureHandler } from './fixtures-BP3SzTkj.js';
|
|
3
|
+
export { h as ActionFixtureContext, k as ActionFixtureMap, l as ActionFixtureMapObject, o as DemoModeOptions, n as DemoRemixProviderConfig, m as DemoRouteOptions, F as FixtureStore, p as FixtureStoreConfig, g as LoaderFixtureContext, i as LoaderFixtureMap, j as LoaderFixtureMapObject, M as MethodActionHandlers, R as RemixResponse, c as createFixtureStore, e as defineRemixActionFixtures, f as defineRemixFixtures, d as defineRemixLoaderFixtures } from './fixtures-BP3SzTkj.js';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
import '@demokit-ai/core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a demo-aware loader function for Remix
|
|
9
|
+
*
|
|
10
|
+
* Returns a loader that intercepts requests when demo mode is enabled
|
|
11
|
+
* and returns fixture data instead of calling the real loader.
|
|
12
|
+
* Demo mode is checked server-side via cookies, headers, or request context.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Basic usage with inline fixture
|
|
16
|
+
* export const loader = createDemoLoader({
|
|
17
|
+
* loader: async ({ params }) => {
|
|
18
|
+
* const user = await db.users.findUnique({ where: { id: params.id } })
|
|
19
|
+
* return json({ user })
|
|
20
|
+
* },
|
|
21
|
+
* isEnabled: (request) => request.headers.get('cookie')?.includes('demo=true') ?? false,
|
|
22
|
+
* fixture: ({ params }) => ({
|
|
23
|
+
* user: { id: params.id, name: 'Demo User', email: 'demo@example.com' }
|
|
24
|
+
* }),
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // With cookie-based detection
|
|
29
|
+
* import { isDemoMode } from '@demokit-ai/remix/server'
|
|
30
|
+
*
|
|
31
|
+
* export const loader = createDemoLoader({
|
|
32
|
+
* loader: realLoader,
|
|
33
|
+
* isEnabled: (request) => isDemoMode(request),
|
|
34
|
+
* fixture: demoData,
|
|
35
|
+
* delay: 200, // Simulate 200ms network latency
|
|
36
|
+
* })
|
|
37
|
+
*/
|
|
38
|
+
declare function createDemoLoader<T = unknown>(options: CreateDemoLoaderOptions<T>): (args: LoaderFunctionArgs) => Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Create a demo-aware action function for Remix
|
|
41
|
+
*
|
|
42
|
+
* Returns an action that intercepts requests when demo mode is enabled
|
|
43
|
+
* and returns fixture data instead of calling the real action.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Basic usage with inline fixture
|
|
47
|
+
* export const action = createDemoAction({
|
|
48
|
+
* action: async ({ request }) => {
|
|
49
|
+
* const formData = await request.formData()
|
|
50
|
+
* const user = await db.users.create({ data: Object.fromEntries(formData) })
|
|
51
|
+
* return json({ user })
|
|
52
|
+
* },
|
|
53
|
+
* isEnabled: (request) => request.headers.get('cookie')?.includes('demo=true') ?? false,
|
|
54
|
+
* fixture: async ({ formData }) => ({
|
|
55
|
+
* user: { id: crypto.randomUUID(), ...Object.fromEntries(formData!) }
|
|
56
|
+
* }),
|
|
57
|
+
* })
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // With method-specific fixtures
|
|
61
|
+
* export const action = createDemoAction({
|
|
62
|
+
* action: realAction,
|
|
63
|
+
* isEnabled: isDemoMode,
|
|
64
|
+
* fixture: {
|
|
65
|
+
* POST: ({ formData }) => json({ created: true, id: crypto.randomUUID() }),
|
|
66
|
+
* DELETE: ({ params }) => json({ deleted: true, id: params.id }),
|
|
67
|
+
* },
|
|
68
|
+
* })
|
|
69
|
+
*/
|
|
70
|
+
declare function createDemoAction<T = unknown>(options: CreateDemoActionOptions<T>): (args: ActionFunctionArgs) => Promise<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Helper to create a demo loader with context from request
|
|
73
|
+
*
|
|
74
|
+
* Simplified version that uses request-based demo mode detection.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* import { withDemoLoader, isDemoMode } from '@demokit-ai/remix'
|
|
78
|
+
*
|
|
79
|
+
* export const loader = withDemoLoader(
|
|
80
|
+
* async ({ params }) => {
|
|
81
|
+
* return json(await db.users.findUnique({ where: { id: params.id } }))
|
|
82
|
+
* },
|
|
83
|
+
* {
|
|
84
|
+
* isEnabled: isDemoMode,
|
|
85
|
+
* fixture: ({ params }) => ({ id: params.id, name: 'Demo User' }),
|
|
86
|
+
* }
|
|
87
|
+
* )
|
|
88
|
+
*/
|
|
89
|
+
declare function withDemoLoader<T = unknown>(loader: (args: LoaderFunctionArgs) => T | Promise<T>, options: Omit<CreateDemoLoaderOptions<T>, 'loader'>): (args: LoaderFunctionArgs) => Promise<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Helper to create a demo action with context from request
|
|
92
|
+
*
|
|
93
|
+
* Simplified version that uses request-based demo mode detection.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* import { withDemoAction, isDemoMode } from '@demokit-ai/remix'
|
|
97
|
+
*
|
|
98
|
+
* export const action = withDemoAction(
|
|
99
|
+
* async ({ request }) => {
|
|
100
|
+
* const formData = await request.formData()
|
|
101
|
+
* return json(await db.users.create({ data: Object.fromEntries(formData) }))
|
|
102
|
+
* },
|
|
103
|
+
* {
|
|
104
|
+
* isEnabled: isDemoMode,
|
|
105
|
+
* fixture: ({ formData }) => ({ id: crypto.randomUUID(), name: formData?.get('name') }),
|
|
106
|
+
* }
|
|
107
|
+
* )
|
|
108
|
+
*/
|
|
109
|
+
declare function withDemoAction<T = unknown>(action: (args: ActionFunctionArgs) => T | Promise<T>, options: Omit<CreateDemoActionOptions<T>, 'action'>): (args: ActionFunctionArgs) => Promise<T>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Context value for demo remix state
|
|
113
|
+
*/
|
|
114
|
+
interface DemoRemixContextValue extends DemoRemixState {
|
|
115
|
+
/** Enable demo mode */
|
|
116
|
+
enable: () => void;
|
|
117
|
+
/** Disable demo mode */
|
|
118
|
+
disable: () => void;
|
|
119
|
+
/** Toggle demo mode */
|
|
120
|
+
toggle: () => boolean;
|
|
121
|
+
/** Set a loader fixture */
|
|
122
|
+
setLoader: (path: string, handler: LoaderFixtureHandler) => void;
|
|
123
|
+
/** Remove a loader fixture */
|
|
124
|
+
removeLoader: (path: string) => void;
|
|
125
|
+
/** Set an action fixture */
|
|
126
|
+
setAction: (path: string, handler: ActionFixtureHandler) => void;
|
|
127
|
+
/** Remove an action fixture */
|
|
128
|
+
removeAction: (path: string) => void;
|
|
129
|
+
/** Clear all fixtures */
|
|
130
|
+
clearFixtures: () => void;
|
|
131
|
+
/** Get delay setting */
|
|
132
|
+
delay: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Provider for demo remix state (client-side)
|
|
136
|
+
*
|
|
137
|
+
* Wrap your Remix app with this provider to enable client-side fixture management
|
|
138
|
+
* and demo mode toggle. For server-side demo mode detection, use the server utilities.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* import { DemoRemixProvider } from '@demokit-ai/remix'
|
|
142
|
+
*
|
|
143
|
+
* const loaders = {
|
|
144
|
+
* '/users': [{ id: '1', name: 'Demo User' }],
|
|
145
|
+
* '/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
|
|
146
|
+
* }
|
|
147
|
+
*
|
|
148
|
+
* const actions = {
|
|
149
|
+
* '/users': ({ formData }) => ({
|
|
150
|
+
* id: crypto.randomUUID(),
|
|
151
|
+
* name: formData?.get('name'),
|
|
152
|
+
* }),
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* export default function App() {
|
|
156
|
+
* return (
|
|
157
|
+
* <DemoRemixProvider loaders={loaders} actions={actions} enabled>
|
|
158
|
+
* <Outlet />
|
|
159
|
+
* </DemoRemixProvider>
|
|
160
|
+
* )
|
|
161
|
+
* }
|
|
162
|
+
*/
|
|
163
|
+
declare function DemoRemixProvider({ children, enabled: initialEnabled, loaders: initialLoaders, actions: initialActions, delay, }: DemoRemixProviderProps): react_jsx_runtime.JSX.Element;
|
|
164
|
+
/**
|
|
165
|
+
* Hook to access demo remix state and controls
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* function DemoModeToggle() {
|
|
169
|
+
* const { enabled, toggle } = useDemoRemix()
|
|
170
|
+
* return (
|
|
171
|
+
* <button onClick={toggle}>
|
|
172
|
+
* Demo Mode: {enabled ? 'ON' : 'OFF'}
|
|
173
|
+
* </button>
|
|
174
|
+
* )
|
|
175
|
+
* }
|
|
176
|
+
*/
|
|
177
|
+
declare function useDemoRemix(): DemoRemixContextValue;
|
|
178
|
+
/**
|
|
179
|
+
* Hook to check if demo mode is enabled
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* function DataDisplay() {
|
|
183
|
+
* const isDemo = useIsDemoRemixMode()
|
|
184
|
+
* return isDemo ? <DemoBanner /> : null
|
|
185
|
+
* }
|
|
186
|
+
*/
|
|
187
|
+
declare function useIsDemoRemixMode(): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Get the demo remix context (may be null if not in provider)
|
|
190
|
+
*
|
|
191
|
+
* Use this for optional demo mode integration.
|
|
192
|
+
*/
|
|
193
|
+
declare function useDemoRemixOptional(): DemoRemixContextValue | null;
|
|
194
|
+
|
|
195
|
+
export { ActionFixtureHandler, CreateDemoActionOptions, CreateDemoLoaderOptions, DemoRemixProvider, DemoRemixProviderProps, DemoRemixState, LoaderFixtureHandler, createDemoAction, createDemoLoader, useDemoRemix, useDemoRemixOptional, useIsDemoRemixMode, withDemoAction, withDemoLoader };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// src/wrappers.ts
|
|
2
|
+
function createDemoLoader(options) {
|
|
3
|
+
const { loader, isEnabled = () => false, fixture, delay = 0, onDemo } = options;
|
|
4
|
+
return async (args) => {
|
|
5
|
+
const enabled = await isEnabled(args.request);
|
|
6
|
+
if (!enabled) {
|
|
7
|
+
return loader(args);
|
|
8
|
+
}
|
|
9
|
+
if (fixture === void 0) {
|
|
10
|
+
return loader(args);
|
|
11
|
+
}
|
|
12
|
+
const context = {
|
|
13
|
+
params: args.params,
|
|
14
|
+
request: args.request,
|
|
15
|
+
path: new URL(args.request.url).pathname
|
|
16
|
+
};
|
|
17
|
+
onDemo?.(context);
|
|
18
|
+
if (delay > 0) {
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
20
|
+
}
|
|
21
|
+
return executeFixture(fixture, context);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createDemoAction(options) {
|
|
25
|
+
const { action, isEnabled = () => false, fixture, delay = 0, onDemo } = options;
|
|
26
|
+
return async (args) => {
|
|
27
|
+
const enabled = await isEnabled(args.request);
|
|
28
|
+
if (!enabled) {
|
|
29
|
+
return action(args);
|
|
30
|
+
}
|
|
31
|
+
if (fixture === void 0) {
|
|
32
|
+
return action(args);
|
|
33
|
+
}
|
|
34
|
+
let formData;
|
|
35
|
+
try {
|
|
36
|
+
formData = await args.request.clone().formData();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
const context = {
|
|
40
|
+
params: args.params,
|
|
41
|
+
request: args.request,
|
|
42
|
+
path: new URL(args.request.url).pathname,
|
|
43
|
+
formData
|
|
44
|
+
};
|
|
45
|
+
onDemo?.(context);
|
|
46
|
+
if (delay > 0) {
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
48
|
+
}
|
|
49
|
+
if (isMethodActionHandlers(fixture)) {
|
|
50
|
+
const method = args.request.method.toUpperCase();
|
|
51
|
+
const handler = fixture[method];
|
|
52
|
+
if (handler) {
|
|
53
|
+
return executeFixture(handler, context);
|
|
54
|
+
}
|
|
55
|
+
return action(args);
|
|
56
|
+
}
|
|
57
|
+
return executeFixture(fixture, context);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function executeFixture(fixture, context) {
|
|
61
|
+
if (typeof fixture === "function") {
|
|
62
|
+
return fixture(context);
|
|
63
|
+
}
|
|
64
|
+
return fixture;
|
|
65
|
+
}
|
|
66
|
+
function isMethodActionHandlers(fixture) {
|
|
67
|
+
if (typeof fixture !== "object" || fixture === null) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const methods = ["POST", "PUT", "PATCH", "DELETE"];
|
|
71
|
+
return methods.some((method) => method in fixture);
|
|
72
|
+
}
|
|
73
|
+
function withDemoLoader(loader, options) {
|
|
74
|
+
return createDemoLoader({ ...options, loader });
|
|
75
|
+
}
|
|
76
|
+
function withDemoAction(action, options) {
|
|
77
|
+
return createDemoAction({ ...options, action });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/fixtures.ts
|
|
81
|
+
import { matchUrl } from "@demokit-ai/core";
|
|
82
|
+
var FixtureStore = class {
|
|
83
|
+
loaders = {};
|
|
84
|
+
actions = {};
|
|
85
|
+
constructor(config) {
|
|
86
|
+
if (config?.loaders) {
|
|
87
|
+
this.loaders = { ...config.loaders };
|
|
88
|
+
}
|
|
89
|
+
if (config?.actions) {
|
|
90
|
+
this.actions = { ...config.actions };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Register a loader fixture
|
|
95
|
+
*/
|
|
96
|
+
setLoader(path, handler) {
|
|
97
|
+
this.loaders[path] = handler;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Register an action fixture
|
|
102
|
+
*/
|
|
103
|
+
setAction(path, handler) {
|
|
104
|
+
this.actions[path] = handler;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a loader fixture for a path
|
|
109
|
+
*/
|
|
110
|
+
getLoader(path) {
|
|
111
|
+
if (path in this.loaders) {
|
|
112
|
+
return this.loaders[path];
|
|
113
|
+
}
|
|
114
|
+
for (const [pattern, handler] of Object.entries(this.loaders)) {
|
|
115
|
+
const fullPattern = pattern.startsWith("GET ") ? pattern : `GET ${pattern}`;
|
|
116
|
+
const result = matchUrl(fullPattern, "GET", path);
|
|
117
|
+
if (result) {
|
|
118
|
+
return handler;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get an action fixture for a path and method
|
|
125
|
+
*/
|
|
126
|
+
getAction(path, method) {
|
|
127
|
+
if (path in this.actions) {
|
|
128
|
+
return resolveActionHandler(this.actions[path], method);
|
|
129
|
+
}
|
|
130
|
+
for (const [pattern, fixture] of Object.entries(this.actions)) {
|
|
131
|
+
const fullPattern = /^(GET|POST|PUT|PATCH|DELETE)\s/.test(pattern) ? pattern : `${method} ${pattern}`;
|
|
132
|
+
const result = matchUrl(fullPattern, method, path);
|
|
133
|
+
if (result) {
|
|
134
|
+
return resolveActionHandler(fixture, method);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Find loader fixture with match info
|
|
141
|
+
*/
|
|
142
|
+
findLoader(path) {
|
|
143
|
+
if (path in this.loaders) {
|
|
144
|
+
return {
|
|
145
|
+
handler: this.loaders[path],
|
|
146
|
+
match: { matched: true, params: {} }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
for (const [pattern, handler] of Object.entries(this.loaders)) {
|
|
150
|
+
const fullPattern = pattern.startsWith("GET ") ? pattern : `GET ${pattern}`;
|
|
151
|
+
const result = matchUrl(fullPattern, "GET", path);
|
|
152
|
+
if (result) {
|
|
153
|
+
return { handler, match: result };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Find action fixture with match info
|
|
160
|
+
*/
|
|
161
|
+
findAction(path, method) {
|
|
162
|
+
if (path in this.actions) {
|
|
163
|
+
const handler = resolveActionHandler(this.actions[path], method);
|
|
164
|
+
if (handler) {
|
|
165
|
+
return {
|
|
166
|
+
handler,
|
|
167
|
+
match: { matched: true, params: {} }
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const [pattern, fixture] of Object.entries(this.actions)) {
|
|
172
|
+
const fullPattern = /^(GET|POST|PUT|PATCH|DELETE)\s/.test(pattern) ? pattern : `${method} ${pattern}`;
|
|
173
|
+
const result = matchUrl(fullPattern, method, path);
|
|
174
|
+
if (result) {
|
|
175
|
+
const handler = resolveActionHandler(fixture, method);
|
|
176
|
+
if (handler) {
|
|
177
|
+
return { handler, match: result };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get all loader fixtures
|
|
185
|
+
*/
|
|
186
|
+
getLoaders() {
|
|
187
|
+
return { ...this.loaders };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get all action fixtures
|
|
191
|
+
*/
|
|
192
|
+
getActions() {
|
|
193
|
+
return { ...this.actions };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Clear all fixtures
|
|
197
|
+
*/
|
|
198
|
+
clear() {
|
|
199
|
+
this.loaders = {};
|
|
200
|
+
this.actions = {};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function resolveActionHandler(fixture, method) {
|
|
204
|
+
if (isMethodActionHandlers2(fixture)) {
|
|
205
|
+
const upperMethod = method.toUpperCase();
|
|
206
|
+
return fixture[upperMethod];
|
|
207
|
+
}
|
|
208
|
+
return fixture;
|
|
209
|
+
}
|
|
210
|
+
function isMethodActionHandlers2(fixture) {
|
|
211
|
+
if (typeof fixture !== "object" || fixture === null) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const methods = ["POST", "PUT", "PATCH", "DELETE"];
|
|
215
|
+
return methods.some((method) => method in fixture);
|
|
216
|
+
}
|
|
217
|
+
function createFixtureStore(config) {
|
|
218
|
+
return new FixtureStore(config);
|
|
219
|
+
}
|
|
220
|
+
function defineRemixLoaderFixtures(fixtures) {
|
|
221
|
+
return fixtures;
|
|
222
|
+
}
|
|
223
|
+
function defineRemixActionFixtures(fixtures) {
|
|
224
|
+
return fixtures;
|
|
225
|
+
}
|
|
226
|
+
function defineRemixFixtures(config) {
|
|
227
|
+
return {
|
|
228
|
+
loaders: config.loaders ?? {},
|
|
229
|
+
actions: config.actions ?? {}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/provider.tsx
|
|
234
|
+
import { createContext, useContext, useMemo, useState, useCallback } from "react";
|
|
235
|
+
import { jsx } from "react/jsx-runtime";
|
|
236
|
+
var DemoRemixContext = createContext(null);
|
|
237
|
+
function toLoaderMap(obj) {
|
|
238
|
+
if (!obj) return /* @__PURE__ */ new Map();
|
|
239
|
+
return new Map(Object.entries(obj));
|
|
240
|
+
}
|
|
241
|
+
function toActionMap(obj) {
|
|
242
|
+
if (!obj) return /* @__PURE__ */ new Map();
|
|
243
|
+
return new Map(Object.entries(obj));
|
|
244
|
+
}
|
|
245
|
+
function DemoRemixProvider({
|
|
246
|
+
children,
|
|
247
|
+
enabled: initialEnabled = false,
|
|
248
|
+
loaders: initialLoaders,
|
|
249
|
+
actions: initialActions,
|
|
250
|
+
delay = 0
|
|
251
|
+
}) {
|
|
252
|
+
const [enabled, setEnabled] = useState(initialEnabled);
|
|
253
|
+
const [loaders, setLoaders] = useState(() => toLoaderMap(initialLoaders));
|
|
254
|
+
const [actions, setActions] = useState(() => toActionMap(initialActions));
|
|
255
|
+
const enable = useCallback(() => setEnabled(true), []);
|
|
256
|
+
const disable = useCallback(() => setEnabled(false), []);
|
|
257
|
+
const toggle = useCallback(() => {
|
|
258
|
+
setEnabled((prev) => !prev);
|
|
259
|
+
return !enabled;
|
|
260
|
+
}, [enabled]);
|
|
261
|
+
const setLoader = useCallback((path, handler) => {
|
|
262
|
+
setLoaders((prev) => new Map(prev).set(path, handler));
|
|
263
|
+
}, []);
|
|
264
|
+
const removeLoader = useCallback((path) => {
|
|
265
|
+
setLoaders((prev) => {
|
|
266
|
+
const next = new Map(prev);
|
|
267
|
+
next.delete(path);
|
|
268
|
+
return next;
|
|
269
|
+
});
|
|
270
|
+
}, []);
|
|
271
|
+
const setAction = useCallback((path, handler) => {
|
|
272
|
+
setActions((prev) => new Map(prev).set(path, handler));
|
|
273
|
+
}, []);
|
|
274
|
+
const removeAction = useCallback((path) => {
|
|
275
|
+
setActions((prev) => {
|
|
276
|
+
const next = new Map(prev);
|
|
277
|
+
next.delete(path);
|
|
278
|
+
return next;
|
|
279
|
+
});
|
|
280
|
+
}, []);
|
|
281
|
+
const clearFixtures = useCallback(() => {
|
|
282
|
+
setLoaders(/* @__PURE__ */ new Map());
|
|
283
|
+
setActions(/* @__PURE__ */ new Map());
|
|
284
|
+
}, []);
|
|
285
|
+
const value = useMemo(
|
|
286
|
+
() => ({
|
|
287
|
+
enabled,
|
|
288
|
+
loaders,
|
|
289
|
+
actions,
|
|
290
|
+
delay,
|
|
291
|
+
enable,
|
|
292
|
+
disable,
|
|
293
|
+
toggle,
|
|
294
|
+
setLoader,
|
|
295
|
+
removeLoader,
|
|
296
|
+
setAction,
|
|
297
|
+
removeAction,
|
|
298
|
+
clearFixtures
|
|
299
|
+
}),
|
|
300
|
+
[
|
|
301
|
+
enabled,
|
|
302
|
+
loaders,
|
|
303
|
+
actions,
|
|
304
|
+
delay,
|
|
305
|
+
enable,
|
|
306
|
+
disable,
|
|
307
|
+
toggle,
|
|
308
|
+
setLoader,
|
|
309
|
+
removeLoader,
|
|
310
|
+
setAction,
|
|
311
|
+
removeAction,
|
|
312
|
+
clearFixtures
|
|
313
|
+
]
|
|
314
|
+
);
|
|
315
|
+
return /* @__PURE__ */ jsx(DemoRemixContext.Provider, { value, children });
|
|
316
|
+
}
|
|
317
|
+
function useDemoRemix() {
|
|
318
|
+
const context = useContext(DemoRemixContext);
|
|
319
|
+
if (!context) {
|
|
320
|
+
throw new Error("useDemoRemix must be used within a DemoRemixProvider");
|
|
321
|
+
}
|
|
322
|
+
return context;
|
|
323
|
+
}
|
|
324
|
+
function useIsDemoRemixMode() {
|
|
325
|
+
const context = useContext(DemoRemixContext);
|
|
326
|
+
return context?.enabled ?? false;
|
|
327
|
+
}
|
|
328
|
+
function useDemoRemixOptional() {
|
|
329
|
+
return useContext(DemoRemixContext);
|
|
330
|
+
}
|
|
331
|
+
export {
|
|
332
|
+
DemoRemixProvider,
|
|
333
|
+
FixtureStore,
|
|
334
|
+
createDemoAction,
|
|
335
|
+
createDemoLoader,
|
|
336
|
+
createFixtureStore,
|
|
337
|
+
defineRemixActionFixtures,
|
|
338
|
+
defineRemixFixtures,
|
|
339
|
+
defineRemixLoaderFixtures,
|
|
340
|
+
useDemoRemix,
|
|
341
|
+
useDemoRemixOptional,
|
|
342
|
+
useIsDemoRemixMode,
|
|
343
|
+
withDemoAction,
|
|
344
|
+
withDemoLoader
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wrappers.ts","../src/fixtures.ts","../src/provider.tsx"],"sourcesContent":["import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node'\nimport type {\n CreateDemoLoaderOptions,\n CreateDemoActionOptions,\n LoaderFixtureContext,\n ActionFixtureContext,\n LoaderFixtureHandler,\n ActionFixtureHandler,\n MethodActionHandlers,\n} from './types'\n\n/**\n * Create a demo-aware loader function for Remix\n *\n * Returns a loader that intercepts requests when demo mode is enabled\n * and returns fixture data instead of calling the real loader.\n * Demo mode is checked server-side via cookies, headers, or request context.\n *\n * @example\n * // Basic usage with inline fixture\n * export const loader = createDemoLoader({\n * loader: async ({ params }) => {\n * const user = await db.users.findUnique({ where: { id: params.id } })\n * return json({ user })\n * },\n * isEnabled: (request) => request.headers.get('cookie')?.includes('demo=true') ?? false,\n * fixture: ({ params }) => ({\n * user: { id: params.id, name: 'Demo User', email: 'demo@example.com' }\n * }),\n * })\n *\n * @example\n * // With cookie-based detection\n * import { isDemoMode } from '@demokit-ai/remix/server'\n *\n * export const loader = createDemoLoader({\n * loader: realLoader,\n * isEnabled: (request) => isDemoMode(request),\n * fixture: demoData,\n * delay: 200, // Simulate 200ms network latency\n * })\n */\nexport function createDemoLoader<T = unknown>(\n options: CreateDemoLoaderOptions<T>\n): (args: LoaderFunctionArgs) => Promise<T> {\n const { loader, isEnabled = () => false, fixture, delay = 0, onDemo } = options\n\n return async (args: LoaderFunctionArgs): Promise<T> => {\n // Check if demo mode is enabled (async to support cookie parsing)\n const enabled = await isEnabled(args.request)\n if (!enabled) {\n return loader(args)\n }\n\n // If no fixture provided, fall back to real loader\n if (fixture === undefined) {\n return loader(args)\n }\n\n // Build context for fixture handler\n const context: LoaderFixtureContext = {\n params: args.params,\n request: args.request,\n path: new URL(args.request.url).pathname,\n }\n\n // Notify callback\n onDemo?.(context)\n\n // Apply delay if configured\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n\n // Execute fixture\n return executeFixture(fixture, context)\n }\n}\n\n/**\n * Create a demo-aware action function for Remix\n *\n * Returns an action that intercepts requests when demo mode is enabled\n * and returns fixture data instead of calling the real action.\n *\n * @example\n * // Basic usage with inline fixture\n * export const action = createDemoAction({\n * action: async ({ request }) => {\n * const formData = await request.formData()\n * const user = await db.users.create({ data: Object.fromEntries(formData) })\n * return json({ user })\n * },\n * isEnabled: (request) => request.headers.get('cookie')?.includes('demo=true') ?? false,\n * fixture: async ({ formData }) => ({\n * user: { id: crypto.randomUUID(), ...Object.fromEntries(formData!) }\n * }),\n * })\n *\n * @example\n * // With method-specific fixtures\n * export const action = createDemoAction({\n * action: realAction,\n * isEnabled: isDemoMode,\n * fixture: {\n * POST: ({ formData }) => json({ created: true, id: crypto.randomUUID() }),\n * DELETE: ({ params }) => json({ deleted: true, id: params.id }),\n * },\n * })\n */\nexport function createDemoAction<T = unknown>(\n options: CreateDemoActionOptions<T>\n): (args: ActionFunctionArgs) => Promise<T> {\n const { action, isEnabled = () => false, fixture, delay = 0, onDemo } = options\n\n return async (args: ActionFunctionArgs): Promise<T> => {\n // Check if demo mode is enabled (async to support cookie parsing)\n const enabled = await isEnabled(args.request)\n if (!enabled) {\n return action(args)\n }\n\n // If no fixture provided, fall back to real action\n if (fixture === undefined) {\n return action(args)\n }\n\n // Parse form data for convenience\n let formData: FormData | undefined\n try {\n formData = await args.request.clone().formData()\n } catch {\n // Not form data, that's fine\n }\n\n // Build context for fixture handler\n const context: ActionFixtureContext = {\n params: args.params,\n request: args.request,\n path: new URL(args.request.url).pathname,\n formData,\n }\n\n // Notify callback\n onDemo?.(context)\n\n // Apply delay if configured\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n\n // Check if fixture is method-specific\n if (isMethodActionHandlers(fixture)) {\n const method = args.request.method.toUpperCase() as keyof MethodActionHandlers\n const handler = fixture[method]\n if (handler) {\n return executeFixture(handler, context) as Promise<T>\n }\n // No handler for this method, fall back to real action\n return action(args)\n }\n\n // Execute fixture\n return executeFixture(fixture as ActionFixtureHandler<T>, context)\n }\n}\n\n/**\n * Execute a fixture handler (static value or function)\n */\nasync function executeFixture<T, C extends LoaderFixtureContext | ActionFixtureContext>(\n fixture: T | ((context: C) => T | Promise<T>),\n context: C\n): Promise<T> {\n if (typeof fixture === 'function') {\n return (fixture as (context: C) => T | Promise<T>)(context)\n }\n return fixture\n}\n\n/**\n * Type guard for method action handlers\n */\nfunction isMethodActionHandlers(fixture: unknown): fixture is MethodActionHandlers {\n if (typeof fixture !== 'object' || fixture === null) {\n return false\n }\n const methods = ['POST', 'PUT', 'PATCH', 'DELETE']\n return methods.some((method) => method in fixture)\n}\n\n/**\n * Helper to create a demo loader with context from request\n *\n * Simplified version that uses request-based demo mode detection.\n *\n * @example\n * import { withDemoLoader, isDemoMode } from '@demokit-ai/remix'\n *\n * export const loader = withDemoLoader(\n * async ({ params }) => {\n * return json(await db.users.findUnique({ where: { id: params.id } }))\n * },\n * {\n * isEnabled: isDemoMode,\n * fixture: ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * }\n * )\n */\nexport function withDemoLoader<T = unknown>(\n loader: (args: LoaderFunctionArgs) => T | Promise<T>,\n options: Omit<CreateDemoLoaderOptions<T>, 'loader'>\n): (args: LoaderFunctionArgs) => Promise<T> {\n return createDemoLoader({ ...options, loader })\n}\n\n/**\n * Helper to create a demo action with context from request\n *\n * Simplified version that uses request-based demo mode detection.\n *\n * @example\n * import { withDemoAction, isDemoMode } from '@demokit-ai/remix'\n *\n * export const action = withDemoAction(\n * async ({ request }) => {\n * const formData = await request.formData()\n * return json(await db.users.create({ data: Object.fromEntries(formData) }))\n * },\n * {\n * isEnabled: isDemoMode,\n * fixture: ({ formData }) => ({ id: crypto.randomUUID(), name: formData?.get('name') }),\n * }\n * )\n */\nexport function withDemoAction<T = unknown>(\n action: (args: ActionFunctionArgs) => T | Promise<T>,\n options: Omit<CreateDemoActionOptions<T>, 'action'>\n): (args: ActionFunctionArgs) => Promise<T> {\n return createDemoAction({ ...options, action })\n}\n","import { matchUrl, type MatchResult } from '@demokit-ai/core'\nimport type {\n LoaderFixtureMapObject,\n ActionFixtureMapObject,\n LoaderFixtureHandler,\n ActionFixtureHandler,\n MethodActionHandlers,\n FixtureStoreConfig,\n} from './types'\n\n/**\n * Server-side fixture store for managing demo data\n *\n * Use this to centralize fixture definitions for your Remix app.\n * Works with the demo mode detection utilities.\n */\nexport class FixtureStore {\n private loaders: LoaderFixtureMapObject = {}\n private actions: ActionFixtureMapObject = {}\n\n constructor(config?: FixtureStoreConfig) {\n if (config?.loaders) {\n this.loaders = { ...config.loaders }\n }\n if (config?.actions) {\n this.actions = { ...config.actions }\n }\n }\n\n /**\n * Register a loader fixture\n */\n setLoader(path: string, handler: LoaderFixtureHandler): this {\n this.loaders[path] = handler\n return this\n }\n\n /**\n * Register an action fixture\n */\n setAction(path: string, handler: ActionFixtureHandler | MethodActionHandlers): this {\n this.actions[path] = handler\n return this\n }\n\n /**\n * Get a loader fixture for a path\n */\n getLoader(path: string): LoaderFixtureHandler | undefined {\n // Try exact match first\n if (path in this.loaders) {\n return this.loaders[path]\n }\n\n // Try pattern matching\n for (const [pattern, handler] of Object.entries(this.loaders)) {\n const fullPattern = pattern.startsWith('GET ') ? pattern : `GET ${pattern}`\n const result = matchUrl(fullPattern, 'GET', path)\n if (result) {\n return handler\n }\n }\n\n return undefined\n }\n\n /**\n * Get an action fixture for a path and method\n */\n getAction(path: string, method: string): ActionFixtureHandler | undefined {\n // Try exact match first\n if (path in this.actions) {\n return resolveActionHandler(this.actions[path], method)\n }\n\n // Try pattern matching\n for (const [pattern, fixture] of Object.entries(this.actions)) {\n const fullPattern = /^(GET|POST|PUT|PATCH|DELETE)\\s/.test(pattern)\n ? pattern\n : `${method} ${pattern}`\n const result = matchUrl(fullPattern, method, path)\n if (result) {\n return resolveActionHandler(fixture, method)\n }\n }\n\n return undefined\n }\n\n /**\n * Find loader fixture with match info\n */\n findLoader(path: string): { handler: LoaderFixtureHandler; match: MatchResult } | null {\n // Try exact match first\n if (path in this.loaders) {\n return {\n handler: this.loaders[path],\n match: { matched: true, params: {} },\n }\n }\n\n // Try pattern matching\n for (const [pattern, handler] of Object.entries(this.loaders)) {\n const fullPattern = pattern.startsWith('GET ') ? pattern : `GET ${pattern}`\n const result = matchUrl(fullPattern, 'GET', path)\n if (result) {\n return { handler, match: result }\n }\n }\n\n return null\n }\n\n /**\n * Find action fixture with match info\n */\n findAction(\n path: string,\n method: string\n ): { handler: ActionFixtureHandler; match: MatchResult } | null {\n // Try exact match first\n if (path in this.actions) {\n const handler = resolveActionHandler(this.actions[path], method)\n if (handler) {\n return {\n handler,\n match: { matched: true, params: {} },\n }\n }\n }\n\n // Try pattern matching\n for (const [pattern, fixture] of Object.entries(this.actions)) {\n const fullPattern = /^(GET|POST|PUT|PATCH|DELETE)\\s/.test(pattern)\n ? pattern\n : `${method} ${pattern}`\n const result = matchUrl(fullPattern, method, path)\n if (result) {\n const handler = resolveActionHandler(fixture, method)\n if (handler) {\n return { handler, match: result }\n }\n }\n }\n\n return null\n }\n\n /**\n * Get all loader fixtures\n */\n getLoaders(): LoaderFixtureMapObject {\n return { ...this.loaders }\n }\n\n /**\n * Get all action fixtures\n */\n getActions(): ActionFixtureMapObject {\n return { ...this.actions }\n }\n\n /**\n * Clear all fixtures\n */\n clear(): void {\n this.loaders = {}\n this.actions = {}\n }\n}\n\n/**\n * Resolve action handler based on method\n */\nfunction resolveActionHandler(\n fixture: ActionFixtureHandler | MethodActionHandlers,\n method: string\n): ActionFixtureHandler | undefined {\n if (isMethodActionHandlers(fixture)) {\n const upperMethod = method.toUpperCase() as keyof MethodActionHandlers\n return fixture[upperMethod]\n }\n return fixture\n}\n\n/**\n * Type guard for method action handlers\n */\nfunction isMethodActionHandlers(fixture: unknown): fixture is MethodActionHandlers {\n if (typeof fixture !== 'object' || fixture === null) {\n return false\n }\n const methods = ['POST', 'PUT', 'PATCH', 'DELETE']\n return methods.some((method) => method in fixture)\n}\n\n/**\n * Create a fixture store with initial fixtures\n *\n * @example\n * import { createFixtureStore } from '@demokit-ai/remix/server'\n *\n * export const fixtures = createFixtureStore({\n * loaders: {\n * '/users': [{ id: '1', name: 'Demo User' }],\n * '/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * },\n * actions: {\n * '/users': {\n * POST: ({ formData }) => ({ id: crypto.randomUUID(), name: formData?.get('name') }),\n * },\n * '/users/:id': {\n * PUT: ({ formData }) => ({ updated: true }),\n * DELETE: ({ params }) => ({ deleted: true, id: params.id }),\n * },\n * },\n * })\n */\nexport function createFixtureStore(config?: FixtureStoreConfig): FixtureStore {\n return new FixtureStore(config)\n}\n\n/**\n * Define loader fixtures with type inference\n *\n * @example\n * const loaders = defineRemixLoaderFixtures({\n * '/users': [{ id: '1', name: 'Demo User' }],\n * '/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * '/projects': async () => fetchDemoProjects(),\n * })\n */\nexport function defineRemixLoaderFixtures<T extends LoaderFixtureMapObject>(fixtures: T): T {\n return fixtures\n}\n\n/**\n * Define action fixtures with type inference\n *\n * @example\n * const actions = defineRemixActionFixtures({\n * '/users': {\n * POST: ({ formData }) => ({ id: crypto.randomUUID(), name: formData?.get('name') }),\n * },\n * '/users/:id': {\n * PUT: ({ formData }) => ({ updated: true }),\n * DELETE: ({ params }) => ({ deleted: true, id: params.id }),\n * },\n * })\n */\nexport function defineRemixActionFixtures<T extends ActionFixtureMapObject>(fixtures: T): T {\n return fixtures\n}\n\n/**\n * Define complete Remix fixtures (loaders + actions)\n *\n * @example\n * const { loaders, actions } = defineRemixFixtures({\n * loaders: {\n * '/users': [{ id: '1', name: 'Demo User' }],\n * },\n * actions: {\n * '/users': ({ formData }) => ({ created: true }),\n * },\n * })\n */\nexport function defineRemixFixtures<\n L extends LoaderFixtureMapObject,\n A extends ActionFixtureMapObject,\n>(config: { loaders?: L; actions?: A }): { loaders: L; actions: A } {\n return {\n loaders: (config.loaders ?? {}) as L,\n actions: (config.actions ?? {}) as A,\n }\n}\n","import { createContext, useContext, useMemo, useState, useCallback } from 'react'\nimport type {\n DemoRemixProviderProps,\n DemoRemixState,\n LoaderFixtureMap,\n ActionFixtureMap,\n LoaderFixtureMapObject,\n ActionFixtureMapObject,\n LoaderFixtureHandler,\n ActionFixtureHandler,\n} from './types'\n\n/**\n * Context value for demo remix state\n */\ninterface DemoRemixContextValue extends DemoRemixState {\n /** Enable demo mode */\n enable: () => void\n /** Disable demo mode */\n disable: () => void\n /** Toggle demo mode */\n toggle: () => boolean\n /** Set a loader fixture */\n setLoader: (path: string, handler: LoaderFixtureHandler) => void\n /** Remove a loader fixture */\n removeLoader: (path: string) => void\n /** Set an action fixture */\n setAction: (path: string, handler: ActionFixtureHandler) => void\n /** Remove an action fixture */\n removeAction: (path: string) => void\n /** Clear all fixtures */\n clearFixtures: () => void\n /** Get delay setting */\n delay: number\n}\n\nconst DemoRemixContext = createContext<DemoRemixContextValue | null>(null)\n\n/**\n * Convert object fixture map to Map\n */\nfunction toLoaderMap(obj?: LoaderFixtureMapObject): LoaderFixtureMap {\n if (!obj) return new Map()\n return new Map(Object.entries(obj))\n}\n\n/**\n * Convert object fixture map to Map\n */\nfunction toActionMap(obj?: ActionFixtureMapObject): ActionFixtureMap {\n if (!obj) return new Map()\n return new Map(Object.entries(obj))\n}\n\n/**\n * Provider for demo remix state (client-side)\n *\n * Wrap your Remix app with this provider to enable client-side fixture management\n * and demo mode toggle. For server-side demo mode detection, use the server utilities.\n *\n * @example\n * import { DemoRemixProvider } from '@demokit-ai/remix'\n *\n * const loaders = {\n * '/users': [{ id: '1', name: 'Demo User' }],\n * '/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * }\n *\n * const actions = {\n * '/users': ({ formData }) => ({\n * id: crypto.randomUUID(),\n * name: formData?.get('name'),\n * }),\n * }\n *\n * export default function App() {\n * return (\n * <DemoRemixProvider loaders={loaders} actions={actions} enabled>\n * <Outlet />\n * </DemoRemixProvider>\n * )\n * }\n */\nexport function DemoRemixProvider({\n children,\n enabled: initialEnabled = false,\n loaders: initialLoaders,\n actions: initialActions,\n delay = 0,\n}: DemoRemixProviderProps) {\n const [enabled, setEnabled] = useState(initialEnabled)\n const [loaders, setLoaders] = useState<LoaderFixtureMap>(() => toLoaderMap(initialLoaders))\n const [actions, setActions] = useState<ActionFixtureMap>(() => toActionMap(initialActions))\n\n const enable = useCallback(() => setEnabled(true), [])\n const disable = useCallback(() => setEnabled(false), [])\n const toggle = useCallback(() => {\n setEnabled((prev) => !prev)\n return !enabled\n }, [enabled])\n\n const setLoader = useCallback((path: string, handler: LoaderFixtureHandler) => {\n setLoaders((prev) => new Map(prev).set(path, handler))\n }, [])\n\n const removeLoader = useCallback((path: string) => {\n setLoaders((prev) => {\n const next = new Map(prev)\n next.delete(path)\n return next\n })\n }, [])\n\n const setAction = useCallback((path: string, handler: ActionFixtureHandler) => {\n setActions((prev) => new Map(prev).set(path, handler))\n }, [])\n\n const removeAction = useCallback((path: string) => {\n setActions((prev) => {\n const next = new Map(prev)\n next.delete(path)\n return next\n })\n }, [])\n\n const clearFixtures = useCallback(() => {\n setLoaders(new Map())\n setActions(new Map())\n }, [])\n\n const value = useMemo<DemoRemixContextValue>(\n () => ({\n enabled,\n loaders,\n actions,\n delay,\n enable,\n disable,\n toggle,\n setLoader,\n removeLoader,\n setAction,\n removeAction,\n clearFixtures,\n }),\n [\n enabled,\n loaders,\n actions,\n delay,\n enable,\n disable,\n toggle,\n setLoader,\n removeLoader,\n setAction,\n removeAction,\n clearFixtures,\n ]\n )\n\n return <DemoRemixContext.Provider value={value}>{children}</DemoRemixContext.Provider>\n}\n\n/**\n * Hook to access demo remix state and controls\n *\n * @example\n * function DemoModeToggle() {\n * const { enabled, toggle } = useDemoRemix()\n * return (\n * <button onClick={toggle}>\n * Demo Mode: {enabled ? 'ON' : 'OFF'}\n * </button>\n * )\n * }\n */\nexport function useDemoRemix(): DemoRemixContextValue {\n const context = useContext(DemoRemixContext)\n if (!context) {\n throw new Error('useDemoRemix must be used within a DemoRemixProvider')\n }\n return context\n}\n\n/**\n * Hook to check if demo mode is enabled\n *\n * @example\n * function DataDisplay() {\n * const isDemo = useIsDemoRemixMode()\n * return isDemo ? <DemoBanner /> : null\n * }\n */\nexport function useIsDemoRemixMode(): boolean {\n const context = useContext(DemoRemixContext)\n return context?.enabled ?? false\n}\n\n/**\n * Get the demo remix context (may be null if not in provider)\n *\n * Use this for optional demo mode integration.\n */\nexport function useDemoRemixOptional(): DemoRemixContextValue | null {\n return useContext(DemoRemixContext)\n}\n"],"mappings":"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","names":["isMethodActionHandlers"]}
|