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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carlos López
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # JsonBadger
2
+
3
+ JsonBadger is a Node.js library that simplifies working with complex JSON data in PostgreSQL. Instead of writing raw, complex SQL to query nested `jsonb` columns, JsonBadger lets you define schemas and interact with your data using a clean, object-based API and simple query operators.
4
+
5
+ ## Why did I create this utility?
6
+
7
+ I originally built my personal systems and small business client projects using MongoDB and Mongoose. Over time, that setup was no longer viable or cost-effective on shared hosting. I needed a solution that was more contained and budget-friendly. Since most shared hosting providers offer free, robust PostgreSQL databases, I created this utility to leverage PostgreSQL's `jsonb` fields as an alternative for document-based storage.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Install](#install)
12
+ - [Examples (Quick Start)](#examples-quick-start)
13
+ - [Examples Cheat Sheet](#examples-cheat-sheet)
14
+ - [Core Features](#core-features)
15
+ - [Documentation](#documentation)
16
+ - [Author](#author)
17
+ - [License](#license)
18
+
19
+ ## Install
20
+
21
+ Requirements:
22
+ - Node.js >=20
23
+ - PostgreSQL
24
+
25
+ ```bash
26
+ npm install jsonbadger
27
+ ```
28
+
29
+ ## Examples (Quick Start)
30
+
31
+ ```js
32
+ import jsonbadger from 'jsonbadger';
33
+
34
+ // 1. Connect to database
35
+ await jsonbadger.connect('postgresql://user:pass@localhost:5432/dbname', {
36
+ debug: false,
37
+ max: 10,
38
+ ssl: false,
39
+ auto_index: true,
40
+ id_strategy: jsonbadger.IdStrategies.bigserial // server default: bigserial | uuidv7 (native PostgreSQL uuidv7() required)
41
+ });
42
+
43
+ // 2. Define schema (FieldType format)
44
+ const user_schema = new jsonbadger.Schema({
45
+ user_name: {type: String, required: true, index: true},
46
+ age: {type: Number, min: 0, index: -1},
47
+ type: {type: String},
48
+ email: {type: String, index: {unique: true, name: 'idx_users_email_unique'}}
49
+ });
50
+
51
+ // 3. Declare indexes on schema (path-level + schema-level compound index)
52
+ // Object-form create_index(...) is how you define a compound index.
53
+ user_schema.create_index({user_name: 1, type: -1});
54
+
55
+ // 4. Create model
56
+ const User = jsonbadger.model(user_schema, {
57
+ table_name: 'users',
58
+ data_column: 'data',
59
+ auto_index: false, // optional model-level override
60
+ id_strategy: jsonbadger.IdStrategies.bigserial // optional model-level override (uuidv7 uses DB-generated ids)
61
+ });
62
+
63
+ // 5. Run migrations manually (optional if you rely on first-write auto table creation via save()/update_one())
64
+ await User.ensure_table();
65
+ await User.ensure_index();
66
+
67
+ // 6. Save a document
68
+ const saved_user = await new User({
69
+ user_name: 'john',
70
+ age: 30,
71
+ type: 'admin'
72
+ }).save();
73
+
74
+ // 7. Find a document
75
+ const found_user = await User.find_one({
76
+ user_name: 'john'
77
+ }).exec();
78
+ ```
79
+
80
+ When using `IdStrategies.uuidv7`, jsonbadger checks PostgreSQL native `uuidv7()` support automatically during `connect(...)` and reuses cached capability metadata for later model-level `uuidv7` overrides. You do not need to run manual version checks; `SELECT version();` and `SHOW server_version;` are optional troubleshooting commands only.
81
+
82
+ ## Examples Cheat Sheet
83
+
84
+ For a complete copy-paste operator and runtime cheat sheet (queries, updates, and document methods), see [`docs/examples.md`](docs/examples.md).
85
+
86
+ ## Core Features
87
+
88
+ * **JSONB-first**: Model API designed specifically for PostgreSQL JSONB.
89
+ * **Validation**: FieldType-based schema validation built in.
90
+ * **Querying**: Mongo-style query operators (e.g., `$gt`, `$regex`) compiled into SQL.
91
+ * **Runtime document APIs**: `get`, `set`, alias virtuals, serialization helpers, `mark_modified`/dirty tracking, and immutable enforcement after save.
92
+ * **JSON update operators**: PostgreSQL-native `jsonb_set`, `jsonb_insert`, and `jsonb_set_lax` support in `update_one(...)`.
93
+ * **Migrations**: Helpers for `ensure_table` and `ensure_index`.
94
+ * **Configurable row IDs**: use `IdStrategies.bigserial` and `IdStrategies.uuidv7` (`uuidv7` is generated by PostgreSQL via native `uuidv7()` support).
95
+ * **Configurable index lifecycle**: `auto_index` can be set at connection and overridden per model.
96
+ * **UUIDv7 compatibility gating**: jsonbadger checks support automatically and fails early with server version/capability context when unavailable.
97
+ * **Modern**: Native ESM package.
98
+
99
+ ## Documentation
100
+
101
+ * [`docs/api.md`](docs/api.md)
102
+ * [`docs/examples.md`](docs/examples.md) (complete example cheat sheet for queries, updates, and runtime document methods)
103
+ * [`docs/query-translation.md`](docs/query-translation.md) (includes PostgreSQL capability map for query/update operators and indexability expectations)
104
+ * [`docs/local-integration-testing.md`](docs/local-integration-testing.md)
105
+ * [`CHANGELOG.md`](CHANGELOG.md) for release notes and version history
106
+
107
+ ## Author
108
+
109
+ **Carlos López**
110
+ * GitHub: [@carlosjln](https://github.com/carlosjln)
111
+
112
+ ## License
113
+
114
+ MIT. See `LICENSE`.
package/docs/api.md ADDED
@@ -0,0 +1,152 @@
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.