@ghom/orm 2.1.1 → 2.1.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/package.json +1 -1
- package/readme.md +173 -35
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -21,12 +21,12 @@ const orm = new ORM({
|
|
|
21
21
|
// tables directory
|
|
22
22
|
tableLocation: "./tables",
|
|
23
23
|
|
|
24
|
-
// knex config (sqlite3 by default)
|
|
25
|
-
database: {
|
|
24
|
+
// knex config (sqlite3 in-memory by default)
|
|
25
|
+
database: { client: "sqlite3", connection: { filename: ":memory:" } },
|
|
26
26
|
|
|
27
|
-
// custom logger (
|
|
27
|
+
// optional custom logger (must have log, error, warn methods)
|
|
28
28
|
logger: console,
|
|
29
|
-
|
|
29
|
+
loggerStyles: { highlight: "cyan", rawValue: "yellow", description: "dim" },
|
|
30
30
|
|
|
31
31
|
// caching options for all tables and rawCache queries (default to Infinity)
|
|
32
32
|
caching: 10 * 60 * 1000,
|
|
@@ -34,6 +34,12 @@ const orm = new ORM({
|
|
|
34
34
|
// configuration for the database backups
|
|
35
35
|
backups: {
|
|
36
36
|
location: "./backups",
|
|
37
|
+
chunkSize: 1000, // rows per backup file chunk
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// migration behavior configuration
|
|
41
|
+
migrations: {
|
|
42
|
+
alphabeticalOrder: false // default
|
|
37
43
|
}
|
|
38
44
|
})
|
|
39
45
|
|
|
@@ -67,11 +73,14 @@ The tables are automatically loaded from the `tableLocation` directory. Types ar
|
|
|
67
73
|
```typescript
|
|
68
74
|
// tables/user.ts
|
|
69
75
|
|
|
70
|
-
import { Table, col } from "@ghom/orm"
|
|
76
|
+
import { Table, col, migrate } from "@ghom/orm"
|
|
71
77
|
|
|
72
78
|
export default new Table({
|
|
73
79
|
name: "user",
|
|
74
80
|
|
|
81
|
+
// optional description for logging
|
|
82
|
+
description: "User accounts",
|
|
83
|
+
|
|
75
84
|
// the higher the priority, the earlier the table is compiled
|
|
76
85
|
priority: 0,
|
|
77
86
|
|
|
@@ -81,32 +90,108 @@ export default new Table({
|
|
|
81
90
|
username: col.string().unique(),
|
|
82
91
|
password: col.string(),
|
|
83
92
|
age: col.integer().nullable(),
|
|
84
|
-
role: col.enum(["admin", "user"]).defaultTo("user"),
|
|
93
|
+
role: col.enum(["admin", "user"] as const).defaultTo("user"),
|
|
85
94
|
}),
|
|
86
95
|
|
|
87
96
|
// migrations are executed in order based on key pattern (see Migration Keys section)
|
|
88
97
|
migrations: {
|
|
89
|
-
"
|
|
90
|
-
table.renameColumn("name", "username")
|
|
91
|
-
}
|
|
98
|
+
"001_add_email": migrate.addColumn("email", col.string()),
|
|
92
99
|
},
|
|
93
100
|
|
|
94
101
|
// then is executed after the table is created and the migrations are run (only if table is empty)
|
|
95
|
-
then: (
|
|
96
|
-
query.insert({ username: "admin", password: "admin", role: "admin" })
|
|
102
|
+
then: (table) => {
|
|
103
|
+
table.query.insert({ username: "admin", password: "admin", role: "admin", email: "admin@admin.com" })
|
|
97
104
|
},
|
|
98
105
|
|
|
99
106
|
caching: 10 * 60 * 1000 // The table cache. Default to the ORM cache or Infinity
|
|
100
107
|
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The type is automatically inferred from columns + migrations:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// { id: number; username: string; password: string; age: number | null; role: "admin" | "user"; email: string }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
You can export and use the type from another file:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// somewhere else in your code
|
|
120
|
+
import userTable from "./tables/user"
|
|
101
121
|
|
|
102
|
-
// Type is automatically inferred:
|
|
103
|
-
// { id: number; username: string; password: string; age: number | null; role: "admin" | "user" }
|
|
104
122
|
type User = typeof userTable.$type
|
|
105
123
|
```
|
|
106
124
|
|
|
107
|
-
|
|
125
|
+
## Column Types
|
|
126
|
+
|
|
127
|
+
All available column types with their TypeScript types:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { col } from "@ghom/orm"
|
|
131
|
+
|
|
132
|
+
// Numeric types
|
|
133
|
+
col.increments() // number - auto-incrementing primary key
|
|
134
|
+
col.bigIncrements() // bigint - big auto-incrementing primary key
|
|
135
|
+
col.integer() // number
|
|
136
|
+
col.bigInteger() // bigint
|
|
137
|
+
col.tinyint() // number (0-255)
|
|
138
|
+
col.smallint() // number
|
|
139
|
+
col.mediumint() // number
|
|
140
|
+
col.float(precision?, scale?) // number
|
|
141
|
+
col.double(precision?, scale?) // number
|
|
142
|
+
col.decimal(precision?, scale?) // number
|
|
143
|
+
|
|
144
|
+
// String types
|
|
145
|
+
col.string(length?) // string (default: 255)
|
|
146
|
+
col.text(textType?) // string - "text" | "mediumtext" | "longtext"
|
|
147
|
+
col.uuid() // string
|
|
148
|
+
|
|
149
|
+
// Boolean
|
|
150
|
+
col.boolean() // boolean
|
|
151
|
+
|
|
152
|
+
// Date/Time types
|
|
153
|
+
col.date() // Date
|
|
154
|
+
col.datetime(options?) // Date - { useTz?: boolean; precision?: number }
|
|
155
|
+
col.timestamp(options?) // Date - { useTz?: boolean; precision?: number }
|
|
156
|
+
col.time() // string
|
|
157
|
+
|
|
158
|
+
// Other types
|
|
159
|
+
col.binary(length?) // Buffer
|
|
160
|
+
col.enum(values) // union of values - col.enum(["a", "b"] as const) => "a" | "b"
|
|
161
|
+
col.json<T>() // T (default: unknown)
|
|
162
|
+
col.jsonb<T>() // T (PostgreSQL)
|
|
163
|
+
col.specificType<T>(type) // T - database-specific type
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Column Modifiers
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
col.string()
|
|
170
|
+
.nullable() // allows null values
|
|
171
|
+
.defaultTo(value) // sets default value
|
|
172
|
+
.unique() // adds unique constraint
|
|
173
|
+
.primary() // sets as primary key
|
|
174
|
+
.index(indexName?) // adds an index
|
|
175
|
+
.comment(comment) // adds a column comment
|
|
176
|
+
.collate(collation) // sets collation
|
|
177
|
+
|
|
178
|
+
col.integer()
|
|
179
|
+
.unsigned() // only positive values (numeric columns only)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Foreign Key References
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
col.integer()
|
|
186
|
+
.references("id") // column name in referenced table
|
|
187
|
+
.inTable("users") // referenced table name
|
|
188
|
+
.onDelete("CASCADE") // CASCADE | SET NULL | RESTRICT | NO ACTION
|
|
189
|
+
.onUpdate("CASCADE") // CASCADE | SET NULL | RESTRICT | NO ACTION
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Typed Migrations
|
|
108
193
|
|
|
109
|
-
|
|
194
|
+
Use typed migrations that automatically update the TypeScript type:
|
|
110
195
|
|
|
111
196
|
```typescript
|
|
112
197
|
import { Table, col, migrate } from "@ghom/orm"
|
|
@@ -127,7 +212,8 @@ export default new Table({
|
|
|
127
212
|
// Final type: { id: number; username: string; email: string; age: number | null }
|
|
128
213
|
```
|
|
129
214
|
|
|
130
|
-
|
|
215
|
+
### Migration Helpers
|
|
216
|
+
|
|
131
217
|
- `migrate.addColumn(name, columnDef)` - Add a new column
|
|
132
218
|
- `migrate.dropColumn(name)` - Remove a column
|
|
133
219
|
- `migrate.renameColumn(oldName, newName)` - Rename a column
|
|
@@ -136,7 +222,25 @@ Available migration helpers:
|
|
|
136
222
|
- `migrate.dropIndex(name)` - Remove an index
|
|
137
223
|
- `migrate.addUnique(columns, name?)` - Add a unique constraint
|
|
138
224
|
- `migrate.dropUnique(name)` - Remove a unique constraint
|
|
139
|
-
- `migrate.raw(callback)` - Custom migration callback
|
|
225
|
+
- `migrate.raw<From, To>(callback)` - Custom migration callback
|
|
226
|
+
- `migrate.sequence(...migrations)` - Combine multiple migrations
|
|
227
|
+
|
|
228
|
+
### Migration Sequences
|
|
229
|
+
|
|
230
|
+
Use `migrate.sequence()` to combine multiple migrations in a single migration key:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
migrations: {
|
|
234
|
+
"001_user_updates": migrate.sequence(
|
|
235
|
+
migrate.addColumn("phone", col.string()),
|
|
236
|
+
migrate.addColumn("address", col.string().nullable()),
|
|
237
|
+
migrate.addIndex(["phone"], "idx_phone"),
|
|
238
|
+
migrate.renameColumn("name", "username"),
|
|
239
|
+
),
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Intermediate columns (added then removed in the sequence) are excluded from the final type automatically.
|
|
140
244
|
|
|
141
245
|
## Migration Keys
|
|
142
246
|
|
|
@@ -176,21 +280,44 @@ The ORM performs a runtime check on initialization and will throw an error if th
|
|
|
176
280
|
## Launch a query
|
|
177
281
|
|
|
178
282
|
For more information about the query builder, see [knexjs.org](https://knexjs.org/).
|
|
179
|
-
You can launch a SQL query on a table like
|
|
283
|
+
You can launch a SQL query on a table like this:
|
|
180
284
|
|
|
181
285
|
```typescript
|
|
182
|
-
import
|
|
286
|
+
import userTable from "./tables/user"
|
|
183
287
|
|
|
184
|
-
export async function compareHash(username, hash): Promise<boolean> {
|
|
185
|
-
const user = await
|
|
288
|
+
export async function compareHash(username: string, hash: string): Promise<boolean> {
|
|
289
|
+
const user = await userTable.query
|
|
186
290
|
.select()
|
|
187
291
|
.where("username", username)
|
|
188
292
|
.first()
|
|
189
293
|
|
|
190
|
-
return user && user.password === hash
|
|
294
|
+
return user !== undefined && user.password === hash
|
|
191
295
|
}
|
|
192
296
|
```
|
|
193
297
|
|
|
298
|
+
### Table Utilities
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Check if a column exists
|
|
302
|
+
await table.hasColumn("email") // boolean
|
|
303
|
+
|
|
304
|
+
// Get column info
|
|
305
|
+
await table.getColumn("email") // Knex.ColumnInfo
|
|
306
|
+
|
|
307
|
+
// Get all columns info
|
|
308
|
+
await table.getColumns() // Record<string, Knex.ColumnInfo>
|
|
309
|
+
|
|
310
|
+
// Get column names
|
|
311
|
+
await table.getColumnNames() // string[]
|
|
312
|
+
|
|
313
|
+
// Check if table is empty
|
|
314
|
+
await table.isEmpty() // boolean
|
|
315
|
+
|
|
316
|
+
// Count rows
|
|
317
|
+
await table.count() // number
|
|
318
|
+
await table.count("status = 'active'") // number with where clause
|
|
319
|
+
```
|
|
320
|
+
|
|
194
321
|
## Backup
|
|
195
322
|
|
|
196
323
|
You can backup the database by calling the `createBackup` and `restoreBackup` methods on the ORM instance. The backup is stored in the `config.backups.location` directory.
|
|
@@ -210,33 +337,38 @@ The cache is automatically managed by the ORM. When a table is requested from th
|
|
|
210
337
|
```typescript
|
|
211
338
|
// get the number of rows in the table with caching
|
|
212
339
|
await table.cache.count() // => 10
|
|
340
|
+
await table.cache.count("status = 'active'") // with where clause
|
|
213
341
|
|
|
214
|
-
// add a row with caching
|
|
342
|
+
// add a row with caching (automatically invalidates cache)
|
|
215
343
|
await table.cache.set((query) => {
|
|
216
344
|
return query.insert({ name: "test" })
|
|
217
345
|
})
|
|
218
346
|
|
|
219
347
|
await table.cache.count() // => 11
|
|
220
348
|
|
|
221
|
-
// Get
|
|
222
|
-
// After the first call, the
|
|
223
|
-
// the cache is
|
|
224
|
-
await table.cache.get("
|
|
225
|
-
return query.
|
|
226
|
-
}) // => { name: "test" }
|
|
349
|
+
// Get data with caching.
|
|
350
|
+
// After the first call, the result is cached until
|
|
351
|
+
// the cache is invalidated by a "cache.set" or "cache.invalidate" call
|
|
352
|
+
await table.cache.get("all users", (query) => {
|
|
353
|
+
return query.select("*")
|
|
354
|
+
}) // => [{ name: "test" }, ...]
|
|
227
355
|
|
|
228
356
|
// delete the row without caching
|
|
229
357
|
await table.query.delete().where("name", "test")
|
|
230
358
|
|
|
231
|
-
await table.cache.count() // => 11 (unchanged)
|
|
359
|
+
await table.cache.count() // => 11 (unchanged - cache not invalidated)
|
|
232
360
|
|
|
233
|
-
//
|
|
234
|
-
// and force the cache to be updated
|
|
361
|
+
// manually invalidate cache
|
|
235
362
|
table.cache.invalidate()
|
|
236
363
|
|
|
237
364
|
await table.cache.count() // => 10
|
|
238
365
|
await table.cache.count() // => 10 (no more query to the database)
|
|
239
366
|
|
|
367
|
+
// update with caching (automatically invalidates cache)
|
|
368
|
+
await table.cache.set((query) => {
|
|
369
|
+
return query.update({ status: "inactive" }).where("id", 1)
|
|
370
|
+
})
|
|
371
|
+
|
|
240
372
|
// remove all rows from a table with caching
|
|
241
373
|
await table.cache.set((query) => {
|
|
242
374
|
return query.truncate()
|
|
@@ -249,15 +381,21 @@ await table.cache.count() // => 0
|
|
|
249
381
|
|
|
250
382
|
### Raw cache
|
|
251
383
|
|
|
252
|
-
You can also cache raw queries with the `<ORM>.cache.raw`
|
|
384
|
+
You can also cache raw queries with the `<ORM>.cache.raw` method. The raw cache is useful when you have a complex query that you want to cache.
|
|
253
385
|
|
|
254
386
|
```typescript
|
|
255
387
|
const fooUser = await orm.cache.raw("select * from user where name = 'foo'") // query the database
|
|
256
388
|
const barUser = await orm.cache.raw("select * from user where name = 'bar'") // query the database
|
|
257
|
-
const fooUserCached = await orm.cache.raw("select * from user where name = 'foo'") // no query
|
|
389
|
+
const fooUserCached = await orm.cache.raw("select * from user where name = 'foo'") // cached - no query
|
|
390
|
+
|
|
391
|
+
// To invalidate the cache when you know data has changed externally:
|
|
392
|
+
const result = await orm.cache.raw("select * from user", true) // anyDataUpdated = true
|
|
258
393
|
```
|
|
259
394
|
|
|
260
|
-
The
|
|
395
|
+
The raw cache is invalidated when:
|
|
396
|
+
- You call `orm.cache.invalidate()`
|
|
397
|
+
- You use `table.cache.set()` to modify data
|
|
398
|
+
- You pass `true` as the second argument to `orm.cache.raw()`
|
|
261
399
|
|
|
262
400
|
## Future features
|
|
263
401
|
|