@astryxdesign/cli 0.7.12-canary.f543ec7 → 0.7.12
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/CHANGELOG.md +107 -0
- package/api/build/blocks/blocks.d.mts +41 -0
- package/api/build/blocks/blocks.mjs +118 -68
- package/api/component/__fixtures__/resolutions/modules.txt +3 -3
- package/api/docs/_adapter.d.mts +22 -0
- package/api/docs/_adapter.mjs +81 -3
- package/api/docs/docs.doc.mjs +5 -5
- package/api/docs/docs.type.d.mts +21 -0
- package/api/docs/docs.type.mjs +11 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +72 -6
- package/api/docs/node/node.mjs +2 -40
- package/api/doctor/doctor.d.mts +4 -4
- package/api/doctor/doctor.mjs +248 -78
- package/api/icon/_authoring.mjs +8 -1
- package/api/icon/add/add.mjs +45 -5
- package/api/icon/icon.type.d.mts +24 -0
- package/api/icon/icon.type.mjs +6 -0
- package/api/icon/iconAdd.doc.mjs +1 -1
- package/api/icon/map/map.mjs +11 -4
- package/api/index.d.mts +1 -0
- package/api/index.mjs +1 -0
- package/api/integration/add-helpers.d.mts +5 -1
- package/api/integration/add-helpers.mjs +1 -1
- package/api/render/_adapter.mjs +9 -0
- package/api/render/render.doc.mjs +3 -3
- package/api/render/render.type.d.mts +1 -1
- package/api/render/render.type.mjs +1 -1
- package/api/search/ORDERING.md +3 -0
- package/api/search/_adapter.d.mts +32 -3
- package/api/search/_adapter.mjs +50 -22
- package/api/search/_fit-order.d.mts +47 -0
- package/api/search/_fit-order.mjs +257 -0
- package/api/search/_main-searcher.mjs +15 -5
- package/api/search/exportSearchIndex.doc.d.mts +10 -0
- package/api/search/exportSearchIndex.doc.mjs +58 -0
- package/api/search/rankSearchIndex.doc.d.mts +10 -0
- package/api/search/rankSearchIndex.doc.mjs +79 -0
- package/api/search/search-index.d.mts +44 -0
- package/api/search/search-index.mjs +270 -0
- package/api/search/search.type.d.mts +28 -0
- package/api/search/search.type.mjs +17 -0
- package/api/swizzle/_moduleGraph.d.mts +7 -0
- package/api/swizzle/_moduleGraph.mjs +191 -83
- package/api/swizzle/copy/copy.mjs +28 -6
- package/api/template/skeleton/skeleton.mjs +1 -1
- package/api/template/template.mjs +1 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
- package/assets/discover/official-packages.json +16 -0
- package/assets/docs/styling-overview.doc.mjs +3 -3
- package/assets/docs/tree/icon-sets.doc.mjs +4 -4
- package/assets/docs/tree/render-pages.doc.mjs +3 -3
- package/assets/docs/tree/tailwind.doc.mjs +41 -1
- package/assets/docs/tree/type-scale.doc.mjs +19 -2
- package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +41 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
- package/assets/templates/pages/table-filter/page.tsx +15 -23
- package/assets/templates/pages/table-grouped/page.tsx +30 -30
- package/assets/templates/pages/table-inbox/page.tsx +3 -6
- package/assets/templates/themes/butter/butterTheme.ts +1 -1
- package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
- package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
- package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/docs.doc.mjs +5 -1
- package/clients/cli/commands/docs.mjs +30 -2
- package/clients/cli/commands/experimental/icon.mjs +25 -4
- package/clients/cli/commands/experimental-render.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +204 -181
- package/clients/cli/lib/resolve-theme.mjs +16 -7
- package/foundation/discovery/component-discovery.mjs +1 -1
- package/foundation/discovery/hook-discovery.mjs +31 -8
- package/foundation/discovery/icon-set-writer.d.mts +21 -5
- package/foundation/discovery/icon-set-writer.mjs +114 -13
- package/foundation/discovery/template-adapter.mjs +2 -2
- package/foundation/doc-compiler/import.d.mts +5 -1
- package/foundation/doc-compiler/import.mjs +40 -6
- package/foundation/fs/import-url.d.mts +20 -0
- package/foundation/fs/import-url.mjs +22 -0
- package/foundation/fs/module-loader.d.mts +24 -3
- package/foundation/fs/module-loader.mjs +69 -47
- package/foundation/render/charts.d.mts +59 -0
- package/foundation/render/charts.mjs +216 -0
- package/foundation/render/embedded-imports.d.mts +7 -7
- package/foundation/render/embedded-imports.mjs +63 -54
- package/foundation/render/page-host.d.mts +4 -0
- package/foundation/render/page-host.mjs +45 -0
- package/foundation/render/page-worker.mjs +350 -57
- package/foundation/render/render-page.d.mts +11 -6
- package/foundation/render/render-page.mjs +164 -54
- package/foundation/render/ts-paths.d.mts +63 -0
- package/foundation/render/ts-paths.mjs +138 -0
- package/foundation/response/error-codes.doc.mjs +3 -3
- package/foundation/response/error-codes.mjs +3 -2
- package/foundation/text/levenshtein.d.mts +6 -1
- package/foundation/text/levenshtein.mjs +25 -9
- package/foundation/text/suggest-order.d.mts +8 -1
- package/foundation/text/suggest-order.mjs +72 -6
- package/foundation/xle/registry-core.mjs +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The results with the first kept. Entries #2 to #10 split into runs of one
|
|
6
|
+
* band and kind. Inside a run, the entries that fit the query under FIT_BAR
|
|
7
|
+
* times the run's median fit move, in order, after the others, unless their
|
|
8
|
+
* name spells NAME_WORDS or more of the query's words. A run whose
|
|
9
|
+
* median fit is zero or less keeps its order. Nothing crosses a run, and
|
|
10
|
+
* nothing is added or dropped.
|
|
11
|
+
* @param {any[]} results
|
|
12
|
+
* @param {any[]} candidates
|
|
13
|
+
* @param {string} query
|
|
14
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
15
|
+
* the block picker's weights, or a function that reads them; the function
|
|
16
|
+
* runs only when a run can change. Without weights the order stays
|
|
17
|
+
* @returns {any[]}
|
|
18
|
+
*/
|
|
19
|
+
export function orderByFit(results: any[], candidates: any[], query: string, weights: Weights | (() => Weights | null | undefined) | null | undefined): any[];
|
|
20
|
+
/**
|
|
21
|
+
* The search response with its results ordered by fit (`orderByFit`). Any
|
|
22
|
+
* other response passes through.
|
|
23
|
+
* @template T
|
|
24
|
+
* @param {T} response
|
|
25
|
+
* @param {any[]} candidates
|
|
26
|
+
* @param {string} query
|
|
27
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
28
|
+
* @returns {T}
|
|
29
|
+
*/
|
|
30
|
+
export function withFitOrder<T>(response: T, candidates: any[], query: string, weights: Weights | (() => Weights | null | undefined) | null | undefined): T;
|
|
31
|
+
/**
|
|
32
|
+
* The search response with at most `limit` results.
|
|
33
|
+
* @template T
|
|
34
|
+
* @param {T} response
|
|
35
|
+
* @param {number} limit
|
|
36
|
+
* @returns {T}
|
|
37
|
+
*/
|
|
38
|
+
export function withLimit<T>(response: T, limit: number): T;
|
|
39
|
+
/** An entry stays in place when its fit is at least this times the median. */
|
|
40
|
+
export const FIT_BAR: 1.7;
|
|
41
|
+
/** The first result stays; the pass orders the places after it up to here. */
|
|
42
|
+
export const FIT_PLACES: 10;
|
|
43
|
+
export type Weights = import("../build/blocks/blocks.mjs").BlockPickerWeights;
|
|
44
|
+
export type Side = {
|
|
45
|
+
vector: Map<number, number>;
|
|
46
|
+
projected: Float64Array;
|
|
47
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file A last pass over a searcher's answer: inside each run of one score
|
|
5
|
+
* band and kind, the entries from #2 to #10 that fit the whole query much
|
|
6
|
+
* worse than the others in their run move, in order, after them.
|
|
7
|
+
* @input the ordered response, the gathered candidates, and the query
|
|
8
|
+
* @output the same response with the same results, maybe in a new order
|
|
9
|
+
* @position api/search; rankWith (_main-searcher.mjs) runs it after
|
|
10
|
+
* orderMatches, with the weights the adapter loads (loadSearchData) through
|
|
11
|
+
* build's own loader. The fit is build's block-picker match
|
|
12
|
+
* (build/blocks/blocks.mjs) with its shipped weights, so an entry the weights
|
|
13
|
+
* never saw is scored from its own docs the same way. Without readable
|
|
14
|
+
* weights the order stays.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
docSideOf,
|
|
19
|
+
matchOf,
|
|
20
|
+
requestSideOf,
|
|
21
|
+
stemOf,
|
|
22
|
+
termsOf,
|
|
23
|
+
wordsOf,
|
|
24
|
+
} from '../build/blocks/blocks.mjs';
|
|
25
|
+
import {bandOf} from './_order.mjs';
|
|
26
|
+
import {searchedComponentsOf} from './_result.mjs';
|
|
27
|
+
|
|
28
|
+
/** An entry stays in place when its fit is at least this times the median. */
|
|
29
|
+
export const FIT_BAR = 1.7;
|
|
30
|
+
|
|
31
|
+
/** An entry whose name spells at least this many query words never moves. */
|
|
32
|
+
const NAME_WORDS = 2;
|
|
33
|
+
|
|
34
|
+
/** The first result stays; the pass orders the places after it up to here. */
|
|
35
|
+
export const FIT_PLACES = 10;
|
|
36
|
+
|
|
37
|
+
/** @typedef {import('../build/blocks/blocks.mjs').BlockPickerWeights} Weights */
|
|
38
|
+
/** @typedef {{vector: Map<number, number>, projected: Float64Array}} Side */
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Each candidate by `domain:name`; a doc also by its topic and by
|
|
42
|
+
* `topic#section`. The first candidate with a key owns it.
|
|
43
|
+
* @type {WeakMap<any[], Map<string, any>>}
|
|
44
|
+
*/
|
|
45
|
+
const keyed = new WeakMap();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Each candidate's docs side, computed when a result first needs it.
|
|
49
|
+
* @type {WeakMap<Weights, WeakMap<any, Side>>}
|
|
50
|
+
*/
|
|
51
|
+
const sides = new WeakMap();
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* An entry's docs text: its name words, display name or first title,
|
|
55
|
+
* description, and keywords.
|
|
56
|
+
* @param {any} c
|
|
57
|
+
*/
|
|
58
|
+
const docText = c =>
|
|
59
|
+
[
|
|
60
|
+
wordsOf(c.name).join(' '),
|
|
61
|
+
c._displayName ?? (Array.isArray(c.titles) ? c.titles[0] : '') ?? '',
|
|
62
|
+
c.description ?? '',
|
|
63
|
+
(Array.isArray(c.keywords) ? c.keywords : []).join(' '),
|
|
64
|
+
].join(' ');
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {any[]} candidates
|
|
68
|
+
* @returns {Map<string, any>}
|
|
69
|
+
*/
|
|
70
|
+
function keysOf(candidates) {
|
|
71
|
+
let byKey = keyed.get(candidates);
|
|
72
|
+
if (!byKey) {
|
|
73
|
+
byKey = new Map();
|
|
74
|
+
for (const c of candidates) {
|
|
75
|
+
const keys = [`${c.domain}:${c._resultName ?? c.name}`];
|
|
76
|
+
if (c.domain === 'doc' && c._topic)
|
|
77
|
+
keys.push(
|
|
78
|
+
c._section ? `doc:${c._topic}#${c._section}` : `doc:${c._topic}`,
|
|
79
|
+
);
|
|
80
|
+
for (const k of keys) if (!byKey.has(k)) byKey.set(k, c);
|
|
81
|
+
}
|
|
82
|
+
keyed.set(candidates, byKey);
|
|
83
|
+
}
|
|
84
|
+
return byKey;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {any} c
|
|
89
|
+
* @param {Weights} weights
|
|
90
|
+
* @returns {Side}
|
|
91
|
+
*/
|
|
92
|
+
function candidateSide(c, weights) {
|
|
93
|
+
let byCandidate = sides.get(weights);
|
|
94
|
+
if (!byCandidate) {
|
|
95
|
+
byCandidate = new WeakMap();
|
|
96
|
+
sides.set(weights, byCandidate);
|
|
97
|
+
}
|
|
98
|
+
let side = byCandidate.get(c);
|
|
99
|
+
if (!side) {
|
|
100
|
+
side = docSideOf(docText(c), weights);
|
|
101
|
+
byCandidate.set(c, side);
|
|
102
|
+
}
|
|
103
|
+
return side;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A result's docs side: its candidate's, else one from the result's own name,
|
|
108
|
+
* display name and description.
|
|
109
|
+
* @param {any} r
|
|
110
|
+
* @param {any[]} candidates
|
|
111
|
+
* @param {Weights} weights
|
|
112
|
+
* @returns {Side}
|
|
113
|
+
*/
|
|
114
|
+
function resultSideOf(r, candidates, weights) {
|
|
115
|
+
const byKey = keysOf(candidates);
|
|
116
|
+
if (r.domain === 'doc' && r.section) {
|
|
117
|
+
const c = byKey.get(`doc:${r.name}#${r.section}`);
|
|
118
|
+
if (c) return candidateSide(c, weights);
|
|
119
|
+
}
|
|
120
|
+
const c = byKey.get(`${r.domain}:${r.name}`);
|
|
121
|
+
return c
|
|
122
|
+
? candidateSide(c, weights)
|
|
123
|
+
: docSideOf(
|
|
124
|
+
`${wordsOf(r.name).join(' ')} ${r.displayName ?? ''} ${r.description ?? ''}`,
|
|
125
|
+
weights,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A result's run: its score band and kind (doc or not), as orderMatches reads
|
|
131
|
+
* them.
|
|
132
|
+
* @param {any} r
|
|
133
|
+
*/
|
|
134
|
+
const runOf = r => `${bandOf(r.score ?? 0)}:${r.domain === 'doc'}`;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The results with the first kept. Entries #2 to #10 split into runs of one
|
|
138
|
+
* band and kind. Inside a run, the entries that fit the query under FIT_BAR
|
|
139
|
+
* times the run's median fit move, in order, after the others, unless their
|
|
140
|
+
* name spells NAME_WORDS or more of the query's words. A run whose
|
|
141
|
+
* median fit is zero or less keeps its order. Nothing crosses a run, and
|
|
142
|
+
* nothing is added or dropped.
|
|
143
|
+
* @param {any[]} results
|
|
144
|
+
* @param {any[]} candidates
|
|
145
|
+
* @param {string} query
|
|
146
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
147
|
+
* the block picker's weights, or a function that reads them; the function
|
|
148
|
+
* runs only when a run can change. Without weights the order stays
|
|
149
|
+
* @returns {any[]}
|
|
150
|
+
*/
|
|
151
|
+
export function orderByFit(results, candidates, query, weights) {
|
|
152
|
+
if (!weights || results.length < 3) return results;
|
|
153
|
+
const end = Math.min(FIT_PLACES, results.length);
|
|
154
|
+
/** @type {[number, number][]} */
|
|
155
|
+
const runs = [];
|
|
156
|
+
for (let i = 1; i < end;) {
|
|
157
|
+
let j = i;
|
|
158
|
+
while (j < end && runOf(results[j]) === runOf(results[i])) j++;
|
|
159
|
+
runs.push([i, j]);
|
|
160
|
+
i = j;
|
|
161
|
+
}
|
|
162
|
+
// Only a run of two or more can change, so the weights stay unread when
|
|
163
|
+
// there is none.
|
|
164
|
+
if (!runs.some(([i, j]) => j - i > 1)) return results;
|
|
165
|
+
const w = typeof weights === 'function' ? weights() : weights;
|
|
166
|
+
if (!w) return results;
|
|
167
|
+
const request = requestSideOf(query, w);
|
|
168
|
+
const queryWords = new Set(termsOf(query).filter(t => !t.includes('_')));
|
|
169
|
+
/** @param {any} r */
|
|
170
|
+
const named = r =>
|
|
171
|
+
new Set(
|
|
172
|
+
wordsOf(r.name)
|
|
173
|
+
.map(stemOf)
|
|
174
|
+
.filter(word => queryWords.has(word)),
|
|
175
|
+
).size >= NAME_WORDS;
|
|
176
|
+
const out = results.slice();
|
|
177
|
+
let changed = false;
|
|
178
|
+
for (const [i, j] of runs) {
|
|
179
|
+
if (j - i < 2) continue;
|
|
180
|
+
const moved = demoteWeak(
|
|
181
|
+
results.slice(i, j),
|
|
182
|
+
request,
|
|
183
|
+
candidates,
|
|
184
|
+
w,
|
|
185
|
+
named,
|
|
186
|
+
);
|
|
187
|
+
moved.forEach((r, k) => {
|
|
188
|
+
if (r !== out[i + k]) changed = true;
|
|
189
|
+
out[i + k] = r;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return changed ? out : results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* One run, with the entries under FIT_BAR times its median fit moved, in
|
|
197
|
+
* order, after the others. An entry whose name spells NAME_WORDS or more of
|
|
198
|
+
* the query's words stays with the others: its name is strong evidence.
|
|
199
|
+
* @param {any[]} run
|
|
200
|
+
* @param {Side} request
|
|
201
|
+
* @param {any[]} candidates
|
|
202
|
+
* @param {Weights} weights
|
|
203
|
+
* @param {(r: any) => boolean} named
|
|
204
|
+
* @returns {any[]}
|
|
205
|
+
*/
|
|
206
|
+
function demoteWeak(run, request, candidates, weights, named) {
|
|
207
|
+
const fits = run.map(
|
|
208
|
+
r => matchOf(request, resultSideOf(r, candidates, weights), weights).match,
|
|
209
|
+
);
|
|
210
|
+
const sorted = [...fits].sort((a, b) => a - b);
|
|
211
|
+
const n = sorted.length;
|
|
212
|
+
const median =
|
|
213
|
+
n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
|
|
214
|
+
if (!(median > 0)) return run;
|
|
215
|
+
const bar = FIT_BAR * median;
|
|
216
|
+
const kept = run.map((r, k) => fits[k] >= bar || named(r));
|
|
217
|
+
return [...run.filter((_, k) => kept[k]), ...run.filter((_, k) => !kept[k])];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The search response with its results ordered by fit (`orderByFit`). Any
|
|
222
|
+
* other response passes through.
|
|
223
|
+
* @template T
|
|
224
|
+
* @param {T} response
|
|
225
|
+
* @param {any[]} candidates
|
|
226
|
+
* @param {string} query
|
|
227
|
+
* @param {Weights | (() => Weights | null | undefined) | null | undefined} weights
|
|
228
|
+
* @returns {T}
|
|
229
|
+
*/
|
|
230
|
+
export function withFitOrder(response, candidates, query, weights) {
|
|
231
|
+
const r = /** @type {any} */ (response);
|
|
232
|
+
const results = r?.data?.results;
|
|
233
|
+
if (!Array.isArray(results)) return response;
|
|
234
|
+
const ordered = orderByFit(results, candidates, query, weights);
|
|
235
|
+
if (ordered === results) return response;
|
|
236
|
+
const out = {...r, data: {...r.data, results: ordered}};
|
|
237
|
+
const searched = searchedComponentsOf.get(r);
|
|
238
|
+
if (searched) searchedComponentsOf.set(out, searched);
|
|
239
|
+
return /** @type {T} */ (out);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The search response with at most `limit` results.
|
|
244
|
+
* @template T
|
|
245
|
+
* @param {T} response
|
|
246
|
+
* @param {number} limit
|
|
247
|
+
* @returns {T}
|
|
248
|
+
*/
|
|
249
|
+
export function withLimit(response, limit) {
|
|
250
|
+
const r = /** @type {any} */ (response);
|
|
251
|
+
const results = r?.data?.results;
|
|
252
|
+
if (!Array.isArray(results) || results.length <= limit) return response;
|
|
253
|
+
const out = {...r, data: {...r.data, results: results.slice(0, limit)}};
|
|
254
|
+
const searched = searchedComponentsOf.get(r);
|
|
255
|
+
if (searched) searchedComponentsOf.set(out, searched);
|
|
256
|
+
return /** @type {T} */ (out);
|
|
257
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import {routeKit} from '../../foundation/route/route.mjs';
|
|
16
16
|
import {rank, signals} from './_features.mjs';
|
|
17
17
|
import {entryTable} from './_entries.mjs';
|
|
18
|
+
import {FIT_PLACES, withFitOrder, withLimit} from './_fit-order.mjs';
|
|
18
19
|
import {orderMatches} from './_order.mjs';
|
|
19
20
|
import {routeProbs} from './_query-words.mjs';
|
|
20
21
|
import {namesWhole, rankLexical} from './_lexical.mjs';
|
|
@@ -94,13 +95,22 @@ export function rankWith(request, catalog, weights) {
|
|
|
94
95
|
);
|
|
95
96
|
const route = routeProbs(catalog.data.router, query);
|
|
96
97
|
const rows = signals(table, query, lexical.data.results, route);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const limit = options.limit ?? 20;
|
|
99
|
+
// The fit pass reads the first FIT_PLACES results at every limit, so a
|
|
100
|
+
// smaller limit shows the top of that order.
|
|
101
|
+
const ordered = withFitOrder(
|
|
102
|
+
orderMatches(
|
|
103
|
+
table,
|
|
104
|
+
rank(table, rows, weights),
|
|
105
|
+
lexical,
|
|
106
|
+
query,
|
|
107
|
+
Math.max(limit, FIT_PLACES),
|
|
108
|
+
),
|
|
109
|
+
catalog.all,
|
|
101
110
|
query,
|
|
102
|
-
|
|
111
|
+
() => catalog.data.picker,
|
|
103
112
|
);
|
|
113
|
+
return withLimit(ordered, limit);
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
/** @type {import('./tree.mjs').SearchKit} */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file FunctionDoc for `exportSearchIndex()`. Colocated with the API function it
|
|
6
|
+
* documents; the shape source of truth stays in `search.type.mjs`.
|
|
7
|
+
* @position packages/cli/api/search — function documentation
|
|
8
|
+
*/
|
|
9
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
10
|
+
export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file FunctionDoc for `exportSearchIndex()`. Colocated with the API function it
|
|
5
|
+
* documents; the shape source of truth stays in `search.type.mjs`.
|
|
6
|
+
* @position packages/cli/api/search — function documentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
10
|
+
export const doc = {
|
|
11
|
+
type: 'function',
|
|
12
|
+
kind: 'api',
|
|
13
|
+
name: 'exportSearchIndex',
|
|
14
|
+
namespace: 'cli/api',
|
|
15
|
+
stability: 'experimental',
|
|
16
|
+
displayName: 'exportSearchIndex()',
|
|
17
|
+
summary:
|
|
18
|
+
'The candidates search ranks in a folder, as plain JSON to store and rank later with rankSearchIndex().',
|
|
19
|
+
description:
|
|
20
|
+
"Gathers what `search()` would rank in `cwd` and returns it as `{version, candidates}`. Gathering reads each installed package's docs and loads its integration manifest, which runs that package's code, so a catalog that lists packages it must not load makes the index where loading is safe (a folder with those packages installed), stores it, and ranks it with {@link function:rankSearchIndex}. With `package`, the index keeps only that package's candidates, for a catalog that stores one index per package. A component description's inline doc tokens are resolved where the index is made, as `search` prints them; when Core could not be had there, the index says why in `coreSkipped`. Fields that point into the folder are left out, so an index reads the same wherever it was made. The candidates are the ranker's input, not a stable shape: an index is ranked only by the CLI version that made it. Experimental: it may change or be removed in a patch release.",
|
|
21
|
+
importPath: '@astryxdesign/cli/api',
|
|
22
|
+
signature: 'exportSearchIndex(options?: {cwd?: string, package?: string}): Promise<SearchIndex>',
|
|
23
|
+
keywords: ['search index', 'stored search index'],
|
|
24
|
+
params: [
|
|
25
|
+
{
|
|
26
|
+
name: 'options.cwd',
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'The folder to gather from, read as `search()` reads it.',
|
|
29
|
+
default: 'process.cwd()',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'options.package',
|
|
33
|
+
type: 'string',
|
|
34
|
+
description:
|
|
35
|
+
"Keep only the candidates this npm package owns: its components, templates, themes, and doc parts. Without it the index holds everything the folder's search ranks, Core's entries and the CLI's docs included.",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
returns: [
|
|
39
|
+
{
|
|
40
|
+
type: 'SearchIndex',
|
|
41
|
+
description:
|
|
42
|
+
'`version`, the CLI version that made it, and `candidates`, the gathered entries as plain JSON.',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
throws: [
|
|
46
|
+
{
|
|
47
|
+
code: 'ERR_INVALID_ARGUMENT',
|
|
48
|
+
when: '`package` is given and is not a non-empty string',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
label: 'Index one installed integration',
|
|
54
|
+
code: "const index = await exportSearchIndex({cwd: '/tmp/harness', package: '@acme/ui'});",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
related: ['search', 'rankSearchIndex'],
|
|
58
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file FunctionDoc for `rankSearchIndex()`. Colocated with the API function it
|
|
6
|
+
* documents; the shape source of truth stays in `search.type.mjs`.
|
|
7
|
+
* @position packages/cli/api/search — function documentation
|
|
8
|
+
*/
|
|
9
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
10
|
+
export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file FunctionDoc for `rankSearchIndex()`. Colocated with the API function it
|
|
5
|
+
* documents; the shape source of truth stays in `search.type.mjs`.
|
|
6
|
+
* @position packages/cli/api/search — function documentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
10
|
+
export const doc = {
|
|
11
|
+
type: 'function',
|
|
12
|
+
kind: 'api',
|
|
13
|
+
name: 'rankSearchIndex',
|
|
14
|
+
namespace: 'cli/api',
|
|
15
|
+
stability: 'experimental',
|
|
16
|
+
displayName: 'rankSearchIndex()',
|
|
17
|
+
summary:
|
|
18
|
+
"Ranks stored search indexes with search's own tree: an index made in one folder answers as search() does there.",
|
|
19
|
+
description:
|
|
20
|
+
"Ranks the candidates of every index together, with the same tree, weights, and order as {@link function:search}. An index made with `exportSearchIndex()` in one folder ranks exactly as `search(query)` does in that folder: the same results, order, and scores. Indexes made in separate folders are ranked together as one list; a doc that one package replaces or extends in another is not merged across them, so a project's own `search` can differ where that happens. A theme result's command is the in-project `theme add`. When every index was made where Core could not be had, an open rank (no `type`) carries `meta.partial`, as `search` does; a rank of `type` `component`, `hook`, or `template` throws the error `search` throws there. Every index must come from this CLI's version, because the candidates' shape is the ranker's input. Experimental: it may change or be removed in a patch release.",
|
|
21
|
+
importPath: '@astryxdesign/cli/api',
|
|
22
|
+
signature:
|
|
23
|
+
'rankSearchIndex(query: string, indexes: SearchIndex | SearchIndex[], options?: {type?: SearchDomain, limit?: number}): Promise<SearchResponse>',
|
|
24
|
+
keywords: ['search index', 'stored search index'],
|
|
25
|
+
params: [
|
|
26
|
+
{
|
|
27
|
+
name: 'query',
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Free-text search term.',
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'indexes',
|
|
34
|
+
type: 'SearchIndex | SearchIndex[]',
|
|
35
|
+
description:
|
|
36
|
+
'Indexes made by `exportSearchIndex()` with this CLI version.',
|
|
37
|
+
required: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'options.type',
|
|
41
|
+
type: "'component' | 'hook' | 'doc' | 'template' | 'theme'",
|
|
42
|
+
description: 'Restrict results to a single domain.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'options.limit',
|
|
46
|
+
type: 'number',
|
|
47
|
+
description: 'Maximum number of results.',
|
|
48
|
+
default: '20',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
returns: [
|
|
52
|
+
{
|
|
53
|
+
type: 'search',
|
|
54
|
+
description:
|
|
55
|
+
'The same response as `search()`: the query, `matchCount`, and the ranked results bounded by `limit`.',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
throws: [
|
|
59
|
+
{
|
|
60
|
+
code: 'ERR_INVALID_ARGUMENT',
|
|
61
|
+
when: 'the query is empty, `type` is unknown, `limit` is not a positive integer, an index is not `{version, candidates}`, or an index was made by another CLI version',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: 'ERR_CORE_NOT_FOUND',
|
|
65
|
+
when: 'every index was made where @astryxdesign/core could not be found, and `type` names a domain that reads it: `component`, `hook`, or `template`',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: 'ERR_VERSIONS_UNAVAILABLE',
|
|
69
|
+
when: 'every index was made where the Core of the release that answers could not be installed into the per-user cache, and `type` names a domain that reads it: `component`, `hook`, or `template`; a rank with no `type` answers with `meta.partial` instead',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
examples: [
|
|
73
|
+
{
|
|
74
|
+
label: 'Rank stored indexes',
|
|
75
|
+
code: 'const r = await rankSearchIndex(text, [coreIndex, ...packageIndexes]);',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
related: ['search', 'exportSearchIndex'],
|
|
79
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The candidates `search` ranks in a folder, as plain JSON: store it, and rank
|
|
6
|
+
* it later with {@link rankSearchIndex}.
|
|
7
|
+
*
|
|
8
|
+
* Experimental (spec:AST-017 FR27): may change or be removed in a patch
|
|
9
|
+
* release.
|
|
10
|
+
* @experimental
|
|
11
|
+
* @param {object} [options]
|
|
12
|
+
* @param {string} [options.cwd] - The folder to gather from, as `search` reads it.
|
|
13
|
+
* @param {string} [options.package] - Keep only the candidates this npm package
|
|
14
|
+
* owns (its components, templates, themes, and doc parts).
|
|
15
|
+
* @returns {Promise<import('./search.type.mjs').SearchIndex>}
|
|
16
|
+
*/
|
|
17
|
+
export function exportSearchIndex(options?: {
|
|
18
|
+
cwd?: string | undefined;
|
|
19
|
+
package?: string | undefined;
|
|
20
|
+
}): Promise<import("./search.type.mjs").SearchIndex>;
|
|
21
|
+
/**
|
|
22
|
+
* Ranks stored indexes with search's tree. An index made in one folder answers
|
|
23
|
+
* as `search(query)` does there (a theme result's command is the in-project
|
|
24
|
+
* `theme add`). Indexes made in separate folders, such as one per package
|
|
25
|
+
* (made with `package`) and one for Core, are ranked together as one list; a
|
|
26
|
+
* doc that one package replaces or extends in another is not merged across
|
|
27
|
+
* them. Every index must come from this CLI's version, since the tree reads
|
|
28
|
+
* the candidates' shape. When every index was made where Core could not be
|
|
29
|
+
* had, the answer carries `meta.partial`, as `search` does.
|
|
30
|
+
*
|
|
31
|
+
* Experimental (spec:AST-017 FR27): may change or be removed in a patch
|
|
32
|
+
* release.
|
|
33
|
+
* @experimental
|
|
34
|
+
* @param {string} query - Free-text search term.
|
|
35
|
+
* @param {import('./search.type.mjs').SearchIndex | import('./search.type.mjs').SearchIndex[]} indexes
|
|
36
|
+
* @param {object} [options]
|
|
37
|
+
* @param {import('./search.type.mjs').SearchDomain} [options.type] - Restrict to one domain.
|
|
38
|
+
* @param {number} [options.limit] - Max results (default 20).
|
|
39
|
+
* @returns {Promise<import('./search.type.mjs').SearchResponse>}
|
|
40
|
+
*/
|
|
41
|
+
export function rankSearchIndex(query: string, indexes: import("./search.type.mjs").SearchIndex | import("./search.type.mjs").SearchIndex[], options?: {
|
|
42
|
+
type?: import("./search.type.mjs").SearchDomain | undefined;
|
|
43
|
+
limit?: number | undefined;
|
|
44
|
+
}): Promise<import("./search.type.mjs").SearchResponse>;
|