asasvirtuais 1.1.1 → 2.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/README.md CHANGED
@@ -191,7 +191,7 @@ function DeleteButton({ userId }: { userId: string }) {
191
191
 
192
192
  ## React Interface: Data-Driven Applications
193
193
 
194
- The `react-interface` package provides a complete abstraction for building data-driven React apps. Define your schema once, and get type-safe hooks and components automatically wired to your API.
194
+ The `react-interface` package provides components and hooks for building data-driven React apps. Define your schema once, and use the components directly—no initialization needed.
195
195
 
196
196
  ### Complete Todo App Example
197
197
 
@@ -201,53 +201,68 @@ The `react-interface` package provides a complete abstraction for building data-
201
201
  // app/database.ts
202
202
  import { z } from 'zod';
203
203
 
204
- export const schema = {
205
- todos: {
206
- readable: z.object({
207
- id: z.string(),
208
- text: z.string(),
209
- completed: z.boolean(),
210
- createdAt: z.date(),
211
- }),
212
- writable: z.object({
213
- text: z.string(),
214
- completed: z.boolean().optional(),
215
- }),
216
- },
217
- };
204
+ export const todoSchema = {
205
+ readable: z.object({
206
+ id: z.string(),
207
+ text: z.string(),
208
+ completed: z.boolean(),
209
+ createdAt: z.date(),
210
+ }),
211
+ writable: z.object({
212
+ text: z.string(),
213
+ completed: z.boolean().optional(),
214
+ }),
215
+ }
216
+
217
+ // You can export multiple schemas
218
+ export const userSchema = {
219
+ readable: z.object({
220
+ id: z.string(),
221
+ name: z.string(),
222
+ email: z.string(),
223
+ }),
224
+ writable: z.object({
225
+ name: z.string(),
226
+ email: z.string(),
227
+ }),
228
+ }
218
229
  ```
219
230
 
220
- #### 2. Initialize the Interface
231
+ #### 2. Create Your Table Interface
221
232
 
222
233
  ```typescript
223
234
  // app/interface.ts
224
- import { reactInterface } from '@asasvirtuais/interface';
225
- import { schema } from './database';
226
-
227
- export const {
228
- DatabaseProvider,
229
- useTable,
230
- CreateForm,
231
- UpdateForm,
232
- FilterForm,
233
- SingleProvider,
234
- useSingle,
235
- } = reactInterface<typeof schema>(schema, yourDataInterface);
235
+ import { fetchInterface } from '@asasvirtuais/fetch-interface'
236
+ import { todoSchema } from './database'
237
+
238
+ // Create interface for your API
239
+ export const todosInterface = fetchInterface({
240
+ schema: todoSchema,
241
+ defaultTable: 'todos',
242
+ baseUrl: '/api/v1'
243
+ })
236
244
  ```
237
245
 
238
- #### 3. Provide Data Context
246
+ #### 3. Provide Table Context
239
247
 
240
248
  ```tsx
241
- // app/layout.tsx
242
- import { DatabaseProvider } from '@/app/interface';
249
+ // app/todos/layout.tsx
250
+ import { TableProvider } from '@asasvirtuais/react-interface'
251
+ import { todoSchema, todosInterface } from '@/app/interface'
243
252
 
244
- export default async function RootLayout({ children }) {
245
- const initialTodos = await fetchTodos();
253
+ export default async function TodosLayout({ children }) {
254
+ const initialTodos = await fetchTodos()
255
+
246
256
  return (
247
- <DatabaseProvider todos={initialTodos}>
257
+ <TableProvider
258
+ table="todos"
259
+ schema={todoSchema}
260
+ interface={todosInterface}
261
+ asAbove={initialTodos}
262
+ >
248
263
  {children}
249
- </DatabaseProvider>
250
- );
264
+ </TableProvider>
265
+ )
251
266
  }
252
267
  ```
253
268
 
@@ -255,16 +270,19 @@ export default async function RootLayout({ children }) {
255
270
 
256
271
  ```tsx
257
272
  // app/todos/page.tsx
258
- 'use client';
259
- import { useTable, CreateForm } from '@/app/interface';
273
+ 'use client'
274
+ import { useDatabaseTable, CreateForm } from '@asasvirtuais/react-interface'
275
+ import { todoSchema } from '@/app/database'
260
276
 
261
277
  function TodoList() {
262
- const { array: todos, remove, update } = useTable('todos');
278
+ const { index, remove, update } = useDatabaseTable('todos')
279
+ const todos = Object.values(index.index)
263
280
 
264
281
  return (
265
282
  <>
266
- <CreateForm<typeof schema, 'todos'>
283
+ <CreateForm
267
284
  table="todos"
285
+ schema={todoSchema}
268
286
  defaults={{ text: '' }}
269
287
  >
270
288
  {({ fields, setField, submit, loading }) => (
@@ -300,7 +318,42 @@ function TodoList() {
300
318
  ))}
301
319
  </ul>
302
320
  </>
303
- );
321
+ )
322
+ }
323
+ ```
324
+
325
+ #### 5. Multiple Tables with DatabaseProvider
326
+
327
+ For apps with multiple tables, wrap them all in a DatabaseProvider:
328
+
329
+ ```tsx
330
+ // app/layout.tsx
331
+ import { DatabaseProvider, TableProvider } from '@asasvirtuais/react-interface'
332
+ import { todoSchema, userSchema } from './database'
333
+ import { todosInterface, usersInterface } from './interface'
334
+
335
+ export default async function RootLayout({ children }) {
336
+ const [initialTodos, initialUsers] = await Promise.all([
337
+ fetchTodos(),
338
+ fetchUsers()
339
+ ])
340
+
341
+ return (
342
+ <DatabaseProvider>
343
+ <TableProvider table="todos" schema={todoSchema} interface={todosInterface} asAbove={initialTodos}>
344
+ <TableProvider table="users" schema={userSchema} interface={usersInterface} asAbove={initialUsers}>
345
+ {children}
346
+ </TableProvider>
347
+ </TableProvider>
348
+ </DatabaseProvider>
349
+ )
350
+ }
351
+
352
+ // Now any component can access tables:
353
+ function MyComponent() {
354
+ const todos = useDatabaseTable('todos')
355
+ const users = useDatabaseTable('users')
356
+ // ...
304
357
  }
305
358
  ```
306
359
 
@@ -374,8 +427,12 @@ In React, you control exactly when effects happen by writing code around form ac
374
427
  Run code before submission:
375
428
 
376
429
  ```tsx
377
- <CreateForm<typeof schema, 'messages'>
430
+ import { CreateForm } from '@asasvirtuais/react-interface'
431
+ import { messageSchema } from '@/app/database'
432
+
433
+ <CreateForm
378
434
  table="messages"
435
+ schema={messageSchema}
379
436
  defaults={{ content: '' }}
380
437
  >
381
438
  {({ fields, setField, submit, loading }) => (
@@ -405,8 +462,9 @@ Run code before submission:
405
462
  Run code after successful submission:
406
463
 
407
464
  ```tsx
408
- <CreateForm<typeof schema, 'messages'>
465
+ <CreateForm
409
466
  table="messages"
467
+ schema={messageSchema}
410
468
  defaults={{ content: '' }}
411
469
  onSuccess={(message) => {
412
470
  // Post-flight effects - run after success
@@ -423,6 +481,39 @@ Run code after successful submission:
423
481
  </CreateForm>
424
482
  ```
425
483
 
484
+ #### Using Field Values Without Submitting
485
+
486
+ Sometimes you want to use the form's field values without calling the server action:
487
+
488
+ ```tsx
489
+ <CreateForm
490
+ table="messages"
491
+ schema={messageSchema}
492
+ defaults={{ content: '' }}
493
+ >
494
+ {(form) => (
495
+ <div>
496
+ <textarea
497
+ value={form.fields.content}
498
+ onChange={(e) => form.setField('content', e.target.value)}
499
+ />
500
+
501
+ {/* This button calls the server action */}
502
+ <button onClick={form.submit}>Send to Server</button>
503
+
504
+ {/* This button uses field values without calling the action */}
505
+ <button onClick={() => {
506
+ // Just use the field values directly for local operations
507
+ saveToLocalStorage(form.fields)
508
+ showPreview(form.fields)
509
+ }}>
510
+ Save Draft Locally
511
+ </button>
512
+ </div>
513
+ )}
514
+ </CreateForm>
515
+ ```
516
+
426
517
  ### Backend Effects (API Routes)
427
518
 
428
519
  On the backend, effects are just functions wrapping other functions. No framework magic.
@@ -433,10 +524,10 @@ On the backend, effects are just functions wrapping other functions. No framewor
433
524
  // app/api/v1/[...params]/route.ts
434
525
  import { tableInterface } from '@asasvirtuais/interface'
435
526
  import { firestoreInterface } from '@/lib/firestore'
436
- import { schema } from '@/app/database'
527
+ import { messageSchema } from '@/app/database'
437
528
 
438
529
  // Wrap your base interface with business logic
439
- const messagesInterface = tableInterface(schema, 'messages', {
530
+ const messagesInterface = tableInterface(messageSchema, 'messages', {
440
531
  async create(props) {
441
532
  // Pre-flight validation
442
533
  await checkUserQuota(props.data.userId)
@@ -482,7 +573,7 @@ const messagesInterface = tableInterface(schema, 'messages', {
482
573
  find: firestoreInterface.find,
483
574
  list: firestoreInterface.list,
484
575
  })
485
- ```
576
+
486
577
 
487
578
  ### Key Principles
488
579
 
package/package.json CHANGED
@@ -1,54 +1,54 @@
1
- {
2
- "name": "asasvirtuais",
3
- "type": "module",
4
- "version": "1.1.1",
5
- "description": "React form and action management utilities",
6
- "directories": {
7
- "packages": "./packages"
8
- },
9
- "files": [
10
- "packages",
11
- "tsconfig.json"
12
- ],
13
- "exports": {
14
- "./package.json": "./package.json",
15
- "./action": {
16
- "types": "./packages/action.tsx",
17
- "default": "./packages/action.tsx"
18
- },
19
- "./fields": {
20
- "types": "./packages/fields.tsx",
21
- "default": "./packages/fields.tsx"
22
- },
23
- "./form": {
24
- "types": "./packages/form.tsx",
25
- "default": "./packages/form.tsx"
26
- },
27
- "./hooks": {
28
- "types": "./packages/hooks.tsx",
29
- "default": "./packages/hooks.tsx"
30
- },
31
- "./interface": {
32
- "types": "./packages/interface/interface.ts",
33
- "default": "./packages/interface/interface.ts"
34
- },
35
- "./react-interface": {
36
- "types": "./packages/react-interface.tsx",
37
- "default": "./packages/react-interface.tsx"
38
- },
39
- "./next-interface": {
40
- "types": "./packages/next-interface.tsx",
41
- "default": "./packages/next-interface.tsx"
42
- }
43
- },
44
- "peerDependencies": {
45
- "react": "^19.2.3"
46
- },
47
- "dependencies": {
48
- "next": "^16.1.1",
49
- "zod": "^3.25.76"
50
- },
51
- "devDependencies": {
52
- "@types/react": "^19.2.7"
53
- }
54
- }
1
+ {
2
+ "name": "asasvirtuais",
3
+ "type": "module",
4
+ "version": "2.0.1",
5
+ "description": "React form and action management utilities",
6
+ "directories": {
7
+ "packages": "./packages"
8
+ },
9
+ "files": [
10
+ "packages",
11
+ "tsconfig.json"
12
+ ],
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ "./action": {
16
+ "types": "./packages/action.tsx",
17
+ "default": "./packages/action.tsx"
18
+ },
19
+ "./fields": {
20
+ "types": "./packages/fields.tsx",
21
+ "default": "./packages/fields.tsx"
22
+ },
23
+ "./form": {
24
+ "types": "./packages/form.tsx",
25
+ "default": "./packages/form.tsx"
26
+ },
27
+ "./hooks": {
28
+ "types": "./packages/hooks.tsx",
29
+ "default": "./packages/hooks.tsx"
30
+ },
31
+ "./interface": {
32
+ "types": "./packages/interface/interface.ts",
33
+ "default": "./packages/interface/interface.ts"
34
+ },
35
+ "./react-interface": {
36
+ "types": "./packages/react-interface.tsx",
37
+ "default": "./packages/react-interface.tsx"
38
+ },
39
+ "./next-interface": {
40
+ "types": "./packages/next-interface.tsx",
41
+ "default": "./packages/next-interface.tsx"
42
+ }
43
+ },
44
+ "peerDependencies": {
45
+ "react": "^19.2.3"
46
+ },
47
+ "dependencies": {
48
+ "next": "^16.1.1",
49
+ "zod": "^4.3.5"
50
+ },
51
+ "devDependencies": {
52
+ "@types/react": "^19.2.8"
53
+ }
54
+ }
@@ -80,7 +80,7 @@ export function useIndex<T>(value: Record<string, any>) {
80
80
  }))
81
81
  }, [])
82
82
 
83
- const remove = useCallback((...params: readable[]) => {
83
+ const unset = useCallback((...params: readable[]) => {
84
84
  setIndex(prev => {
85
85
  const newState = { ...prev }
86
86
  for (const data of params) {
@@ -97,7 +97,7 @@ export function useIndex<T>(value: Record<string, any>) {
97
97
  array,
98
98
  set,
99
99
  setIndex,
100
- remove,
100
+ unset,
101
101
  }
102
102
  }
103
103
 
@@ -8,12 +8,14 @@ export interface TableInterface<Readable, Writable = Readable> {
8
8
  list (props: ListProps<Readable>) : Promise<Readable[]>
9
9
  }
10
10
 
11
- export function tableInterface<Schema extends DatabaseInterface, Table extends keyof Schema & string>(schema: Schema, table?: Table | null, tableInterface?: TableInterface<z.infer<Schema[Table]['readable']>, z.infer<Schema[Table]['writable']>>) {
11
+ export function tableInterface<Schema extends DatabaseSchema, Table extends keyof Schema & string>(schema: Schema, table?: Table | null, tableInterface?: TableInterface<z.infer<Schema[Table]['readable']>, z.infer<Schema[Table]['writable']>>) {
12
12
  return tableInterface
13
13
  }
14
14
 
15
- export interface DatabaseInterface {
16
- [T: string]: { readable: z.ZodObject<z.ZodRawShape>, writable: z.ZodObject<z.ZodRawShape> }
15
+ export interface TableSchema { readable: z.ZodObject<z.ZodRawShape>, writable: z.ZodObject<z.ZodRawShape> }
16
+
17
+ export interface DatabaseSchema {
18
+ [T: string]: TableSchema
17
19
  }
18
20
 
19
21
  export type BasicOperators<T, K extends keyof T> = {
@@ -1,394 +1,315 @@
1
- import { z } from 'zod'
2
- import {
3
- useState,
4
- useCallback,
5
- useEffect,
6
- useMemo,
7
- createContext,
8
- useContext,
9
- } from 'react'
10
- import { useAction as useAsyncAction, useIndex } from './hooks'
1
+ import z from 'zod'
2
+ import { DatabaseSchema, ListProps, TableInterface, TableSchema } from './interface'
3
+ import { createContextFromHook, useAction as useAsyncAction, useIndex } from './hooks'
4
+ import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from 'react'
11
5
  import { ActionProvider, useAction, useActionProvider } from './action'
12
- import { TableInterface, ListProps, DatabaseInterface } from './interface'
13
6
  import { FieldsProvider, useFields } from './fields'
14
7
 
15
- export function reactInterface<
16
- Database extends DatabaseInterface>(
17
- database: Database,
18
- {
19
- find,
20
- create,
21
- update,
22
- remove,
23
- list,
24
- }: TableInterface<
25
- z.infer<Database[keyof Database]['readable']>,
26
- z.infer<Database[keyof Database]['writable']>
27
- >
28
- ) {
29
- type TableKey = keyof Database & string
30
-
31
- type TableProviderProps<Table extends TableKey> = {
32
- table: Table
33
- asAbove?: Record<string, z.infer<Database[Table]["readable"]>>
34
- }
35
-
36
- function useTableProvider<Table extends TableKey>({
37
- table,
38
- asAbove,
39
- }: TableProviderProps<Table>) {
40
- type Readable = z.infer<Database[Table]['readable']>
41
- type Writable = z.infer<Database[Table]['writable']>
42
-
43
- const index = useIndex<Readable>({ ...(asAbove ?? {}) })
8
+ export function useDatabaseProvider() {
44
9
 
45
- const array = useMemo(
46
- () => Object.values(index.index) as Readable[],
47
- [index.index]
48
- )
49
-
50
- useEffect(function soBelow() {
51
- index.setIndex((prev) => ({ ...prev, ...asAbove }))
52
- }, [])
53
-
54
- const methods = { find, create, update, remove, list } as TableInterface<
55
- Readable,
56
- Writable
57
- >
10
+ const [database, setDatabase] = useState<Record<string, ReturnType<typeof useInterface<any>>>>({})
58
11
 
59
12
  return {
60
- ...index,
61
- array,
62
- find: useAsyncAction(((props) => methods.find({ ...props, table }).then(res => {
63
- index.set(res)
64
- return res
65
- })) as typeof find),
66
- create: useAsyncAction(((props) => create({ ...props, table }).then(res => {
67
- index.set(res)
68
- return res
69
- })) as typeof create),
70
- update: useAsyncAction(((props) => update({ ...props, table }).then(res => {
71
- index.set(res)
72
- return res
73
- })) as typeof update),
74
- remove: useAsyncAction(((props) => remove({ ...props, table }).then(res => {
75
- index.remove(res)
76
- return res
77
- })) as typeof remove),
78
- list: useAsyncAction(((props) => list({ ...props, table }).then(arr => {
79
- index.set(...arr)
80
- return arr
81
- })) as typeof list),
13
+ database,
14
+ setDatabase,
82
15
  }
83
- }
84
-
85
- function useDatabaseProvider(tables: { [T in TableKey]: Record<string, z.infer<Database[T]['readable']>> }): {
86
- [T in TableKey]: ReturnType<typeof useTableProvider<T>>
87
- } {
88
- return Object.fromEntries(
89
- Object.entries(tables).map(([table, value]) => [
90
- table, useTableProvider({ table: table as TableKey, asAbove: value })
91
- ])
92
- ) as {
93
- [T in TableKey]: ReturnType<typeof useTableProvider<T>>
94
- }
95
- }
96
- // Create a separate context for each table dynamically
97
- const tableContexts = new Map<TableKey, React.Context<ReturnType<typeof useTableProvider<any>> | undefined>>();
98
-
99
- function getTableContext<T extends TableKey>(table: T) {
100
- if (!tableContexts.has(table)) {
101
- tableContexts.set(table, createContext<ReturnType<typeof useTableProvider<T>> | undefined>(undefined));
102
- }
103
- return tableContexts.get(table)!;
104
- }
16
+ }
105
17
 
106
- function TableProvider<Table extends TableKey>({ children, ...props }: TableProviderProps<Table> & {
107
- children: React.ReactNode | ((props: ReturnType<typeof useTableProvider<Table>>) => React.ReactNode)
108
- }) {
109
- const context = useTableProvider(props);
110
- const TableContext = getTableContext(props.table);
18
+ export const [DatabaseProvider, useDatabase] = createContextFromHook(useDatabaseProvider)
111
19
 
112
- return (
113
- <TableContext.Provider value={context}>
114
- {typeof children === 'function' ? children(context) : children}
115
- </TableContext.Provider>
116
- )
117
- }
20
+ export function useDatabaseTable<TSchema extends TableSchema>(table: string) {
21
+ const { database } = useDatabase()
118
22
 
119
- function DatabaseProvider({
120
- children,
121
- ...tables
122
- }: React.PropsWithChildren<{
123
- [T in TableKey]?: Record<string, z.infer<Database[T]['readable']>>
124
- }>) {
125
- return Object.entries(tables).reduce(
126
- (prev, [table, asAbove]) => {
127
- return (
128
- <TableProvider table={table as TableKey} asAbove={asAbove}>
129
- {prev}
130
- </TableProvider>
131
- );
132
- },
133
- children as React.ReactNode
134
- )
135
- }
23
+ const tableMethods = database[table] as ReturnType<typeof useInterface<TSchema>> | undefined
136
24
 
137
- function useTable<T extends TableKey>(name: T) {
138
- const TableContext = getTableContext(name);
139
- const context = useContext(TableContext);
25
+ if (!tableMethods)
26
+ throw new Error(`Table "${table}" is not defined in the database schema.`)
140
27
 
141
- if (!context) {
142
- throw new Error(`useTable("${String(name)}") must be used within a TableProvider or DatabaseProvider with table="${String(name)}"`);
143
- }
28
+ return tableMethods
29
+ }
144
30
 
145
- return context as ReturnType<typeof useTableProvider<T>>;
146
- }
31
+ export type TableProviderProps<TSchema extends TableSchema> = {
32
+ table: string
33
+ schema: TSchema
34
+ interface: TableInterface<z.infer<TSchema['readable']>, z.infer<TSchema['writable']>>
35
+ asAbove?: Record<string, z.infer<TSchema['readable']>>
36
+ }
147
37
 
148
- function useSingleProvider<Table extends TableKey>({
149
- id,
150
- table,
151
- }: {
152
- id: string
153
- table: Table
154
- }) {
155
- const { index, find } = useTable(table)
156
- const [single, setSingle] = useState<z.infer<Database[Table]['readable']>>(
157
- () => index[id]
158
- )
159
- useEffect(() => {
160
- if (!single) find.trigger({ id }).then(setSingle)
161
- }, [])
162
- useEffect(() => {
163
- setSingle(index[id])
164
- }, [index[id]])
38
+ export function useInterface<TSchema extends TableSchema>(table: string, {
39
+ find, create, update, remove, list
40
+ }: TableInterface<z.infer<TSchema['readable']>, z.infer<TSchema['writable']>>, index: ReturnType<typeof useIndex<z.infer<TSchema['readable']>>>) {
165
41
  return {
166
- id,
167
- single,
168
- setSingle,
169
- loading: find.loading,
42
+ index,
43
+ find: useAsyncAction(((props) => find({ ...props, table }).then(res => {
44
+ index.set(res)
45
+ return res
46
+ })) as typeof find),
47
+ create: useAsyncAction(((props) => create({ ...props, table }).then(res => {
48
+ index.set(res)
49
+ return res
50
+ })) as typeof create),
51
+ update: useAsyncAction(((props) => update({ ...props, table }).then(res => {
52
+ index.set(res)
53
+ return res
54
+ })) as typeof update),
55
+ remove: useAsyncAction(((props) => remove({ ...props, table }).then(res => {
56
+ index.unset(res)
57
+ return res
58
+ })) as typeof remove),
59
+ list: useAsyncAction(((props) => list({ ...props, table }).then(arr => {
60
+ index.set(...arr)
61
+ return arr
62
+ })) as typeof list),
170
63
  }
171
- }
64
+ }
172
65
 
173
- const SingleContext = createContext<
174
- ReturnType<typeof useSingleProvider<any>> | undefined
175
- >(undefined)
66
+ export function useTableProvider<TSchema extends TableSchema>({
67
+ table,
68
+ schema,
69
+ interface: tableInterface,
70
+ asAbove,
71
+ }: TableProviderProps<TSchema>) {
176
72
 
177
- function SingleProvider<Table extends TableKey>({
178
- children,
179
- ...props
180
- }: {
181
- id: string
182
- table: Table
183
- children: React.ReactNode | ((props: ReturnType<typeof useSingleProvider<Table>>) => React.ReactNode)
184
- }) {
185
- const value = useSingleProvider(props)
186
- if (!value.single) return null
187
- return (
188
- <SingleContext.Provider value={value}>
189
- {typeof children === 'function' ? (
190
- children(value)
191
- ) : (
192
- children
193
- )}
194
- </SingleContext.Provider>
195
- )
196
- }
73
+ type Readable = z.infer<TableSchema['readable']>
197
74
 
198
- const useSingle = <Table extends TableKey>(table: Table) =>
199
- useContext(SingleContext) as ReturnType<typeof useSingleProvider<Table>>
75
+ const index = useIndex<Readable>({ ...(asAbove ?? {}) })
200
76
 
201
- function CreateForm<
202
- T extends TableKey
203
- >({
204
- table,
205
- defaults,
206
- onSuccess,
207
- children,
208
- }: {
209
- table: T
210
- defaults?: Partial<z.infer<Database[T]['writable']>>
211
- onSuccess?: (result: z.infer<Database[T]['readable']>) => void
77
+ useEffect(function soBelow() {
78
+ index.setIndex((prev) => ({ ...prev, ...asAbove }))
79
+ }, [])
80
+
81
+ return useInterface<TSchema>(table, tableInterface, index)
82
+ }
83
+
84
+ export const [TableContextProvider, useTableContext] = createContextFromHook(useTableProvider<any>)
85
+
86
+ export function TableProvider<TSchema extends TableSchema>({ children, ...props }: PropsWithChildren<TableProviderProps<TSchema>>) {
87
+ return <TableContextProvider {...props}>{children}</TableContextProvider>
88
+ }
89
+
90
+ export function useTable<TSchema extends TableSchema>() {
91
+ return useTableContext() as ReturnType<typeof useTableProvider<TSchema>>
92
+ }
93
+
94
+ export function CreateForm<TSchema extends TableSchema>({ table, defaults, onSuccess, children }: {
95
+ table: string
96
+ schema: TSchema
97
+ defaults?: Partial<z.infer<TSchema['writable']>>
98
+ onSuccess?: (result: z.infer<TSchema['readable']>) => void
212
99
  children: React.ReactNode | (
213
- ( props: ReturnType<typeof useActionProvider<z.infer<Database[T]['writable']>, z.infer<Database[T]['readable']>>>
214
- & ReturnType<typeof useFields<z.infer<Database[T]['writable']>>>
215
- ) => React.ReactNode
100
+ (props: ReturnType<typeof useActionProvider<z.infer<TSchema['writable']>, z.infer<TSchema['readable']>>>
101
+ & ReturnType<typeof useFields<z.infer<TSchema['writable']>>>
102
+ ) => React.ReactNode
216
103
  )
217
- }) {
218
- type Readable = z.infer<Database[T]['readable']>
219
- type Writable = z.infer<Database[T]['writable']>
104
+ }) {
105
+ type Readable = z.infer<TSchema['readable']>
106
+ type Writable = z.infer<TSchema['writable']>
220
107
 
221
- const { create } = useTable(table)
108
+ const { create } = useDatabaseTable(table)
222
109
 
223
110
  const callback = useCallback(
224
- async (fields: Writable) => {
225
- const result = await create.trigger({ data: fields })
226
- if (onSuccess) onSuccess(result as Readable)
227
- return result
228
- },
229
- [create, onSuccess]
111
+ async (fields: Writable) => {
112
+ const result = await create.trigger({ data: fields })
113
+ if (onSuccess) onSuccess(result as Readable)
114
+ return result
115
+ },
116
+ [create, onSuccess]
230
117
  )
231
118
 
232
119
  return (
233
- <FieldsProvider<Writable> defaults={defaults || ({} as Writable)}>
234
- {fields => (
235
- <ActionProvider<Writable, Readable> action={callback} params={fields.fields}>
236
- {typeof children === 'function' ? (
237
- form => children({ ...form, ...fields })
238
- ) : (
239
- children
120
+ <FieldsProvider<Writable> defaults={defaults || ({} as Writable)}>
121
+ {fields => (
122
+ <ActionProvider<Writable, Readable> action={callback} params={fields.fields}>
123
+ {typeof children === 'function' ? (
124
+ form => children({ ...form, ...fields })
125
+ ) : (
126
+ children
127
+ )}
128
+ </ActionProvider>
240
129
  )}
241
- </ActionProvider>
242
- )}
243
- </FieldsProvider>
130
+ </FieldsProvider>
244
131
  )
245
- }
132
+ }
246
133
 
247
- function UpdateForm<T extends TableKey>({
134
+ export function UpdateForm<TSchema extends TableSchema>({
135
+ schema,
248
136
  table,
249
137
  id,
250
138
  defaults,
251
139
  onSuccess,
252
140
  children,
253
- }: {
254
- table: T
141
+ }: {
142
+ schema: TSchema
143
+ table: string
255
144
  id: string
256
- defaults?: Partial<z.infer<Database[T]['writable']>>
257
- onSuccess?: (result: z.infer<Database[T]['readable']>) => void
145
+ defaults?: Partial<z.infer<TSchema['writable']>>
146
+ onSuccess?: (result: z.infer<TSchema['readable']>) => void
258
147
  children: React.ReactNode | (
259
- (props: ReturnType<typeof useActionProvider<Partial<z.infer<Database[T]['writable']>>, z.infer<Database[T]['readable']>>>
260
- & ReturnType<typeof useFields<z.infer<Database[T]['writable']>>>
261
- ) => React.ReactNode
148
+ (props: ReturnType<typeof useActionProvider<Partial<z.infer<TSchema['writable']>>, z.infer<TSchema['readable']>>>
149
+ & ReturnType<typeof useFields<z.infer<TSchema['writable']>>>
150
+ ) => React.ReactNode
262
151
  )
263
- }) {
264
- type Readable = z.infer<Database[T]['readable']>
265
- type Writable = z.infer<Database[T]['writable']>
152
+ }) {
153
+ type Readable = z.infer<TSchema['readable']>
154
+ type Writable = z.infer<TSchema['writable']>
266
155
 
267
- const { update } = useTable(table)
156
+ const { update } = useDatabaseTable(table)
268
157
 
269
158
  const callback = useCallback(
270
- async (fields: Partial<Writable>) => {
271
- const result = await update.trigger({ id, data: fields })
272
- if (onSuccess) onSuccess(result as Readable)
273
- return result
274
- },
275
- [update, id, onSuccess]
159
+ async (fields: Partial<Writable>) => {
160
+ const result = await update.trigger({ id, data: fields })
161
+ if (onSuccess) onSuccess(result as Readable)
162
+ return result
163
+ },
164
+ [update, id, onSuccess]
276
165
  )
277
166
 
278
167
  return (
279
- <FieldsProvider<Writable>
280
- defaults={defaults || ({} as Partial<Writable>)}
281
- >
282
- {fields => (
283
- <ActionProvider<Partial<Writable>, Readable> action={callback} params={fields.fields}>
284
- {typeof children === 'function' ? (
285
- form => children({ ...form, ...fields })
286
- ) : (
287
- children
168
+ <FieldsProvider<Writable>
169
+ defaults={defaults || ({} as Partial<Writable>)}
170
+ >
171
+ {fields => (
172
+ <ActionProvider<Partial<Writable>, Readable> action={callback} params={fields.fields}>
173
+ {typeof children === 'function' ? (
174
+ form => children({ ...form, ...fields })
175
+ ) : (
176
+ children
177
+ )}
178
+ </ActionProvider>
288
179
  )}
289
- </ActionProvider>
290
- )}
291
- </FieldsProvider>
180
+ </FieldsProvider>
292
181
  )
293
- }
182
+ }
294
183
 
295
- function FilterForm<T extends TableKey>({
184
+ export function FilterForm<TSchema extends TableSchema>({
185
+ schema,
296
186
  table,
297
187
  defaults,
298
188
  onSuccess,
299
189
  children,
300
- }: {
301
- table: T
302
- defaults?: Partial<ListProps<z.infer<Database[T]['readable']>>>
303
- onSuccess?: (result: z.infer<Database[T]['readable']>[]) => void
190
+ }: {
191
+ schema: TSchema
192
+ table: string
193
+ defaults?: Partial<ListProps<z.infer<TSchema['readable']>>>
194
+ onSuccess?: (result: z.infer<TSchema['readable']>[]) => void
304
195
  children: React.ReactNode | (
305
- ( props: ReturnType<typeof useActionProvider<ListProps<z.infer<Database[T]['readable']>>, z.infer<Database[T]['readable']>[]>>
306
- & ReturnType<typeof useFields<ListProps<z.infer<Database[T]['readable']>>>>
307
- ) => React.ReactNode
196
+ (props: ReturnType<typeof useActionProvider<ListProps<z.infer<TSchema['readable']>>, z.infer<TSchema['readable']>[]>>
197
+ & ReturnType<typeof useFields<ListProps<z.infer<TSchema['readable']>>>>
198
+ ) => React.ReactNode
308
199
  )
309
- }) {
310
- type Readable = z.infer<Database[T]['readable']>
311
- type Writable = z.infer<Database[T]['writable']>
200
+ }) {
201
+ type Readable = z.infer<TSchema['readable']>
312
202
 
313
- const { list } = useTable(table)
203
+ const { list } = useDatabaseTable(table)
314
204
 
315
205
  const callback = useCallback(
316
- async (fields: Omit<ListProps<Readable>, 'table'>) => {
317
- const result = await list.trigger(fields)
318
- if (onSuccess) onSuccess(result)
319
- return result
320
- },
321
- [list, onSuccess]
206
+ async (fields: Omit<ListProps<Readable>, 'table'>) => {
207
+ const result = await list.trigger(fields)
208
+ if (onSuccess) onSuccess(result)
209
+ return result
210
+ },
211
+ [list, onSuccess]
322
212
  )
323
213
 
324
214
  return (
325
- <FieldsProvider<ListProps<Readable>>
326
- defaults={(defaults || { query: {} }) as ListProps<Readable>}
327
- >
328
- {fields => (
329
- <ActionProvider<ListProps<Readable>, Readable[]> action={callback} params={fields.fields}>
330
- {typeof children === 'function' ? (
331
- form => children({ ...form, ...fields })
332
- ) : (
333
- children
215
+ <FieldsProvider<ListProps<Readable>>
216
+ defaults={(defaults || { query: {} }) as ListProps<Readable>}
217
+ >
218
+ {fields => (
219
+ <ActionProvider<ListProps<Readable>, Readable[]> action={callback} params={fields.fields}>
220
+ {typeof children === 'function' ? (
221
+ form => children({ ...form, ...fields })
222
+ ) : (
223
+ children
224
+ )}
225
+ </ActionProvider>
334
226
  )}
335
- </ActionProvider>
336
- )}
337
- </FieldsProvider>
227
+ </FieldsProvider>
338
228
  )
339
- }
229
+ }
340
230
 
341
- const useCreateForm = <T extends TableKey>(table: T) => {
231
+ export function useCreateForm<TSchema extends TableSchema>(schema: TSchema) {
342
232
  return {
343
- ...useFields<z.infer<Database[T]['writable']>>(),
344
- ...useAction<
345
- z.infer<Database[T]['writable']>,
346
- z.infer<Database[T]['readable']>
347
- >(),
233
+ ...useFields<z.infer<TSchema['writable']>>(),
234
+ ...useAction<
235
+ z.infer<TSchema['writable']>,
236
+ z.infer<TSchema['readable']>
237
+ >()
348
238
  }
349
- }
350
- const useUpdateForm = <T extends TableKey>(table: T) => {
239
+ }
240
+
241
+ export function useUpdateForm<TSchema extends TableSchema>(schema: TSchema) {
351
242
  return {
352
- ...useFields<Partial<z.infer<Database[T]['writable']>>>(),
353
- ...useAction<
354
- Partial<z.infer<Database[T]['writable']>>,
355
- z.infer<Database[T]['readable']>
356
- >(),
243
+ ...useFields<Partial<z.infer<TSchema['writable']>>>(),
244
+ ...useAction<
245
+ Partial<z.infer<TSchema['writable']>>,
246
+ z.infer<TSchema['readable']>
247
+ >()
357
248
  }
358
- }
359
- const useFiltersForm = <T extends TableKey>(table: T) => {
249
+ }
250
+
251
+ export function useFiltersForm <TSchema extends TableSchema>(schema: TSchema) {
360
252
  return {
361
- ...useFields<z.infer<Database[T]['readable']>>(),
362
- ...useAction<z.infer<Database[T]['readable']>,
363
- z.infer<Database[T]['readable']>[]
364
- >(),
253
+ ...useFields<z.infer<TSchema['readable']>>(),
254
+ ...useAction<z.infer<TSchema['readable']>,
255
+ z.infer<TSchema['readable']>[]
256
+ >()
365
257
  }
366
- }
367
-
368
- return {
369
- DatabaseProvider,
370
- useTable,
371
- useTableProvider,
372
- TableProvider,
373
- SingleProvider,
374
- useSingle,
375
- CreateForm,
376
- UpdateForm,
377
- FilterForm,
378
- useCreateForm,
379
- useUpdateForm,
380
- useFiltersForm,
381
- }
382
258
  }
383
259
 
384
- export class ReactInterface<Database extends DatabaseInterface> {
385
- constructor(
386
- database: Database,
387
- tableInterface: TableInterface<
388
- z.infer<Database[keyof Database]['readable']>,
389
- z.infer<Database[keyof Database]['writable']>
390
- >
391
- ) {
392
- return reactInterface(database, tableInterface)
393
- }
260
+ export function useSingleProvider<T>({
261
+ id,
262
+ table,
263
+ }: {
264
+ id: string
265
+ table: string
266
+ }) {
267
+ const { index, find } = useDatabaseTable(table)
268
+ const [single, setSingle] = useState<T>(
269
+ // @ts-expect-error
270
+ () => index[id as keyof typeof index]
271
+ )
272
+ useEffect(() => {
273
+ // @ts-expect-error
274
+ if (!single) find.trigger({ id }).then(setSingle)
275
+ }, [])
276
+ useEffect(() => {
277
+ // @ts-expect-error
278
+ setSingle(index[id as keyof typeof index])
279
+ }, [index[id as keyof typeof index]])
280
+ return {
281
+ id,
282
+ single,
283
+ setSingle,
284
+ loading: find.loading,
285
+ }
286
+ }
287
+
288
+ export const SingleContext = createContext<
289
+ ReturnType<typeof useSingleProvider> | undefined
290
+ >(undefined)
291
+
292
+ export function SingleProvider<T>({
293
+ children,
294
+ ...props
295
+ }: {
296
+ id: string
297
+ table: string
298
+ children: React.ReactNode | ((props: ReturnType<typeof useSingleProvider>) => React.ReactNode)
299
+ }) {
300
+ const value = useSingleProvider(props)
301
+ if (!value.single) return null
302
+ return (
303
+ <SingleContext.Provider value={value}>
304
+ {typeof children === 'function' ? (
305
+ children(value)
306
+ ) : (
307
+ children
308
+ )}
309
+ </SingleContext.Provider>
310
+ )
311
+ }
312
+
313
+ export function useSingle<T>() {
314
+ return useContext(SingleContext) as ReturnType<typeof useSingleProvider<T>>
394
315
  }