Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 241 additions & 25 deletions GRAPHILE.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,230 @@
# Graphile v5 RC Dependency Management
# Graphile v5 — Plugin Architecture & Dependency Management

## Overview
## Plugin Architecture

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.
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.

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

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.
## Active Packages

### graphile-settings

**The main configuration package.** Exports `ConstructivePreset` and all sub-presets. This is the only package most consumers need to import.

- Combines all plugins into a single composable preset
- Disables `condition` argument (all filtering via `where`)
- Configures connection filter options (logical operators, arrays, computed columns)
- Aggregates satellite plugin operator factories

```typescript
import { ConstructivePreset } from 'graphile-settings/presets';
```

### graphile-connection-filter

**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`).

Key features:
- Standard operators: `equalTo`, `notEqualTo`, `isNull`, `in`, `notIn`, `lessThan`, `greaterThan`
- Pattern matching: `includes`, `startsWith`, `endsWith`, `like` + case-insensitive variants
- Type-specific operators: JSONB, hstore, inet, array, range
- Logical operators: `and`, `or`, `not`
- Relation filters: filter by related table fields (forward and backward)
- **Declarative operator API**: satellite plugins register custom operators via `connectionFilterOperatorFactories`

```typescript
import { ConnectionFilterPreset } from 'graphile-connection-filter';
```

### graphile-search

**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.

Each algorithm is a ~50-line adapter implementing the `SearchAdapter` interface:
- **tsvector** — PostgreSQL full-text search via `ts_rank`
- **BM25** — `pg_textsearch` extension scoring
- **pg_trgm** — Trigram fuzzy matching via `similarity()`
- **pgvector** — Vector similarity search (cosine, L2, inner product)

Generated GraphQL fields per adapter:
- **Score fields**: `{column}{Algorithm}{Metric}` (e.g. `bodyBm25Score`, `titleTrgmSimilarity`)
- **Composite score**: `searchScore` — normalized 0..1 aggregating all active search signals
- **OrderBy enums**: `{COLUMN}_{ALGORITHM}_{METRIC}_ASC/DESC` + `SEARCH_SCORE_ASC/DESC`
- **Filter fields**: `{algorithm}{Column}` on connection filter input types (e.g. `fullTextBody`, `trgmTitle`)

Zero config — auto-discovers columns and indexes per adapter.

```typescript
import { UnifiedSearchPreset } from 'graphile-search';
```

### graphile-postgis

**PostGIS support.** Generates GraphQL types for geometry/geography columns including GeoJSON scalar types, dimension-aware interfaces, and spatial filter operators.

Features:
- GeoJSON scalar type for input/output
- Concrete types for all geometry subtypes (Point, LineString, Polygon, etc.)
- Geography-aware field naming (longitude/latitude instead of x/y)
- Spatial filter operators via `createPostgisOperatorFactory()`
- Graceful degradation when PostGIS is not installed

```typescript
import { GraphilePostgisPreset, createPostgisOperatorFactory } from 'graphile-postgis';
```

### graphile-misc-plugins

**Collection of smaller plugins** that don't warrant their own package:

| Plugin/Preset | Description |
|---|---|
| `MinimalPreset` | PostGraphile without Node/Relay features |
| `InflektPreset` | Custom inflection using inflekt library |
| `ConflictDetectorPreset` | Warns about naming conflicts between schemas |
| `InflectorLoggerPreset` | Debug logging (enable with `INFLECTOR_LOG=1`) |
| `EnableAllFilterColumnsPreset` | Allow filtering on all columns (not just indexed) |
| `ManyToManyOptInPreset` | Many-to-many via `@behavior +manyToMany` smart tag |
| `NoUniqueLookupPreset` | Disable non-primary-key unique lookups |
| `MetaSchemaPreset` | `_meta` query for database schema introspection |
| `PgTypeMappingsPreset` | Custom PostgreSQL type → GraphQL scalar mappings |

### graphile-cache

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:
**PostGraphile instance LRU cache** with automatic cleanup when PostgreSQL pools are disposed. Integrates with `pg-cache` for pool management.

```typescript
import { graphileCache } from 'graphile-cache';
```
graphile-build-pg 5.0.0-rc.5
- missing peer @dataplan/pg@^1.0.0-rc.5
- missing peer tamedevil@^0.1.0-rc.4
postgraphile 5.0.0-rc.7
- missing peer @dataplan/pg@^1.0.0-rc.5
- missing peer @dataplan/json@^1.0.0-rc.5
- missing peer grafserv@^1.0.0-rc.6
- missing peer tamedevil@^0.1.0-rc.4

### graphile-schema

**Lightweight GraphQL SDL builder.** Build schemas directly from a database or fetch from a running endpoint — no server dependencies.

```typescript
import { buildSchemaSDL } from 'graphile-schema';
import { fetchEndpointSchemaSDL } from 'graphile-schema';
```

### graphile-query

**GraphQL query execution utilities.** Supports `pgSettings`, role-based access control, and custom request context.

- `GraphileQuery` — Full-featured with roles, settings, and request context
- `GraphileQuerySimple` — Minimal wrapper for simple execution

```typescript
import { GraphileQuery } from 'graphile-query';
```

## Our Approach: Pinned Exact Versions
### graphile-test

**Testing utilities for PostGraphile.** Builds on `pgsql-test` to provide isolated, seeded, role-aware test databases with GraphQL helpers.

- Per-test rollback via savepoints
- RLS-aware context injection (`setContext`)
- GraphQL `query()` function with snapshot support
- Seed support for SQL, JSON, CSV, Constructive, or Sqitch

For batteries-included testing with all Constructive plugins, use `@constructive-io/graphql-test` instead.

```typescript
import { getConnections, seed } from 'graphile-test';
```

### graphile-sql-expression-validator

**SQL expression validation** for PostGraphile v5. Validates SQL expressions against a configurable allowlist of functions and schemas.

```typescript
import { SqlExpressionValidatorPreset } from 'graphile-sql-expression-validator';
```

### graphile-upload-plugin

**File upload support** for PostGraphile v5. Handles S3/MinIO uploads for image, upload, and attachment domain columns.

```typescript
import { UploadPreset } from 'graphile-upload-plugin';
```

## Legacy Directories (Not Source Code)

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:

| Directory | Status |
|---|---|
| `graphile-i18n` | Upstream v4 package |
| `graphile-many-to-many` | Upstream `@graphile-contrib/pg-many-to-many` |
| `graphile-meta-schema` | Folded into `graphile-misc-plugins` as `MetaSchemaPreset` |
| `graphile-pg-type-mappings` | Folded into `graphile-misc-plugins` as `PgTypeMappingsPreset` |
| `graphile-plugin-connection-filter` | Replaced by v5-native `graphile-connection-filter` |
| `graphile-plugin-fulltext-filter` | Replaced by `graphile-search` unified plugin |
| `graphile-simple-inflector` | Replaced by `InflektPreset` in `graphile-misc-plugins` |

## How Satellite Plugins Register Filter Operators

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:

```typescript
// In constructive-preset.ts
schema: {
connectionFilterOperatorFactories: [
createMatchesOperatorFactory('FullText', 'english'), // tsvector matches
createTrgmOperatorFactories(), // similarTo, wordSimilarTo
createPostgisOperatorFactory(), // spatial operators
],
}
```

This ensures `graphile-config`'s array replacement behavior is handled correctly — all factories are collected at the top-level preset.

## Key Design Decisions

1. **`condition` is disabled.** All filtering lives under `where` (the v5-native filter argument). `PgConditionArgumentPlugin` and `PgConditionCustomFieldsPlugin` are explicitly disabled in the preset.

2. **All columns are filterable.** `EnableAllFilterColumnsPreset` overrides PostGraphile's default of only filtering indexed columns. Index optimization is left to DBAs.

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.

4. **Relation filters are enabled.** `connectionFilterRelations: true` allows filtering across foreign key relationships (forward and backward).

5. **Search is unified.** One plugin (`graphile-search`) handles all four search algorithms via adapters instead of four separate plugins.

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.

---

## RC Dependency Management

### Overview

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.

### The Problem (Why We Pin)

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.

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.

### Our Approach: Pinned Exact Versions

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

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

## Current Pinned Versions
### Current Pinned Versions

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

## Packages That Use Graphile
### Packages That Use Graphile

### `graphile/` packages
#### `graphile/` packages

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

### `graphql/` packages
#### `graphql/` packages

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

**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.

## Upgrading Graphile RC Versions
### Upgrading Graphile RC Versions

When upgrading to a new Graphile RC set:

Expand All @@ -84,3 +287,16 @@ When upgrading to a new Graphile RC set:
6. Run `pnpm build` to verify no type errors
7. Run tests to verify nothing broke
8. Update the version table in this document

## Testing

| Test Suite | Command | What It Covers |
|---|---|---|
| `graphile-connection-filter` | `pnpm --filter graphile-connection-filter test` | 51 filter operator tests |
| `graphile-search` | `pnpm --filter graphile-search test` | Unified search adapter tests |
| `graphile-settings` | `pnpm --filter graphile-settings test` | Preset integration + metadata tests |
| `graphile-test` | `pnpm --filter graphile-test test` | Test framework self-tests |
| `graphql/test` | `pnpm --filter @constructive-io/graphql-test test` | Full ConstructivePreset integration (84 tests) |
| `graphql/server-test` | `pnpm --filter @constructive-io/graphql-server-test test` | Server-level integration + search mega queries |

All tests run in CI against `constructiveio/postgres-plus:18` (includes PostGIS, pg_trgm, pgvector, pg_textsearch).
Loading