From 6a96a0e477d75b171f358c4dd6415392974897a4 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Tue, 5 May 2026 07:20:37 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20add=20ltree-helpers=20module=20?= =?UTF-8?q?=E2=80=94=20slash/ltree/lquery=20path=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New @pgpm/ltree-helpers package with ltree_helpers schema containing: - ltree_helpers.to_path(text) -> ltree: slash path to ltree - ltree_helpers.to_slash(ltree) -> text: ltree to slash path - ltree_helpers.to_query(text) -> lquery: glob to lquery Includes deploy/verify/revert scripts, tests, and compiled SQL. --- packages/ltree-helpers/LICENSE | 22 +++++ packages/ltree-helpers/Makefile | 6 ++ packages/ltree-helpers/README.md | 75 +++++++++++++++ .../__tests__/ltree-helpers.test.ts | 92 +++++++++++++++++++ .../ltree_helpers/procedures/to_path.sql | 16 ++++ .../ltree_helpers/procedures/to_query.sql | 19 ++++ .../ltree_helpers/procedures/to_slash.sql | 15 +++ .../deploy/schemas/ltree_helpers/schema.sql | 14 +++ packages/ltree-helpers/jest.config.js | 15 +++ packages/ltree-helpers/package.json | 38 ++++++++ .../ltree-helpers/pgpm-ltree-helpers.control | 7 ++ packages/ltree-helpers/pgpm.plan | 8 ++ .../ltree_helpers/procedures/to_path.sql | 7 ++ .../ltree_helpers/procedures/to_query.sql | 7 ++ .../ltree_helpers/procedures/to_slash.sql | 7 ++ .../revert/schemas/ltree_helpers/schema.sql | 7 ++ .../sql/pgpm-ltree-helpers--0.21.0.sql | 22 +++++ .../ltree_helpers/procedures/to_path.sql | 7 ++ .../ltree_helpers/procedures/to_query.sql | 7 ++ .../ltree_helpers/procedures/to_slash.sql | 7 ++ .../verify/schemas/ltree_helpers/schema.sql | 7 ++ pnpm-lock.yaml | 10 ++ 22 files changed, 415 insertions(+) create mode 100644 packages/ltree-helpers/LICENSE create mode 100644 packages/ltree-helpers/Makefile create mode 100644 packages/ltree-helpers/README.md create mode 100644 packages/ltree-helpers/__tests__/ltree-helpers.test.ts create mode 100644 packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_path.sql create mode 100644 packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_query.sql create mode 100644 packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_slash.sql create mode 100644 packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql create mode 100644 packages/ltree-helpers/jest.config.js create mode 100644 packages/ltree-helpers/package.json create mode 100644 packages/ltree-helpers/pgpm-ltree-helpers.control create mode 100644 packages/ltree-helpers/pgpm.plan create mode 100644 packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_path.sql create mode 100644 packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_query.sql create mode 100644 packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_slash.sql create mode 100644 packages/ltree-helpers/revert/schemas/ltree_helpers/schema.sql create mode 100644 packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql create mode 100644 packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_path.sql create mode 100644 packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_query.sql create mode 100644 packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_slash.sql create mode 100644 packages/ltree-helpers/verify/schemas/ltree_helpers/schema.sql diff --git a/packages/ltree-helpers/LICENSE b/packages/ltree-helpers/LICENSE new file mode 100644 index 000000000..7b18c9183 --- /dev/null +++ b/packages/ltree-helpers/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2025 Dan Lynch +Copyright (c) 2025 Constructive + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/ltree-helpers/Makefile b/packages/ltree-helpers/Makefile new file mode 100644 index 000000000..ec5aa1b98 --- /dev/null +++ b/packages/ltree-helpers/Makefile @@ -0,0 +1,6 @@ +EXTENSION = pgpm-ltree-helpers +DATA = sql/pgpm-ltree-helpers--0.21.0.sql + +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) diff --git a/packages/ltree-helpers/README.md b/packages/ltree-helpers/README.md new file mode 100644 index 000000000..eceee5694 --- /dev/null +++ b/packages/ltree-helpers/README.md @@ -0,0 +1,75 @@ +# @pgpm/ltree-helpers + +

+ +

+ +

+ + + + + +

+ +Slash-path to ltree/lquery conversion helpers for PostgreSQL. + +## Overview + +`@pgpm/ltree-helpers` provides simple SQL functions for converting between user-facing slash-delimited paths (like `/projects/alpha/docs`) and PostgreSQL's `ltree`/`lquery` types. This keeps ltree as an implementation detail while exposing a familiar filesystem-style path API. + +## Features + +- **Slash to ltree**: Convert `/projects/alpha/docs` to `projects.alpha.docs` +- **ltree to slash**: Convert `projects.alpha.docs` to `/projects/alpha/docs` +- **Glob to lquery**: Convert `/projects/*/docs` to `projects.*.docs` and `/**` to `.*{1,}` +- **Pure SQL**: All functions are `IMMUTABLE STRICT` for maximum performance and plan caching +- **Own schema**: Functions live in the `ltree_helpers` schema, not in `public` + +## Installation + +```bash +cd packages/my-module +pgpm install @pgpm/ltree-helpers +``` + +## Usage + +```sql +-- Slash path to ltree +SELECT ltree_helpers.to_path('/projects/alpha/docs'); +-- => 'projects.alpha.docs'::ltree + +-- ltree to slash path +SELECT ltree_helpers.to_slash('projects.alpha.docs'::ltree); +-- => '/projects/alpha/docs' + +-- Glob to lquery (single-level wildcard) +SELECT ltree_helpers.to_query('/projects/*/docs'); +-- => 'projects.*.docs'::lquery + +-- Glob to lquery (recursive wildcard) +SELECT ltree_helpers.to_query('/projects/**'); +-- => 'projects.*{1,}'::lquery + +-- Use with ltree operators +SELECT * FROM files +WHERE path <@ ltree_helpers.to_path('/projects/alpha'); + +-- Glob matching +SELECT * FROM files +WHERE path ~ ltree_helpers.to_query('/projects/*/docs'); +``` + +## API + +| Function | Signature | Description | +|----------|-----------|-------------| +| `ltree_helpers.to_path` | `(text) -> ltree` | Slash path to ltree | +| `ltree_helpers.to_slash` | `(ltree) -> text` | ltree to slash path | +| `ltree_helpers.to_query` | `(text) -> lquery` | Glob pattern to lquery | + +## Dependencies + +- `ltree` (PostgreSQL contrib extension) +- `pgpm-verify` diff --git a/packages/ltree-helpers/__tests__/ltree-helpers.test.ts b/packages/ltree-helpers/__tests__/ltree-helpers.test.ts new file mode 100644 index 000000000..fe91f3200 --- /dev/null +++ b/packages/ltree-helpers/__tests__/ltree-helpers.test.ts @@ -0,0 +1,92 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +let pg: PgTestClient; +let teardown: () => Promise; + +beforeAll(async () => { + ({ pg, teardown } = await getConnections()); +}); + +afterAll(async () => { + await teardown(); +}); + +describe('to_path', () => { + it('converts slash path with leading slash', async () => { + const { to_path } = await pg.one( + `SELECT ltree_helpers.to_path($1)::text AS to_path`, + ['/projects/alpha/docs'] + ); + expect(to_path).toBe('projects.alpha.docs'); + }); + + it('converts slash path without leading slash', async () => { + const { to_path } = await pg.one( + `SELECT ltree_helpers.to_path($1)::text AS to_path`, + ['projects/alpha'] + ); + expect(to_path).toBe('projects.alpha'); + }); + + it('converts single segment', async () => { + const { to_path } = await pg.one( + `SELECT ltree_helpers.to_path($1)::text AS to_path`, + ['/root'] + ); + expect(to_path).toBe('root'); + }); +}); + +describe('to_slash', () => { + it('converts ltree to slash path', async () => { + const { to_slash } = await pg.one( + `SELECT ltree_helpers.to_slash($1::ltree) AS to_slash`, + ['projects.alpha.docs'] + ); + expect(to_slash).toBe('/projects/alpha/docs'); + }); + + it('converts single label', async () => { + const { to_slash } = await pg.one( + `SELECT ltree_helpers.to_slash($1::ltree) AS to_slash`, + ['root'] + ); + expect(to_slash).toBe('/root'); + }); +}); + +describe('to_query', () => { + it('converts single-level wildcard', async () => { + const { to_query } = await pg.one( + `SELECT ltree_helpers.to_query($1)::text AS to_query`, + ['/projects/*/docs'] + ); + expect(to_query).toBe('projects.*.docs'); + }); + + it('converts recursive wildcard', async () => { + const { to_query } = await pg.one( + `SELECT ltree_helpers.to_query($1)::text AS to_query`, + ['/projects/**'] + ); + expect(to_query).toBe('projects.*{1,}'); + }); + + it('converts exact path (no wildcards)', async () => { + const { to_query } = await pg.one( + `SELECT ltree_helpers.to_query($1)::text AS to_query`, + ['/projects/alpha'] + ); + expect(to_query).toBe('projects.alpha'); + }); +}); + +describe('roundtrip', () => { + it('to_path then to_slash returns original path', async () => { + const { roundtrip } = await pg.one( + `SELECT ltree_helpers.to_slash(ltree_helpers.to_path($1)) AS roundtrip`, + ['/projects/alpha/docs'] + ); + expect(roundtrip).toBe('/projects/alpha/docs'); + }); +}); diff --git a/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_path.sql b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_path.sql new file mode 100644 index 000000000..b6c167850 --- /dev/null +++ b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_path.sql @@ -0,0 +1,16 @@ +-- Deploy schemas/ltree_helpers/procedures/to_path to pg + +-- requires: schemas/ltree_helpers/schema + +BEGIN; + +-- Convert a slash-delimited path to an ltree value. +-- '/projects/alpha/docs' => 'projects.alpha.docs' +-- 'projects/alpha' => 'projects.alpha' +CREATE FUNCTION ltree_helpers.to_path( + slash_path text +) RETURNS ltree AS $$ + SELECT replace(ltrim(slash_path, '/'), '/', '.')::ltree; +$$ LANGUAGE sql IMMUTABLE STRICT; + +COMMIT; diff --git a/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_query.sql b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_query.sql new file mode 100644 index 000000000..ff75a6e23 --- /dev/null +++ b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_query.sql @@ -0,0 +1,19 @@ +-- Deploy schemas/ltree_helpers/procedures/to_query to pg + +-- requires: schemas/ltree_helpers/schema + +BEGIN; + +-- Convert a glob-style path to an lquery value. +-- '/projects/*/docs' => 'projects.*.docs' +-- '/projects/**' => 'projects.*{1,}' +CREATE FUNCTION ltree_helpers.to_query( + glob text +) RETURNS lquery AS $$ + SELECT replace( + replace(ltrim(glob, '/'), '**', '*{1,}'), + '/', '.' + )::lquery; +$$ LANGUAGE sql IMMUTABLE STRICT; + +COMMIT; diff --git a/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_slash.sql b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_slash.sql new file mode 100644 index 000000000..2e9715302 --- /dev/null +++ b/packages/ltree-helpers/deploy/schemas/ltree_helpers/procedures/to_slash.sql @@ -0,0 +1,15 @@ +-- Deploy schemas/ltree_helpers/procedures/to_slash to pg + +-- requires: schemas/ltree_helpers/schema + +BEGIN; + +-- Convert an ltree value to a slash-delimited path. +-- 'projects.alpha.docs' => '/projects/alpha/docs' +CREATE FUNCTION ltree_helpers.to_slash( + lpath ltree +) RETURNS text AS $$ + SELECT '/' || replace(lpath::text, '.', '/'); +$$ LANGUAGE sql IMMUTABLE STRICT; + +COMMIT; diff --git a/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql b/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql new file mode 100644 index 000000000..00b33fa9d --- /dev/null +++ b/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql @@ -0,0 +1,14 @@ +-- Deploy schemas/ltree_helpers/schema to pg + + +BEGIN; + +CREATE SCHEMA ltree_helpers; + +GRANT USAGE ON SCHEMA ltree_helpers TO public; + +ALTER DEFAULT PRIVILEGES IN SCHEMA ltree_helpers +GRANT EXECUTE ON FUNCTIONS +TO public; + +COMMIT; diff --git a/packages/ltree-helpers/jest.config.js b/packages/ltree-helpers/jest.config.js new file mode 100644 index 000000000..e20e7efb5 --- /dev/null +++ b/packages/ltree-helpers/jest.config.js @@ -0,0 +1,15 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + + // Match both __tests__ and colocated test files + testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], + + // Ignore build artifacts and type declarations + testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'], + modulePathIgnorePatterns: ['/dist/'], + watchPathIgnorePatterns: ['/dist/'], + + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], +}; diff --git a/packages/ltree-helpers/package.json b/packages/ltree-helpers/package.json new file mode 100644 index 000000000..c33131926 --- /dev/null +++ b/packages/ltree-helpers/package.json @@ -0,0 +1,38 @@ +{ + "name": "@pgpm/ltree-helpers", + "version": "0.21.0", + "description": "Slash-path to ltree/lquery conversion helpers for PostgreSQL", + "author": "Dan Lynch ", + "contributors": [ + "Constructive " + ], + "keywords": [ + "postgresql", + "pgpm", + "ltree", + "filesystem", + "paths" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "bundle": "pgpm package", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@pgpm/verify": "workspace:*" + }, + "devDependencies": { + "pgpm": "^4.16.6" + }, + "repository": { + "type": "git", + "url": "https://github.com/constructive-io/pgpm-modules" + }, + "homepage": "https://github.com/constructive-io/pgpm-modules", + "bugs": { + "url": "https://github.com/constructive-io/pgpm-modules/issues" + } +} \ No newline at end of file diff --git a/packages/ltree-helpers/pgpm-ltree-helpers.control b/packages/ltree-helpers/pgpm-ltree-helpers.control new file mode 100644 index 000000000..ac6d57435 --- /dev/null +++ b/packages/ltree-helpers/pgpm-ltree-helpers.control @@ -0,0 +1,7 @@ +# pgpm-ltree-helpers extension +comment = 'pgpm-ltree-helpers extension' +default_version = '0.21.0' +module_pathname = '$libdir/pgpm-ltree-helpers' +requires = 'plpgsql,ltree,pgpm-verify' +relocatable = false +superuser = false diff --git a/packages/ltree-helpers/pgpm.plan b/packages/ltree-helpers/pgpm.plan new file mode 100644 index 000000000..650d80561 --- /dev/null +++ b/packages/ltree-helpers/pgpm.plan @@ -0,0 +1,8 @@ +%syntax-version=1.0.0 +%project=pgpm-ltree-helpers +%uri=pgpm-ltree-helpers + +schemas/ltree_helpers/schema 2026-05-01T06:00:00Z devin # add schemas/ltree_helpers/schema +schemas/ltree_helpers/procedures/to_path [schemas/ltree_helpers/schema] 2026-05-01T06:00:00Z devin # add schemas/ltree_helpers/procedures/to_path +schemas/ltree_helpers/procedures/to_slash [schemas/ltree_helpers/schema] 2026-05-01T06:00:00Z devin # add schemas/ltree_helpers/procedures/to_slash +schemas/ltree_helpers/procedures/to_query [schemas/ltree_helpers/schema] 2026-05-01T06:00:00Z devin # add schemas/ltree_helpers/procedures/to_query diff --git a/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_path.sql b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_path.sql new file mode 100644 index 000000000..9cf9d89f6 --- /dev/null +++ b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_path.sql @@ -0,0 +1,7 @@ +-- Revert schemas/ltree_helpers/procedures/to_path from pg + +BEGIN; + +DROP FUNCTION ltree_helpers.to_path; + +COMMIT; diff --git a/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_query.sql b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_query.sql new file mode 100644 index 000000000..cc81a9dfd --- /dev/null +++ b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_query.sql @@ -0,0 +1,7 @@ +-- Revert schemas/ltree_helpers/procedures/to_query from pg + +BEGIN; + +DROP FUNCTION ltree_helpers.to_query; + +COMMIT; diff --git a/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_slash.sql b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_slash.sql new file mode 100644 index 000000000..d2fee251c --- /dev/null +++ b/packages/ltree-helpers/revert/schemas/ltree_helpers/procedures/to_slash.sql @@ -0,0 +1,7 @@ +-- Revert schemas/ltree_helpers/procedures/to_slash from pg + +BEGIN; + +DROP FUNCTION ltree_helpers.to_slash; + +COMMIT; diff --git a/packages/ltree-helpers/revert/schemas/ltree_helpers/schema.sql b/packages/ltree-helpers/revert/schemas/ltree_helpers/schema.sql new file mode 100644 index 000000000..2f76d5531 --- /dev/null +++ b/packages/ltree-helpers/revert/schemas/ltree_helpers/schema.sql @@ -0,0 +1,7 @@ +-- Revert schemas/ltree_helpers/schema from pg + +BEGIN; + +DROP SCHEMA ltree_helpers CASCADE; + +COMMIT; diff --git a/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql b/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql new file mode 100644 index 000000000..b151ea479 --- /dev/null +++ b/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql @@ -0,0 +1,22 @@ +\echo Use "CREATE EXTENSION pgpm-ltree-helpers" to load this file. \quit +CREATE SCHEMA ltree_helpers; + +GRANT USAGE ON SCHEMA ltree_helpers TO PUBLIC; + +ALTER DEFAULT PRIVILEGES IN SCHEMA ltree_helpers + GRANT EXECUTE ON FUNCTIONS TO PUBLIC; + +CREATE FUNCTION ltree_helpers.to_path(slash_path text) RETURNS ltree AS $EOFCODE$ + SELECT replace(ltrim(slash_path, '/'), '/', '.')::ltree; +$EOFCODE$ LANGUAGE sql IMMUTABLE STRICT; + +CREATE FUNCTION ltree_helpers.to_slash(lpath ltree) RETURNS text AS $EOFCODE$ + SELECT '/' || replace(lpath::text, '.', '/'); +$EOFCODE$ LANGUAGE sql IMMUTABLE STRICT; + +CREATE FUNCTION ltree_helpers.to_query(glob text) RETURNS lquery AS $EOFCODE$ + SELECT replace( + replace(ltrim(glob, '/'), '**', '*{1,}'), + '/', '.' + )::lquery; +$EOFCODE$ LANGUAGE sql IMMUTABLE STRICT; \ No newline at end of file diff --git a/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_path.sql b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_path.sql new file mode 100644 index 000000000..28a480f4e --- /dev/null +++ b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_path.sql @@ -0,0 +1,7 @@ +-- Verify schemas/ltree_helpers/procedures/to_path on pg + +BEGIN; + +SELECT verify_function ('ltree_helpers.to_path'); + +ROLLBACK; diff --git a/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_query.sql b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_query.sql new file mode 100644 index 000000000..ad2e13056 --- /dev/null +++ b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_query.sql @@ -0,0 +1,7 @@ +-- Verify schemas/ltree_helpers/procedures/to_query on pg + +BEGIN; + +SELECT verify_function ('ltree_helpers.to_query'); + +ROLLBACK; diff --git a/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_slash.sql b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_slash.sql new file mode 100644 index 000000000..2957c7014 --- /dev/null +++ b/packages/ltree-helpers/verify/schemas/ltree_helpers/procedures/to_slash.sql @@ -0,0 +1,7 @@ +-- Verify schemas/ltree_helpers/procedures/to_slash on pg + +BEGIN; + +SELECT verify_function ('ltree_helpers.to_slash'); + +ROLLBACK; diff --git a/packages/ltree-helpers/verify/schemas/ltree_helpers/schema.sql b/packages/ltree-helpers/verify/schemas/ltree_helpers/schema.sql new file mode 100644 index 000000000..9f8dbe338 --- /dev/null +++ b/packages/ltree-helpers/verify/schemas/ltree_helpers/schema.sql @@ -0,0 +1,7 @@ +-- Verify schemas/ltree_helpers/schema on pg + +BEGIN; + +SELECT verify_schema ('ltree_helpers'); + +ROLLBACK; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca756ad1e..305ea4aff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,6 +191,16 @@ importers: specifier: ^4.16.6 version: 4.16.6(@dataplan/json@1.0.0(grafast@1.0.0(graphql@16.13.0)))(@dataplan/pg@1.0.0(@dataplan/json@1.0.0(grafast@1.0.0(graphql@16.13.0)))(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0)(pg-sql2@5.0.0)(pg@8.20.0))(@types/node@22.19.17)(grafserv@1.0.0(@types/node@22.19.17)(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0))(graphile-build@5.0.0(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0))(pg-sql2@5.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tamedevil@0.1.0)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0) + packages/ltree-helpers: + dependencies: + '@pgpm/verify': + specifier: workspace:* + version: link:../verify + devDependencies: + pgpm: + specifier: ^4.16.6 + version: 4.16.6(@dataplan/json@1.0.0(grafast@1.0.0(graphql@16.13.0)))(@dataplan/pg@1.0.0(@dataplan/json@1.0.0(grafast@1.0.0(graphql@16.13.0)))(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0)(pg-sql2@5.0.0)(pg@8.20.0))(@types/node@22.19.17)(grafserv@1.0.0(@types/node@22.19.17)(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0))(graphile-build@5.0.0(grafast@1.0.0(graphql@16.13.0))(graphile-config@1.0.0)(graphql@16.13.0))(pg-sql2@5.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tamedevil@0.1.0)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0) + packages/measurements: dependencies: '@pgpm/verify': From e74ec7b23925712502e2c4650b02cf942f1c637a Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Tue, 5 May 2026 07:27:24 +0000 Subject: [PATCH 2/2] fix: use authenticated,anonymous grants (not public) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches jwt-claims/stamps pattern — functions called from RLS policies need to be accessible to authenticated and anonymous roles. --- .../ltree-helpers/deploy/schemas/ltree_helpers/schema.sql | 5 +++-- packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql b/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql index 00b33fa9d..199cf6b5e 100644 --- a/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql +++ b/packages/ltree-helpers/deploy/schemas/ltree_helpers/schema.sql @@ -5,10 +5,11 @@ BEGIN; CREATE SCHEMA ltree_helpers; -GRANT USAGE ON SCHEMA ltree_helpers TO public; +GRANT USAGE ON SCHEMA ltree_helpers +TO authenticated, anonymous; ALTER DEFAULT PRIVILEGES IN SCHEMA ltree_helpers GRANT EXECUTE ON FUNCTIONS -TO public; +TO authenticated; COMMIT; diff --git a/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql b/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql index b151ea479..b1265b3ed 100644 --- a/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql +++ b/packages/ltree-helpers/sql/pgpm-ltree-helpers--0.21.0.sql @@ -1,10 +1,10 @@ \echo Use "CREATE EXTENSION pgpm-ltree-helpers" to load this file. \quit CREATE SCHEMA ltree_helpers; -GRANT USAGE ON SCHEMA ltree_helpers TO PUBLIC; +GRANT USAGE ON SCHEMA ltree_helpers TO authenticated, anonymous; ALTER DEFAULT PRIVILEGES IN SCHEMA ltree_helpers - GRANT EXECUTE ON FUNCTIONS TO PUBLIC; + GRANT EXECUTE ON FUNCTIONS TO authenticated; CREATE FUNCTION ltree_helpers.to_path(slash_path text) RETURNS ltree AS $EOFCODE$ SELECT replace(ltrim(slash_path, '/'), '/', '.')::ltree;