mevn-orm 3.2.2 → 4.0.1
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 +361 -39
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/src/config.d.ts +46 -0
- package/dist/src/config.js +196 -0
- package/dist/src/model.d.ts +51 -0
- package/dist/src/model.js +243 -0
- package/dist/src/relationships.d.ts +18 -0
- package/dist/src/relationships.js +50 -0
- package/package.json +30 -15
- package/.env.example +0 -9
- package/.eslintrc.json +0 -36
- package/.gitattributes +0 -8
- package/CODE_OF_CONDUCT.md +0 -76
- package/changelog.md +0 -135
- package/index.js +0 -3
- package/knexfile.js +0 -46
- package/lib/model.js +0 -252
- package/pnpm-workspace.yaml +0 -8
package/README.md
CHANGED
|
@@ -1,63 +1,385 @@
|
|
|
1
1
|
# Mevn ORM
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
|
+
[](https://github.com/StanleyMasinde/mevn-orm/blob/master/LICENSE)
|
|
5
|
+

|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
When I started this, I had so much intertest in the JS ecosytem and ORMs. I still have some intertest in javascript
|
|
7
|
-
bit not ORMs.
|
|
7
|
+
Mevn ORM is a small ActiveRecord-style ORM built on top of Knex.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
It exports:
|
|
10
|
+
- `Model`: base class for your models
|
|
11
|
+
- `configureDatabase`: initialize with simple DB options by dialect
|
|
12
|
+
- `configure`: initialize with raw Knex config (advanced)
|
|
13
|
+
- migration helpers: `makeMigration`, `migrateLatest`, `migrateRollback`, `migrateList`, `migrateCurrentVersion`
|
|
14
|
+
- `DB`: initialized Knex instance (after `configure`)
|
|
11
15
|
|
|
12
|
-
##
|
|
16
|
+
## Status
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
const { Model } = require('mevn-orm')
|
|
18
|
+
This project is in maintenance mode. Core functionality works, but new features are limited.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
## Requirements
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
- Node.js 20+ (ESM runtime)
|
|
23
|
+
- Database driver for your selected client (`mysql2`, `sqlite3`, etc.)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install mevn-orm knex mysql2
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For SQLite development/testing:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install sqlite3
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1) Configure Mevn ORM
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { configureDatabase } from 'mevn-orm'
|
|
43
|
+
|
|
44
|
+
configureDatabase({
|
|
45
|
+
dialect: 'sqlite',
|
|
46
|
+
filename: './dev.sqlite'
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Supported dialects:
|
|
51
|
+
- `sqlite`, `better-sqlite3`
|
|
52
|
+
- `mysql`, `mysql2`
|
|
53
|
+
- `postgres`, `postgresql`, `pg`, `pgnative`
|
|
54
|
+
- `cockroachdb`, `redshift`
|
|
55
|
+
- `mssql`
|
|
56
|
+
- `oracledb`, `oracle`
|
|
57
|
+
|
|
58
|
+
Connection styles:
|
|
59
|
+
- `connectionString` for one-string connection setup
|
|
60
|
+
- field-based setup (`host`, `port`, `user`, `password`, `database`)
|
|
61
|
+
- sqlite file setup via `filename`
|
|
62
|
+
|
|
63
|
+
### 2) Define a model
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { Model } from 'mevn-orm'
|
|
67
|
+
|
|
68
|
+
class User extends Model {
|
|
69
|
+
override fillable = ['name', 'email', 'password']
|
|
70
|
+
override hidden = ['password']
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3) Use it
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const created = await User.create({
|
|
78
|
+
name: 'Jane Doe',
|
|
79
|
+
email: 'jane@example.com',
|
|
80
|
+
password: 'hash-me-first'
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const found = await User.find(created.id as number)
|
|
84
|
+
await found?.update({ name: 'Jane Updated' })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### Exports
|
|
90
|
+
|
|
91
|
+
- `Model`
|
|
92
|
+
- `configureDatabase(config): Knex`
|
|
93
|
+
- `createKnexConfig(config): Knex.Config`
|
|
94
|
+
- `configure(config: Knex.Config | Knex): Knex`
|
|
95
|
+
- `getDB(): Knex`
|
|
96
|
+
- `DB` (Knex instance once configured)
|
|
97
|
+
- `setMigrationConfig(config): MigrationConfig`
|
|
98
|
+
- `getMigrationConfig(): MigrationConfig`
|
|
99
|
+
- `makeMigration(name, config?): Promise<string>`
|
|
100
|
+
- `migrateLatest(config?): Promise<{ batch: number; log: string[] }>`
|
|
101
|
+
- `migrateRollback(config?, all?): Promise<{ batch: number; log: string[] }>`
|
|
102
|
+
- `migrateCurrentVersion(config?): Promise<string>`
|
|
103
|
+
- `migrateList(config?): Promise<{ completed: string[]; pending: string[] }>`
|
|
104
|
+
|
|
105
|
+
### Model instance members
|
|
106
|
+
|
|
107
|
+
- `fillable: string[]`
|
|
108
|
+
- Attributes allowed through `save()`.
|
|
109
|
+
- `hidden: string[]`
|
|
110
|
+
- Attributes removed when serializing model results.
|
|
111
|
+
- `table: string`
|
|
112
|
+
- Inferred from class name using pluralization (for `User` -> `users`).
|
|
113
|
+
- `id?: number`
|
|
114
|
+
|
|
115
|
+
### Instance methods
|
|
116
|
+
|
|
117
|
+
- `save(): Promise<this>`
|
|
118
|
+
- Inserts record using only `fillable` fields.
|
|
119
|
+
- `update(properties): Promise<this>`
|
|
120
|
+
- Updates by `id`, then reloads from DB.
|
|
121
|
+
- `delete(): Promise<void>`
|
|
122
|
+
- Deletes current row by `id`.
|
|
123
|
+
- `hasOne(RelatedModel, localKey?, foreignKey?): Promise<Model | null>`
|
|
124
|
+
- Loads one related record.
|
|
125
|
+
- `hasMany(RelatedModel, localKey?, foreignKey?): Promise<Model[]>`
|
|
126
|
+
- Loads related records by foreign key.
|
|
127
|
+
- `belongsTo(RelatedModel, foreignKey?, ownerKey?): Promise<Model | null>`
|
|
128
|
+
- Loads the owning/parent record.
|
|
129
|
+
|
|
130
|
+
### Static methods
|
|
131
|
+
|
|
132
|
+
- `find(id, columns = '*'): Promise<Model | null>`
|
|
133
|
+
- `findOrFail(id, columns = '*'): Promise<Model>`
|
|
134
|
+
- `create(properties): Promise<Model>`
|
|
135
|
+
- `createMany(properties[]): Promise<Model[]>`
|
|
136
|
+
- `firstOrCreate(attributes, values = {}): Promise<Model>`
|
|
137
|
+
- `where(conditions = {}): typeof Model`
|
|
138
|
+
- `first(columns = '*'): Promise<Model | null>`
|
|
139
|
+
- `all(columns = '*'): Promise<Model[]>`
|
|
140
|
+
- `count(column = '*'): Promise<number>`
|
|
141
|
+
- `update(properties): Promise<number | undefined>`
|
|
142
|
+
- `destroy(): Promise<number | undefined>`
|
|
143
|
+
|
|
144
|
+
## Using `DB` directly
|
|
145
|
+
|
|
146
|
+
You can always drop down to Knex after configuration:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { configureDatabase, DB } from 'mevn-orm'
|
|
150
|
+
|
|
151
|
+
configureDatabase({
|
|
152
|
+
dialect: 'sqlite',
|
|
153
|
+
filename: './dev.sqlite'
|
|
154
|
+
})
|
|
155
|
+
const users = await DB('users').select('*')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## More Configuration Examples
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { configureDatabase } from 'mevn-orm'
|
|
162
|
+
|
|
163
|
+
// MySQL / mysql2
|
|
164
|
+
configureDatabase({
|
|
165
|
+
dialect: 'mysql',
|
|
166
|
+
host: '127.0.0.1',
|
|
167
|
+
port: 3306,
|
|
168
|
+
user: 'root',
|
|
169
|
+
password: 'secret',
|
|
170
|
+
database: 'app_db'
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// Postgres
|
|
174
|
+
configureDatabase({
|
|
175
|
+
dialect: 'postgres',
|
|
176
|
+
connectionString: process.env.DATABASE_URL
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// MSSQL
|
|
180
|
+
configureDatabase({
|
|
181
|
+
dialect: 'mssql',
|
|
182
|
+
host: '127.0.0.1',
|
|
183
|
+
user: 'sa',
|
|
184
|
+
password: 'StrongPassword!',
|
|
185
|
+
database: 'app_db',
|
|
186
|
+
ssl: true
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Nuxt/Nitro Example
|
|
191
|
+
|
|
192
|
+
Use a server plugin to initialize the ORM once at Nitro startup.
|
|
193
|
+
You can also run idempotent migrations (and optional rollback) during boot.
|
|
194
|
+
|
|
195
|
+
Because Nitro bundles server code, migration files must be copied into the build output.
|
|
196
|
+
|
|
197
|
+
`nuxt.config.ts`:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { cp } from 'node:fs/promises'
|
|
201
|
+
|
|
202
|
+
export default defineNuxtConfig({
|
|
203
|
+
nitro: {
|
|
204
|
+
hooks: {
|
|
205
|
+
compiled: async () => {
|
|
206
|
+
await cp('server/assets/migrations', '.output/server/assets/migrations', {
|
|
207
|
+
recursive: true
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`server/plugins/mevn-orm.ts`:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { defineNitroPlugin } from 'nitropack/runtime'
|
|
219
|
+
import { existsSync } from 'node:fs'
|
|
220
|
+
import {
|
|
221
|
+
configureDatabase,
|
|
222
|
+
setMigrationConfig,
|
|
223
|
+
migrateLatest,
|
|
224
|
+
migrateRollback
|
|
225
|
+
} from 'mevn-orm'
|
|
226
|
+
|
|
227
|
+
const isIgnorableMigrationError = (error: unknown): boolean => {
|
|
228
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
|
|
229
|
+
return (
|
|
230
|
+
message.includes('already exists') ||
|
|
231
|
+
message.includes('duplicate') ||
|
|
232
|
+
message.includes('does not exist') ||
|
|
233
|
+
message.includes('no such table') ||
|
|
234
|
+
message.includes('the migration directory is corrupt')
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export default defineNitroPlugin(async () => {
|
|
239
|
+
configureDatabase({
|
|
240
|
+
dialect: 'postgres',
|
|
241
|
+
connectionString: process.env.DATABASE_URL
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Nitro runtime path differs in dev vs built server.
|
|
245
|
+
const migrationDirectory = existsSync('./.output/server/assets/migrations')
|
|
246
|
+
? './.output/server/assets/migrations'
|
|
247
|
+
: './server/assets/migrations'
|
|
248
|
+
|
|
249
|
+
setMigrationConfig({
|
|
250
|
+
directory: migrationDirectory,
|
|
251
|
+
extension: 'ts'
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Idempotent at boot: if already migrated, Knex returns empty log.
|
|
255
|
+
try {
|
|
256
|
+
await migrateLatest()
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (!isIgnorableMigrationError(error)) {
|
|
259
|
+
throw error
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Optional rollback at boot (usually only for dev/preview).
|
|
264
|
+
if (process.env.NITRO_ROLLBACK_ON_BOOT === 'true') {
|
|
265
|
+
try {
|
|
266
|
+
await migrateRollback(undefined, false)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (!isIgnorableMigrationError(error)) {
|
|
269
|
+
throw error
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For fully idempotent behavior across SQL dialects, write migrations with guards
|
|
277
|
+
(`createTableIfNotExists`, checking column existence, or raw `IF EXISTS/IF NOT EXISTS`).
|
|
278
|
+
|
|
279
|
+
`server/models/User.ts`:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { Model } from 'mevn-orm'
|
|
283
|
+
|
|
284
|
+
export class User extends Model {
|
|
285
|
+
override fillable = ['name', 'email', 'password']
|
|
286
|
+
override hidden = ['password']
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`server/api/users.get.ts`:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { User } from '../models/User'
|
|
294
|
+
|
|
295
|
+
export default defineEventHandler(async () => {
|
|
296
|
+
return await User.all()
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`server/api/users.post.ts`:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { User } from '../models/User'
|
|
304
|
+
|
|
305
|
+
export default defineEventHandler(async (event) => {
|
|
306
|
+
const body = await readBody(event)
|
|
307
|
+
return await User.create({
|
|
308
|
+
name: body.name,
|
|
309
|
+
email: body.email,
|
|
310
|
+
password: body.password // hash before storing
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
If you prefer Nuxt runtime config:
|
|
316
|
+
|
|
317
|
+
`nuxt.config.ts`:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
export default defineNuxtConfig({
|
|
321
|
+
runtimeConfig: {
|
|
322
|
+
db: {
|
|
323
|
+
dialect: 'postgres',
|
|
324
|
+
connectionString: process.env.DATABASE_URL
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Migrations
|
|
331
|
+
|
|
332
|
+
Migrations are now programmatic and use Knex’s migration API under the hood.
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
import {
|
|
336
|
+
configureDatabase,
|
|
337
|
+
setMigrationConfig,
|
|
338
|
+
makeMigration,
|
|
339
|
+
migrateLatest,
|
|
340
|
+
migrateRollback,
|
|
341
|
+
migrateList
|
|
342
|
+
} from 'mevn-orm'
|
|
343
|
+
|
|
344
|
+
configureDatabase({
|
|
345
|
+
dialect: 'sqlite',
|
|
346
|
+
filename: './dev.sqlite'
|
|
23
347
|
})
|
|
24
|
-
````
|
|
25
348
|
|
|
26
|
-
|
|
349
|
+
setMigrationConfig({
|
|
350
|
+
directory: './migrations',
|
|
351
|
+
extension: 'ts'
|
|
352
|
+
})
|
|
27
353
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* SQLite support for testing
|
|
34
|
-
* Knex-based migration support
|
|
354
|
+
await makeMigration('create_users_table')
|
|
355
|
+
await migrateLatest()
|
|
356
|
+
const { completed, pending } = await migrateList()
|
|
357
|
+
await migrateRollback(undefined, false) // rollback last batch
|
|
358
|
+
```
|
|
35
359
|
|
|
36
|
-
|
|
360
|
+
Repository migration commands (no `knexfile` required):
|
|
37
361
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* [x] Raw queries
|
|
46
|
-
* [x] Knex passthrough
|
|
47
|
-
* [x] SQLite3 test DB
|
|
48
|
-
* [x] Uses `mysql2` for production
|
|
49
|
-
* [x] `dotenv` support
|
|
362
|
+
```bash
|
|
363
|
+
pnpm run migrate
|
|
364
|
+
pnpm run migrate:make -- create_users_table
|
|
365
|
+
pnpm run migrate:rollback
|
|
366
|
+
pnpm run migrate:list
|
|
367
|
+
pnpm run migrate:version
|
|
368
|
+
```
|
|
50
369
|
|
|
51
|
-
##
|
|
370
|
+
## Security Notes
|
|
52
371
|
|
|
53
|
-
|
|
372
|
+
- Hash passwords before calling `create()` or `save()`.
|
|
373
|
+
- Validate and sanitize input before persisting.
|
|
374
|
+
- Keep `knex`, DB drivers, and Node.js versions up to date.
|
|
54
375
|
|
|
55
|
-
|
|
376
|
+
## Development (Repository)
|
|
56
377
|
|
|
57
378
|
```bash
|
|
58
379
|
pnpm install
|
|
59
380
|
pnpm run migrate
|
|
60
381
|
pnpm run test
|
|
382
|
+
pnpm run typecheck
|
|
61
383
|
```
|
|
62
384
|
|
|
63
385
|
## License
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList } from './src/model.js';
|
|
2
|
+
export { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, } from './src/model.js';
|
|
2
|
+
export { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
declare let DB: Knex | undefined;
|
|
3
|
+
type SimpleDialect = 'sqlite' | 'better-sqlite3' | 'mysql' | 'mysql2' | 'postgres' | 'postgresql' | 'pg' | 'pgnative' | 'cockroachdb' | 'redshift' | 'mssql' | 'oracledb' | 'oracle';
|
|
4
|
+
interface SimpleDatabaseConfig {
|
|
5
|
+
dialect: SimpleDialect;
|
|
6
|
+
connectionString?: string;
|
|
7
|
+
filename?: string;
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
user?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
database?: string;
|
|
13
|
+
ssl?: boolean | Record<string, unknown>;
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
pool?: Knex.PoolConfig;
|
|
16
|
+
}
|
|
17
|
+
interface MigrationResult {
|
|
18
|
+
batch: number;
|
|
19
|
+
log: string[];
|
|
20
|
+
}
|
|
21
|
+
/** Returns the active Knex instance or throws if the ORM has not been configured. */
|
|
22
|
+
declare const getDB: () => Knex;
|
|
23
|
+
/** Configures the ORM using a Knex config object or an existing Knex instance. */
|
|
24
|
+
declare const configure: (config: Knex.Config | Knex) => Knex;
|
|
25
|
+
/** Sets default migration options used by migration helpers. */
|
|
26
|
+
declare const setMigrationConfig: (config: Knex.MigratorConfig) => Knex.MigratorConfig;
|
|
27
|
+
/** Returns the currently configured default migration options. */
|
|
28
|
+
declare const getMigrationConfig: () => Knex.MigratorConfig;
|
|
29
|
+
/** Builds a Knex config from a simplified, dialect-first configuration object. */
|
|
30
|
+
declare const createKnexConfig: (config: SimpleDatabaseConfig) => Knex.Config;
|
|
31
|
+
/** Configures the ORM from simplified database options. */
|
|
32
|
+
declare const configureDatabase: (config: SimpleDatabaseConfig) => Knex;
|
|
33
|
+
/** Generates a migration file and returns its path. */
|
|
34
|
+
declare const makeMigration: (name: string, config?: Knex.MigratorConfig) => Promise<string>;
|
|
35
|
+
/** Runs pending migrations and returns the batch number and migration filenames. */
|
|
36
|
+
declare const migrateLatest: (config?: Knex.MigratorConfig) => Promise<MigrationResult>;
|
|
37
|
+
/** Rolls back migrations. Set `all` to true to rollback all completed batches. */
|
|
38
|
+
declare const migrateRollback: (config?: Knex.MigratorConfig, all?: boolean) => Promise<MigrationResult>;
|
|
39
|
+
/** Returns the current migration version recorded by Knex. */
|
|
40
|
+
declare const migrateCurrentVersion: (config?: Knex.MigratorConfig) => Promise<string>;
|
|
41
|
+
/** Returns completed and pending migration filenames. */
|
|
42
|
+
declare const migrateList: (config?: Knex.MigratorConfig) => Promise<{
|
|
43
|
+
completed: string[];
|
|
44
|
+
pending: string[];
|
|
45
|
+
}>;
|
|
46
|
+
export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import knexModule from 'knex';
|
|
2
|
+
const knexFactory = knexModule.knex ??
|
|
3
|
+
knexModule;
|
|
4
|
+
const toError = (error) => {
|
|
5
|
+
if (error instanceof Error) {
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
return new Error(String(error));
|
|
9
|
+
};
|
|
10
|
+
let DB;
|
|
11
|
+
let defaultMigrationConfig = {};
|
|
12
|
+
/** Returns the active Knex instance or throws if the ORM has not been configured. */
|
|
13
|
+
const getDB = () => {
|
|
14
|
+
if (!DB) {
|
|
15
|
+
throw new Error('Mevn ORM is not configured. Call configure({ client, connection, ... }) before using Model.');
|
|
16
|
+
}
|
|
17
|
+
return DB;
|
|
18
|
+
};
|
|
19
|
+
/** Configures the ORM using a Knex config object or an existing Knex instance. */
|
|
20
|
+
const configure = (config) => {
|
|
21
|
+
if (typeof config === 'function') {
|
|
22
|
+
DB = config;
|
|
23
|
+
return DB;
|
|
24
|
+
}
|
|
25
|
+
DB = knexFactory(config);
|
|
26
|
+
return DB;
|
|
27
|
+
};
|
|
28
|
+
/** Sets default migration options used by migration helpers. */
|
|
29
|
+
const setMigrationConfig = (config) => {
|
|
30
|
+
defaultMigrationConfig = { ...config };
|
|
31
|
+
return { ...defaultMigrationConfig };
|
|
32
|
+
};
|
|
33
|
+
/** Returns the currently configured default migration options. */
|
|
34
|
+
const getMigrationConfig = () => ({ ...defaultMigrationConfig });
|
|
35
|
+
const resolveMigrationConfig = (config) => ({
|
|
36
|
+
...defaultMigrationConfig,
|
|
37
|
+
...(config ?? {}),
|
|
38
|
+
});
|
|
39
|
+
const normalizeDialect = (dialect) => {
|
|
40
|
+
switch (dialect) {
|
|
41
|
+
case 'sqlite':
|
|
42
|
+
return 'sqlite3';
|
|
43
|
+
case 'mysql':
|
|
44
|
+
return 'mysql2';
|
|
45
|
+
case 'postgres':
|
|
46
|
+
case 'postgresql':
|
|
47
|
+
case 'pg':
|
|
48
|
+
return 'pg';
|
|
49
|
+
case 'oracle':
|
|
50
|
+
return 'oracledb';
|
|
51
|
+
default:
|
|
52
|
+
return dialect;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const requireField = (value, field, dialect) => {
|
|
56
|
+
if (value === undefined || value === null || value === '') {
|
|
57
|
+
throw new Error(`Missing required field "${field}" for dialect "${dialect}".`);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const buildConnection = (config) => {
|
|
61
|
+
if (config.connectionString) {
|
|
62
|
+
return config.connectionString;
|
|
63
|
+
}
|
|
64
|
+
const client = normalizeDialect(config.dialect);
|
|
65
|
+
if (client === 'sqlite3' || client === 'better-sqlite3') {
|
|
66
|
+
requireField(config.filename, 'filename', config.dialect);
|
|
67
|
+
return { filename: config.filename };
|
|
68
|
+
}
|
|
69
|
+
if (client === 'mssql') {
|
|
70
|
+
const server = config.host;
|
|
71
|
+
requireField(server, 'host', config.dialect);
|
|
72
|
+
requireField(config.user, 'user', config.dialect);
|
|
73
|
+
requireField(config.database, 'database', config.dialect);
|
|
74
|
+
const connection = {
|
|
75
|
+
server: server,
|
|
76
|
+
user: config.user,
|
|
77
|
+
database: config.database,
|
|
78
|
+
};
|
|
79
|
+
if (typeof config.port === 'number') {
|
|
80
|
+
connection.port = config.port;
|
|
81
|
+
}
|
|
82
|
+
if (config.password !== undefined) {
|
|
83
|
+
connection.password = config.password;
|
|
84
|
+
}
|
|
85
|
+
if (config.ssl) {
|
|
86
|
+
connection.options = { encrypt: true };
|
|
87
|
+
}
|
|
88
|
+
return connection;
|
|
89
|
+
}
|
|
90
|
+
requireField(config.host, 'host', config.dialect);
|
|
91
|
+
requireField(config.user, 'user', config.dialect);
|
|
92
|
+
requireField(config.database, 'database', config.dialect);
|
|
93
|
+
const connection = {
|
|
94
|
+
host: config.host,
|
|
95
|
+
user: config.user,
|
|
96
|
+
database: config.database,
|
|
97
|
+
};
|
|
98
|
+
if (typeof config.port === 'number') {
|
|
99
|
+
connection.port = config.port;
|
|
100
|
+
}
|
|
101
|
+
if (config.password !== undefined) {
|
|
102
|
+
connection.password = config.password;
|
|
103
|
+
}
|
|
104
|
+
if (config.ssl !== undefined) {
|
|
105
|
+
connection.ssl = config.ssl;
|
|
106
|
+
}
|
|
107
|
+
return connection;
|
|
108
|
+
};
|
|
109
|
+
/** Builds a Knex config from a simplified, dialect-first configuration object. */
|
|
110
|
+
const createKnexConfig = (config) => {
|
|
111
|
+
const client = normalizeDialect(config.dialect);
|
|
112
|
+
const base = {
|
|
113
|
+
client,
|
|
114
|
+
connection: buildConnection(config),
|
|
115
|
+
};
|
|
116
|
+
if (config.pool) {
|
|
117
|
+
base.pool = config.pool;
|
|
118
|
+
}
|
|
119
|
+
if (typeof config.debug === 'boolean') {
|
|
120
|
+
base.debug = config.debug;
|
|
121
|
+
}
|
|
122
|
+
if (client === 'sqlite3') {
|
|
123
|
+
base.useNullAsDefault = true;
|
|
124
|
+
}
|
|
125
|
+
return base;
|
|
126
|
+
};
|
|
127
|
+
/** Configures the ORM from simplified database options. */
|
|
128
|
+
const configureDatabase = (config) => configure(createKnexConfig(config));
|
|
129
|
+
/** Generates a migration file and returns its path. */
|
|
130
|
+
const makeMigration = async (name, config) => {
|
|
131
|
+
try {
|
|
132
|
+
return await getDB().migrate.make(name, resolveMigrationConfig(config));
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
throw toError(error);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/** Runs pending migrations and returns the batch number and migration filenames. */
|
|
139
|
+
const migrateLatest = async (config) => {
|
|
140
|
+
try {
|
|
141
|
+
const [batch, log] = await getDB().migrate.latest(resolveMigrationConfig(config));
|
|
142
|
+
return { batch, log };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw toError(error);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
/** Rolls back migrations. Set `all` to true to rollback all completed batches. */
|
|
149
|
+
const migrateRollback = async (config, all = false) => {
|
|
150
|
+
try {
|
|
151
|
+
const [batch, log] = all
|
|
152
|
+
? await getDB().migrate.rollback(resolveMigrationConfig(config), true)
|
|
153
|
+
: await getDB().migrate.rollback(resolveMigrationConfig(config));
|
|
154
|
+
return { batch, log };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw toError(error);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
/** Returns the current migration version recorded by Knex. */
|
|
161
|
+
const migrateCurrentVersion = async (config) => {
|
|
162
|
+
try {
|
|
163
|
+
return await getDB().migrate.currentVersion(resolveMigrationConfig(config));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
throw toError(error);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
/** Returns completed and pending migration filenames. */
|
|
170
|
+
const migrateList = async (config) => {
|
|
171
|
+
try {
|
|
172
|
+
const [completed, pending] = await getDB().migrate.list(resolveMigrationConfig(config));
|
|
173
|
+
const toName = (entry) => {
|
|
174
|
+
if (typeof entry === 'string') {
|
|
175
|
+
return entry;
|
|
176
|
+
}
|
|
177
|
+
if (entry && typeof entry === 'object') {
|
|
178
|
+
if ('name' in entry) {
|
|
179
|
+
return String(entry.name);
|
|
180
|
+
}
|
|
181
|
+
if ('file' in entry) {
|
|
182
|
+
return String(entry.file);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return String(entry);
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
completed: completed.map(toName),
|
|
189
|
+
pending: pending.map(toName),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw toError(error);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
|