@proofkit/fmodata 0.1.0-alpha.8 → 0.1.0-beta.23
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.md +21 -0
- package/README.md +651 -449
- package/dist/esm/client/batch-builder.d.ts +10 -9
- package/dist/esm/client/batch-builder.js +119 -56
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js +16 -21
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +10 -0
- package/dist/esm/client/builders/default-select.js +41 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +45 -0
- package/dist/esm/client/builders/expand-builder.js +185 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +9 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
- package/dist/esm/client/builders/query-string-builder.js +21 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +43 -0
- package/dist/esm/client/builders/response-processor.js +175 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +25 -0
- package/dist/esm/client/builders/select-mixin.js +28 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +18 -0
- package/dist/esm/client/builders/select-utils.js +30 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +40 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +44 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +34 -22
- package/dist/esm/client/database.js +48 -84
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +25 -30
- package/dist/esm/client/delete-builder.js +45 -30
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +35 -43
- package/dist/esm/client/entity-set.js +110 -52
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +25 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +26 -7
- package/dist/esm/client/filemaker-odata.js +65 -42
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +19 -24
- package/dist/esm/client/insert-builder.js +94 -58
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +4 -0
- package/dist/esm/client/query/query-builder.d.ts +132 -0
- package/dist/esm/client/query/query-builder.js +456 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +25 -0
- package/dist/esm/client/query/types.d.ts +77 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +100 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +2 -115
- package/dist/esm/client/record-builder.d.ts +108 -36
- package/dist/esm/client/record-builder.js +284 -119
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +4 -9
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/schema-manager.d.ts +5 -5
- package/dist/esm/client/schema-manager.js +45 -31
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -40
- package/dist/esm/client/update-builder.js +99 -58
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +189 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/errors.d.ts +19 -2
- package/dist/esm/errors.js +39 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -8
- package/dist/esm/index.js +40 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +69 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/column.d.ts +62 -0
- package/dist/esm/orm/column.js +63 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +164 -0
- package/dist/esm/orm/field-builders.js +158 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +5 -0
- package/dist/esm/orm/operators.d.ts +173 -0
- package/dist/esm/orm/operators.js +260 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +355 -0
- package/dist/esm/orm/table.js +202 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +44 -45
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +96 -30
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +22 -12
- package/dist/esm/validation.js +132 -85
- package/dist/esm/validation.js.map +1 -1
- package/package.json +28 -20
- package/src/client/batch-builder.ts +153 -89
- package/src/client/batch-request.ts +25 -41
- package/src/client/builders/default-select.ts +75 -0
- package/src/client/builders/expand-builder.ts +246 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +46 -0
- package/src/client/builders/response-processor.ts +279 -0
- package/src/client/builders/select-mixin.ts +65 -0
- package/src/client/builders/select-utils.ts +59 -0
- package/src/client/builders/shared-types.ts +45 -0
- package/src/client/builders/table-utils.ts +83 -0
- package/src/client/database.ts +89 -183
- package/src/client/delete-builder.ts +74 -84
- package/src/client/entity-set.ts +266 -293
- package/src/client/error-parser.ts +41 -0
- package/src/client/filemaker-odata.ts +98 -66
- package/src/client/insert-builder.ts +157 -118
- package/src/client/query/expand-builder.ts +160 -0
- package/src/client/query/index.ts +14 -0
- package/src/client/query/query-builder.ts +729 -0
- package/src/client/query/response-processor.ts +226 -0
- package/src/client/query/types.ts +126 -0
- package/src/client/query/url-builder.ts +151 -0
- package/src/client/query-builder.ts +10 -1455
- package/src/client/record-builder.ts +575 -240
- package/src/client/response-processor.ts +15 -42
- package/src/client/sanitize-json.ts +64 -0
- package/src/client/schema-manager.ts +61 -76
- package/src/client/update-builder.ts +161 -143
- package/src/client/webhook-builder.ts +265 -0
- package/src/errors.ts +49 -16
- package/src/index.ts +99 -54
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +116 -0
- package/src/orm/column.ts +106 -0
- package/src/orm/field-builders.ts +250 -0
- package/src/orm/index.ts +61 -0
- package/src/orm/operators.ts +473 -0
- package/src/orm/table.ts +741 -0
- package/src/transform.ts +90 -70
- package/src/types.ts +154 -113
- package/src/validation.ts +200 -115
- package/dist/esm/client/base-table.d.ts +0 -125
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -896
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -72
- package/dist/esm/client/table-occurrence.js +0 -74
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/client/base-table.ts +0 -166
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -175
- package/src/filter-types.ts +0 -97
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { needsFieldQuoting } from "../client/builders/select-utils";
|
|
2
|
+
import type { Column } from "./column";
|
|
3
|
+
import { isColumn } from "./column";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FilterExpression represents a filter condition that can be used in where() clauses.
|
|
7
|
+
* Internal representation of operator expressions that get converted to OData filter syntax.
|
|
8
|
+
*/
|
|
9
|
+
export class FilterExpression {
|
|
10
|
+
readonly operator: string;
|
|
11
|
+
// biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type
|
|
12
|
+
readonly operands: (Column | any | FilterExpression)[];
|
|
13
|
+
|
|
14
|
+
// biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type
|
|
15
|
+
constructor(operator: string, operands: (Column | any | FilterExpression)[]) {
|
|
16
|
+
this.operator = operator;
|
|
17
|
+
this.operands = operands;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert this expression to OData filter syntax.
|
|
22
|
+
* @internal Used by QueryBuilder
|
|
23
|
+
*/
|
|
24
|
+
toODataFilter(useEntityIds?: boolean): string {
|
|
25
|
+
switch (this.operator) {
|
|
26
|
+
// Comparison operators
|
|
27
|
+
case "eq":
|
|
28
|
+
return this._binaryOp("eq", useEntityIds);
|
|
29
|
+
case "ne":
|
|
30
|
+
return this._binaryOp("ne", useEntityIds);
|
|
31
|
+
case "gt":
|
|
32
|
+
return this._binaryOp("gt", useEntityIds);
|
|
33
|
+
case "gte":
|
|
34
|
+
return this._binaryOp("ge", useEntityIds);
|
|
35
|
+
case "lt":
|
|
36
|
+
return this._binaryOp("lt", useEntityIds);
|
|
37
|
+
case "lte":
|
|
38
|
+
return this._binaryOp("le", useEntityIds);
|
|
39
|
+
case "in":
|
|
40
|
+
return this._inOp(useEntityIds);
|
|
41
|
+
case "notIn":
|
|
42
|
+
return this._notInOp(useEntityIds);
|
|
43
|
+
|
|
44
|
+
// String operators
|
|
45
|
+
case "contains":
|
|
46
|
+
return this._functionOp("contains", useEntityIds);
|
|
47
|
+
case "startsWith":
|
|
48
|
+
return this._functionOp("startswith", useEntityIds);
|
|
49
|
+
case "endsWith":
|
|
50
|
+
return this._functionOp("endswith", useEntityIds);
|
|
51
|
+
|
|
52
|
+
// Null checks
|
|
53
|
+
case "isNull":
|
|
54
|
+
return this._isNullOp(useEntityIds);
|
|
55
|
+
case "isNotNull":
|
|
56
|
+
return this._isNotNullOp(useEntityIds);
|
|
57
|
+
|
|
58
|
+
// Logical operators
|
|
59
|
+
case "and":
|
|
60
|
+
return this._logicalOp("and", useEntityIds);
|
|
61
|
+
case "or":
|
|
62
|
+
return this._logicalOp("or", useEntityIds);
|
|
63
|
+
case "not":
|
|
64
|
+
return this._notOp(useEntityIds);
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Unknown operator: ${this.operator}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _binaryOp(op: string, useEntityIds?: boolean): string {
|
|
72
|
+
const [left, right] = this.operands;
|
|
73
|
+
// For binary ops, the column is typically the first operand and value is the second
|
|
74
|
+
// But we also support column-to-column comparisons, so check both
|
|
75
|
+
let columnForValue: typeof left | typeof right | undefined;
|
|
76
|
+
if (isColumn(left) && !isColumn(right)) {
|
|
77
|
+
columnForValue = left;
|
|
78
|
+
} else if (isColumn(right) && !isColumn(left)) {
|
|
79
|
+
columnForValue = right;
|
|
80
|
+
} else {
|
|
81
|
+
columnForValue = undefined;
|
|
82
|
+
}
|
|
83
|
+
const leftStr = this._operandToString(left, useEntityIds, columnForValue);
|
|
84
|
+
const rightStr = this._operandToString(right, useEntityIds, columnForValue);
|
|
85
|
+
return `${leftStr} ${op} ${rightStr}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _functionOp(fnName: string, useEntityIds?: boolean): string {
|
|
89
|
+
const [column, value] = this.operands;
|
|
90
|
+
const columnInstance = isColumn(column) ? column : undefined;
|
|
91
|
+
const columnStr = this._operandToString(column, useEntityIds);
|
|
92
|
+
const valueStr = this._operandToString(value, useEntityIds, columnInstance);
|
|
93
|
+
return `${fnName}(${columnStr}, ${valueStr})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private _inOp(useEntityIds?: boolean): string {
|
|
97
|
+
const [column, values] = this.operands;
|
|
98
|
+
const columnInstance = isColumn(column) ? column : undefined;
|
|
99
|
+
const columnStr = this._operandToString(column, useEntityIds);
|
|
100
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input
|
|
101
|
+
const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(", ");
|
|
102
|
+
return `${columnStr} in (${valuesStr})`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private _notInOp(useEntityIds?: boolean): string {
|
|
106
|
+
const [column, values] = this.operands;
|
|
107
|
+
const columnInstance = isColumn(column) ? column : undefined;
|
|
108
|
+
const columnStr = this._operandToString(column, useEntityIds);
|
|
109
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input
|
|
110
|
+
const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(", ");
|
|
111
|
+
return `not (${columnStr} in (${valuesStr}))`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private _isNullOp(useEntityIds?: boolean): string {
|
|
115
|
+
const [column] = this.operands;
|
|
116
|
+
const columnStr = this._operandToString(column, useEntityIds);
|
|
117
|
+
return `${columnStr} eq null`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _isNotNullOp(useEntityIds?: boolean): string {
|
|
121
|
+
const [column] = this.operands;
|
|
122
|
+
const columnStr = this._operandToString(column, useEntityIds);
|
|
123
|
+
return `${columnStr} ne null`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private _logicalOp(op: string, useEntityIds?: boolean): string {
|
|
127
|
+
const expressions = this.operands.map((expr) => {
|
|
128
|
+
if (expr instanceof FilterExpression) {
|
|
129
|
+
const innerExpr = expr.toODataFilter(useEntityIds);
|
|
130
|
+
// Wrap in parens if it's a logical expression to ensure precedence
|
|
131
|
+
if (expr.operator === "and" || expr.operator === "or") {
|
|
132
|
+
return `(${innerExpr})`;
|
|
133
|
+
}
|
|
134
|
+
return innerExpr;
|
|
135
|
+
}
|
|
136
|
+
throw new Error("Logical operators require FilterExpression operands");
|
|
137
|
+
});
|
|
138
|
+
return expressions.join(` ${op} `);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private _notOp(useEntityIds?: boolean): string {
|
|
142
|
+
const [expr] = this.operands;
|
|
143
|
+
if (expr instanceof FilterExpression) {
|
|
144
|
+
return `not (${expr.toODataFilter(useEntityIds)})`;
|
|
145
|
+
}
|
|
146
|
+
throw new Error("NOT operator requires a FilterExpression operand");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private _operandToString(
|
|
150
|
+
// biome-ignore lint/suspicious/noExplicitAny: Operand can be Column, FilterExpression, or any value type
|
|
151
|
+
operand: any,
|
|
152
|
+
useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
153
|
+
column?: Column<any, any, any, any>,
|
|
154
|
+
): string {
|
|
155
|
+
if (isColumn(operand)) {
|
|
156
|
+
const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);
|
|
157
|
+
// Quote field names in OData filters per FileMaker OData API requirements
|
|
158
|
+
return needsFieldQuoting(fieldIdentifier) ? `"${fieldIdentifier}"` : fieldIdentifier;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If we have a column with an input validator, apply it to transform the value
|
|
162
|
+
let value = operand;
|
|
163
|
+
if (column?.inputValidator) {
|
|
164
|
+
try {
|
|
165
|
+
const result = column.inputValidator["~standard"].validate(value);
|
|
166
|
+
// Handle async validators (though they shouldn't be async for filters)
|
|
167
|
+
if (result instanceof Promise) {
|
|
168
|
+
// For filters, we can't use async validators, so skip transformation
|
|
169
|
+
// This is a limitation - async validators won't work in filters
|
|
170
|
+
value = operand;
|
|
171
|
+
} else if ("issues" in result && result.issues) {
|
|
172
|
+
// Validation failed, use original value
|
|
173
|
+
value = operand;
|
|
174
|
+
} else if ("value" in result) {
|
|
175
|
+
// Validation succeeded, use transformed value
|
|
176
|
+
value = result.value;
|
|
177
|
+
}
|
|
178
|
+
} catch (_error) {
|
|
179
|
+
// If validation throws, use the original value (will likely cause a query error)
|
|
180
|
+
// This maintains backward compatibility and allows the server to handle validation
|
|
181
|
+
value = operand;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof value === "string") {
|
|
186
|
+
return `'${value.replace(/'/g, "''")}'`; // Escape single quotes
|
|
187
|
+
}
|
|
188
|
+
if (value === null || value === undefined) {
|
|
189
|
+
return "null";
|
|
190
|
+
}
|
|
191
|
+
if (value instanceof Date) {
|
|
192
|
+
return value.toISOString();
|
|
193
|
+
}
|
|
194
|
+
return String(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Comparison Operators
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Equal operator - checks if column equals a value or another column.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* eq(users.name, "John") // name equals "John"
|
|
207
|
+
* eq(users.id, contacts.id_user) // cross-table comparison
|
|
208
|
+
*/
|
|
209
|
+
export function eq<TOutput, TInput>(
|
|
210
|
+
column1: Column<TOutput, TInput>,
|
|
211
|
+
column2: Column<TOutput, TInput> | NoInfer<TInput>,
|
|
212
|
+
): FilterExpression;
|
|
213
|
+
// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads
|
|
214
|
+
export function eq(column: Column, value: any): FilterExpression {
|
|
215
|
+
return new FilterExpression("eq", [column, value]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Not equal operator - checks if column does not equal a value or another column.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ne(users.status, "inactive") // status not equal to "inactive"
|
|
223
|
+
* ne(users.id, contacts.id_user) // cross-table comparison
|
|
224
|
+
*/
|
|
225
|
+
export function ne<TOutput, TInput>(
|
|
226
|
+
column1: Column<TOutput, TInput>,
|
|
227
|
+
column2: Column<TOutput, TInput> | NoInfer<TInput>,
|
|
228
|
+
): FilterExpression;
|
|
229
|
+
// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads
|
|
230
|
+
export function ne(column: Column, value: any): FilterExpression {
|
|
231
|
+
return new FilterExpression("ne", [column, value]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Greater than operator - checks if column is greater than a value.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* gt(users.age, 18) // age greater than 18
|
|
239
|
+
*/
|
|
240
|
+
export function gt<TOutput extends number | string | Date | null, TInput>(
|
|
241
|
+
column: Column<TOutput, TInput>,
|
|
242
|
+
value: NoInfer<TInput>,
|
|
243
|
+
): FilterExpression {
|
|
244
|
+
return new FilterExpression("gt", [column, value]);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Greater than or equal operator - checks if column is >= a value.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* gte(users.age, 18) // age >= 18
|
|
252
|
+
*/
|
|
253
|
+
export function gte<TOutput extends number | string | Date | null, TInput>(
|
|
254
|
+
column: Column<TOutput, TInput>,
|
|
255
|
+
value: NoInfer<TInput>,
|
|
256
|
+
): FilterExpression {
|
|
257
|
+
return new FilterExpression("gte", [column, value]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Less than operator - checks if column is less than a value.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* lt(users.age, 65) // age less than 65
|
|
265
|
+
*/
|
|
266
|
+
export function lt<TOutput extends number | string | Date | null, TInput>(
|
|
267
|
+
column: Column<TOutput, TInput>,
|
|
268
|
+
value: NoInfer<TInput>,
|
|
269
|
+
): FilterExpression {
|
|
270
|
+
return new FilterExpression("lt", [column, value]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Less than or equal operator - checks if column is <= a value.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* lte(users.age, 65) // age <= 65
|
|
278
|
+
*/
|
|
279
|
+
export function lte<TOutput extends number | string | Date | null, TInput>(
|
|
280
|
+
column: Column<TOutput, TInput>,
|
|
281
|
+
value: NoInfer<TInput>,
|
|
282
|
+
): FilterExpression {
|
|
283
|
+
return new FilterExpression("lte", [column, value]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// String Operators
|
|
288
|
+
// ============================================================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Contains operator - checks if a string column contains a substring.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* contains(users.name, "John") // name contains "John"
|
|
295
|
+
*/
|
|
296
|
+
export function contains<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {
|
|
297
|
+
return new FilterExpression("contains", [column, value]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Starts with operator - checks if a string column starts with a prefix.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* startsWith(users.email, "admin") // email starts with "admin"
|
|
305
|
+
*/
|
|
306
|
+
export function startsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {
|
|
307
|
+
return new FilterExpression("startsWith", [column, value]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Ends with operator - checks if a string column ends with a suffix.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* endsWith(users.email, "@example.com") // email ends with "@example.com"
|
|
315
|
+
*/
|
|
316
|
+
export function endsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {
|
|
317
|
+
return new FilterExpression("endsWith", [column, value]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Array Operators
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* In array operator - checks if column value is in an array of values.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* inArray(users.status, ["active", "pending"]) // status is "active" or "pending"
|
|
329
|
+
*/
|
|
330
|
+
export function inArray<TOutput, TInput>(column: Column<TOutput, TInput>, values: NoInfer<TInput>[]): FilterExpression {
|
|
331
|
+
return new FilterExpression("in", [column, values]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Not in array operator - checks if column value is not in an array of values.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* notInArray(users.status, ["deleted", "banned"]) // status is neither "deleted" nor "banned"
|
|
339
|
+
*/
|
|
340
|
+
export function notInArray<TOutput, TInput>(
|
|
341
|
+
column: Column<TOutput, TInput>,
|
|
342
|
+
values: NoInfer<TInput>[],
|
|
343
|
+
): FilterExpression {
|
|
344
|
+
return new FilterExpression("notIn", [column, values]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// Null Check Operators
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Is null operator - checks if column value is null.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* isNull(users.deletedAt) // deletedAt is null
|
|
356
|
+
*/
|
|
357
|
+
export function isNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {
|
|
358
|
+
return new FilterExpression("isNull", [column]);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Is not null operator - checks if column value is not null.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* isNotNull(users.email) // email is not null
|
|
366
|
+
*/
|
|
367
|
+
export function isNotNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {
|
|
368
|
+
return new FilterExpression("isNotNull", [column]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ============================================================================
|
|
372
|
+
// Logical Operators
|
|
373
|
+
// ============================================================================
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* AND operator - combines multiple filter expressions with logical AND.
|
|
377
|
+
* All expressions must be true for the record to match.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* and(
|
|
381
|
+
* eq(users.active, true),
|
|
382
|
+
* gt(users.age, 18)
|
|
383
|
+
* ) // active is true AND age > 18
|
|
384
|
+
*/
|
|
385
|
+
export function and(...expressions: FilterExpression[]): FilterExpression {
|
|
386
|
+
if (expressions.length === 0) {
|
|
387
|
+
throw new Error("AND operator requires at least one expression");
|
|
388
|
+
}
|
|
389
|
+
if (expressions.length === 1 && expressions[0] !== undefined) {
|
|
390
|
+
return expressions[0];
|
|
391
|
+
}
|
|
392
|
+
return new FilterExpression("and", expressions);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* OR operator - combines multiple filter expressions with logical OR.
|
|
397
|
+
* At least one expression must be true for the record to match.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* or(
|
|
401
|
+
* eq(users.role, "admin"),
|
|
402
|
+
* eq(users.role, "moderator")
|
|
403
|
+
* ) // role is "admin" OR "moderator"
|
|
404
|
+
*/
|
|
405
|
+
export function or(...expressions: FilterExpression[]): FilterExpression {
|
|
406
|
+
if (expressions.length === 0) {
|
|
407
|
+
throw new Error("OR operator requires at least one expression");
|
|
408
|
+
}
|
|
409
|
+
if (expressions.length === 1 && expressions[0] !== undefined) {
|
|
410
|
+
return expressions[0];
|
|
411
|
+
}
|
|
412
|
+
return new FilterExpression("or", expressions);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* NOT operator - negates a filter expression.
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* not(eq(users.status, "deleted")) // status is NOT "deleted"
|
|
420
|
+
*/
|
|
421
|
+
export function not(expression: FilterExpression): FilterExpression {
|
|
422
|
+
return new FilterExpression("not", [expression]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// OrderBy Operators
|
|
427
|
+
// ============================================================================
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* OrderByExpression represents a sort order specification for a column.
|
|
431
|
+
* Used in orderBy() clauses to provide type-safe sorting with direction.
|
|
432
|
+
*/
|
|
433
|
+
export class OrderByExpression<TableName extends string = string> {
|
|
434
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
435
|
+
readonly column: Column<any, any, TableName>;
|
|
436
|
+
readonly direction: "asc" | "desc";
|
|
437
|
+
|
|
438
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
439
|
+
constructor(column: Column<any, any, TableName>, direction: "asc" | "desc") {
|
|
440
|
+
this.column = column;
|
|
441
|
+
this.direction = direction;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Type guard to check if a value is an OrderByExpression instance.
|
|
447
|
+
*/
|
|
448
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type
|
|
449
|
+
export function isOrderByExpression(value: any): value is OrderByExpression {
|
|
450
|
+
return value instanceof OrderByExpression;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Ascending order operator - sorts a column in ascending order.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* asc(users.name) // Sort by name ascending
|
|
458
|
+
*/
|
|
459
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
460
|
+
export function asc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {
|
|
461
|
+
return new OrderByExpression(column, "asc");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Descending order operator - sorts a column in descending order.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* desc(users.age) // Sort by age descending
|
|
469
|
+
*/
|
|
470
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
471
|
+
export function desc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {
|
|
472
|
+
return new OrderByExpression(column, "desc");
|
|
473
|
+
}
|