asasvirtuais 2.1.3 → 2.1.5
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 +192 -2
- package/package.json +13 -4
- package/packages/react-interface.tsx +125 -34
- package/tsconfig.json +41 -0
package/README.md
CHANGED
|
@@ -573,7 +573,7 @@ const messagesInterface = tableInterface(messageSchema, 'messages', {
|
|
|
573
573
|
find: firestoreInterface.find,
|
|
574
574
|
list: firestoreInterface.list,
|
|
575
575
|
})
|
|
576
|
-
|
|
576
|
+
```
|
|
577
577
|
|
|
578
578
|
### Key Principles
|
|
579
579
|
|
|
@@ -673,6 +673,196 @@ Give AI the asasvirtuais documentation and watch it generate multi-step forms wi
|
|
|
673
673
|
Try it with [Google AI Studio](https://ai.studio/apps/drive/1-MwQzpbgMZhRqSbpqQYX1IRpvj61F_l8).
|
|
674
674
|
|
|
675
675
|
|
|
676
|
+
# Model Package Instructions
|
|
677
|
+
|
|
678
|
+
A model package is a self-contained module that defines a data model and provides React components for interacting with that data. Based on the chat example, here's how to structure a model package:
|
|
679
|
+
|
|
680
|
+
## File Structure
|
|
681
|
+
|
|
682
|
+
```
|
|
683
|
+
app/[model-name]/
|
|
684
|
+
├── index.ts # Schema definitions and types
|
|
685
|
+
├── fields.tsx # Individual form field components
|
|
686
|
+
├── forms.tsx # Complete form components
|
|
687
|
+
└── table.tsx # Provider and hooks for data access
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
## 1. Schema Definition (`index.ts`)
|
|
691
|
+
|
|
692
|
+
Define your data model using Zod schemas:
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
import z from 'zod'
|
|
696
|
+
|
|
697
|
+
// Define the complete object structure (what comes from the database)
|
|
698
|
+
export const readable = z.object({
|
|
699
|
+
id: z.string(),
|
|
700
|
+
// ... other fields that can be read
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
// Define which fields can be written/modified
|
|
704
|
+
export const writable = readable.pick({
|
|
705
|
+
// ... fields that can be created/updated
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
// Export the schema object
|
|
709
|
+
export const schema = { readable, writable }
|
|
710
|
+
|
|
711
|
+
// Export TypeScript types
|
|
712
|
+
export type Readable = z.infer<typeof readable>
|
|
713
|
+
export type Writable = z.infer<typeof writable>
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
**Key Points:**
|
|
717
|
+
- `readable`: Full object schema including `id` and all readable fields
|
|
718
|
+
- `writable`: Subset of fields that users can create/modify (typically excludes `id`)
|
|
719
|
+
- Use `.pick()` to select fields from readable for writable
|
|
720
|
+
- Export both the schema object and TypeScript types
|
|
721
|
+
|
|
722
|
+
## 2. Field Components (`fields.tsx`)
|
|
723
|
+
|
|
724
|
+
Create reusable field components for individual form inputs:
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
import { Input, InputProps } from '@chakra-ui/react'
|
|
728
|
+
import { useFields } from 'asasvirtuais/fields'
|
|
729
|
+
|
|
730
|
+
export function [FieldName]Field(props: InputProps) {
|
|
731
|
+
const { fields, setField } = useFields<{fieldName: type}>()
|
|
732
|
+
|
|
733
|
+
return (
|
|
734
|
+
<Input
|
|
735
|
+
name='fieldName'
|
|
736
|
+
value={fields.fieldName}
|
|
737
|
+
onChange={e => setField('fieldName', e.target.value)}
|
|
738
|
+
{...props}
|
|
739
|
+
/>
|
|
740
|
+
)
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Key Points:**
|
|
745
|
+
- Use `useFields` hook with type annotation matching your field
|
|
746
|
+
- Pass through additional props using spread operator
|
|
747
|
+
- Set appropriate `name` attribute
|
|
748
|
+
- Handle `onChange` with `setField`
|
|
749
|
+
|
|
750
|
+
## 3. Form Components (`forms.tsx`)
|
|
751
|
+
|
|
752
|
+
Create complete forms for creating and filtering data:
|
|
753
|
+
|
|
754
|
+
```typescript
|
|
755
|
+
import { CreateForm, FilterForm, useTableInterface } from 'asasvirtuais/react-interface'
|
|
756
|
+
import { schema } from '.'
|
|
757
|
+
import { [Field]Field } from './fields'
|
|
758
|
+
import { Stack, Button } from '@chakra-ui/react'
|
|
759
|
+
|
|
760
|
+
// Create form
|
|
761
|
+
export function Create[Model]() {
|
|
762
|
+
return (
|
|
763
|
+
<CreateForm table='tableName' schema={schema}>
|
|
764
|
+
{form => (
|
|
765
|
+
<Stack as='form' onSubmit={form.submit}>
|
|
766
|
+
<[Field]Field />
|
|
767
|
+
<Button type='submit'>Create [Model]</Button>
|
|
768
|
+
</Stack>
|
|
769
|
+
)}
|
|
770
|
+
</CreateForm>
|
|
771
|
+
)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Filter/List form
|
|
775
|
+
export function Filter[Models]() {
|
|
776
|
+
const { remove } = useTableInterface('tableName', schema)
|
|
777
|
+
|
|
778
|
+
return (
|
|
779
|
+
<FilterForm table='tableName' schema={schema}>
|
|
780
|
+
{form => (
|
|
781
|
+
<Stack>
|
|
782
|
+
{form.result?.map(item => (
|
|
783
|
+
<div key={item.id}>
|
|
784
|
+
{/* Display item data */}
|
|
785
|
+
<Button onClick={() => remove.trigger({id: item.id})}>
|
|
786
|
+
Delete
|
|
787
|
+
</Button>
|
|
788
|
+
</div>
|
|
789
|
+
))}
|
|
790
|
+
</Stack>
|
|
791
|
+
)}
|
|
792
|
+
</FilterForm>
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**Key Points:**
|
|
798
|
+
- `CreateForm` handles creation logic, provides `form.submit`
|
|
799
|
+
- `FilterForm` provides `form.result` with filtered data
|
|
800
|
+
- Use `useTableInterface` for operations like `remove`
|
|
801
|
+
- Always provide `table` name and `schema` to forms
|
|
802
|
+
|
|
803
|
+
## 4. Provider and Hooks (`table.tsx`)
|
|
804
|
+
|
|
805
|
+
Set up the data provider and custom hooks:
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
'use client'
|
|
809
|
+
import { TableProvider, useTableInterface } from 'asasvirtuais/react-interface'
|
|
810
|
+
import { fetchInterface } from 'asasvirtuais/fetch-interface'
|
|
811
|
+
import { schema } from '.'
|
|
812
|
+
|
|
813
|
+
export function use[Models]() {
|
|
814
|
+
return useTableInterface<typeof schema>('tableName')
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export function [Models]Provider({ children }: { children: React.ReactNode }) {
|
|
818
|
+
return (
|
|
819
|
+
<TableProvider
|
|
820
|
+
table='tableName'
|
|
821
|
+
schema={schema}
|
|
822
|
+
interface={fetchInterface({
|
|
823
|
+
schema,
|
|
824
|
+
baseUrl: '/api/v1',
|
|
825
|
+
defaultTable: 'tableName'
|
|
826
|
+
})}
|
|
827
|
+
>
|
|
828
|
+
{children}
|
|
829
|
+
</TableProvider>
|
|
830
|
+
)
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
**Key Points:**
|
|
835
|
+
- Mark as `'use client'` for Next.js
|
|
836
|
+
- Create a custom hook for easy access to table interface
|
|
837
|
+
- Provide `fetchInterface` configuration with baseUrl and table name
|
|
838
|
+
- Wrap your app/components with the Provider to enable data access
|
|
839
|
+
|
|
840
|
+
## Naming Conventions
|
|
841
|
+
|
|
842
|
+
- **Model name**: Singular (e.g., `chat`, `user`, `product`)
|
|
843
|
+
- **Table name**: Plural (e.g., `chats`, `users`, `products`)
|
|
844
|
+
- **Field components**: `[Field]Field` (e.g., `TitleField`, `EmailField`)
|
|
845
|
+
- **Form components**: `Create[Model]`, `Filter[Models]` (e.g., `CreateChat`, `FilterChats`)
|
|
846
|
+
- **Provider**: `[Models]Provider` (e.g., `ChatsProvider`)
|
|
847
|
+
- **Hook**: `use[Models]` (e.g., `useChats`)
|
|
848
|
+
|
|
849
|
+
## Usage Example
|
|
850
|
+
|
|
851
|
+
```typescript
|
|
852
|
+
import { ChatsProvider } from './chat/table'
|
|
853
|
+
import { CreateChat, FilterChats } from './chat/forms'
|
|
854
|
+
|
|
855
|
+
function App() {
|
|
856
|
+
return (
|
|
857
|
+
<ChatsProvider>
|
|
858
|
+
<CreateChat />
|
|
859
|
+
<FilterChats />
|
|
860
|
+
</ChatsProvider>
|
|
861
|
+
)
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
|
|
676
866
|
## Contributing
|
|
677
867
|
|
|
678
868
|
This is the result of years of meditation on overengineering. If you see ways to make it simpler (not more feature-rich, simpler), I'm interested.
|
|
@@ -683,4 +873,4 @@ MIT
|
|
|
683
873
|
|
|
684
874
|
---
|
|
685
875
|
|
|
686
|
-
*Built by someone who spent 7 years learning that the hard way is usually the wrong way.*
|
|
876
|
+
*Built by someone who spent 7 years learning that the hard way is usually the wrong way.*
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asasvirtuais",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.5",
|
|
5
5
|
"description": "React form and action management utilities",
|
|
6
6
|
"directories": {
|
|
7
7
|
"packages": "./packages"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "next dev"
|
|
11
|
+
},
|
|
9
12
|
"files": [
|
|
10
13
|
"packages",
|
|
11
14
|
"tsconfig.json"
|
|
@@ -29,8 +32,8 @@
|
|
|
29
32
|
"default": "./packages/hooks.tsx"
|
|
30
33
|
},
|
|
31
34
|
"./interface": {
|
|
32
|
-
"types": "./packages/interface
|
|
33
|
-
"default": "./packages/interface
|
|
35
|
+
"types": "./packages/interface.ts",
|
|
36
|
+
"default": "./packages/interface.ts"
|
|
34
37
|
},
|
|
35
38
|
"./react-interface": {
|
|
36
39
|
"types": "./packages/react-interface.tsx",
|
|
@@ -49,10 +52,16 @@
|
|
|
49
52
|
"react": "^19.2.3"
|
|
50
53
|
},
|
|
51
54
|
"dependencies": {
|
|
55
|
+
"@chakra-ui/react": "^3.30.0",
|
|
52
56
|
"next": "^16.1.1",
|
|
57
|
+
"next-themes": "^0.4.6",
|
|
58
|
+
"react-icons": "^5.5.0",
|
|
53
59
|
"zod": "^4.3.5"
|
|
54
60
|
},
|
|
55
61
|
"devDependencies": {
|
|
56
|
-
"@types/
|
|
62
|
+
"@types/node": "^25.0.3",
|
|
63
|
+
"@types/react": "^19.2.8",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"typescript": "^5.9.3"
|
|
57
66
|
}
|
|
58
67
|
}
|
|
@@ -1,26 +1,89 @@
|
|
|
1
|
+
'use client'
|
|
1
2
|
import z from 'zod'
|
|
2
|
-
import {
|
|
3
|
+
import { ListProps, TableInterface, TableSchema } from './interface'
|
|
3
4
|
import { createContextFromHook, useAction as useAsyncAction, useIndex } from './hooks'
|
|
4
|
-
import { createContext,
|
|
5
|
+
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
|
5
6
|
import { ActionProvider, useAction, useActionProvider } from './action'
|
|
6
7
|
import { FieldsProvider, useFields } from './fields'
|
|
7
8
|
|
|
8
9
|
export function useDatabaseProvider() {
|
|
9
10
|
|
|
10
|
-
const [
|
|
11
|
+
const [interfaces, setInterfaces] = useState<Record<string, ReturnType<typeof useInterface<any>>>>({})
|
|
12
|
+
const [indexes, setIndexes] = useState<Record<string, {
|
|
13
|
+
[table: string]: { [id: string]: any }
|
|
14
|
+
}>>({})
|
|
15
|
+
|
|
16
|
+
const set = useCallback((table: string, ...params: any[]) => {
|
|
17
|
+
setIndexes(prev => ({
|
|
18
|
+
[table]: {
|
|
19
|
+
...prev[table],
|
|
20
|
+
...Object.fromEntries(params.map(data => ([(data as any & { id: string} ).id, data])))
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
}, [])
|
|
24
|
+
const unset = useCallback((table: string, ...params: any[]) => {
|
|
25
|
+
setIndexes(prev => ({
|
|
26
|
+
[table]: {
|
|
27
|
+
...prev[table],
|
|
28
|
+
...Object.fromEntries(params.map(data => ([(data as any & { id: string} ).id, data])))
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
interfaces,
|
|
35
|
+
setInterfaces,
|
|
36
|
+
indexes,
|
|
37
|
+
set,
|
|
38
|
+
unset,
|
|
39
|
+
setIndexes,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
11
42
|
|
|
43
|
+
export function useTableIndex<TSchema extends TableSchema>(table: string, schema: TSchema) {
|
|
44
|
+
const { indexes, setIndexes } = useDatabase()
|
|
45
|
+
const index = useMemo(() => indexes[table], [indexes, table])
|
|
46
|
+
const array = useMemo(() => Object.values(index || {}) as z.infer<TSchema['readable']>[], [index])
|
|
47
|
+
function set(...params: z.infer<TSchema['readable'] >[]) {
|
|
48
|
+
setIndexes(prev => ({
|
|
49
|
+
[table]: {
|
|
50
|
+
...prev[table],
|
|
51
|
+
...Object.fromEntries(params.map(data => ([(data as z.infer<TSchema['readable']> & { id: string} ).id, data])))
|
|
52
|
+
},
|
|
53
|
+
...prev,
|
|
54
|
+
}))
|
|
55
|
+
}
|
|
56
|
+
function unset(...params: z.infer<TSchema['readable']>[]) {
|
|
57
|
+
setIndexes(prev => {
|
|
58
|
+
const tableIndex = prev[table] || {}
|
|
59
|
+
const newIndex = { ...tableIndex }
|
|
60
|
+
for (const data of params) {
|
|
61
|
+
const id = (data as z.infer<TSchema['readable']> & { id: string} ).id
|
|
62
|
+
if (newIndex[id])
|
|
63
|
+
delete newIndex[id]
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
[table]: {
|
|
67
|
+
...newIndex,
|
|
68
|
+
},
|
|
69
|
+
...prev,
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
12
73
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
74
|
+
index,
|
|
75
|
+
array,
|
|
76
|
+
set,
|
|
77
|
+
unset,
|
|
15
78
|
}
|
|
16
79
|
}
|
|
17
80
|
|
|
18
81
|
export const [DatabaseProvider, useDatabase] = createContextFromHook(useDatabaseProvider)
|
|
19
82
|
|
|
20
|
-
export function
|
|
21
|
-
const {
|
|
83
|
+
export function useTableInterface<TSchema extends TableSchema>(table: string, schema: TSchema) {
|
|
84
|
+
const { interfaces } = useDatabase()
|
|
22
85
|
|
|
23
|
-
const tableMethods =
|
|
86
|
+
const tableMethods = interfaces[table] as ReturnType<typeof useInterface<TSchema>> | undefined
|
|
24
87
|
|
|
25
88
|
if (!tableMethods)
|
|
26
89
|
throw new Error(`Table "${table}" is not defined in the database schema.`)
|
|
@@ -35,11 +98,10 @@ export type TableProviderProps<TSchema extends TableSchema> = {
|
|
|
35
98
|
asAbove?: Record<string, z.infer<TSchema['readable']>>
|
|
36
99
|
}
|
|
37
100
|
|
|
38
|
-
export function useInterface<TSchema extends TableSchema>(table: string, {
|
|
101
|
+
export function useInterface<TSchema extends TableSchema>(table: string, schema: TSchema, {
|
|
39
102
|
find, create, update, remove, list
|
|
40
103
|
}: TableInterface<z.infer<TSchema['readable']>, z.infer<TSchema['writable']>>, index: ReturnType<typeof useIndex<z.infer<TSchema['readable']>>>) {
|
|
41
104
|
return {
|
|
42
|
-
index,
|
|
43
105
|
find: useAsyncAction(((props) => find({ ...props, table }).then(res => {
|
|
44
106
|
index.set(res)
|
|
45
107
|
return res
|
|
@@ -70,7 +132,7 @@ export function useTableProvider<TSchema extends TableSchema>({
|
|
|
70
132
|
asAbove,
|
|
71
133
|
}: TableProviderProps<TSchema>) {
|
|
72
134
|
|
|
73
|
-
type Readable = z.infer<
|
|
135
|
+
type Readable = z.infer<TSchema['readable']>
|
|
74
136
|
|
|
75
137
|
const index = useIndex<Readable>({ ...(asAbove ?? {}) })
|
|
76
138
|
|
|
@@ -78,20 +140,46 @@ export function useTableProvider<TSchema extends TableSchema>({
|
|
|
78
140
|
index.setIndex((prev) => ({ ...prev, ...asAbove }))
|
|
79
141
|
}, [])
|
|
80
142
|
|
|
81
|
-
|
|
143
|
+
const methods = useInterface<TSchema>(table, schema, tableInterface, index)
|
|
144
|
+
|
|
145
|
+
const { interfaces, setInterfaces, indexes, setIndexes } = useDatabase()
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
setInterfaces(prev => ({
|
|
149
|
+
[table]: methods,
|
|
150
|
+
...prev,
|
|
151
|
+
}))
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
...methods,
|
|
156
|
+
...index
|
|
157
|
+
}
|
|
82
158
|
}
|
|
83
159
|
|
|
84
|
-
|
|
160
|
+
const TableContext = createContext<ReturnType<typeof useTableProvider<any>> | undefined>(undefined)
|
|
161
|
+
|
|
162
|
+
export function TableProvider<TSchema extends TableSchema>({children, ...props}: React.PropsWithChildren<TableProviderProps<TSchema>>) {
|
|
163
|
+
|
|
164
|
+
const context = useTableProvider(props)
|
|
85
165
|
|
|
86
|
-
|
|
87
|
-
|
|
166
|
+
const { interfaces, setInterfaces, indexes, setIndexes } = useDatabase()
|
|
167
|
+
|
|
168
|
+
if (! interfaces[props.table])
|
|
169
|
+
return null
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<TableContext.Provider value={context}>
|
|
173
|
+
{children}
|
|
174
|
+
</TableContext.Provider>
|
|
175
|
+
)
|
|
88
176
|
}
|
|
89
177
|
|
|
90
178
|
export function useTable<TSchema extends TableSchema>() {
|
|
91
|
-
return
|
|
179
|
+
return useContext(TableContext) as ReturnType<typeof useTableProvider<TSchema>>
|
|
92
180
|
}
|
|
93
181
|
|
|
94
|
-
export function CreateForm<TSchema extends TableSchema>({ table, defaults, onSuccess, children }: {
|
|
182
|
+
export function CreateForm<TSchema extends TableSchema>({ table, schema, defaults, onSuccess, children }: {
|
|
95
183
|
table: string
|
|
96
184
|
schema: TSchema
|
|
97
185
|
defaults?: Partial<z.infer<TSchema['writable']>>
|
|
@@ -105,7 +193,7 @@ export function CreateForm<TSchema extends TableSchema>({ table, defaults, onSuc
|
|
|
105
193
|
type Readable = z.infer<TSchema['readable']>
|
|
106
194
|
type Writable = z.infer<TSchema['writable']>
|
|
107
195
|
|
|
108
|
-
const { create } =
|
|
196
|
+
const { create } = useTableInterface<TSchema>(table, schema)
|
|
109
197
|
|
|
110
198
|
const callback = useCallback(
|
|
111
199
|
async (fields: Writable) => {
|
|
@@ -153,7 +241,7 @@ export function UpdateForm<TSchema extends TableSchema>({
|
|
|
153
241
|
type Readable = z.infer<TSchema['readable']>
|
|
154
242
|
type Writable = z.infer<TSchema['writable']>
|
|
155
243
|
|
|
156
|
-
const { update } =
|
|
244
|
+
const { update } = useTableInterface<TSchema>(table, schema)
|
|
157
245
|
|
|
158
246
|
const callback = useCallback(
|
|
159
247
|
async (fields: Partial<Writable>) => {
|
|
@@ -200,7 +288,7 @@ export function FilterForm<TSchema extends TableSchema>({
|
|
|
200
288
|
}) {
|
|
201
289
|
type Readable = z.infer<TSchema['readable']>
|
|
202
290
|
|
|
203
|
-
const { list } =
|
|
291
|
+
const { list } = useTableInterface<TSchema>(table, schema)
|
|
204
292
|
|
|
205
293
|
const callback = useCallback(
|
|
206
294
|
async (fields: Omit<ListProps<Readable>, 'table'>) => {
|
|
@@ -257,26 +345,26 @@ export function useFiltersForm <TSchema extends TableSchema>(schema: TSchema) {
|
|
|
257
345
|
}
|
|
258
346
|
}
|
|
259
347
|
|
|
260
|
-
export function useSingleProvider<
|
|
348
|
+
export function useSingleProvider<TSchema extends TableSchema>({
|
|
261
349
|
id,
|
|
262
350
|
table,
|
|
351
|
+
schema,
|
|
263
352
|
}: {
|
|
264
353
|
id: string
|
|
265
354
|
table: string
|
|
355
|
+
schema: TSchema
|
|
266
356
|
}) {
|
|
267
|
-
const {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
() => index[id as keyof typeof index]
|
|
357
|
+
const { find } = useTableInterface(table, schema)
|
|
358
|
+
const { index } = useTableIndex<TSchema>(table, schema)
|
|
359
|
+
const [single, setSingle] = useState<z.infer<TSchema['readable']>>(
|
|
360
|
+
() => index[table][id as keyof typeof index]
|
|
271
361
|
)
|
|
272
362
|
useEffect(() => {
|
|
273
|
-
// @ts-expect-error
|
|
274
363
|
if (!single) find.trigger({ id }).then(setSingle)
|
|
275
364
|
}, [])
|
|
276
365
|
useEffect(() => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}, [index[id as keyof typeof index]])
|
|
366
|
+
setSingle(index[table][id as keyof typeof index])
|
|
367
|
+
}, [index[table][id as keyof typeof index]])
|
|
280
368
|
return {
|
|
281
369
|
id,
|
|
282
370
|
single,
|
|
@@ -289,17 +377,19 @@ export const SingleContext = createContext<
|
|
|
289
377
|
ReturnType<typeof useSingleProvider> | undefined
|
|
290
378
|
>(undefined)
|
|
291
379
|
|
|
292
|
-
export function SingleProvider<
|
|
380
|
+
export function SingleProvider<TSchema extends TableSchema>({
|
|
293
381
|
children,
|
|
294
382
|
...props
|
|
295
383
|
}: {
|
|
296
384
|
id: string
|
|
297
385
|
table: string
|
|
298
|
-
|
|
386
|
+
schema: TSchema
|
|
387
|
+
children: React.ReactNode | ((props: ReturnType<typeof useSingleProvider<TSchema>>) => React.ReactNode)
|
|
299
388
|
}) {
|
|
300
|
-
const value = useSingleProvider(props)
|
|
389
|
+
const value = useSingleProvider<TSchema>(props)
|
|
301
390
|
if (!value.single) return null
|
|
302
391
|
return (
|
|
392
|
+
// @ts-expect-error
|
|
303
393
|
<SingleContext.Provider value={value}>
|
|
304
394
|
{typeof children === 'function' ? (
|
|
305
395
|
children(value)
|
|
@@ -310,6 +400,7 @@ export function SingleProvider<T>({
|
|
|
310
400
|
)
|
|
311
401
|
}
|
|
312
402
|
|
|
313
|
-
export function useSingle<
|
|
314
|
-
|
|
403
|
+
export function useSingle<TSchema extends TableSchema>() {
|
|
404
|
+
// @ts-expect-error
|
|
405
|
+
return useContext(SingleContext) as ReturnType<typeof useSingleProvider<TSchema>>
|
|
315
406
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"dom",
|
|
5
|
+
"dom.iterable",
|
|
6
|
+
"esnext"
|
|
7
|
+
],
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"target": "ESNext",
|
|
17
|
+
"module": "ESNext",
|
|
18
|
+
"moduleResolution": "Bundler",
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"next-env.d.ts",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/types/**/*.ts",
|
|
36
|
+
".next/dev/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|