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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/docs/api.md +152 -0
  4. package/docs/examples.md +612 -0
  5. package/docs/local-integration-testing.md +17 -0
  6. package/docs/query-translation.md +98 -0
  7. package/index.js +2 -0
  8. package/package.json +58 -0
  9. package/src/connection/connect.js +56 -0
  10. package/src/connection/disconnect.js +16 -0
  11. package/src/connection/pool-store.js +46 -0
  12. package/src/connection/server-capabilities.js +59 -0
  13. package/src/constants/defaults.js +20 -0
  14. package/src/constants/id-strategies.js +29 -0
  15. package/src/debug/debug-logger.js +15 -0
  16. package/src/errors/query-error.js +23 -0
  17. package/src/errors/validation-error.js +23 -0
  18. package/src/field-types/base-field-type.js +140 -0
  19. package/src/field-types/builtins/advanced.js +365 -0
  20. package/src/field-types/builtins/index.js +585 -0
  21. package/src/field-types/registry.js +122 -0
  22. package/src/index.js +36 -0
  23. package/src/migration/ensure-index.js +155 -0
  24. package/src/migration/ensure-schema.js +16 -0
  25. package/src/migration/ensure-table.js +31 -0
  26. package/src/migration/schema-indexes-resolver.js +6 -0
  27. package/src/model/document-instance.js +540 -0
  28. package/src/model/model-factory.js +555 -0
  29. package/src/query/limit-skip-compiler.js +31 -0
  30. package/src/query/operators/all.js +10 -0
  31. package/src/query/operators/contains.js +7 -0
  32. package/src/query/operators/elem-match.js +3 -0
  33. package/src/query/operators/eq.js +6 -0
  34. package/src/query/operators/gt.js +16 -0
  35. package/src/query/operators/gte.js +16 -0
  36. package/src/query/operators/has-all-keys.js +11 -0
  37. package/src/query/operators/has-any-keys.js +11 -0
  38. package/src/query/operators/has-key.js +6 -0
  39. package/src/query/operators/in.js +12 -0
  40. package/src/query/operators/index.js +60 -0
  41. package/src/query/operators/jsonpath-exists.js +15 -0
  42. package/src/query/operators/jsonpath-match.js +15 -0
  43. package/src/query/operators/lt.js +16 -0
  44. package/src/query/operators/lte.js +16 -0
  45. package/src/query/operators/ne.js +6 -0
  46. package/src/query/operators/nin.js +12 -0
  47. package/src/query/operators/regex.js +8 -0
  48. package/src/query/operators/size.js +16 -0
  49. package/src/query/path-parser.js +43 -0
  50. package/src/query/query-builder.js +93 -0
  51. package/src/query/sort-compiler.js +30 -0
  52. package/src/query/where-compiler.js +477 -0
  53. package/src/schema/field-definition-parser.js +218 -0
  54. package/src/schema/path-introspection.js +82 -0
  55. package/src/schema/schema-compiler.js +212 -0
  56. package/src/schema/schema.js +234 -0
  57. package/src/sql/parameter-binder.js +13 -0
  58. package/src/sql/sql-runner.js +31 -0
  59. package/src/utils/array.js +31 -0
  60. package/src/utils/assert.js +27 -0
  61. package/src/utils/json-safe.js +9 -0
  62. package/src/utils/json.js +21 -0
  63. package/src/utils/object-path.js +33 -0
  64. package/src/utils/object.js +168 -0
  65. package/src/utils/value.js +30 -0
@@ -0,0 +1,98 @@
1
+ # query translation
2
+
3
+ This project compiles Mongo-like filters into PostgreSQL SQL over JSONB data.
4
+
5
+ ## base behavior
6
+
7
+ - all model records are stored in one JSONB column (`data` by default)
8
+ - dot paths are translated to PostgreSQL JSON operators (`->`, `->>`, `#>`, `#>>`)
9
+ - filters are parameterized (`$1`, `$2`, ...) for query safety
10
+
11
+ ## scalar equality
12
+
13
+ - `{ user_name: 'john' }` -> text extraction + `=`
14
+ - `{ age: { $ne: 20 } }` -> text extraction + `!=`
15
+
16
+ ## numeric comparisons
17
+
18
+ - `$gt`, `$gte`, `$lt`, `$lte` cast extracted text to `numeric`
19
+ - invalid numeric inputs throw `query_error` before SQL execution
20
+ - `bigint` query values are bound without `Number(...)` coercion to avoid precision loss
21
+
22
+ ## set and array operators
23
+
24
+ - `$in`, `$nin` -> `ANY($n::text[])`
25
+ - `$all` -> JSONB containment `@>` with JSON array param
26
+ - `$size` -> `jsonb_array_length(...) = $n`
27
+ - `$contains` -> JSONB containment `@>`
28
+ - `$elem_match` -> `EXISTS (SELECT 1 FROM jsonb_array_elements(...) ...)`
29
+
30
+ ## JSONB key existence operators
31
+
32
+ - `$has_key` -> `?`
33
+ - `$has_any_keys` -> `?|`
34
+ - `$has_all_keys` -> `?&`
35
+
36
+ Top-level semantics note:
37
+ - PostgreSQL key-existence operators only check keys at the top level of the left-hand JSONB value.
38
+ - jsonbadger preserves that behavior exactly.
39
+ - If the filter path is nested (for example `profile.city`), jsonbadger first extracts that nested JSONB value (`#>`), then applies the existence operator to the extracted value's top level.
40
+ - jsonbadger does not emulate recursive/deep key search for these operators.
41
+
42
+ ## JSONPath operators
43
+
44
+ - `$json_path_exists` -> `@?` with a `::jsonpath` parameter
45
+ - `$json_path_match` -> `@@` with a `::jsonpath` parameter
46
+ - invalid/empty JSONPath values fail before SQL execution
47
+
48
+ ## JSON update operators
49
+
50
+ - `$set` -> `jsonb_set(target, path, value, true)`
51
+ - `$insert` -> `jsonb_insert(target, path, value, insert_after)`
52
+ - `$set_lax` -> `jsonb_set_lax(target, path, value, create_if_missing, null_value_treatment)`
53
+
54
+ Update path behavior:
55
+ - Dot paths are validated before SQL execution.
56
+ - Nested numeric segments are allowed for JSON array index paths in updates (for example `tags.0`).
57
+ - Conflicting update paths in a single `update_one(...)` call (same path or parent/child overlap) fail before SQL execution.
58
+
59
+ ## regex
60
+
61
+ - `$regex` uses PostgreSQL regex operators:
62
+ - `~` (case-sensitive)
63
+ - `~*` when `$options` includes `i`
64
+
65
+ ## sort, limit, skip
66
+
67
+ - sort values use `-1` for `DESC`, everything else as `ASC`
68
+ - limit/skip only apply for non-negative numeric values
69
+
70
+
71
+ ## PostgreSQL capability map
72
+
73
+ This table is the implementation-facing capability map for currently supported query/update operators over JSONB.
74
+
75
+ | jsonbadger feature | PostgreSQL operator/function | Notes | Indexability expectation |
76
+ | --- | --- | --- | --- |
77
+ | scalar equality (`{ path: value }`, `$ne`) | JSON extraction (`->>`, `#>>`) + `=` / `!=` | Compares extracted text/scalar representation | Manual expression indexes required for fast path filtering/sorting; current schema index helpers do not auto-create these expression indexes |
78
+ | numeric comparisons (`$gt`, `$gte`, `$lt`, `$lte`) | JSON extraction + `::numeric` + comparison | Invalid numeric inputs fail before SQL execution | Manual expression indexes may be used; no automatic expression-index creation |
79
+ | `$in`, `$nin` | `= ANY($n::text[])` / `!= ALL(...)` over extracted text | Set membership over extracted scalar text | Manual expression indexes only |
80
+ | `$contains` | `@>` | JSONB containment over extracted JSONB value | GIN on JSONB value is typically the relevant index strategy |
81
+ | `$all` | `@>` (JSON array containment) | Encodes requested list as JSON array | GIN on JSONB value is typically the relevant index strategy |
82
+ | `$size` | `jsonb_array_length(...)` | Array length equality | Usually expression-based optimization if needed; no auto-created support |
83
+ | `$elem_match` | `jsonb_array_elements(...)` + nested predicates | Uses set-returning expansion | Often less index-friendly than direct containment; depends on query shape |
84
+ | `$regex` | `~` / `~*` on extracted text | PostgreSQL regex semantics | Manual text/expression indexing strategy required; jsonbadger does not auto-create regex-specific indexes |
85
+ | `$has_key` | `?` | Top-level key existence on the left JSONB value | GIN on JSONB value is the expected index family |
86
+ | `$has_any_keys` | `?|` | Any-key existence | GIN on JSONB value is the expected index family |
87
+ | `$has_all_keys` | `?&` | All-keys existence | GIN on JSONB value is the expected index family |
88
+ | `$json_path_exists` | `@?` | JSONPath existence predicate | Can benefit from JSONB GIN indexing depending on operator class/query shape |
89
+ | `$json_path_match` | `@@` | JSONPath predicate match | Can benefit from JSONB GIN indexing depending on operator class/query shape |
90
+ | `update_one.$set` | `jsonb_set(...)` | Creates missing path segments when configured (`true`) | N/A (write-path function) |
91
+ | `update_one.$insert` | `jsonb_insert(...)` | Supports `insert_after` and numeric array-index path segments | N/A (write-path function) |
92
+ | `update_one.$set_lax` | `jsonb_set_lax(...)` | Supports `create_if_missing` + `null_value_treatment` | N/A (write-path function) |
93
+
94
+ Index helper behavior (current implementation):
95
+ - `schema.create_index('path')` creates a GIN index on the extracted JSONB path expression.
96
+ - `schema.create_index({ path: 1 })` (and compound object specs) create BTREE indexes on extracted text path expressions.
97
+ - Single-path string GIN indexes do not support `unique` in jsonbadger; object specs support `unique` because they compile to BTREE indexes.
98
+
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from './src/index.js';
2
+ export * from './src/index.js';
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "jsonbadger",
3
+ "version": "0.5.0",
4
+ "description": "JSONB query/model library for PostgreSQL",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./index.js"
9
+ },
10
+ "imports": {
11
+ "#src/*": "./src/*",
12
+ "#test/*": "./test/*"
13
+ },
14
+ "files": [
15
+ "index.js",
16
+ "src",
17
+ "docs",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/carlosjln/jsonbadger.git"
27
+ },
28
+ "license": "MIT",
29
+ "author": "Carlos López",
30
+ "keywords": [
31
+ "postgres",
32
+ "postgresql",
33
+ "jsonb",
34
+ "query-builder",
35
+ "schema-validation"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "tag": "next"
40
+ },
41
+ "dependencies": {
42
+ "pg": "^8.18.0"
43
+ },
44
+ "devDependencies": {
45
+ "joi": "^18.0.2",
46
+ "dotenv": "^17.3.1",
47
+ "jest": "^30.2.0"
48
+ },
49
+ "scripts": {
50
+ "jest-cli": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config jest.config.mjs",
51
+ "test": "npm run test-unit",
52
+ "test-unit": "npm run jest-cli -- --runInBand test/unit",
53
+ "test-integration": "npm run jest-cli -- --runInBand test/integration/postgres",
54
+ "test-all": "npm run test-unit && npm run test-integration",
55
+ "test-output": "npm run jest-cli -- --runInBand test/unit --json --outputFile=test/test-output.json",
56
+ "test-coverage": "npm run jest-cli -- --runInBand test/unit --coverage"
57
+ }
58
+ }
@@ -0,0 +1,56 @@
1
+ import {Pool} from 'pg';
2
+
3
+ import defaults from '#src/constants/defaults.js';
4
+ import debug_logger from '#src/debug/debug-logger.js';
5
+ import {assert_condition} from '#src/utils/assert.js';
6
+ import {assert_valid_id_strategy} from '#src/constants/id-strategies.js';
7
+ import {scan_server_capabilities, assert_id_strategy_capability} from '#src/connection/server-capabilities.js';
8
+ import {is_string} from '#src/utils/value.js';
9
+ import {get_pool, has_pool, set_pool} from '#src/connection/pool-store.js';
10
+
11
+ export default async function connect(uri, options) {
12
+ assert_condition(is_string(uri) && uri.length > 0, 'connection_uri is required');
13
+
14
+ if(has_pool()) {
15
+ return get_pool();
16
+ }
17
+
18
+ const final_options = Object.assign({}, defaults.connection_options, options);
19
+ const {debug, id_strategy, auto_index, ...pool_options} = final_options;
20
+
21
+ assert_valid_id_strategy(id_strategy);
22
+ assert_condition(typeof auto_index === 'boolean', 'auto_index must be a boolean');
23
+
24
+ const pool_instance = new Pool(Object.assign({}, pool_options, {connectionString: uri}));
25
+ let server_capabilities;
26
+
27
+ try {
28
+ await pool_instance.query('SELECT 1');
29
+ server_capabilities = await scan_server_capabilities(pool_instance);
30
+ assert_id_strategy_capability(id_strategy, server_capabilities);
31
+ set_pool(pool_instance, final_options, server_capabilities);
32
+ } catch(error) {
33
+ await close_pool_quietly(pool_instance);
34
+ throw error;
35
+ }
36
+
37
+ debug_logger(debug, 'connection_ready', {
38
+ max: pool_options.max,
39
+ server_version: server_capabilities.server_version,
40
+ supports_uuidv7: server_capabilities.supports_uuidv7
41
+ });
42
+
43
+ return pool_instance;
44
+ }
45
+
46
+ async function close_pool_quietly(pool_instance) {
47
+ if(!pool_instance || typeof pool_instance.end !== 'function') {
48
+ return;
49
+ }
50
+
51
+ try {
52
+ await pool_instance.end();
53
+ } catch(error) {
54
+ // Ignore cleanup failures so the original connection/capability error is preserved.
55
+ }
56
+ }
@@ -0,0 +1,16 @@
1
+ import debug_logger from '#src/debug/debug-logger.js';
2
+ import {clear_pool, get_debug_mode, get_pool, has_pool} from '#src/connection/pool-store.js';
3
+
4
+ export default async function disconnect() {
5
+ if(!has_pool()) {
6
+ return;
7
+ }
8
+
9
+ const debug_mode = get_debug_mode();
10
+ const pool_instance = get_pool();
11
+
12
+ await pool_instance.end();
13
+ clear_pool();
14
+
15
+ debug_logger(debug_mode, 'connection_closed', null);
16
+ }
@@ -0,0 +1,46 @@
1
+ import defaults from '#src/constants/defaults.js';
2
+
3
+ const connection_state = {
4
+ pool_instance: null,
5
+ debug_mode: defaults.connection_options.debug,
6
+ connection_options: Object.assign({}, defaults.connection_options),
7
+ server_capabilities: null
8
+ };
9
+
10
+ export function has_pool() {
11
+ return connection_state.pool_instance !== null;
12
+ }
13
+
14
+ export function get_pool() {
15
+ if(connection_state.pool_instance) {
16
+ return connection_state.pool_instance;
17
+ }
18
+
19
+ throw new Error('jsonbadger is not connected. Call connect() first.');
20
+ }
21
+
22
+ export function set_pool(pool_instance, connection_options, server_capabilities) {
23
+ connection_state.pool_instance = pool_instance;
24
+ connection_state.connection_options = Object.assign({}, defaults.connection_options, connection_options || {});
25
+ connection_state.debug_mode = connection_state.connection_options.debug;
26
+ connection_state.server_capabilities = server_capabilities === null ? null : Object.freeze(Object.assign({}, server_capabilities));
27
+ }
28
+
29
+ export function clear_pool() {
30
+ connection_state.pool_instance = null;
31
+ connection_state.connection_options = Object.assign({}, defaults.connection_options);
32
+ connection_state.debug_mode = defaults.connection_options.debug;
33
+ connection_state.server_capabilities = null;
34
+ }
35
+
36
+ export function get_debug_mode() {
37
+ return connection_state.debug_mode;
38
+ }
39
+
40
+ export function get_connection_options() {
41
+ return Object.assign({}, connection_state.connection_options);
42
+ }
43
+
44
+ export function get_server_capabilities() {
45
+ return connection_state.server_capabilities;
46
+ }
@@ -0,0 +1,59 @@
1
+ import IdStrategies from '#src/constants/id-strategies.js';
2
+ import {assert_condition} from '#src/utils/assert.js';
3
+
4
+ const MIN_POSTGRES_NATIVE_UUIDV7_VERSION_NUM = 180000;
5
+
6
+ async function scan_server_capabilities(pool_instance) {
7
+ assert_condition(pool_instance && typeof pool_instance.query === 'function', 'pool_instance.query is required');
8
+
9
+ const server_version_num_result = await pool_instance.query('SHOW server_version_num;');
10
+ const server_version_result = await pool_instance.query('SHOW server_version;');
11
+ const uuidv7_function_result = await pool_instance.query(
12
+ "SELECT to_regprocedure('uuidv7()') IS NOT NULL AS has_uuidv7_function;"
13
+ );
14
+
15
+ const server_version_num_text = server_version_num_result.rows?.[0]?.server_version_num;
16
+ const server_version = server_version_result.rows?.[0]?.server_version;
17
+ const has_uuidv7_function = uuidv7_function_result.rows?.[0]?.has_uuidv7_function === true;
18
+ const server_version_num = Number.parseInt(String(server_version_num_text), 10);
19
+
20
+ assert_condition(Number.isInteger(server_version_num), 'Unable to determine PostgreSQL server_version_num');
21
+ assert_condition(typeof server_version === 'string' && server_version.length > 0, 'Unable to determine PostgreSQL server_version');
22
+
23
+ const supports_uuidv7 = server_version_num >= MIN_POSTGRES_NATIVE_UUIDV7_VERSION_NUM && has_uuidv7_function;
24
+
25
+ return {
26
+ server_version: server_version,
27
+ server_version_num: server_version_num,
28
+ supports_uuidv7: supports_uuidv7
29
+ };
30
+ }
31
+
32
+ function assert_id_strategy_capability(id_strategy, server_capabilities) {
33
+ if(id_strategy !== IdStrategies.uuidv7) {
34
+ return;
35
+ }
36
+
37
+ assert_condition(
38
+ server_capabilities && typeof server_capabilities === 'object',
39
+ 'PostgreSQL server capabilities are unavailable. Reconnect so jsonbadger can run compatibility checks for id_strategy=uuidv7.'
40
+ );
41
+
42
+ assert_condition(server_capabilities.supports_uuidv7 === true, build_uuidv7_capability_error(server_capabilities));
43
+ }
44
+
45
+ function build_uuidv7_capability_error(server_capabilities) {
46
+ const server_version = server_capabilities.server_version ?? 'unknown';
47
+ const server_version_num = server_capabilities.server_version_num ?? 'unknown';
48
+ const supports_uuidv7 = server_capabilities.supports_uuidv7 === true ? 'true' : 'false';
49
+
50
+ return 'id_strategy=uuidv7 requires PostgreSQL native uuidv7() support (PostgreSQL 18+). ' +
51
+ 'Detected server_version=' + server_version + ', server_version_num=' + server_version_num +
52
+ ', supports_uuidv7=' + supports_uuidv7 + '.';
53
+ }
54
+
55
+ export {
56
+ MIN_POSTGRES_NATIVE_UUIDV7_VERSION_NUM,
57
+ scan_server_capabilities,
58
+ assert_id_strategy_capability
59
+ };
@@ -0,0 +1,20 @@
1
+ import IdStrategies from '#src/constants/id-strategies.js';
2
+
3
+ const defaults = {
4
+ connection_options: {
5
+ max: 10,
6
+ debug: false,
7
+ auto_index: true,
8
+ id_strategy: IdStrategies.bigserial
9
+ },
10
+
11
+ schema_options: {},
12
+ model_options: {
13
+ table_name: null,
14
+ data_column: 'data',
15
+ auto_index: null,
16
+ id_strategy: null
17
+ }
18
+ };
19
+
20
+ export default defaults;
@@ -0,0 +1,29 @@
1
+ import {assert_condition} from '#src/utils/assert.js';
2
+
3
+ const IdStrategies = Object.freeze({
4
+ bigserial: 'bigserial',
5
+ uuidv7: 'uuidv7'
6
+ });
7
+
8
+ const id_strategy_values = Object.freeze(Object.values(IdStrategies));
9
+
10
+ function is_valid(id_strategy) {
11
+ return id_strategy_values.includes(id_strategy);
12
+ }
13
+
14
+ function id_strategy_error() {
15
+ return 'id_strategy must be one of: ' + id_strategy_values.join(', ');
16
+ }
17
+
18
+ // TODO: consider moving this to assert file
19
+ function assert_valid_id_strategy(id_strategy) {
20
+ assert_condition(is_valid(id_strategy), id_strategy_error());
21
+ }
22
+
23
+ export {
24
+ IdStrategies,
25
+ is_valid,
26
+ assert_valid_id_strategy
27
+ };
28
+
29
+ export default IdStrategies;
@@ -0,0 +1,15 @@
1
+ import safe_json_stringify from '#src/utils/json-safe.js';
2
+
3
+ export default function debug_logger(debug_mode, event_name, event_data) {
4
+ if(!debug_mode) {
5
+ return;
6
+ }
7
+
8
+ const log_entry = {
9
+ event_name: event_name,
10
+ event_data: event_data || null,
11
+ created_at: new Date().toISOString()
12
+ };
13
+
14
+ console.log('[jsonbadger][debug] ' + safe_json_stringify(log_entry));
15
+ }
@@ -0,0 +1,23 @@
1
+ export default function QueryError(message, details) {
2
+ this.name = 'query_error';
3
+ this.message = message || 'Query failed';
4
+ this.details = details || null;
5
+
6
+ if(Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, QueryError);
8
+ }
9
+ }
10
+
11
+ QueryError.prototype = Object.create(Error.prototype);
12
+ QueryError.prototype.constructor = QueryError;
13
+
14
+ QueryError.prototype.to_json = function () {
15
+ return {
16
+ success: false,
17
+ error: {
18
+ type: 'query_error',
19
+ message: this.message,
20
+ details: this.details
21
+ }
22
+ };
23
+ };
@@ -0,0 +1,23 @@
1
+ export default function ValidationError(message, details) {
2
+ this.name = 'validation_error';
3
+ this.message = message || 'Validation failed';
4
+ this.details = details || null;
5
+
6
+ if(Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, ValidationError);
8
+ }
9
+ }
10
+
11
+ ValidationError.prototype = Object.create(Error.prototype);
12
+ ValidationError.prototype.constructor = ValidationError;
13
+
14
+ ValidationError.prototype.to_json = function () {
15
+ return {
16
+ success: false,
17
+ error: {
18
+ type: 'validation_error',
19
+ message: this.message,
20
+ details: this.details
21
+ }
22
+ };
23
+ };
@@ -0,0 +1,140 @@
1
+ /*
2
+ Assumptions and trade-offs:
3
+ - Universal options are normalized here, while runtime-only behaviors are stored for later phases.
4
+ - Validation/casting fails fast per path and defers aggregation to schema_compiler.
5
+ */
6
+ function BaseFieldType(path_value, options) {
7
+ this.path = path_value;
8
+ this.options = options || {};
9
+ this.instance = 'Mixed';
10
+ this.validators = [];
11
+ this.regExp = null;
12
+ this.enum_values = null;
13
+ this.register_universal_validators();
14
+ }
15
+
16
+ BaseFieldType.prototype.register_universal_validators = function () {
17
+ if(this.options.required !== undefined) {
18
+ this.validators.push({
19
+ kind: 'required'
20
+ });
21
+ }
22
+
23
+ if(typeof this.options.validate === 'function') {
24
+ this.validators.push({
25
+ kind: 'custom'
26
+ });
27
+ }
28
+ };
29
+
30
+ BaseFieldType.prototype.create_field_error = function (code_value, message, value) {
31
+ const field_error = new Error(message);
32
+ field_error.code = code_value;
33
+ field_error.path = this.path;
34
+ field_error.value = value;
35
+ return field_error;
36
+ };
37
+
38
+ BaseFieldType.prototype.is_required = function (context_value) {
39
+ const required_option = this.options.required;
40
+
41
+ if(typeof required_option === 'function') {
42
+ return Boolean(required_option.call(null, context_value || {}));
43
+ }
44
+
45
+ return required_option === true;
46
+ };
47
+
48
+ BaseFieldType.prototype.resolve_default = function (context_value) {
49
+ if(this.options.default === undefined) {
50
+ return undefined;
51
+ }
52
+
53
+ if(typeof this.options.default === 'function') {
54
+ return this.options.default.call(null, context_value || {});
55
+ }
56
+
57
+ return this.options.default;
58
+ };
59
+
60
+ BaseFieldType.prototype.apply_set = function (value, context_value) {
61
+ if(typeof this.options.set !== 'function') {
62
+ return value;
63
+ }
64
+
65
+ return this.options.set.call(null, value, context_value || {});
66
+ };
67
+
68
+ BaseFieldType.prototype.apply_get = function (value, context_value) {
69
+ if(typeof this.options.get !== 'function') {
70
+ return value;
71
+ }
72
+
73
+ return this.options.get.call(null, value, context_value || {});
74
+ };
75
+
76
+ BaseFieldType.prototype.cast = function (value) {
77
+ return value;
78
+ };
79
+
80
+ BaseFieldType.prototype.run_custom_validator = function (value, context_value) {
81
+ if(typeof this.options.validate !== 'function') {
82
+ return;
83
+ }
84
+
85
+ let validator_result = false;
86
+
87
+ try {
88
+ validator_result = this.options.validate.call(null, value, context_value || {});
89
+ } catch(error) {
90
+ const message_value = error && error.message ? error.message : 'Custom validator failed for path "' + this.path + '"';
91
+ throw this.create_field_error('validator_error', message_value, value);
92
+ }
93
+
94
+ if(!validator_result) {
95
+ throw this.create_field_error('validator_error', 'Custom validator failed for path "' + this.path + '"', value);
96
+ }
97
+ };
98
+
99
+ BaseFieldType.prototype.run_type_validators = function () {
100
+ return;
101
+ };
102
+
103
+ BaseFieldType.prototype.validate = function (value, context_value) {
104
+ this.run_custom_validator(value, context_value || {});
105
+ this.run_type_validators(value, context_value || {});
106
+ };
107
+
108
+ BaseFieldType.prototype.normalize = function (value, context_value) {
109
+ const normalized_context = context_value || {};
110
+ let current_value = value;
111
+
112
+ if(current_value === undefined) {
113
+ current_value = this.resolve_default(normalized_context);
114
+ }
115
+
116
+ if(current_value === undefined || current_value === null) {
117
+ if(this.is_required(normalized_context)) {
118
+ throw this.create_field_error('required_error', 'Path "' + this.path + '" is required', current_value);
119
+ }
120
+
121
+ return current_value;
122
+ }
123
+
124
+ current_value = this.apply_set(current_value, normalized_context);
125
+ current_value = this.cast(current_value, normalized_context);
126
+ this.validate(current_value, normalized_context);
127
+ return current_value;
128
+ };
129
+
130
+ BaseFieldType.prototype.to_introspection = function () {
131
+ return {
132
+ path: this.path,
133
+ instance: this.instance,
134
+ validators: this.validators,
135
+ regExp: this.regExp || null,
136
+ enum_values: this.enum_values || null
137
+ };
138
+ };
139
+
140
+ export default BaseFieldType;