@proofkit/fmodata 0.1.0-alpha.2 → 0.1.0-alpha.20

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 (162) hide show
  1. package/README.md +1250 -377
  2. package/dist/esm/client/batch-builder.d.ts +56 -0
  3. package/dist/esm/client/batch-builder.js +238 -0
  4. package/dist/esm/client/batch-builder.js.map +1 -0
  5. package/dist/esm/client/batch-request.d.ts +61 -0
  6. package/dist/esm/client/batch-request.js +252 -0
  7. package/dist/esm/client/batch-request.js.map +1 -0
  8. package/dist/esm/client/builders/default-select.d.ts +10 -0
  9. package/dist/esm/client/builders/default-select.js +43 -0
  10. package/dist/esm/client/builders/default-select.js.map +1 -0
  11. package/dist/esm/client/builders/expand-builder.d.ts +45 -0
  12. package/dist/esm/client/builders/expand-builder.js +174 -0
  13. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  14. package/dist/esm/client/builders/index.d.ts +8 -0
  15. package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
  16. package/dist/esm/client/builders/query-string-builder.js +25 -0
  17. package/dist/esm/client/builders/query-string-builder.js.map +1 -0
  18. package/dist/esm/client/builders/response-processor.d.ts +43 -0
  19. package/dist/esm/client/builders/response-processor.js +176 -0
  20. package/dist/esm/client/builders/response-processor.js.map +1 -0
  21. package/dist/esm/client/builders/select-mixin.d.ts +32 -0
  22. package/dist/esm/client/builders/select-mixin.js +30 -0
  23. package/dist/esm/client/builders/select-mixin.js.map +1 -0
  24. package/dist/esm/client/builders/select-utils.d.ts +18 -0
  25. package/dist/esm/client/builders/select-utils.js +23 -0
  26. package/dist/esm/client/builders/select-utils.js.map +1 -0
  27. package/dist/esm/client/builders/shared-types.d.ts +40 -0
  28. package/dist/esm/client/builders/table-utils.d.ts +35 -0
  29. package/dist/esm/client/builders/table-utils.js +45 -0
  30. package/dist/esm/client/builders/table-utils.js.map +1 -0
  31. package/dist/esm/client/database.d.ts +68 -15
  32. package/dist/esm/client/database.js +88 -34
  33. package/dist/esm/client/database.js.map +1 -1
  34. package/dist/esm/client/delete-builder.d.ts +31 -17
  35. package/dist/esm/client/delete-builder.js +114 -47
  36. package/dist/esm/client/delete-builder.js.map +1 -1
  37. package/dist/esm/client/entity-set.d.ts +33 -27
  38. package/dist/esm/client/entity-set.js +123 -45
  39. package/dist/esm/client/entity-set.js.map +1 -1
  40. package/dist/esm/client/error-parser.d.ts +12 -0
  41. package/dist/esm/client/error-parser.js +30 -0
  42. package/dist/esm/client/error-parser.js.map +1 -0
  43. package/dist/esm/client/filemaker-odata.d.ts +44 -6
  44. package/dist/esm/client/filemaker-odata.js +172 -28
  45. package/dist/esm/client/filemaker-odata.js.map +1 -1
  46. package/dist/esm/client/insert-builder.d.ts +39 -9
  47. package/dist/esm/client/insert-builder.js +265 -36
  48. package/dist/esm/client/insert-builder.js.map +1 -1
  49. package/dist/esm/client/query/expand-builder.d.ts +35 -0
  50. package/dist/esm/client/query/index.d.ts +3 -0
  51. package/dist/esm/client/query/query-builder.d.ts +139 -0
  52. package/dist/esm/client/query/query-builder.js +481 -0
  53. package/dist/esm/client/query/query-builder.js.map +1 -0
  54. package/dist/esm/client/query/response-processor.d.ts +25 -0
  55. package/dist/esm/client/query/types.d.ts +77 -0
  56. package/dist/esm/client/query/url-builder.d.ts +71 -0
  57. package/dist/esm/client/query/url-builder.js +107 -0
  58. package/dist/esm/client/query/url-builder.js.map +1 -0
  59. package/dist/esm/client/query-builder.d.ts +1 -94
  60. package/dist/esm/client/record-builder.d.ts +107 -22
  61. package/dist/esm/client/record-builder.js +342 -64
  62. package/dist/esm/client/record-builder.js.map +1 -1
  63. package/dist/esm/client/response-processor.d.ts +33 -0
  64. package/dist/esm/client/sanitize-json.d.ts +35 -0
  65. package/dist/esm/client/sanitize-json.js +27 -0
  66. package/dist/esm/client/sanitize-json.js.map +1 -0
  67. package/dist/esm/client/schema-manager.d.ts +57 -0
  68. package/dist/esm/client/schema-manager.js +132 -0
  69. package/dist/esm/client/schema-manager.js.map +1 -0
  70. package/dist/esm/client/update-builder.d.ts +42 -25
  71. package/dist/esm/client/update-builder.js +179 -46
  72. package/dist/esm/client/update-builder.js.map +1 -1
  73. package/dist/esm/client/webhook-builder.d.ts +126 -0
  74. package/dist/esm/client/webhook-builder.js +197 -0
  75. package/dist/esm/client/webhook-builder.js.map +1 -0
  76. package/dist/esm/errors.d.ts +90 -0
  77. package/dist/esm/errors.js +180 -0
  78. package/dist/esm/errors.js.map +1 -0
  79. package/dist/esm/index.d.ts +12 -4
  80. package/dist/esm/index.js +59 -6
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/logger.d.ts +47 -0
  83. package/dist/esm/logger.js +72 -0
  84. package/dist/esm/logger.js.map +1 -0
  85. package/dist/esm/logger.test.d.ts +1 -0
  86. package/dist/esm/orm/column.d.ts +62 -0
  87. package/dist/esm/orm/column.js +62 -0
  88. package/dist/esm/orm/column.js.map +1 -0
  89. package/dist/esm/orm/field-builders.d.ts +164 -0
  90. package/dist/esm/orm/field-builders.js +168 -0
  91. package/dist/esm/orm/field-builders.js.map +1 -0
  92. package/dist/esm/orm/index.d.ts +4 -0
  93. package/dist/esm/orm/operators.d.ts +175 -0
  94. package/dist/esm/orm/operators.js +242 -0
  95. package/dist/esm/orm/operators.js.map +1 -0
  96. package/dist/esm/orm/table.d.ts +355 -0
  97. package/dist/esm/orm/table.js +200 -0
  98. package/dist/esm/orm/table.js.map +1 -0
  99. package/dist/esm/transform.d.ts +64 -0
  100. package/dist/esm/transform.js +110 -0
  101. package/dist/esm/transform.js.map +1 -0
  102. package/dist/esm/types.d.ts +157 -7
  103. package/dist/esm/types.js +7 -0
  104. package/dist/esm/types.js.map +1 -0
  105. package/dist/esm/validation.d.ts +22 -9
  106. package/dist/esm/validation.js +195 -50
  107. package/dist/esm/validation.js.map +1 -1
  108. package/package.json +19 -4
  109. package/src/client/batch-builder.ts +334 -0
  110. package/src/client/batch-request.ts +485 -0
  111. package/src/client/builders/default-select.ts +80 -0
  112. package/src/client/builders/expand-builder.ts +245 -0
  113. package/src/client/builders/index.ts +11 -0
  114. package/src/client/builders/query-string-builder.ts +49 -0
  115. package/src/client/builders/response-processor.ts +286 -0
  116. package/src/client/builders/select-mixin.ts +75 -0
  117. package/src/client/builders/select-utils.ts +56 -0
  118. package/src/client/builders/shared-types.ts +42 -0
  119. package/src/client/builders/table-utils.ts +87 -0
  120. package/src/client/database.ts +147 -89
  121. package/src/client/delete-builder.ts +189 -87
  122. package/src/client/entity-set.ts +316 -205
  123. package/src/client/error-parser.ts +59 -0
  124. package/src/client/filemaker-odata.ts +254 -41
  125. package/src/client/insert-builder.ts +420 -49
  126. package/src/client/query/expand-builder.ts +164 -0
  127. package/src/client/query/index.ts +13 -0
  128. package/src/client/query/query-builder.ts +905 -0
  129. package/src/client/query/response-processor.ts +236 -0
  130. package/src/client/query/types.ts +128 -0
  131. package/src/client/query/url-builder.ts +179 -0
  132. package/src/client/query-builder.ts +8 -1076
  133. package/src/client/record-builder.ts +704 -139
  134. package/src/client/response-processor.ts +89 -0
  135. package/src/client/sanitize-json.ts +66 -0
  136. package/src/client/schema-manager.ts +246 -0
  137. package/src/client/update-builder.ts +318 -90
  138. package/src/client/webhook-builder.ts +285 -0
  139. package/src/errors.ts +261 -0
  140. package/src/index.ts +122 -14
  141. package/src/logger.test.ts +34 -0
  142. package/src/logger.ts +140 -0
  143. package/src/orm/column.ts +106 -0
  144. package/src/orm/field-builders.ts +318 -0
  145. package/src/orm/index.ts +60 -0
  146. package/src/orm/operators.ts +487 -0
  147. package/src/orm/table.ts +759 -0
  148. package/src/transform.ts +263 -0
  149. package/src/types.ts +275 -55
  150. package/src/validation.ts +255 -55
  151. package/dist/esm/client/base-table.d.ts +0 -13
  152. package/dist/esm/client/base-table.js +0 -19
  153. package/dist/esm/client/base-table.js.map +0 -1
  154. package/dist/esm/client/query-builder.js +0 -649
  155. package/dist/esm/client/query-builder.js.map +0 -1
  156. package/dist/esm/client/table-occurrence.d.ts +0 -25
  157. package/dist/esm/client/table-occurrence.js +0 -47
  158. package/dist/esm/client/table-occurrence.js.map +0 -1
  159. package/dist/esm/filter-types.d.ts +0 -76
  160. package/src/client/base-table.ts +0 -25
  161. package/src/client/table-occurrence.ts +0 -100
  162. package/src/filter-types.ts +0 -97
@@ -0,0 +1,334 @@
1
+ import type {
2
+ ExecutableBuilder,
3
+ ExecutionContext,
4
+ Result,
5
+ ExecuteOptions,
6
+ BatchResult,
7
+ BatchItemResult,
8
+ ExecuteMethodOptions,
9
+ } from "../types";
10
+ import { BatchTruncatedError } from "../errors";
11
+ import { type FFetchOptions } from "@fetchkit/ffetch";
12
+ import {
13
+ formatBatchRequestFromNative,
14
+ parseBatchResponse,
15
+ type ParsedBatchResponse,
16
+ } from "./batch-request";
17
+
18
+ /**
19
+ * Helper type to extract result types from a tuple of ExecutableBuilders.
20
+ * Uses a mapped type which TypeScript 4.1+ can handle for tuples.
21
+ */
22
+ type ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {
23
+ [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;
24
+ };
25
+
26
+ /**
27
+ * Converts a ParsedBatchResponse to a native Response object
28
+ * @param parsed - The parsed batch response
29
+ * @returns A native Response object
30
+ */
31
+ function parsedToResponse(parsed: ParsedBatchResponse): Response {
32
+ const headers = new Headers(parsed.headers);
33
+
34
+ // Handle null body
35
+ if (parsed.body === null || parsed.body === undefined) {
36
+ return new Response(null, {
37
+ status: parsed.status,
38
+ statusText: parsed.statusText,
39
+ headers,
40
+ });
41
+ }
42
+
43
+ // Convert body to string if it's not already
44
+ const bodyString =
45
+ typeof parsed.body === "string" ? parsed.body : JSON.stringify(parsed.body);
46
+
47
+ // Handle 204 No Content status - it cannot have a body per HTTP spec
48
+ // If FileMaker returns 204 with a body, treat it as 200
49
+ let status = parsed.status;
50
+ if (status === 204 && bodyString && bodyString.trim() !== "") {
51
+ status = 200;
52
+ }
53
+
54
+ return new Response(status === 204 ? null : bodyString, {
55
+ status: status,
56
+ statusText: parsed.statusText,
57
+ headers,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Builder for batch operations that allows multiple queries to be executed together
63
+ * in a single transactional request.
64
+ *
65
+ * Note: BatchBuilder does not implement ExecutableBuilder because execute() returns
66
+ * BatchResult instead of Result, which is a different return type structure.
67
+ */
68
+ export class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]> {
69
+ private builders: ExecutableBuilder<any>[];
70
+ private readonly originalBuilders: Builders;
71
+
72
+ constructor(
73
+ builders: Builders,
74
+ private readonly databaseName: string,
75
+ private readonly context: ExecutionContext,
76
+ ) {
77
+ // Convert readonly tuple to mutable array for dynamic additions
78
+ this.builders = [...builders];
79
+ // Store original tuple for type preservation
80
+ this.originalBuilders = builders;
81
+ }
82
+
83
+ /**
84
+ * Add a request to the batch dynamically.
85
+ * This allows building up batch operations programmatically.
86
+ *
87
+ * @param builder - An executable builder to add to the batch
88
+ * @returns This BatchBuilder for method chaining
89
+ * @example
90
+ * ```ts
91
+ * const batch = db.batch([]);
92
+ * batch.addRequest(db.from('contacts').list());
93
+ * batch.addRequest(db.from('users').list());
94
+ * const result = await batch.execute();
95
+ * ```
96
+ */
97
+ addRequest<T>(builder: ExecutableBuilder<T>): this {
98
+ this.builders.push(builder);
99
+ return this;
100
+ }
101
+
102
+ /**
103
+ * Get the request configuration for this batch operation.
104
+ * This is used internally by the execution system.
105
+ */
106
+ getRequestConfig(): { method: string; url: string; body?: any } {
107
+ // Note: This method is kept for compatibility but batch operations
108
+ // should use execute() directly which handles the full Request/Response flow
109
+ return {
110
+ method: "POST",
111
+ url: `/${this.databaseName}/$batch`,
112
+ body: undefined, // Body is constructed in execute()
113
+ };
114
+ }
115
+
116
+ toRequest(baseUrl: string, options?: ExecuteOptions): Request {
117
+ // Batch operations are not designed to be nested, but we provide
118
+ // a basic implementation for interface compliance
119
+ const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;
120
+ return new Request(fullUrl, {
121
+ method: "POST",
122
+ headers: {
123
+ "Content-Type": "multipart/mixed",
124
+ "OData-Version": "4.0",
125
+ },
126
+ });
127
+ }
128
+
129
+ async processResponse(
130
+ response: Response,
131
+ options?: ExecuteOptions,
132
+ ): Promise<Result<any>> {
133
+ // This should not typically be called for batch operations
134
+ // as they handle their own response processing
135
+ return {
136
+ data: undefined,
137
+ error: {
138
+ name: "NotImplementedError",
139
+ message: "Batch operations handle response processing internally",
140
+ timestamp: new Date(),
141
+ } as any,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Execute the batch operation.
147
+ *
148
+ * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)
149
+ * @returns A BatchResult containing individual results for each operation
150
+ */
151
+ async execute<EO extends ExecuteOptions>(
152
+ options?: ExecuteMethodOptions<EO>,
153
+ ): Promise<BatchResult<ExtractTupleTypes<Builders>>> {
154
+ const baseUrl = this.context._getBaseUrl?.();
155
+ if (!baseUrl) {
156
+ // Return BatchResult with all operations marked as failed
157
+ const errorCount = this.builders.length;
158
+ const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({
159
+ data: undefined,
160
+ error: {
161
+ name: "ConfigurationError",
162
+ message:
163
+ "Base URL not available - execution context must implement _getBaseUrl()",
164
+ timestamp: new Date(),
165
+ } as any,
166
+ status: 0,
167
+ }));
168
+
169
+ return {
170
+ results: results as any,
171
+ successCount: 0,
172
+ errorCount,
173
+ truncated: false,
174
+ firstErrorIndex: 0,
175
+ };
176
+ }
177
+
178
+ try {
179
+ // Convert builders to native Request objects
180
+ const requests: Request[] = this.builders.map((builder) =>
181
+ builder.toRequest(baseUrl, options),
182
+ );
183
+
184
+ // Format batch request (automatically groups mutations into changesets)
185
+ const { body, boundary } = await formatBatchRequestFromNative(
186
+ requests,
187
+ baseUrl,
188
+ );
189
+
190
+ // Execute the batch request
191
+ const response = await this.context._makeRequest<string>(
192
+ `/${this.databaseName}/$batch`,
193
+ {
194
+ ...options,
195
+ method: "POST",
196
+ headers: {
197
+ ...options?.headers,
198
+ "Content-Type": `multipart/mixed; boundary=${boundary}`,
199
+ "OData-Version": "4.0",
200
+ },
201
+ body,
202
+ },
203
+ );
204
+
205
+ if (response.error) {
206
+ // Return BatchResult with all operations marked as failed
207
+ const errorCount = this.builders.length;
208
+ const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({
209
+ data: undefined,
210
+ error: response.error,
211
+ status: 0,
212
+ }));
213
+
214
+ return {
215
+ results: results as any,
216
+ successCount: 0,
217
+ errorCount,
218
+ truncated: false,
219
+ firstErrorIndex: 0,
220
+ };
221
+ }
222
+
223
+ // Extract the actual boundary from the response
224
+ // FileMaker uses its own boundary, not the one we sent
225
+ const firstLine =
226
+ response.data.split("\r\n")[0] || response.data.split("\n")[0] || "";
227
+ const actualBoundary = firstLine.startsWith("--")
228
+ ? firstLine.substring(2)
229
+ : boundary;
230
+
231
+ // Parse the multipart response
232
+ const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;
233
+ const parsedResponses = parseBatchResponse(
234
+ response.data,
235
+ contentTypeHeader,
236
+ );
237
+
238
+ // Process each response using the corresponding builder
239
+ // Build BatchResult with per-item results
240
+ type ResultTuple = ExtractTupleTypes<Builders>;
241
+
242
+ const results: BatchItemResult<any>[] = [];
243
+ let successCount = 0;
244
+ let errorCount = 0;
245
+ let firstErrorIndex: number | null = null;
246
+ const truncated = parsedResponses.length < this.builders.length;
247
+
248
+ // Process builders sequentially to preserve tuple order
249
+ for (let i = 0; i < this.builders.length; i++) {
250
+ const builder = this.builders[i];
251
+ const parsed = parsedResponses[i];
252
+
253
+ if (!parsed) {
254
+ // Truncated - operation never executed
255
+ const failedAtIndex = firstErrorIndex ?? i;
256
+ results.push({
257
+ data: undefined,
258
+ error: new BatchTruncatedError(i, failedAtIndex),
259
+ status: 0,
260
+ });
261
+ errorCount++;
262
+ continue;
263
+ }
264
+
265
+ if (!builder) {
266
+ // Should not happen, but handle gracefully
267
+ results.push({
268
+ data: undefined,
269
+ error: {
270
+ name: "BatchError",
271
+ message: `Builder at index ${i} is undefined`,
272
+ timestamp: new Date(),
273
+ } as any,
274
+ status: parsed.status,
275
+ });
276
+ errorCount++;
277
+ if (firstErrorIndex === null) firstErrorIndex = i;
278
+ continue;
279
+ }
280
+
281
+ // Convert parsed response to native Response
282
+ const nativeResponse = parsedToResponse(parsed);
283
+
284
+ // Let the builder process its own response
285
+ const result = await builder.processResponse(nativeResponse, options);
286
+
287
+ if (result.error) {
288
+ results.push({
289
+ data: undefined,
290
+ error: result.error,
291
+ status: parsed.status,
292
+ });
293
+ errorCount++;
294
+ if (firstErrorIndex === null) firstErrorIndex = i;
295
+ } else {
296
+ results.push({
297
+ data: result.data,
298
+ error: undefined,
299
+ status: parsed.status,
300
+ });
301
+ successCount++;
302
+ }
303
+ }
304
+
305
+ return {
306
+ results: results as any,
307
+ successCount,
308
+ errorCount,
309
+ truncated,
310
+ firstErrorIndex,
311
+ };
312
+ } catch (err) {
313
+ // On exception, return a BatchResult with all operations marked as failed
314
+ const errorCount = this.builders.length;
315
+ const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({
316
+ data: undefined,
317
+ error: {
318
+ name: "BatchError",
319
+ message: err instanceof Error ? err.message : "Unknown error",
320
+ timestamp: new Date(),
321
+ } as any,
322
+ status: 0,
323
+ }));
324
+
325
+ return {
326
+ results: results as any,
327
+ successCount: 0,
328
+ errorCount,
329
+ truncated: false,
330
+ firstErrorIndex: 0,
331
+ };
332
+ }
333
+ }
334
+ }