@proofkit/fmodata 0.1.0-alpha.13 → 0.1.0-alpha.14
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 +489 -334
- package/dist/esm/client/batch-builder.d.ts +7 -4
- package/dist/esm/client/batch-builder.js +84 -25
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +7 -0
- package/dist/esm/client/builders/default-select.js +42 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +43 -0
- package/dist/esm/client/builders/expand-builder.js +173 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +8 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +15 -0
- package/dist/esm/client/builders/query-string-builder.js +25 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +39 -0
- package/dist/esm/client/builders/response-processor.js +170 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +31 -0
- package/dist/esm/client/builders/select-mixin.js +30 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +8 -0
- package/dist/esm/client/builders/select-utils.js +15 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +39 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +45 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +3 -22
- package/dist/esm/client/database.js +14 -76
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +11 -15
- package/dist/esm/client/delete-builder.js +26 -26
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +32 -32
- package/dist/esm/client/entity-set.js +92 -69
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +30 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +2 -4
- package/dist/esm/client/filemaker-odata.js +1 -5
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +7 -9
- package/dist/esm/client/insert-builder.js +70 -24
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +3 -0
- package/dist/esm/client/query/query-builder.d.ts +134 -0
- package/dist/esm/client/query/query-builder.js +505 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +22 -0
- package/dist/esm/client/query/types.d.ts +52 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +107 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +1 -111
- package/dist/esm/client/record-builder.d.ts +56 -63
- package/dist/esm/client/record-builder.js +158 -297
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +3 -3
- package/dist/esm/client/update-builder.d.ts +16 -21
- package/dist/esm/client/update-builder.js +56 -30
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +8 -1
- package/dist/esm/errors.js +17 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +3 -7
- package/dist/esm/index.js +37 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/orm/column.d.ts +45 -0
- package/dist/esm/orm/column.js +59 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +154 -0
- package/dist/esm/orm/field-builders.js +152 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +4 -0
- package/dist/esm/orm/operators.d.ts +175 -0
- package/dist/esm/orm/operators.js +221 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +341 -0
- package/dist/esm/orm/table.js +211 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +34 -34
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +16 -13
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation.d.ts +14 -4
- package/dist/esm/validation.js +45 -1
- package/dist/esm/validation.js.map +1 -1
- package/package.json +20 -17
- package/src/client/batch-builder.ts +100 -32
- package/src/client/builders/default-select.ts +69 -0
- package/src/client/builders/expand-builder.ts +236 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +41 -0
- package/src/client/builders/response-processor.ts +273 -0
- package/src/client/builders/select-mixin.ts +74 -0
- package/src/client/builders/select-utils.ts +34 -0
- package/src/client/builders/shared-types.ts +41 -0
- package/src/client/builders/table-utils.ts +87 -0
- package/src/client/database.ts +19 -160
- package/src/client/delete-builder.ts +46 -51
- package/src/client/entity-set.ts +227 -302
- package/src/client/error-parser.ts +59 -0
- package/src/client/filemaker-odata.ts +3 -14
- package/src/client/insert-builder.ts +124 -43
- package/src/client/query/expand-builder.ts +164 -0
- package/src/client/query/index.ts +13 -0
- package/src/client/query/query-builder.ts +816 -0
- package/src/client/query/response-processor.ts +244 -0
- package/src/client/query/types.ts +102 -0
- package/src/client/query/url-builder.ts +179 -0
- package/src/client/query-builder.ts +8 -1454
- package/src/client/record-builder.ts +325 -585
- package/src/client/response-processor.ts +4 -5
- package/src/client/update-builder.ts +102 -73
- package/src/errors.ts +22 -1
- package/src/index.ts +55 -5
- package/src/orm/column.ts +78 -0
- package/src/orm/field-builders.ts +296 -0
- package/src/orm/index.ts +60 -0
- package/src/orm/operators.ts +428 -0
- package/src/orm/table.ts +759 -0
- package/src/transform.ts +62 -48
- package/src/types.ts +20 -63
- package/src/validation.ts +76 -4
- package/LICENSE.md +0 -21
- package/dist/esm/client/base-table.d.ts +0 -128
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/build-occurrences.d.ts +0 -74
- package/dist/esm/client/build-occurrences.js +0 -31
- package/dist/esm/client/build-occurrences.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -900
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -86
- package/dist/esm/client/table-occurrence.js +0 -58
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/src/client/base-table.ts +0 -178
- package/src/client/build-occurrences.ts +0 -155
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -156
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FMTable } from "../orm/table";
|
|
3
3
|
import type { ExecuteOptions } from "../types";
|
|
4
4
|
import type { ExpandValidationConfig } from "../validation";
|
|
5
5
|
import { ValidationError, ResponseStructureError } from "../errors";
|
|
@@ -23,16 +23,15 @@ export type ODataRecordResponse<T = unknown> = ODataResponse<
|
|
|
23
23
|
}
|
|
24
24
|
>;
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
/**
|
|
28
|
-
* Transform field IDs back to names using the
|
|
27
|
+
* Transform field IDs back to names using the table configuration
|
|
29
28
|
*/
|
|
30
29
|
export function applyFieldTransformation<T extends Record<string, unknown>>(
|
|
31
30
|
response: ODataResponse<T> | ODataListResponse<T>,
|
|
32
|
-
|
|
31
|
+
table: FMTable<any, any>,
|
|
33
32
|
expandConfigs?: ExpandValidationConfig[],
|
|
34
33
|
): ODataResponse<T> | ODataListResponse<T> {
|
|
35
|
-
return transformResponseFields(response,
|
|
34
|
+
return transformResponseFields(response, table, expandConfigs) as
|
|
36
35
|
| ODataResponse<T>
|
|
37
36
|
| ODataListResponse<T>;
|
|
38
37
|
}
|
|
@@ -6,45 +6,44 @@ import type {
|
|
|
6
6
|
ExecuteOptions,
|
|
7
7
|
} from "../types";
|
|
8
8
|
import { getAcceptHeader } from "../types";
|
|
9
|
-
import type {
|
|
10
|
-
import
|
|
9
|
+
import type { FMTable, InferSchemaOutputFromFMTable } from "../orm/table";
|
|
10
|
+
import {
|
|
11
|
+
getTableName,
|
|
12
|
+
getTableId as getTableIdHelper,
|
|
13
|
+
getBaseTableConfig,
|
|
14
|
+
isUsingEntityIds,
|
|
15
|
+
} from "../orm/table";
|
|
11
16
|
import { QueryBuilder } from "./query-builder";
|
|
12
17
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
getTableIdentifiers,
|
|
17
|
-
} from "../transform";
|
|
18
|
+
import { transformFieldNamesToIds } from "../transform";
|
|
19
|
+
import { parseErrorResponse } from "./error-parser";
|
|
20
|
+
import { validateAndTransformInput } from "../validation";
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Initial update builder returned from EntitySet.update(data)
|
|
21
24
|
* Requires calling .byId() or .where() before .execute() is available
|
|
22
25
|
*/
|
|
23
26
|
export class UpdateBuilder<
|
|
24
|
-
|
|
25
|
-
BT extends BaseTable<any, any, any, any>,
|
|
27
|
+
Occ extends FMTable<any, any>,
|
|
26
28
|
ReturnPreference extends "minimal" | "representation" = "minimal",
|
|
27
29
|
> {
|
|
28
|
-
private tableName: string;
|
|
29
30
|
private databaseName: string;
|
|
30
31
|
private context: ExecutionContext;
|
|
31
|
-
private
|
|
32
|
-
private data: Partial<
|
|
32
|
+
private table: Occ;
|
|
33
|
+
private data: Partial<InferSchemaOutputFromFMTable<Occ>>;
|
|
33
34
|
private returnPreference: ReturnPreference;
|
|
34
35
|
|
|
35
36
|
private databaseUseEntityIds: boolean;
|
|
36
37
|
|
|
37
38
|
constructor(config: {
|
|
38
|
-
occurrence
|
|
39
|
-
tableName: string;
|
|
39
|
+
occurrence: Occ;
|
|
40
40
|
databaseName: string;
|
|
41
41
|
context: ExecutionContext;
|
|
42
|
-
data: Partial<
|
|
42
|
+
data: Partial<InferSchemaOutputFromFMTable<Occ>>;
|
|
43
43
|
returnPreference: ReturnPreference;
|
|
44
44
|
databaseUseEntityIds?: boolean;
|
|
45
45
|
}) {
|
|
46
|
-
this.
|
|
47
|
-
this.tableName = config.tableName;
|
|
46
|
+
this.table = config.occurrence;
|
|
48
47
|
this.databaseName = config.databaseName;
|
|
49
48
|
this.context = config.context;
|
|
50
49
|
this.data = config.data;
|
|
@@ -58,10 +57,9 @@ export class UpdateBuilder<
|
|
|
58
57
|
*/
|
|
59
58
|
byId(
|
|
60
59
|
id: string | number,
|
|
61
|
-
): ExecutableUpdateBuilder<
|
|
62
|
-
return new ExecutableUpdateBuilder<
|
|
63
|
-
occurrence: this.
|
|
64
|
-
tableName: this.tableName,
|
|
60
|
+
): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {
|
|
61
|
+
return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({
|
|
62
|
+
occurrence: this.table,
|
|
65
63
|
databaseName: this.databaseName,
|
|
66
64
|
context: this.context,
|
|
67
65
|
data: this.data,
|
|
@@ -79,19 +77,12 @@ export class UpdateBuilder<
|
|
|
79
77
|
*/
|
|
80
78
|
where(
|
|
81
79
|
fn: (
|
|
82
|
-
q: QueryBuilder<
|
|
83
|
-
) => QueryBuilder<
|
|
84
|
-
): ExecutableUpdateBuilder<
|
|
80
|
+
q: QueryBuilder<Occ>,
|
|
81
|
+
) => QueryBuilder<Occ>,
|
|
82
|
+
): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {
|
|
85
83
|
// Create a QueryBuilder for the user to configure
|
|
86
|
-
const queryBuilder = new QueryBuilder<
|
|
87
|
-
|
|
88
|
-
keyof WithSystemFields<T>,
|
|
89
|
-
false,
|
|
90
|
-
false,
|
|
91
|
-
undefined
|
|
92
|
-
>({
|
|
93
|
-
occurrence: undefined,
|
|
94
|
-
tableName: this.tableName,
|
|
84
|
+
const queryBuilder = new QueryBuilder<Occ>({
|
|
85
|
+
occurrence: this.table,
|
|
95
86
|
databaseName: this.databaseName,
|
|
96
87
|
context: this.context,
|
|
97
88
|
});
|
|
@@ -99,9 +90,8 @@ export class UpdateBuilder<
|
|
|
99
90
|
// Let the user configure it
|
|
100
91
|
const configuredBuilder = fn(queryBuilder);
|
|
101
92
|
|
|
102
|
-
return new ExecutableUpdateBuilder<
|
|
103
|
-
occurrence: this.
|
|
104
|
-
tableName: this.tableName,
|
|
93
|
+
return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({
|
|
94
|
+
occurrence: this.table,
|
|
105
95
|
databaseName: this.databaseName,
|
|
106
96
|
context: this.context,
|
|
107
97
|
data: this.data,
|
|
@@ -119,39 +109,36 @@ export class UpdateBuilder<
|
|
|
119
109
|
* Can return either updated count or full record based on returnFullRecord option
|
|
120
110
|
*/
|
|
121
111
|
export class ExecutableUpdateBuilder<
|
|
122
|
-
|
|
112
|
+
Occ extends FMTable<any, any>,
|
|
123
113
|
IsByFilter extends boolean,
|
|
124
114
|
ReturnPreference extends "minimal" | "representation" = "minimal",
|
|
125
115
|
> implements
|
|
126
116
|
ExecutableBuilder<
|
|
127
|
-
ReturnPreference extends "minimal" ? { updatedCount: number } :
|
|
117
|
+
ReturnPreference extends "minimal" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>
|
|
128
118
|
>
|
|
129
119
|
{
|
|
130
|
-
private tableName: string;
|
|
131
120
|
private databaseName: string;
|
|
132
121
|
private context: ExecutionContext;
|
|
133
|
-
private
|
|
134
|
-
private data: Partial<
|
|
122
|
+
private table: Occ;
|
|
123
|
+
private data: Partial<InferSchemaOutputFromFMTable<Occ>>;
|
|
135
124
|
private mode: "byId" | "byFilter";
|
|
136
125
|
private recordId?: string | number;
|
|
137
|
-
private queryBuilder?: QueryBuilder<
|
|
126
|
+
private queryBuilder?: QueryBuilder<Occ>;
|
|
138
127
|
private returnPreference: ReturnPreference;
|
|
139
128
|
private databaseUseEntityIds: boolean;
|
|
140
129
|
|
|
141
130
|
constructor(config: {
|
|
142
|
-
occurrence
|
|
143
|
-
tableName: string;
|
|
131
|
+
occurrence: Occ;
|
|
144
132
|
databaseName: string;
|
|
145
133
|
context: ExecutionContext;
|
|
146
|
-
data: Partial<
|
|
134
|
+
data: Partial<InferSchemaOutputFromFMTable<Occ>>;
|
|
147
135
|
mode: "byId" | "byFilter";
|
|
148
136
|
recordId?: string | number;
|
|
149
|
-
queryBuilder?: QueryBuilder<
|
|
137
|
+
queryBuilder?: QueryBuilder<Occ>;
|
|
150
138
|
returnPreference: ReturnPreference;
|
|
151
139
|
databaseUseEntityIds?: boolean;
|
|
152
140
|
}) {
|
|
153
|
-
this.
|
|
154
|
-
this.tableName = config.tableName;
|
|
141
|
+
this.table = config.occurrence;
|
|
155
142
|
this.databaseName = config.databaseName;
|
|
156
143
|
this.context = config.context;
|
|
157
144
|
this.data = config.data;
|
|
@@ -180,30 +167,25 @@ export class ExecutableUpdateBuilder<
|
|
|
180
167
|
* @param useEntityIds - Optional override for entity ID usage
|
|
181
168
|
*/
|
|
182
169
|
private getTableId(useEntityIds?: boolean): string {
|
|
183
|
-
if (!this.occurrence) {
|
|
184
|
-
return this.tableName;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
170
|
const contextDefault = this.context._getUseEntityIds?.() ?? false;
|
|
188
171
|
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
189
172
|
|
|
190
173
|
if (shouldUseIds) {
|
|
191
|
-
|
|
192
|
-
if (!identifiers.id) {
|
|
174
|
+
if (!isUsingEntityIds(this.table)) {
|
|
193
175
|
throw new Error(
|
|
194
|
-
`useEntityIds is true but
|
|
176
|
+
`useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`,
|
|
195
177
|
);
|
|
196
178
|
}
|
|
197
|
-
return
|
|
179
|
+
return getTableIdHelper(this.table);
|
|
198
180
|
}
|
|
199
181
|
|
|
200
|
-
return this.
|
|
182
|
+
return getTableName(this.table);
|
|
201
183
|
}
|
|
202
184
|
|
|
203
185
|
async execute(
|
|
204
186
|
options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
|
|
205
187
|
): Promise<
|
|
206
|
-
Result<ReturnPreference extends "minimal" ? { updatedCount: number } :
|
|
188
|
+
Result<ReturnPreference extends "minimal" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>
|
|
207
189
|
> {
|
|
208
190
|
// Merge database-level useEntityIds with per-request options
|
|
209
191
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
@@ -211,14 +193,31 @@ export class ExecutableUpdateBuilder<
|
|
|
211
193
|
// Get table identifier with override support
|
|
212
194
|
const tableId = this.getTableId(mergedOptions.useEntityIds);
|
|
213
195
|
|
|
196
|
+
// Validate and transform input data using input validators (writeValidators)
|
|
197
|
+
let validatedData = this.data;
|
|
198
|
+
if (this.table) {
|
|
199
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
200
|
+
const inputSchema = baseTableConfig.inputSchema;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
validatedData = await validateAndTransformInput(this.data, inputSchema);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
// If validation fails, return error immediately
|
|
206
|
+
return {
|
|
207
|
+
data: undefined,
|
|
208
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
209
|
+
} as any;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
214
213
|
// Transform field names to FMFIDs if using entity IDs
|
|
215
214
|
// Only transform if useEntityIds resolves to true (respects per-request override)
|
|
216
215
|
const shouldUseIds = mergedOptions.useEntityIds ?? false;
|
|
217
216
|
|
|
218
217
|
const transformedData =
|
|
219
|
-
this.
|
|
220
|
-
? transformFieldNamesToIds(
|
|
221
|
-
:
|
|
218
|
+
this.table && shouldUseIds
|
|
219
|
+
? transformFieldNamesToIds(validatedData, this.table)
|
|
220
|
+
: validatedData;
|
|
222
221
|
|
|
223
222
|
let url: string;
|
|
224
223
|
|
|
@@ -235,10 +234,11 @@ export class ExecutableUpdateBuilder<
|
|
|
235
234
|
const queryString = this.queryBuilder.getQueryString();
|
|
236
235
|
// The query string will have the tableId already transformed by QueryBuilder
|
|
237
236
|
// Remove the leading "/" and table name from the query string as we'll build our own URL
|
|
237
|
+
const tableName = getTableName(this.table);
|
|
238
238
|
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
239
239
|
? queryString.slice(`/${tableId}`.length)
|
|
240
|
-
: queryString.startsWith(`/${
|
|
241
|
-
? queryString.slice(`/${
|
|
240
|
+
: queryString.startsWith(`/${tableName}`)
|
|
241
|
+
? queryString.slice(`/${tableName}`.length)
|
|
242
242
|
: queryString;
|
|
243
243
|
|
|
244
244
|
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
@@ -273,7 +273,7 @@ export class ExecutableUpdateBuilder<
|
|
|
273
273
|
return {
|
|
274
274
|
data: response as ReturnPreference extends "minimal"
|
|
275
275
|
? { updatedCount: number }
|
|
276
|
-
:
|
|
276
|
+
: InferSchemaOutputFromFMTable<Occ>,
|
|
277
277
|
error: undefined,
|
|
278
278
|
};
|
|
279
279
|
} else {
|
|
@@ -290,7 +290,7 @@ export class ExecutableUpdateBuilder<
|
|
|
290
290
|
return {
|
|
291
291
|
data: { updatedCount } as ReturnPreference extends "minimal"
|
|
292
292
|
? { updatedCount: number }
|
|
293
|
-
:
|
|
293
|
+
: InferSchemaOutputFromFMTable<Occ>,
|
|
294
294
|
error: undefined,
|
|
295
295
|
};
|
|
296
296
|
}
|
|
@@ -298,12 +298,13 @@ export class ExecutableUpdateBuilder<
|
|
|
298
298
|
|
|
299
299
|
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
300
300
|
// For batch operations, use database-level setting (no per-request override available here)
|
|
301
|
+
// Note: Input validation happens in execute() and processResponse() for batch operations
|
|
301
302
|
const tableId = this.getTableId(this.databaseUseEntityIds);
|
|
302
303
|
|
|
303
304
|
// Transform field names to FMFIDs if using entity IDs
|
|
304
305
|
const transformedData =
|
|
305
|
-
this.
|
|
306
|
-
? transformFieldNamesToIds(this.data, this.
|
|
306
|
+
this.table && this.databaseUseEntityIds
|
|
307
|
+
? transformFieldNamesToIds(this.data, this.table)
|
|
307
308
|
: this.data;
|
|
308
309
|
|
|
309
310
|
let url: string;
|
|
@@ -316,10 +317,11 @@ export class ExecutableUpdateBuilder<
|
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
const queryString = this.queryBuilder.getQueryString();
|
|
320
|
+
const tableName = getTableName(this.table);
|
|
319
321
|
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
320
322
|
? queryString.slice(`/${tableId}`.length)
|
|
321
|
-
: queryString.startsWith(`/${
|
|
322
|
-
? queryString.slice(`/${
|
|
323
|
+
: queryString.startsWith(`/${tableName}`)
|
|
324
|
+
? queryString.slice(`/${tableName}`.length)
|
|
323
325
|
: queryString;
|
|
324
326
|
|
|
325
327
|
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
@@ -350,8 +352,18 @@ export class ExecutableUpdateBuilder<
|
|
|
350
352
|
response: Response,
|
|
351
353
|
options?: ExecuteOptions,
|
|
352
354
|
): Promise<
|
|
353
|
-
Result<ReturnPreference extends "minimal" ? { updatedCount: number } :
|
|
355
|
+
Result<ReturnPreference extends "minimal" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>
|
|
354
356
|
> {
|
|
357
|
+
// Check for error responses (important for batch operations)
|
|
358
|
+
if (!response.ok) {
|
|
359
|
+
const tableName = getTableName(this.table);
|
|
360
|
+
const error = await parseErrorResponse(
|
|
361
|
+
response,
|
|
362
|
+
response.url || `/${this.databaseName}/${tableName}`,
|
|
363
|
+
);
|
|
364
|
+
return { data: undefined, error };
|
|
365
|
+
}
|
|
366
|
+
|
|
355
367
|
// Check for empty response (204 No Content)
|
|
356
368
|
const text = await response.text();
|
|
357
369
|
if (!text || text.trim() === "") {
|
|
@@ -361,20 +373,37 @@ export class ExecutableUpdateBuilder<
|
|
|
361
373
|
return {
|
|
362
374
|
data: { updatedCount } as ReturnPreference extends "minimal"
|
|
363
375
|
? { updatedCount: number }
|
|
364
|
-
:
|
|
376
|
+
: InferSchemaOutputFromFMTable<Occ>,
|
|
365
377
|
error: undefined,
|
|
366
378
|
};
|
|
367
379
|
}
|
|
368
380
|
|
|
369
381
|
const rawResponse = JSON.parse(text);
|
|
370
382
|
|
|
383
|
+
// Validate and transform input data using input validators (writeValidators)
|
|
384
|
+
// This is needed for processResponse because it's called from batch operations
|
|
385
|
+
// where the data hasn't been validated yet
|
|
386
|
+
let validatedData = this.data;
|
|
387
|
+
if (this.table) {
|
|
388
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
389
|
+
const inputSchema = baseTableConfig.inputSchema;
|
|
390
|
+
try {
|
|
391
|
+
validatedData = await validateAndTransformInput(this.data, inputSchema);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
data: undefined,
|
|
395
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
396
|
+
} as any;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
371
400
|
// Handle based on return preference
|
|
372
401
|
if (this.returnPreference === "representation") {
|
|
373
402
|
// Return the full updated record
|
|
374
403
|
return {
|
|
375
404
|
data: rawResponse as ReturnPreference extends "minimal"
|
|
376
405
|
? { updatedCount: number }
|
|
377
|
-
:
|
|
406
|
+
: InferSchemaOutputFromFMTable<Occ>,
|
|
378
407
|
error: undefined,
|
|
379
408
|
};
|
|
380
409
|
} else {
|
|
@@ -391,7 +420,7 @@ export class ExecutableUpdateBuilder<
|
|
|
391
420
|
return {
|
|
392
421
|
data: { updatedCount } as ReturnPreference extends "minimal"
|
|
393
422
|
? { updatedCount: number }
|
|
394
|
-
:
|
|
423
|
+
: InferSchemaOutputFromFMTable<Occ>,
|
|
395
424
|
error: undefined,
|
|
396
425
|
};
|
|
397
426
|
}
|
package/src/errors.ts
CHANGED
|
@@ -167,6 +167,20 @@ export class ResponseParseError extends FMODataError {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
export class BatchTruncatedError extends FMODataError {
|
|
171
|
+
readonly kind = "BatchTruncatedError" as const;
|
|
172
|
+
readonly operationIndex: number;
|
|
173
|
+
readonly failedAtIndex: number;
|
|
174
|
+
|
|
175
|
+
constructor(operationIndex: number, failedAtIndex: number) {
|
|
176
|
+
super(
|
|
177
|
+
`Operation ${operationIndex} was not executed because operation ${failedAtIndex} failed`,
|
|
178
|
+
);
|
|
179
|
+
this.operationIndex = operationIndex;
|
|
180
|
+
this.failedAtIndex = failedAtIndex;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
170
184
|
// ============================================
|
|
171
185
|
// Type Guards
|
|
172
186
|
// ============================================
|
|
@@ -207,6 +221,12 @@ export function isResponseParseError(
|
|
|
207
221
|
return error instanceof ResponseParseError;
|
|
208
222
|
}
|
|
209
223
|
|
|
224
|
+
export function isBatchTruncatedError(
|
|
225
|
+
error: unknown,
|
|
226
|
+
): error is BatchTruncatedError {
|
|
227
|
+
return error instanceof BatchTruncatedError;
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
export function isFMODataError(error: unknown): error is FMODataError {
|
|
211
231
|
return error instanceof FMODataError;
|
|
212
232
|
}
|
|
@@ -237,4 +257,5 @@ export type FMODataErrorType =
|
|
|
237
257
|
| ResponseStructureError
|
|
238
258
|
| RecordCountMismatchError
|
|
239
259
|
| InvalidLocationHeaderError
|
|
240
|
-
| ResponseParseError
|
|
260
|
+
| ResponseParseError
|
|
261
|
+
| BatchTruncatedError;
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,60 @@
|
|
|
1
1
|
// Barrel file - exports all public API from the client folder
|
|
2
2
|
|
|
3
3
|
// Main API - use these functions to create tables and occurrences
|
|
4
|
-
export { defineBaseTable } from "./client/base-table";
|
|
5
|
-
export { defineTableOccurrence } from "./client/table-occurrence";
|
|
6
|
-
export { buildOccurrences } from "./client/build-occurrences";
|
|
7
4
|
export { FMServerConnection } from "./client/filemaker-odata";
|
|
8
5
|
|
|
6
|
+
// NEW ORM API - Drizzle-inspired field builders and operators
|
|
7
|
+
export {
|
|
8
|
+
// Field builders
|
|
9
|
+
textField,
|
|
10
|
+
numberField,
|
|
11
|
+
dateField,
|
|
12
|
+
timeField,
|
|
13
|
+
timestampField,
|
|
14
|
+
containerField,
|
|
15
|
+
calcField,
|
|
16
|
+
type FieldBuilder,
|
|
17
|
+
// Table definition
|
|
18
|
+
fmTableOccurrence,
|
|
19
|
+
FMTable,
|
|
20
|
+
type FMTableWithColumns as TableOccurrenceResult,
|
|
21
|
+
type InferTableSchema,
|
|
22
|
+
// Table helper functions
|
|
23
|
+
// getTableFields,
|
|
24
|
+
// getDefaultSelect,
|
|
25
|
+
// getBaseTableConfig,
|
|
26
|
+
// getFieldId,
|
|
27
|
+
// getFieldName,
|
|
28
|
+
// getTableId,
|
|
29
|
+
getTableColumns,
|
|
30
|
+
// Column references
|
|
31
|
+
type Column,
|
|
32
|
+
isColumn,
|
|
33
|
+
// Filter operators
|
|
34
|
+
type FilterExpression,
|
|
35
|
+
eq,
|
|
36
|
+
ne,
|
|
37
|
+
gt,
|
|
38
|
+
gte,
|
|
39
|
+
lt,
|
|
40
|
+
lte,
|
|
41
|
+
contains,
|
|
42
|
+
startsWith,
|
|
43
|
+
endsWith,
|
|
44
|
+
inArray,
|
|
45
|
+
notInArray,
|
|
46
|
+
isNull,
|
|
47
|
+
isNotNull,
|
|
48
|
+
and,
|
|
49
|
+
or,
|
|
50
|
+
not,
|
|
51
|
+
// OrderBy operators
|
|
52
|
+
type OrderByExpression,
|
|
53
|
+
asc,
|
|
54
|
+
desc,
|
|
55
|
+
} from "./orm/index";
|
|
56
|
+
|
|
9
57
|
// Type-only exports - for type annotations only, not direct instantiation
|
|
10
|
-
export type { BaseTable } from "./client/base-table";
|
|
11
|
-
export type { TableOccurrence } from "./client/table-occurrence";
|
|
12
58
|
export type { Database } from "./client/database";
|
|
13
59
|
export type { EntitySet } from "./client/entity-set";
|
|
14
60
|
export type {
|
|
@@ -25,6 +71,8 @@ export type {
|
|
|
25
71
|
// Utility types for type annotations
|
|
26
72
|
export type {
|
|
27
73
|
Result,
|
|
74
|
+
BatchResult,
|
|
75
|
+
BatchItemResult,
|
|
28
76
|
InferSchemaType,
|
|
29
77
|
InsertData,
|
|
30
78
|
UpdateData,
|
|
@@ -63,6 +111,7 @@ export {
|
|
|
63
111
|
ResponseStructureError,
|
|
64
112
|
RecordCountMismatchError,
|
|
65
113
|
ResponseParseError,
|
|
114
|
+
BatchTruncatedError,
|
|
66
115
|
isHTTPError,
|
|
67
116
|
isValidationError,
|
|
68
117
|
isODataError,
|
|
@@ -70,6 +119,7 @@ export {
|
|
|
70
119
|
isResponseStructureError,
|
|
71
120
|
isRecordCountMismatchError,
|
|
72
121
|
isResponseParseError,
|
|
122
|
+
isBatchTruncatedError,
|
|
73
123
|
isFMODataError,
|
|
74
124
|
} from "./errors";
|
|
75
125
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column represents a type-safe reference to a table field.
|
|
3
|
+
* Used in queries, filters, and operators to provide autocomplete and type checking.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The TypeScript type of the column's value
|
|
6
|
+
* @template TableName - The table name as a string literal type (for validation)
|
|
7
|
+
* @template IsContainer - Whether this column represents a container field (cannot be selected)
|
|
8
|
+
*/
|
|
9
|
+
export class Column<
|
|
10
|
+
T = any,
|
|
11
|
+
TableName extends string = string,
|
|
12
|
+
IsContainer extends boolean = false,
|
|
13
|
+
> {
|
|
14
|
+
readonly fieldName: string;
|
|
15
|
+
readonly entityId?: `FMFID:${string}`;
|
|
16
|
+
readonly tableName: TableName;
|
|
17
|
+
readonly tableEntityId?: `FMTID:${string}`;
|
|
18
|
+
|
|
19
|
+
// Phantom type for TypeScript inference - never actually holds a value
|
|
20
|
+
readonly _phantom!: T;
|
|
21
|
+
readonly _isContainer!: IsContainer;
|
|
22
|
+
|
|
23
|
+
constructor(config: {
|
|
24
|
+
fieldName: string;
|
|
25
|
+
entityId?: `FMFID:${string}`;
|
|
26
|
+
tableName: TableName;
|
|
27
|
+
tableEntityId?: `FMTID:${string}`;
|
|
28
|
+
}) {
|
|
29
|
+
this.fieldName = config.fieldName;
|
|
30
|
+
this.entityId = config.entityId;
|
|
31
|
+
this.tableName = config.tableName;
|
|
32
|
+
this.tableEntityId = config.tableEntityId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the field identifier (entity ID if available, otherwise field name).
|
|
37
|
+
* Used when building OData queries.
|
|
38
|
+
*/
|
|
39
|
+
getFieldIdentifier(useEntityIds?: boolean): string {
|
|
40
|
+
if (useEntityIds && this.entityId) {
|
|
41
|
+
return this.entityId;
|
|
42
|
+
}
|
|
43
|
+
return this.fieldName;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the table identifier (entity ID if available, otherwise table name).
|
|
48
|
+
* Used when building OData queries.
|
|
49
|
+
*/
|
|
50
|
+
getTableIdentifier(useEntityIds?: boolean): string {
|
|
51
|
+
if (useEntityIds && this.tableEntityId) {
|
|
52
|
+
return this.tableEntityId;
|
|
53
|
+
}
|
|
54
|
+
return this.tableName;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if this column is from a specific table.
|
|
59
|
+
* Useful for validation in cross-table operations.
|
|
60
|
+
*/
|
|
61
|
+
isFromTable(tableName: string): boolean {
|
|
62
|
+
return this.tableName === tableName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a string representation for debugging.
|
|
67
|
+
*/
|
|
68
|
+
toString(): string {
|
|
69
|
+
return `${this.tableName}.${this.fieldName}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Type guard to check if a value is a Column instance.
|
|
75
|
+
*/
|
|
76
|
+
export function isColumn(value: any): value is Column<any, any, any> {
|
|
77
|
+
return value instanceof Column;
|
|
78
|
+
}
|