@proofkit/fmodata 0.1.0-alpha.4 → 0.1.0-alpha.7
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 +690 -31
- package/dist/esm/client/base-table.d.ts +122 -5
- package/dist/esm/client/base-table.js +46 -5
- package/dist/esm/client/base-table.js.map +1 -1
- package/dist/esm/client/batch-builder.d.ts +54 -0
- package/dist/esm/client/batch-builder.js +179 -0
- package/dist/esm/client/batch-builder.js.map +1 -0
- package/dist/esm/client/batch-request.d.ts +61 -0
- package/dist/esm/client/batch-request.js +252 -0
- package/dist/esm/client/batch-request.js.map +1 -0
- package/dist/esm/client/database.d.ts +54 -5
- package/dist/esm/client/database.js +118 -15
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +21 -2
- package/dist/esm/client/delete-builder.js +96 -32
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +22 -8
- package/dist/esm/client/entity-set.js +28 -8
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +22 -3
- package/dist/esm/client/filemaker-odata.js +122 -27
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +38 -3
- package/dist/esm/client/insert-builder.js +231 -34
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +26 -5
- package/dist/esm/client/query-builder.js +455 -208
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +19 -4
- package/dist/esm/client/record-builder.js +132 -40
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +38 -0
- package/dist/esm/client/schema-manager.d.ts +57 -0
- package/dist/esm/client/schema-manager.js +132 -0
- package/dist/esm/client/schema-manager.js.map +1 -0
- package/dist/esm/client/table-occurrence.d.ts +66 -2
- package/dist/esm/client/table-occurrence.js +36 -1
- package/dist/esm/client/table-occurrence.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -11
- package/dist/esm/client/update-builder.js +135 -31
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +73 -0
- package/dist/esm/errors.js +148 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.js +27 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transform.d.ts +65 -0
- package/dist/esm/transform.js +114 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +89 -5
- package/dist/esm/validation.d.ts +6 -3
- package/dist/esm/validation.js +104 -33
- package/dist/esm/validation.js.map +1 -1
- package/package.json +10 -1
- package/src/client/base-table.ts +155 -8
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +173 -16
- package/src/client/delete-builder.ts +149 -48
- package/src/client/entity-set.ts +99 -15
- package/src/client/filemaker-odata.ts +178 -34
- package/src/client/insert-builder.ts +350 -40
- package/src/client/query-builder.ts +609 -236
- package/src/client/record-builder.ts +186 -53
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +118 -4
- package/src/client/update-builder.ts +235 -49
- package/src/errors.ts +217 -0
- package/src/index.ts +43 -1
- package/src/transform.ts +249 -0
- package/src/types.ts +201 -35
- package/src/validation.ts +120 -36
package/src/client/entity-set.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from "zod/v4";
|
|
2
1
|
import type {
|
|
3
2
|
ExecutionContext,
|
|
4
3
|
InferSchemaType,
|
|
@@ -6,6 +5,7 @@ import type {
|
|
|
6
5
|
InsertData,
|
|
7
6
|
UpdateData,
|
|
8
7
|
} from "../types";
|
|
8
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
9
9
|
import type { BaseTable } from "./base-table";
|
|
10
10
|
import type { TableOccurrence } from "./table-occurrence";
|
|
11
11
|
import { QueryBuilder } from "./query-builder";
|
|
@@ -13,6 +13,7 @@ import { RecordBuilder } from "./record-builder";
|
|
|
13
13
|
import { InsertBuilder } from "./insert-builder";
|
|
14
14
|
import { DeleteBuilder } from "./delete-builder";
|
|
15
15
|
import { UpdateBuilder } from "./update-builder";
|
|
16
|
+
import { Database } from "./database";
|
|
16
17
|
|
|
17
18
|
// Helper type to extract navigation relation names from an occurrence
|
|
18
19
|
type ExtractNavigationNames<
|
|
@@ -59,19 +60,19 @@ type FindNavigationTarget<
|
|
|
59
60
|
? Name extends keyof Nav
|
|
60
61
|
? ResolveNavigationItem<Nav[Name]>
|
|
61
62
|
: TableOccurrence<
|
|
62
|
-
BaseTable<Record<string,
|
|
63
|
+
BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
|
|
63
64
|
any,
|
|
64
65
|
any,
|
|
65
66
|
any
|
|
66
67
|
>
|
|
67
68
|
: TableOccurrence<
|
|
68
|
-
BaseTable<Record<string,
|
|
69
|
+
BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
|
|
69
70
|
any,
|
|
70
71
|
any,
|
|
71
72
|
any
|
|
72
73
|
>
|
|
73
74
|
: TableOccurrence<
|
|
74
|
-
BaseTable<Record<string,
|
|
75
|
+
BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
|
|
75
76
|
any,
|
|
76
77
|
any,
|
|
77
78
|
any
|
|
@@ -85,20 +86,21 @@ type GetTargetSchemaType<
|
|
|
85
86
|
TableOccurrence<infer BT, any, any, any>,
|
|
86
87
|
]
|
|
87
88
|
? [BT] extends [BaseTable<infer S, any>]
|
|
88
|
-
? [S] extends [Record<string,
|
|
89
|
+
? [S] extends [Record<string, StandardSchemaV1>]
|
|
89
90
|
? InferSchemaType<S>
|
|
90
91
|
: Record<string, any>
|
|
91
92
|
: Record<string, any>
|
|
92
93
|
: Record<string, any>;
|
|
93
94
|
|
|
94
95
|
export class EntitySet<
|
|
95
|
-
Schema extends Record<string,
|
|
96
|
+
Schema extends Record<string, StandardSchemaV1> = any,
|
|
96
97
|
Occ extends TableOccurrence<any, any, any, any> | undefined = undefined,
|
|
97
98
|
> {
|
|
98
99
|
private occurrence?: Occ;
|
|
99
100
|
private tableName: string;
|
|
100
101
|
private databaseName: string;
|
|
101
102
|
private context: ExecutionContext;
|
|
103
|
+
private database: Database<any>; // Database instance for accessing occurrences
|
|
102
104
|
private isNavigateFromEntitySet?: boolean;
|
|
103
105
|
private navigateRelation?: string;
|
|
104
106
|
private navigateSourceTableName?: string;
|
|
@@ -108,30 +110,39 @@ export class EntitySet<
|
|
|
108
110
|
tableName: string;
|
|
109
111
|
databaseName: string;
|
|
110
112
|
context: ExecutionContext;
|
|
113
|
+
database?: any;
|
|
111
114
|
}) {
|
|
112
115
|
this.occurrence = config.occurrence;
|
|
113
116
|
this.tableName = config.tableName;
|
|
114
117
|
this.databaseName = config.databaseName;
|
|
115
118
|
this.context = config.context;
|
|
119
|
+
this.database = config.database;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
// Type-only method to help TypeScript infer the schema from occurrence
|
|
119
123
|
static create<
|
|
120
|
-
OccurrenceSchema extends Record<string,
|
|
124
|
+
OccurrenceSchema extends Record<string, StandardSchemaV1>,
|
|
121
125
|
Occ extends
|
|
122
|
-
| TableOccurrence<
|
|
126
|
+
| TableOccurrence<
|
|
127
|
+
BaseTable<OccurrenceSchema, any, any, any>,
|
|
128
|
+
any,
|
|
129
|
+
any,
|
|
130
|
+
any
|
|
131
|
+
>
|
|
123
132
|
| undefined = undefined,
|
|
124
133
|
>(config: {
|
|
125
134
|
occurrence?: Occ;
|
|
126
135
|
tableName: string;
|
|
127
136
|
databaseName: string;
|
|
128
137
|
context: ExecutionContext;
|
|
138
|
+
database: Database<any>;
|
|
129
139
|
}): EntitySet<OccurrenceSchema, Occ> {
|
|
130
140
|
return new EntitySet<OccurrenceSchema, Occ>({
|
|
131
141
|
occurrence: config.occurrence,
|
|
132
142
|
tableName: config.tableName,
|
|
133
143
|
databaseName: config.databaseName,
|
|
134
144
|
context: config.context,
|
|
145
|
+
database: config.database,
|
|
135
146
|
});
|
|
136
147
|
}
|
|
137
148
|
|
|
@@ -157,6 +168,7 @@ export class EntitySet<
|
|
|
157
168
|
tableName: this.tableName,
|
|
158
169
|
databaseName: this.databaseName,
|
|
159
170
|
context: this.context,
|
|
171
|
+
databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
|
|
160
172
|
});
|
|
161
173
|
|
|
162
174
|
// Apply defaultSelect if occurrence exists and select hasn't been called
|
|
@@ -209,6 +221,7 @@ export class EntitySet<
|
|
|
209
221
|
databaseName: this.databaseName,
|
|
210
222
|
context: this.context,
|
|
211
223
|
recordId: id,
|
|
224
|
+
databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
|
|
212
225
|
});
|
|
213
226
|
// Propagate navigation context if present
|
|
214
227
|
if (this.isNavigateFromEntitySet) {
|
|
@@ -219,49 +232,119 @@ export class EntitySet<
|
|
|
219
232
|
return builder;
|
|
220
233
|
}
|
|
221
234
|
|
|
235
|
+
// Overload: when returnFullRecord is explicitly false
|
|
222
236
|
insert(
|
|
223
237
|
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
224
238
|
? BT extends BaseTable<any, any, any, any>
|
|
225
239
|
? InsertData<BT>
|
|
226
240
|
: Partial<InferSchemaType<Schema>>
|
|
227
241
|
: Partial<InferSchemaType<Schema>>,
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
options: { returnFullRecord: false },
|
|
243
|
+
): InsertBuilder<InferSchemaType<Schema>, Occ, "minimal">;
|
|
244
|
+
|
|
245
|
+
// Overload: when returnFullRecord is true or omitted (default)
|
|
246
|
+
insert(
|
|
247
|
+
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
248
|
+
? BT extends BaseTable<any, any, any, any>
|
|
249
|
+
? InsertData<BT>
|
|
250
|
+
: Partial<InferSchemaType<Schema>>
|
|
251
|
+
: Partial<InferSchemaType<Schema>>,
|
|
252
|
+
options?: { returnFullRecord?: true },
|
|
253
|
+
): InsertBuilder<InferSchemaType<Schema>, Occ, "representation">;
|
|
254
|
+
|
|
255
|
+
// Implementation
|
|
256
|
+
insert(
|
|
257
|
+
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
258
|
+
? BT extends BaseTable<any, any, any, any>
|
|
259
|
+
? InsertData<BT>
|
|
260
|
+
: Partial<InferSchemaType<Schema>>
|
|
261
|
+
: Partial<InferSchemaType<Schema>>,
|
|
262
|
+
options?: { returnFullRecord?: boolean },
|
|
263
|
+
): InsertBuilder<InferSchemaType<Schema>, Occ, "minimal" | "representation"> {
|
|
264
|
+
const returnPref =
|
|
265
|
+
options?.returnFullRecord === false ? "minimal" : "representation";
|
|
266
|
+
return new InsertBuilder<InferSchemaType<Schema>, Occ, typeof returnPref>({
|
|
230
267
|
occurrence: this.occurrence,
|
|
231
268
|
tableName: this.tableName,
|
|
232
269
|
databaseName: this.databaseName,
|
|
233
270
|
context: this.context,
|
|
234
271
|
data: data as Partial<InferSchemaType<Schema>>,
|
|
272
|
+
returnPreference: returnPref as any,
|
|
273
|
+
databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
|
|
235
274
|
});
|
|
236
275
|
}
|
|
237
276
|
|
|
277
|
+
// Overload: when returnFullRecord is explicitly true
|
|
238
278
|
update(
|
|
239
279
|
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
240
280
|
? BT extends BaseTable<any, any, any, any>
|
|
241
281
|
? UpdateData<BT>
|
|
242
282
|
: Partial<InferSchemaType<Schema>>
|
|
243
283
|
: Partial<InferSchemaType<Schema>>,
|
|
284
|
+
options: { returnFullRecord: true },
|
|
244
285
|
): UpdateBuilder<
|
|
245
286
|
InferSchemaType<Schema>,
|
|
246
287
|
Occ extends TableOccurrence<infer BT, any, any, any>
|
|
247
288
|
? BT extends BaseTable<any, any, any, any>
|
|
248
289
|
? BT
|
|
249
290
|
: BaseTable<Schema, any, any, any>
|
|
250
|
-
: BaseTable<Schema, any, any, any
|
|
291
|
+
: BaseTable<Schema, any, any, any>,
|
|
292
|
+
"representation"
|
|
293
|
+
>;
|
|
294
|
+
|
|
295
|
+
// Overload: when returnFullRecord is false or omitted (default returns count)
|
|
296
|
+
update(
|
|
297
|
+
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
298
|
+
? BT extends BaseTable<any, any, any, any>
|
|
299
|
+
? UpdateData<BT>
|
|
300
|
+
: Partial<InferSchemaType<Schema>>
|
|
301
|
+
: Partial<InferSchemaType<Schema>>,
|
|
302
|
+
options?: { returnFullRecord?: false },
|
|
303
|
+
): UpdateBuilder<
|
|
304
|
+
InferSchemaType<Schema>,
|
|
305
|
+
Occ extends TableOccurrence<infer BT, any, any, any>
|
|
306
|
+
? BT extends BaseTable<any, any, any, any>
|
|
307
|
+
? BT
|
|
308
|
+
: BaseTable<Schema, any, any, any>
|
|
309
|
+
: BaseTable<Schema, any, any, any>,
|
|
310
|
+
"minimal"
|
|
311
|
+
>;
|
|
312
|
+
|
|
313
|
+
// Implementation
|
|
314
|
+
update(
|
|
315
|
+
data: Occ extends TableOccurrence<infer BT, any, any, any>
|
|
316
|
+
? BT extends BaseTable<any, any, any, any>
|
|
317
|
+
? UpdateData<BT>
|
|
318
|
+
: Partial<InferSchemaType<Schema>>
|
|
319
|
+
: Partial<InferSchemaType<Schema>>,
|
|
320
|
+
options?: { returnFullRecord?: boolean },
|
|
321
|
+
): UpdateBuilder<
|
|
322
|
+
InferSchemaType<Schema>,
|
|
323
|
+
Occ extends TableOccurrence<infer BT, any, any, any>
|
|
324
|
+
? BT extends BaseTable<any, any, any, any>
|
|
325
|
+
? BT
|
|
326
|
+
: BaseTable<Schema, any, any, any>
|
|
327
|
+
: BaseTable<Schema, any, any, any>,
|
|
328
|
+
"minimal" | "representation"
|
|
251
329
|
> {
|
|
330
|
+
const returnPref =
|
|
331
|
+
options?.returnFullRecord === true ? "representation" : "minimal";
|
|
252
332
|
return new UpdateBuilder<
|
|
253
333
|
InferSchemaType<Schema>,
|
|
254
334
|
Occ extends TableOccurrence<infer BT, any, any, any>
|
|
255
335
|
? BT extends BaseTable<any, any, any, any>
|
|
256
336
|
? BT
|
|
257
337
|
: BaseTable<Schema, any, any, any>
|
|
258
|
-
: BaseTable<Schema, any, any, any
|
|
338
|
+
: BaseTable<Schema, any, any, any>,
|
|
339
|
+
typeof returnPref
|
|
259
340
|
>({
|
|
260
341
|
occurrence: this.occurrence,
|
|
261
342
|
tableName: this.tableName,
|
|
262
343
|
databaseName: this.databaseName,
|
|
263
344
|
context: this.context,
|
|
264
345
|
data: data as Partial<InferSchemaType<Schema>>,
|
|
346
|
+
returnPreference: returnPref as any,
|
|
347
|
+
databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
|
|
265
348
|
});
|
|
266
349
|
}
|
|
267
350
|
|
|
@@ -271,6 +354,7 @@ export class EntitySet<
|
|
|
271
354
|
tableName: this.tableName,
|
|
272
355
|
databaseName: this.databaseName,
|
|
273
356
|
context: this.context,
|
|
357
|
+
databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
|
|
274
358
|
});
|
|
275
359
|
}
|
|
276
360
|
|
|
@@ -280,15 +364,15 @@ export class EntitySet<
|
|
|
280
364
|
): EntitySet<
|
|
281
365
|
ExtractSchemaFromOccurrence<
|
|
282
366
|
FindNavigationTarget<Occ, RelationName>
|
|
283
|
-
> extends Record<string,
|
|
367
|
+
> extends Record<string, StandardSchemaV1>
|
|
284
368
|
? ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>
|
|
285
|
-
: Record<string,
|
|
369
|
+
: Record<string, StandardSchemaV1>,
|
|
286
370
|
FindNavigationTarget<Occ, RelationName>
|
|
287
371
|
>;
|
|
288
372
|
// Overload for arbitrary strings - returns generic EntitySet
|
|
289
373
|
navigate(
|
|
290
374
|
relationName: string,
|
|
291
|
-
): EntitySet<Record<string,
|
|
375
|
+
): EntitySet<Record<string, StandardSchemaV1>, undefined>;
|
|
292
376
|
// Implementation
|
|
293
377
|
navigate(relationName: string): EntitySet<any, any> {
|
|
294
378
|
// Use the target occurrence if available, otherwise allow untyped navigation
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import createClient, {
|
|
2
|
-
|
|
1
|
+
import createClient, {
|
|
2
|
+
FFetchOptions,
|
|
3
|
+
TimeoutError,
|
|
4
|
+
AbortError,
|
|
5
|
+
NetworkError,
|
|
6
|
+
RetryLimitError,
|
|
7
|
+
CircuitOpenError,
|
|
8
|
+
} from "@fetchkit/ffetch";
|
|
9
|
+
import type { Auth, ExecutionContext, Result } from "../types";
|
|
10
|
+
import { HTTPError, ODataError, SchemaLockedError } from "../errors";
|
|
3
11
|
import { Database } from "./database";
|
|
4
12
|
import { TableOccurrence } from "./table-occurrence";
|
|
5
13
|
|
|
@@ -7,6 +15,7 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
7
15
|
private fetchClient: ReturnType<typeof createClient>;
|
|
8
16
|
private serverUrl: string;
|
|
9
17
|
private auth: Auth;
|
|
18
|
+
private useEntityIds: boolean = false;
|
|
10
19
|
constructor(config: {
|
|
11
20
|
serverUrl: string;
|
|
12
21
|
auth: Auth;
|
|
@@ -27,14 +36,42 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
27
36
|
this.auth = config.auth;
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
* Sets whether to use FileMaker entity IDs (FMFID/FMTID) in requests
|
|
42
|
+
*/
|
|
43
|
+
_setUseEntityIds(useEntityIds: boolean): void {
|
|
44
|
+
this.useEntityIds = useEntityIds;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @internal
|
|
49
|
+
* Gets whether to use FileMaker entity IDs (FMFID/FMTID) in requests
|
|
50
|
+
*/
|
|
51
|
+
_getUseEntityIds(): boolean {
|
|
52
|
+
return this.useEntityIds;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @internal
|
|
57
|
+
* Gets the base URL for OData requests
|
|
58
|
+
*/
|
|
59
|
+
_getBaseUrl(): string {
|
|
60
|
+
return `${this.serverUrl}${"apiKey" in this.auth ? `/otto` : ""}/fmi/odata/v4`;
|
|
61
|
+
}
|
|
62
|
+
|
|
30
63
|
/**
|
|
31
64
|
* @internal
|
|
32
65
|
*/
|
|
33
66
|
async _makeRequest<T>(
|
|
34
67
|
url: string,
|
|
35
|
-
options?: RequestInit & FFetchOptions,
|
|
36
|
-
): Promise<T
|
|
68
|
+
options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
|
|
69
|
+
): Promise<Result<T>> {
|
|
37
70
|
const baseUrl = `${this.serverUrl}${"apiKey" in this.auth ? `/otto` : ""}/fmi/odata/v4`;
|
|
71
|
+
const fullUrl = baseUrl + url;
|
|
72
|
+
|
|
73
|
+
// Use per-request override if provided, otherwise use the database-level setting
|
|
74
|
+
const useEntityIds = options?.useEntityIds ?? this.useEntityIds;
|
|
38
75
|
|
|
39
76
|
const headers = {
|
|
40
77
|
Authorization:
|
|
@@ -43,6 +80,7 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
43
80
|
: `Basic ${btoa(`${this.auth.username}:${this.auth.password}`)}`,
|
|
44
81
|
"Content-Type": "application/json",
|
|
45
82
|
Accept: "application/json",
|
|
83
|
+
...(useEntityIds ? { Prefer: "fmodata.entity-ids" } : {}),
|
|
46
84
|
...(options?.headers || {}),
|
|
47
85
|
};
|
|
48
86
|
|
|
@@ -61,44 +99,147 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
61
99
|
? createClient({ retries: 0, fetchHandler })
|
|
62
100
|
: this.fetchClient;
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
try {
|
|
103
|
+
const finalOptions = {
|
|
104
|
+
...restOptions,
|
|
105
|
+
headers,
|
|
106
|
+
};
|
|
68
107
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
108
|
+
// For batch requests, use native fetch to avoid any potential serialization issues with ffetch
|
|
109
|
+
const resp = url.includes("/$batch")
|
|
110
|
+
? await fetch(fullUrl, {
|
|
111
|
+
method: finalOptions.method,
|
|
112
|
+
headers: finalOptions.headers,
|
|
113
|
+
body: finalOptions.body,
|
|
114
|
+
})
|
|
115
|
+
: await clientToUse(fullUrl, finalOptions);
|
|
74
116
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
// Handle HTTP errors
|
|
118
|
+
if (!resp.ok) {
|
|
119
|
+
// Try to parse error body if it's JSON
|
|
120
|
+
let errorBody;
|
|
121
|
+
try {
|
|
122
|
+
if (resp.headers.get("content-type")?.includes("application/json")) {
|
|
123
|
+
errorBody = await resp.json();
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Ignore JSON parse errors
|
|
127
|
+
}
|
|
81
128
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
// Check if it's an OData error response
|
|
130
|
+
if (errorBody?.error) {
|
|
131
|
+
const errorCode = errorBody.error.code;
|
|
132
|
+
const errorMessage = errorBody.error.message || resp.statusText;
|
|
133
|
+
|
|
134
|
+
// Check for schema locked error (code 303)
|
|
135
|
+
if (errorCode === "303" || errorCode === 303) {
|
|
136
|
+
return {
|
|
137
|
+
data: undefined,
|
|
138
|
+
error: new SchemaLockedError(
|
|
139
|
+
fullUrl,
|
|
140
|
+
errorMessage,
|
|
141
|
+
errorBody.error,
|
|
142
|
+
),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
data: undefined,
|
|
148
|
+
error: new ODataError(
|
|
149
|
+
fullUrl,
|
|
150
|
+
errorMessage,
|
|
151
|
+
errorCode,
|
|
152
|
+
errorBody.error,
|
|
153
|
+
),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
data: undefined,
|
|
159
|
+
error: new HTTPError(
|
|
160
|
+
fullUrl,
|
|
161
|
+
resp.status,
|
|
162
|
+
resp.statusText,
|
|
163
|
+
errorBody,
|
|
164
|
+
),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check for affected rows header (for DELETE and bulk PATCH operations)
|
|
169
|
+
// FileMaker may return this with 204 No Content or 200 OK
|
|
170
|
+
const affectedRows = resp.headers.get("fmodata.affected_rows");
|
|
171
|
+
if (affectedRows !== null) {
|
|
172
|
+
return { data: parseInt(affectedRows, 10) as T, error: undefined };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Handle 204 No Content with no body
|
|
176
|
+
if (resp.status === 204) {
|
|
177
|
+
// Check for Location header (used for insert with return=minimal)
|
|
178
|
+
// Use optional chaining for safety with mocks that might not have proper headers
|
|
179
|
+
const locationHeader =
|
|
180
|
+
resp.headers?.get?.("Location") || resp.headers?.get?.("location");
|
|
181
|
+
if (locationHeader) {
|
|
182
|
+
// Return the location header so InsertBuilder can extract ROWID
|
|
183
|
+
return { data: { _location: locationHeader } as T, error: undefined };
|
|
184
|
+
}
|
|
185
|
+
return { data: 0 as T, error: undefined };
|
|
186
|
+
}
|
|
86
187
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
188
|
+
// Parse response
|
|
189
|
+
if (resp.headers.get("content-type")?.includes("application/json")) {
|
|
190
|
+
const data = await resp.json();
|
|
191
|
+
|
|
192
|
+
// Check for embedded OData errors
|
|
193
|
+
if (data.error) {
|
|
194
|
+
const errorCode = data.error.code;
|
|
195
|
+
const errorMessage = data.error.message || "Unknown OData error";
|
|
196
|
+
|
|
197
|
+
// Check for schema locked error (code 303)
|
|
198
|
+
if (errorCode === "303" || errorCode === 303) {
|
|
199
|
+
return {
|
|
200
|
+
data: undefined,
|
|
201
|
+
error: new SchemaLockedError(fullUrl, errorMessage, data.error),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
data: undefined,
|
|
207
|
+
error: new ODataError(fullUrl, errorMessage, errorCode, data.error),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { data: data as T, error: undefined };
|
|
91
212
|
}
|
|
92
|
-
|
|
213
|
+
|
|
214
|
+
return { data: (await resp.text()) as T, error: undefined };
|
|
215
|
+
} catch (err) {
|
|
216
|
+
// Map ffetch errors - return them directly (no re-wrapping)
|
|
217
|
+
if (
|
|
218
|
+
err instanceof TimeoutError ||
|
|
219
|
+
err instanceof AbortError ||
|
|
220
|
+
err instanceof NetworkError ||
|
|
221
|
+
err instanceof RetryLimitError ||
|
|
222
|
+
err instanceof CircuitOpenError
|
|
223
|
+
) {
|
|
224
|
+
return { data: undefined, error: err };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Unknown error - wrap it as NetworkError
|
|
228
|
+
return {
|
|
229
|
+
data: undefined,
|
|
230
|
+
error: new NetworkError(fullUrl, err),
|
|
231
|
+
};
|
|
93
232
|
}
|
|
94
|
-
return (await resp.text()) as T;
|
|
95
233
|
}
|
|
96
234
|
|
|
97
235
|
database<
|
|
98
236
|
const Occurrences extends readonly TableOccurrence<any, any, any, any>[],
|
|
99
237
|
>(
|
|
100
238
|
name: string,
|
|
101
|
-
config?: {
|
|
239
|
+
config?: {
|
|
240
|
+
occurrences?: Occurrences | undefined;
|
|
241
|
+
useEntityIds?: boolean;
|
|
242
|
+
},
|
|
102
243
|
): Database<Occurrences> {
|
|
103
244
|
return new Database(name, this, config);
|
|
104
245
|
}
|
|
@@ -108,11 +249,14 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
108
249
|
* @returns Promise resolving to an array of database names
|
|
109
250
|
*/
|
|
110
251
|
async listDatabaseNames(): Promise<string[]> {
|
|
111
|
-
const
|
|
252
|
+
const result = await this._makeRequest<{
|
|
112
253
|
value?: Array<{ name: string }>;
|
|
113
|
-
};
|
|
114
|
-
if (
|
|
115
|
-
|
|
254
|
+
}>("/");
|
|
255
|
+
if (result.error) {
|
|
256
|
+
throw result.error;
|
|
257
|
+
}
|
|
258
|
+
if (result.data.value && Array.isArray(result.data.value)) {
|
|
259
|
+
return result.data.value.map((item) => item.name);
|
|
116
260
|
}
|
|
117
261
|
return [];
|
|
118
262
|
}
|