@dockstat/sqlite-wrapper 1.3.5 → 1.3.6
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 +1 -1
- package/package.json +1 -1
- package/src/index.ts +24 -17
- package/src/migration.ts +28 -54
- package/src/types.ts +32 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Schema-first table helpers, an expressive chainable QueryBuilder, safe defaults
|
|
|
22
22
|
### Bug Fixes
|
|
23
23
|
|
|
24
24
|
- **Fixed Boolean parsing** — Boolean columns now correctly convert SQLite's `0`/`1` to JavaScript `true`/`false`
|
|
25
|
-
- **Fixed
|
|
25
|
+
- **Fixed incorrect packaging** — Before the `publish` script was added, workspace dependencies were not correctly propagated
|
|
26
26
|
|
|
27
27
|
### Architecture Improvements
|
|
28
28
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,9 +18,9 @@ import type {
|
|
|
18
18
|
ColumnDefinition,
|
|
19
19
|
IndexColumn,
|
|
20
20
|
IndexMethod,
|
|
21
|
+
MigrationOptions,
|
|
21
22
|
Parser,
|
|
22
23
|
TableOptions,
|
|
23
|
-
MigrationOptions,
|
|
24
24
|
} from "./types"
|
|
25
25
|
import { createLogger, type SqliteLogger } from "./utils"
|
|
26
26
|
|
|
@@ -220,6 +220,23 @@ class DB {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
private normalizeMigrationOptions(migrate: TableOptions<Record<string, unknown>>["migrate"]): {
|
|
224
|
+
enabled: boolean
|
|
225
|
+
options: MigrationOptions
|
|
226
|
+
} {
|
|
227
|
+
if (migrate === false) return { enabled: false, options: {} }
|
|
228
|
+
if (migrate && typeof migrate === "object") {
|
|
229
|
+
return { enabled: true, options: migrate }
|
|
230
|
+
}
|
|
231
|
+
return { enabled: true, options: {} }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private shouldCheckExistingTable<_T>(tableName: string, options?: TableOptions<_T>): boolean {
|
|
235
|
+
if (options?.temporary) return false
|
|
236
|
+
if (tableName === ":memory:") return false
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
223
240
|
/**
|
|
224
241
|
* Create a table with comprehensive type safety and feature support.
|
|
225
242
|
*
|
|
@@ -234,20 +251,15 @@ class DB {
|
|
|
234
251
|
columns: Record<keyof _T, ColumnDefinition>,
|
|
235
252
|
options?: TableOptions<_T>
|
|
236
253
|
): QueryBuilder<_T> {
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
const { enabled: migrateEnabled, options: migrationOptions } = this.normalizeMigrationOptions(
|
|
255
|
+
options?.migrate
|
|
256
|
+
)
|
|
239
257
|
|
|
240
|
-
// Check if table already exists
|
|
241
258
|
const tableAlreadyExists =
|
|
242
|
-
|
|
259
|
+
this.shouldCheckExistingTable(tableName, options) && tableExists(this.db, tableName)
|
|
243
260
|
|
|
244
261
|
if (tableAlreadyExists) {
|
|
245
|
-
if (
|
|
246
|
-
// Migration is enabled, check if we need to migrate
|
|
247
|
-
const migrationOptions: MigrationOptions =
|
|
248
|
-
typeof migrateOption === "object" ? migrateOption : {}
|
|
249
|
-
|
|
250
|
-
// Build table constraints to pass to migration
|
|
262
|
+
if (migrateEnabled) {
|
|
251
263
|
let tableConstraints: string[] = []
|
|
252
264
|
if (isTableSchema(columns) && options?.constraints) {
|
|
253
265
|
tableConstraints = buildTableConstraints(options.constraints)
|
|
@@ -263,21 +275,16 @@ class DB {
|
|
|
263
275
|
)
|
|
264
276
|
|
|
265
277
|
if (migrated) {
|
|
266
|
-
// Table was migrated, skip creation and go directly to parser setup
|
|
267
278
|
return this._setupTableParser(tableName, columns, options)
|
|
268
279
|
}
|
|
269
280
|
}
|
|
270
281
|
|
|
271
|
-
// Table exists and either:
|
|
272
|
-
// 1. Migration is disabled, or
|
|
273
|
-
// 2. Migration is enabled but no changes were needed
|
|
274
|
-
// In both cases, return the existing table without trying to create it
|
|
275
282
|
if (!options?.ifNotExists) {
|
|
276
283
|
return this._setupTableParser(tableName, columns, options)
|
|
277
284
|
}
|
|
278
285
|
}
|
|
279
286
|
|
|
280
|
-
const temp = options?.temporary ? "TEMPORARY " : tableName === ":memory" ? "TEMPORARY " : ""
|
|
287
|
+
const temp = options?.temporary ? "TEMPORARY " : tableName === ":memory:" ? "TEMPORARY " : ""
|
|
281
288
|
const ifNot = options?.ifNotExists ? "IF NOT EXISTS " : ""
|
|
282
289
|
const withoutRowId = options?.withoutRowId ? " WITHOUT ROWID" : ""
|
|
283
290
|
|
package/src/migration.ts
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
-
import type { ColumnDefinition, MigrationOptions } from "./types"
|
|
3
2
|
import { buildColumnSQL } from "./lib/table/buildColumnSQL"
|
|
3
|
+
import type {
|
|
4
|
+
ColumnDefinition,
|
|
5
|
+
ForeignKeyInfo,
|
|
6
|
+
ForeignKeyStatus,
|
|
7
|
+
IndexInfo,
|
|
8
|
+
MigrationOptions,
|
|
9
|
+
TableColumn,
|
|
10
|
+
} from "./types"
|
|
4
11
|
import { SqliteLogger } from "./utils/logger"
|
|
5
12
|
|
|
6
|
-
export interface TableColumn {
|
|
7
|
-
cid: number
|
|
8
|
-
name: string
|
|
9
|
-
type: string
|
|
10
|
-
notnull: number
|
|
11
|
-
dflt_value: string | number | null
|
|
12
|
-
pk: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface IndexInfo {
|
|
16
|
-
name: string
|
|
17
|
-
sql: string | null
|
|
18
|
-
unique: boolean
|
|
19
|
-
origin: string
|
|
20
|
-
partial: number
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ForeignKeyInfo {
|
|
24
|
-
id: number
|
|
25
|
-
seq: number
|
|
26
|
-
table: string
|
|
27
|
-
from: string
|
|
28
|
-
to: string
|
|
29
|
-
on_update: string
|
|
30
|
-
on_delete: string
|
|
31
|
-
match: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
13
|
/**
|
|
35
14
|
* Get the current schema of a table
|
|
36
15
|
*/
|
|
@@ -59,10 +38,6 @@ export function getTableIndexes(db: Database, tableName: string): IndexInfo[] {
|
|
|
59
38
|
const indexes = stmt.all() as PragmaIndex[]
|
|
60
39
|
|
|
61
40
|
return indexes.map((idx) => {
|
|
62
|
-
const infoStmt = db.prepare(`PRAGMA index_info("${idx.name}")`)
|
|
63
|
-
infoStmt.all() // Execute but don't need the result for this use case
|
|
64
|
-
|
|
65
|
-
// Get the CREATE INDEX statement if available
|
|
66
41
|
const sqlStmt = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?`)
|
|
67
42
|
const sqlResult = sqlStmt.get(idx.name) as SqliteMasterRow | null
|
|
68
43
|
|
|
@@ -120,7 +95,9 @@ export function schemasAreDifferent(
|
|
|
120
95
|
|
|
121
96
|
// Check if column count differs
|
|
122
97
|
if (currentColumnNames.size !== newColumnNames.size) {
|
|
123
|
-
logger.info(
|
|
98
|
+
logger.info(
|
|
99
|
+
`Column count differs (current: ${currentColumnNames.size} [${Array.from(currentColumnNames).join(", ")}], new: ${newColumnNames.size} [${Array.from(newColumnNames).join(", ")}])`
|
|
100
|
+
)
|
|
124
101
|
return true
|
|
125
102
|
}
|
|
126
103
|
|
|
@@ -229,26 +206,31 @@ export function migrateTable(
|
|
|
229
206
|
newColumns: Record<string, ColumnDefinition>,
|
|
230
207
|
options: MigrationOptions = {},
|
|
231
208
|
tableConstraints: string[] = [],
|
|
232
|
-
migrationLog: SqliteLogger
|
|
209
|
+
migrationLog: SqliteLogger,
|
|
210
|
+
currentSchema?: TableColumn[]
|
|
233
211
|
): void {
|
|
234
212
|
const { preserveData = true, onConflict = "fail", tempTableSuffix = "_migration_temp" } = options
|
|
235
213
|
|
|
236
214
|
migrationLog.info(`Starting migration for table: ${tableName}`)
|
|
237
215
|
|
|
238
216
|
// Get current table info
|
|
239
|
-
const
|
|
217
|
+
const effectiveSchema = currentSchema ?? getTableColumns(db, tableName)
|
|
240
218
|
const indexes = getTableIndexes(db, tableName)
|
|
241
219
|
const triggers = getTableTriggers(db, tableName)
|
|
242
220
|
|
|
243
221
|
// Check if migration is needed
|
|
244
|
-
if (!schemasAreDifferent(
|
|
222
|
+
if (!schemasAreDifferent(effectiveSchema, newColumns, migrationLog)) {
|
|
245
223
|
migrationLog.info(`No migration needed for table: ${tableName}`)
|
|
246
224
|
return
|
|
247
225
|
}
|
|
248
226
|
|
|
249
227
|
const tempTableName = `${tableName}${tempTableSuffix}`
|
|
250
228
|
|
|
251
|
-
|
|
229
|
+
const fkStatus = db.prepare("PRAGMA foreign_keys").get() as ForeignKeyStatus | null
|
|
230
|
+
if (fkStatus && fkStatus.foreign_keys === 1) {
|
|
231
|
+
db.run("PRAGMA foreign_keys = OFF")
|
|
232
|
+
}
|
|
233
|
+
|
|
252
234
|
db.transaction(() => {
|
|
253
235
|
try {
|
|
254
236
|
// Step 1: Create temporary table with new schema
|
|
@@ -270,8 +252,8 @@ export function migrateTable(
|
|
|
270
252
|
db.run(createTempTableSql)
|
|
271
253
|
|
|
272
254
|
// Step 2: Copy data if requested
|
|
273
|
-
if (preserveData &&
|
|
274
|
-
const { selectColumns, insertColumns } = generateColumnMapping(
|
|
255
|
+
if (preserveData && effectiveSchema.length > 0) {
|
|
256
|
+
const { selectColumns, insertColumns } = generateColumnMapping(effectiveSchema, newColumns)
|
|
275
257
|
|
|
276
258
|
if (selectColumns.length > 0) {
|
|
277
259
|
migrationLog.debug(`Copying data from ${tableName} to ${tempTableName}`)
|
|
@@ -297,15 +279,6 @@ export function migrateTable(
|
|
|
297
279
|
// Step 3: Drop the original table
|
|
298
280
|
migrationLog.debug(`Dropping original table: ${tableName}`)
|
|
299
281
|
|
|
300
|
-
// Temporarily disable foreign key constraints
|
|
301
|
-
interface ForeignKeyStatus {
|
|
302
|
-
foreign_keys: number
|
|
303
|
-
}
|
|
304
|
-
const fkStatus = db.prepare("PRAGMA foreign_keys").get() as ForeignKeyStatus | null
|
|
305
|
-
if (fkStatus && fkStatus.foreign_keys === 1) {
|
|
306
|
-
db.run("PRAGMA foreign_keys = OFF")
|
|
307
|
-
}
|
|
308
|
-
|
|
309
282
|
db.run(`DROP TABLE "${tableName}"`)
|
|
310
283
|
|
|
311
284
|
// Step 4: Rename temporary table to original name
|
|
@@ -335,9 +308,6 @@ export function migrateTable(
|
|
|
335
308
|
}
|
|
336
309
|
|
|
337
310
|
// Re-enable foreign key constraints if they were enabled
|
|
338
|
-
if (fkStatus && fkStatus.foreign_keys === 1) {
|
|
339
|
-
db.run("PRAGMA foreign_keys = ON")
|
|
340
|
-
}
|
|
341
311
|
|
|
342
312
|
migrationLog.info(`Successfully migrated table: ${tableName}`)
|
|
343
313
|
} catch (error) {
|
|
@@ -345,6 +315,10 @@ export function migrateTable(
|
|
|
345
315
|
throw error
|
|
346
316
|
}
|
|
347
317
|
})()
|
|
318
|
+
|
|
319
|
+
if (fkStatus && fkStatus.foreign_keys === 1) {
|
|
320
|
+
db.run("PRAGMA foreign_keys = ON")
|
|
321
|
+
}
|
|
348
322
|
}
|
|
349
323
|
|
|
350
324
|
/**
|
|
@@ -361,13 +335,13 @@ export function checkAndMigrate(
|
|
|
361
335
|
const logger = new SqliteLogger(tableName, migrationLog.getBaseLogger())
|
|
362
336
|
|
|
363
337
|
if (!tableExists(db, tableName)) {
|
|
364
|
-
return false
|
|
338
|
+
return false
|
|
365
339
|
}
|
|
366
340
|
|
|
367
341
|
const currentSchema = getTableColumns(db, tableName)
|
|
368
342
|
|
|
369
343
|
if (schemasAreDifferent(currentSchema, newColumns, logger)) {
|
|
370
|
-
migrateTable(db, tableName, newColumns, options, tableConstraints, migrationLog)
|
|
344
|
+
migrateTable(db, tableName, newColumns, options, tableConstraints, migrationLog, currentSchema)
|
|
371
345
|
return true
|
|
372
346
|
}
|
|
373
347
|
|
package/src/types.ts
CHANGED
|
@@ -240,8 +240,6 @@ export interface TableConstraints<T> {
|
|
|
240
240
|
export interface MigrationOptions {
|
|
241
241
|
/** Whether to preserve data during migration (default: true) */
|
|
242
242
|
preserveData?: boolean
|
|
243
|
-
/** Whether to drop columns not in new schema (default: true) */
|
|
244
|
-
dropMissingColumns?: boolean
|
|
245
243
|
/** How to handle constraint violations during data copy */
|
|
246
244
|
onConflict?: "fail" | "ignore" | "replace"
|
|
247
245
|
/** Suffix for temporary table during migration (default: '_migration_temp') */
|
|
@@ -630,3 +628,35 @@ export type SqlParameter = SQLQueryBindings
|
|
|
630
628
|
* Database row with unknown structure
|
|
631
629
|
*/
|
|
632
630
|
export type DatabaseRowData = Record<string, SQLQueryBindings>
|
|
631
|
+
|
|
632
|
+
export interface TableColumn {
|
|
633
|
+
cid: number
|
|
634
|
+
name: string
|
|
635
|
+
type: string
|
|
636
|
+
notnull: number
|
|
637
|
+
dflt_value: string | number | null
|
|
638
|
+
pk: number
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export interface IndexInfo {
|
|
642
|
+
name: string
|
|
643
|
+
sql: string | null
|
|
644
|
+
unique: boolean
|
|
645
|
+
origin: string
|
|
646
|
+
partial: number
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export interface ForeignKeyInfo {
|
|
650
|
+
id: number
|
|
651
|
+
seq: number
|
|
652
|
+
table: string
|
|
653
|
+
from: string
|
|
654
|
+
to: string
|
|
655
|
+
on_update: string
|
|
656
|
+
on_delete: string
|
|
657
|
+
match: string
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export interface ForeignKeyStatus {
|
|
661
|
+
foreign_keys: number
|
|
662
|
+
}
|