jsonbadger 0.5.0
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 +21 -0
- package/README.md +114 -0
- package/docs/api.md +152 -0
- package/docs/examples.md +612 -0
- package/docs/local-integration-testing.md +17 -0
- package/docs/query-translation.md +98 -0
- package/index.js +2 -0
- package/package.json +58 -0
- package/src/connection/connect.js +56 -0
- package/src/connection/disconnect.js +16 -0
- package/src/connection/pool-store.js +46 -0
- package/src/connection/server-capabilities.js +59 -0
- package/src/constants/defaults.js +20 -0
- package/src/constants/id-strategies.js +29 -0
- package/src/debug/debug-logger.js +15 -0
- package/src/errors/query-error.js +23 -0
- package/src/errors/validation-error.js +23 -0
- package/src/field-types/base-field-type.js +140 -0
- package/src/field-types/builtins/advanced.js +365 -0
- package/src/field-types/builtins/index.js +585 -0
- package/src/field-types/registry.js +122 -0
- package/src/index.js +36 -0
- package/src/migration/ensure-index.js +155 -0
- package/src/migration/ensure-schema.js +16 -0
- package/src/migration/ensure-table.js +31 -0
- package/src/migration/schema-indexes-resolver.js +6 -0
- package/src/model/document-instance.js +540 -0
- package/src/model/model-factory.js +555 -0
- package/src/query/limit-skip-compiler.js +31 -0
- package/src/query/operators/all.js +10 -0
- package/src/query/operators/contains.js +7 -0
- package/src/query/operators/elem-match.js +3 -0
- package/src/query/operators/eq.js +6 -0
- package/src/query/operators/gt.js +16 -0
- package/src/query/operators/gte.js +16 -0
- package/src/query/operators/has-all-keys.js +11 -0
- package/src/query/operators/has-any-keys.js +11 -0
- package/src/query/operators/has-key.js +6 -0
- package/src/query/operators/in.js +12 -0
- package/src/query/operators/index.js +60 -0
- package/src/query/operators/jsonpath-exists.js +15 -0
- package/src/query/operators/jsonpath-match.js +15 -0
- package/src/query/operators/lt.js +16 -0
- package/src/query/operators/lte.js +16 -0
- package/src/query/operators/ne.js +6 -0
- package/src/query/operators/nin.js +12 -0
- package/src/query/operators/regex.js +8 -0
- package/src/query/operators/size.js +16 -0
- package/src/query/path-parser.js +43 -0
- package/src/query/query-builder.js +93 -0
- package/src/query/sort-compiler.js +30 -0
- package/src/query/where-compiler.js +477 -0
- package/src/schema/field-definition-parser.js +218 -0
- package/src/schema/path-introspection.js +82 -0
- package/src/schema/schema-compiler.js +212 -0
- package/src/schema/schema.js +234 -0
- package/src/sql/parameter-binder.js +13 -0
- package/src/sql/sql-runner.js +31 -0
- package/src/utils/array.js +31 -0
- package/src/utils/assert.js +27 -0
- package/src/utils/json-safe.js +9 -0
- package/src/utils/json.js +21 -0
- package/src/utils/object-path.js +33 -0
- package/src/utils/object.js +168 -0
- package/src/utils/value.js +30 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Assumptions and trade-offs:
|
|
3
|
+
- Foundational FieldTypes focus on deterministic cast/validate behavior for save-path use.
|
|
4
|
+
- Advanced type families and deep collection sub-schema behavior are deferred to later phases.
|
|
5
|
+
*/
|
|
6
|
+
import BaseFieldType from '#src/field-types/base-field-type.js';
|
|
7
|
+
import {
|
|
8
|
+
decimal128_type_reference,
|
|
9
|
+
double_type_reference,
|
|
10
|
+
BigIntFieldType,
|
|
11
|
+
Decimal128FieldType,
|
|
12
|
+
DoubleFieldType,
|
|
13
|
+
int32_type_reference,
|
|
14
|
+
Int32FieldType,
|
|
15
|
+
union_type_reference,
|
|
16
|
+
UnionFieldType
|
|
17
|
+
} from './advanced.js';
|
|
18
|
+
|
|
19
|
+
const uuidv7_pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
20
|
+
const boolean_convert_to_true = new Set([true, 'true', 1, '1', 'yes']);
|
|
21
|
+
const boolean_convert_to_false = new Set([false, 'false', 0, '0', 'no']);
|
|
22
|
+
|
|
23
|
+
function uuidv7_type_reference() {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function StringFieldType(path_value, options) {
|
|
28
|
+
BaseFieldType.call(this, path_value, options);
|
|
29
|
+
this.instance = 'String';
|
|
30
|
+
|
|
31
|
+
if(options && options.match instanceof RegExp) {
|
|
32
|
+
this.regExp = options.match;
|
|
33
|
+
this.validators.push({
|
|
34
|
+
kind: 'match'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if(options && Array.isArray(options.enum)) {
|
|
39
|
+
this.enum_values = options.enum.slice();
|
|
40
|
+
this.validators.push({
|
|
41
|
+
kind: 'enum'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if(options && options.minLength !== undefined) {
|
|
46
|
+
this.validators.push({
|
|
47
|
+
kind: 'minLength'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if(options && options.maxLength !== undefined) {
|
|
52
|
+
this.validators.push({
|
|
53
|
+
kind: 'maxLength'
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
StringFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
59
|
+
StringFieldType.prototype.constructor = StringFieldType;
|
|
60
|
+
|
|
61
|
+
StringFieldType.prototype.cast = function (value) {
|
|
62
|
+
if(value === undefined || value === null) {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if(Array.isArray(value)) {
|
|
67
|
+
throw this.create_field_error('cast_error', 'Cast to String failed for path "' + this.path + '"', value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let casted_value = value;
|
|
71
|
+
|
|
72
|
+
if(typeof casted_value !== 'string') {
|
|
73
|
+
if(casted_value && typeof casted_value.toString === 'function' && casted_value.toString !== Object.prototype.toString) {
|
|
74
|
+
casted_value = casted_value.toString();
|
|
75
|
+
} else {
|
|
76
|
+
throw this.create_field_error('cast_error', 'Cast to String failed for path "' + this.path + '"', value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if(this.options.trim === true) {
|
|
81
|
+
casted_value = casted_value.trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if(this.options.lowercase === true) {
|
|
85
|
+
casted_value = casted_value.toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if(this.options.uppercase === true) {
|
|
89
|
+
casted_value = casted_value.toUpperCase();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return casted_value;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
StringFieldType.prototype.run_type_validators = function (value) {
|
|
96
|
+
if(this.regExp && !this.regExp.test(value)) {
|
|
97
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" does not match pattern', value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if(this.enum_values && this.enum_values.indexOf(value) === -1) {
|
|
101
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" must be one of enum values', value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if(this.options.minLength !== undefined && value.length < Number(this.options.minLength)) {
|
|
105
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is shorter than minLength', value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if(this.options.maxLength !== undefined && value.length > Number(this.options.maxLength)) {
|
|
109
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is longer than maxLength', value);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function NumberFieldType(path_value, options) {
|
|
114
|
+
BaseFieldType.call(this, path_value, options);
|
|
115
|
+
this.instance = 'Number';
|
|
116
|
+
|
|
117
|
+
if(options && Array.isArray(options.enum)) {
|
|
118
|
+
this.enum_values = options.enum.slice();
|
|
119
|
+
this.validators.push({
|
|
120
|
+
kind: 'enum'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if(options && options.min !== undefined) {
|
|
125
|
+
this.validators.push({
|
|
126
|
+
kind: 'min'
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if(options && options.max !== undefined) {
|
|
131
|
+
this.validators.push({
|
|
132
|
+
kind: 'max'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
NumberFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
138
|
+
NumberFieldType.prototype.constructor = NumberFieldType;
|
|
139
|
+
|
|
140
|
+
NumberFieldType.prototype.cast = function (value) {
|
|
141
|
+
if(value === undefined || value === null) {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if(Array.isArray(value)) {
|
|
146
|
+
throw this.create_field_error('cast_error', 'Cast to Number failed for path "' + this.path + '"', value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if(typeof value === 'number') {
|
|
150
|
+
if(!Number.isFinite(value)) {
|
|
151
|
+
throw this.create_field_error('cast_error', 'Cast to Number failed for path "' + this.path + '"', value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if(value === true) {
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if(value === false) {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if(typeof value === 'string') {
|
|
166
|
+
if(value.trim() === '') {
|
|
167
|
+
throw this.create_field_error('cast_error', 'Cast to Number failed for path "' + this.path + '"', value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const string_number = Number(value);
|
|
171
|
+
|
|
172
|
+
if(!Number.isFinite(string_number)) {
|
|
173
|
+
throw this.create_field_error('cast_error', 'Cast to Number failed for path "' + this.path + '"', value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return string_number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if(value && typeof value.valueOf === 'function') {
|
|
180
|
+
const value_of_result = value.valueOf();
|
|
181
|
+
|
|
182
|
+
if(typeof value_of_result === 'number' && Number.isFinite(value_of_result)) {
|
|
183
|
+
return value_of_result;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw this.create_field_error('cast_error', 'Cast to Number failed for path "' + this.path + '"', value);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
NumberFieldType.prototype.run_type_validators = function (value) {
|
|
191
|
+
if(this.enum_values && this.enum_values.indexOf(value) === -1) {
|
|
192
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" must be one of enum values', value);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if(this.options.min !== undefined && value < Number(this.options.min)) {
|
|
196
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is lower than min', value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if(this.options.max !== undefined && value > Number(this.options.max)) {
|
|
200
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is greater than max', value);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
function DateFieldType(path_value, options) {
|
|
205
|
+
BaseFieldType.call(this, path_value, options);
|
|
206
|
+
this.instance = 'Date';
|
|
207
|
+
|
|
208
|
+
if(options && options.min !== undefined) {
|
|
209
|
+
this.validators.push({
|
|
210
|
+
kind: 'min'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if(options && options.max !== undefined) {
|
|
215
|
+
this.validators.push({
|
|
216
|
+
kind: 'max'
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
DateFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
222
|
+
DateFieldType.prototype.constructor = DateFieldType;
|
|
223
|
+
|
|
224
|
+
DateFieldType.prototype.cast = function (value) {
|
|
225
|
+
if(value === undefined || value === null) {
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if(value instanceof Date) {
|
|
230
|
+
if(Number.isNaN(value.getTime())) {
|
|
231
|
+
throw this.create_field_error('cast_error', 'Cast to Date failed for path "' + this.path + '"', value);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const casted_date = new Date(value);
|
|
238
|
+
|
|
239
|
+
if(Number.isNaN(casted_date.getTime())) {
|
|
240
|
+
throw this.create_field_error('cast_error', 'Cast to Date failed for path "' + this.path + '"', value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return casted_date;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
DateFieldType.prototype.run_type_validators = function (value) {
|
|
247
|
+
if(!(value instanceof Date)) {
|
|
248
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" must be a Date', value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if(this.options.min !== undefined) {
|
|
252
|
+
const min_date = this.cast(this.options.min);
|
|
253
|
+
|
|
254
|
+
if(value.getTime() < min_date.getTime()) {
|
|
255
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is lower than min date', value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if(this.options.max !== undefined) {
|
|
260
|
+
const max_date = this.cast(this.options.max);
|
|
261
|
+
|
|
262
|
+
if(value.getTime() > max_date.getTime()) {
|
|
263
|
+
throw this.create_field_error('validator_error', 'Path "' + this.path + '" is greater than max date', value);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function BooleanFieldType(path_value, options) {
|
|
269
|
+
BaseFieldType.call(this, path_value, options);
|
|
270
|
+
this.instance = 'Boolean';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
BooleanFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
274
|
+
BooleanFieldType.prototype.constructor = BooleanFieldType;
|
|
275
|
+
|
|
276
|
+
BooleanFieldType.prototype.cast = function (value) {
|
|
277
|
+
if(value === undefined || value === null) {
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if(boolean_convert_to_true.has(value)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if(boolean_convert_to_false.has(value)) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw this.create_field_error('cast_error', 'Cast to Boolean failed for path "' + this.path + '"', value);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
function UUIDv7FieldType(path_value, options) {
|
|
293
|
+
BaseFieldType.call(this, path_value, options);
|
|
294
|
+
this.instance = 'UUIDv7';
|
|
295
|
+
this.validators.push({
|
|
296
|
+
kind: 'uuidv7'
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
UUIDv7FieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
301
|
+
UUIDv7FieldType.prototype.constructor = UUIDv7FieldType;
|
|
302
|
+
|
|
303
|
+
UUIDv7FieldType.prototype.cast = function (value) {
|
|
304
|
+
if(value === undefined || value === null) {
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if(typeof value !== 'string') {
|
|
309
|
+
throw this.create_field_error('cast_error', 'Cast to UUIDv7 failed for path "' + this.path + '"', value);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const normalized_value = value.toLowerCase();
|
|
313
|
+
|
|
314
|
+
if(!uuidv7_pattern.test(normalized_value)) {
|
|
315
|
+
throw this.create_field_error('cast_error', 'Cast to UUIDv7 failed for path "' + this.path + '"', value);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return normalized_value;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
function BufferFieldType(path_value, options) {
|
|
322
|
+
BaseFieldType.call(this, path_value, options);
|
|
323
|
+
this.instance = 'Buffer';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
BufferFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
327
|
+
BufferFieldType.prototype.constructor = BufferFieldType;
|
|
328
|
+
|
|
329
|
+
BufferFieldType.prototype.cast = function (value) {
|
|
330
|
+
if(value === undefined || value === null) {
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if(Buffer.isBuffer(value)) {
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if(typeof value === 'string') {
|
|
339
|
+
return Buffer.from(value);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if(typeof value === 'number' && Number.isFinite(value)) {
|
|
343
|
+
return Buffer.from([value & 255]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if(Array.isArray(value)) {
|
|
347
|
+
try {
|
|
348
|
+
return Buffer.from(value);
|
|
349
|
+
} catch(error) {
|
|
350
|
+
throw this.create_field_error('cast_error', 'Cast to Buffer failed for path "' + this.path + '"', value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if(value && value.type === 'Buffer' && Array.isArray(value.data)) {
|
|
355
|
+
try {
|
|
356
|
+
return Buffer.from(value.data);
|
|
357
|
+
} catch(error) {
|
|
358
|
+
throw this.create_field_error('cast_error', 'Cast to Buffer failed for path "' + this.path + '"', value);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw this.create_field_error('cast_error', 'Cast to Buffer failed for path "' + this.path + '"', value);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
function MixedFieldType(path_value, options) {
|
|
366
|
+
BaseFieldType.call(this, path_value, options);
|
|
367
|
+
this.instance = 'Mixed';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
MixedFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
371
|
+
MixedFieldType.prototype.constructor = MixedFieldType;
|
|
372
|
+
|
|
373
|
+
MixedFieldType.prototype.cast = function (value) {
|
|
374
|
+
return value;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
function ArrayFieldType(path_value, options) {
|
|
378
|
+
BaseFieldType.call(this, path_value, options);
|
|
379
|
+
this.instance = 'Array';
|
|
380
|
+
this.of_field_type = options ? options.of_field_type || null : null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
ArrayFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
384
|
+
ArrayFieldType.prototype.constructor = ArrayFieldType;
|
|
385
|
+
|
|
386
|
+
ArrayFieldType.prototype.resolve_default = function (context_value) {
|
|
387
|
+
if(this.options.default !== undefined) {
|
|
388
|
+
return BaseFieldType.prototype.resolve_default.call(this, context_value);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return [];
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
ArrayFieldType.prototype.cast = function (value, context_value) {
|
|
395
|
+
if(value === undefined || value === null) {
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if(!Array.isArray(value)) {
|
|
400
|
+
throw this.create_field_error('cast_error', 'Cast to Array failed for path "' + this.path + '"', value);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if(!this.of_field_type) {
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const casted_array = [];
|
|
408
|
+
let value_index = 0;
|
|
409
|
+
|
|
410
|
+
while(value_index < value.length) {
|
|
411
|
+
const array_value = value[value_index];
|
|
412
|
+
const casted_item = this.of_field_type.normalize(array_value, {
|
|
413
|
+
path: this.path + '.' + value_index,
|
|
414
|
+
parent_path: this.path,
|
|
415
|
+
parent_instance: this.instance,
|
|
416
|
+
parent_context: context_value || {}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
casted_array.push(casted_item);
|
|
420
|
+
value_index += 1;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return casted_array;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
function MapFieldType(path_value, options) {
|
|
427
|
+
BaseFieldType.call(this, path_value, options);
|
|
428
|
+
this.instance = 'Map';
|
|
429
|
+
this.of_field_type = options ? options.of_field_type || null : null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
MapFieldType.prototype = Object.create(BaseFieldType.prototype);
|
|
433
|
+
MapFieldType.prototype.constructor = MapFieldType;
|
|
434
|
+
|
|
435
|
+
MapFieldType.prototype.cast = function (value, context_value) {
|
|
436
|
+
if(value === undefined || value === null) {
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const map_output = {};
|
|
441
|
+
let source_entries = null;
|
|
442
|
+
|
|
443
|
+
if(value instanceof Map) {
|
|
444
|
+
source_entries = Array.from(value.entries());
|
|
445
|
+
} else if(value && typeof value === 'object' && !Array.isArray(value) && !Buffer.isBuffer(value) && !(value instanceof Date)) {
|
|
446
|
+
source_entries = Object.entries(value);
|
|
447
|
+
} else {
|
|
448
|
+
throw this.create_field_error('cast_error', 'Cast to Map failed for path "' + this.path + '"', value);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let entry_index = 0;
|
|
452
|
+
|
|
453
|
+
while(entry_index < source_entries.length) {
|
|
454
|
+
const source_entry = source_entries[entry_index];
|
|
455
|
+
const map_key = source_entry[0];
|
|
456
|
+
const map_value = source_entry[1];
|
|
457
|
+
|
|
458
|
+
if(typeof map_key !== 'string') {
|
|
459
|
+
throw this.create_field_error('cast_error', 'Map key must be a string for path "' + this.path + '"', map_key);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if(this.of_field_type) {
|
|
463
|
+
map_output[map_key] = this.of_field_type.normalize(map_value, {
|
|
464
|
+
path: this.path + '.' + map_key,
|
|
465
|
+
parent_path: this.path,
|
|
466
|
+
parent_instance: this.instance,
|
|
467
|
+
parent_context: context_value || {}
|
|
468
|
+
});
|
|
469
|
+
} else {
|
|
470
|
+
map_output[map_key] = map_value;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
entry_index += 1;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return map_output;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
function get_foundational_field_types() {
|
|
480
|
+
return {
|
|
481
|
+
String: {
|
|
482
|
+
constructor: StringFieldType,
|
|
483
|
+
references: [String, 'String']
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
Number: {
|
|
487
|
+
constructor: NumberFieldType,
|
|
488
|
+
references: [Number, 'Number']
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
Date: {
|
|
492
|
+
constructor: DateFieldType,
|
|
493
|
+
references: [Date, 'Date']
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
Boolean: {
|
|
497
|
+
constructor: BooleanFieldType,
|
|
498
|
+
references: [Boolean, 'Boolean']
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
UUIDv7: {
|
|
502
|
+
constructor: UUIDv7FieldType,
|
|
503
|
+
references: [uuidv7_type_reference, 'UUIDv7']
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
Buffer: {
|
|
507
|
+
constructor: BufferFieldType,
|
|
508
|
+
references: [Buffer, 'Buffer']
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
Mixed: {
|
|
512
|
+
constructor: MixedFieldType,
|
|
513
|
+
references: ['Mixed', Object]
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
Array: {
|
|
517
|
+
constructor: ArrayFieldType,
|
|
518
|
+
references: [Array, 'Array']
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
Map: {
|
|
522
|
+
constructor: MapFieldType,
|
|
523
|
+
references: [Map, 'Map']
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
// Advanced FieldTypes
|
|
527
|
+
Decimal128: {
|
|
528
|
+
constructor: Decimal128FieldType,
|
|
529
|
+
references: [decimal128_type_reference, 'Decimal128']
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
BigInt: {
|
|
533
|
+
constructor: BigIntFieldType,
|
|
534
|
+
references: [BigInt, 'BigInt']
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
Double: {
|
|
538
|
+
constructor: DoubleFieldType,
|
|
539
|
+
references: [double_type_reference, 'Double']
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
Int32: {
|
|
543
|
+
constructor: Int32FieldType,
|
|
544
|
+
references: [int32_type_reference, 'Int32']
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
Union: {
|
|
548
|
+
constructor: UnionFieldType,
|
|
549
|
+
references: [union_type_reference, 'Union']
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export {
|
|
555
|
+
// Type references
|
|
556
|
+
decimal128_type_reference,
|
|
557
|
+
double_type_reference,
|
|
558
|
+
int32_type_reference,
|
|
559
|
+
union_type_reference,
|
|
560
|
+
uuidv7_type_reference,
|
|
561
|
+
|
|
562
|
+
// FieldType constructors
|
|
563
|
+
ArrayFieldType,
|
|
564
|
+
BigIntFieldType,
|
|
565
|
+
BooleanFieldType,
|
|
566
|
+
BufferFieldType,
|
|
567
|
+
DateFieldType,
|
|
568
|
+
Decimal128FieldType,
|
|
569
|
+
DoubleFieldType,
|
|
570
|
+
Int32FieldType,
|
|
571
|
+
MapFieldType,
|
|
572
|
+
MixedFieldType,
|
|
573
|
+
NumberFieldType,
|
|
574
|
+
StringFieldType,
|
|
575
|
+
UnionFieldType,
|
|
576
|
+
UUIDv7FieldType,
|
|
577
|
+
|
|
578
|
+
// Registry entries
|
|
579
|
+
get_foundational_field_types,
|
|
580
|
+
|
|
581
|
+
// Constants
|
|
582
|
+
boolean_convert_to_false,
|
|
583
|
+
boolean_convert_to_true,
|
|
584
|
+
uuidv7_pattern
|
|
585
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Assumptions and trade-offs:
|
|
3
|
+
- A process-level default registry is used for built-ins and extension registration.
|
|
4
|
+
- Type resolution is explicit; unsupported references throw immediately.
|
|
5
|
+
*/
|
|
6
|
+
import {get_foundational_field_types} from '#src/field-types/builtins/index.js';
|
|
7
|
+
|
|
8
|
+
function FieldTypeRegistry() {
|
|
9
|
+
this.type_constructors_by_name = Object.create(null);
|
|
10
|
+
this.type_name_by_reference = new Map();
|
|
11
|
+
this.register_foundational_types();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
FieldTypeRegistry.prototype.register_foundational_types = function () {
|
|
15
|
+
const foundational_type_entries = get_foundational_type_entries();
|
|
16
|
+
let entry_index = 0;
|
|
17
|
+
|
|
18
|
+
while(entry_index < foundational_type_entries.length) {
|
|
19
|
+
const type_entry = foundational_type_entries[entry_index];
|
|
20
|
+
const type_name = type_entry[0];
|
|
21
|
+
const type_definition = type_entry[1];
|
|
22
|
+
|
|
23
|
+
this.register_field_type(type_name, type_definition.constructor, type_definition.references);
|
|
24
|
+
entry_index += 1;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
FieldTypeRegistry.prototype.register_field_type = function (type_name, type_constructor, references) {
|
|
29
|
+
const resolved_name = resolve_type_name_value(type_name);
|
|
30
|
+
const resolved_references = resolve_reference_list(references);
|
|
31
|
+
|
|
32
|
+
this.type_constructors_by_name[resolved_name] = type_constructor;
|
|
33
|
+
this.type_name_by_reference.set(resolved_name, resolved_name);
|
|
34
|
+
register_reference_aliases(this.type_name_by_reference, resolved_name, resolved_references);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
FieldTypeRegistry.prototype.resolve_field_type_name = function (type_reference) {
|
|
38
|
+
return resolve_field_type_name_value(
|
|
39
|
+
this.type_name_by_reference,
|
|
40
|
+
this.type_constructors_by_name,
|
|
41
|
+
type_reference
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
FieldTypeRegistry.prototype.has_field_type = function (type_reference) {
|
|
46
|
+
const type_name = this.resolve_field_type_name(type_reference);
|
|
47
|
+
return type_name !== null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
FieldTypeRegistry.prototype.create_field_type = function (path_value, type_reference, options) {
|
|
51
|
+
const type_name = this.resolve_field_type_name(type_reference);
|
|
52
|
+
assert_supported_type_name(path_value, type_name);
|
|
53
|
+
|
|
54
|
+
const type_constructor = this.type_constructors_by_name[type_name];
|
|
55
|
+
const field_options = create_field_options(options);
|
|
56
|
+
|
|
57
|
+
return new type_constructor(path_value, field_options, this);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function get_foundational_type_entries() {
|
|
61
|
+
const foundational_types = get_foundational_field_types();
|
|
62
|
+
return Object.entries(foundational_types);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolve_type_name_value(type_name) {
|
|
66
|
+
return String(type_name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolve_reference_list(references) {
|
|
70
|
+
if(Array.isArray(references)) {
|
|
71
|
+
return references;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function register_reference_aliases(type_name_by_reference, resolved_name, resolved_references) {
|
|
78
|
+
let reference_index = 0;
|
|
79
|
+
|
|
80
|
+
while(reference_index < resolved_references.length) {
|
|
81
|
+
type_name_by_reference.set(resolved_references[reference_index], resolved_name);
|
|
82
|
+
reference_index += 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolve_field_type_name_value(type_name_by_reference, type_constructors_by_name, type_reference) {
|
|
87
|
+
if(type_name_by_reference.has(type_reference)) {
|
|
88
|
+
return type_name_by_reference.get(type_reference);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if(typeof type_reference === 'string' && type_constructors_by_name[type_reference]) {
|
|
92
|
+
return type_reference;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function create_field_options(options) {
|
|
99
|
+
return Object.assign({}, options || {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function assert_supported_type_name(path_value, type_name) {
|
|
103
|
+
if(!type_name) {
|
|
104
|
+
throw new Error('Unsupported field type at path "' + path_value + '"');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const default_field_type_registry = new FieldTypeRegistry();
|
|
109
|
+
|
|
110
|
+
function register_field_type(type_name, type_constructor, references) {
|
|
111
|
+
default_field_type_registry.register_field_type(type_name, type_constructor, references);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolve_field_type(type_reference) {
|
|
115
|
+
return default_field_type_registry.resolve_field_type_name(type_reference);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function create_field_type(path_value, type_reference, options) {
|
|
119
|
+
return default_field_type_registry.create_field_type(path_value, type_reference, options);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {create_field_type, default_field_type_registry, FieldTypeRegistry, register_field_type, resolve_field_type};
|