@proofkit/fmodata 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +746 -65
- package/dist/esm/client/base-table.d.ts +117 -5
- package/dist/esm/client/base-table.js +43 -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 +55 -6
- 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 +25 -11
- package/dist/esm/client/entity-set.js +31 -11
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +23 -4
- package/dist/esm/client/filemaker-odata.js +124 -29
- 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 +27 -6
- package/dist/esm/client/query-builder.js +457 -210
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +96 -9
- package/dist/esm/client/record-builder.js +378 -39
- 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 +48 -1
- package/dist/esm/client/table-occurrence.js +29 -2
- 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 +10 -3
- package/dist/esm/index.js +28 -5
- 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 +158 -8
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +175 -18
- package/src/client/delete-builder.ts +149 -48
- package/src/client/entity-set.ts +114 -23
- package/src/client/filemaker-odata.ts +179 -35
- package/src/client/insert-builder.ts +350 -40
- package/src/client/query-builder.ts +616 -237
- package/src/client/query-builder.ts.bak +1457 -0
- package/src/client/record-builder.ts +692 -65
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +78 -3
- package/src/client/update-builder.ts +235 -49
- package/src/errors.ts +217 -0
- package/src/index.ts +59 -2
- package/src/transform.ts +249 -0
- package/src/types.ts +201 -35
- package/src/validation.ts +120 -36
|
@@ -4,21 +4,41 @@ import type {
|
|
|
4
4
|
Result,
|
|
5
5
|
ODataRecordMetadata,
|
|
6
6
|
InferSchemaType,
|
|
7
|
+
ExecuteOptions,
|
|
8
|
+
ConditionallyWithODataAnnotations,
|
|
7
9
|
} from "../types";
|
|
8
10
|
import type { TableOccurrence } from "./table-occurrence";
|
|
9
11
|
import { validateSingleResponse } from "../validation";
|
|
10
12
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
13
|
+
import {
|
|
14
|
+
transformFieldNamesToIds,
|
|
15
|
+
transformTableName,
|
|
16
|
+
transformResponseFields,
|
|
17
|
+
getTableIdentifiers,
|
|
18
|
+
} from "../transform";
|
|
19
|
+
import { InvalidLocationHeaderError } from "../errors";
|
|
20
|
+
|
|
21
|
+
export type InsertOptions = {
|
|
22
|
+
return?: "minimal" | "representation";
|
|
23
|
+
};
|
|
11
24
|
|
|
12
25
|
export class InsertBuilder<
|
|
13
26
|
T extends Record<string, any>,
|
|
14
27
|
Occ extends TableOccurrence<any, any, any, any> | undefined = undefined,
|
|
15
|
-
|
|
28
|
+
ReturnPreference extends "minimal" | "representation" = "representation",
|
|
29
|
+
> implements
|
|
30
|
+
ExecutableBuilder<
|
|
31
|
+
ReturnPreference extends "minimal" ? { ROWID: number } : T
|
|
32
|
+
>
|
|
16
33
|
{
|
|
17
34
|
private occurrence?: Occ;
|
|
18
35
|
private tableName: string;
|
|
19
36
|
private databaseName: string;
|
|
20
37
|
private context: ExecutionContext;
|
|
21
38
|
private data: Partial<T>;
|
|
39
|
+
private returnPreference: ReturnPreference;
|
|
40
|
+
|
|
41
|
+
private databaseUseEntityIds: boolean;
|
|
22
42
|
|
|
23
43
|
constructor(config: {
|
|
24
44
|
occurrence?: Occ;
|
|
@@ -26,68 +46,358 @@ export class InsertBuilder<
|
|
|
26
46
|
databaseName: string;
|
|
27
47
|
context: ExecutionContext;
|
|
28
48
|
data: Partial<T>;
|
|
49
|
+
returnPreference?: ReturnPreference;
|
|
50
|
+
databaseUseEntityIds?: boolean;
|
|
29
51
|
}) {
|
|
30
52
|
this.occurrence = config.occurrence;
|
|
31
53
|
this.tableName = config.tableName;
|
|
32
54
|
this.databaseName = config.databaseName;
|
|
33
55
|
this.context = config.context;
|
|
34
56
|
this.data = config.data;
|
|
57
|
+
this.returnPreference = (config.returnPreference ||
|
|
58
|
+
"representation") as ReturnPreference;
|
|
59
|
+
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
35
60
|
}
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
64
|
+
*/
|
|
65
|
+
private mergeExecuteOptions(
|
|
66
|
+
options?: RequestInit & FFetchOptions & ExecuteOptions,
|
|
67
|
+
): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
|
|
68
|
+
// If useEntityIds is not set in options, use the database-level setting
|
|
69
|
+
return {
|
|
70
|
+
...options,
|
|
71
|
+
useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Helper to conditionally strip OData annotations based on options
|
|
77
|
+
*/
|
|
78
|
+
private stripODataAnnotationsIfNeeded<T extends Record<string, any>>(
|
|
79
|
+
data: T,
|
|
80
|
+
options?: ExecuteOptions,
|
|
81
|
+
): T {
|
|
82
|
+
// Only include annotations if explicitly requested
|
|
83
|
+
if (options?.includeODataAnnotations === true) {
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Strip OData annotations
|
|
88
|
+
const { "@id": _id, "@editLink": _editLink, ...rest } = data;
|
|
89
|
+
return rest as T;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse ROWID from Location header
|
|
94
|
+
* Expected formats:
|
|
95
|
+
* - contacts(ROWID=4583)
|
|
96
|
+
* - contacts('some-uuid')
|
|
97
|
+
*/
|
|
98
|
+
private parseLocationHeader(locationHeader: string | undefined): number {
|
|
99
|
+
if (!locationHeader) {
|
|
100
|
+
throw new InvalidLocationHeaderError(
|
|
101
|
+
"Location header is required but was not provided",
|
|
63
102
|
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Try to match ROWID=number pattern
|
|
106
|
+
const rowidMatch = locationHeader.match(/ROWID=(\d+)/);
|
|
107
|
+
if (rowidMatch && rowidMatch[1]) {
|
|
108
|
+
return parseInt(rowidMatch[1], 10);
|
|
109
|
+
}
|
|
64
110
|
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
// Try to extract value from parentheses and parse as number
|
|
112
|
+
const parenMatch = locationHeader.match(/\(['"]?([^'"]+)['"]?\)/);
|
|
113
|
+
if (parenMatch && parenMatch[1]) {
|
|
114
|
+
const value = parenMatch[1];
|
|
115
|
+
const numValue = parseInt(value, 10);
|
|
116
|
+
if (!isNaN(numValue)) {
|
|
117
|
+
return numValue;
|
|
67
118
|
}
|
|
119
|
+
}
|
|
68
120
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
throw new InvalidLocationHeaderError(
|
|
122
|
+
`Could not extract ROWID from Location header: ${locationHeader}`,
|
|
123
|
+
locationHeader,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
129
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
130
|
+
*/
|
|
131
|
+
private getTableId(useEntityIds?: boolean): string {
|
|
132
|
+
if (!this.occurrence) {
|
|
133
|
+
return this.tableName;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const contextDefault = this.context._getUseEntityIds?.() ?? false;
|
|
137
|
+
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
138
|
+
|
|
139
|
+
if (shouldUseIds) {
|
|
140
|
+
const identifiers = getTableIdentifiers(this.occurrence);
|
|
141
|
+
if (!identifiers.id) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return identifiers.id;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this.occurrence.getTableName();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async execute<EO extends ExecuteOptions>(
|
|
153
|
+
options?: RequestInit & FFetchOptions & EO,
|
|
154
|
+
): Promise<
|
|
155
|
+
Result<
|
|
156
|
+
ReturnPreference extends "minimal"
|
|
157
|
+
? { ROWID: number }
|
|
158
|
+
: ConditionallyWithODataAnnotations<
|
|
159
|
+
T,
|
|
160
|
+
EO["includeODataAnnotations"] extends true ? true : false
|
|
161
|
+
>
|
|
162
|
+
>
|
|
163
|
+
> {
|
|
164
|
+
// Merge database-level useEntityIds with per-request options
|
|
165
|
+
const mergedOptions = this.mergeExecuteOptions(options);
|
|
166
|
+
|
|
167
|
+
// Get table identifier with override support
|
|
168
|
+
const tableId = this.getTableId(mergedOptions.useEntityIds);
|
|
169
|
+
const url = `/${this.databaseName}/${tableId}`;
|
|
170
|
+
|
|
171
|
+
// Transform field names to FMFIDs if using entity IDs
|
|
172
|
+
// Only transform if useEntityIds resolves to true (respects per-request override)
|
|
173
|
+
const shouldUseIds = mergedOptions.useEntityIds ?? false;
|
|
174
|
+
|
|
175
|
+
const transformedData = this.occurrence?.baseTable && shouldUseIds
|
|
176
|
+
? transformFieldNamesToIds(this.data, this.occurrence.baseTable)
|
|
177
|
+
: this.data;
|
|
178
|
+
|
|
179
|
+
// Set Prefer header based on return preference
|
|
180
|
+
const preferHeader =
|
|
181
|
+
this.returnPreference === "minimal"
|
|
182
|
+
? "return=minimal"
|
|
183
|
+
: "return=representation";
|
|
184
|
+
|
|
185
|
+
// Make POST request with JSON body
|
|
186
|
+
const result = await this.context._makeRequest<any>(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
Prefer: preferHeader,
|
|
191
|
+
...((mergedOptions as any)?.headers || {}),
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify(transformedData),
|
|
194
|
+
...mergedOptions,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (result.error) {
|
|
198
|
+
return { data: undefined, error: result.error };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Handle return=minimal case
|
|
202
|
+
if (this.returnPreference === "minimal") {
|
|
203
|
+
// The response should be empty (204 No Content)
|
|
204
|
+
// _makeRequest will return { _location: string } when there's a Location header
|
|
205
|
+
const responseData = result.data as any;
|
|
206
|
+
|
|
207
|
+
if (!responseData || !responseData._location) {
|
|
208
|
+
throw new InvalidLocationHeaderError(
|
|
209
|
+
"Location header is required when using return=minimal but was not found in response",
|
|
210
|
+
);
|
|
75
211
|
}
|
|
76
212
|
|
|
77
|
-
|
|
78
|
-
|
|
213
|
+
const rowid = this.parseLocationHeader(responseData._location);
|
|
214
|
+
return { data: { ROWID: rowid } as any, error: undefined };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let response = result.data;
|
|
218
|
+
|
|
219
|
+
// Transform response field IDs back to names if using entity IDs
|
|
220
|
+
// Only transform if useEntityIds resolves to true (respects per-request override)
|
|
221
|
+
if (this.occurrence?.baseTable && shouldUseIds) {
|
|
222
|
+
response = transformResponseFields(
|
|
223
|
+
response,
|
|
224
|
+
this.occurrence.baseTable,
|
|
225
|
+
undefined, // No expand configs for insert
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Get schema from occurrence if available
|
|
230
|
+
const schema = this.occurrence?.baseTable?.schema;
|
|
231
|
+
|
|
232
|
+
// Validate the response (FileMaker returns the created record)
|
|
233
|
+
const validation = await validateSingleResponse<T>(
|
|
234
|
+
response,
|
|
235
|
+
schema,
|
|
236
|
+
undefined, // No selected fields for insert
|
|
237
|
+
undefined, // No expand configs
|
|
238
|
+
"exact", // Expect exactly one record
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (!validation.valid) {
|
|
242
|
+
return { data: undefined, error: validation.error };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle null response (shouldn't happen for insert, but handle it)
|
|
246
|
+
if (validation.data === null) {
|
|
79
247
|
return {
|
|
80
248
|
data: undefined,
|
|
81
|
-
error:
|
|
249
|
+
error: new Error("Insert operation returned null response"),
|
|
82
250
|
};
|
|
83
251
|
}
|
|
252
|
+
|
|
253
|
+
// Strip OData annotations unless explicitly requested
|
|
254
|
+
const finalData = this.stripODataAnnotationsIfNeeded(
|
|
255
|
+
validation.data,
|
|
256
|
+
options,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return { data: finalData as any, error: undefined };
|
|
84
260
|
}
|
|
85
261
|
|
|
86
262
|
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
263
|
+
// For batch operations, use database-level setting (no per-request override available here)
|
|
264
|
+
const tableId = this.getTableId(this.databaseUseEntityIds);
|
|
265
|
+
|
|
266
|
+
// Transform field names to FMFIDs if using entity IDs
|
|
267
|
+
const transformedData = this.occurrence?.baseTable && this.databaseUseEntityIds
|
|
268
|
+
? transformFieldNamesToIds(this.data, this.occurrence.baseTable)
|
|
269
|
+
: this.data;
|
|
270
|
+
|
|
87
271
|
return {
|
|
88
272
|
method: "POST",
|
|
89
|
-
url: `/${this.databaseName}/${
|
|
90
|
-
body: JSON.stringify(
|
|
273
|
+
url: `/${this.databaseName}/${tableId}`,
|
|
274
|
+
body: JSON.stringify(transformedData),
|
|
91
275
|
};
|
|
92
276
|
}
|
|
277
|
+
|
|
278
|
+
toRequest(baseUrl: string): Request {
|
|
279
|
+
const config = this.getRequestConfig();
|
|
280
|
+
const fullUrl = `${baseUrl}${config.url}`;
|
|
281
|
+
|
|
282
|
+
// Set Prefer header based on return preference
|
|
283
|
+
const preferHeader =
|
|
284
|
+
this.returnPreference === "minimal"
|
|
285
|
+
? "return=minimal"
|
|
286
|
+
: "return=representation";
|
|
287
|
+
|
|
288
|
+
return new Request(fullUrl, {
|
|
289
|
+
method: config.method,
|
|
290
|
+
headers: {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
Accept: "application/json",
|
|
293
|
+
Prefer: preferHeader,
|
|
294
|
+
},
|
|
295
|
+
body: config.body,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async processResponse(
|
|
300
|
+
response: Response,
|
|
301
|
+
options?: ExecuteOptions,
|
|
302
|
+
): Promise<
|
|
303
|
+
Result<ReturnPreference extends "minimal" ? { ROWID: number } : T>
|
|
304
|
+
> {
|
|
305
|
+
// Handle 204 No Content (common in batch/changeset operations)
|
|
306
|
+
// FileMaker uses return=minimal for changeset operations regardless of Prefer header
|
|
307
|
+
if (response.status === 204) {
|
|
308
|
+
// Check for Location header (for return=minimal)
|
|
309
|
+
if (this.returnPreference === "minimal") {
|
|
310
|
+
const locationHeader =
|
|
311
|
+
response.headers.get("Location") || response.headers.get("location");
|
|
312
|
+
if (locationHeader) {
|
|
313
|
+
const rowid = this.parseLocationHeader(locationHeader);
|
|
314
|
+
return { data: { ROWID: rowid } as any, error: undefined };
|
|
315
|
+
}
|
|
316
|
+
throw new InvalidLocationHeaderError(
|
|
317
|
+
"Location header is required when using return=minimal but was not found in response",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// For 204 responses without return=minimal, FileMaker doesn't return the created entity
|
|
322
|
+
// This is valid OData behavior for changeset operations
|
|
323
|
+
// We return a success indicator but no actual data
|
|
324
|
+
return {
|
|
325
|
+
data: {} as any,
|
|
326
|
+
error: undefined,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// If we expected return=minimal but got a body, that's unexpected
|
|
331
|
+
if (this.returnPreference === "minimal") {
|
|
332
|
+
throw new InvalidLocationHeaderError(
|
|
333
|
+
"Expected 204 No Content for return=minimal, but received response with body",
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let rawResponse;
|
|
338
|
+
try {
|
|
339
|
+
rawResponse = await response.json();
|
|
340
|
+
} catch (err) {
|
|
341
|
+
// If parsing fails with 204, handle it gracefully
|
|
342
|
+
if (response.status === 204) {
|
|
343
|
+
return {
|
|
344
|
+
data: {} as any,
|
|
345
|
+
error: undefined,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
data: undefined,
|
|
350
|
+
error: {
|
|
351
|
+
name: "ResponseParseError",
|
|
352
|
+
message: `Failed to parse response JSON: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
353
|
+
timestamp: new Date(),
|
|
354
|
+
} as any,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Transform response field IDs back to names if using entity IDs
|
|
359
|
+
// Only transform if useEntityIds resolves to true (respects per-request override)
|
|
360
|
+
const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;
|
|
361
|
+
|
|
362
|
+
let transformedResponse = rawResponse;
|
|
363
|
+
if (this.occurrence?.baseTable && shouldUseIds) {
|
|
364
|
+
transformedResponse = transformResponseFields(
|
|
365
|
+
rawResponse,
|
|
366
|
+
this.occurrence.baseTable,
|
|
367
|
+
undefined, // No expand configs for insert
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Get schema from occurrence if available
|
|
372
|
+
const schema = this.occurrence?.baseTable?.schema;
|
|
373
|
+
|
|
374
|
+
// Validate the response (FileMaker returns the created record)
|
|
375
|
+
const validation = await validateSingleResponse<T>(
|
|
376
|
+
transformedResponse,
|
|
377
|
+
schema,
|
|
378
|
+
undefined, // No selected fields for insert
|
|
379
|
+
undefined, // No expand configs
|
|
380
|
+
"exact", // Expect exactly one record
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (!validation.valid) {
|
|
384
|
+
return { data: undefined, error: validation.error };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Handle null response (shouldn't happen for insert, but handle it)
|
|
388
|
+
if (validation.data === null) {
|
|
389
|
+
return {
|
|
390
|
+
data: undefined,
|
|
391
|
+
error: new Error("Insert operation returned null response"),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Strip OData annotations unless explicitly requested
|
|
396
|
+
const finalData = this.stripODataAnnotationsIfNeeded(
|
|
397
|
+
validation.data,
|
|
398
|
+
options,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
return { data: finalData as any, error: undefined };
|
|
402
|
+
}
|
|
93
403
|
}
|