|
| 1 | +--- |
| 2 | +name: constructive-env |
| 3 | +description: Unified environment configuration for Constructive and PGPM projects — typed defaults, env var parsing, config file discovery, and hierarchical option merging. Use when asked to "configure environment", "set env vars", "manage defaults", "use getEnvOptions", "configure database connection", "set up server config", "configure SMTP", "configure CDN", "manage configuration hierarchy", or when working with @pgpmjs/env or @constructive-io/graphql-env. |
| 4 | +compatibility: Node.js 18+, @pgpmjs/env, @constructive-io/graphql-env |
| 5 | +metadata: |
| 6 | + author: constructive-io |
| 7 | + version: "1.0.0" |
| 8 | +--- |
| 9 | + |
| 10 | +# Constructive Environment Configuration |
| 11 | + |
| 12 | +Unified, type-safe environment configuration for all Constructive and PGPM projects. Two packages work together to provide a clean, hierarchical configuration system with sensible defaults. |
| 13 | + |
| 14 | +## When to Apply |
| 15 | + |
| 16 | +Use this skill when: |
| 17 | +- Configuring database connections, server settings, CDN, SMTP, or jobs programmatically |
| 18 | +- Understanding how environment variables map to typed configuration objects |
| 19 | +- Setting up `pgpm.json` config files |
| 20 | +- Writing code that needs to read environment-aware configuration |
| 21 | +- Choosing between `@pgpmjs/env` and `@constructive-io/graphql-env` |
| 22 | +- Understanding the merge hierarchy (defaults → config file → env vars → overrides) |
| 23 | + |
| 24 | +## Architecture Overview |
| 25 | + |
| 26 | +Two packages form a layered configuration system: |
| 27 | + |
| 28 | +``` |
| 29 | +@pgpmjs/types → PgpmOptions interface + pgpmDefaults |
| 30 | + ↓ |
| 31 | +@pgpmjs/env → getEnvVars() + loadConfigSync() + getEnvOptions() |
| 32 | + ↓ (extends) |
| 33 | +@constructive-io/graphql-types → ConstructiveOptions interface + constructiveDefaults |
| 34 | + ↓ |
| 35 | +@constructive-io/graphql-env → getGraphQLEnvVars() + getEnvOptions() |
| 36 | +``` |
| 37 | + |
| 38 | +### Which Package to Import |
| 39 | + |
| 40 | +| You're working in... | Import from | |
| 41 | +|----------------------|-------------| |
| 42 | +| PGPM packages (`pgpm/*`) | `@pgpmjs/env` | |
| 43 | +| PostgreSQL tools (`postgres/*`) | `@pgpmjs/env` or `pg-env` | |
| 44 | +| GraphQL server, explorer, codegen | `@constructive-io/graphql-env` | |
| 45 | +| Constructive CLI commands | `@constructive-io/graphql-env` | |
| 46 | +| Job functions, SMTP | `@pgpmjs/env` | |
| 47 | +| Tests (pgsql-test based) | `@pgpmjs/env` (via `getConnEnvOptions`) | |
| 48 | + |
| 49 | +**Rule of thumb:** If your code needs GraphQL/Graphile/API options, use `@constructive-io/graphql-env`. Otherwise, use `@pgpmjs/env`. |
| 50 | + |
| 51 | +## Merge Hierarchy |
| 52 | + |
| 53 | +Options are merged in this order (later overrides earlier): |
| 54 | + |
| 55 | +1. **Defaults** — `pgpmDefaults` (+ `constructiveGraphqlDefaults` for GraphQL layer) |
| 56 | +2. **Config file** — `pgpm.json` or `pgpm.config.js` discovered by walking up directories |
| 57 | +3. **Environment variables** — parsed from `process.env` |
| 58 | +4. **Runtime overrides** — passed programmatically to `getEnvOptions()` |
| 59 | + |
| 60 | +Arrays are **replaced**, not concatenated — the later source wins completely. |
| 61 | + |
| 62 | +## Quick Start |
| 63 | + |
| 64 | +### Core PGPM Options |
| 65 | + |
| 66 | +```typescript |
| 67 | +import { getEnvOptions } from '@pgpmjs/env'; |
| 68 | + |
| 69 | +// Get fully merged options (defaults + config + env + overrides) |
| 70 | +const opts = getEnvOptions(); |
| 71 | + |
| 72 | +// With runtime overrides |
| 73 | +const opts = getEnvOptions({ |
| 74 | + pg: { database: 'mydb' }, |
| 75 | + deployment: { fast: true } |
| 76 | +}); |
| 77 | + |
| 78 | +// With custom working directory and env object |
| 79 | +const opts = getEnvOptions({}, '/path/to/project', process.env); |
| 80 | +``` |
| 81 | + |
| 82 | +### Constructive Options (includes GraphQL) |
| 83 | + |
| 84 | +```typescript |
| 85 | +import { getEnvOptions } from '@constructive-io/graphql-env'; |
| 86 | + |
| 87 | +// Gets everything: PGPM defaults + GraphQL defaults + config + env + overrides |
| 88 | +const opts = getEnvOptions({ |
| 89 | + pg: { database: 'constructive' }, |
| 90 | + api: { isPublic: true } |
| 91 | +}); |
| 92 | +``` |
| 93 | + |
| 94 | +### Specialized Accessors |
| 95 | + |
| 96 | +```typescript |
| 97 | +import { getConnEnvOptions, getDeploymentEnvOptions } from '@pgpmjs/env'; |
| 98 | + |
| 99 | +// Database connection options with roles resolved |
| 100 | +const connOpts = getConnEnvOptions({ prefix: 'test-' }); |
| 101 | + |
| 102 | +// Deployment options only |
| 103 | +const deployOpts = getDeploymentEnvOptions({ fast: true }); |
| 104 | +``` |
| 105 | + |
| 106 | +## Type System |
| 107 | + |
| 108 | +### PgpmOptions (core) |
| 109 | + |
| 110 | +```typescript |
| 111 | +interface PgpmOptions { |
| 112 | + db?: Partial<PgTestConnectionOptions>; // DB config, roles, connections |
| 113 | + pg?: Partial<PgConfig>; // PostgreSQL connection (host, port, user, etc.) |
| 114 | + server?: ServerOptions; // HTTP server (host, port, trustProxy, origin) |
| 115 | + cdn?: CDNOptions; // S3/MinIO file storage |
| 116 | + deployment?: DeploymentOptions; // Migration deployment settings |
| 117 | + migrations?: MigrationOptions; // Code generation settings |
| 118 | + jobs?: JobsConfig; // Job worker/scheduler config |
| 119 | + errorOutput?: ErrorOutputOptions; // Error formatting |
| 120 | + smtp?: SmtpOptions; // Email configuration |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### ConstructiveOptions (extends PgpmOptions) |
| 125 | + |
| 126 | +```typescript |
| 127 | +interface ConstructiveOptions extends PgpmOptions { |
| 128 | + graphile?: GraphileOptions; // PostGraphile schema config |
| 129 | + features?: GraphileFeatureOptions; // Feature flags (inflection, PostGIS) |
| 130 | + api?: ApiOptions; // API routing (public/admin, roles, schemas) |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +## Utility Functions |
| 135 | + |
| 136 | +### Env Var Parsers |
| 137 | + |
| 138 | +```typescript |
| 139 | +import { parseEnvBoolean, parseEnvNumber } from '@pgpmjs/env'; |
| 140 | + |
| 141 | +parseEnvBoolean('true'); // true |
| 142 | +parseEnvBoolean('1'); // true |
| 143 | +parseEnvBoolean('yes'); // true |
| 144 | +parseEnvBoolean('false'); // false |
| 145 | +parseEnvBoolean(undefined); // undefined |
| 146 | + |
| 147 | +parseEnvNumber('5432'); // 5432 |
| 148 | +parseEnvNumber('invalid'); // undefined |
| 149 | +``` |
| 150 | + |
| 151 | +### Node Environment |
| 152 | + |
| 153 | +```typescript |
| 154 | +import { getNodeEnv } from '@pgpmjs/env'; |
| 155 | + |
| 156 | +const env = getNodeEnv(); // 'development' | 'production' | 'test' |
| 157 | +``` |
| 158 | + |
| 159 | +### Config File Discovery |
| 160 | + |
| 161 | +```typescript |
| 162 | +import { loadConfigSync, resolvePgpmPath } from '@pgpmjs/env'; |
| 163 | + |
| 164 | +// Load pgpm.json by walking up directory tree |
| 165 | +const config = loadConfigSync('/path/to/project'); |
| 166 | + |
| 167 | +// Find the pgpm config file path |
| 168 | +const pgpmPath = resolvePgpmPath('/path/to/project'); |
| 169 | +``` |
| 170 | + |
| 171 | +## Anti-Patterns |
| 172 | + |
| 173 | +**Never do this:** |
| 174 | + |
| 175 | +```typescript |
| 176 | +// BAD: Manual env var access scattered throughout code |
| 177 | +const host = process.env.PGHOST || 'localhost'; |
| 178 | +const port = parseInt(process.env.PGPORT || '5432'); |
| 179 | +const usePublic = process.env.API_IS_PUBLIC === 'true'; |
| 180 | +``` |
| 181 | + |
| 182 | +**Do this instead:** |
| 183 | + |
| 184 | +```typescript |
| 185 | +// GOOD: Use the unified configuration system |
| 186 | +import { getEnvOptions } from '@pgpmjs/env'; |
| 187 | +const opts = getEnvOptions(); |
| 188 | +const { host, port } = opts.pg; |
| 189 | +``` |
| 190 | + |
| 191 | +```typescript |
| 192 | +// GOOD: For Constructive/GraphQL packages |
| 193 | +import { getEnvOptions } from '@constructive-io/graphql-env'; |
| 194 | +const opts = getEnvOptions(); |
| 195 | +const { isPublic } = opts.api; |
| 196 | +``` |
| 197 | + |
| 198 | +## Reference Guide |
| 199 | + |
| 200 | +| Reference | Topic | Consult When | |
| 201 | +|-----------|-------|--------------| |
| 202 | +| [references/env-vars.md](references/env-vars.md) | Complete environment variables reference | Looking up specific env var names, defaults, and which config key they map to | |
| 203 | +| [references/defaults.md](references/defaults.md) | Default values for all configuration | Understanding what values are used when nothing is overridden | |
| 204 | +| [references/config-file.md](references/config-file.md) | pgpm.json configuration | Setting up project-level configuration files | |
| 205 | + |
| 206 | +## Cross-References |
| 207 | + |
| 208 | +- `pgpm` skill (`references/env.md`) — The `pgpm env` CLI command for shell-level env var management |
| 209 | +- `constructive-server-config` skill — Server-specific env vars and startup configuration |
| 210 | +- `constructive-deployment` skill — Deployment-specific configuration |
| 211 | +- `constructive-functions` skill — Using `parseEnvBoolean` in Knative functions |
0 commit comments