weifuwu 0.27.2 → 0.27.4

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.
Files changed (90) hide show
  1. package/README.md +0 -19
  2. package/dist/ai/provider.d.ts +45 -0
  3. package/dist/ai/stream.d.ts +13 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +131 -0
  6. package/dist/core/cookie.d.ts +36 -0
  7. package/dist/core/env.d.ts +69 -0
  8. package/dist/core/logger.d.ts +16 -0
  9. package/dist/core/router.d.ts +72 -0
  10. package/dist/core/serve.d.ts +38 -0
  11. package/dist/core/sse.d.ts +47 -0
  12. package/dist/core/trace.d.ts +95 -0
  13. package/dist/graphql.d.ts +16 -0
  14. package/dist/hub.d.ts +36 -0
  15. package/dist/index.d.ts +59 -0
  16. package/dist/index.js +3937 -0
  17. package/dist/mailer.d.ts +51 -0
  18. package/dist/middleware/compress.d.ts +20 -0
  19. package/dist/middleware/cors.d.ts +25 -0
  20. package/dist/middleware/csrf.d.ts +47 -0
  21. package/dist/middleware/flash.d.ts +90 -0
  22. package/dist/middleware/health.d.ts +24 -0
  23. package/dist/middleware/helmet.d.ts +33 -0
  24. package/dist/middleware/i18n.d.ts +39 -0
  25. package/dist/middleware/rate-limit.d.ts +44 -0
  26. package/dist/middleware/request-id.d.ts +40 -0
  27. package/dist/middleware/static.d.ts +23 -0
  28. package/dist/middleware/theme.d.ts +31 -0
  29. package/dist/middleware/upload.d.ts +55 -0
  30. package/dist/middleware/validate.d.ts +32 -0
  31. package/dist/postgres/client.d.ts +4 -0
  32. package/dist/postgres/index.d.ts +4 -0
  33. package/dist/postgres/module.d.ts +16 -0
  34. package/dist/postgres/schema/columns.d.ts +99 -0
  35. package/dist/postgres/schema/index.d.ts +6 -0
  36. package/dist/postgres/schema/sql.d.ts +22 -0
  37. package/dist/postgres/schema/table.d.ts +141 -0
  38. package/dist/postgres/schema/where.d.ts +29 -0
  39. package/dist/postgres/types.d.ts +49 -0
  40. package/dist/queue/cron.d.ts +9 -0
  41. package/dist/queue/index.d.ts +2 -0
  42. package/dist/queue/types.d.ts +61 -0
  43. package/dist/redis/client.d.ts +2 -0
  44. package/{redis/index.ts → dist/redis/index.d.ts} +2 -2
  45. package/dist/redis/types.d.ts +17 -0
  46. package/dist/test/test-utils.d.ts +193 -0
  47. package/dist/types.d.ts +50 -0
  48. package/package.json +10 -12
  49. package/ai/provider.ts +0 -129
  50. package/ai/stream.ts +0 -63
  51. package/cli.ts +0 -147
  52. package/core/cookie.ts +0 -114
  53. package/core/env.ts +0 -142
  54. package/core/logger.ts +0 -72
  55. package/core/router.ts +0 -795
  56. package/core/serve.ts +0 -294
  57. package/core/sse.ts +0 -85
  58. package/core/trace.ts +0 -146
  59. package/graphql.ts +0 -267
  60. package/hub.ts +0 -133
  61. package/index.ts +0 -71
  62. package/mailer.ts +0 -81
  63. package/middleware/compress.ts +0 -103
  64. package/middleware/cors.ts +0 -81
  65. package/middleware/csrf.ts +0 -112
  66. package/middleware/flash.ts +0 -144
  67. package/middleware/health.ts +0 -44
  68. package/middleware/helmet.ts +0 -98
  69. package/middleware/i18n.ts +0 -175
  70. package/middleware/rate-limit.ts +0 -167
  71. package/middleware/request-id.ts +0 -60
  72. package/middleware/static.ts +0 -149
  73. package/middleware/theme.ts +0 -84
  74. package/middleware/upload.ts +0 -168
  75. package/middleware/validate.ts +0 -186
  76. package/postgres/client.ts +0 -132
  77. package/postgres/index.ts +0 -4
  78. package/postgres/module.ts +0 -37
  79. package/postgres/schema/columns.ts +0 -186
  80. package/postgres/schema/index.ts +0 -36
  81. package/postgres/schema/sql.ts +0 -39
  82. package/postgres/schema/table.ts +0 -548
  83. package/postgres/schema/where.ts +0 -99
  84. package/postgres/types.ts +0 -48
  85. package/queue/cron.ts +0 -90
  86. package/queue/index.ts +0 -654
  87. package/queue/types.ts +0 -60
  88. package/redis/client.ts +0 -24
  89. package/redis/types.ts +0 -28
  90. package/types.ts +0 -78
@@ -1,548 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { SqlClient } from '../../types.ts'
3
- import { ColumnBuilder, toDDL, type PartitionByDef } from './columns.ts'
4
- import { SQL } from './sql.ts'
5
- import { and } from './where.ts'
6
-
7
- /** Options for table index creation. */
8
- export interface IndexOptions {
9
- /** Whether the index should be UNIQUE. */
10
- unique?: boolean
11
- /** Index type: btree (default), hnsw (pgvector), gin (JSONB). */
12
- type?: 'btree' | 'hnsw' | 'gin'
13
- /** Create index in DESC order. */
14
- desc?: boolean
15
- /** Custom operator class (e.g. `vector_cosine_ops`). */
16
- operator?: string
17
- }
18
-
19
- /** Options for CREATE TABLE. */
20
- export interface CreateOptions {
21
- /** Partition by clause (RANGE, LIST, or HASH). */
22
- partitionBy?: PartitionByDef
23
- }
24
-
25
- /** Options for find/read queries. */
26
- export interface FindOptions {
27
- /** ORDER BY clause: `{ column: 'asc' | 'desc' }`. */
28
- orderBy?: Record<string, 'asc' | 'desc'>
29
- /** LIMIT. */
30
- limit?: number
31
- /** OFFSET. */
32
- offset?: number
33
- /** Columns to SELECT (default: all). */
34
- select?: string[]
35
- /** Include soft-deleted rows (also sets `withDeleted` context). */
36
- withDeleted?: boolean
37
- }
38
-
39
- interface ColEntry {
40
- prop: string
41
- db: string
42
- auto: boolean
43
- column: ColumnBuilder<unknown>
44
- }
45
-
46
- /**
47
- * Type-safe table schema + CRUD operations.
48
- *
49
- * Create an instance with {@link pgTable}, then call `.bind(sql)` to get a
50
- * `BoundTable` for running queries.
51
- *
52
- * ```ts
53
- * const users = pgTable('users', {
54
- * id: serial('id').primaryKey(),
55
- * name: text('name').notNull(),
56
- * email: text('email').unique(),
57
- * })
58
- *
59
- * const db = users.bind(sql)
60
- * await db.create()
61
- * await db.insert({ name: 'Alice', email: 'a@b.com' })
62
- * const row = await db.findBy({ email: 'a@b.com' })
63
- * ```
64
- */
65
- export class Table<R extends Record<string, unknown>> {
66
- /** Database table name. */
67
- readonly tableName: string
68
- /** All column builders (order-preserving). */
69
- readonly columns: ColumnBuilder<unknown>[]
70
- /** Column builders keyed by property name. */
71
- readonly builders: Record<string, ColumnBuilder<unknown>>
72
- private colEntries: ColEntry[]
73
-
74
- constructor(tableName: string, builders: Record<string, ColumnBuilder<unknown>>) {
75
- this.tableName = tableName
76
- this.builders = builders
77
- this.columns = Object.values(builders)
78
- this.colEntries = Object.entries(builders).map(([prop, col]) => ({
79
- prop,
80
- db: col.name,
81
- auto: col.isAutoGenerate,
82
- column: col,
83
- }))
84
- }
85
-
86
- /** Check if the table has a column with the given DB name. */
87
- hasColumn(dbName: string): boolean {
88
- return this.colEntries.some((e) => e.db === dbName)
89
- }
90
-
91
- /**
92
- * Bind this table schema to a SQL connection, returning a `BoundTable`
93
- * that can run queries without passing `sql` to every call.
94
- */
95
- bind(sql: SqlClient): BoundTable<R> {
96
- return new BoundTable(sql, this.tableName, this.builders)
97
- }
98
-
99
- /** Returns the primary key column name (DB name), or 'id' as fallback. */
100
- private get pkColumn(): string {
101
- const entry = this.colEntries.find((e) => e.column.isPrimaryKey)
102
- return entry ? entry.db : 'id'
103
- }
104
-
105
- /** Adds `deleted_at IS NULL` condition if the table has soft delete and not explicitly excluded. */
106
- private _softDeleteFilter(where: unknown, opts?: FindOptions): string | null {
107
- if (!this.hasColumn('deleted_at')) return null
108
- if (opts?.withDeleted) return null
109
- // If user explicitly filters by deleted_at, don't add our own
110
- if (where && typeof where === 'object' && !Array.isArray(where) && !(where instanceof SQL)) {
111
- if ('deleted_at' in (where as Record<string, unknown>)) return null
112
- }
113
- return '"deleted_at" IS NULL'
114
- }
115
-
116
- async create(sql: SqlClient, opts?: CreateOptions): Promise<void> {
117
- const colDDL = this.columns.map(toDDL)
118
- let ddl = `CREATE TABLE IF NOT EXISTS "${this.tableName}" (\n ${colDDL.join(',\n ')}\n)`
119
- if (opts?.partitionBy) {
120
- ddl += ` PARTITION BY ${opts.partitionBy.type} ("${opts.partitionBy.column}")`
121
- }
122
- await sql.unsafe(ddl)
123
- }
124
-
125
- async drop(sql: SqlClient, opts?: { cascade?: boolean }): Promise<void> {
126
- const cascade = opts?.cascade ? ' CASCADE' : ''
127
- await sql.unsafe(`DROP TABLE IF EXISTS "${this.tableName}"${cascade}`)
128
- }
129
-
130
- async createIndex(
131
- sql: SqlClient,
132
- columns: string | string[],
133
- opts?: IndexOptions,
134
- ): Promise<void> {
135
- const cols = Array.isArray(columns) ? columns : [columns]
136
- const name = `"${this.tableName}_${cols.join('_')}${opts?.unique ? '_uidx' : '_idx'}"`
137
- const unique = opts?.unique ? 'UNIQUE' : ''
138
- const using = opts?.type ? `USING ${opts.type.toUpperCase()}` : ''
139
- const colList = cols.map((c) => (opts?.desc ? `"${c}" DESC` : `"${c}"`)).join(', ')
140
- const operator = opts?.operator ? ` ${opts.operator}` : ''
141
-
142
- const ddl =
143
- `CREATE ${unique} INDEX IF NOT EXISTS ${name} ON "${this.tableName}" ${using} (${colList}${operator})`.replace(
144
- /\s+/g,
145
- ' ',
146
- )
147
- await sql.unsafe(ddl)
148
- }
149
-
150
- async createUniqueIndex(sql: SqlClient, columns: string | string[]): Promise<void> {
151
- await this.createIndex(sql, columns, { unique: true })
152
- }
153
-
154
- // --- Private helpers ---
155
-
156
- private _buildConditions(
157
- where: Partial<R> | SQL | SQL[] | undefined,
158
- startIndex: number,
159
- ): { conditions: string[]; values: unknown[] } {
160
- const conditions: string[] = []
161
- const values: unknown[] = []
162
-
163
- let w = where
164
- if (Array.isArray(w)) {
165
- w = w.length > 0 ? and(...w) : undefined
166
- }
167
-
168
- if (w instanceof SQL) {
169
- let fragment = ''
170
- for (let i = 0; i < w.strings.length; i++) {
171
- fragment += w.strings[i]
172
- if (i < w.values.length) {
173
- fragment += `$${startIndex + values.length + 1}`
174
- values.push(w.values[i])
175
- }
176
- }
177
- conditions.push(fragment)
178
- } else {
179
- for (const [prop, value] of Object.entries(w || {}) as [string, unknown][]) {
180
- if (value === undefined) continue
181
- const entry = this.colEntries.find((e) => e.prop === prop)
182
- const db = entry ? entry.db : prop
183
- conditions.push(`"${db}" = $${startIndex + values.length + 1}`)
184
- values.push(value)
185
- }
186
- }
187
-
188
- return { conditions, values }
189
- }
190
-
191
- private _buildSET(data: Partial<R>): { sets: string[]; values: unknown[] } {
192
- const sets: string[] = []
193
- const values: unknown[] = []
194
-
195
- const d = data as Record<string, unknown>
196
- for (const { prop, db } of this.colEntries) {
197
- if (prop in d && d[prop] !== undefined) {
198
- const val = d[prop]
199
- if (val instanceof SQL) {
200
- sets.push(`"${db}" = ${val.toSQL()}`)
201
- } else {
202
- sets.push(`"${db}" = $${sets.length + 1}`)
203
- values.push(val)
204
- }
205
- }
206
- }
207
-
208
- if (this.hasColumn('updated_at') && !d.updated_at) {
209
- sets.push('"updated_at" = NOW()')
210
- }
211
-
212
- return { sets, values }
213
- }
214
-
215
- // --- CRUD ---
216
-
217
- async insert(sql: SqlClient, data: Partial<R>): Promise<R> {
218
- const filtered: Record<string, unknown> = {}
219
- for (const { prop, db, auto } of this.colEntries) {
220
- if (auto) continue
221
- const val = (data as Record<string, unknown>)[prop]
222
- if (val !== undefined) {
223
- filtered[db] = val
224
- }
225
- }
226
- const [row] = await sql`
227
- INSERT INTO ${sql(this.tableName as string)} ${sql(filtered as any)} RETURNING *
228
- `
229
- return row as unknown as R
230
- }
231
-
232
- async insertMany(sql: SqlClient, data: Partial<R>[]): Promise<R[]> {
233
- const filtered: Record<string, unknown>[] = []
234
- for (const item of data) {
235
- const row: Record<string, unknown> = {}
236
- for (const { prop, db, auto } of this.colEntries) {
237
- if (auto) continue
238
- const val = (item as Record<string, unknown>)[prop]
239
- if (val !== undefined) {
240
- row[db] = val
241
- }
242
- }
243
- filtered.push(row)
244
- }
245
- const rows = await sql`
246
- INSERT INTO ${sql(this.tableName as string)} ${sql(filtered as any)} RETURNING *
247
- `
248
- return rows as unknown as R[]
249
- }
250
-
251
- async read(
252
- sql: SqlClient,
253
- id: string | number,
254
- opts?: Pick<FindOptions, 'select' | 'withDeleted'>,
255
- ): Promise<R | undefined> {
256
- const pk = this.pkColumn
257
- const columns = opts?.select?.length ? opts.select.map((c) => `"${c}"`).join(', ') : '*'
258
- const softDel = this._softDeleteFilter(null, opts)
259
- const extraAnd = softDel ? ` AND ${softDel}` : ''
260
-
261
- const [row] = await sql.unsafe(
262
- `SELECT ${columns} FROM "${this.tableName}" WHERE "${pk}" = $1${extraAnd} LIMIT 1`,
263
- [id],
264
- )
265
- return (row as unknown as R) ?? undefined
266
- }
267
-
268
- async readMany(
269
- sql: SqlClient,
270
- where?: Partial<R> | SQL | SQL[],
271
- opts?: FindOptions,
272
- ): Promise<{ count: number; data: R[] }> {
273
- const { conditions, values } = this._buildConditions(where, 0)
274
-
275
- const softDel = this._softDeleteFilter(where, opts)
276
- if (softDel) conditions.push(softDel)
277
-
278
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''
279
-
280
- const [countRow] = await sql.unsafe(
281
- `SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
282
- values as any[],
283
- )
284
- const count = Number((countRow as Record<string, number>)._total)
285
-
286
- if (
287
- conditions.length === 0 &&
288
- !opts?.orderBy &&
289
- !opts?.limit &&
290
- !opts?.offset &&
291
- !opts?.select
292
- ) {
293
- const rows = await sql`SELECT * FROM ${sql(this.tableName as string)}`
294
- return { count, data: rows as unknown as R[] }
295
- }
296
-
297
- const columns = opts?.select?.length ? opts.select.map((c) => `"${c}"`).join(', ') : '*'
298
- let query = `SELECT ${columns} FROM "${this.tableName}"${whereClause}`
299
- if (opts?.orderBy) {
300
- const orders = Object.entries(opts.orderBy)
301
- .map(([prop, dir]) => {
302
- const entry = this.colEntries.find((e) => e.prop === prop)
303
- return `"${entry?.db || prop}" ${dir.toUpperCase()}`
304
- })
305
- .join(', ')
306
- query += ` ORDER BY ${orders}`
307
- }
308
- if (opts?.limit) query += ` LIMIT ${opts.limit}`
309
- if (opts?.offset) query += ` OFFSET ${opts.offset}`
310
-
311
- const rows = await sql.unsafe(query, values as any[])
312
- return { count, data: rows as unknown as R[] }
313
- }
314
-
315
- async update(sql: SqlClient, id: string | number, data: Partial<R>): Promise<R | undefined> {
316
- const { sets, values: setValues } = this._buildSET(data)
317
- if (sets.length === 0) return undefined
318
-
319
- const pk = this.pkColumn
320
- const [row] = await sql.unsafe(
321
- `UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE "${pk}" = $${setValues.length + 1} RETURNING *`,
322
- [...setValues, id] as any[],
323
- )
324
- return (row as unknown as R) ?? undefined
325
- }
326
-
327
- async updateMany(
328
- sql: SqlClient,
329
- where: Partial<R> | SQL | SQL[],
330
- data: Partial<R>,
331
- ): Promise<number> {
332
- const { sets, values: setValues } = this._buildSET(data)
333
- if (sets.length === 0) return 0
334
-
335
- const { conditions: wConditions, values: wValues } = this._buildConditions(
336
- where,
337
- setValues.length,
338
- )
339
- if (wConditions.length === 0) return 0
340
-
341
- const rows = await sql.unsafe(
342
- `UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE ${wConditions.join(' AND ')} RETURNING 1`,
343
- [...setValues, ...wValues] as any[],
344
- )
345
- return rows.length
346
- }
347
-
348
- async delete(sql: SqlClient, id: string | number): Promise<R | undefined> {
349
- const pk = this.pkColumn
350
- if (this.hasColumn('deleted_at')) {
351
- const [row] = await sql.unsafe(
352
- `UPDATE "${this.tableName}" SET "deleted_at" = NOW() WHERE "${pk}" = $1 RETURNING *`,
353
- [id],
354
- )
355
- return (row as unknown as R) ?? undefined
356
- }
357
- const [row] = await sql.unsafe(
358
- `DELETE FROM "${this.tableName}" WHERE "${pk}" = $1 RETURNING *`,
359
- [id],
360
- )
361
- return (row as unknown as R) ?? undefined
362
- }
363
-
364
- async hardDelete(sql: SqlClient, id: string | number): Promise<R | undefined> {
365
- const pk = this.pkColumn
366
- const [row] = await sql.unsafe(
367
- `DELETE FROM "${this.tableName}" WHERE "${pk}" = $1 RETURNING *`,
368
- [id],
369
- )
370
- return (row as unknown as R) ?? undefined
371
- }
372
-
373
- async deleteMany(sql: SqlClient, where: Partial<R> | SQL | SQL[]): Promise<number> {
374
- const { conditions, values } = this._buildConditions(where, 0)
375
- if (conditions.length === 0) return 0
376
-
377
- if (this.hasColumn('deleted_at')) {
378
- const rows = await sql.unsafe(
379
- `UPDATE "${this.tableName}" SET "deleted_at" = NOW() WHERE ${conditions.join(' AND ')} RETURNING 1`,
380
- values as any[],
381
- )
382
- return rows.length
383
- }
384
-
385
- const rows = await sql.unsafe(
386
- `DELETE FROM "${this.tableName}" WHERE ${conditions.join(' AND ')} RETURNING 1`,
387
- values as any[],
388
- )
389
- return rows.length
390
- }
391
-
392
- async hardDeleteMany(sql: SqlClient, where: Partial<R> | SQL | SQL[]): Promise<number> {
393
- const { conditions, values } = this._buildConditions(where, 0)
394
- if (conditions.length === 0) return 0
395
-
396
- const rows = await sql.unsafe(
397
- `DELETE FROM "${this.tableName}" WHERE ${conditions.join(' AND ')} RETURNING 1`,
398
- values as any[],
399
- )
400
- return rows.length
401
- }
402
-
403
- async upsert(sql: SqlClient, data: Partial<R>, conflict: string | string[]): Promise<R> {
404
- const filtered: Record<string, unknown> = {}
405
- for (const { prop, db, auto } of this.colEntries) {
406
- if (auto) continue
407
- const val = (data as Record<string, unknown>)[prop]
408
- if (val !== undefined) {
409
- filtered[db] = val
410
- }
411
- }
412
-
413
- const keys = Object.keys(filtered)
414
- if (keys.length === 0) throw new Error('upsert: no data to insert')
415
-
416
- const conflictCols = Array.isArray(conflict) ? conflict : [conflict]
417
- const dbCols = keys.map((c) => `"${c}"`)
418
- const placeholders = keys.map((_, i) => `$${i + 1}`)
419
- const updateSet = keys
420
- .filter((k) => !conflictCols.includes(k))
421
- .map((k) => `"${k}" = EXCLUDED."${k}"`)
422
- .join(', ')
423
-
424
- if (!updateSet) {
425
- const [row] = await sql.unsafe(
426
- `INSERT INTO "${this.tableName}" (${dbCols.join(', ')}) VALUES (${placeholders.join(', ')}) ON CONFLICT (${conflictCols.map((c) => `"${c}"`).join(', ')}) DO NOTHING RETURNING *`,
427
- Object.values(filtered) as any[],
428
- )
429
- return (row as unknown as R) ?? (undefined as unknown as R)
430
- }
431
-
432
- const [row] = await sql.unsafe(
433
- `INSERT INTO "${this.tableName}" (${dbCols.join(', ')}) VALUES (${placeholders.join(', ')}) ON CONFLICT (${conflictCols.map((c) => `"${c}"`).join(', ')}) DO UPDATE SET ${updateSet} RETURNING *`,
434
- Object.values(filtered) as any[],
435
- )
436
- return row as unknown as R
437
- }
438
-
439
- async count(sql: SqlClient, where?: Partial<R> | SQL | SQL[]): Promise<number> {
440
- const { conditions, values } = this._buildConditions(where, 0)
441
-
442
- const softDel = this._softDeleteFilter(where)
443
- if (softDel) conditions.push(softDel)
444
-
445
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''
446
- const [row] = await sql.unsafe(
447
- `SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
448
- values as any[],
449
- )
450
- return Number((row as Record<string, number>)._total)
451
- }
452
- }
453
-
454
- export class BoundTable<R extends Record<string, unknown>> {
455
- private inner: Table<R>
456
- private sql: SqlClient
457
-
458
- /** The underlying table name. */
459
- get tableName(): string {
460
- return this.inner.tableName
461
- }
462
-
463
- constructor(sql: SqlClient, tableName: string, builders: Record<string, ColumnBuilder<unknown>>) {
464
- this.inner = new Table<R>(tableName, builders)
465
- this.sql = sql
466
- }
467
-
468
- async create(opts?: CreateOptions): Promise<void> {
469
- await this.inner.create(this.sql, opts)
470
- }
471
- async drop(opts?: { cascade?: boolean }): Promise<void> {
472
- await this.inner.drop(this.sql, opts)
473
- }
474
- async createIndex(columns: string | string[], opts?: IndexOptions): Promise<void> {
475
- await this.inner.createIndex(this.sql, columns, opts)
476
- }
477
- async createUniqueIndex(columns: string | string[]): Promise<void> {
478
- await this.inner.createUniqueIndex(this.sql, columns)
479
- }
480
-
481
- async insert(data: Partial<R>): Promise<R> {
482
- return await this.inner.insert(this.sql, data)
483
- }
484
- async insertMany(data: Partial<R>[]): Promise<R[]> {
485
- return await this.inner.insertMany(this.sql, data)
486
- }
487
- async read(
488
- id: string | number,
489
- opts?: Pick<FindOptions, 'select' | 'withDeleted'>,
490
- ): Promise<R | undefined> {
491
- return await this.inner.read(this.sql, id, opts)
492
- }
493
- async readMany(
494
- where?: Partial<R> | SQL | SQL[],
495
- opts?: FindOptions,
496
- ): Promise<{ count: number; data: R[] }> {
497
- return await this.inner.readMany(this.sql, where, opts)
498
- }
499
- async update(id: string | number, data: Partial<R>): Promise<R | undefined> {
500
- return await this.inner.update(this.sql, id, data)
501
- }
502
- async updateMany(where: Partial<R> | SQL | SQL[], data: Partial<R>): Promise<number> {
503
- return await this.inner.updateMany(this.sql, where, data)
504
- }
505
- async delete(id: string | number): Promise<R | undefined> {
506
- return await this.inner.delete(this.sql, id)
507
- }
508
- async hardDelete(id: string | number): Promise<R | undefined> {
509
- return await this.inner.hardDelete(this.sql, id)
510
- }
511
- async deleteMany(where: Partial<R> | SQL | SQL[]): Promise<number> {
512
- return await this.inner.deleteMany(this.sql, where)
513
- }
514
- async hardDeleteMany(where: Partial<R> | SQL | SQL[]): Promise<number> {
515
- return await this.inner.hardDeleteMany(this.sql, where)
516
- }
517
- async upsert(data: Partial<R>, conflict: string | string[]): Promise<R> {
518
- return await this.inner.upsert(this.sql, data, conflict)
519
- }
520
- async count(where?: Partial<R> | SQL | SQL[]): Promise<number> {
521
- return await this.inner.count(this.sql, where)
522
- }
523
-
524
- withSql(sql: SqlClient): BoundTable<R> {
525
- return new BoundTable(sql, this.inner.tableName, this.inner.builders)
526
- }
527
- }
528
-
529
- /**
530
- * Define a type-safe table schema.
531
- *
532
- * ```ts
533
- * const users = pgTable('users', {
534
- * id: serial('id').primaryKey(),
535
- * name: text('name').notNull(),
536
- * email: text('email').unique(),
537
- * })
538
- *
539
- * // The generic type R preserves the column types:
540
- * // Table<{ id: number; name: string; email: string }>
541
- * ```
542
- */
543
- export function pgTable<R extends Record<string, unknown>>(
544
- tableName: string,
545
- builders: { [K in keyof R]: ColumnBuilder<R[K]> },
546
- ): Table<R> {
547
- return new Table<R>(tableName, builders as unknown as Record<string, ColumnBuilder<unknown>>)
548
- }
@@ -1,99 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { SQL } from './sql.ts'
3
-
4
- function op(col: string, sqlOp: string, val: unknown): SQL {
5
- return new SQL([`"${col}" ${sqlOp} `, ''] as any, [val])
6
- }
7
-
8
- /** Column equals value: `col = val`. */
9
- export function eq(col: string, val: unknown): SQL {
10
- return op(col, '=', val)
11
- }
12
- /** Column not equals value: `col != val`. */
13
- export function ne(col: string, val: unknown): SQL {
14
- return op(col, '!=', val)
15
- }
16
- /** Column greater than value: `col > val`. */
17
- export function gt(col: string, val: unknown): SQL {
18
- return op(col, '>', val)
19
- }
20
- /** Column greater than or equal value: `col >= val`. */
21
- export function gte(col: string, val: unknown): SQL {
22
- return op(col, '>=', val)
23
- }
24
- /** Column less than value: `col < val`. */
25
- export function lt(col: string, val: unknown): SQL {
26
- return op(col, '<', val)
27
- }
28
- /** Column less than or equal value: `col <= val`. */
29
- export function lte(col: string, val: unknown): SQL {
30
- return op(col, '<=', val)
31
- }
32
-
33
- /** Column IS NULL. */
34
- export function isNull(col: string): SQL {
35
- return new SQL([`"${col}" IS NULL`] as any, [])
36
- }
37
-
38
- /** Column IS NOT NULL. */
39
- export function isNotNull(col: string): SQL {
40
- return new SQL([`"${col}" IS NOT NULL`] as any, [])
41
- }
42
-
43
- /** Column LIKE pattern. */
44
- export function like(col: string, pattern: string): SQL {
45
- return op(col, 'LIKE', pattern)
46
- }
47
-
48
- /** Negate a condition: `NOT (condition)`. */
49
- export function not(condition: SQL): SQL {
50
- const strings = condition.strings
51
- const values = condition.values
52
- if (strings.length === 1 && strings[0] === '') return new SQL(['NOT ()'] as any, [])
53
- const result: string[] = ['NOT (']
54
- for (let i = 0; i < strings.length; i++) {
55
- if (i > 0) result.push(strings[i])
56
- else result[0] += strings[i]
57
- }
58
- result[result.length - 1] += ')'
59
- return new SQL(result as any, [...values])
60
- }
61
-
62
- /** JSONB containment: `col @> val` (does `col` contain `val`?). */
63
- export function contains(col: string, val: Record<string, unknown>): SQL {
64
- return new SQL([`"${col}" @> `, ''] as any, [val])
65
- }
66
-
67
- /** Column value is in array: `col = ANY(val)`. */
68
- export function in_(col: string, val: unknown[]): SQL {
69
- const close = ')'
70
- return new SQL([`"${col}" = ANY(`, close] as any, [val])
71
- }
72
-
73
- function combine(conditions: SQL[], joiner: string): SQL {
74
- if (conditions.length === 0) return new SQL([''] as any, [])
75
- const strings: string[] = ['(']
76
- const values: unknown[] = []
77
- for (let i = 0; i < conditions.length; i++) {
78
- if (i > 0) strings[strings.length - 1] += ` ${joiner} `
79
- const s = conditions[i]
80
- for (let j = 0; j < s.strings.length; j++) {
81
- strings[strings.length - 1] += s.strings[j]
82
- if (j < s.values.length) {
83
- strings.push('')
84
- values.push(s.values[j])
85
- }
86
- }
87
- }
88
- strings[strings.length - 1] += ')'
89
- return new SQL(strings as any, values)
90
- }
91
-
92
- /** Combine conditions with AND. */
93
- export function and(...conditions: SQL[]): SQL {
94
- return combine(conditions, 'AND')
95
- }
96
- /** Combine conditions with OR. */
97
- export function or(...conditions: SQL[]): SQL {
98
- return combine(conditions, 'OR')
99
- }
package/postgres/types.ts DELETED
@@ -1,48 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { SqlClient, Context, Middleware, Closeable } from '../types.ts'
3
- import type { ColumnBuilder, BoundTable, Table } from './schema/index.ts'
4
-
5
- declare module '../types.ts' {
6
- interface Context {
7
- sql: SqlClient
8
- }
9
- }
10
-
11
- export interface PostgresInjected {
12
- sql: SqlClient
13
- }
14
-
15
- export interface PostgresOptions {
16
- connection?: string | Record<string, unknown>
17
- signal?: AbortSignal
18
- closeTimeout?: number
19
- max?: number
20
- ssl?: boolean | Record<string, unknown>
21
- idle_timeout?: number
22
- connect_timeout?: number
23
- /** Per-statement timeout in ms. Set to 0 to disable. Default: 30_000. */
24
- statementTimeout?: number
25
- /** Called after every query completes. Receives query text, duration in ms, and row count. */
26
- onQuery?: (query: string, durationMs: number, rowCount: number) => void
27
- }
28
-
29
- export interface PostgresClient extends Middleware<Context, Context & PostgresInjected>, Closeable {
30
- sql: SqlClient
31
- /** Creates the migration tracking table (_weifuwu_migrations). Called once at startup. */
32
- migrate: () => Promise<void>
33
- /** Record that a module's migration has been applied (idempotent). */
34
- markMigrated: (moduleName: string) => Promise<void>
35
- /** Check whether a module has already been migrated. */
36
- isMigrated: (moduleName: string) => Promise<boolean>
37
- table: {
38
- <R extends Record<string, unknown>>(
39
- tableName: string,
40
- builders: { [K in keyof R]: ColumnBuilder<R[K]> },
41
- ): BoundTable<R>
42
- <R extends Record<string, unknown>>(schema: Table<R>): BoundTable<R>
43
- }
44
- transaction: <T>(fn: (sql: any) => Promise<T>, retryOpts?: { maxRetries?: number }) => Promise<T>
45
- /** Snapshot of connection pool state: active, idle, waiting, max connections. */
46
- poolStats: () => { active: number; idle: number; waiting: number; max: number }
47
- close: () => Promise<void>
48
- }