jsonbadger 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -18
- package/docs/api/connection.md +144 -0
- package/docs/api/delta-tracker.md +106 -0
- package/docs/api/document.md +77 -0
- package/docs/api/field-types.md +329 -0
- package/docs/api/index.md +35 -0
- package/docs/api/model.md +392 -0
- package/docs/api/query-builder.md +81 -0
- package/docs/api/schema.md +204 -0
- package/docs/architecture-flow.md +397 -0
- package/docs/examples.md +495 -218
- package/docs/jsonb-ops.md +171 -0
- package/docs/lifecycle/model-compilation.md +111 -0
- package/docs/lifecycle.md +146 -0
- package/docs/query-translation.md +11 -10
- package/package.json +10 -3
- package/src/connection/connect.js +12 -17
- package/src/connection/connection.js +128 -0
- package/src/connection/server-capabilities.js +60 -59
- package/src/constants/defaults.js +32 -19
- package/src/constants/{id-strategies.js → id-strategy.js} +28 -29
- package/src/constants/intake-mode.js +8 -0
- package/src/debug/debug-logger.js +17 -15
- package/src/errors/model-overwrite-error.js +25 -0
- package/src/errors/query-error.js +25 -23
- package/src/errors/validation-error.js +25 -23
- package/src/field-types/base-field-type.js +137 -140
- package/src/field-types/builtins/advanced.js +365 -365
- package/src/field-types/builtins/index.js +579 -585
- package/src/field-types/field-type-namespace.js +9 -0
- package/src/field-types/registry.js +149 -122
- package/src/index.js +26 -36
- package/src/migration/ensure-index.js +157 -154
- package/src/migration/ensure-schema.js +27 -15
- package/src/migration/ensure-table.js +44 -31
- package/src/migration/schema-indexes-resolver.js +8 -6
- package/src/model/document-instance.js +29 -540
- package/src/model/document.js +60 -0
- package/src/model/factory/constants.js +36 -0
- package/src/model/factory/index.js +58 -0
- package/src/model/model.js +875 -0
- package/src/model/operations/delete-one.js +39 -0
- package/src/model/operations/insert-one.js +35 -0
- package/src/model/operations/query-builder.js +132 -0
- package/src/model/operations/update-one.js +333 -0
- package/src/model/state.js +34 -0
- package/src/schema/field-definition-parser.js +213 -218
- package/src/schema/path-introspection.js +87 -82
- package/src/schema/schema-compiler.js +126 -212
- package/src/schema/schema.js +621 -138
- package/src/sql/index.js +17 -0
- package/src/sql/jsonb/ops.js +153 -0
- package/src/{query → sql/jsonb}/path-parser.js +54 -43
- package/src/sql/jsonb/read/elem-match.js +133 -0
- package/src/{query → sql/jsonb/read}/operators/contains.js +13 -7
- package/src/sql/jsonb/read/operators/elem-match.js +9 -0
- package/src/{query → sql/jsonb/read}/operators/has-all-keys.js +17 -11
- package/src/{query → sql/jsonb/read}/operators/has-any-keys.js +18 -11
- package/src/sql/jsonb/read/operators/has-key.js +12 -0
- package/src/{query → sql/jsonb/read}/operators/jsonpath-exists.js +22 -15
- package/src/{query → sql/jsonb/read}/operators/jsonpath-match.js +22 -15
- package/src/{query → sql/jsonb/read}/operators/size.js +23 -16
- package/src/sql/parameter-binder.js +18 -13
- package/src/sql/read/build-count-query.js +12 -0
- package/src/sql/read/build-find-query.js +25 -0
- package/src/sql/read/limit-skip.js +21 -0
- package/src/sql/read/sort.js +85 -0
- package/src/sql/read/where/base-fields.js +310 -0
- package/src/sql/read/where/casting.js +90 -0
- package/src/sql/read/where/context.js +79 -0
- package/src/sql/read/where/field-clause.js +58 -0
- package/src/sql/read/where/index.js +38 -0
- package/src/sql/read/where/operator-entries.js +29 -0
- package/src/{query → sql/read/where}/operators/all.js +16 -10
- package/src/sql/read/where/operators/eq.js +12 -0
- package/src/{query → sql/read/where}/operators/gt.js +23 -16
- package/src/{query → sql/read/where}/operators/gte.js +23 -16
- package/src/{query → sql/read/where}/operators/in.js +18 -12
- package/src/sql/read/where/operators/index.js +40 -0
- package/src/{query → sql/read/where}/operators/lt.js +23 -16
- package/src/{query → sql/read/where}/operators/lte.js +23 -16
- package/src/sql/read/where/operators/ne.js +12 -0
- package/src/{query → sql/read/where}/operators/nin.js +18 -12
- package/src/{query → sql/read/where}/operators/regex.js +14 -8
- package/src/sql/read/where/operators.js +126 -0
- package/src/sql/read/where/text-operators.js +83 -0
- package/src/sql/run.js +46 -0
- package/src/sql/write/build-delete-query.js +33 -0
- package/src/sql/write/build-insert-query.js +42 -0
- package/src/sql/write/build-update-query.js +65 -0
- package/src/utils/assert.js +34 -27
- package/src/utils/delta-tracker/.archive/1 tracker-redesign-codex-v2.md +250 -0
- package/src/utils/delta-tracker/.archive/1 tracker-redesign-gemini.md +101 -0
- package/src/utils/delta-tracker/.archive/2 evaluation by gemini.txt +65 -0
- package/src/utils/delta-tracker/.archive/2 evaluation by grok.txt +39 -0
- package/src/utils/delta-tracker/.archive/3 gemini evaluate grok.txt +37 -0
- package/src/utils/delta-tracker/.archive/3 grok evaluate gemini.txt +63 -0
- package/src/utils/delta-tracker/.archive/4 gemini veredict.txt +16 -0
- package/src/utils/delta-tracker/.archive/index.1.js +587 -0
- package/src/utils/delta-tracker/.archive/index.2.js +612 -0
- package/src/utils/delta-tracker/index.js +592 -0
- package/src/utils/dirty-tracker/inline.js +335 -0
- package/src/utils/dirty-tracker/instance.js +414 -0
- package/src/utils/dirty-tracker/static.js +343 -0
- package/src/utils/json-safe.js +13 -9
- package/src/utils/object-path.js +227 -33
- package/src/utils/object.js +408 -168
- package/src/utils/string.js +55 -0
- package/src/utils/value.js +169 -30
- package/docs/api.md +0 -152
- package/src/connection/disconnect.js +0 -16
- package/src/connection/pool-store.js +0 -46
- package/src/model/model-factory.js +0 -555
- package/src/query/limit-skip-compiler.js +0 -31
- package/src/query/operators/elem-match.js +0 -3
- package/src/query/operators/eq.js +0 -6
- package/src/query/operators/has-key.js +0 -6
- package/src/query/operators/index.js +0 -60
- package/src/query/operators/ne.js +0 -6
- package/src/query/query-builder.js +0 -93
- package/src/query/sort-compiler.js +0 -30
- package/src/query/where-compiler.js +0 -477
- package/src/sql/sql-runner.js +0 -31
package/docs/examples.md
CHANGED
|
@@ -1,110 +1,201 @@
|
|
|
1
|
-
#
|
|
1
|
+
# JsonBadger examples cheat sheet
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JsonBadger is a PostgreSQL-backed document mapper for working with JSONB data through a model/document API.
|
|
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
|
+
|
|
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.
|
|
8
|
+
Use [`docs/lifecycle.md`](lifecycle.md) when you need the document phase map instead of operator syntax.
|
|
6
9
|
|
|
7
10
|
## How to Read This Page
|
|
8
11
|
|
|
9
12
|
- Examples are ordered from setup -> model usage -> queries -> updates -> runtime behavior.
|
|
10
13
|
- Snippets reuse a `User` model shape where possible.
|
|
11
|
-
- Query operators use the current `$` + `snake_case` naming (for example `$elem_match`, `$has_key`, `$json_path_exists`).
|
|
12
|
-
- This page only includes currently implemented behavior.
|
|
13
|
-
- `Assumes:` notes tell you what earlier setup/data a snippet depends on.
|
|
14
|
-
- Important mechanics: queries run when you call `.exec()` (for example `await User.find({}).exec()`), and direct in-place mutations on
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
14
|
+
- Query operators use the current `$` + `snake_case` naming (for example `$elem_match`, `$has_key`, `$json_path_exists`).
|
|
15
|
+
- This page only includes currently implemented behavior.
|
|
16
|
+
- `Assumes:` notes tell you what earlier setup/data a snippet depends on.
|
|
17
|
+
- Important mechanics: queries run when you call `.exec()` (for example `await User.find({}).exec()`), and direct in-place mutations on `doc.document[...]` bypass `doc.set(...)` and assignment-time casting.
|
|
18
|
+
- Operation scope: this page covers `create`, `save`, `find`, `find_one`, `find_by_id`, `count_documents`, `update_one`, and `delete_one`. Bulk helpers (`insert_many`, `update_many`, `delete_many`) are not part of the current API surface.
|
|
19
|
+
|
|
20
|
+
Quick jump:
|
|
21
|
+
|
|
22
|
+
Setup
|
|
23
|
+
- [How to Read This Page](#how-to-read-this-page)
|
|
24
|
+
- [Shared Fixture and Assumptions](#shared-fixture-and-assumptions)
|
|
25
|
+
- [Reserved Base Fields](#reserved-base-fields)
|
|
26
|
+
- [Setup and Connect](#setup-and-connect)
|
|
27
|
+
- [ID Strategy Examples](#id-strategy-examples)
|
|
28
|
+
- [Schema and Model Definition](#schema-and-model-definition)
|
|
29
|
+
- [Built-in FieldType Examples](#built-in-fieldtype-examples)
|
|
30
|
+
- [Create and Save Documents](#create-and-save-documents)
|
|
31
|
+
|
|
32
|
+
Queries
|
|
33
|
+
- [Query Basics (find, find_one, count_documents)](#query-basics-find-find_one-count_documents)
|
|
34
|
+
- [Direct Equality and Scalar Comparisons](#direct-equality-and-scalar-comparisons)
|
|
35
|
+
- [Regex Operators](#regex-operators)
|
|
36
|
+
- [Array and JSON Query Operators](#array-and-json-query-operators)
|
|
37
|
+
- [JSONB Key Existence Operators](#jsonb-key-existence-operators)
|
|
38
|
+
- [JSONPath Operators](#jsonpath-operators)
|
|
39
|
+
|
|
40
|
+
Mutations
|
|
41
|
+
- [Update Operators (`update_one`)](#update-operators-update_one)
|
|
42
|
+
- [Delete Operations](#delete-operations)
|
|
43
|
+
|
|
44
|
+
Advanced
|
|
45
|
+
- [Runtime Document Methods (get/set, dirty tracking, serialization)](#runtime-document-methods-getset-dirty-tracking-serialization)
|
|
46
|
+
- [Lifecycle Quick Reference](#lifecycle-quick-reference)
|
|
47
|
+
- [Optional Alias Path Example](#optional-alias-path-example)
|
|
48
|
+
- [Complete Operator Checklist (Query and Update)](#complete-operator-checklist-query-and-update)
|
|
49
|
+
- [Related Docs](#related-docs)
|
|
36
50
|
|
|
37
51
|
## Shared Fixture and Assumptions
|
|
38
52
|
|
|
39
53
|
Most snippets below are intentionally short and not fully standalone. Unless a section says otherwise, examples assume:
|
|
40
54
|
|
|
41
|
-
- You
|
|
55
|
+
- You are running ESM (`import ...`) with top-level `await` enabled.
|
|
56
|
+
- You already connected with `JsonBadger.connect(...)`.
|
|
42
57
|
- You defined the `User` model from `Schema and Model Definition`.
|
|
43
|
-
- You
|
|
58
|
+
- You already ran startup/bootstrap setup from the migration section below.
|
|
44
59
|
- You seeded at least one `User` document using the `Create and Save Documents` example (including `tags`, `orders`, and `payload`).
|
|
45
60
|
|
|
46
|
-
When a snippet uses a different value (for example `
|
|
61
|
+
When a snippet uses a different value (for example `name: 'jane'`), either seed a matching row first or replace the filter value with one that exists in your local data.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Reserved Base Fields
|
|
65
|
+
|
|
66
|
+
- `id`, `created_at`, and `updated_at` are top-level base fields returned in data-returning operations.
|
|
67
|
+
- These base fields exist in schema/runtime introspection even when omitted from user schema input.
|
|
68
|
+
- If user schema declares any of these paths, the user definition is preserved.
|
|
69
|
+
- Query/sort support for base fields is top-level only (no dotted base-field paths like `created_at.value`).
|
|
70
|
+
|
|
71
|
+
`id` behavior:
|
|
72
|
+
- Create with `IdStrategies.uuidv7`:
|
|
73
|
+
- caller-provided `id` is used.
|
|
74
|
+
- if `id` is omitted, PostgreSQL default `uuidv7()` generates it.
|
|
75
|
+
- Create with `IdStrategies.bigserial`:
|
|
76
|
+
- caller-provided `id` is ignored silently.
|
|
77
|
+
- PostgreSQL sequence generates it.
|
|
78
|
+
- Update paths cannot mutate `id`.
|
|
47
79
|
|
|
80
|
+
Timestamp helper behavior:
|
|
81
|
+
- Create:
|
|
82
|
+
- provided `created_at` / `updated_at` values are kept.
|
|
83
|
+
- omitted values are auto-filled.
|
|
84
|
+
- Update:
|
|
85
|
+
- provided `updated_at` is kept.
|
|
86
|
+
- omitted `updated_at` is auto-set.
|
|
87
|
+
- `created_at` is not auto-updated.
|
|
48
88
|
|
|
49
89
|
## Setup and Connect
|
|
50
90
|
|
|
91
|
+
> Important: `IdStrategies.uuidv7` requires native PostgreSQL `uuidv7()` support (PostgreSQL 18+).
|
|
92
|
+
|
|
51
93
|
```js
|
|
52
|
-
import
|
|
94
|
+
import JsonBadger from 'jsonbadger';
|
|
53
95
|
|
|
54
|
-
|
|
96
|
+
const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
|
|
97
|
+
const options = {
|
|
55
98
|
debug: false,
|
|
56
99
|
max: 10,
|
|
57
100
|
ssl: false,
|
|
58
101
|
auto_index: true,
|
|
59
|
-
id_strategy:
|
|
60
|
-
}
|
|
102
|
+
id_strategy: JsonBadger.IdStrategies.bigserial
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const connection = await JsonBadger.connect(db_uri, options);
|
|
61
106
|
```
|
|
62
107
|
|
|
63
108
|
UUIDv7 server default (PostgreSQL 18+ native `uuidv7()` required):
|
|
64
109
|
|
|
65
110
|
```js
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
|
|
112
|
+
const options = {
|
|
113
|
+
id_strategy: JsonBadger.IdStrategies.uuidv7
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const connection = await JsonBadger.connect(db_uri, options);
|
|
69
117
|
```
|
|
70
118
|
|
|
71
119
|
Notes:
|
|
72
|
-
-
|
|
73
|
-
-
|
|
120
|
+
- JsonBadger checks native `uuidv7()` support automatically during `connect(...)` and caches the capability result.
|
|
121
|
+
- Run `ensure_table()` / `ensure_indexes()` during startup/bootstrap before normal runtime operations, or use `ensure_model()` when you want the combined path.
|
|
74
122
|
|
|
75
|
-
|
|
123
|
+
Teardown example:
|
|
76
124
|
|
|
77
|
-
|
|
125
|
+
```js
|
|
126
|
+
await connection.disconnect();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Connection failure pattern (before a pool is established in the process):
|
|
78
130
|
|
|
79
131
|
```js
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
132
|
+
try {
|
|
133
|
+
await JsonBadger.connect('postgresql://wrong_user:wrong_pass@localhost:5432/dbname');
|
|
134
|
+
} catch(error) {
|
|
135
|
+
// connection/auth/network failures bubble directly from `pg`
|
|
136
|
+
console.error(error.message);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## ID Strategy Examples
|
|
83
141
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
142
|
+
Schema-level `id_strategy` selection:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
|
|
146
|
+
const connection = await JsonBadger.connect(db_uri);
|
|
147
|
+
|
|
148
|
+
const AuditLog = connection.model({
|
|
149
|
+
name: 'AuditLog',
|
|
150
|
+
schema: new JsonBadger.Schema({
|
|
151
|
+
event_name: String
|
|
152
|
+
}, {
|
|
153
|
+
id_strategy: JsonBadger.IdStrategies.uuidv7 // PostgreSQL 18+ native uuidv7() required
|
|
154
|
+
}),
|
|
155
|
+
table_name: 'audit_logs'
|
|
89
156
|
});
|
|
90
157
|
|
|
91
|
-
const Counter =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
158
|
+
const Counter = connection.model({
|
|
159
|
+
name: 'Counter',
|
|
160
|
+
schema: new JsonBadger.Schema({
|
|
161
|
+
label: String
|
|
162
|
+
}, {
|
|
163
|
+
id_strategy: JsonBadger.IdStrategies.bigserial // schema override
|
|
164
|
+
}),
|
|
165
|
+
table_name: 'counters'
|
|
96
166
|
});
|
|
97
167
|
```
|
|
98
168
|
|
|
99
169
|
Notes:
|
|
100
|
-
- `id_strategy`
|
|
101
|
-
- `IdStrategies.uuidv7` uses database-generated IDs (`DEFAULT uuidv7()`) and
|
|
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.
|
|
172
|
+
|
|
173
|
+
Create-time `id` behavior example:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
// uuidv7 model: pass-through when provided
|
|
177
|
+
const uuid_user = await AuditLog.create({
|
|
178
|
+
id: '0194f028-579a-7b5b-8107-b9ad31395f43',
|
|
179
|
+
event_name: 'login'
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// uuidv7 model: DB generates when omitted
|
|
183
|
+
const generated_uuid_user = await AuditLog.create({
|
|
184
|
+
event_name: 'logout'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// bigserial model: caller id is ignored, DB generates numeric id
|
|
188
|
+
const serial_counter = await Counter.create({
|
|
189
|
+
id: 999,
|
|
190
|
+
label: 'requests'
|
|
191
|
+
});
|
|
192
|
+
```
|
|
102
193
|
|
|
103
194
|
## Schema and Model Definition
|
|
104
195
|
|
|
105
196
|
```js
|
|
106
|
-
const user_schema = new
|
|
107
|
-
|
|
197
|
+
const user_schema = new JsonBadger.Schema({
|
|
198
|
+
name: {type: String, required: true, trim: true, index: true, get: (value) => value && value.toUpperCase()},
|
|
108
199
|
age: {type: Number, min: 0, index: -1},
|
|
109
200
|
status: {type: String, immutable: true},
|
|
110
201
|
tags: [String],
|
|
@@ -112,36 +203,40 @@ const user_schema = new jsonbadger.Schema({
|
|
|
112
203
|
city: String,
|
|
113
204
|
country: String
|
|
114
205
|
},
|
|
115
|
-
orders: [{sku: String, qty: Number, price: Number}],
|
|
116
|
-
payload: {}
|
|
117
|
-
});
|
|
206
|
+
orders: [{sku: String, qty: Number, price: Number}],
|
|
207
|
+
payload: {}
|
|
208
|
+
});
|
|
118
209
|
|
|
119
210
|
// Schema-level indexes (single path and compound)
|
|
120
|
-
user_schema.create_index('profile.city');
|
|
121
|
-
user_schema.create_index({
|
|
211
|
+
user_schema.create_index({using: 'gin', path: 'profile.city'});
|
|
212
|
+
user_schema.create_index({using: 'btree', paths: {name: 1, age: -1}});
|
|
122
213
|
|
|
123
|
-
const
|
|
214
|
+
const connection = await JsonBadger.connect(db_uri, options);
|
|
215
|
+
const User = connection.model({
|
|
216
|
+
name: 'User',
|
|
217
|
+
schema: user_schema,
|
|
124
218
|
table_name: 'users',
|
|
125
|
-
data_column: 'data',
|
|
126
219
|
auto_index: true,
|
|
127
|
-
id_strategy:
|
|
220
|
+
id_strategy: JsonBadger.IdStrategies.bigserial
|
|
128
221
|
});
|
|
129
222
|
```
|
|
130
223
|
|
|
131
224
|
Migration helpers (manual/explicit path):
|
|
132
225
|
|
|
133
226
|
```js
|
|
227
|
+
// Option A: run table and indexes explicitly
|
|
134
228
|
await User.ensure_table();
|
|
135
|
-
await User.
|
|
136
|
-
|
|
137
|
-
|
|
229
|
+
await User.ensure_indexes();
|
|
230
|
+
|
|
231
|
+
// Option B: run the combined model bootstrap path
|
|
232
|
+
await User.ensure_model();
|
|
138
233
|
```
|
|
139
234
|
|
|
140
235
|
## Built-in FieldType Examples
|
|
141
236
|
|
|
142
237
|
```js
|
|
143
|
-
const account_schema = new
|
|
144
|
-
|
|
238
|
+
const account_schema = new JsonBadger.Schema({
|
|
239
|
+
name: { type: String, trim: true, required: true },
|
|
145
240
|
age: { type: Number, min: 0 },
|
|
146
241
|
is_active: Boolean,
|
|
147
242
|
joined_at: Date,
|
|
@@ -158,22 +253,38 @@ const account_schema = new jsonbadger.Schema({
|
|
|
158
253
|
});
|
|
159
254
|
```
|
|
160
255
|
|
|
256
|
+
Quick FieldType reference:
|
|
257
|
+
|
|
258
|
+
| Field | FieldType | Example |
|
|
259
|
+
| --- | --- | --- |
|
|
260
|
+
| `name` | `String` | `{ type: String, trim: true, required: true }` |
|
|
261
|
+
| `age` | `Number` | `{ type: Number, min: 0 }` |
|
|
262
|
+
| `is_active` | `Boolean` | `is_active: Boolean` |
|
|
263
|
+
| `joined_at` | `Date` | `joined_at: Date` |
|
|
264
|
+
| `owner_id` | `UUIDv7` | `{ type: 'UUIDv7' }` |
|
|
265
|
+
| `avatar` | `Buffer` | `avatar: Buffer` |
|
|
266
|
+
| `payload` | `Mixed` | `payload: {}` |
|
|
267
|
+
| `tags` | `Array<String>` | `tags: [String]` |
|
|
268
|
+
| `handles` | `Map<String>` | `{ type: Map, of: String }` |
|
|
269
|
+
| `amount` | `Decimal128` | `{ type: 'Decimal128' }` |
|
|
270
|
+
| `visits_64` | `BigInt` | `{ type: 'BigInt' }` |
|
|
271
|
+
| `ratio` | `Double` | `{ type: 'Double' }` |
|
|
272
|
+
| `score_32` | `Int32` | `{ type: 'Int32' }` |
|
|
273
|
+
| `code_or_label` | `Union<String, Number>` | `{ type: 'Union', of: [String, Number] }` |
|
|
274
|
+
|
|
161
275
|
Edge cases and practical notes:
|
|
162
|
-
- In-place changes to
|
|
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.
|
|
163
277
|
- Arrays default to `[]`; set `default: undefined` to disable the implicit empty-array default.
|
|
164
278
|
- For `Map`-like paths, prefer `document.set('handles.github', 'name')` so casting and dirty tracking run.
|
|
165
|
-
- Serialization applies getters by default; use `doc.to_object({ getters: false })` or `doc.to_json({ getters: false })` to bypass them.
|
|
166
279
|
|
|
167
280
|
## Create and Save Documents
|
|
168
281
|
|
|
169
|
-
First write can create the table if it does not exist yet.
|
|
170
|
-
|
|
171
282
|
Assumes:
|
|
172
283
|
- `User` is defined from the schema/model section above.
|
|
173
284
|
|
|
174
285
|
```js
|
|
175
|
-
const user_doc =
|
|
176
|
-
|
|
286
|
+
const user_doc = User.from({
|
|
287
|
+
name: 'john',
|
|
177
288
|
age: 30,
|
|
178
289
|
status: 'active',
|
|
179
290
|
tags: ['vip', 'beta'],
|
|
@@ -191,47 +302,159 @@ const user_doc = new User({
|
|
|
191
302
|
}
|
|
192
303
|
});
|
|
193
304
|
|
|
194
|
-
await user_doc.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
```
|
|
305
|
+
const saved_user = await user_doc.save();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Returns: `saved_user` is a `User` document instance.
|
|
309
|
+
Raw document shape: `saved_user.document` -> `{ id, data, created_at, updated_at }` when the default slug is still `data`.
|
|
310
|
+
Runtime note: read the default slug through `saved_user.document[User.schema.get_default_slug()]`.
|
|
311
|
+
|
|
312
|
+
Static create and id lookup:
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
const created_user = await User.create({
|
|
316
|
+
name: 'maria',
|
|
317
|
+
age: 29
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const same_user = await User.find_by_id(created_user.id).exec();
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Build a new document from external input without marking it persisted:
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const imported_user = User.from({
|
|
327
|
+
name: ' maria ',
|
|
328
|
+
age: '29',
|
|
329
|
+
created_at: '2026-03-03T08:00:00.000Z'
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`User.from(...)` extracts root `id`, `created_at`, and `updated_at` into base fields and treats every other non-base root key as default-slug payload unless that root key is registered in `schema.options.slugs`. It does not interpret payload input as a persisted row envelope.
|
|
334
|
+
|
|
335
|
+
Set base fields after construction when needed:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
const imported_user = User.from({
|
|
339
|
+
name: 'maria'
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
imported_user.id = '7';
|
|
343
|
+
imported_user.created_at = '2026-03-03T08:00:00.000Z';
|
|
344
|
+
imported_user.updated_at = '2026-03-03T09:00:00.000Z';
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Hydrate a persisted document from raw row-like data:
|
|
348
|
+
|
|
349
|
+
```js
|
|
350
|
+
const hydrated_user = User.hydrate({
|
|
351
|
+
id: '7',
|
|
352
|
+
data: {
|
|
353
|
+
name: 'maria',
|
|
354
|
+
age: '29'
|
|
355
|
+
},
|
|
356
|
+
created_at: '2026-03-03T08:00:00.000Z',
|
|
357
|
+
updated_at: '2026-03-03T09:00:00.000Z'
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`User.hydrate(...)` is the row-like path. It reads the payload from the configured default slug key on the outer object and ignores unregistered extra root slugs.
|
|
362
|
+
|
|
363
|
+
Timestamp helper examples on create:
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
// Caller-provided timestamp values are kept
|
|
367
|
+
const user_with_timestamps = await User.create({
|
|
368
|
+
name: 'timed-user',
|
|
369
|
+
created_at: '2026-03-03T08:00:00.000Z',
|
|
370
|
+
updated_at: '2026-03-03T09:00:00.000Z'
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Omitted timestamps are auto-filled
|
|
374
|
+
const user_with_auto_timestamps = await User.create({
|
|
375
|
+
name: 'auto-timestamp-user'
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Validation failure pattern (`validation_error`):
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
try {
|
|
383
|
+
const invalid_user = User.from({
|
|
384
|
+
name: 'bad_input',
|
|
385
|
+
age: -1 // violates `min: 0` from the schema example
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await invalid_user.save();
|
|
389
|
+
} catch(error) {
|
|
390
|
+
if(error.name === 'validation_error') {
|
|
391
|
+
const error_payload = error.to_json();
|
|
392
|
+
// shape: { success: false, error: { type: 'validation_error', message: '...', details: ... } }
|
|
393
|
+
// `error.details` is the same validation detail payload included in `error_payload.error.details`
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Constraint/query failure pattern (`query_error`):
|
|
399
|
+
|
|
400
|
+
Assumes:
|
|
401
|
+
- You created a unique index and applied it (for example via `ensure_table()` + `ensure_indexes()` or `ensure_model()`).
|
|
402
|
+
|
|
403
|
+
```js
|
|
404
|
+
const unique_user_schema = new JsonBadger.Schema({
|
|
405
|
+
email: {type: String, required: true}
|
|
406
|
+
});
|
|
407
|
+
unique_user_schema.create_index({
|
|
408
|
+
using: 'btree',
|
|
409
|
+
path: 'email',
|
|
410
|
+
unique: true,
|
|
411
|
+
name: 'idx_unique_users_email'
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const UniqueUser = connection.model({
|
|
415
|
+
name: 'UniqueUser',
|
|
416
|
+
schema: unique_user_schema,
|
|
417
|
+
table_name: 'unique_users'
|
|
418
|
+
});
|
|
419
|
+
await UniqueUser.ensure_table();
|
|
420
|
+
await UniqueUser.ensure_indexes();
|
|
421
|
+
|
|
422
|
+
await new UniqueUser({email: 'john@example.com'}).save();
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
await new UniqueUser({email: 'john@example.com'}).save();
|
|
426
|
+
} catch(error) {
|
|
427
|
+
if(error.name === 'query_error') {
|
|
428
|
+
const error_payload = error.to_json();
|
|
429
|
+
const cause_message = error_payload.error.details?.cause;
|
|
430
|
+
// usually includes: duplicate key value violates unique constraint
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Query Basics (find, find_one, count_documents)
|
|
436
|
+
|
|
437
|
+
Assumes (for the query sections below):
|
|
438
|
+
- `User` is defined and at least one document was saved (see `## Create and Save Documents`).
|
|
439
|
+
- Examples that filter by `name: 'john'`, `tags`, `orders`, or `payload` assume those fields/values exist in seeded data.
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
const all_users = await User.find({}).exec();
|
|
444
|
+
|
|
445
|
+
const found_user = await User.find_one({name: 'john'}).exec();
|
|
446
|
+
|
|
447
|
+
const by_id_user = await User.find_by_id('1').exec();
|
|
448
|
+
|
|
449
|
+
const adult_count = await User.count_documents({age: {$gte: 18}}).exec();
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
- `all_users`: `User[]` (document instances)
|
|
454
|
+
- `found_user`: `User | null`
|
|
455
|
+
- `by_id_user`: `User | null`
|
|
456
|
+
- `adult_count`: `number`
|
|
457
|
+
- Snapshot example: `all_users[0]?.document` -> `{ id, data, created_at, updated_at }` when the default slug is still `data`
|
|
235
458
|
|
|
236
459
|
Query builder chaining:
|
|
237
460
|
|
|
@@ -244,16 +467,13 @@ const page_of_users = await User.find({status: 'active'})
|
|
|
244
467
|
.exec();
|
|
245
468
|
```
|
|
246
469
|
|
|
247
|
-
Read behavior note:
|
|
248
|
-
- `find(...).exec()`, `find_one(...).exec()`, and `count_documents(...).exec()` do not call `ensure_table()`.
|
|
249
|
-
|
|
250
470
|
## Direct Equality and Scalar Comparisons
|
|
251
471
|
|
|
252
472
|
Direct equality and explicit `$eq` both work:
|
|
253
473
|
|
|
254
474
|
```js
|
|
255
|
-
await User.find({
|
|
256
|
-
await User.find({
|
|
475
|
+
await User.find({name: 'john'}).exec();
|
|
476
|
+
await User.find({name: {$eq: 'john'}}).exec();
|
|
257
477
|
```
|
|
258
478
|
|
|
259
479
|
Scalar comparison operators:
|
|
@@ -273,19 +493,27 @@ await User.find({status: {$in: ['active', 'pending']}}).exec();
|
|
|
273
493
|
await User.find({status: {$nin: ['disabled', 'banned']}}).exec();
|
|
274
494
|
```
|
|
275
495
|
|
|
496
|
+
Reserved base-field filters (top-level only):
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
await User.find({id: {$in: [1, 2, 3]}}).exec(); // bigserial default
|
|
500
|
+
await User.find({created_at: {$gte: '2026-01-01T00:00:00.000Z'}}).exec();
|
|
501
|
+
await User.find({updated_at: {$lt: '2026-12-31T23:59:59.999Z'}}).sort({updated_at: -1}).exec();
|
|
502
|
+
```
|
|
503
|
+
|
|
276
504
|
## Regex Operators
|
|
277
505
|
|
|
278
506
|
Regex literal shorthand:
|
|
279
507
|
|
|
280
508
|
```js
|
|
281
|
-
await User.find({
|
|
509
|
+
await User.find({name: /jo/i}).exec();
|
|
282
510
|
```
|
|
283
511
|
|
|
284
512
|
`$regex` with `$options`:
|
|
285
513
|
|
|
286
514
|
```js
|
|
287
515
|
await User.find({
|
|
288
|
-
|
|
516
|
+
name: {
|
|
289
517
|
$regex: '^jo',
|
|
290
518
|
$options: 'i'
|
|
291
519
|
}
|
|
@@ -355,7 +583,7 @@ await User.find({payload: {$has_any_keys: ['profile', 'flags']}}).exec();
|
|
|
355
583
|
await User.find({payload: {$has_all_keys: ['profile', 'score']}}).exec();
|
|
356
584
|
```
|
|
357
585
|
|
|
358
|
-
Nested-scope existence (
|
|
586
|
+
Nested-scope existence (JsonBadger extracts the nested JSONB value first, then applies top-level existence there):
|
|
359
587
|
|
|
360
588
|
```js
|
|
361
589
|
await User.find({'payload.profile': {$has_key: 'city'}}).exec();
|
|
@@ -368,14 +596,14 @@ await User.find({payload: {$has_key: 'profile.city'}}).exec();
|
|
|
368
596
|
|
|
369
597
|
`$json_path_exists` (`@?`) and `$json_path_match` (`@@`):
|
|
370
598
|
|
|
371
|
-
Assumes:
|
|
372
|
-
- Your seeded `payload` includes shapes like `items[*].qty` and `score` (as shown in `## Create and Save Documents`).
|
|
373
|
-
|
|
374
|
-
Target shape reminder:
|
|
375
|
-
- `payload` is a JSON object with keys like `score` and `items`, where `items` is an array of objects (for example `{qty: 2}`).
|
|
376
|
-
|
|
377
|
-
```js
|
|
378
|
-
await User.find({
|
|
599
|
+
Assumes:
|
|
600
|
+
- Your seeded `payload` includes shapes like `items[*].qty` and `score` (as shown in `## Create and Save Documents`).
|
|
601
|
+
|
|
602
|
+
Target shape reminder:
|
|
603
|
+
- `payload` is a JSON object with keys like `score` and `items`, where `items` is an array of objects (for example `{qty: 2}`).
|
|
604
|
+
|
|
605
|
+
```js
|
|
606
|
+
await User.find({
|
|
379
607
|
payload: {$json_path_exists: '$.items[*] ? (@.qty > 1)'}
|
|
380
608
|
}).exec();
|
|
381
609
|
|
|
@@ -392,39 +620,41 @@ Expected behavior:
|
|
|
392
620
|
|
|
393
621
|
`update_one(...)` supports `$set`, `$insert`, and `$set_lax`.
|
|
394
622
|
|
|
395
|
-
Assumes (for update and delete sections below):
|
|
396
|
-
- A matching row exists (examples use `
|
|
397
|
-
- The seeded row includes `tags` and `payload` fields from the create/save example.
|
|
398
|
-
|
|
399
|
-
Target shape reminder:
|
|
400
|
-
- `tags` is an array, `profile` is an object, and `payload.profile` / `payload.score` are nested JSON values in the seeded row.
|
|
401
|
-
|
|
402
|
-
Basic `$set` (maps to `jsonb_set(...)`):
|
|
623
|
+
Assumes (for update and delete sections below):
|
|
624
|
+
- A matching row exists (examples use `name: 'john'` and `name: 'missing'`).
|
|
625
|
+
- The seeded row includes `tags` and `payload` fields from the create/save example.
|
|
626
|
+
|
|
627
|
+
Target shape reminder:
|
|
628
|
+
- `tags` is an array, `profile` is an object, and `payload.profile` / `payload.score` are nested JSON values in the seeded row.
|
|
629
|
+
|
|
630
|
+
Basic `$set` (maps to `jsonb_set(...)`):
|
|
403
631
|
|
|
404
632
|
```js
|
|
405
633
|
const updated_user = await User.update_one(
|
|
406
|
-
{
|
|
407
|
-
{
|
|
408
|
-
$set: {
|
|
409
|
-
age: 31,
|
|
634
|
+
{name: 'john'},
|
|
635
|
+
{
|
|
636
|
+
$set: {
|
|
637
|
+
age: 31,
|
|
410
638
|
'profile.city': 'Orlando',
|
|
411
639
|
'payload.profile.state': 'FL'
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Returns: `updated_user` is `User | null`.
|
|
646
|
+
Snapshot shape: `updated_user?.document` -> `{ id, data, created_at, updated_at }` when the default slug is still `data`.
|
|
417
647
|
|
|
418
648
|
`$insert` (maps to `jsonb_insert(...)`) with numeric array index paths:
|
|
419
649
|
|
|
420
650
|
```js
|
|
421
|
-
await User.update_one({
|
|
651
|
+
await User.update_one({name: 'john'}, {
|
|
422
652
|
$insert: {
|
|
423
653
|
'tags.0': 'first_tag'
|
|
424
654
|
}
|
|
425
655
|
});
|
|
426
656
|
|
|
427
|
-
await User.update_one({
|
|
657
|
+
await User.update_one({name: 'john'}, {
|
|
428
658
|
$insert: {
|
|
429
659
|
'tags.0': {
|
|
430
660
|
value: 'after_first',
|
|
@@ -434,13 +664,13 @@ await User.update_one({user_name: 'john'}, {
|
|
|
434
664
|
});
|
|
435
665
|
```
|
|
436
666
|
|
|
437
|
-
`$set_lax` (maps to `jsonb_set_lax(...)`) object form:
|
|
438
|
-
|
|
439
|
-
Target shape reminder for `$set_lax`:
|
|
440
|
-
- `payload` is a JSON object; these examples update or delete nested keys inside `payload`.
|
|
441
|
-
|
|
442
|
-
```js
|
|
443
|
-
await User.update_one({
|
|
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`.
|
|
671
|
+
|
|
672
|
+
```js
|
|
673
|
+
await User.update_one({name: 'john'}, {
|
|
444
674
|
$set_lax: {
|
|
445
675
|
'payload.cleanup_flag': {
|
|
446
676
|
value: null,
|
|
@@ -453,7 +683,7 @@ await User.update_one({user_name: 'john'}, {
|
|
|
453
683
|
`$set_lax` options example (`create_if_missing`, `null_value_treatment`):
|
|
454
684
|
|
|
455
685
|
```js
|
|
456
|
-
await User.update_one({
|
|
686
|
+
await User.update_one({name: 'john'}, {
|
|
457
687
|
$set_lax: {
|
|
458
688
|
'payload.archived_at': {
|
|
459
689
|
value: null,
|
|
@@ -467,7 +697,7 @@ await User.update_one({user_name: 'john'}, {
|
|
|
467
697
|
Multiple update operators in one call (application order is `$set` -> `$insert` -> `$set_lax`):
|
|
468
698
|
|
|
469
699
|
```js
|
|
470
|
-
await User.update_one({
|
|
700
|
+
await User.update_one({name: 'john'}, {
|
|
471
701
|
$set: {
|
|
472
702
|
'payload.score': 50
|
|
473
703
|
},
|
|
@@ -486,7 +716,7 @@ await User.update_one({user_name: 'john'}, {
|
|
|
486
716
|
Conflict rule (rejected before SQL):
|
|
487
717
|
|
|
488
718
|
```js
|
|
489
|
-
await User.update_one({
|
|
719
|
+
await User.update_one({name: 'john'}, {
|
|
490
720
|
$set: {
|
|
491
721
|
payload: {nested: true}
|
|
492
722
|
},
|
|
@@ -497,116 +727,163 @@ await User.update_one({user_name: 'john'}, {
|
|
|
497
727
|
// throws: conflicting update paths (same path or parent/child overlap)
|
|
498
728
|
```
|
|
499
729
|
|
|
730
|
+
Timestamp helper examples on update:
|
|
731
|
+
|
|
732
|
+
```js
|
|
733
|
+
// Caller-provided updated_at is kept
|
|
734
|
+
await User.update_one({name: 'john'}, {
|
|
735
|
+
$set: {
|
|
736
|
+
age: 32,
|
|
737
|
+
updated_at: '2026-03-03T10:00:00.000Z'
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Omitted updated_at is auto-refreshed
|
|
742
|
+
await User.update_one({name: 'john'}, {
|
|
743
|
+
$set: {
|
|
744
|
+
age: 33
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
500
749
|
## Delete Operations
|
|
501
750
|
|
|
502
|
-
`delete_one(...)` deletes one matching row and returns the deleted document
|
|
751
|
+
`delete_one(...)` deletes one matching row and returns the deleted document instance.
|
|
503
752
|
|
|
504
|
-
```js
|
|
505
|
-
const deleted_user = await User.delete_one({
|
|
506
|
-
|
|
507
|
-
|
|
753
|
+
```js
|
|
754
|
+
const deleted_user = await User.delete_one({name: 'john'});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
Returns: `deleted_user` is `User | null`.
|
|
758
|
+
Snapshot shape: `deleted_user?.document` -> `{ id, data, created_at, updated_at }` when the default slug is still `data`.
|
|
508
759
|
|
|
509
760
|
No match (or missing table) returns `null`:
|
|
510
761
|
|
|
511
|
-
```js
|
|
512
|
-
const maybe_deleted = await User.delete_one({
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
762
|
+
```js
|
|
763
|
+
const maybe_deleted = await User.delete_one({name: 'missing'});
|
|
764
|
+
if(maybe_deleted === null) {
|
|
765
|
+
// no matching row, or table does not exist yet
|
|
766
|
+
}
|
|
517
767
|
```
|
|
518
768
|
|
|
519
|
-
|
|
769
|
+
Returns: `null` when no row matches (or when the table does not exist yet).
|
|
770
|
+
|
|
771
|
+
## Runtime Document Methods (get/set and direct document access)
|
|
520
772
|
|
|
521
|
-
Runtime getters/setters
|
|
773
|
+
Runtime getters/setters:
|
|
522
774
|
|
|
523
775
|
Assumes:
|
|
524
|
-
- `User` is defined from the schema/model example and includes the `
|
|
776
|
+
- `User` is defined from the schema/model example and includes the `name` getter configuration shown there.
|
|
525
777
|
- This snippet demonstrates in-memory runtime behavior; no database read is required until `save()`.
|
|
526
778
|
|
|
527
779
|
|
|
528
780
|
```js
|
|
529
|
-
const doc =
|
|
530
|
-
|
|
781
|
+
const doc = User.from({
|
|
782
|
+
name: 'jane',
|
|
531
783
|
status: 'active',
|
|
532
784
|
payload: {count: 1},
|
|
533
785
|
profile: {city: 'Miami'}
|
|
534
786
|
});
|
|
535
787
|
|
|
536
|
-
|
|
788
|
+
const default_slug = User.schema.get_default_slug();
|
|
537
789
|
|
|
538
|
-
doc.set('
|
|
539
|
-
const
|
|
790
|
+
doc.set(default_slug + '.name', ' jane_doe ');
|
|
791
|
+
const user_name = doc.get(default_slug + '.name');
|
|
540
792
|
|
|
541
|
-
doc.set('profile.city', 'Orlando');
|
|
542
|
-
const city = doc.get('profile.city');
|
|
793
|
+
doc.set(default_slug + '.profile.city', 'Orlando');
|
|
794
|
+
const city = doc.get(default_slug + '.profile.city');
|
|
543
795
|
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
doc.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
796
|
+
// raw document access still works
|
|
797
|
+
doc.document[default_slug].payload.count += 1;
|
|
798
|
+
const pending_delta = doc.document.$get_delta();
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
Bind live document fields onto another object:
|
|
802
|
+
|
|
803
|
+
```js
|
|
804
|
+
const user_entity = {};
|
|
805
|
+
doc.bind_document(user_entity);
|
|
551
806
|
|
|
552
|
-
|
|
807
|
+
user_entity.name; // reads from the default slug
|
|
808
|
+
user_entity.profile.city = 'Tampa'; // live nested default-slug object
|
|
553
809
|
```
|
|
554
810
|
|
|
811
|
+
> **Note:** `bind_document(...)` flattens default-slug root fields onto the target root, but keeps extra slugs nested. It also throws if the target already owns one of the field names it needs to bind.
|
|
812
|
+
|
|
555
813
|
### Optional Alias Path Example
|
|
556
814
|
|
|
557
815
|
Use aliases only when you need an alternate path name (for example, a short path shortcut for a nested field).
|
|
558
816
|
|
|
559
817
|
```js
|
|
560
|
-
const alias_schema = new
|
|
818
|
+
const alias_schema = new JsonBadger.Schema({
|
|
561
819
|
profile: {
|
|
562
820
|
city: {type: String, alias: 'city'}
|
|
563
821
|
}
|
|
564
822
|
});
|
|
565
823
|
|
|
566
|
-
const AliasUser =
|
|
567
|
-
|
|
824
|
+
const AliasUser = connection.model({
|
|
825
|
+
name: 'AliasUser',
|
|
826
|
+
schema: alias_schema,
|
|
827
|
+
table_name: 'alias_users'
|
|
828
|
+
});
|
|
829
|
+
const alias_doc = AliasUser.from({profile: {city: 'Miami'}});
|
|
568
830
|
|
|
569
831
|
// alias path -> underlying path
|
|
570
832
|
alias_doc.get('city'); // 'Miami'
|
|
571
833
|
alias_doc.set('city', 'Orlando');
|
|
572
|
-
alias_doc.get('profile.city'); // 'Orlando'
|
|
834
|
+
alias_doc.get('data.profile.city'); // 'Orlando' when the default slug is still `data`
|
|
573
835
|
```
|
|
574
836
|
|
|
575
|
-
Aliases are path
|
|
837
|
+
Aliases are schema-defined alternate path names for `doc.get(...)` and `doc.set(...)`. They are collected when the schema is created, while canonical schema paths remain the persisted keys.
|
|
576
838
|
|
|
577
|
-
|
|
839
|
+
Immutable behavior (`immutable: true`) after first persist:
|
|
578
840
|
|
|
579
841
|
```js
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
842
|
+
await doc.save();
|
|
843
|
+
|
|
844
|
+
// after successful save, immutable fields reject updates
|
|
845
|
+
// doc.set('status', 'disabled'); // throws
|
|
583
846
|
```
|
|
584
847
|
|
|
585
|
-
|
|
848
|
+
## Lifecycle Quick Reference
|
|
586
849
|
|
|
587
850
|
```js
|
|
851
|
+
const doc = User.from({name: 'john'});
|
|
852
|
+
doc.set('data.profile.city', 'Miami');
|
|
588
853
|
await doc.save();
|
|
589
854
|
|
|
590
|
-
|
|
591
|
-
|
|
855
|
+
const found = await User.find_by_id(doc.id).exec();
|
|
856
|
+
const snapshot = found.document;
|
|
592
857
|
```
|
|
593
858
|
|
|
859
|
+
Phase summary:
|
|
860
|
+
- `User.from(...)` -> constructed
|
|
861
|
+
- first runtime interaction (`set/get/save/delete`) -> runtime-ready
|
|
862
|
+
- `save()` on new doc -> persisted
|
|
863
|
+
- `find_one(...).exec()` / `find_by_id(...).exec()` -> queried/hydrated
|
|
864
|
+
|
|
865
|
+
For the full lifecycle contract, see [`docs/lifecycle.md`](lifecycle.md).
|
|
866
|
+
|
|
594
867
|
## Complete Operator Checklist (Query and Update)
|
|
595
868
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
## Related Docs
|
|
869
|
+
| Family | Supported operators/methods |
|
|
870
|
+
| --- | --- |
|
|
871
|
+
| Scalar equality/comparison | direct equality, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte` |
|
|
872
|
+
| Set membership | `$in`, `$nin` |
|
|
873
|
+
| Regex | RegExp literal shorthand, `$regex`, `$options` |
|
|
874
|
+
| Array operators | `$all`, `$size`, `$elem_match` |
|
|
875
|
+
| JSON containment | `$contains` |
|
|
876
|
+
| JSONB key existence | `$has_key`, `$has_any_keys`, `$has_all_keys` |
|
|
877
|
+
| JSONPath | `$json_path_exists`, `$json_path_match` |
|
|
878
|
+
| Updates (`update_one`) | `$set`, `$insert`, `$set_lax` |
|
|
879
|
+
|
|
880
|
+
## Related Docs
|
|
608
881
|
|
|
609
882
|
- [`README.md`](../README.md) (quick start + selected examples)
|
|
610
|
-
- [`docs/api.md`](api.md) (API
|
|
883
|
+
- [`docs/api/index.md`](api/index.md) (module API map)
|
|
884
|
+
- [`docs/api/model.md`](api/model.md) (model construction, queries, persistence, and document methods)
|
|
885
|
+
- [`docs/api/schema.md`](api/schema.md) (schema API and index declaration rules)
|
|
886
|
+
- [`docs/api/query-builder.md`](api/query-builder.md) (query builder chain and filter families)
|
|
887
|
+
- [`docs/lifecycle.md`](lifecycle.md) (document phases, hydration/save flow, dirty tracking, and serialization)
|
|
611
888
|
- [`docs/query-translation.md`](query-translation.md) (PostgreSQL operator/function mapping)
|
|
612
889
|
- [`docs/local-integration-testing.md`](local-integration-testing.md) (local integration test setup)
|