@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,481 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import buildQuery from "odata-query";
5
+ import { RecordCountMismatchError } from "../../errors.js";
6
+ import { transformOrderByField } from "../../transform.js";
7
+ import { safeJsonParse } from "../sanitize-json.js";
8
+ import { parseErrorResponse } from "../error-parser.js";
9
+ import { isColumn } from "../../orm/column.js";
10
+ import { isOrderByExpression } from "../../orm/operators.js";
11
+ import { getTableName } from "../../orm/table.js";
12
+ import { mergeExecuteOptions, resolveTableId, createODataRequest } from "../builders/table-utils.js";
13
+ import { processSelectWithRenames } from "../builders/select-mixin.js";
14
+ import { ExpandBuilder } from "../builders/expand-builder.js";
15
+ import { processQueryResponse } from "../builders/response-processor.js";
16
+ import { buildSelectExpandQueryString } from "../builders/query-string-builder.js";
17
+ import { QueryUrlBuilder } from "./url-builder.js";
18
+ import { createLogger } from "../../logger.js";
19
+ class QueryBuilder {
20
+ constructor(config) {
21
+ __publicField(this, "queryOptions", {});
22
+ __publicField(this, "expandConfigs", []);
23
+ __publicField(this, "singleMode", false);
24
+ __publicField(this, "isCountMode", false);
25
+ __publicField(this, "occurrence");
26
+ __publicField(this, "databaseName");
27
+ __publicField(this, "context");
28
+ __publicField(this, "navigation");
29
+ __publicField(this, "databaseUseEntityIds");
30
+ __publicField(this, "databaseIncludeSpecialColumns");
31
+ __publicField(this, "expandBuilder");
32
+ __publicField(this, "urlBuilder");
33
+ // Mapping from field names to output keys (for renamed fields in select)
34
+ __publicField(this, "fieldMapping");
35
+ // System columns requested via select() second argument
36
+ __publicField(this, "systemColumns");
37
+ __publicField(this, "logger");
38
+ var _a, _b;
39
+ this.occurrence = config.occurrence;
40
+ this.databaseName = config.databaseName;
41
+ this.context = config.context;
42
+ this.logger = ((_b = (_a = config.context) == null ? void 0 : _a._getLogger) == null ? void 0 : _b.call(_a)) ?? createLogger();
43
+ this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
44
+ this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;
45
+ this.expandBuilder = new ExpandBuilder(
46
+ this.databaseUseEntityIds,
47
+ this.logger
48
+ );
49
+ this.urlBuilder = new QueryUrlBuilder(
50
+ this.databaseName,
51
+ this.occurrence,
52
+ this.context
53
+ );
54
+ }
55
+ /**
56
+ * Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options
57
+ */
58
+ mergeExecuteOptions(options) {
59
+ const merged = mergeExecuteOptions(options, this.databaseUseEntityIds);
60
+ return {
61
+ ...merged,
62
+ includeSpecialColumns: (options == null ? void 0 : options.includeSpecialColumns) ?? this.databaseIncludeSpecialColumns
63
+ };
64
+ }
65
+ /**
66
+ * Gets the FMTable instance
67
+ */
68
+ getTable() {
69
+ return this.occurrence;
70
+ }
71
+ /**
72
+ * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
73
+ * @param useEntityIds - Optional override for entity ID usage
74
+ */
75
+ getTableIdOrName(useEntityIds) {
76
+ return resolveTableId(
77
+ this.occurrence,
78
+ getTableName(this.occurrence),
79
+ this.context,
80
+ useEntityIds
81
+ );
82
+ }
83
+ /**
84
+ * Creates a new QueryBuilder with modified configuration.
85
+ * Used by single(), maybeSingle(), count(), and select() to create new instances.
86
+ */
87
+ cloneWithChanges(changes) {
88
+ const newBuilder = new QueryBuilder({
89
+ occurrence: this.occurrence,
90
+ databaseName: this.databaseName,
91
+ context: this.context,
92
+ databaseUseEntityIds: this.databaseUseEntityIds,
93
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns
94
+ });
95
+ newBuilder.queryOptions = {
96
+ ...this.queryOptions,
97
+ ...changes.queryOptions
98
+ };
99
+ newBuilder.expandConfigs = [...this.expandConfigs];
100
+ newBuilder.singleMode = changes.singleMode ?? this.singleMode;
101
+ newBuilder.isCountMode = changes.isCountMode ?? this.isCountMode;
102
+ newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;
103
+ newBuilder.systemColumns = changes.systemColumns !== void 0 ? changes.systemColumns : this.systemColumns;
104
+ newBuilder.navigation = this.navigation;
105
+ newBuilder.urlBuilder = new QueryUrlBuilder(
106
+ this.databaseName,
107
+ this.occurrence,
108
+ this.context
109
+ );
110
+ return newBuilder;
111
+ }
112
+ /**
113
+ * Select fields using column references.
114
+ * Allows renaming fields by using different keys in the object.
115
+ * Container fields cannot be selected and will cause a type error.
116
+ *
117
+ * @example
118
+ * db.from(users).list().select({
119
+ * name: users.name,
120
+ * userEmail: users.email // renamed!
121
+ * })
122
+ *
123
+ * @example
124
+ * // Include system columns (ROWID, ROWMODID) when using select()
125
+ * db.from(users).list().select(
126
+ * { name: users.name },
127
+ * { ROWID: true, ROWMODID: true }
128
+ * )
129
+ *
130
+ * @param fields - Object mapping output keys to column references (container fields excluded)
131
+ * @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)
132
+ * @returns QueryBuilder with updated selected fields
133
+ */
134
+ select(fields, systemColumns) {
135
+ const tableName = getTableName(this.occurrence);
136
+ const { selectedFields, fieldMapping } = processSelectWithRenames(
137
+ fields,
138
+ tableName,
139
+ this.logger
140
+ );
141
+ const finalSelectedFields = [...selectedFields];
142
+ if (systemColumns == null ? void 0 : systemColumns.ROWID) {
143
+ finalSelectedFields.push("ROWID");
144
+ }
145
+ if (systemColumns == null ? void 0 : systemColumns.ROWMODID) {
146
+ finalSelectedFields.push("ROWMODID");
147
+ }
148
+ return this.cloneWithChanges({
149
+ selectedFields: fields,
150
+ queryOptions: {
151
+ select: finalSelectedFields
152
+ },
153
+ fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : void 0,
154
+ systemColumns
155
+ });
156
+ }
157
+ /**
158
+ * Filter results using operator expressions (new ORM-style API).
159
+ * Supports eq, gt, lt, and, or, etc. operators with Column references.
160
+ * Also supports raw OData filter strings as an escape hatch.
161
+ *
162
+ * @example
163
+ * .where(eq(users.hobby, "reading"))
164
+ * .where(and(eq(users.active, true), gt(users.age, 18)))
165
+ * .where("status eq 'active'") // Raw OData string escape hatch
166
+ */
167
+ where(expression) {
168
+ if (typeof expression === "string") {
169
+ this.queryOptions.filter = expression;
170
+ return this;
171
+ }
172
+ const filterString = expression.toODataFilter(this.databaseUseEntityIds);
173
+ this.queryOptions.filter = filterString;
174
+ return this;
175
+ }
176
+ /**
177
+ * Specify the sort order for query results.
178
+ *
179
+ * @example Single field (ascending by default)
180
+ * ```ts
181
+ * .orderBy("name")
182
+ * .orderBy(users.name) // Column reference
183
+ * .orderBy(asc(users.name)) // Explicit ascending
184
+ * ```
185
+ *
186
+ * @example Single field with explicit direction
187
+ * ```ts
188
+ * .orderBy(["name", "desc"])
189
+ * .orderBy([users.name, "desc"]) // Column reference
190
+ * .orderBy(desc(users.name)) // Explicit descending
191
+ * ```
192
+ *
193
+ * @example Multiple fields with directions
194
+ * ```ts
195
+ * .orderBy([["name", "asc"], ["createdAt", "desc"]])
196
+ * .orderBy([[users.name, "asc"], [users.createdAt, "desc"]]) // Column references
197
+ * .orderBy(users.name, desc(users.age)) // Variadic with helpers
198
+ * ```
199
+ */
200
+ orderBy(...orderByArgs) {
201
+ const tableName = getTableName(this.occurrence);
202
+ if (orderByArgs.length > 1) {
203
+ const orderByParts = orderByArgs.map((arg) => {
204
+ if (isOrderByExpression(arg)) {
205
+ if (arg.column.tableName !== tableName) {
206
+ this.logger.warn(
207
+ `Column ${arg.column.toString()} is from table "${arg.column.tableName}", but query is for table "${tableName}"`
208
+ );
209
+ }
210
+ const fieldName = arg.column.fieldName;
211
+ const transformedField = this.occurrence ? transformOrderByField(fieldName, this.occurrence) : fieldName;
212
+ return `${transformedField} ${arg.direction}`;
213
+ } else if (isColumn(arg)) {
214
+ if (arg.tableName !== tableName) {
215
+ this.logger.warn(
216
+ `Column ${arg.toString()} is from table "${arg.tableName}", but query is for table "${tableName}"`
217
+ );
218
+ }
219
+ const fieldName = arg.fieldName;
220
+ const transformedField = this.occurrence ? transformOrderByField(fieldName, this.occurrence) : fieldName;
221
+ return transformedField;
222
+ } else {
223
+ throw new Error(
224
+ "Variadic orderBy() only accepts Column or OrderByExpression arguments"
225
+ );
226
+ }
227
+ });
228
+ this.queryOptions.orderBy = orderByParts;
229
+ return this;
230
+ }
231
+ const orderBy = orderByArgs[0];
232
+ if (isOrderByExpression(orderBy)) {
233
+ if (orderBy.column.tableName !== tableName) {
234
+ this.logger.warn(
235
+ `Column ${orderBy.column.toString()} is from table "${orderBy.column.tableName}", but query is for table "${tableName}"`
236
+ );
237
+ }
238
+ const fieldName = orderBy.column.fieldName;
239
+ const transformedField = this.occurrence ? transformOrderByField(fieldName, this.occurrence) : fieldName;
240
+ this.queryOptions.orderBy = `${transformedField} ${orderBy.direction}`;
241
+ return this;
242
+ }
243
+ if (isColumn(orderBy)) {
244
+ if (orderBy.tableName !== tableName) {
245
+ this.logger.warn(
246
+ `Column ${orderBy.toString()} is from table "${orderBy.tableName}", but query is for table "${tableName}"`
247
+ );
248
+ }
249
+ const fieldName = orderBy.fieldName;
250
+ this.queryOptions.orderBy = this.occurrence ? transformOrderByField(fieldName, this.occurrence) : fieldName;
251
+ return this;
252
+ }
253
+ if (this.occurrence && orderBy) {
254
+ if (Array.isArray(orderBy)) {
255
+ if (orderBy.length === 2 && (typeof orderBy[0] === "string" || isColumn(orderBy[0])) && (orderBy[1] === "asc" || orderBy[1] === "desc")) {
256
+ const field = isColumn(orderBy[0]) ? orderBy[0].fieldName : orderBy[0];
257
+ const direction = orderBy[1];
258
+ this.queryOptions.orderBy = `${transformOrderByField(field, this.occurrence)} ${direction}`;
259
+ } else {
260
+ this.queryOptions.orderBy = orderBy.map(([fieldOrCol, direction]) => {
261
+ const field = isColumn(fieldOrCol) ? fieldOrCol.fieldName : String(fieldOrCol);
262
+ const transformedField = transformOrderByField(
263
+ field,
264
+ this.occurrence
265
+ );
266
+ return `${transformedField} ${direction}`;
267
+ });
268
+ }
269
+ } else {
270
+ this.queryOptions.orderBy = transformOrderByField(
271
+ String(orderBy),
272
+ this.occurrence
273
+ );
274
+ }
275
+ } else {
276
+ if (Array.isArray(orderBy)) {
277
+ if (orderBy.length === 2 && (typeof orderBy[0] === "string" || isColumn(orderBy[0])) && (orderBy[1] === "asc" || orderBy[1] === "desc")) {
278
+ const field = isColumn(orderBy[0]) ? orderBy[0].fieldName : orderBy[0];
279
+ const direction = orderBy[1];
280
+ this.queryOptions.orderBy = `${field} ${direction}`;
281
+ } else {
282
+ this.queryOptions.orderBy = orderBy.map(([fieldOrCol, direction]) => {
283
+ const field = isColumn(fieldOrCol) ? fieldOrCol.fieldName : String(fieldOrCol);
284
+ return `${field} ${direction}`;
285
+ });
286
+ }
287
+ } else {
288
+ this.queryOptions.orderBy = orderBy;
289
+ }
290
+ }
291
+ return this;
292
+ }
293
+ top(count) {
294
+ this.queryOptions.top = count;
295
+ return this;
296
+ }
297
+ skip(count) {
298
+ this.queryOptions.skip = count;
299
+ return this;
300
+ }
301
+ expand(targetTable, callback) {
302
+ const expandConfig = this.expandBuilder.processExpand(
303
+ targetTable,
304
+ this.occurrence,
305
+ callback,
306
+ () => new QueryBuilder({
307
+ occurrence: targetTable,
308
+ databaseName: this.databaseName,
309
+ context: this.context,
310
+ databaseUseEntityIds: this.databaseUseEntityIds,
311
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns
312
+ })
313
+ );
314
+ this.expandConfigs.push(expandConfig);
315
+ return this;
316
+ }
317
+ single() {
318
+ return this.cloneWithChanges({ singleMode: "exact" });
319
+ }
320
+ maybeSingle() {
321
+ return this.cloneWithChanges({ singleMode: "maybe" });
322
+ }
323
+ count() {
324
+ return this.cloneWithChanges({
325
+ isCountMode: true,
326
+ queryOptions: { count: true }
327
+ });
328
+ }
329
+ /**
330
+ * Builds the OData query string from current query options and expand configs.
331
+ */
332
+ buildQueryString(includeSpecialColumns) {
333
+ const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };
334
+ const originalSelect = queryOptionsWithoutExpandAndSelect.select;
335
+ delete queryOptionsWithoutExpandAndSelect.expand;
336
+ delete queryOptionsWithoutExpandAndSelect.select;
337
+ let queryString = buildQuery(queryOptionsWithoutExpandAndSelect);
338
+ const selectArray = originalSelect ? Array.isArray(originalSelect) ? originalSelect.map(String) : [String(originalSelect)] : void 0;
339
+ includeSpecialColumns ?? this.databaseIncludeSpecialColumns;
340
+ const selectExpandString = buildSelectExpandQueryString({
341
+ selectedFields: selectArray,
342
+ expandConfigs: this.expandConfigs,
343
+ table: this.occurrence,
344
+ useEntityIds: this.databaseUseEntityIds,
345
+ logger: this.logger
346
+ });
347
+ if (selectExpandString) {
348
+ const params = selectExpandString.startsWith("?") ? selectExpandString.slice(1) : selectExpandString;
349
+ const separator = queryString.includes("?") ? "&" : "?";
350
+ queryString = `${queryString}${separator}${params}`;
351
+ }
352
+ return queryString;
353
+ }
354
+ async execute(options) {
355
+ const mergedOptions = this.mergeExecuteOptions(options);
356
+ const queryString = this.buildQueryString(
357
+ mergedOptions.includeSpecialColumns
358
+ );
359
+ if (this.isCountMode) {
360
+ const url2 = this.urlBuilder.build(queryString, {
361
+ isCount: true,
362
+ useEntityIds: mergedOptions.useEntityIds,
363
+ navigation: this.navigation
364
+ });
365
+ const result2 = await this.context._makeRequest(url2, mergedOptions);
366
+ if (result2.error) {
367
+ return { data: void 0, error: result2.error };
368
+ }
369
+ const count = typeof result2.data === "string" ? Number(result2.data) : result2.data;
370
+ return { data: count, error: void 0 };
371
+ }
372
+ const url = this.urlBuilder.build(queryString, {
373
+ isCount: this.isCountMode,
374
+ useEntityIds: mergedOptions.useEntityIds,
375
+ navigation: this.navigation
376
+ });
377
+ const result = await this.context._makeRequest(url, mergedOptions);
378
+ if (result.error) {
379
+ return { data: void 0, error: result.error };
380
+ }
381
+ this.queryOptions.select !== void 0;
382
+ return processQueryResponse(result.data, {
383
+ occurrence: this.occurrence,
384
+ singleMode: this.singleMode,
385
+ queryOptions: this.queryOptions,
386
+ expandConfigs: this.expandConfigs,
387
+ skipValidation: options == null ? void 0 : options.skipValidation,
388
+ useEntityIds: mergedOptions.useEntityIds,
389
+ includeSpecialColumns: mergedOptions.includeSpecialColumns,
390
+ fieldMapping: this.fieldMapping,
391
+ logger: this.logger
392
+ });
393
+ }
394
+ getQueryString() {
395
+ const queryString = this.buildQueryString();
396
+ return this.urlBuilder.buildPath(queryString, {
397
+ useEntityIds: this.databaseUseEntityIds,
398
+ navigation: this.navigation
399
+ });
400
+ }
401
+ getRequestConfig() {
402
+ const queryString = this.buildQueryString();
403
+ const url = this.urlBuilder.build(queryString, {
404
+ isCount: this.isCountMode,
405
+ useEntityIds: this.databaseUseEntityIds,
406
+ navigation: this.navigation
407
+ });
408
+ return {
409
+ method: "GET",
410
+ url
411
+ };
412
+ }
413
+ toRequest(baseUrl, options) {
414
+ const config = this.getRequestConfig();
415
+ return createODataRequest(baseUrl, config, options);
416
+ }
417
+ async processResponse(response, options) {
418
+ if (!response.ok) {
419
+ const error = await parseErrorResponse(
420
+ response,
421
+ response.url || `/${this.databaseName}/${getTableName(this.occurrence)}`
422
+ );
423
+ return { data: void 0, error };
424
+ }
425
+ if (response.status === 204) {
426
+ if (this.singleMode !== false) {
427
+ if (this.singleMode === "maybe") {
428
+ return { data: null, error: void 0 };
429
+ }
430
+ return {
431
+ data: void 0,
432
+ error: new RecordCountMismatchError("one", 0)
433
+ };
434
+ }
435
+ return { data: [], error: void 0 };
436
+ }
437
+ let rawData;
438
+ try {
439
+ rawData = await safeJsonParse(response);
440
+ } catch (err) {
441
+ if (err instanceof SyntaxError && response.status === 204) {
442
+ return { data: [], error: void 0 };
443
+ }
444
+ return {
445
+ data: void 0,
446
+ error: {
447
+ name: "ResponseParseError",
448
+ message: `Failed to parse response JSON: ${err instanceof Error ? err.message : "Unknown error"}`,
449
+ timestamp: /* @__PURE__ */ new Date()
450
+ }
451
+ };
452
+ }
453
+ if (!rawData) {
454
+ return {
455
+ data: void 0,
456
+ error: {
457
+ name: "ResponseError",
458
+ message: "Response body was empty or null",
459
+ timestamp: /* @__PURE__ */ new Date()
460
+ }
461
+ };
462
+ }
463
+ const mergedOptions = this.mergeExecuteOptions(options);
464
+ this.queryOptions.select !== void 0;
465
+ return processQueryResponse(rawData, {
466
+ occurrence: this.occurrence,
467
+ singleMode: this.singleMode,
468
+ queryOptions: this.queryOptions,
469
+ expandConfigs: this.expandConfigs,
470
+ skipValidation: options == null ? void 0 : options.skipValidation,
471
+ useEntityIds: mergedOptions.useEntityIds,
472
+ includeSpecialColumns: mergedOptions.includeSpecialColumns,
473
+ fieldMapping: this.fieldMapping,
474
+ logger: this.logger
475
+ });
476
+ }
477
+ }
478
+ export {
479
+ QueryBuilder
480
+ };
481
+ //# sourceMappingURL=query-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-builder.js","sources":["../../../../src/client/query/query-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ConditionallyWithSpecialColumns,\n NormalizeIncludeSpecialColumns,\n ExecuteMethodOptions,\n} from \"../../types\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { transformOrderByField } from \"../../transform\";\nimport { safeJsonParse } from \"../sanitize-json\";\nimport { parseErrorResponse } from \"../error-parser\";\nimport { isColumn, type Column } from \"../../orm/column\";\nimport {\n FilterExpression,\n OrderByExpression,\n isOrderByExpression,\n} from \"../../orm/operators\";\nimport {\n FMTable,\n type InferSchemaOutputFromFMTable,\n type ValidExpandTarget,\n type ExtractTableName,\n getTableName,\n} from \"../../orm/table\";\nimport {\n ExpandBuilder,\n type ExpandConfig,\n type ExpandedRelations,\n resolveTableId,\n mergeExecuteOptions,\n processQueryResponse,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"../builders/index\";\nimport { QueryUrlBuilder, type NavigationConfig } from \"./url-builder\";\nimport type {\n TypeSafeOrderBy,\n QueryReturnType,\n SystemColumnsOption,\n} from \"./types\";\nimport { createLogger, InternalLogger } from \"../../logger\";\n\n// Re-export QueryReturnType for backward compatibility\nexport type { QueryReturnType };\n\n/**\n * Default maximum number of records to return in a list query.\n * This prevents stack overflow issues with large datasets while still\n * allowing substantial data retrieval. Users can override with .top().\n */\nconst DEFAULT_TOP = 1000;\n\nexport type { TypeSafeOrderBy, ExpandedRelations };\n\nexport class QueryBuilder<\n Occ extends FMTable<any, any>,\n Selected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<\n string,\n Column<any, any, ExtractTableName<Occ>>\n > = keyof InferSchemaOutputFromFMTable<Occ>,\n SingleMode extends \"exact\" | \"maybe\" | false = false,\n IsCount extends boolean = false,\n Expands extends ExpandedRelations = {},\n DatabaseIncludeSpecialColumns extends boolean = false,\n SystemCols extends SystemColumnsOption | undefined = undefined,\n> implements\n ExecutableBuilder<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n SystemCols\n >\n >\n{\n private queryOptions: Partial<\n QueryOptions<InferSchemaOutputFromFMTable<Occ>>\n > = {};\n private expandConfigs: ExpandConfig[] = [];\n private singleMode: SingleMode = false as SingleMode;\n private isCountMode = false as IsCount;\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private navigation?: NavigationConfig;\n private databaseUseEntityIds: boolean;\n private databaseIncludeSpecialColumns: boolean;\n private expandBuilder: ExpandBuilder;\n private urlBuilder: QueryUrlBuilder;\n // Mapping from field names to output keys (for renamed fields in select)\n private fieldMapping?: Record<string, string>;\n // System columns requested via select() second argument\n private systemColumns?: SystemColumnsOption;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.databaseIncludeSpecialColumns =\n config.databaseIncludeSpecialColumns ?? false;\n this.expandBuilder = new ExpandBuilder(\n this.databaseUseEntityIds,\n this.logger,\n );\n this.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n }\n\n /**\n * Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit &\n FFetchOptions & {\n useEntityIds?: boolean;\n includeSpecialColumns?: boolean;\n } {\n const merged = mergeExecuteOptions(options, this.databaseUseEntityIds);\n return {\n ...merged,\n includeSpecialColumns:\n options?.includeSpecialColumns ?? this.databaseIncludeSpecialColumns,\n };\n }\n\n /**\n * Gets the FMTable instance\n */\n private getTable(): FMTable<any, any> | undefined {\n return this.occurrence;\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableIdOrName(useEntityIds?: boolean): string {\n return resolveTableId(\n this.occurrence,\n getTableName(this.occurrence),\n this.context,\n useEntityIds,\n );\n }\n\n /**\n * Creates a new QueryBuilder with modified configuration.\n * Used by single(), maybeSingle(), count(), and select() to create new instances.\n */\n private cloneWithChanges<\n NewSelected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<string, Column<any, any, ExtractTableName<Occ>>> = Selected,\n NewSingle extends \"exact\" | \"maybe\" | false = SingleMode,\n NewCount extends boolean = IsCount,\n NewSystemCols extends SystemColumnsOption | undefined = SystemCols,\n >(changes: {\n selectedFields?: NewSelected;\n singleMode?: NewSingle;\n isCountMode?: NewCount;\n queryOptions?: Partial<QueryOptions<InferSchemaOutputFromFMTable<Occ>>>;\n fieldMapping?: Record<string, string>;\n systemColumns?: NewSystemCols;\n }): QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n NewSystemCols\n > {\n const newBuilder = new QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n NewSystemCols\n >({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,\n });\n newBuilder.queryOptions = {\n ...this.queryOptions,\n ...changes.queryOptions,\n };\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.singleMode = (changes.singleMode ?? this.singleMode) as any;\n newBuilder.isCountMode = (changes.isCountMode ?? this.isCountMode) as any;\n newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;\n newBuilder.systemColumns =\n changes.systemColumns !== undefined\n ? changes.systemColumns\n : this.systemColumns;\n // Copy navigation metadata\n newBuilder.navigation = this.navigation;\n newBuilder.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n return newBuilder;\n }\n\n /**\n * Select fields using column references.\n * Allows renaming fields by using different keys in the object.\n * Container fields cannot be selected and will cause a type error.\n *\n * @example\n * db.from(users).list().select({\n * name: users.name,\n * userEmail: users.email // renamed!\n * })\n *\n * @example\n * // Include system columns (ROWID, ROWMODID) when using select()\n * db.from(users).list().select(\n * { name: users.name },\n * { ROWID: true, ROWMODID: true }\n * )\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)\n * @returns QueryBuilder with updated selected fields\n */\n select<\n TSelect extends Record<\n string,\n Column<any, any, ExtractTableName<Occ>, false>\n >,\n TSystemCols extends SystemColumnsOption = {},\n >(\n fields: TSelect,\n systemColumns?: TSystemCols,\n ): QueryBuilder<\n Occ,\n TSelect,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n TSystemCols\n > {\n const tableName = getTableName(this.occurrence);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n this.logger,\n );\n\n // Add system columns to selectedFields if requested\n const finalSelectedFields = [...selectedFields];\n if (systemColumns?.ROWID) {\n finalSelectedFields.push(\"ROWID\");\n }\n if (systemColumns?.ROWMODID) {\n finalSelectedFields.push(\"ROWMODID\");\n }\n\n return this.cloneWithChanges({\n selectedFields: fields as any,\n queryOptions: {\n select: finalSelectedFields,\n },\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n systemColumns: systemColumns as any,\n });\n }\n\n /**\n * Filter results using operator expressions (new ORM-style API).\n * Supports eq, gt, lt, and, or, etc. operators with Column references.\n * Also supports raw OData filter strings as an escape hatch.\n *\n * @example\n * .where(eq(users.hobby, \"reading\"))\n * .where(and(eq(users.active, true), gt(users.age, 18)))\n * .where(\"status eq 'active'\") // Raw OData string escape hatch\n */\n where(\n expression: FilterExpression | string,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n // Handle raw string filters (escape hatch)\n if (typeof expression === \"string\") {\n this.queryOptions.filter = expression;\n return this;\n }\n // Convert FilterExpression to OData filter string\n const filterString = expression.toODataFilter(this.databaseUseEntityIds);\n this.queryOptions.filter = filterString;\n return this;\n }\n\n /**\n * Specify the sort order for query results.\n *\n * @example Single field (ascending by default)\n * ```ts\n * .orderBy(\"name\")\n * .orderBy(users.name) // Column reference\n * .orderBy(asc(users.name)) // Explicit ascending\n * ```\n *\n * @example Single field with explicit direction\n * ```ts\n * .orderBy([\"name\", \"desc\"])\n * .orderBy([users.name, \"desc\"]) // Column reference\n * .orderBy(desc(users.name)) // Explicit descending\n * ```\n *\n * @example Multiple fields with directions\n * ```ts\n * .orderBy([[\"name\", \"asc\"], [\"createdAt\", \"desc\"]])\n * .orderBy([[users.name, \"asc\"], [users.createdAt, \"desc\"]]) // Column references\n * .orderBy(users.name, desc(users.age)) // Variadic with helpers\n * ```\n */\n orderBy(\n ...orderByArgs:\n | [\n | TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>>\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>,\n ]\n | [\n Column<any, any, ExtractTableName<Occ>>,\n ...Array<\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>\n >,\n ]\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n const tableName = getTableName(this.occurrence);\n\n // Handle variadic arguments (multiple fields)\n if (orderByArgs.length > 1) {\n const orderByParts = orderByArgs.map((arg) => {\n if (isOrderByExpression(arg)) {\n // Validate table match\n if (arg.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.column.toString()} is from table \"${arg.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return `${transformedField} ${arg.direction}`;\n } else if (isColumn(arg)) {\n // Validate table match\n if (arg.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.toString()} is from table \"${arg.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return transformedField; // Default to ascending\n } else {\n throw new Error(\n \"Variadic orderBy() only accepts Column or OrderByExpression arguments\",\n );\n }\n });\n this.queryOptions.orderBy = orderByParts;\n return this;\n }\n\n // Handle single argument\n const orderBy = orderByArgs[0];\n\n // Handle OrderByExpression\n if (isOrderByExpression(orderBy)) {\n // Validate table match\n if (orderBy.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.column.toString()} is from table \"${orderBy.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = orderBy.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n this.queryOptions.orderBy = `${transformedField} ${orderBy.direction}`;\n return this;\n }\n\n // Handle Column references\n if (isColumn(orderBy)) {\n // Validate table match\n if (orderBy.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.toString()} is from table \"${orderBy.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n // Single Column reference without direction (defaults to ascending)\n const fieldName = orderBy.fieldName;\n this.queryOptions.orderBy = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return this;\n }\n // Transform field names to FMFIDs if using entity IDs\n if (this.occurrence && orderBy) {\n if (Array.isArray(orderBy)) {\n // Check if it's a single tuple [field, direction] or array of tuples\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${transformOrderByField(field, this.occurrence)} ${direction}`;\n } else {\n // Array of tuples: [[field, dir], [field, dir], ...]\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n const transformedField = transformOrderByField(\n field,\n this.occurrence!,\n );\n return `${transformedField} ${direction}`;\n });\n }\n } else {\n // Single field name (string)\n this.queryOptions.orderBy = transformOrderByField(\n String(orderBy),\n this.occurrence,\n );\n }\n } else {\n // No occurrence/baseTable - pass through as-is\n if (Array.isArray(orderBy)) {\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${field} ${direction}`;\n } else {\n // Array of tuples\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n return `${field} ${direction}`;\n });\n }\n } else {\n this.queryOptions.orderBy = orderBy;\n }\n }\n return this;\n }\n\n top(\n count: number,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n this.queryOptions.top = count;\n return this;\n }\n\n skip(\n count: number,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n this.queryOptions.skip = count;\n return this;\n }\n\n expand<\n TargetTable extends FMTable<any, any>,\n TSelected extends\n | keyof InferSchemaOutputFromFMTable<TargetTable>\n | Record<\n string,\n Column<any, any, ExtractTableName<TargetTable>>\n > = keyof InferSchemaOutputFromFMTable<TargetTable>,\n TNestedExpands extends ExpandedRelations = {},\n >(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n callback?: (\n builder: QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false,\n {}\n >,\n ) => QueryBuilder<TargetTable, TSelected, any, any, TNestedExpands>,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands & {\n [K in ExtractTableName<TargetTable>]: {\n schema: InferSchemaOutputFromFMTable<TargetTable>;\n selected: TSelected;\n nested: TNestedExpands;\n };\n },\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n // Use ExpandBuilder.processExpand to handle the expand logic\n type TargetBuilder = QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false,\n {},\n DatabaseIncludeSpecialColumns\n >;\n const expandConfig = this.expandBuilder.processExpand<\n TargetTable,\n TargetBuilder\n >(\n targetTable,\n this.occurrence,\n callback as ((builder: TargetBuilder) => TargetBuilder) | undefined,\n () =>\n new QueryBuilder<\n TargetTable,\n any,\n any,\n any,\n any,\n DatabaseIncludeSpecialColumns,\n undefined\n >({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,\n }),\n );\n\n this.expandConfigs.push(expandConfig);\n return this as any;\n }\n\n single(): QueryBuilder<\n Occ,\n Selected,\n \"exact\",\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n return this.cloneWithChanges({ singleMode: \"exact\" as const });\n }\n\n maybeSingle(): QueryBuilder<\n Occ,\n Selected,\n \"maybe\",\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n return this.cloneWithChanges({ singleMode: \"maybe\" as const });\n }\n\n count(): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n true,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n return this.cloneWithChanges({\n isCountMode: true as const,\n queryOptions: { count: true },\n });\n }\n\n /**\n * Builds the OData query string from current query options and expand configs.\n */\n private buildQueryString(includeSpecialColumns?: boolean): string {\n // Build query without expand and select (we'll add them manually if using entity IDs)\n const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };\n const originalSelect = queryOptionsWithoutExpandAndSelect.select;\n delete queryOptionsWithoutExpandAndSelect.expand;\n delete queryOptionsWithoutExpandAndSelect.select;\n\n let queryString = buildQuery(queryOptionsWithoutExpandAndSelect);\n\n // Use shared helper for select/expand portion\n const selectArray = originalSelect\n ? Array.isArray(originalSelect)\n ? originalSelect.map(String)\n : [String(originalSelect)]\n : undefined;\n\n // Use merged includeSpecialColumns if provided, otherwise use database-level default\n const finalIncludeSpecialColumns =\n includeSpecialColumns ?? this.databaseIncludeSpecialColumns;\n\n const selectExpandString = buildSelectExpandQueryString({\n selectedFields: selectArray,\n expandConfigs: this.expandConfigs,\n table: this.occurrence,\n useEntityIds: this.databaseUseEntityIds,\n logger: this.logger,\n includeSpecialColumns: finalIncludeSpecialColumns,\n });\n\n // Append select/expand to existing query string\n if (selectExpandString) {\n // Strip leading ? from helper result and append with appropriate separator\n const params = selectExpandString.startsWith(\"?\")\n ? selectExpandString.slice(1)\n : selectExpandString;\n const separator = queryString.includes(\"?\") ? \"&\" : \"?\";\n queryString = `${queryString}${separator}${params}`;\n }\n\n return queryString;\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n ConditionallyWithSpecialColumns<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n SystemCols\n >,\n // Use the merged value: if explicitly provided in options, use that; otherwise use database default\n NormalizeIncludeSpecialColumns<\n EO[\"includeSpecialColumns\"],\n DatabaseIncludeSpecialColumns\n >,\n // Check if select was applied: if Selected is Record (object select) or a subset of keys, select was applied\n Selected extends Record<string, Column<any, any, any>>\n ? true\n : Selected extends keyof InferSchemaOutputFromFMTable<Occ>\n ? false\n : true\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n const mergedOptions = this.mergeExecuteOptions(options);\n const queryString = this.buildQueryString(\n mergedOptions.includeSpecialColumns,\n );\n\n // Handle $count endpoint\n if (this.isCountMode) {\n const url = this.urlBuilder.build(queryString, {\n isCount: true,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // OData returns count as a string, convert to number\n const count =\n typeof result.data === \"string\" ? Number(result.data) : result.data;\n return { data: count as number, error: undefined } as any;\n }\n\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Check if select was applied (runtime check)\n const hasSelect = this.queryOptions.select !== undefined;\n\n return processQueryResponse(result.data, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n includeSpecialColumns: mergedOptions.includeSpecialColumns,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n\n getQueryString(): string {\n const queryString = this.buildQueryString();\n return this.urlBuilder.buildPath(queryString, {\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n const queryString = this.buildQueryString();\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n return createODataRequest(baseUrl, config, options);\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n SystemCols\n >\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const error = await parseErrorResponse(\n response,\n response.url ||\n `/${this.databaseName}/${getTableName(this.occurrence)}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (shouldn't happen for queries, but handle it gracefully)\n if (response.status === 204) {\n // Return empty list for list queries, null for single queries\n if (this.singleMode !== false) {\n if (this.singleMode === \"maybe\") {\n return { data: null as any, error: undefined };\n }\n return {\n data: undefined,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n return { data: [] as any, error: undefined };\n }\n\n // Parse the response body (using safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values)\n let rawData;\n try {\n rawData = await safeJsonParse(response);\n } catch (err) {\n // Check if it's an empty body error (common with 204 responses)\n if (err instanceof SyntaxError && response.status === 204) {\n // Handled above, but just in case\n return { data: [] as any, error: undefined };\n }\n return {\n data: undefined,\n error: {\n name: \"ResponseParseError\",\n message: `Failed to parse response JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n if (!rawData) {\n return {\n data: undefined,\n error: {\n name: \"ResponseError\",\n message: \"Response body was empty or null\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n // Check if select was applied (runtime check)\n const hasSelect = this.queryOptions.select !== undefined;\n\n return processQueryResponse(rawData, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n includeSpecialColumns: mergedOptions.includeSpecialColumns,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n}\n"],"names":["url","result"],"mappings":";;;;;;;;;;;;;;;;;;AA6DO,MAAM,aAwBb;AAAA,EAqBE,YAAY,QAMT;AA1BK,wCAEJ,CAAC;AACG,yCAAgC,CAAC;AACjC,sCAAyB;AACzB,uCAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAEA;AAAA;AACA;;AASN,SAAK,aAAa,OAAO;AACzB,SAAK,eAAe,OAAO;AAC3B,SAAK,UAAU,OAAO;AACtB,SAAK,WAAS,kBAAO,YAAP,mBAAgB,eAAhB,gCAAkC,aAAa;AACxD,SAAA,uBAAuB,OAAO,wBAAwB;AACtD,SAAA,gCACH,OAAO,iCAAiC;AAC1C,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,SAAK,aAAa,IAAI;AAAA,MACpB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMM,oBACN,SAKE;AACF,UAAM,SAAS,oBAAoB,SAAS,KAAK,oBAAoB;AAC9D,WAAA;AAAA,MACL,GAAG;AAAA,MACH,wBACE,mCAAS,0BAAyB,KAAK;AAAA,IAC3C;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMM,WAA0C;AAChD,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAON,iBAAiB,cAAgC;AAChD,WAAA;AAAA,MACL,KAAK;AAAA,MACL,aAAa,KAAK,UAAU;AAAA,MAC5B,KAAK;AAAA,MACL;AAAA,IACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,iBAON,SAeA;AACM,UAAA,aAAa,IAAI,aAQrB;AAAA,MACA,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,SAAS,KAAK;AAAA,MACd,sBAAsB,KAAK;AAAA,MAC3B,+BAA+B,KAAK;AAAA,IAAA,CACrC;AACD,eAAW,eAAe;AAAA,MACxB,GAAG,KAAK;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AACA,eAAW,gBAAgB,CAAC,GAAG,KAAK,aAAa;AACtC,eAAA,aAAc,QAAQ,cAAc,KAAK;AACzC,eAAA,cAAe,QAAQ,eAAe,KAAK;AAC3C,eAAA,eAAe,QAAQ,gBAAgB,KAAK;AACvD,eAAW,gBACT,QAAQ,kBAAkB,SACtB,QAAQ,gBACR,KAAK;AAEX,eAAW,aAAa,KAAK;AAC7B,eAAW,aAAa,IAAI;AAAA,MAC1B,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACO,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBT,OAOE,QACA,eASA;AACM,UAAA,YAAY,aAAa,KAAK,UAAU;AACxC,UAAA,EAAE,gBAAgB,aAAA,IAAiB;AAAA,MACvC;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAGM,UAAA,sBAAsB,CAAC,GAAG,cAAc;AAC9C,QAAI,+CAAe,OAAO;AACxB,0BAAoB,KAAK,OAAO;AAAA,IAAA;AAElC,QAAI,+CAAe,UAAU;AAC3B,0BAAoB,KAAK,UAAU;AAAA,IAAA;AAGrC,WAAO,KAAK,iBAAiB;AAAA,MAC3B,gBAAgB;AAAA,MAChB,cAAc;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA,cACE,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,MACxD;AAAA,IAAA,CACD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaH,MACE,YASA;AAEI,QAAA,OAAO,eAAe,UAAU;AAClC,WAAK,aAAa,SAAS;AACpB,aAAA;AAAA,IAAA;AAGT,UAAM,eAAe,WAAW,cAAc,KAAK,oBAAoB;AACvE,SAAK,aAAa,SAAS;AACpB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BT,WACK,aAqBH;AACM,UAAA,YAAY,aAAa,KAAK,UAAU;AAG1C,QAAA,YAAY,SAAS,GAAG;AAC1B,YAAM,eAAe,YAAY,IAAI,CAAC,QAAQ;AACxC,YAAA,oBAAoB,GAAG,GAAG;AAExB,cAAA,IAAI,OAAO,cAAc,WAAW;AACtC,iBAAK,OAAO;AAAA,cACV,UAAU,IAAI,OAAO,SAAS,CAAC,mBAAmB,IAAI,OAAO,SAAS,8BAA8B,SAAS;AAAA,YAC/G;AAAA,UAAA;AAEI,gBAAA,YAAY,IAAI,OAAO;AAC7B,gBAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACJ,iBAAO,GAAG,gBAAgB,IAAI,IAAI,SAAS;AAAA,QAAA,WAClC,SAAS,GAAG,GAAG;AAEpB,cAAA,IAAI,cAAc,WAAW;AAC/B,iBAAK,OAAO;AAAA,cACV,UAAU,IAAI,UAAU,mBAAmB,IAAI,SAAS,8BAA8B,SAAS;AAAA,YACjG;AAAA,UAAA;AAEF,gBAAM,YAAY,IAAI;AACtB,gBAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACG,iBAAA;AAAA,QAAA,OACF;AACL,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QAAA;AAAA,MACF,CACD;AACD,WAAK,aAAa,UAAU;AACrB,aAAA;AAAA,IAAA;AAIH,UAAA,UAAU,YAAY,CAAC;AAGzB,QAAA,oBAAoB,OAAO,GAAG;AAE5B,UAAA,QAAQ,OAAO,cAAc,WAAW;AAC1C,aAAK,OAAO;AAAA,UACV,UAAU,QAAQ,OAAO,SAAS,CAAC,mBAAmB,QAAQ,OAAO,SAAS,8BAA8B,SAAS;AAAA,QACvH;AAAA,MAAA;AAEI,YAAA,YAAY,QAAQ,OAAO;AACjC,YAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACJ,WAAK,aAAa,UAAU,GAAG,gBAAgB,IAAI,QAAQ,SAAS;AAC7D,aAAA;AAAA,IAAA;AAIL,QAAA,SAAS,OAAO,GAAG;AAEjB,UAAA,QAAQ,cAAc,WAAW;AACnC,aAAK,OAAO;AAAA,UACV,UAAU,QAAQ,UAAU,mBAAmB,QAAQ,SAAS,8BAA8B,SAAS;AAAA,QACzG;AAAA,MAAA;AAGF,YAAM,YAAY,QAAQ;AACrB,WAAA,aAAa,UAAU,KAAK,aAC7B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACG,aAAA;AAAA,IAAA;AAGL,QAAA,KAAK,cAAc,SAAS;AAC1B,UAAA,MAAM,QAAQ,OAAO,GAAG;AAGxB,YAAA,QAAQ,WAAW,MAClB,OAAO,QAAQ,CAAC,MAAM,YAAY,SAAS,QAAQ,CAAC,CAAC,OACrD,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,MAAM,SACxC;AAEM,gBAAA,QAAQ,SAAS,QAAQ,CAAC,CAAC,IAC7B,QAAQ,CAAC,EAAE,YACX,QAAQ,CAAC;AACP,gBAAA,YAAY,QAAQ,CAAC;AACtB,eAAA,aAAa,UAAU,GAAG,sBAAsB,OAAO,KAAK,UAAU,CAAC,IAAI,SAAS;AAAA,QAAA,OACpF;AAEA,eAAA,aAAa,UAChB,QACA,IAAI,CAAC,CAAC,YAAY,SAAS,MAAM;AACjC,kBAAM,QAAQ,SAAS,UAAU,IAC7B,WAAW,YACX,OAAO,UAAU;AACrB,kBAAM,mBAAmB;AAAA,cACvB;AAAA,cACA,KAAK;AAAA,YACP;AACO,mBAAA,GAAG,gBAAgB,IAAI,SAAS;AAAA,UAAA,CACxC;AAAA,QAAA;AAAA,MACH,OACK;AAEL,aAAK,aAAa,UAAU;AAAA,UAC1B,OAAO,OAAO;AAAA,UACd,KAAK;AAAA,QACP;AAAA,MAAA;AAAA,IACF,OACK;AAED,UAAA,MAAM,QAAQ,OAAO,GAAG;AAExB,YAAA,QAAQ,WAAW,MAClB,OAAO,QAAQ,CAAC,MAAM,YAAY,SAAS,QAAQ,CAAC,CAAC,OACrD,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,MAAM,SACxC;AAEM,gBAAA,QAAQ,SAAS,QAAQ,CAAC,CAAC,IAC7B,QAAQ,CAAC,EAAE,YACX,QAAQ,CAAC;AACP,gBAAA,YAAY,QAAQ,CAAC;AAC3B,eAAK,aAAa,UAAU,GAAG,KAAK,IAAI,SAAS;AAAA,QAAA,OAC5C;AAEA,eAAA,aAAa,UAChB,QACA,IAAI,CAAC,CAAC,YAAY,SAAS,MAAM;AACjC,kBAAM,QAAQ,SAAS,UAAU,IAC7B,WAAW,YACX,OAAO,UAAU;AACd,mBAAA,GAAG,KAAK,IAAI,SAAS;AAAA,UAAA,CAC7B;AAAA,QAAA;AAAA,MACH,OACK;AACL,aAAK,aAAa,UAAU;AAAA,MAAA;AAAA,IAC9B;AAEK,WAAA;AAAA,EAAA;AAAA,EAGT,IACE,OASA;AACA,SAAK,aAAa,MAAM;AACjB,WAAA;AAAA,EAAA;AAAA,EAGT,KACE,OASA;AACA,SAAK,aAAa,OAAO;AAClB,WAAA;AAAA,EAAA;AAAA,EAGT,OAUE,aACA,UAuBA;AAUM,UAAA,eAAe,KAAK,cAAc;AAAA,MAItC;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA,MACE,IAAI,aAQF;AAAA,QACA,YAAY;AAAA,QACZ,cAAc,KAAK;AAAA,QACnB,SAAS,KAAK;AAAA,QACd,sBAAsB,KAAK;AAAA,QAC3B,+BAA+B,KAAK;AAAA,MACrC,CAAA;AAAA,IACL;AAEK,SAAA,cAAc,KAAK,YAAY;AAC7B,WAAA;AAAA,EAAA;AAAA,EAGT,SAQE;AACA,WAAO,KAAK,iBAAiB,EAAE,YAAY,SAAkB;AAAA,EAAA;AAAA,EAG/D,cAQE;AACA,WAAO,KAAK,iBAAiB,EAAE,YAAY,SAAkB;AAAA,EAAA;AAAA,EAG/D,QAQE;AACA,WAAO,KAAK,iBAAiB;AAAA,MAC3B,aAAa;AAAA,MACb,cAAc,EAAE,OAAO,KAAK;AAAA,IAAA,CAC7B;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMK,iBAAiB,uBAAyC;AAEhE,UAAM,qCAAqC,EAAE,GAAG,KAAK,aAAa;AAClE,UAAM,iBAAiB,mCAAmC;AAC1D,WAAO,mCAAmC;AAC1C,WAAO,mCAAmC;AAEtC,QAAA,cAAc,WAAW,kCAAkC;AAG/D,UAAM,cAAc,iBAChB,MAAM,QAAQ,cAAc,IAC1B,eAAe,IAAI,MAAM,IACzB,CAAC,OAAO,cAAc,CAAC,IACzB;AAIF,6BAAyB,KAAK;AAEhC,UAAM,qBAAqB,6BAA6B;AAAA,MACtD,gBAAgB;AAAA,MAChB,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAEf,CAAC;AAGD,QAAI,oBAAoB;AAEhB,YAAA,SAAS,mBAAmB,WAAW,GAAG,IAC5C,mBAAmB,MAAM,CAAC,IAC1B;AACJ,YAAM,YAAY,YAAY,SAAS,GAAG,IAAI,MAAM;AACpD,oBAAc,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM;AAAA,IAAA;AAG5C,WAAA;AAAA,EAAA;AAAA,EAGT,MAAM,QACJ,SA4BA;AACM,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AACtD,UAAM,cAAc,KAAK;AAAA,MACvB,cAAc;AAAA,IAChB;AAGA,QAAI,KAAK,aAAa;AACpB,YAAMA,OAAM,KAAK,WAAW,MAAM,aAAa;AAAA,QAC7C,SAAS;AAAA,QACT,cAAc,cAAc;AAAA,QAC5B,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,YAAMC,UAAS,MAAM,KAAK,QAAQ,aAAaD,MAAK,aAAa;AAEjE,UAAIC,QAAO,OAAO;AAChB,eAAO,EAAE,MAAM,QAAW,OAAOA,QAAO,MAAM;AAAA,MAAA;AAI1C,YAAA,QACJ,OAAOA,QAAO,SAAS,WAAW,OAAOA,QAAO,IAAI,IAAIA,QAAO;AACjE,aAAO,EAAE,MAAM,OAAiB,OAAO,OAAU;AAAA,IAAA;AAGnD,UAAM,MAAM,KAAK,WAAW,MAAM,aAAa;AAAA,MAC7C,SAAS,KAAK;AAAA,MACd,cAAc,cAAc;AAAA,MAC5B,YAAY,KAAK;AAAA,IAAA,CAClB;AAED,UAAM,SAAS,MAAM,KAAK,QAAQ,aAAa,KAAK,aAAa;AAEjE,QAAI,OAAO,OAAO;AAChB,aAAO,EAAE,MAAM,QAAW,OAAO,OAAO,MAAM;AAAA,IAAA;AAI9B,SAAK,aAAa,WAAW;AAExC,WAAA,qBAAqB,OAAO,MAAM;AAAA,MACvC,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,gBAAgB,mCAAS;AAAA,MACzB,cAAc,cAAc;AAAA,MAC5B,uBAAuB,cAAc;AAAA,MACrC,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAAA,EAGH,iBAAyB;AACjB,UAAA,cAAc,KAAK,iBAAiB;AACnC,WAAA,KAAK,WAAW,UAAU,aAAa;AAAA,MAC5C,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,IAAA,CAClB;AAAA,EAAA;AAAA,EAGH,mBAAgE;AACxD,UAAA,cAAc,KAAK,iBAAiB;AAC1C,UAAM,MAAM,KAAK,WAAW,MAAM,aAAa;AAAA,MAC7C,SAAS,KAAK;AAAA,MACd,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,IAAA,CAClB;AAEM,WAAA;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAAA,EAGF,UAAU,SAAiB,SAAmC;AACtD,UAAA,SAAS,KAAK,iBAAiB;AAC9B,WAAA,mBAAmB,SAAS,QAAQ,OAAO;AAAA,EAAA;AAAA,EAGpD,MAAM,gBACJ,UACA,SAYA;AAEI,QAAA,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA,SAAS,OACP,IAAI,KAAK,YAAY,IAAI,aAAa,KAAK,UAAU,CAAC;AAAA,MAC1D;AACO,aAAA,EAAE,MAAM,QAAW,MAAM;AAAA,IAAA;AAI9B,QAAA,SAAS,WAAW,KAAK;AAEvB,UAAA,KAAK,eAAe,OAAO;AACzB,YAAA,KAAK,eAAe,SAAS;AAC/B,iBAAO,EAAE,MAAM,MAAa,OAAO,OAAU;AAAA,QAAA;AAExC,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,IAAI,yBAAyB,OAAO,CAAC;AAAA,QAC9C;AAAA,MAAA;AAEF,aAAO,EAAE,MAAM,IAAW,OAAO,OAAU;AAAA,IAAA;AAIzC,QAAA;AACA,QAAA;AACQ,gBAAA,MAAM,cAAc,QAAQ;AAAA,aAC/B,KAAK;AAEZ,UAAI,eAAe,eAAe,SAAS,WAAW,KAAK;AAEzD,eAAO,EAAE,MAAM,IAAW,OAAO,OAAU;AAAA,MAAA;AAEtC,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS,kCAAkC,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,UAC/F,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAGF,QAAI,CAAC,SAAS;AACL,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,UACT,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAGI,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AAEpC,SAAK,aAAa,WAAW;AAE/C,WAAO,qBAAqB,SAAS;AAAA,MACnC,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,gBAAgB,mCAAS;AAAA,MACzB,cAAc,cAAc;AAAA,MAC5B,uBAAuB,cAAc;AAAA,MACrC,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAEL;"}
@@ -0,0 +1,25 @@
1
+ import { QueryOptions } from 'odata-query';
2
+ import { FMTable } from '../../orm/table.js';
3
+ import { Result } from '../../types.js';
4
+ import { ExpandConfig } from './expand-builder.js';
5
+ import { InternalLogger } from '../../logger.js';
6
+ /**
7
+ * Configuration for processing query responses
8
+ */
9
+ export interface ProcessQueryResponseConfig<T> {
10
+ occurrence?: FMTable<any, any>;
11
+ singleMode: "exact" | "maybe" | false;
12
+ queryOptions: Partial<QueryOptions<T>>;
13
+ expandConfigs: ExpandConfig[];
14
+ skipValidation?: boolean;
15
+ useEntityIds?: boolean;
16
+ includeSpecialColumns?: boolean;
17
+ fieldMapping?: Record<string, string>;
18
+ logger: InternalLogger;
19
+ }
20
+ /**
21
+ * Processes a query response by transforming field IDs and validating the data.
22
+ * This function consolidates the response processing logic that was duplicated
23
+ * across multiple navigation branches in QueryBuilder.execute().
24
+ */
25
+ export declare function processQueryResponse<T>(response: any, config: ProcessQueryResponseConfig<T>): Promise<Result<any>>;
@@ -0,0 +1,77 @@
1
+ import { Column } from '../../orm/column.js';
2
+ /**
3
+ * Type-safe orderBy type that provides better DX than odata-query's default.
4
+ *
5
+ * Supported forms:
6
+ * - `keyof T` - single field name (defaults to ascending)
7
+ * - `[keyof T, 'asc' | 'desc']` - single field with explicit direction
8
+ * - `Array<[keyof T, 'asc' | 'desc']>` - multiple fields with directions
9
+ *
10
+ * This type intentionally EXCLUDES `Array<keyof T>` to avoid ambiguity
11
+ * between [field1, field2] and [field, direction].
12
+ */
13
+ export type TypeSafeOrderBy<T> = (keyof T & string) | [keyof T & string, "asc" | "desc"] | Array<[keyof T & string, "asc" | "desc"]>;
14
+ export type ExpandConfig = {
15
+ relation: string;
16
+ options?: Partial<import('odata-query').QueryOptions<any>>;
17
+ targetTable?: import('../../orm/table.js').FMTable<any, any>;
18
+ };
19
+ export type ExpandedRelations = Record<string, {
20
+ schema: any;
21
+ selected: any;
22
+ nested?: ExpandedRelations;
23
+ }>;
24
+ /**
25
+ * Extract the value type from a Column.
26
+ * This uses the phantom type stored in Column to get the actual value type (output type for reading).
27
+ */
28
+ type ExtractColumnType<C> = C extends Column<infer T, any, any, any> ? T : never;
29
+ /**
30
+ * Map a select object to its return type.
31
+ * For each key in the select object, extract the type from the corresponding Column.
32
+ */
33
+ type MapSelectToReturnType<TSelect extends Record<string, Column<any, any, any, any>>, TSchema extends Record<string, any>> = {
34
+ [K in keyof TSelect]: ExtractColumnType<TSelect[K]>;
35
+ };
36
+ /**
37
+ * Helper: Resolve a single expand's return type, including nested expands
38
+ */
39
+ export type ResolveExpandType<Exp extends {
40
+ schema: any;
41
+ selected: any;
42
+ nested?: ExpandedRelations;
43
+ }> = // Handle the selected fields
44
+ (Exp["selected"] extends Record<string, Column<any, any, any, any>> ? MapSelectToReturnType<Exp["selected"], Exp["schema"]> : Exp["selected"] extends keyof Exp["schema"] ? Pick<Exp["schema"], Exp["selected"]> : Exp["schema"]) & (Exp["nested"] extends ExpandedRelations ? ResolveExpandedRelations<Exp["nested"]> : {});
45
+ /**
46
+ * Helper: Resolve all expanded relations recursively
47
+ */
48
+ export type ResolveExpandedRelations<Exps extends ExpandedRelations> = {
49
+ [K in keyof Exps]: ResolveExpandType<Exps[K]>[];
50
+ };
51
+ /**
52
+ * System columns option for select() method.
53
+ * Allows explicitly requesting ROWID and/or ROWMODID when using select().
54
+ */
55
+ export type SystemColumnsOption = {
56
+ ROWID?: boolean;
57
+ ROWMODID?: boolean;
58
+ };
59
+ /**
60
+ * Extract system columns type from SystemColumnsOption.
61
+ * Returns an object type with ROWID and/or ROWMODID properties when set to true.
62
+ */
63
+ export type SystemColumnsFromOption<T extends SystemColumnsOption | undefined> = (T extends {
64
+ ROWID: true;
65
+ } ? {
66
+ ROWID: number;
67
+ } : {}) & (T extends {
68
+ ROWMODID: true;
69
+ } ? {
70
+ ROWMODID: number;
71
+ } : {});
72
+ export type QueryReturnType<T extends Record<string, any>, Selected extends keyof T | Record<string, Column<any, any, any, any>>, SingleMode extends "exact" | "maybe" | false, IsCount extends boolean, Expands extends ExpandedRelations, SystemCols extends SystemColumnsOption | undefined = undefined> = IsCount extends true ? number : [
73
+ Selected
74
+ ] extends [Record<string, Column<any, any, any, any>>] ? SingleMode extends "exact" ? MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols> : SingleMode extends "maybe" ? (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>) | null : (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>)[] : [
75
+ Selected
76
+ ] extends [keyof T] ? SingleMode extends "exact" ? Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols> : SingleMode extends "maybe" ? (Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>) | null : (Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>)[] : never;
77
+ export {};