@kysera/dialects 0.7.3
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/LICENSE +21 -0
- package/README.md +1015 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
- package/src/adapters/mysql.ts +143 -0
- package/src/adapters/postgres.ts +121 -0
- package/src/adapters/sqlite.ts +109 -0
- package/src/connection.ts +66 -0
- package/src/factory.ts +58 -0
- package/src/helpers.ts +149 -0
- package/src/index.ts +55 -0
- package/src/types.ts +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
# @kysera/dialects
|
|
2
|
+
|
|
3
|
+
> Dialect-specific utilities for Kysely - PostgreSQL, MySQL, and SQLite support with unified adapter interface.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@kysera/dialects)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
## 🎯 Features
|
|
10
|
+
|
|
11
|
+
- ✅ **Zero Runtime Dependencies** - Only peer dependency on Kysely
|
|
12
|
+
- ✅ **Unified Adapter Interface** - Consistent API across all dialects
|
|
13
|
+
- ✅ **Multi-Database Support** - PostgreSQL, MySQL, and SQLite
|
|
14
|
+
- ✅ **Error Detection** - Detect unique, foreign key, and not-null constraint violations
|
|
15
|
+
- ✅ **Connection Utilities** - Parse and build connection URLs
|
|
16
|
+
- ✅ **Schema Introspection** - Table existence checks, column enumeration, database size
|
|
17
|
+
- ✅ **Testing Helpers** - Truncate tables, clean databases
|
|
18
|
+
- ✅ **100% Type Safe** - Full TypeScript support with strict mode
|
|
19
|
+
- ✅ **Cross-Runtime** - Works on Node.js, Bun, and Deno
|
|
20
|
+
|
|
21
|
+
## 📦 Related Packages
|
|
22
|
+
|
|
23
|
+
- **[@kysera/core](../core)** - Error handling, pagination, types, logger interface
|
|
24
|
+
- **[@kysera/executor](../executor)** - Unified Execution Layer with plugin interception
|
|
25
|
+
- **[@kysera/dal](../dal)** - Functional Data Access Layer with composable queries
|
|
26
|
+
- **[@kysera/repository](../repository)** - Repository pattern with plugin support
|
|
27
|
+
- **[@kysera/testing](../testing)** - Testing utilities, factories, database cleanup
|
|
28
|
+
|
|
29
|
+
## 📥 Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# npm
|
|
33
|
+
npm install @kysera/dialects kysely
|
|
34
|
+
|
|
35
|
+
# pnpm
|
|
36
|
+
pnpm add @kysera/dialects kysely
|
|
37
|
+
|
|
38
|
+
# bun
|
|
39
|
+
bun add @kysera/dialects kysely
|
|
40
|
+
|
|
41
|
+
# deno
|
|
42
|
+
import * as dialects from "npm:@kysera/dialects"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🚀 Quick Start
|
|
46
|
+
|
|
47
|
+
### Using Adapter Interface
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Kysely, PostgresDialect } from 'kysely'
|
|
51
|
+
import { Pool } from 'pg'
|
|
52
|
+
import { getAdapter } from '@kysera/dialects'
|
|
53
|
+
|
|
54
|
+
// Create database connection
|
|
55
|
+
const db = new Kysely({
|
|
56
|
+
dialect: new PostgresDialect({ pool: new Pool({ /* config */ }) })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Get dialect adapter
|
|
60
|
+
const adapter = getAdapter('postgres')
|
|
61
|
+
|
|
62
|
+
// Check if table exists
|
|
63
|
+
const exists = await adapter.tableExists(db, 'users')
|
|
64
|
+
|
|
65
|
+
// Get table columns
|
|
66
|
+
const columns = await adapter.getTableColumns(db, 'users')
|
|
67
|
+
// ['id', 'email', 'name', 'created_at', 'updated_at']
|
|
68
|
+
|
|
69
|
+
// Get all tables
|
|
70
|
+
const tables = await adapter.getTables(db)
|
|
71
|
+
|
|
72
|
+
// Get database size
|
|
73
|
+
const sizeBytes = await adapter.getDatabaseSize(db, 'mydb')
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Using Helper Functions
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import {
|
|
80
|
+
tableExists,
|
|
81
|
+
getTableColumns,
|
|
82
|
+
escapeIdentifier,
|
|
83
|
+
isUniqueConstraintError,
|
|
84
|
+
isForeignKeyError,
|
|
85
|
+
isNotNullError
|
|
86
|
+
} from '@kysera/dialects'
|
|
87
|
+
|
|
88
|
+
// Standalone helpers (backward compatible)
|
|
89
|
+
const exists = await tableExists(db, 'users', 'postgres')
|
|
90
|
+
const columns = await getTableColumns(db, 'users', 'postgres')
|
|
91
|
+
|
|
92
|
+
// Escape identifiers
|
|
93
|
+
const escaped = escapeIdentifier('user-data', 'mysql') // `user-data`
|
|
94
|
+
const pgEscaped = escapeIdentifier('user-data', 'postgres') // "user-data"
|
|
95
|
+
|
|
96
|
+
// Error detection
|
|
97
|
+
try {
|
|
98
|
+
await db.insertInto('users').values({ email: 'duplicate@example.com' }).execute()
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (isUniqueConstraintError(error, 'postgres')) {
|
|
101
|
+
console.error('Email already exists')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Connection URL Utilities
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { parseConnectionUrl, buildConnectionUrl, getDefaultPort } from '@kysera/dialects'
|
|
110
|
+
|
|
111
|
+
// Parse connection URL
|
|
112
|
+
const config = parseConnectionUrl('postgresql://user:pass@localhost:5432/mydb?ssl=true')
|
|
113
|
+
// {
|
|
114
|
+
// host: 'localhost',
|
|
115
|
+
// port: 5432,
|
|
116
|
+
// database: 'mydb',
|
|
117
|
+
// user: 'user',
|
|
118
|
+
// password: 'pass',
|
|
119
|
+
// ssl: true
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
// Build connection URL
|
|
123
|
+
const url = buildConnectionUrl('postgres', {
|
|
124
|
+
host: 'localhost',
|
|
125
|
+
database: 'mydb',
|
|
126
|
+
user: 'admin',
|
|
127
|
+
password: 'secret'
|
|
128
|
+
})
|
|
129
|
+
// 'postgresql://admin:secret@localhost:5432/mydb'
|
|
130
|
+
|
|
131
|
+
// Get default ports
|
|
132
|
+
getDefaultPort('postgres') // 5432
|
|
133
|
+
getDefaultPort('mysql') // 3306
|
|
134
|
+
getDefaultPort('sqlite') // null
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 📚 Table of Contents
|
|
140
|
+
|
|
141
|
+
1. [Adapter Interface](#-adapter-interface)
|
|
142
|
+
- [PostgreSQL Adapter](#postgresql-adapter)
|
|
143
|
+
- [MySQL Adapter](#mysql-adapter)
|
|
144
|
+
- [SQLite Adapter](#sqlite-adapter)
|
|
145
|
+
2. [Connection Utilities](#-connection-utilities)
|
|
146
|
+
- [Parse Connection URL](#parse-connection-url)
|
|
147
|
+
- [Build Connection URL](#build-connection-url)
|
|
148
|
+
- [Get Default Port](#get-default-port)
|
|
149
|
+
3. [Error Detection](#-error-detection)
|
|
150
|
+
- [Unique Constraint Errors](#unique-constraint-errors)
|
|
151
|
+
- [Foreign Key Errors](#foreign-key-errors)
|
|
152
|
+
- [Not-Null Errors](#not-null-errors)
|
|
153
|
+
4. [Helper Functions](#-helper-functions)
|
|
154
|
+
- [Schema Introspection](#schema-introspection)
|
|
155
|
+
- [Identifier Escaping](#identifier-escaping)
|
|
156
|
+
- [Timestamp Utilities](#timestamp-utilities)
|
|
157
|
+
- [Database Management](#database-management)
|
|
158
|
+
5. [API Reference](#-api-reference)
|
|
159
|
+
6. [Best Practices](#-best-practices)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🔌 Adapter Interface
|
|
164
|
+
|
|
165
|
+
The adapter pattern provides a unified interface for dialect-specific operations. Each dialect has its own adapter implementing the `DialectAdapter` interface.
|
|
166
|
+
|
|
167
|
+
### Getting Adapters
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { getAdapter, createDialectAdapter } from '@kysera/dialects'
|
|
171
|
+
|
|
172
|
+
// Get singleton adapter (recommended)
|
|
173
|
+
const adapter = getAdapter('postgres')
|
|
174
|
+
|
|
175
|
+
// Create new adapter instance
|
|
176
|
+
const newAdapter = createDialectAdapter('mysql')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### PostgreSQL Adapter
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { PostgresAdapter, postgresAdapter } from '@kysera/dialects'
|
|
183
|
+
|
|
184
|
+
// Use singleton
|
|
185
|
+
const adapter = postgresAdapter
|
|
186
|
+
|
|
187
|
+
// Or create instance
|
|
188
|
+
const adapter = new PostgresAdapter()
|
|
189
|
+
|
|
190
|
+
// Adapter methods
|
|
191
|
+
adapter.getDefaultPort() // 5432
|
|
192
|
+
adapter.getCurrentTimestamp() // 'CURRENT_TIMESTAMP'
|
|
193
|
+
adapter.escapeIdentifier('col') // '"col"'
|
|
194
|
+
adapter.formatDate(new Date()) // ISO 8601 string
|
|
195
|
+
|
|
196
|
+
// PostgreSQL error detection
|
|
197
|
+
adapter.isUniqueConstraintError(error) // Code: 23505
|
|
198
|
+
adapter.isForeignKeyError(error) // Code: 23503
|
|
199
|
+
adapter.isNotNullError(error) // Code: 23502
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**PostgreSQL-specific features:**
|
|
203
|
+
- Uses `information_schema.tables` for schema introspection
|
|
204
|
+
- Filters by `table_schema = 'public'` by default
|
|
205
|
+
- Supports `pg_database_size()` for database size queries
|
|
206
|
+
- Error detection via PostgreSQL error codes (23xxx series)
|
|
207
|
+
|
|
208
|
+
### MySQL Adapter
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { MySQLAdapter, mysqlAdapter } from '@kysera/dialects'
|
|
212
|
+
|
|
213
|
+
const adapter = mysqlAdapter
|
|
214
|
+
|
|
215
|
+
adapter.getDefaultPort() // 3306
|
|
216
|
+
adapter.getCurrentTimestamp() // 'CURRENT_TIMESTAMP'
|
|
217
|
+
adapter.escapeIdentifier('col') // '`col`'
|
|
218
|
+
adapter.formatDate(new Date()) // ISO 8601 string
|
|
219
|
+
|
|
220
|
+
// MySQL error detection
|
|
221
|
+
adapter.isUniqueConstraintError(error) // ER_DUP_ENTRY, ER_DUP_KEY
|
|
222
|
+
adapter.isForeignKeyError(error) // ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED
|
|
223
|
+
adapter.isNotNullError(error) // ER_BAD_NULL_ERROR, ER_NO_DEFAULT_FOR_FIELD
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**MySQL-specific features:**
|
|
227
|
+
- Uses `information_schema.tables` with `table_schema = database()`
|
|
228
|
+
- Supports backtick identifier escaping
|
|
229
|
+
- Error detection via MySQL error codes (ER_* constants)
|
|
230
|
+
- Database size queries via `information_schema.tables`
|
|
231
|
+
|
|
232
|
+
### SQLite Adapter
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { SQLiteAdapter, sqliteAdapter } from '@kysera/dialects'
|
|
236
|
+
|
|
237
|
+
const adapter = sqliteAdapter
|
|
238
|
+
|
|
239
|
+
adapter.getDefaultPort() // null (file-based)
|
|
240
|
+
adapter.getCurrentTimestamp() // "datetime('now')"
|
|
241
|
+
adapter.escapeIdentifier('col') // '"col"'
|
|
242
|
+
adapter.formatDate(new Date()) // ISO 8601 string
|
|
243
|
+
|
|
244
|
+
// SQLite error detection (message-based)
|
|
245
|
+
adapter.isUniqueConstraintError(error) // "UNIQUE constraint failed"
|
|
246
|
+
adapter.isForeignKeyError(error) // "FOREIGN KEY constraint failed"
|
|
247
|
+
adapter.isNotNullError(error) // "NOT NULL constraint failed"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**SQLite-specific features:**
|
|
251
|
+
- Uses `sqlite_master` for schema introspection
|
|
252
|
+
- No default port (file-based database)
|
|
253
|
+
- Error detection via message parsing
|
|
254
|
+
- Lightweight database size calculation via `page_count * page_size`
|
|
255
|
+
|
|
256
|
+
### Custom Adapter Registration
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { registerAdapter, type DialectAdapter } from '@kysera/dialects'
|
|
260
|
+
|
|
261
|
+
class CustomDialectAdapter implements DialectAdapter {
|
|
262
|
+
readonly dialect = 'custom' as any
|
|
263
|
+
|
|
264
|
+
getDefaultPort() { return 9999 }
|
|
265
|
+
getCurrentTimestamp() { return 'NOW()' }
|
|
266
|
+
// ... implement all required methods
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Register custom adapter
|
|
270
|
+
registerAdapter(new CustomDialectAdapter())
|
|
271
|
+
|
|
272
|
+
// Now available via getAdapter
|
|
273
|
+
const adapter = getAdapter('custom' as any)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 🔗 Connection Utilities
|
|
279
|
+
|
|
280
|
+
### Parse Connection URL
|
|
281
|
+
|
|
282
|
+
Parse a database connection URL into a structured configuration object.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { parseConnectionUrl } from '@kysera/dialects'
|
|
286
|
+
|
|
287
|
+
// PostgreSQL
|
|
288
|
+
const config = parseConnectionUrl('postgresql://user:pass@localhost:5432/mydb?ssl=true')
|
|
289
|
+
// {
|
|
290
|
+
// host: 'localhost',
|
|
291
|
+
// port: 5432,
|
|
292
|
+
// database: 'mydb',
|
|
293
|
+
// user: 'user',
|
|
294
|
+
// password: 'pass',
|
|
295
|
+
// ssl: true
|
|
296
|
+
// }
|
|
297
|
+
|
|
298
|
+
// MySQL
|
|
299
|
+
const mysqlConfig = parseConnectionUrl('mysql://admin:secret@db.example.com:3306/production')
|
|
300
|
+
// {
|
|
301
|
+
// host: 'db.example.com',
|
|
302
|
+
// port: 3306,
|
|
303
|
+
// database: 'production',
|
|
304
|
+
// user: 'admin',
|
|
305
|
+
// password: 'secret',
|
|
306
|
+
// ssl: false
|
|
307
|
+
// }
|
|
308
|
+
|
|
309
|
+
// SQLite (file path)
|
|
310
|
+
const sqliteConfig = parseConnectionUrl('sqlite:///path/to/database.db')
|
|
311
|
+
// {
|
|
312
|
+
// host: '',
|
|
313
|
+
// port: undefined,
|
|
314
|
+
// database: '/path/to/database.db',
|
|
315
|
+
// user: undefined,
|
|
316
|
+
// password: undefined,
|
|
317
|
+
// ssl: false
|
|
318
|
+
// }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Supported URL formats:**
|
|
322
|
+
- `postgresql://[user[:password]@][host][:port]/database[?ssl=true]`
|
|
323
|
+
- `mysql://[user[:password]@][host][:port]/database[?ssl=true]`
|
|
324
|
+
- `sqlite:///path/to/file.db`
|
|
325
|
+
|
|
326
|
+
### Build Connection URL
|
|
327
|
+
|
|
328
|
+
Build a connection URL from a configuration object.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { buildConnectionUrl } from '@kysera/dialects'
|
|
332
|
+
|
|
333
|
+
// Basic URL
|
|
334
|
+
const url = buildConnectionUrl('postgres', {
|
|
335
|
+
host: 'localhost',
|
|
336
|
+
database: 'mydb'
|
|
337
|
+
})
|
|
338
|
+
// 'postgresql://localhost:5432/mydb'
|
|
339
|
+
|
|
340
|
+
// With authentication
|
|
341
|
+
const authUrl = buildConnectionUrl('mysql', {
|
|
342
|
+
host: 'db.example.com',
|
|
343
|
+
database: 'production',
|
|
344
|
+
user: 'admin',
|
|
345
|
+
password: 'secret',
|
|
346
|
+
port: 3306
|
|
347
|
+
})
|
|
348
|
+
// 'mysql://admin:secret@db.example.com:3306/production'
|
|
349
|
+
|
|
350
|
+
// With SSL
|
|
351
|
+
const sslUrl = buildConnectionUrl('postgres', {
|
|
352
|
+
host: 'secure.db.com',
|
|
353
|
+
database: 'app',
|
|
354
|
+
user: 'readonly',
|
|
355
|
+
ssl: true
|
|
356
|
+
})
|
|
357
|
+
// 'postgresql://readonly@secure.db.com:5432/app?ssl=true'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Default ports:**
|
|
361
|
+
- PostgreSQL: 5432
|
|
362
|
+
- MySQL: 3306
|
|
363
|
+
- SQLite: null (file-based)
|
|
364
|
+
|
|
365
|
+
### Get Default Port
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { getDefaultPort } from '@kysera/dialects'
|
|
369
|
+
|
|
370
|
+
getDefaultPort('postgres') // 5432
|
|
371
|
+
getDefaultPort('mysql') // 3306
|
|
372
|
+
getDefaultPort('sqlite') // null
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 🚨 Error Detection
|
|
378
|
+
|
|
379
|
+
Detect database constraint violations across different dialects with a unified API.
|
|
380
|
+
|
|
381
|
+
### Unique Constraint Errors
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { isUniqueConstraintError } from '@kysera/dialects'
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
await db
|
|
388
|
+
.insertInto('users')
|
|
389
|
+
.values({ email: 'existing@example.com', name: 'John' })
|
|
390
|
+
.execute()
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (isUniqueConstraintError(error, 'postgres')) {
|
|
393
|
+
console.error('Email already exists')
|
|
394
|
+
// Handle duplicate error
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Detection criteria:**
|
|
400
|
+
|
|
401
|
+
| Dialect | Detection Method |
|
|
402
|
+
|---------|------------------|
|
|
403
|
+
| PostgreSQL | Error code `23505` or message contains "unique constraint" |
|
|
404
|
+
| MySQL | Error code `ER_DUP_ENTRY` or `ER_DUP_KEY` |
|
|
405
|
+
| SQLite | Message contains "UNIQUE constraint failed" |
|
|
406
|
+
|
|
407
|
+
### Foreign Key Errors
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { isForeignKeyError } from '@kysera/dialects'
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
await db
|
|
414
|
+
.insertInto('posts')
|
|
415
|
+
.values({ user_id: 999, title: 'Post', content: '...' })
|
|
416
|
+
.execute()
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (isForeignKeyError(error, 'postgres')) {
|
|
419
|
+
console.error('User does not exist')
|
|
420
|
+
// Handle foreign key violation
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Detection criteria:**
|
|
426
|
+
|
|
427
|
+
| Dialect | Detection Method |
|
|
428
|
+
|---------|------------------|
|
|
429
|
+
| PostgreSQL | Error code `23503` or message contains "foreign key constraint" |
|
|
430
|
+
| MySQL | Error code `ER_NO_REFERENCED_ROW` or `ER_ROW_IS_REFERENCED` |
|
|
431
|
+
| SQLite | Message contains "FOREIGN KEY constraint failed" |
|
|
432
|
+
|
|
433
|
+
### Not-Null Errors
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { isNotNullError } from '@kysera/dialects'
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
await db
|
|
440
|
+
.insertInto('users')
|
|
441
|
+
.values({ name: 'John' }) // Missing required email
|
|
442
|
+
.execute()
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (isNotNullError(error, 'postgres')) {
|
|
445
|
+
console.error('Missing required field')
|
|
446
|
+
// Handle not-null violation
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Detection criteria:**
|
|
452
|
+
|
|
453
|
+
| Dialect | Detection Method |
|
|
454
|
+
|---------|------------------|
|
|
455
|
+
| PostgreSQL | Error code `23502` or message contains "not-null constraint" |
|
|
456
|
+
| MySQL | Error code `ER_BAD_NULL_ERROR` or `ER_NO_DEFAULT_FOR_FIELD` |
|
|
457
|
+
| SQLite | Message contains "NOT NULL constraint failed" |
|
|
458
|
+
|
|
459
|
+
### Adapter-based Error Detection
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { getAdapter } from '@kysera/dialects'
|
|
463
|
+
|
|
464
|
+
const adapter = getAdapter('postgres')
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
await db.insertInto('users').values(data).execute()
|
|
468
|
+
} catch (error) {
|
|
469
|
+
if (adapter.isUniqueConstraintError(error)) {
|
|
470
|
+
// Handle unique violation
|
|
471
|
+
} else if (adapter.isForeignKeyError(error)) {
|
|
472
|
+
// Handle foreign key violation
|
|
473
|
+
} else if (adapter.isNotNullError(error)) {
|
|
474
|
+
// Handle not-null violation
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## 🛠 Helper Functions
|
|
482
|
+
|
|
483
|
+
### Schema Introspection
|
|
484
|
+
|
|
485
|
+
#### Check Table Existence
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { tableExists } from '@kysera/dialects'
|
|
489
|
+
|
|
490
|
+
const exists = await tableExists(db, 'users', 'postgres')
|
|
491
|
+
|
|
492
|
+
if (!exists) {
|
|
493
|
+
console.log('Users table does not exist')
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Get Table Columns
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { getTableColumns } from '@kysera/dialects'
|
|
501
|
+
|
|
502
|
+
const columns = await getTableColumns(db, 'users', 'postgres')
|
|
503
|
+
// ['id', 'email', 'name', 'created_at', 'updated_at']
|
|
504
|
+
|
|
505
|
+
// Check if column exists
|
|
506
|
+
if (columns.includes('deleted_at')) {
|
|
507
|
+
console.log('Table has soft-delete support')
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### Get All Tables
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { getTables } from '@kysera/dialects'
|
|
515
|
+
|
|
516
|
+
const tables = await getTables(db, 'postgres')
|
|
517
|
+
// ['users', 'posts', 'comments', 'tags']
|
|
518
|
+
|
|
519
|
+
console.log(`Database has ${tables.length} tables`)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Identifier Escaping
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { escapeIdentifier } from '@kysera/dialects'
|
|
526
|
+
|
|
527
|
+
// PostgreSQL (double quotes)
|
|
528
|
+
escapeIdentifier('user-data', 'postgres') // "user-data"
|
|
529
|
+
escapeIdentifier('select', 'postgres') // "select"
|
|
530
|
+
|
|
531
|
+
// MySQL (backticks)
|
|
532
|
+
escapeIdentifier('user-data', 'mysql') // `user-data`
|
|
533
|
+
escapeIdentifier('order', 'mysql') // `order`
|
|
534
|
+
|
|
535
|
+
// SQLite (double quotes)
|
|
536
|
+
escapeIdentifier('user-data', 'sqlite') // "user-data"
|
|
537
|
+
|
|
538
|
+
// Handles quotes in identifiers
|
|
539
|
+
escapeIdentifier('user"data', 'postgres') // "user""data"
|
|
540
|
+
escapeIdentifier('user`data', 'mysql') // `user``data`
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Timestamp Utilities
|
|
544
|
+
|
|
545
|
+
#### Get Current Timestamp
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
import { getCurrentTimestamp } from '@kysera/dialects'
|
|
549
|
+
|
|
550
|
+
// PostgreSQL
|
|
551
|
+
getCurrentTimestamp('postgres') // 'CURRENT_TIMESTAMP'
|
|
552
|
+
|
|
553
|
+
// MySQL
|
|
554
|
+
getCurrentTimestamp('mysql') // 'CURRENT_TIMESTAMP'
|
|
555
|
+
|
|
556
|
+
// SQLite
|
|
557
|
+
getCurrentTimestamp('sqlite') // "datetime('now')"
|
|
558
|
+
|
|
559
|
+
// Usage in queries
|
|
560
|
+
const timestamp = getCurrentTimestamp('postgres')
|
|
561
|
+
await db
|
|
562
|
+
.insertInto('logs')
|
|
563
|
+
.values({ message: 'Event', created_at: sql`${sql.raw(timestamp)}` })
|
|
564
|
+
.execute()
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
#### Format Date
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
import { formatDate } from '@kysera/dialects'
|
|
571
|
+
|
|
572
|
+
const date = new Date('2024-01-15T10:30:00Z')
|
|
573
|
+
|
|
574
|
+
// All dialects return ISO 8601 format
|
|
575
|
+
formatDate(date, 'postgres') // '2024-01-15T10:30:00.000Z'
|
|
576
|
+
formatDate(date, 'mysql') // '2024-01-15T10:30:00.000Z'
|
|
577
|
+
formatDate(date, 'sqlite') // '2024-01-15T10:30:00.000Z'
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Database Management
|
|
581
|
+
|
|
582
|
+
#### Get Database Size
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { getDatabaseSize } from '@kysera/dialects'
|
|
586
|
+
|
|
587
|
+
// PostgreSQL
|
|
588
|
+
const size = await getDatabaseSize(db, 'mydb', 'postgres')
|
|
589
|
+
console.log(`Database size: ${(size / 1024 / 1024).toFixed(2)} MB`)
|
|
590
|
+
|
|
591
|
+
// MySQL
|
|
592
|
+
const mysqlSize = await getDatabaseSize(db, 'production', 'mysql')
|
|
593
|
+
|
|
594
|
+
// SQLite
|
|
595
|
+
const sqliteSize = await getDatabaseSize(db, undefined, 'sqlite')
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Returns:** Size in bytes
|
|
599
|
+
|
|
600
|
+
#### Truncate All Tables
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
import { truncateAllTables } from '@kysera/dialects'
|
|
604
|
+
|
|
605
|
+
// Truncate all tables (for testing)
|
|
606
|
+
await truncateAllTables(db, 'postgres')
|
|
607
|
+
|
|
608
|
+
// Exclude specific tables
|
|
609
|
+
await truncateAllTables(db, 'postgres', ['migrations', 'schema_version'])
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Warning:** This permanently deletes all data. Use only in test environments.
|
|
613
|
+
|
|
614
|
+
**Behavior:**
|
|
615
|
+
- PostgreSQL: `TRUNCATE TABLE ... CASCADE`
|
|
616
|
+
- MySQL: `TRUNCATE TABLE ...`
|
|
617
|
+
- SQLite: `DELETE FROM ...` (no TRUNCATE support)
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## 📖 API Reference
|
|
622
|
+
|
|
623
|
+
### Factory Functions
|
|
624
|
+
|
|
625
|
+
#### `getAdapter(dialect: DatabaseDialect): DialectAdapter`
|
|
626
|
+
|
|
627
|
+
Get singleton adapter for specified dialect.
|
|
628
|
+
|
|
629
|
+
**Parameters:**
|
|
630
|
+
- `dialect` - `'postgres' | 'mysql' | 'sqlite'`
|
|
631
|
+
|
|
632
|
+
**Returns:** Dialect adapter instance
|
|
633
|
+
|
|
634
|
+
**Throws:** Error if dialect is unknown
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
#### `createDialectAdapter(dialect: DatabaseDialect): DialectAdapter`
|
|
639
|
+
|
|
640
|
+
Create new adapter instance.
|
|
641
|
+
|
|
642
|
+
**Parameters:**
|
|
643
|
+
- `dialect` - `'postgres' | 'mysql' | 'sqlite'`
|
|
644
|
+
|
|
645
|
+
**Returns:** New dialect adapter instance
|
|
646
|
+
|
|
647
|
+
**Use Case:** When you need multiple adapter instances with different configurations.
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
#### `registerAdapter(adapter: DialectAdapter): void`
|
|
652
|
+
|
|
653
|
+
Register custom dialect adapter.
|
|
654
|
+
|
|
655
|
+
**Parameters:**
|
|
656
|
+
- `adapter` - Custom adapter implementing `DialectAdapter` interface
|
|
657
|
+
|
|
658
|
+
**Use Case:** Extend with custom database support.
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
### Adapter Interface
|
|
663
|
+
|
|
664
|
+
#### `DialectAdapter`
|
|
665
|
+
|
|
666
|
+
Interface for dialect-specific operations.
|
|
667
|
+
|
|
668
|
+
**Properties:**
|
|
669
|
+
- `dialect: DatabaseDialect` - The dialect this adapter handles
|
|
670
|
+
|
|
671
|
+
**Methods:**
|
|
672
|
+
- `getDefaultPort(): number | null` - Get default port for this dialect
|
|
673
|
+
- `getCurrentTimestamp(): string` - Get SQL expression for current timestamp
|
|
674
|
+
- `escapeIdentifier(identifier: string): string` - Escape identifier for this dialect
|
|
675
|
+
- `formatDate(date: Date): string` - Format date for this dialect
|
|
676
|
+
- `isUniqueConstraintError(error: unknown): boolean` - Check for unique constraint violation
|
|
677
|
+
- `isForeignKeyError(error: unknown): boolean` - Check for foreign key violation
|
|
678
|
+
- `isNotNullError(error: unknown): boolean` - Check for not-null violation
|
|
679
|
+
- `tableExists(db: Kysely<any>, tableName: string): Promise<boolean>` - Check if table exists
|
|
680
|
+
- `getTableColumns(db: Kysely<any>, tableName: string): Promise<string[]>` - Get table columns
|
|
681
|
+
- `getTables(db: Kysely<any>): Promise<string[]>` - Get all tables
|
|
682
|
+
- `getDatabaseSize(db: Kysely<any>, databaseName?: string): Promise<number>` - Get database size in bytes
|
|
683
|
+
- `truncateTable(db: Kysely<any>, tableName: string): Promise<void>` - Truncate a table
|
|
684
|
+
- `truncateAllTables(db: Kysely<any>, exclude?: string[]): Promise<void>` - Truncate all tables
|
|
685
|
+
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
### Connection Utilities
|
|
689
|
+
|
|
690
|
+
#### `parseConnectionUrl(url: string): ConnectionConfig`
|
|
691
|
+
|
|
692
|
+
Parse connection URL into configuration object.
|
|
693
|
+
|
|
694
|
+
**Parameters:**
|
|
695
|
+
- `url` - Database connection URL
|
|
696
|
+
|
|
697
|
+
**Returns:** `ConnectionConfig` object
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
#### `buildConnectionUrl(dialect: DatabaseDialect, config: ConnectionConfig): string`
|
|
702
|
+
|
|
703
|
+
Build connection URL from configuration.
|
|
704
|
+
|
|
705
|
+
**Parameters:**
|
|
706
|
+
- `dialect` - Database dialect
|
|
707
|
+
- `config` - Connection configuration
|
|
708
|
+
|
|
709
|
+
**Returns:** Connection URL string
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
#### `getDefaultPort(dialect: DatabaseDialect): number | null`
|
|
714
|
+
|
|
715
|
+
Get default port for dialect.
|
|
716
|
+
|
|
717
|
+
**Parameters:**
|
|
718
|
+
- `dialect` - Database dialect
|
|
719
|
+
|
|
720
|
+
**Returns:** Port number or null for SQLite
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
### Helper Functions
|
|
725
|
+
|
|
726
|
+
#### `tableExists(db: Kysely<any>, tableName: string, dialect: DatabaseDialect): Promise<boolean>`
|
|
727
|
+
|
|
728
|
+
Check if table exists.
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
#### `getTableColumns(db: Kysely<any>, tableName: string, dialect: DatabaseDialect): Promise<string[]>`
|
|
733
|
+
|
|
734
|
+
Get column names for a table.
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
#### `getTables(db: Kysely<any>, dialect: DatabaseDialect): Promise<string[]>`
|
|
739
|
+
|
|
740
|
+
Get all tables in database.
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
#### `escapeIdentifier(identifier: string, dialect: DatabaseDialect): string`
|
|
745
|
+
|
|
746
|
+
Escape identifier for SQL.
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
#### `getCurrentTimestamp(dialect: DatabaseDialect): string`
|
|
751
|
+
|
|
752
|
+
Get SQL expression for current timestamp.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
#### `formatDate(date: Date, dialect: DatabaseDialect): string`
|
|
757
|
+
|
|
758
|
+
Format date for SQL.
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
#### `isUniqueConstraintError(error: unknown, dialect: DatabaseDialect): boolean`
|
|
763
|
+
|
|
764
|
+
Check if error is unique constraint violation.
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
#### `isForeignKeyError(error: unknown, dialect: DatabaseDialect): boolean`
|
|
769
|
+
|
|
770
|
+
Check if error is foreign key violation.
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
#### `isNotNullError(error: unknown, dialect: DatabaseDialect): boolean`
|
|
775
|
+
|
|
776
|
+
Check if error is not-null violation.
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
#### `getDatabaseSize(db: Kysely<any>, databaseName: string | undefined, dialect: DatabaseDialect): Promise<number>`
|
|
781
|
+
|
|
782
|
+
Get database size in bytes.
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
#### `truncateAllTables(db: Kysely<any>, dialect: DatabaseDialect, exclude?: string[]): Promise<void>`
|
|
787
|
+
|
|
788
|
+
Truncate all tables in database.
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
### Types
|
|
793
|
+
|
|
794
|
+
#### `type DatabaseDialect = 'postgres' | 'mysql' | 'sqlite'`
|
|
795
|
+
|
|
796
|
+
Supported database dialects.
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
#### `interface ConnectionConfig`
|
|
801
|
+
|
|
802
|
+
Database connection configuration.
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
interface ConnectionConfig {
|
|
806
|
+
host?: string | undefined
|
|
807
|
+
port?: number | undefined
|
|
808
|
+
database: string
|
|
809
|
+
user?: string | undefined
|
|
810
|
+
password?: string | undefined
|
|
811
|
+
ssl?: boolean | undefined
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
#### `interface DatabaseErrorLike`
|
|
818
|
+
|
|
819
|
+
Error object shape for database error detection.
|
|
820
|
+
|
|
821
|
+
```typescript
|
|
822
|
+
interface DatabaseErrorLike {
|
|
823
|
+
message?: string
|
|
824
|
+
code?: string
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## ✨ Best Practices
|
|
831
|
+
|
|
832
|
+
### 1. Use Adapter Interface for Dialect-Agnostic Code
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
// ✅ Good: Works with any dialect
|
|
836
|
+
import { getAdapter } from '@kysera/dialects'
|
|
837
|
+
|
|
838
|
+
function checkSchema(db: Kysely<any>, dialect: DatabaseDialect) {
|
|
839
|
+
const adapter = getAdapter(dialect)
|
|
840
|
+
return adapter.tableExists(db, 'users')
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ❌ Bad: Hard-coded dialect logic
|
|
844
|
+
function checkSchema(db: Kysely<any>) {
|
|
845
|
+
return db.selectFrom('information_schema.tables')
|
|
846
|
+
.where('table_name', '=', 'users')
|
|
847
|
+
.executeTakeFirst()
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### 2. Use Helper Functions for Simple Operations
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
// ✅ Good: Simple and readable
|
|
855
|
+
const exists = await tableExists(db, 'users', 'postgres')
|
|
856
|
+
|
|
857
|
+
// ❌ Unnecessary: Adapter for single operation
|
|
858
|
+
const adapter = getAdapter('postgres')
|
|
859
|
+
const exists = await adapter.tableExists(db, 'users')
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### 3. Centralize Error Handling
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
import { getAdapter } from '@kysera/dialects'
|
|
866
|
+
import { parseDatabaseError } from '@kysera/core'
|
|
867
|
+
|
|
868
|
+
async function handleDatabaseError(error: unknown, dialect: DatabaseDialect) {
|
|
869
|
+
const adapter = getAdapter(dialect)
|
|
870
|
+
|
|
871
|
+
if (adapter.isUniqueConstraintError(error)) {
|
|
872
|
+
return { type: 'duplicate', message: 'Record already exists' }
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (adapter.isForeignKeyError(error)) {
|
|
876
|
+
return { type: 'reference', message: 'Referenced record not found' }
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (adapter.isNotNullError(error)) {
|
|
880
|
+
return { type: 'required', message: 'Required field missing' }
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Use @kysera/core for detailed error parsing
|
|
884
|
+
const dbError = parseDatabaseError(error, dialect)
|
|
885
|
+
return { type: 'database', message: dbError.message }
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### 4. Parse Connection URLs in Configuration
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
import { parseConnectionUrl } from '@kysera/dialects'
|
|
893
|
+
|
|
894
|
+
// ✅ Good: Parse from environment variable
|
|
895
|
+
const config = parseConnectionUrl(process.env.DATABASE_URL!)
|
|
896
|
+
|
|
897
|
+
const db = new Kysely({
|
|
898
|
+
dialect: new PostgresDialect({
|
|
899
|
+
pool: new Pool({
|
|
900
|
+
host: config.host,
|
|
901
|
+
port: config.port,
|
|
902
|
+
database: config.database,
|
|
903
|
+
user: config.user,
|
|
904
|
+
password: config.password,
|
|
905
|
+
ssl: config.ssl
|
|
906
|
+
})
|
|
907
|
+
})
|
|
908
|
+
})
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### 5. Use Type Guards for Error Detection
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
import { getAdapter } from '@kysera/dialects'
|
|
915
|
+
|
|
916
|
+
const adapter = getAdapter(dialect)
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
await db.insertInto('users').values(data).execute()
|
|
920
|
+
} catch (error) {
|
|
921
|
+
// ✅ Good: Type-safe error detection
|
|
922
|
+
if (adapter.isUniqueConstraintError(error)) {
|
|
923
|
+
throw new Error('Email already registered')
|
|
924
|
+
}
|
|
925
|
+
throw error
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### 6. Escape Dynamic Identifiers
|
|
930
|
+
|
|
931
|
+
```typescript
|
|
932
|
+
import { escapeIdentifier } from '@kysera/dialects'
|
|
933
|
+
import { sql } from 'kysely'
|
|
934
|
+
|
|
935
|
+
// ✅ Good: Escape dynamic table/column names
|
|
936
|
+
function selectFromTable(tableName: string, dialect: DatabaseDialect) {
|
|
937
|
+
const escaped = escapeIdentifier(tableName, dialect)
|
|
938
|
+
return db.selectFrom(sql.raw(escaped)).selectAll()
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// ❌ Bad: SQL injection risk
|
|
942
|
+
function selectFromTable(tableName: string) {
|
|
943
|
+
return db.selectFrom(sql.raw(tableName)).selectAll()
|
|
944
|
+
}
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### 7. Use Truncate for Test Cleanup
|
|
948
|
+
|
|
949
|
+
```typescript
|
|
950
|
+
import { truncateAllTables } from '@kysera/dialects'
|
|
951
|
+
|
|
952
|
+
describe('User tests', () => {
|
|
953
|
+
afterEach(async () => {
|
|
954
|
+
// ✅ Fast cleanup for tests
|
|
955
|
+
await truncateAllTables(db, 'postgres', ['migrations'])
|
|
956
|
+
})
|
|
957
|
+
|
|
958
|
+
// ❌ Slow: Individual deletes
|
|
959
|
+
afterEach(async () => {
|
|
960
|
+
await db.deleteFrom('users').execute()
|
|
961
|
+
await db.deleteFrom('posts').execute()
|
|
962
|
+
// ...
|
|
963
|
+
})
|
|
964
|
+
})
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### 8. Cache Adapters in Long-Running Applications
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
// ✅ Good: Cache adapter instance
|
|
971
|
+
const adapter = getAdapter(dialect)
|
|
972
|
+
|
|
973
|
+
for (const table of tables) {
|
|
974
|
+
await adapter.tableExists(db, table)
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// ❌ Unnecessary: Re-fetch adapter each time
|
|
978
|
+
for (const table of tables) {
|
|
979
|
+
const adapter = getAdapter(dialect)
|
|
980
|
+
await adapter.tableExists(db, table)
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
---
|
|
985
|
+
|
|
986
|
+
## 🤝 Contributing
|
|
987
|
+
|
|
988
|
+
Contributions are welcome! This package follows strict development principles:
|
|
989
|
+
|
|
990
|
+
- ✅ **Zero runtime dependencies** (peer deps only)
|
|
991
|
+
- ✅ **100% type safe** (TypeScript strict mode)
|
|
992
|
+
- ✅ **Comprehensive test coverage** (95%+ coverage)
|
|
993
|
+
- ✅ **Cross-database compatible** (PostgreSQL, MySQL, SQLite)
|
|
994
|
+
- ✅ **ESM only** (no CommonJS)
|
|
995
|
+
|
|
996
|
+
See [CLAUDE.md](../../CLAUDE.md) for development guidelines.
|
|
997
|
+
|
|
998
|
+
---
|
|
999
|
+
|
|
1000
|
+
## 📄 License
|
|
1001
|
+
|
|
1002
|
+
MIT © Kysera
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
## 🔗 Links
|
|
1007
|
+
|
|
1008
|
+
- [GitHub Repository](https://github.com/kysera-dev/kysera)
|
|
1009
|
+
- [Kysely Documentation](https://kysely.dev)
|
|
1010
|
+
- [Issue Tracker](https://github.com/kysera-dev/kysera/issues)
|
|
1011
|
+
- [Changelog](../../CHANGELOG.md)
|
|
1012
|
+
|
|
1013
|
+
---
|
|
1014
|
+
|
|
1015
|
+
**Built with ❤️ for production TypeScript applications**
|