@fitzzero/quickdraw-core 0.1.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/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # @quickdraw/core
2
+
3
+ Fast fullstack patterns for real-time applications with Socket.io and TanStack Query.
4
+
5
+ ## Features
6
+
7
+ - **Server Core**: BaseService class with typed CRUD, ACL-based access control, and real-time subscriptions
8
+ - **Client Core**: TanStack Query integration with Socket.io for real-time state management
9
+ - **Socket Inputs**: Pre-built form components that sync with server state
10
+ - **Custom OAuth**: JWT-based authentication with Discord and Google providers
11
+ - **Type Safety**: End-to-end TypeScript support with shared type definitions
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @quickdraw/core
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### Server Setup
22
+
23
+ ```typescript
24
+ import { createQuickdrawServer, BaseService } from '@quickdraw/core/server';
25
+ import { PrismaClient } from '@prisma/client';
26
+
27
+ const prisma = new PrismaClient();
28
+
29
+ // Define your service
30
+ class ChatService extends BaseService<Chat, Prisma.ChatCreateInput, Prisma.ChatUpdateInput, ChatServiceMethods> {
31
+ constructor() {
32
+ super({ serviceName: 'chatService', hasEntryACL: true });
33
+ this.setDelegate(prisma.chat);
34
+
35
+ // Define public methods
36
+ this.createChat = this.defineMethod('createChat', 'Read', async (payload, ctx) => {
37
+ const chat = await this.create({ title: payload.title, ownerId: ctx.userId });
38
+ return { id: chat.id };
39
+ });
40
+ }
41
+
42
+ createChat: ReturnType<typeof this.defineMethod<'createChat'>>;
43
+ }
44
+
45
+ // Start server
46
+ const { io, httpServer } = createQuickdrawServer({
47
+ port: 4000,
48
+ cors: { origin: 'http://localhost:3000' },
49
+ services: {
50
+ chatService: new ChatService(),
51
+ },
52
+ auth: {
53
+ authenticate: async (socket, auth) => {
54
+ const payload = await verifyJWT(auth.token, process.env.JWT_SECRET);
55
+ return payload?.userId;
56
+ },
57
+ },
58
+ });
59
+ ```
60
+
61
+ ### Client Setup
62
+
63
+ ```tsx
64
+ // app/layout.tsx
65
+ import { QuickdrawProvider } from '@quickdraw/core/client';
66
+
67
+ export default function RootLayout({ children }) {
68
+ return (
69
+ <QuickdrawProvider
70
+ serverUrl="http://localhost:4000"
71
+ authToken={getAuthToken()}
72
+ >
73
+ {children}
74
+ </QuickdrawProvider>
75
+ );
76
+ }
77
+
78
+ // app/chat/page.tsx
79
+ import { useService, useSubscription } from '@quickdraw/core/client';
80
+
81
+ function ChatPage({ chatId }: { chatId: string }) {
82
+ // Subscribe to real-time updates
83
+ const { data: chat, isLoading } = useSubscription('chatService', chatId);
84
+
85
+ // Mutation hook
86
+ const updateTitle = useService('chatService', 'updateTitle', {
87
+ onSuccess: () => console.log('Title updated!'),
88
+ });
89
+
90
+ if (isLoading) return <div>Loading...</div>;
91
+
92
+ return (
93
+ <div>
94
+ <h1>{chat?.title}</h1>
95
+ <button onClick={() => updateTitle.mutate({ id: chatId, title: 'New Title' })}>
96
+ Update Title
97
+ </button>
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### Socket Inputs
104
+
105
+ ```tsx
106
+ import { SocketTextField } from '@quickdraw/core/client';
107
+
108
+ function ChatTitleEditor({ chat, updateChat }) {
109
+ return (
110
+ <SocketTextField
111
+ state={chat}
112
+ update={(patch) => updateChat.mutateAsync({ id: chat.id, ...patch })}
113
+ property="title"
114
+ commitMode="debounce"
115
+ debounceMs={500}
116
+ placeholder="Chat title..."
117
+ />
118
+ );
119
+ }
120
+ ```
121
+
122
+ ## Package Exports
123
+
124
+ ```typescript
125
+ // Shared types (both server and client)
126
+ import { ServiceResponse, AccessLevel, ServiceMethodMap } from '@quickdraw/core';
127
+
128
+ // Server
129
+ import {
130
+ BaseService,
131
+ ServiceRegistry,
132
+ createQuickdrawServer,
133
+ createJWT,
134
+ verifyJWT,
135
+ discordProvider,
136
+ googleProvider,
137
+ } from '@quickdraw/core/server';
138
+
139
+ // Server testing
140
+ import { createTestServer, connectAsUser, emitWithAck } from '@quickdraw/core/server/testing';
141
+
142
+ // Client
143
+ import {
144
+ QuickdrawProvider,
145
+ useQuickdrawSocket,
146
+ useService,
147
+ useSubscription,
148
+ SocketCheckbox,
149
+ SocketTextField,
150
+ SocketSelect,
151
+ SocketSlider,
152
+ SocketSwitch,
153
+ } from '@quickdraw/core/client';
154
+
155
+ // Client testing
156
+ import { createMockSocket, createTestWrapper } from '@quickdraw/core/client/testing';
157
+ ```
158
+
159
+ ## Type Definitions
160
+
161
+ Define your service methods in a shared types file:
162
+
163
+ ```typescript
164
+ // shared/types.ts
165
+ import type { ServiceMethodMap } from '@quickdraw/core';
166
+
167
+ export type ChatServiceMethods = ServiceMethodMap<{
168
+ createChat: {
169
+ payload: { title: string };
170
+ response: { id: string };
171
+ };
172
+ updateTitle: {
173
+ payload: { id: string; title: string };
174
+ response: { id: string; title: string };
175
+ };
176
+ inviteUser: {
177
+ payload: { id: string; userId: string; level: 'Read' | 'Moderate' | 'Admin' };
178
+ response: { id: string };
179
+ };
180
+ }>;
181
+ ```
182
+
183
+ ## Architecture
184
+
185
+ ```
186
+ ┌─────────────────────────────────────────────────────────────────┐
187
+ │ Client (React) │
188
+ ├─────────────────────────────────────────────────────────────────┤
189
+ │ QuickdrawProvider │
190
+ │ ├── TanStack QueryClient │
191
+ │ └── Socket.io Connection │
192
+ │ │
193
+ │ useService() ──────────────────────────────────────────────┐ │
194
+ │ useSubscription() ─────────────────────────────────────────┤ │
195
+ │ SocketTextField, SocketCheckbox, ... ──────────────────────┤ │
196
+ │ │ │
197
+ └─────────────────────────────────────────────────────────────│───┘
198
+ │
199
+ Socket.io Events │
200
+ ▼
201
+ ┌─────────────────────────────────────────────────────────────────┐
202
+ │ Server (Node.js) │
203
+ ├─────────────────────────────────────────────────────────────────┤
204
+ │ createQuickdrawServer() │
205
+ │ └── ServiceRegistry │
206
+ │ ├── Auto-discovers public methods │
207
+ │ └── Wires methods to Socket.io events │
208
+ │ │
209
+ │ BaseService<Entity, CreateInput, UpdateInput, ServiceMethods> │
210
+ │ ├── defineMethod() - Type-safe method definition │
211
+ │ ├── subscribe() / unsubscribe() - Real-time subscriptions │
212
+ │ ├── create() / update() / delete() - CRUD with auto-emit │
213
+ │ └── checkAccess() - ACL enforcement │
214
+ │ │
215
+ │ Auth Utilities │
216
+ │ ├── createJWT() / verifyJWT() │
217
+ │ └── OAuth providers (Discord, Google) │
218
+ │ │
219
+ └─────────────────────────────────────────────────────────────────┘
220
+ ```
221
+
222
+ ## Access Control
223
+
224
+ Quickdraw supports two levels of access control:
225
+
226
+ ### Service-level ACL
227
+
228
+ Stored in `user.serviceAccess` JSON field:
229
+ ```json
230
+ { "chatService": "Admin", "userService": "Read" }
231
+ ```
232
+
233
+ ### Entry-level ACL
234
+
235
+ Stored in entity's `acl` JSON field (when `hasEntryACL: true`):
236
+ ```json
237
+ [
238
+ { "userId": "user-1", "level": "Admin" },
239
+ { "userId": "user-2", "level": "Read" }
240
+ ]
241
+ ```
242
+
243
+ ### Access Levels
244
+
245
+ - **Public**: No authentication required
246
+ - **Read**: Authenticated users (default for subscriptions)
247
+ - **Moderate**: Edit access (update, moderate content)
248
+ - **Admin**: Full access (delete, manage ACL)
249
+
250
+ ## Testing
251
+
252
+ ### Server Integration Tests
253
+
254
+ ```typescript
255
+ import { createTestServer, connectAsUser, emitWithAck } from '@quickdraw/core/server/testing';
256
+
257
+ describe('ChatService', () => {
258
+ let server;
259
+
260
+ beforeAll(async () => {
261
+ server = await createTestServer({
262
+ services: { chatService: new ChatService() },
263
+ seedDb: async () => { /* seed test data */ },
264
+ });
265
+ });
266
+
267
+ afterAll(() => server.stop());
268
+
269
+ it('creates chat', async () => {
270
+ const client = await server.connectAs('user-id');
271
+ const chat = await client.emit('chatService:createChat', { title: 'Test' });
272
+ expect(chat.id).toBeDefined();
273
+ client.close();
274
+ });
275
+ });
276
+ ```
277
+
278
+ ### Client Component Tests
279
+
280
+ ```typescript
281
+ import { createTestWrapper, createMockSocket, mockSuccessEmit } from '@quickdraw/core/client/testing';
282
+
283
+ test('renders chat', () => {
284
+ const mockSocket = createMockSocket();
285
+ mockSocket.emit.mockImplementation(mockSuccessEmit({ title: 'Test Chat' }));
286
+
287
+ const wrapper = createTestWrapper({ socketContext: { socket: mockSocket } });
288
+ render(<ChatView chatId="123" />, { wrapper });
289
+
290
+ expect(screen.getByText('Test Chat')).toBeInTheDocument();
291
+ });
292
+ ```
293
+
294
+ ## Contributing
295
+
296
+ Contributions are welcome! Please read our contributing guide for details.
297
+
298
+ ## License
299
+
300
+ MIT
@@ -0,0 +1,349 @@
1
+ import * as React from 'react';
2
+ import { a as QuickdrawProviderProps, Q as QuickdrawSocketContextValue, U as UseServiceOptions, b as UseServiceResult, c as UseSubscriptionOptions, d as UseSubscriptionResult } from '../types-CUExGF7F.js';
3
+ export { C as ClientServiceMethodMap, S as SubscriptionDataMap } from '../types-CUExGF7F.js';
4
+ import 'socket.io-client';
5
+ import '@tanstack/react-query';
6
+ import '../shared/index.js';
7
+ import 'zod';
8
+
9
+ /**
10
+ * Hook to access the quickdraw socket context.
11
+ */
12
+ declare function useQuickdrawSocket(): QuickdrawSocketContextValue;
13
+ /**
14
+ * Provider component that sets up TanStack Query and Socket.io connection.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * // In your app layout
19
+ * export default function RootLayout({ children }) {
20
+ * return (
21
+ * <QuickdrawProvider
22
+ * serverUrl="http://localhost:4000"
23
+ * authToken={token}
24
+ * >
25
+ * {children}
26
+ * </QuickdrawProvider>
27
+ * );
28
+ * }
29
+ * ```
30
+ */
31
+ declare function QuickdrawProvider({ children, serverUrl, queryClient, authToken, autoConnect, }: QuickdrawProviderProps): React.ReactElement;
32
+
33
+ /**
34
+ * Hook for invoking service methods via Socket.io with TanStack Query integration.
35
+ *
36
+ * @typeParam TServiceMethods - Map of service method definitions
37
+ * @typeParam TService - Service name
38
+ * @typeParam TMethod - Method name
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * // Define your service methods type
43
+ * type ChatServiceMethods = {
44
+ * createChat: { payload: { title: string }; response: { id: string } };
45
+ * updateTitle: { payload: { id: string; title: string }; response: { id: string; title: string } };
46
+ * };
47
+ *
48
+ * // In your component
49
+ * function CreateChatButton() {
50
+ * const createChat = useService<ChatServiceMethods, 'chatService', 'createChat'>(
51
+ * 'chatService',
52
+ * 'createChat',
53
+ * {
54
+ * onSuccess: (data) => {
55
+ * router.push(`/chat/${data.id}`);
56
+ * },
57
+ * }
58
+ * );
59
+ *
60
+ * return (
61
+ * <button
62
+ * onClick={() => createChat.mutate({ title: 'New Chat' })}
63
+ * disabled={createChat.isPending}
64
+ * >
65
+ * {createChat.isPending ? 'Creating...' : 'Create Chat'}
66
+ * </button>
67
+ * );
68
+ * }
69
+ * ```
70
+ */
71
+ declare function useService<TServiceMethods extends Record<string, Record<string, {
72
+ payload: unknown;
73
+ response: unknown;
74
+ }>>, TService extends keyof TServiceMethods & string, TMethod extends keyof TServiceMethods[TService] & string>(serviceName: TService, methodName: TMethod, options?: UseServiceOptions<TServiceMethods[TService][TMethod]["response"]>): UseServiceResult<TServiceMethods[TService][TMethod]["payload"], TServiceMethods[TService][TMethod]["response"]>;
75
+ /**
76
+ * Simplified hook for basic service method calls.
77
+ * Use this when you don't need the full TanStack Query mutation features.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * const { execute, loading, error, data } = useServiceMethod('chatService', 'createChat');
82
+ *
83
+ * const handleCreate = async () => {
84
+ * const result = await execute({ title: 'New Chat' });
85
+ * if (result) {
86
+ * router.push(`/chat/${result.id}`);
87
+ * }
88
+ * };
89
+ * ```
90
+ */
91
+ declare function useServiceMethod<TPayload = unknown, TResponse = unknown>(serviceName: string, methodName: string, options?: UseServiceOptions<TResponse>): {
92
+ execute: (payload: TPayload) => Promise<TResponse | null>;
93
+ loading: boolean;
94
+ error: string | null;
95
+ data: TResponse | null;
96
+ isReady: boolean;
97
+ };
98
+
99
+ /**
100
+ * Hook for subscribing to real-time entity updates with TanStack Query integration.
101
+ *
102
+ * Features:
103
+ * - Automatic subscription management
104
+ * - TanStack Query cache integration for optimistic updates
105
+ * - Subscription deduplication across components
106
+ * - Automatic reconnection handling
107
+ *
108
+ * @typeParam TData - The entity type
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * function ChatView({ chatId }: { chatId: string }) {
113
+ * const { data: chat, isLoading, error } = useSubscription<Chat>(
114
+ * 'chatService',
115
+ * chatId,
116
+ * {
117
+ * onData: (chat) => {
118
+ * console.log('Chat updated:', chat.title);
119
+ * },
120
+ * }
121
+ * );
122
+ *
123
+ * if (isLoading) return <div>Loading...</div>;
124
+ * if (error) return <div>Error: {error}</div>;
125
+ * if (!chat) return <div>Chat not found</div>;
126
+ *
127
+ * return <div>{chat.title}</div>;
128
+ * }
129
+ * ```
130
+ */
131
+ declare function useSubscription<TData extends {
132
+ id: string;
133
+ }>(serviceName: string, entryId: string | null, options?: UseSubscriptionOptions<TData>): UseSubscriptionResult<TData>;
134
+
135
+ type CommitMode = "change" | "blur" | "debounce";
136
+ interface UseSocketInputOptions<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> {
137
+ /**
138
+ * Current state of the entity
139
+ */
140
+ state: TEntry | null;
141
+ /**
142
+ * Property key to update
143
+ */
144
+ property: TKey;
145
+ /**
146
+ * Update function that sends the patch to the server
147
+ */
148
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
149
+ /**
150
+ * When to commit changes to the server
151
+ * - "change": immediately on every change
152
+ * - "blur": when input loses focus
153
+ * - "debounce": after debounceMs of inactivity
154
+ */
155
+ commitMode: CommitMode;
156
+ /**
157
+ * Debounce delay in milliseconds (only used when commitMode is "debounce")
158
+ */
159
+ debounceMs?: number;
160
+ /**
161
+ * Format the value from state for display
162
+ */
163
+ format?: (value: unknown) => unknown;
164
+ /**
165
+ * Parse the display value back to the state format
166
+ */
167
+ parse?: (value: unknown) => unknown;
168
+ /**
169
+ * Callback on successful update
170
+ */
171
+ onSuccess?: (entry: TEntry | undefined | null) => void;
172
+ /**
173
+ * Callback on error
174
+ */
175
+ onError?: (error: string) => void;
176
+ }
177
+ interface UseSocketInputResult<T> {
178
+ /**
179
+ * Current local value (may differ from server while editing)
180
+ */
181
+ value: T;
182
+ /**
183
+ * Update the local value
184
+ */
185
+ onLocalChange: (newValue: T) => void;
186
+ /**
187
+ * Manually commit the current value to the server
188
+ */
189
+ commit: () => void;
190
+ /**
191
+ * Handle blur event (commits if commitMode is "blur")
192
+ */
193
+ onBlur: () => void;
194
+ /**
195
+ * Whether an update is currently in flight
196
+ */
197
+ inFlight: boolean;
198
+ /**
199
+ * Whether the local value differs from the server value
200
+ */
201
+ isDirty: boolean;
202
+ }
203
+ /**
204
+ * Hook for managing input state that syncs with the server.
205
+ * Handles optimistic updates, debouncing, and conflict resolution.
206
+ */
207
+ declare function useSocketInput<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(options: UseSocketInputOptions<TEntry, TAllowedUpdate, TKey>): UseSocketInputResult<unknown>;
208
+
209
+ interface SocketCheckboxProps<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "checked" | "onChange" | "type" | "onError" | "property"> {
210
+ state: TEntry | null;
211
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
212
+ property: TKey;
213
+ commitMode?: CommitMode;
214
+ debounceMs?: number;
215
+ format?: (v: unknown) => unknown;
216
+ parse?: (v: unknown) => unknown;
217
+ onSuccess?: (entry: TEntry | undefined | null) => void;
218
+ onError?: (error: string) => void;
219
+ }
220
+ /**
221
+ * A checkbox input that syncs its checked state with the server via socket.
222
+ *
223
+ * @example
224
+ * ```tsx
225
+ * <SocketCheckbox
226
+ * state={chat}
227
+ * update={(patch) => updateChat.mutateAsync({ id: chat.id, ...patch })}
228
+ * property="isArchived"
229
+ * commitMode="change"
230
+ * />
231
+ * ```
232
+ */
233
+ declare function SocketCheckbox<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(props: SocketCheckboxProps<TEntry, TAllowedUpdate, TKey>): React.ReactElement;
234
+
235
+ interface SocketTextFieldProps<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "onError" | "property"> {
236
+ state: TEntry | null;
237
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
238
+ property: TKey;
239
+ commitMode?: CommitMode;
240
+ debounceMs?: number;
241
+ format?: (v: unknown) => unknown;
242
+ parse?: (v: unknown) => unknown;
243
+ onSuccess?: (entry: TEntry | undefined | null) => void;
244
+ onError?: (error: string) => void;
245
+ }
246
+ /**
247
+ * A text input that syncs its value with the server via socket.
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * <SocketTextField
252
+ * state={chat}
253
+ * update={(patch) => updateChat.mutateAsync({ id: chat.id, ...patch })}
254
+ * property="title"
255
+ * commitMode="debounce"
256
+ * debounceMs={500}
257
+ * placeholder="Chat title..."
258
+ * />
259
+ * ```
260
+ */
261
+ declare function SocketTextField<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(props: SocketTextFieldProps<TEntry, TAllowedUpdate, TKey>): React.ReactElement;
262
+
263
+ interface SocketSelectProps<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "value" | "onChange" | "onError" | "property"> {
264
+ state: TEntry | null;
265
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
266
+ property: TKey;
267
+ children: React.ReactNode;
268
+ commitMode?: CommitMode;
269
+ debounceMs?: number;
270
+ format?: (v: unknown) => unknown;
271
+ parse?: (v: unknown) => unknown;
272
+ onSuccess?: (entry: TEntry | undefined | null) => void;
273
+ onError?: (error: string) => void;
274
+ }
275
+ /**
276
+ * A select input that syncs its value with the server via socket.
277
+ *
278
+ * @example
279
+ * ```tsx
280
+ * <SocketSelect
281
+ * state={chat}
282
+ * update={(patch) => updateChat.mutateAsync({ id: chat.id, ...patch })}
283
+ * property="status"
284
+ * commitMode="change"
285
+ * >
286
+ * <option value="active">Active</option>
287
+ * <option value="archived">Archived</option>
288
+ * </SocketSelect>
289
+ * ```
290
+ */
291
+ declare function SocketSelect<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(props: SocketSelectProps<TEntry, TAllowedUpdate, TKey>): React.ReactElement;
292
+
293
+ interface SocketSliderProps<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "type" | "onError" | "property"> {
294
+ state: TEntry | null;
295
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
296
+ property: TKey;
297
+ commitMode?: CommitMode;
298
+ debounceMs?: number;
299
+ format?: (v: unknown) => unknown;
300
+ parse?: (v: unknown) => unknown;
301
+ onSuccess?: (entry: TEntry | undefined | null) => void;
302
+ onError?: (error: string) => void;
303
+ }
304
+ /**
305
+ * A range slider input that syncs its value with the server via socket.
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * <SocketSlider
310
+ * state={settings}
311
+ * update={(patch) => updateSettings.mutateAsync({ id: settings.id, ...patch })}
312
+ * property="volume"
313
+ * min={0}
314
+ * max={100}
315
+ * commitMode="debounce"
316
+ * debounceMs={200}
317
+ * />
318
+ * ```
319
+ */
320
+ declare function SocketSlider<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(props: SocketSliderProps<TEntry, TAllowedUpdate, TKey>): React.ReactElement;
321
+
322
+ interface SocketSwitchProps<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate> extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "checked" | "onChange" | "type" | "onError" | "property"> {
323
+ state: TEntry | null;
324
+ update: (patch: Partial<TAllowedUpdate>) => Promise<TEntry | undefined | null>;
325
+ property: TKey;
326
+ commitMode?: CommitMode;
327
+ debounceMs?: number;
328
+ format?: (v: unknown) => unknown;
329
+ parse?: (v: unknown) => unknown;
330
+ onSuccess?: (entry: TEntry | undefined | null) => void;
331
+ onError?: (error: string) => void;
332
+ }
333
+ /**
334
+ * A switch/toggle input that syncs its state with the server via socket.
335
+ * Styled as a switch by default using CSS.
336
+ *
337
+ * @example
338
+ * ```tsx
339
+ * <SocketSwitch
340
+ * state={settings}
341
+ * update={(patch) => updateSettings.mutateAsync({ id: settings.id, ...patch })}
342
+ * property="darkMode"
343
+ * commitMode="change"
344
+ * />
345
+ * ```
346
+ */
347
+ declare function SocketSwitch<TEntry, TAllowedUpdate, TKey extends keyof TAllowedUpdate>(props: SocketSwitchProps<TEntry, TAllowedUpdate, TKey>): React.ReactElement;
348
+
349
+ export { type CommitMode, QuickdrawProvider, QuickdrawProviderProps, QuickdrawSocketContextValue, SocketCheckbox, type SocketCheckboxProps, SocketSelect, type SocketSelectProps, SocketSlider, type SocketSliderProps, SocketSwitch, type SocketSwitchProps, SocketTextField, type SocketTextFieldProps, UseServiceOptions, UseServiceResult, type UseSocketInputOptions, type UseSocketInputResult, UseSubscriptionOptions, UseSubscriptionResult, useQuickdrawSocket, useService, useServiceMethod, useSocketInput, useSubscription };