graphile-ltree 1.2.0 → 1.4.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.
package/esm/index.d.ts CHANGED
@@ -1,24 +1,19 @@
1
1
  /**
2
2
  * graphile-ltree — PostGraphile v5 ltree Plugin
3
3
  *
4
- * Two presets:
5
- *
6
- * - `GraphileLtreePreset` — base ltree operators (isAncestorOf, isDescendantOf, matchesGlob)
7
- * - `GraphileFolderPreset` — folder-oriented layer (within, ancestorOf, glob) + pathFolder fields
4
+ * File-path syntax for ltree columns. The LTree scalar auto-converts between
5
+ * slash-delimited file paths ("/projects/alpha/docs") in the GraphQL API and
6
+ * dot-delimited ltree ("projects.alpha.docs") in PostgreSQL.
8
7
  *
9
8
  * @example
10
9
  * ```typescript
11
- * // For folder-style interface (recommended):
12
- * import { GraphileFolderPreset } from 'graphile-ltree';
13
- *
14
- * // For raw ltree operators only:
15
10
  * import { GraphileLtreePreset } from 'graphile-ltree';
16
11
  * ```
17
12
  */
18
13
  export { GraphileFolderPreset, GraphileLtreePreset } from './preset';
19
14
  export type { LtreeExtensionInfo } from './plugins/detect-ltree';
20
15
  export { LtreeExtensionDetectionPlugin } from './plugins/detect-ltree';
16
+ export { ltreeToSlash, LTREE_SCALAR_NAME, LtreeCodecPlugin, slashToLtree } from './plugins/ltree-codec';
17
+ export { createFolderOperatorFactory } from './plugins/folder-filter-operators';
21
18
  export { LtreeFolderFieldPlugin } from './plugins/folder-field';
22
- export { LTREE_SCALAR_NAME, LtreeCodecPlugin } from './plugins/ltree-codec';
23
19
  export { createLtreeOperatorFactory } from './plugins/connection-filter-operators';
24
- export { createFolderOperatorFactory } from './plugins/folder-filter-operators';
package/esm/index.js CHANGED
@@ -1,25 +1,22 @@
1
1
  /**
2
2
  * graphile-ltree — PostGraphile v5 ltree Plugin
3
3
  *
4
- * Two presets:
5
- *
6
- * - `GraphileLtreePreset` — base ltree operators (isAncestorOf, isDescendantOf, matchesGlob)
7
- * - `GraphileFolderPreset` — folder-oriented layer (within, ancestorOf, glob) + pathFolder fields
4
+ * File-path syntax for ltree columns. The LTree scalar auto-converts between
5
+ * slash-delimited file paths ("/projects/alpha/docs") in the GraphQL API and
6
+ * dot-delimited ltree ("projects.alpha.docs") in PostgreSQL.
8
7
  *
9
8
  * @example
10
9
  * ```typescript
11
- * // For folder-style interface (recommended):
12
- * import { GraphileFolderPreset } from 'graphile-ltree';
13
- *
14
- * // For raw ltree operators only:
15
10
  * import { GraphileLtreePreset } from 'graphile-ltree';
16
11
  * ```
17
12
  */
18
13
  // Presets
19
14
  export { GraphileFolderPreset, GraphileLtreePreset } from './preset';
20
15
  export { LtreeExtensionDetectionPlugin } from './plugins/detect-ltree';
21
- export { LtreeFolderFieldPlugin } from './plugins/folder-field';
22
- export { LTREE_SCALAR_NAME, LtreeCodecPlugin } from './plugins/ltree-codec';
16
+ export { ltreeToSlash, LTREE_SCALAR_NAME, LtreeCodecPlugin, slashToLtree } from './plugins/ltree-codec';
23
17
  // Connection filter operator factories
24
- export { createLtreeOperatorFactory } from './plugins/connection-filter-operators';
25
18
  export { createFolderOperatorFactory } from './plugins/folder-filter-operators';
19
+ // Deprecated — folder field plugin is no longer needed (scalar handles conversion)
20
+ export { LtreeFolderFieldPlugin } from './plugins/folder-field';
21
+ // Deprecated — use createFolderOperatorFactory instead (better operator names)
22
+ export { createLtreeOperatorFactory } from './plugins/connection-filter-operators';
@@ -7,10 +7,33 @@
7
7
  * This plugin:
8
8
  * 1. Falls back to registering codecs if PostGraphile's native handling misses them
9
9
  * 2. Re-enables ltree columns for select/filterBy (overrides rc.8 HIDE_BY_DEFAULT)
10
+ * 3. Converts between slash-delimited file paths (external API) and dot-delimited
11
+ * ltree (internal storage) at the GraphQL boundary via the codec's fromPg/toPg.
12
+ *
13
+ * External API: "/projects/alpha/docs" (slash-delimited file paths)
14
+ * Internal DB: "projects.alpha.docs" (ltree native format)
15
+ *
16
+ * The conversion happens at the codec level (fromPg/toPg) so it works regardless
17
+ * of whether PostGraphile registers its own LTree scalar or we register ours.
10
18
  *
11
19
  * The native scalar name is "LTree" (capital T). All operators and downstream
12
20
  * plugins should reference LTREE_SCALAR_NAME for consistency.
13
21
  */
14
22
  import type { GraphileConfig } from 'graphile-config';
15
23
  export declare const LTREE_SCALAR_NAME = "LTree";
24
+ /**
25
+ * Convert a slash-delimited file path to ltree format.
26
+ * Idempotent on ltree values (no slashes → no change).
27
+ *
28
+ * "/projects/alpha/docs" → "projects.alpha.docs"
29
+ * "projects.alpha.docs" → "projects.alpha.docs" (no-op)
30
+ */
31
+ export declare function slashToLtree(value: string): string;
32
+ /**
33
+ * Convert an ltree value to a slash-delimited file path.
34
+ *
35
+ * "projects.alpha.docs" → "/projects/alpha/docs"
36
+ * "" → "/"
37
+ */
38
+ export declare function ltreeToSlash(value: string): string;
16
39
  export declare const LtreeCodecPlugin: GraphileConfig.Plugin;
@@ -7,6 +7,14 @@
7
7
  * This plugin:
8
8
  * 1. Falls back to registering codecs if PostGraphile's native handling misses them
9
9
  * 2. Re-enables ltree columns for select/filterBy (overrides rc.8 HIDE_BY_DEFAULT)
10
+ * 3. Converts between slash-delimited file paths (external API) and dot-delimited
11
+ * ltree (internal storage) at the GraphQL boundary via the codec's fromPg/toPg.
12
+ *
13
+ * External API: "/projects/alpha/docs" (slash-delimited file paths)
14
+ * Internal DB: "projects.alpha.docs" (ltree native format)
15
+ *
16
+ * The conversion happens at the codec level (fromPg/toPg) so it works regardless
17
+ * of whether PostGraphile registers its own LTree scalar or we register ours.
10
18
  *
11
19
  * The native scalar name is "LTree" (capital T). All operators and downstream
12
20
  * plugins should reference LTREE_SCALAR_NAME for consistency.
@@ -14,10 +22,31 @@
14
22
  import { GraphQLString } from 'graphql';
15
23
  import sql from 'pg-sql2';
16
24
  export const LTREE_SCALAR_NAME = 'LTree';
25
+ /**
26
+ * Convert a slash-delimited file path to ltree format.
27
+ * Idempotent on ltree values (no slashes → no change).
28
+ *
29
+ * "/projects/alpha/docs" → "projects.alpha.docs"
30
+ * "projects.alpha.docs" → "projects.alpha.docs" (no-op)
31
+ */
32
+ export function slashToLtree(value) {
33
+ return value.replace(/^\//, '').replace(/\//g, '.');
34
+ }
35
+ /**
36
+ * Convert an ltree value to a slash-delimited file path.
37
+ *
38
+ * "projects.alpha.docs" → "/projects/alpha/docs"
39
+ * "" → "/"
40
+ */
41
+ export function ltreeToSlash(value) {
42
+ if (value === '')
43
+ return '/';
44
+ return '/' + value.replace(/\./g, '/');
45
+ }
17
46
  export const LtreeCodecPlugin = {
18
47
  name: 'LtreeCodecPlugin',
19
- version: '1.0.0',
20
- description: 'Ensures ltree/lquery codecs are properly registered and enabled',
48
+ version: '2.0.0',
49
+ description: 'Ensures ltree/lquery codecs are registered; converts between file paths and ltree at the GraphQL boundary',
21
50
  gather: {
22
51
  hooks: {
23
52
  async pgCodecs_findPgCodec(info, event) {
@@ -52,6 +81,37 @@ export const LtreeCodecPlugin = {
52
81
  },
53
82
  schema: {
54
83
  hooks: {
84
+ /**
85
+ * Patch the ltree codec's fromPg/toPg to convert between file paths
86
+ * and ltree. This runs in the `build` hook (before type registration)
87
+ * so it works regardless of whether PostGraphile registers its own
88
+ * LTree scalar or we register ours.
89
+ *
90
+ * fromPg: "projects.alpha.docs" → "/projects/alpha/docs"
91
+ * toPg: "/projects/alpha/docs" → "projects.alpha.docs"
92
+ */
93
+ build(build) {
94
+ for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
95
+ if (codec.name === 'ltree') {
96
+ const c = codec;
97
+ const origFromPg = c.fromPg;
98
+ c.fromPg = (value) => {
99
+ const raw = origFromPg ? origFromPg(value) : value;
100
+ if (raw == null)
101
+ return raw;
102
+ return ltreeToSlash(String(raw));
103
+ };
104
+ const origToPg = c.toPg;
105
+ c.toPg = (value) => {
106
+ if (value == null)
107
+ return origToPg ? origToPg(value) : value;
108
+ const converted = slashToLtree(String(value));
109
+ return origToPg ? origToPg(converted) : converted;
110
+ };
111
+ }
112
+ }
113
+ return build;
114
+ },
55
115
  init: {
56
116
  before: ['PgCodecs', 'PgConnectionArgFilterPlugin'],
57
117
  callback(_, build) {
@@ -60,14 +120,18 @@ export const LtreeCodecPlugin = {
60
120
  if (codec.name === 'ltree') {
61
121
  if (!hasGraphQLTypeForPgCodec(codec, 'input')) {
62
122
  build.registerScalarType(LTREE_SCALAR_NAME, {}, () => ({
63
- description: 'A PostgreSQL ltree hierarchical label path, represented as a dot-delimited string (e.g. "projects.alpha.docs").',
123
+ description: 'A hierarchical file path. Accepts slash-delimited paths (e.g. "/projects/alpha/docs"). ' +
124
+ 'Stored internally as PostgreSQL ltree.',
64
125
  serialize(value) {
126
+ if (value == null)
127
+ return null;
65
128
  return String(value);
66
129
  },
67
130
  parseValue(value) {
68
- if (typeof value === 'string')
69
- return value;
70
- throw new Error(`${LTREE_SCALAR_NAME} must be a string`);
131
+ if (typeof value !== 'string') {
132
+ throw new Error(`${LTREE_SCALAR_NAME} must be a string`);
133
+ }
134
+ return value;
71
135
  },
72
136
  parseLiteral(lit) {
73
137
  if (lit.kind === 'NullValue')
package/esm/preset.d.ts CHANGED
@@ -2,27 +2,23 @@ import type { GraphileConfig } from 'graphile-config';
2
2
  /**
3
3
  * GraphileLtreePreset
4
4
  *
5
- * Base preset: ltree codec, detection, and raw ltree operators.
5
+ * Full ltree support with file-path syntax.
6
6
  *
7
- * Operators on LTree fields: isAncestorOf, isDescendantOf, matchesGlob
8
- * (accept dot-delimited or slash-delimited paths)
9
- */
10
- export declare const GraphileLtreePreset: GraphileConfig.Preset;
11
- /**
12
- * GraphileFolderPreset
13
- *
14
- * Folder-oriented layer on top of the base ltree preset.
15
- *
16
- * Adds:
17
- * - Virtual `{column}Folder` fields with slash-delimited paths
18
- * - Folder operators: within, ancestorOf, glob (always slash-delimited)
7
+ * - LTree scalar auto-converts between slash-delimited file paths (API) and
8
+ * dot-delimited ltree (DB). External API always uses "/projects/alpha/docs".
9
+ * - Filter operators: within, ancestorOf, glob (all accept slash-delimited paths)
19
10
  *
20
11
  * @example
21
12
  * ```graphql
22
13
  * allFiles(where: { path: { within: "/projects/alpha" } }) {
23
- * nodes { pathFolder filename }
14
+ * nodes { path filename }
24
15
  * }
25
16
  * ```
26
17
  */
18
+ export declare const GraphileLtreePreset: GraphileConfig.Preset;
19
+ /**
20
+ * @deprecated Use GraphileLtreePreset instead. The folder field plugin is no
21
+ * longer needed — the LTree scalar now handles slash↔ltree conversion directly.
22
+ */
27
23
  export declare const GraphileFolderPreset: GraphileConfig.Preset;
28
- export default GraphileFolderPreset;
24
+ export default GraphileLtreePreset;
package/esm/preset.js CHANGED
@@ -1,47 +1,26 @@
1
- import { createLtreeOperatorFactory } from './plugins/connection-filter-operators';
2
1
  import { LtreeExtensionDetectionPlugin } from './plugins/detect-ltree';
3
- import { LtreeFolderFieldPlugin } from './plugins/folder-field';
4
2
  import { createFolderOperatorFactory } from './plugins/folder-filter-operators';
5
3
  import { LtreeCodecPlugin } from './plugins/ltree-codec';
6
4
  /**
7
5
  * GraphileLtreePreset
8
6
  *
9
- * Base preset: ltree codec, detection, and raw ltree operators.
7
+ * Full ltree support with file-path syntax.
10
8
  *
11
- * Operators on LTree fields: isAncestorOf, isDescendantOf, matchesGlob
12
- * (accept dot-delimited or slash-delimited paths)
13
- */
14
- export const GraphileLtreePreset = {
15
- plugins: [
16
- LtreeExtensionDetectionPlugin,
17
- LtreeCodecPlugin
18
- ],
19
- schema: {
20
- connectionFilterOperatorFactories: [
21
- createLtreeOperatorFactory()
22
- ]
23
- }
24
- };
25
- /**
26
- * GraphileFolderPreset
27
- *
28
- * Folder-oriented layer on top of the base ltree preset.
29
- *
30
- * Adds:
31
- * - Virtual `{column}Folder` fields with slash-delimited paths
32
- * - Folder operators: within, ancestorOf, glob (always slash-delimited)
9
+ * - LTree scalar auto-converts between slash-delimited file paths (API) and
10
+ * dot-delimited ltree (DB). External API always uses "/projects/alpha/docs".
11
+ * - Filter operators: within, ancestorOf, glob (all accept slash-delimited paths)
33
12
  *
34
13
  * @example
35
14
  * ```graphql
36
15
  * allFiles(where: { path: { within: "/projects/alpha" } }) {
37
- * nodes { pathFolder filename }
16
+ * nodes { path filename }
38
17
  * }
39
18
  * ```
40
19
  */
41
- export const GraphileFolderPreset = {
42
- extends: [GraphileLtreePreset],
20
+ export const GraphileLtreePreset = {
43
21
  plugins: [
44
- LtreeFolderFieldPlugin
22
+ LtreeExtensionDetectionPlugin,
23
+ LtreeCodecPlugin
45
24
  ],
46
25
  schema: {
47
26
  connectionFilterOperatorFactories: [
@@ -49,4 +28,9 @@ export const GraphileFolderPreset = {
49
28
  ]
50
29
  }
51
30
  };
52
- export default GraphileFolderPreset;
31
+ /**
32
+ * @deprecated Use GraphileLtreePreset instead. The folder field plugin is no
33
+ * longer needed — the LTree scalar now handles slash↔ltree conversion directly.
34
+ */
35
+ export const GraphileFolderPreset = GraphileLtreePreset;
36
+ export default GraphileLtreePreset;
package/index.d.ts CHANGED
@@ -1,24 +1,19 @@
1
1
  /**
2
2
  * graphile-ltree — PostGraphile v5 ltree Plugin
3
3
  *
4
- * Two presets:
5
- *
6
- * - `GraphileLtreePreset` — base ltree operators (isAncestorOf, isDescendantOf, matchesGlob)
7
- * - `GraphileFolderPreset` — folder-oriented layer (within, ancestorOf, glob) + pathFolder fields
4
+ * File-path syntax for ltree columns. The LTree scalar auto-converts between
5
+ * slash-delimited file paths ("/projects/alpha/docs") in the GraphQL API and
6
+ * dot-delimited ltree ("projects.alpha.docs") in PostgreSQL.
8
7
  *
9
8
  * @example
10
9
  * ```typescript
11
- * // For folder-style interface (recommended):
12
- * import { GraphileFolderPreset } from 'graphile-ltree';
13
- *
14
- * // For raw ltree operators only:
15
10
  * import { GraphileLtreePreset } from 'graphile-ltree';
16
11
  * ```
17
12
  */
18
13
  export { GraphileFolderPreset, GraphileLtreePreset } from './preset';
19
14
  export type { LtreeExtensionInfo } from './plugins/detect-ltree';
20
15
  export { LtreeExtensionDetectionPlugin } from './plugins/detect-ltree';
16
+ export { ltreeToSlash, LTREE_SCALAR_NAME, LtreeCodecPlugin, slashToLtree } from './plugins/ltree-codec';
17
+ export { createFolderOperatorFactory } from './plugins/folder-filter-operators';
21
18
  export { LtreeFolderFieldPlugin } from './plugins/folder-field';
22
- export { LTREE_SCALAR_NAME, LtreeCodecPlugin } from './plugins/ltree-codec';
23
19
  export { createLtreeOperatorFactory } from './plugins/connection-filter-operators';
24
- export { createFolderOperatorFactory } from './plugins/folder-filter-operators';
package/index.js CHANGED
@@ -2,35 +2,34 @@
2
2
  /**
3
3
  * graphile-ltree — PostGraphile v5 ltree Plugin
4
4
  *
5
- * Two presets:
6
- *
7
- * - `GraphileLtreePreset` — base ltree operators (isAncestorOf, isDescendantOf, matchesGlob)
8
- * - `GraphileFolderPreset` — folder-oriented layer (within, ancestorOf, glob) + pathFolder fields
5
+ * File-path syntax for ltree columns. The LTree scalar auto-converts between
6
+ * slash-delimited file paths ("/projects/alpha/docs") in the GraphQL API and
7
+ * dot-delimited ltree ("projects.alpha.docs") in PostgreSQL.
9
8
  *
10
9
  * @example
11
10
  * ```typescript
12
- * // For folder-style interface (recommended):
13
- * import { GraphileFolderPreset } from 'graphile-ltree';
14
- *
15
- * // For raw ltree operators only:
16
11
  * import { GraphileLtreePreset } from 'graphile-ltree';
17
12
  * ```
18
13
  */
19
14
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.createFolderOperatorFactory = exports.createLtreeOperatorFactory = exports.LtreeCodecPlugin = exports.LTREE_SCALAR_NAME = exports.LtreeFolderFieldPlugin = exports.LtreeExtensionDetectionPlugin = exports.GraphileLtreePreset = exports.GraphileFolderPreset = void 0;
15
+ exports.createLtreeOperatorFactory = exports.LtreeFolderFieldPlugin = exports.createFolderOperatorFactory = exports.slashToLtree = exports.LtreeCodecPlugin = exports.LTREE_SCALAR_NAME = exports.ltreeToSlash = exports.LtreeExtensionDetectionPlugin = exports.GraphileLtreePreset = exports.GraphileFolderPreset = void 0;
21
16
  // Presets
22
17
  var preset_1 = require("./preset");
23
18
  Object.defineProperty(exports, "GraphileFolderPreset", { enumerable: true, get: function () { return preset_1.GraphileFolderPreset; } });
24
19
  Object.defineProperty(exports, "GraphileLtreePreset", { enumerable: true, get: function () { return preset_1.GraphileLtreePreset; } });
25
20
  var detect_ltree_1 = require("./plugins/detect-ltree");
26
21
  Object.defineProperty(exports, "LtreeExtensionDetectionPlugin", { enumerable: true, get: function () { return detect_ltree_1.LtreeExtensionDetectionPlugin; } });
27
- var folder_field_1 = require("./plugins/folder-field");
28
- Object.defineProperty(exports, "LtreeFolderFieldPlugin", { enumerable: true, get: function () { return folder_field_1.LtreeFolderFieldPlugin; } });
29
22
  var ltree_codec_1 = require("./plugins/ltree-codec");
23
+ Object.defineProperty(exports, "ltreeToSlash", { enumerable: true, get: function () { return ltree_codec_1.ltreeToSlash; } });
30
24
  Object.defineProperty(exports, "LTREE_SCALAR_NAME", { enumerable: true, get: function () { return ltree_codec_1.LTREE_SCALAR_NAME; } });
31
25
  Object.defineProperty(exports, "LtreeCodecPlugin", { enumerable: true, get: function () { return ltree_codec_1.LtreeCodecPlugin; } });
26
+ Object.defineProperty(exports, "slashToLtree", { enumerable: true, get: function () { return ltree_codec_1.slashToLtree; } });
32
27
  // Connection filter operator factories
33
- var connection_filter_operators_1 = require("./plugins/connection-filter-operators");
34
- Object.defineProperty(exports, "createLtreeOperatorFactory", { enumerable: true, get: function () { return connection_filter_operators_1.createLtreeOperatorFactory; } });
35
28
  var folder_filter_operators_1 = require("./plugins/folder-filter-operators");
36
29
  Object.defineProperty(exports, "createFolderOperatorFactory", { enumerable: true, get: function () { return folder_filter_operators_1.createFolderOperatorFactory; } });
30
+ // Deprecated — folder field plugin is no longer needed (scalar handles conversion)
31
+ var folder_field_1 = require("./plugins/folder-field");
32
+ Object.defineProperty(exports, "LtreeFolderFieldPlugin", { enumerable: true, get: function () { return folder_field_1.LtreeFolderFieldPlugin; } });
33
+ // Deprecated — use createFolderOperatorFactory instead (better operator names)
34
+ var connection_filter_operators_1 = require("./plugins/connection-filter-operators");
35
+ Object.defineProperty(exports, "createLtreeOperatorFactory", { enumerable: true, get: function () { return connection_filter_operators_1.createLtreeOperatorFactory; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-ltree",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "PostGraphile v5 ltree plugin — auto-detects ltree columns, exposes slash-path folder fields, and provides containment/glob filter operators",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -34,7 +34,7 @@
34
34
  "graphile-build": "5.0.0",
35
35
  "graphile-build-pg": "5.0.0",
36
36
  "graphile-config": "1.0.0",
37
- "graphile-connection-filter": "^1.6.0",
37
+ "graphile-connection-filter": "^1.7.0",
38
38
  "graphql": "16.13.0",
39
39
  "pg-sql2": "5.0.0",
40
40
  "postgraphile": "5.0.0"
@@ -46,9 +46,9 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.19.11",
49
- "graphile-test": "^4.10.0",
49
+ "graphile-test": "^4.11.0",
50
50
  "makage": "^0.3.0",
51
- "pgsql-test": "^4.10.0"
51
+ "pgsql-test": "^4.11.0"
52
52
  },
53
53
  "keywords": [
54
54
  "postgraphile",
@@ -63,5 +63,5 @@
63
63
  "folder",
64
64
  "tree"
65
65
  ],
66
- "gitHead": "fb12131bc72ca66e2eb554e15c49e35dc5b362ac"
66
+ "gitHead": "0ad9af424bb7760e50835edb9ee083b919a4b1f0"
67
67
  }
@@ -7,10 +7,33 @@
7
7
  * This plugin:
8
8
  * 1. Falls back to registering codecs if PostGraphile's native handling misses them
9
9
  * 2. Re-enables ltree columns for select/filterBy (overrides rc.8 HIDE_BY_DEFAULT)
10
+ * 3. Converts between slash-delimited file paths (external API) and dot-delimited
11
+ * ltree (internal storage) at the GraphQL boundary via the codec's fromPg/toPg.
12
+ *
13
+ * External API: "/projects/alpha/docs" (slash-delimited file paths)
14
+ * Internal DB: "projects.alpha.docs" (ltree native format)
15
+ *
16
+ * The conversion happens at the codec level (fromPg/toPg) so it works regardless
17
+ * of whether PostGraphile registers its own LTree scalar or we register ours.
10
18
  *
11
19
  * The native scalar name is "LTree" (capital T). All operators and downstream
12
20
  * plugins should reference LTREE_SCALAR_NAME for consistency.
13
21
  */
14
22
  import type { GraphileConfig } from 'graphile-config';
15
23
  export declare const LTREE_SCALAR_NAME = "LTree";
24
+ /**
25
+ * Convert a slash-delimited file path to ltree format.
26
+ * Idempotent on ltree values (no slashes → no change).
27
+ *
28
+ * "/projects/alpha/docs" → "projects.alpha.docs"
29
+ * "projects.alpha.docs" → "projects.alpha.docs" (no-op)
30
+ */
31
+ export declare function slashToLtree(value: string): string;
32
+ /**
33
+ * Convert an ltree value to a slash-delimited file path.
34
+ *
35
+ * "projects.alpha.docs" → "/projects/alpha/docs"
36
+ * "" → "/"
37
+ */
38
+ export declare function ltreeToSlash(value: string): string;
16
39
  export declare const LtreeCodecPlugin: GraphileConfig.Plugin;
@@ -8,6 +8,14 @@
8
8
  * This plugin:
9
9
  * 1. Falls back to registering codecs if PostGraphile's native handling misses them
10
10
  * 2. Re-enables ltree columns for select/filterBy (overrides rc.8 HIDE_BY_DEFAULT)
11
+ * 3. Converts between slash-delimited file paths (external API) and dot-delimited
12
+ * ltree (internal storage) at the GraphQL boundary via the codec's fromPg/toPg.
13
+ *
14
+ * External API: "/projects/alpha/docs" (slash-delimited file paths)
15
+ * Internal DB: "projects.alpha.docs" (ltree native format)
16
+ *
17
+ * The conversion happens at the codec level (fromPg/toPg) so it works regardless
18
+ * of whether PostGraphile registers its own LTree scalar or we register ours.
11
19
  *
12
20
  * The native scalar name is "LTree" (capital T). All operators and downstream
13
21
  * plugins should reference LTREE_SCALAR_NAME for consistency.
@@ -17,13 +25,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
25
  };
18
26
  Object.defineProperty(exports, "__esModule", { value: true });
19
27
  exports.LtreeCodecPlugin = exports.LTREE_SCALAR_NAME = void 0;
28
+ exports.slashToLtree = slashToLtree;
29
+ exports.ltreeToSlash = ltreeToSlash;
20
30
  const graphql_1 = require("graphql");
21
31
  const pg_sql2_1 = __importDefault(require("pg-sql2"));
22
32
  exports.LTREE_SCALAR_NAME = 'LTree';
33
+ /**
34
+ * Convert a slash-delimited file path to ltree format.
35
+ * Idempotent on ltree values (no slashes → no change).
36
+ *
37
+ * "/projects/alpha/docs" → "projects.alpha.docs"
38
+ * "projects.alpha.docs" → "projects.alpha.docs" (no-op)
39
+ */
40
+ function slashToLtree(value) {
41
+ return value.replace(/^\//, '').replace(/\//g, '.');
42
+ }
43
+ /**
44
+ * Convert an ltree value to a slash-delimited file path.
45
+ *
46
+ * "projects.alpha.docs" → "/projects/alpha/docs"
47
+ * "" → "/"
48
+ */
49
+ function ltreeToSlash(value) {
50
+ if (value === '')
51
+ return '/';
52
+ return '/' + value.replace(/\./g, '/');
53
+ }
23
54
  exports.LtreeCodecPlugin = {
24
55
  name: 'LtreeCodecPlugin',
25
- version: '1.0.0',
26
- description: 'Ensures ltree/lquery codecs are properly registered and enabled',
56
+ version: '2.0.0',
57
+ description: 'Ensures ltree/lquery codecs are registered; converts between file paths and ltree at the GraphQL boundary',
27
58
  gather: {
28
59
  hooks: {
29
60
  async pgCodecs_findPgCodec(info, event) {
@@ -58,6 +89,37 @@ exports.LtreeCodecPlugin = {
58
89
  },
59
90
  schema: {
60
91
  hooks: {
92
+ /**
93
+ * Patch the ltree codec's fromPg/toPg to convert between file paths
94
+ * and ltree. This runs in the `build` hook (before type registration)
95
+ * so it works regardless of whether PostGraphile registers its own
96
+ * LTree scalar or we register ours.
97
+ *
98
+ * fromPg: "projects.alpha.docs" → "/projects/alpha/docs"
99
+ * toPg: "/projects/alpha/docs" → "projects.alpha.docs"
100
+ */
101
+ build(build) {
102
+ for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
103
+ if (codec.name === 'ltree') {
104
+ const c = codec;
105
+ const origFromPg = c.fromPg;
106
+ c.fromPg = (value) => {
107
+ const raw = origFromPg ? origFromPg(value) : value;
108
+ if (raw == null)
109
+ return raw;
110
+ return ltreeToSlash(String(raw));
111
+ };
112
+ const origToPg = c.toPg;
113
+ c.toPg = (value) => {
114
+ if (value == null)
115
+ return origToPg ? origToPg(value) : value;
116
+ const converted = slashToLtree(String(value));
117
+ return origToPg ? origToPg(converted) : converted;
118
+ };
119
+ }
120
+ }
121
+ return build;
122
+ },
61
123
  init: {
62
124
  before: ['PgCodecs', 'PgConnectionArgFilterPlugin'],
63
125
  callback(_, build) {
@@ -66,14 +128,18 @@ exports.LtreeCodecPlugin = {
66
128
  if (codec.name === 'ltree') {
67
129
  if (!hasGraphQLTypeForPgCodec(codec, 'input')) {
68
130
  build.registerScalarType(exports.LTREE_SCALAR_NAME, {}, () => ({
69
- description: 'A PostgreSQL ltree hierarchical label path, represented as a dot-delimited string (e.g. "projects.alpha.docs").',
131
+ description: 'A hierarchical file path. Accepts slash-delimited paths (e.g. "/projects/alpha/docs"). ' +
132
+ 'Stored internally as PostgreSQL ltree.',
70
133
  serialize(value) {
134
+ if (value == null)
135
+ return null;
71
136
  return String(value);
72
137
  },
73
138
  parseValue(value) {
74
- if (typeof value === 'string')
75
- return value;
76
- throw new Error(`${exports.LTREE_SCALAR_NAME} must be a string`);
139
+ if (typeof value !== 'string') {
140
+ throw new Error(`${exports.LTREE_SCALAR_NAME} must be a string`);
141
+ }
142
+ return value;
77
143
  },
78
144
  parseLiteral(lit) {
79
145
  if (lit.kind === 'NullValue')
package/preset.d.ts CHANGED
@@ -2,27 +2,23 @@ import type { GraphileConfig } from 'graphile-config';
2
2
  /**
3
3
  * GraphileLtreePreset
4
4
  *
5
- * Base preset: ltree codec, detection, and raw ltree operators.
5
+ * Full ltree support with file-path syntax.
6
6
  *
7
- * Operators on LTree fields: isAncestorOf, isDescendantOf, matchesGlob
8
- * (accept dot-delimited or slash-delimited paths)
9
- */
10
- export declare const GraphileLtreePreset: GraphileConfig.Preset;
11
- /**
12
- * GraphileFolderPreset
13
- *
14
- * Folder-oriented layer on top of the base ltree preset.
15
- *
16
- * Adds:
17
- * - Virtual `{column}Folder` fields with slash-delimited paths
18
- * - Folder operators: within, ancestorOf, glob (always slash-delimited)
7
+ * - LTree scalar auto-converts between slash-delimited file paths (API) and
8
+ * dot-delimited ltree (DB). External API always uses "/projects/alpha/docs".
9
+ * - Filter operators: within, ancestorOf, glob (all accept slash-delimited paths)
19
10
  *
20
11
  * @example
21
12
  * ```graphql
22
13
  * allFiles(where: { path: { within: "/projects/alpha" } }) {
23
- * nodes { pathFolder filename }
14
+ * nodes { path filename }
24
15
  * }
25
16
  * ```
26
17
  */
18
+ export declare const GraphileLtreePreset: GraphileConfig.Preset;
19
+ /**
20
+ * @deprecated Use GraphileLtreePreset instead. The folder field plugin is no
21
+ * longer needed — the LTree scalar now handles slash↔ltree conversion directly.
22
+ */
27
23
  export declare const GraphileFolderPreset: GraphileConfig.Preset;
28
- export default GraphileFolderPreset;
24
+ export default GraphileLtreePreset;
package/preset.js CHANGED
@@ -1,50 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GraphileFolderPreset = exports.GraphileLtreePreset = void 0;
4
- const connection_filter_operators_1 = require("./plugins/connection-filter-operators");
5
4
  const detect_ltree_1 = require("./plugins/detect-ltree");
6
- const folder_field_1 = require("./plugins/folder-field");
7
5
  const folder_filter_operators_1 = require("./plugins/folder-filter-operators");
8
6
  const ltree_codec_1 = require("./plugins/ltree-codec");
9
7
  /**
10
8
  * GraphileLtreePreset
11
9
  *
12
- * Base preset: ltree codec, detection, and raw ltree operators.
10
+ * Full ltree support with file-path syntax.
13
11
  *
14
- * Operators on LTree fields: isAncestorOf, isDescendantOf, matchesGlob
15
- * (accept dot-delimited or slash-delimited paths)
16
- */
17
- exports.GraphileLtreePreset = {
18
- plugins: [
19
- detect_ltree_1.LtreeExtensionDetectionPlugin,
20
- ltree_codec_1.LtreeCodecPlugin
21
- ],
22
- schema: {
23
- connectionFilterOperatorFactories: [
24
- (0, connection_filter_operators_1.createLtreeOperatorFactory)()
25
- ]
26
- }
27
- };
28
- /**
29
- * GraphileFolderPreset
30
- *
31
- * Folder-oriented layer on top of the base ltree preset.
32
- *
33
- * Adds:
34
- * - Virtual `{column}Folder` fields with slash-delimited paths
35
- * - Folder operators: within, ancestorOf, glob (always slash-delimited)
12
+ * - LTree scalar auto-converts between slash-delimited file paths (API) and
13
+ * dot-delimited ltree (DB). External API always uses "/projects/alpha/docs".
14
+ * - Filter operators: within, ancestorOf, glob (all accept slash-delimited paths)
36
15
  *
37
16
  * @example
38
17
  * ```graphql
39
18
  * allFiles(where: { path: { within: "/projects/alpha" } }) {
40
- * nodes { pathFolder filename }
19
+ * nodes { path filename }
41
20
  * }
42
21
  * ```
43
22
  */
44
- exports.GraphileFolderPreset = {
45
- extends: [exports.GraphileLtreePreset],
23
+ exports.GraphileLtreePreset = {
46
24
  plugins: [
47
- folder_field_1.LtreeFolderFieldPlugin
25
+ detect_ltree_1.LtreeExtensionDetectionPlugin,
26
+ ltree_codec_1.LtreeCodecPlugin
48
27
  ],
49
28
  schema: {
50
29
  connectionFilterOperatorFactories: [
@@ -52,4 +31,9 @@ exports.GraphileFolderPreset = {
52
31
  ]
53
32
  }
54
33
  };
55
- exports.default = exports.GraphileFolderPreset;
34
+ /**
35
+ * @deprecated Use GraphileLtreePreset instead. The folder field plugin is no
36
+ * longer needed — the LTree scalar now handles slash↔ltree conversion directly.
37
+ */
38
+ exports.GraphileFolderPreset = exports.GraphileLtreePreset;
39
+ exports.default = exports.GraphileLtreePreset;