graphile-search 1.1.3 → 1.2.1
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.
|
@@ -18,5 +18,11 @@ export declare function createMatchesOperatorFactory(fullTextScalarName: string,
|
|
|
18
18
|
* Creates the `similarTo` and `wordSimilarTo` filter operator factories
|
|
19
19
|
* for pg_trgm fuzzy text matching. Declared here so they're registered
|
|
20
20
|
* via the declarative `connectionFilterOperatorFactories` API.
|
|
21
|
+
*
|
|
22
|
+
* These operators target 'StringTrgm' (resolved to 'StringTrgmFilter'),
|
|
23
|
+
* NOT the global 'String' type. The unified search plugin registers
|
|
24
|
+
* 'StringTrgmFilter' and selectively assigns it to string columns on
|
|
25
|
+
* tables that qualify for trgm (via intentional search or @trgmSearch tag).
|
|
26
|
+
* This prevents trgm operators from appearing on every string field.
|
|
21
27
|
*/
|
|
22
28
|
export declare function createTrgmOperatorFactories(): ConnectionFilterOperatorFactory;
|
|
@@ -38,13 +38,19 @@ function createMatchesOperatorFactory(fullTextScalarName, tsConfig) {
|
|
|
38
38
|
* Creates the `similarTo` and `wordSimilarTo` filter operator factories
|
|
39
39
|
* for pg_trgm fuzzy text matching. Declared here so they're registered
|
|
40
40
|
* via the declarative `connectionFilterOperatorFactories` API.
|
|
41
|
+
*
|
|
42
|
+
* These operators target 'StringTrgm' (resolved to 'StringTrgmFilter'),
|
|
43
|
+
* NOT the global 'String' type. The unified search plugin registers
|
|
44
|
+
* 'StringTrgmFilter' and selectively assigns it to string columns on
|
|
45
|
+
* tables that qualify for trgm (via intentional search or @trgmSearch tag).
|
|
46
|
+
* This prevents trgm operators from appearing on every string field.
|
|
41
47
|
*/
|
|
42
48
|
function createTrgmOperatorFactories() {
|
|
43
49
|
return (build) => {
|
|
44
50
|
const { sql } = build;
|
|
45
51
|
return [
|
|
46
52
|
{
|
|
47
|
-
typeNames: '
|
|
53
|
+
typeNames: 'StringTrgm',
|
|
48
54
|
operatorName: 'similarTo',
|
|
49
55
|
spec: {
|
|
50
56
|
description: 'Fuzzy matches using pg_trgm trigram similarity. Tolerates typos and misspellings.',
|
|
@@ -62,7 +68,7 @@ function createTrgmOperatorFactories() {
|
|
|
62
68
|
},
|
|
63
69
|
},
|
|
64
70
|
{
|
|
65
|
-
typeNames: '
|
|
71
|
+
typeNames: 'StringTrgm',
|
|
66
72
|
operatorName: 'wordSimilarTo',
|
|
67
73
|
spec: {
|
|
68
74
|
description: 'Fuzzy matches using pg_trgm word_similarity. Finds the best matching substring within the column value.',
|
|
@@ -18,5 +18,11 @@ export declare function createMatchesOperatorFactory(fullTextScalarName: string,
|
|
|
18
18
|
* Creates the `similarTo` and `wordSimilarTo` filter operator factories
|
|
19
19
|
* for pg_trgm fuzzy text matching. Declared here so they're registered
|
|
20
20
|
* via the declarative `connectionFilterOperatorFactories` API.
|
|
21
|
+
*
|
|
22
|
+
* These operators target 'StringTrgm' (resolved to 'StringTrgmFilter'),
|
|
23
|
+
* NOT the global 'String' type. The unified search plugin registers
|
|
24
|
+
* 'StringTrgmFilter' and selectively assigns it to string columns on
|
|
25
|
+
* tables that qualify for trgm (via intentional search or @trgmSearch tag).
|
|
26
|
+
* This prevents trgm operators from appearing on every string field.
|
|
21
27
|
*/
|
|
22
28
|
export declare function createTrgmOperatorFactories(): ConnectionFilterOperatorFactory;
|
|
@@ -34,13 +34,19 @@ export function createMatchesOperatorFactory(fullTextScalarName, tsConfig) {
|
|
|
34
34
|
* Creates the `similarTo` and `wordSimilarTo` filter operator factories
|
|
35
35
|
* for pg_trgm fuzzy text matching. Declared here so they're registered
|
|
36
36
|
* via the declarative `connectionFilterOperatorFactories` API.
|
|
37
|
+
*
|
|
38
|
+
* These operators target 'StringTrgm' (resolved to 'StringTrgmFilter'),
|
|
39
|
+
* NOT the global 'String' type. The unified search plugin registers
|
|
40
|
+
* 'StringTrgmFilter' and selectively assigns it to string columns on
|
|
41
|
+
* tables that qualify for trgm (via intentional search or @trgmSearch tag).
|
|
42
|
+
* This prevents trgm operators from appearing on every string field.
|
|
37
43
|
*/
|
|
38
44
|
export function createTrgmOperatorFactories() {
|
|
39
45
|
return (build) => {
|
|
40
46
|
const { sql } = build;
|
|
41
47
|
return [
|
|
42
48
|
{
|
|
43
|
-
typeNames: '
|
|
49
|
+
typeNames: 'StringTrgm',
|
|
44
50
|
operatorName: 'similarTo',
|
|
45
51
|
spec: {
|
|
46
52
|
description: 'Fuzzy matches using pg_trgm trigram similarity. Tolerates typos and misspellings.',
|
|
@@ -58,7 +64,7 @@ export function createTrgmOperatorFactories() {
|
|
|
58
64
|
},
|
|
59
65
|
},
|
|
60
66
|
{
|
|
61
|
-
typeNames: '
|
|
67
|
+
typeNames: 'StringTrgm',
|
|
62
68
|
operatorName: 'wordSimilarTo',
|
|
63
69
|
spec: {
|
|
64
70
|
description: 'Fuzzy matches using pg_trgm word_similarity. Finds the best matching substring within the column value.',
|
package/esm/plugin.js
CHANGED
|
@@ -61,10 +61,15 @@ export function createUnifiedSearchPlugin(options) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
// Phase 2:
|
|
65
|
-
//
|
|
64
|
+
// Phase 2: Run supplementary adapters if intentional search exists
|
|
65
|
+
// OR if the table/column has a @trgmSearch smart tag.
|
|
66
66
|
// pgvector (isIntentionalSearch: false) alone won't trigger trgm.
|
|
67
|
-
|
|
67
|
+
const hasTrgmSearchTag =
|
|
68
|
+
// Table-level tag
|
|
69
|
+
codec.extensions?.tags?.trgmSearch ||
|
|
70
|
+
// Column-level tag
|
|
71
|
+
(codec.attributes && Object.values(codec.attributes).some((attr) => attr?.extensions?.tags?.trgmSearch));
|
|
72
|
+
if (hasIntentionalSearch || hasTrgmSearchTag) {
|
|
68
73
|
for (const adapter of supplementaryAdapters) {
|
|
69
74
|
const columns = adapter.detectColumns(codec, build);
|
|
70
75
|
if (columns.length > 0) {
|
|
@@ -85,6 +90,8 @@ export function createUnifiedSearchPlugin(options) {
|
|
|
85
90
|
'PgConnectionArgFilterAttributesPlugin',
|
|
86
91
|
'PgConnectionArgFilterOperatorsPlugin',
|
|
87
92
|
'AddConnectionFilterOperatorPlugin',
|
|
93
|
+
'ConnectionFilterTypesPlugin',
|
|
94
|
+
'ConnectionFilterCustomOperatorsPlugin',
|
|
88
95
|
// Allow individual codec plugins to load first (e.g. Bm25CodecPlugin)
|
|
89
96
|
'Bm25CodecPlugin',
|
|
90
97
|
'VectorCodecPlugin',
|
|
@@ -161,6 +168,26 @@ export function createUnifiedSearchPlugin(options) {
|
|
|
161
168
|
for (const adapter of adapters) {
|
|
162
169
|
adapter.registerTypes(build);
|
|
163
170
|
}
|
|
171
|
+
// Register StringTrgmFilter — a variant of StringFilter that includes
|
|
172
|
+
// trgm operators (similarTo, wordSimilarTo). Only string columns on
|
|
173
|
+
// tables that qualify for trgm will use this type instead of StringFilter.
|
|
174
|
+
const hasTrgmAdapter = adapters.some((a) => a.name === 'trgm');
|
|
175
|
+
if (hasTrgmAdapter) {
|
|
176
|
+
const DPTYPES = build.dataplanPg?.TYPES;
|
|
177
|
+
const textCodec = DPTYPES?.text ?? TYPES.text;
|
|
178
|
+
build.registerInputObjectType('StringTrgmFilter', {
|
|
179
|
+
pgConnectionFilterOperators: {
|
|
180
|
+
isList: false,
|
|
181
|
+
pgCodecs: [textCodec],
|
|
182
|
+
inputTypeName: 'String',
|
|
183
|
+
rangeElementInputTypeName: null,
|
|
184
|
+
domainBaseTypeName: null,
|
|
185
|
+
},
|
|
186
|
+
}, () => ({
|
|
187
|
+
description: 'A filter to be used against String fields with pg_trgm support. ' +
|
|
188
|
+
'All fields are combined with a logical \u2018and.\u2019',
|
|
189
|
+
}), 'UnifiedSearchPlugin (StringTrgmFilter)');
|
|
190
|
+
}
|
|
164
191
|
return _;
|
|
165
192
|
},
|
|
166
193
|
/**
|
|
@@ -446,6 +473,26 @@ export function createUnifiedSearchPlugin(options) {
|
|
|
446
473
|
return fields;
|
|
447
474
|
}
|
|
448
475
|
let newFields = fields;
|
|
476
|
+
// ── StringFilter → StringTrgmFilter type swapping ──
|
|
477
|
+
// For tables that qualify for trgm, swap the type of string attribute
|
|
478
|
+
// filter fields so they get similarTo/wordSimilarTo operators.
|
|
479
|
+
const hasTrgm = adapterColumns.some((ac) => ac.adapter.name === 'trgm');
|
|
480
|
+
if (hasTrgm) {
|
|
481
|
+
const StringTrgmFilterType = build.getTypeByName('StringTrgmFilter');
|
|
482
|
+
const StringFilterType = build.getTypeByName('StringFilter');
|
|
483
|
+
if (StringTrgmFilterType && StringFilterType) {
|
|
484
|
+
const swapped = {};
|
|
485
|
+
for (const [key, field] of Object.entries(newFields)) {
|
|
486
|
+
if (field && typeof field === 'object' && field.type === StringFilterType) {
|
|
487
|
+
swapped[key] = Object.assign({}, field, { type: StringTrgmFilterType });
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
swapped[key] = field;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
newFields = swapped;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
449
496
|
for (const { adapter, columns } of adapterColumns) {
|
|
450
497
|
for (const column of columns) {
|
|
451
498
|
const fieldName = inflection.camelCase(`${adapter.filterPrefix}_${column.attributeName}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-search",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Unified PostGraphile v5 search plugin — abstracts tsvector, BM25, pg_trgm, and pgvector behind a single adapter-based architecture with composite searchScore",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.19.11",
|
|
33
33
|
"@types/pg": "^8.18.0",
|
|
34
|
-
"graphile-connection-filter": "^1.1.
|
|
35
|
-
"graphile-test": "^4.5.
|
|
34
|
+
"graphile-connection-filter": "^1.1.5",
|
|
35
|
+
"graphile-test": "^4.5.7",
|
|
36
36
|
"makage": "^0.1.10",
|
|
37
37
|
"pg": "^8.20.0",
|
|
38
|
-
"pgsql-test": "^4.5.
|
|
38
|
+
"pgsql-test": "^4.5.7"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@dataplan/pg": "1.0.0-rc.8",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"hybrid-search",
|
|
63
63
|
"searchScore"
|
|
64
64
|
],
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "c18ef8e002d958001fe69fff3758d1f89613eb2b"
|
|
66
66
|
}
|
package/plugin.js
CHANGED
|
@@ -64,10 +64,15 @@ function createUnifiedSearchPlugin(options) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
// Phase 2:
|
|
68
|
-
//
|
|
67
|
+
// Phase 2: Run supplementary adapters if intentional search exists
|
|
68
|
+
// OR if the table/column has a @trgmSearch smart tag.
|
|
69
69
|
// pgvector (isIntentionalSearch: false) alone won't trigger trgm.
|
|
70
|
-
|
|
70
|
+
const hasTrgmSearchTag =
|
|
71
|
+
// Table-level tag
|
|
72
|
+
codec.extensions?.tags?.trgmSearch ||
|
|
73
|
+
// Column-level tag
|
|
74
|
+
(codec.attributes && Object.values(codec.attributes).some((attr) => attr?.extensions?.tags?.trgmSearch));
|
|
75
|
+
if (hasIntentionalSearch || hasTrgmSearchTag) {
|
|
71
76
|
for (const adapter of supplementaryAdapters) {
|
|
72
77
|
const columns = adapter.detectColumns(codec, build);
|
|
73
78
|
if (columns.length > 0) {
|
|
@@ -88,6 +93,8 @@ function createUnifiedSearchPlugin(options) {
|
|
|
88
93
|
'PgConnectionArgFilterAttributesPlugin',
|
|
89
94
|
'PgConnectionArgFilterOperatorsPlugin',
|
|
90
95
|
'AddConnectionFilterOperatorPlugin',
|
|
96
|
+
'ConnectionFilterTypesPlugin',
|
|
97
|
+
'ConnectionFilterCustomOperatorsPlugin',
|
|
91
98
|
// Allow individual codec plugins to load first (e.g. Bm25CodecPlugin)
|
|
92
99
|
'Bm25CodecPlugin',
|
|
93
100
|
'VectorCodecPlugin',
|
|
@@ -164,6 +171,26 @@ function createUnifiedSearchPlugin(options) {
|
|
|
164
171
|
for (const adapter of adapters) {
|
|
165
172
|
adapter.registerTypes(build);
|
|
166
173
|
}
|
|
174
|
+
// Register StringTrgmFilter — a variant of StringFilter that includes
|
|
175
|
+
// trgm operators (similarTo, wordSimilarTo). Only string columns on
|
|
176
|
+
// tables that qualify for trgm will use this type instead of StringFilter.
|
|
177
|
+
const hasTrgmAdapter = adapters.some((a) => a.name === 'trgm');
|
|
178
|
+
if (hasTrgmAdapter) {
|
|
179
|
+
const DPTYPES = build.dataplanPg?.TYPES;
|
|
180
|
+
const textCodec = DPTYPES?.text ?? pg_1.TYPES.text;
|
|
181
|
+
build.registerInputObjectType('StringTrgmFilter', {
|
|
182
|
+
pgConnectionFilterOperators: {
|
|
183
|
+
isList: false,
|
|
184
|
+
pgCodecs: [textCodec],
|
|
185
|
+
inputTypeName: 'String',
|
|
186
|
+
rangeElementInputTypeName: null,
|
|
187
|
+
domainBaseTypeName: null,
|
|
188
|
+
},
|
|
189
|
+
}, () => ({
|
|
190
|
+
description: 'A filter to be used against String fields with pg_trgm support. ' +
|
|
191
|
+
'All fields are combined with a logical \u2018and.\u2019',
|
|
192
|
+
}), 'UnifiedSearchPlugin (StringTrgmFilter)');
|
|
193
|
+
}
|
|
167
194
|
return _;
|
|
168
195
|
},
|
|
169
196
|
/**
|
|
@@ -449,6 +476,26 @@ function createUnifiedSearchPlugin(options) {
|
|
|
449
476
|
return fields;
|
|
450
477
|
}
|
|
451
478
|
let newFields = fields;
|
|
479
|
+
// ── StringFilter → StringTrgmFilter type swapping ──
|
|
480
|
+
// For tables that qualify for trgm, swap the type of string attribute
|
|
481
|
+
// filter fields so they get similarTo/wordSimilarTo operators.
|
|
482
|
+
const hasTrgm = adapterColumns.some((ac) => ac.adapter.name === 'trgm');
|
|
483
|
+
if (hasTrgm) {
|
|
484
|
+
const StringTrgmFilterType = build.getTypeByName('StringTrgmFilter');
|
|
485
|
+
const StringFilterType = build.getTypeByName('StringFilter');
|
|
486
|
+
if (StringTrgmFilterType && StringFilterType) {
|
|
487
|
+
const swapped = {};
|
|
488
|
+
for (const [key, field] of Object.entries(newFields)) {
|
|
489
|
+
if (field && typeof field === 'object' && field.type === StringFilterType) {
|
|
490
|
+
swapped[key] = Object.assign({}, field, { type: StringTrgmFilterType });
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
swapped[key] = field;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
newFields = swapped;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
452
499
|
for (const { adapter, columns } of adapterColumns) {
|
|
453
500
|
for (const column of columns) {
|
|
454
501
|
const fieldName = inflection.camelCase(`${adapter.filterPrefix}_${column.attributeName}`);
|