0
# Utilities and Plugins
1
2
Utility functions for column mapping, mixins, and extending model functionality.
3
4
## Capabilities
5
6
### Snake Case Mappers
7
8
Utility functions for converting between camelCase and snake_case column names.
9
10
```javascript { .api }
11
/**
12
* Create column name mappers for snake_case conversion
13
* @param options - Snake case conversion options
14
* @returns Column name mappers object
15
*/
16
function snakeCaseMappers(options?: SnakeCaseMappersOptions): ColumnNameMappers;
17
18
interface SnakeCaseMappersOptions {
19
/** Convert to uppercase (SCREAMING_SNAKE_CASE) */
20
upperCase?: boolean;
21
22
/** Add underscore before digits */
23
underscoreBeforeDigits?: boolean;
24
25
/** Add underscore between consecutive uppercase letters */
26
underscoreBetweenUppercaseLetters?: boolean;
27
}
28
29
interface ColumnNameMappers {
30
/** Parse database column names to model property names */
31
parse(json: object): object;
32
33
/** Format model property names to database column names */
34
format(json: object): object;
35
}
36
```
37
38
**Usage Examples:**
39
40
```javascript
41
const { snakeCaseMappers } = require('objection');
42
43
class Person extends Model {
44
static get tableName() {
45
return 'persons';
46
}
47
48
static get columnNameMappers() {
49
return snakeCaseMappers();
50
}
51
}
52
53
// Now you can use camelCase in your models
54
const person = await Person.query().insert({
55
firstName: 'John', // Maps to first_name in database
56
lastName: 'Doe', // Maps to last_name in database
57
phoneNumber: '555-1234' // Maps to phone_number in database
58
});
59
60
// Custom options
61
const mappers = snakeCaseMappers({
62
upperCase: true, // FIRST_NAME instead of first_name
63
underscoreBeforeDigits: true, // address2 -> address_2
64
underscoreBetweenUppercaseLetters: true // XMLParser -> x_m_l_parser
65
});
66
67
class CustomModel extends Model {
68
static get columnNameMappers() {
69
return mappers;
70
}
71
}
72
```
73
74
### Knex Snake Case Mappers
75
76
Knex-level mappers for global snake_case conversion.
77
78
```javascript { .api }
79
/**
80
* Create Knex mappers for snake_case conversion
81
* @param options - Snake case conversion options
82
* @returns Knex mappers object
83
*/
84
function knexSnakeCaseMappers(options?: SnakeCaseMappersOptions): KnexMappers;
85
86
interface KnexMappers {
87
/** Wrap identifiers (table/column names) for queries */
88
wrapIdentifier(identifier: string, origWrap: (str: string) => string): string;
89
90
/** Process response data after query execution */
91
postProcessResponse(response: any): any;
92
}
93
```
94
95
**Usage Examples:**
96
97
```javascript
98
const { knexSnakeCaseMappers } = require('objection');
99
const Knex = require('knex');
100
101
// Configure Knex with snake case mappers
102
const knex = Knex({
103
client: 'postgresql',
104
connection: connectionConfig,
105
...knexSnakeCaseMappers()
106
});
107
108
// All queries will automatically convert between camelCase and snake_case
109
Model.knex(knex);
110
111
// Model properties in camelCase
112
class Person extends Model {
113
static get tableName() {
114
return 'persons'; // Actually maps to 'persons' table with snake_case columns
115
}
116
}
117
118
// Usage is the same, but conversion happens automatically
119
const person = await Person.query().insert({
120
firstName: 'John', // Automatically becomes first_name in SQL
121
lastName: 'Doe', // Automatically becomes last_name in SQL
122
dateOfBirth: new Date() // Automatically becomes date_of_birth in SQL
123
});
124
```
125
126
### Identifier Mapping
127
128
Advanced identifier mapping functionality.
129
130
```javascript { .api }
131
/**
132
* Create identifier mapping configuration for Knex
133
* @param options - Mapping options
134
* @returns Knex configuration object
135
*/
136
function knexIdentifierMapping(options?: IdentifierMappingOptions): object;
137
138
interface IdentifierMappingOptions {
139
upperCase?: boolean;
140
underscoreBeforeDigits?: boolean;
141
underscoreBetweenUppercaseLetters?: boolean;
142
}
143
```
144
145
### Model Mixins
146
147
Plugin system for extending model functionality.
148
149
```javascript { .api }
150
/**
151
* Apply plugins to a model class
152
* @param modelClass - Model class to extend
153
* @param plugins - Plugins to apply
154
* @returns Extended model class
155
*/
156
function mixin<T extends typeof Model>(
157
modelClass: T,
158
...plugins: Plugin[]
159
): T;
160
161
/**
162
* Compose multiple plugins into a single plugin
163
* @param plugins - Plugins to compose
164
* @returns Composed plugin
165
*/
166
function compose(...plugins: Plugin[]): Plugin;
167
168
/**
169
* Plugin function type
170
*/
171
type Plugin = <T extends typeof Model>(modelClass: T) => T;
172
```
173
174
**Usage Examples:**
175
176
```javascript
177
const { mixin, compose } = require('objection');
178
179
// Define plugins
180
const timestampPlugin = (Model) => {
181
return class extends Model {
182
$beforeInsert() {
183
this.createdAt = new Date();
184
this.updatedAt = new Date();
185
}
186
187
$beforeUpdate() {
188
this.updatedAt = new Date();
189
}
190
};
191
};
192
193
const softDeletePlugin = (Model) => {
194
return class extends Model {
195
static get modifiers() {
196
return {
197
...super.modifiers,
198
notDeleted: (query) => query.whereNull('deletedAt')
199
};
200
}
201
202
$beforeDelete() {
203
return this.$query().patch({ deletedAt: new Date() });
204
}
205
};
206
};
207
208
// Apply single plugin
209
const PersonWithTimestamps = mixin(Person, timestampPlugin);
210
211
// Apply multiple plugins
212
const PersonWithPlugins = mixin(Person, timestampPlugin, softDeletePlugin);
213
214
// Compose plugins
215
const combinedPlugin = compose(timestampPlugin, softDeletePlugin);
216
const PersonWithCombined = mixin(Person, combinedPlugin);
217
218
// Create model with plugins applied
219
class EnhancedPerson extends mixin(Model, timestampPlugin, softDeletePlugin) {
220
static get tableName() {
221
return 'persons';
222
}
223
}
224
```
225
226
### Model Traversal
227
228
Utilities for traversing model graphs and relationships.
229
230
```javascript { .api }
231
/**
232
* Traverse model instances and their relations
233
* @param models - Model instance(s) to traverse
234
* @param traverser - Function called for each model
235
*/
236
static traverse(
237
models: Model | Model[],
238
traverser: TraverserFunction
239
): void;
240
241
/**
242
* Traverse model instances with filtering
243
* @param filterConstructor - Only traverse models of this type
244
* @param models - Model instance(s) to traverse
245
* @param traverser - Function called for each model
246
*/
247
static traverse(
248
filterConstructor: typeof Model,
249
models: Model | Model[],
250
traverser: TraverserFunction
251
): void;
252
253
/**
254
* Async version of traverse
255
*/
256
static traverseAsync(
257
models: Model | Model[],
258
traverser: TraverserFunction
259
): Promise<void>;
260
261
type TraverserFunction = (
262
model: Model,
263
parentModel?: Model,
264
relationName?: string
265
) => void;
266
```
267
268
**Usage Examples:**
269
270
```javascript
271
// Traverse all models in a graph
272
const people = await Person.query()
273
.withGraphFetched('[pets, movies.actors]');
274
275
Person.traverse(people, (model, parent, relationName) => {
276
console.log(`Found ${model.constructor.name}:`, model.id);
277
if (parent) {
278
console.log(` Child of ${parent.constructor.name} via ${relationName}`);
279
}
280
});
281
282
// Traverse only specific model types
283
Person.traverse(Pet, people, (pet) => {
284
console.log('Found pet:', pet.name);
285
});
286
287
// Async traversal for database operations
288
await Person.traverseAsync(people, async (model) => {
289
if (model instanceof Person) {
290
await model.$query().patch({ lastAccessed: new Date() });
291
}
292
});
293
294
// Instance-level traversal
295
const person = await Person.query()
296
.findById(1)
297
.withGraphFetched('pets');
298
299
person.$traverse((model) => {
300
model.accessed = true;
301
});
302
```
303
304
### Initialization
305
306
Initialize models with database connections and metadata.
307
308
```javascript { .api }
309
/**
310
* Initialize model classes with Knex instance and fetch metadata
311
* @param knex - Knex instance to bind
312
* @param modelClasses - Model classes to initialize
313
* @returns Promise resolving when initialization is complete
314
*/
315
function initialize(
316
knex: Knex,
317
modelClasses: Array<typeof Model>
318
): Promise<void>;
319
320
/**
321
* Initialize with default Knex instance
322
* @param modelClasses - Model classes to initialize
323
* @returns Promise resolving when initialization is complete
324
*/
325
function initialize(modelClasses: Array<typeof Model>): Promise<void>;
326
```
327
328
**Usage Examples:**
329
330
```javascript
331
const { initialize } = require('objection');
332
const Knex = require('knex');
333
334
const knex = Knex({
335
client: 'postgresql',
336
connection: connectionConfig
337
});
338
339
// Initialize models
340
await initialize(knex, [Person, Pet, Movie]);
341
342
// Models are now ready to use
343
const people = await Person.query();
344
```
345
346
### Table Metadata
347
348
Utilities for fetching and caching table metadata.
349
350
```javascript { .api }
351
/**
352
* Get cached table metadata
353
* @param options - Metadata options
354
* @returns Table metadata object
355
*/
356
static tableMetadata(options?: TableMetadataOptions): TableMetadata;
357
358
/**
359
* Fetch fresh table metadata from database
360
* @param options - Fetch options
361
* @returns Promise resolving to table metadata
362
*/
363
static fetchTableMetadata(options?: FetchTableMetadataOptions): Promise<TableMetadata>;
364
365
interface TableMetadata {
366
columns: string[];
367
}
368
369
interface TableMetadataOptions {
370
table?: string;
371
}
372
373
interface FetchTableMetadataOptions {
374
knex?: Knex;
375
force?: boolean;
376
table?: string;
377
}
378
```
379
380
**Usage Examples:**
381
382
```javascript
383
// Get cached metadata
384
const metadata = Person.tableMetadata();
385
console.log('Columns:', metadata.columns);
386
387
// Fetch fresh metadata
388
const freshMetadata = await Person.fetchTableMetadata({ force: true });
389
console.log('Fresh columns:', freshMetadata.columns);
390
391
// Fetch metadata for specific table
392
const petMetadata = await Person.fetchTableMetadata({ table: 'pets' });
393
```
394
395
### Class Utilities
396
397
Low-level utilities for class manipulation and inheritance.
398
399
```javascript { .api }
400
/**
401
* Set up inheritance between classes (used internally)
402
* @param Child - Child class
403
* @param Parent - Parent class
404
*/
405
function inherit(Child: Function, Parent: Function): void;
406
```
407
408
## Types
409
410
```typescript { .api }
411
interface SnakeCaseMappersOptions {
412
upperCase?: boolean;
413
underscoreBeforeDigits?: boolean;
414
underscoreBetweenUppercaseLetters?: boolean;
415
}
416
417
interface ColumnNameMappers {
418
parse(json: object): object;
419
format(json: object): object;
420
}
421
422
interface KnexMappers {
423
wrapIdentifier(identifier: string, origWrap: (str: string) => string): string;
424
postProcessResponse(response: any): any;
425
}
426
427
type Plugin = <T extends typeof Model>(modelClass: T) => T;
428
429
type TraverserFunction = (
430
model: Model,
431
parentModel?: Model,
432
relationName?: string
433
) => void;
434
435
interface TableMetadata {
436
columns: string[];
437
}
438
439
interface TableMetadataOptions {
440
table?: string;
441
}
442
443
interface FetchTableMetadataOptions {
444
knex?: Knex;
445
force?: boolean;
446
table?: string;
447
}
448
```