jsonbadger 0.6.0 → 0.6.2

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # JsonBadger
2
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.
3
+ JsonBadger is a Node.js library for working with complex JSON data in PostgreSQL through a schema-driven model API. Instead of building nested JSONB queries by hand, you define schemas and use document-style models, query operators, and persistence helpers.
4
4
 
5
5
  ## Why did I create this utility?
6
6
 
@@ -36,8 +36,7 @@ const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
36
36
  const options = {
37
37
  debug: false,
38
38
  max: 10,
39
- ssl: false,
40
- auto_index: true
39
+ ssl: false
41
40
  };
42
41
  const connection = await JsonBadger.connect(db_uri, options);
43
42
 
@@ -48,7 +47,8 @@ const user_schema = new JsonBadger.Schema({
48
47
  type: {type: String},
49
48
  email: {type: String, index: {unique: true, name: 'idx_users_email_unique'}}
50
49
  }, {
51
- id_strategy: JsonBadger.IdStrategies.bigserial
50
+ id_strategy: JsonBadger.ID_STRATEGY.bigserial,
51
+ auto_index: true
52
52
  });
53
53
 
54
54
  // 3. Declare indexes on schema (path-level + schema-level compound index)
@@ -59,16 +59,15 @@ user_schema.create_index({using: 'btree', paths: {name: 1, type: -1}});
59
59
  const User = connection.model({
60
60
  name: 'User',
61
61
  schema: user_schema,
62
- table_name: 'users',
63
- auto_index: false // optional model-level override
62
+ table_name: 'users'
64
63
  });
65
64
 
66
- // 5. Run migrations manually (optional if you rely on first-write auto table creation via save()/update_one())
65
+ // 5. Run migrations manually
67
66
  await User.ensure_table();
68
- await User.ensure_index();
67
+ await User.ensure_indexes();
69
68
 
70
69
  // 6. Save a document
71
- const saved_user = await new User({
70
+ const saved_user = await User.from({
72
71
  name: 'john',
73
72
  age: 30,
74
73
  type: 'admin'
@@ -84,7 +83,7 @@ const found_user = await User.find_one({
84
83
  // found_user?.to_json() -> { id, name, age, type, created_at, updated_at }
85
84
  ```
86
85
 
87
- When using `IdStrategies.uuidv7`, declare it on the schema. JsonBadger checks PostgreSQL native `uuidv7()` support automatically during `Model.ensure_table()` using capability data captured at `connect(...)`. You do not need to run manual version checks; `SELECT version();` and `SHOW server_version;` are optional troubleshooting commands only.
86
+ When using `ID_STRATEGY.uuidv7`, declare it on the schema. JsonBadger checks support automatically during `Model.ensure_table()` using capability data captured at `connect(...)`.
88
87
 
89
88
  ## Examples Cheat Sheet
90
89
 
@@ -96,15 +95,15 @@ For the document state flow from `new Model(...)` through `save()`, hydration, d
96
95
 
97
96
  * **JSONB-first**: Model API designed specifically for PostgreSQL JSONB.
98
97
  * **Validation**: FieldType-based schema validation built in.
99
- * **Querying**: Mongo-style query operators (e.g., `$gt`, `$regex`) compiled into SQL.
98
+ * **Querying**: Mongo-style query operators such as `$gt`, `$regex`, `$contains`, and `$elem_match`.
100
99
  * **Runtime document APIs**: `get`, `set`, alias virtuals, serialization helpers, `mark_modified`/dirty tracking, and immutable enforcement after save.
101
- * **JSON update operators**: PostgreSQL-native `jsonb_set`, `jsonb_insert`, and `jsonb_set_lax` support in `update_one(...)`.
102
- * **Migrations**: Helpers for `ensure_table` and `ensure_index`.
103
- * **Configurable row IDs**: use `IdStrategies.bigserial` and `IdStrategies.uuidv7` (`uuidv7` is generated by PostgreSQL via native `uuidv7()` support).
100
+ * **JSON updates**: `update_one(...)` supports implicit keys, `$set`, `$unset`, and `$replace_roots`.
101
+ * **Migrations**: Helpers for `ensure_table`, `ensure_indexes`, and `ensure_model`.
102
+ * **Configurable row IDs**: use `ID_STRATEGY.bigserial` and `ID_STRATEGY.uuidv7`.
104
103
  * **ID create semantics**: for `uuidv7`, caller-provided `id` is used on create when present; otherwise PostgreSQL generates it. For `bigserial`, create ignores caller-provided `id` and PostgreSQL generates it.
105
104
  * **Timestamp helper semantics**: `created_at` and `updated_at` are helper fields. Create keeps caller values when provided, otherwise auto-fills both. Updates keep caller `updated_at` when provided, otherwise auto-refresh `updated_at`.
106
- * **Configurable index lifecycle**: `auto_index` can be set at connection and overridden per model.
107
- * **UUIDv7 compatibility gating**: JsonBadger checks support automatically and fails early with server version/capability context when unavailable.
105
+ * **Configurable index lifecycle**: `auto_index` is a schema option that controls whether `ensure_model()` also ensures indexes.
106
+ * **UUIDv7 compatibility checks**: JsonBadger checks support automatically and fails early when unavailable.
108
107
  * **Document instance returns**: data-returning methods return document instances with top-level base fields (`id`, `created_at`, `updated_at`), and `.to_json()` / `.$serialize()` for plain-object snapshots.
109
108
  * **Modern**: Native ESM package.
110
109
 
@@ -118,7 +117,7 @@ For the document state flow from `new Model(...)` through `save()`, hydration, d
118
117
  * [`docs/api/field-types.md`](docs/api/field-types.md)
119
118
  * [`docs/examples.md`](docs/examples.md) (complete example cheat sheet for queries, updates, and runtime document methods)
120
119
  * [`docs/lifecycle.md`](docs/lifecycle.md) (document phases, hydration/save flow, dirty tracking, and serialization)
121
- * [`docs/query-translation.md`](docs/query-translation.md) (includes PostgreSQL capability map for query/update operators and indexability expectations)
120
+ * [`docs/advanced/query-translation.md`](docs/advanced/query-translation.md) (advanced PostgreSQL reference for operators and query behavior)
122
121
  * [`docs/local-integration-testing.md`](docs/local-integration-testing.md)
123
122
  * [`CHANGELOG.md`](CHANGELOG.md) for release notes and version history
124
123
 
@@ -1,6 +1,6 @@
1
1
  # Architecture Flow
2
2
 
3
- This page shows the current JsonBadger runtime flow as ASCII diagrams.
3
+ Use this page for architecture review and contributor work. If you only want to use the library, start with [`../examples.md`](../examples.md).
4
4
 
5
5
  ## 1. Bootstrap
6
6
 
@@ -1,10 +1,12 @@
1
1
  # JSONB Update Pipeline
2
2
 
3
- This document describes the current JSONB update flow.
3
+ This page is a maintainer reference for the JSONB update flow.
4
+
5
+ If you only want to use `update_one(...)`, use [`../examples.md`](../examples.md) instead.
4
6
 
5
7
  ## Example
6
8
 
7
- This page describes the internal JSONB operator layer after `Model.update_one(...)` input has already been normalized.
9
+ This page describes the lower-level JSONB operator layer after `Model.update_one(...)` input has already been normalized.
8
10
 
9
11
  ```js
10
12
  const payload = {
@@ -1,6 +1,8 @@
1
1
  # query translation
2
2
 
3
- This project compiles Mongo-like filters into PostgreSQL SQL over JSONB data.
3
+ Advanced reference for people who want to understand how JsonBadger queries relate to PostgreSQL JSONB features.
4
+
5
+ If you only want to use the library, start with [`../examples.md`](../examples.md) and [`../api/query-builder.md`](../api/query-builder.md).
4
6
 
5
7
  ## base behavior
6
8
 
@@ -44,17 +46,18 @@ Top-level semantics note:
44
46
  - `$json_path_exists` -> `@?` with a `::jsonpath` parameter
45
47
  - `$json_path_match` -> `@@` with a `::jsonpath` parameter
46
48
  - invalid/empty JSONPath values fail before SQL execution
49
+ - these operators require native PostgreSQL JSONPath support
47
50
 
48
51
  ## JSON update operators
49
52
 
50
53
  - `$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)`
54
+ - `$unset` -> `target #- path`
55
+ - `$replace_roots` -> replace the full JSONB target
56
+ - implicit top-level keys are normalized into `$set`
53
57
 
54
58
  Update path behavior:
55
59
  - 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.
60
+ - Tracker-delta shapes (`replace_roots`, `set`, `unset`) are normalized into operator-style buckets before SQL generation.
58
61
 
59
62
  ## regex
60
63
 
@@ -70,7 +73,7 @@ Update path behavior:
70
73
 
71
74
  ## PostgreSQL capability map
72
75
 
73
- This table is the implementation-facing capability map for currently supported query/update operators over JSONB.
76
+ This table is an advanced PostgreSQL reference for the currently supported query and update operators.
74
77
 
75
78
  | JsonBadger feature | PostgreSQL operator/function | Notes | Indexability expectation |
76
79
  | --- | --- | --- | --- |
@@ -85,11 +88,11 @@ This table is the implementation-facing capability map for currently supported q
85
88
  | `$has_key` | `?` | Top-level key existence on the left JSONB value | GIN on JSONB value is the expected index family |
86
89
  | `$has_any_keys` | `?|` | Any-key existence | GIN on JSONB value is the expected index family |
87
90
  | `$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 |
91
+ | `$json_path_exists` | `@?` | JSONPath existence predicate; requires native PostgreSQL JSONPath support | Can benefit from JSONB GIN indexing depending on operator class/query shape |
92
+ | `$json_path_match` | `@@` | JSONPath predicate match; requires native PostgreSQL JSONPath support | Can benefit from JSONB GIN indexing depending on operator class/query shape |
90
93
  | `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) |
94
+ | `update_one.$unset` | `#-` | Removes one JSON path from the target | N/A (write-path function) |
95
+ | `update_one.$replace_roots` | direct JSONB replacement | Replaces the full JSONB root before later operations | N/A (write-path function) |
93
96
 
94
97
  Index helper behavior:
95
98
  - `schema.create_index({using: 'gin', path: 'profile.city'})` creates a GIN index on the extracted JSONB path expression.
@@ -22,7 +22,6 @@ const connection = await JsonBadger.connect(uri, options);
22
22
 
23
23
  - `max`: pool size
24
24
  - `debug`: logs SQL/debug events when `true`
25
- - `auto_index`: server-wide default for automatic index creation when `Model.ensure_table()` is called
26
25
  - any supported `pg` pool options (`ssl`, `host`, `port`, `user`, `password`, `database`)
27
26
 
28
27
  ## Connection Lifecycle
@@ -39,7 +38,7 @@ await connection.disconnect();
39
38
  - `connect(...)` performs a PostgreSQL capability scan and caches the result
40
39
  - later schema-level `uuidv7` selections are checked against the cached capability info during `Model.ensure_table()`
41
40
 
42
- > **Note:** Troubleshooting SQL like `SELECT version();` and `SHOW server_version;` is optional. Runtime checks are machine-readable and internal.
41
+ > **Note:** You do not need to run manual version checks during normal usage. JsonBadger performs the compatibility checks it needs automatically.
43
42
 
44
43
  ## Connection Reuse Pattern
45
44
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  Most app code should go through `Model.from(...)`, `Model.hydrate(...)`, and model instance methods such as `doc.get(...)`, `doc.set(...)`, and `doc.save()`.
6
6
 
7
- Use `Document` directly when you are working on runtime internals, custom persistence flows, or when you need to adopt trusted state through `doc.rebase(...)`.
7
+ Use `Document` directly only for advanced or framework-level code, such as custom persistence flows or adopting trusted state through `doc.rebase(...)`.
8
8
 
9
9
  ## TOC
10
10
 
@@ -249,7 +249,7 @@ const schema = new Schema({
249
249
 
250
250
  ## Introspection
251
251
 
252
- Use `schema.get_path(path_name)` to inspect one compiled field type:
252
+ Use `schema.get_path(path_name)` to inspect one resolved field type:
253
253
 
254
254
  ```js
255
255
  const schema = new Schema({
package/docs/api/index.md CHANGED
@@ -32,4 +32,4 @@ Default export (`JsonBadger`) includes the same plus:
32
32
  - [`model.md`](model.md)
33
33
  - [`query-builder.md`](query-builder.md)
34
34
  - [`field-types.md`](field-types.md)
35
- - [`../query-translation.md`](../query-translation.md)
35
+ - [`../advanced/query-translation.md`](../advanced/query-translation.md)
@@ -47,7 +47,9 @@ Read/query execution:
47
47
  - JSONB key existence: `$has_key`, `$has_any_keys`, `$has_all_keys`
48
48
  - JSONPath: `$json_path_exists`, `$json_path_match`
49
49
 
50
- See [`../query-translation.md`](../query-translation.md) for PostgreSQL operator/function mapping.
50
+ > **Note:** Use `$json_path_exists` and `$json_path_match` only when your PostgreSQL version supports native JSONPath queries.
51
+
52
+ See [`../advanced/query-translation.md`](../advanced/query-translation.md) when you want the advanced PostgreSQL reference behind these operators.
51
53
 
52
54
  ## Base-field Query Rules
53
55
 
@@ -67,15 +69,13 @@ Operator matrix:
67
69
 
68
70
  ## JSON Key Existence
69
71
 
70
- - `$has_key` maps to PostgreSQL `?`
71
- - `$has_any_keys` maps to PostgreSQL `?|`
72
- - `$has_all_keys` maps to PostgreSQL `?&`
72
+ - `$has_key` checks whether one key is present
73
+ - `$has_any_keys` checks whether any key from a list is present
74
+ - `$has_all_keys` checks whether all keys from a list are present
73
75
 
74
76
  When used with a nested field path, JsonBadger extracts that nested JSONB value first, then applies the existence operator to the extracted value.
75
77
 
76
78
  ## JSONPath
77
79
 
78
- - `$json_path_exists` maps to `@?`
79
- - `$json_path_match` maps to `@@`
80
-
81
- JsonBadger binds the JSONPath string as a `::jsonpath` parameter.
80
+ - `$json_path_exists` checks whether a JSONPath query finds a match
81
+ - `$json_path_match` checks whether a JSONPath predicate matches
package/docs/examples.md CHANGED
@@ -4,7 +4,7 @@ JsonBadger is a PostgreSQL-backed document mapper for working with JSONB data th
4
4
 
5
5
  This page is an example-first cheat sheet for users and AI agents. It shows copy-pasteable JsonBadger usage in increasing complexity and covers the currently implemented query and update operator surface.
6
6
 
7
- Use this page for syntax and working shapes. Use [`docs/api/index.md`](api/index.md) for the module API map and [`docs/query-translation.md`](query-translation.md) for PostgreSQL operator/function semantics.
7
+ Use this page for syntax and working shapes. Use [`docs/api/index.md`](api/index.md) for the module API map and [`docs/advanced/query-translation.md`](advanced/query-translation.md) only when you want the advanced PostgreSQL reference behind the query surface.
8
8
  Use [`docs/lifecycle.md`](lifecycle.md) when you need the document phase map instead of operator syntax.
9
9
 
10
10
  ## How to Read This Page
@@ -69,10 +69,10 @@ When a snippet uses a different value (for example `name: 'jane'`), either seed
69
69
  - Query/sort support for base fields is top-level only (no dotted base-field paths like `created_at.value`).
70
70
 
71
71
  `id` behavior:
72
- - Create with `IdStrategies.uuidv7`:
72
+ - Create with `ID_STRATEGY.uuidv7`:
73
73
  - caller-provided `id` is used.
74
74
  - if `id` is omitted, PostgreSQL default `uuidv7()` generates it.
75
- - Create with `IdStrategies.bigserial`:
75
+ - Create with `ID_STRATEGY.bigserial`:
76
76
  - caller-provided `id` is ignored silently.
77
77
  - PostgreSQL sequence generates it.
78
78
  - Update paths cannot mutate `id`.
@@ -88,7 +88,7 @@ Timestamp helper behavior:
88
88
 
89
89
  ## Setup and Connect
90
90
 
91
- > Important: `IdStrategies.uuidv7` requires native PostgreSQL `uuidv7()` support (PostgreSQL 18+).
91
+ > Important: `ID_STRATEGY.uuidv7` requires native PostgreSQL `uuidv7()` support (PostgreSQL 18+).
92
92
 
93
93
  ```js
94
94
  import JsonBadger from 'jsonbadger';
@@ -97,27 +97,27 @@ const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
97
97
  const options = {
98
98
  debug: false,
99
99
  max: 10,
100
- ssl: false,
101
- auto_index: true,
102
- id_strategy: JsonBadger.IdStrategies.bigserial
100
+ ssl: false
103
101
  };
104
102
 
105
103
  const connection = await JsonBadger.connect(db_uri, options);
106
104
  ```
107
105
 
108
- UUIDv7 server default (PostgreSQL 18+ native `uuidv7()` required):
106
+ Schema-level UUIDv7 selection (PostgreSQL 18+ native `uuidv7()` required):
109
107
 
110
108
  ```js
111
109
  const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
112
- const options = {
113
- id_strategy: JsonBadger.IdStrategies.uuidv7
114
- };
110
+ const connection = await JsonBadger.connect(db_uri);
115
111
 
116
- const connection = await JsonBadger.connect(db_uri, options);
112
+ const event_schema = new JsonBadger.Schema({
113
+ name: String
114
+ }, {
115
+ id_strategy: JsonBadger.ID_STRATEGY.uuidv7
116
+ });
117
117
  ```
118
118
 
119
119
  Notes:
120
- - JsonBadger checks native `uuidv7()` support automatically during `connect(...)` and caches the capability result.
120
+ - JsonBadger checks native `uuidv7()` support automatically during `Model.ensure_table()` using the capability snapshot captured at `connect(...)`.
121
121
  - Run `ensure_table()` / `ensure_indexes()` during startup/bootstrap before normal runtime operations, or use `ensure_model()` when you want the combined path.
122
122
 
123
123
  Teardown example:
@@ -150,7 +150,7 @@ const AuditLog = connection.model({
150
150
  schema: new JsonBadger.Schema({
151
151
  event_name: String
152
152
  }, {
153
- id_strategy: JsonBadger.IdStrategies.uuidv7 // PostgreSQL 18+ native uuidv7() required
153
+ id_strategy: JsonBadger.ID_STRATEGY.uuidv7 // PostgreSQL 18+ native uuidv7() required
154
154
  }),
155
155
  table_name: 'audit_logs'
156
156
  });
@@ -160,15 +160,15 @@ const Counter = connection.model({
160
160
  schema: new JsonBadger.Schema({
161
161
  label: String
162
162
  }, {
163
- id_strategy: JsonBadger.IdStrategies.bigserial // schema override
163
+ id_strategy: JsonBadger.ID_STRATEGY.bigserial // schema override
164
164
  }),
165
165
  table_name: 'counters'
166
166
  });
167
167
  ```
168
168
 
169
169
  Notes:
170
- - `id_strategy` lives on the schema. If omitted, the library default is `bigserial`.
171
- - `IdStrategies.uuidv7` uses database-generated IDs (`DEFAULT uuidv7()`) and JsonBadger validates support internally.
170
+ - `id_strategy` lives on the schema. If omitted, the library default is `uuidv7`.
171
+ - `ID_STRATEGY.uuidv7` uses database-generated IDs and JsonBadger validates support automatically.
172
172
 
173
173
  Create-time `id` behavior example:
174
174
 
@@ -205,6 +205,8 @@ const user_schema = new JsonBadger.Schema({
205
205
  },
206
206
  orders: [{sku: String, qty: Number, price: Number}],
207
207
  payload: {}
208
+ }, {
209
+ id_strategy: JsonBadger.ID_STRATEGY.bigserial
208
210
  });
209
211
 
210
212
  // Schema-level indexes (single path and compound)
@@ -215,9 +217,7 @@ const connection = await JsonBadger.connect(db_uri, options);
215
217
  const User = connection.model({
216
218
  name: 'User',
217
219
  schema: user_schema,
218
- table_name: 'users',
219
- auto_index: true,
220
- id_strategy: JsonBadger.IdStrategies.bigserial
220
+ table_name: 'users'
221
221
  });
222
222
  ```
223
223
 
@@ -274,7 +274,7 @@ Quick FieldType reference:
274
274
 
275
275
  Edge cases and practical notes:
276
276
  - In-place changes to nested values under `doc.document[...]` bypass `doc.set(...)`; prefer `doc.set(...)` when you want assignment-time casting and setter logic.
277
- - Arrays default to `[]`; set `default: undefined` to disable the implicit empty-array default.
277
+ - Arrays default to `null` unless you explicitly declare a default such as `default: []`.
278
278
  - For `Map`-like paths, prefer `document.set('handles.github', 'name')` so casting and dirty tracking run.
279
279
 
280
280
  ## Create and Save Documents
@@ -419,10 +419,10 @@ const UniqueUser = connection.model({
419
419
  await UniqueUser.ensure_table();
420
420
  await UniqueUser.ensure_indexes();
421
421
 
422
- await new UniqueUser({email: 'john@example.com'}).save();
422
+ await UniqueUser.from({email: 'john@example.com'}).save();
423
423
 
424
424
  try {
425
- await new UniqueUser({email: 'john@example.com'}).save();
425
+ await UniqueUser.from({email: 'john@example.com'}).save();
426
426
  } catch(error) {
427
427
  if(error.name === 'query_error') {
428
428
  const error_payload = error.to_json();
@@ -596,6 +596,8 @@ await User.find({payload: {$has_key: 'profile.city'}}).exec();
596
596
 
597
597
  `$json_path_exists` (`@?`) and `$json_path_match` (`@@`):
598
598
 
599
+ > **Note:** Use these operators only when your PostgreSQL version supports native JSONPath queries.
600
+
599
601
  Assumes:
600
602
  - Your seeded `payload` includes shapes like `items[*].qty` and `score` (as shown in `## Create and Save Documents`).
601
603
 
@@ -618,7 +620,7 @@ Expected behavior:
618
620
 
619
621
  ## Update Operators (`update_one`)
620
622
 
621
- `update_one(...)` supports `$set`, `$insert`, and `$set_lax`.
623
+ `update_one(...)` supports `$set`, `$unset`, `$replace_roots`, plus implicit top-level keys that are routed into `$set`.
622
624
 
623
625
  Assumes (for update and delete sections below):
624
626
  - A matching row exists (examples use `name: 'john'` and `name: 'missing'`).
@@ -627,7 +629,7 @@ Assumes (for update and delete sections below):
627
629
  Target shape reminder:
628
630
  - `tags` is an array, `profile` is an object, and `payload.profile` / `payload.score` are nested JSON values in the seeded row.
629
631
 
630
- Basic `$set` (maps to `jsonb_set(...)`):
632
+ Use `$set` to assign or replace one or more JSON values:
631
633
 
632
634
  ```js
633
635
  const updated_user = await User.update_one(
@@ -645,86 +647,55 @@ const updated_user = await User.update_one(
645
647
  Returns: `updated_user` is `User | null`.
646
648
  Snapshot shape: `updated_user?.document` -> `{ id, data, created_at, updated_at }` when the default slug is still `data`.
647
649
 
648
- `$insert` (maps to `jsonb_insert(...)`) with numeric array index paths:
650
+ Use `$unset` to remove one or more JSON paths:
649
651
 
650
652
  ```js
651
653
  await User.update_one({name: 'john'}, {
652
- $insert: {
653
- 'tags.0': 'first_tag'
654
- }
655
- });
656
-
657
- await User.update_one({name: 'john'}, {
658
- $insert: {
659
- 'tags.0': {
660
- value: 'after_first',
661
- insert_after: true
662
- }
663
- }
654
+ $unset: [
655
+ 'payload.cleanup_flag',
656
+ 'profile.country'
657
+ ]
664
658
  });
665
659
  ```
666
660
 
667
- `$set_lax` (maps to `jsonb_set_lax(...)`) object form:
668
-
669
- Target shape reminder for `$set_lax`:
670
- - `payload` is a JSON object; these examples update or delete nested keys inside `payload`.
661
+ `$replace_roots` (replaces the full JSONB document):
671
662
 
672
663
  ```js
673
664
  await User.update_one({name: 'john'}, {
674
- $set_lax: {
675
- 'payload.cleanup_flag': {
676
- value: null,
677
- null_value_treatment: 'delete_key'
678
- }
665
+ $replace_roots: {
666
+ name: 'john',
667
+ age: 31,
668
+ status: 'active',
669
+ profile: {city: 'Orlando'}
679
670
  }
680
671
  });
681
672
  ```
682
673
 
683
- `$set_lax` options example (`create_if_missing`, `null_value_treatment`):
674
+ Implicit top-level keys are routed into `$set`:
684
675
 
685
676
  ```js
686
677
  await User.update_one({name: 'john'}, {
687
- $set_lax: {
688
- 'payload.archived_at': {
689
- value: null,
690
- create_if_missing: false,
691
- null_value_treatment: 'return_target'
692
- }
693
- }
678
+ age: 32,
679
+ 'profile.city': 'Tampa'
694
680
  });
695
681
  ```
696
682
 
697
- Multiple update operators in one call (application order is `$set` -> `$insert` -> `$set_lax`):
683
+ Multiple update operators in one call (application order is `$replace_roots` -> `$unset` -> `$set`):
698
684
 
699
685
  ```js
700
686
  await User.update_one({name: 'john'}, {
701
- $set: {
702
- 'payload.score': 50
703
- },
704
- $insert: {
705
- 'tags.0': {value: 'vip', insert_after: true}
687
+ $replace_roots: {
688
+ name: 'john',
689
+ status: 'active',
690
+ profile: {city: 'Miami'}
706
691
  },
707
- $set_lax: {
708
- 'payload.cleanup_flag': {
709
- value: null,
710
- null_value_treatment: 'delete_key'
711
- }
712
- }
713
- });
714
- ```
715
-
716
- Conflict rule (rejected before SQL):
717
-
718
- ```js
719
- await User.update_one({name: 'john'}, {
692
+ $unset: [
693
+ 'profile.country'
694
+ ],
720
695
  $set: {
721
- payload: {nested: true}
722
- },
723
- $set_lax: {
724
- 'payload.value': 'ok'
696
+ 'payload.score': 50
725
697
  }
726
698
  });
727
- // throws: conflicting update paths (same path or parent/child overlap)
728
699
  ```
729
700
 
730
701
  Timestamp helper examples on update:
@@ -875,7 +846,7 @@ For the full lifecycle contract, see [`docs/lifecycle.md`](lifecycle.md).
875
846
  | JSON containment | `$contains` |
876
847
  | JSONB key existence | `$has_key`, `$has_any_keys`, `$has_all_keys` |
877
848
  | JSONPath | `$json_path_exists`, `$json_path_match` |
878
- | Updates (`update_one`) | `$set`, `$insert`, `$set_lax` |
849
+ | Updates (`update_one`) | implicit keys, `$set`, `$unset`, `$replace_roots` |
879
850
 
880
851
  ## Related Docs
881
852
 
@@ -885,5 +856,5 @@ For the full lifecycle contract, see [`docs/lifecycle.md`](lifecycle.md).
885
856
  - [`docs/api/schema.md`](api/schema.md) (schema API and index declaration rules)
886
857
  - [`docs/api/query-builder.md`](api/query-builder.md) (query builder chain and filter families)
887
858
  - [`docs/lifecycle.md`](lifecycle.md) (document phases, hydration/save flow, dirty tracking, and serialization)
888
- - [`docs/query-translation.md`](query-translation.md) (PostgreSQL operator/function mapping)
859
+ - [`docs/advanced/query-translation.md`](advanced/query-translation.md) (advanced PostgreSQL reference)
889
860
  - [`docs/local-integration-testing.md`](local-integration-testing.md) (local integration test setup)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsonbadger",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "JSONB query/model library for PostgreSQL",
5
5
  "main": "index.js",
6
6
  "type": "module",