mongo-query-normalizer 0.1.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 (316) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +193 -0
  3. package/README.zh-CN.md +194 -0
  4. package/dist/ast/builders.d.ts +8 -0
  5. package/dist/ast/builders.d.ts.map +1 -0
  6. package/dist/ast/builders.js +27 -0
  7. package/dist/ast/builders.js.map +1 -0
  8. package/dist/ast/contains-opaque.d.ts +3 -0
  9. package/dist/ast/contains-opaque.d.ts.map +1 -0
  10. package/dist/ast/contains-opaque.js +14 -0
  11. package/dist/ast/contains-opaque.js.map +1 -0
  12. package/dist/ast/guards.d.ts +7 -0
  13. package/dist/ast/guards.d.ts.map +1 -0
  14. package/dist/ast/guards.js +23 -0
  15. package/dist/ast/guards.js.map +1 -0
  16. package/dist/ast/hash.d.ts +4 -0
  17. package/dist/ast/hash.d.ts.map +1 -0
  18. package/dist/ast/hash.js +67 -0
  19. package/dist/ast/hash.js.map +1 -0
  20. package/dist/ast/index.d.ts +6 -0
  21. package/dist/ast/index.d.ts.map +1 -0
  22. package/dist/ast/index.js +22 -0
  23. package/dist/ast/index.js.map +1 -0
  24. package/dist/ast/types.d.ts +28 -0
  25. package/dist/ast/types.d.ts.map +1 -0
  26. package/dist/ast/types.js +3 -0
  27. package/dist/ast/types.js.map +1 -0
  28. package/dist/ast/visitor.d.ts +3 -0
  29. package/dist/ast/visitor.d.ts.map +1 -0
  30. package/dist/ast/visitor.js +12 -0
  31. package/dist/ast/visitor.js.map +1 -0
  32. package/dist/common/types.d.ts +46 -0
  33. package/dist/common/types.d.ts.map +1 -0
  34. package/dist/common/types.js +3 -0
  35. package/dist/common/types.js.map +1 -0
  36. package/dist/compile/compile.d.ts +5 -0
  37. package/dist/compile/compile.d.ts.map +1 -0
  38. package/dist/compile/compile.js +69 -0
  39. package/dist/compile/compile.js.map +1 -0
  40. package/dist/compile-selector.d.ts +9 -0
  41. package/dist/compile-selector.d.ts.map +1 -0
  42. package/dist/compile-selector.js +14 -0
  43. package/dist/compile-selector.js.map +1 -0
  44. package/dist/conflict.d.ts +18 -0
  45. package/dist/conflict.d.ts.map +1 -0
  46. package/dist/conflict.js +30 -0
  47. package/dist/conflict.js.map +1 -0
  48. package/dist/core/canonicalize.d.ts +11 -0
  49. package/dist/core/canonicalize.d.ts.map +1 -0
  50. package/dist/core/canonicalize.js +104 -0
  51. package/dist/core/canonicalize.js.map +1 -0
  52. package/dist/core/conditions.d.ts +8 -0
  53. package/dist/core/conditions.d.ts.map +1 -0
  54. package/dist/core/conditions.js +282 -0
  55. package/dist/core/conditions.js.map +1 -0
  56. package/dist/core/conflicts-and-tighten.d.ts +15 -0
  57. package/dist/core/conflicts-and-tighten.d.ts.map +1 -0
  58. package/dist/core/conflicts-and-tighten.js +26 -0
  59. package/dist/core/conflicts-and-tighten.js.map +1 -0
  60. package/dist/core/conflicts.d.ts +14 -0
  61. package/dist/core/conflicts.d.ts.map +1 -0
  62. package/dist/core/conflicts.js +157 -0
  63. package/dist/core/conflicts.js.map +1 -0
  64. package/dist/core/constraint-propagation.d.ts +15 -0
  65. package/dist/core/constraint-propagation.d.ts.map +1 -0
  66. package/dist/core/constraint-propagation.js +93 -0
  67. package/dist/core/constraint-propagation.js.map +1 -0
  68. package/dist/core/field-condition-normalize.d.ts +22 -0
  69. package/dist/core/field-condition-normalize.d.ts.map +1 -0
  70. package/dist/core/field-condition-normalize.js +225 -0
  71. package/dist/core/field-condition-normalize.js.map +1 -0
  72. package/dist/core/index.d.ts +6 -0
  73. package/dist/core/index.d.ts.map +1 -0
  74. package/dist/core/index.js +14 -0
  75. package/dist/core/index.js.map +1 -0
  76. package/dist/core/normalize.d.ts +8 -0
  77. package/dist/core/normalize.d.ts.map +1 -0
  78. package/dist/core/normalize.js +44 -0
  79. package/dist/core/normalize.js.map +1 -0
  80. package/dist/core/operators.d.ts +11 -0
  81. package/dist/core/operators.d.ts.map +1 -0
  82. package/dist/core/operators.js +26 -0
  83. package/dist/core/operators.js.map +1 -0
  84. package/dist/core/predicate-merge.d.ts +9 -0
  85. package/dist/core/predicate-merge.d.ts.map +1 -0
  86. package/dist/core/predicate-merge.js +105 -0
  87. package/dist/core/predicate-merge.js.map +1 -0
  88. package/dist/core/predicateMerge.d.ts +17 -0
  89. package/dist/core/predicateMerge.d.ts.map +1 -0
  90. package/dist/core/predicateMerge.js +57 -0
  91. package/dist/core/predicateMerge.js.map +1 -0
  92. package/dist/core/simplify.d.ts +11 -0
  93. package/dist/core/simplify.d.ts.map +1 -0
  94. package/dist/core/simplify.js +128 -0
  95. package/dist/core/simplify.js.map +1 -0
  96. package/dist/core/stable-structural-key.d.ts +7 -0
  97. package/dist/core/stable-structural-key.d.ts.map +1 -0
  98. package/dist/core/stable-structural-key.js +71 -0
  99. package/dist/core/stable-structural-key.js.map +1 -0
  100. package/dist/core/utils.d.ts +16 -0
  101. package/dist/core/utils.d.ts.map +1 -0
  102. package/dist/core/utils.js +109 -0
  103. package/dist/core/utils.js.map +1 -0
  104. package/dist/flatten/flatten.d.ts +43 -0
  105. package/dist/flatten/flatten.d.ts.map +1 -0
  106. package/dist/flatten/flatten.js +68 -0
  107. package/dist/flatten/flatten.js.map +1 -0
  108. package/dist/flatten/index.d.ts +2 -0
  109. package/dist/flatten/index.d.ts.map +1 -0
  110. package/dist/flatten/index.js +10 -0
  111. package/dist/flatten/index.js.map +1 -0
  112. package/dist/flatten.d.ts +43 -0
  113. package/dist/flatten.d.ts.map +1 -0
  114. package/dist/flatten.js +68 -0
  115. package/dist/flatten.js.map +1 -0
  116. package/dist/index.d.ts +4 -0
  117. package/dist/index.d.ts.map +1 -0
  118. package/dist/index.js +8 -0
  119. package/dist/index.js.map +1 -0
  120. package/dist/merge-ops.d.ts +33 -0
  121. package/dist/merge-ops.d.ts.map +1 -0
  122. package/dist/merge-ops.js +52 -0
  123. package/dist/merge-ops.js.map +1 -0
  124. package/dist/normalize-context.d.ts +16 -0
  125. package/dist/normalize-context.d.ts.map +1 -0
  126. package/dist/normalize-context.js +19 -0
  127. package/dist/normalize-context.js.map +1 -0
  128. package/dist/normalize.d.ts +6 -0
  129. package/dist/normalize.d.ts.map +1 -0
  130. package/dist/normalize.js +122 -0
  131. package/dist/normalize.js.map +1 -0
  132. package/dist/observe/diff.d.ts +7 -0
  133. package/dist/observe/diff.d.ts.map +1 -0
  134. package/dist/observe/diff.js +15 -0
  135. package/dist/observe/diff.js.map +1 -0
  136. package/dist/observe/level-boundary-hints.d.ts +11 -0
  137. package/dist/observe/level-boundary-hints.d.ts.map +1 -0
  138. package/dist/observe/level-boundary-hints.js +42 -0
  139. package/dist/observe/level-boundary-hints.js.map +1 -0
  140. package/dist/observe/level-preview-warning.d.ts +11 -0
  141. package/dist/observe/level-preview-warning.d.ts.map +1 -0
  142. package/dist/observe/level-preview-warning.js +42 -0
  143. package/dist/observe/level-preview-warning.js.map +1 -0
  144. package/dist/observe/metrics.d.ts +4 -0
  145. package/dist/observe/metrics.d.ts.map +1 -0
  146. package/dist/observe/metrics.js +30 -0
  147. package/dist/observe/metrics.js.map +1 -0
  148. package/dist/observe/warnings.d.ts +6 -0
  149. package/dist/observe/warnings.d.ts.map +1 -0
  150. package/dist/observe/warnings.js +24 -0
  151. package/dist/observe/warnings.js.map +1 -0
  152. package/dist/operations/compile.d.ts +7 -0
  153. package/dist/operations/compile.d.ts.map +1 -0
  154. package/dist/operations/compile.js +70 -0
  155. package/dist/operations/compile.js.map +1 -0
  156. package/dist/operations/conflicts-and-tighten.d.ts +29 -0
  157. package/dist/operations/conflicts-and-tighten.d.ts.map +1 -0
  158. package/dist/operations/conflicts-and-tighten.js +40 -0
  159. package/dist/operations/conflicts-and-tighten.js.map +1 -0
  160. package/dist/operations/conflicts.d.ts +28 -0
  161. package/dist/operations/conflicts.d.ts.map +1 -0
  162. package/dist/operations/conflicts.js +216 -0
  163. package/dist/operations/conflicts.js.map +1 -0
  164. package/dist/operations/conflictsAndTighten.d.ts +29 -0
  165. package/dist/operations/conflictsAndTighten.d.ts.map +1 -0
  166. package/dist/operations/conflictsAndTighten.js +40 -0
  167. package/dist/operations/conflictsAndTighten.js.map +1 -0
  168. package/dist/operations/merge.d.ts +18 -0
  169. package/dist/operations/merge.d.ts.map +1 -0
  170. package/dist/operations/merge.js +266 -0
  171. package/dist/operations/merge.js.map +1 -0
  172. package/dist/operations/parse.d.ts +20 -0
  173. package/dist/operations/parse.d.ts.map +1 -0
  174. package/dist/operations/parse.js +128 -0
  175. package/dist/operations/parse.js.map +1 -0
  176. package/dist/ops/compile-selector.d.ts +9 -0
  177. package/dist/ops/compile-selector.d.ts.map +1 -0
  178. package/dist/ops/compile-selector.js +14 -0
  179. package/dist/ops/compile-selector.js.map +1 -0
  180. package/dist/ops/index.d.ts +4 -0
  181. package/dist/ops/index.d.ts.map +1 -0
  182. package/dist/ops/index.js +14 -0
  183. package/dist/ops/index.js.map +1 -0
  184. package/dist/ops/merge-ops.d.ts +33 -0
  185. package/dist/ops/merge-ops.d.ts.map +1 -0
  186. package/dist/ops/merge-ops.js +52 -0
  187. package/dist/ops/merge-ops.js.map +1 -0
  188. package/dist/ops/parse-selector.d.ts +18 -0
  189. package/dist/ops/parse-selector.d.ts.map +1 -0
  190. package/dist/ops/parse-selector.js +29 -0
  191. package/dist/ops/parse-selector.js.map +1 -0
  192. package/dist/optimize.d.ts +18 -0
  193. package/dist/optimize.d.ts.map +1 -0
  194. package/dist/optimize.js +33 -0
  195. package/dist/optimize.js.map +1 -0
  196. package/dist/options/constants.d.ts +6 -0
  197. package/dist/options/constants.d.ts.map +1 -0
  198. package/dist/options/constants.js +72 -0
  199. package/dist/options/constants.js.map +1 -0
  200. package/dist/options/resolve.d.ts +3 -0
  201. package/dist/options/resolve.d.ts.map +1 -0
  202. package/dist/options/resolve.js +35 -0
  203. package/dist/options/resolve.js.map +1 -0
  204. package/dist/options/types.d.ts +2 -0
  205. package/dist/options/types.d.ts.map +1 -0
  206. package/dist/options/types.js +3 -0
  207. package/dist/options/types.js.map +1 -0
  208. package/dist/parse/parse.d.ts +4 -0
  209. package/dist/parse/parse.d.ts.map +1 -0
  210. package/dist/parse/parse.js +114 -0
  211. package/dist/parse/parse.js.map +1 -0
  212. package/dist/parse/plain-object.d.ts +2 -0
  213. package/dist/parse/plain-object.d.ts.map +1 -0
  214. package/dist/parse/plain-object.js +7 -0
  215. package/dist/parse/plain-object.js.map +1 -0
  216. package/dist/parse-selector.d.ts +18 -0
  217. package/dist/parse-selector.d.ts.map +1 -0
  218. package/dist/parse-selector.js +29 -0
  219. package/dist/parse-selector.js.map +1 -0
  220. package/dist/passes/canonicalize.d.ts +4 -0
  221. package/dist/passes/canonicalize.d.ts.map +1 -0
  222. package/dist/passes/canonicalize.js +40 -0
  223. package/dist/passes/canonicalize.js.map +1 -0
  224. package/dist/passes/normalize-predicate.d.ts +4 -0
  225. package/dist/passes/normalize-predicate.d.ts.map +1 -0
  226. package/dist/passes/normalize-predicate.js +57 -0
  227. package/dist/passes/normalize-predicate.js.map +1 -0
  228. package/dist/passes/normalize-shape.d.ts +4 -0
  229. package/dist/passes/normalize-shape.d.ts.map +1 -0
  230. package/dist/passes/normalize-shape.js +37 -0
  231. package/dist/passes/normalize-shape.js.map +1 -0
  232. package/dist/passes/simplify.d.ts +4 -0
  233. package/dist/passes/simplify.d.ts.map +1 -0
  234. package/dist/passes/simplify.js +49 -0
  235. package/dist/passes/simplify.js.map +1 -0
  236. package/dist/prune/conflict.d.ts +18 -0
  237. package/dist/prune/conflict.d.ts.map +1 -0
  238. package/dist/prune/conflict.js +30 -0
  239. package/dist/prune/conflict.js.map +1 -0
  240. package/dist/prune/index.d.ts +3 -0
  241. package/dist/prune/index.d.ts.map +1 -0
  242. package/dist/prune/index.js +9 -0
  243. package/dist/prune/index.js.map +1 -0
  244. package/dist/prune/prune.d.ts +11 -0
  245. package/dist/prune/prune.d.ts.map +1 -0
  246. package/dist/prune/prune.js +19 -0
  247. package/dist/prune/prune.js.map +1 -0
  248. package/dist/prune.d.ts +11 -0
  249. package/dist/prune.d.ts.map +1 -0
  250. package/dist/prune.js +19 -0
  251. package/dist/prune.js.map +1 -0
  252. package/dist/rewrite.d.ts +27 -0
  253. package/dist/rewrite.d.ts.map +1 -0
  254. package/dist/rewrite.js +62 -0
  255. package/dist/rewrite.js.map +1 -0
  256. package/dist/rules/experimental/hoist-common-predicates-from-or.d.ts +5 -0
  257. package/dist/rules/experimental/hoist-common-predicates-from-or.d.ts.map +1 -0
  258. package/dist/rules/experimental/hoist-common-predicates-from-or.js +153 -0
  259. package/dist/rules/experimental/hoist-common-predicates-from-or.js.map +1 -0
  260. package/dist/rules/logical/detect-common-predicates-in-or.d.ts +5 -0
  261. package/dist/rules/logical/detect-common-predicates-in-or.d.ts.map +1 -0
  262. package/dist/rules/logical/detect-common-predicates-in-or.js +82 -0
  263. package/dist/rules/logical/detect-common-predicates-in-or.js.map +1 -0
  264. package/dist/rules/predicate/collapse-contradictions.d.ts +5 -0
  265. package/dist/rules/predicate/collapse-contradictions.d.ts.map +1 -0
  266. package/dist/rules/predicate/collapse-contradictions.js +45 -0
  267. package/dist/rules/predicate/collapse-contradictions.js.map +1 -0
  268. package/dist/rules/predicate/dedupe-same-field-predicates.d.ts +5 -0
  269. package/dist/rules/predicate/dedupe-same-field-predicates.d.ts.map +1 -0
  270. package/dist/rules/predicate/dedupe-same-field-predicates.js +33 -0
  271. package/dist/rules/predicate/dedupe-same-field-predicates.js.map +1 -0
  272. package/dist/rules/predicate/merge-comparable-predicates.d.ts +5 -0
  273. package/dist/rules/predicate/merge-comparable-predicates.d.ts.map +1 -0
  274. package/dist/rules/predicate/merge-comparable-predicates.js +30 -0
  275. package/dist/rules/predicate/merge-comparable-predicates.js.map +1 -0
  276. package/dist/rules/predicate/merge-predicates-internal.d.ts +7 -0
  277. package/dist/rules/predicate/merge-predicates-internal.d.ts.map +1 -0
  278. package/dist/rules/predicate/merge-predicates-internal.js +186 -0
  279. package/dist/rules/predicate/merge-predicates-internal.js.map +1 -0
  280. package/dist/rules/shape/collapse-single-child-logical.d.ts +5 -0
  281. package/dist/rules/shape/collapse-single-child-logical.d.ts.map +1 -0
  282. package/dist/rules/shape/collapse-single-child-logical.js +20 -0
  283. package/dist/rules/shape/collapse-single-child-logical.js.map +1 -0
  284. package/dist/rules/shape/dedupe-logical-children.d.ts +6 -0
  285. package/dist/rules/shape/dedupe-logical-children.d.ts.map +1 -0
  286. package/dist/rules/shape/dedupe-logical-children.js +37 -0
  287. package/dist/rules/shape/dedupe-logical-children.js.map +1 -0
  288. package/dist/rules/shape/flatten-logical.d.ts +6 -0
  289. package/dist/rules/shape/flatten-logical.d.ts.map +1 -0
  290. package/dist/rules/shape/flatten-logical.js +37 -0
  291. package/dist/rules/shape/flatten-logical.js.map +1 -0
  292. package/dist/rules/shape/remove-empty-logical.d.ts +5 -0
  293. package/dist/rules/shape/remove-empty-logical.d.ts.map +1 -0
  294. package/dist/rules/shape/remove-empty-logical.js +21 -0
  295. package/dist/rules/shape/remove-empty-logical.js.map +1 -0
  296. package/dist/types.d.ts +70 -0
  297. package/dist/types.d.ts.map +1 -0
  298. package/dist/types.js +8 -0
  299. package/dist/types.js.map +1 -0
  300. package/dist/utils/compare-values.d.ts +6 -0
  301. package/dist/utils/compare-values.d.ts.map +1 -0
  302. package/dist/utils/compare-values.js +31 -0
  303. package/dist/utils/compare-values.js.map +1 -0
  304. package/dist/utils/deep-equal.d.ts +2 -0
  305. package/dist/utils/deep-equal.d.ts.map +1 -0
  306. package/dist/utils/deep-equal.js +30 -0
  307. package/dist/utils/deep-equal.js.map +1 -0
  308. package/dist/utils/is-opaque.d.ts +4 -0
  309. package/dist/utils/is-opaque.d.ts.map +1 -0
  310. package/dist/utils/is-opaque.js +42 -0
  311. package/dist/utils/is-opaque.js.map +1 -0
  312. package/dist/utils/stable-sort.d.ts +2 -0
  313. package/dist/utils/stable-sort.d.ts.map +1 -0
  314. package/dist/utils/stable-sort.js +16 -0
  315. package/dist/utils/stable-sort.js.map +1 -0
  316. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Mongo Query Normalizer
2
+
3
+ **English** | [中文](README.zh-CN.md)
4
+
5
+ An **observable, level-based** normalizer for MongoDB query objects. It stabilizes query **shape** at the conservative default, offers **preview** higher levels for analysis and experiments, and returns **predictable** output plus **metadata**—not a MongoDB planner optimizer.
6
+
7
+ > **v0.1.0 — production scope:** For **general production** traffic, use **`shape` only**—it is the **sole** level we recommend for that role in this release. **`predicate`**, **`logical`**, and **`experimental`** are **preview / experimental** surfaces; they fit **offline analysis**, **replay testing**, **semantic validation**, and **targeted experiments** better than a blanket default for all online requests.
8
+
9
+ ---
10
+
11
+ ## Why it exists
12
+
13
+ - Query **shape** diverges across builders and hand-written filters.
14
+ - Outputs can be **hard to compare**, log, or diff without a stable pass.
15
+ - You need a **low-risk normalization layer** that defaults to conservative behavior.
16
+
17
+ This library does **not** promise to make queries faster or to pick optimal indexes.
18
+
19
+ ---
20
+
21
+ ## Features
22
+
23
+ - **Level-based** normalization (`shape` → `predicate` → `logical` → `experimental`)
24
+ - **Conservative default**: `shape` only out of the box (the **only** level we recommend for general production in v0.1.0)
25
+ - **Observable** `meta`: changed flags, applied/skipped rules, warnings, hashes, optional stats
26
+ - **Stable / idempotent** output when rules apply (same options)
27
+ - **Opaque fallback** for unsupported operators (passthrough, not semantically rewritten)
28
+
29
+ ---
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install mongo-query-normalizer
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ import { normalizeQuery } from "mongo-query-normalizer";
43
+
44
+ const result = normalizeQuery({
45
+ $and: [{ status: "open" }, { $and: [{ priority: { $gte: 1 } }] }],
46
+ });
47
+
48
+ console.log(result.query);
49
+ console.log(result.meta);
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Default behavior
55
+
56
+ - **Default `level` is `"shape"`** (see `resolveNormalizeOptions()`).
57
+ - By default there is **no** aggressive predicate merge or logical hoisting.
58
+ - The goal is **stability and observability**, not “smart optimization.”
59
+
60
+ ---
61
+
62
+ ## Production guidance (v0.1.0)
63
+
64
+ - Use **`shape`** for **general production** traffic. It is the **only** level recommended for that purpose in v0.1.0.
65
+ - Levels above `shape` (`predicate`, `logical`, `experimental`) are **preview / unstable** surfaces. Use them for **offline analysis**, **replay testing**, and **targeted experiments** when you explicitly accept preview semantics—not as a default for all online requests.
66
+ - If you opt into a non-`shape` level, **`meta.warnings` includes a boundary notice** for that call. In **non-production** runs (`NODE_ENV !== "production"`), the library also prints a **matching `console.warn` once per level per process** so local development surfaces the same guidance without spamming repeated logs.
67
+
68
+ ---
69
+
70
+ ## Levels
71
+
72
+ ### `shape` (default)
73
+
74
+ **Recommended for production hot paths** (the only v0.1.0 level recommended for general production). Safe structural normalization only, for example:
75
+
76
+ - flatten logical nodes
77
+ - remove empty logical nodes
78
+ - collapse single-child logical nodes
79
+ - dedupe logical children
80
+ - canonical ordering
81
+
82
+ ### `predicate`
83
+
84
+ **Preview / not recommended for general production** in v0.1.0. On top of `shape`, conservative **predicate** cleanup:
85
+
86
+ - dedupe same-field predicates
87
+ - merge comparable predicates where modeled
88
+ - collapse clear contradictions to an unsatisfiable filter
89
+ - merge **direct** `$and` children that share the same field name before further predicate work (so contradictions like `{ $and: [{ a: 1 }, { a: 2 }] }` can be detected)
90
+
91
+ ### `logical`
92
+
93
+ **Preview / not recommended for general production** in v0.1.0. On top of `predicate`:
94
+
95
+ - **detect** common predicates inside `$or` (detection / metadata; **no** default hoisting)
96
+
97
+ ### `experimental`
98
+
99
+ **Preview / not recommended for general production** in v0.1.0. May **hoist** common predicates from `$or` when the corresponding rule is enabled—**not** for blanket production rollout.
100
+
101
+ ---
102
+
103
+ ## `meta` fields
104
+
105
+ | Field | Meaning |
106
+ |--------|---------|
107
+ | `changed` | Structural/predicate output differs from input (hash-based) |
108
+ | `level` | Resolved normalization level |
109
+ | `appliedRules` / `skippedRules` | Rule tracing |
110
+ | `warnings` | Non-fatal issues when observation is enabled, plus a **v0.1.0 boundary warning** whenever the resolved level is not `shape` (always present for that case, independent of `observe.collectWarnings`) |
111
+ | `bailedOut` | Safety stop; output reverts to pre-pass parse for that call |
112
+ | `bailoutReason` | Why bailout happened, if any |
113
+ | `beforeHash` / `afterHash` | Stable hashes for diffing |
114
+ | `stats` | Optional before/after tree metrics (`observe.collectMetrics`) |
115
+
116
+ ---
117
+
118
+ ## Unsupported / opaque behavior
119
+
120
+ Structures such as **`$nor`**, **`$regex`**, **`$not`**, **`$elemMatch`**, **`$expr`**, geo/text queries, and **unknown** operators are generally treated as **opaque**: they pass through or are preserved without full semantic rewriting. They are **not** guaranteed to participate in merge or contradiction logic.
121
+
122
+ ---
123
+
124
+ ## Stability policy
125
+
126
+ The **public contract** is:
127
+
128
+ - `normalizeQuery`
129
+ - `resolveNormalizeOptions`
130
+ - the exported **types** listed in the package entry
131
+
132
+ **Not** part of the public contract: internal AST, `parseQuery`, `compileQuery`, individual rules/passes, or utilities. They may change between versions.
133
+
134
+ ---
135
+
136
+ ## Principles (explicit)
137
+
138
+ 1. Default level is **`shape`**.
139
+ 2. At the default **`shape`** level, the API is **intended for general production use** in v0.1.0.
140
+ 3. **`predicate`** and above may change structure while aiming for **semantic equivalence** on modeled operators.
141
+ 4. **`experimental`** is for experiments or offline replay—**not** default online traffic.
142
+ 5. **Opaque** nodes are not rewritten semantically.
143
+ 6. Output should be **idempotent** under the same options when no bailout occurs.
144
+ 7. This library is **not** the MongoDB query planner or an optimizer.
145
+
146
+ ---
147
+
148
+ ## Example scenarios
149
+
150
+ **Online main path** — use default (`shape`); this is the supported production default in v0.1.0:
151
+
152
+ ```ts
153
+ normalizeQuery(query);
154
+ ```
155
+
156
+ **Offline analysis / replay / experiments** — opt into higher levels only when you accept preview semantics and non-`shape` boundary warnings (and optional dev console hints):
157
+
158
+ ```ts
159
+ normalizeQuery(query, { level: "predicate" });
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Public API
165
+
166
+ ```ts
167
+ normalizeQuery(query, options?) => { query, meta }
168
+ resolveNormalizeOptions(options?) => ResolvedNormalizeOptions
169
+ ```
170
+
171
+ Types: `NormalizeLevel`, `NormalizeOptions`, `NormalizeRules`, `NormalizeSafety`, `NormalizeObserve`, `ResolvedNormalizeOptions`, `NormalizeResult`, `NormalizeStats`.
172
+
173
+ ---
174
+
175
+ ## Semantic tests (property-based)
176
+
177
+ Randomized tests use **`mongodb-memory-server`** + **`fast-check`** to compare **real** `find` results (same `sort` / `skip` / `limit`, projection `{ _id: 1 }`) before and after `normalizeQuery` on a **fixed document schema** and a **restricted operator set** (see `test/helpers/arbitraries.js`). They assert matching **`_id` order**, **idempotency** of the returned `query`, and (for opaque operators) **non-crash / stable second pass** only. **`FC_SEED` / `FC_RUNS` defaults are centralized in `test/helpers/fc-config.js`** (also re-exported from `arbitraries.js`).
178
+
179
+ - **`npm run test:unit`** — unit tests (excludes `test/semantic/**`, `test/regression/**`, `test/property/**`; includes `test/contracts/**`, `test/invariants/**`, `test/performance/**`).
180
+ - **`npm run test:semantic`** — semantic + regression + property folders (defaults when env unset: see `fc-config.js`).
181
+ - **`npm run test:semantic:quick`** — lower **`FC_RUNS`** (script sets `45`) + **`FC_SEED=42`**, still runs `test/regression/**` and `test/property/**`.
182
+ - **`npm run test:semantic:ci`** — CI-oriented env (`FC_RUNS=200`, `FC_SEED=42` in script).
183
+
184
+ Override property-test parameters: **`FC_SEED`**, **`FC_RUNS`**, optional **`FC_QUICK=1`** (see `fc-config.js`). How to reproduce failures and when to add a fixed regression case: **`test/REGRESSION.md`**.
185
+
186
+ Full-text, geo, heavy **`$expr`**, **`$where`**, aggregation, collation, etc. stay **out** of the main semantic equivalence generator; opaque contracts live in **`test/contracts/opaque-operators.test.js`**.
187
+
188
+ ---
189
+
190
+ ## Contributor notes
191
+
192
+ - [SPEC.md](SPEC.md) — behavior-oriented specification.
193
+ - [docs/CANONICAL_FORM.md](docs/CANONICAL_FORM.md) — idempotency and canonical shape notes.
@@ -0,0 +1,194 @@
1
+ # Mongo Query Normalizer
2
+
3
+ [English](README.md) | **中文**
4
+
5
+ 一个面向 **MongoDB 查询对象** 的**分层规范化**工具:强调**稳定、可控、可观测**,而不是激进改写或执行计划优化。
6
+
7
+ > **v0.1.0 版本边界:** 面向**一般生产流量**,请**只使用 `shape`**——在本版本中,它是我们**唯一**作此用途推荐的 level。其上的 **`predicate`、`logical`、`experimental`** 属于**预览 / 实验向**能力,更适合**离线分析、回放测试、语义校验与定向实验**,不宜无差别地作为全量在线请求的默认策略。
8
+
9
+ ---
10
+
11
+ ## 为什么需要它
12
+
13
+ - 查询 **结构** 在不同写法下容易发散。
14
+ - 没有稳定层时,**对比、日志、回放** 成本高。
15
+ - 需要一层 **低风险** 的 query normalization,默认行为要保守。
16
+
17
+ 本库**不以**「自动让查询更快」或「替代 planner」作为卖点。
18
+
19
+ ---
20
+
21
+ ## 核心特性
22
+
23
+ - **按 level 分层**:`shape` → `predicate` → `logical` → `experimental`
24
+ - **默认保守**:开箱仅 `shape`;在 **v0.1.0** 中,这也是**唯一**建议用于**一般生产环境**的 level
25
+ - **可观测的 `meta`**:变更、规则、告警、哈希、可选统计
26
+ - **稳定 / 幂等**(相同 options、未熔断时)
27
+ - **不透明(opaque)回退**:不支持的算子以透传为主,不做完整语义改写
28
+
29
+ ---
30
+
31
+ ## 安装
32
+
33
+ ```bash
34
+ npm install mongo-query-normalizer
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 快速开始
40
+
41
+ ```ts
42
+ import { normalizeQuery } from "mongo-query-normalizer";
43
+
44
+ const result = normalizeQuery({
45
+ $and: [{ status: "open" }, { $and: [{ priority: { $gte: 1 } }] }],
46
+ });
47
+
48
+ console.log(result.query);
49
+ console.log(result.meta);
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 默认行为说明
55
+
56
+ - **默认 `level` 为 `shape`**(见 `resolveNormalizeOptions()`)。
57
+ - 默认**不会**做激进的谓词合并或逻辑上提。
58
+ - 默认目标是 **稳定与可观测**,不是「智能优化」。
59
+
60
+ ---
61
+
62
+ ## 生产环境建议(v0.1.0)
63
+
64
+ - **一般生产流量**请使用 **`shape`**;在 **v0.1.0** 中,这是**唯一**建议用于该场景的 level。
65
+ - `shape` 之上的 **`predicate`、`logical`、`experimental`** 属于**预览 / 不稳定**能力面,宜在**明确接受预览语义**的前提下,用于**离线分析**、**回放测试**、**语义验证**与**定向实验**,不宜作为全量在线请求的默认策略。
66
+ - 若启用**非 `shape`** 的 level,每次调用都会在 **`meta.warnings`** 中写入一条 **v0.1.0 版本边界说明**。在**非生产**环境(`NODE_ENV !== "production"`)下,还会在进程内对**同一 level 至多输出一次**与之对应的 **`console.warn`**,便于本地开发看到同样提示,又避免重复刷屏。
67
+
68
+ ---
69
+
70
+ ## Level 说明
71
+
72
+ ### `shape`(默认)
73
+
74
+ **推荐用于线上主路径**;在 **v0.1.0** 中,亦是**唯一**建议用于**一般生产环境**的层级。只做安全结构规范化,例如:
75
+
76
+ - flatten logical
77
+ - remove empty logical
78
+ - collapse single-child logical
79
+ - dedupe logical children
80
+ - canonical ordering
81
+
82
+ ### `predicate`
83
+
84
+ **预览能力**:在 **v0.1.0** 中**不建议**作为一般生产环境的默认选择。在 `shape` 之上增加**保守**谓词整理:
85
+
86
+ - 同字段谓词去重
87
+ - 可建模的比较类谓词合并
88
+ - 明确矛盾收敛为不可满足过滤器
89
+ - 在 `normalizePredicate` 中,**`$and` 下同名 field 的直接子 `FieldNode` 会先合并**,以便检出诸如 `{ $and: [{ a: 1 }, { a: 2 }] }` 的矛盾
90
+
91
+ ### `logical`
92
+
93
+ **预览能力**:在 **v0.1.0** 中**不建议**作为一般生产环境的默认选择。在 `predicate` 之上:
94
+
95
+ - **检测** `$or` 中的公共谓词(默认**只检测**,默认**不上提**)
96
+
97
+ ### `experimental`
98
+
99
+ **实验 / 预览层**:在 **v0.1.0** 中**不建议**作为一般生产环境的默认选择。
100
+
101
+ - 可在规则开启时对 `$or` 做 **hoist** 等实验性变换;**禁止**作为线上全量默认
102
+
103
+ ---
104
+
105
+ ## `meta` 说明
106
+
107
+ | 字段 | 含义 |
108
+ |------|------|
109
+ | `changed` | 输出相对输入是否变化(基于哈希) |
110
+ | `level` | 实际使用的规范化层级 |
111
+ | `appliedRules` / `skippedRules` | 规则应用轨迹 |
112
+ | `warnings` | 观察选项开启时的非致命告警;此外,只要解析后的 level **不是** `shape`,就会**始终**附带一条 **v0.1.0 边界说明**(**不**受 `observe.collectWarnings` 影响) |
113
+ | `bailedOut` | 是否触发安全熔断 |
114
+ | `bailoutReason` | 熔断原因 |
115
+ | `beforeHash` / `afterHash` | 前后稳定哈希 |
116
+ | `stats` | 可选的前后树统计(`observe.collectMetrics`) |
117
+
118
+ ---
119
+
120
+ ## 不支持 / opaque 行为
121
+
122
+ 以下结构通常**只透传或不参与完整语义改写**,例如:
123
+
124
+ `$nor`、`$regex`、`$not`、`$elemMatch`、`$expr`、geo / text、未知算子等。
125
+
126
+ ---
127
+
128
+ ## 稳定性策略
129
+
130
+ **对外承诺**仅包括:
131
+
132
+ - `normalizeQuery`
133
+ - `resolveNormalizeOptions`
134
+ - 入口导出的 **类型**
135
+
136
+ **不属于**对外契约:内部 AST、`parseQuery`、`compileQuery`、各 pass/rule、工具函数等,版本间可能变化。
137
+
138
+ ---
139
+
140
+ ## 必须明确的原则
141
+
142
+ 1. 默认是 **`shape`**。
143
+ 2. 在默认 **`shape`** 路径上,API 面向 **v0.1.0** 的**一般生产用途**而设计;更高 level 不在此承诺范围内。
144
+ 3. **`predicate` 及以上**可能改变查询结构,但在已建模算子上追求 **语义等价**。
145
+ 4. **`experimental`** 仅用于实验或离线回放验证。
146
+ 5. **opaque** 节点不会被语义重写。
147
+ 6. 在未熔断时,输出应对相同 options 保持 **幂等**。
148
+ 7. 本库 **不是** MongoDB 的 planner optimizer。
149
+
150
+ ---
151
+
152
+ ## 示例场景
153
+
154
+ - **线上主路径**:`normalizeQuery(query)`(默认 `shape`;**v0.1.0** 约定的生产侧默认路径)
155
+ - **离线分析 / 回放测试 / 语义验证 / 定向实验**:仅在可接受预览语义,以及非 `shape` 时的 `meta.warnings` 边界说明(与非生产环境下按 level 一次性的 `console.warn`)时,再启用更高 level,例如:
156
+
157
+ ```ts
158
+ normalizeQuery(query, { level: "predicate" });
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 对外 API
164
+
165
+ ```ts
166
+ normalizeQuery(query, options?) => { query, meta }
167
+ resolveNormalizeOptions(options?) => ResolvedNormalizeOptions
168
+ ```
169
+
170
+ 类型:`NormalizeLevel`、`NormalizeOptions`、`NormalizeRules`、`NormalizeSafety`、`NormalizeObserve`、`ResolvedNormalizeOptions`、`NormalizeResult`、`NormalizeStats`。
171
+
172
+ ---
173
+
174
+ ## 语义测试(基于属性的随机测试)
175
+
176
+ 使用 `mongodb-memory-server` 与 `fast-check`,在固定文档 schema 与受限算子集合下对比 normalize 前后真实 `find` 结果(相同 `sort` / `skip` / `limit`,投影 `{ _id: 1 }`),并校验返回 `query` 的幂等。生成器见 `test/helpers/arbitraries.js`,**fast-check 的 seed / runs 统一由 `test/helpers/fc-config.js` 读取**(勿在单测里硬编码默认值)。
177
+
178
+ - `npm run test:unit`:单元测试(含 `test/contracts`、`test/invariants`、`test/performance`)
179
+ - `npm run test:semantic`:语义 + 全量回归 + property(默认 `FC_RUNS=200`,见 `fc-config.js`)
180
+ - `npm run test:semantic:quick`:本地快速跑:**降低 `FC_RUNS`(当前脚本为 45)**,仍包含 `test/regression/**` 与 `test/property/**`
181
+ - `npm run test:semantic:ci`:CI 较完整配置(脚本内 `FC_RUNS=200`、`FC_SEED=42`)
182
+
183
+ 环境变量:`FC_SEED`、`FC_RUNS`;可选 `FC_QUICK=1` 在未设 `FC_RUNS` 时将默认 runs 降为 50(见 `fc-config.js`)。
184
+
185
+ **property 失败如何复现、何时沉淀成固定用例、命名与分类**:见 [`test/REGRESSION.md`](test/REGRESSION.md)。
186
+
187
+ 主随机语义等价**不包含**全文、地理、复杂 `$expr`、`$where`、聚合、collation 等;opaque 算子契约见 `test/contracts/opaque-operators.test.js`。
188
+
189
+ ---
190
+
191
+ ## 延伸阅读
192
+
193
+ - [SPEC.zh-CN.md](SPEC.zh-CN.md)
194
+ - [docs/CANONICAL_FORM.md](docs/CANONICAL_FORM.md)
@@ -0,0 +1,8 @@
1
+ import type { FalseNode, FieldNode, FieldPredicate, LogicalNode, OpaqueNode, QueryNode, TrueNode } from "./types";
2
+ export declare function trueNode(): TrueNode;
3
+ export declare function falseNode(): FalseNode;
4
+ export declare function opaqueNode(raw: unknown, reason?: string): OpaqueNode;
5
+ export declare function andNode(children: QueryNode[]): LogicalNode;
6
+ export declare function orNode(children: QueryNode[]): LogicalNode;
7
+ export declare function fieldNode(field: string, predicates: FieldPredicate[]): FieldNode;
8
+ //# sourceMappingURL=builders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/ast/builders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElH,wBAAgB,QAAQ,IAAI,QAAQ,CAEnC;AAED,wBAAgB,SAAS,IAAI,SAAS,CAErC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAEpE;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,CAE1D;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,CAEzD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,SAAS,CAEhF"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trueNode = trueNode;
4
+ exports.falseNode = falseNode;
5
+ exports.opaqueNode = opaqueNode;
6
+ exports.andNode = andNode;
7
+ exports.orNode = orNode;
8
+ exports.fieldNode = fieldNode;
9
+ function trueNode() {
10
+ return { type: "true" };
11
+ }
12
+ function falseNode() {
13
+ return { type: "false" };
14
+ }
15
+ function opaqueNode(raw, reason) {
16
+ return { type: "opaque", raw, reason };
17
+ }
18
+ function andNode(children) {
19
+ return { type: "logical", op: "$and", children };
20
+ }
21
+ function orNode(children) {
22
+ return { type: "logical", op: "$or", children };
23
+ }
24
+ function fieldNode(field, predicates) {
25
+ return { type: "field", field, predicates };
26
+ }
27
+ //# sourceMappingURL=builders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/ast/builders.ts"],"names":[],"mappings":";;AAEA,4BAEC;AAED,8BAEC;AAED,gCAEC;AAED,0BAEC;AAED,wBAEC;AAED,8BAEC;AAtBD,SAAgB,QAAQ;IACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,SAAgB,SAAS;IACrB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,SAAgB,UAAU,CAAC,GAAY,EAAE,MAAe;IACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED,SAAgB,OAAO,CAAC,QAAqB;IACzC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACrD,CAAC;AAED,SAAgB,MAAM,CAAC,QAAqB;IACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACpD,CAAC;AAED,SAAgB,SAAS,CAAC,KAAa,EAAE,UAA4B;IACjE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { QueryNode } from "./types";
2
+ export declare function containsOpaqueNode(node: QueryNode): boolean;
3
+ //# sourceMappingURL=contains-opaque.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contains-opaque.d.ts","sourceRoot":"","sources":["../../src/ast/contains-opaque.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAQ3D"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.containsOpaqueNode = containsOpaqueNode;
4
+ const guards_1 = require("./guards");
5
+ function containsOpaqueNode(node) {
6
+ if ((0, guards_1.isOpaqueNode)(node)) {
7
+ return true;
8
+ }
9
+ if ((0, guards_1.isLogicalNode)(node)) {
10
+ return node.children.some(containsOpaqueNode);
11
+ }
12
+ return false;
13
+ }
14
+ //# sourceMappingURL=contains-opaque.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contains-opaque.js","sourceRoot":"","sources":["../../src/ast/contains-opaque.ts"],"names":[],"mappings":";;AAGA,gDAQC;AAXD,qCAAuD;AAGvD,SAAgB,kBAAkB,CAAC,IAAe;IAC9C,IAAI,IAAA,qBAAY,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,IAAA,sBAAa,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { FalseNode, FieldNode, LogicalNode, OpaqueNode, QueryNode, TrueNode } from "./types";
2
+ export declare function isLogicalNode(node: QueryNode): node is LogicalNode;
3
+ export declare function isFieldNode(node: QueryNode): node is FieldNode;
4
+ export declare function isTrueNode(node: QueryNode): node is TrueNode;
5
+ export declare function isFalseNode(node: QueryNode): node is FalseNode;
6
+ export declare function isOpaqueNode(node: QueryNode): node is OpaqueNode;
7
+ //# sourceMappingURL=guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../src/ast/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,UAAU,EACV,SAAS,EACT,QAAQ,EACX,MAAM,SAAS,CAAC;AAEjB,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,WAAW,CAElE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,SAAS,CAE9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,QAAQ,CAE5D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,SAAS,CAE9D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,UAAU,CAEhE"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLogicalNode = isLogicalNode;
4
+ exports.isFieldNode = isFieldNode;
5
+ exports.isTrueNode = isTrueNode;
6
+ exports.isFalseNode = isFalseNode;
7
+ exports.isOpaqueNode = isOpaqueNode;
8
+ function isLogicalNode(node) {
9
+ return node.type === "logical";
10
+ }
11
+ function isFieldNode(node) {
12
+ return node.type === "field";
13
+ }
14
+ function isTrueNode(node) {
15
+ return node.type === "true";
16
+ }
17
+ function isFalseNode(node) {
18
+ return node.type === "false";
19
+ }
20
+ function isOpaqueNode(node) {
21
+ return node.type === "opaque";
22
+ }
23
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/ast/guards.ts"],"names":[],"mappings":";;AASA,sCAEC;AAED,kCAEC;AAED,gCAEC;AAED,kCAEC;AAED,oCAEC;AAlBD,SAAgB,aAAa,CAAC,IAAe;IACzC,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AACnC,CAAC;AAED,SAAgB,WAAW,CAAC,IAAe;IACvC,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC;AACjC,CAAC;AAED,SAAgB,UAAU,CAAC,IAAe;IACtC,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;AAChC,CAAC;AAED,SAAgB,WAAW,CAAC,IAAe;IACvC,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC;AACjC,CAAC;AAED,SAAgB,YAAY,CAAC,IAAe;IACxC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;AAClC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { FieldPredicate, QueryNode } from "./types";
2
+ export declare function hashPredicate(p: FieldPredicate): string;
3
+ export declare function hashNode(node: QueryNode): string;
4
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/ast/hash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzD,wBAAgB,aAAa,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAEvD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAmBhD"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashPredicate = hashPredicate;
4
+ exports.hashNode = hashNode;
5
+ function hashPredicate(p) {
6
+ return `op:${p.op}|opaque:${p.opaque ? 1 : 0}|v:${serializeValue(p.value)}`;
7
+ }
8
+ function hashNode(node) {
9
+ switch (node.type) {
10
+ case "true":
11
+ return "T";
12
+ case "false":
13
+ return "F";
14
+ case "opaque":
15
+ return `O:${node.reason ?? ""}:${serializeValue(node.raw)}`;
16
+ case "field": {
17
+ const parts = node.predicates.map(hashPredicate).sort();
18
+ return `F:${node.field}(${parts.join("\u001f")})`;
19
+ }
20
+ case "logical": {
21
+ const childKeys = node.children.map(hashNode).sort();
22
+ return `L:${node.op}(${childKeys.join("\u001f")})`;
23
+ }
24
+ default:
25
+ return "U";
26
+ }
27
+ }
28
+ function serializeValue(value) {
29
+ if (value === undefined) {
30
+ return "u";
31
+ }
32
+ if (value === null) {
33
+ return "n";
34
+ }
35
+ if (typeof value === "number") {
36
+ if (Object.is(value, -0)) {
37
+ return "num:-0";
38
+ }
39
+ if (!Number.isFinite(value)) {
40
+ return `num:${String(value)}`;
41
+ }
42
+ return `num:${value}`;
43
+ }
44
+ if (typeof value === "boolean") {
45
+ return `b:${value}`;
46
+ }
47
+ if (typeof value === "string") {
48
+ return `s:${JSON.stringify(value)}`;
49
+ }
50
+ if (value instanceof Date) {
51
+ return `d:${value.toISOString()}`;
52
+ }
53
+ if (value instanceof RegExp) {
54
+ return `r:${value.source}\u0000${value.flags}`;
55
+ }
56
+ if (Array.isArray(value)) {
57
+ return `a:[${value.map(serializeValue).join("\u001f")}]`;
58
+ }
59
+ if (typeof value === "object") {
60
+ const o = value;
61
+ const keys = Object.keys(o).sort();
62
+ const inner = keys.map((k) => `${JSON.stringify(k)}:${serializeValue(o[k])}`).join("\u001f");
63
+ return `o:{${inner}}`;
64
+ }
65
+ return `x:${String(value)}`;
66
+ }
67
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/ast/hash.ts"],"names":[],"mappings":";;AAEA,sCAEC;AAED,4BAmBC;AAvBD,SAAgB,aAAa,CAAC,CAAiB;IAC3C,OAAO,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;AAChF,CAAC;AAED,SAAgB,QAAQ,CAAC,IAAe;IACpC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM;YACP,OAAO,GAAG,CAAC;QACf,KAAK,OAAO;YACR,OAAO,GAAG,CAAC;QACf,KAAK,QAAQ;YACT,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAChE,KAAK,OAAO,CAAC,CAAC,CAAC;YACX,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;YACxD,OAAO,KAAK,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtD,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACb,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACrD,OAAO,KAAK,IAAI,CAAC,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACvD,CAAC;QACD;YACI,OAAO,GAAG,CAAC;IACnB,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,GAAG,CAAC;IACf,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,CAAC;IACf,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,OAAO,QAAQ,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,OAAO,KAAK,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,KAAK,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QACxB,OAAO,KAAK,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;IACtC,CAAC;IACD,IAAI,KAAK,YAAY,MAAM,EAAE,CAAC;QAC1B,OAAO,KAAK,KAAK,CAAC,MAAM,SAAS,KAAK,CAAC,KAAK,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,MAAM,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7F,OAAO,MAAM,KAAK,GAAG,CAAC;IAC1B,CAAC;IACD,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type { FieldNode, FieldPredicate, LogicalNode, OpaqueNode, QueryNode, TrueNode, FalseNode } from "./types";
2
+ export { andNode, falseNode, fieldNode, opaqueNode, orNode, trueNode } from "./builders";
3
+ export { isFalseNode, isFieldNode, isLogicalNode, isOpaqueNode, isTrueNode, } from "./guards";
4
+ export { hashNode, hashPredicate } from "./hash";
5
+ export { containsOpaqueNode } from "./contains-opaque";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ast/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EACH,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.containsOpaqueNode = exports.hashPredicate = exports.hashNode = exports.isTrueNode = exports.isOpaqueNode = exports.isLogicalNode = exports.isFieldNode = exports.isFalseNode = exports.trueNode = exports.orNode = exports.opaqueNode = exports.fieldNode = exports.falseNode = exports.andNode = void 0;
4
+ var builders_1 = require("./builders");
5
+ Object.defineProperty(exports, "andNode", { enumerable: true, get: function () { return builders_1.andNode; } });
6
+ Object.defineProperty(exports, "falseNode", { enumerable: true, get: function () { return builders_1.falseNode; } });
7
+ Object.defineProperty(exports, "fieldNode", { enumerable: true, get: function () { return builders_1.fieldNode; } });
8
+ Object.defineProperty(exports, "opaqueNode", { enumerable: true, get: function () { return builders_1.opaqueNode; } });
9
+ Object.defineProperty(exports, "orNode", { enumerable: true, get: function () { return builders_1.orNode; } });
10
+ Object.defineProperty(exports, "trueNode", { enumerable: true, get: function () { return builders_1.trueNode; } });
11
+ var guards_1 = require("./guards");
12
+ Object.defineProperty(exports, "isFalseNode", { enumerable: true, get: function () { return guards_1.isFalseNode; } });
13
+ Object.defineProperty(exports, "isFieldNode", { enumerable: true, get: function () { return guards_1.isFieldNode; } });
14
+ Object.defineProperty(exports, "isLogicalNode", { enumerable: true, get: function () { return guards_1.isLogicalNode; } });
15
+ Object.defineProperty(exports, "isOpaqueNode", { enumerable: true, get: function () { return guards_1.isOpaqueNode; } });
16
+ Object.defineProperty(exports, "isTrueNode", { enumerable: true, get: function () { return guards_1.isTrueNode; } });
17
+ var hash_1 = require("./hash");
18
+ Object.defineProperty(exports, "hashNode", { enumerable: true, get: function () { return hash_1.hashNode; } });
19
+ Object.defineProperty(exports, "hashPredicate", { enumerable: true, get: function () { return hash_1.hashPredicate; } });
20
+ var contains_opaque_1 = require("./contains-opaque");
21
+ Object.defineProperty(exports, "containsOpaqueNode", { enumerable: true, get: function () { return contains_opaque_1.containsOpaqueNode; } });
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ast/index.ts"],"names":[],"mappings":";;;AACA,uCAAyF;AAAhF,mGAAA,OAAO,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,kGAAA,MAAM,OAAA;AAAE,oGAAA,QAAQ,OAAA;AACpE,mCAMkB;AALd,qGAAA,WAAW,OAAA;AACX,qGAAA,WAAW,OAAA;AACX,uGAAA,aAAa,OAAA;AACb,sGAAA,YAAY,OAAA;AACZ,oGAAA,UAAU,OAAA;AAEd,+BAAiD;AAAxC,gGAAA,QAAQ,OAAA;AAAE,qGAAA,aAAa,OAAA;AAChC,qDAAuD;AAA9C,qHAAA,kBAAkB,OAAA"}