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.
@@ -1,244 +0,0 @@
1
- ---
2
- name: upgrading-bigal-v16
3
- description: Migrates BigAl projects from v15 decorator-based models to v16 function-based table() API. Use when upgrading BigAl, converting decorator models, or migrating from class-based Entity models to the new schema API.
4
- ---
5
-
6
- # Upgrading to BigAl v16
7
-
8
- ## Quick Start
9
-
10
- v16 replaces decorators with function-based schema definitions. No classes,
11
- no `experimentalDecorators`, no `extends Entity`.
12
-
13
- **Before (v15):**
14
-
15
- ```typescript
16
- import { column, primaryColumn, table, Entity, initialize } from 'bigal';
17
-
18
- @table({ name: 'products' })
19
- class Product extends Entity {
20
- @primaryColumn({ type: 'integer' })
21
- public id!: number;
22
-
23
- @column({ type: 'string', required: true })
24
- public name!: string;
25
-
26
- @column({ model: 'Store', name: 'store_id' })
27
- public store!: Store | number;
28
- }
29
-
30
- const repos = initialize({ models: [Product, Store], pool });
31
- const Product = repos.Product as Repository<Product>;
32
- ```
33
-
34
- **After (v16):**
35
-
36
- ```typescript
37
- import { defineTable as table, serial, text, belongsTo, initialize } from 'bigal';
38
- import type { Repository } from 'bigal';
39
-
40
- const Product = table('products', {
41
- id: serial().primaryKey(),
42
- name: text().notNull(),
43
- store: belongsTo('Store'),
44
- });
45
-
46
- const { Product } = initialize({ pool, models: { Product, Store } });
47
- // Product is Repository<typeof Product> -- fully typed, no assertion needed
48
- ```
49
-
50
- ## Migration Steps
51
-
52
- ### Step 1: Convert model files
53
-
54
- For each model class, create a `table()` call:
55
-
56
- | v15 Decorator | v16 Builder |
57
- | --------------------------------------------------------- | --------------------------------------- |
58
- | `@primaryColumn({ type: 'integer' })` | `serial().primaryKey()` |
59
- | `@column({ type: 'string', required: true })` | `text().notNull()` |
60
- | `@column({ type: 'string' })` | `text()` |
61
- | `@column({ type: 'integer' })` | `integer()` |
62
- | `@column({ type: 'float' })` | `float()` |
63
- | `@column({ type: 'boolean' })` | `boolean()` |
64
- | `@column({ type: 'json' })` | `jsonb()` |
65
- | `@column({ type: 'datetime' })` | `timestamptz()` |
66
- | `@column({ type: 'date' })` | `date()` |
67
- | `@column({ type: 'uuid' })` | `uuid()` |
68
- | `@column({ type: 'binary' })` | `bytea()` |
69
- | `@column({ type: 'string[]' })` | `textArray()` |
70
- | `@column({ type: 'integer[]' })` | `integerArray()` |
71
- | `@column({ type: 'boolean[]' })` | `booleanArray()` |
72
- | `@createDateColumn()` | `createdAt()` |
73
- | `@updateDateColumn()` | `updatedAt()` |
74
- | `@versionColumn()` | `integer().version()` |
75
- | `@column({ model: 'Store', name: 'store_id' })` | `belongsTo('Store')` |
76
- | `@column({ collection: 'Product', via: 'store' })` | `hasMany('Product').via('store')` |
77
- | `@column({ collection: 'Cat', through: 'PC', via: 'p' })` | `hasMany('Cat').through('PC').via('p')` |
78
-
79
- Column names auto-derive from property keys via snakeCase. Only specify
80
- a name when the convention doesn't match:
81
-
82
- ```typescript
83
- aliases: textArray({ name: 'alias_names' }).default([]),
84
- ```
85
-
86
- ### Step 2: Replace class inheritance with spread
87
-
88
- Only extract a shared base for columns that genuinely appear in every model (typically
89
- `id` + timestamps). Domain-specific columns like `organization: belongsTo(...)` should
90
- stay inline in each model, even if repeated. A bit of repetition is better than a hierarchy
91
- of base objects that becomes hard to maintain.
92
-
93
- ```typescript
94
- // Before
95
- abstract class ModelBase extends Entity {
96
- @primaryColumn({ type: 'integer' }) public id!: number;
97
- @createDateColumn() public createdAt!: Date;
98
- @updateDateColumn() public updatedAt!: Date;
99
- }
100
- class Product extends ModelBase { ... }
101
-
102
- // After - one shared base for universal columns
103
- const modelBase = {
104
- id: serial().primaryKey(),
105
- createdAt: createdAt(),
106
- updatedAt: updatedAt(),
107
- };
108
-
109
- const Product = table('products', {
110
- ...modelBase,
111
- name: text().notNull(),
112
- organization: belongsTo<string>('Organization'),
113
- });
114
- ```
115
-
116
- **Avoid** creating a hierarchy of base objects like `orgBase`, `accountingBase`, etc.
117
- Each model should declare its own relationships and domain columns inline. Use
118
- `QueryResult<typeof Product>` for the row type instead of creating separate base row types.
119
-
120
- ### Step 3: Convert hooks
121
-
122
- Move static `beforeCreate`/`beforeUpdate` methods to table options.
123
- v16 supports all 7 lifecycle hooks:
124
-
125
- ```typescript
126
- // Before
127
- class Product extends Entity {
128
- static override beforeCreate(values) {
129
- return { ...values, slug: slugify(values.name) };
130
- }
131
- }
132
-
133
- // After
134
- const Product = table(
135
- 'products',
136
- { ...columns },
137
- {
138
- hooks: {
139
- beforeCreate(values) {
140
- return { ...values, slug: slugify(values.name) };
141
- },
142
- },
143
- },
144
- );
145
- ```
146
-
147
- Available hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`,
148
- `afterUpdate`, `beforeDestroy`, `afterDestroy`, `afterFind`.
149
-
150
- ### Step 4: Convert initialization
151
-
152
- ```typescript
153
- // Before
154
- const repos = initialize({ models: [Product, Store], pool });
155
- const Product = repos.Product as Repository<Product>;
156
-
157
- // After -- object style (typed destructuring, recommended)
158
- const { Product, Store } = initialize({
159
- pool,
160
- models: { Product, Store },
161
- });
162
-
163
- // After -- array style (use getRepository)
164
- const bigal = initialize({ pool, models: [Product, Store] });
165
- const Product = bigal.getRepository(Product);
166
- ```
167
-
168
- ### Step 5: Remove old imports and config
169
-
170
- - Remove `extends Entity` from all classes
171
- - Remove `import { Entity, column, primaryColumn, ... } from 'bigal'`
172
- - Remove `experimentalDecorators: true` from tsconfig.json
173
- - Remove `useDefineForClassFields: false` from tsconfig.json
174
- - Remove `NotEntity<T>` wrappers - no longer needed
175
- - Remove redundant `{ name: '...' }` options where the column name matches the auto-derived
176
- snakeCase (e.g., `priceCents: integer({ name: 'price_cents' })` should be just
177
- `priceCents: integer()`)
178
-
179
- ### Step 6: Update type references
180
-
181
- ```typescript
182
- // Before (v15)
183
- let product: Product;
184
- let params: CreateUpdateParams<Product>;
185
- let result: QueryResult<Product>;
186
-
187
- // After (v16) - use typeof Model everywhere
188
- import type { QueryResult, CreateUpdateParams, InferInsert, Repository } from 'bigal';
189
-
190
- let product: QueryResult<typeof Product>;
191
- let params: CreateUpdateParams<QueryResult<typeof Product>>;
192
- let insert: InferInsert<(typeof Product)['schema']>;
193
-
194
- // For repository type annotations:
195
- function getProducts(repo: Repository<typeof Product>) {
196
- /* ... */
197
- }
198
- ```
199
-
200
- Use `typeof Product` as the single source of truth for all type references:
201
-
202
- - `QueryResult<typeof Product>` - row type for query results (narrows FKs, excludes hasMany)
203
- - `CreateUpdateParams<QueryResult<typeof Product>>` - partial type for create/update
204
- - `Repository<typeof Product>` - typed repository
205
- - `InferInsert<(typeof Product)['schema']>` - insert params with required/optional awareness
206
-
207
- ## Removed Exports
208
-
209
- These v15 exports no longer exist:
210
-
211
- - `Entity`, `EntityStatic`, `NotEntity`, `NotEntityBrand`
212
- - `column`, `primaryColumn`, `createDateColumn`, `updateDateColumn`,
213
- `versionColumn` (decorators)
214
- - `table` decorator (replaced by `table` function, exported as
215
- `defineTable`)
216
- - `getMetadataStorage`, `MetadataStorage`
217
-
218
- ## New Features in v16
219
-
220
- - `view()` - define readonly models backed by PostgreSQL views
221
- - `toSQL()` - inspect generated SQL without executing on any operation
222
- - `onQuery` - observability callback on `initialize()`
223
- - Global filters - auto-applied where clauses (soft delete,
224
- multi-tenancy)
225
- - All 7 lifecycle hooks - `beforeCreate`, `afterCreate`,
226
- `beforeUpdate`, `afterUpdate`, `beforeDestroy`, `afterDestroy`,
227
- `afterFind`
228
- - String references - `belongsTo('Store')` with model name strings only
229
- - Auto-derived names - column names from property keys, model names
230
- from table names
231
- - pgvector - `vector({ dimensions })` with nearest-neighbor sort and
232
- distance filtering
233
- - `Repository<typeof Product>` - type alias for typed repository
234
- annotations
235
-
236
- ## Guidelines
237
-
238
- - Run the codemod first for mechanical conversions:
239
- `npx tsx scripts/migrate-v16.ts 'src/models/**/*.ts'`
240
- - Convert one model at a time and verify tests pass
241
- - Instance methods on Entity classes should become `afterFind` hooks or
242
- standalone functions
243
- - The query API (`find`, `findOne`, `create`, `update`, `destroy`,
244
- `populate`, `where`, `sort`) is unchanged