@kuindji/typed-sql 0.1.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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/builder/assemble.d.ts +13 -0
- package/dist/builder/assemble.d.ts.map +1 -0
- package/dist/builder/assemble.js +86 -0
- package/dist/builder/assemble.js.map +1 -0
- package/dist/builder/condition-tree.d.ts +27 -0
- package/dist/builder/condition-tree.d.ts.map +1 -0
- package/dist/builder/condition-tree.js +91 -0
- package/dist/builder/condition-tree.js.map +1 -0
- package/dist/builder/conditional-sql.d.ts +80 -0
- package/dist/builder/conditional-sql.d.ts.map +1 -0
- package/dist/builder/conditional-sql.js +88 -0
- package/dist/builder/conditional-sql.js.map +1 -0
- package/dist/builder/db.d.ts +76 -0
- package/dist/builder/db.d.ts.map +1 -0
- package/dist/builder/db.js +12 -0
- package/dist/builder/db.js.map +1 -0
- package/dist/builder/delete.d.ts +39 -0
- package/dist/builder/delete.d.ts.map +1 -0
- package/dist/builder/delete.js +33 -0
- package/dist/builder/delete.js.map +1 -0
- package/dist/builder/extract-params.d.ts +97 -0
- package/dist/builder/extract-params.d.ts.map +1 -0
- package/dist/builder/extract-params.js +2 -0
- package/dist/builder/extract-params.js.map +1 -0
- package/dist/builder/index.d.ts +23 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +14 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/insert.d.ts +51 -0
- package/dist/builder/insert.d.ts.map +1 -0
- package/dist/builder/insert.js +39 -0
- package/dist/builder/insert.js.map +1 -0
- package/dist/builder/mutate.d.ts +28 -0
- package/dist/builder/mutate.d.ts.map +1 -0
- package/dist/builder/mutate.js +17 -0
- package/dist/builder/mutate.js.map +1 -0
- package/dist/builder/params.d.ts +22 -0
- package/dist/builder/params.d.ts.map +1 -0
- package/dist/builder/params.js +65 -0
- package/dist/builder/params.js.map +1 -0
- package/dist/builder/return-type.d.ts +39 -0
- package/dist/builder/return-type.d.ts.map +1 -0
- package/dist/builder/return-type.js +2 -0
- package/dist/builder/return-type.js.map +1 -0
- package/dist/builder/scanner.d.ts +49 -0
- package/dist/builder/scanner.d.ts.map +1 -0
- package/dist/builder/scanner.js +240 -0
- package/dist/builder/scanner.js.map +1 -0
- package/dist/builder/select.d.ts +76 -0
- package/dist/builder/select.d.ts.map +1 -0
- package/dist/builder/select.js +240 -0
- package/dist/builder/select.js.map +1 -0
- package/dist/builder/sql-tag.d.ts +319 -0
- package/dist/builder/sql-tag.d.ts.map +1 -0
- package/dist/builder/sql-tag.js +3 -0
- package/dist/builder/sql-tag.js.map +1 -0
- package/dist/builder/sql.d.ts +17 -0
- package/dist/builder/sql.d.ts.map +1 -0
- package/dist/builder/sql.js +36 -0
- package/dist/builder/sql.js.map +1 -0
- package/dist/builder/state.d.ts +53 -0
- package/dist/builder/state.d.ts.map +1 -0
- package/dist/builder/state.js +18 -0
- package/dist/builder/state.js.map +1 -0
- package/dist/builder/update.d.ts +60 -0
- package/dist/builder/update.d.ts.map +1 -0
- package/dist/builder/update.js +40 -0
- package/dist/builder/update.js.map +1 -0
- package/dist/builder/write-assemble.d.ts +5 -0
- package/dist/builder/write-assemble.d.ts.map +1 -0
- package/dist/builder/write-assemble.js +57 -0
- package/dist/builder/write-assemble.js.map +1 -0
- package/dist/builder/write-state.d.ts +39 -0
- package/dist/builder/write-state.d.ts.map +1 -0
- package/dist/builder/write-state.js +6 -0
- package/dist/builder/write-state.js.map +1 -0
- package/dist/builder/write-tag.d.ts +91 -0
- package/dist/builder/write-tag.d.ts.map +1 -0
- package/dist/builder/write-tag.js +2 -0
- package/dist/builder/write-tag.js.map +1 -0
- package/dist/columns.d.ts +33 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +2 -0
- package/dist/columns.js.map +1 -0
- package/dist/expressions.d.ts +71 -0
- package/dist/expressions.d.ts.map +1 -0
- package/dist/expressions.js +2 -0
- package/dist/expressions.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parsing/extract.d.ts +47 -0
- package/dist/parsing/extract.d.ts.map +1 -0
- package/dist/parsing/extract.js +2 -0
- package/dist/parsing/extract.js.map +1 -0
- package/dist/parsing/normalize.d.ts +44 -0
- package/dist/parsing/normalize.d.ts.map +1 -0
- package/dist/parsing/normalize.js +2 -0
- package/dist/parsing/normalize.js.map +1 -0
- package/dist/parsing/pg-literals.d.ts +37 -0
- package/dist/parsing/pg-literals.d.ts.map +1 -0
- package/dist/parsing/pg-literals.js +2 -0
- package/dist/parsing/pg-literals.js.map +1 -0
- package/dist/parsing/split.d.ts +100 -0
- package/dist/parsing/split.d.ts.map +1 -0
- package/dist/parsing/split.js +2 -0
- package/dist/parsing/split.js.map +1 -0
- package/dist/parsing/string-utils.d.ts +29 -0
- package/dist/parsing/string-utils.d.ts.map +1 -0
- package/dist/parsing/string-utils.js +2 -0
- package/dist/parsing/string-utils.js.map +1 -0
- package/dist/parsing/tokenize.d.ts +27 -0
- package/dist/parsing/tokenize.d.ts.map +1 -0
- package/dist/parsing/tokenize.js +2 -0
- package/dist/parsing/tokenize.js.map +1 -0
- package/dist/parsing.d.ts +7 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +9 -0
- package/dist/parsing.js.map +1 -0
- package/dist/partial.d.ts +30 -0
- package/dist/partial.d.ts.map +1 -0
- package/dist/partial.js +10 -0
- package/dist/partial.js.map +1 -0
- package/dist/schema.d.ts +28 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/tables.d.ts +34 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +2 -0
- package/dist/tables.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation/cte.d.ts +54 -0
- package/dist/validation/cte.d.ts.map +1 -0
- package/dist/validation/cte.js +2 -0
- package/dist/validation/cte.js.map +1 -0
- package/dist/validation/dispatch.d.ts +31 -0
- package/dist/validation/dispatch.d.ts.map +1 -0
- package/dist/validation/dispatch.js +2 -0
- package/dist/validation/dispatch.js.map +1 -0
- package/dist/validation/joins.d.ts +16 -0
- package/dist/validation/joins.d.ts.map +1 -0
- package/dist/validation/joins.js +2 -0
- package/dist/validation/joins.js.map +1 -0
- package/dist/validation/return-derived.d.ts +67 -0
- package/dist/validation/return-derived.d.ts.map +1 -0
- package/dist/validation/return-derived.js +5 -0
- package/dist/validation/return-derived.js.map +1 -0
- package/dist/validation/return-types.d.ts +41 -0
- package/dist/validation/return-types.d.ts.map +1 -0
- package/dist/validation/return-types.js +2 -0
- package/dist/validation/return-types.js.map +1 -0
- package/dist/validation/validate-columns.d.ts +63 -0
- package/dist/validation/validate-columns.d.ts.map +1 -0
- package/dist/validation/validate-columns.js +2 -0
- package/dist/validation/validate-columns.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +9 -0
- package/dist/validation.js.map +1 -0
- package/package.json +64 -0
- package/src/builder/assemble.ts +100 -0
- package/src/builder/condition-tree.ts +162 -0
- package/src/builder/conditional-sql.ts +325 -0
- package/src/builder/db.ts +281 -0
- package/src/builder/delete.ts +57 -0
- package/src/builder/extract-params.ts +507 -0
- package/src/builder/index.ts +58 -0
- package/src/builder/insert.ts +75 -0
- package/src/builder/mutate.ts +55 -0
- package/src/builder/params.ts +95 -0
- package/src/builder/return-type.ts +66 -0
- package/src/builder/scanner.ts +254 -0
- package/src/builder/select.ts +470 -0
- package/src/builder/sql-tag.ts +422 -0
- package/src/builder/sql.ts +51 -0
- package/src/builder/state.ts +55 -0
- package/src/builder/update.ts +77 -0
- package/src/builder/write-assemble.ts +52 -0
- package/src/builder/write-state.ts +43 -0
- package/src/builder/write-tag.ts +119 -0
- package/src/columns.ts +336 -0
- package/src/expressions.ts +745 -0
- package/src/index.ts +81 -0
- package/src/parsing/extract.ts +260 -0
- package/src/parsing/normalize.ts +243 -0
- package/src/parsing/pg-literals.ts +289 -0
- package/src/parsing/split.ts +288 -0
- package/src/parsing/string-utils.ts +172 -0
- package/src/parsing/tokenize.ts +321 -0
- package/src/parsing.ts +8 -0
- package/src/partial.ts +241 -0
- package/src/schema.ts +130 -0
- package/src/tables.ts +278 -0
- package/src/utils.ts +43 -0
- package/src/validation/cte.ts +198 -0
- package/src/validation/dispatch.ts +312 -0
- package/src/validation/joins.ts +198 -0
- package/src/validation/return-derived.ts +253 -0
- package/src/validation/return-types.ts +271 -0
- package/src/validation/validate-columns.ts +489 -0
- package/src/validation.ts +8 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// src/builder/conditional-sql.ts
|
|
2
|
+
//
|
|
3
|
+
// Conditional SQL templates with `if:condition` / `endif` comment blocks and
|
|
4
|
+
// `:name` parameters. Runtime + type-level processing ported verbatim from the
|
|
5
|
+
// predecessor package; the result/validity matcher is rewired onto the new core
|
|
6
|
+
// (GetReturnType / ValidateSQL).
|
|
7
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
8
|
+
import type { GetReturnType, ValidateSQL } from "../index.js";
|
|
9
|
+
import type { QueryParamValue } from "./params.js";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Runtime (ported from OLD conditional/runtime.ts)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export interface ConditionalSQLOptions {
|
|
16
|
+
/** If true, preserves conditional comment markers in output (debugging). */
|
|
17
|
+
preserveMarkers?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ConditionalSQLOutput {
|
|
21
|
+
/** The processed SQL string with conditions applied. */
|
|
22
|
+
sql: string;
|
|
23
|
+
/** The parameter values in order of appearance. */
|
|
24
|
+
params: QueryParamValue[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Get a nested value from an object using dot notation. */
|
|
28
|
+
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
29
|
+
const keys = path.split(".");
|
|
30
|
+
let current: unknown = obj;
|
|
31
|
+
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
if (current === null || current === undefined) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (typeof current !== "object") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
current = (current as Record<string, unknown>)[key];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Process conditional blocks in a SQL template (innermost-first, iterative). */
|
|
46
|
+
export function processConditionalSQL(
|
|
47
|
+
template: string,
|
|
48
|
+
conditions: Record<string, unknown>,
|
|
49
|
+
): string {
|
|
50
|
+
// Matches an if-block with no nested if inside its content (innermost first).
|
|
51
|
+
const pattern =
|
|
52
|
+
/\/\*if:(!?[\w.]+)\*\/((?:(?!\/\*if:)[\s\S])*?)\/\*endif\*\//g;
|
|
53
|
+
|
|
54
|
+
let result = template;
|
|
55
|
+
let hasMatches = true;
|
|
56
|
+
|
|
57
|
+
// Process iteratively to handle nested conditions.
|
|
58
|
+
while (hasMatches) {
|
|
59
|
+
hasMatches = false;
|
|
60
|
+
|
|
61
|
+
result = result.replace(
|
|
62
|
+
pattern,
|
|
63
|
+
(_, condition: string, content: string) => {
|
|
64
|
+
hasMatches = true;
|
|
65
|
+
|
|
66
|
+
const isNegated = condition.startsWith("!");
|
|
67
|
+
const key = isNegated ? condition.slice(1) : condition;
|
|
68
|
+
const value = getNestedValue(conditions, key);
|
|
69
|
+
const isTruthy = Boolean(value);
|
|
70
|
+
|
|
71
|
+
return (isNegated ? !isTruthy : isTruthy) ? content : "";
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Convert :name placeholders to positional $n; return processed SQL + values. */
|
|
80
|
+
export function processParams(
|
|
81
|
+
sql: string,
|
|
82
|
+
params: Record<string, QueryParamValue>,
|
|
83
|
+
): ConditionalSQLOutput {
|
|
84
|
+
// Find all param references in order of first appearance.
|
|
85
|
+
const paramRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])/g;
|
|
86
|
+
const usedParams: string[] = [];
|
|
87
|
+
let match;
|
|
88
|
+
|
|
89
|
+
while ((match = paramRegex.exec(sql)) !== null) {
|
|
90
|
+
const name = match[1];
|
|
91
|
+
if (name in params && !usedParams.includes(name)) {
|
|
92
|
+
usedParams.push(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Replace each param with positional placeholder.
|
|
97
|
+
let processedSql = sql;
|
|
98
|
+
for (let i = 0; i < usedParams.length; i++) {
|
|
99
|
+
const name = usedParams[i];
|
|
100
|
+
const regex = new RegExp(`:${name}(?![a-zA-Z0-9_])`, "g");
|
|
101
|
+
processedSql = processedSql.replace(regex, `$${i + 1}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Extract param values in order.
|
|
105
|
+
const paramValues = usedParams.map(name => params[name]);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
sql: processedSql,
|
|
109
|
+
params: paramValues,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Process a template with both conditions and parameters. */
|
|
114
|
+
export function conditionalSQL(
|
|
115
|
+
template: string,
|
|
116
|
+
conditions: Record<string, unknown>,
|
|
117
|
+
params: Record<string, QueryParamValue> = {},
|
|
118
|
+
): ConditionalSQLOutput {
|
|
119
|
+
// Step 1: Process conditional blocks.
|
|
120
|
+
const conditionalProcessed = processConditionalSQL(template, conditions);
|
|
121
|
+
|
|
122
|
+
// Step 2: Process parameters.
|
|
123
|
+
return processParams(conditionalProcessed, params);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Normalize whitespace in SQL (collapse spaces, tidy commas/parens). */
|
|
127
|
+
export function normalizeWhitespace(sql: string): string {
|
|
128
|
+
return sql
|
|
129
|
+
.replace(/\s+/g, " ")
|
|
130
|
+
.replace(/\s*,\s*/g, ", ")
|
|
131
|
+
.replace(/\s*\(\s*/g, "(")
|
|
132
|
+
.replace(/\s*\)\s*/g, ")")
|
|
133
|
+
.trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Type-level condition evaluation (ported from OLD conditional/types.ts)
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
/** Get a nested value type using a dot-notation path. */
|
|
141
|
+
export type GetPath<T, Path extends string> = Path extends
|
|
142
|
+
`${infer Key}.${infer Rest}`
|
|
143
|
+
? Key extends keyof T ? GetPath<T[Key], Rest>
|
|
144
|
+
: undefined
|
|
145
|
+
: Path extends keyof T ? T[Path]
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
/** Type-level truthiness; resolves to `boolean` for non-literal wide types. */
|
|
149
|
+
export type IsTruthy<T> =
|
|
150
|
+
[T] extends [false | 0 | "" | null | undefined] ? false
|
|
151
|
+
: [T] extends [never] ? false
|
|
152
|
+
: [T] extends [boolean]
|
|
153
|
+
? ([boolean] extends [T] ? boolean : true)
|
|
154
|
+
: [T] extends [string] ? ([string] extends [T] ? boolean : true)
|
|
155
|
+
: [T] extends [number] ? ([number] extends [T] ? boolean : true)
|
|
156
|
+
: true;
|
|
157
|
+
|
|
158
|
+
/** Evaluate a condition string against a data type (supports `!` negation). */
|
|
159
|
+
export type EvalCondition<Cond extends string, Data> = Cond extends
|
|
160
|
+
`!${infer Key}`
|
|
161
|
+
? IsTruthy<GetPath<Data, Key>> extends true ? false
|
|
162
|
+
: IsTruthy<GetPath<Data, Key>> extends false ? true
|
|
163
|
+
: boolean
|
|
164
|
+
: IsTruthy<GetPath<Data, Cond>>;
|
|
165
|
+
|
|
166
|
+
/** Check if a string contains a specific pattern. */
|
|
167
|
+
type Contains<S extends string, Pattern extends string> = S extends
|
|
168
|
+
`${string}${Pattern}${string}` ? true : false;
|
|
169
|
+
|
|
170
|
+
/** Check if any condition in the template has an indeterminate (wide) type. */
|
|
171
|
+
type HasIndeterminateCondition<
|
|
172
|
+
Template extends string,
|
|
173
|
+
Data extends Record<string, unknown>,
|
|
174
|
+
> = Template extends `${string}/*if:${infer Cond}*/${infer Rest}`
|
|
175
|
+
? EvalCondition<Cond, Data> extends boolean
|
|
176
|
+
? boolean extends EvalCondition<Cond, Data> ? true
|
|
177
|
+
: HasIndeterminateCondition<Rest, Data>
|
|
178
|
+
: HasIndeterminateCondition<Rest, Data>
|
|
179
|
+
: false;
|
|
180
|
+
|
|
181
|
+
/** Process the innermost conditional block (inside-out). */
|
|
182
|
+
type ProcessInnermost<
|
|
183
|
+
Template extends string,
|
|
184
|
+
Data extends Record<string, unknown>,
|
|
185
|
+
> = Template extends
|
|
186
|
+
`${infer Before}/*if:${infer Cond}*/${infer Content}/*endif*/${infer After}`
|
|
187
|
+
? Contains<Content, "/*if:"> extends true
|
|
188
|
+
? `${Before}/*if:${Cond}*/${ProcessInnermost<
|
|
189
|
+
`${Content}/*endif*/${After}`,
|
|
190
|
+
Data
|
|
191
|
+
>}`
|
|
192
|
+
: EvalCondition<Cond, Data> extends true ? `${Before}${Content}${After}`
|
|
193
|
+
: EvalCondition<Cond, Data> extends false ? `${Before}${After}`
|
|
194
|
+
: string
|
|
195
|
+
: Template;
|
|
196
|
+
|
|
197
|
+
/** Recursively process all conditional blocks until none remain. */
|
|
198
|
+
export type ProcessConditionalSQL<
|
|
199
|
+
Template extends string,
|
|
200
|
+
Data extends Record<string, unknown>,
|
|
201
|
+
Depth extends number[] = [],
|
|
202
|
+
> =
|
|
203
|
+
HasIndeterminateCondition<Template, Data> extends true ? string
|
|
204
|
+
: Depth["length"] extends 20 ? Template
|
|
205
|
+
: Contains<Template, "/*if:"> extends true ? ProcessConditionalSQL<
|
|
206
|
+
ProcessInnermost<Template, Data>,
|
|
207
|
+
Data,
|
|
208
|
+
[...Depth, 0]
|
|
209
|
+
>
|
|
210
|
+
: Template;
|
|
211
|
+
|
|
212
|
+
/** Force every condition value true (maximal column set). */
|
|
213
|
+
export type AllConditionsTrue<Data extends Record<string, unknown>> = {
|
|
214
|
+
[K in keyof Data]: Data[K] extends Record<string, unknown>
|
|
215
|
+
? AllConditionsTrue<Data[K]>
|
|
216
|
+
: true;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** Force every condition value false (minimal column set). */
|
|
220
|
+
export type AllConditionsFalse<Data extends Record<string, unknown>> = {
|
|
221
|
+
[K in keyof Data]: Data[K] extends Record<string, unknown>
|
|
222
|
+
? AllConditionsFalse<Data[K]>
|
|
223
|
+
: false;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/** Marker: columns from conditional SELECT clauses are `T | undefined`. */
|
|
227
|
+
export type ConditionalColumn<T> = T | undefined;
|
|
228
|
+
|
|
229
|
+
/** Marker: columns from conditional LEFT JOINs are `T | null | undefined`. */
|
|
230
|
+
export type ConditionalLeftJoinColumn<T> = T | null | undefined;
|
|
231
|
+
|
|
232
|
+
/** Extract :name parameter names from a SQL string. */
|
|
233
|
+
export type ExtractParamNames<
|
|
234
|
+
SQL extends string,
|
|
235
|
+
Acc extends string[] = [],
|
|
236
|
+
> = SQL extends `${string}:${infer Name}${infer Rest}`
|
|
237
|
+
? Name extends `${infer ParamName}${" " | "," | ")" | "\n" | "\t"}`
|
|
238
|
+
? ExtractParamNames<Rest, [...Acc, ParamName]>
|
|
239
|
+
: ExtractParamNames<Rest, [...Acc, Name]>
|
|
240
|
+
: Acc;
|
|
241
|
+
|
|
242
|
+
/** Validate that all required params are provided. */
|
|
243
|
+
export type ValidateParams<
|
|
244
|
+
SQL extends string,
|
|
245
|
+
Params extends Record<string, unknown>,
|
|
246
|
+
> = ExtractParamNames<SQL> extends infer Names extends string[]
|
|
247
|
+
? Names[number] extends keyof Params ? true
|
|
248
|
+
: `Missing parameter: ${Exclude<Names[number], keyof Params>}`
|
|
249
|
+
: true;
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Rewired matcher + factory (onto the new core)
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
type Flatten<T> = { [K in keyof T]: T[K] } & {};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Result type for a conditional SQL query, rewired onto the new core.
|
|
259
|
+
* 1. all conditions TRUE -> full column set (GetReturnType<FullSQL>)
|
|
260
|
+
* 2. all conditions FALSE -> base column set (GetReturnType<BaseSQL>)
|
|
261
|
+
* 3. columns in full but not base -> `| undefined`
|
|
262
|
+
*/
|
|
263
|
+
export type ConditionalQueryResult<
|
|
264
|
+
Template extends string,
|
|
265
|
+
Conditions extends Record<string, unknown>,
|
|
266
|
+
Schema extends DatabaseSchema,
|
|
267
|
+
> = ProcessConditionalSQL<Template, AllConditionsTrue<Conditions>> extends infer FullSQL extends string
|
|
268
|
+
? ProcessConditionalSQL<Template, AllConditionsFalse<Conditions>> extends infer BaseSQL extends string
|
|
269
|
+
? GetReturnType<FullSQL, Schema> extends infer Full
|
|
270
|
+
? GetReturnType<BaseSQL, Schema> extends infer Base
|
|
271
|
+
? MergeConditionalResults<Full, Base>
|
|
272
|
+
: Full
|
|
273
|
+
: {}
|
|
274
|
+
: {}
|
|
275
|
+
: {};
|
|
276
|
+
|
|
277
|
+
export type MergeConditionalResults<Full, Base> = Flatten<
|
|
278
|
+
& { [K in keyof Full as K extends keyof Base ? K : never]: Full[K] }
|
|
279
|
+
& { [K in keyof Full as K extends keyof Base ? never : K]: Full[K] | undefined }
|
|
280
|
+
>;
|
|
281
|
+
|
|
282
|
+
export type ProcessedSQL<
|
|
283
|
+
Template extends string,
|
|
284
|
+
Conditions extends Record<string, unknown>,
|
|
285
|
+
> = ProcessConditionalSQL<Template, Conditions>;
|
|
286
|
+
|
|
287
|
+
export type ValidateConditionalSQL<
|
|
288
|
+
Template extends string,
|
|
289
|
+
Conditions extends Record<string, unknown>,
|
|
290
|
+
Schema extends DatabaseSchema,
|
|
291
|
+
> = ProcessConditionalSQL<Template, AllConditionsTrue<Conditions>> extends infer FullSQL extends string
|
|
292
|
+
? ValidateSQL<FullSQL, Schema>
|
|
293
|
+
: false;
|
|
294
|
+
|
|
295
|
+
export interface TypedConditionalSQLOutput<Result> extends ConditionalSQLOutput {
|
|
296
|
+
readonly __resultType?: Result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function createConditionalQuery<Schema extends DatabaseSchema>() {
|
|
300
|
+
function query<
|
|
301
|
+
Template extends string,
|
|
302
|
+
Conditions extends Record<string, unknown>,
|
|
303
|
+
Params extends Record<string, QueryParamValue> = {},
|
|
304
|
+
>(
|
|
305
|
+
template: Template,
|
|
306
|
+
conditions: Conditions,
|
|
307
|
+
params?: Params,
|
|
308
|
+
): TypedConditionalSQLOutput<ConditionalQueryResult<Template, Conditions, Schema>> {
|
|
309
|
+
const result = conditionalSQL(template, conditions, params ?? {});
|
|
310
|
+
return result as TypedConditionalSQLOutput<
|
|
311
|
+
ConditionalQueryResult<Template, Conditions, Schema>
|
|
312
|
+
>;
|
|
313
|
+
}
|
|
314
|
+
return query;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function withConditions<StaticConditions extends Record<string, unknown>>(
|
|
318
|
+
queryFn: ReturnType<typeof createConditionalQuery>,
|
|
319
|
+
) {
|
|
320
|
+
return <Template extends string, Params extends Record<string, QueryParamValue> = {}>(
|
|
321
|
+
template: Template,
|
|
322
|
+
conditions: StaticConditions,
|
|
323
|
+
params?: Params,
|
|
324
|
+
) => queryFn(template, conditions, params);
|
|
325
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// src/builder/db.ts
|
|
2
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
3
|
+
import type { GetReturnType, ValidateSQL } from "../index.js";
|
|
4
|
+
import type {
|
|
5
|
+
ValidateFromPart,
|
|
6
|
+
ValidateJoinPart,
|
|
7
|
+
} from "../index.js";
|
|
8
|
+
import type {
|
|
9
|
+
ValidateClausePartScoped,
|
|
10
|
+
ValidateSelectIdentifiersScoped,
|
|
11
|
+
} from "../partial.js";
|
|
12
|
+
import type { SelectQueryBuilder } from "./select.js";
|
|
13
|
+
import type { BuilderReturnType, BuilderSQL } from "./return-type.js";
|
|
14
|
+
import type { Frag, SelFrag, SqlTag } from "./sql-tag.js";
|
|
15
|
+
import type { NormalizeQuery } from "../parsing.js";
|
|
16
|
+
import type { TablesInQuery, AliasesInQuery } from "../tables.js";
|
|
17
|
+
|
|
18
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
19
|
+
|
|
20
|
+
/** String-query validity (core).
|
|
21
|
+
*
|
|
22
|
+
* Size-gated, mirroring `ValidQueryBuilder` below (the gate it gained in the
|
|
23
|
+
* "size-gated whole-query validation" change). The whole-query `ValidateSQL`
|
|
24
|
+
* pass only runs when `Q` is small enough to resolve within the TS
|
|
25
|
+
* instantiation budget.
|
|
26
|
+
*
|
|
27
|
+
* Why the gate is needed on the raw-string path too: `ValidQuery` is used as a
|
|
28
|
+
* PARAMETER type by api.ts's raw-string `typedSelect` overload
|
|
29
|
+
* (`query: ValidQuery<Q, MainDatabase>`), where TS must INFER `Q` from the
|
|
30
|
+
* argument *through* this type. Inferring `Q` while evaluating
|
|
31
|
+
* `ValidateSQL<Q, Schema>` on a heavy query against a large schema (e.g.
|
|
32
|
+
* `MainDatabase`) exhausts the budget — TS abandons inferring `Q`, it collapses
|
|
33
|
+
* to `string`, and `ValidateSQL<string>` then rejects the (valid) argument.
|
|
34
|
+
* Direct `ValidateSQL<concreteLiteral, Schema>` does NOT blow (Q is already
|
|
35
|
+
* known), which is why fn-plain's direct-form mirror passes while the real
|
|
36
|
+
* overload call fails.
|
|
37
|
+
*
|
|
38
|
+
* For an over-gate query we pass `Q` through unvalidated — `Q` stays inferrable
|
|
39
|
+
* and row inference via `GetReturnType<Q>` is unaffected; only the dev-time
|
|
40
|
+
* whole-query validation is skipped. Same trade-off `ValidQueryBuilder` makes
|
|
41
|
+
* for large builder queries. Small queries are still fully validated. */
|
|
42
|
+
export type ValidQuery<Q extends string, Schema extends DatabaseSchema> =
|
|
43
|
+
BuilderSqlSmall<Q> extends true
|
|
44
|
+
? ValidateSQL<Q, Schema> extends infer V
|
|
45
|
+
? V extends true
|
|
46
|
+
? Q
|
|
47
|
+
// `ValidateSQL` reports a boolean `false`, not a string message,
|
|
48
|
+
// so `${V & string}` would interpolate `never` and collapse the
|
|
49
|
+
// whole template to `never` (silently swallowing the rejection).
|
|
50
|
+
// Surface a real `[SQL Error] …` literal instead; the
|
|
51
|
+
// `V extends string` arm future-proofs for descriptive verdicts.
|
|
52
|
+
: V extends string
|
|
53
|
+
? `[SQL Error] ${V}`
|
|
54
|
+
: `[SQL Error] invalid query: ${Q}`
|
|
55
|
+
: never
|
|
56
|
+
: Q;
|
|
57
|
+
|
|
58
|
+
// --- per-fragment validation over LITERAL fragments only ---
|
|
59
|
+
// A fragment whose text is non-literal `string` is skipped (→ never error).
|
|
60
|
+
// The partial validators return `true` (valid OR out-of-scope/skipped) or a
|
|
61
|
+
// boolean `false` (a reference resolvable WITHIN the fragment that does not
|
|
62
|
+
// exist). Treat any non-`true` verdict as an error: a `string` verdict is used
|
|
63
|
+
// as-is (future-proofing for descriptive messages), and a `false` verdict
|
|
64
|
+
// becomes a labeled "invalid <clause> fragment" error carrying the text.
|
|
65
|
+
type FragErr<Verdict, Label extends string, Text extends string> =
|
|
66
|
+
Verdict extends true ? never
|
|
67
|
+
: Verdict extends string ? Verdict
|
|
68
|
+
: `invalid ${Label} fragment: ${Text}`;
|
|
69
|
+
|
|
70
|
+
// Join all literal select-fragment texts into one comma list; bail to `string`
|
|
71
|
+
// if any fragment text is non-literal (the dispatch already allow-unknowns that).
|
|
72
|
+
type SelectListText<List extends readonly SelFrag[], Acc extends string = ""> =
|
|
73
|
+
List extends readonly [infer H extends SelFrag, ...infer R extends readonly SelFrag[]]
|
|
74
|
+
? string extends H["text"]
|
|
75
|
+
? string
|
|
76
|
+
: SelectListText<R, Acc extends "" ? H["text"] : `${Acc}, ${H["text"]}`>
|
|
77
|
+
: Acc;
|
|
78
|
+
|
|
79
|
+
type SelectErrors<List extends readonly SelFrag[], Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
80
|
+
SelectListText<List> extends infer Txt extends string
|
|
81
|
+
? string extends Txt
|
|
82
|
+
? never
|
|
83
|
+
: FragErr<ValidateSelectIdentifiersScoped<Txt, Tables, Aliases, S>, "SELECT", Txt>
|
|
84
|
+
: never;
|
|
85
|
+
|
|
86
|
+
type FromError<From extends string | null, S extends DatabaseSchema> =
|
|
87
|
+
From extends null ? never
|
|
88
|
+
: string extends (From & string) ? never
|
|
89
|
+
: FragErr<ValidateFromPart<From & string, S>, "FROM", From & string>;
|
|
90
|
+
|
|
91
|
+
type JoinErrors<List extends readonly Frag[], S extends DatabaseSchema> =
|
|
92
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
93
|
+
? (string extends H["text"] ? never : FragErr<ValidateJoinPart<H["text"], S>, "JOIN", H["text"]>)
|
|
94
|
+
| JoinErrors<R, S>
|
|
95
|
+
: never;
|
|
96
|
+
|
|
97
|
+
type WhereErrors<List extends readonly Frag[], Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
98
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
99
|
+
? (string extends H["text"] ? never : FragErr<ValidateClausePartScoped<H["text"], Tables, Aliases, S>, "WHERE", H["text"]>)
|
|
100
|
+
| WhereErrors<R, Tables, Aliases, S>
|
|
101
|
+
: never;
|
|
102
|
+
|
|
103
|
+
type GroupErrors<List extends readonly Frag[], Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
104
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
105
|
+
? (string extends H["text"] ? never : FragErr<ValidateClausePartScoped<H["text"], Tables, Aliases, S>, "GROUP BY", H["text"]>)
|
|
106
|
+
| GroupErrors<R, Tables, Aliases, S>
|
|
107
|
+
: never;
|
|
108
|
+
|
|
109
|
+
type HavingErrors<List extends readonly Frag[], Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
110
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
111
|
+
? (string extends H["text"] ? never : FragErr<ValidateClausePartScoped<H["text"], Tables, Aliases, S>, "HAVING", H["text"]>)
|
|
112
|
+
| HavingErrors<R, Tables, Aliases, S>
|
|
113
|
+
: never;
|
|
114
|
+
|
|
115
|
+
type OrderErrors<List extends readonly Frag[], Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
116
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
117
|
+
? (string extends H["text"] ? never : FragErr<ValidateClausePartScoped<H["text"], Tables, Aliases, S>, "ORDER BY", H["text"]>)
|
|
118
|
+
| OrderErrors<R, Tables, Aliases, S>
|
|
119
|
+
: never;
|
|
120
|
+
|
|
121
|
+
// Concatenate the join fragment texts into one string. Each fragment is
|
|
122
|
+
// prefixed with a ` join ` keyword so the whole-query table/alias collectors
|
|
123
|
+
// recognise it as a JOIN source. Builder join texts MAY omit the leading keyword
|
|
124
|
+
// (e.g. "Other o2 on o2.id = o.id") — without the prefix the collector reads the
|
|
125
|
+
// join table as a stray token and never records its alias. When the text ALREADY
|
|
126
|
+
// carries a keyword (e.g. "left join Other o2 on ..."), the extra ` join ` is a
|
|
127
|
+
// harmless structural sentinel: the spurious `join left`/`join join` cycle
|
|
128
|
+
// resolves no real table (→ no-op) and the genuine keyword still collects the
|
|
129
|
+
// table/alias correctly.
|
|
130
|
+
type JoinSourceText<List extends readonly Frag[]> =
|
|
131
|
+
List extends readonly [infer H extends Frag, ...infer R extends readonly Frag[]]
|
|
132
|
+
? ` join ${H["text"]}${JoinSourceText<R>}`
|
|
133
|
+
: "";
|
|
134
|
+
|
|
135
|
+
// Build a from-clause-shaped string from the FROM text + all JOIN texts, so the
|
|
136
|
+
// existing whole-query collectors can read the full table scope. `null` FROM
|
|
137
|
+
// (or a non-literal `string` FROM) yields "" → empty scope (everything skipped).
|
|
138
|
+
type ScopeSourceText<Sql extends SqlTag> =
|
|
139
|
+
Sql["from"] extends null
|
|
140
|
+
? ""
|
|
141
|
+
: string extends (Sql["from"] & string)
|
|
142
|
+
? ""
|
|
143
|
+
: `from ${Sql["from"] & string}${JoinSourceText<Sql["joins"]>}`;
|
|
144
|
+
|
|
145
|
+
// Table keys in scope (union of `schema.table`), via the depth-safe collector.
|
|
146
|
+
export type ScopeTables<Sql extends SqlTag, S extends DatabaseSchema> =
|
|
147
|
+
ScopeSourceText<Sql> extends infer N extends string
|
|
148
|
+
? N extends "" ? never : TablesInQuery<NormalizeQuery<N>, S>
|
|
149
|
+
: never;
|
|
150
|
+
|
|
151
|
+
// Alias→key entries in scope, via the depth-safe collector.
|
|
152
|
+
export type ScopeAliases<Sql extends SqlTag, S extends DatabaseSchema> =
|
|
153
|
+
ScopeSourceText<Sql> extends infer N extends string
|
|
154
|
+
? N extends "" ? never : AliasesInQuery<NormalizeQuery<N>, S>
|
|
155
|
+
: never;
|
|
156
|
+
|
|
157
|
+
// One-time budget tuple of length N (a constant; instantiated once per N).
|
|
158
|
+
type MkBudget<N extends number, Acc extends any[] = []> =
|
|
159
|
+
Acc["length"] extends N ? Acc : MkBudget<N, [any, ...Acc]>;
|
|
160
|
+
|
|
161
|
+
// Char-length threshold below which the precise whole-query ValidateSQL is safe
|
|
162
|
+
// to run. Tunable: raise toward where the whole-query pass starts blowing,
|
|
163
|
+
// lower if a query under it still blows.
|
|
164
|
+
type SqlSizeThreshold = MkBudget<600>;
|
|
165
|
+
|
|
166
|
+
// Walk S against the budget. As soon as chars remain with the budget exhausted,
|
|
167
|
+
// the query is "large" (false). Cost is bounded by the threshold (early-exit),
|
|
168
|
+
// not by the query length.
|
|
169
|
+
type LenWithin<S extends string, Budget extends any[]> =
|
|
170
|
+
S extends `${infer _C}${infer Tail}`
|
|
171
|
+
? Budget extends [any, ...infer Rest extends any[]]
|
|
172
|
+
? LenWithin<Tail, Rest>
|
|
173
|
+
: false
|
|
174
|
+
: true;
|
|
175
|
+
|
|
176
|
+
// True = small enough for the precise whole-query pass.
|
|
177
|
+
export type BuilderSqlSmall<SQL extends string> =
|
|
178
|
+
string extends SQL ? false : LenWithin<SQL, SqlSizeThreshold>;
|
|
179
|
+
|
|
180
|
+
/** Per-fragment errors over the literal fragments of B's Sql tag. */
|
|
181
|
+
export type FragmentErrors<B, Schema extends DatabaseSchema> =
|
|
182
|
+
B extends SelectQueryBuilder<Schema, infer Sql extends SqlTag>
|
|
183
|
+
? ScopeTables<Sql, Schema> extends infer Tbls extends string
|
|
184
|
+
? ScopeAliases<Sql, Schema> extends infer Als extends string
|
|
185
|
+
? (
|
|
186
|
+
| SelectErrors<Sql["selects"], Tbls, Als, Schema>
|
|
187
|
+
| FromError<Sql["from"], Schema>
|
|
188
|
+
| JoinErrors<Sql["joins"], Schema>
|
|
189
|
+
| WhereErrors<Sql["wheres"], Tbls, Als, Schema>
|
|
190
|
+
| GroupErrors<Sql["groupBys"], Tbls, Als, Schema>
|
|
191
|
+
| HavingErrors<Sql["havings"], Tbls, Als, Schema>
|
|
192
|
+
| OrderErrors<Sql["orderBys"], Tbls, Als, Schema>
|
|
193
|
+
) extends infer E
|
|
194
|
+
? [E] extends [never] ? [] : (E & string)[]
|
|
195
|
+
: []
|
|
196
|
+
: []
|
|
197
|
+
: []
|
|
198
|
+
: [];
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Builder validity: per-fragment literal errors first; else whole-query check
|
|
202
|
+
* with allow-unknown when the assembled SQL is non-literal `string`.
|
|
203
|
+
*/
|
|
204
|
+
export type ValidQueryBuilder<Schema extends DatabaseSchema, B extends SelectQueryBuilder<Schema, any>> =
|
|
205
|
+
FragmentErrors<B, Schema> extends []
|
|
206
|
+
? BuilderSQL<B> extends infer SQL extends string
|
|
207
|
+
? string extends SQL
|
|
208
|
+
? B // some fragment text non-literal → allow, untyped
|
|
209
|
+
: BuilderSqlSmall<SQL> extends true
|
|
210
|
+
? ValidateSQL<SQL, Schema> extends infer V
|
|
211
|
+
? V extends true
|
|
212
|
+
? B
|
|
213
|
+
// Boolean `false` (not a string) ⇒ `Extract<…, string>`
|
|
214
|
+
// is `never`, which would collapse the template to
|
|
215
|
+
// `never` and swallow the rejection. Emit a real
|
|
216
|
+
// `[SQL Error] …` literal; `V extends string`
|
|
217
|
+
// future-proofs for descriptive verdicts.
|
|
218
|
+
: V extends string
|
|
219
|
+
? `[SQL Error] ${V}`
|
|
220
|
+
: `[SQL Error] invalid query: ${SQL}`
|
|
221
|
+
: B
|
|
222
|
+
: B // large query: rely on scope-aware FragmentErrors (depth-safe)
|
|
223
|
+
: B
|
|
224
|
+
: `[SQL Error] ${FragmentErrors<B, Schema>[number]}`;
|
|
225
|
+
|
|
226
|
+
export type SelectResult<SQL extends string, Schema extends DatabaseSchema> =
|
|
227
|
+
Prettify<GetReturnType<SQL, Schema>>;
|
|
228
|
+
export type SelectResultArray<SQL extends string, Schema extends DatabaseSchema> =
|
|
229
|
+
Prettify<GetReturnType<SQL, Schema>>[];
|
|
230
|
+
|
|
231
|
+
type InvalidOverrideKeys<Result, Overrides> = Exclude<keyof Overrides, keyof Result>;
|
|
232
|
+
|
|
233
|
+
export type MergeOverrides<Result, Overrides> = keyof Overrides extends never
|
|
234
|
+
? Result
|
|
235
|
+
: InvalidOverrideKeys<Result, Overrides> extends never
|
|
236
|
+
? Prettify<Omit<Result, keyof Overrides> & Overrides>
|
|
237
|
+
: {
|
|
238
|
+
__error: true;
|
|
239
|
+
message: `Override contains keys not in result type: ${InvalidOverrideKeys<Result, Overrides> & string}`;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type SelectBuilderResult<B extends SelectQueryBuilder<any, any>> =
|
|
243
|
+
Prettify<BuilderReturnType<B>>;
|
|
244
|
+
export type SelectBuilderResultArray<B extends SelectQueryBuilder<any, any>> =
|
|
245
|
+
SelectBuilderResult<B>[];
|
|
246
|
+
|
|
247
|
+
export type QueryHandler = (query: string, params?: unknown[]) => unknown;
|
|
248
|
+
|
|
249
|
+
export type IsValidSelect<SQL extends string, Schema extends DatabaseSchema> =
|
|
250
|
+
ValidateSQL<SQL, Schema> extends true ? true : false;
|
|
251
|
+
|
|
252
|
+
export function createSelectFn<
|
|
253
|
+
Schema extends DatabaseSchema,
|
|
254
|
+
Overrides extends Record<string, unknown> = {},
|
|
255
|
+
>(handler: QueryHandler) {
|
|
256
|
+
// String query overload
|
|
257
|
+
function select<Q extends string>(
|
|
258
|
+
query: ValidQuery<Q, Schema>,
|
|
259
|
+
params?: unknown[],
|
|
260
|
+
): Promise<MergeOverrides<SelectResultArray<Q, Schema>[number], Overrides>[]>;
|
|
261
|
+
|
|
262
|
+
// Typed builder overload
|
|
263
|
+
function select<B extends SelectQueryBuilder<Schema, any>>(
|
|
264
|
+
query: ValidQueryBuilder<Schema, B>,
|
|
265
|
+
params?: unknown[],
|
|
266
|
+
): Promise<MergeOverrides<SelectBuilderResult<B>, Overrides>[]>;
|
|
267
|
+
|
|
268
|
+
function select(
|
|
269
|
+
query: ValidQuery<string, Schema> | SelectQueryBuilder<Schema, any>,
|
|
270
|
+
params?: unknown[],
|
|
271
|
+
) {
|
|
272
|
+
if (typeof query === "string") {
|
|
273
|
+
return handler(query, params) as Promise<any>;
|
|
274
|
+
}
|
|
275
|
+
const sql = query.toString();
|
|
276
|
+
const finalParams = params ?? [...query.getParams()];
|
|
277
|
+
return handler(sql, finalParams) as Promise<any>;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return select;
|
|
281
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/builder/delete.ts
|
|
2
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
3
|
+
import { assembleDeleteSQL } from "./write-assemble.js";
|
|
4
|
+
import { EMPTY_DELETE_STATE, type RuntimeDeleteState } from "./write-state.js";
|
|
5
|
+
import {
|
|
6
|
+
assertAllProvided, collectScanned, expandScanned, type DriverParamValue,
|
|
7
|
+
} from "./scanner.js";
|
|
8
|
+
import type { DeleteTag, WriteParamsFor } from "./write-tag.js";
|
|
9
|
+
import type { BoundWrite } from "./insert.js";
|
|
10
|
+
|
|
11
|
+
type PushUsing<T extends DeleteTag, Text extends string, Cond extends boolean> =
|
|
12
|
+
Omit<T, "using"> & { readonly using: readonly [...T["using"], { text: Text; cond: Cond }] };
|
|
13
|
+
type PushWhere<T extends DeleteTag, Text extends string, Cond extends boolean> =
|
|
14
|
+
Omit<T, "wheres"> & { readonly wheres: readonly [...T["wheres"], { text: Text; cond: Cond }] };
|
|
15
|
+
|
|
16
|
+
export interface DeleteQueryBuilder<S extends DatabaseSchema, T extends DeleteTag> {
|
|
17
|
+
from<Tbl extends string>(table: Tbl): DeleteQueryBuilder<S, Omit<T, "table"> & { table: Tbl }>;
|
|
18
|
+
using<Text extends string>(source: Text): DeleteQueryBuilder<S, PushUsing<T, Text, false>>;
|
|
19
|
+
usingIf<Text extends string>(cond: boolean, source: Text): DeleteQueryBuilder<S, PushUsing<T, Text, true>>;
|
|
20
|
+
where<Text extends string>(cond: Text): DeleteQueryBuilder<S, PushWhere<T, Text, false>>;
|
|
21
|
+
whereIf<Text extends string>(cond: boolean, clause: Text): DeleteQueryBuilder<S, PushWhere<T, Text, true>>;
|
|
22
|
+
returning<R extends string>(cols: R): DeleteQueryBuilder<S, Omit<T, "returning"> & { returning: R }>;
|
|
23
|
+
withParams(params: WriteParamsFor<T, S>): BoundWrite<S, T>;
|
|
24
|
+
toString(): string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class DeleteImpl<S extends DatabaseSchema, T extends DeleteTag> {
|
|
28
|
+
constructor(private readonly st: RuntimeDeleteState) {}
|
|
29
|
+
private next(st: RuntimeDeleteState): any { return new DeleteImpl<S, any>(st); }
|
|
30
|
+
from(table: string): any { return this.next({ ...this.st, table }); }
|
|
31
|
+
using(src: string): any { return this.next({ ...this.st, usings: [...this.st.usings, src] }); }
|
|
32
|
+
usingIf(c: boolean, src: string): any { return c ? this.using(src) : this.next(this.st); }
|
|
33
|
+
where(cond: string): any { return this.next({ ...this.st, wheres: [...this.st.wheres, cond] }); }
|
|
34
|
+
whereIf(c: boolean, cond: string): any { return c ? this.where(cond) : this.next(this.st); }
|
|
35
|
+
returning(cols: string): any { return this.next({ ...this.st, returning: cols }); }
|
|
36
|
+
withParams(params: Record<string, DriverParamValue>): any {
|
|
37
|
+
return this.next({ ...this.st, namedParams: { ...this.st.namedParams, ...params } });
|
|
38
|
+
}
|
|
39
|
+
toString(): string {
|
|
40
|
+
const sql = assembleDeleteSQL(this.st);
|
|
41
|
+
assertAllProvided(sql, this.st.namedParams);
|
|
42
|
+
return expandScanned(sql, this.st.namedParams);
|
|
43
|
+
}
|
|
44
|
+
getParams(): ReadonlyArray<DriverParamValue> {
|
|
45
|
+
const sql = assembleDeleteSQL(this.st);
|
|
46
|
+
assertAllProvided(sql, this.st.namedParams);
|
|
47
|
+
return collectScanned(sql, this.st.namedParams);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type EmptyDeleteTag = {
|
|
52
|
+
kind: "delete"; table: ""; using: readonly []; wheres: readonly []; returning: null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function createDeleteQuery<S extends DatabaseSchema>(): DeleteQueryBuilder<S, EmptyDeleteTag> {
|
|
56
|
+
return new DeleteImpl<S, EmptyDeleteTag>(EMPTY_DELETE_STATE) as unknown as DeleteQueryBuilder<S, EmptyDeleteTag>;
|
|
57
|
+
}
|