asasvirtuais 2.1.5 → 2.2.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 CHANGED
@@ -228,37 +228,42 @@ export const userSchema = {
228
228
  }
229
229
  ```
230
230
 
231
- #### 2. Create Your Table Interface
231
+ #### 2. Provide the Interface Context
232
232
 
233
- ```typescript
234
- // app/interface.ts
235
- import { fetchInterface } from '@asasvirtuais/fetch-interface'
233
+ ```tsx
234
+ // app/layout.tsx
235
+ import { InterfaceProvider } from 'asasvirtuais/fetch-interface'
236
+ import { DatabaseProvider, TableProvider } from 'asasvirtuais/react-interface'
236
237
  import { todoSchema } from './database'
237
238
 
238
- // Create interface for your API
239
- export const todosInterface = fetchInterface({
240
- schema: todoSchema,
241
- defaultTable: 'todos',
242
- baseUrl: '/api/v1'
243
- })
239
+ export default function RootLayout({ children }) {
240
+ return (
241
+ <DatabaseProvider>
242
+ <InterfaceProvider schema={todoSchema} baseUrl='/api/v1'>
243
+ <TodosProvider>
244
+ {children}
245
+ </TodosProvider>
246
+ </InterfaceProvider>
247
+ </DatabaseProvider>
248
+ )
249
+ }
244
250
  ```
245
251
 
246
- #### 3. Provide Table Context
252
+ #### 3. Create Your Table Provider
247
253
 
248
254
  ```tsx
249
- // app/todos/layout.tsx
250
- import { TableProvider } from '@asasvirtuais/react-interface'
251
- import { todoSchema, todosInterface } from '@/app/interface'
255
+ // app/todos/provider.tsx
256
+ 'use client'
257
+ import { TableProvider } from 'asasvirtuais/react-interface'
258
+ import { useInterface } from 'asasvirtuais/fetch-interface'
259
+ import { todoSchema } from './database'
252
260
 
253
- export default async function TodosLayout({ children }) {
254
- const initialTodos = await fetchTodos()
255
-
261
+ export function TodosProvider({ children }) {
256
262
  return (
257
263
  <TableProvider
258
- table="todos"
264
+ table='todos'
259
265
  schema={todoSchema}
260
- interface={todosInterface}
261
- asAbove={initialTodos}
266
+ interface={useInterface()}
262
267
  >
263
268
  {children}
264
269
  </TableProvider>
@@ -802,28 +807,24 @@ export function Filter[Models]() {
802
807
 
803
808
  ## 4. Provider and Hooks (`table.tsx`)
804
809
 
805
- Set up the data provider and custom hooks:
810
+ Set up the data provider and custom hooks using `InterfaceProvider` and `useInterface`:
806
811
 
807
812
  ```typescript
808
813
  'use client'
809
814
  import { TableProvider, useTableInterface } from 'asasvirtuais/react-interface'
810
- import { fetchInterface } from 'asasvirtuais/fetch-interface'
815
+ import { useInterface } from 'asasvirtuais/fetch-interface'
811
816
  import { schema } from '.'
812
817
 
813
818
  export function use[Models]() {
814
- return useTableInterface<typeof schema>('tableName')
819
+ return useTableInterface('tableName', schema)
815
820
  }
816
821
 
817
822
  export function [Models]Provider({ children }: { children: React.ReactNode }) {
818
823
  return (
819
- <TableProvider
820
- table='tableName'
821
- schema={schema}
822
- interface={fetchInterface({
823
- schema,
824
- baseUrl: '/api/v1',
825
- defaultTable: 'tableName'
826
- })}
824
+ <TableProvider
825
+ table='tableName'
826
+ schema={schema}
827
+ interface={useInterface()}
827
828
  >
828
829
  {children}
829
830
  </TableProvider>
@@ -831,11 +832,28 @@ export function [Models]Provider({ children }: { children: React.ReactNode }) {
831
832
  }
832
833
  ```
833
834
 
835
+ Then in your layout, wrap with `InterfaceProvider` to set up the context:
836
+
837
+ ```tsx
838
+ import { InterfaceProvider } from 'asasvirtuais/fetch-interface'
839
+ import { DatabaseProvider } from 'asasvirtuais/react-interface'
840
+ import { [Models]Provider } from './table'
841
+ import { schema } from '.'
842
+
843
+ <DatabaseProvider>
844
+ <InterfaceProvider schema={schema} baseUrl='/api/v1'>
845
+ <[Models]Provider>
846
+ {children}
847
+ </[Models]Provider>
848
+ </InterfaceProvider>
849
+ </DatabaseProvider>
850
+ ```
851
+
834
852
  **Key Points:**
835
- - Mark as `'use client'` for Next.js
853
+ - Mark table.tsx as `'use client'` for Next.js
854
+ - `InterfaceProvider` creates the fetch interface and provides it via context
855
+ - `useInterface()` retrieves the interface from context inside `TableProvider`
836
856
  - 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
857
 
840
858
  ## Naming Conventions
841
859
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "asasvirtuais",
3
3
  "type": "module",
4
- "version": "2.1.5",
4
+ "version": "2.2.0",
5
5
  "description": "React form and action management utilities",
6
6
  "directories": {
7
7
  "packages": "./packages"
@@ -46,6 +46,18 @@
46
46
  "./fetch-interface": {
47
47
  "types": "./packages/fetch-interface.tsx",
48
48
  "default": "./packages/fetch-interface.tsx"
49
+ },
50
+ "./interface-provider": {
51
+ "types": "./packages/interface-provider.tsx",
52
+ "default": "./packages/interface-provider.tsx"
53
+ },
54
+ "./mem-interface": {
55
+ "types": "./packages/mem-interface.tsx",
56
+ "default": "./packages/mem-interface.tsx"
57
+ },
58
+ "./indexed-interface": {
59
+ "types": "./packages/indexed-interface.tsx",
60
+ "default": "./packages/indexed-interface.tsx"
49
61
  }
50
62
  },
51
63
  "peerDependencies": {
@@ -53,6 +65,7 @@
53
65
  },
54
66
  "dependencies": {
55
67
  "@chakra-ui/react": "^3.30.0",
68
+ "dexie": "^4.3.0",
56
69
  "next": "^16.1.1",
57
70
  "next-themes": "^0.4.6",
58
71
  "react-icons": "^5.5.0",
@@ -64,4 +77,4 @@
64
77
  "@types/react-dom": "^19.2.3",
65
78
  "typescript": "^5.9.3"
66
79
  }
67
- }
80
+ }
@@ -1,11 +1,21 @@
1
+ 'use client'
1
2
  import { z } from 'zod'
3
+ import { useMemo } from 'react'
4
+ import { createContextFromHook } from './hooks'
2
5
  import type { TableInterface, TableSchema } from './interface'
3
6
 
4
- export interface FetchInterfaceConfig {
7
+ export function useFetchInterfaceProvider<TSchema extends TableSchema>({
8
+ schema, baseUrl = '/api/v1', headers = {},
9
+ }: {
10
+ schema: TSchema
5
11
  baseUrl?: string
6
12
  headers?: Record<string, string>
13
+ }) {
14
+ return useMemo(() => fetchInterface({ schema, baseUrl, headers }), [schema, baseUrl])
7
15
  }
8
16
 
17
+ export const [FetchInterfaceProvider, useFetchInterface] = createContextFromHook(useFetchInterfaceProvider<any>)
18
+
9
19
  export function fetchInterface<Schema extends TableSchema, Table extends string>({
10
20
  schema, defaultTable, baseUrl = '/api/v1', headers = {}
11
21
  }: {
@@ -0,0 +1,139 @@
1
+ 'use client'
2
+ import Dexie from 'dexie'
3
+ import { z } from 'zod'
4
+ import { useMemo } from 'react'
5
+ import { createContextFromHook } from './hooks'
6
+ import type { DatabaseSchema, TableInterface } from './interface'
7
+
8
+ export function useIndexedInterfaceProvider<Schema extends DatabaseSchema>({
9
+ dbName, schema,
10
+ }: {
11
+ dbName: string
12
+ schema: Schema
13
+ }) {
14
+ return useMemo(() => indexedInterface(dbName, schema), [dbName, schema])
15
+ }
16
+
17
+ export const [IndexedInterfaceProvider, useIndexedInterface] = createContextFromHook(useIndexedInterfaceProvider<any>)
18
+
19
+ /**
20
+ * Creates a TableInterface adapter for IndexedDB using Dexie.js.
21
+ * This allows the framework to be used in a client-side only context.
22
+ *
23
+ * @param dbName The name of the IndexedDB database.
24
+ * @param schema The Zod schema definition for the database tables.
25
+ * @returns A TableInterface implementation for Dexie.
26
+ */
27
+ export function indexedInterface<Schema extends DatabaseSchema>(
28
+ dbName: string,
29
+ schema: Schema
30
+ ): TableInterface<z.infer<Schema[keyof Schema]['readable']>, z.infer<Schema[keyof Schema]['writable']>> {
31
+
32
+ const db = new Dexie(dbName)
33
+
34
+ // Dynamically define the database schema for Dexie from the Zod schema.
35
+ // It marks 'id' as the primary key and indexes all other top-level readable fields.
36
+ const dexieSchema = Object.fromEntries(
37
+ Object.keys(schema).map(tableName => {
38
+ const fields = Object.keys(schema[tableName].readable.shape)
39
+ // 'id' is the primary key, the rest are indexed fields.
40
+ const indexedFields = fields.filter(f => f !== 'id').join(', ')
41
+ return [tableName, `id, ${indexedFields}`]
42
+ })
43
+ )
44
+
45
+ db.version(1).stores(dexieSchema)
46
+
47
+ type GenericReadable = z.infer<Schema[keyof Schema]['readable']>
48
+ type GenericWritable = z.infer<Schema[keyof Schema]['writable']>
49
+
50
+ return {
51
+ async find({ table, id }) {
52
+ if (!table) throw new Error('Table name must be provided.')
53
+ const result = await db.table(table).get(id)
54
+ if (!result) throw new Error(`Record with id ${id} not found in ${table}`)
55
+ return result
56
+ },
57
+
58
+ async list({ table, query }) {
59
+ if (!table) throw new Error('Table name must be provided.')
60
+ let collection = db.table(table).toCollection()
61
+
62
+ const { $limit, $skip, $sort, ...filters } = query ?? {}
63
+
64
+ // Handle filtering
65
+ if (Object.keys(filters).length > 0) {
66
+ collection = collection.filter(item => {
67
+ return Object.entries(filters).every(([key, value]) => {
68
+ const itemValue = item[key as keyof typeof item]
69
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
70
+ return Object.entries(value).every(([op, opValue]) => {
71
+ switch (op) {
72
+ case '$ne': return itemValue !== opValue
73
+ case '$in': return Array.isArray(opValue) && opValue.includes(itemValue)
74
+ case '$nin': return Array.isArray(opValue) && !opValue.includes(itemValue)
75
+ case '$lt': return itemValue < (opValue as number)
76
+ case '$lte': return itemValue <= (opValue as number)
77
+ case '$gt': return itemValue > (opValue as number)
78
+ case '$gte': return itemValue >= (opValue as number)
79
+ default: return true // Ignore unknown operators
80
+ }
81
+ })
82
+ } else {
83
+ return itemValue === value
84
+ }
85
+ })
86
+ })
87
+ }
88
+
89
+ // Handle sorting
90
+ if ($sort) {
91
+ const sortKey = Object.keys($sort)[0] as keyof GenericReadable
92
+ const direction = $sort[sortKey] === -1 ? 'desc' : 'asc'
93
+ if (direction === 'desc')
94
+ collection = collection.reverse()
95
+ // @ts-expect-error
96
+ collection = await collection.sortBy(sortKey)
97
+ }
98
+
99
+ // Handle pagination
100
+ if ($skip) {
101
+ collection = collection.offset($skip)
102
+ }
103
+ if ($limit) {
104
+ collection = collection.limit($limit)
105
+ }
106
+
107
+ return collection.toArray()
108
+ },
109
+
110
+ async create({ table, data }) {
111
+ if (!table) throw new Error('Table name must be provided.')
112
+ // Use existing id or generate a new UUID
113
+ const id = (data as any).id || crypto.randomUUID()
114
+ const record = { ...data, id }
115
+ await db.table(table).add(record)
116
+ return record as GenericReadable
117
+ },
118
+
119
+ async update({ table, id, data }) {
120
+ if (!table) throw new Error('Table name must be provided.')
121
+ const updatedCount = await db.table(table).update(id, data)
122
+ if (updatedCount === 0) {
123
+ throw new Error(`Record with id ${id} not found in ${table}, cannot update.`)
124
+ }
125
+ const result = await this.find({ table, id })
126
+ return result as GenericReadable
127
+ },
128
+
129
+ async remove({ table, id }) {
130
+ if (!table) throw new Error('Table name must be provided.')
131
+ const record = await this.find({ table, id })
132
+ if (!record) {
133
+ throw new Error(`Record with id ${id} not found in ${table}, cannot remove.`)
134
+ }
135
+ await db.table(table).delete(id)
136
+ return record as GenericReadable
137
+ },
138
+ } as TableInterface<GenericReadable, GenericWritable>
139
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+ import { createContextFromHook } from './hooks'
3
+ import type { TableInterface } from './interface'
4
+
5
+ function useInterfaceProvider({ interface: tableInterface }: {
6
+ interface: TableInterface<any, any>
7
+ }) {
8
+ return tableInterface
9
+ }
10
+
11
+ export const [InterfaceProvider, useInterface] = createContextFromHook(useInterfaceProvider)
@@ -0,0 +1,104 @@
1
+ 'use client'
2
+ import { useMemo } from 'react'
3
+ import { createContextFromHook } from './hooks'
4
+ import type { TableInterface, TableSchema } from './interface'
5
+
6
+ export function useMemInterfaceProvider<TSchema extends TableSchema>({
7
+ schema,
8
+ }: {
9
+ schema: TSchema
10
+ }) {
11
+ return useMemo(() => memInterface<TSchema>(), [schema])
12
+ }
13
+
14
+ export const [MemInterfaceProvider, useMemInterface] = createContextFromHook(useMemInterfaceProvider<any>)
15
+
16
+ export function memInterface<TSchema extends TableSchema>(): TableInterface<any, any> {
17
+
18
+ const store: Record<string, Record<string, any>> = {}
19
+
20
+ function getTable(table?: string) {
21
+ if (!table) throw new Error('Table name must be provided.')
22
+ if (!store[table]) store[table] = {}
23
+ return store[table]
24
+ }
25
+
26
+ return {
27
+ async find({ table, id }) {
28
+ const t = getTable(table)
29
+ const record = t[id]
30
+ if (!record) throw new Error(`Record with id ${id} not found in ${table}`)
31
+ return record
32
+ },
33
+
34
+ async create({ table, data }) {
35
+ const t = getTable(table)
36
+ const id = (data as any).id || crypto.randomUUID()
37
+ const record = { ...data, id }
38
+ t[id] = record
39
+ return record
40
+ },
41
+
42
+ async update({ table, id, data }) {
43
+ const t = getTable(table)
44
+ if (!t[id]) throw new Error(`Record with id ${id} not found in ${table}, cannot update.`)
45
+ t[id] = { ...t[id], ...data }
46
+ return t[id]
47
+ },
48
+
49
+ async remove({ table, id }) {
50
+ const t = getTable(table)
51
+ const record = t[id]
52
+ if (!record) throw new Error(`Record with id ${id} not found in ${table}, cannot remove.`)
53
+ delete t[id]
54
+ return record
55
+ },
56
+
57
+ async list({ table, query }) {
58
+ const t = getTable(table)
59
+ let results = Object.values(t)
60
+
61
+ if (query) {
62
+ const { $limit, $skip, $sort, ...filters } = query
63
+
64
+ if (Object.keys(filters).length > 0) {
65
+ results = results.filter(item =>
66
+ Object.entries(filters).every(([key, value]) => {
67
+ const itemValue = item[key]
68
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
69
+ return Object.entries(value).every(([op, opValue]) => {
70
+ switch (op) {
71
+ case '$ne': return itemValue !== opValue
72
+ case '$in': return Array.isArray(opValue) && opValue.includes(itemValue)
73
+ case '$nin': return Array.isArray(opValue) && !opValue.includes(itemValue)
74
+ case '$lt': return itemValue < (opValue as number)
75
+ case '$lte': return itemValue <= (opValue as number)
76
+ case '$gt': return itemValue > (opValue as number)
77
+ case '$gte': return itemValue >= (opValue as number)
78
+ default: return true
79
+ }
80
+ })
81
+ }
82
+ return itemValue === value
83
+ })
84
+ )
85
+ }
86
+
87
+ if ($sort) {
88
+ const sortKey = Object.keys($sort)[0]
89
+ const direction = ($sort as any)[sortKey]
90
+ results.sort((a, b) => {
91
+ if (a[sortKey] < b[sortKey]) return direction === -1 ? 1 : -1
92
+ if (a[sortKey] > b[sortKey]) return direction === -1 ? -1 : 1
93
+ return 0
94
+ })
95
+ }
96
+
97
+ if ($skip) results = results.slice($skip)
98
+ if ($limit) results = results.slice(0, $limit)
99
+ }
100
+
101
+ return results
102
+ }
103
+ }
104
+ }
@@ -175,8 +175,13 @@ export function TableProvider<TSchema extends TableSchema>({children, ...props}:
175
175
  )
176
176
  }
177
177
 
178
- export function useTable<TSchema extends TableSchema>() {
179
- return useContext(TableContext) as ReturnType<typeof useTableProvider<TSchema>>
178
+ export function useTable<TSchema extends TableSchema>(table: string, schema: TSchema) {
179
+ const methods = useTableInterface(table, schema)
180
+ const index = useTableIndex(table, schema)
181
+ return {
182
+ ...methods,
183
+ ...index,
184
+ }
180
185
  }
181
186
 
182
187
  export function CreateForm<TSchema extends TableSchema>({ table, schema, defaults, onSuccess, children }: {