graphile-search 1.1.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.
Files changed (59) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +123 -0
  3. package/adapters/bm25.d.ts +32 -0
  4. package/adapters/bm25.js +119 -0
  5. package/adapters/index.d.ts +14 -0
  6. package/adapters/index.js +17 -0
  7. package/adapters/pgvector.d.ts +21 -0
  8. package/adapters/pgvector.js +125 -0
  9. package/adapters/trgm.d.ts +20 -0
  10. package/adapters/trgm.js +83 -0
  11. package/adapters/tsvector.d.ts +20 -0
  12. package/adapters/tsvector.js +60 -0
  13. package/codecs/bm25-codec.d.ts +42 -0
  14. package/codecs/bm25-codec.js +199 -0
  15. package/codecs/index.d.ts +12 -0
  16. package/codecs/index.js +22 -0
  17. package/codecs/operator-factories.d.ts +22 -0
  18. package/codecs/operator-factories.js +84 -0
  19. package/codecs/tsvector-codec.d.ts +53 -0
  20. package/codecs/tsvector-codec.js +162 -0
  21. package/codecs/vector-codec.d.ts +18 -0
  22. package/codecs/vector-codec.js +116 -0
  23. package/esm/adapters/bm25.d.ts +32 -0
  24. package/esm/adapters/bm25.js +116 -0
  25. package/esm/adapters/index.d.ts +14 -0
  26. package/esm/adapters/index.js +10 -0
  27. package/esm/adapters/pgvector.d.ts +21 -0
  28. package/esm/adapters/pgvector.js +122 -0
  29. package/esm/adapters/trgm.d.ts +20 -0
  30. package/esm/adapters/trgm.js +80 -0
  31. package/esm/adapters/tsvector.d.ts +20 -0
  32. package/esm/adapters/tsvector.js +57 -0
  33. package/esm/codecs/bm25-codec.d.ts +42 -0
  34. package/esm/codecs/bm25-codec.js +160 -0
  35. package/esm/codecs/index.d.ts +12 -0
  36. package/esm/codecs/index.js +10 -0
  37. package/esm/codecs/operator-factories.d.ts +22 -0
  38. package/esm/codecs/operator-factories.js +80 -0
  39. package/esm/codecs/tsvector-codec.d.ts +53 -0
  40. package/esm/codecs/tsvector-codec.js +155 -0
  41. package/esm/codecs/vector-codec.d.ts +18 -0
  42. package/esm/codecs/vector-codec.js +110 -0
  43. package/esm/index.d.ts +40 -0
  44. package/esm/index.js +41 -0
  45. package/esm/plugin.d.ts +50 -0
  46. package/esm/plugin.js +553 -0
  47. package/esm/preset.d.ts +79 -0
  48. package/esm/preset.js +82 -0
  49. package/esm/types.d.ts +171 -0
  50. package/esm/types.js +7 -0
  51. package/index.d.ts +40 -0
  52. package/index.js +60 -0
  53. package/package.json +66 -0
  54. package/plugin.d.ts +50 -0
  55. package/plugin.js +556 -0
  56. package/preset.d.ts +79 -0
  57. package/preset.js +85 -0
  58. package/types.d.ts +171 -0
  59. package/types.js +8 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Bm25CodecPlugin
3
+ *
4
+ * Teaches PostGraphile v5 how to handle the pg_textsearch `bm25query` type
5
+ * and discovers all BM25 indexes in the database.
6
+ *
7
+ * This plugin:
8
+ * 1. Creates a codec for bm25query via gather.hooks.pgCodecs_findPgCodec
9
+ * 2. Discovers all BM25 indexes via gather.hooks.pgIntrospection_introspection
10
+ * by querying pg_index + pg_am + pg_class + pg_attribute
11
+ * 3. Stores discovered BM25 index info in a module-level Map for use by
12
+ * the BM25 adapter during the schema build phase
13
+ */
14
+ import 'graphile-build-pg';
15
+ import type { GraphileConfig } from 'graphile-config';
16
+ /**
17
+ * Represents a discovered BM25 index in the database.
18
+ */
19
+ export interface Bm25IndexInfo {
20
+ /** Schema name (e.g. 'public') */
21
+ schemaName: string;
22
+ /** Table name (e.g. 'documents') */
23
+ tableName: string;
24
+ /** Column name (e.g. 'content') */
25
+ columnName: string;
26
+ /** Index name (e.g. 'docs_idx') — needed for to_bm25query() */
27
+ indexName: string;
28
+ }
29
+ /**
30
+ * Module-level store for discovered BM25 indexes.
31
+ * Populated during the gather phase, read during the schema build phase.
32
+ *
33
+ * Key: "schemaName.tableName.columnName"
34
+ * Value: Bm25IndexInfo
35
+ */
36
+ export declare const bm25IndexStore: Map<string, Bm25IndexInfo>;
37
+ /**
38
+ * Whether pg_textsearch extension was detected in the database.
39
+ */
40
+ export declare let bm25ExtensionDetected: boolean;
41
+ export declare const Bm25CodecPlugin: GraphileConfig.Plugin;
42
+ export declare const Bm25CodecPreset: GraphileConfig.Preset;
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ /**
3
+ * Bm25CodecPlugin
4
+ *
5
+ * Teaches PostGraphile v5 how to handle the pg_textsearch `bm25query` type
6
+ * and discovers all BM25 indexes in the database.
7
+ *
8
+ * This plugin:
9
+ * 1. Creates a codec for bm25query via gather.hooks.pgCodecs_findPgCodec
10
+ * 2. Discovers all BM25 indexes via gather.hooks.pgIntrospection_introspection
11
+ * by querying pg_index + pg_am + pg_class + pg_attribute
12
+ * 3. Stores discovered BM25 index info in a module-level Map for use by
13
+ * the BM25 adapter during the schema build phase
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.Bm25CodecPreset = exports.Bm25CodecPlugin = exports.bm25ExtensionDetected = exports.bm25IndexStore = void 0;
53
+ require("graphile-build-pg");
54
+ const pg_sql2_1 = __importDefault(require("pg-sql2"));
55
+ /**
56
+ * Module-level store for discovered BM25 indexes.
57
+ * Populated during the gather phase, read during the schema build phase.
58
+ *
59
+ * Key: "schemaName.tableName.columnName"
60
+ * Value: Bm25IndexInfo
61
+ */
62
+ exports.bm25IndexStore = new Map();
63
+ /**
64
+ * Whether pg_textsearch extension was detected in the database.
65
+ */
66
+ exports.bm25ExtensionDetected = false;
67
+ /**
68
+ * The SQL query that discovers BM25 indexes in the database.
69
+ * Joins pg_index -> pg_class -> pg_am to find all indexes using the 'bm25'
70
+ * access method, then resolves the schema, table, column, and index names.
71
+ */
72
+ const BM25_DISCOVERY_SQL = `
73
+ SELECT
74
+ n.nspname AS schema_name,
75
+ c.relname AS table_name,
76
+ a.attname AS column_name,
77
+ i.relname AS index_name
78
+ FROM pg_index ix
79
+ JOIN pg_class i ON i.oid = ix.indexrelid
80
+ JOIN pg_am am ON am.oid = i.relam
81
+ JOIN pg_class c ON c.oid = ix.indrelid
82
+ JOIN pg_namespace n ON n.oid = c.relnamespace
83
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(ix.indkey)
84
+ WHERE am.amname = 'bm25'
85
+ `;
86
+ exports.Bm25CodecPlugin = {
87
+ name: 'Bm25CodecPlugin',
88
+ version: '1.0.0',
89
+ description: 'Registers a codec for the pg_textsearch bm25query type and discovers BM25 indexes',
90
+ gather: {
91
+ hooks: {
92
+ /**
93
+ * Register the bm25query codec when detected during type introspection.
94
+ */
95
+ async pgCodecs_findPgCodec(info, event) {
96
+ if (event.pgCodec)
97
+ return;
98
+ const { pgType: type, serviceName } = event;
99
+ if (type.typname !== 'bm25query')
100
+ return;
101
+ const typeNamespace = await info.helpers.pgIntrospection.getNamespace(serviceName, type.typnamespace);
102
+ if (!typeNamespace)
103
+ return;
104
+ const schemaName = typeNamespace.nspname;
105
+ event.pgCodec = {
106
+ name: 'bm25query',
107
+ sqlType: pg_sql2_1.default.identifier(schemaName, 'bm25query'),
108
+ // PG sends bm25query as text
109
+ fromPg(value) {
110
+ return value;
111
+ },
112
+ // string -> bm25query text
113
+ toPg(value) {
114
+ return value;
115
+ },
116
+ attributes: undefined,
117
+ executor: null,
118
+ extensions: {
119
+ oid: type._id,
120
+ pg: { serviceName, schemaName, name: 'bm25query' },
121
+ },
122
+ };
123
+ },
124
+ /**
125
+ * After introspection completes, query for all BM25 indexes.
126
+ * Uses the pgService's adaptorSettings to create a direct pg.Pool
127
+ * connection and runs the BM25 discovery query.
128
+ */
129
+ async pgIntrospection_introspection(info, event) {
130
+ const { serviceName } = event;
131
+ // Get the pgService from the resolved preset
132
+ const pgService = info.resolvedPreset?.pgServices?.find((s) => (s.name ?? 'main') === serviceName);
133
+ if (!pgService)
134
+ return;
135
+ // Clear previous entries for this introspection run
136
+ exports.bm25IndexStore.clear();
137
+ try {
138
+ const adaptorSettings = pgService.adaptorSettings;
139
+ if (!adaptorSettings?.connectionString && !adaptorSettings?.pool) {
140
+ return;
141
+ }
142
+ // Import pg dynamically for the discovery query
143
+ const { Pool } = await Promise.resolve().then(() => __importStar(require('pg')));
144
+ const existingPool = adaptorSettings.pool;
145
+ const pool = existingPool ?? new Pool({
146
+ connectionString: adaptorSettings.connectionString,
147
+ max: 1,
148
+ });
149
+ const isOwnPool = !existingPool;
150
+ try {
151
+ const result = await pool.query(BM25_DISCOVERY_SQL);
152
+ if (result.rows && result.rows.length > 0) {
153
+ exports.bm25ExtensionDetected = true;
154
+ for (const row of result.rows) {
155
+ const key = `${row.schema_name}.${row.table_name}.${row.column_name}`;
156
+ exports.bm25IndexStore.set(key, {
157
+ schemaName: row.schema_name,
158
+ tableName: row.table_name,
159
+ columnName: row.column_name,
160
+ indexName: row.index_name,
161
+ });
162
+ }
163
+ }
164
+ }
165
+ finally {
166
+ if (isOwnPool) {
167
+ await pool.end();
168
+ }
169
+ }
170
+ }
171
+ catch {
172
+ // pg_textsearch not installed or query failed — gracefully skip
173
+ exports.bm25ExtensionDetected = false;
174
+ }
175
+ },
176
+ },
177
+ },
178
+ schema: {
179
+ hooks: {
180
+ init: {
181
+ before: ['PgCodecs'],
182
+ callback(_, build) {
183
+ const { setGraphQLTypeForPgCodec } = build;
184
+ // Map bm25query codec to String for both input and output
185
+ for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
186
+ if (codec.name === 'bm25query') {
187
+ setGraphQLTypeForPgCodec(codec, 'input', build.graphql.GraphQLString.name);
188
+ setGraphQLTypeForPgCodec(codec, 'output', build.graphql.GraphQLString.name);
189
+ }
190
+ }
191
+ return _;
192
+ },
193
+ },
194
+ },
195
+ },
196
+ };
197
+ exports.Bm25CodecPreset = {
198
+ plugins: [exports.Bm25CodecPlugin],
199
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Codec Plugin Exports
3
+ *
4
+ * These plugins teach PostGraphile v5 how to handle custom PostgreSQL types
5
+ * used by the search adapters. They run during the gather phase to discover
6
+ * types and indexes before the schema build phase.
7
+ */
8
+ export { TsvectorCodecPlugin, TsvectorCodecPreset, createTsvectorCodecPlugin, } from './tsvector-codec';
9
+ export type { TsvectorCodecPluginOptions } from './tsvector-codec';
10
+ export { Bm25CodecPlugin, Bm25CodecPreset, bm25IndexStore, bm25ExtensionDetected, } from './bm25-codec';
11
+ export type { Bm25IndexInfo } from './bm25-codec';
12
+ export { VectorCodecPlugin, VectorCodecPreset, } from './vector-codec';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * Codec Plugin Exports
4
+ *
5
+ * These plugins teach PostGraphile v5 how to handle custom PostgreSQL types
6
+ * used by the search adapters. They run during the gather phase to discover
7
+ * types and indexes before the schema build phase.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.VectorCodecPreset = exports.VectorCodecPlugin = exports.bm25ExtensionDetected = exports.bm25IndexStore = exports.Bm25CodecPreset = exports.Bm25CodecPlugin = exports.createTsvectorCodecPlugin = exports.TsvectorCodecPreset = exports.TsvectorCodecPlugin = void 0;
11
+ var tsvector_codec_1 = require("./tsvector-codec");
12
+ Object.defineProperty(exports, "TsvectorCodecPlugin", { enumerable: true, get: function () { return tsvector_codec_1.TsvectorCodecPlugin; } });
13
+ Object.defineProperty(exports, "TsvectorCodecPreset", { enumerable: true, get: function () { return tsvector_codec_1.TsvectorCodecPreset; } });
14
+ Object.defineProperty(exports, "createTsvectorCodecPlugin", { enumerable: true, get: function () { return tsvector_codec_1.createTsvectorCodecPlugin; } });
15
+ var bm25_codec_1 = require("./bm25-codec");
16
+ Object.defineProperty(exports, "Bm25CodecPlugin", { enumerable: true, get: function () { return bm25_codec_1.Bm25CodecPlugin; } });
17
+ Object.defineProperty(exports, "Bm25CodecPreset", { enumerable: true, get: function () { return bm25_codec_1.Bm25CodecPreset; } });
18
+ Object.defineProperty(exports, "bm25IndexStore", { enumerable: true, get: function () { return bm25_codec_1.bm25IndexStore; } });
19
+ Object.defineProperty(exports, "bm25ExtensionDetected", { enumerable: true, get: function () { return bm25_codec_1.bm25ExtensionDetected; } });
20
+ var vector_codec_1 = require("./vector-codec");
21
+ Object.defineProperty(exports, "VectorCodecPlugin", { enumerable: true, get: function () { return vector_codec_1.VectorCodecPlugin; } });
22
+ Object.defineProperty(exports, "VectorCodecPreset", { enumerable: true, get: function () { return vector_codec_1.VectorCodecPreset; } });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Connection Filter Operator Factories for Search
3
+ *
4
+ * These factories register filter operators on the connection filter system
5
+ * for tsvector (matches) and pg_trgm (similarTo, wordSimilarTo).
6
+ *
7
+ * They are used in the ConstructivePreset's connectionFilterOperatorFactories
8
+ * array to wire search operators into the declarative filter system.
9
+ */
10
+ import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
11
+ /**
12
+ * Creates the `matches` filter operator factory for full-text search.
13
+ * Declared here so it's registered via the declarative
14
+ * `connectionFilterOperatorFactories` API.
15
+ */
16
+ export declare function createMatchesOperatorFactory(fullTextScalarName: string, tsConfig: string): ConnectionFilterOperatorFactory;
17
+ /**
18
+ * Creates the `similarTo` and `wordSimilarTo` filter operator factories
19
+ * for pg_trgm fuzzy text matching. Declared here so they're registered
20
+ * via the declarative `connectionFilterOperatorFactories` API.
21
+ */
22
+ export declare function createTrgmOperatorFactories(): ConnectionFilterOperatorFactory;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * Connection Filter Operator Factories for Search
4
+ *
5
+ * These factories register filter operators on the connection filter system
6
+ * for tsvector (matches) and pg_trgm (similarTo, wordSimilarTo).
7
+ *
8
+ * They are used in the ConstructivePreset's connectionFilterOperatorFactories
9
+ * array to wire search operators into the declarative filter system.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createMatchesOperatorFactory = createMatchesOperatorFactory;
13
+ exports.createTrgmOperatorFactories = createTrgmOperatorFactories;
14
+ /**
15
+ * Creates the `matches` filter operator factory for full-text search.
16
+ * Declared here so it's registered via the declarative
17
+ * `connectionFilterOperatorFactories` API.
18
+ */
19
+ function createMatchesOperatorFactory(fullTextScalarName, tsConfig) {
20
+ return (build) => {
21
+ const { sql, graphql: { GraphQLString } } = build;
22
+ const TYPES = build.dataplanPg?.TYPES;
23
+ return [{
24
+ typeNames: fullTextScalarName,
25
+ operatorName: 'matches',
26
+ spec: {
27
+ description: 'Performs a full text search on the field.',
28
+ resolveType: () => GraphQLString,
29
+ resolveInputCodec: TYPES ? () => TYPES.text : undefined,
30
+ resolve(sqlIdentifier, sqlValue, _input, _$where, _details) {
31
+ return sql `${sqlIdentifier} @@ websearch_to_tsquery(${sql.literal(tsConfig)}, ${sqlValue})`;
32
+ },
33
+ },
34
+ }];
35
+ };
36
+ }
37
+ /**
38
+ * Creates the `similarTo` and `wordSimilarTo` filter operator factories
39
+ * for pg_trgm fuzzy text matching. Declared here so they're registered
40
+ * via the declarative `connectionFilterOperatorFactories` API.
41
+ */
42
+ function createTrgmOperatorFactories() {
43
+ return (build) => {
44
+ const { sql } = build;
45
+ return [
46
+ {
47
+ typeNames: 'String',
48
+ operatorName: 'similarTo',
49
+ spec: {
50
+ description: 'Fuzzy matches using pg_trgm trigram similarity. Tolerates typos and misspellings.',
51
+ resolveType: () => build.getTypeByName('TrgmSearchInput'),
52
+ resolve(sqlIdentifier, _sqlValue, input, _$where, _details) {
53
+ if (input == null)
54
+ return null;
55
+ const { value, threshold } = input;
56
+ if (!value || typeof value !== 'string' || value.trim().length === 0) {
57
+ return null;
58
+ }
59
+ const th = threshold != null ? threshold : 0.3;
60
+ return sql `similarity(${sqlIdentifier}, ${sql.value(value)}) > ${sql.value(th)}`;
61
+ },
62
+ },
63
+ },
64
+ {
65
+ typeNames: 'String',
66
+ operatorName: 'wordSimilarTo',
67
+ spec: {
68
+ description: 'Fuzzy matches using pg_trgm word_similarity. Finds the best matching substring within the column value.',
69
+ resolveType: () => build.getTypeByName('TrgmSearchInput'),
70
+ resolve(sqlIdentifier, _sqlValue, input, _$where, _details) {
71
+ if (input == null)
72
+ return null;
73
+ const { value, threshold } = input;
74
+ if (!value || typeof value !== 'string' || value.trim().length === 0) {
75
+ return null;
76
+ }
77
+ const th = threshold != null ? threshold : 0.3;
78
+ return sql `word_similarity(${sql.value(value)}, ${sqlIdentifier}) > ${sql.value(th)}`;
79
+ },
80
+ },
81
+ },
82
+ ];
83
+ };
84
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TsvectorCodecPlugin
3
+ *
4
+ * Teaches PostGraphile v5 how to handle PostgreSQL's tsvector and tsquery types.
5
+ * Without this, tsvector columns are invisible to the schema builder and the
6
+ * search plugin cannot generate condition fields.
7
+ *
8
+ * This plugin:
9
+ * 1. Creates codecs for tsvector/tsquery via gather.hooks.pgCodecs_findPgCodec
10
+ * 2. Registers a custom "FullText" scalar type for tsvector columns
11
+ * 3. Maps tsvector codec to the FullText scalar (isolating filter operators)
12
+ * 4. Maps tsquery codec to GraphQL String
13
+ * 5. Optionally hides tsvector columns from output types
14
+ */
15
+ import type { GraphileConfig } from 'graphile-config';
16
+ /**
17
+ * Options for the TsvectorCodecPlugin.
18
+ */
19
+ export interface TsvectorCodecPluginOptions {
20
+ /**
21
+ * Prefix for tsvector condition fields.
22
+ * @default 'tsv'
23
+ */
24
+ pgSearchPrefix?: string;
25
+ /**
26
+ * Whether to hide tsvector columns from output types.
27
+ * @default false
28
+ */
29
+ hideTsvectorColumns?: boolean;
30
+ /**
31
+ * Name of the custom GraphQL scalar for tsvector columns.
32
+ * @default 'FullText'
33
+ */
34
+ fullTextScalarName?: string;
35
+ /**
36
+ * PostgreSQL text search configuration used with `websearch_to_tsquery`.
37
+ * @default 'english'
38
+ */
39
+ tsConfig?: string;
40
+ }
41
+ /**
42
+ * Creates a TsvectorCodecPlugin with the given options.
43
+ *
44
+ * @param options - Plugin configuration
45
+ * @returns GraphileConfig.Plugin
46
+ */
47
+ export declare function createTsvectorCodecPlugin(options?: TsvectorCodecPluginOptions): GraphileConfig.Plugin;
48
+ /**
49
+ * Default static instance using default options.
50
+ * Maps tsvector to the "FullText" scalar.
51
+ */
52
+ export declare const TsvectorCodecPlugin: GraphileConfig.Plugin;
53
+ export declare const TsvectorCodecPreset: GraphileConfig.Preset;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ /**
3
+ * TsvectorCodecPlugin
4
+ *
5
+ * Teaches PostGraphile v5 how to handle PostgreSQL's tsvector and tsquery types.
6
+ * Without this, tsvector columns are invisible to the schema builder and the
7
+ * search plugin cannot generate condition fields.
8
+ *
9
+ * This plugin:
10
+ * 1. Creates codecs for tsvector/tsquery via gather.hooks.pgCodecs_findPgCodec
11
+ * 2. Registers a custom "FullText" scalar type for tsvector columns
12
+ * 3. Maps tsvector codec to the FullText scalar (isolating filter operators)
13
+ * 4. Maps tsquery codec to GraphQL String
14
+ * 5. Optionally hides tsvector columns from output types
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.TsvectorCodecPreset = exports.TsvectorCodecPlugin = void 0;
21
+ exports.createTsvectorCodecPlugin = createTsvectorCodecPlugin;
22
+ const graphql_1 = require("graphql");
23
+ const pg_sql2_1 = __importDefault(require("pg-sql2"));
24
+ /**
25
+ * Creates a TsvectorCodecPlugin with the given options.
26
+ *
27
+ * @param options - Plugin configuration
28
+ * @returns GraphileConfig.Plugin
29
+ */
30
+ function createTsvectorCodecPlugin(options = {}) {
31
+ const { fullTextScalarName = 'FullText', hideTsvectorColumns = false, } = options;
32
+ return {
33
+ name: 'TsvectorCodecPlugin',
34
+ version: '1.0.0',
35
+ gather: {
36
+ hooks: {
37
+ async pgCodecs_findPgCodec(info, event) {
38
+ if (event.pgCodec) {
39
+ return;
40
+ }
41
+ const { pgType: type, serviceName } = event;
42
+ const pgCatalog = await info.helpers.pgIntrospection.getNamespaceByName(serviceName, 'pg_catalog');
43
+ if (!pgCatalog) {
44
+ return;
45
+ }
46
+ if (type.typnamespace === pgCatalog._id && type.typname === 'tsvector') {
47
+ event.pgCodec = {
48
+ name: 'tsvector',
49
+ sqlType: pg_sql2_1.default.identifier('pg_catalog', 'tsvector'),
50
+ fromPg: (value) => value,
51
+ toPg: (value) => value,
52
+ attributes: undefined,
53
+ executor: null,
54
+ extensions: {
55
+ oid: type._id,
56
+ pg: {
57
+ serviceName,
58
+ schemaName: 'pg_catalog',
59
+ name: 'tsvector',
60
+ },
61
+ },
62
+ };
63
+ return;
64
+ }
65
+ if (type.typnamespace === pgCatalog._id && type.typname === 'tsquery') {
66
+ event.pgCodec = {
67
+ name: 'tsquery',
68
+ sqlType: pg_sql2_1.default.identifier('pg_catalog', 'tsquery'),
69
+ fromPg: (value) => value,
70
+ toPg: (value) => value,
71
+ attributes: undefined,
72
+ executor: null,
73
+ extensions: {
74
+ oid: type._id,
75
+ pg: {
76
+ serviceName,
77
+ schemaName: 'pg_catalog',
78
+ name: 'tsquery',
79
+ },
80
+ },
81
+ };
82
+ return;
83
+ }
84
+ },
85
+ },
86
+ },
87
+ schema: {
88
+ hooks: {
89
+ // Must run before PgCodecsPlugin's init (to avoid "unknown codec" warning)
90
+ // and before PgConnectionArgFilterPlugin's init (which creates filter
91
+ // types like FullTextFilter based on codec→GraphQL type mappings).
92
+ init: {
93
+ before: ['PgCodecs', 'PgConnectionArgFilterPlugin'],
94
+ callback(_, build) {
95
+ const { setGraphQLTypeForPgCodec } = build;
96
+ // Register a custom scalar type for tsvector columns.
97
+ // This ensures filter operators like `matches` only appear on
98
+ // tsvector filters, not on all String filters.
99
+ build.registerScalarType(fullTextScalarName, {}, () => ({
100
+ description: 'A full-text search tsvector value represented as a string.',
101
+ serialize(value) {
102
+ return String(value);
103
+ },
104
+ parseValue(value) {
105
+ if (typeof value === 'string') {
106
+ return value;
107
+ }
108
+ throw new Error(`${fullTextScalarName} must be a string`);
109
+ },
110
+ parseLiteral(lit) {
111
+ if (lit.kind === 'NullValue')
112
+ return null;
113
+ if (lit.kind !== 'StringValue') {
114
+ throw new Error(`${fullTextScalarName} must be a string`);
115
+ }
116
+ return lit.value;
117
+ },
118
+ }), `TsvectorCodecPlugin registering ${fullTextScalarName} scalar`);
119
+ for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
120
+ if (codec.name === 'tsvector') {
121
+ setGraphQLTypeForPgCodec(codec, 'input', fullTextScalarName);
122
+ setGraphQLTypeForPgCodec(codec, 'output', fullTextScalarName);
123
+ }
124
+ else if (codec.name === 'tsquery') {
125
+ setGraphQLTypeForPgCodec(codec, 'input', graphql_1.GraphQLString.name);
126
+ setGraphQLTypeForPgCodec(codec, 'output', graphql_1.GraphQLString.name);
127
+ }
128
+ }
129
+ return _;
130
+ },
131
+ },
132
+ },
133
+ ...(hideTsvectorColumns
134
+ ? {
135
+ entityBehavior: {
136
+ pgCodecAttribute: {
137
+ inferred: {
138
+ after: ['postInferred'],
139
+ provides: ['hideTsvectorColumns'],
140
+ callback(behavior, [codec, attributeName]) {
141
+ const attr = codec.attributes?.[attributeName];
142
+ if (attr?.codec?.name === 'tsvector') {
143
+ return [behavior, '-select'];
144
+ }
145
+ return behavior;
146
+ },
147
+ },
148
+ },
149
+ },
150
+ }
151
+ : {}),
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * Default static instance using default options.
157
+ * Maps tsvector to the "FullText" scalar.
158
+ */
159
+ exports.TsvectorCodecPlugin = createTsvectorCodecPlugin();
160
+ exports.TsvectorCodecPreset = {
161
+ plugins: [exports.TsvectorCodecPlugin],
162
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * VectorCodecPlugin
3
+ *
4
+ * Teaches PostGraphile v5 how to handle the pgvector `vector` type.
5
+ *
6
+ * Without this:
7
+ * - `vector(n)` columns are silently invisible in the schema
8
+ * - SQL functions with `vector` args are skipped entirely
9
+ *
10
+ * Wire format: PostgreSQL sends vector as text `[0.1,0.2,...,0.768]`
11
+ * JavaScript: number[]
12
+ * GraphQL: `Vector` scalar (serialized as [Float])
13
+ */
14
+ import 'graphile-build-pg';
15
+ import 'graphile-build';
16
+ import type { GraphileConfig } from 'graphile-config';
17
+ export declare const VectorCodecPlugin: GraphileConfig.Plugin;
18
+ export declare const VectorCodecPreset: GraphileConfig.Preset;