@es-joy/jsoe 0.28.1 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +4 -0
- package/badges/coverage-badge.svg +1 -1
- package/badges/tests-badge.svg +1 -1
- package/dist/deepEqual.d.ts +3 -0
- package/dist/deepEqual.d.ts.map +1 -0
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/search/queryTree.d.ts +180 -0
- package/dist/search/queryTree.d.ts.map +1 -0
- package/dist/search/queryTreeBuilders.d.ts +156 -0
- package/dist/search/queryTreeBuilders.d.ts.map +1 -0
- package/dist/search/searchDispatch.d.ts +77 -0
- package/dist/search/searchDispatch.d.ts.map +1 -0
- package/dist/utils/rawTypesonEditor.d.ts.map +1 -1
- package/docs/proposals/search-plan.md +166 -0
- package/package.json +1 -1
- package/src/formats/schema.js +1 -0
- package/src/utils/rawTypesonEditor.js +12 -2
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export type QueryAnd = {
|
|
2
|
+
$and: QueryNode[];
|
|
3
|
+
};
|
|
4
|
+
export type QueryOr = {
|
|
5
|
+
$or: QueryNode[];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* `object` (via `$exists: true/false`).
|
|
9
|
+
*/
|
|
10
|
+
export type QueryHasPropertyLeaf = {
|
|
11
|
+
kind: "hasProperty";
|
|
12
|
+
path: string;
|
|
13
|
+
$exists: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* `array`/`set`/tuple-with-rest/`filelist` (via `$size`, or a range of
|
|
17
|
+
* operators when the schema doesn't pin an exact length), +`sparseCheck`
|
|
18
|
+
* for array sparse/not-sparse.
|
|
19
|
+
*/
|
|
20
|
+
export type QueryLengthSizeLeaf = {
|
|
21
|
+
kind: "lengthSize";
|
|
22
|
+
path: string;
|
|
23
|
+
$size?: number;
|
|
24
|
+
$gt?: number;
|
|
25
|
+
$gte?: number;
|
|
26
|
+
$lt?: number;
|
|
27
|
+
$lte?: number;
|
|
28
|
+
sparseCheck?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `number`/`NumberObject`/`bigint`/`date`/`buffersource`, with `valueType`
|
|
32
|
+
* distinguishing them. Bounds are Mongo-style `$gt`/`$gte`/`$lt`/`$lte`
|
|
33
|
+
* rather than a `min`/`max` pair plus an inclusive boolean - inclusivity is
|
|
34
|
+
* simply which operator is present. The README's "Is Not Range" variant
|
|
35
|
+
* wraps the same leaf in `$not` rather than being a separate kind.
|
|
36
|
+
*/
|
|
37
|
+
export type QueryRangeLeaf = {
|
|
38
|
+
kind: "range";
|
|
39
|
+
path: string;
|
|
40
|
+
valueType: "number" | "NumberObject" | "bigint" | "date" | "buffersource";
|
|
41
|
+
$gt?: number | bigint | string;
|
|
42
|
+
$gte?: number | bigint | string;
|
|
43
|
+
$lt?: number | bigint | string;
|
|
44
|
+
$lte?: number | bigint | string;
|
|
45
|
+
};
|
|
46
|
+
export type QueryNotLeaf = {
|
|
47
|
+
kind: "not";
|
|
48
|
+
query: QueryNode;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* `number`/`NumberObject`: is/is-not an integer.
|
|
52
|
+
*/
|
|
53
|
+
export type QueryIntegerCheckLeaf = {
|
|
54
|
+
kind: "integerCheck";
|
|
55
|
+
path: string;
|
|
56
|
+
isInteger: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* `string`/`StringObject`/`Blob`/`File`/regexp-source/symbol-description
|
|
60
|
+
* (via `$in`/`$nin`).
|
|
61
|
+
*/
|
|
62
|
+
export type QueryLiteralSetLeaf = {
|
|
63
|
+
kind: "literalSet";
|
|
64
|
+
path: string;
|
|
65
|
+
$in?: unknown[];
|
|
66
|
+
$nin?: unknown[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* `string`/`StringObject`/`Blob`/`File`/regexp-source/symbol-description
|
|
70
|
+
* (via `$regex`/`$options`, matching Mongo's own field names).
|
|
71
|
+
*/
|
|
72
|
+
export type QueryRegexLeaf = {
|
|
73
|
+
kind: "regex";
|
|
74
|
+
path: string;
|
|
75
|
+
$regex: string;
|
|
76
|
+
$options?: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* `string`/`StringObject`/`Blob`/`File`/regexp-source/symbol-description:
|
|
80
|
+
* a substring the value must not contain.
|
|
81
|
+
*/
|
|
82
|
+
export type QueryNotContainsLeaf = {
|
|
83
|
+
kind: "notContains";
|
|
84
|
+
path: string;
|
|
85
|
+
value: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* `enum`, SpecialNumber's `Infinity`/`-Infinity`/`NaN`/`-0` (via
|
|
89
|
+
* `$in`/`$nin`).
|
|
90
|
+
*/
|
|
91
|
+
export type QueryMultiSelectLeaf = {
|
|
92
|
+
kind: "multiSelect";
|
|
93
|
+
path: string;
|
|
94
|
+
$in?: unknown[];
|
|
95
|
+
$nin?: unknown[];
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Native enum key-vs-value; no Mongo equivalent, stays custom.
|
|
99
|
+
*/
|
|
100
|
+
export type QueryKeyValueEnumLeaf = {
|
|
101
|
+
kind: "keyValueEnum";
|
|
102
|
+
path: string;
|
|
103
|
+
matchKeys: boolean;
|
|
104
|
+
values: unknown[];
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Union/xor/discriminatedUnion "has type", carrying `discriminatorValue`
|
|
108
|
+
* when applicable, including when nested under a Map/Record key or value;
|
|
109
|
+
* no Mongo equivalent, stays custom.
|
|
110
|
+
*/
|
|
111
|
+
export type QueryTypeOfLeaf = {
|
|
112
|
+
kind: "typeOf";
|
|
113
|
+
path: string;
|
|
114
|
+
searchType: string;
|
|
115
|
+
discriminatorValue?: unknown;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* XPath/CSS-selector/full-text/raw-HTML-regex; no Mongo equivalent.
|
|
119
|
+
*/
|
|
120
|
+
export type QueryBlobHTMLLeaf = {
|
|
121
|
+
kind: "blobHTML";
|
|
122
|
+
path: string;
|
|
123
|
+
mode: "xpath" | "cssSelector" | "fullText" | "rawHTMLRegex";
|
|
124
|
+
value: string;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Per-dimension ranges for DOMRect/Point/Matrix, each dimension itself a
|
|
128
|
+
* `range` leaf, + `readonlyCheck`/`dimensionCheck` (is/is-not readonly,
|
|
129
|
+
* is/is-not 3d); no Mongo equivalent.
|
|
130
|
+
*/
|
|
131
|
+
export type QueryDomShapeLeaf = {
|
|
132
|
+
kind: "domShape";
|
|
133
|
+
path: string;
|
|
134
|
+
dimensions: {
|
|
135
|
+
[dimension: string]: QueryRangeLeaf;
|
|
136
|
+
};
|
|
137
|
+
readonlyCheck?: boolean;
|
|
138
|
+
dimensionCheck?: 2 | 3;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Paired key+value leaves with a joint-match flag; no Mongo equivalent.
|
|
142
|
+
*/
|
|
143
|
+
export type QueryMapRecordJointLeaf = {
|
|
144
|
+
kind: "mapRecordJoint";
|
|
145
|
+
path: string;
|
|
146
|
+
keyQuery?: QueryNode;
|
|
147
|
+
valueQuery?: QueryNode;
|
|
148
|
+
joint: boolean;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Promise/literal/catch/function: forwards to a nested `QueryNode` for the
|
|
152
|
+
* child schema so the tree stays uniform even where a type adds no
|
|
153
|
+
* constraint of its own; purely structural, no Mongo equivalent.
|
|
154
|
+
*/
|
|
155
|
+
export type QueryPassThroughLeaf = {
|
|
156
|
+
kind: "passThrough";
|
|
157
|
+
path: string;
|
|
158
|
+
query?: QueryNode;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Undefined/void/null (via `$exists`).
|
|
162
|
+
*/
|
|
163
|
+
export type QueryPresenceLeaf = {
|
|
164
|
+
kind: "presence";
|
|
165
|
+
path: string;
|
|
166
|
+
$exists: boolean;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Boolean/BooleanObject - Mongo would normally express this as a bare
|
|
170
|
+
* `{field: true}` shorthand, which doesn't fit our path-carrying leaf
|
|
171
|
+
* shape, so this stays a custom kind.
|
|
172
|
+
*/
|
|
173
|
+
export type QueryBooleanEqualsLeaf = {
|
|
174
|
+
kind: "booleanEquals";
|
|
175
|
+
path: string;
|
|
176
|
+
value: boolean;
|
|
177
|
+
};
|
|
178
|
+
export type QueryLeaf = QueryHasPropertyLeaf | QueryLengthSizeLeaf | QueryRangeLeaf | QueryNotLeaf | QueryIntegerCheckLeaf | QueryLiteralSetLeaf | QueryRegexLeaf | QueryNotContainsLeaf | QueryMultiSelectLeaf | QueryKeyValueEnumLeaf | QueryTypeOfLeaf | QueryBlobHTMLLeaf | QueryDomShapeLeaf | QueryMapRecordJointLeaf | QueryPassThroughLeaf | QueryPresenceLeaf | QueryBooleanEqualsLeaf;
|
|
179
|
+
export type QueryNode = QueryAnd | QueryOr | QueryLeaf;
|
|
180
|
+
//# sourceMappingURL=queryTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryTree.d.ts","sourceRoot":"","sources":["../../src/search/queryTree.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only runtime pieces for building `queryTree.js` nodes - every search
|
|
3
|
+
* module builds nodes through these factories rather than hand-rolling leaf
|
|
4
|
+
* objects, so tests can import the same constructors to build expected-value
|
|
5
|
+
* fixtures.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('./queryTree.js').QueryNode[]} nodes
|
|
9
|
+
* @returns {import('./queryTree.js').QueryAnd}
|
|
10
|
+
*/
|
|
11
|
+
export function makeAndNode(nodes: import("./queryTree.js").QueryNode[]): import("./queryTree.js").QueryAnd;
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('./queryTree.js').QueryNode[]} nodes
|
|
14
|
+
* @returns {import('./queryTree.js').QueryOr}
|
|
15
|
+
*/
|
|
16
|
+
export function makeOrNode(nodes: import("./queryTree.js").QueryNode[]): import("./queryTree.js").QueryOr;
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} path
|
|
19
|
+
* @param {boolean} $exists
|
|
20
|
+
* @returns {import('./queryTree.js').QueryHasPropertyLeaf}
|
|
21
|
+
*/
|
|
22
|
+
export function makeHasPropertyLeaf(path: string, $exists: boolean): import("./queryTree.js").QueryHasPropertyLeaf;
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} path
|
|
25
|
+
* @param {{
|
|
26
|
+
* $size?: number,
|
|
27
|
+
* $gt?: number, $gte?: number, $lt?: number, $lte?: number,
|
|
28
|
+
* sparseCheck?: boolean
|
|
29
|
+
* }} cfg
|
|
30
|
+
* @returns {import('./queryTree.js').QueryLengthSizeLeaf}
|
|
31
|
+
*/
|
|
32
|
+
export function makeLengthSizeLeaf(path: string, cfg?: {
|
|
33
|
+
$size?: number;
|
|
34
|
+
$gt?: number;
|
|
35
|
+
$gte?: number;
|
|
36
|
+
$lt?: number;
|
|
37
|
+
$lte?: number;
|
|
38
|
+
sparseCheck?: boolean;
|
|
39
|
+
}): import("./queryTree.js").QueryLengthSizeLeaf;
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} path
|
|
42
|
+
* @param {import('./queryTree.js').QueryRangeLeaf['valueType']} valueType
|
|
43
|
+
* @param {{
|
|
44
|
+
* $gt?: number|bigint|string, $gte?: number|bigint|string,
|
|
45
|
+
* $lt?: number|bigint|string, $lte?: number|bigint|string
|
|
46
|
+
* }} cfg
|
|
47
|
+
* @returns {import('./queryTree.js').QueryRangeLeaf}
|
|
48
|
+
*/
|
|
49
|
+
export function makeRangeLeaf(path: string, valueType: import("./queryTree.js").QueryRangeLeaf["valueType"], cfg?: {
|
|
50
|
+
$gt?: number | bigint | string;
|
|
51
|
+
$gte?: number | bigint | string;
|
|
52
|
+
$lt?: number | bigint | string;
|
|
53
|
+
$lte?: number | bigint | string;
|
|
54
|
+
}): import("./queryTree.js").QueryRangeLeaf;
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('./queryTree.js').QueryNode} query
|
|
57
|
+
* @returns {import('./queryTree.js').QueryNotLeaf}
|
|
58
|
+
*/
|
|
59
|
+
export function makeNotLeaf(query: import("./queryTree.js").QueryNode): import("./queryTree.js").QueryNotLeaf;
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} path
|
|
62
|
+
* @param {boolean} isInteger
|
|
63
|
+
* @returns {import('./queryTree.js').QueryIntegerCheckLeaf}
|
|
64
|
+
*/
|
|
65
|
+
export function makeIntegerCheckLeaf(path: string, isInteger: boolean): import("./queryTree.js").QueryIntegerCheckLeaf;
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} path
|
|
68
|
+
* @param {{$in?: unknown[], $nin?: unknown[]}} cfg
|
|
69
|
+
* @returns {import('./queryTree.js').QueryLiteralSetLeaf}
|
|
70
|
+
*/
|
|
71
|
+
export function makeLiteralSetLeaf(path: string, cfg?: {
|
|
72
|
+
$in?: unknown[];
|
|
73
|
+
$nin?: unknown[];
|
|
74
|
+
}): import("./queryTree.js").QueryLiteralSetLeaf;
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} path
|
|
77
|
+
* @param {string} $regex
|
|
78
|
+
* @param {string} [$options]
|
|
79
|
+
* @returns {import('./queryTree.js').QueryRegexLeaf}
|
|
80
|
+
*/
|
|
81
|
+
export function makeRegexLeaf(path: string, $regex: string, $options?: string): import("./queryTree.js").QueryRegexLeaf;
|
|
82
|
+
/**
|
|
83
|
+
* @param {string} path
|
|
84
|
+
* @param {string} value
|
|
85
|
+
* @returns {import('./queryTree.js').QueryNotContainsLeaf}
|
|
86
|
+
*/
|
|
87
|
+
export function makeNotContainsLeaf(path: string, value: string): import("./queryTree.js").QueryNotContainsLeaf;
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} path
|
|
90
|
+
* @param {{$in?: unknown[], $nin?: unknown[]}} cfg
|
|
91
|
+
* @returns {import('./queryTree.js').QueryMultiSelectLeaf}
|
|
92
|
+
*/
|
|
93
|
+
export function makeMultiSelectLeaf(path: string, cfg?: {
|
|
94
|
+
$in?: unknown[];
|
|
95
|
+
$nin?: unknown[];
|
|
96
|
+
}): import("./queryTree.js").QueryMultiSelectLeaf;
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} path
|
|
99
|
+
* @param {boolean} matchKeys
|
|
100
|
+
* @param {unknown[]} values
|
|
101
|
+
* @returns {import('./queryTree.js').QueryKeyValueEnumLeaf}
|
|
102
|
+
*/
|
|
103
|
+
export function makeKeyValueEnumLeaf(path: string, matchKeys: boolean, values: unknown[]): import("./queryTree.js").QueryKeyValueEnumLeaf;
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} path
|
|
106
|
+
* @param {string} searchType
|
|
107
|
+
* @param {unknown} [discriminatorValue]
|
|
108
|
+
* @returns {import('./queryTree.js').QueryTypeOfLeaf}
|
|
109
|
+
*/
|
|
110
|
+
export function makeTypeOfLeaf(path: string, searchType: string, discriminatorValue?: unknown): import("./queryTree.js").QueryTypeOfLeaf;
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} path
|
|
113
|
+
* @param {import('./queryTree.js').QueryBlobHTMLLeaf['mode']} mode
|
|
114
|
+
* @param {string} value
|
|
115
|
+
* @returns {import('./queryTree.js').QueryBlobHTMLLeaf}
|
|
116
|
+
*/
|
|
117
|
+
export function makeBlobHTMLLeaf(path: string, mode: import("./queryTree.js").QueryBlobHTMLLeaf["mode"], value: string): import("./queryTree.js").QueryBlobHTMLLeaf;
|
|
118
|
+
/**
|
|
119
|
+
* @param {string} path
|
|
120
|
+
* @param {{[dimension: string]: import('./queryTree.js').QueryRangeLeaf}} dimensions
|
|
121
|
+
* @param {{readonlyCheck?: boolean, dimensionCheck?: 2|3}} [cfg]
|
|
122
|
+
* @returns {import('./queryTree.js').QueryDomShapeLeaf}
|
|
123
|
+
*/
|
|
124
|
+
export function makeDomShapeLeaf(path: string, dimensions: {
|
|
125
|
+
[dimension: string]: import("./queryTree.js").QueryRangeLeaf;
|
|
126
|
+
}, cfg?: {
|
|
127
|
+
readonlyCheck?: boolean;
|
|
128
|
+
dimensionCheck?: 2 | 3;
|
|
129
|
+
}): import("./queryTree.js").QueryDomShapeLeaf;
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} path
|
|
132
|
+
* @param {boolean} joint
|
|
133
|
+
* @param {import('./queryTree.js').QueryNode} [keyQuery]
|
|
134
|
+
* @param {import('./queryTree.js').QueryNode} [valueQuery]
|
|
135
|
+
* @returns {import('./queryTree.js').QueryMapRecordJointLeaf}
|
|
136
|
+
*/
|
|
137
|
+
export function makeMapRecordJointLeaf(path: string, joint: boolean, keyQuery?: import("./queryTree.js").QueryNode, valueQuery?: import("./queryTree.js").QueryNode): import("./queryTree.js").QueryMapRecordJointLeaf;
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} path
|
|
140
|
+
* @param {import('./queryTree.js').QueryNode} [query]
|
|
141
|
+
* @returns {import('./queryTree.js').QueryPassThroughLeaf}
|
|
142
|
+
*/
|
|
143
|
+
export function makePassThroughLeaf(path: string, query?: import("./queryTree.js").QueryNode): import("./queryTree.js").QueryPassThroughLeaf;
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} path
|
|
146
|
+
* @param {boolean} $exists
|
|
147
|
+
* @returns {import('./queryTree.js').QueryPresenceLeaf}
|
|
148
|
+
*/
|
|
149
|
+
export function makePresenceLeaf(path: string, $exists: boolean): import("./queryTree.js").QueryPresenceLeaf;
|
|
150
|
+
/**
|
|
151
|
+
* @param {string} path
|
|
152
|
+
* @param {boolean} value
|
|
153
|
+
* @returns {import('./queryTree.js').QueryBooleanEqualsLeaf}
|
|
154
|
+
*/
|
|
155
|
+
export function makeBooleanEqualsLeaf(path: string, value: boolean): import("./queryTree.js").QueryBooleanEqualsLeaf;
|
|
156
|
+
//# sourceMappingURL=queryTreeBuilders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryTreeBuilders.d.ts","sourceRoot":"","sources":["../../src/search/queryTreeBuilders.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-only analogue of `getSchemaType` (`src/formats/schema.js:145-166`).
|
|
3
|
+
* Must not reimplement that function's special-casing (stringbool pipe,
|
|
4
|
+
* `codec`/filelist, `instanceof`, `literal`, `enum`, `templateLiteral`,
|
|
5
|
+
* `getCheckedType` fallback) - it calls straight through to it, and only
|
|
6
|
+
* adds the extra shape distinctions `getSchemaType`'s own
|
|
7
|
+
* `AvailableArbitraryType` output collapses (tuple, record/looseRecord,
|
|
8
|
+
* union/xor/discriminatedUnion all currently reduce to `'array'`/`'object'`/
|
|
9
|
+
* nothing, per `zodexToStructuredCloningTypeMap`,
|
|
10
|
+
* `src/formats/schema.js:30-56`).
|
|
11
|
+
*
|
|
12
|
+
* Intersection schemas are deliberately not a case here - they're resolved
|
|
13
|
+
* before dispatch, by the search-widget walker calling `getTypesForSchema`
|
|
14
|
+
* (search plan §3), reusing jsoe's existing intersection-merging machinery
|
|
15
|
+
* rather than this function reinventing it.
|
|
16
|
+
* @param {import('../formats/schema.js').ZodexSchema} schemaObject
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function getSearchSchemaType(schemaObject: import("../formats/schema.js").ZodexSchema): string;
|
|
20
|
+
/**
|
|
21
|
+
* The one exported call site every recursive search widget uses (analogous
|
|
22
|
+
* to `types.getTypeObject(type)`, `src/types.js:1106-1108`, but schema-in
|
|
23
|
+
* rather than type-string-in, since there's no value to key off). Falls
|
|
24
|
+
* back to `noneditableSearchType` for any schema shape `getSearchSchemaType`
|
|
25
|
+
* reports that isn't (yet) registered, rather than throwing - the same
|
|
26
|
+
* escape hatch a bare top-level `instanceof` schema falls through to.
|
|
27
|
+
* @param {import('../formats/schema.js').ZodexSchema} schemaObject
|
|
28
|
+
* @returns {SearchTypeObject}
|
|
29
|
+
*/
|
|
30
|
+
export function getSearchTypeObject(schemaObject: import("../formats/schema.js").ZodexSchema): SearchTypeObject;
|
|
31
|
+
export type QueryNode = import("./queryTree.js").QueryNode;
|
|
32
|
+
/**
|
|
33
|
+
* The search-side analogue of `TypeObject` (`src/types.js:308-408`): two
|
|
34
|
+
* methods, `buildUI` (a `jml` array) and `getQuery` (reads the built DOM
|
|
35
|
+
* back into a `QueryNode`, or `undefined` when no constraint was entered).
|
|
36
|
+
*/
|
|
37
|
+
export type SearchTypeObject = {
|
|
38
|
+
buildUI: (cfg: {
|
|
39
|
+
schemaObject: import("../formats/schema.js").ZodexSchema;
|
|
40
|
+
path: string;
|
|
41
|
+
typeNamespace?: string;
|
|
42
|
+
topRoot?: import("../types.js").RootElement;
|
|
43
|
+
types?: import("../types.js").default;
|
|
44
|
+
}) => import("../types.js").JamilihArray;
|
|
45
|
+
getQuery: (cfg: {
|
|
46
|
+
root: HTMLElement;
|
|
47
|
+
path: string;
|
|
48
|
+
}) => QueryNode | undefined;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* `Record<string, SearchTypeObject>` registry, analogous to
|
|
52
|
+
* `Types.prototype.availableTypes` (`src/types.js:473-581`). Every entry is
|
|
53
|
+
* a stub for now (build order step 1); later steps replace individual
|
|
54
|
+
* entries with their real `*SearchType.js` module, one at a time, without
|
|
55
|
+
* touching this shape.
|
|
56
|
+
*
|
|
57
|
+
* A few keys deliberately share one module reference rather than getting
|
|
58
|
+
* their own: `BooleanObject`/`NumberObject`/`StringObject`/`bigintObject`
|
|
59
|
+
* point at their primitive counterpart's module because a search leaf only
|
|
60
|
+
* cares about the query semantics (e.g. "true or false"), never about how
|
|
61
|
+
* the value-editing side constructs the boxed wrapper at runtime. Likewise
|
|
62
|
+
* `arrayNonindexKeys` mirrors `array`, `looseRecord` mirrors `record`, and
|
|
63
|
+
* `templateLiteral` mirrors `string` (README: a template literal is still
|
|
64
|
+
* fundamentally a string-shape constraint for this pass; see §3 of the
|
|
65
|
+
* search plan).
|
|
66
|
+
* @type {{[key: string]: SearchTypeObject}}
|
|
67
|
+
*/
|
|
68
|
+
export const availableSearchTypes: {
|
|
69
|
+
[key: string]: SearchTypeObject;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Escape hatch for `instanceof`-outside-`FileList` and any schema shape not
|
|
73
|
+
* (yet) recognized: no widget is rendered for it, only this stand-in.
|
|
74
|
+
* @type {SearchTypeObject}
|
|
75
|
+
*/
|
|
76
|
+
export const noneditableSearchType: SearchTypeObject;
|
|
77
|
+
//# sourceMappingURL=searchDispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchDispatch.d.ts","sourceRoot":"","sources":["../../src/search/searchDispatch.js"],"names":[],"mappings":"AA2HA;;;;;;;;;;;;;;;;;GAiBG;AACH,kDAHW,OAAO,sBAAsB,EAAE,WAAW,GACxC,MAAM,CAuBlB;AAED;;;;;;;;;GASG;AACH,kDAHW,OAAO,sBAAsB,EAAE,WAAW,GACxC,gBAAgB,CAK5B;wBA9KY,OAAO,gBAAgB,EAAE,SAAS;;;;;;+BAOlC;IACR,OAAO,EAAE,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;QACzD,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,WAAW,CAAC;QAC5C,KAAK,CAAC,EAAE,OAAO,aAAa,EAAE,OAAO,CAAA;KACtC,KAAK,OAAO,aAAa,EAAE,YAAY,CAAC;IACzC,QAAQ,EAAE,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,WAAW,CAAC;QAClB,IAAI,EAAE,MAAM,CAAA;KACb,KAAK,SAAS,GAAC,SAAS,CAAA;CAC1B;AAyBJ;;;;;;;;;;;;;;;;;GAiBG;AACH,mCAFU;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAC,CA0DzC;AAjFF;;;;GAIG;AACH,oCAFU,gBAAgB,CAEkC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rawTypesonEditor.d.ts","sourceRoot":"","sources":["../../src/utils/rawTypesonEditor.js"],"names":[],"mappings":"AAgGA;;;;;GAKG;AACH,8CAHW,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CAY3B;AAED;;;;;GAKG;AACH,6CAHW,MAAM,GACJ,OAAO,CAMnB;AAED;;;;;;;GAOG;AACH,0CAHW,MAAM,GACJ,OAAO,CAQnB;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,+CALW,OAAO,SACP,GAAG,CAAC,OAAO,CAAC,GAEV,OAAO,CAAC,MAAM,CAAC,CA8Q3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,2HAVG;IAAgE,KAAK,EAA7D,YAAY,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC;IACL,MAAM,EAAnD,OAAO,eAAe,EAAE,eAAe;IACW,IAAI,EAAtD,OAAO,aAAa,EAAE,sBAAsB;IACxB,IAAI,EAAxB,cAAc;IACM,OAAO,EAA3B,cAAc;IACD,aAAa;IACI,oBAAoB;IACrC,KAAK,EAAlB,OAAO;CACf,GAAU,OAAO,CAAC,IAAI,CAAC,CAiCzB;AAED;;;;;;;;;;;;;;GAcG;AACH,2HAVG;IAAgE,KAAK,EAA7D,YAAY,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC;IACL,MAAM,EAAnD,OAAO,eAAe,EAAE,eAAe;IACW,IAAI,EAAtD,OAAO,aAAa,EAAE,sBAAsB;IACxB,IAAI,EAAxB,cAAc;IACM,OAAO,EAA3B,cAAc;IACD,aAAa;IACI,oBAAoB;IACrC,QAAQ,EAArB,OAAO;CACf,GAAU,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"rawTypesonEditor.d.ts","sourceRoot":"","sources":["../../src/utils/rawTypesonEditor.js"],"names":[],"mappings":"AAgGA;;;;;GAKG;AACH,8CAHW,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CAY3B;AAED;;;;;GAKG;AACH,6CAHW,MAAM,GACJ,OAAO,CAMnB;AAED;;;;;;;GAOG;AACH,0CAHW,MAAM,GACJ,OAAO,CAQnB;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,+CALW,OAAO,SACP,GAAG,CAAC,OAAO,CAAC,GAEV,OAAO,CAAC,MAAM,CAAC,CA8Q3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,2HAVG;IAAgE,KAAK,EAA7D,YAAY,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC;IACL,MAAM,EAAnD,OAAO,eAAe,EAAE,eAAe;IACW,IAAI,EAAtD,OAAO,aAAa,EAAE,sBAAsB;IACxB,IAAI,EAAxB,cAAc;IACM,OAAO,EAA3B,cAAc;IACD,aAAa;IACI,oBAAoB;IACrC,KAAK,EAAlB,OAAO;CACf,GAAU,OAAO,CAAC,IAAI,CAAC,CAiCzB;AAED;;;;;;;;;;;;;;GAcG;AACH,2HAVG;IAAgE,KAAK,EAA7D,YAAY,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC;IACL,MAAM,EAAnD,OAAO,eAAe,EAAE,eAAe;IACW,IAAI,EAAtD,OAAO,aAAa,EAAE,sBAAsB;IACxB,IAAI,EAAxB,cAAc;IACM,OAAO,EAA3B,cAAc;IACD,aAAa;IACI,oBAAoB;IACrC,QAAQ,EAArB,OAAO;CACf,GAAU,OAAO,CAAC,IAAI,CAAC,CA+JzB"}
|
|
@@ -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.
|