jsonbadger 0.5.0 → 0.6.1

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 (123) hide show
  1. package/README.md +36 -18
  2. package/docs/api/connection.md +144 -0
  3. package/docs/api/delta-tracker.md +106 -0
  4. package/docs/api/document.md +77 -0
  5. package/docs/api/field-types.md +329 -0
  6. package/docs/api/index.md +35 -0
  7. package/docs/api/model.md +392 -0
  8. package/docs/api/query-builder.md +81 -0
  9. package/docs/api/schema.md +204 -0
  10. package/docs/architecture-flow.md +397 -0
  11. package/docs/examples.md +495 -218
  12. package/docs/jsonb-ops.md +171 -0
  13. package/docs/lifecycle/model-compilation.md +111 -0
  14. package/docs/lifecycle.md +146 -0
  15. package/docs/query-translation.md +11 -10
  16. package/package.json +10 -3
  17. package/src/connection/connect.js +12 -17
  18. package/src/connection/connection.js +128 -0
  19. package/src/connection/server-capabilities.js +60 -59
  20. package/src/constants/defaults.js +32 -19
  21. package/src/constants/{id-strategies.js → id-strategy.js} +28 -29
  22. package/src/constants/intake-mode.js +8 -0
  23. package/src/debug/debug-logger.js +17 -15
  24. package/src/errors/model-overwrite-error.js +25 -0
  25. package/src/errors/query-error.js +25 -23
  26. package/src/errors/validation-error.js +25 -23
  27. package/src/field-types/base-field-type.js +137 -140
  28. package/src/field-types/builtins/advanced.js +365 -365
  29. package/src/field-types/builtins/index.js +579 -585
  30. package/src/field-types/field-type-namespace.js +9 -0
  31. package/src/field-types/registry.js +149 -122
  32. package/src/index.js +26 -36
  33. package/src/migration/ensure-index.js +157 -154
  34. package/src/migration/ensure-schema.js +27 -15
  35. package/src/migration/ensure-table.js +44 -31
  36. package/src/migration/schema-indexes-resolver.js +8 -6
  37. package/src/model/document-instance.js +29 -540
  38. package/src/model/document.js +60 -0
  39. package/src/model/factory/constants.js +36 -0
  40. package/src/model/factory/index.js +58 -0
  41. package/src/model/model.js +875 -0
  42. package/src/model/operations/delete-one.js +39 -0
  43. package/src/model/operations/insert-one.js +35 -0
  44. package/src/model/operations/query-builder.js +132 -0
  45. package/src/model/operations/update-one.js +333 -0
  46. package/src/model/state.js +34 -0
  47. package/src/schema/field-definition-parser.js +213 -218
  48. package/src/schema/path-introspection.js +87 -82
  49. package/src/schema/schema-compiler.js +126 -212
  50. package/src/schema/schema.js +621 -138
  51. package/src/sql/index.js +17 -0
  52. package/src/sql/jsonb/ops.js +153 -0
  53. package/src/{query → sql/jsonb}/path-parser.js +54 -43
  54. package/src/sql/jsonb/read/elem-match.js +133 -0
  55. package/src/{query → sql/jsonb/read}/operators/contains.js +13 -7
  56. package/src/sql/jsonb/read/operators/elem-match.js +9 -0
  57. package/src/{query → sql/jsonb/read}/operators/has-all-keys.js +17 -11
  58. package/src/{query → sql/jsonb/read}/operators/has-any-keys.js +18 -11
  59. package/src/sql/jsonb/read/operators/has-key.js +12 -0
  60. package/src/{query → sql/jsonb/read}/operators/jsonpath-exists.js +22 -15
  61. package/src/{query → sql/jsonb/read}/operators/jsonpath-match.js +22 -15
  62. package/src/{query → sql/jsonb/read}/operators/size.js +23 -16
  63. package/src/sql/parameter-binder.js +18 -13
  64. package/src/sql/read/build-count-query.js +12 -0
  65. package/src/sql/read/build-find-query.js +25 -0
  66. package/src/sql/read/limit-skip.js +21 -0
  67. package/src/sql/read/sort.js +85 -0
  68. package/src/sql/read/where/base-fields.js +310 -0
  69. package/src/sql/read/where/casting.js +90 -0
  70. package/src/sql/read/where/context.js +79 -0
  71. package/src/sql/read/where/field-clause.js +58 -0
  72. package/src/sql/read/where/index.js +38 -0
  73. package/src/sql/read/where/operator-entries.js +29 -0
  74. package/src/{query → sql/read/where}/operators/all.js +16 -10
  75. package/src/sql/read/where/operators/eq.js +12 -0
  76. package/src/{query → sql/read/where}/operators/gt.js +23 -16
  77. package/src/{query → sql/read/where}/operators/gte.js +23 -16
  78. package/src/{query → sql/read/where}/operators/in.js +18 -12
  79. package/src/sql/read/where/operators/index.js +40 -0
  80. package/src/{query → sql/read/where}/operators/lt.js +23 -16
  81. package/src/{query → sql/read/where}/operators/lte.js +23 -16
  82. package/src/sql/read/where/operators/ne.js +12 -0
  83. package/src/{query → sql/read/where}/operators/nin.js +18 -12
  84. package/src/{query → sql/read/where}/operators/regex.js +14 -8
  85. package/src/sql/read/where/operators.js +126 -0
  86. package/src/sql/read/where/text-operators.js +83 -0
  87. package/src/sql/run.js +46 -0
  88. package/src/sql/write/build-delete-query.js +33 -0
  89. package/src/sql/write/build-insert-query.js +42 -0
  90. package/src/sql/write/build-update-query.js +65 -0
  91. package/src/utils/assert.js +34 -27
  92. package/src/utils/delta-tracker/.archive/1 tracker-redesign-codex-v2.md +250 -0
  93. package/src/utils/delta-tracker/.archive/1 tracker-redesign-gemini.md +101 -0
  94. package/src/utils/delta-tracker/.archive/2 evaluation by gemini.txt +65 -0
  95. package/src/utils/delta-tracker/.archive/2 evaluation by grok.txt +39 -0
  96. package/src/utils/delta-tracker/.archive/3 gemini evaluate grok.txt +37 -0
  97. package/src/utils/delta-tracker/.archive/3 grok evaluate gemini.txt +63 -0
  98. package/src/utils/delta-tracker/.archive/4 gemini veredict.txt +16 -0
  99. package/src/utils/delta-tracker/.archive/index.1.js +587 -0
  100. package/src/utils/delta-tracker/.archive/index.2.js +612 -0
  101. package/src/utils/delta-tracker/index.js +592 -0
  102. package/src/utils/dirty-tracker/inline.js +335 -0
  103. package/src/utils/dirty-tracker/instance.js +414 -0
  104. package/src/utils/dirty-tracker/static.js +343 -0
  105. package/src/utils/json-safe.js +13 -9
  106. package/src/utils/object-path.js +227 -33
  107. package/src/utils/object.js +408 -168
  108. package/src/utils/string.js +55 -0
  109. package/src/utils/value.js +169 -30
  110. package/docs/api.md +0 -152
  111. package/src/connection/disconnect.js +0 -16
  112. package/src/connection/pool-store.js +0 -46
  113. package/src/model/model-factory.js +0 -555
  114. package/src/query/limit-skip-compiler.js +0 -31
  115. package/src/query/operators/elem-match.js +0 -3
  116. package/src/query/operators/eq.js +0 -6
  117. package/src/query/operators/has-key.js +0 -6
  118. package/src/query/operators/index.js +0 -60
  119. package/src/query/operators/ne.js +0 -6
  120. package/src/query/query-builder.js +0 -93
  121. package/src/query/sort-compiler.js +0 -30
  122. package/src/query/where-compiler.js +0 -477
  123. package/src/sql/sql-runner.js +0 -31
@@ -1,30 +1,169 @@
1
- const is_object = (value) => {
2
- return value !== null && typeof value === 'object' && !Array.isArray(value);
3
- };
4
-
5
- const is_not_object = (value) => {
6
- return value === null || Array.isArray(value) || typeof value !== 'object';
7
- };
8
-
9
- const is_nan = (value) => {
10
- const numeric_value = Number(value);
11
- return Number.isNaN(numeric_value);
12
- };
13
-
14
- const is_string = (value) => {
15
- return typeof value === 'string';
16
- };
17
-
18
- export {
19
- is_object,
20
- is_not_object,
21
- is_nan,
22
- is_string
23
- };
24
-
25
- export default {
26
- is_object,
27
- is_not_object,
28
- is_nan,
29
- is_string
30
- };
1
+ 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;
2
+
3
+ function is_object(value) {
4
+ return typeof value === 'object'
5
+ && !(value == null)
6
+ && !(value instanceof Date)
7
+ && !(value instanceof Map)
8
+ && !(value instanceof Set)
9
+ && !Array.isArray(value)
10
+ && !ArrayBuffer.isView(value);
11
+ }
12
+
13
+ function is_not_object(value) {
14
+ return typeof value !== 'object'
15
+ || value == null
16
+ || value instanceof Date
17
+ || value instanceof Map
18
+ || value instanceof Set
19
+ || Array.isArray(value)
20
+ || ArrayBuffer.isView(value);
21
+ }
22
+
23
+ /**
24
+ * Checks whether a value is a plain object with a null or default object prototype.
25
+ *
26
+ * @param {*} value Value to inspect.
27
+ * @returns {boolean}
28
+ */
29
+ function is_plain_object(value) {
30
+ if(!is_object(value)) {
31
+ return false;
32
+ }
33
+
34
+ const prototype_value = Object.getPrototypeOf(value);
35
+ return prototype_value === Object.prototype || prototype_value === null;
36
+ }
37
+
38
+ function is_nan(value) {
39
+ if(value === null || value === undefined) {
40
+ return true;
41
+ }
42
+
43
+ const numeric_value = Number(value);
44
+ return Number.isNaN(numeric_value);
45
+ }
46
+
47
+ /**
48
+ * Normalizes one value into a finite number, falling back to zero.
49
+ *
50
+ * @param {*} value Value to normalize.
51
+ * @returns {number}
52
+ */
53
+ function to_number(value) {
54
+ const number = Number(value || 0);
55
+ return Number.isFinite(number) ? number : 0;
56
+ }
57
+
58
+ function to_int(value) {
59
+ return Math.floor(Number(value));
60
+ }
61
+
62
+ /**
63
+ * Checks whether one value is a valid UUIDv7 string.
64
+ *
65
+ * @param {*} value Value to inspect.
66
+ * @returns {boolean}
67
+ */
68
+ function is_uuid_v7(value) {
69
+ if(typeof value !== 'string') {
70
+ return false;
71
+ }
72
+
73
+ return uuidv7_pattern.test(value);
74
+ }
75
+
76
+ function is_string(value) {
77
+ return typeof value === 'string';
78
+ }
79
+
80
+ function is_function(value) {
81
+ return typeof value === 'function';
82
+ }
83
+
84
+ function is_boolean(value) {
85
+ return typeof value === 'boolean';
86
+ }
87
+
88
+ function is_date(value) {
89
+ return value instanceof Date;
90
+ }
91
+
92
+ function is_not_date(value) {
93
+ return !(value instanceof Date);
94
+ }
95
+
96
+ function is_valid_timestamp(value) {
97
+ return !Number.isNaN(new Date(value).getTime());
98
+ }
99
+
100
+ function to_iso_timestamp(value) {
101
+ // Treat falsy values as non-normalizable input and return them unchanged
102
+ // rather than assuming they represent a valid timestamp.
103
+ if(!value) {
104
+ return null;
105
+ }
106
+
107
+ if(value instanceof Date) {
108
+ return value.toISOString();
109
+ }
110
+
111
+ const parsed_timestamp = new Date(value);
112
+
113
+ if(Number.isNaN(parsed_timestamp.getTime())) {
114
+ return String(value);
115
+ }
116
+
117
+ return parsed_timestamp.toISOString();
118
+ }
119
+
120
+ /**
121
+ * Returns the value if the evaluator passes, otherwise returns the default.
122
+ * @param {function} evaluator - The condition to check against.
123
+ * @param {any} value - The primary value to test.
124
+ * @param {any} default_value - The fallback value.
125
+ * @returns {any}
126
+ */
127
+ function get_if(evaluator, value, default_value) {
128
+ if(evaluator(value)) {
129
+ return value;
130
+ }
131
+
132
+ return default_value;
133
+ }
134
+
135
+ export {
136
+ is_object,
137
+ is_plain_object,
138
+ is_not_object,
139
+ is_nan,
140
+ is_uuid_v7,
141
+ to_int,
142
+ to_number,
143
+ is_string,
144
+ is_function,
145
+ is_boolean,
146
+ is_date,
147
+ is_not_date,
148
+ is_valid_timestamp,
149
+ to_iso_timestamp,
150
+ get_if
151
+ };
152
+
153
+ export default {
154
+ is_object,
155
+ is_plain_object,
156
+ is_not_object,
157
+ is_nan,
158
+ is_uuid_v7,
159
+ to_int,
160
+ to_number,
161
+ is_string,
162
+ is_function,
163
+ is_boolean,
164
+ is_date,
165
+ is_not_date,
166
+ is_valid_timestamp,
167
+ to_iso_timestamp,
168
+ get_if
169
+ };
package/docs/api.md DELETED
@@ -1,152 +0,0 @@
1
- # jsonbadger API
2
-
3
- ## Top-level exports
4
-
5
- Named exports:
6
- - `connect(connection_uri, connection_options)`
7
- - `disconnect()`
8
- - `Schema`
9
- - `IdStrategies`
10
- - `model(schema_instance, model_options)`
11
- - `create_field_type(path_value, type_reference, options?)`
12
- - `register_field_type(type_name, type_constructor, references?)`
13
- - `resolve_field_type(type_reference)`
14
-
15
- Default export (`jsonbadger`) includes the same plus:
16
- - `field_types.UUIDv7`
17
- - `field_types.boolean_convert_to_true`
18
- - `field_types.boolean_convert_to_false`
19
-
20
- ## connect
21
-
22
- `connect(connection_uri, connection_options)` opens (or reuses) a shared PostgreSQL pool.
23
-
24
- `connection_options`:
25
- - `max`: pool size (default `10`)
26
- - `debug`: logs SQL/debug events when `true`
27
- - `auto_index`: server-wide default for automatic index creation when `Model.ensure_table()` is called (default `true`)
28
- - `id_strategy`: server-wide default row-id strategy (`IdStrategies.bigserial` or `IdStrategies.uuidv7`, default `IdStrategies.bigserial`)
29
- - any `pg` pool options (`ssl`, `host`, `port`, `user`, `password`, `database`)
30
-
31
- UUIDv7 compatibility behavior:
32
- - `connect(...)` performs an internal PostgreSQL capability scan (server version + native `uuidv7()` function presence) and caches the result for later model/runtime checks.
33
- - If the connection-level `id_strategy` resolves to `IdStrategies.uuidv7` and native support is unavailable, `connect(...)` fails fast.
34
- - Model-level `uuidv7` overrides defined after `connect(...)` are validated on use with the cached capability metadata.
35
- - Optional troubleshooting commands only: `SELECT version();` and `SHOW server_version;`. Runtime checks use machine-readable checks internally.
36
-
37
- ## schema
38
-
39
- `new Schema(schema_def, options?)`
40
-
41
- `Schema` is responsible for document structure and index declarations.
42
-
43
- methods:
44
- - `validate(payload)`
45
- - `path(path_name)`
46
- - `get_path_type(path_name)`
47
- - `is_array_root(path_name)`
48
- - `create_index(index_spec, index_options?)`
49
- - `get_indexes()`
50
-
51
- `create_index` supports:
52
- - single-path index: `schema.create_index('profile.city')`
53
- - compound index: `schema.create_index({name: 1, type: -1})`
54
- - optional options: `unique` and `name`
55
-
56
- Notes:
57
- - Single-path string specs (`'profile.city'`) create GIN indexes and do not support `unique`.
58
- - Object specs (`{field: 1}` / compound) create BTREE indexes and support `unique`.
59
-
60
- Path-level sugar in schema definitions is also supported:
61
- - `{name: {type: String, index: true}}` -> single-path index
62
- - `{age: {type: Number, index: 1}}` -> ascending single-path spec
63
- - `{age: {type: Number, index: -1}}` -> descending single-path spec
64
- - `{email: {type: String, index: {unique: true, name: 'idx_users_email_unique'}}}` -> path-level index options
65
- - optional direction in object form: `direction` or `order` (`1` or `-1`)
66
-
67
- ## model
68
-
69
- `const User = model(user_schema, { table_name, data_column?, id_strategy?, auto_index? })`
70
-
71
- - `table_name` is required.
72
- - `data_column` defaults to `data`.
73
- - `id_strategy` defaults to connection-level `id_strategy`, then library default `IdStrategies.bigserial`.
74
- - `auto_index` defaults to model option if set, otherwise connection-level `auto_index`.
75
-
76
- UUIDv7 notes:
77
- - `IdStrategies.uuidv7` uses database-generated row IDs via table DDL (`id UUID PRIMARY KEY DEFAULT uuidv7()`).
78
- - jsonbadger validates `uuidv7` support before uuidv7-backed table/schema/write paths using cached server capability metadata.
79
-
80
- static methods:
81
- - `ensure_table()`: manual migration call that ensures the table exists (for `IdStrategies.uuidv7`, table DDL uses `DEFAULT uuidv7()`); when `auto_index` is enabled, applies declared schema indexes once per model instance. First-write methods (`save()`, `update_one(...)`) can also create the table automatically.
82
- - `ensure_index()`: ensures table exists and applies declared schema indexes immediately.
83
- - `ensure_schema()`: ensures table and applies declared schema indexes.
84
- - `find(query_filter, projection_value?)`
85
- - `find_one(query_filter)`
86
- - `count_documents(query_filter)`
87
- - `update_one(query_filter, update_definition)`
88
- - supported operators: `$set`, `$insert`, `$set_lax`
89
- - operator application order is deterministic and nested: `$set` -> `$insert` -> `$set_lax`
90
- - `$insert` maps to PostgreSQL `jsonb_insert(...)`
91
- - `$set_lax` maps to PostgreSQL `jsonb_set_lax(...)` and supports object form `{ value, create_if_missing?, null_value_treatment? }`
92
- - update paths allow numeric nested segments for JSON array indexes (for example, `tags.0`)
93
- - conflicting update paths (same path or parent/child overlap across operators) are rejected before SQL execution
94
- - `delete_one(query_filter)`
95
- - deletes one matching row and returns the deleted document data
96
- - returns `null` when no row matches
97
- - does not implicitly call `ensure_table()`; if the table does not exist yet, returns `null`
98
-
99
- instance methods:
100
- - `validate()`
101
- - `get(path_name, runtime_options?)`
102
- - `set(path_name, value, runtime_options?)`
103
- - `mark_modified(path_name)`
104
- - `is_modified(path_name?)`
105
- - `clear_modified()`
106
- - `to_object(serialization_options?)`
107
- - `to_json(serialization_options?)`
108
- - `save()`
109
-
110
- Runtime behavior notes:
111
- - Read/query execution (`find(...).exec()`, `find_one(...).exec()`, `count_documents(...).exec()`) does not implicitly call `ensure_table()`.
112
- - Use `ensure_table()` / `ensure_schema()` explicitly, or let a first write (`save()` / `update_one(...)`) create the table.
113
- - `set(...)` applies field setters + casting by default and marks the path as modified.
114
- - `mark_modified(path_name)` is required after in-place `Mixed`/`Date` mutations (for example mutating nested objects or calling `Date` mutator methods directly).
115
- - `is_modified(path_name?)` and `clear_modified()` are available runtime helpers for dirty-path inspection/reset.
116
- - `immutable: true` fields are writable before first persist and become protected after a successful `save()`.
117
- - `to_object(...)` / `to_json(...)` apply getters by default; pass `{ getters: false }` to bypass getter transforms during serialization.
118
-
119
- For built-in FieldType declaration examples and edge-case notes, see [`docs/examples.md`](examples.md).
120
-
121
- For copy-paste usage examples (including all current query/update operators), see [`docs/examples.md`](examples.md). For PostgreSQL translation semantics, see [`docs/query-translation.md`](query-translation.md).
122
-
123
- ## query filters
124
-
125
- Supported filter/operator families (current implemented set):
126
- - scalar comparisons: direct equality, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`
127
- - sets/arrays: `$in`, `$nin`, `$all`, `$size`, `$contains`, `$elem_match`
128
- - regex: `$regex` (+ `$options`)
129
- - JSONB key existence: `$has_key`, `$has_any_keys`, `$has_all_keys`
130
- - JSONPath: `$json_path_exists`, `$json_path_match`
131
-
132
- Existence semantics:
133
- - `$has_key` / `$has_any_keys` / `$has_all_keys` map directly to PostgreSQL `?`, `?|`, and `?&`.
134
- - These operators are top-level for the JSONB value on the left side of the operator.
135
- - When used with a nested field path, jsonbadger extracts that nested JSONB value first, then applies the existence operator to that extracted value.
136
-
137
- JSONPath notes:
138
- - `$json_path_exists` maps to `@?` and binds the JSONPath string as a `::jsonpath` parameter.
139
- - `$json_path_match` maps to `@@` and binds the JSONPath string as a `::jsonpath` parameter.
140
-
141
- ## query builder
142
-
143
- builder chain:
144
- - `.where(filter)`
145
- - `.sort(sort_definition)`
146
- - `.limit(limit_value)`
147
- - `.skip(skip_value)`
148
- - `.exec()`
149
-
150
- ## PostgreSQL capability map (summary)
151
-
152
- See [`docs/query-translation.md`](query-translation.md) for the dedicated operator/function capability map, including update operators (`jsonb_set`, `jsonb_insert`, `jsonb_set_lax`) and indexability expectations for query operators over JSONB.
@@ -1,16 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
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
- }