@proofkit/fmodata 0.1.0-alpha.9 → 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 -175
  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,18 +1,82 @@
1
- import { RecordCountMismatchError, ResponseStructureError, ValidationError } from "./errors.js";
2
- async function validateRecord(record, schema, selectedFields, expandConfigs) {
3
- var _a, _b;
1
+ import { ValidationError, RecordCountMismatchError, ResponseStructureError } from "./errors.js";
2
+ async function validateAndTransformInput(data, inputSchema) {
3
+ if (!inputSchema) {
4
+ return data;
5
+ }
6
+ const transformedData = { ...data };
7
+ for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {
8
+ if (!fieldSchema) {
9
+ continue;
10
+ }
11
+ if (fieldName in data) {
12
+ const inputValue = data[fieldName];
13
+ try {
14
+ let result = fieldSchema["~standard"].validate(inputValue);
15
+ if (result instanceof Promise) {
16
+ result = await result;
17
+ }
18
+ if (result.issues) {
19
+ throw new ValidationError(`Input validation failed for field '${fieldName}'`, result.issues, {
20
+ field: fieldName,
21
+ value: inputValue,
22
+ cause: result.issues
23
+ });
24
+ }
25
+ transformedData[fieldName] = result.value;
26
+ } catch (error) {
27
+ if (error instanceof ValidationError) {
28
+ throw error;
29
+ }
30
+ throw new ValidationError(`Input validation failed for field '${fieldName}'`, [], {
31
+ field: fieldName,
32
+ value: inputValue,
33
+ cause: error
34
+ });
35
+ }
36
+ }
37
+ }
38
+ return transformedData;
39
+ }
40
+ async function validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns) {
41
+ var _a, _b, _c, _d;
4
42
  const { "@id": id, "@editLink": editLink, ...rest } = record;
5
- const metadata = {
6
- "@id": id || "",
7
- "@editLink": editLink || ""
8
- };
43
+ const metadata = {};
44
+ if (id) {
45
+ metadata["@id"] = id;
46
+ }
47
+ if (editLink) {
48
+ metadata["@editLink"] = editLink;
49
+ }
9
50
  if (!schema) {
51
+ const { ROWID: ROWID2, ROWMODID: ROWMODID2, ...restWithoutSystemFields2 } = rest;
52
+ const specialColumns2 = {};
53
+ if (includeSpecialColumns) {
54
+ if (ROWID2 !== void 0) {
55
+ specialColumns2.ROWID = ROWID2;
56
+ }
57
+ if (ROWMODID2 !== void 0) {
58
+ specialColumns2.ROWMODID = ROWMODID2;
59
+ }
60
+ }
10
61
  return {
11
62
  valid: true,
12
- data: { ...rest, ...metadata }
63
+ data: {
64
+ ...restWithoutSystemFields2,
65
+ ...specialColumns2,
66
+ ...metadata
67
+ }
13
68
  };
14
69
  }
15
70
  const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;
71
+ const specialColumns = {};
72
+ if (includeSpecialColumns) {
73
+ if (ROWID !== void 0) {
74
+ specialColumns.ROWID = ROWID;
75
+ }
76
+ if (ROWMODID !== void 0) {
77
+ specialColumns.ROWMODID = ROWMODID;
78
+ }
79
+ }
16
80
  if (selectedFields && selectedFields.length > 0) {
17
81
  const validatedRecord2 = {};
18
82
  for (const field of selectedFields) {
@@ -22,38 +86,40 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
22
86
  const input = rest[fieldName];
23
87
  try {
24
88
  let result = fieldSchema["~standard"].validate(input);
25
- if (result instanceof Promise) result = await result;
89
+ if (result instanceof Promise) {
90
+ result = await result;
91
+ }
26
92
  if (result.issues) {
27
93
  return {
28
94
  valid: false,
29
- error: new ValidationError(
30
- `Validation failed for field '${fieldName}'`,
31
- result.issues,
32
- {
33
- field: fieldName,
34
- value: input,
35
- cause: result.issues
36
- }
37
- )
95
+ error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {
96
+ field: fieldName,
97
+ value: input,
98
+ cause: result.issues
99
+ })
38
100
  };
39
101
  }
40
102
  validatedRecord2[fieldName] = result.value;
41
103
  } catch (originalError) {
42
104
  return {
43
105
  valid: false,
44
- error: new ValidationError(
45
- `Validation failed for field '${fieldName}'`,
46
- [],
47
- {
48
- field: fieldName,
49
- value: input,
50
- cause: originalError
51
- }
52
- )
106
+ error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {
107
+ field: fieldName,
108
+ value: input,
109
+ cause: originalError
110
+ })
53
111
  };
54
112
  }
55
113
  } else {
56
- validatedRecord2[fieldName] = rest[fieldName];
114
+ if (fieldName === "ROWID" || fieldName === "ROWMODID") {
115
+ if (fieldName === "ROWID" && ROWID !== void 0) {
116
+ validatedRecord2[fieldName] = ROWID;
117
+ } else if (fieldName === "ROWMODID" && ROWMODID !== void 0) {
118
+ validatedRecord2[fieldName] = ROWMODID;
119
+ }
120
+ } else {
121
+ validatedRecord2[fieldName] = rest[fieldName];
122
+ }
57
123
  }
58
124
  }
59
125
  if (expandConfigs && expandConfigs.length > 0) {
@@ -64,9 +130,7 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
64
130
  const errorDetail = (_a = rest.error[0]) == null ? void 0 : _a.error;
65
131
  if (errorDetail == null ? void 0 : errorDetail.message) {
66
132
  const errorMessage = errorDetail.message;
67
- const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || expandConfig.selectedFields && expandConfig.selectedFields.some(
68
- (field) => errorMessage.toLowerCase().includes(field.toLowerCase())
69
- );
133
+ const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || ((_b = expandConfig.selectedFields) == null ? void 0 : _b.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase())));
70
134
  if (isRelatedToExpand) {
71
135
  return {
72
136
  valid: false,
@@ -90,7 +154,8 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
90
154
  item,
91
155
  expandConfig.targetSchema,
92
156
  expandConfig.selectedFields,
93
- expandConfig.nestedExpands
157
+ expandConfig.nestedExpands,
158
+ includeSpecialColumns
94
159
  );
95
160
  if (!itemValidation.valid) {
96
161
  return {
@@ -113,7 +178,8 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
113
178
  expandValue,
114
179
  expandConfig.targetSchema,
115
180
  expandConfig.selectedFields,
116
- expandConfig.nestedExpands
181
+ expandConfig.nestedExpands,
182
+ includeSpecialColumns
117
183
  );
118
184
  if (!itemValidation.valid) {
119
185
  return {
@@ -135,42 +201,39 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
135
201
  }
136
202
  return {
137
203
  valid: true,
138
- data: { ...validatedRecord2, ...metadata }
204
+ data: { ...validatedRecord2, ...specialColumns, ...metadata }
139
205
  };
140
206
  }
141
207
  const validatedRecord = { ...restWithoutSystemFields };
142
208
  for (const [fieldName, fieldSchema] of Object.entries(schema)) {
209
+ if (!fieldSchema) {
210
+ continue;
211
+ }
143
212
  const input = rest[fieldName];
144
213
  try {
145
214
  let result = fieldSchema["~standard"].validate(input);
146
- if (result instanceof Promise) result = await result;
215
+ if (result instanceof Promise) {
216
+ result = await result;
217
+ }
147
218
  if (result.issues) {
148
219
  return {
149
220
  valid: false,
150
- error: new ValidationError(
151
- `Validation failed for field '${fieldName}'`,
152
- result.issues,
153
- {
154
- field: fieldName,
155
- value: input,
156
- cause: result.issues
157
- }
158
- )
221
+ error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {
222
+ field: fieldName,
223
+ value: input,
224
+ cause: result.issues
225
+ })
159
226
  };
160
227
  }
161
228
  validatedRecord[fieldName] = result.value;
162
229
  } catch (originalError) {
163
230
  return {
164
231
  valid: false,
165
- error: new ValidationError(
166
- `Validation failed for field '${fieldName}'`,
167
- [],
168
- {
169
- field: fieldName,
170
- value: input,
171
- cause: originalError
172
- }
173
- )
232
+ error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {
233
+ field: fieldName,
234
+ value: input,
235
+ cause: originalError
236
+ })
174
237
  };
175
238
  }
176
239
  }
@@ -179,12 +242,10 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
179
242
  const expandValue = rest[expandConfig.relation];
180
243
  if (expandValue === void 0) {
181
244
  if (Array.isArray(rest.error) && rest.error.length > 0) {
182
- const errorDetail = (_b = rest.error[0]) == null ? void 0 : _b.error;
245
+ const errorDetail = (_c = rest.error[0]) == null ? void 0 : _c.error;
183
246
  if (errorDetail == null ? void 0 : errorDetail.message) {
184
247
  const errorMessage = errorDetail.message;
185
- const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || expandConfig.selectedFields && expandConfig.selectedFields.some(
186
- (field) => errorMessage.toLowerCase().includes(field.toLowerCase())
187
- );
248
+ const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || ((_d = expandConfig.selectedFields) == null ? void 0 : _d.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase())));
188
249
  if (isRelatedToExpand) {
189
250
  return {
190
251
  valid: false,
@@ -208,7 +269,8 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
208
269
  item,
209
270
  expandConfig.targetSchema,
210
271
  expandConfig.selectedFields,
211
- expandConfig.nestedExpands
272
+ expandConfig.nestedExpands,
273
+ includeSpecialColumns
212
274
  );
213
275
  if (!itemValidation.valid) {
214
276
  return {
@@ -231,7 +293,8 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
231
293
  expandValue,
232
294
  expandConfig.targetSchema,
233
295
  expandConfig.selectedFields,
234
- expandConfig.nestedExpands
296
+ expandConfig.nestedExpands,
297
+ includeSpecialColumns
235
298
  );
236
299
  if (!itemValidation.valid) {
237
300
  return {
@@ -253,35 +316,26 @@ async function validateRecord(record, schema, selectedFields, expandConfigs) {
253
316
  }
254
317
  return {
255
318
  valid: true,
256
- data: { ...validatedRecord, ...metadata }
319
+ data: { ...validatedRecord, ...specialColumns, ...metadata }
257
320
  };
258
321
  }
259
- async function validateListResponse(response, schema, selectedFields, expandConfigs) {
322
+ async function validateListResponse(response, schema, selectedFields, expandConfigs, includeSpecialColumns) {
260
323
  if (!response || typeof response !== "object") {
261
324
  return {
262
325
  valid: false,
263
326
  error: new ResponseStructureError("an object", response)
264
327
  };
265
328
  }
266
- const { "@context": context, value, ...rest } = response;
329
+ const { "@context": context, value, ..._rest } = response;
267
330
  if (!Array.isArray(value)) {
268
331
  return {
269
332
  valid: false,
270
- error: new ResponseStructureError(
271
- "'value' property to be an array",
272
- value
273
- )
333
+ error: new ResponseStructureError("'value' property to be an array", value)
274
334
  };
275
335
  }
276
336
  const validatedRecords = [];
277
- for (let i = 0; i < value.length; i++) {
278
- const record = value[i];
279
- const validation = await validateRecord(
280
- record,
281
- schema,
282
- selectedFields,
283
- expandConfigs
284
- );
337
+ for (const record of value) {
338
+ const validation = await validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns);
285
339
  if (!validation.valid) {
286
340
  return {
287
341
  valid: false,
@@ -295,15 +349,12 @@ async function validateListResponse(response, schema, selectedFields, expandConf
295
349
  data: validatedRecords
296
350
  };
297
351
  }
298
- async function validateSingleResponse(response, schema, selectedFields, expandConfigs, mode = "maybe") {
352
+ async function validateSingleResponse(response, schema, selectedFields, expandConfigs, mode = "maybe", includeSpecialColumns) {
299
353
  var _a;
300
354
  if (response.value && Array.isArray(response.value) && response.value.length > 1) {
301
355
  return {
302
356
  valid: false,
303
- error: new RecordCountMismatchError(
304
- mode === "exact" ? "one" : "at-most-one",
305
- response.value.length
306
- )
357
+ error: new RecordCountMismatchError(mode === "exact" ? "one" : "at-most-one", response.value.length)
307
358
  };
308
359
  }
309
360
  if (!response || response.value && response.value.length === 0) {
@@ -319,12 +370,7 @@ async function validateSingleResponse(response, schema, selectedFields, expandCo
319
370
  };
320
371
  }
321
372
  const record = ((_a = response.value) == null ? void 0 : _a[0]) ?? response;
322
- const validation = await validateRecord(
323
- record,
324
- schema,
325
- selectedFields,
326
- expandConfigs
327
- );
373
+ const validation = await validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns);
328
374
  if (!validation.valid) {
329
375
  return validation;
330
376
  }
@@ -334,6 +380,7 @@ async function validateSingleResponse(response, schema, selectedFields, expandCo
334
380
  };
335
381
  }
336
382
  export {
383
+ validateAndTransformInput,
337
384
  validateListResponse,
338
385
  validateRecord,
339
386
  validateSingleResponse
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["import type { ODataRecordMetadata } from \"./types\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { TableOccurrence } from \"./client/table-occurrence\";\nimport {\n ValidationError,\n ResponseStructureError,\n RecordCountMismatchError,\n} from \"./errors\";\n\n// Type for expand validation configuration\nexport type ExpandValidationConfig = {\n relation: string;\n targetSchema?: Record<string, StandardSchemaV1>;\n targetOccurrence?: TableOccurrence<any, any, any, any>;\n targetBaseTable?: any; // BaseTable instance for transformation\n occurrence?: TableOccurrence<any, any, any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n};\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\nexport async function validateRecord<T extends Record<string, any>>(\n record: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: T & ODataRecordMetadata }\n | { valid: false; error: ValidationError }\n> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Include metadata fields if present (don't validate they exist)\n const metadata: ODataRecordMetadata = {\n \"@id\": id || \"\",\n \"@editLink\": editLink || \"\",\n };\n\n // If no schema, just return the data with metadata\n if (!schema) {\n return {\n valid: true,\n data: { ...rest, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Filter out FileMaker system fields that shouldn't be in responses by default\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Merge validated data with metadata\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\nexport async function validateListResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata)[] }\n | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ...rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\n \"'value' property to be an array\",\n value,\n ),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (let i = 0; i < value.length; i++) {\n const record = value[i];\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\nexport async function validateSingleResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (\n response.value &&\n Array.isArray(response.value) &&\n response.value.length > 1\n ) {\n return {\n valid: false,\n error: new RecordCountMismatchError(\n mode === \"exact\" ? \"one\" : \"at-most-one\",\n response.value.length,\n ),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["validatedRecord"],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["/** biome-ignore-all lint/style/useCollapsedElseIf: easier to read */\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { RecordCountMismatchError, ResponseStructureError, ValidationError } from \"./errors\";\nimport type { FMTable } from \"./orm/table\";\nimport type { ODataRecordMetadata } from \"./types\";\n\n/**\n * Validates and transforms input data for insert/update operations.\n * Applies input validators (writeValidators) to transform user input to database format.\n * Fields without input validators are passed through unchanged.\n *\n * @param data - The input data to validate and transform\n * @param inputSchema - Optional schema containing input validators for each field\n * @returns Transformed data ready to send to the server\n * @throws ValidationError if any field fails validation\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateAndTransformInput<T extends Record<string, any>>(\n data: Partial<T>,\n inputSchema?: Partial<Record<string, StandardSchemaV1>>,\n): Promise<Partial<T>> {\n // If no input schema, return data as-is\n if (!inputSchema) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformedData: Record<string, any> = { ...data };\n\n // Process each field that has an input validator\n for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n // Only process fields that are present in the input data\n if (fieldName in data) {\n const inputValue = data[fieldName];\n\n try {\n // Run the input validator to transform the value\n let result = fieldSchema[\"~standard\"].validate(inputValue);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // Check for validation errors\n if (result.issues) {\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: inputValue,\n cause: result.issues,\n });\n }\n\n // Store the transformed value\n transformedData[fieldName] = result.value;\n } catch (error) {\n // If it's already a ValidationError, re-throw it\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // Otherwise, wrap the error\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: inputValue,\n cause: error,\n });\n }\n }\n }\n\n // Fields without input validators are already in transformedData (passed through)\n return transformedData as Partial<T>;\n}\n\n// Type for expand validation configuration\nexport interface ExpandValidationConfig {\n relation: string;\n targetSchema?: Partial<Record<string, StandardSchemaV1>>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n targetTable?: FMTable<any, any>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n}\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateRecord<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record validation\n record: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<{ valid: true; data: T & ODataRecordMetadata } | { valid: false; error: ValidationError }> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Only include metadata fields if they actually exist and have values\n const metadata: Partial<ODataRecordMetadata> = {};\n if (id) {\n metadata[\"@id\"] = id;\n }\n if (editLink) {\n metadata[\"@editLink\"] = editLink;\n }\n\n // If no schema, just return the data with metadata\n // Exclude special columns if includeSpecialColumns is false\n if (!schema) {\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n return {\n valid: true,\n data: {\n ...restWithoutSystemFields,\n ...specialColumns,\n ...metadata,\n } as T & ODataRecordMetadata,\n };\n }\n\n // Extract FileMaker special columns - preserve them if includeSpecialColumns is enabled\n // Note: Special columns are excluded when using single() method (per OData spec behavior)\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n // Only include special columns if explicitly enabled (they're excluded for single() by design)\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // Check if it's a special column that was destructured earlier\n if (fieldName === \"ROWID\" || fieldName === \"ROWMODID\") {\n // Use the destructured value since it was removed from rest\n if (fieldName === \"ROWID\" && ROWID !== undefined) {\n validatedRecord[fieldName] = ROWID;\n } else if (fieldName === \"ROWMODID\" && ROWMODID !== undefined) {\n validatedRecord[fieldName] = ROWMODID;\n }\n } else {\n // For other fields not in schema, include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Return only the validated fields plus metadata and special columns\n // Do NOT merge restWithoutSystemFields as that would overwrite validated values\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default (unless includeSpecialColumns is enabled)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateListResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<\n { valid: true; data: (T & ODataRecordMetadata)[] } | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ..._rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\"'value' property to be an array\", value),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (const record of value) {\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateSingleResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n includeSpecialColumns?: boolean,\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (response.value && Array.isArray(response.value) && response.value.length > 1) {\n return {\n valid: false,\n error: new RecordCountMismatchError(mode === \"exact\" ? \"one\" : \"at-most-one\", response.value.length),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["ROWID","ROWMODID","restWithoutSystemFields","specialColumns","validatedRecord"],"mappings":";AAiBsB,eAAA,0BACpB,MACA,aACqB;AAErB,MAAI,CAAC,aAAa;AACT,WAAA;AAAA,EAAA;AAIH,QAAA,kBAAuC,EAAE,GAAG,KAAK;AAGvD,aAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,WAAW,GAAG;AAElE,QAAI,CAAC,aAAa;AAChB;AAAA,IAAA;AAIF,QAAI,aAAa,MAAM;AACf,YAAA,aAAa,KAAK,SAAS;AAE7B,UAAA;AAEF,YAAI,SAAS,YAAY,WAAW,EAAE,SAAS,UAAU;AACzD,YAAI,kBAAkB,SAAS;AAC7B,mBAAS,MAAM;AAAA,QAAA;AAIjB,YAAI,OAAO,QAAQ;AACjB,gBAAM,IAAI,gBAAgB,sCAAsC,SAAS,KAAK,OAAO,QAAQ;AAAA,YAC3F,OAAO;AAAA,YACP,OAAO;AAAA,YACP,OAAO,OAAO;AAAA,UAAA,CACf;AAAA,QAAA;AAIa,wBAAA,SAAS,IAAI,OAAO;AAAA,eAC7B,OAAO;AAEd,YAAI,iBAAiB,iBAAiB;AAC9B,gBAAA;AAAA,QAAA;AAIR,cAAM,IAAI,gBAAgB,sCAAsC,SAAS,KAAK,IAAI;AAAA,UAChF,OAAO;AAAA,UACP,OAAO;AAAA,UACP,OAAO;AAAA,QAAA,CACR;AAAA,MAAA;AAAA,IACH;AAAA,EACF;AAIK,SAAA;AACT;AAmBA,eAAsB,eAEpB,QACA,QACA,gBACA,eACA,uBACoG;;AAEpG,QAAM,EAAE,OAAO,IAAI,aAAa,UAAU,GAAG,SAAS;AAGtD,QAAM,WAAyC,CAAC;AAChD,MAAI,IAAI;AACN,aAAS,KAAK,IAAI;AAAA,EAAA;AAEpB,MAAI,UAAU;AACZ,aAAS,WAAW,IAAI;AAAA,EAAA;AAK1B,MAAI,CAAC,QAAQ;AACX,UAAM,EAAE,OAAAA,QAAO,UAAAC,WAAU,GAAGC,6BAA4B;AACxD,UAAMC,kBAAwD,CAAC;AAC/D,QAAI,uBAAuB;AACzB,UAAIH,WAAU,QAAW;AACvBG,wBAAe,QAAQH;AAAAA,MAAA;AAEzB,UAAIC,cAAa,QAAW;AAC1BE,wBAAe,WAAWF;AAAAA,MAAA;AAAA,IAC5B;AAEK,WAAA;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,GAAGC;AAAAA,QACH,GAAGC;AAAAA,QACH,GAAG;AAAA,MAAA;AAAA,IAEP;AAAA,EAAA;AAKF,QAAM,EAAE,OAAO,UAAU,GAAG,wBAA4B,IAAA;AACxD,QAAM,iBAAwD,CAAC;AAE/D,MAAI,uBAAuB;AACzB,QAAI,UAAU,QAAW;AACvB,qBAAe,QAAQ;AAAA,IAAA;AAEzB,QAAI,aAAa,QAAW;AAC1B,qBAAe,WAAW;AAAA,IAAA;AAAA,EAC5B;AAIE,MAAA,kBAAkB,eAAe,SAAS,GAAG;AAE/C,UAAMC,mBAAuC,CAAC;AAE9C,eAAW,SAAS,gBAAgB;AAC5B,YAAA,YAAY,OAAO,KAAK;AACxB,YAAA,cAAc,OAAO,SAAS;AAEpC,UAAI,aAAa;AACT,cAAA,QAAQ,KAAK,SAAS;AACxB,YAAA;AACF,cAAI,SAAS,YAAY,WAAW,EAAE,SAAS,KAAK;AACpD,cAAI,kBAAkB,SAAS;AAC7B,qBAAS,MAAM;AAAA,UAAA;AAIjB,cAAI,OAAO,QAAQ;AACV,mBAAA;AAAA,cACL,OAAO;AAAA,cACP,OAAO,IAAI,gBAAgB,gCAAgC,SAAS,KAAK,OAAO,QAAQ;AAAA,gBACtF,OAAO;AAAA,gBACP,OAAO;AAAA,gBACP,OAAO,OAAO;AAAA,cACf,CAAA;AAAA,YACH;AAAA,UAAA;AAGFA,2BAAgB,SAAS,IAAI,OAAO;AAAA,iBAC7B,eAAe;AAEf,iBAAA;AAAA,YACL,OAAO;AAAA,YACP,OAAO,IAAI,gBAAgB,gCAAgC,SAAS,KAAK,IAAI;AAAA,cAC3E,OAAO;AAAA,cACP,OAAO;AAAA,cACP,OAAO;AAAA,YACR,CAAA;AAAA,UACH;AAAA,QAAA;AAAA,MACF,OACK;AAGD,YAAA,cAAc,WAAW,cAAc,YAAY;AAEjD,cAAA,cAAc,WAAW,UAAU,QAAW;AAChDA,6BAAgB,SAAS,IAAI;AAAA,UACpB,WAAA,cAAc,cAAc,aAAa,QAAW;AAC7DA,6BAAgB,SAAS,IAAI;AAAA,UAAA;AAAA,QAC/B,OACK;AAELA,2BAAgB,SAAS,IAAI,KAAK,SAAS;AAAA,QAAA;AAAA,MAC7C;AAAA,IACF;AAIE,QAAA,iBAAiB,cAAc,SAAS,GAAG;AAC7C,iBAAW,gBAAgB,eAAe;AAClC,cAAA,cAAc,KAAK,aAAa,QAAQ;AAG9C,YAAI,gBAAgB,QAAW;AAEzB,cAAA,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;AAEtD,kBAAM,eAAc,UAAK,MAAM,CAAC,MAAZ,mBAAe;AACnC,gBAAI,2CAAa,SAAS;AACxB,oBAAM,eAAe,YAAY;AAI3B,oBAAA,oBACJ,aAAa,YAAY,EAAE,SAAS,aAAa,SAAS,YAAY,CAAC,OACvE,kBAAa,mBAAb,mBAA6B,KAAK,CAAC,UAAU,aAAa,cAAc,SAAS,MAAM,YAAY,CAAC;AAEtG,kBAAI,mBAAmB;AACd,uBAAA;AAAA,kBACL,OAAO;AAAA,kBACP,OAAO,IAAI;AAAA,oBACT,4CAA4C,aAAa,QAAQ,MAAM,YAAY;AAAA,oBACnF,CAAC;AAAA,oBACD;AAAA,sBACE,OAAO,aAAa;AAAA,oBAAA;AAAA,kBACtB;AAAA,gBAEJ;AAAA,cAAA;AAAA,YACF;AAAA,UACF;AAAA,QACF,OAIK;AAED,cAAA,MAAM,QAAQ,WAAW,GAAG;AAG9B,kBAAM,yBAAgC,CAAC;AACvC,qBAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AACrC,oBAAA,OAAO,YAAY,CAAC;AAC1B,oBAAM,iBAAiB,MAAM;AAAA,gBAC3B;AAAA,gBACA,aAAa;AAAA,gBACb,aAAa;AAAA,gBACb,aAAa;AAAA,gBACb;AAAA,cACF;AACI,kBAAA,CAAC,eAAe,OAAO;AAClB,uBAAA;AAAA,kBACL,OAAO;AAAA,kBACP,OAAO,IAAI;AAAA,oBACT,4CAA4C,aAAa,QAAQ,cAAc,CAAC,KAAK,eAAe,MAAM,OAAO;AAAA,oBACjH,eAAe,MAAM;AAAA,oBACrB;AAAA,sBACE,OAAO,aAAa;AAAA,sBACpB,OAAO,eAAe,MAAM;AAAA,oBAAA;AAAA,kBAC9B;AAAA,gBAEJ;AAAA,cAAA;AAEqB,qCAAA,KAAK,eAAe,IAAI;AAAA,YAAA;AAEjDA,6BAAgB,aAAa,QAAQ,IAAI;AAAA,UAAA,OACpC;AAEL,kBAAM,iBAAiB,MAAM;AAAA,cAC3B;AAAA,cACA,aAAa;AAAA,cACb,aAAa;AAAA,cACb,aAAa;AAAA,cACb;AAAA,YACF;AACI,gBAAA,CAAC,eAAe,OAAO;AAClB,qBAAA;AAAA,gBACL,OAAO;AAAA,gBACP,OAAO,IAAI;AAAA,kBACT,4CAA4C,aAAa,QAAQ,MAAM,eAAe,MAAM,OAAO;AAAA,kBACnG,eAAe,MAAM;AAAA,kBACrB;AAAA,oBACE,OAAO,aAAa;AAAA,oBACpB,OAAO,eAAe,MAAM;AAAA,kBAAA;AAAA,gBAC9B;AAAA,cAEJ;AAAA,YAAA;AAEFA,6BAAgB,aAAa,QAAQ,IAAI,eAAe;AAAA,UAAA;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAKK,WAAA;AAAA,MACL,OAAO;AAAA,MACP,MAAM,EAAE,GAAGA,kBAAiB,GAAG,gBAAgB,GAAG,SAAS;AAAA,IAC7D;AAAA,EAAA;AAKI,QAAA,kBAAuC,EAAE,GAAG,wBAAwB;AAE1E,aAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AAE7D,QAAI,CAAC,aAAa;AAChB;AAAA,IAAA;AAGI,UAAA,QAAQ,KAAK,SAAS;AACxB,QAAA;AACF,UAAI,SAAS,YAAY,WAAW,EAAE,SAAS,KAAK;AACpD,UAAI,kBAAkB,SAAS;AAC7B,iBAAS,MAAM;AAAA,MAAA;AAIjB,UAAI,OAAO,QAAQ;AACV,eAAA;AAAA,UACL,OAAO;AAAA,UACP,OAAO,IAAI,gBAAgB,gCAAgC,SAAS,KAAK,OAAO,QAAQ;AAAA,YACtF,OAAO;AAAA,YACP,OAAO;AAAA,YACP,OAAO,OAAO;AAAA,UACf,CAAA;AAAA,QACH;AAAA,MAAA;AAGc,sBAAA,SAAS,IAAI,OAAO;AAAA,aAC7B,eAAe;AAGf,aAAA;AAAA,QACL,OAAO;AAAA,QACP,OAAO,IAAI,gBAAgB,gCAAgC,SAAS,KAAK,IAAI;AAAA,UAC3E,OAAO;AAAA,UACP,OAAO;AAAA,UACP,OAAO;AAAA,QACR,CAAA;AAAA,MACH;AAAA,IAAA;AAAA,EACF;AAIE,MAAA,iBAAiB,cAAc,SAAS,GAAG;AAC7C,eAAW,gBAAgB,eAAe;AAClC,YAAA,cAAc,KAAK,aAAa,QAAQ;AAG9C,UAAI,gBAAgB,QAAW;AAEzB,YAAA,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;AAEtD,gBAAM,eAAc,UAAK,MAAM,CAAC,MAAZ,mBAAe;AACnC,cAAI,2CAAa,SAAS;AACxB,kBAAM,eAAe,YAAY;AAI3B,kBAAA,oBACJ,aAAa,YAAY,EAAE,SAAS,aAAa,SAAS,YAAY,CAAC,OACvE,kBAAa,mBAAb,mBAA6B,KAAK,CAAC,UAAU,aAAa,cAAc,SAAS,MAAM,YAAY,CAAC;AAEtG,gBAAI,mBAAmB;AACd,qBAAA;AAAA,gBACL,OAAO;AAAA,gBACP,OAAO,IAAI;AAAA,kBACT,4CAA4C,aAAa,QAAQ,MAAM,YAAY;AAAA,kBACnF,CAAC;AAAA,kBACD;AAAA,oBACE,OAAO,aAAa;AAAA,kBAAA;AAAA,gBACtB;AAAA,cAEJ;AAAA,YAAA;AAAA,UACF;AAAA,QACF;AAAA,MACF,OAIK;AAED,YAAA,MAAM,QAAQ,WAAW,GAAG;AAG9B,gBAAM,yBAAgC,CAAC;AACvC,mBAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AACrC,kBAAA,OAAO,YAAY,CAAC;AAC1B,kBAAM,iBAAiB,MAAM;AAAA,cAC3B;AAAA,cACA,aAAa;AAAA,cACb,aAAa;AAAA,cACb,aAAa;AAAA,cACb;AAAA,YACF;AACI,gBAAA,CAAC,eAAe,OAAO;AAClB,qBAAA;AAAA,gBACL,OAAO;AAAA,gBACP,OAAO,IAAI;AAAA,kBACT,4CAA4C,aAAa,QAAQ,cAAc,CAAC,KAAK,eAAe,MAAM,OAAO;AAAA,kBACjH,eAAe,MAAM;AAAA,kBACrB;AAAA,oBACE,OAAO,aAAa;AAAA,oBACpB,OAAO,eAAe,MAAM;AAAA,kBAAA;AAAA,gBAC9B;AAAA,cAEJ;AAAA,YAAA;AAEqB,mCAAA,KAAK,eAAe,IAAI;AAAA,UAAA;AAEjC,0BAAA,aAAa,QAAQ,IAAI;AAAA,QAAA,OACpC;AAEL,gBAAM,iBAAiB,MAAM;AAAA,YAC3B;AAAA,YACA,aAAa;AAAA,YACb,aAAa;AAAA,YACb,aAAa;AAAA,YACb;AAAA,UACF;AACI,cAAA,CAAC,eAAe,OAAO;AAClB,mBAAA;AAAA,cACL,OAAO;AAAA,cACP,OAAO,IAAI;AAAA,gBACT,4CAA4C,aAAa,QAAQ,MAAM,eAAe,MAAM,OAAO;AAAA,gBACnG,eAAe,MAAM;AAAA,gBACrB;AAAA,kBACE,OAAO,aAAa;AAAA,kBACpB,OAAO,eAAe,MAAM;AAAA,gBAAA;AAAA,cAC9B;AAAA,YAEJ;AAAA,UAAA;AAEc,0BAAA,aAAa,QAAQ,IAAI,eAAe;AAAA,QAAA;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAGK,SAAA;AAAA,IACL,OAAO;AAAA,IACP,MAAM,EAAE,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,SAAS;AAAA,EAC7D;AACF;AAMA,eAAsB,qBAEpB,UACA,QACA,gBACA,eACA,uBAGA;AAEA,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AACtC,WAAA;AAAA,MACL,OAAO;AAAA,MACP,OAAO,IAAI,uBAAuB,aAAa,QAAQ;AAAA,IACzD;AAAA,EAAA;AAIF,QAAM,EAAE,YAAY,SAAS,OAAO,GAAG,MAAU,IAAA;AAEjD,MAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AAClB,WAAA;AAAA,MACL,OAAO;AAAA,MACP,OAAO,IAAI,uBAAuB,mCAAmC,KAAK;AAAA,IAC5E;AAAA,EAAA;AAIF,QAAM,mBAAgD,CAAC;AAEvD,aAAW,UAAU,OAAO;AAC1B,UAAM,aAAa,MAAM,eAAkB,QAAQ,QAAQ,gBAAgB,eAAe,qBAAqB;AAE3G,QAAA,CAAC,WAAW,OAAO;AACd,aAAA;AAAA,QACL,OAAO;AAAA,QACP,OAAO,WAAW;AAAA,MACpB;AAAA,IAAA;AAGe,qBAAA,KAAK,WAAW,IAAI;AAAA,EAAA;AAGhC,SAAA;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAMA,eAAsB,uBAEpB,UACA,QACA,gBACA,eACA,OAA0B,SAC1B,uBAIA;;AAEI,MAAA,SAAS,SAAS,MAAM,QAAQ,SAAS,KAAK,KAAK,SAAS,MAAM,SAAS,GAAG;AACzE,WAAA;AAAA,MACL,OAAO;AAAA,MACP,OAAO,IAAI,yBAAyB,SAAS,UAAU,QAAQ,eAAe,SAAS,MAAM,MAAM;AAAA,IACrG;AAAA,EAAA;AAIF,MAAI,CAAC,YAAa,SAAS,SAAS,SAAS,MAAM,WAAW,GAAI;AAChE,QAAI,SAAS,SAAS;AACb,aAAA;AAAA,QACL,OAAO;AAAA,QACP,OAAO,IAAI,yBAAyB,OAAO,CAAC;AAAA,MAC9C;AAAA,IAAA;AAGK,WAAA;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EAAA;AAIF,QAAM,WAAS,cAAS,UAAT,mBAAiB,OAAM;AACtC,QAAM,aAAa,MAAM,eAAkB,QAAQ,QAAQ,gBAAgB,eAAe,qBAAqB;AAE3G,MAAA,CAAC,WAAW,OAAO;AACd,WAAA;AAAA,EAAA;AAGF,SAAA;AAAA,IACL,OAAO;AAAA,IACP,MAAM,WAAW;AAAA,EACnB;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofkit/fmodata",
3
- "version": "0.1.0-alpha.9",
3
+ "version": "0.1.0-beta.23",
4
4
  "description": "FileMaker OData API client",
5
5
  "repository": "git@github.com:proofgeist/proofkit.git",
6
6
  "author": "Eric <37158449+eluce2@users.noreply.github.com>",
@@ -18,23 +18,10 @@
18
18
  },
19
19
  "./package.json": "./package.json"
20
20
  },
21
- "scripts": {
22
- "build": "tsc && vite build && publint --strict",
23
- "build:watch": "tsc && vite build --watch",
24
- "check-format": "prettier --check .",
25
- "format": "prettier --write .",
26
- "dev": "tsc --watch",
27
- "test": "vitest run --typecheck",
28
- "test:typecheck": "vitest run --typecheck",
29
- "test:watch": "vitest --typecheck",
30
- "test:e2e": "op inject -i op.env -o .env.local -f && vitest run tests/e2e.test.ts",
31
- "capture": "op inject -i op.env -o .env.local -f && tsx scripts/capture-responses.ts",
32
- "knip": "knip",
33
- "pub:alpha": "bun run scripts/publish-alpha.ts"
34
- },
35
21
  "dependencies": {
36
- "@fetchkit/ffetch": "^4.1.0",
22
+ "@fetchkit/ffetch": "^4.2.0",
37
23
  "dotenv": "^16.5.0",
24
+ "es-toolkit": "^1.38.0",
38
25
  "neverthrow": "^8.2.0",
39
26
  "odata-query": "^8.0.4"
40
27
  },
@@ -50,14 +37,14 @@
50
37
  "@standard-schema/spec": "^1.0.0",
51
38
  "@tanstack/vite-config": "^0.2.0",
52
39
  "@types/node": "^22.17.1",
53
- "prettier": "^3.5.3",
40
+ "fast-xml-parser": "^5.3.2",
54
41
  "publint": "^0.3.12",
55
42
  "tsx": "^4.19.2",
56
43
  "typescript": "^5.9.3",
57
44
  "vite": "^6.3.4",
58
45
  "vite-plugin-dts": "^4.5.4",
59
46
  "vitest": "^4.0.7",
60
- "zod": "4.1.12"
47
+ "zod": "^4.1.13"
61
48
  },
62
49
  "engines": {
63
50
  "node": ">=18.0.0"
@@ -73,5 +60,26 @@
73
60
  "odata",
74
61
  "proofgeist",
75
62
  "proofkit"
76
- ]
77
- }
63
+ ],
64
+ "scripts": {
65
+ "build": "tsc && vite build && publint --strict",
66
+ "build:watch": "tsc && vite build --watch",
67
+ "check-format": "biome format --check .",
68
+ "format": "biome format --write .",
69
+ "lint": "biome check . --write",
70
+ "lint:summary": "biome check . --reporter=summary",
71
+ "lint:fix": "pnpm lint --fix",
72
+ "dev": "tsc --watch",
73
+ "test": "vitest run --typecheck",
74
+ "tsc": "tsc --noEmit",
75
+ "test:typecheck": "vitest run --typecheck",
76
+ "test:watch": "vitest --typecheck",
77
+ "test:build": "pnpm build && TEST_BUILD=true vitest run --typecheck",
78
+ "test:watch:build": "TEST_BUILD=true vitest --typecheck",
79
+ "test:e2e": "op inject -i op.env -o .env.local -f && vitest run tests/e2e.test.ts",
80
+ "capture": "op inject -i op.env -o .env.local -f && tsx scripts/capture-responses.ts",
81
+ "knip": "knip",
82
+ "pub:alpha": "bun run scripts/publish-alpha.ts",
83
+ "global:link": "pnpm link --global"
84
+ }
85
+ }