@gscdump/analysis 0.24.1 → 0.25.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.
- package/package.json +4 -9
- package/dist/query/index.d.mts +0 -53
- package/dist/query/index.mjs +0 -444
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/analysis",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.25.0",
|
|
5
5
|
"description": "GSC analyzers — striking-distance, opportunity, movers, decay, brand, clustering, concentration, seasonality. Pure row-based + DuckDB-native.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -36,11 +36,6 @@
|
|
|
36
36
|
"import": "./dist/default-registry.mjs",
|
|
37
37
|
"default": "./dist/default-registry.mjs"
|
|
38
38
|
},
|
|
39
|
-
"./query": {
|
|
40
|
-
"types": "./dist/query/index.d.mts",
|
|
41
|
-
"import": "./dist/query/index.mjs",
|
|
42
|
-
"default": "./dist/query/index.mjs"
|
|
43
|
-
},
|
|
44
39
|
"./source": {
|
|
45
40
|
"types": "./dist/source/index.d.mts",
|
|
46
41
|
"import": "./dist/source/index.mjs",
|
|
@@ -75,9 +70,9 @@
|
|
|
75
70
|
},
|
|
76
71
|
"dependencies": {
|
|
77
72
|
"drizzle-orm": "1.0.0-rc.3",
|
|
78
|
-
"
|
|
79
|
-
"@gscdump/engine-gsc-api": "0.
|
|
80
|
-
"gscdump": "0.
|
|
73
|
+
"gscdump": "0.25.0",
|
|
74
|
+
"@gscdump/engine-gsc-api": "0.25.0",
|
|
75
|
+
"@gscdump/engine": "0.25.0"
|
|
81
76
|
},
|
|
82
77
|
"devDependencies": {
|
|
83
78
|
"vitest": "^4.1.7"
|
package/dist/query/index.d.mts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { ResolverOptions } from "@gscdump/engine/resolver";
|
|
2
|
-
import { BuilderState } from "gscdump/query";
|
|
3
|
-
import { AnalysisParams } from "@gscdump/engine/analysis-types";
|
|
4
|
-
import { Row } from "@gscdump/engine/contracts";
|
|
5
|
-
interface QueryAnalyzerExtraQuery {
|
|
6
|
-
name: string;
|
|
7
|
-
sql: string;
|
|
8
|
-
params: unknown[];
|
|
9
|
-
}
|
|
10
|
-
interface QueryAnalyzerPlan<TK extends string = string> {
|
|
11
|
-
tableKey: TK;
|
|
12
|
-
sql: string;
|
|
13
|
-
params: unknown[];
|
|
14
|
-
extraQueries?: QueryAnalyzerExtraQuery[];
|
|
15
|
-
}
|
|
16
|
-
declare function buildDataQueryPlan<TK extends string>(params: AnalysisParams, options: ResolverOptions<TK>): QueryAnalyzerPlan<TK>;
|
|
17
|
-
/** Row-query plan for `data-query`. Comparison (`qc`) joins two periods. */
|
|
18
|
-
declare function buildDataQueryRows(params: AnalysisParams): Record<string, BuilderState>;
|
|
19
|
-
/** Post-processing for `data-query` row results. Mirrors `shapeDataQuery`. */
|
|
20
|
-
declare function shapeDataQueryRowResults(rowMap: Record<string, Row[]>, params: AnalysisParams): {
|
|
21
|
-
results: Row[];
|
|
22
|
-
meta: Record<string, unknown>;
|
|
23
|
-
};
|
|
24
|
-
/** Row-query plan for `data-detail`. `qc` adds a previous-period totals query. */
|
|
25
|
-
declare function buildDataDetailRows(params: AnalysisParams): Record<string, BuilderState>;
|
|
26
|
-
/** Post-processing for `data-detail` row results. Mirrors `shapeDataDetailRows`. */
|
|
27
|
-
declare function shapeDataDetailRowResults(rowMap: Record<string, Row[]>, params: AnalysisParams): {
|
|
28
|
-
results: Row[];
|
|
29
|
-
meta: Record<string, unknown>;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Pure post-processing for `data-query` rows. Adapter-free so reducers can
|
|
33
|
-
* call it without re-running the SQL plan.
|
|
34
|
-
*/
|
|
35
|
-
declare function shapeDataQueryRows(rows: Row[], params: AnalysisParams, extras?: Record<string, Row[]>): {
|
|
36
|
-
results: Row[];
|
|
37
|
-
meta: Record<string, unknown>;
|
|
38
|
-
};
|
|
39
|
-
declare function buildDataDetailPlan<TK extends string>(params: AnalysisParams, options: ResolverOptions<TK>): QueryAnalyzerPlan<TK>;
|
|
40
|
-
/**
|
|
41
|
-
* Pure post-processing for `data-detail` rows. Reads the date range off
|
|
42
|
-
* `params.q` so it stays adapter-free; reducers call it directly.
|
|
43
|
-
*/
|
|
44
|
-
declare function shapeDataDetailRows(rows: Row[], params: AnalysisParams, extras?: Record<string, Row[]>): {
|
|
45
|
-
results: Row[];
|
|
46
|
-
meta: Record<string, unknown>;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Produce a canonical form of a search query for grouping near-duplicates.
|
|
50
|
-
* Idempotent: `normalizeQuery(normalizeQuery(q)) === normalizeQuery(q)`.
|
|
51
|
-
*/
|
|
52
|
-
declare function normalizeQuery(query: string): string;
|
|
53
|
-
export { type QueryAnalyzerExtraQuery, type QueryAnalyzerPlan, buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, normalizeQuery, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows };
|
package/dist/query/index.mjs
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import { padTimeseries } from "@gscdump/engine/period";
|
|
2
|
-
import { buildExtrasQueries, buildTotalsSql, mergeExtras, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
|
|
3
|
-
import { extractDateRange } from "gscdump/query";
|
|
4
|
-
function requireBuilderState(input, tool) {
|
|
5
|
-
if (!input || typeof input !== "object" || !("dimensions" in input) || !Array.isArray(input.dimensions)) throw new Error(`${tool}: params.q is required (BuilderState)`);
|
|
6
|
-
return input;
|
|
7
|
-
}
|
|
8
|
-
function optionalBuilderState(input, tool, key) {
|
|
9
|
-
if (input == null) return null;
|
|
10
|
-
if (typeof input !== "object" || !("dimensions" in input) || !Array.isArray(input.dimensions)) throw new Error(`${tool}: params.${key} must be a BuilderState`);
|
|
11
|
-
return input;
|
|
12
|
-
}
|
|
13
|
-
const NUMERIC_METRIC_COLS = [
|
|
14
|
-
"clicks",
|
|
15
|
-
"impressions",
|
|
16
|
-
"ctr",
|
|
17
|
-
"position",
|
|
18
|
-
"prevClicks",
|
|
19
|
-
"prevImpressions",
|
|
20
|
-
"prevCtr",
|
|
21
|
-
"prevPosition",
|
|
22
|
-
"variantCount",
|
|
23
|
-
"totalCount"
|
|
24
|
-
];
|
|
25
|
-
function coerceNumericCols(row) {
|
|
26
|
-
const out = { ...row };
|
|
27
|
-
for (const col of NUMERIC_METRIC_COLS) if (col in out && out[col] != null) out[col] = Number(out[col]);
|
|
28
|
-
return out;
|
|
29
|
-
}
|
|
30
|
-
function shapeDataQuery(rows, extras, opts) {
|
|
31
|
-
let totalCount;
|
|
32
|
-
let cleaned;
|
|
33
|
-
if (opts.hasPrev) {
|
|
34
|
-
cleaned = rows.map(coerceNumericCols);
|
|
35
|
-
totalCount = Number((extras?.count?.[0])?.total ?? cleaned.length);
|
|
36
|
-
} else {
|
|
37
|
-
const first = rows[0];
|
|
38
|
-
totalCount = Number(first?.totalCount ?? 0);
|
|
39
|
-
cleaned = rows.map((raw) => {
|
|
40
|
-
const { totalCount: _tc, totalClicks: _tclk, totalImpressions: _timp, totalCtr: _tctr, totalPosition: _tpos, sum_position: _sp, ...rest } = raw;
|
|
41
|
-
return coerceNumericCols(rest);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
const totalsRow = extras?.totals?.[0] ?? {};
|
|
45
|
-
const totals = {
|
|
46
|
-
clicks: Number(totalsRow.clicks ?? 0),
|
|
47
|
-
impressions: Number(totalsRow.impressions ?? 0),
|
|
48
|
-
ctr: Number(totalsRow.ctr ?? 0),
|
|
49
|
-
position: Number(totalsRow.position ?? 0)
|
|
50
|
-
};
|
|
51
|
-
const extrasResults = [];
|
|
52
|
-
if (extras?.canonicalExtras) extrasResults.push({
|
|
53
|
-
key: "canonicalExtras",
|
|
54
|
-
results: extras.canonicalExtras
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
results: mergeExtras(cleaned, extrasResults),
|
|
58
|
-
meta: {
|
|
59
|
-
totalCount,
|
|
60
|
-
totals
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function buildDataQueryPlan(params, options) {
|
|
65
|
-
const state = requireBuilderState(params.q, "data-query");
|
|
66
|
-
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
67
|
-
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
68
|
-
const totals = buildTotalsSql(state, options);
|
|
69
|
-
const extras = buildExtrasQueries(state, options);
|
|
70
|
-
const extraQueries = [{
|
|
71
|
-
name: "totals",
|
|
72
|
-
sql: totals.sql,
|
|
73
|
-
params: totals.params
|
|
74
|
-
}, ...extras.map((extra) => ({
|
|
75
|
-
name: extra.key,
|
|
76
|
-
sql: extra.sql,
|
|
77
|
-
params: extra.params
|
|
78
|
-
}))];
|
|
79
|
-
const tableKey = options.adapter.inferTable(state.dimensions);
|
|
80
|
-
if (prev) {
|
|
81
|
-
const comparison = resolveComparisonSQL(state, prev, options, params.comparisonFilter);
|
|
82
|
-
extraQueries.push({
|
|
83
|
-
name: "count",
|
|
84
|
-
sql: comparison.countSql,
|
|
85
|
-
params: comparison.countParams
|
|
86
|
-
});
|
|
87
|
-
return {
|
|
88
|
-
tableKey,
|
|
89
|
-
sql: comparison.sql,
|
|
90
|
-
params: comparison.params,
|
|
91
|
-
extraQueries
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const optimized = resolveToSQLOptimized(state, options);
|
|
95
|
-
return {
|
|
96
|
-
tableKey,
|
|
97
|
-
sql: optimized.sql,
|
|
98
|
-
params: optimized.params,
|
|
99
|
-
extraQueries
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function totalsState(state) {
|
|
103
|
-
return {
|
|
104
|
-
...state,
|
|
105
|
-
dimensions: [],
|
|
106
|
-
orderBy: void 0,
|
|
107
|
-
rowLimit: 1
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function readTotals(row) {
|
|
111
|
-
return {
|
|
112
|
-
clicks: Number(row?.clicks ?? 0),
|
|
113
|
-
impressions: Number(row?.impressions ?? 0),
|
|
114
|
-
ctr: Number(row?.ctr ?? 0),
|
|
115
|
-
position: Number(row?.position ?? 0)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function buildDataQueryRows(params) {
|
|
119
|
-
const state = requireBuilderState(params.q, "data-query");
|
|
120
|
-
if (state.dimensions.includes("date")) throw new Error("data-query: date dimension not supported; use data-detail");
|
|
121
|
-
const queries = {
|
|
122
|
-
main: state,
|
|
123
|
-
totals: totalsState(state)
|
|
124
|
-
};
|
|
125
|
-
const prev = optionalBuilderState(params.qc, "data-query", "qc");
|
|
126
|
-
if (prev) {
|
|
127
|
-
queries.prevMain = prev;
|
|
128
|
-
queries.prevTotals = totalsState(prev);
|
|
129
|
-
}
|
|
130
|
-
return queries;
|
|
131
|
-
}
|
|
132
|
-
function shapeDataQueryRowResults(rowMap, params) {
|
|
133
|
-
const main = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
134
|
-
const totals = readTotals(rowMap.totals?.[0]);
|
|
135
|
-
if (params.qc == null) return {
|
|
136
|
-
results: main,
|
|
137
|
-
meta: {
|
|
138
|
-
totalCount: main.length,
|
|
139
|
-
totals
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
const state = requireBuilderState(params.q, "data-query");
|
|
143
|
-
const dims = state.dimensions;
|
|
144
|
-
const keyOf = (row) => dims.map((d) => String(row[d] ?? "")).join("\0");
|
|
145
|
-
const prevByKey = /* @__PURE__ */ new Map();
|
|
146
|
-
for (const r of rowMap.prevMain ?? []) prevByKey.set(keyOf(r), r);
|
|
147
|
-
const filter = params.comparisonFilter;
|
|
148
|
-
const merged = [];
|
|
149
|
-
const seen = /* @__PURE__ */ new Set();
|
|
150
|
-
for (const cur of main) {
|
|
151
|
-
const key = keyOf(cur);
|
|
152
|
-
seen.add(key);
|
|
153
|
-
const prev = prevByKey.get(key);
|
|
154
|
-
const row = {
|
|
155
|
-
...cur,
|
|
156
|
-
prevClicks: Number(prev?.clicks ?? 0),
|
|
157
|
-
prevImpressions: Number(prev?.impressions ?? 0),
|
|
158
|
-
prevCtr: Number(prev?.ctr ?? 0),
|
|
159
|
-
prevPosition: Number(prev?.position ?? 0)
|
|
160
|
-
};
|
|
161
|
-
if (passesComparisonFilter(filter, {
|
|
162
|
-
isNew: !prev,
|
|
163
|
-
isLost: false,
|
|
164
|
-
clicksChange: Number(cur.clicks ?? 0) - Number(prev?.clicks ?? 0)
|
|
165
|
-
})) merged.push(row);
|
|
166
|
-
}
|
|
167
|
-
for (const prev of rowMap.prevMain ?? []) {
|
|
168
|
-
const p = prev;
|
|
169
|
-
const key = keyOf(p);
|
|
170
|
-
if (seen.has(key)) continue;
|
|
171
|
-
const row = {
|
|
172
|
-
...p,
|
|
173
|
-
clicks: 0,
|
|
174
|
-
impressions: 0,
|
|
175
|
-
ctr: 0,
|
|
176
|
-
position: 0,
|
|
177
|
-
prevClicks: Number(p.clicks ?? 0),
|
|
178
|
-
prevImpressions: Number(p.impressions ?? 0),
|
|
179
|
-
prevCtr: Number(p.ctr ?? 0),
|
|
180
|
-
prevPosition: Number(p.position ?? 0)
|
|
181
|
-
};
|
|
182
|
-
if (passesComparisonFilter(filter, {
|
|
183
|
-
isNew: false,
|
|
184
|
-
isLost: true,
|
|
185
|
-
clicksChange: -Number(p.clicks ?? 0)
|
|
186
|
-
})) merged.push(row);
|
|
187
|
-
}
|
|
188
|
-
if (state.orderBy) {
|
|
189
|
-
const { column, dir } = state.orderBy;
|
|
190
|
-
merged.sort((a, b) => {
|
|
191
|
-
const av = Number(a[column]) || 0;
|
|
192
|
-
const bv = Number(b[column]) || 0;
|
|
193
|
-
return dir === "asc" ? av - bv : bv - av;
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
results: merged.map((r) => coerceNumericCols(r)),
|
|
198
|
-
meta: {
|
|
199
|
-
totalCount: merged.length,
|
|
200
|
-
totals
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function passesComparisonFilter(filter, ctx) {
|
|
205
|
-
switch (filter) {
|
|
206
|
-
case "new": return ctx.isNew;
|
|
207
|
-
case "lost": return ctx.isLost;
|
|
208
|
-
case "improving": return ctx.clicksChange > 0;
|
|
209
|
-
case "declining": return ctx.clicksChange < 0;
|
|
210
|
-
default: return true;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function buildDataDetailRows(params) {
|
|
214
|
-
const state = requireBuilderState(params.q, "data-detail");
|
|
215
|
-
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
216
|
-
const queries = {
|
|
217
|
-
main: state,
|
|
218
|
-
totals: totalsState(state)
|
|
219
|
-
};
|
|
220
|
-
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
221
|
-
if (prev) queries.prevTotals = totalsState(prev);
|
|
222
|
-
return queries;
|
|
223
|
-
}
|
|
224
|
-
function shapeDataDetailRowResults(rowMap, params) {
|
|
225
|
-
const { startDate, endDate } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
226
|
-
const coerced = (rowMap.main ?? []).map((r) => coerceNumericCols(r));
|
|
227
|
-
const daily = startDate && endDate ? padTimeseries(coerced, {
|
|
228
|
-
startDate,
|
|
229
|
-
endDate
|
|
230
|
-
}) : coerced;
|
|
231
|
-
const meta = { totals: readTotals(rowMap.totals?.[0]) };
|
|
232
|
-
if (rowMap.prevTotals) meta.previousTotals = readTotals(rowMap.prevTotals[0]);
|
|
233
|
-
return {
|
|
234
|
-
results: daily,
|
|
235
|
-
meta
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
function shapeDataQueryRows(rows, params, extras) {
|
|
239
|
-
return shapeDataQuery(rows, extras, { hasPrev: params.qc != null });
|
|
240
|
-
}
|
|
241
|
-
function buildDataDetailPlan(params, options) {
|
|
242
|
-
const state = requireBuilderState(params.q, "data-detail");
|
|
243
|
-
if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
|
|
244
|
-
const main = resolveToSQL(state, options);
|
|
245
|
-
const totals = buildTotalsSql(state, options);
|
|
246
|
-
const prev = optionalBuilderState(params.qc, "data-detail", "qc");
|
|
247
|
-
const extraQueries = [{
|
|
248
|
-
name: "totals",
|
|
249
|
-
sql: totals.sql,
|
|
250
|
-
params: totals.params
|
|
251
|
-
}];
|
|
252
|
-
if (prev) {
|
|
253
|
-
const previousTotals = buildTotalsSql(prev, options);
|
|
254
|
-
extraQueries.push({
|
|
255
|
-
name: "prevTotals",
|
|
256
|
-
sql: previousTotals.sql,
|
|
257
|
-
params: previousTotals.params
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
tableKey: options.adapter.inferTable(state.dimensions),
|
|
262
|
-
sql: main.sql,
|
|
263
|
-
params: main.params,
|
|
264
|
-
extraQueries
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function shapeDataDetailRows(rows, params, extras) {
|
|
268
|
-
const { startDate: rangeStart, endDate: rangeEnd } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
|
|
269
|
-
const coerced = rows.map(coerceNumericCols);
|
|
270
|
-
const daily = rangeStart && rangeEnd ? padTimeseries(coerced, {
|
|
271
|
-
startDate: rangeStart,
|
|
272
|
-
endDate: rangeEnd
|
|
273
|
-
}) : coerced;
|
|
274
|
-
const totalsRow = extras?.totals?.[0] ?? {};
|
|
275
|
-
const meta = { totals: {
|
|
276
|
-
clicks: Number(totalsRow.clicks ?? 0),
|
|
277
|
-
impressions: Number(totalsRow.impressions ?? 0),
|
|
278
|
-
ctr: Number(totalsRow.ctr ?? 0),
|
|
279
|
-
position: Number(totalsRow.position ?? 0)
|
|
280
|
-
} };
|
|
281
|
-
if (extras?.prevTotals) {
|
|
282
|
-
const previousTotalsRow = extras.prevTotals[0] ?? {};
|
|
283
|
-
meta.previousTotals = {
|
|
284
|
-
clicks: Number(previousTotalsRow.clicks ?? 0),
|
|
285
|
-
impressions: Number(previousTotalsRow.impressions ?? 0),
|
|
286
|
-
ctr: Number(previousTotalsRow.ctr ?? 0),
|
|
287
|
-
position: Number(previousTotalsRow.position ?? 0)
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
results: daily,
|
|
292
|
-
meta
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
const SYNONYMS = {
|
|
296
|
-
checker: "validator",
|
|
297
|
-
tester: "validator",
|
|
298
|
-
verifier: "validator",
|
|
299
|
-
verify: "validate",
|
|
300
|
-
check: "validate",
|
|
301
|
-
test: "validate",
|
|
302
|
-
checking: "validate",
|
|
303
|
-
testing: "validate",
|
|
304
|
-
creator: "generator",
|
|
305
|
-
builder: "generator",
|
|
306
|
-
maker: "generator",
|
|
307
|
-
create: "generate",
|
|
308
|
-
build: "generate",
|
|
309
|
-
make: "generate",
|
|
310
|
-
lookup: "search",
|
|
311
|
-
finder: "search",
|
|
312
|
-
find: "search",
|
|
313
|
-
online: "",
|
|
314
|
-
free: ""
|
|
315
|
-
};
|
|
316
|
-
const NO_STRIP_S = new Set([
|
|
317
|
-
"css",
|
|
318
|
-
"js",
|
|
319
|
-
"ts",
|
|
320
|
-
"os",
|
|
321
|
-
"as",
|
|
322
|
-
"is",
|
|
323
|
-
"us",
|
|
324
|
-
"has",
|
|
325
|
-
"was",
|
|
326
|
-
"its",
|
|
327
|
-
"this",
|
|
328
|
-
"yes",
|
|
329
|
-
"no",
|
|
330
|
-
"bus",
|
|
331
|
-
"gas",
|
|
332
|
-
"dns",
|
|
333
|
-
"rss",
|
|
334
|
-
"sms",
|
|
335
|
-
"gps",
|
|
336
|
-
"aws",
|
|
337
|
-
"sas",
|
|
338
|
-
"cms",
|
|
339
|
-
"ios",
|
|
340
|
-
"less",
|
|
341
|
-
"loss",
|
|
342
|
-
"miss",
|
|
343
|
-
"pass",
|
|
344
|
-
"class",
|
|
345
|
-
"access",
|
|
346
|
-
"process",
|
|
347
|
-
"express",
|
|
348
|
-
"address",
|
|
349
|
-
"cross",
|
|
350
|
-
"press",
|
|
351
|
-
"stress",
|
|
352
|
-
"progress",
|
|
353
|
-
"success",
|
|
354
|
-
"business",
|
|
355
|
-
"wordpress",
|
|
356
|
-
"status",
|
|
357
|
-
"radius",
|
|
358
|
-
"nexus",
|
|
359
|
-
"focus",
|
|
360
|
-
"bonus",
|
|
361
|
-
"campus",
|
|
362
|
-
"census",
|
|
363
|
-
"corpus",
|
|
364
|
-
"nucleus",
|
|
365
|
-
"stimulus",
|
|
366
|
-
"terminus",
|
|
367
|
-
"versus",
|
|
368
|
-
"virus",
|
|
369
|
-
"surplus",
|
|
370
|
-
"cactus",
|
|
371
|
-
"analysis",
|
|
372
|
-
"basis",
|
|
373
|
-
"thesis",
|
|
374
|
-
"crisis",
|
|
375
|
-
"axis",
|
|
376
|
-
"genesis",
|
|
377
|
-
"synopsis",
|
|
378
|
-
"diagnosis",
|
|
379
|
-
"emphasis",
|
|
380
|
-
"hypothesis",
|
|
381
|
-
"synthesis",
|
|
382
|
-
"parenthesis",
|
|
383
|
-
"redis",
|
|
384
|
-
"apis",
|
|
385
|
-
"chaos",
|
|
386
|
-
"demos",
|
|
387
|
-
"logos",
|
|
388
|
-
"photos",
|
|
389
|
-
"videos",
|
|
390
|
-
"nuxtjs",
|
|
391
|
-
"nextjs",
|
|
392
|
-
"nodejs",
|
|
393
|
-
"reactjs",
|
|
394
|
-
"vuejs",
|
|
395
|
-
"angularjs",
|
|
396
|
-
"expressjs",
|
|
397
|
-
"nestjs",
|
|
398
|
-
"threejs",
|
|
399
|
-
"alpinejs",
|
|
400
|
-
"solidjs",
|
|
401
|
-
"sveltejs",
|
|
402
|
-
"dejs",
|
|
403
|
-
"bunjs",
|
|
404
|
-
"denojs",
|
|
405
|
-
"canvas",
|
|
406
|
-
"atlas",
|
|
407
|
-
"alias",
|
|
408
|
-
"bias",
|
|
409
|
-
"perhaps",
|
|
410
|
-
"whereas",
|
|
411
|
-
"kubernetes",
|
|
412
|
-
"sass",
|
|
413
|
-
"postgres",
|
|
414
|
-
"always",
|
|
415
|
-
"across",
|
|
416
|
-
"previous",
|
|
417
|
-
"various",
|
|
418
|
-
"serious",
|
|
419
|
-
"famous",
|
|
420
|
-
"anonymous",
|
|
421
|
-
"continuous",
|
|
422
|
-
"dangerous",
|
|
423
|
-
"generous",
|
|
424
|
-
"obvious",
|
|
425
|
-
"numerous",
|
|
426
|
-
"curious",
|
|
427
|
-
"nervous",
|
|
428
|
-
"conscious"
|
|
429
|
-
]);
|
|
430
|
-
function depluralize(token) {
|
|
431
|
-
if (token.length <= 3) return token;
|
|
432
|
-
if (NO_STRIP_S.has(token)) return token;
|
|
433
|
-
if (token.endsWith("ies") && token.length > 4) return `${token.slice(0, -3)}y`;
|
|
434
|
-
if (token.endsWith("ses") && token.length > 4) return token.slice(0, -1);
|
|
435
|
-
if (token.endsWith("shes") || token.endsWith("ches") || token.endsWith("xes") || token.endsWith("zes")) return token.slice(0, -2);
|
|
436
|
-
if (token.endsWith("s") && !token.endsWith("ss")) return token.slice(0, -1);
|
|
437
|
-
return token;
|
|
438
|
-
}
|
|
439
|
-
const SEPARATOR_RE = /[-_/.@#:+]+/g;
|
|
440
|
-
const WHITESPACE_RE = /\s+/g;
|
|
441
|
-
function normalizeQuery(query) {
|
|
442
|
-
return query.toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim().split(" ").filter(Boolean).map((token) => SYNONYMS[token] ?? token).filter(Boolean).map(depluralize).sort().join(" ");
|
|
443
|
-
}
|
|
444
|
-
export { buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, normalizeQuery, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows };
|