@es-joy/jsoe 0.28.1 → 0.29.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 (171) hide show
  1. package/CHANGES.md +23 -0
  2. package/README.md +2 -0
  3. package/badges/coverage-badge.svg +1 -1
  4. package/badges/tests-badge.svg +1 -1
  5. package/dist/deepEqual.d.ts +3 -0
  6. package/dist/deepEqual.d.ts.map +1 -0
  7. package/dist/formatAndTypeChoices.d.ts +6 -6
  8. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  9. package/dist/formats/schema.d.ts +5 -0
  10. package/dist/formats/schema.d.ts.map +1 -1
  11. package/dist/formats.d.ts +2 -2
  12. package/dist/formats.d.ts.map +1 -1
  13. package/dist/fundamentalTypes/dateType.d.ts +21 -2
  14. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  15. package/dist/fundamentalTypes/domexceptionType.d.ts +7 -0
  16. package/dist/fundamentalTypes/domexceptionType.d.ts.map +1 -1
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/jtltExtensions.d.ts +112 -0
  22. package/dist/jtltExtensions.d.ts.map +1 -0
  23. package/dist/search/fundamentalTypes/arraySearchType.d.ts +15 -0
  24. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -0
  25. package/dist/search/fundamentalTypes/bigintSearchType.d.ts +10 -0
  26. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -0
  27. package/dist/search/fundamentalTypes/blobSearchType.d.ts +19 -0
  28. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -0
  29. package/dist/search/fundamentalTypes/booleanSearchType.d.ts +11 -0
  30. package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -0
  31. package/dist/search/fundamentalTypes/catchSearchType.d.ts +18 -0
  32. package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -0
  33. package/dist/search/fundamentalTypes/dateSearchType.d.ts +10 -0
  34. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -0
  35. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts +16 -0
  36. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -0
  37. package/dist/search/fundamentalTypes/enumSearchType.d.ts +22 -0
  38. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -0
  39. package/dist/search/fundamentalTypes/errorSearchType.d.ts +9 -0
  40. package/dist/search/fundamentalTypes/errorSearchType.d.ts.map +1 -0
  41. package/dist/search/fundamentalTypes/fileSearchType.d.ts +19 -0
  42. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -0
  43. package/dist/search/fundamentalTypes/filelistSearchType.d.ts +32 -0
  44. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -0
  45. package/dist/search/fundamentalTypes/functionSearchType.d.ts +18 -0
  46. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -0
  47. package/dist/search/fundamentalTypes/mapSearchType.d.ts +18 -0
  48. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -0
  49. package/dist/search/fundamentalTypes/nanSearchType.d.ts +7 -0
  50. package/dist/search/fundamentalTypes/nanSearchType.d.ts.map +1 -0
  51. package/dist/search/fundamentalTypes/nullSearchType.d.ts +7 -0
  52. package/dist/search/fundamentalTypes/nullSearchType.d.ts.map +1 -0
  53. package/dist/search/fundamentalTypes/numberSearchType.d.ts +11 -0
  54. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -0
  55. package/dist/search/fundamentalTypes/objectSearchType.d.ts +15 -0
  56. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -0
  57. package/dist/search/fundamentalTypes/promiseSearchType.d.ts +20 -0
  58. package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -0
  59. package/dist/search/fundamentalTypes/regexpSearchType.d.ts +14 -0
  60. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -0
  61. package/dist/search/fundamentalTypes/setSearchType.d.ts +14 -0
  62. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -0
  63. package/dist/search/fundamentalTypes/stringSearchType.d.ts +13 -0
  64. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -0
  65. package/dist/search/fundamentalTypes/symbolSearchType.d.ts +16 -0
  66. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -0
  67. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts +7 -0
  68. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts.map +1 -0
  69. package/dist/search/index.d.ts +60 -0
  70. package/dist/search/index.d.ts.map +1 -0
  71. package/dist/search/queryTree.d.ts +180 -0
  72. package/dist/search/queryTree.d.ts.map +1 -0
  73. package/dist/search/queryTreeBuilders.d.ts +168 -0
  74. package/dist/search/queryTreeBuilders.d.ts.map +1 -0
  75. package/dist/search/searchDispatch.d.ts +121 -0
  76. package/dist/search/searchDispatch.d.ts.map +1 -0
  77. package/dist/search/searchElementUtils.d.ts +97 -0
  78. package/dist/search/searchElementUtils.d.ts.map +1 -0
  79. package/dist/search/searchUtils.d.ts +225 -0
  80. package/dist/search/searchUtils.d.ts.map +1 -0
  81. package/dist/search/subTypes/blobHTMLSearchType.d.ts +16 -0
  82. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -0
  83. package/dist/search/subTypes/recordSearchType.d.ts +18 -0
  84. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -0
  85. package/dist/search/subTypes/tupleSearchType.d.ts +21 -0
  86. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -0
  87. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts +14 -0
  88. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -0
  89. package/dist/search/superTypes/buffersourceSearchType.d.ts +17 -0
  90. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -0
  91. package/dist/search/superTypes/dommatrixSearchType.d.ts +11 -0
  92. package/dist/search/superTypes/dommatrixSearchType.d.ts.map +1 -0
  93. package/dist/search/superTypes/dompointSearchType.d.ts +9 -0
  94. package/dist/search/superTypes/dompointSearchType.d.ts.map +1 -0
  95. package/dist/search/superTypes/domrectSearchType.d.ts +9 -0
  96. package/dist/search/superTypes/domrectSearchType.d.ts.map +1 -0
  97. package/dist/search/superTypes/errorsSpecialSearchType.d.ts +11 -0
  98. package/dist/search/superTypes/errorsSpecialSearchType.d.ts.map +1 -0
  99. package/dist/search/unions/discriminatedUnionSearchType.d.ts +12 -0
  100. package/dist/search/unions/discriminatedUnionSearchType.d.ts.map +1 -0
  101. package/dist/search/unions/unionFamilySearchType.d.ts +36 -0
  102. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -0
  103. package/dist/search/unions/unionSearchType.d.ts +8 -0
  104. package/dist/search/unions/unionSearchType.d.ts.map +1 -0
  105. package/dist/search/unions/xorSearchType.d.ts +12 -0
  106. package/dist/search/unions/xorSearchType.d.ts.map +1 -0
  107. package/dist/superTypes/buffersourceType.d.ts.map +1 -1
  108. package/dist/typeChoices.d.ts +3 -3
  109. package/dist/typeChoices.d.ts.map +1 -1
  110. package/dist/types.d.ts +3 -3
  111. package/dist/types.d.ts.map +1 -1
  112. package/dist/utils/rawTypesonEditor.d.ts.map +1 -1
  113. package/docs/proposals/search-plan.md +166 -0
  114. package/package.json +5 -4
  115. package/pnpm-workspace.yaml +1 -1
  116. package/rollup.config.js +12 -0
  117. package/src/formatAndTypeChoices.js +4 -4
  118. package/src/formats/schema.js +2 -1
  119. package/src/formats.js +1 -1
  120. package/src/fundamentalTypes/dateType.js +35 -14
  121. package/src/fundamentalTypes/domexceptionType.js +41 -33
  122. package/src/index.js +6 -0
  123. package/src/jtltExtensions.js +103 -0
  124. package/src/search/fundamentalTypes/arraySearchType.js +66 -0
  125. package/src/search/fundamentalTypes/bigintSearchType.js +62 -0
  126. package/src/search/fundamentalTypes/blobSearchType.js +41 -0
  127. package/src/search/fundamentalTypes/booleanSearchType.js +40 -0
  128. package/src/search/fundamentalTypes/catchSearchType.js +59 -0
  129. package/src/search/fundamentalTypes/dateSearchType.js +73 -0
  130. package/src/search/fundamentalTypes/domexceptionSearchType.js +55 -0
  131. package/src/search/fundamentalTypes/enumSearchType.js +58 -0
  132. package/src/search/fundamentalTypes/errorSearchType.js +11 -0
  133. package/src/search/fundamentalTypes/fileSearchType.js +41 -0
  134. package/src/search/fundamentalTypes/filelistSearchType.js +78 -0
  135. package/src/search/fundamentalTypes/functionSearchType.js +78 -0
  136. package/src/search/fundamentalTypes/mapSearchType.js +87 -0
  137. package/src/search/fundamentalTypes/nanSearchType.js +11 -0
  138. package/src/search/fundamentalTypes/nullSearchType.js +11 -0
  139. package/src/search/fundamentalTypes/numberSearchType.js +52 -0
  140. package/src/search/fundamentalTypes/objectSearchType.js +148 -0
  141. package/src/search/fundamentalTypes/promiseSearchType.js +61 -0
  142. package/src/search/fundamentalTypes/regexpSearchType.js +48 -0
  143. package/src/search/fundamentalTypes/setSearchType.js +65 -0
  144. package/src/search/fundamentalTypes/stringSearchType.js +35 -0
  145. package/src/search/fundamentalTypes/symbolSearchType.js +38 -0
  146. package/src/search/fundamentalTypes/undefinedSearchType.js +11 -0
  147. package/src/search/index.js +79 -0
  148. package/src/search/queryTree.js +235 -0
  149. package/src/search/queryTreeBuilders.js +224 -0
  150. package/src/search/searchDispatch.js +291 -0
  151. package/src/search/searchElementUtils.js +289 -0
  152. package/src/search/searchUtils.js +355 -0
  153. package/src/search/subTypes/blobHTMLSearchType.js +65 -0
  154. package/src/search/subTypes/recordSearchType.js +84 -0
  155. package/src/search/subTypes/tupleSearchType.js +111 -0
  156. package/src/search/superTypes/SpecialRealNumberSearchType.js +42 -0
  157. package/src/search/superTypes/buffersourceSearchType.js +47 -0
  158. package/src/search/superTypes/dommatrixSearchType.js +24 -0
  159. package/src/search/superTypes/dompointSearchType.js +15 -0
  160. package/src/search/superTypes/domrectSearchType.js +15 -0
  161. package/src/search/superTypes/errorsSpecialSearchType.js +15 -0
  162. package/src/search/unions/discriminatedUnionSearchType.js +16 -0
  163. package/src/search/unions/unionFamilySearchType.js +160 -0
  164. package/src/search/unions/unionSearchType.js +12 -0
  165. package/src/search/unions/xorSearchType.js +16 -0
  166. package/src/superTypes/buffersourceType.js +13 -15
  167. package/src/typeChoices.js +2 -2
  168. package/src/types.js +2 -2
  169. package/src/utils/rawTypesonEditor.js +12 -2
  170. package/tsconfig-prod.json +12 -0
  171. package/tsconfig.json +5 -10
@@ -0,0 +1,166 @@
1
+ # Schema-driven search widget subsystem
2
+
3
+ ## Context
4
+
5
+ jsoe today has two things: per-type modules under `src/fundamentalTypes/`, `src/subTypes/`, `src/superTypes/` that render `viewUI`/`editUI` controls for *editing a value* against a zodexy/Zod schema, and a `README.md` to-do item ("**Schema-driven search**", `README.md:162-185`) that already specs, type-by-type, a second capability that has never been built: iterating a *schema alone* (no value) to build a hierarchical search widget — e.g. a `Date` property gets two date pickers for a range query, an `Object` schema gets additive "has property X" controls, a `Tuple` schema gets distinct per-position controls unlike a plain `Array`. The goal of this work is to build that second capability as a new, parallel subsystem, reusing jsoe's existing schema-shape recognition and rendering conventions rather than reinventing them.
6
+
7
+ Decisions the user has already made (not open for re-litigation during implementation):
8
+ 1. **Structure**: a parallel module tree, not new methods bolted onto the existing `viewUI`/`editUI` type objects — because schema-only iteration needs different per-type behavior than value iteration (tuple/record/discriminatedUnion in particular need their own dedicated logic, where the value-editing side currently delegates them to `arrayType`/`objectType` via a runtime `specificSchemaObject.type` check). The new modules should still *call into* existing type files to reuse concrete UI-building pieces where that makes sense (e.g. a date-range widget reusing the date `<input>` construction from `dateType.js`).
9
+ 2. **Output**: each widget produces a structured, serializable **query object** (an AND/OR tree of typed leaf constraints), not an in-memory predicate function, so a host app can translate it into an IndexedDB query, an HTTP query string, etc. IndexedDB specifically should be more than "translatable with effort" — the query language's shape is deliberately chosen so its `range` leaves map directly onto `IDBKeyRange` with no translation logic beyond picking the right constructor (see §2's new "IndexedDB executability" note).
10
+ 3. **Scope**: full breadth in this pass — all ~25 type variants in the README to-do, not a small slice first.
11
+ 4. **Query vocabulary**: the tree borrows MongoDB's own operator names (`$and`/`$or`, `$gt`/`$gte`/`$lt`/`$lte`, `$in`/`$nin`, `$regex`/`$options`, `$exists`) wherever a leaf kind has a clean Mongo equivalent, so a host can adapt the common cases to `sift()` (or real MongoDB) almost for free. This is *not* a claim of full drop-in Mongo query-document compatibility — several jsoe-specific leaf kinds (`blobHTML`, `domShape`, `keyValueEnum`, `mapRecordJoint`, `passThrough`) have no Mongo equivalent and stay custom, and the tree keeps its own `kind`-discriminated, path-carrying leaf shape rather than Mongo's field-keyed document shape, since jsoe needs things Mongo's shape can't express (e.g. multiple OR'd alternative constraints on the same path, per the README's "OR range/Is Not Range" pattern).
12
+ 5. **DOM primitives**: search controls are implemented as custom elements, registered via `jml`'s own `$define` attribute (confirmed in `~/jamilih/src/jml.js` — it already wraps `customElements.define()` for exactly this; no separate registration mechanism needed). Every search element — leaf and AND/OR container alike — implements the same small **polymorphic instance-method interface** (chiefly `getQuery()`), so reading and composing query state happens through method calls on element references rather than through fragile `querySelector`/class-name string matching into another control's implementation details. No shadow root: not because state-reading needs to reach through it (the method interface makes that a non-issue either way) but because Cypress/E2E tests still need to simulate real user interaction — typing into an actual `<input>`, clicking an actual checkbox — and a shadow boundary would get in the way of that, independent of how application state is read back. See §8. Retrofitting the *existing* value-editing modules (`src/fundamentalTypes/*.js` etc.) to the same custom-element-plus-method-interface pattern is a deliberate future to-do — also §8 — not part of this pass.
13
+
14
+ Confirmed mid-design clarifications from the user:
15
+ - jsoe represents several runtime types via zodexy's "checked" mechanism — `{type: 'any', checks: [{name: 'blob'}]}` for `Blob`, and similarly for `regexp`, `error`, `domrect`, etc. — read by `getCheckedType` (`src/formats/schema.js:117-121`, confirmed at lines 117-166). The new dispatcher must go through the *same* recognizer, not reimplement it.
16
+ - `type: 'instanceof'` is **not** a general-purpose case to handle broadly. It occurs in exactly one place: the `File` element schema nested inside a `FileList`'s `codec` definition (confirmed at `src/formats/schema.js:963-969`, resolving `parentSchema.output.element` when `typesonType === 'file'`). No standalone/top-level `instanceof` schema exists elsewhere in this codebase's usage; a bare `instanceof` schema outside that FileList nesting should fall through to the non-editable/escape-hatch case, not to a generic "instanceof" search widget.
17
+
18
+ ## Architecture
19
+
20
+ ### 1. New file layout — `src/search/`
21
+
22
+ A fourth top-level tree, sibling to `fundamentalTypes/`, `subTypes/`, `superTypes/`, mirroring their names so a contributor can always find `src/search/fundamentalTypes/dateSearchType.js` next to `src/fundamentalTypes/dateType.js`:
23
+
24
+ ```
25
+ src/search/
26
+ index.js # exports buildSearchChoices (top-level entry point)
27
+ searchDispatch.js # getSearchSchemaType(), availableSearchTypes registry, getSearchTypeObject()
28
+ queryTree.js # JSDoc typedefs only: QueryAnd/QueryOr/QueryLeaf and all leaf shapes
29
+ queryTreeBuilders.js # runtime helpers: makeAndNode, makeOrNode, makeXLeaf(...) constructors
30
+ searchUtils.js # shared UI helpers: buildPathLabel, buildRangeInputsPair, buildMultiSelect, buildHasPropertyToggle
31
+ fundamentalTypes/ # one *SearchType.js per src/fundamentalTypes/*Type.js: date, number, bigint,
32
+ # string, regexp, boolean, symbol, undefined, null, nan, array, object, map,
33
+ # set, filelist, file, blob, error, domexception, promise, function, enum
34
+ subTypes/ # tupleSearchType.js, recordSearchType.js, blobHTMLSearchType.js
35
+ superTypes/ # domrectSearchType.js, dompointSearchType.js, dommatrixSearchType.js,
36
+ # errorsSpecialSearchType.js, specialNumberSearchType.js,
37
+ # specialRealNumberSearchType.js, buffersourceSearchType.js
38
+ unions/ # unionSearchType.js, xorSearchType.js, discriminatedUnionSearchType.js
39
+ noneditableSearchType.js # instanceof-outside-FileList + not-yet-supported types: no widget, escape hatch only
40
+ ```
41
+
42
+ Modules that get their **own** dedicated file instead of delegating (unlike the value-editing side, which delegates tuple/record to `arrayType.js` and has no dedicated union module at all):
43
+ - `tupleSearchType.js` — each position needs its own control from `.items[i]`/`.rest`, not one control repeated per element.
44
+ - `recordSearchType.js` — record search is "key-schema search AND/OR value-schema search," structurally unlike object's additive "has property."
45
+ - `objectSearchType.js` — "has property `<X>`" is an additive pulldown-driven affordance (README: "avoid listing required"), fundamentally different from rendering every property, which is what value-editing's `objectType.js` (thin wrapper over `arrayType.js`) does.
46
+ - `discriminatedUnionSearchType.js` (plus sibling `unionSearchType.js`/`xorSearchType.js`) — the discriminator field drives a typed pulldown (README explicitly calls out "discriminator of discriminated union" as its own case), which has no value-editing analogue to delegate to.
47
+ - `mapSearchType.js` — key-search × value-search composite, closer to a bespoke type than a delegate.
48
+ - `enumSearchType.js` — the value-editing side has *no* dedicated `enumType.js` (`getSchemaType` resolves `enum` to whatever its underlying value type is, e.g. `'string'`); search needs a genuinely different `multiSelect` control (a list of the enum's actual allowed values) rather than a generic literal/regex box, so the search dispatcher intercepts `enum` explicitly (§3) instead of falling through.
49
+
50
+ ### 2. Query object shape (`src/search/queryTree.js`)
51
+
52
+ JSDoc-only typedefs; leaf `path` values reuse the existing JSON-Pointer convention from `src/utils/jsonPointer.js` (`makeJSONPointer`, `getJSONPointerParts`) rather than inventing a new path format.
53
+
54
+ ```js
55
+ /**
56
+ * @typedef {{$and: QueryNode[]}} QueryAnd
57
+ * @typedef {{$or: QueryNode[]}} QueryOr
58
+ * @typedef {QueryAnd|QueryOr|QueryLeaf} QueryNode
59
+ */
60
+ ```
61
+
62
+ Leaf kinds, discriminated by `kind`, each carrying a JSON-Pointer `path` (jsoe's own path convention, not Mongo's dot-notation — trivial for a host to convert, and keeping it means the search tree uses the same path format as the rest of jsoe, e.g. `arrayType.js`'s `currentPath`):
63
+ - `hasProperty` (object, via `$exists: true/false`); `lengthSize` (array/set/tuple-with-rest/filelist, via `$size`, +`sparseCheck` for array sparse/not-sparse) — if the schema has a minimum or maximum on length, enforce that in input box and show no box if the length is exactly fixed.
64
+ - `range` (number/NumberObject/bigint/date/buffersource, with `valueType` distinguishing them; bounds are Mongo-style `$gt`/`$gte`/`$lt`/`$lte` rather than a `min`/`max` pair plus an inclusive boolean — inclusivity is simply which operator is present. The README's "Is Not Range" variant wraps the same leaf in `$not` rather than being a separate kind), `integerCheck`
65
+ - `literalSet` (via `$in`/`$nin`), `regex` (via `$regex`/`$options`, matching Mongo's own field names), `notContains` (string/StringObject/Blob/File/regexp-source/symbol-description)
66
+ - `multiSelect` (enum, SpecialNumber's Infinity/-Infinity/NaN/-0; via `$in`/`$nin`), `keyValueEnum` (native enum key-vs-value; no Mongo equivalent, stays custom)
67
+ - `typeOf` (union/xor/discriminatedUnion "has type", carrying `discriminatorValue` when applicable, including when nested under a Map/Record key or value; no Mongo equivalent, stays custom)
68
+ - `blobHTML` (XPath/CSS-selector/full-text/raw-HTML-regex; no Mongo equivalent), `domShape` (per-dimension ranges for DOMRect/Point/Matrix, each dimension itself a `range` leaf) + `readonlyCheck`/`dimensionCheck` (is/is-not readonly, is/is-not 3d; no Mongo equivalent)
69
+ - `mapRecordJoint` (paired key+value leaves with a joint-match flag; no Mongo equivalent)
70
+ - `passThrough` (promise/literal/catch/function: forwards to a nested `QueryNode` for the child schema so the tree stays uniform even where a type adds no constraint of its own; purely structural, no Mongo equivalent)
71
+ - `presence` (undefined/void/null, via `$exists`), `booleanEquals` (boolean/BooleanObject — Mongo would normally express this as a bare `{field: true}` shorthand, which doesn't fit our path-carrying leaf shape, so this stays a custom kind)
72
+
73
+ `src/search/queryTreeBuilders.js` exposes the only runtime pieces — `makeAndNode`, `makeOrNode`, and one small factory per leaf kind — so every search module builds nodes through one place and tests can import the same constructors to build expected-value fixtures.
74
+
75
+ **IndexedDB executability.** IndexedDB has no native compound query document — its only real primitive is `IDBKeyRange` (`.only`/`.lowerBound`/`.upperBound`/`.bound`) evaluated against a single index via a cursor or `getAll(range)`, plus whatever a caller filters in JS as it iterates. The leaf shapes above are chosen so a host doesn't have to reinvent this mapping:
76
+ - A single-path `range` leaf translates directly: `$gte`+`$lte` present → `IDBKeyRange.bound(gte, lte, false, false)`; swap in `$gt`/`$lt` for the open-boundary form (`bound`'s 3rd/4th args); only a lower or only an upper bound present → `.lowerBound`/`.upperBound`; a bare equality (`$gte === $lte`, or a future `$eq`) → `.only`. No other leaf kind in this tree needs its own IndexedDB mapping rule beyond this one.
77
+ - A multi-path `$and` can become a *compound* `IDBKeyRange` only if the host already has a compound index (`createIndex(name, [pathA, pathB, ...])`) matching those exact paths in that exact order — jsoe never creates indexes itself, so this is opportunistic, not guaranteed. The normal, expected execution model is two-tier: pick whichever single leaf (or compound-indexed group of leaves) is most selective and IDB-native, open a cursor/`getAll` over just that `IDBKeyRange`, then evaluate the *rest* of the `$and` as an ordinary in-memory predicate per row — which is exactly the kind of walk `sift()` (decision 4, above) already does over this same tree, so the in-memory fallback and the Mongo-flavored vocabulary reinforce each other rather than needing separate code paths.
78
+ - `$or` has no native IndexedDB equivalent either; the standard pattern (multiple cursor queries, one per branch, merged and deduplicated by primary key) applies unchanged and needs nothing special from this tree's shape.
79
+ - Leaf kinds that can *never* be pushed to a native `IDBKeyRange` — `regex`, `blobHTML`, `keyValueEnum`, `mapRecordJoint`, `typeOf`, `passThrough`, and `domShape` as a whole (though each of its per-dimension `range` children individually is IDB-range-shaped) — always require the in-memory fallback pass. This should be documented plainly wherever `buildSearchChoices`/the query shape is documented for consumers, so it's an expected limitation rather than a surprise hit mid-integration. Also worth flagging for whoever implements the IndexedDB adapter: `IDBKeyRange` only accepts IndexedDB's own valid key types (number, string, `Date`, binary, or an `Array` of valid keys) — a `range` leaf whose `valueType` is `bigint` has no valid IndexedDB key representation at all and falls back to in-memory filtering same as the never-pushable kinds above.
80
+
81
+ ### 3. Schema-only dispatcher (`src/search/searchDispatch.js`)
82
+
83
+ Must not reimplement `getSchemaType`'s special-casing (stringbool pipe, `codec`/filelist, `instanceof`, `literal`, `enum`, `templateLiteral`, `getCheckedType` fallback — all at `src/formats/schema.js:117-166`) — it imports and calls `getSchemaType` directly, then adds only the extra shape distinctions that function's own `AvailableArbitraryType` output collapses (tuple, record/looseRecord, union/xor/discriminatedUnion all currently reduce to `'array'`/`'object'`/nothing, per `zodexToStructuredCloningTypeMap`, `schema.js:30-56`):
84
+
85
+ ```js
86
+ export function getSearchSchemaType (schemaObject) {
87
+ if (schemaObject.type === 'tuple') return 'tuple';
88
+ if (schemaObject.type === 'record' || schemaObject.type === 'looseRecord') {
89
+ return schemaObject.type;
90
+ }
91
+ if (['union', 'xor', 'discriminatedUnion'].includes(schemaObject.type)) {
92
+ return schemaObject.type;
93
+ }
94
+
95
+ // An `enum` control can show a multiple-select list and template literal parts might justify their own search controls, so need to detect these schema types.
96
+ if (['templateLiteral', 'enum'].includes(schemaObject.type)) {
97
+ return schemaObject.type;
98
+ }
99
+
100
+ return getSchemaType(schemaObject); // inherits stringbool/codec/instanceof/checks handling verbatim
101
+ }
102
+ ```
103
+
104
+ The `enum`/`templateLiteral` branch resolves as follows: `enum` routes to the new `enumSearchType.js` (its own `multiSelect` control, listing the schema's actual allowed values — falling through to `getSchemaType` here would collapse it into whatever its underlying value type is, landing it in a generic string/number search box instead). `templateLiteral` routes to `stringSearchType.js` for this pass — a template literal is still fundamentally a string-shape constraint, so treating it as one `regex`-style leaf is honestly sufficient for now; giving it dedicated per-part controls (one control per literal/interpolation segment) is a plausible future enhancement but not decided or scoped here, so `availableSearchTypes.templateLiteral` simply points at the same module as `string`.
105
+
106
+ Intersection schemas (`schemaObject.type === 'intersection'`) are **not** a case inside `getSearchSchemaType` at all — they're resolved *before* dispatch. jsoe already has real machinery for this on the value-editing side: `getTypesForSchema`'s `case 'intersection'` (`schema.js:643-652`) recursively flattens both `schemaObject.left` and `schemaObject.right`, then `flattenIntersection`/`mergeSchema` (`schema.js:401-524`) cross-merge same-type branches into concrete synthesized schemas (tighter of the two `min`/`max`/`minLength`/`maxLength` bounds, merged `properties`/`meta`/`description`, throwing on mismatched types or conflicting properties). The search-widget walker should call `getTypesForSchema` for exactly this one node type, hand each resulting merged schema to `getSearchSchemaType`/`getSearchTypeObject` as normal, and never register `'intersection'` itself in `availableSearchTypes`. (`getTypesForSchema` takes an `originalJSON` second argument used elsewhere for `$ref` resolution; the search side will need to supply *something* there — likely the root schema, mirroring existing callers — when this is actually implemented.) Unlike unions, intersection branches never need to stay distinguishable: an intersection is a single AND-of-constraints on one value, so collapsing it to its merged leaf(s) is exactly correct rather than a loss of information. The one edge case worth a passing note: if either side of the intersection is itself a union, flattening can yield more than one merged result — treat that set the same way `unionSearchType.js` treats branches, rather than assuming exactly one.
107
+
108
+ No dispatcher-level `instanceof` branch is added. Because the walker only ever reaches `getSchemaType`'s `instanceof` case when recursing into a FileList's `.output.element` (the one real occurrence in this codebase, per `schema.js:963-969`), that recursion naturally resolves to `'file'` and routes to `fileSearchType.js` — enforcing the "FileList-only" invariant by construction. A bare top-level `instanceof` schema (which doesn't occur via any current codec) falls through to `noneditableSearchType.js`'s escape hatch, same as any other unrecognized/not-yet-supported shape.
109
+
110
+ `availableSearchTypes` is a `Record<string, SearchTypeObject>` registry (analogous to `Types.prototype.availableTypes`, `src/types.js:473-581`) mapping every key `getSearchSchemaType` can return to its module; `getSearchTypeObject(schemaObject)` is the one exported call site every recursive widget uses (analogous to `types.getTypeObject(type)`, `src/types.js:1106-1108`, but schema-in rather than type-string-in, since there's no value to key off).
111
+
112
+ `SearchTypeObject` (JSDoc typedef alongside `queryTree.js`) is the search-side analogue of `TypeObject` (`src/types.js:308-408`): two methods, `buildUI({schemaObject, path, typeNamespace, topRoot, types})` returning a `jml` array, and `getQuery({root, path})` returning a `QueryNode|undefined` (`undefined` = "no constraint entered here").
113
+
114
+ ### 4. Reuse of existing value-editing modules
115
+
116
+ - **Date** (`src/fundamentalTypes/dateType.js`): extract the `<input type="datetime-local">` construction (currently inlined in `editUI`, lines 174-193, including the `min`/`max` wiring from `dateSchemaObject?.min`/`.max` and the ISO-slice formatting) into a new named export `buildDateInputControl`, added alongside the existing default export. `dateSearchType.js` calls it twice (range start/end). This is the **only** existing file this feature needs to modify, and it's purely additive — the default export and all existing behavior/tests are untouched.
117
+ - **Number/string/regexp** (`numberType.js`, `stringType.js`, `regexpType.js`): no extraction — these types' reusable unit is a single native `<input>`, too trivial to be worth a function boundary, and the value-editing versions carry string round-tripping logic (`stringRegex`/`toValue`) the search UI doesn't need. Search modules build their own inputs directly via `jml`. Exception: `regexpType.js`'s `allowedFlags` list (a plain property, already exported on the object) is imported as-is by `regexpSearchType.js` for its flags multi-select — no extraction needed, it's already accessible.
118
+ - **`getChildSchema`** (closure inside `arrayType.js`'s `editUI`, ~lines 1614-1690): not extracted (entangled with `arrayType.js`'s DOM-diffing state). `tupleSearchType.js`/`recordSearchType.js` write their own 1-3 line equivalents (`schemaObject.items[i] ?? schemaObject.rest`; `schemaObject.value`/`.key`) — below the threshold where duplication is a real risk.
119
+ - **`schemaLabel`** (`src/utils/schemaMeta.js`) and **`isUnionLike`** (`src/utils/types.js:9-25`): imported as-is by every relevant search module, same as the value-editing side already does.
120
+ - **`getXorBranchMatchInfo`** (`src/formats/schema.js:262-279`) is explicitly *not* reused by `xorSearchType.js` — it requires a concrete value to test branch match, which the search side never has.
121
+
122
+ ### 5. Public API entry point
123
+
124
+ New `src/search/index.js` exports `buildSearchChoices({schemaContent, typeNamespace, topRoot, types})`, re-exported from `src/index.js` alongside the existing `Types`/`Formats`/`typeChoices`/`formatAndTypeChoices`/`getTypesForSchema` exports (`src/index.js:13-27`). Mirrors `buildTypeChoices`'s (`src/typeChoices.js:395`) `whenReady`/pull-based convention rather than inventing a push/callback API: returns `{container, $getQuery, whenReady}`, where `$getQuery()` reads the live DOM into a `QueryAnd` on demand (same shape as `typeChoices.js`'s `$getValue`), and a host that wants live updates wraps it in its own `container.addEventListener('input', ...)` since `container` is a plain `HTMLDivElement`.
125
+
126
+ `buildSearchChoices` recurses via `getSearchTypeObject(...).buildUI(...)`, starting at `path = '#/'` — it does **not** route through `getTypesForSchema` (`src/formats/schema.js:581-864`), because that function flattens union members into one flat Set of leaf types for a type-choice dropdown, whereas search needs union branches to stay distinguishable nested sub-widgets for the "has type" affordance. Intersections could be mergeable if of the same type, however (though their constraints should still apply to the search control--e.g., a minlength on a string) — see §3 for the concrete resolution, which reuses jsoe's existing intersection-merging machinery rather than inventing a new one.
127
+
128
+ ### 6. Build order
129
+
130
+ 1. Query-tree contract + `queryTreeBuilders.js` + `searchDispatch.js` skeleton, with every registry entry pointing at a temporary stub (`{buildUI: () => ['span', ['TODO']], getQuery: () => undefined}`) so the full shape typechecks end-to-end immediately.
131
+ 2. `searchUtils.js` shared UI helpers + the additive `buildDateInputControl` export on `dateType.js`.
132
+ 3. Primitive leaves: date, number, bigint, string, regexp, boolean, enum, symbol, undefined, null, nan.
133
+ 4. Object / Array / Set (first types recursing into child schemas via `getSearchTypeObject`).
134
+ 5. Tuple / Record (isolated in their own step since per-position/per-key-vs-value logic is qualitatively different from step 4's recursion).
135
+ 6. Map / FileList / File / Blob — this is where the FileList→instanceof→File routing gets exercised for the first time; add the regression test described below here.
136
+ 7. Union family (union/xor/discriminatedUnion) — lands after composites so a union-of-composites round-trips through already-working recursion.
137
+ 8. Remaining independent leaves/composites: Error family, DOMException, DOMRect/Point/Matrix, BlobHTML, promise/function/literal/catch pass-through, SpecialNumber/SpecialRealNumber, buffersource, noneditable. `buffersource` and `function` are flagged as fuzzy in the README itself ("OR Range/Is Not Range" over raw bytes; args/return-type pass-through) — ship a minimal, honest stub for these two (byte-length range only; pass-through only if there's a searchable child) rather than over-building past what the spec actually defines.
138
+ 9. `buildSearchChoices` + `src/index.js` export (composes everything above).
139
+ 10. Demo page + Cypress suite.
140
+
141
+ Resolved watch item: `tsconfig.json` used to exclude `src/formats/schema.js` from type-checking, presumably because zodexy's generic unions were once thought to defeat strict narrowing there. That exclusion (and a stale, unused `./src/index.ts` entry) has since been removed — all three tsc scripts (`tsc`, `tsc:ts7`, `tsc-cypress`) pass cleanly against the whole repo without it. `src/search/searchDispatch.js`'s narrowing over the same `ZodexSchema` union therefore needs no special dispensation; if it somehow does hit a wall `schema.js` didn't, that would be a new, surprising finding worth its own investigation rather than an expected outcome.
142
+
143
+ ### 7. Test plan
144
+
145
+ Follows the existing Cypress-e2e-against-a-demo-page convention (this repo has no unit-test runner; per prior verified project knowledge, a change also isn't considered typechecked unless `tsc`, `tsc:ts7`, and `tsc-cypress` all pass, not just the default `tsc`):
146
+ - New `demo/index-search.html`/`-instrumented.html` + `demo/index-search.js`, structured like `demo/index-schema.html`/`.js`, reusing existing fixtures from `demo/schema-data.js` where they already cover a shape (e.g. its date schema with real `min`/`max` for the range-widget test), and additively exporting a few new fixtures it lacks (tuple-with-rest, record/looseRecord, a discriminatedUnion with a date branch).
147
+ - New `cypress/e2e/search/` directory mirroring the existing `fundamentalTypes/`/`subTypes/`/`superTypes/` structure, each with an `all.cy.js` aggregator per that convention.
148
+ - A representative spec (`cypress/e2e/search/fundamentalTypes/date.cy.js`): visit the search demo page, type into both range inputs, trigger the demo's "get query" button, assert the logged/parsed JSON matches the expected `QueryAnd` shape (start/end values, `valueType: 'date'`), and separately assert the two `datetime-local` inputs' `min`/`max` HTML attributes match the schema's constraints — proving `dateSearchType.js` is actually calling `buildDateInputControl` rather than a drifted re-implementation.
149
+ - A dedicated regression spec for the FileList/instanceof invariant: build a FileList search widget and assert its element control is `fileSearchType`'s UI (not the generic `noneditableSearchType` stub), and separately confirm a bare `instanceof` schema outside a FileList (if constructible via the demo fixtures) renders `noneditableSearchType`'s escape hatch instead — this is the one test directly protecting the FileList-only `instanceof` invariant from regressing.
150
+
151
+ ### 8. DOM primitives: custom elements with a polymorphic method interface, no shadow DOM
152
+
153
+ Search containers and leaves are built as custom elements rather than generic `div`/`fieldset` soup, for semantic clarity (e.g. `<jsoe-search-and>`, `<jsoe-search-or>`, `<jsoe-search-date>`, `<jsoe-search-object>` instead of a stack of same-tag `div`s distinguished only by class). This needs no new tooling: `jml`'s own `$define` attribute (confirmed in `~/jamilih/src/jml.js`) already wraps `window.customElements.define()`, supporting both autonomous hyphenated tag names and "customized built-ins" via `is=`.
154
+
155
+ This also replaces the `SearchTypeObject` contract from §3 with something more robust than a free function threaded through `{root, path}`: every search element class (leaf and container alike) implements the same small **polymorphic method interface** — chiefly `getQuery(): QueryNode|undefined` — so reading state, composing recursively, and any future host integration go through method calls on element references rather than `querySelector`/class-name matching into another control's private markup (the same brittleness class as the `.jsoe-raw-editor .cm-content` selector chain the raw-editor dialog already leans on, which is exactly what we want to avoid repeating here). `<jsoe-search-and>`/`<jsoe-search-or>` implement `getQuery()` by walking `this.children` and calling `.getQuery()` on each polymorphically — they don't need to know or care what concrete leaf/container type each child is, only that it answers to `.getQuery()` — combining the results per `$and`/`$or` without a separate schema re-dispatch pass at read time. `buildUI` (§3) becomes whatever constructs/populates the element (its constructor or a render method), still calling into existing value-editing helpers like `dateType.js`'s extracted `buildDateInputControl` (§4) internally to build its actual form controls.
156
+
157
+ Given the method interface handles state-reading, the *only* remaining reason to skip `attachShadow()` is that Cypress/E2E tests need to simulate real user interaction against the actual rendered form controls (type into a real `<input>`, click a real checkbox) — a shadow boundary would get in the way of that regardless of how well-designed the JS-facing API is. jsoe's existing `$e`/`$$e` helpers (`src/utils/templateUtils.js`) and the whole Cypress suite's plain-`querySelector` conventions incidentally also keep working unmodified this way, but that's a side effect of the testability call, not the primary reason for it.
158
+
159
+ **Future to-do, explicitly out of scope for this pass**: retrofit the *existing* value-editing modules (`src/fundamentalTypes/*.js`, `src/subTypes/*.js`, `src/superTypes/*.js`) to render via the same custom-element pattern instead of today's generic `div`s. This is a much larger, separate-blast-radius change — it touches every existing type file and every existing Cypress spec's plain-class/tag selectors across the whole suite — with no dependency relationship to the search subsystem, so it's noted here as a marker for a future, standalone initiative rather than scheduled in the build order above.
160
+
161
+ ## Verification
162
+
163
+ - `npm run tsc && npm run tsc:ts7 && npm run tsc-cypress` clean after every step (per existing project convention: all three scripts, not just the default, gate a change).
164
+ - `npm run eslint` clean (`eslint-config-ash-nazg(['sauron','browser'])`).
165
+ - `npm run cypress` (or `npm test`, which runs `eslint && rollup && cypress`) green, including the new `cypress/e2e/search/**` specs.
166
+ - Manually open `demo/index-search.html` in a browser and exercise a Date range, an Object "has property," and a Tuple's per-position controls to confirm the rendered UI and the printed query JSON match expectations before considering any given step done.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@es-joy/jsoe",
3
- "version": "0.28.1",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./src/index.js",
@@ -24,6 +24,7 @@
24
24
  "excludeAfterRemap": true,
25
25
  "exclude": [
26
26
  "node_modules/**",
27
+ "src/jtltExtensions.js",
27
28
  "src/fundamentalTypes/sparseUndefinedType.js"
28
29
  ]
29
30
  },
@@ -45,7 +46,7 @@
45
46
  "acorn": "^8.18.0",
46
47
  "codemirror": "^6.0.2",
47
48
  "crelt": "^1.0.7",
48
- "jamilih": "0.70.0",
49
+ "jamilih": "0.70.2",
49
50
  "json-6": "^1.1.4",
50
51
  "mime": "^4.1.0",
51
52
  "sceditor": "3.2.1",
@@ -67,7 +68,7 @@
67
68
  "@rollup/plugin-node-resolve": "^16.0.3",
68
69
  "@rollup/plugin-terser": "^1.0.0",
69
70
  "@types/jsdom": "^30.0.0",
70
- "@types/node": "^26.4.1",
71
+ "@types/node": "^26.5.1",
71
72
  "axe-core": "^4.13.0",
72
73
  "coveradge": "^0.8.2",
73
74
  "cypress": "^16.0.0",
@@ -112,7 +113,7 @@
112
113
  "scripts": {
113
114
  "tsc": "tsc",
114
115
  "tsc:ts7": "npx -y -p typescript@^7 -c \"tsc\"",
115
- "tsc-cypress": "tsc -p cypress/tsconfig.json",
116
+ "tsc-prod": "tsc -p tsconfig-prod.json",
116
117
  "license-badge": "license-badger --corrections --uncategorizedLicenseTemplate \"\\${license} (\\${name} (\\${version}))\" --filteredTypes=nonempty --textTemplate \"License types\n(project, deps, and bundled devDeps)\" --packageJson --production badges/licenses-badge.svg",
117
118
  "eslint": "eslint .",
118
119
  "lint": "npm run eslint --",
@@ -6,7 +6,7 @@ allowBuilds:
6
6
  unrs-resolver: true
7
7
 
8
8
  minimumReleaseAgeExclude:
9
- - jamilih@0.68.0 || 0.68.1 || 0.69.0
9
+ - jamilih@0.68.0 || 0.68.1 || 0.69.0 || 0.70.1 || 0.70.2
10
10
  - cypress@15.20.1 || 15.21.0 || 16.0.0
11
11
  - '@es-joy/jsdoccomment@0.95.0 || 0.95.1'
12
12
  - '@typescript-eslint/types@8.67.0 || 8.69.0'
package/rollup.config.js CHANGED
@@ -87,6 +87,18 @@ export default [{
87
87
  nodeResolve({browser: true}),
88
88
  commonjs()
89
89
  ]
90
+ }, {
91
+ input: 'demo/index-search.js',
92
+ output: {
93
+ file: 'instrumented/demo/index-search.js',
94
+ format: 'es'
95
+ },
96
+ plugins: [
97
+ builtins(),
98
+ istanbul({exclude: ['node_modules/**']}),
99
+ nodeResolve({browser: true}),
100
+ commonjs()
101
+ ]
90
102
  }, {
91
103
  input: 'node_modules/fast-deep-equal/es6/index.js',
92
104
  output: {
@@ -36,7 +36,7 @@ import {$e, DOM} from './utils/templateUtils.js';
36
36
  * @callback SetFormat
37
37
  * @param {{
38
38
  * valueFormat?: import('./formats.js').AvailableFormat,
39
- * autoTrigger: boolean,
39
+ * autoTrigger?: boolean,
40
40
  * schema?: string
41
41
  * }} cfg
42
42
  * @returns {Promise<void>}
@@ -117,7 +117,7 @@ export const getFormatAndSchemaChoices = ({
117
117
  /**
118
118
  * @callback SetValue
119
119
  * @param {import('./formats.js').StructuredCloneValue} value
120
- * @param {import('./types.js').StateObject} stateObj
120
+ * @param {import('./types.js').StateObject} [stateObj]
121
121
  * @returns {Promise<void>}
122
122
  */
123
123
 
@@ -156,8 +156,8 @@ export const getFormatAndSchemaChoices = ({
156
156
  * formatChoices: FormatChoices,
157
157
  * typesHolder: TypesHolder,
158
158
  * domArray: [formatChoices: FormatChoices, typesHolder: TypesHolder],
159
- * getValue: (stateObj: import('./types.js').StateObject,
160
- * currentPath: string) => import('./formats.js').StructuredCloneValue,
159
+ * getValue: (stateObj?: import('./types.js').StateObject,
160
+ * currentPath?: string) => import('./formats.js').StructuredCloneValue,
161
161
  * getType: () => string,
162
162
  * validValuesSet: () => boolean,
163
163
  * setValue: SetValue,
@@ -35,6 +35,7 @@ const zodexToStructuredCloningTypeMap = new Map([
35
35
  ['string', 'string'],
36
36
  ['date', 'date'],
37
37
  ['file', 'file'],
38
+ ['symbol', 'symbol'],
38
39
  ['undefined', 'undef'],
39
40
  ['void', 'undef'],
40
41
  ['null', 'null'],
@@ -142,7 +143,7 @@ function isStringboolSchema (schemaObject) {
142
143
  * @param {ZodexSchema} schemaObject
143
144
  * @returns {import('../types.js').AvailableArbitraryType|undefined}
144
145
  */
145
- function getSchemaType (schemaObject) {
146
+ export function getSchemaType (schemaObject) {
146
147
  if (isStringboolSchema(schemaObject)) {
147
148
  return 'string';
148
149
  }
package/src/formats.js CHANGED
@@ -146,7 +146,7 @@ class Formats {
146
146
  * @param {import('./types.js').default} types
147
147
  * @param {AvailableFormat} format
148
148
  * @param {StructuredCloneValue} record
149
- * @param {import('./types.js').StateObject} stateObj
149
+ * @param {import('./types.js').StateObject} [stateObj]
150
150
  * @returns {Promise<Required<import('./types.js').StateObject>>}
151
151
  */
152
152
  async getControlsForFormatAndValue (types, format, record, stateObj) {
@@ -8,6 +8,38 @@ import {schemaLabel} from '../utils/schemaMeta.js';
8
8
  * ) => void} SetValidity
9
9
  */
10
10
 
11
+ /**
12
+ * The `<input type="datetime-local">` construction shared between this
13
+ * type's own `editUI` and the search subsystem's date-range widget
14
+ * (`src/search/fundamentalTypes/dateSearchType.js` calls this twice, once
15
+ * per range endpoint).
16
+ * @param {{
17
+ * name: string,
18
+ * value?: Date|string,
19
+ * notANum?: boolean,
20
+ * dateSchemaObject?: import('zodexy').SzDate
21
+ * }} cfg
22
+ * @returns {import('../types.js').JamilihArray}
23
+ */
24
+ export function buildDateInputControl ({
25
+ name, value = '', notANum = false, dateSchemaObject
26
+ }) {
27
+ return ['input', {
28
+ name,
29
+ type: 'datetime-local',
30
+ // Required yyyy-MM-dd format
31
+ value: !value || notANum
32
+ ? ''
33
+ : /** @type {Date} */ (value).toISOString().slice(0, -8),
34
+ min: dateSchemaObject?.min
35
+ ? new Date(dateSchemaObject.min).toISOString().slice(0, -8)
36
+ : undefined,
37
+ max: dateSchemaObject?.max
38
+ ? new Date(dateSchemaObject.max).toISOString().slice(0, -8)
39
+ : undefined
40
+ }];
41
+ }
42
+
11
43
  /**
12
44
  * @type {import('../types.js').TypeObject & {
13
45
  * dateRegex: RegExp,
@@ -176,20 +208,9 @@ const dateType = {
176
208
  hidden: notANum
177
209
  }, [
178
210
  'Date: ',
179
- ['input', {
180
- name: `${typeNamespace}-date`,
181
- type: 'datetime-local',
182
- // Required yyyy-MM-dd format
183
- value: !val || notANum
184
- ? ''
185
- : val.toISOString().slice(0, -8),
186
- min: dateSchemaObject?.min
187
- ? new Date(dateSchemaObject.min).toISOString().slice(0, -8)
188
- : undefined,
189
- max: dateSchemaObject?.max
190
- ? new Date(dateSchemaObject.max).toISOString().slice(0, -8)
191
- : undefined
192
- }]
211
+ buildDateInputControl({
212
+ name: `${typeNamespace}-date`, value: val, notANum, dateSchemaObject
213
+ })
193
214
  ]],
194
215
  invalid
195
216
  ]];
@@ -4,6 +4,46 @@ import {schemaLabel} from '../utils/schemaMeta.js';
4
4
 
5
5
  let domExceptionNameId = 0;
6
6
 
7
+ /**
8
+ * The standard predefined `DOMException` names, shared with
9
+ * `src/search/fundamentalTypes/domexceptionSearchType.js`'s "pull-down of
10
+ * name for DOMException" (README) so the two lists can't drift apart.
11
+ * @type {string[]}
12
+ */
13
+ export const domExceptionNames = [
14
+ 'IndexSizeError',
15
+ 'HierarchyRequestError',
16
+ 'WrongDocumentError',
17
+ 'InvalidCharacterError',
18
+ 'NoModificationAllowedError',
19
+ 'NotFoundError',
20
+ 'NotSupportedError',
21
+ 'InvalidStateError',
22
+ 'InUseAttributeError',
23
+ 'SyntaxError',
24
+ 'InvalidModificationError',
25
+ 'NamespaceError',
26
+ 'InvalidAccessError',
27
+ 'TypeMismatchError',
28
+ 'SecurityError',
29
+ 'NetworkError',
30
+ 'AbortError',
31
+ 'URLMismatchError',
32
+ 'QuotaExceededError',
33
+ 'TimeoutError',
34
+ 'InvalidNodeTypeError',
35
+ 'DataCloneError',
36
+ 'EncodingError',
37
+ 'NotReadableError',
38
+ 'UnknownError',
39
+ 'ConstraintError',
40
+ 'DataError',
41
+ 'TransactionInactiveError',
42
+ 'ReadOnlyErrorVersionError',
43
+ 'OperationError',
44
+ 'NotAllowedError'
45
+ ];
46
+
7
47
  /**
8
48
  * @type {import('../types.js').TypeObject}
9
49
  */
@@ -72,39 +112,7 @@ const domexceptionType = {
72
112
  ['datalist', {
73
113
  id: `domExceptionNames-${domExceptionNameId}`,
74
114
  class: 'predefinedNames'
75
- }, [
76
- 'IndexSizeError',
77
- 'HierarchyRequestError',
78
- 'WrongDocumentError',
79
- 'InvalidCharacterError',
80
- 'NoModificationAllowedError',
81
- 'NotFoundError',
82
- 'NotSupportedError',
83
- 'InvalidStateError',
84
- 'InUseAttributeError',
85
- 'SyntaxError',
86
- 'InvalidModificationError',
87
- 'NamespaceError',
88
- 'InvalidAccessError',
89
- 'TypeMismatchError',
90
- 'SecurityError',
91
- 'NetworkError',
92
- 'AbortError',
93
- 'URLMismatchError',
94
- 'QuotaExceededError',
95
- 'TimeoutError',
96
- 'InvalidNodeTypeError',
97
- 'DataCloneError',
98
- 'EncodingError',
99
- 'NotReadableError',
100
- 'UnknownError',
101
- 'ConstraintError',
102
- 'DataError',
103
- 'TransactionInactiveError',
104
- 'ReadOnlyErrorVersionError',
105
- 'OperationError',
106
- 'NotAllowedError'
107
- ].map((name) => {
115
+ }, domExceptionNames.map((name) => {
108
116
  return ['option', [name]];
109
117
  })],
110
118
  ['br'],
package/src/index.js CHANGED
@@ -26,3 +26,9 @@ export {
26
26
  export {
27
27
  getTypesForSchema
28
28
  } from './formats/schema.js';
29
+
30
+ export {
31
+ buildSearchChoices
32
+ } from './search/index.js';
33
+
34
+ export {default as createJtltExtensions} from './jtltExtensions.js';
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A minimal structural stand-in for the jtlt template `this` context — just
3
+ * the two members these extensions use. Defined locally (rather than
4
+ * imported from jtlt) so this module adds no dependency on jtlt itself;
5
+ * jtlt's own `ContextExtensions` augmentation is a wider, compatible type.
6
+ * `get` mirrors `JSONPathTransformerContext#get(select, wrap)`: a JSONPath
7
+ * query against the current template data (or that data itself when
8
+ * `select` is falsy).
9
+ * @typedef {{
10
+ * appendOutput: (item: unknown) => unknown,
11
+ * get: (select: string|undefined, wrap: boolean) => unknown
12
+ * }} JtltExtensionContext
13
+ */
14
+
15
+ /**
16
+ * The argument object a declarative `{$generateJsoeEditUI: {...}}` /
17
+ * `{$generateJsoeViewUI: {...}}` node supplies, entirely unresolved — see
18
+ * jtlt's own extension-call mechanism (`jsonTemplate.js`'s `runOperation`,
19
+ * jtlt 0.21.0+): jtlt does not interpret any of these keys itself, so
20
+ * resolving `select` (via `this.get`) is this module's own job, not
21
+ * jtlt's. `db`/`store`, when given, name which store's schema should shape
22
+ * the built controls — independent of `select`, since the *value* being
23
+ * edited/viewed and the *store it belongs to* are two separate facts a
24
+ * template may need to supply together (e.g. when a `behavior` reads rows
25
+ * from a store other than the one its own route targets).
26
+ * @typedef {{select: string, db?: string, store?: string}} JsoeUIArgs
27
+ */
28
+
29
+ /**
30
+ * Builds a `generateJsoeEditUI` / `generateJsoeViewUI` pair, ready to spread
31
+ * into a jtlt `config.extensions` object — see
32
+ * `~/idb-manager/ROUTE-OVERRIDES-PLAN.md` §5 ("idb-manager behavior
33
+ * extensions"). Each function is a jtlt extension called with the
34
+ * declarative node's own argument object, e.g. `{$generateJsoeEditUI:
35
+ * {select: '$.record', db: 'myDb', store: 'myStore'}}` → `this.
36
+ * generateJsoeEditUI({select: '$.record', db: 'myDb', store: 'myStore'})`
37
+ * (or, from the raw-JS override tier, the same call made directly). It
38
+ * resolves `select` itself (`this.get(select, false)`), looks up that
39
+ * store's schema via `getSchemaContent` when `db`/`store` are both given,
40
+ * builds jsoe's own edit/view control tree for the resolved value, and
41
+ * inserts it into the current output itself via `this.appendOutput(...)`
42
+ * — matching jtlt's own `$renderDefault` convention of the extension doing
43
+ * its own insertion rather than returning a value for the interpreter to
44
+ * place.
45
+ *
46
+ * Takes the caller's own `Types` instance rather than constructing one:
47
+ * idb-manager (for example) already has a single, app-wide `Types`
48
+ * instance (`new Types()` in `Router.js`) configured with whatever
49
+ * constructor options it needs, and every control this builds should share
50
+ * that configuration, not a second, independently-configured one.
51
+ * @param {import('./types.js').default} types
52
+ * @param {{
53
+ * typeNamespace?: string,
54
+ * getSchemaContent?: (db: string, store: string) => (
55
+ * import('./formats/schema.js').ZodexSchema | undefined |
56
+ * Promise<import('./formats/schema.js').ZodexSchema | undefined>
57
+ * )
58
+ * }} [options] - `typeNamespace` scopes the jsoe-internal type-choice state
59
+ * so this doesn't collide with unrelated controls the same page builds
60
+ * elsewhere (see other `getControlsForFormatAndValue` call sites' own
61
+ * `typeNamespace` values); defaults to `'jtlt'`. `getSchemaContent`, when
62
+ * given, is called with each call's own `db`/`store` (e.g. wrapping
63
+ * idb-manager's `Schemas.js` lookup) — resolved fresh per call, so a
64
+ * single bundle correctly serves a `behavior` that edits/views records
65
+ * from more than one store, each with its own schema. Omitted (or a
66
+ * call's `db`/`store` omitted) means no schema — jsoe's controls still
67
+ * work, inferring type choices from the value alone.
68
+ * @returns {{
69
+ * generateJsoeEditUI: (
70
+ * this: JtltExtensionContext, argObject: JsoeUIArgs
71
+ * ) => Promise<void>,
72
+ * generateJsoeViewUI: (
73
+ * this: JtltExtensionContext, argObject: JsoeUIArgs
74
+ * ) => Promise<void>
75
+ * }}
76
+ */
77
+ export default function createJtltExtensions (
78
+ types, {typeNamespace = 'jtlt', getSchemaContent} = {}
79
+ ) {
80
+ /**
81
+ * @param {boolean} readonly
82
+ * @returns {(
83
+ * this: JtltExtensionContext, argObject: JsoeUIArgs
84
+ * ) => Promise<void>}
85
+ */
86
+ const buildAndAppend = (readonly) => async function (argObject) {
87
+ const {select, db, store} = argObject;
88
+ const value = this.get(select, false);
89
+ const schemaContent = db !== undefined && store !== undefined &&
90
+ getSchemaContent
91
+ ? await getSchemaContent(db, store)
92
+ : undefined;
93
+ const el = await types.getControlsForFormatAndValue(
94
+ 'structuredCloning', value, {readonly, typeNamespace, schemaContent}
95
+ );
96
+ this.appendOutput(el);
97
+ };
98
+
99
+ return {
100
+ generateJsoeEditUI: buildAndAppend(false),
101
+ generateJsoeViewUI: buildAndAppend(true)
102
+ };
103
+ }