Skip to content

Commit 15e9709

Browse files
authored
Merge pull request #1056 from constructive-io/feat/graphile-ltree-plugin
feat: add graphile-ltree plugin — auto-detect ltree columns, folder fields, containment/glob filters
2 parents c3fb0f2 + 1ba92d4 commit 15e9709

18 files changed

Lines changed: 3888 additions & 7468 deletions

.github/workflows/run-tests.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ jobs:
123123
env: {}
124124
- package: graphile/graphile-bucket-provisioner-plugin
125125
env: {}
126+
- package: graphile/graphile-ltree
127+
env: {}
126128

127129
env:
128130
PGHOST: localhost

graphile/graphile-ltree/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# graphile-ltree
2+
3+
PostGraphile v5 plugin for PostgreSQL's ltree hierarchical data type.
4+
5+
Auto-detects ltree columns, exposes slash-path folder fields, and provides containment/glob filter operators for hierarchical data.
6+
7+
## Features
8+
9+
- **Ltree scalar type** — ltree columns are exposed as the `Ltree` GraphQL scalar instead of `String`
10+
- **Folder fields** — virtual `{column}Folder` fields that convert dot-notation to slash paths (`projects.alpha.docs` -> `/projects/alpha/docs`)
11+
- **Connection filter operators**`isAncestorOf`, `isDescendantOf`, `matchesGlob` for containment and pattern queries
12+
- **Auto-detection** — no configuration needed; the plugin scans the database for ltree columns
13+
- **ltree_helpers integration** — automatically uses `ltree_helpers.to_path()` / `to_query()` when available
14+
15+
## Usage
16+
17+
```typescript
18+
import { GraphileLtreePreset } from 'graphile-ltree';
19+
20+
const preset = {
21+
extends: [GraphileLtreePreset],
22+
};
23+
```
24+
25+
Or add to the Constructive preset (already included):
26+
27+
```typescript
28+
import { ConstructivePreset } from 'graphile-settings/presets';
29+
```
30+
31+
## GraphQL API
32+
33+
### Folder fields
34+
35+
For a table with an ltree column `path`:
36+
37+
```graphql
38+
{
39+
allFiles {
40+
nodes {
41+
path # "projects.alpha.docs" (raw ltree)
42+
pathFolder # "/projects/alpha/docs" (slash-delimited)
43+
}
44+
}
45+
}
46+
```
47+
48+
### Filter operators
49+
50+
```graphql
51+
# Files under /projects/alpha (containment)
52+
{
53+
allFiles(where: { path: { isAncestorOf: "projects.alpha" } }) {
54+
nodes { filename pathFolder }
55+
}
56+
}
57+
58+
# Ancestors of a deep path
59+
{
60+
allFiles(where: { path: { isDescendantOf: "projects.alpha.docs.images" } }) {
61+
nodes { filename pathFolder }
62+
}
63+
}
64+
65+
# Glob/lquery pattern matching
66+
{
67+
allFiles(where: { path: { matchesGlob: "projects.*.docs" } }) {
68+
nodes { filename pathFolder }
69+
}
70+
}
71+
```
72+
73+
## Plugins
74+
75+
| Plugin | Purpose |
76+
|--------|---------|
77+
| `LtreeExtensionDetectionPlugin` | Scans pgRegistry for ltree/lquery codecs |
78+
| `LtreeCodecPlugin` | Registers `Ltree` scalar, maps ltree/lquery types |
79+
| `LtreeFolderFieldPlugin` | Adds virtual `{column}Folder` fields |
80+
| `createLtreeOperatorFactory()` | Connection filter operators for containment/glob |
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'node',
5+
testTimeout: 60000,
6+
transform: {
7+
'^.+\\.tsx?$': [
8+
'ts-jest',
9+
{
10+
babelConfig: false,
11+
tsconfig: 'tsconfig.json'
12+
}
13+
]
14+
},
15+
transformIgnorePatterns: [`/node_modules/*`],
16+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
17+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
18+
modulePathIgnorePatterns: ['dist/*']
19+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"name": "graphile-ltree",
3+
"version": "1.0.0",
4+
"description": "PostGraphile v5 ltree plugin — auto-detects ltree columns, exposes slash-path folder fields, and provides containment/glob filter operators",
5+
"author": "Constructive <developers@constructive.io>",
6+
"homepage": "https://github.com/constructive-io/constructive",
7+
"license": "MIT",
8+
"main": "index.js",
9+
"module": "esm/index.js",
10+
"types": "index.d.ts",
11+
"scripts": {
12+
"clean": "makage clean",
13+
"prepack": "npm run build",
14+
"build": "makage build",
15+
"build:dev": "makage build --dev",
16+
"lint": "eslint . --fix",
17+
"test": "jest --forceExit",
18+
"test:watch": "jest --watch"
19+
},
20+
"publishConfig": {
21+
"access": "public",
22+
"directory": "dist"
23+
},
24+
"repository": {
25+
"type": "git",
26+
"url": "https://github.com/constructive-io/constructive"
27+
},
28+
"bugs": {
29+
"url": "https://github.com/constructive-io/constructive/issues"
30+
},
31+
"peerDependencies": {
32+
"@dataplan/pg": "1.0.0",
33+
"grafast": "1.0.0",
34+
"graphile-build": "5.0.0",
35+
"graphile-build-pg": "5.0.0",
36+
"graphile-config": "1.0.0",
37+
"graphile-connection-filter": "workspace:^",
38+
"graphql": "16.13.0",
39+
"pg-sql2": "5.0.0",
40+
"postgraphile": "5.0.0"
41+
},
42+
"peerDependenciesMeta": {
43+
"graphile-connection-filter": {
44+
"optional": true
45+
}
46+
},
47+
"devDependencies": {
48+
"@types/node": "^22.19.11",
49+
"graphile-test": "workspace:^",
50+
"makage": "^0.3.0",
51+
"pgsql-test": "workspace:^"
52+
},
53+
"keywords": [
54+
"postgraphile",
55+
"graphile",
56+
"constructive",
57+
"plugin",
58+
"postgres",
59+
"graphql",
60+
"ltree",
61+
"hierarchy",
62+
"path",
63+
"folder",
64+
"tree"
65+
]
66+
}

0 commit comments

Comments
 (0)