@porulle/adapter-pg-search 0.10.6 → 0.11.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/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +66 -4
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +67 -4
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAKnB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,sBAAsB;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,sBAAsB,EAAE,CAAA;KAAE,CAAC,CAAC;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAKnB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,sBAAsB;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,sBAAsB,EAAE,CAAA;KAAE,CAAC,CAAC;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyMD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa,CAgK9E"}
|
package/dist/src/index.js
CHANGED
|
@@ -39,7 +39,37 @@ function parseBrands(value) {
|
|
|
39
39
|
}
|
|
40
40
|
return [];
|
|
41
41
|
}
|
|
42
|
+
function parseAttributes(value) {
|
|
43
|
+
let parsed = value;
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(value);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
53
|
+
return {};
|
|
54
|
+
const attributes = {};
|
|
55
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
56
|
+
if (typeof entry === "string") {
|
|
57
|
+
attributes[key] = entry;
|
|
58
|
+
}
|
|
59
|
+
else if (Array.isArray(entry) && entry.every((item) => typeof item === "string")) {
|
|
60
|
+
attributes[key] = entry;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return attributes;
|
|
64
|
+
}
|
|
65
|
+
function attributeValues(document, name) {
|
|
66
|
+
const value = document.attributes?.[name];
|
|
67
|
+
if (value == null)
|
|
68
|
+
return [];
|
|
69
|
+
return Array.isArray(value) ? value : [value];
|
|
70
|
+
}
|
|
42
71
|
function toDocument(row) {
|
|
72
|
+
const attributes = parseAttributes(row.attributes);
|
|
43
73
|
return {
|
|
44
74
|
id: String(row.id ?? ""),
|
|
45
75
|
type: String(row.type ?? ""),
|
|
@@ -50,6 +80,7 @@ function toDocument(row) {
|
|
|
50
80
|
categories: parseCategories(row.categories),
|
|
51
81
|
brands: parseBrands(row.brands),
|
|
52
82
|
text: String(row.text ?? ""),
|
|
83
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
53
84
|
...(row.payload && typeof row.payload === "object" ? { payload: row.payload } : {}),
|
|
54
85
|
};
|
|
55
86
|
}
|
|
@@ -76,6 +107,20 @@ function buildWhere(params, dictionary) {
|
|
|
76
107
|
values.push(params.filters.brand);
|
|
77
108
|
clauses.push(`$${values.length} = ANY(brands)`);
|
|
78
109
|
}
|
|
110
|
+
for (const [name, requested] of Object.entries(params.filters?.attributes ?? {})) {
|
|
111
|
+
values.push(name);
|
|
112
|
+
const keyPlaceholder = `$${values.length}`;
|
|
113
|
+
const requestedValues = Array.isArray(requested) ? requested : [requested];
|
|
114
|
+
if (requestedValues.length === 0) {
|
|
115
|
+
clauses.push("FALSE");
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const valuePlaceholders = requestedValues.map((value) => {
|
|
119
|
+
values.push(value);
|
|
120
|
+
return `$${values.length}`;
|
|
121
|
+
});
|
|
122
|
+
clauses.push(`EXISTS (SELECT 1 FROM jsonb_array_elements_text(CASE WHEN jsonb_typeof(attributes -> ${keyPlaceholder}) = 'array' THEN attributes -> ${keyPlaceholder} ELSE jsonb_build_array(attributes ->> ${keyPlaceholder}) END) AS attribute_value WHERE attribute_value IN (${valuePlaceholders.join(", ")}))`);
|
|
123
|
+
}
|
|
79
124
|
return {
|
|
80
125
|
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
81
126
|
values,
|
|
@@ -113,6 +158,21 @@ function computeFacets(documents, requested) {
|
|
|
113
158
|
}
|
|
114
159
|
}
|
|
115
160
|
}
|
|
161
|
+
for (const facet of facets) {
|
|
162
|
+
const attributeName = facet.startsWith("attributes.")
|
|
163
|
+
? facet.slice("attributes.".length)
|
|
164
|
+
: facet;
|
|
165
|
+
if (["type", "status", "category", "categories", "brand", "brands"].includes(attributeName))
|
|
166
|
+
continue;
|
|
167
|
+
const values = {};
|
|
168
|
+
for (const document of documents) {
|
|
169
|
+
for (const value of new Set(attributeValues(document, attributeName))) {
|
|
170
|
+
values[value] = (values[value] ?? 0) + 1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (Object.keys(values).length > 0)
|
|
174
|
+
output[attributeName] = values;
|
|
175
|
+
}
|
|
116
176
|
return output;
|
|
117
177
|
}
|
|
118
178
|
export function pgSearchAdapter(options) {
|
|
@@ -125,8 +185,8 @@ export function pgSearchAdapter(options) {
|
|
|
125
185
|
if (documents.length === 0)
|
|
126
186
|
return Ok(undefined);
|
|
127
187
|
for (const document of documents) {
|
|
128
|
-
await options.query(`INSERT INTO ${table} (id, type, slug, title, description, status, categories, brands, text, payload)
|
|
129
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb)
|
|
188
|
+
await options.query(`INSERT INTO ${table} (id, type, slug, title, description, status, categories, brands, text, attributes, payload)
|
|
189
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb)
|
|
130
190
|
ON CONFLICT (id)
|
|
131
191
|
DO UPDATE SET
|
|
132
192
|
type = EXCLUDED.type,
|
|
@@ -137,6 +197,7 @@ export function pgSearchAdapter(options) {
|
|
|
137
197
|
categories = EXCLUDED.categories,
|
|
138
198
|
brands = EXCLUDED.brands,
|
|
139
199
|
text = EXCLUDED.text,
|
|
200
|
+
attributes = EXCLUDED.attributes,
|
|
140
201
|
payload = EXCLUDED.payload`, [
|
|
141
202
|
document.id,
|
|
142
203
|
document.type,
|
|
@@ -147,6 +208,7 @@ export function pgSearchAdapter(options) {
|
|
|
147
208
|
document.categories,
|
|
148
209
|
document.brands,
|
|
149
210
|
document.text,
|
|
211
|
+
JSON.stringify(document.attributes ?? {}),
|
|
150
212
|
JSON.stringify(document.payload ?? {}),
|
|
151
213
|
]);
|
|
152
214
|
}
|
|
@@ -182,14 +244,14 @@ export function pgSearchAdapter(options) {
|
|
|
182
244
|
const scoreExpr = params.query.trim().length > 0
|
|
183
245
|
? `ts_rank(to_tsvector('${dictionary}', text), plainto_tsquery('${dictionary}', $1))`
|
|
184
246
|
: "0";
|
|
185
|
-
const rows = await options.query(`SELECT id, type, slug, title, description, status, categories, brands, text, payload, ${scoreExpr} AS score
|
|
247
|
+
const rows = await options.query(`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload, ${scoreExpr} AS score
|
|
186
248
|
FROM ${table}
|
|
187
249
|
${where.sql}
|
|
188
250
|
ORDER BY score DESC, title ASC
|
|
189
251
|
LIMIT $${where.values.length + 1}
|
|
190
252
|
OFFSET $${where.values.length + 2}`, [...where.values, limit, offset]);
|
|
191
253
|
const countRows = await options.query(`SELECT COUNT(*)::int AS total FROM ${table} ${where.sql}`, where.values);
|
|
192
|
-
const facetRows = await options.query(`SELECT id, type, slug, title, description, status, categories, brands, text, payload
|
|
254
|
+
const facetRows = await options.query(`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload
|
|
193
255
|
FROM ${table}
|
|
194
256
|
${where.sql}`, where.values);
|
|
195
257
|
const documents = facetRows.rows.map((row) => toDocument(row));
|