@proofkit/fmodata 0.1.0-alpha.8 → 0.1.0-beta.23

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 (163) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +651 -449
  3. package/dist/esm/client/batch-builder.d.ts +10 -9
  4. package/dist/esm/client/batch-builder.js +119 -56
  5. package/dist/esm/client/batch-builder.js.map +1 -1
  6. package/dist/esm/client/batch-request.js +16 -21
  7. package/dist/esm/client/batch-request.js.map +1 -1
  8. package/dist/esm/client/builders/default-select.d.ts +10 -0
  9. package/dist/esm/client/builders/default-select.js +41 -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 +185 -0
  13. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  14. package/dist/esm/client/builders/index.d.ts +9 -0
  15. package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
  16. package/dist/esm/client/builders/query-string-builder.js +21 -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 +175 -0
  20. package/dist/esm/client/builders/response-processor.js.map +1 -0
  21. package/dist/esm/client/builders/select-mixin.d.ts +25 -0
  22. package/dist/esm/client/builders/select-mixin.js +28 -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 +30 -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 +44 -0
  30. package/dist/esm/client/builders/table-utils.js.map +1 -0
  31. package/dist/esm/client/database.d.ts +34 -22
  32. package/dist/esm/client/database.js +48 -84
  33. package/dist/esm/client/database.js.map +1 -1
  34. package/dist/esm/client/delete-builder.d.ts +25 -30
  35. package/dist/esm/client/delete-builder.js +45 -30
  36. package/dist/esm/client/delete-builder.js.map +1 -1
  37. package/dist/esm/client/entity-set.d.ts +35 -43
  38. package/dist/esm/client/entity-set.js +110 -52
  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 +25 -0
  42. package/dist/esm/client/error-parser.js.map +1 -0
  43. package/dist/esm/client/filemaker-odata.d.ts +26 -7
  44. package/dist/esm/client/filemaker-odata.js +65 -42
  45. package/dist/esm/client/filemaker-odata.js.map +1 -1
  46. package/dist/esm/client/insert-builder.d.ts +19 -24
  47. package/dist/esm/client/insert-builder.js +94 -58
  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 +4 -0
  51. package/dist/esm/client/query/query-builder.d.ts +132 -0
  52. package/dist/esm/client/query/query-builder.js +456 -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 +100 -0
  58. package/dist/esm/client/query/url-builder.js.map +1 -0
  59. package/dist/esm/client/query-builder.d.ts +2 -115
  60. package/dist/esm/client/record-builder.d.ts +108 -36
  61. package/dist/esm/client/record-builder.js +284 -119
  62. package/dist/esm/client/record-builder.js.map +1 -1
  63. package/dist/esm/client/response-processor.d.ts +4 -9
  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 +5 -5
  68. package/dist/esm/client/schema-manager.js +45 -31
  69. package/dist/esm/client/schema-manager.js.map +1 -1
  70. package/dist/esm/client/update-builder.d.ts +34 -40
  71. package/dist/esm/client/update-builder.js +99 -58
  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 +189 -0
  75. package/dist/esm/client/webhook-builder.js.map +1 -0
  76. package/dist/esm/errors.d.ts +19 -2
  77. package/dist/esm/errors.js +39 -4
  78. package/dist/esm/errors.js.map +1 -1
  79. package/dist/esm/index.d.ts +10 -8
  80. package/dist/esm/index.js +40 -10
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/logger.d.ts +47 -0
  83. package/dist/esm/logger.js +69 -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 +63 -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 +158 -0
  91. package/dist/esm/orm/field-builders.js.map +1 -0
  92. package/dist/esm/orm/index.d.ts +5 -0
  93. package/dist/esm/orm/operators.d.ts +173 -0
  94. package/dist/esm/orm/operators.js +260 -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 +202 -0
  98. package/dist/esm/orm/table.js.map +1 -0
  99. package/dist/esm/transform.d.ts +20 -21
  100. package/dist/esm/transform.js +44 -45
  101. package/dist/esm/transform.js.map +1 -1
  102. package/dist/esm/types.d.ts +96 -30
  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 -12
  106. package/dist/esm/validation.js +132 -85
  107. package/dist/esm/validation.js.map +1 -1
  108. package/package.json +28 -20
  109. package/src/client/batch-builder.ts +153 -89
  110. package/src/client/batch-request.ts +25 -41
  111. package/src/client/builders/default-select.ts +75 -0
  112. package/src/client/builders/expand-builder.ts +246 -0
  113. package/src/client/builders/index.ts +11 -0
  114. package/src/client/builders/query-string-builder.ts +46 -0
  115. package/src/client/builders/response-processor.ts +279 -0
  116. package/src/client/builders/select-mixin.ts +65 -0
  117. package/src/client/builders/select-utils.ts +59 -0
  118. package/src/client/builders/shared-types.ts +45 -0
  119. package/src/client/builders/table-utils.ts +83 -0
  120. package/src/client/database.ts +89 -183
  121. package/src/client/delete-builder.ts +74 -84
  122. package/src/client/entity-set.ts +266 -293
  123. package/src/client/error-parser.ts +41 -0
  124. package/src/client/filemaker-odata.ts +98 -66
  125. package/src/client/insert-builder.ts +157 -118
  126. package/src/client/query/expand-builder.ts +160 -0
  127. package/src/client/query/index.ts +14 -0
  128. package/src/client/query/query-builder.ts +729 -0
  129. package/src/client/query/response-processor.ts +226 -0
  130. package/src/client/query/types.ts +126 -0
  131. package/src/client/query/url-builder.ts +151 -0
  132. package/src/client/query-builder.ts +10 -1455
  133. package/src/client/record-builder.ts +575 -240
  134. package/src/client/response-processor.ts +15 -42
  135. package/src/client/sanitize-json.ts +64 -0
  136. package/src/client/schema-manager.ts +61 -76
  137. package/src/client/update-builder.ts +161 -143
  138. package/src/client/webhook-builder.ts +265 -0
  139. package/src/errors.ts +49 -16
  140. package/src/index.ts +99 -54
  141. package/src/logger.test.ts +34 -0
  142. package/src/logger.ts +116 -0
  143. package/src/orm/column.ts +106 -0
  144. package/src/orm/field-builders.ts +250 -0
  145. package/src/orm/index.ts +61 -0
  146. package/src/orm/operators.ts +473 -0
  147. package/src/orm/table.ts +741 -0
  148. package/src/transform.ts +90 -70
  149. package/src/types.ts +154 -113
  150. package/src/validation.ts +200 -115
  151. package/dist/esm/client/base-table.d.ts +0 -125
  152. package/dist/esm/client/base-table.js +0 -57
  153. package/dist/esm/client/base-table.js.map +0 -1
  154. package/dist/esm/client/query-builder.js +0 -896
  155. package/dist/esm/client/query-builder.js.map +0 -1
  156. package/dist/esm/client/table-occurrence.d.ts +0 -72
  157. package/dist/esm/client/table-occurrence.js +0 -74
  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 -166
  161. package/src/client/query-builder.ts.bak +0 -1457
  162. package/src/client/table-occurrence.ts +0 -175
  163. package/src/filter-types.ts +0 -97
@@ -1,25 +1,31 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { validateSingleResponse } from "../validation.js";
5
- import { getTableIdentifiers, transformFieldNamesToIds, transformResponseFields } from "../transform.js";
6
4
  import { InvalidLocationHeaderError } from "../errors.js";
5
+ import { isUsingEntityIds, getTableName, getTableId, getBaseTableConfig } from "../orm/table.js";
6
+ import { transformFieldNamesToIds, transformResponseFields } from "../transform.js";
7
+ import { getAcceptHeader } from "../types.js";
8
+ import { validateAndTransformInput, validateSingleResponse } from "../validation.js";
9
+ import { parseErrorResponse } from "./error-parser.js";
10
+ import { safeJsonParse } from "./sanitize-json.js";
11
+ const ROWID_MATCH_REGEX = /ROWID=(\d+)/;
12
+ const PAREN_VALUE_REGEX = /\(['"]?([^'"]+)['"]?\)/;
7
13
  class InsertBuilder {
8
14
  constructor(config) {
9
- __publicField(this, "occurrence");
10
- __publicField(this, "tableName");
15
+ __publicField(this, "table");
11
16
  __publicField(this, "databaseName");
12
17
  __publicField(this, "context");
13
18
  __publicField(this, "data");
14
19
  __publicField(this, "returnPreference");
15
20
  __publicField(this, "databaseUseEntityIds");
16
- this.occurrence = config.occurrence;
17
- this.tableName = config.tableName;
21
+ __publicField(this, "databaseIncludeSpecialColumns");
22
+ this.table = config.occurrence;
18
23
  this.databaseName = config.databaseName;
19
24
  this.context = config.context;
20
25
  this.data = config.data;
21
26
  this.returnPreference = config.returnPreference || "representation";
22
27
  this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
28
+ this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;
23
29
  }
24
30
  /**
25
31
  * Helper to merge database-level useEntityIds with per-request options
@@ -30,16 +36,6 @@ class InsertBuilder {
30
36
  useEntityIds: (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds
31
37
  };
32
38
  }
33
- /**
34
- * Helper to conditionally strip OData annotations based on options
35
- */
36
- stripODataAnnotationsIfNeeded(data, options) {
37
- if ((options == null ? void 0 : options.includeODataAnnotations) === true) {
38
- return data;
39
- }
40
- const { "@id": _id, "@editLink": _editLink, ...rest } = data;
41
- return rest;
42
- }
43
39
  /**
44
40
  * Parse ROWID from Location header
45
41
  * Expected formats:
@@ -48,19 +44,17 @@ class InsertBuilder {
48
44
  */
49
45
  parseLocationHeader(locationHeader) {
50
46
  if (!locationHeader) {
51
- throw new InvalidLocationHeaderError(
52
- "Location header is required but was not provided"
53
- );
47
+ throw new InvalidLocationHeaderError("Location header is required but was not provided");
54
48
  }
55
- const rowidMatch = locationHeader.match(/ROWID=(\d+)/);
56
- if (rowidMatch && rowidMatch[1]) {
57
- return parseInt(rowidMatch[1], 10);
49
+ const rowidMatch = locationHeader.match(ROWID_MATCH_REGEX);
50
+ if (rowidMatch == null ? void 0 : rowidMatch[1]) {
51
+ return Number.parseInt(rowidMatch[1], 10);
58
52
  }
59
- const parenMatch = locationHeader.match(/\(['"]?([^'"]+)['"]?\)/);
60
- if (parenMatch && parenMatch[1]) {
53
+ const parenMatch = locationHeader.match(PAREN_VALUE_REGEX);
54
+ if (parenMatch == null ? void 0 : parenMatch[1]) {
61
55
  const value = parenMatch[1];
62
- const numValue = parseInt(value, 10);
63
- if (!isNaN(numValue)) {
56
+ const numValue = Number.parseInt(value, 10);
57
+ if (!Number.isNaN(numValue)) {
64
58
  return numValue;
65
59
  }
66
60
  }
@@ -75,35 +69,48 @@ class InsertBuilder {
75
69
  */
76
70
  getTableId(useEntityIds) {
77
71
  var _a, _b;
78
- if (!this.occurrence) {
79
- return this.tableName;
72
+ if (!this.table) {
73
+ throw new Error("Table occurrence is required");
80
74
  }
81
75
  const contextDefault = ((_b = (_a = this.context)._getUseEntityIds) == null ? void 0 : _b.call(_a)) ?? false;
82
76
  const shouldUseIds = useEntityIds ?? contextDefault;
83
77
  if (shouldUseIds) {
84
- const identifiers = getTableIdentifiers(this.occurrence);
85
- if (!identifiers.id) {
78
+ if (!isUsingEntityIds(this.table)) {
86
79
  throw new Error(
87
- `useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`
80
+ `useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`
88
81
  );
89
82
  }
90
- return identifiers.id;
83
+ return getTableId(this.table);
91
84
  }
92
- return this.occurrence.getTableName();
85
+ return getTableName(this.table);
93
86
  }
94
87
  async execute(options) {
95
- var _a, _b, _c, _d;
96
88
  const mergedOptions = this.mergeExecuteOptions(options);
97
89
  const tableId = this.getTableId(mergedOptions.useEntityIds);
98
90
  const url = `/${this.databaseName}/${tableId}`;
91
+ let validatedData = this.data;
92
+ if (this.table) {
93
+ const baseTableConfig = getBaseTableConfig(this.table);
94
+ const inputSchema = baseTableConfig.inputSchema;
95
+ try {
96
+ validatedData = await validateAndTransformInput(this.data, inputSchema);
97
+ } catch (error) {
98
+ return {
99
+ data: void 0,
100
+ error: error instanceof Error ? error : new Error(String(error))
101
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
102
+ };
103
+ }
104
+ }
99
105
  const shouldUseIds = mergedOptions.useEntityIds ?? false;
100
- const transformedData = ((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds ? transformFieldNamesToIds(this.data, this.occurrence.baseTable) : this.data;
106
+ const transformedData = this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;
101
107
  const preferHeader = this.returnPreference === "minimal" ? "return=minimal" : "return=representation";
102
108
  const result = await this.context._makeRequest(url, {
103
109
  method: "POST",
104
110
  headers: {
105
111
  "Content-Type": "application/json",
106
112
  Prefer: preferHeader,
113
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for headers object
107
114
  ...(mergedOptions == null ? void 0 : mergedOptions.headers) || {}
108
115
  },
109
116
  body: JSON.stringify(transformedData),
@@ -114,7 +121,7 @@ class InsertBuilder {
114
121
  }
115
122
  if (this.returnPreference === "minimal") {
116
123
  const responseData = result.data;
117
- if (!responseData || !responseData._location) {
124
+ if (!(responseData == null ? void 0 : responseData._location)) {
118
125
  throw new InvalidLocationHeaderError(
119
126
  "Location header is required when using return=minimal but was not found in response"
120
127
  );
@@ -123,15 +130,23 @@ class InsertBuilder {
123
130
  return { data: { ROWID: rowid }, error: void 0 };
124
131
  }
125
132
  let response = result.data;
126
- if (((_b = this.occurrence) == null ? void 0 : _b.baseTable) && shouldUseIds) {
133
+ if (this.table && shouldUseIds) {
127
134
  response = transformResponseFields(
128
135
  response,
129
- this.occurrence.baseTable,
136
+ this.table,
130
137
  void 0
131
138
  // No expand configs for insert
132
139
  );
133
140
  }
134
- const schema = (_d = (_c = this.occurrence) == null ? void 0 : _c.baseTable) == null ? void 0 : _d.schema;
141
+ let schema;
142
+ if (this.table) {
143
+ const baseTableConfig = getBaseTableConfig(this.table);
144
+ const containerFields = baseTableConfig.containerFields || [];
145
+ schema = { ...baseTableConfig.schema };
146
+ for (const containerField of containerFields) {
147
+ delete schema[containerField];
148
+ }
149
+ }
135
150
  const validation = await validateSingleResponse(
136
151
  response,
137
152
  schema,
@@ -151,23 +166,19 @@ class InsertBuilder {
151
166
  error: new Error("Insert operation returned null response")
152
167
  };
153
168
  }
154
- const finalData = this.stripODataAnnotationsIfNeeded(
155
- validation.data,
156
- options
157
- );
158
- return { data: finalData, error: void 0 };
169
+ return { data: validation.data, error: void 0 };
159
170
  }
171
+ // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value
160
172
  getRequestConfig() {
161
- var _a;
162
173
  const tableId = this.getTableId(this.databaseUseEntityIds);
163
- const transformedData = ((_a = this.occurrence) == null ? void 0 : _a.baseTable) && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.occurrence.baseTable) : this.data;
174
+ const transformedData = this.table && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;
164
175
  return {
165
176
  method: "POST",
166
177
  url: `/${this.databaseName}/${tableId}`,
167
178
  body: JSON.stringify(transformedData)
168
179
  };
169
180
  }
170
- toRequest(baseUrl) {
181
+ toRequest(baseUrl, options) {
171
182
  const config = this.getRequestConfig();
172
183
  const fullUrl = `${baseUrl}${config.url}`;
173
184
  const preferHeader = this.returnPreference === "minimal" ? "return=minimal" : "return=representation";
@@ -175,14 +186,18 @@ class InsertBuilder {
175
186
  method: config.method,
176
187
  headers: {
177
188
  "Content-Type": "application/json",
178
- Accept: "application/json",
189
+ Accept: getAcceptHeader(options == null ? void 0 : options.includeODataAnnotations),
179
190
  Prefer: preferHeader
180
191
  },
181
192
  body: config.body
182
193
  });
183
194
  }
184
195
  async processResponse(response, options) {
185
- var _a, _b, _c;
196
+ if (!response.ok) {
197
+ const tableName = this.table ? getTableName(this.table) : "unknown";
198
+ const error = await parseErrorResponse(response, response.url || `/${this.databaseName}/${tableName}`);
199
+ return { data: void 0, error };
200
+ }
186
201
  if (response.status === 204) {
187
202
  if (this.returnPreference === "minimal") {
188
203
  const locationHeader = response.headers.get("Location") || response.headers.get("location");
@@ -195,6 +210,7 @@ class InsertBuilder {
195
210
  );
196
211
  }
197
212
  return {
213
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
198
214
  data: {},
199
215
  error: void 0
200
216
  };
@@ -206,10 +222,11 @@ class InsertBuilder {
206
222
  }
207
223
  let rawResponse;
208
224
  try {
209
- rawResponse = await response.json();
225
+ rawResponse = await safeJsonParse(response);
210
226
  } catch (err) {
211
227
  if (response.status === 204) {
212
228
  return {
229
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
213
230
  data: {},
214
231
  error: void 0
215
232
  };
@@ -220,20 +237,43 @@ class InsertBuilder {
220
237
  name: "ResponseParseError",
221
238
  message: `Failed to parse response JSON: ${err instanceof Error ? err.message : "Unknown error"}`,
222
239
  timestamp: /* @__PURE__ */ new Date()
240
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for error object
223
241
  }
224
242
  };
225
243
  }
244
+ let _validatedData = this.data;
245
+ if (this.table) {
246
+ const baseTableConfig = getBaseTableConfig(this.table);
247
+ const inputSchema = baseTableConfig.inputSchema;
248
+ try {
249
+ _validatedData = await validateAndTransformInput(this.data, inputSchema);
250
+ } catch (error) {
251
+ return {
252
+ data: void 0,
253
+ error: error instanceof Error ? error : new Error(String(error))
254
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
255
+ };
256
+ }
257
+ }
226
258
  const shouldUseIds = (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds;
227
259
  let transformedResponse = rawResponse;
228
- if (((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds) {
260
+ if (this.table && shouldUseIds) {
229
261
  transformedResponse = transformResponseFields(
230
262
  rawResponse,
231
- this.occurrence.baseTable,
263
+ this.table,
232
264
  void 0
233
265
  // No expand configs for insert
234
266
  );
235
267
  }
236
- const schema = (_c = (_b = this.occurrence) == null ? void 0 : _b.baseTable) == null ? void 0 : _c.schema;
268
+ let schema;
269
+ if (this.table) {
270
+ const baseTableConfig = getBaseTableConfig(this.table);
271
+ const containerFields = baseTableConfig.containerFields || [];
272
+ schema = { ...baseTableConfig.schema };
273
+ for (const containerField of containerFields) {
274
+ delete schema[containerField];
275
+ }
276
+ }
237
277
  const validation = await validateSingleResponse(
238
278
  transformedResponse,
239
279
  schema,
@@ -253,11 +293,7 @@ class InsertBuilder {
253
293
  error: new Error("Insert operation returned null response")
254
294
  };
255
295
  }
256
- const finalData = this.stripODataAnnotationsIfNeeded(
257
- validation.data,
258
- options
259
- );
260
- return { data: finalData, error: void 0 };
296
+ return { data: validation.data, error: void 0 };
261
297
  }
262
298
  }
263
299
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n InferSchemaType,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport { validateSingleResponse } from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformTableName,\n transformResponseFields,\n getTableIdentifiers,\n} from \"../transform\";\nimport { InvalidLocationHeaderError } from \"../errors\";\n\nexport type InsertOptions = {\n return?: \"minimal\" | \"representation\";\n};\n\nexport class InsertBuilder<\n T extends Record<string, any>,\n Occ extends TableOccurrence<any, any, any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\" ? { ROWID: number } : T\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private data: Partial<T>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference ||\n \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Helper to conditionally strip OData annotations based on options\n */\n private stripODataAnnotationsIfNeeded<T extends Record<string, any>>(\n data: T,\n options?: ExecuteOptions,\n ): T {\n // Only include annotations if explicitly requested\n if (options?.includeODataAnnotations === true) {\n return data;\n }\n\n // Strip OData annotations\n const { \"@id\": _id, \"@editLink\": _editLink, ...rest } = data;\n return rest as T;\n }\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\n \"Location header is required but was not provided\",\n );\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(/ROWID=(\\d+)/);\n if (rowidMatch && rowidMatch[1]) {\n return parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(/\\(['\"]?([^'\"]+)['\"]?\\)/);\n if (parenMatch && parenMatch[1]) {\n const value = parenMatch[1];\n const numValue = parseInt(value, 10);\n if (!isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\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 getTableId(useEntityIds?: boolean): string {\n if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n T,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n \n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n \n const transformedData = this.occurrence?.baseTable && shouldUseIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n // Make POST request with JSON body\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n const responseData = result.data as any;\n\n if (!responseData || !responseData._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.occurrence?.baseTable && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<T>(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n // Strip OData annotations unless explicitly requested\n const finalData = this.stripODataAnnotationsIfNeeded(\n validation.data,\n options,\n );\n\n return { data: finalData as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData = this.occurrence?.baseTable && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { ROWID: number } : T>\n > {\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader =\n response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n let rawResponse;\n try {\n rawResponse = await response.json();\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n data: {} as any,\n error: undefined,\n };\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 // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n \n let transformedResponse = rawResponse;\n if (this.occurrence?.baseTable && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.occurrence.baseTable,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<T>(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n // Strip OData annotations unless explicitly requested\n const finalData = this.stripODataAnnotationsIfNeeded(\n validation.data,\n options,\n );\n\n return { data: finalData as any, error: undefined };\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport { InvalidLocationHeaderError } from \"../errors\";\nimport type { FMTable } from \"../orm/table\";\nimport { getBaseTableConfig, getTableId as getTableIdHelper, getTableName, isUsingEntityIds } from \"../orm/table\";\nimport { transformFieldNamesToIds, transformResponseFields } from \"../transform\";\nimport type {\n ConditionallyWithODataAnnotations,\n ExecutableBuilder,\n ExecuteMethodOptions,\n ExecuteOptions,\n ExecutionContext,\n Result,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport { validateAndTransformInput, validateSingleResponse } from \"../validation\";\nimport { parseErrorResponse } from \"./error-parser\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\nconst ROWID_MATCH_REGEX = /ROWID=(\\d+)/;\nconst PAREN_VALUE_REGEX = /\\(['\"]?([^'\"]+)['\"]?\\)/;\n\nexport interface InsertOptions {\n return?: \"minimal\" | \"representation\";\n}\n\nimport type { InferSchemaOutputFromFMTable } from \"../orm/table\";\n\nexport class InsertBuilder<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n Occ extends FMTable<any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\" ? { ROWID: number } : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n{\n private readonly table?: Occ;\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n private readonly data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n private readonly returnPreference: ReturnPreference;\n\n private readonly databaseUseEntityIds: boolean;\n private readonly databaseIncludeSpecialColumns: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference || \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\"Location header is required but was not provided\");\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(ROWID_MATCH_REGEX);\n if (rowidMatch?.[1]) {\n return Number.parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(PAREN_VALUE_REGEX);\n if (parenMatch?.[1]) {\n const value = parenMatch[1];\n const numValue = Number.parseInt(value, 10);\n if (!Number.isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\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 getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;\n\n // Set Prefer header based on return preference\n const preferHeader = this.returnPreference === \"minimal\" ? \"return=minimal\" : \"return=representation\";\n\n // Make POST request with JSON body\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for headers object\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n const responseData = result.data as any;\n\n if (!responseData?._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.table && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic schema shape from table configuration\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<InferSchemaOutputFromFMTable<NonNullable<Occ>>>(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n return { data: validation.data as any, error: undefined };\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader = this.returnPreference === \"minimal\" ? \"return=minimal\" : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { ROWID: number } : InferSchemaOutputFromFMTable<NonNullable<Occ>>>\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(response, response.url || `/${this.databaseName}/${tableName}`);\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader = response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n let rawResponse: unknown;\n try {\n rawResponse = await safeJsonParse(response);\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n data: {} as any,\n error: undefined,\n };\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 // biome-ignore lint/suspicious/noExplicitAny: Type assertion for error object\n } as any,\n };\n }\n\n // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let _validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n _validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n\n let transformedResponse = rawResponse;\n if (this.table && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic schema shape from table configuration\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<InferSchemaOutputFromFMTable<NonNullable<Occ>>>(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":["getTableIdHelper"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { QueryOptions } from 'odata-query';
2
+ import { FMTable } from '../../orm/table.js';
3
+ import { ExpandValidationConfig } from '../../validation.js';
4
+ /**
5
+ * Internal type for expand configuration
6
+ */
7
+ export interface ExpandConfig {
8
+ relation: string;
9
+ options?: Partial<QueryOptions<any>>;
10
+ targetTable?: FMTable<any, any>;
11
+ }
12
+ /**
13
+ * Builds OData expand query strings and validation configs.
14
+ * Handles nested expands recursively and transforms relation names to FMTIDs
15
+ * when using entity IDs.
16
+ */
17
+ export declare class ExpandBuilder {
18
+ private readonly useEntityIds;
19
+ constructor(useEntityIds: boolean);
20
+ /**
21
+ * Builds OData expand query string from expand configurations.
22
+ * Handles nested expands recursively.
23
+ * Transforms relation names to FMTIDs if using entity IDs.
24
+ */
25
+ buildExpandString(configs: ExpandConfig[]): string;
26
+ /**
27
+ * Builds a single expand string with its options.
28
+ */
29
+ private buildSingleExpand;
30
+ /**
31
+ * Builds expand validation configs from internal expand configurations.
32
+ * These are used to validate expanded navigation properties.
33
+ */
34
+ buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[];
35
+ }
@@ -0,0 +1,4 @@
1
+ /** biome-ignore-all lint/performance/noBarrelFile: Re-exporting QueryBuilder and types */
2
+ export type { ExpandConfig } from './expand-builder.js';
3
+ export type { ExpandedRelations, QueryReturnType, TypeSafeOrderBy, } from './query-builder.js';
4
+ export { QueryBuilder } from './query-builder.js';
@@ -0,0 +1,132 @@
1
+ import { Column } from '../../orm/column.js';
2
+ import { FilterExpression, OrderByExpression } from '../../orm/operators.js';
3
+ import { ExtractTableName, FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget } from '../../orm/table.js';
4
+ import { ConditionallyWithODataAnnotations, ConditionallyWithSpecialColumns, ExecutableBuilder, ExecuteMethodOptions, ExecuteOptions, ExecutionContext, NormalizeIncludeSpecialColumns, Result } from '../../types.js';
5
+ import { ExpandedRelations } from '../builders/index.js';
6
+ import { QueryReturnType, SystemColumnsOption, TypeSafeOrderBy } from './types.js';
7
+ export type { ExpandedRelations } from '../builders/index.js';
8
+ export type { QueryReturnType, SystemColumnsOption, TypeSafeOrderBy } from './types.js';
9
+ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}, DatabaseIncludeSpecialColumns extends boolean = false, SystemCols extends SystemColumnsOption | undefined = undefined> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>> {
10
+ private queryOptions;
11
+ private expandConfigs;
12
+ private singleMode;
13
+ private isCountMode;
14
+ private readonly occurrence;
15
+ private readonly databaseName;
16
+ private readonly context;
17
+ private navigation?;
18
+ private readonly databaseUseEntityIds;
19
+ private readonly databaseIncludeSpecialColumns;
20
+ private readonly expandBuilder;
21
+ private urlBuilder;
22
+ private fieldMapping?;
23
+ private systemColumns?;
24
+ private readonly logger;
25
+ constructor(config: {
26
+ occurrence: Occ;
27
+ databaseName: string;
28
+ context: ExecutionContext;
29
+ databaseUseEntityIds?: boolean;
30
+ databaseIncludeSpecialColumns?: boolean;
31
+ });
32
+ /**
33
+ * Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options
34
+ */
35
+ private mergeExecuteOptions;
36
+ /**
37
+ * Creates a new QueryBuilder with modified configuration.
38
+ * Used by single(), maybeSingle(), count(), and select() to create new instances.
39
+ */
40
+ private cloneWithChanges;
41
+ /**
42
+ * Select fields using column references.
43
+ * Allows renaming fields by using different keys in the object.
44
+ * Container fields cannot be selected and will cause a type error.
45
+ *
46
+ * @example
47
+ * db.from(users).list().select({
48
+ * name: users.name,
49
+ * userEmail: users.email // renamed!
50
+ * })
51
+ *
52
+ * @example
53
+ * // Include system columns (ROWID, ROWMODID) when using select()
54
+ * db.from(users).list().select(
55
+ * { name: users.name },
56
+ * { ROWID: true, ROWMODID: true }
57
+ * )
58
+ *
59
+ * @param fields - Object mapping output keys to column references (container fields excluded)
60
+ * @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)
61
+ * @returns QueryBuilder with updated selected fields
62
+ */
63
+ select<TSelect extends Record<string, Column<any, any, ExtractTableName<Occ>, false>>, TSystemCols extends SystemColumnsOption = {}>(fields: TSelect, systemColumns?: TSystemCols): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, TSystemCols>;
64
+ /**
65
+ * Filter results using operator expressions (new ORM-style API).
66
+ * Supports eq, gt, lt, and, or, etc. operators with Column references.
67
+ * Also supports raw OData filter strings as an escape hatch.
68
+ *
69
+ * @example
70
+ * .where(eq(users.hobby, "reading"))
71
+ * .where(and(eq(users.active, true), gt(users.age, 18)))
72
+ * .where("status eq 'active'") // Raw OData string escape hatch
73
+ */
74
+ where(expression: FilterExpression | string): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
75
+ /**
76
+ * Specify the sort order for query results.
77
+ *
78
+ * @example Single field (ascending by default)
79
+ * ```ts
80
+ * .orderBy("name")
81
+ * .orderBy(users.name) // Column reference
82
+ * .orderBy(asc(users.name)) // Explicit ascending
83
+ * ```
84
+ *
85
+ * @example Single field with explicit direction
86
+ * ```ts
87
+ * .orderBy(["name", "desc"])
88
+ * .orderBy([users.name, "desc"]) // Column reference
89
+ * .orderBy(desc(users.name)) // Explicit descending
90
+ * ```
91
+ *
92
+ * @example Multiple fields with directions
93
+ * ```ts
94
+ * .orderBy([["name", "asc"], ["createdAt", "desc"]])
95
+ * .orderBy([[users.name, "asc"], [users.createdAt, "desc"]]) // Column references
96
+ * .orderBy(users.name, desc(users.age)) // Variadic with helpers
97
+ * ```
98
+ */
99
+ orderBy(...orderByArgs: [
100
+ TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>> | Column<any, any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>
101
+ ] | [
102
+ Column<any, any, ExtractTableName<Occ>>,
103
+ ...Array<Column<any, any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>>
104
+ ]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
105
+ top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
106
+ skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
107
+ expand<TargetTable extends FMTable<any, any>, TSelected extends keyof InferSchemaOutputFromFMTable<TargetTable> | Record<string, Column<any, any, ExtractTableName<TargetTable>>> = keyof InferSchemaOutputFromFMTable<TargetTable>, TNestedExpands extends ExpandedRelations = {}>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false, {}>) => QueryBuilder<TargetTable, TSelected, any, any, TNestedExpands>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands & {
108
+ [K in ExtractTableName<TargetTable>]: {
109
+ schema: InferSchemaOutputFromFMTable<TargetTable>;
110
+ selected: TSelected;
111
+ nested: TNestedExpands;
112
+ };
113
+ }, DatabaseIncludeSpecialColumns, SystemCols>;
114
+ single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
115
+ maybeSingle(): QueryBuilder<Occ, Selected, "maybe", IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
116
+ count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
117
+ /**
118
+ * Builds the OData query string from current query options and expand configs.
119
+ */
120
+ private buildQueryString;
121
+ execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ConditionallyWithODataAnnotations<ConditionallyWithSpecialColumns<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>, NormalizeIncludeSpecialColumns<EO["includeSpecialColumns"], DatabaseIncludeSpecialColumns>, Selected extends Record<string, Column<any, any, any>> ? true : Selected extends keyof InferSchemaOutputFromFMTable<Occ> ? false : true>, EO["includeODataAnnotations"] extends true ? true : false>>>;
122
+ getQueryString(options?: {
123
+ useEntityIds?: boolean;
124
+ }): string;
125
+ getRequestConfig(): {
126
+ method: string;
127
+ url: string;
128
+ body?: any;
129
+ };
130
+ toRequest(baseUrl: string, options?: ExecuteOptions): Request;
131
+ processResponse(response: Response, options?: ExecuteOptions): Promise<Result<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>>>;
132
+ }