@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.
- package/LICENSE.md +21 -0
- package/README.md +651 -449
- package/dist/esm/client/batch-builder.d.ts +10 -9
- package/dist/esm/client/batch-builder.js +119 -56
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js +16 -21
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +10 -0
- package/dist/esm/client/builders/default-select.js +41 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +45 -0
- package/dist/esm/client/builders/expand-builder.js +185 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +9 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
- package/dist/esm/client/builders/query-string-builder.js +21 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +43 -0
- package/dist/esm/client/builders/response-processor.js +175 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +25 -0
- package/dist/esm/client/builders/select-mixin.js +28 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +18 -0
- package/dist/esm/client/builders/select-utils.js +30 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +40 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +44 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +34 -22
- package/dist/esm/client/database.js +48 -84
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +25 -30
- package/dist/esm/client/delete-builder.js +45 -30
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +35 -43
- package/dist/esm/client/entity-set.js +110 -52
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +25 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +26 -7
- package/dist/esm/client/filemaker-odata.js +65 -42
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +19 -24
- package/dist/esm/client/insert-builder.js +94 -58
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +4 -0
- package/dist/esm/client/query/query-builder.d.ts +132 -0
- package/dist/esm/client/query/query-builder.js +456 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +25 -0
- package/dist/esm/client/query/types.d.ts +77 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +100 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +2 -115
- package/dist/esm/client/record-builder.d.ts +108 -36
- package/dist/esm/client/record-builder.js +284 -119
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +4 -9
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/schema-manager.d.ts +5 -5
- package/dist/esm/client/schema-manager.js +45 -31
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -40
- package/dist/esm/client/update-builder.js +99 -58
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +189 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/errors.d.ts +19 -2
- package/dist/esm/errors.js +39 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -8
- package/dist/esm/index.js +40 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +69 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/column.d.ts +62 -0
- package/dist/esm/orm/column.js +63 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +164 -0
- package/dist/esm/orm/field-builders.js +158 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +5 -0
- package/dist/esm/orm/operators.d.ts +173 -0
- package/dist/esm/orm/operators.js +260 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +355 -0
- package/dist/esm/orm/table.js +202 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +44 -45
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +96 -30
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +22 -12
- package/dist/esm/validation.js +132 -85
- package/dist/esm/validation.js.map +1 -1
- package/package.json +28 -20
- package/src/client/batch-builder.ts +153 -89
- package/src/client/batch-request.ts +25 -41
- package/src/client/builders/default-select.ts +75 -0
- package/src/client/builders/expand-builder.ts +246 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +46 -0
- package/src/client/builders/response-processor.ts +279 -0
- package/src/client/builders/select-mixin.ts +65 -0
- package/src/client/builders/select-utils.ts +59 -0
- package/src/client/builders/shared-types.ts +45 -0
- package/src/client/builders/table-utils.ts +83 -0
- package/src/client/database.ts +89 -183
- package/src/client/delete-builder.ts +74 -84
- package/src/client/entity-set.ts +266 -293
- package/src/client/error-parser.ts +41 -0
- package/src/client/filemaker-odata.ts +98 -66
- package/src/client/insert-builder.ts +157 -118
- package/src/client/query/expand-builder.ts +160 -0
- package/src/client/query/index.ts +14 -0
- package/src/client/query/query-builder.ts +729 -0
- package/src/client/query/response-processor.ts +226 -0
- package/src/client/query/types.ts +126 -0
- package/src/client/query/url-builder.ts +151 -0
- package/src/client/query-builder.ts +10 -1455
- package/src/client/record-builder.ts +575 -240
- package/src/client/response-processor.ts +15 -42
- package/src/client/sanitize-json.ts +64 -0
- package/src/client/schema-manager.ts +61 -76
- package/src/client/update-builder.ts +161 -143
- package/src/client/webhook-builder.ts +265 -0
- package/src/errors.ts +49 -16
- package/src/index.ts +99 -54
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +116 -0
- package/src/orm/column.ts +106 -0
- package/src/orm/field-builders.ts +250 -0
- package/src/orm/index.ts +61 -0
- package/src/orm/operators.ts +473 -0
- package/src/orm/table.ts +741 -0
- package/src/transform.ts +90 -70
- package/src/types.ts +154 -113
- package/src/validation.ts +200 -115
- package/dist/esm/client/base-table.d.ts +0 -125
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -896
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -72
- package/dist/esm/client/table-occurrence.js +0 -74
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/client/base-table.ts +0 -175
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -175
- package/src/filter-types.ts +0 -97
package/dist/esm/validation.js
CHANGED
|
@@ -1,18 +1,82 @@
|
|
|
1
|
-
import { RecordCountMismatchError, ResponseStructureError
|
|
2
|
-
async function
|
|
3
|
-
|
|
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
|
-
|
|
7
|
-
"@
|
|
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: {
|
|
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)
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 = (
|
|
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
|
|
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, ...
|
|
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 (
|
|
278
|
-
const
|
|
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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proofkit/fmodata",
|
|
3
|
-
"version": "0.1.0-
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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
|
+
}
|