asasvirtuais 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 +78 -0
- package/actions/draw.ts +110 -0
- package/components/OAuthCard.tsx +346 -0
- package/components/icons.tsx +11 -0
- package/components/markdown.tsx +18 -0
- package/components/stack/list.tsx +21 -0
- package/components/stack/menu.tsx +40 -0
- package/components/stack/nav.tsx +39 -0
- package/components/table/fixed.tsx +59 -0
- package/components/table/key-value.tsx +19 -0
- package/components/ui/color-mode.tsx +108 -0
- package/components/ui/provider.tsx +15 -0
- package/components/ui/toaster.tsx +43 -0
- package/components/ui/tooltip.tsx +46 -0
- package/hooks/useBoolean.tsx +11 -0
- package/hooks/useForwardAs.tsx +29 -0
- package/hooks/useHash copy.tsx +27 -0
- package/hooks/useHash.tsx +27 -0
- package/hooks/useIsMobile.tsx +6 -0
- package/hooks/useOAuthTokens.ts +97 -0
- package/hooks/useOpenRouterModels.ts +80 -0
- package/lib/auth0.ts +11 -0
- package/lib/blob.ts +3 -0
- package/lib/client-token-storage.ts +216 -0
- package/lib/oauth-tokens.ts +85 -0
- package/lib/react/context.tsx +20 -0
- package/lib/react/index.ts +1 -0
- package/lib/tools.ts +375 -0
- package/next-env.d.ts +5 -0
- package/next.config.ts +23 -0
- package/package.json +72 -0
- package/packages/blob.ts +97 -0
- package/packages/chat/components/chat/feed/index.tsx +76 -0
- package/packages/chat/components/chat/feed/story.tsx +18 -0
- package/packages/chat/components/chat/index.tsx +16 -0
- package/packages/chat/components/chat/story.tsx +74 -0
- package/packages/chat/components/debug/index.tsx +54 -0
- package/packages/chat/components/header/index.tsx +14 -0
- package/packages/chat/components/header/menu/index.tsx +63 -0
- package/packages/chat/components/header/story.tsx +33 -0
- package/packages/chat/components/header/title/index.tsx +35 -0
- package/packages/chat/components/index.ts +13 -0
- package/packages/chat/components/input/index.tsx +17 -0
- package/packages/chat/components/input/menu/index.tsx +35 -0
- package/packages/chat/components/input/send.tsx +21 -0
- package/packages/chat/components/input/story.tsx +35 -0
- package/packages/chat/components/input/textarea/index.tsx +20 -0
- package/packages/chat/components/message/file.tsx +103 -0
- package/packages/chat/components/message/menu/index.tsx +26 -0
- package/packages/chat/components/message/story.tsx +49 -0
- package/packages/chat/components/messages/index.tsx +23 -0
- package/packages/chat/components/messages/story.tsx +11 -0
- package/packages/chat/components/ui/prose.tsx +263 -0
- package/packages/chat/edit-message.tsx +49 -0
- package/packages/chat/header.tsx +118 -0
- package/packages/chat/index.ts +14 -0
- package/packages/chat/input.tsx +89 -0
- package/packages/chat/message-menu.tsx +57 -0
- package/packages/chat/message.tsx +44 -0
- package/packages/chat/messages.tsx +44 -0
- package/packages/chat/model-selector.tsx +172 -0
- package/packages/chat/scenarios.tsx +68 -0
- package/packages/chat/settings.tsx +98 -0
- package/packages/chat/temperature-slider.tsx +67 -0
- package/packages/chat/tool-results.tsx +32 -0
- package/packages/crud/core.ts +75 -0
- package/packages/crud/fetcher.ts +64 -0
- package/packages/crud/index.ts +2 -0
- package/packages/crud/next.ts +128 -0
- package/packages/crud/react.tsx +365 -0
- package/packages/env.ts +8 -0
- package/packages/fields.tsx +157 -0
- package/packages/firebase.ts +13 -0
- package/packages/firestore.ts +51 -0
- package/packages/form.tsx +66 -0
- package/packages/next.ts +64 -0
- package/packages/openrouter.ts +4 -0
- package/packages/react/context.tsx +21 -0
- package/packages/react/crud.tsx +372 -0
- package/packages/react/hooks.ts +90 -0
- package/packages/react/store.tsx +20 -0
- package/packages/replit-db.ts +219 -0
- package/packages/wretch.ts +22 -0
- package/packages/yaml.ts +163 -0
- package/pnpm-workspace.yaml +4 -0
- package/server/db.ts +15 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { UpdateForm, useSingle, useTable } from '@/app/module'
|
|
3
|
+
import { Dialog, Button, VStack, Menu, Portal, useDisclosure, Spinner, HStack, Text } from '@chakra-ui/react'
|
|
4
|
+
import React, { useState, useCallback, useMemo } from 'react'
|
|
5
|
+
import { ModelSelector } from './model-selector'
|
|
6
|
+
import { TemperatureSlider } from './temperature-slider'
|
|
7
|
+
import { useParams } from 'next/navigation'
|
|
8
|
+
|
|
9
|
+
export function SettingsDialog() {
|
|
10
|
+
const { id } = useParams()
|
|
11
|
+
const { index } = useTable('chats')
|
|
12
|
+
const chat = useMemo(() => index[id as keyof typeof index], [id, index])
|
|
13
|
+
const { open, onOpen, onClose } = useDisclosure()
|
|
14
|
+
|
|
15
|
+
if (!chat)
|
|
16
|
+
return null
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Menu.Item value="settings" onClick={onOpen}>
|
|
21
|
+
Settings <Menu.ItemCommand>⚙️</Menu.ItemCommand>
|
|
22
|
+
</Menu.Item>
|
|
23
|
+
|
|
24
|
+
<Dialog.Root open={open} onOpenChange={(e) => !e.open && onClose()}>
|
|
25
|
+
<Portal>
|
|
26
|
+
<Dialog.Backdrop />
|
|
27
|
+
<Dialog.Positioner>
|
|
28
|
+
<Dialog.Content>
|
|
29
|
+
<Dialog.Header>
|
|
30
|
+
<Dialog.Title>Chat Settings</Dialog.Title>
|
|
31
|
+
<Dialog.CloseTrigger />
|
|
32
|
+
</Dialog.Header>
|
|
33
|
+
|
|
34
|
+
<UpdateForm
|
|
35
|
+
table="chats"
|
|
36
|
+
id={chat.id}
|
|
37
|
+
defaults={{
|
|
38
|
+
model: chat.model || 'openrouter/auto',
|
|
39
|
+
temperature: chat.temperature || 1
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
{(props) => {
|
|
43
|
+
return (
|
|
44
|
+
<form onSubmit={async (e: React.FormEvent) => {
|
|
45
|
+
await props.submit(e)
|
|
46
|
+
onClose()
|
|
47
|
+
}}>
|
|
48
|
+
<Dialog.Body>
|
|
49
|
+
<VStack gap={6} align="stretch">
|
|
50
|
+
{/* Model Selection */}
|
|
51
|
+
<ModelSelector
|
|
52
|
+
value={props.fields.model ?? 'openrouter/auto'}
|
|
53
|
+
onValueChange={value => props.setField('model', value)}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
{/* Temperature Slider */}
|
|
57
|
+
<TemperatureSlider
|
|
58
|
+
value={props.fields.temperature ?? 1}
|
|
59
|
+
onValueChangeEnd={value => props.setField('temperature', value)}
|
|
60
|
+
/>
|
|
61
|
+
</VStack>
|
|
62
|
+
</Dialog.Body>
|
|
63
|
+
|
|
64
|
+
<Dialog.Footer>
|
|
65
|
+
<HStack gap={2}>
|
|
66
|
+
<Dialog.CloseTrigger asChild>
|
|
67
|
+
<Button variant="outline" disabled={props.loading}>
|
|
68
|
+
Cancel
|
|
69
|
+
</Button>
|
|
70
|
+
</Dialog.CloseTrigger>
|
|
71
|
+
|
|
72
|
+
<Button
|
|
73
|
+
type='submit'
|
|
74
|
+
colorPalette="blue"
|
|
75
|
+
disabled={props.loading}
|
|
76
|
+
>
|
|
77
|
+
{props.loading ? (
|
|
78
|
+
<>
|
|
79
|
+
<Spinner size="sm" />
|
|
80
|
+
Saving...
|
|
81
|
+
</>
|
|
82
|
+
) : (
|
|
83
|
+
'Save Changes'
|
|
84
|
+
)}
|
|
85
|
+
</Button>
|
|
86
|
+
</HStack>
|
|
87
|
+
</Dialog.Footer>
|
|
88
|
+
</form>
|
|
89
|
+
)
|
|
90
|
+
}}
|
|
91
|
+
</UpdateForm>
|
|
92
|
+
</Dialog.Content>
|
|
93
|
+
</Dialog.Positioner>
|
|
94
|
+
</Portal>
|
|
95
|
+
</Dialog.Root>
|
|
96
|
+
</>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useState, useEffect, useCallback, memo } from 'react'
|
|
3
|
+
import { VStack, HStack, Text, Slider } from '@chakra-ui/react'
|
|
4
|
+
|
|
5
|
+
interface TemperatureSliderProps {
|
|
6
|
+
value: number
|
|
7
|
+
onValueChangeEnd: (value: number) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Memoize the component to prevent re-renders unless props change
|
|
11
|
+
export const TemperatureSlider = memo(function TemperatureSlider({ value, onValueChangeEnd }: TemperatureSliderProps) {
|
|
12
|
+
// Use local state for immediate feedback
|
|
13
|
+
const [localValue, setLocalValue] = useState(value || 1)
|
|
14
|
+
|
|
15
|
+
// Update local value when prop changes (e.g., when settings are loaded)
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setLocalValue(value || 1)
|
|
18
|
+
}, [value])
|
|
19
|
+
|
|
20
|
+
// Handle value change - only updates local state
|
|
21
|
+
const handleValueChange = useCallback((values: { value: number[] }) => {
|
|
22
|
+
setLocalValue(values.value[0])
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
// Handle drag end - updates parent
|
|
26
|
+
const handleValueChangeEnd = useCallback(() => {
|
|
27
|
+
onValueChangeEnd(localValue)
|
|
28
|
+
}, [localValue, onValueChangeEnd])
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<VStack align="stretch" gap={2}>
|
|
32
|
+
<HStack justify="space-between">
|
|
33
|
+
<Text fontWeight="medium">Temperature</Text>
|
|
34
|
+
<Text fontSize="sm" color="gray.600">
|
|
35
|
+
{localValue.toFixed(1)}
|
|
36
|
+
</Text>
|
|
37
|
+
</HStack>
|
|
38
|
+
|
|
39
|
+
<Text fontSize="sm" color="gray.600">
|
|
40
|
+
Controls randomness: lower is more focused, higher is more creative
|
|
41
|
+
</Text>
|
|
42
|
+
|
|
43
|
+
<Slider.Root
|
|
44
|
+
value={[localValue]}
|
|
45
|
+
onValueChange={handleValueChange}
|
|
46
|
+
onValueChangeEnd={handleValueChangeEnd}
|
|
47
|
+
min={0}
|
|
48
|
+
max={2}
|
|
49
|
+
step={0.1}
|
|
50
|
+
>
|
|
51
|
+
<Slider.Control>
|
|
52
|
+
<Slider.Track>
|
|
53
|
+
<Slider.Range />
|
|
54
|
+
</Slider.Track>
|
|
55
|
+
<Slider.Thumb index={0}>
|
|
56
|
+
<Slider.HiddenInput />
|
|
57
|
+
</Slider.Thumb>
|
|
58
|
+
</Slider.Control>
|
|
59
|
+
<HStack justify="space-between" fontSize="xs" color="gray.500" mt={1}>
|
|
60
|
+
<Text>0 (Focused)</Text>
|
|
61
|
+
<Text>1 (Balanced)</Text>
|
|
62
|
+
<Text>2 (Creative)</Text>
|
|
63
|
+
</HStack>
|
|
64
|
+
</Slider.Root>
|
|
65
|
+
</VStack>
|
|
66
|
+
)
|
|
67
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { clientTools } from '@/app/tools'
|
|
4
|
+
|
|
5
|
+
export function ToolResults({ tools }: { tools: Record<string, any> }) {
|
|
6
|
+
return (
|
|
7
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
8
|
+
{Object.entries(tools).map(([tool, data]) => (
|
|
9
|
+
<ToolResult key={tool} tool={tool} data={data} />
|
|
10
|
+
))}
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ToolResult({ tool, data }: { tool: string, data: any }) {
|
|
16
|
+
const module = clientTools[tool]
|
|
17
|
+
if (module?.View) {
|
|
18
|
+
const View = module.View
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<div style={{ fontSize: 12, color: '#6b7280', marginBottom: 4 }}>{tool}</div>
|
|
22
|
+
<View data={data} />
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
return (
|
|
27
|
+
<div>
|
|
28
|
+
<div style={{ fontSize: 12, color: '#6b7280', marginBottom: 4 }}>{tool}</div>
|
|
29
|
+
<pre style={{ whiteSpace: 'pre-wrap' }}>{JSON.stringify(data, null, 2)}</pre>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import z from "zod"
|
|
2
|
+
|
|
3
|
+
// Core query types for consistent querying across all backends
|
|
4
|
+
export type BasicOperators<T, K extends keyof T> = {
|
|
5
|
+
'$ne'?: T[K]
|
|
6
|
+
'$in'?: T[K][]
|
|
7
|
+
'$nin'?: T[K][]
|
|
8
|
+
'$lt'?: T[K]
|
|
9
|
+
'$lte'?: T[K]
|
|
10
|
+
'$gt'?: T[K]
|
|
11
|
+
'$gte'?: T[K]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Filters<T> = {
|
|
15
|
+
'$limit'?: number
|
|
16
|
+
'$skip'?: number
|
|
17
|
+
'$sort'?: {
|
|
18
|
+
[K in keyof T]?: 1 | -1
|
|
19
|
+
}
|
|
20
|
+
'$select'?: Array<keyof T>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Query<T = any> = {
|
|
24
|
+
[K in keyof T]?: T[K] | BasicOperators<T, K>
|
|
25
|
+
} & Filters<T> & {
|
|
26
|
+
'$or'?: Array<Query<T>>
|
|
27
|
+
'$and'?: Array<Query<T>>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type Operators<T, K extends keyof T> = BasicOperators<T, K> & {
|
|
31
|
+
'$or'?: Array<Query<T>>
|
|
32
|
+
'$and'?: Array<Query<T>>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Core CRUD operations using props pattern
|
|
36
|
+
export interface FindProps {
|
|
37
|
+
table?: string
|
|
38
|
+
id: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CreateProps<T = any> {
|
|
42
|
+
table?: string
|
|
43
|
+
data: T
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UpdateProps<T = any> {
|
|
47
|
+
table?: string
|
|
48
|
+
id: string
|
|
49
|
+
data: Partial<T>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RemoveProps {
|
|
53
|
+
table?: string
|
|
54
|
+
id: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ListProps<T = any> {
|
|
58
|
+
table?: string
|
|
59
|
+
query?: Query<T>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DatabaseInterface {
|
|
63
|
+
[T: string]: {
|
|
64
|
+
readable: z.SomeZodObject,
|
|
65
|
+
writable: z.SomeZodObject
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TableInterface<Readable, Writable = Readable> {
|
|
70
|
+
find (props: FindProps ): Promise<Readable>
|
|
71
|
+
create (props: CreateProps<Writable> ): Promise<Readable>
|
|
72
|
+
update (props: UpdateProps<Writable> ): Promise<Readable>
|
|
73
|
+
remove (props: RemoveProps ): Promise<Readable>
|
|
74
|
+
list (props: ListProps<Readable> ): Promise<Readable[]>
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import z from 'zod'
|
|
2
|
+
import type { DatabaseInterface, FindProps, CreateProps, UpdateProps, RemoveProps, ListProps, TableInterface } from './core'
|
|
3
|
+
|
|
4
|
+
export interface FetcherConfig {
|
|
5
|
+
baseUrl?: string
|
|
6
|
+
headers?: Record<string, string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function tableInterface<Schema extends DatabaseInterface, Table extends keyof Schema>({
|
|
10
|
+
schema,
|
|
11
|
+
defaultTable,
|
|
12
|
+
baseUrl = '/api/v1',
|
|
13
|
+
headers = {}
|
|
14
|
+
} : {
|
|
15
|
+
schema: Schema,
|
|
16
|
+
defaultTable?: Table,
|
|
17
|
+
baseUrl?: string,
|
|
18
|
+
headers?: Record<string, string>
|
|
19
|
+
} ): TableInterface<z.infer<Schema[keyof Schema]['readable']>, z.infer<Schema[keyof Schema]['writable']>>{
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
async find({ table = defaultTable as string, id }) {
|
|
23
|
+
const response = await fetch(`${baseUrl}/${table}/${id}`, {
|
|
24
|
+
headers
|
|
25
|
+
})
|
|
26
|
+
return response.json()
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async create({ table = defaultTable as string, data }) {
|
|
30
|
+
console.log(baseUrl, table, data)
|
|
31
|
+
const response = await fetch(`${baseUrl}/${table}`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
34
|
+
body: JSON.stringify(data)
|
|
35
|
+
})
|
|
36
|
+
return response.json()
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async update({ table = defaultTable as string, id, data }){
|
|
40
|
+
const response = await fetch(`${baseUrl}/${table}/${id}`, {
|
|
41
|
+
method: 'PATCH',
|
|
42
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
43
|
+
body: JSON.stringify(data)
|
|
44
|
+
})
|
|
45
|
+
return response.json()
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async remove({ table = defaultTable as string, id }){
|
|
49
|
+
const response = await fetch(`${baseUrl}/${table}/${id}`, {
|
|
50
|
+
method: 'DELETE',
|
|
51
|
+
headers
|
|
52
|
+
})
|
|
53
|
+
return response.json()
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async list({ table = defaultTable as string, query }){
|
|
57
|
+
const params = new URLSearchParams(query as any)
|
|
58
|
+
const response = await fetch(`${baseUrl}/${table}?${params}`, {
|
|
59
|
+
headers
|
|
60
|
+
})
|
|
61
|
+
return response.json()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { DatabaseInterface, TableInterface } from './core'
|
|
3
|
+
|
|
4
|
+
export function routes(implementation: TableInterface<any, any>) {
|
|
5
|
+
return {
|
|
6
|
+
// GET /api/v1/[table]/[id]
|
|
7
|
+
find: (
|
|
8
|
+
request: NextRequest,
|
|
9
|
+
{ params }: { params: Promise<{ table: string; id: string }> }
|
|
10
|
+
) =>
|
|
11
|
+
handleRoute(request, params, ({ table, id }) =>
|
|
12
|
+
implementation.find({ table, id })
|
|
13
|
+
),
|
|
14
|
+
|
|
15
|
+
// GET /api/v1/[table]
|
|
16
|
+
list: (
|
|
17
|
+
request: NextRequest,
|
|
18
|
+
{ params }: { params: Promise<{ table: string }> }
|
|
19
|
+
) =>
|
|
20
|
+
handleRoute(request, params, ({ table }) => {
|
|
21
|
+
const url = new URL(request.url)
|
|
22
|
+
const query = Object.fromEntries(url.searchParams)
|
|
23
|
+
return implementation.list({ table, query })
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
// POST /api/v1/[table]
|
|
27
|
+
create: (
|
|
28
|
+
request: NextRequest,
|
|
29
|
+
{ params }: { params: Promise<{ table: string }> }
|
|
30
|
+
) =>
|
|
31
|
+
handleRoute(request, params, async ({ table }) => {
|
|
32
|
+
const data = await request.json()
|
|
33
|
+
return implementation.create({ table, data })
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// PATCH /api/v1/[table]/[id]
|
|
37
|
+
update: (
|
|
38
|
+
request: NextRequest,
|
|
39
|
+
{ params }: { params: Promise<{ table: string; id: string }> }
|
|
40
|
+
) =>
|
|
41
|
+
handleRoute(request, params, async ({ table, id }) => {
|
|
42
|
+
const data = await request.json()
|
|
43
|
+
return implementation.update({ table, id, data })
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
// DELETE /api/v1/[table]/[id]
|
|
47
|
+
remove: (
|
|
48
|
+
request: NextRequest,
|
|
49
|
+
{ params }: { params: Promise<{ table: string; id: string }> }
|
|
50
|
+
) =>
|
|
51
|
+
handleRoute(request, params, ({ table, id }) =>
|
|
52
|
+
implementation.remove({ table, id })
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function handleRoute<P, R>(
|
|
58
|
+
request: NextRequest,
|
|
59
|
+
params: Promise<P>,
|
|
60
|
+
handler: (resolvedParams: any) => Promise<R>
|
|
61
|
+
) {
|
|
62
|
+
try {
|
|
63
|
+
const resolvedParams = await params
|
|
64
|
+
const result = await handler(resolvedParams)
|
|
65
|
+
return Response.json(result)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("Route error:", error)
|
|
68
|
+
return Response.json(
|
|
69
|
+
{ error: error instanceof Error ? error.message : "Unknown error" },
|
|
70
|
+
{ status: 500 }
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Utility for creating a single dynamic route handler
|
|
76
|
+
export function createDynamicRoute(implementation: TableInterface<any, any>) {
|
|
77
|
+
const routeHandlers = routes(implementation)
|
|
78
|
+
|
|
79
|
+
return async function dynamicRoute(
|
|
80
|
+
request: NextRequest,
|
|
81
|
+
{ params: promise }: { params: Promise<{ params: string[] }> }
|
|
82
|
+
) {
|
|
83
|
+
const { params } = await promise
|
|
84
|
+
const [table, id] = params
|
|
85
|
+
|
|
86
|
+
const method = request.method
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
switch (method) {
|
|
90
|
+
case "GET":
|
|
91
|
+
if (id) {
|
|
92
|
+
return routeHandlers.find(request, {
|
|
93
|
+
params: Promise.resolve({ table, id }),
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
return routeHandlers.list(request, {
|
|
97
|
+
params: Promise.resolve({
|
|
98
|
+
table,
|
|
99
|
+
...Object.fromEntries(request.nextUrl.searchParams.entries()),
|
|
100
|
+
}),
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
case "POST":
|
|
104
|
+
return routeHandlers.create(request, {
|
|
105
|
+
params: Promise.resolve({ table }),
|
|
106
|
+
})
|
|
107
|
+
case "PATCH":
|
|
108
|
+
if (!id) throw new Error("ID required for PATCH")
|
|
109
|
+
return routeHandlers.update(request, {
|
|
110
|
+
params: Promise.resolve({ table, id }),
|
|
111
|
+
})
|
|
112
|
+
case "DELETE":
|
|
113
|
+
if (!id) throw new Error("ID required for DELETE")
|
|
114
|
+
return routeHandlers.remove(request, {
|
|
115
|
+
params: Promise.resolve({ table, id }),
|
|
116
|
+
})
|
|
117
|
+
default:
|
|
118
|
+
return Response.json({ error: "Method not allowed" }, { status: 405 })
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(error)
|
|
122
|
+
return Response.json(
|
|
123
|
+
{ error: error instanceof Error ? error.message : "Unknown error" },
|
|
124
|
+
{ status: 500 }
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|