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.
- 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/README.md
CHANGED
|
@@ -29,35 +29,38 @@ npm install jsonbadger
|
|
|
29
29
|
## Examples (Quick Start)
|
|
30
30
|
|
|
31
31
|
```js
|
|
32
|
-
import
|
|
32
|
+
import JsonBadger from 'jsonbadger';
|
|
33
33
|
|
|
34
34
|
// 1. Connect to database
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
45
|
-
|
|
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
|
-
//
|
|
53
|
-
user_schema.create_index({
|
|
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 =
|
|
59
|
+
const User = connection.model({
|
|
60
|
+
name: 'User',
|
|
61
|
+
schema: user_schema,
|
|
57
62
|
table_name: 'users',
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
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**:
|
|
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)
|