Skip to content

Commit bfd89c0

Browse files
authored
Merge pull request #79 from constructive-io/feat/inflection-db-readme
docs: add README for inflection-db
2 parents 05fd01b + 3a45073 commit bfd89c0

1 file changed

Lines changed: 300 additions & 0 deletions

File tree

packages/inflection-db/README.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# @pgpm/inflection-db
2+
3+
<p align="center" width="100%">
4+
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
5+
</p>
6+
7+
<p align="center" width="100%">
8+
<a href="https://github.com/constructive-io/pgpm-modules/actions/workflows/ci.yml">
9+
<img height="20" src="https://github.com/constructive-io/pgpm-modules/actions/workflows/ci.yml/badge.svg" />
10+
</a>
11+
<a href="https://github.com/constructive-io/pgpm-modules/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
12+
<a href="https://www.npmjs.com/package/@pgpm/inflection-db"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/pgpm-modules?filename=packages%2Finflection-db%2Fpackage.json"/></a>
13+
</p>
14+
15+
Database-level string inflection and naming convention utilities for PostgreSQL
16+
17+
## Overview
18+
19+
`@pgpm/inflection-db` provides a set of deterministic naming convention functions for PostgreSQL that build on top of `@pgpm/inflection`. These functions generate consistent, 63-character-safe identifiers for tables, columns, indexes, constraints, and schemas — ensuring that all generated names respect PostgreSQL's maximum identifier length. This package is essential for code generation systems, schema introspection, and any tooling that programmatically creates database objects.
20+
21+
## Features
22+
23+
- **Identifier Safety**: All names are automatically truncated to 63 characters (PostgreSQL's max identifier length)
24+
- **Table Naming**: Pluralized, underscored table names from any input format
25+
- **Field Naming**: Underscored column/field names
26+
- **Index Naming**: Consistent `{table}_{fields}_idx` patterns
27+
- **Constraint Naming**: Foreign key (`_fkey`), primary key (`_pkey`), unique (`_key`), and check (`_chk`) constraint names
28+
- **Schema Naming**: Dashed schema names from component parts
29+
- **Namespace Naming**: Underscored namespace identifiers from arrays
30+
- **Overloaded Signatures**: Key functions accept both `text` and `text[]` inputs
31+
- **Built on @pgpm/inflection**: Leverages proven pluralization, singularization, and case conversion
32+
33+
## Installation
34+
35+
If you have `pgpm` installed:
36+
37+
```bash
38+
pgpm install @pgpm/inflection-db
39+
pgpm deploy
40+
```
41+
42+
This is a quick way to get started. The sections below provide more detailed installation options.
43+
44+
### Prerequisites
45+
46+
```bash
47+
# Install pgpm CLI
48+
npm install -g pgpm
49+
50+
# Start local Postgres (via Docker) and export env vars
51+
pgpm docker start
52+
eval "$(pgpm env)"
53+
```
54+
55+
> **Tip:** Already running Postgres? Skip the Docker step and just export your `PG*` environment variables.
56+
57+
### **Add to an Existing Package**
58+
59+
```bash
60+
# 1. Install the package
61+
pgpm install @pgpm/inflection-db
62+
63+
# 2. Deploy locally
64+
pgpm deploy
65+
```
66+
67+
### **Add to a New Project**
68+
69+
```bash
70+
# 1. Create a workspace
71+
pgpm init workspace
72+
73+
# 2. Create your first module
74+
cd my-workspace
75+
pgpm init
76+
77+
# 3. Install a package
78+
cd packages/my-module
79+
pgpm install @pgpm/inflection-db
80+
81+
# 4. Deploy everything
82+
pgpm deploy --createdb --database mydb1
83+
```
84+
85+
## Core Functions
86+
87+
### inflection_db.get_identifier(text)
88+
89+
Base wrapper that truncates any string to PostgreSQL's 63-character identifier limit.
90+
91+
```sql
92+
SELECT inflection_db.get_identifier('some_very_long_identifier_name');
93+
-- some_very_long_identifier_name (truncated at 63 chars if needed)
94+
```
95+
96+
### inflection_db.get_table_name(text) / get_table_name(text[])
97+
98+
Generate a pluralized, underscored table name.
99+
100+
```sql
101+
SELECT inflection_db.get_table_name('UserProfile');
102+
-- user_profiles
103+
104+
SELECT inflection_db.get_table_name('BlogPost');
105+
-- blog_posts
106+
107+
SELECT inflection_db.get_table_name(ARRAY['user', 'profile']);
108+
-- user_profiles
109+
```
110+
111+
### inflection_db.get_table_singular_name(text)
112+
113+
Generate a singularized, underscored table name.
114+
115+
```sql
116+
SELECT inflection_db.get_table_singular_name('UserProfiles');
117+
-- user_profile
118+
119+
SELECT inflection_db.get_table_singular_name('BlogPosts');
120+
-- blog_post
121+
```
122+
123+
### inflection_db.get_table_plural_name(text)
124+
125+
Generate a pluralized, underscored table name (alias-style for explicit pluralization).
126+
127+
```sql
128+
SELECT inflection_db.get_table_plural_name('UserProfile');
129+
-- user_profiles
130+
```
131+
132+
### inflection_db.get_field_name(text) / get_field_name(text[])
133+
134+
Generate an underscored field/column name.
135+
136+
```sql
137+
SELECT inflection_db.get_field_name('firstName');
138+
-- first_name
139+
140+
SELECT inflection_db.get_field_name('EmailAddress');
141+
-- email_address
142+
143+
SELECT inflection_db.get_field_name(ARRAY['user', 'email']);
144+
-- user_email
145+
```
146+
147+
### inflection_db.get_identifier_name(text) / get_identifier_name(text[])
148+
149+
Generate a generic underscored identifier.
150+
151+
```sql
152+
SELECT inflection_db.get_identifier_name('MyCustomType');
153+
-- my_custom_type
154+
155+
SELECT inflection_db.get_identifier_name(ARRAY['app', 'settings']);
156+
-- app_settings
157+
```
158+
159+
### inflection_db.get_schema_name(text[])
160+
161+
Generate a dashed schema name from component parts.
162+
163+
```sql
164+
SELECT inflection_db.get_schema_name(ARRAY['my', 'app', 'schema']);
165+
-- my-app-schema
166+
```
167+
168+
### inflection_db.get_namespace_name(text[])
169+
170+
Generate an underscored namespace name from component parts.
171+
172+
```sql
173+
SELECT inflection_db.get_namespace_name(ARRAY['my', 'app']);
174+
-- my_app
175+
```
176+
177+
### inflection_db.get_foreign_key_field_name(text)
178+
179+
Generate a foreign key column name from a referenced table.
180+
181+
```sql
182+
SELECT inflection_db.get_foreign_key_field_name('UserProfile');
183+
-- user_profile_id
184+
```
185+
186+
### inflection_db.get_index_name(text, text[])
187+
188+
Generate an index name following the `{table}_{fields}_idx` pattern.
189+
190+
```sql
191+
SELECT inflection_db.get_index_name('UserProfile', ARRAY['email']);
192+
-- user_profiles_email_idx
193+
194+
SELECT inflection_db.get_index_name('BlogPost', ARRAY['author_id', 'created_at']);
195+
-- blog_posts_author_id_created_at_idx
196+
```
197+
198+
### inflection_db.get_primary_key_index_name(text)
199+
200+
Generate a primary key constraint name following the `{table}_pkey` pattern.
201+
202+
```sql
203+
SELECT inflection_db.get_primary_key_index_name('UserProfile');
204+
-- user_profiles_pkey
205+
```
206+
207+
### inflection_db.get_foreign_key_index_name(text, text)
208+
209+
Generate a foreign key constraint name following the `{table}_{field}_fkey` pattern.
210+
211+
```sql
212+
SELECT inflection_db.get_foreign_key_index_name('BlogPost', 'author_id');
213+
-- blog_posts_author_id_fkey
214+
```
215+
216+
### inflection_db.get_unique_index_name(text, text[])
217+
218+
Generate a unique constraint name following the `{table}_{fields}_key` pattern.
219+
220+
```sql
221+
SELECT inflection_db.get_unique_index_name('UserProfile', ARRAY['email']);
222+
-- user_profiles_email_key
223+
```
224+
225+
### inflection_db.get_check_constraint_name(text, text[])
226+
227+
Generate a check constraint name following the `{table}_{fields}_chk` pattern.
228+
229+
```sql
230+
SELECT inflection_db.get_check_constraint_name('UserProfile', ARRAY['age']);
231+
-- user_profiles_age_chk
232+
```
233+
234+
## Usage Examples
235+
236+
### Schema Generation
237+
238+
```sql
239+
-- Generate consistent names for a complete table setup
240+
SELECT inflection_db.get_table_name('BlogPost') AS table_name,
241+
inflection_db.get_primary_key_index_name('BlogPost') AS pk_name,
242+
inflection_db.get_foreign_key_field_name('User') AS fk_field,
243+
inflection_db.get_foreign_key_index_name('BlogPost', 'user_id') AS fk_name,
244+
inflection_db.get_index_name('BlogPost', ARRAY['created_at']) AS idx_name,
245+
inflection_db.get_unique_index_name('BlogPost', ARRAY['slug']) AS uq_name;
246+
247+
-- table_name: blog_posts
248+
-- pk_name: blog_posts_pkey
249+
-- fk_field: user_id
250+
-- fk_name: blog_posts_user_id_fkey
251+
-- idx_name: blog_posts_created_at_idx
252+
-- uq_name: blog_posts_slug_key
253+
```
254+
255+
### Dynamic DDL Generation
256+
257+
```sql
258+
-- Build a CREATE TABLE with consistent naming
259+
DO $$
260+
DECLARE
261+
tbl text := inflection_db.get_table_name('UserProfile');
262+
pk text := inflection_db.get_primary_key_index_name('UserProfile');
263+
BEGIN
264+
EXECUTE format(
265+
'CREATE TABLE %I (id uuid CONSTRAINT %I PRIMARY KEY DEFAULT gen_random_uuid())',
266+
tbl, pk
267+
);
268+
END $$;
269+
```
270+
271+
## Testing
272+
273+
```bash
274+
pnpm test
275+
```
276+
277+
## Dependencies
278+
279+
- `@pgpm/inflection`: String inflection utilities (pluralization, case conversion, etc.)
280+
- `@pgpm/verify`: Verification utilities
281+
282+
## Related Packages
283+
284+
- [`@pgpm/inflection`](https://www.npmjs.com/package/@pgpm/inflection): The underlying string transformation library this package builds on
285+
286+
## Related Tooling
287+
288+
* [pgpm](https://github.com/constructive-io/constructive/tree/main/packages/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
289+
* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/packages/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
290+
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/packages/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
291+
* [graphile-test](https://github.com/constructive-io/constructive/tree/main/packages/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
292+
* [pgsql-parser](https://github.com/constructive-io/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
293+
* [libpg-query-node](https://github.com/constructive-io/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
294+
* [pg-proto-parser](https://github.com/constructive-io/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
295+
296+
## Disclaimer
297+
298+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
299+
300+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.

0 commit comments

Comments
 (0)