mevn-orm 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -170
- 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 +15 -3
- 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.ts +0 -31
- package/initDb.ts +0 -112
- package/knexfile.ts +0 -46
- package/pnpm-workspace.yaml +0 -9
- package/scripts/migrate.ts +0 -97
- package/src/config.ts +0 -270
- package/src/model.ts +0 -301
- package/src/relationships.ts +0 -93
- package/tsconfig.json +0 -27
- package/types/pluralize.d.ts +0 -4
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ Mevn ORM is a small ActiveRecord-style ORM built on top of Knex.
|
|
|
8
8
|
|
|
9
9
|
It exports:
|
|
10
10
|
- `Model`: base class for your models
|
|
11
|
-
- `configureDatabase`:
|
|
12
|
-
- `configure`:
|
|
11
|
+
- `configureDatabase`: initialise with simple DB options by `client`
|
|
12
|
+
- `configure`: initialise with raw Knex config (advanced)
|
|
13
13
|
- migration helpers: `makeMigration`, `migrateLatest`, `migrateRollback`, `migrateList`, `migrateCurrentVersion`
|
|
14
|
-
- `DB`:
|
|
14
|
+
- `DB`: initialised Knex instance (after `configure`)
|
|
15
15
|
|
|
16
16
|
## Status
|
|
17
17
|
|
|
@@ -20,18 +20,21 @@ This project is in maintenance mode. Core functionality works, but new features
|
|
|
20
20
|
## Requirements
|
|
21
21
|
|
|
22
22
|
- Node.js 20+ (ESM runtime)
|
|
23
|
-
- Database driver for your selected client (`mysql2`, `sqlite3`, etc.)
|
|
23
|
+
- Database driver for your selected client (`mysql2`, `pg`, `sqlite3`, etc.)
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npm install mevn-orm knex
|
|
29
|
-
|
|
28
|
+
npm install mevn-orm knex
|
|
29
|
+
npm install mysql2
|
|
30
|
+
````
|
|
30
31
|
|
|
31
32
|
For SQLite development/testing:
|
|
32
33
|
|
|
33
34
|
```bash
|
|
34
35
|
npm install sqlite3
|
|
36
|
+
# or:
|
|
37
|
+
npm install better-sqlite3
|
|
35
38
|
```
|
|
36
39
|
|
|
37
40
|
## Quick Start
|
|
@@ -42,23 +45,26 @@ npm install sqlite3
|
|
|
42
45
|
import { configureDatabase } from 'mevn-orm'
|
|
43
46
|
|
|
44
47
|
configureDatabase({
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
client: 'sqlite3',
|
|
49
|
+
connection: {
|
|
50
|
+
filename: './dev.sqlite'
|
|
51
|
+
}
|
|
47
52
|
})
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
Supported
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
Supported clients (canonical Knex client names):
|
|
56
|
+
|
|
57
|
+
* `sqlite3`, `better-sqlite3`
|
|
58
|
+
* `mysql2`
|
|
59
|
+
* `pg` (also used for CockroachDB/Redshift via the pg driver)
|
|
60
|
+
* `mssql`
|
|
61
|
+
* `oracledb`
|
|
57
62
|
|
|
58
63
|
Connection styles:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
|
|
65
|
+
* connection string: `connection: process.env.DATABASE_URL`
|
|
66
|
+
* object: `connection: { host, port, user, password, database }`
|
|
67
|
+
* sqlite file: `connection: { filename }`
|
|
62
68
|
|
|
63
69
|
### 2) Define a model
|
|
64
70
|
|
|
@@ -88,58 +94,19 @@ await found?.update({ name: 'Jane Updated' })
|
|
|
88
94
|
|
|
89
95
|
### Exports
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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>`
|
|
97
|
+
* `Model`
|
|
98
|
+
* `configureDatabase(config): Knex`
|
|
99
|
+
* `createKnexConfig(config): Knex.Config`
|
|
100
|
+
* `configure(config: Knex.Config | Knex): Knex`
|
|
101
|
+
* `getDB(): Knex`
|
|
102
|
+
* `DB` (Knex instance once configured)
|
|
103
|
+
* `setMigrationConfig(config): MigrationConfig`
|
|
104
|
+
* `getMigrationConfig(): MigrationConfig`
|
|
105
|
+
* `makeMigration(name, config?): Promise<string>`
|
|
106
|
+
* `migrateLatest(config?): Promise<{ batch: number; log: string[] }>`
|
|
107
|
+
* `migrateRollback(config?, all?): Promise<{ batch: number; log: string[] }>`
|
|
108
|
+
* `migrateCurrentVersion(config?): Promise<string>`
|
|
109
|
+
* `migrateList(config?): Promise<{ completed: string[]; pending: string[] }>`
|
|
143
110
|
|
|
144
111
|
## Using `DB` directly
|
|
145
112
|
|
|
@@ -149,9 +116,12 @@ You can always drop down to Knex after configuration:
|
|
|
149
116
|
import { configureDatabase, DB } from 'mevn-orm'
|
|
150
117
|
|
|
151
118
|
configureDatabase({
|
|
152
|
-
|
|
153
|
-
|
|
119
|
+
client: 'sqlite3',
|
|
120
|
+
connection: {
|
|
121
|
+
filename: './dev.sqlite'
|
|
122
|
+
}
|
|
154
123
|
})
|
|
124
|
+
|
|
155
125
|
const users = await DB('users').select('*')
|
|
156
126
|
```
|
|
157
127
|
|
|
@@ -162,34 +132,41 @@ import { configureDatabase } from 'mevn-orm'
|
|
|
162
132
|
|
|
163
133
|
// MySQL / mysql2
|
|
164
134
|
configureDatabase({
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
135
|
+
client: 'mysql2',
|
|
136
|
+
connection: {
|
|
137
|
+
host: '127.0.0.1',
|
|
138
|
+
port: 3306,
|
|
139
|
+
user: 'root',
|
|
140
|
+
password: 'secret',
|
|
141
|
+
database: 'app_db'
|
|
142
|
+
}
|
|
171
143
|
})
|
|
172
144
|
|
|
173
|
-
// Postgres
|
|
145
|
+
// Postgres (connection string)
|
|
174
146
|
configureDatabase({
|
|
175
|
-
|
|
176
|
-
|
|
147
|
+
client: 'pg',
|
|
148
|
+
connection: process.env.DATABASE_URL
|
|
177
149
|
})
|
|
178
150
|
|
|
179
151
|
// MSSQL
|
|
180
152
|
configureDatabase({
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
153
|
+
client: 'mssql',
|
|
154
|
+
connection: {
|
|
155
|
+
host: '127.0.0.1',
|
|
156
|
+
user: 'sa',
|
|
157
|
+
password: 'StrongPassword!',
|
|
158
|
+
database: 'app_db',
|
|
159
|
+
// MSSQL driver options live under `options` (passed through to tedious)
|
|
160
|
+
options: {
|
|
161
|
+
encrypt: true
|
|
162
|
+
}
|
|
163
|
+
}
|
|
187
164
|
})
|
|
188
165
|
```
|
|
189
166
|
|
|
190
167
|
## Nuxt/Nitro Example
|
|
191
168
|
|
|
192
|
-
Use a server plugin to
|
|
169
|
+
Use a server plugin to initialise the ORM once at Nitro startup.
|
|
193
170
|
You can also run idempotent migrations (and optional rollback) during boot.
|
|
194
171
|
|
|
195
172
|
Because Nitro bundles server code, migration files must be copied into the build output.
|
|
@@ -225,7 +202,9 @@ import {
|
|
|
225
202
|
} from 'mevn-orm'
|
|
226
203
|
|
|
227
204
|
const isIgnorableMigrationError = (error: unknown): boolean => {
|
|
228
|
-
const message =
|
|
205
|
+
const message =
|
|
206
|
+
error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
|
|
207
|
+
|
|
229
208
|
return (
|
|
230
209
|
message.includes('already exists') ||
|
|
231
210
|
message.includes('duplicate') ||
|
|
@@ -237,8 +216,8 @@ const isIgnorableMigrationError = (error: unknown): boolean => {
|
|
|
237
216
|
|
|
238
217
|
export default defineNitroPlugin(async () => {
|
|
239
218
|
configureDatabase({
|
|
240
|
-
|
|
241
|
-
|
|
219
|
+
client: 'pg',
|
|
220
|
+
connection: process.env.DATABASE_URL
|
|
242
221
|
})
|
|
243
222
|
|
|
244
223
|
// Nitro runtime path differs in dev vs built server.
|
|
@@ -255,9 +234,7 @@ export default defineNitroPlugin(async () => {
|
|
|
255
234
|
try {
|
|
256
235
|
await migrateLatest()
|
|
257
236
|
} catch (error) {
|
|
258
|
-
if (!isIgnorableMigrationError(error))
|
|
259
|
-
throw error
|
|
260
|
-
}
|
|
237
|
+
if (!isIgnorableMigrationError(error)) throw error
|
|
261
238
|
}
|
|
262
239
|
|
|
263
240
|
// Optional rollback at boot (usually only for dev/preview).
|
|
@@ -265,63 +242,7 @@ export default defineNitroPlugin(async () => {
|
|
|
265
242
|
try {
|
|
266
243
|
await migrateRollback(undefined, false)
|
|
267
244
|
} 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
|
|
245
|
+
if (!isIgnorableMigrationError(error)) throw error
|
|
325
246
|
}
|
|
326
247
|
}
|
|
327
248
|
})
|
|
@@ -329,7 +250,7 @@ export default defineNuxtConfig({
|
|
|
329
250
|
|
|
330
251
|
## Migrations
|
|
331
252
|
|
|
332
|
-
Migrations are
|
|
253
|
+
Migrations are programmatic and use Knex’s migration API under the hood.
|
|
333
254
|
|
|
334
255
|
```ts
|
|
335
256
|
import {
|
|
@@ -342,8 +263,8 @@ import {
|
|
|
342
263
|
} from 'mevn-orm'
|
|
343
264
|
|
|
344
265
|
configureDatabase({
|
|
345
|
-
|
|
346
|
-
filename: './dev.sqlite'
|
|
266
|
+
client: 'sqlite3',
|
|
267
|
+
connection: { filename: './dev.sqlite' }
|
|
347
268
|
})
|
|
348
269
|
|
|
349
270
|
setMigrationConfig({
|
|
@@ -369,19 +290,6 @@ pnpm run migrate:version
|
|
|
369
290
|
|
|
370
291
|
## Security Notes
|
|
371
292
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
## Development (Repository)
|
|
377
|
-
|
|
378
|
-
```bash
|
|
379
|
-
pnpm install
|
|
380
|
-
pnpm run migrate
|
|
381
|
-
pnpm run test
|
|
382
|
-
pnpm run typecheck
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
## License
|
|
386
|
-
|
|
387
|
-
[MIT](./LICENSE)
|
|
293
|
+
* Hash passwords before calling `create()` or `save()`.
|
|
294
|
+
* Validate and sanitise input before persisting.
|
|
295
|
+
* Keep `knex`, DB drivers, and Node.js versions up to date.
|
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, };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
type Row = Record<string, unknown>;
|
|
3
|
+
declare class Model {
|
|
4
|
+
#private;
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
static currentTable: string;
|
|
7
|
+
static currentQuery: Knex.QueryBuilder<Row, Row[]> | undefined;
|
|
8
|
+
fillable: string[];
|
|
9
|
+
hidden: string[];
|
|
10
|
+
modelName: string;
|
|
11
|
+
table: string;
|
|
12
|
+
id?: number;
|
|
13
|
+
constructor(properties?: Row);
|
|
14
|
+
/** Inserts the current model using `fillable` attributes and reloads it from the database. */
|
|
15
|
+
save(): Promise<this>;
|
|
16
|
+
/** Updates the current row by primary key and returns a refreshed model instance. */
|
|
17
|
+
update(properties: Row): Promise<this>;
|
|
18
|
+
/** Deletes the current row by primary key. */
|
|
19
|
+
delete(): Promise<void>;
|
|
20
|
+
/** Updates rows in the model table, optionally scoped by `where()`. */
|
|
21
|
+
static update(properties: Row): Promise<number | undefined>;
|
|
22
|
+
/** Deletes rows in the model table, optionally scoped by `where()`. */
|
|
23
|
+
static destroy(): Promise<number | undefined>;
|
|
24
|
+
/** Finds a single model by primary key. */
|
|
25
|
+
static find(this: typeof Model, id: number | string, columns?: string | string[]): Promise<Model | null>;
|
|
26
|
+
/** Finds a model by primary key or throws if it does not exist. */
|
|
27
|
+
static findOrFail(this: typeof Model, id: number | string, columns?: string | string[]): Promise<Model>;
|
|
28
|
+
/** Creates and returns a single model record. */
|
|
29
|
+
static create(this: typeof Model, properties: Row): Promise<Model>;
|
|
30
|
+
/** Creates multiple model records and returns created model instances. */
|
|
31
|
+
static createMany(this: typeof Model, properties: Row[]): Promise<Model[]>;
|
|
32
|
+
/** Returns the first matching row or creates it with merged values when missing. */
|
|
33
|
+
static firstOrCreate(this: typeof Model, attributes: Row, values?: Row): Promise<Model>;
|
|
34
|
+
/** Applies a query scope used by chained static query methods. */
|
|
35
|
+
static where(this: typeof Model, conditions?: Row): typeof Model;
|
|
36
|
+
/** Returns the first model for the current scope (or table if unscoped). */
|
|
37
|
+
static first(this: typeof Model, columns?: string | string[]): Promise<Model | null>;
|
|
38
|
+
/** Returns all models for the current scope (or table if unscoped). */
|
|
39
|
+
static all(this: typeof Model, columns?: string | string[]): Promise<Model[]>;
|
|
40
|
+
/** Returns a row count for the current scope (or table if unscoped). */
|
|
41
|
+
static count(this: typeof Model, column?: string): Promise<number>;
|
|
42
|
+
/** Removes internal and hidden fields from a model instance. */
|
|
43
|
+
stripColumns<T extends Model>(model: T, keepInternalState?: boolean): T;
|
|
44
|
+
}
|
|
45
|
+
interface Model {
|
|
46
|
+
hasOne(Related: typeof Model, localKey?: number | string, foreignKey?: string): Promise<Model | null>;
|
|
47
|
+
hasMany(Related: typeof Model, localKey?: number | string, foreignKey?: string): Promise<Model[]>;
|
|
48
|
+
belongsTo(Related: typeof Model, foreignKey?: string, ownerKey?: string): Promise<Model | null>;
|
|
49
|
+
}
|
|
50
|
+
export { Model };
|
|
51
|
+
export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, } from './config.js';
|