@proofkit/fmodata 0.1.0-alpha.14 → 0.1.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -165
- package/dist/esm/client/batch-builder.d.ts +2 -3
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/builders/expand-builder.d.ts +1 -1
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/builders/select-mixin.d.ts +2 -2
- package/dist/esm/client/builders/select-mixin.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +2 -5
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +3 -13
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +2 -3
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +11 -19
- package/dist/esm/client/query/query-builder.js +7 -76
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/types.d.ts +5 -5
- package/dist/esm/client/query-builder.d.ts +1 -1
- package/dist/esm/client/record-builder.d.ts +8 -9
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +2 -5
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/orm/column.d.ts +21 -4
- package/dist/esm/orm/column.js +5 -2
- package/dist/esm/orm/column.js.map +1 -1
- package/dist/esm/orm/field-builders.d.ts +1 -1
- package/dist/esm/orm/field-builders.js +1 -1
- package/dist/esm/orm/field-builders.js.map +1 -1
- package/dist/esm/orm/operators.d.ts +19 -19
- package/dist/esm/orm/operators.js +31 -12
- package/dist/esm/orm/operators.js.map +1 -1
- package/dist/esm/orm/table.d.ts +10 -9
- package/dist/esm/orm/table.js +5 -3
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/transform.js +1 -5
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +26 -2
- package/dist/esm/types.js.map +1 -1
- package/package.json +3 -1
- package/src/client/batch-builder.ts +2 -1
- package/src/client/builders/expand-builder.ts +3 -3
- package/src/client/builders/select-mixin.ts +2 -3
- package/src/client/delete-builder.ts +2 -1
- package/src/client/entity-set.ts +26 -8
- package/src/client/error-parser.ts +3 -0
- package/src/client/insert-builder.ts +2 -1
- package/src/client/query/query-builder.ts +27 -126
- package/src/client/query/types.ts +6 -5
- package/src/client/query-builder.ts +1 -1
- package/src/client/record-builder.ts +22 -9
- package/src/client/update-builder.ts +17 -8
- package/src/index.ts +6 -15
- package/src/orm/column.ts +33 -5
- package/src/orm/field-builders.ts +1 -1
- package/src/orm/operators.ts +105 -51
- package/src/orm/table.ts +21 -13
- package/src/types.ts +33 -6
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/filter-types.ts +0 -97
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proofkit/fmodata",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.16",
|
|
4
4
|
"description": "FileMaker OData API client",
|
|
5
5
|
"repository": "git@github.com:proofgeist/proofkit.git",
|
|
6
6
|
"author": "Eric <37158449+eluce2@users.noreply.github.com>",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"tsc": "tsc --noEmit",
|
|
29
29
|
"test:typecheck": "vitest run --typecheck",
|
|
30
30
|
"test:watch": "vitest --typecheck",
|
|
31
|
+
"test:build": "pnpm build && TEST_BUILD=true vitest run --typecheck",
|
|
32
|
+
"test:watch:build": "TEST_BUILD=true vitest --typecheck",
|
|
31
33
|
"test:e2e": "op inject -i op.env -o .env.local -f && vitest run tests/e2e.test.ts",
|
|
32
34
|
"capture": "op inject -i op.env -o .env.local -f && tsx scripts/capture-responses.ts",
|
|
33
35
|
"knip": "knip",
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ExecuteOptions,
|
|
6
6
|
BatchResult,
|
|
7
7
|
BatchItemResult,
|
|
8
|
+
ExecuteMethodOptions,
|
|
8
9
|
} from "../types";
|
|
9
10
|
import { BatchTruncatedError } from "../errors";
|
|
10
11
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
@@ -148,7 +149,7 @@ export class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]> {
|
|
|
148
149
|
* @returns A BatchResult containing individual results for each operation
|
|
149
150
|
*/
|
|
150
151
|
async execute<EO extends ExecuteOptions>(
|
|
151
|
-
options?:
|
|
152
|
+
options?: ExecuteMethodOptions<EO>,
|
|
152
153
|
): Promise<BatchResult<ExtractTupleTypes<Builders>>> {
|
|
153
154
|
const baseUrl = this.context._getBaseUrl?.();
|
|
154
155
|
if (!baseUrl) {
|
|
@@ -77,11 +77,11 @@ export class ExpandBuilder {
|
|
|
77
77
|
* @param builderFactory - Function that creates a QueryBuilder for the target table
|
|
78
78
|
* @returns ExpandConfig to add to the builder's expandConfigs array
|
|
79
79
|
*/
|
|
80
|
-
processExpand<TargetTable extends FMTable<any, any
|
|
80
|
+
processExpand<TargetTable extends FMTable<any, any>, Builder = any>(
|
|
81
81
|
targetTable: TargetTable,
|
|
82
82
|
sourceTable: FMTable<any, any> | undefined,
|
|
83
|
-
callback?: (builder:
|
|
84
|
-
builderFactory?: () =>
|
|
83
|
+
callback?: (builder: Builder) => Builder,
|
|
84
|
+
builderFactory?: () => Builder,
|
|
85
85
|
): ExpandConfig {
|
|
86
86
|
// Extract name and validate
|
|
87
87
|
const relationName = getTableName(targetTable);
|
|
@@ -8,7 +8,7 @@ import { isColumn, type Column } from "../../orm/column";
|
|
|
8
8
|
* @returns Object with selectedFields array
|
|
9
9
|
*/
|
|
10
10
|
export function processSelectFields(
|
|
11
|
-
...fields: (string | Column<any, string>)[]
|
|
11
|
+
...fields: (string | Column<any, any, string>)[]
|
|
12
12
|
): { selectedFields: string[] } {
|
|
13
13
|
const fieldNames = fields.map((field) => {
|
|
14
14
|
if (isColumn(field)) {
|
|
@@ -29,7 +29,7 @@ export function processSelectFields(
|
|
|
29
29
|
* @returns Object with selectedFields array and fieldMapping for renamed fields
|
|
30
30
|
*/
|
|
31
31
|
export function processSelectWithRenames<TTableName extends string>(
|
|
32
|
-
fields: Record<string, Column<any, TTableName>>,
|
|
32
|
+
fields: Record<string, Column<any, any, TTableName>>,
|
|
33
33
|
tableName: string,
|
|
34
34
|
): { selectedFields: string[]; fieldMapping: Record<string, string> } {
|
|
35
35
|
const selectedFields: string[] = [];
|
|
@@ -71,4 +71,3 @@ export function processSelectWithRenames<TTableName extends string>(
|
|
|
71
71
|
export class SelectMixin {
|
|
72
72
|
static processSelect = processSelectFields;
|
|
73
73
|
}
|
|
74
|
-
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
Result,
|
|
5
5
|
WithSystemFields,
|
|
6
6
|
ExecuteOptions,
|
|
7
|
+
ExecuteMethodOptions,
|
|
7
8
|
} from "../types";
|
|
8
9
|
import { getAcceptHeader } from "../types";
|
|
9
10
|
import type { FMTable, InferSchemaOutputFromFMTable } from "../orm/table";
|
|
@@ -147,7 +148,7 @@ export class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
async execute(
|
|
150
|
-
options?:
|
|
151
|
+
options?: ExecuteMethodOptions<ExecuteOptions>,
|
|
151
152
|
): Promise<Result<{ deletedCount: number }>> {
|
|
152
153
|
// Merge database-level useEntityIds with per-request options
|
|
153
154
|
const mergedOptions = this.mergeExecuteOptions(options);
|
package/src/client/entity-set.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExecutionContext, InferSchemaType } from "../types";
|
|
2
2
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
import { QueryBuilder } from "./query";
|
|
3
|
+
import { QueryBuilder } from "./query/index";
|
|
4
4
|
import { RecordBuilder } from "./record-builder";
|
|
5
5
|
import { InsertBuilder } from "./insert-builder";
|
|
6
6
|
import { DeleteBuilder } from "./delete-builder";
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ExtractTableName,
|
|
17
17
|
FMTableWithColumns,
|
|
18
18
|
InferFieldOutput,
|
|
19
|
+
ColumnMap,
|
|
19
20
|
} from "../orm/table";
|
|
20
21
|
import {
|
|
21
22
|
FMTable as FMTableClass,
|
|
@@ -42,7 +43,7 @@ type ExtractDefaultSelect<O> =
|
|
|
42
43
|
type ExtractColumnsFromOcc<T> =
|
|
43
44
|
T extends FMTable<infer TFields, infer TName, any>
|
|
44
45
|
? TFields extends Record<string, FieldBuilder<any, any, any, any>>
|
|
45
|
-
?
|
|
46
|
+
? ColumnMap<TFields, TName>
|
|
46
47
|
: never
|
|
47
48
|
: never;
|
|
48
49
|
|
|
@@ -87,7 +88,13 @@ export class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
list()
|
|
91
|
+
list(): QueryBuilder<
|
|
92
|
+
Occ,
|
|
93
|
+
keyof InferSchemaOutputFromFMTable<Occ>,
|
|
94
|
+
false,
|
|
95
|
+
false,
|
|
96
|
+
{}
|
|
97
|
+
> {
|
|
91
98
|
const builder = new QueryBuilder<Occ>({
|
|
92
99
|
occurrence: this.occurrence as Occ,
|
|
93
100
|
databaseName: this.databaseName,
|
|
@@ -117,18 +124,29 @@ export class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
117
124
|
if (defaultSelectValue === "schema") {
|
|
118
125
|
// Use getTableColumns to get all columns and select them
|
|
119
126
|
// This is equivalent to select(getTableColumns(occurrence))
|
|
120
|
-
//
|
|
127
|
+
// Cast to the declared return type - runtime behavior handles the actual selection
|
|
121
128
|
const allColumns = getTableColumns(
|
|
122
129
|
this.occurrence as any,
|
|
123
130
|
) as ExtractColumnsFromOcc<Occ>;
|
|
124
|
-
return builder.select(allColumns).top(1000)
|
|
131
|
+
return builder.select(allColumns).top(1000) as QueryBuilder<
|
|
132
|
+
Occ,
|
|
133
|
+
keyof InferSchemaOutputFromFMTable<Occ>,
|
|
134
|
+
false,
|
|
135
|
+
false,
|
|
136
|
+
{}
|
|
137
|
+
>;
|
|
125
138
|
} else if (typeof defaultSelectValue === "object") {
|
|
126
139
|
// defaultSelectValue is a select object (Record<string, Column>)
|
|
127
|
-
//
|
|
128
|
-
// Use ExtractColumnsFromOcc to preserve the properly-typed column types
|
|
140
|
+
// Cast to the declared return type - runtime behavior handles the actual selection
|
|
129
141
|
return builder
|
|
130
142
|
.select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)
|
|
131
|
-
.top(1000)
|
|
143
|
+
.top(1000) as QueryBuilder<
|
|
144
|
+
Occ,
|
|
145
|
+
keyof InferSchemaOutputFromFMTable<Occ>,
|
|
146
|
+
false,
|
|
147
|
+
false,
|
|
148
|
+
{}
|
|
149
|
+
>;
|
|
132
150
|
}
|
|
133
151
|
// If defaultSelect is "all", no changes needed (current behavior)
|
|
134
152
|
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
InferSchemaType,
|
|
7
7
|
ExecuteOptions,
|
|
8
8
|
ConditionallyWithODataAnnotations,
|
|
9
|
+
ExecuteMethodOptions,
|
|
9
10
|
} from "../types";
|
|
10
11
|
import { getAcceptHeader } from "../types";
|
|
11
12
|
import type { FMTable } from "../orm/table";
|
|
@@ -142,7 +143,7 @@ export class InsertBuilder<
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
async execute<EO extends ExecuteOptions>(
|
|
145
|
-
options?:
|
|
146
|
+
options?: ExecuteMethodOptions<EO>,
|
|
146
147
|
): Promise<
|
|
147
148
|
Result<
|
|
148
149
|
ReturnPreference extends "minimal"
|
|
@@ -7,12 +7,11 @@ import type {
|
|
|
7
7
|
ExecuteOptions,
|
|
8
8
|
ConditionallyWithODataAnnotations,
|
|
9
9
|
ExtractSchemaFromOccurrence,
|
|
10
|
+
ExecuteMethodOptions,
|
|
10
11
|
} from "../../types";
|
|
11
|
-
import type { Filter } from "../../filter-types";
|
|
12
12
|
import { RecordCountMismatchError } from "../../errors";
|
|
13
13
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
14
14
|
import {
|
|
15
|
-
transformFieldName,
|
|
16
15
|
transformFieldNamesArray,
|
|
17
16
|
transformOrderByField,
|
|
18
17
|
} from "../../transform";
|
|
@@ -43,7 +42,7 @@ import {
|
|
|
43
42
|
processSelectWithRenames,
|
|
44
43
|
buildSelectExpandQueryString,
|
|
45
44
|
createODataRequest,
|
|
46
|
-
} from "../builders";
|
|
45
|
+
} from "../builders/index";
|
|
47
46
|
import { QueryUrlBuilder, type NavigationConfig } from "./url-builder";
|
|
48
47
|
import type { TypeSafeOrderBy, QueryReturnType } from "./types";
|
|
49
48
|
|
|
@@ -65,7 +64,7 @@ export class QueryBuilder<
|
|
|
65
64
|
| keyof InferSchemaOutputFromFMTable<Occ>
|
|
66
65
|
| Record<
|
|
67
66
|
string,
|
|
68
|
-
Column<any, ExtractTableName<Occ>>
|
|
67
|
+
Column<any, any, ExtractTableName<Occ>>
|
|
69
68
|
> = keyof InferSchemaOutputFromFMTable<Occ>,
|
|
70
69
|
SingleMode extends "exact" | "maybe" | false = false,
|
|
71
70
|
IsCount extends boolean = false,
|
|
@@ -151,7 +150,7 @@ export class QueryBuilder<
|
|
|
151
150
|
private cloneWithChanges<
|
|
152
151
|
NewSelected extends
|
|
153
152
|
| keyof InferSchemaOutputFromFMTable<Occ>
|
|
154
|
-
| Record<string, Column<any, ExtractTableName<Occ>>> = Selected,
|
|
153
|
+
| Record<string, Column<any, any, ExtractTableName<Occ>>> = Selected,
|
|
155
154
|
NewSingle extends "exact" | "maybe" | false = SingleMode,
|
|
156
155
|
NewCount extends boolean = IsCount,
|
|
157
156
|
>(changes: {
|
|
@@ -206,7 +205,7 @@ export class QueryBuilder<
|
|
|
206
205
|
* @returns QueryBuilder with updated selected fields
|
|
207
206
|
*/
|
|
208
207
|
select<
|
|
209
|
-
TSelect extends Record<string, Column<any, ExtractTableName<Occ>, false>>,
|
|
208
|
+
TSelect extends Record<string, Column<any, any, ExtractTableName<Occ>, false>>,
|
|
210
209
|
>(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands> {
|
|
211
210
|
const tableName = getTableName(this.occurrence);
|
|
212
211
|
const { selectedFields, fieldMapping } = processSelectWithRenames(
|
|
@@ -224,131 +223,24 @@ export class QueryBuilder<
|
|
|
224
223
|
});
|
|
225
224
|
}
|
|
226
225
|
|
|
227
|
-
/**
|
|
228
|
-
* Transforms our filter format to odata-query's expected format
|
|
229
|
-
* - Arrays of operators are converted to AND conditions
|
|
230
|
-
* - Single operator objects pass through as-is
|
|
231
|
-
* - Shorthand values are handled by odata-query
|
|
232
|
-
*/
|
|
233
|
-
private transformFilter(
|
|
234
|
-
filter: Filter<ExtractSchemaFromOccurrence<Occ>>,
|
|
235
|
-
): QueryOptions<InferSchemaOutputFromFMTable<Occ>>["filter"] {
|
|
236
|
-
if (typeof filter === "string") {
|
|
237
|
-
// Raw string filters pass through
|
|
238
|
-
return filter;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (Array.isArray(filter)) {
|
|
242
|
-
// Array of filters - odata-query handles this as implicit AND
|
|
243
|
-
return filter.map((f) => this.transformFilter(f as any)) as any;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Check if it's a logical filter (and/or/not)
|
|
247
|
-
if ("and" in filter || "or" in filter || "not" in filter) {
|
|
248
|
-
const result: any = {};
|
|
249
|
-
if ("and" in filter && Array.isArray(filter.and)) {
|
|
250
|
-
result.and = filter.and.map((f: any) => this.transformFilter(f));
|
|
251
|
-
}
|
|
252
|
-
if ("or" in filter && Array.isArray(filter.or)) {
|
|
253
|
-
result.or = filter.or.map((f: any) => this.transformFilter(f));
|
|
254
|
-
}
|
|
255
|
-
if ("not" in filter && filter.not) {
|
|
256
|
-
result.not = this.transformFilter(filter.not as any);
|
|
257
|
-
}
|
|
258
|
-
return result;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Transform field filters
|
|
262
|
-
const result: any = {};
|
|
263
|
-
const andConditions: any[] = [];
|
|
264
|
-
|
|
265
|
-
for (const [field, value] of Object.entries(filter)) {
|
|
266
|
-
// Transform field name to FMFID if using entity IDs AND the feature is enabled
|
|
267
|
-
const shouldTransform = this.occurrence && this.databaseUseEntityIds;
|
|
268
|
-
const fieldId = shouldTransform
|
|
269
|
-
? transformFieldName(field, this.occurrence!)
|
|
270
|
-
: field;
|
|
271
|
-
|
|
272
|
-
if (Array.isArray(value)) {
|
|
273
|
-
// Array of operators - convert to AND conditions
|
|
274
|
-
if (value.length === 1) {
|
|
275
|
-
// Single operator in array - unwrap it
|
|
276
|
-
result[fieldId] = value[0];
|
|
277
|
-
} else {
|
|
278
|
-
// Multiple operators - combine with AND
|
|
279
|
-
// Create separate conditions for each operator
|
|
280
|
-
for (const op of value) {
|
|
281
|
-
andConditions.push({ [fieldId]: op });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
} else if (
|
|
285
|
-
value &&
|
|
286
|
-
typeof value === "object" &&
|
|
287
|
-
!(value instanceof Date) &&
|
|
288
|
-
!Array.isArray(value)
|
|
289
|
-
) {
|
|
290
|
-
// Check if it's an operator object (has operator keys like eq, gt, etc.)
|
|
291
|
-
const operatorKeys = [
|
|
292
|
-
"eq",
|
|
293
|
-
"ne",
|
|
294
|
-
"gt",
|
|
295
|
-
"ge",
|
|
296
|
-
"lt",
|
|
297
|
-
"le",
|
|
298
|
-
"contains",
|
|
299
|
-
"startswith",
|
|
300
|
-
"endswith",
|
|
301
|
-
"in",
|
|
302
|
-
];
|
|
303
|
-
const isOperatorObject = operatorKeys.some((key) => key in value);
|
|
304
|
-
|
|
305
|
-
if (isOperatorObject) {
|
|
306
|
-
// Single operator object - pass through
|
|
307
|
-
result[fieldId] = value;
|
|
308
|
-
} else {
|
|
309
|
-
// Regular object - might be nested filter, pass through
|
|
310
|
-
result[fieldId] = value;
|
|
311
|
-
}
|
|
312
|
-
} else {
|
|
313
|
-
// Primitive value (shorthand) - pass through
|
|
314
|
-
result[fieldId] = value;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// If we have AND conditions from arrays, combine them
|
|
319
|
-
if (andConditions.length > 0) {
|
|
320
|
-
if (Object.keys(result).length > 0) {
|
|
321
|
-
// We have both regular fields and array-derived AND conditions
|
|
322
|
-
// Combine everything with AND
|
|
323
|
-
return { and: [...andConditions, result] };
|
|
324
|
-
} else {
|
|
325
|
-
// Only array-derived AND conditions
|
|
326
|
-
return { and: andConditions };
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
filter(
|
|
334
|
-
filter: Filter<ExtractSchemaFromOccurrence<Occ>>,
|
|
335
|
-
): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
|
|
336
|
-
// Transform our filter format to odata-query's expected format
|
|
337
|
-
this.queryOptions.filter = this.transformFilter(filter) as any;
|
|
338
|
-
return this;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
226
|
/**
|
|
342
227
|
* Filter results using operator expressions (new ORM-style API).
|
|
343
228
|
* Supports eq, gt, lt, and, or, etc. operators with Column references.
|
|
229
|
+
* Also supports raw OData filter strings as an escape hatch.
|
|
344
230
|
*
|
|
345
231
|
* @example
|
|
346
232
|
* .where(eq(users.hobby, "reading"))
|
|
347
233
|
* .where(and(eq(users.active, true), gt(users.age, 18)))
|
|
234
|
+
* .where("status eq 'active'") // Raw OData string escape hatch
|
|
348
235
|
*/
|
|
349
236
|
where(
|
|
350
|
-
expression: FilterExpression,
|
|
237
|
+
expression: FilterExpression | string,
|
|
351
238
|
): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
|
|
239
|
+
// Handle raw string filters (escape hatch)
|
|
240
|
+
if (typeof expression === "string") {
|
|
241
|
+
this.queryOptions.filter = expression;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
352
244
|
// Convert FilterExpression to OData filter string
|
|
353
245
|
const filterString = expression.toODataFilter(this.databaseUseEntityIds);
|
|
354
246
|
this.queryOptions.filter = filterString;
|
|
@@ -383,13 +275,13 @@ export class QueryBuilder<
|
|
|
383
275
|
...orderByArgs:
|
|
384
276
|
| [
|
|
385
277
|
| TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>>
|
|
386
|
-
| Column<any, ExtractTableName<Occ>>
|
|
278
|
+
| Column<any, any, ExtractTableName<Occ>>
|
|
387
279
|
| OrderByExpression<ExtractTableName<Occ>>,
|
|
388
280
|
]
|
|
389
281
|
| [
|
|
390
|
-
Column<any, ExtractTableName<Occ>>,
|
|
282
|
+
Column<any, any, ExtractTableName<Occ>>,
|
|
391
283
|
...Array<
|
|
392
|
-
| Column<any, ExtractTableName<Occ>>
|
|
284
|
+
| Column<any, any, ExtractTableName<Occ>>
|
|
393
285
|
| OrderByExpression<ExtractTableName<Occ>>
|
|
394
286
|
>,
|
|
395
287
|
]
|
|
@@ -573,7 +465,16 @@ export class QueryBuilder<
|
|
|
573
465
|
}
|
|
574
466
|
> {
|
|
575
467
|
// Use ExpandBuilder.processExpand to handle the expand logic
|
|
576
|
-
|
|
468
|
+
type TargetBuilder = QueryBuilder<
|
|
469
|
+
TargetTable,
|
|
470
|
+
keyof InferSchemaOutputFromFMTable<TargetTable>,
|
|
471
|
+
false,
|
|
472
|
+
false
|
|
473
|
+
>;
|
|
474
|
+
const expandConfig = this.expandBuilder.processExpand<
|
|
475
|
+
TargetTable,
|
|
476
|
+
TargetBuilder
|
|
477
|
+
>(
|
|
577
478
|
targetTable,
|
|
578
479
|
this.occurrence,
|
|
579
480
|
callback,
|
|
@@ -645,7 +546,7 @@ export class QueryBuilder<
|
|
|
645
546
|
}
|
|
646
547
|
|
|
647
548
|
async execute<EO extends ExecuteOptions>(
|
|
648
|
-
options?:
|
|
549
|
+
options?: ExecuteMethodOptions<EO>,
|
|
649
550
|
): Promise<
|
|
650
551
|
Result<
|
|
651
552
|
ConditionallyWithODataAnnotations<
|
|
@@ -28,16 +28,17 @@ export type ExpandedRelations = Record<string, { schema: any; selected: any }>;
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Extract the value type from a Column.
|
|
31
|
-
* This uses the phantom type stored in Column to get the actual value type.
|
|
31
|
+
* This uses the phantom type stored in Column to get the actual value type (output type for reading).
|
|
32
32
|
*/
|
|
33
|
-
type ExtractColumnType<C> =
|
|
33
|
+
type ExtractColumnType<C> =
|
|
34
|
+
C extends Column<infer T, any, any, any> ? T : never;
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Map a select object to its return type.
|
|
37
38
|
* For each key in the select object, extract the type from the corresponding Column.
|
|
38
39
|
*/
|
|
39
40
|
type MapSelectToReturnType<
|
|
40
|
-
TSelect extends Record<string, Column<any, any>>,
|
|
41
|
+
TSelect extends Record<string, Column<any, any, any, any>>,
|
|
41
42
|
TSchema extends Record<string, any>,
|
|
42
43
|
> = {
|
|
43
44
|
[K in keyof TSelect]: ExtractColumnType<TSelect[K]>;
|
|
@@ -45,14 +46,14 @@ type MapSelectToReturnType<
|
|
|
45
46
|
|
|
46
47
|
export type QueryReturnType<
|
|
47
48
|
T extends Record<string, any>,
|
|
48
|
-
Selected extends keyof T | Record<string, Column<any, any>>,
|
|
49
|
+
Selected extends keyof T | Record<string, Column<any, any, any, any>>,
|
|
49
50
|
SingleMode extends "exact" | "maybe" | false,
|
|
50
51
|
IsCount extends boolean,
|
|
51
52
|
Expands extends ExpandedRelations,
|
|
52
53
|
> = IsCount extends true
|
|
53
54
|
? number
|
|
54
55
|
: // Use tuple wrapping [Selected] extends [...] to prevent distribution over unions
|
|
55
|
-
[Selected] extends [Record<string, Column<any, any>>]
|
|
56
|
+
[Selected] extends [Record<string, Column<any, any, any, any>>]
|
|
56
57
|
? SingleMode extends "exact"
|
|
57
58
|
? MapSelectToReturnType<Selected, T> & {
|
|
58
59
|
[K in keyof Expands]: Pick<
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ODataFieldResponse,
|
|
6
6
|
ExecuteOptions,
|
|
7
7
|
ConditionallyWithODataAnnotations,
|
|
8
|
+
ExecuteMethodOptions,
|
|
8
9
|
} from "../types";
|
|
9
10
|
import type {
|
|
10
11
|
FMTable,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
processSelectWithRenames,
|
|
31
32
|
buildSelectExpandQueryString,
|
|
32
33
|
createODataRequest,
|
|
33
|
-
} from "./builders";
|
|
34
|
+
} from "./builders/index";
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Extract the value type from a Column.
|
|
@@ -43,7 +44,7 @@ type ExtractColumnType<C> = C extends Column<infer T, any> ? T : never;
|
|
|
43
44
|
* For each key in the select object, extract the type from the corresponding Column.
|
|
44
45
|
*/
|
|
45
46
|
type MapSelectToReturnType<
|
|
46
|
-
TSelect extends Record<string, Column<any, any>>,
|
|
47
|
+
TSelect extends Record<string, Column<any, any, any, any>>,
|
|
47
48
|
TSchema extends Record<string, any>,
|
|
48
49
|
> = {
|
|
49
50
|
[K in keyof TSelect]: ExtractColumnType<TSelect[K]>;
|
|
@@ -56,12 +57,12 @@ export type RecordReturnType<
|
|
|
56
57
|
FieldKey extends keyof Schema,
|
|
57
58
|
Selected extends
|
|
58
59
|
| keyof Schema
|
|
59
|
-
| Record<string, Column<any, ExtractTableName<FMTable<any, any>>>>,
|
|
60
|
+
| Record<string, Column<any, any, ExtractTableName<FMTable<any, any>>>>,
|
|
60
61
|
Expands extends ExpandedRelations,
|
|
61
62
|
> = IsSingleField extends true
|
|
62
63
|
? Schema[FieldKey]
|
|
63
64
|
: // Use tuple wrapping [Selected] extends [...] to prevent distribution over unions
|
|
64
|
-
[Selected] extends [Record<string, Column<any, any>>]
|
|
65
|
+
[Selected] extends [Record<string, Column<any, any, any, any>>]
|
|
65
66
|
? MapSelectToReturnType<Selected, Schema> & {
|
|
66
67
|
[K in keyof Expands]: Pick<
|
|
67
68
|
Expands[K]["schema"],
|
|
@@ -88,7 +89,7 @@ export class RecordBuilder<
|
|
|
88
89
|
| keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>
|
|
89
90
|
| Record<
|
|
90
91
|
string,
|
|
91
|
-
Column<any, ExtractTableName<NonNullable<Occ>>>
|
|
92
|
+
Column<any, any, ExtractTableName<NonNullable<Occ>>>
|
|
92
93
|
> = keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,
|
|
93
94
|
Expands extends ExpandedRelations = {},
|
|
94
95
|
> implements
|
|
@@ -168,7 +169,7 @@ export class RecordBuilder<
|
|
|
168
169
|
| keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>
|
|
169
170
|
| Record<
|
|
170
171
|
string,
|
|
171
|
-
Column<any, ExtractTableName<NonNullable<Occ>>>
|
|
172
|
+
Column<any, any, ExtractTableName<NonNullable<Occ>>>
|
|
172
173
|
> = Selected,
|
|
173
174
|
>(changes: {
|
|
174
175
|
selectedFields?: string[];
|
|
@@ -245,7 +246,10 @@ export class RecordBuilder<
|
|
|
245
246
|
* @returns RecordBuilder with updated selected fields
|
|
246
247
|
*/
|
|
247
248
|
select<
|
|
248
|
-
TSelect extends Record<
|
|
249
|
+
TSelect extends Record<
|
|
250
|
+
string,
|
|
251
|
+
Column<any, any, ExtractTableName<Occ>, false>
|
|
252
|
+
>,
|
|
249
253
|
>(fields: TSelect): RecordBuilder<Occ, false, FieldKey, TSelect, Expands> {
|
|
250
254
|
const tableName = getTableName(this.table);
|
|
251
255
|
const { selectedFields, fieldMapping } = processSelectWithRenames(
|
|
@@ -316,7 +320,16 @@ export class RecordBuilder<
|
|
|
316
320
|
|
|
317
321
|
// Use ExpandBuilder.processExpand to handle the expand logic
|
|
318
322
|
const expandBuilder = new ExpandBuilder(this.databaseUseEntityIds);
|
|
319
|
-
|
|
323
|
+
type TargetBuilder = QueryBuilder<
|
|
324
|
+
TargetTable,
|
|
325
|
+
keyof InferSchemaOutputFromFMTable<TargetTable>,
|
|
326
|
+
false,
|
|
327
|
+
false
|
|
328
|
+
>;
|
|
329
|
+
const expandConfig = expandBuilder.processExpand<
|
|
330
|
+
TargetTable,
|
|
331
|
+
TargetBuilder
|
|
332
|
+
>(
|
|
320
333
|
targetTable,
|
|
321
334
|
this.table ?? undefined,
|
|
322
335
|
callback,
|
|
@@ -414,7 +427,7 @@ export class RecordBuilder<
|
|
|
414
427
|
}
|
|
415
428
|
|
|
416
429
|
async execute<EO extends ExecuteOptions>(
|
|
417
|
-
options?:
|
|
430
|
+
options?: ExecuteMethodOptions<EO>,
|
|
418
431
|
): Promise<
|
|
419
432
|
Result<
|
|
420
433
|
ConditionallyWithODataAnnotations<
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
Result,
|
|
5
5
|
WithSystemFields,
|
|
6
6
|
ExecuteOptions,
|
|
7
|
+
ExecuteMethodOptions,
|
|
7
8
|
} from "../types";
|
|
8
9
|
import { getAcceptHeader } from "../types";
|
|
9
10
|
import type { FMTable, InferSchemaOutputFromFMTable } from "../orm/table";
|
|
@@ -76,9 +77,7 @@ export class UpdateBuilder<
|
|
|
76
77
|
* @param fn Callback that receives a QueryBuilder for building the filter
|
|
77
78
|
*/
|
|
78
79
|
where(
|
|
79
|
-
fn: (
|
|
80
|
-
q: QueryBuilder<Occ>,
|
|
81
|
-
) => QueryBuilder<Occ>,
|
|
80
|
+
fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>,
|
|
82
81
|
): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {
|
|
83
82
|
// Create a QueryBuilder for the user to configure
|
|
84
83
|
const queryBuilder = new QueryBuilder<Occ>({
|
|
@@ -114,7 +113,9 @@ export class ExecutableUpdateBuilder<
|
|
|
114
113
|
ReturnPreference extends "minimal" | "representation" = "minimal",
|
|
115
114
|
> implements
|
|
116
115
|
ExecutableBuilder<
|
|
117
|
-
ReturnPreference extends "minimal"
|
|
116
|
+
ReturnPreference extends "minimal"
|
|
117
|
+
? { updatedCount: number }
|
|
118
|
+
: InferSchemaOutputFromFMTable<Occ>
|
|
118
119
|
>
|
|
119
120
|
{
|
|
120
121
|
private databaseName: string;
|
|
@@ -183,9 +184,13 @@ export class ExecutableUpdateBuilder<
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
async execute(
|
|
186
|
-
options?:
|
|
187
|
+
options?: ExecuteMethodOptions<ExecuteOptions>,
|
|
187
188
|
): Promise<
|
|
188
|
-
Result<
|
|
189
|
+
Result<
|
|
190
|
+
ReturnPreference extends "minimal"
|
|
191
|
+
? { updatedCount: number }
|
|
192
|
+
: InferSchemaOutputFromFMTable<Occ>
|
|
193
|
+
>
|
|
189
194
|
> {
|
|
190
195
|
// Merge database-level useEntityIds with per-request options
|
|
191
196
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
@@ -198,7 +203,7 @@ export class ExecutableUpdateBuilder<
|
|
|
198
203
|
if (this.table) {
|
|
199
204
|
const baseTableConfig = getBaseTableConfig(this.table);
|
|
200
205
|
const inputSchema = baseTableConfig.inputSchema;
|
|
201
|
-
|
|
206
|
+
|
|
202
207
|
try {
|
|
203
208
|
validatedData = await validateAndTransformInput(this.data, inputSchema);
|
|
204
209
|
} catch (error) {
|
|
@@ -352,7 +357,11 @@ export class ExecutableUpdateBuilder<
|
|
|
352
357
|
response: Response,
|
|
353
358
|
options?: ExecuteOptions,
|
|
354
359
|
): Promise<
|
|
355
|
-
Result<
|
|
360
|
+
Result<
|
|
361
|
+
ReturnPreference extends "minimal"
|
|
362
|
+
? { updatedCount: number }
|
|
363
|
+
: InferSchemaOutputFromFMTable<Occ>
|
|
364
|
+
>
|
|
356
365
|
> {
|
|
357
366
|
// Check for error responses (important for batch operations)
|
|
358
367
|
if (!response.ok) {
|
package/src/index.ts
CHANGED
|
@@ -74,25 +74,14 @@ export type {
|
|
|
74
74
|
BatchResult,
|
|
75
75
|
BatchItemResult,
|
|
76
76
|
InferSchemaType,
|
|
77
|
-
InsertData,
|
|
78
|
-
UpdateData,
|
|
79
77
|
ODataRecordMetadata,
|
|
80
78
|
Metadata,
|
|
79
|
+
FetchHandler,
|
|
80
|
+
ExecuteMethodOptions,
|
|
81
|
+
ExecuteOptions,
|
|
81
82
|
} from "./types";
|
|
82
83
|
|
|
83
|
-
//
|
|
84
|
-
export type {
|
|
85
|
-
Filter,
|
|
86
|
-
TypedFilter,
|
|
87
|
-
FieldFilter,
|
|
88
|
-
StringOperators,
|
|
89
|
-
NumberOperators,
|
|
90
|
-
BooleanOperators,
|
|
91
|
-
DateOperators,
|
|
92
|
-
LogicalFilter,
|
|
93
|
-
} from "./filter-types";
|
|
94
|
-
|
|
95
|
-
// Re-export ffetch errors
|
|
84
|
+
// Re-export ffetch errors and types
|
|
96
85
|
export {
|
|
97
86
|
TimeoutError,
|
|
98
87
|
AbortError,
|
|
@@ -101,6 +90,8 @@ export {
|
|
|
101
90
|
CircuitOpenError,
|
|
102
91
|
} from "@fetchkit/ffetch";
|
|
103
92
|
|
|
93
|
+
export type { FFetchOptions } from "@fetchkit/ffetch";
|
|
94
|
+
|
|
104
95
|
// Export our errors
|
|
105
96
|
export {
|
|
106
97
|
FMODataError,
|