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,219 @@
|
|
|
1
|
+
import { DatabaseInterface, TableInterface, Query } from './crud'
|
|
2
|
+
import z from 'zod'
|
|
3
|
+
import { feathers } from '@feathersjs/feathers'
|
|
4
|
+
import { KnexService } from '@feathersjs/knex'
|
|
5
|
+
import knex from 'knex'
|
|
6
|
+
|
|
7
|
+
if (!process.env.DATABASE_URL) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"DATABASE_URL must be set. Did you forget to provision a database?",
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Initialize Knex with PostgreSQL connection
|
|
14
|
+
const db = knex({
|
|
15
|
+
client: 'pg',
|
|
16
|
+
connection: process.env.DATABASE_URL,
|
|
17
|
+
pool: {
|
|
18
|
+
min: 2,
|
|
19
|
+
max: 10
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Initialize FeathersJS app
|
|
24
|
+
const app = feathers()
|
|
25
|
+
|
|
26
|
+
// Cache for FeathersJS services to avoid recreation
|
|
27
|
+
const serviceCache = new Map<string, KnexService>()
|
|
28
|
+
|
|
29
|
+
// Ensure table exists in the database
|
|
30
|
+
async function ensureTableExists(tableName: string) {
|
|
31
|
+
try {
|
|
32
|
+
const exists = await db.schema.hasTable(tableName)
|
|
33
|
+
if (!exists) {
|
|
34
|
+
await db.schema.createTable(tableName, (table) => {
|
|
35
|
+
table.uuid('id').primary().defaultTo(db.raw('gen_random_uuid()'))
|
|
36
|
+
table.jsonb('data').notNullable()
|
|
37
|
+
table.timestamp('created_at').defaultTo(db.fn.now())
|
|
38
|
+
table.timestamp('updated_at').defaultTo(db.fn.now())
|
|
39
|
+
})
|
|
40
|
+
console.log(`[REPLIT-DB] Created table ${tableName}`)
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.warn(`[REPLIT-DB] Could not ensure table ${tableName} exists:`, error)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get or create FeathersJS service for a table
|
|
48
|
+
function getService(tableName: string): KnexService {
|
|
49
|
+
if (!serviceCache.has(tableName)) {
|
|
50
|
+
const service = new KnexService({
|
|
51
|
+
Model: db,
|
|
52
|
+
name: tableName,
|
|
53
|
+
id: 'id',
|
|
54
|
+
paginate: false // Disable pagination for simpler API
|
|
55
|
+
})
|
|
56
|
+
serviceCache.set(tableName, service)
|
|
57
|
+
app.use(tableName, service)
|
|
58
|
+
}
|
|
59
|
+
return serviceCache.get(tableName)!
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function replitDbInterface<Schema extends DatabaseInterface, T extends keyof Schema & string>(defaultTable?: T): TableInterface<z.infer<Schema[T]['readable']>, z.infer<Schema[T]['writable']>> {
|
|
63
|
+
type Readable = z.infer<Schema[T]['readable']>
|
|
64
|
+
type Writable = z.infer<Schema[T]['writable']>
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
async find({ table = defaultTable, id }) {
|
|
68
|
+
const tableName = table as string
|
|
69
|
+
await ensureTableExists(tableName)
|
|
70
|
+
const service = getService(tableName)
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const result = await service.get(id)
|
|
74
|
+
return { id: result.id, ...result.data } as Readable
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Record not found in ${tableName} with id ${id}`)
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async list({ table = defaultTable, query: q = {} }) {
|
|
81
|
+
const tableName = table as string
|
|
82
|
+
await ensureTableExists(tableName)
|
|
83
|
+
|
|
84
|
+
// Build the query using Knex directly for proper JSONB handling
|
|
85
|
+
let knexQuery = db.select('*').from(tableName)
|
|
86
|
+
|
|
87
|
+
if (q) {
|
|
88
|
+
// Handle meta operators
|
|
89
|
+
if (q.$limit) {
|
|
90
|
+
knexQuery = knexQuery.limit(q.$limit)
|
|
91
|
+
}
|
|
92
|
+
if (q.$skip) {
|
|
93
|
+
knexQuery = knexQuery.offset(q.$skip)
|
|
94
|
+
}
|
|
95
|
+
if (q.$sort) {
|
|
96
|
+
for (const [field, direction] of Object.entries(q.$sort)) {
|
|
97
|
+
const order = direction === 1 ? 'asc' : 'desc'
|
|
98
|
+
knexQuery = knexQuery.orderByRaw(`data->>'${field}' ${order}`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Apply all conditions using a recursive function
|
|
103
|
+
knexQuery = applyConditions(knexQuery, q)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const results = await knexQuery
|
|
107
|
+
return results.map(row => ({ id: row.id, ...row.data } as unknown as Readable))
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async create({ table = defaultTable, data }) {
|
|
111
|
+
const tableName = table as string
|
|
112
|
+
await ensureTableExists(tableName)
|
|
113
|
+
const service = getService(tableName)
|
|
114
|
+
|
|
115
|
+
const result = await service.create({
|
|
116
|
+
data: data as any,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
console.log(`[REPLIT-DB] Record created in ${tableName} with id ${result.id}`)
|
|
120
|
+
return { id: result.id, ...data } as Readable
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async update({ table = defaultTable, id, data }) {
|
|
124
|
+
const tableName = table as string
|
|
125
|
+
await ensureTableExists(tableName)
|
|
126
|
+
const service = getService(tableName)
|
|
127
|
+
|
|
128
|
+
// Get current record to merge data
|
|
129
|
+
const current = await service.get(id)
|
|
130
|
+
const mergedData = { ...current.data, ...data }
|
|
131
|
+
|
|
132
|
+
const result = await service.patch(id, {
|
|
133
|
+
data: mergedData,
|
|
134
|
+
updated_at: new Date()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
console.log(`[REPLIT-DB] Record updated in ${tableName} with id ${id}`)
|
|
138
|
+
return { id: result.id, ...result.data } as Readable
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
async remove({ table = defaultTable, id }) {
|
|
142
|
+
const tableName = table as string
|
|
143
|
+
await ensureTableExists(tableName)
|
|
144
|
+
const service = getService(tableName)
|
|
145
|
+
|
|
146
|
+
// Get the record before deletion
|
|
147
|
+
const current = await service.get(id)
|
|
148
|
+
const data = { id: current.id, ...current.data } as Readable
|
|
149
|
+
|
|
150
|
+
await service.remove(id)
|
|
151
|
+
|
|
152
|
+
console.log(`[REPLIT-DB] Record removed from ${tableName} with id ${id}`)
|
|
153
|
+
return data
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Recursive function to apply query conditions with proper operator handling
|
|
159
|
+
function applyConditions(query: any, conditions: any): any {
|
|
160
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
161
|
+
if (key.startsWith('$')) {
|
|
162
|
+
// Handle compound operators
|
|
163
|
+
if (key === '$or' && Array.isArray(value)) {
|
|
164
|
+
query = query.where(function() {
|
|
165
|
+
for (const orCondition of value) {
|
|
166
|
+
this.orWhere(function() {
|
|
167
|
+
applyConditions(this, orCondition)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
} else if (key === '$and' && Array.isArray(value)) {
|
|
172
|
+
for (const andCondition of value) {
|
|
173
|
+
query = query.where(function() {
|
|
174
|
+
applyConditions(this, andCondition)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Skip other meta operators like $limit, $skip, $sort - they're handled elsewhere
|
|
179
|
+
} else {
|
|
180
|
+
// Handle field conditions
|
|
181
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
182
|
+
// Handle operators like $ne, $in, etc.
|
|
183
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
184
|
+
switch (op) {
|
|
185
|
+
case '$ne':
|
|
186
|
+
query = query.whereRaw(`data->>'${key}' != ?`, [opValue])
|
|
187
|
+
break
|
|
188
|
+
case '$in':
|
|
189
|
+
if (Array.isArray(opValue)) {
|
|
190
|
+
query = query.whereRaw(`data->>'${key}' = ANY(?)`, [opValue])
|
|
191
|
+
}
|
|
192
|
+
break
|
|
193
|
+
case '$nin':
|
|
194
|
+
if (Array.isArray(opValue)) {
|
|
195
|
+
query = query.whereRaw(`data->>'${key}' != ALL(?)`, [opValue])
|
|
196
|
+
}
|
|
197
|
+
break
|
|
198
|
+
case '$lt':
|
|
199
|
+
query = query.whereRaw(`(data->>'${key}')::numeric < ?`, [opValue])
|
|
200
|
+
break
|
|
201
|
+
case '$lte':
|
|
202
|
+
query = query.whereRaw(`(data->>'${key}')::numeric <= ?`, [opValue])
|
|
203
|
+
break
|
|
204
|
+
case '$gt':
|
|
205
|
+
query = query.whereRaw(`(data->>'${key}')::numeric > ?`, [opValue])
|
|
206
|
+
break
|
|
207
|
+
case '$gte':
|
|
208
|
+
query = query.whereRaw(`(data->>'${key}')::numeric >= ?`, [opValue])
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Simple equality
|
|
214
|
+
query = query.whereRaw(`data->>'${key}' = ?`, [value])
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return query
|
|
219
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FetchLike } from 'wretch'
|
|
2
|
+
|
|
3
|
+
export function ratePerSec(max: number, delay: number = 1000) {
|
|
4
|
+
let count : number = 0
|
|
5
|
+
|
|
6
|
+
return (next: FetchLike) => {
|
|
7
|
+
|
|
8
|
+
const check: FetchLike = async (url, opts) => {
|
|
9
|
+
if (count < max) {
|
|
10
|
+
count++
|
|
11
|
+
try {
|
|
12
|
+
return await next(url, opts)
|
|
13
|
+
} finally {
|
|
14
|
+
setTimeout(() => count--, delay)
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
return new Promise((resolve) => setTimeout(() => resolve(check(url, opts)), delay))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return check
|
|
21
|
+
}
|
|
22
|
+
}
|
package/packages/yaml.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { writeFile } from 'fs/promises'
|
|
3
|
+
import YAML from 'yaml'
|
|
4
|
+
import { generateId } from 'ai'
|
|
5
|
+
import { DatabaseInterface, TableInterface } from './crud'
|
|
6
|
+
import z from 'zod'
|
|
7
|
+
|
|
8
|
+
export class Mutex {
|
|
9
|
+
private promise : Promise<any> = Promise.resolve()
|
|
10
|
+
|
|
11
|
+
async run<T>(fn: () => Promise<T>): Promise<T> {
|
|
12
|
+
const result = this.promise.then(fn)
|
|
13
|
+
this.promise = result.catch(() => {}) // Don't break chain on errors
|
|
14
|
+
return result
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const fileMutex = new Mutex()
|
|
19
|
+
|
|
20
|
+
export function yamlInterface<Schema extends DatabaseInterface, T extends keyof Schema & string>(defaultTable: string, databasePath = 'database'): TableInterface<z.infer<Schema[T]['readable']>, z.infer<Schema[T]['writable']>>{
|
|
21
|
+
type Readable = z.infer<Schema[T]['readable']>
|
|
22
|
+
type Writable = z.infer<Schema[T]['writable']>
|
|
23
|
+
const tablePath = (name: string) => `${databasePath}/${name}`
|
|
24
|
+
const recordPath = (table: string, id: string) => `${tablePath(table)}/${id}.yaml`
|
|
25
|
+
const readRecord = (table: string, id: string) => {
|
|
26
|
+
const path = recordPath(table, id)
|
|
27
|
+
if ( ! fs.existsSync(path) )
|
|
28
|
+
throw new Error(`Record not found: ${path}`)
|
|
29
|
+
const file = fs.readFileSync(path, 'utf8')
|
|
30
|
+
return YAML.parse(file) as Readable
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
async find({ table = defaultTable, id }) {
|
|
34
|
+
return readRecord(table as string, id)
|
|
35
|
+
},
|
|
36
|
+
async list({ table = defaultTable, query }) {
|
|
37
|
+
const path = tablePath(table as string)
|
|
38
|
+
if (!fs.existsSync(path)) {
|
|
39
|
+
// If the table directory doesn't exist, return an empty array.
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
const files = fs.readdirSync(path)
|
|
43
|
+
let records: Readable[] = []
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
if (file.endsWith('.yaml')) {
|
|
46
|
+
const id = file.slice(0, -5)
|
|
47
|
+
try {
|
|
48
|
+
const record = readRecord(table, id)
|
|
49
|
+
records.push(record)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`Error reading record ${id} from table ${table}:`, error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (query) {
|
|
57
|
+
const { $limit, $skip, $sort, $select, ...filters } = query
|
|
58
|
+
|
|
59
|
+
// Apply filters
|
|
60
|
+
if (Object.keys(filters).length > 0) {
|
|
61
|
+
records = records.filter(record => {
|
|
62
|
+
return Object.entries(filters).every(([key, value]) => {
|
|
63
|
+
const recordValue = record[key as keyof typeof record]
|
|
64
|
+
if (typeof value === 'object' && value !== null) {
|
|
65
|
+
// Handle operators within a field
|
|
66
|
+
return Object.entries(value).every(([op, opValue]) => {
|
|
67
|
+
switch (op) {
|
|
68
|
+
case '$ne':
|
|
69
|
+
return recordValue != opValue
|
|
70
|
+
case '$in':
|
|
71
|
+
return Array.isArray(opValue) && opValue.includes(recordValue)
|
|
72
|
+
case '$nin':
|
|
73
|
+
return Array.isArray(opValue) && !opValue.includes(recordValue)
|
|
74
|
+
case '$lt':
|
|
75
|
+
// @ts-expect-error
|
|
76
|
+
return recordValue < opValue
|
|
77
|
+
case '$lte':
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
return recordValue <= opValue
|
|
80
|
+
case '$gt':
|
|
81
|
+
// @ts-expect-error
|
|
82
|
+
return recordValue > opValue
|
|
83
|
+
case '$gte':
|
|
84
|
+
// @ts-expect-error
|
|
85
|
+
return recordValue >= opValue
|
|
86
|
+
case '$search':
|
|
87
|
+
return typeof recordValue === 'string' && recordValue.toLowerCase().includes(String(opValue).toLowerCase())
|
|
88
|
+
default:
|
|
89
|
+
return true // Unknown operators are ignored
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
return recordValue === value
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Apply sorting
|
|
99
|
+
if ($sort) {
|
|
100
|
+
const sortKeys = Object.keys($sort) as (keyof Readable)[]
|
|
101
|
+
if (sortKeys.length > 0) {
|
|
102
|
+
const key = sortKeys[0]
|
|
103
|
+
const direction = $sort[key] === 1 ? 1 : -1
|
|
104
|
+
records.sort((a, b) => {
|
|
105
|
+
if (a[key] < b[key]) return -1 * direction
|
|
106
|
+
if (a[key] > b[key]) return 1 * direction
|
|
107
|
+
return 0
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Apply pagination
|
|
113
|
+
const skip = $skip || 0
|
|
114
|
+
const limit = $limit || records.length
|
|
115
|
+
records = records.slice(skip, skip + limit)
|
|
116
|
+
|
|
117
|
+
// Apply projection
|
|
118
|
+
if ($select) {
|
|
119
|
+
records = records.map(record => {
|
|
120
|
+
// @ts-expect-error
|
|
121
|
+
const selectedRecord: Partial<Readable> = { id: (record).id }
|
|
122
|
+
// @ts-expect-error
|
|
123
|
+
($select as (keyof Readable)[]).forEach(key => {
|
|
124
|
+
// @ts-expect-error
|
|
125
|
+
selectedRecord[key] = record[key]
|
|
126
|
+
})
|
|
127
|
+
return selectedRecord as Readable
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return records
|
|
133
|
+
},
|
|
134
|
+
async create({table = defaultTable, ...props}) {
|
|
135
|
+
props.data
|
|
136
|
+
const path = tablePath(table as string)
|
|
137
|
+
if ( ! fs.existsSync(path ) )
|
|
138
|
+
fs.mkdirSync(path, { recursive: true })
|
|
139
|
+
const id = generateId()
|
|
140
|
+
const record = { id, ...props.data }
|
|
141
|
+
const filePath = recordPath(table as string, id)
|
|
142
|
+
await fileMutex.run(() => writeFile(filePath, YAML.stringify(record), 'utf8'))
|
|
143
|
+
return record as Readable
|
|
144
|
+
},
|
|
145
|
+
async update({table = defaultTable, id, ...props}) {
|
|
146
|
+
const record = readRecord(table as string, id)
|
|
147
|
+
const updatedRecord = { ...record, ...props.data }
|
|
148
|
+
await fileMutex.run(() => writeFile(recordPath(table as string, id), YAML.stringify(updatedRecord), 'utf8'))
|
|
149
|
+
return updatedRecord as Readable
|
|
150
|
+
},
|
|
151
|
+
async remove({table = defaultTable, id}) {
|
|
152
|
+
const path = recordPath(table as string, id)
|
|
153
|
+
const data = readRecord(table as string, id)
|
|
154
|
+
if ( ! fs.existsSync(path) )
|
|
155
|
+
throw new Error(`Record not found: ${path}`)
|
|
156
|
+
fs.unlinkSync(path)
|
|
157
|
+
return {
|
|
158
|
+
id,
|
|
159
|
+
...data
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
}
|
package/server/db.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Pool, neonConfig } from '@neondatabase/serverless';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/neon-serverless';
|
|
3
|
+
import ws from "ws";
|
|
4
|
+
import * as schema from "@shared/schema";
|
|
5
|
+
|
|
6
|
+
neonConfig.webSocketConstructor = ws;
|
|
7
|
+
|
|
8
|
+
if (!process.env.DATABASE_URL) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"DATABASE_URL must be set. Did you forget to provision a database?",
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
15
|
+
export const db = drizzle({ client: pool, schema });
|