@simplysm/orm-common 14.0.4 → 14.0.6
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 +127 -99
- package/docs/core.md +88 -137
- package/docs/expression.md +177 -174
- package/docs/query-builder.md +95 -71
- package/docs/queryable.md +160 -135
- package/docs/schema-builders.md +151 -209
- package/docs/types.md +273 -254
- package/package.json +2 -2
package/docs/queryable.md
CHANGED
|
@@ -4,233 +4,258 @@ Type-safe query builder and stored procedure executor.
|
|
|
4
4
|
|
|
5
5
|
## Queryable
|
|
6
6
|
|
|
7
|
-
Chainable query builder class for constructing SELECT, INSERT, UPDATE, DELETE, and UPSERT queries against tables/views.
|
|
8
|
-
|
|
9
7
|
```typescript
|
|
10
|
-
class Queryable<TData extends DataRecord, TFrom extends TableBuilder | never> {
|
|
8
|
+
class Queryable<TData extends DataRecord, TFrom extends TableBuilder<any, any> | never> {
|
|
11
9
|
constructor(readonly meta: QueryableMeta<TData>);
|
|
12
10
|
}
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Fluent query builder for composing SELECT, INSERT, UPDATE, DELETE, and UPSERT queries. All methods return new Queryable instances (immutable chaining).
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| `TData` | Query result data type |
|
|
20
|
-
| `TFrom` | Source table builder type. `never` for views/subqueries (no CUD operations). |
|
|
15
|
+
- `TData` - Query result data type
|
|
16
|
+
- `TFrom` - Source table (required for CUD operations, `never` for read-only)
|
|
21
17
|
|
|
22
|
-
### SELECT /
|
|
18
|
+
### Option Methods (SELECT / DISTINCT / LOCK)
|
|
23
19
|
|
|
24
20
|
| Method | Signature | Description |
|
|
25
|
-
|
|
26
|
-
| `select` |
|
|
27
|
-
| `distinct` | `() => Queryable<TData, never>` | Apply DISTINCT
|
|
28
|
-
| `lock` | `() => Queryable<TData, TFrom>` | Apply
|
|
21
|
+
|--------|-----------|-------------|
|
|
22
|
+
| `select` | `<R>(fn: (cols: QueryableRecord<TData>) => R) => Queryable<UnwrapQueryableRecord<R>, never>` | Specify columns to SELECT |
|
|
23
|
+
| `distinct` | `() => Queryable<TData, never>` | Apply DISTINCT |
|
|
24
|
+
| `lock` | `() => Queryable<TData, TFrom>` | Apply FOR UPDATE row lock |
|
|
29
25
|
|
|
30
|
-
###
|
|
26
|
+
### Restrict Methods (TOP / LIMIT)
|
|
31
27
|
|
|
32
28
|
| Method | Signature | Description |
|
|
33
|
-
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
29
|
+
|--------|-----------|-------------|
|
|
30
|
+
| `top` | `(count: number) => Queryable<TData, TFrom>` | Select top N rows |
|
|
31
|
+
| `limit` | `(skip: number, take: number) => Queryable<TData, TFrom>` | Pagination (requires orderBy) |
|
|
36
32
|
|
|
37
|
-
### Sorting
|
|
33
|
+
### Sorting (ORDER BY)
|
|
38
34
|
|
|
39
35
|
| Method | Signature | Description |
|
|
40
|
-
|
|
41
|
-
| `orderBy` | `(fn: (cols) => ExprUnit, orderBy?: "ASC" \| "DESC") => Queryable<TData, TFrom>` | Add
|
|
42
|
-
| `top` | `(count: number) => Queryable<TData, TFrom>` | Limit to N rows (works without ORDER BY). |
|
|
43
|
-
| `limit` | `(skip: number, take: number) => Queryable<TData, TFrom>` | Pagination (OFFSET/LIMIT). Requires `orderBy()` first. |
|
|
36
|
+
|--------|-----------|-------------|
|
|
37
|
+
| `orderBy` | `(fn: (cols) => ExprUnit, orderBy?: "ASC" \| "DESC") => Queryable<TData, TFrom>` | Add sort condition (stackable) |
|
|
44
38
|
|
|
45
|
-
###
|
|
39
|
+
### Filtering (WHERE)
|
|
46
40
|
|
|
47
41
|
| Method | Signature | Description |
|
|
48
|
-
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
42
|
+
|--------|-----------|-------------|
|
|
43
|
+
| `where` | `(predicate: (cols) => WhereExprUnit[]) => Queryable<TData, TFrom>` | Add WHERE conditions (stackable, AND) |
|
|
44
|
+
| `search` | `(fn: (cols) => ExprUnit<string\|undefined>[], searchText: string) => Queryable<TData, TFrom>` | Full-text search across columns |
|
|
45
|
+
|
|
46
|
+
### Grouping (GROUP BY / HAVING)
|
|
47
|
+
|
|
48
|
+
| Method | Signature | Description |
|
|
49
|
+
|--------|-----------|-------------|
|
|
50
|
+
| `groupBy` | `(fn: (cols) => ExprUnit[]) => Queryable<TData, never>` | Group by columns |
|
|
51
|
+
| `having` | `(predicate: (cols) => WhereExprUnit[]) => Queryable<TData, never>` | Filter groups |
|
|
51
52
|
|
|
52
53
|
### JOIN
|
|
53
54
|
|
|
54
55
|
| Method | Signature | Description |
|
|
55
|
-
|
|
56
|
-
| `join` |
|
|
57
|
-
| `joinSingle` |
|
|
58
|
-
| `include` | `(fn: (item: PathProxy) => PathProxy) => Queryable<TData, TFrom>` | Auto-JOIN based on FK/
|
|
56
|
+
|--------|-----------|-------------|
|
|
57
|
+
| `join` | `<A, R>(as: A, fn: (qr: JoinQueryable, cols) => Queryable<R, any>) => Queryable<TData & { [K in A]?: R[] }, TFrom>` | LEFT OUTER JOIN (1:N, result as array) |
|
|
58
|
+
| `joinSingle` | `<A, R>(as: A, fn: (qr: JoinQueryable, cols) => Queryable<R, any>) => Queryable<TData & { [K in A]?: R }, TFrom>` | LEFT OUTER JOIN (N:1 or 1:1, result as single object) |
|
|
59
|
+
| `include` | `(fn: (item: PathProxy<TData>) => PathProxy) => Queryable<TData, TFrom>` | Auto-JOIN based on TableBuilder FK/FKT relations |
|
|
60
|
+
|
|
61
|
+
### Subquery / Union
|
|
62
|
+
|
|
63
|
+
| Method | Signature | Description |
|
|
64
|
+
|--------|-----------|-------------|
|
|
65
|
+
| `wrap` | `() => Queryable<TData, never>` | Wrap as subquery (needed for count after distinct/groupBy) |
|
|
66
|
+
| `Queryable.union` (static) | `(...queries: Queryable<TData, any>[]) => Queryable<TData, never>` | Combine queries with UNION (min 2) |
|
|
59
67
|
|
|
60
|
-
###
|
|
68
|
+
### Recursive CTE
|
|
61
69
|
|
|
62
70
|
| Method | Signature | Description |
|
|
63
|
-
|
|
64
|
-
| `
|
|
65
|
-
| `static union` | `(...queries: Queryable[]) => Queryable<TData, never>` | Combine 2+ Queryables with UNION. |
|
|
66
|
-
| `recursive` | `(fn: (cte: RecursiveQueryable) => Queryable) => Queryable<TData, never>` | Generate a recursive CTE (WITH RECURSIVE). For hierarchical data. |
|
|
71
|
+
|--------|-----------|-------------|
|
|
72
|
+
| `recursive` | `(fn: (cte: RecursiveQueryable<TData>) => Queryable<TData, any>) => Queryable<TData, never>` | Generate recursive CTE (WITH RECURSIVE) |
|
|
67
73
|
|
|
68
74
|
### Execution (SELECT)
|
|
69
75
|
|
|
70
76
|
| Method | Signature | Description |
|
|
71
|
-
|
|
72
|
-
| `execute` | `() => Promise<TData[]>` | Execute SELECT and return
|
|
73
|
-
| `single` | `() => Promise<TData \| undefined>` | Return single result
|
|
74
|
-
| `first` | `() => Promise<TData \| undefined>` | Return first result
|
|
75
|
-
| `count` | `(fn
|
|
76
|
-
| `exists` | `() => Promise<boolean>` | Check if any matching data exists
|
|
77
|
+
|--------|-----------|-------------|
|
|
78
|
+
| `execute` | `() => Promise<TData[]>` | Execute SELECT and return results |
|
|
79
|
+
| `single` | `() => Promise<TData \| undefined>` | Return single result (throws if > 1) |
|
|
80
|
+
| `first` | `() => Promise<TData \| undefined>` | Return first result |
|
|
81
|
+
| `count` | `(fn?: (cols) => ExprUnit) => Promise<number>` | Count rows (throws after distinct/groupBy without wrap) |
|
|
82
|
+
| `exists` | `() => Promise<boolean>` | Check if any matching data exists |
|
|
77
83
|
|
|
78
|
-
###
|
|
84
|
+
### QueryDef Getters (SELECT)
|
|
79
85
|
|
|
80
86
|
| Method | Signature | Description |
|
|
81
|
-
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `insertIfNotExists` | `(record: TFrom["$inferInsert"]) => Promise<void>` | Insert only if WHERE condition matches no rows. |
|
|
85
|
-
| `insertIfNotExists` | `(record, outputColumns: K[]) => Promise<Pick<...>>` | Insert if not exists and return output columns. |
|
|
86
|
-
| `insertInto` | `(targetTable: TableBuilder) => Promise<void>` | INSERT INTO ... SELECT from current query. |
|
|
87
|
-
| `insertInto` | `(targetTable, outputColumns: K[]) => Promise<Pick<...>[]>` | INSERT INTO with output columns. |
|
|
87
|
+
|--------|-----------|-------------|
|
|
88
|
+
| `getSelectQueryDef` | `() => SelectQueryDef` | Get the SELECT QueryDef AST |
|
|
89
|
+
| `getResultMeta` | `(outputColumns?: string[]) => ResultMeta` | Get result parsing metadata |
|
|
88
90
|
|
|
89
|
-
###
|
|
91
|
+
### INSERT
|
|
90
92
|
|
|
91
93
|
| Method | Signature | Description |
|
|
92
|
-
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
94
|
+
|--------|-----------|-------------|
|
|
95
|
+
| `insert` | `(records: TFrom["$inferInsert"][]) => Promise<void>` | Insert records (auto-chunks per 1000) |
|
|
96
|
+
| `insert` | `(records, outputColumns: K[]) => Promise<Pick<...>[]>` | Insert with output columns |
|
|
97
|
+
| `insertIfNotExists` | `(record: TFrom["$inferInsert"]) => Promise<void>` | Insert if WHERE condition has no match |
|
|
98
|
+
| `insertIfNotExists` | `(record, outputColumns: K[]) => Promise<Pick<...>>` | Insert if not exists with output |
|
|
99
|
+
| `insertInto` | `(targetTable: TableBuilder) => Promise<void>` | INSERT INTO ... SELECT |
|
|
100
|
+
| `insertInto` | `(targetTable, outputColumns: K[]) => Promise<Pick<...>[]>` | INSERT INTO ... SELECT with output |
|
|
97
101
|
|
|
98
|
-
###
|
|
102
|
+
### UPDATE / DELETE
|
|
99
103
|
|
|
100
104
|
| Method | Signature | Description |
|
|
101
|
-
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
+
|--------|-----------|-------------|
|
|
106
|
+
| `update` | `(recordFwd: (cols) => QueryableWriteRecord) => Promise<void>` | Update matching rows |
|
|
107
|
+
| `update` | `(recordFwd, outputColumns: K[]) => Promise<Pick<...>[]>` | Update with output |
|
|
108
|
+
| `delete` | `() => Promise<void>` | Delete matching rows |
|
|
109
|
+
| `delete` | `(outputColumns: K[]) => Promise<Pick<...>[]>` | Delete with output |
|
|
105
110
|
|
|
106
|
-
###
|
|
111
|
+
### UPSERT
|
|
107
112
|
|
|
108
|
-
|
|
113
|
+
| Method | Signature | Description |
|
|
114
|
+
|--------|-----------|-------------|
|
|
115
|
+
| `upsert` | `(updateFn: (cols) => WriteRecord) => Promise<void>` | Update or insert (same data) |
|
|
116
|
+
| `upsert` | `(updateFn, insertFn) => Promise<void>` | Update or insert (different data) |
|
|
117
|
+
| `upsert` | `(updateFn, insertFn?, outputColumns?) => Promise<Pick<...>[] \| void>` | With output columns |
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
- `getInsertQueryDef(records, outputColumns?): InsertQueryDef`
|
|
112
|
-
- `getInsertIfNotExistsQueryDef(record, outputColumns?): InsertIfNotExistsQueryDef`
|
|
113
|
-
- `getInsertIntoQueryDef(targetTable, outputColumns?): InsertIntoQueryDef`
|
|
114
|
-
- `getUpdateQueryDef(recordFwd, outputColumns?): UpdateQueryDef`
|
|
115
|
-
- `getDeleteQueryDef(outputColumns?): DeleteQueryDef`
|
|
116
|
-
- `getUpsertQueryDef(updateRecordFn, insertRecordFn, outputColumns?): UpsertQueryDef`
|
|
117
|
-
- `getResultMeta(outputColumns?): ResultMeta`
|
|
119
|
+
### DDL Helper
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
| Method | Signature | Description |
|
|
122
|
+
|--------|-----------|-------------|
|
|
123
|
+
| `switchFk` | `(enabled: boolean) => Promise<void>` | Enable/disable FK constraints on this table |
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
## queryable (factory)
|
|
122
126
|
|
|
123
127
|
```typescript
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
: TData[K] extends DataRecord ? QueryableRecord<TData[K]> : never;
|
|
130
|
-
};
|
|
128
|
+
function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<any, any, any>>(
|
|
129
|
+
db: DbContextBase,
|
|
130
|
+
tableOrView: TBuilder,
|
|
131
|
+
as?: string,
|
|
132
|
+
): () => Queryable<TBuilder["$inferSelect"], TBuilder extends TableBuilder ? TBuilder : never>
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
Creates a factory function that returns a new Queryable instance each time it is called. Each call allocates a fresh alias via `db.getNextAlias()`.
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
## Executable
|
|
136
138
|
|
|
137
139
|
```typescript
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
140
|
+
class Executable<TParams extends ColumnBuilderRecord, TReturns extends ColumnBuilderRecord> {
|
|
141
|
+
constructor(db: DbContextBase, builder: ProcedureBuilder<TParams, TReturns>);
|
|
142
|
+
getExecProcQueryDef(params?: InferColumnExprs<TParams>): { type: "execProc"; ... };
|
|
143
|
+
async execute(params: InferColumnExprs<TParams>): Promise<InferColumnExprs<TReturns>[][]>;
|
|
144
|
+
}
|
|
141
145
|
```
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
Stored procedure execution wrapper.
|
|
148
|
+
|
|
149
|
+
| Method | Signature | Description |
|
|
150
|
+
|--------|-----------|-------------|
|
|
151
|
+
| `getExecProcQueryDef` | `(params?) => ExecProcQueryDef` | Build procedure execution QueryDef |
|
|
152
|
+
| `execute` | `(params) => Promise<T[][]>` | Execute the procedure |
|
|
144
153
|
|
|
145
|
-
|
|
154
|
+
## executable (factory)
|
|
146
155
|
|
|
147
156
|
```typescript
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
157
|
+
function executable<TParams, TReturns>(
|
|
158
|
+
db: DbContextBase,
|
|
159
|
+
builder: ProcedureBuilder<TParams, TReturns>,
|
|
160
|
+
): () => Executable<TParams, TReturns>
|
|
153
161
|
```
|
|
154
162
|
|
|
155
|
-
|
|
163
|
+
Creates a factory function that returns a new Executable instance.
|
|
156
164
|
|
|
157
|
-
|
|
165
|
+
## parseSearchQuery
|
|
158
166
|
|
|
159
167
|
```typescript
|
|
160
|
-
function
|
|
161
|
-
fkCols: string[],
|
|
162
|
-
targetTable: TableBuilder<any, any>,
|
|
163
|
-
): string[];
|
|
168
|
+
function parseSearchQuery(searchText: string): ParsedSearchQuery
|
|
164
169
|
```
|
|
165
170
|
|
|
166
|
-
|
|
171
|
+
Parses a search query string into SQL LIKE patterns.
|
|
167
172
|
|
|
168
|
-
|
|
173
|
+
| Syntax | Meaning | Example |
|
|
174
|
+
|--------|---------|---------|
|
|
175
|
+
| `term1 term2` | OR (match any) | `apple banana` |
|
|
176
|
+
| `+term` | Required (AND) | `+apple +banana` |
|
|
177
|
+
| `-term` | Exclude (NOT) | `apple -banana` |
|
|
178
|
+
| `"exact phrase"` | Exact match (required) | `"delicious fruit"` |
|
|
179
|
+
| `*` | Wildcard | `app*` becomes `app%` |
|
|
169
180
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
Escape sequences: `\\` (literal `\`), `\*`, `\%`, `\"`, `\+`, `\-`.
|
|
182
|
+
|
|
183
|
+
## ParsedSearchQuery
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
```typescript
|
|
186
|
+
interface ParsedSearchQuery {
|
|
187
|
+
or: string[]; // OR conditions - LIKE patterns
|
|
188
|
+
must: string[]; // AND conditions (+ prefix or quotes) - LIKE patterns
|
|
189
|
+
not: string[]; // NOT conditions (- prefix) - LIKE patterns
|
|
176
190
|
}
|
|
177
191
|
```
|
|
178
192
|
|
|
179
|
-
|
|
193
|
+
## getMatchedPrimaryKeys
|
|
180
194
|
|
|
181
195
|
```typescript
|
|
182
|
-
|
|
196
|
+
function getMatchedPrimaryKeys(
|
|
197
|
+
fkCols: string[],
|
|
198
|
+
targetTable: TableBuilder<any, any>,
|
|
199
|
+
): string[]
|
|
183
200
|
```
|
|
184
201
|
|
|
185
|
-
|
|
202
|
+
Match FK column array with the target table's PK. Returns PK column name array. Throws if column counts do not match.
|
|
186
203
|
|
|
187
|
-
|
|
204
|
+
## QueryableRecord
|
|
188
205
|
|
|
189
206
|
```typescript
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
type QueryableRecord<TData extends DataRecord> = {
|
|
208
|
+
[K in keyof TData]: TData[K] extends ColumnPrimitive
|
|
209
|
+
? ExprUnit<TData[K]>
|
|
210
|
+
: TData[K] extends (infer U)[]
|
|
211
|
+
? U extends DataRecord ? QueryableRecord<U>[] : never
|
|
212
|
+
: TData[K] extends DataRecord ? QueryableRecord<TData[K]> : ...
|
|
213
|
+
}
|
|
194
214
|
```
|
|
195
215
|
|
|
196
|
-
|
|
216
|
+
Maps each field of a DataRecord to its corresponding ExprUnit proxy. Used as the column accessor type in Queryable callbacks.
|
|
197
217
|
|
|
198
|
-
|
|
218
|
+
## QueryableWriteRecord
|
|
199
219
|
|
|
200
220
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
must: string[]; // AND conditions (+ prefix or quoted)
|
|
204
|
-
not: string[]; // NOT conditions (- prefix)
|
|
221
|
+
type QueryableWriteRecord<TData> = {
|
|
222
|
+
[K in keyof TData]: TData[K] extends ColumnPrimitive ? ExprInput<TData[K]> : never;
|
|
205
223
|
}
|
|
206
224
|
```
|
|
207
225
|
|
|
208
|
-
|
|
226
|
+
Maps each field to ExprInput for write operations (UPDATE, UPSERT).
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
## NullableQueryableRecord
|
|
211
229
|
|
|
212
230
|
```typescript
|
|
213
|
-
|
|
231
|
+
type NullableQueryableRecord<TData extends DataRecord> = { ... }
|
|
214
232
|
```
|
|
215
233
|
|
|
216
|
-
|
|
234
|
+
Like QueryableRecord but all primitive fields become `ExprUnit<T | undefined>`. Used for LEFT JOIN results where all columns may be NULL.
|
|
217
235
|
|
|
218
|
-
|
|
219
|
-
|---|---|---|
|
|
220
|
-
| `term1 term2` | OR (match any) | `apple banana` |
|
|
221
|
-
| `+term` | Required (AND) | `+apple +banana` |
|
|
222
|
-
| `-term` | Excluded (NOT) | `apple -banana` |
|
|
223
|
-
| `"exact phrase"` | Exact phrase match (required) | `"delicious fruit"` |
|
|
224
|
-
| `*` | Wildcard | `app*` produces `app%` |
|
|
236
|
+
## UnwrapQueryableRecord
|
|
225
237
|
|
|
226
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
type UnwrapQueryableRecord<R> = {
|
|
240
|
+
[K in keyof R]: R[K] extends ExprUnit<infer T> ? T : ...
|
|
241
|
+
}
|
|
242
|
+
```
|
|
227
243
|
|
|
228
|
-
|
|
244
|
+
Reverse-maps QueryableRecord back to a plain DataRecord. Used internally by `select()` to infer the resulting data type.
|
|
245
|
+
|
|
246
|
+
## PathProxy
|
|
229
247
|
|
|
230
248
|
```typescript
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
type PathProxy<TObject> = {
|
|
250
|
+
[K in keyof TObject as TObject[K] extends ColumnPrimitive ? never : K]-?: PathProxy<UnwrapArray<TObject[K]>>;
|
|
251
|
+
} & { readonly [PATH_SYMBOL]: string[] }
|
|
252
|
+
```
|
|
233
253
|
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
Type-safe proxy for `include()` that only exposes non-primitive (relation) fields. Property access builds a path array internally.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Only relation fields are accessible:
|
|
258
|
+
db.post().include((p) => p.author) // OK - author is a relation
|
|
259
|
+
db.post().include((p) => p.author.company) // OK - nested relation
|
|
260
|
+
// db.post().include((p) => p.title) // Compile error - title is string
|
|
236
261
|
```
|