@proofkit/fmodata 0.1.0-beta.25 → 0.1.0-beta.26
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/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +8 -3
- package/dist/esm/orm/column.d.ts +15 -0
- package/dist/esm/orm/column.js +30 -1
- package/dist/esm/orm/column.js.map +1 -1
- package/dist/esm/orm/index.d.ts +2 -2
- package/dist/esm/orm/operators.d.ts +29 -1
- package/dist/esm/orm/operators.js +23 -2
- package/dist/esm/orm/operators.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/orm/column.ts +51 -0
- package/src/orm/index.ts +5 -1
- package/src/orm/operators.ts +60 -2
package/dist/esm/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export type { Webhook, WebhookAddResponse, WebhookInfo, WebhookListResponse, } f
|
|
|
9
9
|
export type { FMODataErrorType } from './errors.js';
|
|
10
10
|
export { BatchTruncatedError, FMODataError, HTTPError, isBatchTruncatedError, isFMODataError, isHTTPError, isODataError, isRecordCountMismatchError, isResponseParseError, isResponseStructureError, isSchemaLockedError, isValidationError, ODataError, RecordCountMismatchError, ResponseParseError, ResponseStructureError, SchemaLockedError, ValidationError, } from './errors.js';
|
|
11
11
|
export type { Logger } from './logger.js';
|
|
12
|
-
export { and, asc, type Column, calcField, containerField, contains, dateField, desc, endsWith, eq, type FieldBuilder, type FilterExpression, FMTable, type FMTableWithColumns as TableOccurrenceResult, fmTableOccurrence, getTableColumns, gt, gte, type InferTableSchema, inArray, isColumn, isNotNull, isNull, lt, lte, ne, not, notInArray, numberField, type OrderByExpression, or, startsWith, textField, timeField, timestampField, } from './orm/index.js';
|
|
12
|
+
export { and, asc, type Column, type ColumnFunction, calcField, containerField, contains, dateField, desc, endsWith, eq, type FieldBuilder, type FilterExpression, FMTable, type FMTableWithColumns as TableOccurrenceResult, fmTableOccurrence, getTableColumns, gt, gte, type InferTableSchema, inArray, isColumn, isColumnFunction, isNotNull, isNull, lt, lte, matchesPattern, ne, not, notInArray, numberField, type OrderByExpression, or, startsWith, textField, timeField, timestampField, tolower, toupper, trim, } from './orm/index.js';
|
|
13
13
|
export type { BatchItemResult, BatchResult, ExecuteMethodOptions, ExecuteOptions, FetchHandler, InferSchemaType, Metadata, ODataRecordMetadata, Result, } from './types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AbortError, CircuitOpenError, NetworkError, RetryLimitError, TimeoutError } from "@fetchkit/ffetch";
|
|
2
2
|
import { FMServerConnection } from "./client/filemaker-odata.js";
|
|
3
3
|
import { BatchTruncatedError, FMODataError, HTTPError, ODataError, RecordCountMismatchError, ResponseParseError, ResponseStructureError, SchemaLockedError, ValidationError, isBatchTruncatedError, isFMODataError, isHTTPError, isODataError, isRecordCountMismatchError, isResponseParseError, isResponseStructureError, isSchemaLockedError, isValidationError } from "./errors.js";
|
|
4
|
-
import { isColumn } from "./orm/column.js";
|
|
4
|
+
import { isColumn, isColumnFunction } from "./orm/column.js";
|
|
5
5
|
import { calcField, containerField, dateField, numberField, textField, timeField, timestampField } from "./orm/field-builders.js";
|
|
6
|
-
import { and, asc, contains, desc, endsWith, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, not, notInArray, or, startsWith } from "./orm/operators.js";
|
|
6
|
+
import { and, asc, contains, desc, endsWith, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, matchesPattern, ne, not, notInArray, or, startsWith, tolower, toupper, trim } from "./orm/operators.js";
|
|
7
7
|
import { FMTable, fmTableOccurrence, getTableColumns } from "./orm/table.js";
|
|
8
8
|
export {
|
|
9
9
|
AbortError,
|
|
@@ -38,6 +38,7 @@ export {
|
|
|
38
38
|
inArray,
|
|
39
39
|
isBatchTruncatedError,
|
|
40
40
|
isColumn,
|
|
41
|
+
isColumnFunction,
|
|
41
42
|
isFMODataError,
|
|
42
43
|
isHTTPError,
|
|
43
44
|
isNotNull,
|
|
@@ -50,6 +51,7 @@ export {
|
|
|
50
51
|
isValidationError,
|
|
51
52
|
lt,
|
|
52
53
|
lte,
|
|
54
|
+
matchesPattern,
|
|
53
55
|
ne,
|
|
54
56
|
not,
|
|
55
57
|
notInArray,
|
|
@@ -58,6 +60,9 @@ export {
|
|
|
58
60
|
startsWith,
|
|
59
61
|
textField,
|
|
60
62
|
timeField,
|
|
61
|
-
timestampField
|
|
63
|
+
timestampField,
|
|
64
|
+
tolower,
|
|
65
|
+
toupper,
|
|
66
|
+
trim
|
|
62
67
|
};
|
|
63
68
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/orm/column.d.ts
CHANGED
|
@@ -48,6 +48,21 @@ export declare class Column<TOutput = any, TInput = TOutput, TableName extends s
|
|
|
48
48
|
* Type guard to check if a value is a Column instance.
|
|
49
49
|
*/
|
|
50
50
|
export declare function isColumn(value: any): value is Column<any, any, any, any>;
|
|
51
|
+
/**
|
|
52
|
+
* ColumnFunction wraps a Column with an OData string function (tolower, toupper, trim).
|
|
53
|
+
* Since it extends Column, it passes `isColumn()` checks and works with all existing operators.
|
|
54
|
+
* Supports nesting: `tolower(trim(col))` → `tolower(trim(name))`.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ColumnFunction<TOutput = any, TInput = TOutput, TableName extends string = string, IsContainer extends boolean = false> extends Column<TOutput, TInput, TableName, IsContainer> {
|
|
57
|
+
readonly fnName: string;
|
|
58
|
+
readonly innerColumn: Column<TOutput, TInput, TableName, IsContainer>;
|
|
59
|
+
constructor(fnName: string, innerColumn: Column<TOutput, TInput, TableName, IsContainer>);
|
|
60
|
+
toFilterString(useEntityIds?: boolean): string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Type guard to check if a value is a ColumnFunction instance.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isColumnFunction(value: any): value is ColumnFunction<any, any, any, any>;
|
|
51
66
|
/**
|
|
52
67
|
* Create a Column with proper type inference from the inputValidator.
|
|
53
68
|
* This helper ensures TypeScript can infer TInput from the validator's input type.
|
package/dist/esm/orm/column.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { needsFieldQuoting } from "../client/builders/select-utils.js";
|
|
4
5
|
class Column {
|
|
5
6
|
constructor(config) {
|
|
6
7
|
__publicField(this, "fieldName");
|
|
@@ -56,8 +57,36 @@ class Column {
|
|
|
56
57
|
function isColumn(value) {
|
|
57
58
|
return value instanceof Column;
|
|
58
59
|
}
|
|
60
|
+
class ColumnFunction extends Column {
|
|
61
|
+
constructor(fnName, innerColumn) {
|
|
62
|
+
super({
|
|
63
|
+
fieldName: innerColumn.fieldName,
|
|
64
|
+
entityId: innerColumn.entityId,
|
|
65
|
+
tableName: innerColumn.tableName,
|
|
66
|
+
tableEntityId: innerColumn.tableEntityId,
|
|
67
|
+
inputValidator: innerColumn.inputValidator
|
|
68
|
+
});
|
|
69
|
+
__publicField(this, "fnName");
|
|
70
|
+
__publicField(this, "innerColumn");
|
|
71
|
+
this.fnName = fnName;
|
|
72
|
+
this.innerColumn = innerColumn;
|
|
73
|
+
}
|
|
74
|
+
toFilterString(useEntityIds) {
|
|
75
|
+
if (isColumnFunction(this.innerColumn)) {
|
|
76
|
+
return `${this.fnName}(${this.innerColumn.toFilterString(useEntityIds)})`;
|
|
77
|
+
}
|
|
78
|
+
const fieldIdentifier = this.innerColumn.getFieldIdentifier(useEntityIds);
|
|
79
|
+
const quoted = needsFieldQuoting(fieldIdentifier) ? `"${fieldIdentifier}"` : fieldIdentifier;
|
|
80
|
+
return `${this.fnName}(${quoted})`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isColumnFunction(value) {
|
|
84
|
+
return value instanceof ColumnFunction;
|
|
85
|
+
}
|
|
59
86
|
export {
|
|
60
87
|
Column,
|
|
61
|
-
|
|
88
|
+
ColumnFunction,
|
|
89
|
+
isColumn,
|
|
90
|
+
isColumnFunction
|
|
62
91
|
};
|
|
63
92
|
//# sourceMappingURL=column.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column.js","sources":["../../../src/orm/column.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Column represents a type-safe reference to a table field.\n * Used in queries, filters, and operators to provide autocomplete and type checking.\n *\n * @template TOutput - The TypeScript type when reading from the database (output type)\n * @template TInput - The TypeScript type when writing to the database (input type, for filters)\n * @template TableName - The table name as a string literal type (for validation)\n * @template IsContainer - Whether this column represents a container field (cannot be selected)\n */\nexport class Column<\n // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\n TOutput = any,\n TInput = TOutput,\n TableName extends string = string,\n IsContainer extends boolean = false,\n> {\n readonly fieldName: string;\n readonly entityId?: `FMFID:${string}`;\n readonly tableName: TableName;\n readonly tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n readonly inputValidator?: StandardSchemaV1<TInput, any>;\n\n // Phantom types for TypeScript inference - never actually hold values\n readonly _phantomOutput!: TOutput;\n readonly _phantomInput!: TInput;\n readonly _isContainer!: IsContainer;\n\n constructor(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TableName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n }) {\n this.fieldName = config.fieldName;\n this.entityId = config.entityId;\n this.tableName = config.tableName;\n this.tableEntityId = config.tableEntityId;\n this.inputValidator = config.inputValidator;\n }\n\n /**\n * Get the field identifier (entity ID if available, otherwise field name).\n * Used when building OData queries.\n */\n getFieldIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.entityId) {\n return this.entityId;\n }\n return this.fieldName;\n }\n\n /**\n * Get the table identifier (entity ID if available, otherwise table name).\n * Used when building OData queries.\n */\n getTableIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.tableEntityId) {\n return this.tableEntityId;\n }\n return this.tableName;\n }\n\n /**\n * Check if this column is from a specific table.\n * Useful for validation in cross-table operations.\n */\n isFromTable(tableName: string): boolean {\n return this.tableName === tableName;\n }\n\n /**\n * Create a string representation for debugging.\n */\n toString(): string {\n return `${this.tableName}.${this.fieldName}`;\n }\n}\n\n/**\n * Type guard to check if a value is a Column instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type, generic constraint accepting any Column configuration\nexport function isColumn(value: any): value is Column<any, any, any, any> {\n return value instanceof Column;\n}\n\n/**\n * Create a Column with proper type inference from the inputValidator.\n * This helper ensures TypeScript can infer TInput from the validator's input type.\n * @internal\n */\nexport function createColumn<TOutput, TInput, TName extends string, IsContainer extends boolean = false>(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n}): Column<TOutput, TInput, TName, IsContainer> {\n return new Column(config) as Column<TOutput, TInput, TName, IsContainer>;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"column.js","sources":["../../../src/orm/column.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { needsFieldQuoting } from \"../client/builders/select-utils\";\n\n/**\n * Column represents a type-safe reference to a table field.\n * Used in queries, filters, and operators to provide autocomplete and type checking.\n *\n * @template TOutput - The TypeScript type when reading from the database (output type)\n * @template TInput - The TypeScript type when writing to the database (input type, for filters)\n * @template TableName - The table name as a string literal type (for validation)\n * @template IsContainer - Whether this column represents a container field (cannot be selected)\n */\nexport class Column<\n // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\n TOutput = any,\n TInput = TOutput,\n TableName extends string = string,\n IsContainer extends boolean = false,\n> {\n readonly fieldName: string;\n readonly entityId?: `FMFID:${string}`;\n readonly tableName: TableName;\n readonly tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n readonly inputValidator?: StandardSchemaV1<TInput, any>;\n\n // Phantom types for TypeScript inference - never actually hold values\n readonly _phantomOutput!: TOutput;\n readonly _phantomInput!: TInput;\n readonly _isContainer!: IsContainer;\n\n constructor(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TableName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n }) {\n this.fieldName = config.fieldName;\n this.entityId = config.entityId;\n this.tableName = config.tableName;\n this.tableEntityId = config.tableEntityId;\n this.inputValidator = config.inputValidator;\n }\n\n /**\n * Get the field identifier (entity ID if available, otherwise field name).\n * Used when building OData queries.\n */\n getFieldIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.entityId) {\n return this.entityId;\n }\n return this.fieldName;\n }\n\n /**\n * Get the table identifier (entity ID if available, otherwise table name).\n * Used when building OData queries.\n */\n getTableIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.tableEntityId) {\n return this.tableEntityId;\n }\n return this.tableName;\n }\n\n /**\n * Check if this column is from a specific table.\n * Useful for validation in cross-table operations.\n */\n isFromTable(tableName: string): boolean {\n return this.tableName === tableName;\n }\n\n /**\n * Create a string representation for debugging.\n */\n toString(): string {\n return `${this.tableName}.${this.fieldName}`;\n }\n}\n\n/**\n * Type guard to check if a value is a Column instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type, generic constraint accepting any Column configuration\nexport function isColumn(value: any): value is Column<any, any, any, any> {\n return value instanceof Column;\n}\n\n/**\n * ColumnFunction wraps a Column with an OData string function (tolower, toupper, trim).\n * Since it extends Column, it passes `isColumn()` checks and works with all existing operators.\n * Supports nesting: `tolower(trim(col))` → `tolower(trim(name))`.\n */\nexport class ColumnFunction<\n // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\n TOutput = any,\n TInput = TOutput,\n TableName extends string = string,\n IsContainer extends boolean = false,\n> extends Column<TOutput, TInput, TableName, IsContainer> {\n readonly fnName: string;\n readonly innerColumn: Column<TOutput, TInput, TableName, IsContainer>;\n\n constructor(\n fnName: string,\n innerColumn: Column<TOutput, TInput, TableName, IsContainer>,\n ) {\n super({\n fieldName: innerColumn.fieldName,\n entityId: innerColumn.entityId,\n tableName: innerColumn.tableName,\n tableEntityId: innerColumn.tableEntityId,\n inputValidator: innerColumn.inputValidator,\n });\n this.fnName = fnName;\n this.innerColumn = innerColumn;\n }\n\n toFilterString(useEntityIds?: boolean): string {\n if (isColumnFunction(this.innerColumn)) {\n return `${this.fnName}(${this.innerColumn.toFilterString(useEntityIds)})`;\n }\n const fieldIdentifier = this.innerColumn.getFieldIdentifier(useEntityIds);\n const quoted = needsFieldQuoting(fieldIdentifier)\n ? `\"${fieldIdentifier}\"`\n : fieldIdentifier;\n return `${this.fnName}(${quoted})`;\n }\n}\n\n/**\n * Type guard to check if a value is a ColumnFunction instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type\nexport function isColumnFunction(value: any): value is ColumnFunction<any, any, any, any> {\n return value instanceof ColumnFunction;\n}\n\n/**\n * Create a Column with proper type inference from the inputValidator.\n * This helper ensures TypeScript can infer TInput from the validator's input type.\n * @internal\n */\nexport function createColumn<TOutput, TInput, TName extends string, IsContainer extends boolean = false>(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n}): Column<TOutput, TInput, TName, IsContainer> {\n return new Column(config) as Column<TOutput, TInput, TName, IsContainer>;\n}\n"],"names":[],"mappings":";;;;AAYO,MAAM,OAMX;AAAA,EAaA,YAAY,QAOT;AAnBM;AACA;AACA;AACA;AAEA;AAAA;AAGA;AAAA;AACA;AACA;AAUP,SAAK,YAAY,OAAO;AACxB,SAAK,WAAW,OAAO;AACvB,SAAK,YAAY,OAAO;AACxB,SAAK,gBAAgB,OAAO;AAC5B,SAAK,iBAAiB,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,cAAgC;AACjD,QAAI,gBAAgB,KAAK,UAAU;AACjC,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,cAAgC;AACjD,QAAI,gBAAgB,KAAK,eAAe;AACtC,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,WAA4B;AACtC,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAmB;AACjB,WAAO,GAAG,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,EAC5C;AACF;AAMO,SAAS,SAAS,OAAiD;AACxE,SAAO,iBAAiB;AAC1B;AAOO,MAAM,uBAMH,OAAgD;AAAA,EAIxD,YACE,QACA,aACA;AACA,UAAM;AAAA,MACJ,WAAW,YAAY;AAAA,MACvB,UAAU,YAAY;AAAA,MACtB,WAAW,YAAY;AAAA,MACvB,eAAe,YAAY;AAAA,MAC3B,gBAAgB,YAAY;AAAA,IAAA,CAC7B;AAbM;AACA;AAaP,SAAK,SAAS;AACd,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,eAAe,cAAgC;AAC7C,QAAI,iBAAiB,KAAK,WAAW,GAAG;AACtC,aAAO,GAAG,KAAK,MAAM,IAAI,KAAK,YAAY,eAAe,YAAY,CAAC;AAAA,IACxE;AACA,UAAM,kBAAkB,KAAK,YAAY,mBAAmB,YAAY;AACxE,UAAM,SAAS,kBAAkB,eAAe,IAC5C,IAAI,eAAe,MACnB;AACJ,WAAO,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EACjC;AACF;AAMO,SAAS,iBAAiB,OAAyD;AACxF,SAAO,iBAAiB;AAC1B;"}
|
package/dist/esm/orm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** biome-ignore-all lint/performance/noBarrelFile: Re-exporting all ORM utilities */
|
|
2
|
-
export { Column, isColumn } from './column.js';
|
|
2
|
+
export { Column, ColumnFunction, isColumn, isColumnFunction } from './column.js';
|
|
3
3
|
export { type ContainerDbType, calcField, containerField, dateField, FieldBuilder, numberField, textField, timeField, timestampField, } from './field-builders.js';
|
|
4
|
-
export { and, asc, contains, desc, endsWith, eq, FilterExpression, gt, gte, inArray, isNotNull, isNull, isOrderByExpression, lt, lte, ne, not, notInArray, OrderByExpression, or, startsWith, } from './operators.js';
|
|
4
|
+
export { and, asc, contains, desc, endsWith, eq, FilterExpression, gt, gte, inArray, isNotNull, isNull, isOrderByExpression, lt, lte, matchesPattern, ne, not, notInArray, OrderByExpression, or, startsWith, tolower, toupper, trim, } from './operators.js';
|
|
5
5
|
export { FMTable, type FMTableWithColumns, fmTableOccurrence, getBaseTableConfig, getDefaultSelect, getFieldId, getFieldName, getTableColumns, getTableEntityId, getTableId, getTableName, type InferTableSchema, isUsingEntityIds, } from './table.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Column } from './column.js';
|
|
1
|
+
import { Column, ColumnFunction } from './column.js';
|
|
2
2
|
/**
|
|
3
3
|
* FilterExpression represents a filter condition that can be used in where() clauses.
|
|
4
4
|
* Internal representation of operator expressions that get converted to OData filter syntax.
|
|
@@ -87,6 +87,34 @@ export declare function startsWith<TOutput, TInput>(column: Column<TOutput, TInp
|
|
|
87
87
|
* endsWith(users.email, "@example.com") // email ends with "@example.com"
|
|
88
88
|
*/
|
|
89
89
|
export declare function endsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression;
|
|
90
|
+
/**
|
|
91
|
+
* Matches pattern operator - checks if a string column matches a regex pattern.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* matchesPattern(users.name, "^A.*e$") // name matches regex pattern
|
|
95
|
+
*/
|
|
96
|
+
export declare function matchesPattern<TOutput extends string | null, TInput>(column: Column<TOutput, TInput>, pattern: string): FilterExpression;
|
|
97
|
+
/**
|
|
98
|
+
* Wraps a column with OData `tolower()` for case-insensitive comparisons.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* eq(tolower(users.name), "john") // tolower(name) eq 'john'
|
|
102
|
+
*/
|
|
103
|
+
export declare function tolower<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(column: Column<TOutput, TInput, TableName, IsContainer>): ColumnFunction<TOutput, TInput, TableName, IsContainer>;
|
|
104
|
+
/**
|
|
105
|
+
* Wraps a column with OData `toupper()` for case-insensitive comparisons.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* eq(toupper(users.name), "JOHN") // toupper(name) eq 'JOHN'
|
|
109
|
+
*/
|
|
110
|
+
export declare function toupper<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(column: Column<TOutput, TInput, TableName, IsContainer>): ColumnFunction<TOutput, TInput, TableName, IsContainer>;
|
|
111
|
+
/**
|
|
112
|
+
* Wraps a column with OData `trim()` to remove leading/trailing whitespace.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* eq(trim(users.name), "John") // trim(name) eq 'John'
|
|
116
|
+
*/
|
|
117
|
+
export declare function trim<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(column: Column<TOutput, TInput, TableName, IsContainer>): ColumnFunction<TOutput, TInput, TableName, IsContainer>;
|
|
90
118
|
/**
|
|
91
119
|
* In array operator - checks if column value is in an array of values.
|
|
92
120
|
*
|
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { needsFieldQuoting } from "../client/builders/select-utils.js";
|
|
5
|
-
import { isColumn } from "./column.js";
|
|
5
|
+
import { ColumnFunction, isColumnFunction, isColumn } from "./column.js";
|
|
6
6
|
class FilterExpression {
|
|
7
7
|
// biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type
|
|
8
8
|
constructor(operator, operands) {
|
|
@@ -42,6 +42,8 @@ class FilterExpression {
|
|
|
42
42
|
return this._functionOp("startswith", useEntityIds);
|
|
43
43
|
case "endsWith":
|
|
44
44
|
return this._functionOp("endswith", useEntityIds);
|
|
45
|
+
case "matchesPattern":
|
|
46
|
+
return this._functionOp("matchesPattern", useEntityIds);
|
|
45
47
|
// Null checks
|
|
46
48
|
case "isNull":
|
|
47
49
|
return this._isNullOp(useEntityIds);
|
|
@@ -124,6 +126,9 @@ class FilterExpression {
|
|
|
124
126
|
throw new Error("NOT operator requires a FilterExpression operand");
|
|
125
127
|
}
|
|
126
128
|
_operandToString(operand, useEntityIds, column) {
|
|
129
|
+
if (isColumnFunction(operand)) {
|
|
130
|
+
return operand.toFilterString(useEntityIds);
|
|
131
|
+
}
|
|
127
132
|
if (isColumn(operand)) {
|
|
128
133
|
const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);
|
|
129
134
|
return needsFieldQuoting(fieldIdentifier) ? `"${fieldIdentifier}"` : fieldIdentifier;
|
|
@@ -182,6 +187,18 @@ function startsWith(column, value) {
|
|
|
182
187
|
function endsWith(column, value) {
|
|
183
188
|
return new FilterExpression("endsWith", [column, value]);
|
|
184
189
|
}
|
|
190
|
+
function matchesPattern(column, pattern) {
|
|
191
|
+
return new FilterExpression("matchesPattern", [column, pattern]);
|
|
192
|
+
}
|
|
193
|
+
function tolower(column) {
|
|
194
|
+
return new ColumnFunction("tolower", column);
|
|
195
|
+
}
|
|
196
|
+
function toupper(column) {
|
|
197
|
+
return new ColumnFunction("toupper", column);
|
|
198
|
+
}
|
|
199
|
+
function trim(column) {
|
|
200
|
+
return new ColumnFunction("trim", column);
|
|
201
|
+
}
|
|
185
202
|
function inArray(column, values) {
|
|
186
203
|
return new FilterExpression("in", [column, values]);
|
|
187
204
|
}
|
|
@@ -251,10 +268,14 @@ export {
|
|
|
251
268
|
isOrderByExpression,
|
|
252
269
|
lt,
|
|
253
270
|
lte,
|
|
271
|
+
matchesPattern,
|
|
254
272
|
ne,
|
|
255
273
|
not,
|
|
256
274
|
notInArray,
|
|
257
275
|
or,
|
|
258
|
-
startsWith
|
|
276
|
+
startsWith,
|
|
277
|
+
tolower,
|
|
278
|
+
toupper,
|
|
279
|
+
trim
|
|
259
280
|
};
|
|
260
281
|
//# sourceMappingURL=operators.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operators.js","sources":["../../../src/orm/operators.ts"],"sourcesContent":["import { needsFieldQuoting } from \"../client/builders/select-utils\";\nimport type { Column } from \"./column\";\nimport { isColumn } from \"./column\";\n\n/**\n * FilterExpression represents a filter condition that can be used in where() clauses.\n * Internal representation of operator expressions that get converted to OData filter syntax.\n */\nexport class FilterExpression {\n readonly operator: string;\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n readonly operands: (Column | any | FilterExpression)[];\n\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n constructor(operator: string, operands: (Column | any | FilterExpression)[]) {\n this.operator = operator;\n this.operands = operands;\n }\n\n /**\n * Convert this expression to OData filter syntax.\n * @internal Used by QueryBuilder\n */\n toODataFilter(useEntityIds?: boolean): string {\n switch (this.operator) {\n // Comparison operators\n case \"eq\":\n return this._binaryOp(\"eq\", useEntityIds);\n case \"ne\":\n return this._binaryOp(\"ne\", useEntityIds);\n case \"gt\":\n return this._binaryOp(\"gt\", useEntityIds);\n case \"gte\":\n return this._binaryOp(\"ge\", useEntityIds);\n case \"lt\":\n return this._binaryOp(\"lt\", useEntityIds);\n case \"lte\":\n return this._binaryOp(\"le\", useEntityIds);\n case \"in\":\n return this._inOp(useEntityIds);\n case \"notIn\":\n return this._notInOp(useEntityIds);\n\n // String operators\n case \"contains\":\n return this._functionOp(\"contains\", useEntityIds);\n case \"startsWith\":\n return this._functionOp(\"startswith\", useEntityIds);\n case \"endsWith\":\n return this._functionOp(\"endswith\", useEntityIds);\n\n // Null checks\n case \"isNull\":\n return this._isNullOp(useEntityIds);\n case \"isNotNull\":\n return this._isNotNullOp(useEntityIds);\n\n // Logical operators\n case \"and\":\n return this._logicalOp(\"and\", useEntityIds);\n case \"or\":\n return this._logicalOp(\"or\", useEntityIds);\n case \"not\":\n return this._notOp(useEntityIds);\n\n default:\n throw new Error(`Unknown operator: ${this.operator}`);\n }\n }\n\n private _binaryOp(op: string, useEntityIds?: boolean): string {\n const [left, right] = this.operands;\n // For binary ops, the column is typically the first operand and value is the second\n // But we also support column-to-column comparisons, so check both\n let columnForValue: typeof left | typeof right | undefined;\n if (isColumn(left) && !isColumn(right)) {\n columnForValue = left;\n } else if (isColumn(right) && !isColumn(left)) {\n columnForValue = right;\n } else {\n columnForValue = undefined;\n }\n const leftStr = this._operandToString(left, useEntityIds, columnForValue);\n const rightStr = this._operandToString(right, useEntityIds, columnForValue);\n return `${leftStr} ${op} ${rightStr}`;\n }\n\n private _functionOp(fnName: string, useEntityIds?: boolean): string {\n const [column, value] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valueStr = this._operandToString(value, useEntityIds, columnInstance);\n return `${fnName}(${columnStr}, ${valueStr})`;\n }\n\n private _inOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(\", \");\n return `${columnStr} in (${valuesStr})`;\n }\n\n private _notInOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(\", \");\n return `not (${columnStr} in (${valuesStr}))`;\n }\n\n private _isNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} eq null`;\n }\n\n private _isNotNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} ne null`;\n }\n\n private _logicalOp(op: string, useEntityIds?: boolean): string {\n const expressions = this.operands.map((expr) => {\n if (expr instanceof FilterExpression) {\n const innerExpr = expr.toODataFilter(useEntityIds);\n // Wrap in parens if it's a logical expression to ensure precedence\n if (expr.operator === \"and\" || expr.operator === \"or\") {\n return `(${innerExpr})`;\n }\n return innerExpr;\n }\n throw new Error(\"Logical operators require FilterExpression operands\");\n });\n return expressions.join(` ${op} `);\n }\n\n private _notOp(useEntityIds?: boolean): string {\n const [expr] = this.operands;\n if (expr instanceof FilterExpression) {\n return `not (${expr.toODataFilter(useEntityIds)})`;\n }\n throw new Error(\"NOT operator requires a FilterExpression operand\");\n }\n\n private _operandToString(\n // biome-ignore lint/suspicious/noExplicitAny: Operand can be Column, FilterExpression, or any value type\n operand: any,\n useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n column?: Column<any, any, any, any>,\n ): string {\n if (isColumn(operand)) {\n const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);\n // Quote field names in OData filters per FileMaker OData API requirements\n return needsFieldQuoting(fieldIdentifier) ? `\"${fieldIdentifier}\"` : fieldIdentifier;\n }\n\n // If we have a column with an input validator, apply it to transform the value\n let value = operand;\n if (column?.inputValidator) {\n try {\n const result = column.inputValidator[\"~standard\"].validate(value);\n // Handle async validators (though they shouldn't be async for filters)\n if (result instanceof Promise) {\n // For filters, we can't use async validators, so skip transformation\n // This is a limitation - async validators won't work in filters\n value = operand;\n } else if (\"issues\" in result && result.issues) {\n // Validation failed, use original value\n value = operand;\n } else if (\"value\" in result) {\n // Validation succeeded, use transformed value\n value = result.value;\n }\n } catch (_error) {\n // If validation throws, use the original value (will likely cause a query error)\n // This maintains backward compatibility and allows the server to handle validation\n value = operand;\n }\n }\n\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`; // Escape single quotes\n }\n if (value === null || value === undefined) {\n return \"null\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n return String(value);\n }\n}\n\n// ============================================================================\n// Comparison Operators\n// ============================================================================\n\n/**\n * Equal operator - checks if column equals a value or another column.\n *\n * @example\n * eq(users.name, \"John\") // name equals \"John\"\n * eq(users.id, contacts.id_user) // cross-table comparison\n */\nexport function eq<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\nexport function eq(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"eq\", [column, value]);\n}\n\n/**\n * Not equal operator - checks if column does not equal a value or another column.\n *\n * @example\n * ne(users.status, \"inactive\") // status not equal to \"inactive\"\n * ne(users.id, contacts.id_user) // cross-table comparison\n */\nexport function ne<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\nexport function ne(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"ne\", [column, value]);\n}\n\n/**\n * Greater than operator - checks if column is greater than a value.\n *\n * @example\n * gt(users.age, 18) // age greater than 18\n */\nexport function gt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gt\", [column, value]);\n}\n\n/**\n * Greater than or equal operator - checks if column is >= a value.\n *\n * @example\n * gte(users.age, 18) // age >= 18\n */\nexport function gte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gte\", [column, value]);\n}\n\n/**\n * Less than operator - checks if column is less than a value.\n *\n * @example\n * lt(users.age, 65) // age less than 65\n */\nexport function lt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lt\", [column, value]);\n}\n\n/**\n * Less than or equal operator - checks if column is <= a value.\n *\n * @example\n * lte(users.age, 65) // age <= 65\n */\nexport function lte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lte\", [column, value]);\n}\n\n// ============================================================================\n// String Operators\n// ============================================================================\n\n/**\n * Contains operator - checks if a string column contains a substring.\n *\n * @example\n * contains(users.name, \"John\") // name contains \"John\"\n */\nexport function contains<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"contains\", [column, value]);\n}\n\n/**\n * Starts with operator - checks if a string column starts with a prefix.\n *\n * @example\n * startsWith(users.email, \"admin\") // email starts with \"admin\"\n */\nexport function startsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"startsWith\", [column, value]);\n}\n\n/**\n * Ends with operator - checks if a string column ends with a suffix.\n *\n * @example\n * endsWith(users.email, \"@example.com\") // email ends with \"@example.com\"\n */\nexport function endsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"endsWith\", [column, value]);\n}\n\n// ============================================================================\n// Array Operators\n// ============================================================================\n\n/**\n * In array operator - checks if column value is in an array of values.\n *\n * @example\n * inArray(users.status, [\"active\", \"pending\"]) // status is \"active\" or \"pending\"\n */\nexport function inArray<TOutput, TInput>(column: Column<TOutput, TInput>, values: NoInfer<TInput>[]): FilterExpression {\n return new FilterExpression(\"in\", [column, values]);\n}\n\n/**\n * Not in array operator - checks if column value is not in an array of values.\n *\n * @example\n * notInArray(users.status, [\"deleted\", \"banned\"]) // status is neither \"deleted\" nor \"banned\"\n */\nexport function notInArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"notIn\", [column, values]);\n}\n\n// ============================================================================\n// Null Check Operators\n// ============================================================================\n\n/**\n * Is null operator - checks if column value is null.\n *\n * @example\n * isNull(users.deletedAt) // deletedAt is null\n */\nexport function isNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {\n return new FilterExpression(\"isNull\", [column]);\n}\n\n/**\n * Is not null operator - checks if column value is not null.\n *\n * @example\n * isNotNull(users.email) // email is not null\n */\nexport function isNotNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {\n return new FilterExpression(\"isNotNull\", [column]);\n}\n\n// ============================================================================\n// Logical Operators\n// ============================================================================\n\n/**\n * AND operator - combines multiple filter expressions with logical AND.\n * All expressions must be true for the record to match.\n *\n * @example\n * and(\n * eq(users.active, true),\n * gt(users.age, 18)\n * ) // active is true AND age > 18\n */\nexport function and(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"AND operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"and\", expressions);\n}\n\n/**\n * OR operator - combines multiple filter expressions with logical OR.\n * At least one expression must be true for the record to match.\n *\n * @example\n * or(\n * eq(users.role, \"admin\"),\n * eq(users.role, \"moderator\")\n * ) // role is \"admin\" OR \"moderator\"\n */\nexport function or(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"OR operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"or\", expressions);\n}\n\n/**\n * NOT operator - negates a filter expression.\n *\n * @example\n * not(eq(users.status, \"deleted\")) // status is NOT \"deleted\"\n */\nexport function not(expression: FilterExpression): FilterExpression {\n return new FilterExpression(\"not\", [expression]);\n}\n\n// ============================================================================\n// OrderBy Operators\n// ============================================================================\n\n/**\n * OrderByExpression represents a sort order specification for a column.\n * Used in orderBy() clauses to provide type-safe sorting with direction.\n */\nexport class OrderByExpression<TableName extends string = string> {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n readonly column: Column<any, any, TableName>;\n readonly direction: \"asc\" | \"desc\";\n\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n constructor(column: Column<any, any, TableName>, direction: \"asc\" | \"desc\") {\n this.column = column;\n this.direction = direction;\n }\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type\nexport function isOrderByExpression(value: any): value is OrderByExpression {\n return value instanceof OrderByExpression;\n}\n\n/**\n * Ascending order operator - sorts a column in ascending order.\n *\n * @example\n * asc(users.name) // Sort by name ascending\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function asc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"asc\");\n}\n\n/**\n * Descending order operator - sorts a column in descending order.\n *\n * @example\n * desc(users.age) // Sort by age descending\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function desc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"desc\");\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"operators.js","sources":["../../../src/orm/operators.ts"],"sourcesContent":["import { needsFieldQuoting } from \"../client/builders/select-utils\";\nimport { type Column, ColumnFunction, isColumn, isColumnFunction } from \"./column\";\n\n/**\n * FilterExpression represents a filter condition that can be used in where() clauses.\n * Internal representation of operator expressions that get converted to OData filter syntax.\n */\nexport class FilterExpression {\n readonly operator: string;\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n readonly operands: (Column | any | FilterExpression)[];\n\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n constructor(operator: string, operands: (Column | any | FilterExpression)[]) {\n this.operator = operator;\n this.operands = operands;\n }\n\n /**\n * Convert this expression to OData filter syntax.\n * @internal Used by QueryBuilder\n */\n toODataFilter(useEntityIds?: boolean): string {\n switch (this.operator) {\n // Comparison operators\n case \"eq\":\n return this._binaryOp(\"eq\", useEntityIds);\n case \"ne\":\n return this._binaryOp(\"ne\", useEntityIds);\n case \"gt\":\n return this._binaryOp(\"gt\", useEntityIds);\n case \"gte\":\n return this._binaryOp(\"ge\", useEntityIds);\n case \"lt\":\n return this._binaryOp(\"lt\", useEntityIds);\n case \"lte\":\n return this._binaryOp(\"le\", useEntityIds);\n case \"in\":\n return this._inOp(useEntityIds);\n case \"notIn\":\n return this._notInOp(useEntityIds);\n\n // String operators\n case \"contains\":\n return this._functionOp(\"contains\", useEntityIds);\n case \"startsWith\":\n return this._functionOp(\"startswith\", useEntityIds);\n case \"endsWith\":\n return this._functionOp(\"endswith\", useEntityIds);\n case \"matchesPattern\":\n return this._functionOp(\"matchesPattern\", useEntityIds);\n\n // Null checks\n case \"isNull\":\n return this._isNullOp(useEntityIds);\n case \"isNotNull\":\n return this._isNotNullOp(useEntityIds);\n\n // Logical operators\n case \"and\":\n return this._logicalOp(\"and\", useEntityIds);\n case \"or\":\n return this._logicalOp(\"or\", useEntityIds);\n case \"not\":\n return this._notOp(useEntityIds);\n\n default:\n throw new Error(`Unknown operator: ${this.operator}`);\n }\n }\n\n private _binaryOp(op: string, useEntityIds?: boolean): string {\n const [left, right] = this.operands;\n // For binary ops, the column is typically the first operand and value is the second\n // But we also support column-to-column comparisons, so check both\n let columnForValue: typeof left | typeof right | undefined;\n if (isColumn(left) && !isColumn(right)) {\n columnForValue = left;\n } else if (isColumn(right) && !isColumn(left)) {\n columnForValue = right;\n } else {\n columnForValue = undefined;\n }\n const leftStr = this._operandToString(left, useEntityIds, columnForValue);\n const rightStr = this._operandToString(right, useEntityIds, columnForValue);\n return `${leftStr} ${op} ${rightStr}`;\n }\n\n private _functionOp(fnName: string, useEntityIds?: boolean): string {\n const [column, value] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valueStr = this._operandToString(value, useEntityIds, columnInstance);\n return `${fnName}(${columnStr}, ${valueStr})`;\n }\n\n private _inOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(\", \");\n return `${columnStr} in (${valuesStr})`;\n }\n\n private _notInOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).join(\", \");\n return `not (${columnStr} in (${valuesStr}))`;\n }\n\n private _isNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} eq null`;\n }\n\n private _isNotNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} ne null`;\n }\n\n private _logicalOp(op: string, useEntityIds?: boolean): string {\n const expressions = this.operands.map((expr) => {\n if (expr instanceof FilterExpression) {\n const innerExpr = expr.toODataFilter(useEntityIds);\n // Wrap in parens if it's a logical expression to ensure precedence\n if (expr.operator === \"and\" || expr.operator === \"or\") {\n return `(${innerExpr})`;\n }\n return innerExpr;\n }\n throw new Error(\"Logical operators require FilterExpression operands\");\n });\n return expressions.join(` ${op} `);\n }\n\n private _notOp(useEntityIds?: boolean): string {\n const [expr] = this.operands;\n if (expr instanceof FilterExpression) {\n return `not (${expr.toODataFilter(useEntityIds)})`;\n }\n throw new Error(\"NOT operator requires a FilterExpression operand\");\n }\n\n private _operandToString(\n // biome-ignore lint/suspicious/noExplicitAny: Operand can be Column, FilterExpression, or any value type\n operand: any,\n useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n column?: Column<any, any, any, any>,\n ): string {\n if (isColumnFunction(operand)) {\n return operand.toFilterString(useEntityIds);\n }\n\n if (isColumn(operand)) {\n const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);\n // Quote field names in OData filters per FileMaker OData API requirements\n return needsFieldQuoting(fieldIdentifier) ? `\"${fieldIdentifier}\"` : fieldIdentifier;\n }\n\n // If we have a column with an input validator, apply it to transform the value\n let value = operand;\n if (column?.inputValidator) {\n try {\n const result = column.inputValidator[\"~standard\"].validate(value);\n // Handle async validators (though they shouldn't be async for filters)\n if (result instanceof Promise) {\n // For filters, we can't use async validators, so skip transformation\n // This is a limitation - async validators won't work in filters\n value = operand;\n } else if (\"issues\" in result && result.issues) {\n // Validation failed, use original value\n value = operand;\n } else if (\"value\" in result) {\n // Validation succeeded, use transformed value\n value = result.value;\n }\n } catch (_error) {\n // If validation throws, use the original value (will likely cause a query error)\n // This maintains backward compatibility and allows the server to handle validation\n value = operand;\n }\n }\n\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`; // Escape single quotes\n }\n if (value === null || value === undefined) {\n return \"null\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n return String(value);\n }\n}\n\n// ============================================================================\n// Comparison Operators\n// ============================================================================\n\n/**\n * Equal operator - checks if column equals a value or another column.\n *\n * @example\n * eq(users.name, \"John\") // name equals \"John\"\n * eq(users.id, contacts.id_user) // cross-table comparison\n */\nexport function eq<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\nexport function eq(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"eq\", [column, value]);\n}\n\n/**\n * Not equal operator - checks if column does not equal a value or another column.\n *\n * @example\n * ne(users.status, \"inactive\") // status not equal to \"inactive\"\n * ne(users.id, contacts.id_user) // cross-table comparison\n */\nexport function ne<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\nexport function ne(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"ne\", [column, value]);\n}\n\n/**\n * Greater than operator - checks if column is greater than a value.\n *\n * @example\n * gt(users.age, 18) // age greater than 18\n */\nexport function gt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gt\", [column, value]);\n}\n\n/**\n * Greater than or equal operator - checks if column is >= a value.\n *\n * @example\n * gte(users.age, 18) // age >= 18\n */\nexport function gte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gte\", [column, value]);\n}\n\n/**\n * Less than operator - checks if column is less than a value.\n *\n * @example\n * lt(users.age, 65) // age less than 65\n */\nexport function lt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lt\", [column, value]);\n}\n\n/**\n * Less than or equal operator - checks if column is <= a value.\n *\n * @example\n * lte(users.age, 65) // age <= 65\n */\nexport function lte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lte\", [column, value]);\n}\n\n// ============================================================================\n// String Operators\n// ============================================================================\n\n/**\n * Contains operator - checks if a string column contains a substring.\n *\n * @example\n * contains(users.name, \"John\") // name contains \"John\"\n */\nexport function contains<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"contains\", [column, value]);\n}\n\n/**\n * Starts with operator - checks if a string column starts with a prefix.\n *\n * @example\n * startsWith(users.email, \"admin\") // email starts with \"admin\"\n */\nexport function startsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"startsWith\", [column, value]);\n}\n\n/**\n * Ends with operator - checks if a string column ends with a suffix.\n *\n * @example\n * endsWith(users.email, \"@example.com\") // email ends with \"@example.com\"\n */\nexport function endsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): FilterExpression {\n return new FilterExpression(\"endsWith\", [column, value]);\n}\n\n/**\n * Matches pattern operator - checks if a string column matches a regex pattern.\n *\n * @example\n * matchesPattern(users.name, \"^A.*e$\") // name matches regex pattern\n */\nexport function matchesPattern<TOutput extends string | null, TInput>(\n column: Column<TOutput, TInput>,\n pattern: string,\n): FilterExpression {\n return new FilterExpression(\"matchesPattern\", [column, pattern]);\n}\n\n// ============================================================================\n// String Transform Functions\n// ============================================================================\n\n/**\n * Wraps a column with OData `tolower()` for case-insensitive comparisons.\n *\n * @example\n * eq(tolower(users.name), \"john\") // tolower(name) eq 'john'\n */\nexport function tolower<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(\n column: Column<TOutput, TInput, TableName, IsContainer>,\n): ColumnFunction<TOutput, TInput, TableName, IsContainer> {\n return new ColumnFunction(\"tolower\", column);\n}\n\n/**\n * Wraps a column with OData `toupper()` for case-insensitive comparisons.\n *\n * @example\n * eq(toupper(users.name), \"JOHN\") // toupper(name) eq 'JOHN'\n */\nexport function toupper<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(\n column: Column<TOutput, TInput, TableName, IsContainer>,\n): ColumnFunction<TOutput, TInput, TableName, IsContainer> {\n return new ColumnFunction(\"toupper\", column);\n}\n\n/**\n * Wraps a column with OData `trim()` to remove leading/trailing whitespace.\n *\n * @example\n * eq(trim(users.name), \"John\") // trim(name) eq 'John'\n */\nexport function trim<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(\n column: Column<TOutput, TInput, TableName, IsContainer>,\n): ColumnFunction<TOutput, TInput, TableName, IsContainer> {\n return new ColumnFunction(\"trim\", column);\n}\n\n// ============================================================================\n// Array Operators\n// ============================================================================\n\n/**\n * In array operator - checks if column value is in an array of values.\n *\n * @example\n * inArray(users.status, [\"active\", \"pending\"]) // status is \"active\" or \"pending\"\n */\nexport function inArray<TOutput, TInput>(column: Column<TOutput, TInput>, values: NoInfer<TInput>[]): FilterExpression {\n return new FilterExpression(\"in\", [column, values]);\n}\n\n/**\n * Not in array operator - checks if column value is not in an array of values.\n *\n * @example\n * notInArray(users.status, [\"deleted\", \"banned\"]) // status is neither \"deleted\" nor \"banned\"\n */\nexport function notInArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"notIn\", [column, values]);\n}\n\n// ============================================================================\n// Null Check Operators\n// ============================================================================\n\n/**\n * Is null operator - checks if column value is null.\n *\n * @example\n * isNull(users.deletedAt) // deletedAt is null\n */\nexport function isNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {\n return new FilterExpression(\"isNull\", [column]);\n}\n\n/**\n * Is not null operator - checks if column value is not null.\n *\n * @example\n * isNotNull(users.email) // email is not null\n */\nexport function isNotNull<TOutput, TInput>(column: Column<TOutput, TInput>): FilterExpression {\n return new FilterExpression(\"isNotNull\", [column]);\n}\n\n// ============================================================================\n// Logical Operators\n// ============================================================================\n\n/**\n * AND operator - combines multiple filter expressions with logical AND.\n * All expressions must be true for the record to match.\n *\n * @example\n * and(\n * eq(users.active, true),\n * gt(users.age, 18)\n * ) // active is true AND age > 18\n */\nexport function and(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"AND operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"and\", expressions);\n}\n\n/**\n * OR operator - combines multiple filter expressions with logical OR.\n * At least one expression must be true for the record to match.\n *\n * @example\n * or(\n * eq(users.role, \"admin\"),\n * eq(users.role, \"moderator\")\n * ) // role is \"admin\" OR \"moderator\"\n */\nexport function or(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"OR operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"or\", expressions);\n}\n\n/**\n * NOT operator - negates a filter expression.\n *\n * @example\n * not(eq(users.status, \"deleted\")) // status is NOT \"deleted\"\n */\nexport function not(expression: FilterExpression): FilterExpression {\n return new FilterExpression(\"not\", [expression]);\n}\n\n// ============================================================================\n// OrderBy Operators\n// ============================================================================\n\n/**\n * OrderByExpression represents a sort order specification for a column.\n * Used in orderBy() clauses to provide type-safe sorting with direction.\n */\nexport class OrderByExpression<TableName extends string = string> {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n readonly column: Column<any, any, TableName>;\n readonly direction: \"asc\" | \"desc\";\n\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n constructor(column: Column<any, any, TableName>, direction: \"asc\" | \"desc\") {\n this.column = column;\n this.direction = direction;\n }\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type\nexport function isOrderByExpression(value: any): value is OrderByExpression {\n return value instanceof OrderByExpression;\n}\n\n/**\n * Ascending order operator - sorts a column in ascending order.\n *\n * @example\n * asc(users.name) // Sort by name ascending\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function asc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"asc\");\n}\n\n/**\n * Descending order operator - sorts a column in descending order.\n *\n * @example\n * desc(users.age) // Sort by age descending\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function desc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"desc\");\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export {
|
|
|
61
61
|
asc,
|
|
62
62
|
// Column references
|
|
63
63
|
type Column,
|
|
64
|
+
type ColumnFunction,
|
|
64
65
|
calcField,
|
|
65
66
|
containerField,
|
|
66
67
|
contains,
|
|
@@ -88,10 +89,12 @@ export {
|
|
|
88
89
|
type InferTableSchema,
|
|
89
90
|
inArray,
|
|
90
91
|
isColumn,
|
|
92
|
+
isColumnFunction,
|
|
91
93
|
isNotNull,
|
|
92
94
|
isNull,
|
|
93
95
|
lt,
|
|
94
96
|
lte,
|
|
97
|
+
matchesPattern,
|
|
95
98
|
ne,
|
|
96
99
|
not,
|
|
97
100
|
notInArray,
|
|
@@ -104,6 +107,9 @@ export {
|
|
|
104
107
|
textField,
|
|
105
108
|
timeField,
|
|
106
109
|
timestampField,
|
|
110
|
+
tolower,
|
|
111
|
+
toupper,
|
|
112
|
+
trim,
|
|
107
113
|
} from "./orm/index";
|
|
108
114
|
// Utility types for type annotations
|
|
109
115
|
export type {
|
package/src/orm/column.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { needsFieldQuoting } from "../client/builders/select-utils";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Column represents a type-safe reference to a table field.
|
|
@@ -89,6 +90,56 @@ export function isColumn(value: any): value is Column<any, any, any, any> {
|
|
|
89
90
|
return value instanceof Column;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/**
|
|
94
|
+
* ColumnFunction wraps a Column with an OData string function (tolower, toupper, trim).
|
|
95
|
+
* Since it extends Column, it passes `isColumn()` checks and works with all existing operators.
|
|
96
|
+
* Supports nesting: `tolower(trim(col))` → `tolower(trim(name))`.
|
|
97
|
+
*/
|
|
98
|
+
export class ColumnFunction<
|
|
99
|
+
// biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility
|
|
100
|
+
TOutput = any,
|
|
101
|
+
TInput = TOutput,
|
|
102
|
+
TableName extends string = string,
|
|
103
|
+
IsContainer extends boolean = false,
|
|
104
|
+
> extends Column<TOutput, TInput, TableName, IsContainer> {
|
|
105
|
+
readonly fnName: string;
|
|
106
|
+
readonly innerColumn: Column<TOutput, TInput, TableName, IsContainer>;
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
fnName: string,
|
|
110
|
+
innerColumn: Column<TOutput, TInput, TableName, IsContainer>,
|
|
111
|
+
) {
|
|
112
|
+
super({
|
|
113
|
+
fieldName: innerColumn.fieldName,
|
|
114
|
+
entityId: innerColumn.entityId,
|
|
115
|
+
tableName: innerColumn.tableName,
|
|
116
|
+
tableEntityId: innerColumn.tableEntityId,
|
|
117
|
+
inputValidator: innerColumn.inputValidator,
|
|
118
|
+
});
|
|
119
|
+
this.fnName = fnName;
|
|
120
|
+
this.innerColumn = innerColumn;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
toFilterString(useEntityIds?: boolean): string {
|
|
124
|
+
if (isColumnFunction(this.innerColumn)) {
|
|
125
|
+
return `${this.fnName}(${this.innerColumn.toFilterString(useEntityIds)})`;
|
|
126
|
+
}
|
|
127
|
+
const fieldIdentifier = this.innerColumn.getFieldIdentifier(useEntityIds);
|
|
128
|
+
const quoted = needsFieldQuoting(fieldIdentifier)
|
|
129
|
+
? `"${fieldIdentifier}"`
|
|
130
|
+
: fieldIdentifier;
|
|
131
|
+
return `${this.fnName}(${quoted})`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Type guard to check if a value is a ColumnFunction instance.
|
|
137
|
+
*/
|
|
138
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type
|
|
139
|
+
export function isColumnFunction(value: any): value is ColumnFunction<any, any, any, any> {
|
|
140
|
+
return value instanceof ColumnFunction;
|
|
141
|
+
}
|
|
142
|
+
|
|
92
143
|
/**
|
|
93
144
|
* Create a Column with proper type inference from the inputValidator.
|
|
94
145
|
* This helper ensures TypeScript can infer TInput from the validator's input type.
|
package/src/orm/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Field builders - main API for defining table schemas
|
|
3
3
|
|
|
4
4
|
// Column references - used in queries and filters
|
|
5
|
-
export { Column, isColumn } from "./column";
|
|
5
|
+
export { Column, ColumnFunction, isColumn, isColumnFunction } from "./column";
|
|
6
6
|
export {
|
|
7
7
|
type ContainerDbType,
|
|
8
8
|
calcField,
|
|
@@ -32,6 +32,7 @@ export {
|
|
|
32
32
|
isOrderByExpression,
|
|
33
33
|
lt,
|
|
34
34
|
lte,
|
|
35
|
+
matchesPattern,
|
|
35
36
|
ne,
|
|
36
37
|
not,
|
|
37
38
|
notInArray,
|
|
@@ -39,6 +40,9 @@ export {
|
|
|
39
40
|
OrderByExpression,
|
|
40
41
|
or,
|
|
41
42
|
startsWith,
|
|
43
|
+
tolower,
|
|
44
|
+
toupper,
|
|
45
|
+
trim,
|
|
42
46
|
} from "./operators";
|
|
43
47
|
|
|
44
48
|
// Table definition - fmTableOccurrence function
|
package/src/orm/operators.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { needsFieldQuoting } from "../client/builders/select-utils";
|
|
2
|
-
import type
|
|
3
|
-
import { isColumn } from "./column";
|
|
2
|
+
import { type Column, ColumnFunction, isColumn, isColumnFunction } from "./column";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* FilterExpression represents a filter condition that can be used in where() clauses.
|
|
@@ -48,6 +47,8 @@ export class FilterExpression {
|
|
|
48
47
|
return this._functionOp("startswith", useEntityIds);
|
|
49
48
|
case "endsWith":
|
|
50
49
|
return this._functionOp("endswith", useEntityIds);
|
|
50
|
+
case "matchesPattern":
|
|
51
|
+
return this._functionOp("matchesPattern", useEntityIds);
|
|
51
52
|
|
|
52
53
|
// Null checks
|
|
53
54
|
case "isNull":
|
|
@@ -152,6 +153,10 @@ export class FilterExpression {
|
|
|
152
153
|
useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration
|
|
153
154
|
column?: Column<any, any, any, any>,
|
|
154
155
|
): string {
|
|
156
|
+
if (isColumnFunction(operand)) {
|
|
157
|
+
return operand.toFilterString(useEntityIds);
|
|
158
|
+
}
|
|
159
|
+
|
|
155
160
|
if (isColumn(operand)) {
|
|
156
161
|
const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);
|
|
157
162
|
// Quote field names in OData filters per FileMaker OData API requirements
|
|
@@ -317,6 +322,59 @@ export function endsWith<TOutput, TInput>(column: Column<TOutput, TInput>, value
|
|
|
317
322
|
return new FilterExpression("endsWith", [column, value]);
|
|
318
323
|
}
|
|
319
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Matches pattern operator - checks if a string column matches a regex pattern.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* matchesPattern(users.name, "^A.*e$") // name matches regex pattern
|
|
330
|
+
*/
|
|
331
|
+
export function matchesPattern<TOutput extends string | null, TInput>(
|
|
332
|
+
column: Column<TOutput, TInput>,
|
|
333
|
+
pattern: string,
|
|
334
|
+
): FilterExpression {
|
|
335
|
+
return new FilterExpression("matchesPattern", [column, pattern]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// String Transform Functions
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Wraps a column with OData `tolower()` for case-insensitive comparisons.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* eq(tolower(users.name), "john") // tolower(name) eq 'john'
|
|
347
|
+
*/
|
|
348
|
+
export function tolower<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(
|
|
349
|
+
column: Column<TOutput, TInput, TableName, IsContainer>,
|
|
350
|
+
): ColumnFunction<TOutput, TInput, TableName, IsContainer> {
|
|
351
|
+
return new ColumnFunction("tolower", column);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Wraps a column with OData `toupper()` for case-insensitive comparisons.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* eq(toupper(users.name), "JOHN") // toupper(name) eq 'JOHN'
|
|
359
|
+
*/
|
|
360
|
+
export function toupper<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(
|
|
361
|
+
column: Column<TOutput, TInput, TableName, IsContainer>,
|
|
362
|
+
): ColumnFunction<TOutput, TInput, TableName, IsContainer> {
|
|
363
|
+
return new ColumnFunction("toupper", column);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Wraps a column with OData `trim()` to remove leading/trailing whitespace.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* eq(trim(users.name), "John") // trim(name) eq 'John'
|
|
371
|
+
*/
|
|
372
|
+
export function trim<TOutput extends string | null, TInput, TableName extends string, IsContainer extends boolean>(
|
|
373
|
+
column: Column<TOutput, TInput, TableName, IsContainer>,
|
|
374
|
+
): ColumnFunction<TOutput, TInput, TableName, IsContainer> {
|
|
375
|
+
return new ColumnFunction("trim", column);
|
|
376
|
+
}
|
|
377
|
+
|
|
320
378
|
// ============================================================================
|
|
321
379
|
// Array Operators
|
|
322
380
|
// ============================================================================
|