@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/readme.md +173 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghom/orm",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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 (console by default)
27
+ // optional custom logger (must have log, error, warn methods)
28
28
  logger: console,
29
- loggerColors: { ... },
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
- "1": (table) => {
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: ({ query }) => {
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
- ### Typed Migrations
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
- You can also use typed migrations that automatically update the TypeScript type:
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
- Available migration helpers:
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 that
283
+ You can launch a SQL query on a table like this:
180
284
 
181
285
  ```typescript
182
- import user from "./tables/user"
286
+ import userTable from "./tables/user"
183
287
 
184
- export async function compareHash(username, hash): Promise<boolean> {
185
- const user = await user.query
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 the row with caching.
222
- // After the first call, the row is cached until
223
- // the cache is invalidate by a "cache.set" or "cache.invalidate" call
224
- await table.cache.get("named test", (query) => {
225
- return query.where("name", "test").first()
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
- // indicate that the cache is invalidate
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` property. The raw cache is useful when you have a complex query that you want to cache.
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 to the database
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 cache of the `<ORM>.cache.raw` method is automatically invalidated when the database is updated.
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