bigal 16.0.0-beta.2 → 16.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/.devcontainer/devcontainer.json +1 -1
- package/.vite-hooks/pre-commit +1 -0
- package/CHANGELOG.md +28 -8
- package/CLAUDE.md +4 -4
- package/README.md +29 -43
- package/dist/index.cjs +3275 -4259
- package/dist/index.d.cts +1511 -1424
- package/dist/index.d.mts +1511 -1424
- package/dist/index.mjs +3265 -4225
- package/docs/.vitepress/config.ts +0 -1
- package/docs/advanced/bigal-vs-raw-sql.md +23 -23
- package/docs/advanced/known-issues.md +26 -21
- package/docs/getting-started.md +35 -29
- package/docs/guide/crud-operations.md +42 -61
- package/docs/guide/models.md +97 -396
- package/docs/guide/querying.md +82 -83
- package/docs/guide/relationships.md +125 -90
- package/docs/guide/subqueries-and-joins.md +39 -32
- package/docs/guide/views.md +51 -79
- package/docs/index.md +31 -27
- package/docs/package.json +3 -2
- package/docs/pnpm-lock.yaml +2338 -0
- package/docs/pnpm-workspace.yaml +3 -0
- package/docs/reference/api.md +86 -384
- package/docs/reference/configuration.md +26 -113
- package/package.json +59 -73
- package/pnpm-workspace.yaml +14 -0
- package/release.config.mjs +1 -1
- package/skills/using-bigal/SKILL.md +213 -276
- package/vite.config.ts +83 -0
- package/.husky/pre-commit +0 -4
- package/.oxfmtrc.json +0 -38
- package/.oxlintrc.json +0 -219
- package/dist/index.d.ts +0 -1526
- package/docs/guide/migration-v16.md +0 -594
- package/docs/package-lock.json +0 -3753
- package/scripts/migrate-v16.ts +0 -497
- package/skills/upgrade-v16/SKILL.md +0 -244
package/docs/guide/models.md
CHANGED
|
@@ -1,486 +1,187 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Define
|
|
2
|
+
description: Define PostgreSQL tables as TypeScript classes with decorators for columns, primary keys, relationships, and automatic timestamps.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Models
|
|
6
6
|
|
|
7
|
-
Models
|
|
8
|
-
a set of column builders that map directly to PostgreSQL column types. Types are inferred from the schema
|
|
9
|
-
definition - no separate interfaces required.
|
|
7
|
+
Models map TypeScript classes to PostgreSQL tables. Every model extends `Entity` and uses decorators for table and column configuration.
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## Table decorator
|
|
12
10
|
|
|
13
|
-
Use
|
|
11
|
+
Use `@table()` to bind a class to a database table:
|
|
14
12
|
|
|
15
13
|
```ts
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
export const Product = table('products', {
|
|
19
|
-
id: serial().primaryKey(),
|
|
20
|
-
name: text().notNull(),
|
|
21
|
-
priceCents: integer().notNull(),
|
|
22
|
-
isActive: boolean().notNull().default(true),
|
|
23
|
-
createdAt: createdAt(),
|
|
24
|
-
updatedAt: updatedAt(),
|
|
25
|
-
});
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
The first argument is the database table name. The second is an object mapping property names to column
|
|
29
|
-
builders. Column builders take no arguments by default - the database column name is auto-derived from
|
|
30
|
-
the property key using snakeCase (e.g., `priceCents` becomes `price_cents`).
|
|
31
|
-
|
|
32
|
-
### Auto-derived names
|
|
33
|
-
|
|
34
|
-
BigAl automatically derives two names from your definitions:
|
|
35
|
-
|
|
36
|
-
- **Column names** - property keys are converted to snake_case for the database column name.
|
|
37
|
-
`priceCents` becomes `price_cents`, `isActive` becomes `is_active`.
|
|
38
|
-
- **Model names** - the table name is singularized and PascalCased for relationship lookups.
|
|
39
|
-
`products` becomes `Product`, `product__category` becomes `ProductCategory`.
|
|
40
|
-
|
|
41
|
-
Override either when the convention does not match:
|
|
14
|
+
import { table, Entity } from 'bigal';
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Override model name
|
|
48
|
-
const Product = table('items', { /* ... */ }, { modelName: 'Product' });
|
|
16
|
+
@table({ name: 'products' })
|
|
17
|
+
export class Product extends Entity {
|
|
18
|
+
// columns go here
|
|
19
|
+
}
|
|
49
20
|
```
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Pass a third argument for additional options:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
const AuditLog = table(
|
|
57
|
-
'audit_logs',
|
|
58
|
-
{
|
|
59
|
-
/* columns */
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
schema: 'audit',
|
|
63
|
-
readonly: true,
|
|
64
|
-
connection: 'audit',
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
```
|
|
22
|
+
Options:
|
|
68
23
|
|
|
69
24
|
| Option | Type | Description |
|
|
70
25
|
| ------------ | --------- | -------------------------------------------------------- |
|
|
26
|
+
| `name` | `string` | Database table or view name |
|
|
71
27
|
| `schema` | `string` | PostgreSQL schema (default: `public`) |
|
|
72
|
-
| `readonly` | `boolean` | If `true`, `initialize()` returns a
|
|
28
|
+
| `readonly` | `boolean` | If `true`, `initialize()` returns a `ReadonlyRepository` |
|
|
73
29
|
| `connection` | `string` | Named connection key (for multi-database setups) |
|
|
74
|
-
| `modelName` | `string` | Override auto-derived model name |
|
|
75
|
-
| `hooks` | `object` | Lifecycle hooks (see [Hooks](#hooks)) |
|
|
76
|
-
| `filters` | `object` | Global filters (see [Global filters](#global-filters)) |
|
|
77
30
|
|
|
78
|
-
## Column
|
|
31
|
+
## Column decorators
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
to make them required. Column builders take no arguments; pass `{ name: 'custom_name' }` only when the
|
|
82
|
-
auto-derived snake_case name does not match your database column.
|
|
33
|
+
### `@primaryColumn()`
|
|
83
34
|
|
|
84
|
-
|
|
35
|
+
Marks the primary key column:
|
|
85
36
|
|
|
86
37
|
```ts
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
name: text(), // TEXT - string | null
|
|
90
|
-
sku: varchar({ length: 100 }), // VARCHAR(100) - string | null
|
|
91
|
-
externalId: uuid(), // UUID - string | null
|
|
92
|
-
status: text<'active' | 'inactive'>(), // TEXT - 'active' | 'inactive' | null
|
|
93
|
-
role: varchar<'admin' | 'user'>({ length: 50 }).notNull(), // VARCHAR(50) - 'admin' | 'user'
|
|
94
|
-
```
|
|
38
|
+
import { primaryColumn } from 'bigal';
|
|
95
39
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### Numeric types
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import { serial, bigserial, integer, bigint, smallint, float, double } from 'bigal';
|
|
103
|
-
|
|
104
|
-
id: serial().primaryKey(), // SERIAL - number (notNull + default implied)
|
|
105
|
-
bigId: bigserial(), // BIGSERIAL - number (notNull + default implied)
|
|
106
|
-
quantity: integer(), // INTEGER - number | null
|
|
107
|
-
views: bigint(), // BIGINT - number | null
|
|
108
|
-
rank: smallint(), // SMALLINT - number | null
|
|
109
|
-
score: float(), // REAL - number | null
|
|
110
|
-
precise: double(), // DOUBLE PRECISION - number | null
|
|
40
|
+
@primaryColumn({ type: 'integer' })
|
|
41
|
+
public id!: number;
|
|
111
42
|
```
|
|
112
43
|
|
|
113
|
-
|
|
114
|
-
`number` on select and optional on insert.
|
|
44
|
+
### `@column()`
|
|
115
45
|
|
|
116
|
-
|
|
46
|
+
Defines a regular column:
|
|
117
47
|
|
|
118
48
|
```ts
|
|
119
|
-
import {
|
|
120
|
-
|
|
121
|
-
isActive: boolean(), // BOOLEAN -- boolean | null
|
|
122
|
-
isPublished: boolean().notNull(), // BOOLEAN -- boolean
|
|
123
|
-
isArchived: boolean().notNull().default(false), // BOOLEAN -- boolean (optional on insert)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Date and time
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
import { date, timestamp, timestamptz, createdAt, updatedAt } from 'bigal';
|
|
130
|
-
|
|
131
|
-
birthDate: date(), // DATE -- Date | null
|
|
132
|
-
occurredAt: timestamp(), // TIMESTAMP -- Date | null
|
|
133
|
-
scheduledFor: timestamptz(), // TIMESTAMPTZ -- Date | null
|
|
134
|
-
createdAt: createdAt(), // TIMESTAMPTZ -- Date (notNull, auto-set on insert)
|
|
135
|
-
updatedAt: updatedAt(), // TIMESTAMPTZ -- Date (notNull, auto-set on insert/update)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
`createdAt()` and `updatedAt()` default to column names `created_at` and `updated_at`. Pass a custom
|
|
139
|
-
name if needed: `createdAt({ name: 'creation_time' })`.
|
|
140
|
-
|
|
141
|
-
### JSON
|
|
49
|
+
import { column } from 'bigal';
|
|
142
50
|
|
|
143
|
-
|
|
144
|
-
|
|
51
|
+
@column({ type: 'string', required: true })
|
|
52
|
+
public name!: string;
|
|
145
53
|
|
|
146
|
-
|
|
147
|
-
|
|
54
|
+
@column({ type: 'string' })
|
|
55
|
+
public sku?: string;
|
|
148
56
|
```
|
|
149
57
|
|
|
150
|
-
|
|
151
|
-
`Record<string, unknown>`.
|
|
58
|
+
### Vector (pgvector)
|
|
152
59
|
|
|
153
|
-
|
|
60
|
+
Declare a `VECTOR(n)` column with `type: 'vector'`. Values are `number[] | null`:
|
|
154
61
|
|
|
155
62
|
```ts
|
|
156
|
-
import {
|
|
63
|
+
import { column } from 'bigal';
|
|
157
64
|
|
|
158
|
-
|
|
65
|
+
@column({ type: 'vector', dimensions: 1536 })
|
|
66
|
+
public embedding?: number[];
|
|
159
67
|
```
|
|
160
68
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
69
|
+
Requires the [pgvector](https://github.com/pgvector/pgvector) extension. The `dimensions` option is
|
|
70
|
+
informational — BigAl does not issue DDL. See
|
|
71
|
+
[Querying > Vector distance queries](/guide/querying#vector-distance-queries) for sorting and
|
|
72
|
+
filtering by similarity.
|
|
165
73
|
|
|
166
|
-
|
|
167
|
-
scores: integerArray(), // INTEGER[] -- number[] | null
|
|
168
|
-
flags: booleanArray(), // BOOLEAN[] -- boolean[] | null
|
|
169
|
-
```
|
|
74
|
+
### `@createDateColumn()`
|
|
170
75
|
|
|
171
|
-
|
|
76
|
+
Automatically set on insert:
|
|
172
77
|
|
|
173
78
|
```ts
|
|
174
|
-
import {
|
|
175
|
-
|
|
176
|
-
embedding: vector({ dimensions: 1536 }), // VECTOR(1536) -- number[] | null
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
The `dimensions` option is required. See [Querying > Vector distance](/guide/querying#vector-distance-queries)
|
|
180
|
-
for sorting and filtering by similarity.
|
|
79
|
+
import { createDateColumn } from 'bigal';
|
|
181
80
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
Chain methods on any column builder to set constraints. Each method returns the builder, so chains
|
|
185
|
-
compose naturally.
|
|
186
|
-
|
|
187
|
-
### .notNull()
|
|
188
|
-
|
|
189
|
-
Removes `null` from the TypeScript type. Maps to a `NOT NULL` constraint.
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
name: text().notNull(), // string (not string | null)
|
|
81
|
+
@createDateColumn()
|
|
82
|
+
public createdAt!: Date;
|
|
193
83
|
```
|
|
194
84
|
|
|
195
|
-
###
|
|
85
|
+
### `@updateDateColumn()`
|
|
196
86
|
|
|
197
|
-
|
|
87
|
+
Automatically set on update:
|
|
198
88
|
|
|
199
89
|
```ts
|
|
200
|
-
|
|
201
|
-
aliases: textArray().default([]), // string[] | null, optional on insert
|
|
202
|
-
```
|
|
90
|
+
import { updateDateColumn } from 'bigal';
|
|
203
91
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Marks the column as the primary key. Implies `.notNull()` and makes the column optional on insert
|
|
207
|
-
(the database assigns the value).
|
|
208
|
-
|
|
209
|
-
```ts
|
|
210
|
-
id: serial().primaryKey(), // number, optional on insert
|
|
211
|
-
id: uuid().primaryKey(), // string, optional on insert
|
|
92
|
+
@updateDateColumn()
|
|
93
|
+
public updatedAt!: Date;
|
|
212
94
|
```
|
|
213
95
|
|
|
214
|
-
###
|
|
96
|
+
### `@versionColumn()`
|
|
215
97
|
|
|
216
|
-
|
|
98
|
+
Auto-incrementing version for optimistic locking:
|
|
217
99
|
|
|
218
100
|
```ts
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### .version()
|
|
223
|
-
|
|
224
|
-
Marks the column for optimistic locking. Implies `.notNull()`. BigAl automatically increments the
|
|
225
|
-
value on each update.
|
|
101
|
+
import { versionColumn } from 'bigal';
|
|
226
102
|
|
|
227
|
-
|
|
228
|
-
|
|
103
|
+
@versionColumn()
|
|
104
|
+
public version!: number;
|
|
229
105
|
```
|
|
230
106
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Modifiers compose in any order:
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
email: text().notNull().unique(),
|
|
237
|
-
isActive: boolean().notNull().default(true),
|
|
238
|
-
score: integer().default(0),
|
|
239
|
-
```
|
|
107
|
+
## Column options
|
|
240
108
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
248
|
-
| `
|
|
249
|
-
| `
|
|
250
|
-
| `
|
|
251
|
-
| `
|
|
252
|
-
| `float()` / `real()` | REAL | `number \| null` | |
|
|
253
|
-
| `double()` | DOUBLE PRECISION | `number \| null` | |
|
|
254
|
-
| `boolean()` | BOOLEAN | `boolean \| null` | |
|
|
255
|
-
| `timestamp()` | TIMESTAMP | `Date \| null` | |
|
|
256
|
-
| `timestamptz()` | TIMESTAMPTZ | `Date \| null` | |
|
|
257
|
-
| `date()` | DATE | `Date \| null` | |
|
|
258
|
-
| `json<T>()` | JSON | `T \| null` | Defaults to `Record<string, unknown>` |
|
|
259
|
-
| `jsonb<T>()` | JSONB | `T \| null` | Defaults to `Record<string, unknown>` |
|
|
260
|
-
| `uuid()` | UUID | `string \| null` | |
|
|
261
|
-
| `bytea()` | BYTEA | `Buffer \| null` | |
|
|
262
|
-
| `textArray()` | TEXT[] | `string[] \| null` | |
|
|
263
|
-
| `integerArray()` | INTEGER[] | `number[] \| null` | |
|
|
264
|
-
| `booleanArray()` | BOOLEAN[] | `boolean[] \| null` | |
|
|
265
|
-
| `vector({ dimensions })` | VECTOR(n) | `number[] \| null` | Requires pgvector extension |
|
|
266
|
-
| `createdAt()` | TIMESTAMPTZ | `Date` | notNull, auto-set on insert |
|
|
267
|
-
| `updatedAt()` | TIMESTAMPTZ | `Date` | notNull, auto-set on insert/update |
|
|
109
|
+
| Option | Type | Description |
|
|
110
|
+
| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `type` | `string` | Column type: `'string'`, `'integer'`, `'float'`, `'boolean'`, `'date'`, `'datetime'`, `'json'`, `'string[]'`, `'integer[]'`, `'float[]'`, `'boolean[]'`, `'vector'` |
|
|
112
|
+
| `name` | `string` | Database column name (if different from property name) |
|
|
113
|
+
| `required` | `boolean` | If `true`, value must not be null |
|
|
114
|
+
| `defaultsTo` | `any` | Default value |
|
|
115
|
+
| `dimensions` | `number` | Number of dimensions for `'vector'` columns. Informational only — BigAl does not issue DDL |
|
|
116
|
+
| `model` | `() => string` | Foreign key relationship (many-to-one) |
|
|
117
|
+
| `collection` | `() => string` | Inverse relationship (one-to-many or many-to-many) |
|
|
118
|
+
| `through` | `() => string` | Join table for many-to-many |
|
|
119
|
+
| `via` | `string` | Property on related model that holds the foreign key |
|
|
268
120
|
|
|
269
121
|
## Relationships
|
|
270
122
|
|
|
271
|
-
### Many-to-one
|
|
272
|
-
|
|
273
|
-
Use `belongsTo` when this table holds the foreign key:
|
|
123
|
+
### Many-to-one
|
|
274
124
|
|
|
275
|
-
|
|
276
|
-
import { belongsTo } from 'bigal';
|
|
277
|
-
|
|
278
|
-
store: belongsTo('Store'),
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
The argument is the model name of the related table. The FK column name is auto-derived as
|
|
282
|
-
`snakeCase(propertyKey) + '_id'` (e.g., `store` becomes `store_id`).
|
|
283
|
-
|
|
284
|
-
Override the FK column name when the convention does not match:
|
|
125
|
+
Use `model` when the current entity holds the foreign key:
|
|
285
126
|
|
|
286
127
|
```ts
|
|
287
|
-
|
|
288
|
-
|
|
128
|
+
import { column, Entity, primaryColumn, table } from 'bigal';
|
|
129
|
+
import type { Store } from './Store';
|
|
289
130
|
|
|
290
|
-
|
|
291
|
-
|
|
131
|
+
@table({ name: 'products' })
|
|
132
|
+
export class Product extends Entity {
|
|
133
|
+
@primaryColumn({ type: 'integer' })
|
|
134
|
+
public id!: number;
|
|
292
135
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
```ts
|
|
298
|
-
import { hasMany } from 'bigal';
|
|
299
|
-
|
|
300
|
-
products: hasMany('Product').via('store'),
|
|
136
|
+
@column({ model: () => 'Store', name: 'store_id' })
|
|
137
|
+
public store!: number | Store;
|
|
138
|
+
}
|
|
301
139
|
```
|
|
302
140
|
|
|
303
|
-
|
|
304
|
-
table.
|
|
141
|
+
The property type is `number | Store` — it holds the foreign key when not populated, or the full entity after `.populate()`.
|
|
305
142
|
|
|
306
|
-
###
|
|
143
|
+
### One-to-many
|
|
307
144
|
|
|
308
|
-
Use
|
|
145
|
+
Use `collection` on the inverse side:
|
|
309
146
|
|
|
310
147
|
```ts
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
.via('product'),
|
|
148
|
+
@column({ collection: () => 'Product', via: 'store' })
|
|
149
|
+
public products?: Product[];
|
|
314
150
|
```
|
|
315
151
|
|
|
316
|
-
|
|
317
|
-
populate. `QueryResult` strips them from results, so they only appear after `.populate()`.
|
|
318
|
-
They are excluded from the insert type entirely.
|
|
319
|
-
|
|
320
|
-
See [Relationships](/guide/relationships) for complete examples.
|
|
152
|
+
Collections **must** be optional (`?`) since they are only present after `.populate()`.
|
|
321
153
|
|
|
322
|
-
|
|
154
|
+
### Many-to-many
|
|
323
155
|
|
|
324
|
-
|
|
325
|
-
Domain-specific columns like relationships should stay inline in each model.
|
|
156
|
+
Use `collection` with `through` for join tables:
|
|
326
157
|
|
|
327
158
|
```ts
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export const Product = table('products', {
|
|
335
|
-
...modelBase,
|
|
336
|
-
name: text().notNull(),
|
|
337
|
-
store: belongsTo('Store'),
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
export const Store = table('stores', {
|
|
341
|
-
...modelBase,
|
|
342
|
-
name: text(),
|
|
343
|
-
});
|
|
159
|
+
@column({
|
|
160
|
+
collection: () => 'Category',
|
|
161
|
+
through: () => 'ProductCategory',
|
|
162
|
+
via: 'product',
|
|
163
|
+
})
|
|
164
|
+
public categories?: Category[];
|
|
344
165
|
```
|
|
345
166
|
|
|
346
|
-
|
|
167
|
+
See [Relationships](/guide/relationships) for complete examples including join tables and self-referencing models.
|
|
347
168
|
|
|
348
|
-
|
|
169
|
+
## Entity base class
|
|
349
170
|
|
|
350
|
-
|
|
351
|
-
import type { QueryResult, InferInsert } from 'bigal';
|
|
352
|
-
|
|
353
|
-
type ProductRow = QueryResult<typeof Product>;
|
|
354
|
-
// { id: number; name: string; priceCents: number; isActive: boolean;
|
|
355
|
-
// store: number; metadata: { color?: string } | null; createdAt: Date; updatedAt: Date }
|
|
356
|
-
// hasMany collections (categories) are stripped by QueryResult
|
|
357
|
-
|
|
358
|
-
type ProductInsert = InferInsert<(typeof Product)['schema']>;
|
|
359
|
-
// { name: string; priceCents: number; store: number; <-- required
|
|
360
|
-
// id?: number; isActive?: boolean; <-- optional (has default or primary key)
|
|
361
|
-
// metadata?: { color?: string } | null; } <-- optional (nullable)
|
|
362
|
-
```
|
|
171
|
+
All models extend `Entity`, which provides no properties by itself — it serves as a marker for BigAl's type system to distinguish ORM entities from plain objects.
|
|
363
172
|
|
|
364
|
-
|
|
173
|
+
## NotEntity\<T\>
|
|
365
174
|
|
|
366
|
-
|
|
367
|
-
`Repository` and `ReadonlyRepository` type aliases:
|
|
175
|
+
If a JSON column contains objects with an `id` field, TypeScript may mistake them for BigAl entities. Wrap the type with `NotEntity<T>`:
|
|
368
176
|
|
|
369
177
|
```ts
|
|
370
|
-
import type {
|
|
371
|
-
|
|
372
|
-
function processProducts(repo: Repository<typeof Product>) {
|
|
373
|
-
return repo.find().where({ name: { contains: 'widget' } });
|
|
374
|
-
}
|
|
178
|
+
import type { NotEntity } from 'bigal';
|
|
375
179
|
|
|
376
|
-
|
|
377
|
-
|
|
180
|
+
interface IMyJsonType {
|
|
181
|
+
id: string;
|
|
182
|
+
foo: string;
|
|
378
183
|
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
These accept `typeof YourTableDef` and resolve to the correctly typed `IRepository` or
|
|
382
|
-
`IReadonlyRepository`.
|
|
383
|
-
|
|
384
|
-
### Insert type rules
|
|
385
|
-
|
|
386
|
-
Columns are **required** on insert when they are `.notNull()` and have no default, are not a primary
|
|
387
|
-
key, and are not auto-set.
|
|
388
|
-
|
|
389
|
-
Columns are **optional** on insert when any of the following is true:
|
|
390
|
-
|
|
391
|
-
- Nullable (no `.notNull()`)
|
|
392
|
-
- Has a `.default()` value
|
|
393
|
-
- Is a `.primaryKey()`
|
|
394
|
-
- Is auto-set (`createdAt()`, `updatedAt()`)
|
|
395
|
-
|
|
396
|
-
`hasMany` columns are excluded from the insert type entirely.
|
|
397
|
-
|
|
398
|
-
## Hooks
|
|
399
|
-
|
|
400
|
-
Define lifecycle hooks in the third argument to `table()`:
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
export const Product = table(
|
|
404
|
-
'products',
|
|
405
|
-
{
|
|
406
|
-
id: serial().primaryKey(),
|
|
407
|
-
name: text().notNull(),
|
|
408
|
-
slug: text().notNull(),
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
hooks: {
|
|
412
|
-
beforeCreate(values) {
|
|
413
|
-
return { ...values, slug: slugify(values.name) };
|
|
414
|
-
},
|
|
415
|
-
afterCreate(result) {
|
|
416
|
-
audit.log('product.created', result.id);
|
|
417
|
-
},
|
|
418
|
-
beforeUpdate(values) {
|
|
419
|
-
if (values.name) {
|
|
420
|
-
return { ...values, slug: slugify(values.name) };
|
|
421
|
-
}
|
|
422
|
-
return values;
|
|
423
|
-
},
|
|
424
|
-
afterUpdate(result) {
|
|
425
|
-
audit.log('product.updated', result.id);
|
|
426
|
-
},
|
|
427
|
-
beforeDestroy(where) {
|
|
428
|
-
return where;
|
|
429
|
-
},
|
|
430
|
-
afterDestroy({ rowCount }) {
|
|
431
|
-
audit.log('product.destroyed', rowCount);
|
|
432
|
-
},
|
|
433
|
-
afterFind(results) {
|
|
434
|
-
return results;
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
);
|
|
439
|
-
```
|
|
440
184
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
| `beforeCreate` | Before `INSERT` | Insert values | Modified insert values |
|
|
444
|
-
| `afterCreate` | After `INSERT` | Created record | void |
|
|
445
|
-
| `beforeUpdate` | Before `UPDATE` | Partial update values | Modified update values |
|
|
446
|
-
| `afterUpdate` | After `UPDATE` | Updated record | void |
|
|
447
|
-
| `beforeDestroy` | Before `DELETE` | Where clause object | Modified where clause |
|
|
448
|
-
| `afterDestroy` | After `DELETE` | `{ rowCount: number }` | void |
|
|
449
|
-
| `afterFind` | After `SELECT` | Array of results | Modified results array |
|
|
450
|
-
|
|
451
|
-
All hook parameters are fully typed based on the schema definition. Hooks can be synchronous or
|
|
452
|
-
return a Promise.
|
|
453
|
-
|
|
454
|
-
## Global filters
|
|
455
|
-
|
|
456
|
-
Define named filters that are automatically applied to every `find` and `findOne` query:
|
|
457
|
-
|
|
458
|
-
```ts
|
|
459
|
-
const Product = table(
|
|
460
|
-
'products',
|
|
461
|
-
{
|
|
462
|
-
/* columns */
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
filters: {
|
|
466
|
-
active: { isActive: true },
|
|
467
|
-
notDeleted: () => ({ deletedAt: null }),
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
);
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
Filters can be static objects or functions that return a where clause dynamically. They are merged
|
|
474
|
-
into every query's WHERE clause.
|
|
475
|
-
|
|
476
|
-
Override filters per query:
|
|
477
|
-
|
|
478
|
-
```ts
|
|
479
|
-
// Disable all filters
|
|
480
|
-
await Product.find().where({}).filters(false);
|
|
481
|
-
|
|
482
|
-
// Disable a specific filter
|
|
483
|
-
await Product.find().where({}).filters({ active: false });
|
|
185
|
+
@column({ type: 'json' })
|
|
186
|
+
public metadata?: NotEntity<IMyJsonType>;
|
|
484
187
|
```
|
|
485
|
-
|
|
486
|
-
See [Querying > Global filters](/guide/querying#global-filters) for more details.
|