@cleverbrush/knex-schema 3.1.0 → 4.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/README.md +156 -1
- package/dist/SchemaQueryBuilder.d.ts +457 -7
- package/dist/chunk-V4TH6K42.js +2 -0
- package/dist/chunk-V4TH6K42.js.map +1 -0
- package/dist/columns.d.ts +73 -4
- package/dist/ddl.d.ts +66 -0
- package/dist/entity.d.ts +264 -0
- package/dist/extension.d.ts +1176 -1
- package/dist/extension.js +2 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/migration.d.ts +160 -0
- package/dist/raw.d.ts +35 -0
- package/dist/snapshot.d.ts +39 -0
- package/dist/types.d.ts +269 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -214,6 +214,161 @@ const rows = await query(db, UserSchema)
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
+
## Scopes
|
|
218
|
+
|
|
219
|
+
Define **reusable WHERE/ORDER/LIMIT conditions** on the schema. A default scope is applied
|
|
220
|
+
automatically unless bypassed with `.unscoped()`.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const PostSchema = object({
|
|
224
|
+
id: number(),
|
|
225
|
+
title: string(),
|
|
226
|
+
status: string(),
|
|
227
|
+
isActive: boolean().hasColumnName('is_active'),
|
|
228
|
+
})
|
|
229
|
+
.hasTableName('posts')
|
|
230
|
+
.scope('published', q => q.where(t => t.status, 'published'))
|
|
231
|
+
.scope('recent', q => q.orderBy(t => t.id, 'desc').limit(10))
|
|
232
|
+
.defaultScope( q => q.where(t => t.isActive, true));
|
|
233
|
+
|
|
234
|
+
// Apply named scopes
|
|
235
|
+
const posts = await query(db, PostSchema)
|
|
236
|
+
.scoped('published')
|
|
237
|
+
.scoped('recent');
|
|
238
|
+
|
|
239
|
+
// Bypass default scope (also skips soft-delete filter if present)
|
|
240
|
+
const all = await query(db, PostSchema).unscoped();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`scoped()` is statically typed: TypeScript only allows registered scope names.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Projections
|
|
248
|
+
|
|
249
|
+
Define **named column subsets** on the schema with `.projection(name, columns)`. At query time,
|
|
250
|
+
`.projected(name)` restricts the `SELECT` clause **and** narrows the TypeScript result type to
|
|
251
|
+
`Pick<Row, Keys>` — accessing columns outside the projection is a compile-time error.
|
|
252
|
+
|
|
253
|
+
### String-tuple form
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const PostSchema = object({
|
|
257
|
+
id: number().primaryKey(),
|
|
258
|
+
title: string(),
|
|
259
|
+
body: string(),
|
|
260
|
+
status: string(),
|
|
261
|
+
})
|
|
262
|
+
.hasTableName('posts')
|
|
263
|
+
.projection('summary', ['id', 'title'] as const)
|
|
264
|
+
.projection('withStatus', ['id', 'title', 'status'] as const);
|
|
265
|
+
|
|
266
|
+
const rows = await query(db, PostSchema)
|
|
267
|
+
.scoped('published')
|
|
268
|
+
.projected('summary');
|
|
269
|
+
|
|
270
|
+
// rows: Array<Pick<Post, 'id' | 'title'>>
|
|
271
|
+
// rows[0].body // ← TypeScript error: 'body' not in projection ✓
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Accessor form
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
.projection('withStatus', t => [t.id, t.title, t.status])
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The accessor receives the schema's property-descriptor tree; each element resolves to the
|
|
281
|
+
property name at runtime. This form is more refactor-safe but does not provide the compile-time
|
|
282
|
+
`Pick<>` narrowing that the tuple form offers.
|
|
283
|
+
|
|
284
|
+
### Conflict rules
|
|
285
|
+
|
|
286
|
+
`.projected()` cannot be combined with `.select()`, `.distinct()`, or any aggregate
|
|
287
|
+
(`.count()`, `.min()`, etc.) on the same query. Attempting to do so throws at runtime.
|
|
288
|
+
|
|
289
|
+
### Column-name mapping
|
|
290
|
+
|
|
291
|
+
`hasColumnName()` is respected: if `isActive` is mapped to `is_active`, the generated SQL
|
|
292
|
+
uses `is_active` automatically.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## ORM Extensions
|
|
297
|
+
|
|
298
|
+
In addition to query building, this package provides the schema-level primitives that
|
|
299
|
+
[`@cleverbrush/orm`](https://www.npmjs.com/package/@cleverbrush/orm) and
|
|
300
|
+
[`@cleverbrush/orm-cli`](https://www.npmjs.com/package/@cleverbrush/orm-cli) build on top of:
|
|
301
|
+
|
|
302
|
+
### `defineEntity(schema)` — relations
|
|
303
|
+
|
|
304
|
+
Wrap a schema to declare typed `belongsTo` / `hasOne` / `hasMany` / `belongsToMany`
|
|
305
|
+
relations that downstream packages use for eager-loading joins and ORM navigation properties.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { defineEntity, object, number, string } from '@cleverbrush/knex-schema';
|
|
309
|
+
|
|
310
|
+
const UserSchema = object({
|
|
311
|
+
id: number().primaryKey(),
|
|
312
|
+
email: string(),
|
|
313
|
+
}).hasTableName('users');
|
|
314
|
+
|
|
315
|
+
const PostSchema = object({
|
|
316
|
+
id: number().primaryKey(),
|
|
317
|
+
title: string(),
|
|
318
|
+
authorId: number().hasColumnName('author_id'),
|
|
319
|
+
author: UserSchema.optional(),
|
|
320
|
+
}).hasTableName('posts');
|
|
321
|
+
|
|
322
|
+
export const PostEntity = defineEntity(PostSchema)
|
|
323
|
+
.belongsTo(t => t.author, l => l.authorId, r => r.id);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The returned `Entity` carries the relation map in its type, so downstream `query(db, entity)`
|
|
327
|
+
calls (and `@cleverbrush/orm`'s `DbSet.include()`) get full inference.
|
|
328
|
+
|
|
329
|
+
### Polymorphism (STI / CTI)
|
|
330
|
+
|
|
331
|
+
Mark a schema as polymorphic to support single-table or class-table inheritance — variants
|
|
332
|
+
are discoverable via `getVariants()` / `getPolymorphicVariantSchemas()`:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { POLYMORPHIC_TYPE_BRAND } from '@cleverbrush/knex-schema';
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
See the `@cleverbrush/orm` docs for the full inheritance API (`.ofVariant()` etc.).
|
|
339
|
+
|
|
340
|
+
### Migration generation (snapshot-based)
|
|
341
|
+
|
|
342
|
+
| Function | Purpose |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `entitiesToSnapshot(entities)` | Materialise entity definitions into a JSON-serialisable schema snapshot |
|
|
345
|
+
| `loadSnapshot(path)` / `writeSnapshot(path, snap)` | Read/write the committed snapshot file |
|
|
346
|
+
| `generateMigrationsForContext(entities, prevSnapshot)` | Diff entities against the snapshot and emit a TS migration source plus the next snapshot |
|
|
347
|
+
| `generateMigration(snapshotA, snapshotB)` | Lower-level snapshot-vs-snapshot diff |
|
|
348
|
+
| `diffSchema(schema, dbState)` / `applyDiff(knex, diff, table)` | Live-database diff/apply (used by `cb-orm db push`) |
|
|
349
|
+
| `introspectDatabase(knex, table)` / `tableExistsInDb(knex, table)` | Database introspection helpers |
|
|
350
|
+
| `generateCreateTable(schema)` / `generateCreatePolymorphicTables(schema)` | Knex-statement builders for fresh `CREATE TABLE` |
|
|
351
|
+
|
|
352
|
+
Most users invoke these indirectly through the [`cb-orm`](https://www.npmjs.com/package/@cleverbrush/orm-cli)
|
|
353
|
+
CLI (`cb-orm migrate generate`, `cb-orm db push`).
|
|
354
|
+
|
|
355
|
+
### Row-version optimistic concurrency
|
|
356
|
+
|
|
357
|
+
Mark a column as a row version with `.rowVersion()` to opt-in to optimistic concurrency
|
|
358
|
+
checks in `@cleverbrush/orm`'s change tracker:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const TodoSchema = object({
|
|
362
|
+
id: number().primaryKey(),
|
|
363
|
+
title: string(),
|
|
364
|
+
rowVersion: number().rowVersion(),
|
|
365
|
+
}).hasTableName('todos');
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
`getRowVersionColumn(schema)` returns the marked column at runtime.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
217
372
|
## Column Reference Patterns
|
|
218
373
|
|
|
219
374
|
Both styles are equivalent and resolve to the same SQL column:
|
|
@@ -246,7 +401,7 @@ Optionally pass a `baseQuery` (e.g. a scoped `knex('users').where('deleted_at',
|
|
|
246
401
|
| Ordering | `.orderBy(col, dir?)`, `.orderByRaw(sql)` |
|
|
247
402
|
| Grouping | `.groupBy(...cols)`, `.groupByRaw(sql)`, `.having(col, op, val)`, `.havingRaw(sql)` |
|
|
248
403
|
| Pagination | `.limit(n)`, `.offset(n)` |
|
|
249
|
-
| Selection | `.select(...cols)`, `.distinct(...cols)` |
|
|
404
|
+
| Selection | `.select(...cols)`, `.distinct(...cols)`, `.projected(name)` |
|
|
250
405
|
| Aggregates | `.count(col?)`, `.countDistinct(col?)`, `.min(col)`, `.max(col)`, `.sum(col)`, `.avg(col)` |
|
|
251
406
|
| Writes | `.insert(data)`, `.insertMany(data[])`, `.update(data)`, `.delete()` |
|
|
252
407
|
| Execution | `.execute()`, `.first()`, `await builder` (thenable) |
|