@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.
Files changed (70) hide show
  1. package/README.md +376 -34
  2. package/dist/esm/client/base-table.d.ts +24 -29
  3. package/dist/esm/client/base-table.js +4 -7
  4. package/dist/esm/client/base-table.js.map +1 -1
  5. package/dist/esm/client/batch-builder.d.ts +54 -0
  6. package/dist/esm/client/batch-builder.js +179 -0
  7. package/dist/esm/client/batch-builder.js.map +1 -0
  8. package/dist/esm/client/batch-request.d.ts +61 -0
  9. package/dist/esm/client/batch-request.js +252 -0
  10. package/dist/esm/client/batch-request.js.map +1 -0
  11. package/dist/esm/client/database.d.ts +44 -12
  12. package/dist/esm/client/database.js +64 -10
  13. package/dist/esm/client/database.js.map +1 -1
  14. package/dist/esm/client/delete-builder.d.ts +21 -2
  15. package/dist/esm/client/delete-builder.js +76 -9
  16. package/dist/esm/client/delete-builder.js.map +1 -1
  17. package/dist/esm/client/entity-set.d.ts +17 -6
  18. package/dist/esm/client/entity-set.js +26 -10
  19. package/dist/esm/client/entity-set.js.map +1 -1
  20. package/dist/esm/client/filemaker-odata.d.ts +11 -5
  21. package/dist/esm/client/filemaker-odata.js +46 -14
  22. package/dist/esm/client/filemaker-odata.js.map +1 -1
  23. package/dist/esm/client/insert-builder.d.ts +38 -3
  24. package/dist/esm/client/insert-builder.js +195 -9
  25. package/dist/esm/client/insert-builder.js.map +1 -1
  26. package/dist/esm/client/query-builder.d.ts +20 -4
  27. package/dist/esm/client/query-builder.js +195 -19
  28. package/dist/esm/client/query-builder.js.map +1 -1
  29. package/dist/esm/client/record-builder.d.ts +18 -3
  30. package/dist/esm/client/record-builder.js +87 -5
  31. package/dist/esm/client/record-builder.js.map +1 -1
  32. package/dist/esm/client/response-processor.d.ts +38 -0
  33. package/dist/esm/client/schema-manager.d.ts +57 -0
  34. package/dist/esm/client/schema-manager.js +132 -0
  35. package/dist/esm/client/schema-manager.js.map +1 -0
  36. package/dist/esm/client/table-occurrence.d.ts +25 -42
  37. package/dist/esm/client/table-occurrence.js +9 -17
  38. package/dist/esm/client/table-occurrence.js.map +1 -1
  39. package/dist/esm/client/update-builder.d.ts +34 -11
  40. package/dist/esm/client/update-builder.js +119 -19
  41. package/dist/esm/client/update-builder.js.map +1 -1
  42. package/dist/esm/errors.d.ts +14 -1
  43. package/dist/esm/errors.js +26 -0
  44. package/dist/esm/errors.js.map +1 -1
  45. package/dist/esm/index.d.ts +5 -4
  46. package/dist/esm/index.js +7 -6
  47. package/dist/esm/transform.d.ts +9 -0
  48. package/dist/esm/transform.js +7 -0
  49. package/dist/esm/transform.js.map +1 -1
  50. package/dist/esm/types.d.ts +69 -1
  51. package/package.json +1 -1
  52. package/src/client/base-table.ts +30 -36
  53. package/src/client/batch-builder.ts +265 -0
  54. package/src/client/batch-request.ts +485 -0
  55. package/src/client/database.ts +110 -56
  56. package/src/client/delete-builder.ts +116 -14
  57. package/src/client/entity-set.ts +89 -12
  58. package/src/client/filemaker-odata.ts +65 -19
  59. package/src/client/insert-builder.ts +296 -18
  60. package/src/client/query-builder.ts +285 -18
  61. package/src/client/query-builder.ts.bak +1457 -0
  62. package/src/client/record-builder.ts +120 -12
  63. package/src/client/response-processor.ts +103 -0
  64. package/src/client/schema-manager.ts +246 -0
  65. package/src/client/table-occurrence.ts +41 -80
  66. package/src/client/update-builder.ts +195 -37
  67. package/src/errors.ts +33 -1
  68. package/src/index.ts +15 -3
  69. package/src/transform.ts +19 -6
  70. package/src/types.ts +89 -1
@@ -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
- // Type-level validation: Check if a TableOccurrence has fmtId (is TableOccurrenceWithIds)
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?: ValidOccurrenceMix<Occurrences> extends true
84
- ? Occurrences
85
- : Occurrences & {
86
- __type_error__: "❌ Cannot mix TableOccurrence with and without entity IDs. Either all occurrences must use TableOccurrenceWithIds (with fmtId and fmfIds) or all must be regular TableOccurrence.";
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
- // Check for mixed usage
118
- if (occurrencesWithIds.length > 0 && occurrencesWithoutIds.length > 0) {
119
- throw new Error(
120
- `Cannot mix TableOccurrence instances with and without entity IDs in the same database. ` +
121
- `Occurrences with entity IDs: [${occurrencesWithIds.join(", ")}]. ` +
122
- `Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
123
- `Either all table occurrences must use entity IDs (fmtId + fmfIds) or none should.`,
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 any,
172
+ occurrence: occurrence as OccType,
168
173
  tableName: name as string,
169
174
  databaseName: this.databaseName,
170
175
  context: this.context,
171
- database: this as any,
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 as any,
184
+ database: this,
180
185
  }) as any;
181
186
  }
182
187
  }
183
188
 
184
- // Example method showing how to use the request method
185
- async getMetadata() {
186
- const result = await this.context._makeRequest(
187
- `/${this.databaseName}/$metadata`,
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
- return result.data;
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 buildQuery from "odata-query";
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}/${this.tableName}('${this.recordId}')`;
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(`/${this.tableName}`)
136
- ? queryString.slice(`/${this.tableName}`.length)
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}/${this.tableName}${queryParams}`;
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
- ...options,
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}/${this.tableName}('${this.recordId}')`;
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(`/${this.tableName}`)
181
- ? queryString.slice(`/${this.tableName}`.length)
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}/${this.tableName}${queryParams}`;
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
  }