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
package/README.md CHANGED
@@ -29,35 +29,38 @@ npm install jsonbadger
29
29
  ## Examples (Quick Start)
30
30
 
31
31
  ```js
32
- import jsonbadger from 'jsonbadger';
32
+ import JsonBadger from 'jsonbadger';
33
33
 
34
34
  // 1. Connect to database
35
- await jsonbadger.connect('postgresql://user:pass@localhost:5432/dbname', {
35
+ const db_uri = 'postgresql://user:pass@localhost:5432/dbname';
36
+ const options = {
36
37
  debug: false,
37
38
  max: 10,
38
39
  ssl: false,
39
- auto_index: true,
40
- id_strategy: jsonbadger.IdStrategies.bigserial // server default: bigserial | uuidv7 (native PostgreSQL uuidv7() required)
41
- });
40
+ auto_index: true
41
+ };
42
+ const connection = await JsonBadger.connect(db_uri, options);
42
43
 
43
44
  // 2. Define schema (FieldType format)
44
- const user_schema = new jsonbadger.Schema({
45
- user_name: {type: String, required: true, index: true},
45
+ const user_schema = new JsonBadger.Schema({
46
+ name: {type: String, required: true, index: true},
46
47
  age: {type: Number, min: 0, index: -1},
47
48
  type: {type: String},
48
49
  email: {type: String, index: {unique: true, name: 'idx_users_email_unique'}}
50
+ }, {
51
+ id_strategy: JsonBadger.IdStrategies.bigserial
49
52
  });
50
53
 
51
54
  // 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});
55
+ // Descriptor-form create_index(...) is how you define explicit index declarations.
56
+ user_schema.create_index({using: 'btree', paths: {name: 1, type: -1}});
54
57
 
55
58
  // 4. Create model
56
- const User = jsonbadger.model(user_schema, {
59
+ const User = connection.model({
60
+ name: 'User',
61
+ schema: user_schema,
57
62
  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)
63
+ auto_index: false // optional model-level override
61
64
  });
62
65
 
63
66
  // 5. Run migrations manually (optional if you rely on first-write auto table creation via save()/update_one())
@@ -66,23 +69,29 @@ await User.ensure_index();
66
69
 
67
70
  // 6. Save a document
68
71
  const saved_user = await new User({
69
- user_name: 'john',
72
+ name: 'john',
70
73
  age: 30,
71
74
  type: 'admin'
72
75
  }).save();
76
+ // returns: User document instance
77
+ // saved_user.to_json() -> { id, name, age, type, created_at, updated_at }
73
78
 
74
79
  // 7. Find a document
75
80
  const found_user = await User.find_one({
76
- user_name: 'john'
81
+ name: 'john'
77
82
  }).exec();
83
+ // returns: User document instance or null
84
+ // found_user?.to_json() -> { id, name, age, type, created_at, updated_at }
78
85
  ```
79
86
 
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.
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.
81
88
 
82
89
  ## Examples Cheat Sheet
83
90
 
84
91
  For a complete copy-paste operator and runtime cheat sheet (queries, updates, and document methods), see [`docs/examples.md`](docs/examples.md).
85
92
 
93
+ For the document state flow from `new Model(...)` through `save()`, hydration, dirty tracking, and serialization, see [`docs/lifecycle.md`](docs/lifecycle.md).
94
+
86
95
  ## Core Features
87
96
 
88
97
  * **JSONB-first**: Model API designed specifically for PostgreSQL JSONB.
@@ -92,14 +101,23 @@ For a complete copy-paste operator and runtime cheat sheet (queries, updates, an
92
101
  * **JSON update operators**: PostgreSQL-native `jsonb_set`, `jsonb_insert`, and `jsonb_set_lax` support in `update_one(...)`.
93
102
  * **Migrations**: Helpers for `ensure_table` and `ensure_index`.
94
103
  * **Configurable row IDs**: use `IdStrategies.bigserial` and `IdStrategies.uuidv7` (`uuidv7` is generated by PostgreSQL via native `uuidv7()` support).
104
+ * **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
+ * **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`.
95
106
  * **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.
107
+ * **UUIDv7 compatibility gating**: JsonBadger checks support automatically and fails early with server version/capability context when unavailable.
108
+ * **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.
97
109
  * **Modern**: Native ESM package.
98
110
 
99
111
  ## Documentation
100
112
 
101
- * [`docs/api.md`](docs/api.md)
113
+ * [`docs/api/index.md`](docs/api/index.md)
114
+ * [`docs/api/model.md`](docs/api/model.md) (model construction, queries, persistence, and document methods)
115
+ * [`docs/api/schema.md`](docs/api/schema.md)
116
+ * [`docs/api/query-builder.md`](docs/api/query-builder.md)
117
+ * [`docs/api/connection.md`](docs/api/connection.md) (connection API, lifecycle, and shared-connection examples)
118
+ * [`docs/api/field-types.md`](docs/api/field-types.md)
102
119
  * [`docs/examples.md`](docs/examples.md) (complete example cheat sheet for queries, updates, and runtime document methods)
120
+ * [`docs/lifecycle.md`](docs/lifecycle.md) (document phases, hydration/save flow, dirty tracking, and serialization)
103
121
  * [`docs/query-translation.md`](docs/query-translation.md) (includes PostgreSQL capability map for query/update operators and indexability expectations)
104
122
  * [`docs/local-integration-testing.md`](docs/local-integration-testing.md)
105
123
  * [`CHANGELOG.md`](CHANGELOG.md) for release notes and version history
@@ -0,0 +1,144 @@
1
+ # Connection API
2
+
3
+ ## TOC
4
+
5
+ - [Connection API](#connection-api)
6
+ - [connect](#connect)
7
+ - [Connection Options](#connection-options)
8
+ - [Connection Lifecycle](#connection-lifecycle)
9
+ - [UUIDv7 Compatibility](#uuidv7-compatibility)
10
+ - [Connection Reuse Pattern](#connection-reuse-pattern)
11
+ - [Cross-File Example](#cross-file-example)
12
+
13
+ ## connect
14
+
15
+ ```js
16
+ const connection = await JsonBadger.connect(uri, options);
17
+ ```
18
+
19
+ `connect(...)` opens a PostgreSQL pool and returns a `Connection` instance.
20
+
21
+ ## Connection Options
22
+
23
+ - `max`: pool size
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
+ - any supported `pg` pool options (`ssl`, `host`, `port`, `user`, `password`, `database`)
27
+
28
+ ## Connection Lifecycle
29
+
30
+ Close the connection through the returned instance:
31
+
32
+ ```js
33
+ const connection = await JsonBadger.connect(uri, options);
34
+ await connection.disconnect();
35
+ ```
36
+
37
+ ## UUIDv7 Compatibility
38
+
39
+ - `connect(...)` performs a PostgreSQL capability scan and caches the result
40
+ - later schema-level `uuidv7` selections are checked against the cached capability info during `Model.ensure_table()`
41
+
42
+ > **Note:** Troubleshooting SQL like `SELECT version();` and `SHOW server_version;` is optional. Runtime checks are machine-readable and internal.
43
+
44
+ ## Connection Reuse Pattern
45
+
46
+ If your app wants one shared connection, keep the returned instance and reuse it explicitly.
47
+
48
+ ```js
49
+ import JsonBadger from 'jsonbadger';
50
+
51
+ let shared_connection = null;
52
+
53
+ export async function connect_db() {
54
+ if(shared_connection) {
55
+ return shared_connection;
56
+ }
57
+
58
+ const uri = 'postgresql://user:pass@localhost:5432/dbname';
59
+ const options = {max: 10, debug: false};
60
+
61
+ shared_connection = await JsonBadger.connect(uri, options);
62
+ return shared_connection;
63
+ }
64
+ ```
65
+
66
+ Repeated calls to your own bootstrap helper reuse the same connection:
67
+
68
+ ```js
69
+ const connection_a = await connect_db();
70
+ const connection_b = await connect_db();
71
+
72
+ connection_a === connection_b;
73
+ // returns: true
74
+ ```
75
+
76
+ ## Cross-File Example
77
+
78
+ Keep the connection bootstrap separate from model and controller code.
79
+
80
+ `config/db.js`
81
+
82
+ ```js
83
+ import JsonBadger from 'jsonbadger';
84
+
85
+ let shared_connection = null;
86
+
87
+ export async function connect_db() {
88
+ if(shared_connection) {
89
+ return shared_connection;
90
+ }
91
+
92
+ const uri = 'postgresql://user:pass@localhost:5432/dbname';
93
+ const options = {max: 10, debug: false};
94
+
95
+ shared_connection = await JsonBadger.connect(uri, options);
96
+ return shared_connection;
97
+ }
98
+ ```
99
+
100
+ `models/user-model.js`
101
+
102
+ ```js
103
+ import JsonBadger from 'jsonbadger';
104
+ import {connect_db} from '../config/db.js';
105
+
106
+ const connection = await connect_db();
107
+ const user_schema = new JsonBadger.Schema({
108
+ username: {type: String, required: true, index: true},
109
+ email: {type: String, required: true, index: {unique: true, name: 'idx_users_email_unique'}}
110
+ });
111
+
112
+ export const user_model = connection.model({name: 'User', schema: user_schema, table_name: 'users'});
113
+ ```
114
+
115
+ `controllers/user-controller.js`
116
+
117
+ ```js
118
+ import {user_model} from '../models/user-model.js';
119
+
120
+ export async function get_users(req, res) {
121
+ const users = await user_model.find({}).exec();
122
+ return res.json(users);
123
+ }
124
+ ```
125
+
126
+ `app.js`
127
+
128
+ ```js
129
+ import express from 'express';
130
+ import {connect_db} from './config/db.js';
131
+ import {get_users} from './controllers/user-controller.js';
132
+
133
+ const app = express();
134
+
135
+ await connect_db();
136
+
137
+ app.get('/users', get_users);
138
+ app.listen(3000);
139
+ ```
140
+
141
+ What to expect:
142
+ - `find(...).exec()` returns document instances
143
+ - `find_one(...).exec()` returns one document instance or `null`
144
+ - `save()`, `update_one(...)`, and `delete_one(...)` return document instances
@@ -0,0 +1,106 @@
1
+ # DeltaTracker
2
+
3
+ Use `DeltaTracker` to watch object changes and export them as one delta snapshot.
4
+
5
+ ## Example
6
+
7
+ ```js
8
+ const tracker = DeltaTracker({
9
+ payload: {
10
+ profile: {
11
+ name: 'John'
12
+ }
13
+ }
14
+ }, {
15
+ track: ['payload']
16
+ });
17
+
18
+ tracker.payload.profile.name = 'Jane';
19
+ delete tracker.payload.profile.age;
20
+
21
+ const delta = tracker.$get_delta();
22
+
23
+ // {
24
+ // replace_roots: {},
25
+ // set: {'payload.profile.name': 'Jane'},
26
+ // unset: ['payload.profile.age']
27
+ // }
28
+ ```
29
+
30
+ > **Note:** When you use `track: ['payload']`, emitted paths stay rooted at `payload.*`. Downstream code decides whether to keep or strip that root at the handoff boundary.
31
+
32
+ > **Note:** `payload` is only an example tracked root here. In model/runtime code, the tracked roots come from the configured slug list.
33
+
34
+ ## What It Does
35
+
36
+ `DeltaTracker` keeps three kinds of change state:
37
+ 1. `replace_roots`: full tracked-root replacements.
38
+ 2. `set`: path-to-value assignments.
39
+ 3. `unset`: removed paths.
40
+
41
+ It also collapses conflicting nested changes so the exported delta stays logically consistent.
42
+
43
+ ## Current API
44
+
45
+ ### `DeltaTracker(target, options)`
46
+
47
+ Create one tracked proxy around `target`.
48
+
49
+ Current options:
50
+ 1. `track`: top-level keys to track.
51
+ 2. `watch`: watcher configuration.
52
+ 3. `intercept_set`: transform assigned values before they are recorded.
53
+
54
+ ### `tracker.$get_delta()`
55
+
56
+ Return the current delta snapshot:
57
+
58
+ ```js
59
+ {
60
+ replace_roots: {},
61
+ set: {},
62
+ unset: []
63
+ }
64
+ ```
65
+
66
+ ### `DeltaTracker.from(object)`
67
+
68
+ Build one delta from plain input against an empty baseline.
69
+
70
+ ```js
71
+ const delta = DeltaTracker.from({
72
+ payload: {
73
+ profile: {
74
+ name: 'Jane'
75
+ }
76
+ }
77
+ });
78
+ ```
79
+
80
+ > **Note:** `DeltaTracker.from(...)` supports nested objects and direct top-level keys. It does not interpret dot-path keys like `'data.profile.name'`.
81
+
82
+ ## Behavior Notes
83
+
84
+ 1. Assigning `undefined` is treated like deletion.
85
+ 2. Array `length` writes are ignored.
86
+ 3. Replacing a tracked root records one `replace_roots` entry and clears nested child deltas under that root.
87
+
88
+ ## Boundary Notes
89
+
90
+ `DeltaTracker` stays generic and reusable. It does not know about document-layer SRP, JSONB slugs, or SQL compilation. The model layer configures tracked roots and hands the exported delta to the next boundary.
91
+
92
+ ## Planned Export Options
93
+
94
+ Future work is expected to stay on the same `get_delta(...options)` method instead of adding multiple export methods.
95
+
96
+ Planned shapes:
97
+
98
+ ```js
99
+ tracker.$get_delta({strip_roots: true});
100
+ // strip all tracked roots
101
+
102
+ tracker.$get_delta({strip_roots: ['data']});
103
+ // strip only selected tracked roots
104
+ ```
105
+
106
+ These options are not implemented yet. They are the planned handoff boundary for consumers that need root-relative paths.
@@ -0,0 +1,77 @@
1
+ # Document API
2
+
3
+ `Document` is the plain runtime container behind every model instance.
4
+
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
+
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(...)`.
8
+
9
+ ## TOC
10
+
11
+ - [Document API](#document-api)
12
+ - [Construction](#construction)
13
+ - [Instance Methods](#instance-methods)
14
+ - [Related Docs](#related-docs)
15
+
16
+ ## Construction
17
+
18
+ Create a document from normalized root state:
19
+
20
+ ```js
21
+ import Document from '#src/model/document.js';
22
+
23
+ const document = new Document({
24
+ id: '9',
25
+ payload: {
26
+ name: 'alice'
27
+ },
28
+ settings: {
29
+ theme: 'dark'
30
+ },
31
+ created_at: '2026-03-03T08:00:00.000Z',
32
+ updated_at: '2026-03-03T09:00:00.000Z'
33
+ });
34
+ ```
35
+
36
+ > **Note:** `Document` does not validate, cast, or normalize external input. If the source is not already trusted runtime state, prefer `Model.from(...)` or `Model.hydrate(...)`.
37
+
38
+ ## Instance Methods
39
+
40
+ - `document.init(data)`
41
+ - applies replacement state onto the same document instance
42
+ - `document.get(path_name)`
43
+ - reads one exact root or nested path
44
+ - `document.set(path_name, value)`
45
+ - writes one exact root or nested path
46
+ - `document.id`
47
+ - `document.created_at`
48
+ - `document.updated_at`
49
+
50
+ Example:
51
+
52
+ ```js
53
+ const document = new Document({
54
+ payload: {
55
+ profile: {
56
+ city: 'Miami'
57
+ }
58
+ }
59
+ });
60
+
61
+ document.get('payload.profile.city'); // 'Miami'
62
+
63
+ document.set('payload.profile.city', 'Madrid');
64
+ document.set('settings.theme', 'dark');
65
+ ```
66
+
67
+ Behavior:
68
+ - keeps document state at the root level
69
+ - supports exact-path reads and writes
70
+ - returns the same instance from `init(...)` and `set(...)`
71
+ - returns `undefined` when `get(...)` reads a missing path
72
+
73
+ ## Related Docs
74
+
75
+ - [`model.md`](model.md)
76
+ - [`schema.md`](schema.md)
77
+ - [`../lifecycle.md`](../lifecycle.md)