Skip to content

Commit 7e9b14b

Browse files
authored
Merge pull request #817 from constructive-io/devin/1773536065-graphile-docs
docs: update GRAPHILE.md with plugin architecture and current state
2 parents 180ba9e + fb4fe0e commit 7e9b14b

1 file changed

Lines changed: 241 additions & 25 deletions

File tree

GRAPHILE.md

Lines changed: 241 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,230 @@
1-
# Graphile v5 RC Dependency Management
1+
# Graphile v5 — Plugin Architecture & Dependency Management
22

3-
## Overview
3+
## Plugin Architecture
44

5-
This monorepo contains several packages that depend on the [Graphile](https://graphile.org/) v5 release candidate (RC) ecosystem. The Graphile v5 RC packages are **tightly coupled** -- all packages in the ecosystem must be at matching RC versions to work correctly.
5+
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.
66

7-
## The Problem (Why We Pin)
7+
```
8+
ConstructivePreset
9+
├── MinimalPreset (no Node/Relay — keeps id as id)
10+
├── InflektPreset (custom inflection via inflekt library)
11+
├── ConflictDetectorPreset (warns about naming conflicts between schemas)
12+
├── InflectorLoggerPreset (debug logging for inflector calls)
13+
├── NoUniqueLookupPreset (primary key only lookups)
14+
├── ConnectionFilterPreset (v5-native connection filter with relation filters)
15+
├── EnableAllFilterColumnsPreset (allow filtering on all columns)
16+
├── ManyToManyOptInPreset (many-to-many via @behavior +manyToMany)
17+
├── MetaSchemaPreset (_meta query for schema introspection)
18+
├── UnifiedSearchPreset (tsvector + BM25 + pg_trgm + pgvector)
19+
├── GraphilePostgisPreset (PostGIS types + spatial filter operators)
20+
├── UploadPreset (S3/MinIO file uploads)
21+
├── SqlExpressionValidatorPreset (validates @sqlExpression columns)
22+
└── PgTypeMappingsPreset (custom type → GraphQL scalar mappings)
23+
```
824

9-
Graphile v5 RC packages declare peer dependencies on each other. When a new RC is released, the minimum required versions of those peer deps often bump together. For example, `graphile-build-pg@5.0.0-rc.5` requires `@dataplan/pg@^1.0.0-rc.5` and `tamedevil@^0.1.0-rc.4`, whereas the earlier `rc.3` only required `rc.3` versions of those peers.
25+
## Active Packages
26+
27+
### graphile-settings
28+
29+
**The main configuration package.** Exports `ConstructivePreset` and all sub-presets. This is the only package most consumers need to import.
30+
31+
- Combines all plugins into a single composable preset
32+
- Disables `condition` argument (all filtering via `where`)
33+
- Configures connection filter options (logical operators, arrays, computed columns)
34+
- Aggregates satellite plugin operator factories
35+
36+
```typescript
37+
import { ConstructivePreset } from 'graphile-settings/presets';
38+
```
39+
40+
### graphile-connection-filter
41+
42+
**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`).
43+
44+
Key features:
45+
- Standard operators: `equalTo`, `notEqualTo`, `isNull`, `in`, `notIn`, `lessThan`, `greaterThan`
46+
- Pattern matching: `includes`, `startsWith`, `endsWith`, `like` + case-insensitive variants
47+
- Type-specific operators: JSONB, hstore, inet, array, range
48+
- Logical operators: `and`, `or`, `not`
49+
- Relation filters: filter by related table fields (forward and backward)
50+
- **Declarative operator API**: satellite plugins register custom operators via `connectionFilterOperatorFactories`
51+
52+
```typescript
53+
import { ConnectionFilterPreset } from 'graphile-connection-filter';
54+
```
55+
56+
### graphile-search
57+
58+
**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.
59+
60+
Each algorithm is a ~50-line adapter implementing the `SearchAdapter` interface:
61+
- **tsvector** — PostgreSQL full-text search via `ts_rank`
62+
- **BM25**`pg_textsearch` extension scoring
63+
- **pg_trgm** — Trigram fuzzy matching via `similarity()`
64+
- **pgvector** — Vector similarity search (cosine, L2, inner product)
65+
66+
Generated GraphQL fields per adapter:
67+
- **Score fields**: `{column}{Algorithm}{Metric}` (e.g. `bodyBm25Score`, `titleTrgmSimilarity`)
68+
- **Composite score**: `searchScore` — normalized 0..1 aggregating all active search signals
69+
- **OrderBy enums**: `{COLUMN}_{ALGORITHM}_{METRIC}_ASC/DESC` + `SEARCH_SCORE_ASC/DESC`
70+
- **Filter fields**: `{algorithm}{Column}` on connection filter input types (e.g. `fullTextBody`, `trgmTitle`)
71+
72+
Zero config — auto-discovers columns and indexes per adapter.
73+
74+
```typescript
75+
import { UnifiedSearchPreset } from 'graphile-search';
76+
```
77+
78+
### graphile-postgis
79+
80+
**PostGIS support.** Generates GraphQL types for geometry/geography columns including GeoJSON scalar types, dimension-aware interfaces, and spatial filter operators.
81+
82+
Features:
83+
- GeoJSON scalar type for input/output
84+
- Concrete types for all geometry subtypes (Point, LineString, Polygon, etc.)
85+
- Geography-aware field naming (longitude/latitude instead of x/y)
86+
- Spatial filter operators via `createPostgisOperatorFactory()`
87+
- Graceful degradation when PostGIS is not installed
88+
89+
```typescript
90+
import { GraphilePostgisPreset, createPostgisOperatorFactory } from 'graphile-postgis';
91+
```
92+
93+
### graphile-misc-plugins
94+
95+
**Collection of smaller plugins** that don't warrant their own package:
96+
97+
| Plugin/Preset | Description |
98+
|---|---|
99+
| `MinimalPreset` | PostGraphile without Node/Relay features |
100+
| `InflektPreset` | Custom inflection using inflekt library |
101+
| `ConflictDetectorPreset` | Warns about naming conflicts between schemas |
102+
| `InflectorLoggerPreset` | Debug logging (enable with `INFLECTOR_LOG=1`) |
103+
| `EnableAllFilterColumnsPreset` | Allow filtering on all columns (not just indexed) |
104+
| `ManyToManyOptInPreset` | Many-to-many via `@behavior +manyToMany` smart tag |
105+
| `NoUniqueLookupPreset` | Disable non-primary-key unique lookups |
106+
| `MetaSchemaPreset` | `_meta` query for database schema introspection |
107+
| `PgTypeMappingsPreset` | Custom PostgreSQL type → GraphQL scalar mappings |
108+
109+
### graphile-cache
10110

11-
If our packages use loose caret ranges (e.g., `^5.0.0-rc.3`), the package manager may resolve to the **latest** RC (e.g., `rc.5` or `rc.7`), which introduces new peer dependency requirements that nothing in our tree satisfies. This causes cascading "missing peer dependency" warnings like:
111+
**PostGraphile instance LRU cache** with automatic cleanup when PostgreSQL pools are disposed. Integrates with `pg-cache` for pool management.
12112

113+
```typescript
114+
import { graphileCache } from 'graphile-cache';
13115
```
14-
graphile-build-pg 5.0.0-rc.5
15-
- missing peer @dataplan/pg@^1.0.0-rc.5
16-
- missing peer tamedevil@^0.1.0-rc.4
17-
postgraphile 5.0.0-rc.7
18-
- missing peer @dataplan/pg@^1.0.0-rc.5
19-
- missing peer @dataplan/json@^1.0.0-rc.5
20-
- missing peer grafserv@^1.0.0-rc.6
21-
- missing peer tamedevil@^0.1.0-rc.4
116+
117+
### graphile-schema
118+
119+
**Lightweight GraphQL SDL builder.** Build schemas directly from a database or fetch from a running endpoint — no server dependencies.
120+
121+
```typescript
122+
import { buildSchemaSDL } from 'graphile-schema';
123+
import { fetchEndpointSchemaSDL } from 'graphile-schema';
124+
```
125+
126+
### graphile-query
127+
128+
**GraphQL query execution utilities.** Supports `pgSettings`, role-based access control, and custom request context.
129+
130+
- `GraphileQuery` — Full-featured with roles, settings, and request context
131+
- `GraphileQuerySimple` — Minimal wrapper for simple execution
132+
133+
```typescript
134+
import { GraphileQuery } from 'graphile-query';
22135
```
23136

24-
## Our Approach: Pinned Exact Versions
137+
### graphile-test
138+
139+
**Testing utilities for PostGraphile.** Builds on `pgsql-test` to provide isolated, seeded, role-aware test databases with GraphQL helpers.
140+
141+
- Per-test rollback via savepoints
142+
- RLS-aware context injection (`setContext`)
143+
- GraphQL `query()` function with snapshot support
144+
- Seed support for SQL, JSON, CSV, Constructive, or Sqitch
145+
146+
For batteries-included testing with all Constructive plugins, use `@constructive-io/graphql-test` instead.
147+
148+
```typescript
149+
import { getConnections, seed } from 'graphile-test';
150+
```
151+
152+
### graphile-sql-expression-validator
153+
154+
**SQL expression validation** for PostGraphile v5. Validates SQL expressions against a configurable allowlist of functions and schemas.
155+
156+
```typescript
157+
import { SqlExpressionValidatorPreset } from 'graphile-sql-expression-validator';
158+
```
159+
160+
### graphile-upload-plugin
161+
162+
**File upload support** for PostGraphile v5. Handles S3/MinIO uploads for image, upload, and attachment domain columns.
163+
164+
```typescript
165+
import { UploadPreset } from 'graphile-upload-plugin';
166+
```
167+
168+
## Legacy Directories (Not Source Code)
169+
170+
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:
171+
172+
| Directory | Status |
173+
|---|---|
174+
| `graphile-i18n` | Upstream v4 package |
175+
| `graphile-many-to-many` | Upstream `@graphile-contrib/pg-many-to-many` |
176+
| `graphile-meta-schema` | Folded into `graphile-misc-plugins` as `MetaSchemaPreset` |
177+
| `graphile-pg-type-mappings` | Folded into `graphile-misc-plugins` as `PgTypeMappingsPreset` |
178+
| `graphile-plugin-connection-filter` | Replaced by v5-native `graphile-connection-filter` |
179+
| `graphile-plugin-fulltext-filter` | Replaced by `graphile-search` unified plugin |
180+
| `graphile-simple-inflector` | Replaced by `InflektPreset` in `graphile-misc-plugins` |
181+
182+
## How Satellite Plugins Register Filter Operators
183+
184+
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:
185+
186+
```typescript
187+
// In constructive-preset.ts
188+
schema: {
189+
connectionFilterOperatorFactories: [
190+
createMatchesOperatorFactory('FullText', 'english'), // tsvector matches
191+
createTrgmOperatorFactories(), // similarTo, wordSimilarTo
192+
createPostgisOperatorFactory(), // spatial operators
193+
],
194+
}
195+
```
196+
197+
This ensures `graphile-config`'s array replacement behavior is handled correctly — all factories are collected at the top-level preset.
198+
199+
## Key Design Decisions
200+
201+
1. **`condition` is disabled.** All filtering lives under `where` (the v5-native filter argument). `PgConditionArgumentPlugin` and `PgConditionCustomFieldsPlugin` are explicitly disabled in the preset.
202+
203+
2. **All columns are filterable.** `EnableAllFilterColumnsPreset` overrides PostGraphile's default of only filtering indexed columns. Index optimization is left to DBAs.
204+
205+
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.
206+
207+
4. **Relation filters are enabled.** `connectionFilterRelations: true` allows filtering across foreign key relationships (forward and backward).
208+
209+
5. **Search is unified.** One plugin (`graphile-search`) handles all four search algorithms via adapters instead of four separate plugins.
210+
211+
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.
212+
213+
---
214+
215+
## RC Dependency Management
216+
217+
### Overview
218+
219+
This monorepo contains several packages that depend on the [Graphile](https://graphile.org/) v5 release candidate (RC) ecosystem. The Graphile v5 RC packages are **tightly coupled** -- all packages in the ecosystem must be at matching RC versions to work correctly.
220+
221+
### The Problem (Why We Pin)
222+
223+
Graphile v5 RC packages declare peer dependencies on each other. When a new RC is released, the minimum required versions of those peer deps often bump together. For example, `graphile-build-pg@5.0.0-rc.5` requires `@dataplan/pg@^1.0.0-rc.5` and `tamedevil@^0.1.0-rc.4`, whereas the earlier `rc.3` only required `rc.3` versions of those peers.
224+
225+
If our packages use loose caret ranges (e.g., `^5.0.0-rc.3`), the package manager may resolve to the **latest** RC (e.g., `rc.5` or `rc.7`), which introduces new peer dependency requirements that nothing in our tree satisfies.
226+
227+
### Our Approach: Pinned Exact Versions
25228

26229
All Graphile RC dependencies are pinned to **exact versions** (no `^` or `~` prefix). This ensures:
27230

@@ -30,7 +233,7 @@ All Graphile RC dependencies are pinned to **exact versions** (no `^` or `~` pre
30233
3. All peer dependency requirements are explicitly satisfied
31234
4. Deterministic installs across environments
32235

33-
## Current Pinned Versions
236+
### Current Pinned Versions
34237

35238
| Package | Pinned Version |
36239
|---------|---------------|
@@ -46,24 +249,24 @@ All Graphile RC dependencies are pinned to **exact versions** (no `^` or `~` pre
46249
| `@dataplan/pg` | `1.0.0-rc.5` |
47250
| `tamedevil` | `0.1.0-rc.4` |
48251
| `@graphile-contrib/pg-many-to-many` | `2.0.0-rc.1` |
49-
| `postgraphile-plugin-connection-filter` | `3.0.0-rc.1` |
50252

51-
## Packages That Use Graphile
253+
### Packages That Use Graphile
52254

53-
### `graphile/` packages
255+
#### `graphile/` packages
54256

55257
- **graphile-settings** -- Core settings/configuration for PostGraphile v5 (most deps, including the transitive peer deps `tamedevil`, `@dataplan/pg`, `@dataplan/json`, `grafserv`)
56258
- **graphile-schema** -- Builds GraphQL SDL from PostgreSQL using PostGraphile v5
57259
- **graphile-query** -- Executes GraphQL queries against PostGraphile v5 schemas
58-
- **graphile-tsvector** -- Full-text search plugin for PostGraphile v5 (tsvector columns, `matches` filter, rank scoring)
59-
- **graphile-bm25** -- BM25 ranked full-text search plugin for PostGraphile v5 (pg_textsearch auto-discovery, score fields, orderBy)
60-
- **graphile-trgm** -- pg_trgm fuzzy text matching plugin for PostGraphile v5 (similarTo/wordSimilarTo operators, similarity scoring)
61-
- **graphile-pgvector** -- pgvector codec + auto-discovered vector search plugin for PostGraphile v5
260+
- **graphile-search** -- Unified search plugin (tsvector + BM25 + pg_trgm + pgvector via adapter pattern)
62261
- **graphile-connection-filter** -- v5-native connection filter plugin (scalar, logical, array, relation, computed column filters)
262+
- **graphile-postgis** -- PostGIS types, GeoJSON scalars, spatial filter operators
63263
- **graphile-cache** -- LRU cache with PostGraphile v5 integration
64264
- **graphile-test** -- PostGraphile v5 testing utilities
265+
- **graphile-sql-expression-validator** -- SQL expression validation for safe default expressions
266+
- **graphile-upload-plugin** -- File upload support (S3/MinIO)
267+
- **graphile-misc-plugins** -- Inflection, conflict detection, meta-schema, type mappings
65268

66-
### `graphql/` packages
269+
#### `graphql/` packages
67270

68271
- **@constructive-io/graphql-server** -- GraphQL server with PostGraphile v5
69272
- **@constructive-io/graphql-test** -- GraphQL testing with all plugins loaded
@@ -72,7 +275,7 @@ All Graphile RC dependencies are pinned to **exact versions** (no `^` or `~` pre
72275

73276
**Important:** Having different versions of `grafast` (or other singleton graphile packages) installed in the same workspace causes runtime errors like `Preset attempted to register version 'X' of 'grafast', but version 'Y' is already registered`. This is why **all** packages must use the same pinned versions.
74277

75-
## Upgrading Graphile RC Versions
278+
### Upgrading Graphile RC Versions
76279

77280
When upgrading to a new Graphile RC set:
78281

@@ -84,3 +287,16 @@ When upgrading to a new Graphile RC set:
84287
6. Run `pnpm build` to verify no type errors
85288
7. Run tests to verify nothing broke
86289
8. Update the version table in this document
290+
291+
## Testing
292+
293+
| Test Suite | Command | What It Covers |
294+
|---|---|---|
295+
| `graphile-connection-filter` | `pnpm --filter graphile-connection-filter test` | 51 filter operator tests |
296+
| `graphile-search` | `pnpm --filter graphile-search test` | Unified search adapter tests |
297+
| `graphile-settings` | `pnpm --filter graphile-settings test` | Preset integration + metadata tests |
298+
| `graphile-test` | `pnpm --filter graphile-test test` | Test framework self-tests |
299+
| `graphql/test` | `pnpm --filter @constructive-io/graphql-test test` | Full ConstructivePreset integration (84 tests) |
300+
| `graphql/server-test` | `pnpm --filter @constructive-io/graphql-server-test test` | Server-level integration + search mega queries |
301+
302+
All tests run in CI against `constructiveio/postgres-plus:18` (includes PostGIS, pg_trgm, pgvector, pg_textsearch).

0 commit comments

Comments
 (0)