mevn-orm 3.2.1 → 4.0.0
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 +365 -41
- package/index.ts +31 -0
- package/initDb.ts +112 -0
- package/knexfile.ts +46 -0
- package/package.json +22 -19
- package/pnpm-workspace.yaml +9 -0
- package/scripts/migrate.ts +97 -0
- package/src/config.ts +270 -0
- package/src/model.ts +301 -0
- package/src/relationships.ts +93 -0
- package/tsconfig.json +27 -0
- package/types/pluralize.d.ts +4 -0
- package/index.js +0 -3
- package/knexfile.js +0 -46
- package/lib/model.js +0 -252
package/README.md
CHANGED
|
@@ -1,61 +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'
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
setMigrationConfig({
|
|
350
|
+
directory: './migrations',
|
|
351
|
+
extension: 'ts'
|
|
23
352
|
})
|
|
24
|
-
````
|
|
25
353
|
|
|
26
|
-
|
|
354
|
+
await makeMigration('create_users_table')
|
|
355
|
+
await migrateLatest()
|
|
356
|
+
const { completed, pending } = await migrateList()
|
|
357
|
+
await migrateRollback(undefined, false) // rollback last batch
|
|
358
|
+
```
|
|
27
359
|
|
|
28
|
-
|
|
29
|
-
* Create, Read, Update, Delete support
|
|
30
|
-
* Chainable query builder (`where`, `first`, `all`)
|
|
31
|
-
* Timestamps
|
|
32
|
-
* Soft deletes
|
|
33
|
-
* SQLite support for testing
|
|
34
|
-
* Knex-based migration support
|
|
360
|
+
Repository migration commands (no `knexfile` required):
|
|
35
361
|
|
|
36
|
-
|
|
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
|
+
```
|
|
37
369
|
|
|
38
|
-
|
|
39
|
-
* [x] `.create`, `.find`, `.update`, `.delete`
|
|
40
|
-
* [x] `.where()`, `.first()`, `.all()` chaining
|
|
41
|
-
* [x] Table name inference
|
|
42
|
-
* [x] Timestamps
|
|
43
|
-
* [x] Soft deletes
|
|
44
|
-
* [x] Basic relationship hooks (`hasOne`, `hasMany`, `belongsTo`)
|
|
45
|
-
* [x] Raw queries
|
|
46
|
-
* [x] Knex passthrough
|
|
47
|
-
* [x] SQLite3 test DB
|
|
48
|
-
* [x] Uses `mysql2` for production
|
|
49
|
-
* [x] `dotenv` support
|
|
370
|
+
## Security Notes
|
|
50
371
|
|
|
51
|
-
|
|
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.
|
|
52
375
|
|
|
53
|
-
|
|
376
|
+
## Development (Repository)
|
|
54
377
|
|
|
55
378
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
379
|
+
pnpm install
|
|
380
|
+
pnpm run migrate
|
|
381
|
+
pnpm run test
|
|
382
|
+
pnpm run typecheck
|
|
59
383
|
```
|
|
60
384
|
|
|
61
385
|
## License
|
package/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Model,
|
|
3
|
+
DB,
|
|
4
|
+
getDB,
|
|
5
|
+
configure,
|
|
6
|
+
createKnexConfig,
|
|
7
|
+
configureDatabase,
|
|
8
|
+
setMigrationConfig,
|
|
9
|
+
getMigrationConfig,
|
|
10
|
+
makeMigration,
|
|
11
|
+
migrateLatest,
|
|
12
|
+
migrateRollback,
|
|
13
|
+
migrateCurrentVersion,
|
|
14
|
+
migrateList,
|
|
15
|
+
} from './src/model.js'
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
Model,
|
|
19
|
+
DB,
|
|
20
|
+
getDB,
|
|
21
|
+
configure,
|
|
22
|
+
createKnexConfig,
|
|
23
|
+
configureDatabase,
|
|
24
|
+
setMigrationConfig,
|
|
25
|
+
getMigrationConfig,
|
|
26
|
+
makeMigration,
|
|
27
|
+
migrateLatest,
|
|
28
|
+
migrateRollback,
|
|
29
|
+
migrateCurrentVersion,
|
|
30
|
+
migrateList,
|
|
31
|
+
}
|
package/initDb.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
import 'dotenv/config'
|
|
5
|
+
import type { Knex } from 'knex'
|
|
6
|
+
import knexModule from 'knex'
|
|
7
|
+
|
|
8
|
+
interface KnexEnvConfig {
|
|
9
|
+
development: Knex.Config
|
|
10
|
+
staging: Knex.Config
|
|
11
|
+
production: Knex.Config
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type KnexFactory = (config: Knex.Config) => Knex
|
|
15
|
+
|
|
16
|
+
const KnexFactoryImpl: KnexFactory =
|
|
17
|
+
(knexModule as unknown as { knex?: KnexFactory }).knex ??
|
|
18
|
+
(knexModule as unknown as KnexFactory)
|
|
19
|
+
|
|
20
|
+
const knexfilePath = `${process.cwd()}/knexfile.ts`
|
|
21
|
+
if (!existsSync(knexfilePath)) {
|
|
22
|
+
throw new Error('knexfile.ts not found in current working directory')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const knexConfigModule = await import(pathToFileURL(knexfilePath).href)
|
|
26
|
+
const knexConfig = (knexConfigModule.default ?? knexConfigModule) as Partial<KnexEnvConfig>
|
|
27
|
+
const { development, staging, production } = knexConfig
|
|
28
|
+
|
|
29
|
+
if (!development || !staging || !production) {
|
|
30
|
+
throw new Error('knexfile.ts must export development, staging, and production config')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const config: Knex.Config = (() => {
|
|
34
|
+
switch (process.env.NODE_ENV) {
|
|
35
|
+
case 'testing':
|
|
36
|
+
case 'test':
|
|
37
|
+
case 'development':
|
|
38
|
+
return development
|
|
39
|
+
case 'staging':
|
|
40
|
+
return staging
|
|
41
|
+
default:
|
|
42
|
+
return production
|
|
43
|
+
}
|
|
44
|
+
})()
|
|
45
|
+
|
|
46
|
+
const initDatabase = async (): Promise<void> => {
|
|
47
|
+
const DB = KnexFactoryImpl(config)
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await DB.schema.dropTableIfExists('farmers')
|
|
51
|
+
await DB.schema.dropTableIfExists('farms')
|
|
52
|
+
await DB.schema.dropTableIfExists('profiles')
|
|
53
|
+
await DB.schema.dropTableIfExists('articles')
|
|
54
|
+
|
|
55
|
+
await DB.schema.createTable('farmers', (table: Knex.CreateTableBuilder) => {
|
|
56
|
+
table.bigIncrements('id')
|
|
57
|
+
table.string('name')
|
|
58
|
+
table.string('email').unique()
|
|
59
|
+
table.string('password')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await DB.schema.createTable('farms', (table: Knex.CreateTableBuilder) => {
|
|
63
|
+
table.bigIncrements('id')
|
|
64
|
+
table.bigInteger('farmer_id')
|
|
65
|
+
table.string('name')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await DB.schema.createTable('profiles', (table: Knex.CreateTableBuilder) => {
|
|
69
|
+
table.bigIncrements('id')
|
|
70
|
+
table.bigInteger('farmer_id')
|
|
71
|
+
table.string('bio')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
await DB.schema.createTable('articles', (table: Knex.CreateTableBuilder) => {
|
|
75
|
+
table.bigIncrements('id')
|
|
76
|
+
table.string('title')
|
|
77
|
+
table.text('body')
|
|
78
|
+
table.bigInteger('postable_id')
|
|
79
|
+
table.string('postable_type')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await DB.table('Farmers').insert([
|
|
83
|
+
{ name: 'Jane Doe', email: 'jane@mail.com', password: 'pasword' },
|
|
84
|
+
{ name: 'Ashley Doe', email: 'ashley@mail.com', password: 'pasword' },
|
|
85
|
+
{ name: 'Alice Doe', email: 'alice@mail.com', password: 'pasword' },
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
await DB.table('farms').insert([
|
|
89
|
+
{ farmer_id: 1, name: 'Awesome Farm' },
|
|
90
|
+
{ farmer_id: 1, name: 'Awesome Farm two' },
|
|
91
|
+
{ farmer_id: 1, name: 'Awesome Farm three' },
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
await DB.table('profiles').insert([{ farmer_id: 1, bio: 'Profile for farmer one' }])
|
|
95
|
+
|
|
96
|
+
await DB.table('articles').insert([
|
|
97
|
+
{
|
|
98
|
+
title: 'Awesome Post',
|
|
99
|
+
body: 'fffgjdfjdbdb something #1',
|
|
100
|
+
postable_id: 1,
|
|
101
|
+
postable_type: 'Farmer',
|
|
102
|
+
},
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
process.exit(0)
|
|
106
|
+
} catch (error: unknown) {
|
|
107
|
+
console.error(error)
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await initDatabase()
|
package/knexfile.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Knex } from 'knex'
|
|
2
|
+
|
|
3
|
+
const config = {
|
|
4
|
+
development: {
|
|
5
|
+
client: 'sqlite3',
|
|
6
|
+
connection: {
|
|
7
|
+
filename: './dev.sqlite',
|
|
8
|
+
},
|
|
9
|
+
useNullAsDefault: true,
|
|
10
|
+
migrations: {
|
|
11
|
+
tableName: 'migrations',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
staging: {
|
|
15
|
+
client: process.env.DB_CLIENT ?? 'mysql2',
|
|
16
|
+
connection: {
|
|
17
|
+
database: process.env.DB_DATABASE ?? 'my_db',
|
|
18
|
+
user: process.env.DB_USER ?? 'username',
|
|
19
|
+
password: process.env.DB_PASSWORD ?? 'password',
|
|
20
|
+
},
|
|
21
|
+
pool: {
|
|
22
|
+
min: 2,
|
|
23
|
+
max: 10,
|
|
24
|
+
},
|
|
25
|
+
migrations: {
|
|
26
|
+
tableName: 'migrations',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
production: {
|
|
30
|
+
client: process.env.DB_CLIENT ?? 'mysql2',
|
|
31
|
+
connection: {
|
|
32
|
+
database: process.env.DB_DATABASE ?? 'my_db',
|
|
33
|
+
user: process.env.DB_USER ?? 'username',
|
|
34
|
+
password: process.env.DB_PASSWORD ?? 'password',
|
|
35
|
+
},
|
|
36
|
+
pool: {
|
|
37
|
+
min: 2,
|
|
38
|
+
max: 10,
|
|
39
|
+
},
|
|
40
|
+
migrations: {
|
|
41
|
+
tableName: 'migrations',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} satisfies Record<'development' | 'staging' | 'production', Knex.Config>
|
|
45
|
+
|
|
46
|
+
export default config
|
package/package.json
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mevn-orm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "simple ORM for express js",
|
|
5
|
-
"type": "
|
|
6
|
-
"scripts": {
|
|
7
|
-
"pretest": "node initDb",
|
|
8
|
-
"test": "mocha --exit",
|
|
9
|
-
"init:db": "node initDb",
|
|
10
|
-
"migrate": "knex migrate:latest",
|
|
11
|
-
"lint": "eslint --ext .js ./"
|
|
12
|
-
},
|
|
5
|
+
"type": "module",
|
|
13
6
|
"peerDependencies": {
|
|
14
|
-
"dotenv": "^17.0.1",
|
|
15
7
|
"knex": "^3.0.0",
|
|
16
8
|
"mysql2": "^3.9.3"
|
|
17
9
|
},
|
|
18
10
|
"peerDependenciesMeta": {
|
|
19
|
-
"dotenv": {
|
|
20
|
-
"optional": false
|
|
21
|
-
},
|
|
22
11
|
"knex": {
|
|
23
12
|
"optional": false
|
|
24
13
|
}
|
|
@@ -46,16 +35,18 @@
|
|
|
46
35
|
"@babel/core": "^7.24.5",
|
|
47
36
|
"@babel/eslint-parser": "^7.24.3",
|
|
48
37
|
"@babel/eslint-plugin": "^7.24.0",
|
|
49
|
-
"@
|
|
50
|
-
"
|
|
38
|
+
"@types/node": "^24.6.0",
|
|
39
|
+
"@faker-js/faker": "^10.0.0",
|
|
51
40
|
"dotenv": "^17.0.1",
|
|
52
41
|
"knex": "^3.0.0",
|
|
53
|
-
"
|
|
42
|
+
"tsx": "^4.20.6",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"vitest": "^4.0.16",
|
|
54
45
|
"mysql": "^2.18.1",
|
|
55
46
|
"sqlite3": "^5.1.7"
|
|
56
47
|
},
|
|
57
48
|
"directories": {
|
|
58
|
-
"
|
|
49
|
+
"src": "src",
|
|
59
50
|
"test": "test"
|
|
60
51
|
},
|
|
61
52
|
"private": false,
|
|
@@ -65,5 +56,17 @@
|
|
|
65
56
|
"email": "stanleymasinde1@gmail.com"
|
|
66
57
|
},
|
|
67
58
|
"homepage": "https://github.com/StanleyMasinde/mevn-orm#readme",
|
|
68
|
-
"main": "index.
|
|
69
|
-
|
|
59
|
+
"main": "index.ts",
|
|
60
|
+
"scripts": {
|
|
61
|
+
"pretest": "node --import tsx initDb.ts",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"init:db": "node --import tsx initDb.ts",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"migrate": "node --import tsx scripts/migrate.ts latest",
|
|
66
|
+
"migrate:make": "node --import tsx scripts/migrate.ts make",
|
|
67
|
+
"migrate:rollback": "node --import tsx scripts/migrate.ts rollback",
|
|
68
|
+
"migrate:list": "node --import tsx scripts/migrate.ts list",
|
|
69
|
+
"migrate:version": "node --import tsx scripts/migrate.ts version",
|
|
70
|
+
"lint": "eslint --ext .js,.ts ./"
|
|
71
|
+
}
|
|
72
|
+
}
|