@proofkit/fmodata 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +376 -34
- package/dist/esm/client/base-table.d.ts +24 -29
- package/dist/esm/client/base-table.js +4 -7
- 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 +44 -12
- package/dist/esm/client/database.js +64 -10
- 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 +76 -9
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +17 -6
- package/dist/esm/client/entity-set.js +26 -10
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +11 -5
- package/dist/esm/client/filemaker-odata.js +46 -14
- 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 +195 -9
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +20 -4
- package/dist/esm/client/query-builder.js +195 -19
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +18 -3
- package/dist/esm/client/record-builder.js +87 -5
- 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 +25 -42
- package/dist/esm/client/table-occurrence.js +9 -17
- 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 +119 -19
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +14 -1
- package/dist/esm/errors.js +26 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -4
- package/dist/esm/index.js +7 -6
- package/dist/esm/transform.d.ts +9 -0
- package/dist/esm/transform.js +7 -0
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +69 -1
- package/package.json +1 -1
- package/src/client/base-table.ts +30 -36
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +110 -56
- package/src/client/delete-builder.ts +116 -14
- package/src/client/entity-set.ts +89 -12
- package/src/client/filemaker-odata.ts +65 -19
- package/src/client/insert-builder.ts +296 -18
- package/src/client/query-builder.ts +285 -18
- package/src/client/query-builder.ts.bak +1457 -0
- package/src/client/record-builder.ts +120 -12
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +41 -80
- package/src/client/update-builder.ts +195 -37
- package/src/errors.ts +33 -1
- package/src/index.ts +15 -3
- package/src/transform.ts +19 -6
- package/src/types.ts +89 -1
package/src/client/database.ts
CHANGED
|
@@ -1,44 +1,15 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type { ExecutionContext } from "../types";
|
|
2
|
+
import type { ExecutionContext, ExecutableBuilder, Metadata } from "../types";
|
|
3
3
|
import type { BaseTable } from "./base-table";
|
|
4
4
|
import type { TableOccurrence } from "./table-occurrence";
|
|
5
5
|
import { EntitySet } from "./entity-set";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type HasFmtId<T> = T extends { fmtId: string } ? true : false;
|
|
9
|
-
|
|
10
|
-
// Check if all occurrences in a tuple have fmtId
|
|
11
|
-
type AllHaveFmtId<Occurrences extends readonly any[]> =
|
|
12
|
-
Occurrences extends readonly [infer First, ...infer Rest]
|
|
13
|
-
? HasFmtId<First> extends true
|
|
14
|
-
? Rest extends readonly []
|
|
15
|
-
? true
|
|
16
|
-
: AllHaveFmtId<Rest>
|
|
17
|
-
: false
|
|
18
|
-
: true; // empty array is valid
|
|
19
|
-
|
|
20
|
-
// Check if none have fmtId
|
|
21
|
-
type NoneHaveFmtId<Occurrences extends readonly any[]> =
|
|
22
|
-
Occurrences extends readonly [infer First, ...infer Rest]
|
|
23
|
-
? HasFmtId<First> extends false
|
|
24
|
-
? Rest extends readonly []
|
|
25
|
-
? true
|
|
26
|
-
: NoneHaveFmtId<Rest>
|
|
27
|
-
: false
|
|
28
|
-
: true; // empty array is valid
|
|
29
|
-
|
|
30
|
-
// Valid if all have fmtId or none have fmtId (no mixing allowed)
|
|
31
|
-
export type ValidOccurrenceMix<Occurrences extends readonly any[]> =
|
|
32
|
-
AllHaveFmtId<Occurrences> extends true
|
|
33
|
-
? true
|
|
34
|
-
: NoneHaveFmtId<Occurrences> extends true
|
|
35
|
-
? true
|
|
36
|
-
: false;
|
|
6
|
+
import { BatchBuilder } from "./batch-builder";
|
|
7
|
+
import { SchemaManager } from "./schema-manager";
|
|
37
8
|
|
|
38
9
|
// Helper type to extract schema from a TableOccurrence
|
|
39
10
|
type ExtractSchemaFromOccurrence<O> =
|
|
40
11
|
O extends TableOccurrence<infer BT, any, any, any>
|
|
41
|
-
? BT extends BaseTable<infer S, any>
|
|
12
|
+
? BT extends BaseTable<infer S, any, any, any>
|
|
42
13
|
? S
|
|
43
14
|
: never
|
|
44
15
|
: never;
|
|
@@ -75,16 +46,19 @@ export class Database<
|
|
|
75
46
|
> {
|
|
76
47
|
private occurrenceMap: Map<string, TableOccurrence<any, any, any, any>>;
|
|
77
48
|
private _useEntityIds: boolean = false;
|
|
49
|
+
public readonly schema: SchemaManager;
|
|
78
50
|
|
|
79
51
|
constructor(
|
|
80
52
|
private readonly databaseName: string,
|
|
81
53
|
private readonly context: ExecutionContext,
|
|
82
54
|
config?: {
|
|
83
|
-
occurrences?:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
55
|
+
occurrences?: Occurrences | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Whether to use entity IDs instead of field names in the actual requests to the server
|
|
58
|
+
* Defaults to true if all occurrences use entity IDs, false otherwise
|
|
59
|
+
* If set to false but some occurrences do not use entity IDs, an error will be thrown
|
|
60
|
+
*/
|
|
61
|
+
useEntityIds?: boolean;
|
|
88
62
|
},
|
|
89
63
|
) {
|
|
90
64
|
this.occurrenceMap = new Map();
|
|
@@ -102,7 +76,6 @@ export class Database<
|
|
|
102
76
|
// An occurrence uses entity IDs if it has both fmtId and fmfIds
|
|
103
77
|
if (hasTableId && hasFieldIds) {
|
|
104
78
|
occurrencesWithIds.push(occ.name);
|
|
105
|
-
this._useEntityIds = true;
|
|
106
79
|
} else if (!hasTableId && !hasFieldIds) {
|
|
107
80
|
occurrencesWithoutIds.push(occ.name);
|
|
108
81
|
} else {
|
|
@@ -114,21 +87,53 @@ export class Database<
|
|
|
114
87
|
}
|
|
115
88
|
}
|
|
116
89
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
90
|
+
// Determine default value: true if all occurrences use entity IDs, false otherwise
|
|
91
|
+
const allOccurrencesUseEntityIds =
|
|
92
|
+
occurrencesWithIds.length > 0 && occurrencesWithoutIds.length === 0;
|
|
93
|
+
const hasMixedUsage =
|
|
94
|
+
occurrencesWithIds.length > 0 && occurrencesWithoutIds.length > 0;
|
|
95
|
+
|
|
96
|
+
// Handle explicit useEntityIds config
|
|
97
|
+
if (config.useEntityIds !== undefined) {
|
|
98
|
+
if (config.useEntityIds === false) {
|
|
99
|
+
// If explicitly set to false, allow mixed usage and use false
|
|
100
|
+
this._useEntityIds = false;
|
|
101
|
+
} else if (config.useEntityIds === true) {
|
|
102
|
+
// If explicitly set to true, validate that all occurrences use entity IDs
|
|
103
|
+
if (hasMixedUsage || occurrencesWithoutIds.length > 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`useEntityIds is set to true but some occurrences do not use entity IDs. ` +
|
|
106
|
+
`Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
|
|
107
|
+
`Either set useEntityIds to false or configure all occurrences with entity IDs.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
this._useEntityIds = true;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// Default: true if all occurrences use entity IDs, false otherwise
|
|
114
|
+
// But throw error if there's mixed usage when using defaults
|
|
115
|
+
if (hasMixedUsage) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Cannot mix TableOccurrence instances with and without entity IDs in the same database. ` +
|
|
118
|
+
`Occurrences with entity IDs: [${occurrencesWithIds.join(", ")}]. ` +
|
|
119
|
+
`Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
|
|
120
|
+
`Either all table occurrences must use entity IDs (fmtId + fmfIds), none should, or explicitly set useEntityIds to false.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
this._useEntityIds = allOccurrencesUseEntityIds;
|
|
125
124
|
}
|
|
125
|
+
} else {
|
|
126
|
+
// No occurrences provided, use explicit config or default to false
|
|
127
|
+
this._useEntityIds = config?.useEntityIds ?? false;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
// Inform the execution context whether to use entity IDs
|
|
129
131
|
if (this.context._setUseEntityIds) {
|
|
130
132
|
this.context._setUseEntityIds(this._useEntityIds);
|
|
131
133
|
}
|
|
134
|
+
|
|
135
|
+
// Initialize schema manager
|
|
136
|
+
this.schema = new SchemaManager(this.databaseName, this.context);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
/**
|
|
@@ -164,11 +169,11 @@ export class Database<
|
|
|
164
169
|
type SchemaType = ExtractSchemaFromOccurrence<OccType>;
|
|
165
170
|
|
|
166
171
|
return EntitySet.create<SchemaType, OccType>({
|
|
167
|
-
occurrence: occurrence as
|
|
172
|
+
occurrence: occurrence as OccType,
|
|
168
173
|
tableName: name as string,
|
|
169
174
|
databaseName: this.databaseName,
|
|
170
175
|
context: this.context,
|
|
171
|
-
database: this
|
|
176
|
+
database: this,
|
|
172
177
|
}) as any;
|
|
173
178
|
} else {
|
|
174
179
|
// Return untyped EntitySet for dynamic table access
|
|
@@ -176,20 +181,44 @@ export class Database<
|
|
|
176
181
|
tableName: name as string,
|
|
177
182
|
databaseName: this.databaseName,
|
|
178
183
|
context: this.context,
|
|
179
|
-
database: this
|
|
184
|
+
database: this,
|
|
180
185
|
}) as any;
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Retrieves the OData metadata for this database.
|
|
191
|
+
* @param args Optional configuration object
|
|
192
|
+
* @param args.format The format to retrieve metadata in. Defaults to "json".
|
|
193
|
+
* @returns The metadata in the specified format
|
|
194
|
+
*/
|
|
195
|
+
async getMetadata(args: { format: "xml" }): Promise<string>;
|
|
196
|
+
async getMetadata(args?: { format?: "json" }): Promise<Metadata>;
|
|
197
|
+
async getMetadata(args?: {
|
|
198
|
+
format?: "xml" | "json";
|
|
199
|
+
}): Promise<string | Metadata> {
|
|
200
|
+
const result = await this.context._makeRequest<
|
|
201
|
+
Record<string, Metadata> | string
|
|
202
|
+
>(`/${this.databaseName}/$metadata`, {
|
|
203
|
+
headers: {
|
|
204
|
+
Accept: args?.format === "xml" ? "application/xml" : "application/json",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
189
207
|
if (result.error) {
|
|
190
208
|
throw result.error;
|
|
191
209
|
}
|
|
192
|
-
|
|
210
|
+
|
|
211
|
+
if (args?.format === "json") {
|
|
212
|
+
const data = result.data as Record<string, Metadata>;
|
|
213
|
+
const metadata = data[this.databaseName];
|
|
214
|
+
if (!metadata) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Metadata for database "${this.databaseName}" not found in response`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return metadata;
|
|
220
|
+
}
|
|
221
|
+
return result.data as string;
|
|
193
222
|
}
|
|
194
223
|
|
|
195
224
|
/**
|
|
@@ -277,4 +306,29 @@ export class Database<
|
|
|
277
306
|
result: response.scriptResult.resultParameter,
|
|
278
307
|
} as any;
|
|
279
308
|
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Create a batch operation builder that allows multiple queries to be executed together
|
|
312
|
+
* in a single atomic request. All operations succeed or fail together (transactional).
|
|
313
|
+
*
|
|
314
|
+
* @param builders - Array of executable query builders to batch
|
|
315
|
+
* @returns A BatchBuilder that can be executed
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* const result = await db.batch([
|
|
319
|
+
* db.from('contacts').list().top(5),
|
|
320
|
+
* db.from('users').list().top(5),
|
|
321
|
+
* db.from('contacts').insert({ name: 'John' })
|
|
322
|
+
* ]).execute();
|
|
323
|
+
*
|
|
324
|
+
* if (result.data) {
|
|
325
|
+
* const [contacts, users, insertResult] = result.data;
|
|
326
|
+
* }
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
batch<const Builders extends readonly ExecutableBuilder<any>[]>(
|
|
330
|
+
builders: Builders,
|
|
331
|
+
): BatchBuilder<Builders> {
|
|
332
|
+
return new BatchBuilder(builders, this.databaseName, this.context);
|
|
333
|
+
}
|
|
280
334
|
}
|
|
@@ -3,11 +3,12 @@ import type {
|
|
|
3
3
|
ExecutableBuilder,
|
|
4
4
|
Result,
|
|
5
5
|
WithSystemFields,
|
|
6
|
+
ExecuteOptions,
|
|
6
7
|
} from "../types";
|
|
7
8
|
import type { TableOccurrence } from "./table-occurrence";
|
|
8
9
|
import { QueryBuilder } from "./query-builder";
|
|
9
10
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
10
|
-
import
|
|
11
|
+
import { getTableIdentifiers } from "../transform";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Initial delete builder returned from EntitySet.delete()
|
|
@@ -18,17 +19,20 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
18
19
|
private databaseName: string;
|
|
19
20
|
private context: ExecutionContext;
|
|
20
21
|
private occurrence?: TableOccurrence<any, any, any, any>;
|
|
22
|
+
private databaseUseEntityIds: boolean;
|
|
21
23
|
|
|
22
24
|
constructor(config: {
|
|
23
25
|
occurrence?: TableOccurrence<any, any, any, any>;
|
|
24
26
|
tableName: string;
|
|
25
27
|
databaseName: string;
|
|
26
28
|
context: ExecutionContext;
|
|
29
|
+
databaseUseEntityIds?: boolean;
|
|
27
30
|
}) {
|
|
28
31
|
this.occurrence = config.occurrence;
|
|
29
32
|
this.tableName = config.tableName;
|
|
30
33
|
this.databaseName = config.databaseName;
|
|
31
34
|
this.context = config.context;
|
|
35
|
+
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -42,6 +46,7 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
42
46
|
context: this.context,
|
|
43
47
|
mode: "byId",
|
|
44
48
|
recordId: id,
|
|
49
|
+
databaseUseEntityIds: this.databaseUseEntityIds,
|
|
45
50
|
});
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -78,6 +83,7 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
78
83
|
context: this.context,
|
|
79
84
|
mode: "byFilter",
|
|
80
85
|
queryBuilder: configuredBuilder,
|
|
86
|
+
databaseUseEntityIds: this.databaseUseEntityIds,
|
|
81
87
|
});
|
|
82
88
|
}
|
|
83
89
|
}
|
|
@@ -96,6 +102,7 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
96
102
|
private mode: "byId" | "byFilter";
|
|
97
103
|
private recordId?: string | number;
|
|
98
104
|
private queryBuilder?: QueryBuilder<any>;
|
|
105
|
+
private databaseUseEntityIds: boolean;
|
|
99
106
|
|
|
100
107
|
constructor(config: {
|
|
101
108
|
occurrence?: TableOccurrence<any, any, any, any>;
|
|
@@ -105,6 +112,7 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
105
112
|
mode: "byId" | "byFilter";
|
|
106
113
|
recordId?: string | number;
|
|
107
114
|
queryBuilder?: QueryBuilder<any>;
|
|
115
|
+
databaseUseEntityIds?: boolean;
|
|
108
116
|
}) {
|
|
109
117
|
this.occurrence = config.occurrence;
|
|
110
118
|
this.tableName = config.tableName;
|
|
@@ -113,16 +121,61 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
113
121
|
this.mode = config.mode;
|
|
114
122
|
this.recordId = config.recordId;
|
|
115
123
|
this.queryBuilder = config.queryBuilder;
|
|
124
|
+
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
129
|
+
*/
|
|
130
|
+
private mergeExecuteOptions(
|
|
131
|
+
options?: RequestInit & FFetchOptions & ExecuteOptions,
|
|
132
|
+
): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
|
|
133
|
+
// If useEntityIds is not set in options, use the database-level setting
|
|
134
|
+
return {
|
|
135
|
+
...options,
|
|
136
|
+
useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
142
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
143
|
+
*/
|
|
144
|
+
private getTableId(useEntityIds?: boolean): string {
|
|
145
|
+
if (!this.occurrence) {
|
|
146
|
+
return this.tableName;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const contextDefault = this.context._getUseEntityIds?.() ?? false;
|
|
150
|
+
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
151
|
+
|
|
152
|
+
if (shouldUseIds) {
|
|
153
|
+
const identifiers = getTableIdentifiers(this.occurrence);
|
|
154
|
+
if (!identifiers.id) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return identifiers.id;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return this.occurrence.getTableName();
|
|
116
163
|
}
|
|
117
164
|
|
|
118
165
|
async execute(
|
|
119
|
-
options?: RequestInit & FFetchOptions,
|
|
166
|
+
options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
|
|
120
167
|
): Promise<Result<{ deletedCount: number }>> {
|
|
168
|
+
// Merge database-level useEntityIds with per-request options
|
|
169
|
+
const mergedOptions = this.mergeExecuteOptions(options);
|
|
170
|
+
|
|
171
|
+
// Get table identifier with override support
|
|
172
|
+
const tableId = this.getTableId(mergedOptions.useEntityIds);
|
|
173
|
+
|
|
121
174
|
let url: string;
|
|
122
175
|
|
|
123
176
|
if (this.mode === "byId") {
|
|
124
177
|
// Delete single record by ID: DELETE /{database}/{table}('id')
|
|
125
|
-
url = `/${this.databaseName}/${
|
|
178
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
126
179
|
} else {
|
|
127
180
|
// Delete by filter: DELETE /{database}/{table}?$filter=...
|
|
128
181
|
if (!this.queryBuilder) {
|
|
@@ -131,18 +184,20 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
131
184
|
|
|
132
185
|
// Get the query string from the configured QueryBuilder
|
|
133
186
|
const queryString = this.queryBuilder.getQueryString();
|
|
134
|
-
// Remove the leading "/" from the query string as we'll build our own URL
|
|
135
|
-
const queryParams = queryString.startsWith(`/${
|
|
136
|
-
? queryString.slice(`/${
|
|
137
|
-
: queryString
|
|
187
|
+
// Remove the leading "/" and table name from the query string as we'll build our own URL
|
|
188
|
+
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
189
|
+
? queryString.slice(`/${tableId}`.length)
|
|
190
|
+
: queryString.startsWith(`/${this.tableName}`)
|
|
191
|
+
? queryString.slice(`/${this.tableName}`.length)
|
|
192
|
+
: queryString;
|
|
138
193
|
|
|
139
|
-
url = `/${this.databaseName}/${
|
|
194
|
+
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
140
195
|
}
|
|
141
196
|
|
|
142
197
|
// Make DELETE request
|
|
143
198
|
const result = await this.context._makeRequest(url, {
|
|
144
199
|
method: "DELETE",
|
|
145
|
-
...
|
|
200
|
+
...mergedOptions,
|
|
146
201
|
});
|
|
147
202
|
|
|
148
203
|
if (result.error) {
|
|
@@ -167,21 +222,26 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
167
222
|
}
|
|
168
223
|
|
|
169
224
|
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
225
|
+
// For batch operations, use database-level setting (no per-request override available here)
|
|
226
|
+
const tableId = this.getTableId(this.databaseUseEntityIds);
|
|
227
|
+
|
|
170
228
|
let url: string;
|
|
171
229
|
|
|
172
230
|
if (this.mode === "byId") {
|
|
173
|
-
url = `/${this.databaseName}/${
|
|
231
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
174
232
|
} else {
|
|
175
233
|
if (!this.queryBuilder) {
|
|
176
234
|
throw new Error("Query builder is required for filter-based delete");
|
|
177
235
|
}
|
|
178
236
|
|
|
179
237
|
const queryString = this.queryBuilder.getQueryString();
|
|
180
|
-
const queryParams = queryString.startsWith(`/${
|
|
181
|
-
? queryString.slice(`/${
|
|
182
|
-
: queryString
|
|
238
|
+
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
239
|
+
? queryString.slice(`/${tableId}`.length)
|
|
240
|
+
: queryString.startsWith(`/${this.tableName}`)
|
|
241
|
+
? queryString.slice(`/${this.tableName}`.length)
|
|
242
|
+
: queryString;
|
|
183
243
|
|
|
184
|
-
url = `/${this.databaseName}/${
|
|
244
|
+
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
185
245
|
}
|
|
186
246
|
|
|
187
247
|
return {
|
|
@@ -189,4 +249,46 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
189
249
|
url,
|
|
190
250
|
};
|
|
191
251
|
}
|
|
252
|
+
|
|
253
|
+
toRequest(baseUrl: string): Request {
|
|
254
|
+
const config = this.getRequestConfig();
|
|
255
|
+
const fullUrl = `${baseUrl}${config.url}`;
|
|
256
|
+
|
|
257
|
+
return new Request(fullUrl, {
|
|
258
|
+
method: config.method,
|
|
259
|
+
headers: {
|
|
260
|
+
Accept: "application/json",
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async processResponse(
|
|
266
|
+
response: Response,
|
|
267
|
+
options?: ExecuteOptions,
|
|
268
|
+
): Promise<Result<{ deletedCount: number }>> {
|
|
269
|
+
// Check for empty response (204 No Content)
|
|
270
|
+
const text = await response.text();
|
|
271
|
+
if (!text || text.trim() === "") {
|
|
272
|
+
// For 204 No Content, check the fmodata.affected_rows header
|
|
273
|
+
const affectedRows = response.headers.get("fmodata.affected_rows");
|
|
274
|
+
const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;
|
|
275
|
+
return { data: { deletedCount }, error: undefined };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const rawResponse = JSON.parse(text);
|
|
279
|
+
|
|
280
|
+
// OData returns 204 No Content with fmodata.affected_rows header
|
|
281
|
+
// The _makeRequest should handle extracting the header value
|
|
282
|
+
// For now, we'll check if response contains the count
|
|
283
|
+
let deletedCount = 0;
|
|
284
|
+
|
|
285
|
+
if (typeof rawResponse === "number") {
|
|
286
|
+
deletedCount = rawResponse;
|
|
287
|
+
} else if (rawResponse && typeof rawResponse === "object") {
|
|
288
|
+
// Check if the response has a count property (fallback)
|
|
289
|
+
deletedCount = (rawResponse as any).deletedCount || 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { data: { deletedCount }, error: undefined };
|
|
293
|
+
}
|
|
192
294
|
}
|