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.
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
@@ -0,0 +1,39 @@
1
+ **Side-by-Side Evaluation & Rating**
2
+
3
+ | Feature / Criterion | Current `instance.js` (baseline) | **Gemini Redesign** | **Codex v2 Redesign** | Winner & Score (out of 10) |
4
+ |--------------------------------------|----------------------------------|---------------------|-----------------------|----------------------------|
5
+ | **Deletion / `$unset` support** | ❌ None (only `set` + `undefined` hack) | ✅ Full (`deleteProperty` + `$unset` array) | ✅ Full (`deleteProperty` + `kind: 'unset'`) | **Codex** 10 (cleaner semantics) |
6
+ | **Root replacement** | ❌ Encoded as fake `$set` (hacky) | ✅ First-class flag `$replace_root` | ✅ First-class `kind: 'replace_root'` + direct `data = $1` | **Codex** 10 (no encoding trick) |
7
+ | **Change representation** | Dirty string Set only (`['data.foo']`) | Structured delta object (`$set` + `$unset`) | Semantic ops array + `change_index` Map | **Codex** 10 (most expressive + indexable) |
8
+ | **Intent preservation** (no reverse-engineering) | ❌ Heavy (extract_diff + snapshot compare) | ✅ Tracker emits ready-to-use delta | ✅ Tracker records ops at mutation time | **Codex** 10 |
9
+ | **Conflict / normalization** | ❌ None (last-write wins by accident) | Partial (via delta) | ✅ Explicit pre-SQL normalization (replace wipes, last-write, parent/child collapse) | **Codex** 10 |
10
+ | **SQL / JSONB compiler fit** | ❌ Forces query builder to guess | ✅ Chained `jsonb_set` + `#-` wrapping (beautiful) | ✅ `build_payload_update_expression` taking ops list + smart folding | **Tie** 9.5 (both excellent, Codex more flexible) |
11
+ | **Model layer thinness** | Medium (still does dirty logic) | Very thin (`$get_delta()` → executor) | Extremely thin (just orchestrates + `build_payload...`) | **Codex** 10 |
12
+ | **Proxy / API compatibility** | Full (`$has_dirty_fields`, watchers, etc.) | Keeps most API + adds `$get_delta()` | Keeps `$has_*` as derived + adds `$get_changes()`, `deleteProperty` | **Gemini** 9 (less breaking) |
13
+ | **Array handling & future-proofing**| Basic (full replace only) | Implicit via `$set` | Explicit rules + “add array ops later” | **Codex** 9 |
14
+ | **Elimination of hacks** (extract_diff, undefined tricks, string stripping) | 0% eliminated | ~95% eliminated | ~100% eliminated | **Codex** 10 |
15
+ | **Layer separation & beauty** | Mixed responsibilities | Excellent (tracker → model → builder) | Perfect single-responsibility (Document plain, tracker = ops, SQL = compiler) | **Codex** 10 |
16
+ | **Overall robustness & scalability**| 5/10 (works but fragile) | 9/10 (huge leap) | 10/10 (Holy Grail feel) | **Codex wins** |
17
+
18
+ ### Quick Verdict
19
+
20
+ **Codex v2 is the clear winner** — it is the more principled, future-proof architecture.
21
+ It turns every “edge case” into a normal first-class operation, removes almost every hack in one stroke, and gives the SQL layer exactly what it needs without any guessing.
22
+
23
+ **Gemini is still excellent** and much simpler to implement incrementally. Its delta object is very close to what MongoDB-style drivers already expect, so if you want the smallest possible migration, Gemini gets you 90% of the benefit with less risk.
24
+
25
+ ### My Recommendation (Hybrid that takes the best of both)
26
+
27
+ 1. Adopt **Codex’s change list** (`[{kind, path, value}]` + `change_index` Map) — this is the single biggest quality leap.
28
+ 2. Keep **Gemini’s `$get_delta()` shape** as the public handoff from tracker → model layer (so `exec_update_one` sees `{ $set, $unset, $replace_root }`).
29
+ 3. Use **Codex’s normalization rules** before compiling.
30
+ 4. Use **Gemini’s chained `jsonb_set` / `#-` builder** (it’s the cleanest SQL output).
31
+ 5. Keep the current proxy API surface almost untouched for zero breakage (`$has_dirty_fields` becomes a derived view over the change list).
32
+
33
+ This hybrid gives you:
34
+ - Zero hacks
35
+ - Perfect Postgres JSONB surgery
36
+ - Full backward compatibility
37
+ - The cleanest mental model of the three options
38
+
39
+ Would you like me to sketch the exact merged `instance.js` + new `dirty-tracker-v3.js` + updated `save_model` flow that implements this hybrid? I can output the full files ready to drop in.
@@ -0,0 +1,37 @@
1
+ This is a great breakdown. It is always fascinating to see how two different analytical engines weight the trade-offs of the same architectural problem.
2
+
3
+ Both evaluations ultimately point toward a hybrid solution, but we took different philosophical paths to get there. Here is a candid comparison of how my initial analysis stacks up against Grok's evaluation.
4
+
5
+ ### Where We Agreed
6
+
7
+ * [cite_start]**Root Replacements:** We both firmly agree that the Codex approach for replacing the root object is vastly superior[cite: 5, 7]. [cite_start]Encoding a full object replacement as a fake `$set` hack in the Gemini proposal is sloppy[cite: 6]. [cite_start]A dedicated `replace_root` operation with the actual payload is the only clean way to handle `document.data = {...}`[cite: 7].
8
+ * [cite_start]**The SQL Compiler:** We both love the Gemini proposal's approach to chained `jsonb_set` and `#-` wrapping[cite: 15, 32]. [cite_start]It allows Postgres to do the heavy lifting in a single, beautiful expression[cite: 15].
9
+ * [cite_start]**The Destination:** We both concluded that combining the two approaches is the ultimate "Holy Grail" architecture[cite: 26, 30]. [cite_start]We both want the strict operational encapsulation of Codex combined with the Postgres-native structure of Gemini[cite: 30, 31].
10
+
11
+ ### Where We Diverged
12
+
13
+ The primary disagreement lies in how the internal state should be tracked inside the proxy before it gets handed off to the model.
14
+
15
+ * **My Stance (State-Collapsed Map):** I favored the Gemini approach of tracking changes directly into a `$set` and `$unset` object map. My reasoning is that object keys natively collapse state. If a developer sets `profile_age` to 30, then 31, then deletes it, an object map naturally overwrites the previous keys (last write wins). It avoids the need to write a custom normalization algorithm.
16
+ * [cite_start]**Grok's Stance (Chronological Array):** Grok strongly prefers the Codex approach of appending every single mutation to an array of semantic operations (`[{kind, path, value}]`)[cite: 8, 9, 10]. [cite_start]Grok argues this is the most expressive and indexable way to preserve intent[cite: 10, 11]. [cite_start]Grok does not mind that this requires an explicit pre-SQL normalization step to collapse parent/child conflicts and last-write wins[cite: 12, 13].
17
+
18
+ ### The Hybrid Architectures Compared
19
+
20
+ Because we diverged on the internal data structure, our proposed "Hybrid" solutions look slightly different.
21
+
22
+ **Grok's Hybrid:**
23
+ * [cite_start]Uses Codex's chronological change list array internally[cite: 30].
24
+ * [cite_start]Runs Codex's normalization rules to resolve conflicts[cite: 32].
25
+ * [cite_start]Formats the output into Gemini's `$get_delta()` shape before handing it to the SQL builder[cite: 31].
26
+
27
+ **My Hybrid:**
28
+ * Uses a modified Gemini delta map internally from the start.
29
+ * Intercepts `deleteProperty` to populate the `unset` map.
30
+ * Clears the map and sets a `replace_root` value if the root is overwritten.
31
+ * Requires zero normalization loops because the object keys resolve conflicts inherently at write-time.
32
+
33
+ ### The Verdict on the Verdict
34
+
35
+ [cite_start]Grok rated Codex as a perfect 10/10 [cite: 18, 23, 25] [cite_start]and crowned it the clear winner[cite: 27], while I viewed the array-based tracking as slightly heavier than necessary. [cite_start]Grok's evaluation heavily prioritizes having a perfect, chronological audit trail of mutations before they are compiled[cite: 11, 13]. If you want strict, sequential operational history, Grok's leaning is correct. If you want the leanest possible proxy that naturally collapses duplicate writes without extra loops, my leaning holds up.
36
+
37
+ Would you like me to draft the `deleteProperty` trap and the `get_delta()` method using the object-map approach to see how light the proxy implementation can be?
@@ -0,0 +1,63 @@
1
+ **Analysis: My Assumptions vs Gemini’s Evaluation**
2
+
3
+ | Dimension | My Assumption (previous response) | Gemini’s Assumption (new eval) | Who Was More Accurate? | Why / Resolution |
4
+ |-----------|-----------------------------------|--------------------------------|-------------------------|------------------|
5
+ | **Core problem with current `instance.js`** | Dirty string Set + snapshot diff is the root evil; must move to **semantic operations at mutation time** | Same diagnosis, but frames it as “reverse-engineering intent” | **Tie** | Both 100% aligned |
6
+ | **Best internal representation** | Codex-style **chronological ops array + change_index Map** is superior because it preserves full history and makes normalization explicit | Gemini-style **collapsed delta object** (`$set`/`$unset`) is superior because it collapses automatically and is lighter | **Gemini correct** | I over-weighted “preserving every mutation”. In practice 99% of use-cases only care about final state. Maintaining an array + manual conflict resolver adds complexity with almost zero payoff. Gemini’s object keys give O(1) collapse for free. |
7
+ | **Conflict / normalization burden** | Codex wins because it forces explicit rules (replace wipes, parent/child collapse) | Gemini wins because the structure itself prevents most conflicts; only a tiny cross-cancellation step needed | **Gemini correct** | My assumption that “array requires complex graph analysis” was overstated. The synthesized structure Gemini proposes needs **only** three simple rules (set removes from unset, unset removes from set, replace_root clears both). Far simpler than maintaining a full ops list. |
8
+ | **Root replacement handling** | Codex clearly better (first-class `kind`) | Codex better on encapsulation; but Gemini can be fixed with a `replace_root` field | **Gemini correct** (and more pragmatic) | I was too dogmatic about “never encode as fake $set”. A single `replace_root: value || null` field in the delta is actually cleaner for the model layer and SQL than a separate `kind` enum that forces branching everywhere. |
9
+ | **SQL compilation ease** | Tie (both good), but Codex more flexible | Gemini dramatically simpler | **Gemini correct** | Chained `jsonb_set` / `#-` is trivial to build from `{set, unset, replace_root}`. Building it from an array would still require a normalization pass first — exactly the extra step I praised Codex for, but which is unnecessary overhead. |
10
+ | **Proxy implementation cost** | Codex heavier but worth it | Gemini lighter and sufficient | **Gemini correct** | Adding `deleteProperty` + root-assignment detection + 3-line cross-cancellation is ~25 LOC. Adding a full ops array + index + normalization function is ~60+ LOC + more edge-case tests. |
11
+ | **Overall “beauty” / future-proofing** | Codex = 10/10 Holy Grail | Synthesized Gemini-style delta = ideal | **Gemini correct** | My hybrid recommendation was actually just Gemini’s idea with extra baggage. The cleanest thing is **exactly** what Gemini synthesized. |
12
+
13
+ ### Key Takeaways — Where I Was Wrong vs Where Gemini Was Right
14
+
15
+ 1. **I romanticized the ops array.**
16
+ Preserving chronological order sounds theoretically pure, but it forces the SQL layer (or a pre-step) to re-collapse everything anyway. Gemini’s collapsed delta is the final form the database actually needs — so we should produce it at the proxy level and stop earlier.
17
+
18
+ 2. **I underestimated how trivial the cross-cancellation rules are.**
19
+ Gemini nailed it: three tiny rules + `deleteProperty` trap is all you need. No “normalize before SQL” function required.
20
+
21
+ 3. **Gemini’s synthesized delta is the clear winner**
22
+ ```js
23
+ {
24
+ replace_root: { ... } || null, // self-contained, no leaking this.payload
25
+ set: { 'a.b': 42, 'c': true },
26
+ unset: ['x.y', 'z']
27
+ }
28
+ ```
29
+ This is MongoDB-native, Postgres-friendly, encapsulation-perfect, and requires the fewest changes to `instance.js`.
30
+
31
+ ### Final Verdict After Comparison
32
+
33
+ **Gemini’s evaluation is more accurate and pragmatic than my first pass.**
34
+ I gave too many points to Codex for “principled purity” that actually adds friction. Gemini correctly identified that the **collapsed delta object** is both simpler to maintain and easier to consume downstream.
35
+
36
+ ### Recommended Path Forward (Now Even Cleaner)
37
+
38
+ Adopt **Gemini’s synthesized architecture exactly** (with the tiny refinements below):
39
+
40
+ 1. `instance.js` updates (minimal):
41
+ - Add `deleteProperty` trap → populates `store.unset_keys` Set
42
+ - In `set` trap: if writing to a tracked root key (e.g. `data = {...}`), set `store.replace_root = value` and clear set/unset
43
+ - Cross-cancellation logic (4 lines)
44
+ - Add `$get_delta()` that returns the exact shape above + `$has_changes()`, `$rebase_changes()`
45
+
46
+ 2. Model layer stays almost identical to what both proposals wanted.
47
+
48
+ 3. SQL compiler gets the simplest possible contract:
49
+ ```js
50
+ if (delta.replace_root !== null) {
51
+ data = $1::jsonb
52
+ } else {
53
+ expr = data
54
+ for each unset → expr = expr #- '{path}'
55
+ for each set → expr = jsonb_set(expr, '{path}', $n::jsonb)
56
+ }
57
+ ```
58
+
59
+ This eliminates **every** hack, keeps the API surface 95% compatible, and is the smallest code change that gives 100% correctness.
60
+
61
+ ✅ I now fully align with Gemini’s evaluation (with the exception of preferring `replace_root: value` over a boolean + outer payload).
62
+
63
+ Would you like me to output the **complete updated `instance.js`** (diff-style or full file) plus the 15-line `$get_delta()` and `deleteProperty` implementation ready to paste? Or the SQL builder helper first?
@@ -0,0 +1,16 @@
1
+ ### Architectural Consensus Achieved
2
+
3
+ It is genuinely satisfying to see this kind of synthesis. [cite_start]Grok's reassessment is spot on—especially the realization that preserving a chronological operations array, while theoretically pure, creates unnecessary normalization overhead before the data ever reaches the database[cite: 61, 62].
4
+
5
+ [cite_start]By leaning into the collapsed delta object, we get O(1) state resolution essentially for free[cite: 44]. The proxy stays incredibly lightweight, and the database receives exactly the instructions it needs without any middleman guessing games.
6
+
7
+ Here is the finalized blueprint based on our mutual agreement:
8
+
9
+ * [cite_start]**The Internal State:** We utilize object keys to naturally collapse duplicate writes, avoiding complex graph analysis or manual conflict resolvers[cite: 43, 44].
10
+ * [cite_start]**The Cross-Cancellation Rules:** Three simple rules govern the proxy: a `set` removes an `unset`, an `unset` removes a `set`, and a `replace_root` clears everything else[cite: 48].
11
+ * [cite_start]**The Handoff:** The `$get_delta()` method will return a clean, self-contained object containing `replace_root`, `set`, and `unset`[cite: 64, 65].
12
+ * [cite_start]**The SQL Compiler:** The query builder accepts this delta and either executes a direct `data = $1::jsonb` replacement, or chains `#-` and `jsonb_set` for surgical updates[cite: 69].
13
+
14
+ We have successfully designed the hacks out of the system. The separation of concerns is perfect: the tracker tracks, the model orchestrates, and the SQL builder compiles.
15
+
16
+ Would you like me to draft the full updated `instance.js` file featuring the new `deleteProperty` trap and cross-cancellation logic, or should we start by writing the SQL builder helper?