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 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.3",
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/interface.ts",
33
- "default": "./packages/interface/interface.ts"
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/react": "^19.2.8"
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 { DatabaseSchema, ListProps, TableInterface, TableSchema } from './interface'
3
+ import { ListProps, TableInterface, TableSchema } from './interface'
3
4
  import { createContextFromHook, useAction as useAsyncAction, useIndex } from './hooks'
4
- import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from 'react'
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 [database, setDatabase] = useState<Record<string, ReturnType<typeof useInterface<any>>>>({})
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
- database,
14
- setDatabase,
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 useDatabaseTable<TSchema extends TableSchema>(table: string) {
21
- const { database } = useDatabase()
83
+ export function useTableInterface<TSchema extends TableSchema>(table: string, schema: TSchema) {
84
+ const { interfaces } = useDatabase()
22
85
 
23
- const tableMethods = database[table] as ReturnType<typeof useInterface<TSchema>> | undefined
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<TableSchema['readable']>
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
- return useInterface<TSchema>(table, tableInterface, index)
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
- export const [TableContextProvider, useTableContext] = createContextFromHook(useTableProvider<any>)
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
- export function TableProvider<TSchema extends TableSchema>({ children, ...props }: PropsWithChildren<TableProviderProps<TSchema>>) {
87
- return <TableContextProvider {...props}>{children}</TableContextProvider>
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 useTableContext() as ReturnType<typeof useTableProvider<TSchema>>
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 } = useDatabaseTable(table)
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 } = useDatabaseTable(table)
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 } = useDatabaseTable(table)
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<T>({
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 { index, find } = useDatabaseTable(table)
268
- const [single, setSingle] = useState<T>(
269
- // @ts-expect-error
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
- // @ts-expect-error
278
- setSingle(index[id as keyof typeof index])
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<T>({
380
+ export function SingleProvider<TSchema extends TableSchema>({
293
381
  children,
294
382
  ...props
295
383
  }: {
296
384
  id: string
297
385
  table: string
298
- children: React.ReactNode | ((props: ReturnType<typeof useSingleProvider>) => React.ReactNode)
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<T>() {
314
- return useContext(SingleContext) as ReturnType<typeof useSingleProvider<T>>
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
+ }