@porulle/adapter-pg-search 0.11.0 → 0.14.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/README.md +1 -2
- package/dist/{src/index.d.ts → index.d.ts} +2 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/{src/index.js → index.js} +76 -9
- package/package.json +4 -4
- package/src/index.ts +104 -10
- package/dist/src/index.d.ts.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ import { pgSearchAdapter } from "@porulle/adapter-pg-search";
|
|
|
11
11
|
export default defineConfig({
|
|
12
12
|
search: {
|
|
13
13
|
adapter: pgSearchAdapter({
|
|
14
|
-
query: (sql, params) => yourPgClient.query(sql, params),
|
|
15
14
|
tableName: "search_index", // default
|
|
16
15
|
dictionary: "english", // default; pick "simple" for non-English heavy stores
|
|
17
16
|
}),
|
|
@@ -20,7 +19,7 @@ export default defineConfig({
|
|
|
20
19
|
});
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
With no options, the adapter uses the configured `databaseAdapter` after the search module starts. To use a separate search database, pass a `query` function as a thin wrapper around your PG client (`postgres`, `pg`, etc.) that returns `{ rows: ... }`; the adapter sends parameterized queries and preserves that callback when the module initializes.
|
|
24
23
|
|
|
25
24
|
## When to pick this over Meilisearch
|
|
26
25
|
|
|
@@ -3,11 +3,11 @@ export interface PgSearchQueryResultRow {
|
|
|
3
3
|
[key: string]: unknown;
|
|
4
4
|
}
|
|
5
5
|
export interface PgSearchAdapterOptions {
|
|
6
|
-
query
|
|
6
|
+
query?: (sql: string, params: unknown[]) => Promise<{
|
|
7
7
|
rows: PgSearchQueryResultRow[];
|
|
8
8
|
}>;
|
|
9
9
|
tableName?: string;
|
|
10
10
|
dictionary?: string;
|
|
11
11
|
}
|
|
12
|
-
export declare function pgSearchAdapter(options
|
|
12
|
+
export declare function pgSearchAdapter(options?: PgSearchAdapterOptions): SearchAdapter;
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAMnB,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,sBAAsB;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,CACN,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EAAE,KACd,OAAO,CAAC;QAAE,IAAI,EAAE,sBAAsB,EAAE,CAAA;KAAE,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8PD,wBAAgB,eAAe,CAC7B,OAAO,GAAE,sBAA2B,GACnC,aAAa,CAkMf"}
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { Err, Ok, } from "@porulle/core";
|
|
2
|
+
import { sql } from "@porulle/core/drizzle";
|
|
3
|
+
function databaseRows(result) {
|
|
4
|
+
if (Array.isArray(result))
|
|
5
|
+
return result;
|
|
6
|
+
if (result && typeof result === "object") {
|
|
7
|
+
const rows = result.rows;
|
|
8
|
+
if (Array.isArray(rows))
|
|
9
|
+
return rows;
|
|
10
|
+
}
|
|
11
|
+
throw new Error("Configured database execute() did not return rows.");
|
|
12
|
+
}
|
|
13
|
+
function toDatabaseQuery(query, params) {
|
|
14
|
+
const chunks = [];
|
|
15
|
+
const placeholders = /\$(\d+)/g;
|
|
16
|
+
let cursor = 0;
|
|
17
|
+
let match;
|
|
18
|
+
while ((match = placeholders.exec(query)) !== null) {
|
|
19
|
+
const parameterIndex = Number(match[1]) - 1;
|
|
20
|
+
if (parameterIndex < 0 || parameterIndex >= params.length) {
|
|
21
|
+
throw new Error(`Query placeholder $${match[1]} has no matching parameter.`);
|
|
22
|
+
}
|
|
23
|
+
chunks.push(sql.raw(query.slice(cursor, match.index)));
|
|
24
|
+
chunks.push(sql.param(params[parameterIndex]));
|
|
25
|
+
cursor = placeholders.lastIndex;
|
|
26
|
+
}
|
|
27
|
+
chunks.push(sql.raw(query.slice(cursor)));
|
|
28
|
+
return sql.join(chunks, sql.empty());
|
|
29
|
+
}
|
|
2
30
|
function safeIdentifier(value) {
|
|
3
31
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
4
32
|
throw new Error(`Invalid SQL identifier: ${value}`);
|
|
@@ -70,8 +98,15 @@ function attributeValues(document, name) {
|
|
|
70
98
|
}
|
|
71
99
|
function toDocument(row) {
|
|
72
100
|
const attributes = parseAttributes(row.attributes);
|
|
101
|
+
const payload = row.payload && typeof row.payload === "object"
|
|
102
|
+
? row.payload
|
|
103
|
+
: {};
|
|
104
|
+
const organizationId = typeof payload.organizationId === "string"
|
|
105
|
+
? payload.organizationId
|
|
106
|
+
: undefined;
|
|
73
107
|
return {
|
|
74
108
|
id: String(row.id ?? ""),
|
|
109
|
+
...(organizationId ? { organizationId } : {}),
|
|
75
110
|
type: String(row.type ?? ""),
|
|
76
111
|
slug: String(row.slug ?? ""),
|
|
77
112
|
title: String(row.title ?? ""),
|
|
@@ -81,7 +116,7 @@ function toDocument(row) {
|
|
|
81
116
|
brands: parseBrands(row.brands),
|
|
82
117
|
text: String(row.text ?? ""),
|
|
83
118
|
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
84
|
-
...(
|
|
119
|
+
...(Object.keys(payload).length > 0 ? { payload } : {}),
|
|
85
120
|
};
|
|
86
121
|
}
|
|
87
122
|
function buildWhere(params, dictionary) {
|
|
@@ -95,6 +130,10 @@ function buildWhere(params, dictionary) {
|
|
|
95
130
|
values.push(params.filters.type);
|
|
96
131
|
clauses.push(`type = $${values.length}`);
|
|
97
132
|
}
|
|
133
|
+
if (params.filters?.organizationId) {
|
|
134
|
+
values.push(params.filters.organizationId);
|
|
135
|
+
clauses.push(`payload ->> 'organizationId' = $${values.length}`);
|
|
136
|
+
}
|
|
98
137
|
if (params.filters?.status) {
|
|
99
138
|
values.push(params.filters.status);
|
|
100
139
|
clauses.push(`status = $${values.length}`);
|
|
@@ -175,17 +214,35 @@ function computeFacets(documents, requested) {
|
|
|
175
214
|
}
|
|
176
215
|
return output;
|
|
177
216
|
}
|
|
178
|
-
export function pgSearchAdapter(options) {
|
|
217
|
+
export function pgSearchAdapter(options = {}) {
|
|
179
218
|
const table = safeIdentifier(options.tableName ?? "search_index");
|
|
180
219
|
const dictionary = options.dictionary ?? "english";
|
|
220
|
+
let execute = options.query;
|
|
221
|
+
function requireExecute() {
|
|
222
|
+
if (!execute) {
|
|
223
|
+
throw new Error("pgSearchAdapter has no way to reach the database: no `query` was supplied and init() has not run. Pass the adapter through config.search.adapter so the search module can wire it.");
|
|
224
|
+
}
|
|
225
|
+
return execute;
|
|
226
|
+
}
|
|
181
227
|
return {
|
|
182
228
|
providerId: "pg-search",
|
|
229
|
+
init({ db }) {
|
|
230
|
+
if (execute)
|
|
231
|
+
return;
|
|
232
|
+
const database = db;
|
|
233
|
+
execute = async (query, params) => ({
|
|
234
|
+
rows: databaseRows(await (database.dialect === undefined
|
|
235
|
+
? database.execute(query, params)
|
|
236
|
+
: database.execute(toDatabaseQuery(query, params)))),
|
|
237
|
+
});
|
|
238
|
+
},
|
|
183
239
|
async index(documents) {
|
|
240
|
+
const query = requireExecute();
|
|
184
241
|
try {
|
|
185
242
|
if (documents.length === 0)
|
|
186
243
|
return Ok(undefined);
|
|
187
244
|
for (const document of documents) {
|
|
188
|
-
await
|
|
245
|
+
await query(`INSERT INTO ${table} (id, type, slug, title, description, status, categories, brands, text, attributes, payload)
|
|
189
246
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb)
|
|
190
247
|
ON CONFLICT (id)
|
|
191
248
|
DO UPDATE SET
|
|
@@ -209,7 +266,10 @@ export function pgSearchAdapter(options) {
|
|
|
209
266
|
document.brands,
|
|
210
267
|
document.text,
|
|
211
268
|
JSON.stringify(document.attributes ?? {}),
|
|
212
|
-
JSON.stringify(
|
|
269
|
+
JSON.stringify({
|
|
270
|
+
...(document.payload ?? {}),
|
|
271
|
+
...(document.organizationId ? { organizationId: document.organizationId } : {}),
|
|
272
|
+
}),
|
|
213
273
|
]);
|
|
214
274
|
}
|
|
215
275
|
return Ok(undefined);
|
|
@@ -222,10 +282,11 @@ export function pgSearchAdapter(options) {
|
|
|
222
282
|
}
|
|
223
283
|
},
|
|
224
284
|
async remove(ids) {
|
|
285
|
+
const query = requireExecute();
|
|
225
286
|
try {
|
|
226
287
|
if (ids.length === 0)
|
|
227
288
|
return Ok(undefined);
|
|
228
|
-
await
|
|
289
|
+
await query(`DELETE FROM ${table} WHERE id = ANY($1::text[])`, [ids]);
|
|
229
290
|
return Ok(undefined);
|
|
230
291
|
}
|
|
231
292
|
catch (error) {
|
|
@@ -236,6 +297,7 @@ export function pgSearchAdapter(options) {
|
|
|
236
297
|
}
|
|
237
298
|
},
|
|
238
299
|
async search(params) {
|
|
300
|
+
const query = requireExecute();
|
|
239
301
|
try {
|
|
240
302
|
const page = Math.max(1, params.page ?? 1);
|
|
241
303
|
const limit = Math.max(1, Math.min(100, params.limit ?? 20));
|
|
@@ -244,14 +306,14 @@ export function pgSearchAdapter(options) {
|
|
|
244
306
|
const scoreExpr = params.query.trim().length > 0
|
|
245
307
|
? `ts_rank(to_tsvector('${dictionary}', text), plainto_tsquery('${dictionary}', $1))`
|
|
246
308
|
: "0";
|
|
247
|
-
const rows = await
|
|
309
|
+
const rows = await query(`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload, ${scoreExpr} AS score
|
|
248
310
|
FROM ${table}
|
|
249
311
|
${where.sql}
|
|
250
312
|
ORDER BY score DESC, title ASC
|
|
251
313
|
LIMIT $${where.values.length + 1}
|
|
252
314
|
OFFSET $${where.values.length + 2}`, [...where.values, limit, offset]);
|
|
253
|
-
const countRows = await
|
|
254
|
-
const facetRows = await
|
|
315
|
+
const countRows = await query(`SELECT COUNT(*)::int AS total FROM ${table} ${where.sql}`, where.values);
|
|
316
|
+
const facetRows = await query(`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload
|
|
255
317
|
FROM ${table}
|
|
256
318
|
${where.sql}`, where.values);
|
|
257
319
|
const documents = facetRows.rows.map((row) => toDocument(row));
|
|
@@ -276,6 +338,7 @@ export function pgSearchAdapter(options) {
|
|
|
276
338
|
}
|
|
277
339
|
},
|
|
278
340
|
async suggest(params) {
|
|
341
|
+
const query = requireExecute();
|
|
279
342
|
try {
|
|
280
343
|
const limit = Math.max(1, Math.min(25, params.limit ?? 10));
|
|
281
344
|
const prefix = params.prefix.trim().toLowerCase();
|
|
@@ -287,8 +350,12 @@ export function pgSearchAdapter(options) {
|
|
|
287
350
|
values.push(params.type);
|
|
288
351
|
conditions.push(`type = $${values.length}`);
|
|
289
352
|
}
|
|
353
|
+
if (params.organizationId) {
|
|
354
|
+
values.push(params.organizationId);
|
|
355
|
+
conditions.push(`payload ->> 'organizationId' = $${values.length}`);
|
|
356
|
+
}
|
|
290
357
|
values.push(limit);
|
|
291
|
-
const rows = await
|
|
358
|
+
const rows = await query(`SELECT DISTINCT title
|
|
292
359
|
FROM ${table}
|
|
293
360
|
WHERE ${conditions.join(" AND ")}
|
|
294
361
|
ORDER BY title ASC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/adapter-pg-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@porulle/core": "0.
|
|
14
|
+
"@porulle/core": "0.14.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^24.5.2",
|
|
18
18
|
"eslint": "^9.39.1",
|
|
19
19
|
"typescript": "5.9.2",
|
|
20
20
|
"vitest": "^3.2.4",
|
|
21
|
-
"@porulle/
|
|
22
|
-
"@porulle/
|
|
21
|
+
"@porulle/typescript-config": "0.1.0",
|
|
22
|
+
"@porulle/eslint-config": "0.1.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -7,18 +7,64 @@ import {
|
|
|
7
7
|
type SearchQueryParams,
|
|
8
8
|
type SearchQueryResult,
|
|
9
9
|
type SearchSuggestParams,
|
|
10
|
+
type PluginDb,
|
|
10
11
|
} from "@porulle/core";
|
|
12
|
+
import { sql, type SQL } from "@porulle/core/drizzle";
|
|
11
13
|
|
|
12
14
|
export interface PgSearchQueryResultRow {
|
|
13
15
|
[key: string]: unknown;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface PgSearchAdapterOptions {
|
|
17
|
-
query
|
|
19
|
+
query?: (
|
|
20
|
+
sql: string,
|
|
21
|
+
params: unknown[],
|
|
22
|
+
) => Promise<{ rows: PgSearchQueryResultRow[] }>;
|
|
18
23
|
tableName?: string;
|
|
19
24
|
dictionary?: string;
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
type PgSearchQuery = (
|
|
28
|
+
sql: string,
|
|
29
|
+
params: unknown[],
|
|
30
|
+
) => Promise<{ rows: PgSearchQueryResultRow[] }>;
|
|
31
|
+
|
|
32
|
+
type PgSearchDatabase = {
|
|
33
|
+
execute(query: SQL | string, params?: unknown[]): Promise<unknown>;
|
|
34
|
+
dialect?: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function databaseRows(result: unknown): PgSearchQueryResultRow[] {
|
|
38
|
+
if (Array.isArray(result)) return result as PgSearchQueryResultRow[];
|
|
39
|
+
if (result && typeof result === "object") {
|
|
40
|
+
const rows = (result as { rows?: unknown }).rows;
|
|
41
|
+
if (Array.isArray(rows)) return rows as PgSearchQueryResultRow[];
|
|
42
|
+
}
|
|
43
|
+
throw new Error("Configured database execute() did not return rows.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toDatabaseQuery(query: string, params: unknown[]): SQL {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
const placeholders = /\$(\d+)/g;
|
|
49
|
+
let cursor = 0;
|
|
50
|
+
let match: RegExpExecArray | null;
|
|
51
|
+
|
|
52
|
+
while ((match = placeholders.exec(query)) !== null) {
|
|
53
|
+
const parameterIndex = Number(match[1]) - 1;
|
|
54
|
+
if (parameterIndex < 0 || parameterIndex >= params.length) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Query placeholder $${match[1]} has no matching parameter.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
chunks.push(sql.raw(query.slice(cursor, match.index)));
|
|
60
|
+
chunks.push(sql.param(params[parameterIndex]));
|
|
61
|
+
cursor = placeholders.lastIndex;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
chunks.push(sql.raw(query.slice(cursor)));
|
|
65
|
+
return sql.join(chunks, sql.empty());
|
|
66
|
+
}
|
|
67
|
+
|
|
22
68
|
function safeIdentifier(value: string): string {
|
|
23
69
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
24
70
|
throw new Error(`Invalid SQL identifier: ${value}`);
|
|
@@ -94,8 +140,15 @@ function attributeValues(document: SearchDocument, name: string): string[] {
|
|
|
94
140
|
|
|
95
141
|
function toDocument(row: PgSearchQueryResultRow): SearchDocument {
|
|
96
142
|
const attributes = parseAttributes(row.attributes);
|
|
143
|
+
const payload = row.payload && typeof row.payload === "object"
|
|
144
|
+
? row.payload as Record<string, unknown>
|
|
145
|
+
: {};
|
|
146
|
+
const organizationId = typeof payload.organizationId === "string"
|
|
147
|
+
? payload.organizationId
|
|
148
|
+
: undefined;
|
|
97
149
|
return {
|
|
98
150
|
id: String(row.id ?? ""),
|
|
151
|
+
...(organizationId ? { organizationId } : {}),
|
|
99
152
|
type: String(row.type ?? ""),
|
|
100
153
|
slug: String(row.slug ?? ""),
|
|
101
154
|
title: String(row.title ?? ""),
|
|
@@ -105,7 +158,7 @@ function toDocument(row: PgSearchQueryResultRow): SearchDocument {
|
|
|
105
158
|
brands: parseBrands(row.brands),
|
|
106
159
|
text: String(row.text ?? ""),
|
|
107
160
|
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
108
|
-
...(
|
|
161
|
+
...(Object.keys(payload).length > 0 ? { payload } : {}),
|
|
109
162
|
};
|
|
110
163
|
}
|
|
111
164
|
|
|
@@ -126,6 +179,11 @@ function buildWhere(
|
|
|
126
179
|
clauses.push(`type = $${values.length}`);
|
|
127
180
|
}
|
|
128
181
|
|
|
182
|
+
if (params.filters?.organizationId) {
|
|
183
|
+
values.push(params.filters.organizationId);
|
|
184
|
+
clauses.push(`payload ->> 'organizationId' = $${values.length}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
129
187
|
if (params.filters?.status) {
|
|
130
188
|
values.push(params.filters.status);
|
|
131
189
|
clauses.push(`status = $${values.length}`);
|
|
@@ -218,19 +276,44 @@ function computeFacets(documents: SearchDocument[], requested?: string[]): Recor
|
|
|
218
276
|
return output;
|
|
219
277
|
}
|
|
220
278
|
|
|
221
|
-
export function pgSearchAdapter(
|
|
279
|
+
export function pgSearchAdapter(
|
|
280
|
+
options: PgSearchAdapterOptions = {},
|
|
281
|
+
): SearchAdapter {
|
|
222
282
|
const table = safeIdentifier(options.tableName ?? "search_index");
|
|
223
283
|
const dictionary = options.dictionary ?? "english";
|
|
284
|
+
let execute: PgSearchQuery | undefined = options.query;
|
|
285
|
+
|
|
286
|
+
function requireExecute(): PgSearchQuery {
|
|
287
|
+
if (!execute) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
"pgSearchAdapter has no way to reach the database: no `query` was supplied and init() has not run. Pass the adapter through config.search.adapter so the search module can wire it.",
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return execute;
|
|
293
|
+
}
|
|
224
294
|
|
|
225
295
|
return {
|
|
226
296
|
providerId: "pg-search",
|
|
227
297
|
|
|
298
|
+
init({ db }: { db: PluginDb }): void {
|
|
299
|
+
if (execute) return;
|
|
300
|
+
const database = db as unknown as PgSearchDatabase;
|
|
301
|
+
execute = async (query, params) => ({
|
|
302
|
+
rows: databaseRows(
|
|
303
|
+
await (database.dialect === undefined
|
|
304
|
+
? database.execute(query, params)
|
|
305
|
+
: database.execute(toDatabaseQuery(query, params))),
|
|
306
|
+
),
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
|
|
228
310
|
async index(documents: SearchDocument[]): Promise<Result<void>> {
|
|
311
|
+
const query = requireExecute();
|
|
229
312
|
try {
|
|
230
313
|
if (documents.length === 0) return Ok(undefined);
|
|
231
314
|
|
|
232
315
|
for (const document of documents) {
|
|
233
|
-
await
|
|
316
|
+
await query(
|
|
234
317
|
`INSERT INTO ${table} (id, type, slug, title, description, status, categories, brands, text, attributes, payload)
|
|
235
318
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb)
|
|
236
319
|
ON CONFLICT (id)
|
|
@@ -256,7 +339,10 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
256
339
|
document.brands,
|
|
257
340
|
document.text,
|
|
258
341
|
JSON.stringify(document.attributes ?? {}),
|
|
259
|
-
JSON.stringify(
|
|
342
|
+
JSON.stringify({
|
|
343
|
+
...(document.payload ?? {}),
|
|
344
|
+
...(document.organizationId ? { organizationId: document.organizationId } : {}),
|
|
345
|
+
}),
|
|
260
346
|
],
|
|
261
347
|
);
|
|
262
348
|
}
|
|
@@ -271,9 +357,10 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
271
357
|
},
|
|
272
358
|
|
|
273
359
|
async remove(ids: string[]): Promise<Result<void>> {
|
|
360
|
+
const query = requireExecute();
|
|
274
361
|
try {
|
|
275
362
|
if (ids.length === 0) return Ok(undefined);
|
|
276
|
-
await
|
|
363
|
+
await query(`DELETE FROM ${table} WHERE id = ANY($1::text[])`, [ids]);
|
|
277
364
|
return Ok(undefined);
|
|
278
365
|
} catch (error) {
|
|
279
366
|
return Err({
|
|
@@ -284,6 +371,7 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
284
371
|
},
|
|
285
372
|
|
|
286
373
|
async search(params: SearchQueryParams): Promise<Result<SearchQueryResult>> {
|
|
374
|
+
const query = requireExecute();
|
|
287
375
|
try {
|
|
288
376
|
const page = Math.max(1, params.page ?? 1);
|
|
289
377
|
const limit = Math.max(1, Math.min(100, params.limit ?? 20));
|
|
@@ -294,7 +382,7 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
294
382
|
? `ts_rank(to_tsvector('${dictionary}', text), plainto_tsquery('${dictionary}', $1))`
|
|
295
383
|
: "0";
|
|
296
384
|
|
|
297
|
-
const rows = await
|
|
385
|
+
const rows = await query(
|
|
298
386
|
`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload, ${scoreExpr} AS score
|
|
299
387
|
FROM ${table}
|
|
300
388
|
${where.sql}
|
|
@@ -304,12 +392,12 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
304
392
|
[...where.values, limit, offset],
|
|
305
393
|
);
|
|
306
394
|
|
|
307
|
-
const countRows = await
|
|
395
|
+
const countRows = await query(
|
|
308
396
|
`SELECT COUNT(*)::int AS total FROM ${table} ${where.sql}`,
|
|
309
397
|
where.values,
|
|
310
398
|
);
|
|
311
399
|
|
|
312
|
-
const facetRows = await
|
|
400
|
+
const facetRows = await query(
|
|
313
401
|
`SELECT id, type, slug, title, description, status, categories, brands, text, attributes, payload
|
|
314
402
|
FROM ${table}
|
|
315
403
|
${where.sql}`,
|
|
@@ -339,6 +427,7 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
339
427
|
},
|
|
340
428
|
|
|
341
429
|
async suggest(params: SearchSuggestParams): Promise<Result<string[]>> {
|
|
430
|
+
const query = requireExecute();
|
|
342
431
|
try {
|
|
343
432
|
const limit = Math.max(1, Math.min(25, params.limit ?? 10));
|
|
344
433
|
const prefix = params.prefix.trim().toLowerCase();
|
|
@@ -353,9 +442,14 @@ export function pgSearchAdapter(options: PgSearchAdapterOptions): SearchAdapter
|
|
|
353
442
|
conditions.push(`type = $${values.length}`);
|
|
354
443
|
}
|
|
355
444
|
|
|
445
|
+
if (params.organizationId) {
|
|
446
|
+
values.push(params.organizationId);
|
|
447
|
+
conditions.push(`payload ->> 'organizationId' = $${values.length}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
356
450
|
values.push(limit);
|
|
357
451
|
|
|
358
|
-
const rows = await
|
|
452
|
+
const rows = await query(
|
|
359
453
|
`SELECT DISTINCT title
|
|
360
454
|
FROM ${table}
|
|
361
455
|
WHERE ${conditions.join(" AND ")}
|
package/dist/src/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|