@cleverbrush/knex-schema 3.0.1 → 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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { ObjectSchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { Entity } from './entity.js';
|
|
4
|
+
import type { DatabaseTableState, MigrationDiff, SchemaSnapshot } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Introspect a PostgreSQL table and return its current state.
|
|
7
|
+
*
|
|
8
|
+
* Queries `information_schema.columns`, `pg_indexes`, `pg_constraint`, and
|
|
9
|
+
* referential constraint metadata to build a complete picture of the table's
|
|
10
|
+
* columns, indexes, foreign keys, and check constraints.
|
|
11
|
+
*
|
|
12
|
+
* @param knex - A configured Knex instance connected to a PostgreSQL database.
|
|
13
|
+
* @param tableName - The table name to introspect.
|
|
14
|
+
* @returns The current database state for the table.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const dbState = await introspectDatabase(knex, 'users');
|
|
19
|
+
* console.log(dbState.columns); // { id: { type: 'integer', ... }, ... }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function introspectDatabase(knex: Knex, tableName: string): Promise<DatabaseTableState>;
|
|
23
|
+
/**
|
|
24
|
+
* Derive a {@link DatabaseTableState} from a code-first `ObjectSchemaBuilder`
|
|
25
|
+
* without connecting to the database.
|
|
26
|
+
*
|
|
27
|
+
* The result mirrors what {@link introspectDatabase} would return after the
|
|
28
|
+
* schema has been fully applied, so it can be fed directly into
|
|
29
|
+
* {@link diffSchema} as the `dbState` argument. This is the foundation of
|
|
30
|
+
* snapshot-based migration generation.
|
|
31
|
+
*
|
|
32
|
+
* @param schema - An `ObjectSchemaBuilder` with DDL extensions.
|
|
33
|
+
* @returns A {@link DatabaseTableState} representing the schema's ideal DB shape.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const state = entitySchemaToTableState(UserSchema);
|
|
38
|
+
* // state.columns, state.foreignKeys, state.indexes …
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function entitySchemaToTableState(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): DatabaseTableState;
|
|
42
|
+
/**
|
|
43
|
+
* Compare a code-first schema model against a live database table state and
|
|
44
|
+
* produce a diff describing the changes needed to bring the database in sync.
|
|
45
|
+
*
|
|
46
|
+
* @param schema - The code-first `ObjectSchemaBuilder`.
|
|
47
|
+
* @param dbState - The current database state from {@link introspectDatabase}.
|
|
48
|
+
* @returns A {@link MigrationDiff} describing columns, indexes, and foreign
|
|
49
|
+
* keys to add, drop, or alter.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const dbState = await introspectDatabase(knex, 'users');
|
|
54
|
+
* const diff = diffSchema(UserSchema, dbState);
|
|
55
|
+
* if (diff.addColumns.length > 0) {
|
|
56
|
+
* const migration = generateMigration(diff, 'users');
|
|
57
|
+
* console.log(migration.up);
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function diffSchema(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>, dbState: DatabaseTableState): MigrationDiff;
|
|
62
|
+
/**
|
|
63
|
+
* Generate migration `up` and `down` code strings from a {@link MigrationDiff}.
|
|
64
|
+
*
|
|
65
|
+
* The generated code is valid TypeScript that uses Knex's schema builder API.
|
|
66
|
+
* Write it to a migration file and run via Knex's migration system.
|
|
67
|
+
*
|
|
68
|
+
* @param diff - The schema diff from {@link diffSchema}.
|
|
69
|
+
* @param tableName - The table name to alter.
|
|
70
|
+
* @returns An object with `up` and `down` migration code strings.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const diff = diffSchema(UserSchema, dbState);
|
|
75
|
+
* const migration = generateMigration(diff, 'users');
|
|
76
|
+
* fs.writeFileSync('migration.ts', migration.full);
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateMigration(diff: MigrationDiff, tableName: string): {
|
|
80
|
+
up: string;
|
|
81
|
+
down: string;
|
|
82
|
+
full: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Check whether a table exists in the connected PostgreSQL database.
|
|
86
|
+
*
|
|
87
|
+
* @param knex - A configured Knex instance (or transaction).
|
|
88
|
+
* @param tableName - The table name to check.
|
|
89
|
+
* @returns `true` when the table exists.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const exists = await tableExistsInDb(knex, 'users');
|
|
94
|
+
* if (!exists) await generateCreateTable(UserSchema)(knex);
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function tableExistsInDb(knex: Knex, tableName: string): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Return `true` when a {@link MigrationDiff} has no operations — i.e. the
|
|
100
|
+
* database table is already in sync with the schema.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isDiffEmpty(diff: MigrationDiff): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Execute a {@link MigrationDiff} directly against a live database without
|
|
105
|
+
* writing a migration file. Intended for `db push` (dev-only schema sync).
|
|
106
|
+
*
|
|
107
|
+
* Foreign-key constraint drops are executed as raw `ALTER TABLE … DROP
|
|
108
|
+
* CONSTRAINT` statements before the main `alterTable` call.
|
|
109
|
+
*
|
|
110
|
+
* @param knex - A configured Knex instance or transaction.
|
|
111
|
+
* @param diff - The diff from {@link diffSchema}.
|
|
112
|
+
* @param tableName - The table to alter.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const dbState = await introspectDatabase(knex, 'users');
|
|
117
|
+
* const diff = diffSchema(UserSchema, dbState);
|
|
118
|
+
* if (!isDiffEmpty(diff)) await applyDiff(knex, diff, 'users');
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare function applyDiff(knex: Knex, diff: MigrationDiff, tableName: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Generate a single combined migration (up + down) for a set of entities by
|
|
124
|
+
* diffing the current code-first schemas against a **serialized snapshot**
|
|
125
|
+
* stored in the repository — no live database connection required.
|
|
126
|
+
*
|
|
127
|
+
* For each entity's table the function:
|
|
128
|
+
* - **New tables** (in entities but not in `prevSnapshot`): emits `CREATE TABLE`.
|
|
129
|
+
* - **Existing tables** (in both): diffs entity schema against the snapshot
|
|
130
|
+
* state via {@link diffSchema} and emits `ALTER TABLE` when changes exist.
|
|
131
|
+
* - **Dropped tables** (in `prevSnapshot` but not in entities): emits
|
|
132
|
+
* `DROP TABLE` with a best-effort `CREATE TABLE` in the `down` direction.
|
|
133
|
+
* - Orders tables topologically by FK dependencies so parent tables are
|
|
134
|
+
* created before child tables in `up` (and dropped after in `down`).
|
|
135
|
+
*
|
|
136
|
+
* @param entities - The entities from your {@link EntityMap}.
|
|
137
|
+
* @param prevSnapshot - The last committed {@link SchemaSnapshot} (empty on first run).
|
|
138
|
+
* @returns `{ up, down, full, isEmpty, nextSnapshot }` where `nextSnapshot`
|
|
139
|
+
* should be written to disk after the migration file is created.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const prev = loadSnapshot('./migrations/snapshot.json');
|
|
144
|
+
* const result = generateMigrationsForContext(
|
|
145
|
+
* Object.values({ todos: TodoEntity, users: UserEntity }),
|
|
146
|
+
* prev
|
|
147
|
+
* );
|
|
148
|
+
* if (!result.isEmpty) {
|
|
149
|
+
* fs.writeFileSync('migrations/20260423000000_init.ts', result.full);
|
|
150
|
+
* writeSnapshot('./migrations/snapshot.json', result.nextSnapshot);
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export declare function generateMigrationsForContext(entities: Entity<any, any>[], prevSnapshot: SchemaSnapshot): {
|
|
155
|
+
up: string;
|
|
156
|
+
down: string;
|
|
157
|
+
full: string;
|
|
158
|
+
isEmpty: boolean;
|
|
159
|
+
nextSnapshot: SchemaSnapshot;
|
|
160
|
+
};
|
package/dist/raw.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Execute a raw SQL query or Knex query builder and map the result rows
|
|
5
|
+
* through the schema's column→property name mapping.
|
|
6
|
+
*
|
|
7
|
+
* This is the escape hatch for complex queries that can't be expressed with
|
|
8
|
+
* the typed `SchemaQueryBuilder` API. The schema is used only for result
|
|
9
|
+
* mapping — column names in the result are converted back to property names.
|
|
10
|
+
* Extra columns (not in the schema) are passed through unchanged.
|
|
11
|
+
*
|
|
12
|
+
* @param knex - A configured Knex instance.
|
|
13
|
+
* @param schema - The `ObjectSchemaBuilder` for result mapping.
|
|
14
|
+
* @param queryOrSql - A raw SQL string or a `Knex.QueryBuilder`.
|
|
15
|
+
* @param bindings - Optional bindings for parameterised SQL queries.
|
|
16
|
+
* @returns Mapped result rows.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Raw SQL with schema result mapping
|
|
21
|
+
* const results = await rawQuery(knex, PostSchema, `
|
|
22
|
+
* SELECT p.*, COUNT(c.id) AS comment_count
|
|
23
|
+
* FROM posts p
|
|
24
|
+
* LEFT JOIN comments c ON c.post_id = p.id
|
|
25
|
+
* GROUP BY p.id
|
|
26
|
+
* ORDER BY comment_count DESC
|
|
27
|
+
* LIMIT ?
|
|
28
|
+
* `, [10]);
|
|
29
|
+
*
|
|
30
|
+
* // Knex query builder as the source
|
|
31
|
+
* const subQuery = knex('posts').select('author_id', knex.raw('COUNT(*) as post_count')).groupBy('author_id');
|
|
32
|
+
* const results = await rawQuery(knex, UserSchema, subQuery);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function rawQuery<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(knex: Knex, schema: TSchema, queryOrSql: string | Knex.QueryBuilder, bindings?: any[]): Promise<(InferType<TSchema> & Record<string, any>)[]>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Entity } from './entity.js';
|
|
2
|
+
import type { SchemaSnapshot } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a {@link SchemaSnapshot} from a set of entities without a live DB.
|
|
5
|
+
*
|
|
6
|
+
* Handles polymorphic (CTI) entities by including each variant table.
|
|
7
|
+
* Deduplicates tables so STI variants sharing a base table are only included
|
|
8
|
+
* once.
|
|
9
|
+
*
|
|
10
|
+
* @param entities - The entities from your `EntityMap`.
|
|
11
|
+
* @returns A snapshot reflecting the current code-first schema state.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const snapshot = entitiesToSnapshot(Object.values(entityMap));
|
|
16
|
+
* writeSnapshot('./migrations/snapshot.json', snapshot);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function entitiesToSnapshot(entities: Entity<any, any>[]): SchemaSnapshot;
|
|
20
|
+
/**
|
|
21
|
+
* Load a {@link SchemaSnapshot} from disk.
|
|
22
|
+
*
|
|
23
|
+
* Returns an empty snapshot (no tables) when the file does not exist — this
|
|
24
|
+
* is the "first run" case, which causes `migrate generate` to emit a single
|
|
25
|
+
* migration containing `CREATE TABLE` for every entity.
|
|
26
|
+
*
|
|
27
|
+
* @param snapshotPath - Absolute or cwd-relative path to `snapshot.json`.
|
|
28
|
+
* @returns The parsed snapshot, or `{ version: 1, tables: {} }` if missing or
|
|
29
|
+
* unreadable.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadSnapshot(snapshotPath: string): SchemaSnapshot;
|
|
32
|
+
/**
|
|
33
|
+
* Write a {@link SchemaSnapshot} to disk atomically (temp-file + rename) with
|
|
34
|
+
* deterministic key ordering so diffs in version control are minimal.
|
|
35
|
+
*
|
|
36
|
+
* @param snapshotPath - Absolute or cwd-relative path to write `snapshot.json`.
|
|
37
|
+
* @param snapshot - The snapshot to serialize.
|
|
38
|
+
*/
|
|
39
|
+
export declare function writeSnapshot(snapshotPath: string, snapshot: SchemaSnapshot): void;
|
package/dist/types.d.ts
CHANGED
|
@@ -89,4 +89,273 @@ export type ValidatedSpec = ({
|
|
|
89
89
|
type: 'many';
|
|
90
90
|
} & ValidatedJoinManySpec);
|
|
91
91
|
export type InsertType<T extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = InferType<ReturnType<T['makeAllPropsOptional']>>;
|
|
92
|
+
import type { COMPOSITE_PRIMARY_KEY_BRAND, PRIMARY_KEY_BRAND } from './extension.js';
|
|
93
|
+
/**
|
|
94
|
+
* Detect whether a property schema is branded as a primary key (i.e. its
|
|
95
|
+
* schema was created with `.primaryKey()`).
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
type IsPkBranded<TPropSchema> = TPropSchema extends {
|
|
100
|
+
readonly [PRIMARY_KEY_BRAND]?: true;
|
|
101
|
+
} ? true : false;
|
|
102
|
+
/**
|
|
103
|
+
* Extract the primary-key column descriptor for a schema.
|
|
104
|
+
*
|
|
105
|
+
* Returns:
|
|
106
|
+
* - the literal property-key string for a single-column primary key
|
|
107
|
+
* (e.g. `'id'`),
|
|
108
|
+
* - a tuple of property-key strings for a composite primary key
|
|
109
|
+
* (e.g. `['userId', 'roleId']`),
|
|
110
|
+
* - or `never` if no primary key is declared.
|
|
111
|
+
*
|
|
112
|
+
* Composite primary keys are detected via the `COMPOSITE_PRIMARY_KEY_BRAND`
|
|
113
|
+
* placed on the object schema by `.hasPrimaryKey([...] as const)`. Use
|
|
114
|
+
* `as const` on the column tuple to preserve ordering at the type level.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
export type PrimaryKeyOf<S extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = S extends {
|
|
119
|
+
readonly [COMPOSITE_PRIMARY_KEY_BRAND]?: infer TCols;
|
|
120
|
+
} ? TCols extends readonly string[] ? TCols : never : {
|
|
121
|
+
[K in keyof SchemaPropsForPk<S> & string]: IsPkBranded<SchemaPropsForPk<S>[K]> extends true ? K : never;
|
|
122
|
+
}[keyof SchemaPropsForPk<S> & string];
|
|
123
|
+
/**
|
|
124
|
+
* Extract the schema's property record for primary-key inference. Mirrors
|
|
125
|
+
* `SchemaProps` from `entity.ts` but lives here to avoid a circular import.
|
|
126
|
+
*
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
type SchemaPropsForPk<T> = T extends ObjectSchemaBuilder<infer P, any, any, any, any, any, any> ? P : never;
|
|
130
|
+
/**
|
|
131
|
+
* The runtime value type of a schema's primary key.
|
|
132
|
+
*
|
|
133
|
+
* - For a single-column PK, the inferred type of that property
|
|
134
|
+
* (e.g. `number`).
|
|
135
|
+
* - For a composite PK, a tuple of inferred property types in declared
|
|
136
|
+
* order (e.g. `[number, number]`).
|
|
137
|
+
* - `never` if no primary key is declared.
|
|
138
|
+
*
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
export type PrimaryKeyValueOf<S extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = PrimaryKeyOf<S> extends readonly (infer _Item extends string)[] ? PrimaryKeyOf<S> extends readonly string[] ? PkTupleValue<S, PrimaryKeyOf<S>> : never : PrimaryKeyOf<S> extends string ? InferType<S>[PrimaryKeyOf<S> & keyof InferType<S>] : never;
|
|
142
|
+
/**
|
|
143
|
+
* Map a tuple of PK property-key names to their inferred value types.
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
type PkTupleValue<S extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TKeys extends readonly string[]> = {
|
|
148
|
+
[I in keyof TKeys]: TKeys[I] extends keyof InferType<S> ? InferType<S>[TKeys[I]] : unknown;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Extract the property schema captured inside a `PropertyDescriptor`.
|
|
152
|
+
*
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
type DescriptorPropertySchema<T> = T extends PropertyDescriptor<any, infer S, any, any> ? S : never;
|
|
156
|
+
/**
|
|
157
|
+
* Result row type produced by a {@link SelectProjection} selector.
|
|
158
|
+
*
|
|
159
|
+
* Each entry's value is the {@link InferType} of the property schema the
|
|
160
|
+
* descriptor points at, so `.select(t => ({ id: t.id, n: t.title }))`
|
|
161
|
+
* yields `{ id: number; n: string }`.
|
|
162
|
+
*
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
export type SelectProjection<R extends Record<string, unknown>> = {
|
|
166
|
+
[K in keyof R]: InferType<DescriptorPropertySchema<R[K]>>;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Callback shape accepted by the projection overload of `select`. The
|
|
170
|
+
* callback receives the schema's property-descriptor tree and returns an
|
|
171
|
+
* `{ alias: descriptor }` record.
|
|
172
|
+
*
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
export type SelectSelector<T extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = (tree: PropertyDescriptorTree<SchemaBase<T>, SchemaBase<T>>) => Record<string, PropertyDescriptor<any, any, any>>;
|
|
176
|
+
/** Result of offset-based pagination via {@link SchemaQueryBuilder.paginate}. */
|
|
177
|
+
export interface PaginationResult<T> {
|
|
178
|
+
/** The rows for the current page. */
|
|
179
|
+
data: T[];
|
|
180
|
+
/** Total number of matching rows across all pages. */
|
|
181
|
+
total: number;
|
|
182
|
+
/** Current page number (1-based). */
|
|
183
|
+
page: number;
|
|
184
|
+
/** Number of rows per page. */
|
|
185
|
+
pageSize: number;
|
|
186
|
+
/** Total number of pages. */
|
|
187
|
+
totalPages: number;
|
|
188
|
+
/** Whether a next page exists. */
|
|
189
|
+
hasNextPage: boolean;
|
|
190
|
+
/** Whether a previous page exists. */
|
|
191
|
+
hasPreviousPage: boolean;
|
|
192
|
+
}
|
|
193
|
+
/** Result of cursor-based pagination via {@link SchemaQueryBuilder.paginateAfter}. */
|
|
194
|
+
export interface CursorPaginationResult<T> {
|
|
195
|
+
/** The rows for the current page. */
|
|
196
|
+
data: T[];
|
|
197
|
+
/** Cursor value for the next page, or `null` if no more rows. */
|
|
198
|
+
nextCursor: string | null;
|
|
199
|
+
/** Whether more rows exist after this page. */
|
|
200
|
+
hasMore: boolean;
|
|
201
|
+
}
|
|
202
|
+
/** Storage strategy for a polymorphic variant. */
|
|
203
|
+
export type VariantStorageType = 'cti' | 'sti';
|
|
204
|
+
/** @internal Resolved form of a variant relation stored on {@link ResolvedVariantSpec}. */
|
|
205
|
+
export interface ResolvedVariantRelationSpec {
|
|
206
|
+
name: string;
|
|
207
|
+
type: 'hasMany' | 'hasOne' | 'belongsTo' | 'belongsToMany';
|
|
208
|
+
schema: any;
|
|
209
|
+
/** Resolved FK *column* name on the variant or foreign table. */
|
|
210
|
+
foreignKey?: string;
|
|
211
|
+
through?: {
|
|
212
|
+
table: string;
|
|
213
|
+
localKey: string;
|
|
214
|
+
foreignKey: string;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** @internal Resolved, normalised variant spec stored in schema extensions. */
|
|
218
|
+
export interface ResolvedVariantSpec {
|
|
219
|
+
storage: VariantStorageType;
|
|
220
|
+
schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>;
|
|
221
|
+
/** CTI: FK column name on the variant table. */
|
|
222
|
+
foreignKey?: string;
|
|
223
|
+
/** CTI: variant table name (from `schema.hasTableName()`). */
|
|
224
|
+
tableName?: string;
|
|
225
|
+
allowOrphan: boolean;
|
|
226
|
+
enforceCheck: boolean;
|
|
227
|
+
/** Variant-scoped relations (resolved), populated by `.withVariants()`. */
|
|
228
|
+
relations: ResolvedVariantRelationSpec[];
|
|
229
|
+
}
|
|
230
|
+
/** @internal Full variant config stored in the schema extension `'variants'`. */
|
|
231
|
+
export interface ResolvedVariantConfig {
|
|
232
|
+
/** Property key on the base schema that is the discriminator. */
|
|
233
|
+
discriminatorKey: string;
|
|
234
|
+
/** SQL column name corresponding to `discriminatorKey`. Filled by query builder. */
|
|
235
|
+
discriminatorColumn: string;
|
|
236
|
+
/** Map from discriminator value → resolved variant spec. */
|
|
237
|
+
variants: Record<string, ResolvedVariantSpec>;
|
|
238
|
+
}
|
|
239
|
+
/** @internal Pending filter registered via `.whereVariant()`. */
|
|
240
|
+
export interface VariantWhereFilter {
|
|
241
|
+
/** The discriminator value this filter applies to (e.g. `'image'`). */
|
|
242
|
+
key: string;
|
|
243
|
+
/** SQL column expression on the variant alias (e.g. `__v_image.width`). */
|
|
244
|
+
qualifiedColumn: string;
|
|
245
|
+
/** SQL comparison operator (validated). */
|
|
246
|
+
op: string;
|
|
247
|
+
value: any;
|
|
248
|
+
}
|
|
249
|
+
/** @internal Relation metadata stored via `.hasMany()`, `.belongsTo()`, etc. */
|
|
250
|
+
export interface RelationSpec {
|
|
251
|
+
type: 'hasMany' | 'hasOne' | 'belongsTo' | 'belongsToMany';
|
|
252
|
+
name: string;
|
|
253
|
+
schema: any;
|
|
254
|
+
foreignKey?: any;
|
|
255
|
+
through?: {
|
|
256
|
+
table: string;
|
|
257
|
+
localKey: string;
|
|
258
|
+
foreignKey: string;
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** Column information read from the database. */
|
|
262
|
+
export interface DatabaseColumnInfo {
|
|
263
|
+
name: string;
|
|
264
|
+
type: string;
|
|
265
|
+
nullable: boolean;
|
|
266
|
+
defaultValue: string | null;
|
|
267
|
+
maxLength: number | null;
|
|
268
|
+
numericPrecision: number | null;
|
|
269
|
+
}
|
|
270
|
+
/** Index information read from the database. */
|
|
271
|
+
export interface DatabaseIndexInfo {
|
|
272
|
+
name: string;
|
|
273
|
+
columns: string[];
|
|
274
|
+
unique: boolean;
|
|
275
|
+
definition: string;
|
|
276
|
+
}
|
|
277
|
+
/** Foreign key information read from the database. */
|
|
278
|
+
export interface DatabaseForeignKeyInfo {
|
|
279
|
+
constraintName: string;
|
|
280
|
+
columnName: string;
|
|
281
|
+
foreignTable: string;
|
|
282
|
+
foreignColumn: string;
|
|
283
|
+
deleteRule: string;
|
|
284
|
+
updateRule: string;
|
|
285
|
+
}
|
|
286
|
+
/** Check constraint information read from the database. */
|
|
287
|
+
export interface DatabaseCheckInfo {
|
|
288
|
+
name: string;
|
|
289
|
+
definition: string;
|
|
290
|
+
}
|
|
291
|
+
/** Full database table state from introspection. */
|
|
292
|
+
export interface DatabaseTableState {
|
|
293
|
+
columns: Record<string, DatabaseColumnInfo>;
|
|
294
|
+
indexes: DatabaseIndexInfo[];
|
|
295
|
+
foreignKeys: DatabaseForeignKeyInfo[];
|
|
296
|
+
checks: DatabaseCheckInfo[];
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Serialized snapshot of all entity schemas at a given point in time.
|
|
300
|
+
*
|
|
301
|
+
* Stored in `<migrations.directory>/snapshot.json` and committed to version
|
|
302
|
+
* control. `migrate generate` diffs the current code against this snapshot
|
|
303
|
+
* (instead of a live database) to produce migration files, so no DB
|
|
304
|
+
* connection is required.
|
|
305
|
+
*
|
|
306
|
+
* Each entry in `tables` mirrors the shape that `introspectDatabase` would
|
|
307
|
+
* return — allowing `diffSchema` to be reused unchanged.
|
|
308
|
+
*
|
|
309
|
+
* @public
|
|
310
|
+
*/
|
|
311
|
+
export interface SchemaSnapshot {
|
|
312
|
+
version: 1;
|
|
313
|
+
/** Map of table name → database state derived from entity schemas. */
|
|
314
|
+
tables: Record<string, DatabaseTableState>;
|
|
315
|
+
}
|
|
316
|
+
/** A column to add in a migration. */
|
|
317
|
+
export interface AddColumnDiff {
|
|
318
|
+
name: string;
|
|
319
|
+
type: string;
|
|
320
|
+
nullable: boolean;
|
|
321
|
+
defaultValue?: any;
|
|
322
|
+
references?: {
|
|
323
|
+
table: string;
|
|
324
|
+
column: string;
|
|
325
|
+
};
|
|
326
|
+
onDelete?: string;
|
|
327
|
+
onUpdate?: string;
|
|
328
|
+
}
|
|
329
|
+
/** Changes to apply to an existing column. */
|
|
330
|
+
export interface AlterColumnDiff {
|
|
331
|
+
name: string;
|
|
332
|
+
changes: Record<string, {
|
|
333
|
+
from: any;
|
|
334
|
+
to: any;
|
|
335
|
+
}>;
|
|
336
|
+
}
|
|
337
|
+
/** An index to add in a migration. */
|
|
338
|
+
export interface AddIndexDiff {
|
|
339
|
+
columns: string[];
|
|
340
|
+
name?: string;
|
|
341
|
+
unique?: boolean;
|
|
342
|
+
}
|
|
343
|
+
/** A foreign key to add in a migration. */
|
|
344
|
+
export interface AddForeignKeyDiff {
|
|
345
|
+
column: string;
|
|
346
|
+
foreignTable: string;
|
|
347
|
+
foreignColumn: string;
|
|
348
|
+
onDelete?: string;
|
|
349
|
+
onUpdate?: string;
|
|
350
|
+
}
|
|
351
|
+
/** Schema diff result between the code-first model and the live database. */
|
|
352
|
+
export interface MigrationDiff {
|
|
353
|
+
addColumns: AddColumnDiff[];
|
|
354
|
+
dropColumns: string[];
|
|
355
|
+
alterColumns: AlterColumnDiff[];
|
|
356
|
+
addIndexes: AddIndexDiff[];
|
|
357
|
+
dropIndexes: string[];
|
|
358
|
+
addForeignKeys: AddForeignKeyDiff[];
|
|
359
|
+
dropForeignKeys: string[];
|
|
360
|
+
}
|
|
92
361
|
export {};
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"email": "andrew_zol@cleverbrush.com"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cleverbrush/schema": "^
|
|
8
|
+
"@cleverbrush/schema": "^4.0.0"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
11
|
"knex": ">=3.1.0"
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
".": {
|
|
33
33
|
"types": "./dist/index.d.ts",
|
|
34
34
|
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./extension": {
|
|
37
|
+
"types": "./dist/extension.d.ts",
|
|
38
|
+
"import": "./dist/extension.js"
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"sideEffects": false,
|
|
@@ -48,5 +52,5 @@
|
|
|
48
52
|
},
|
|
49
53
|
"type": "module",
|
|
50
54
|
"types": "./dist/index.d.ts",
|
|
51
|
-
"version": "
|
|
55
|
+
"version": "4.0.0"
|
|
52
56
|
}
|