|
| 1 | +# Graphile Plugin Architecture |
| 2 | + |
| 3 | +This document describes the PostGraphile v5 plugin ecosystem in the Constructive monorepo. All plugins are v5-native, written in TypeScript, and compose via Graphile's preset system. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The `graphile/` directory contains all PostGraphile v5 plugins, organized as individual packages in the pnpm workspace. The central entry point is `graphile-settings`, which exports `ConstructivePreset` — a single preset that wires everything together. |
| 8 | + |
| 9 | +``` |
| 10 | +ConstructivePreset |
| 11 | +├── MinimalPreset (no Node/Relay — keeps id as id) |
| 12 | +├── InflektPreset (custom inflection via inflekt library) |
| 13 | +├── ConflictDetectorPreset (warns about naming conflicts between schemas) |
| 14 | +├── InflectorLoggerPreset (debug logging for inflector calls) |
| 15 | +├── NoUniqueLookupPreset (primary key only lookups) |
| 16 | +├── ConnectionFilterPreset (v5-native connection filter with relation filters) |
| 17 | +├── EnableAllFilterColumnsPreset (allow filtering on all columns) |
| 18 | +├── ManyToManyOptInPreset (many-to-many via @behavior +manyToMany) |
| 19 | +├── MetaSchemaPreset (_meta query for schema introspection) |
| 20 | +├── UnifiedSearchPreset (tsvector + BM25 + pg_trgm + pgvector) |
| 21 | +├── GraphilePostgisPreset (PostGIS types + spatial filter operators) |
| 22 | +├── UploadPreset (S3/MinIO file uploads) |
| 23 | +├── SqlExpressionValidatorPreset (validates @sqlExpression columns) |
| 24 | +└── PgTypeMappingsPreset (custom type → GraphQL scalar mappings) |
| 25 | +``` |
| 26 | + |
| 27 | +## Active Packages |
| 28 | + |
| 29 | +### graphile-settings |
| 30 | + |
| 31 | +**The main configuration package.** Exports `ConstructivePreset` and all sub-presets. This is the only package most consumers need to import. |
| 32 | + |
| 33 | +- Combines all plugins into a single composable preset |
| 34 | +- Disables `condition` argument (all filtering via `filter`/`where`) |
| 35 | +- Configures connection filter options (logical operators, arrays, computed columns) |
| 36 | +- Aggregates satellite plugin operator factories |
| 37 | + |
| 38 | +```typescript |
| 39 | +import { ConstructivePreset, makePgService } from 'graphile-settings'; |
| 40 | +``` |
| 41 | + |
| 42 | +### graphile-connection-filter |
| 43 | + |
| 44 | +**V5-native connection filter plugin.** Adds the `where` argument to connections with per-table filter types (e.g. `UserFilter`) and per-scalar operator types (e.g. `StringFilter`, `IntFilter`). |
| 45 | + |
| 46 | +Key features: |
| 47 | +- Standard operators: `equalTo`, `notEqualTo`, `isNull`, `in`, `notIn`, `lessThan`, `greaterThan` |
| 48 | +- Pattern matching: `includes`, `startsWith`, `endsWith`, `like` + case-insensitive variants |
| 49 | +- Type-specific operators: JSONB, hstore, inet, array, range |
| 50 | +- Logical operators: `and`, `or`, `not` |
| 51 | +- Relation filters: filter by related table fields (forward and backward) |
| 52 | +- **Declarative operator API**: satellite plugins register custom operators via `connectionFilterOperatorFactories` |
| 53 | + |
| 54 | +```typescript |
| 55 | +import { ConnectionFilterPreset } from 'graphile-connection-filter'; |
| 56 | +``` |
| 57 | + |
| 58 | +### graphile-search |
| 59 | + |
| 60 | +**Unified search plugin with adapter pattern.** Replaces the previous separate plugins (`graphile-tsvector`, `graphile-bm25`, `graphile-trgm`, `graphile-pgvector`) with a single plugin that supports all four search algorithms. |
| 61 | + |
| 62 | +Each algorithm is a ~50-line adapter implementing the `SearchAdapter` interface: |
| 63 | +- **tsvector** — PostgreSQL full-text search via `ts_rank` |
| 64 | +- **BM25** — `pg_textsearch` extension scoring |
| 65 | +- **pg_trgm** — Trigram fuzzy matching via `similarity()` |
| 66 | +- **pgvector** — Vector similarity search (cosine, L2, inner product) |
| 67 | + |
| 68 | +Generated GraphQL fields per adapter: |
| 69 | +- **Score fields**: `{column}{Algorithm}{Metric}` (e.g. `bodyBm25Score`, `titleTrgmSimilarity`) |
| 70 | +- **Composite score**: `searchScore` — normalized 0..1 aggregating all active search signals |
| 71 | +- **OrderBy enums**: `{COLUMN}_{ALGORITHM}_{METRIC}_ASC/DESC` + `SEARCH_SCORE_ASC/DESC` |
| 72 | +- **Filter fields**: `{algorithm}{Column}` on connection filter input types (e.g. `fullTextBody`, `trgmTitle`) |
| 73 | + |
| 74 | +Zero config — auto-discovers columns and indexes per adapter. |
| 75 | + |
| 76 | +```typescript |
| 77 | +import { UnifiedSearchPreset } from 'graphile-search'; |
| 78 | +``` |
| 79 | + |
| 80 | +### graphile-postgis |
| 81 | + |
| 82 | +**PostGIS support.** Generates GraphQL types for geometry/geography columns including GeoJSON scalar types, dimension-aware interfaces, and spatial filter operators. |
| 83 | + |
| 84 | +Features: |
| 85 | +- GeoJSON scalar type for input/output |
| 86 | +- Concrete types for all geometry subtypes (Point, LineString, Polygon, etc.) |
| 87 | +- Geography-aware field naming (longitude/latitude instead of x/y) |
| 88 | +- Spatial filter operators via `createPostgisOperatorFactory()` |
| 89 | +- Graceful degradation when PostGIS is not installed |
| 90 | + |
| 91 | +```typescript |
| 92 | +import { GraphilePostgisPreset, createPostgisOperatorFactory } from 'graphile-postgis'; |
| 93 | +``` |
| 94 | + |
| 95 | +### graphile-misc-plugins |
| 96 | + |
| 97 | +**Collection of smaller plugins** that don't warrant their own package: |
| 98 | + |
| 99 | +| Plugin/Preset | Description | |
| 100 | +|---|---| |
| 101 | +| `MinimalPreset` | PostGraphile without Node/Relay features | |
| 102 | +| `InflektPreset` | Custom inflection using inflekt library | |
| 103 | +| `ConflictDetectorPreset` | Warns about naming conflicts between schemas | |
| 104 | +| `InflectorLoggerPreset` | Debug logging (enable with `INFLECTOR_LOG=1`) | |
| 105 | +| `EnableAllFilterColumnsPreset` | Allow filtering on all columns (not just indexed) | |
| 106 | +| `ManyToManyOptInPreset` | Many-to-many via `@behavior +manyToMany` smart tag | |
| 107 | +| `NoUniqueLookupPreset` | Disable non-primary-key unique lookups | |
| 108 | +| `MetaSchemaPreset` | `_meta` query for database schema introspection | |
| 109 | +| `PgTypeMappingsPreset` | Custom PostgreSQL type → GraphQL scalar mappings | |
| 110 | + |
| 111 | +### graphile-cache |
| 112 | + |
| 113 | +**PostGraphile instance LRU cache** with automatic cleanup when PostgreSQL pools are disposed. Integrates with `pg-cache` for pool management. |
| 114 | + |
| 115 | +```typescript |
| 116 | +import { graphileCache } from 'graphile-cache'; |
| 117 | +``` |
| 118 | + |
| 119 | +### graphile-schema |
| 120 | + |
| 121 | +**Lightweight GraphQL SDL builder.** Build schemas directly from a database or fetch from a running endpoint — no server dependencies. |
| 122 | + |
| 123 | +```typescript |
| 124 | +import { buildSchemaSDL } from 'graphile-schema'; |
| 125 | +import { fetchEndpointSchemaSDL } from 'graphile-schema'; |
| 126 | +``` |
| 127 | + |
| 128 | +### graphile-query |
| 129 | + |
| 130 | +**GraphQL query execution utilities.** Supports `pgSettings`, role-based access control, and custom request context. |
| 131 | + |
| 132 | +- `GraphileQuery` — Full-featured with roles, settings, and request context |
| 133 | +- `GraphileQuerySimple` — Minimal wrapper for simple execution |
| 134 | + |
| 135 | +```typescript |
| 136 | +import { GraphileQuery } from 'graphile-query'; |
| 137 | +``` |
| 138 | + |
| 139 | +### graphile-test |
| 140 | + |
| 141 | +**Testing utilities for PostGraphile.** Builds on `pgsql-test` to provide isolated, seeded, role-aware test databases with GraphQL helpers. |
| 142 | + |
| 143 | +- Per-test rollback via savepoints |
| 144 | +- RLS-aware context injection (`setContext`) |
| 145 | +- GraphQL `query()` function with snapshot support |
| 146 | +- Seed support for SQL, JSON, CSV, Constructive, or Sqitch |
| 147 | + |
| 148 | +For batteries-included testing with all Constructive plugins, use `@constructive-io/graphql-test` instead. |
| 149 | + |
| 150 | +```typescript |
| 151 | +import { getConnections, seed } from 'graphile-test'; |
| 152 | +``` |
| 153 | + |
| 154 | +### graphile-sql-expression-validator |
| 155 | + |
| 156 | +**SQL expression validation** for PostGraphile v5. Validates SQL expressions against a configurable allowlist of functions and schemas. |
| 157 | + |
| 158 | +```typescript |
| 159 | +import { SqlExpressionValidatorPreset } from 'graphile-sql-expression-validator'; |
| 160 | +``` |
| 161 | + |
| 162 | +### graphile-upload-plugin |
| 163 | + |
| 164 | +**File upload support** for PostGraphile v5. Handles S3/MinIO uploads for image, upload, and attachment domain columns. |
| 165 | + |
| 166 | +```typescript |
| 167 | +import { UploadPreset } from 'graphile-upload-plugin'; |
| 168 | +``` |
| 169 | + |
| 170 | +## Legacy Directories (Not Source Code) |
| 171 | + |
| 172 | +The following directories contain npm-installed upstream packages from the v4 era. They have no `package.json` or `src/` — only `dist/` and `node_modules/`. They are consumed as dependencies but not maintained as source: |
| 173 | + |
| 174 | +| Directory | Status | |
| 175 | +|---|---| |
| 176 | +| `graphile-i18n` | Upstream v4 package | |
| 177 | +| `graphile-many-to-many` | Upstream `@graphile-contrib/pg-many-to-many` | |
| 178 | +| `graphile-meta-schema` | Folded into `graphile-misc-plugins` as `MetaSchemaPreset` | |
| 179 | +| `graphile-pg-type-mappings` | Folded into `graphile-misc-plugins` as `PgTypeMappingsPreset` | |
| 180 | +| `graphile-plugin-connection-filter` | Replaced by v5-native `graphile-connection-filter` | |
| 181 | +| `graphile-plugin-fulltext-filter` | Replaced by `graphile-search` unified plugin | |
| 182 | +| `graphile-simple-inflector` | Replaced by `InflektPreset` in `graphile-misc-plugins` | |
| 183 | + |
| 184 | +## How Satellite Plugins Register Filter Operators |
| 185 | + |
| 186 | +Satellite plugins (search, PostGIS, trgm) register custom filter operators via the **declarative operator factory API** on `graphile-connection-filter`. Each factory is a function that receives the Graphile `build` object and returns operator registrations: |
| 187 | + |
| 188 | +```typescript |
| 189 | +// In constructive-preset.ts |
| 190 | +schema: { |
| 191 | + connectionFilterOperatorFactories: [ |
| 192 | + createMatchesOperatorFactory('FullText', 'english'), // tsvector matches |
| 193 | + createTrgmOperatorFactories(), // similarTo, wordSimilarTo |
| 194 | + createPostgisOperatorFactory(), // spatial operators |
| 195 | + ], |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +This ensures `graphile-config`'s array replacement behavior is handled correctly — all factories are collected at the top-level preset. |
| 200 | + |
| 201 | +## Key Design Decisions |
| 202 | + |
| 203 | +1. **`condition` is disabled.** All filtering lives under `where` (the v5-native filter argument). `PgConditionArgumentPlugin` and `PgConditionCustomFieldsPlugin` are explicitly disabled in the preset. |
| 204 | + |
| 205 | +2. **All columns are filterable.** `EnableAllFilterColumnsPreset` overrides PostGraphile's default of only filtering indexed columns. Index optimization is left to DBAs. |
| 206 | + |
| 207 | +3. **Many-to-many is opt-in.** No many-to-many fields are generated unless the junction table has `@behavior +manyToMany` smart tag. This prevents API bloat. |
| 208 | + |
| 209 | +4. **Relation filters are enabled.** `connectionFilterRelations: true` allows filtering across foreign key relationships (forward and backward). |
| 210 | + |
| 211 | +5. **Search is unified.** One plugin (`graphile-search`) handles all four search algorithms via adapters instead of four separate plugins. |
| 212 | + |
| 213 | +6. **Computed fields are excluded from codegen defaults.** The codegen's `getSelectableScalarFields()` helper uses the TypeRegistry to filter out plugin-added computed fields (search scores, hash UUIDs) from default CLI select objects. |
| 214 | + |
| 215 | +## Testing |
| 216 | + |
| 217 | +| Test Suite | Command | What It Covers | |
| 218 | +|---|---|---| |
| 219 | +| `graphile-connection-filter` | `pnpm --filter graphile-connection-filter test` | 51 filter operator tests | |
| 220 | +| `graphile-search` | `pnpm --filter graphile-search test` | Unified search adapter tests | |
| 221 | +| `graphile-settings` | `pnpm --filter graphile-settings test` | Preset integration + metadata tests | |
| 222 | +| `graphile-test` | `pnpm --filter graphile-test test` | Test framework self-tests | |
| 223 | +| `graphql/test` | `pnpm --filter @constructive-io/graphql-test test` | Full ConstructivePreset integration (84 tests) | |
| 224 | +| `graphql/server-test` | `pnpm --filter @constructive-io/graphql-server-test test` | Server-level integration + search mega queries | |
| 225 | + |
| 226 | +All tests run in CI against `constructiveio/postgres-plus:18` (includes PostGIS, pg_trgm, pgvector, pg_textsearch). |
0 commit comments