pipework 0.15.0 → 0.17.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/CHANGELOG.md +27 -1
- package/REFERENCE.md +191 -2
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +12 -10
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/core/config/base-dir.d.ts +6 -0
- package/dist/core/config/base-dir.d.ts.map +1 -0
- package/dist/core/config/base-dir.js +21 -0
- package/dist/core/config/base-dir.js.map +1 -0
- package/dist/core/config/discover.d.ts.map +1 -1
- package/dist/core/config/discover.js +2 -15
- package/dist/core/config/discover.js.map +1 -1
- package/dist/core/config/env-types.d.ts +70 -0
- package/dist/core/config/env-types.d.ts.map +1 -1
- package/dist/core/config/env.d.ts.map +1 -1
- package/dist/core/config/env.js +10 -6
- package/dist/core/config/env.js.map +1 -1
- package/dist/core/config/import-config.d.ts +3 -0
- package/dist/core/config/import-config.d.ts.map +1 -0
- package/dist/core/config/import-config.js +34 -0
- package/dist/core/config/import-config.js.map +1 -0
- package/dist/core/config/load.d.ts +7 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +15 -5
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/resolve.d.ts.map +1 -1
- package/dist/core/config/resolve.js +23 -1
- package/dist/core/config/resolve.js.map +1 -1
- package/dist/core/config/schema.d.ts +3 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +7 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/statement-observer.d.ts +48 -0
- package/dist/core/config/statement-observer.d.ts.map +1 -0
- package/dist/core/config/statement-observer.js +2 -0
- package/dist/core/config/statement-observer.js.map +1 -0
- package/dist/core/pipework.d.ts +19 -0
- package/dist/core/pipework.d.ts.map +1 -1
- package/dist/core/pipework.js +25 -0
- package/dist/core/pipework.js.map +1 -1
- package/dist/core/surface/types.d.ts +1 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/core/validation/queue-tables.d.ts +8 -0
- package/dist/core/validation/queue-tables.d.ts.map +1 -0
- package/dist/core/validation/queue-tables.js +53 -0
- package/dist/core/validation/queue-tables.js.map +1 -0
- package/dist/core/validation/rls-role.d.ts +16 -0
- package/dist/core/validation/rls-role.d.ts.map +1 -0
- package/dist/core/validation/rls-role.js +54 -0
- package/dist/core/validation/rls-role.js.map +1 -0
- package/dist/core/validation/startup.d.ts +2 -0
- package/dist/core/validation/startup.d.ts.map +1 -1
- package/dist/core/validation/startup.js +6 -0
- package/dist/core/validation/startup.js.map +1 -1
- package/dist/data/db/instrumentation.d.ts +17 -1
- package/dist/data/db/instrumentation.d.ts.map +1 -1
- package/dist/data/db/instrumentation.js +31 -1
- package/dist/data/db/instrumentation.js.map +1 -1
- package/dist/data/db/pool.d.ts +16 -0
- package/dist/data/db/pool.d.ts.map +1 -1
- package/dist/data/db/pool.js +18 -2
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/raw-client-warning.d.ts +19 -0
- package/dist/data/db/raw-client-warning.d.ts.map +1 -0
- package/dist/data/db/raw-client-warning.js +32 -0
- package/dist/data/db/raw-client-warning.js.map +1 -0
- package/dist/data/migrate/generate.d.ts +1 -1
- package/dist/data/migrate/generate.d.ts.map +1 -1
- package/dist/data/migrate/generate.js +15 -8
- package/dist/data/migrate/generate.js.map +1 -1
- package/dist/data/query/postgres-js/driver-error-boundary.d.ts +47 -0
- package/dist/data/query/postgres-js/driver-error-boundary.d.ts.map +1 -0
- package/dist/data/query/postgres-js/driver-error-boundary.js +144 -0
- package/dist/data/query/postgres-js/driver-error-boundary.js.map +1 -0
- package/dist/data/query/postgres-js/session.d.ts.map +1 -1
- package/dist/data/query/postgres-js/session.js +15 -12
- package/dist/data/query/postgres-js/session.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lint/resolver.d.ts.map +1 -1
- package/dist/lint/resolver.js +2 -11
- package/dist/lint/resolver.js.map +1 -1
- package/dist/test/plugin.d.ts +2 -1
- package/dist/test/plugin.d.ts.map +1 -1
- package/dist/test/plugin.js +13 -5
- package/dist/test/plugin.js.map +1 -1
- package/dist/test/setup.d.ts.map +1 -1
- package/dist/test/setup.js +33 -0
- package/dist/test/setup.js.map +1 -1
- package/dist/test/vitest-workspace.d.ts.map +1 -1
- package/dist/test/vitest-workspace.js +2 -11
- package/dist/test/vitest-workspace.js.map +1 -1
- package/package.json +1 -1
- package/dist/REFERENCE.md +0 -1033
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
- **Feature: `observability.onStatement` — a caller-supplied observer that fires once per statement written to the wire, on every connection the manifold owns.** The callback receives `{ connectionId, query, parameters }` and is pure observation: the return value is discarded, the statement is never mutated, and nothing is installed when the option is unset. It exists for exact wire-level statement accounting, which no application-level hook can do — the driver issues its own `BEGIN`/`SAVEPOINT`/`COMMIT`/`ROLLBACK`, and those are observed too.
|
|
6
|
+
- **Honored on both connection arms.** Pooled connections (`createManagedConnection`) *and* the test harness's own connections — both the per-test rolled-back connection and the setup connection behind `useSetupDb()`. The test harness builds its postgres.js clients directly rather than through the pool, so a config-only hook would have been silently dead under `pipework test`; that arm is wired explicitly and covered by its own tests.
|
|
7
|
+
- **Composes with `logAllQueries`.** Either alone installs the driver hook; with both configured both fire. Query logging keeps its 500-character truncation; the observer receives the query untruncated, because a counter must not see mangled SQL.
|
|
8
|
+
- **`connectionId` is the driver's process-local connection ordinal, not the PostgreSQL backend pid.** Unique and stable per connection for that connection's lifetime within the process, meaningless across processes. Correct for per-connection accounting — every statement of one transaction carries the same id, which is asserted by a test — and wrong for correlating with `pg_stat_activity`.
|
|
9
|
+
- **Counts wire truth, not application intent.** Two things a total will include that callers should expect: driver-issued transaction control, and one `pg_catalog.pg_type` lookup per connection the first time that connection is used.
|
|
10
|
+
- Admitted through zod with `z.custom`, deliberately not `z.function()` — in zod 4 the latter returns a validating wrapper, so the parsed value is not the function the caller passed (identity is lost) and every call pays argument validation, on a hook that fires once per statement.
|
|
11
|
+
|
|
12
|
+
- **Observation adds no leak surface: a driver error boundary keeps the observer from widening what query errors serialize.** postgres.js defines `stack`/`query`/`parameters`/`args`/`types` on every query error and marks them **enumerable** exactly when its `debug` option is truthy — and installing an observer requires turning `debug` on. Without a boundary, merely observing statements would start leaking bind parameters into every log drain that serializes errors. Errors from observed connections are now put back to their unobserved shape: observed and unobserved errors have identical own-key sets, and the values stay reachable by property access so debugging is unaffected. `instanceof`, `message`, `code` and every server field survive. Applies to every statement path pipework mediates — `pipe()`, every `pipe.*` write, `connection.drizzle`, transactions and savepoints. `logAllQueries: true` is untouched and behaves exactly as before; that mode already opted into verbose errors.
|
|
13
|
+
- **The one gap, deliberate and announced: `ManagedConnection.client`.** The raw driver door stays raw — that is what it is for — so queries issued through it are outside the boundary and their errors do serialize bind parameters. Reading `.client` on an observed connection now logs a warning saying so, once per database, and the contract is stated on `StatementObserver` and on `ManagedConnection.client`.
|
|
14
|
+
- **Wrapping the `Sql` client to close that gap was considered and rejected.** It would mean proxying a callable tagged-template whose `begin`/`savepoint` hand out fresh client instances needing recursive re-wrapping, on the hottest path in the system — more risk than the narrow, documented gap it closes. Recorded here so it is not re-proposed as cleanup.
|
|
15
|
+
|
|
16
|
+
- **Fix: the migration safety tests no longer depend on a virgin database.** `migrateOne` integration tests assert on hazards in *pending* migrations, but their migration tags are fixed while the tracker outlives the run — so from the second run onward those tags read as already-applied, dropped out of the pending set, and `hasDestructive` flipped to false. The tracker is now dropped before each of those tests. (Surfaced by 0.16.0's move to tag membership; under the old timestamp high-water mark the stale rows were inert.)
|
|
17
|
+
|
|
18
|
+
- **Fix: `docker-compose.test.yml`'s published port is parameterized (`PIPEWORK_TEST_PORT`, default 5432).** Where 5432 is already held, compose silently failed to publish and the suite ran against whatever else answered there — which matters because the harness creates and drops databases and provisions BYPASSRLS fixture roles, so it must talk to a disposable cluster where its own role is the superuser. Stock machines are unaffected.
|
|
19
|
+
|
|
20
|
+
## 0.16.0
|
|
21
|
+
|
|
22
|
+
- **Fix: a queue registered by a module reached only through the schema glob now makes it into `pipework generate`'s output.** The CLI used to snapshot internal definitions (including registered queues) *before* the schema files were imported, so a `createQueue()` living in a glob-matched module — with no side-effect import in the config — was silently dropped from the migration, and consumers worked around it by importing the queue module from `pipework.config.ts`. Internal definitions are now collected after the schema-file imports. Breaking for API consumers: `generateForDatabase`'s `internalDefinitions` parameter is now a thunk (`() => Promise<Record<string, unknown>>`).
|
|
23
|
+
- **Feature: a registered queue whose table is missing from the database is now a loud, actionable failure at startup and at test bootstrap — not a crash at first insert.** Startup validation (`pipework serve`/`dev`, `manifold.start()`) and the test harness's template provisioning both verify every `createQueue()`-registered table exists in the default database (`to_regclass` probe), and refuse with a message naming the table, the schema-glob rule, and the fix (`pipework generate`, then `pipework migrate`).
|
|
24
|
+
- **Behavior change: a database whose `testUrl` resolves to the same connection string as its `url` is refused at config resolution in the test environment.** Identical values mean "tests" run against the real database: the test harness creates/drops databases and rolls back transactions on what it assumes is a dedicated instance, and RLS-inertness goes unnoticed — with zero signal from the framework. The refusal names both env vars and the fix (point the test var at a separate database); `testUrl` naming the *same env var* as `url` is reported as its own case. Applies to both the url/testUrl and appUrl/appTestUrl pairs.
|
|
25
|
+
- **Fix: `pipework generate` surfaces the real config-load error instead of misreporting every failure as "No pipework.config.ts found".**
|
|
26
|
+
- **Behavior change: a database with `rls` configured whose runtime role is SUPERUSER or BYPASSRLS now fails startup validation and test bootstrap.** RLS policies never apply to such a role, so the entire tenant-isolation policy set is silently inert — the framework now says so, naming the database, the role, the flag, and the fix (provision a non-bypassing application role; point the runtime connection at it). At startup the probed connection is `appUrl ?? url`; at test bootstrap it is the connection tests actually execute on — transaction isolation collapses every role onto the resolved `url`, database/container isolation resolves roles normally. Consumers whose test role is the compose superuser are refused until they provision a non-bypassing test role — deliberate: that is exactly the world where an RLS test suite proves nothing.
|
|
27
|
+
- **Feature: `.env.local` and `.env.test.local` are loaded alongside `.env`/`.env.test`, with the `.local` (machine-local, never-committed) variant taking precedence** — the standard dotenv convention.
|
|
28
|
+
- **Docs: the `env` config field is now a first-class section in REFERENCE.md** — every declaration shape (`string`/`number`/`boolean`/`string[]`), `required`/`default`/per-environment `defaults`/`sensitive`, typed `manifold.env` access, and a complete `createManifold` example. Previously it appeared only inside a type signature, leaving the feature undiscoverable while consumers accumulated ad-hoc `process.env` reads. The reference generator now renders multi-line JSDoc and `@example` blocks as fenced code instead of flattening them to one line.
|
|
29
|
+
- **Behavior change: relative `migrations`/`schema` paths now resolve against the directory of `pipework.config.ts`, never against the process working directory.** Previously every consumer of these fields (`pipework generate`, `pipework migrate`'s safety gate, test template provisioning) resolved them against cwd — so `pnpm --filter <module> test` (which cd's into the module) failed at `beforeAll` with "No migration journal found", and the only workaround was hand-absolutizing the paths with `import.meta.url`. `ResolvedDatabase.migrations` is now absolute (resolved once, at config load), `ResolvedDatabase` carries the new `baseDir` field (the config file's directory), and schema globs are matched against `baseDir`. Already-absolute consumer paths are unchanged (`resolve(base, abs) === abs`). Breaking for API consumers: `generateForDatabase` loses its `cwd` parameter — the resolved config is the single source of path truth.
|
|
4
30
|
|
|
5
31
|
- **Fix: the migration tracker now records WHICH migrations have been applied (tag membership), replacing the `max(created_at)` timestamp high-water mark that silently skipped any unapplied migration dated earlier than the newest applied one.** Two real production hits of the old model: (1) two manifolds sharing one physical database under the same tracker table name (both declared database key `app`) — the second manifold's entire journal predated the first's newest entry, so its migrations were silently skipped and its schema never materialized; (2) a migration merged from a branch with an earlier date than migrations already applied was silently skipped ("relation does not exist" at runtime, in CI). The tracker table gains a `tag` column (unique) and `applyMigrations` decides "already applied" by membership: tag recorded → skip (after verifying the recorded content hash still matches — see below), tag absent → apply, in journal order. Existing deployed tracker tables are upgraded in place on the next `migrate`: the `tag` column is added and each legacy row's identity is recovered by matching its recorded content hash against the journal's files; rows matching no current journal entry keep a NULL tag and never participate in membership.
|
|
6
32
|
- **Fix: editing an already-applied migration file is now a loud error instead of silently ignored.** Membership verifies the recorded sha256 against the file: a tag recorded with a different hash aborts with an error naming the migration, the two hashes, and the fix (put schema changes in a NEW migration; if two manifolds share a database and collide on a tag, that is a schema-level collision no tracker can reconcile — separate databases or distinct `migrationTable` names).
|
package/REFERENCE.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Pipework API Reference
|
|
2
2
|
|
|
3
3
|
> Auto-generated from source JSDoc. Do not edit manually.
|
|
4
|
-
> Generated: 2026-
|
|
4
|
+
> Generated: 2026-07-19
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
7
7
|
|
|
8
8
|
- [Core](#core)
|
|
9
|
+
- [env — Environment Variables](#env-environment-variables)
|
|
9
10
|
- [jobs — Background Jobs](#jobs-background-jobs)
|
|
10
11
|
- [pipeline — Multi-Step Pipelines](#pipeline-multi-step-pipelines)
|
|
11
12
|
- [state — State Machines](#state-state-machines)
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
- [http — Server](#http-server)
|
|
34
35
|
- [openapi — API Documentation](#openapi-api-documentation)
|
|
35
36
|
- [test](#test)
|
|
37
|
+
- [data](#data)
|
|
36
38
|
- [trace](#trace)
|
|
37
39
|
|
|
38
40
|
## Core
|
|
@@ -41,7 +43,29 @@ Application instance and foundational types.
|
|
|
41
43
|
|
|
42
44
|
### `createManifold`
|
|
43
45
|
|
|
44
|
-
Creates the Pipework application instance. This is the entry point for every Pipework app.
|
|
46
|
+
Creates the Pipework application instance. This is the entry point for every Pipework app.
|
|
47
|
+
Define in `pipework.config.ts` and export as default.
|
|
48
|
+
|
|
49
|
+
The `env` field declares the app's environment variables — the canonical
|
|
50
|
+
home for runtime config (ports, service tokens, bucket names, flags) in
|
|
51
|
+
place of scattered `process.env` reads. Each variable is read once at config
|
|
52
|
+
load, validated and coerced to its declared type (loud error on a bad or
|
|
53
|
+
missing-required value), and exposed typed on `manifold.env`.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const manifold = createManifold({
|
|
58
|
+
databases: { app: { url: 'DATABASE_URL', testUrl: 'DATABASE_URL_TEST' } },
|
|
59
|
+
env: {
|
|
60
|
+
PORT: { type: 'number', defaults: { development: '3000', test: '0' } },
|
|
61
|
+
S3_BUCKET: { type: 'string', required: true },
|
|
62
|
+
API_TOKEN: { type: 'string', required: true, sensitive: true },
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
manifold.env.PORT // number — from process.env.PORT, or the per-environment default
|
|
66
|
+
manifold.env.S3_BUCKET // string — startup fails loudly if unset
|
|
67
|
+
export default manifold
|
|
68
|
+
```
|
|
45
69
|
|
|
46
70
|
```typescript
|
|
47
71
|
function createManifold<const TEnv extends EnvDefs = EnvDefs>( raw: Omit<BlueprintInput, 'env'> &
|
|
@@ -79,6 +103,114 @@ interface IsolationState
|
|
|
79
103
|
interface ConnectionTracker
|
|
80
104
|
```
|
|
81
105
|
|
|
106
|
+
## env — Environment Variables
|
|
107
|
+
|
|
108
|
+
Declared environment variables — the canonical home for runtime config (ports, service tokens, bucket names, feature flags). Declare them under the `env` blueprint field; each is read once at config load, validated and coerced to its declared type (loud error on a bad or missing-required value), and exposed typed on `manifold.env` — in place of scattered ad-hoc `process.env` reads.
|
|
109
|
+
|
|
110
|
+
### Types
|
|
111
|
+
|
|
112
|
+
**`StringEnvVar`**
|
|
113
|
+
|
|
114
|
+
A string environment variable — resolved verbatim.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
S3_BUCKET: { type: 'string', required: true }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
interface StringEnvVar extends EnvVarBase
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`NumberEnvVar`**
|
|
126
|
+
|
|
127
|
+
A number environment variable — parsed with a loud error on a non-numeric value.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
PORT: { type: 'number', default: 3000 }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
interface NumberEnvVar extends EnvVarBase
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**`BooleanEnvVar`**
|
|
139
|
+
|
|
140
|
+
A boolean environment variable — accepts true/false/1/0, loud error otherwise.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
FEATURE_GRAPH: { type: 'boolean', default: false }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface BooleanEnvVar extends EnvVarBase
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**`StringArrayEnvVar`**
|
|
152
|
+
|
|
153
|
+
A string-array environment variable — the raw value split on `separator`
|
|
154
|
+
(default `,`), each element trimmed.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
CORS_ORIGINS: { type: 'string[]', separator: ',', default: [] }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface StringArrayEnvVar extends EnvVarBase
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**`EnvVarDef`**
|
|
166
|
+
|
|
167
|
+
One declared environment variable: its type, whether it is required, its
|
|
168
|
+
fallbacks, and whether the value is sensitive. Declared under the `env`
|
|
169
|
+
blueprint field; resolved once at config load and exposed typed on
|
|
170
|
+
`manifold.env` — the canonical replacement for ad-hoc `process.env` reads.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
type EnvVarDef = StringEnvVar | NumberEnvVar | BooleanEnvVar | StringArrayEnvVar
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**`EnvDefs`**
|
|
177
|
+
|
|
178
|
+
The `env` blueprint field: environment-variable declarations keyed by
|
|
179
|
+
variable name. Passing this map to `createManifold` is what types
|
|
180
|
+
`manifold.env`.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
export default createManifold({
|
|
185
|
+
databases: { app: { url: 'DATABASE_URL', testUrl: 'DATABASE_URL_TEST' } },
|
|
186
|
+
env: {
|
|
187
|
+
PORT: { type: 'number', defaults: { development: '3000', test: '0' } },
|
|
188
|
+
S3_BUCKET: { type: 'string', required: true },
|
|
189
|
+
API_TOKEN: { type: 'string', required: true, sensitive: true },
|
|
190
|
+
CORS_ORIGINS: { type: 'string[]', default: [] },
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
// manifold.env.PORT → number
|
|
194
|
+
// manifold.env.S3_BUCKET → string
|
|
195
|
+
// manifold.env.CORS_ORIGINS → string[]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
type EnvDefs = Record<string, EnvVarDef>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**`ResolvedEnvType`** — The resolved runtime type of one declaration: 'string' → string, 'number' → number, 'boolean' → boolean, 'string[]' → string[].
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
type ResolvedEnvType<T extends EnvVarDef> =
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**`ResolveEnvDefs`** — Maps a declaration map to the shape of `manifold.env` — each key typed by its declared `type`.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
type ResolveEnvDefs<T extends EnvDefs> =
|
|
212
|
+
```
|
|
213
|
+
|
|
82
214
|
## jobs — Background Jobs
|
|
83
215
|
|
|
84
216
|
Background job queue namespace — Postgres-backed with SKIP LOCKED, LISTEN/NOTIFY, cron scheduling.
|
|
@@ -367,6 +499,38 @@ The zod schema for Blueprint validation — useful for custom config tooling.
|
|
|
367
499
|
|
|
368
500
|
Thrown for configuration problems — includes a suggestion for how to fix.
|
|
369
501
|
|
|
502
|
+
### Types
|
|
503
|
+
|
|
504
|
+
**`StatementEvent`**
|
|
505
|
+
|
|
506
|
+
One statement as it is handed to the driver for writing to the wire —
|
|
507
|
+
including the transaction-control statements the driver issues on its own
|
|
508
|
+
(`BEGIN`, `SAVEPOINT`, `COMMIT`, `ROLLBACK`), which no application-level hook
|
|
509
|
+
can see.
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
interface StatementEvent
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**`StatementObserver`**
|
|
516
|
+
|
|
517
|
+
Caller-supplied per-statement observer. Fires once per statement written to
|
|
518
|
+
the wire, on every connection the manifold owns — pooled connections and the
|
|
519
|
+
test harness's own connections alike.
|
|
520
|
+
|
|
521
|
+
Pure observation: the return value is discarded and the statement is never
|
|
522
|
+
mutated. It runs synchronously on the driver's write path, so it must be
|
|
523
|
+
cheap and must not throw.
|
|
524
|
+
|
|
525
|
+
Counts wire truth, not application intent. Two consequences worth knowing
|
|
526
|
+
before you assert on a total: the driver issues its own transaction control
|
|
527
|
+
(`BEGIN`/`SAVEPOINT`/`COMMIT`/`ROLLBACK`), and it issues one `pg_catalog.pg_type`
|
|
528
|
+
lookup per connection the first time that connection is used.
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
type StatementObserver = (event: StatementEvent) => void
|
|
532
|
+
```
|
|
533
|
+
|
|
370
534
|
## errors — Error Classes
|
|
371
535
|
|
|
372
536
|
Standard error classes — all extend PipeworkError with structured error codes and actionable messages.
|
|
@@ -564,6 +728,18 @@ interface ValueColumnDef
|
|
|
564
728
|
interface SerializableOptions
|
|
565
729
|
```
|
|
566
730
|
|
|
731
|
+
**`ManagedConnection`** — A standalone pooled connection: the raw postgres.js client, its drizzle handle, and a `close()` that ends the pool.
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
interface ManagedConnection
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**`ConnectOptions`** — Options for `tap()` — pool size (default 1) and idle timeout in seconds.
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
interface ConnectOptions
|
|
741
|
+
```
|
|
742
|
+
|
|
567
743
|
## jsonb — JSONB Column Helpers
|
|
568
744
|
|
|
569
745
|
JSONB query operators — typed helpers for PostgreSQL JSONB containment, key checks, and field extraction.
|
|
@@ -1022,6 +1198,19 @@ Generates an OpenAPI 3.1 document from route registrations — schemas derived f
|
|
|
1022
1198
|
|
|
1023
1199
|
## test
|
|
1024
1200
|
|
|
1201
|
+
## data
|
|
1202
|
+
|
|
1203
|
+
### `tap`
|
|
1204
|
+
|
|
1205
|
+
Opens a standalone managed connection outside the manifold's pools — the
|
|
1206
|
+
sanctioned door for contexts where `pipe()` structurally cannot resolve:
|
|
1207
|
+
`worker_threads`, detached monitors, one-off scripts. Callers own the
|
|
1208
|
+
lifecycle: always `await conn.close()`.
|
|
1209
|
+
|
|
1210
|
+
```typescript
|
|
1211
|
+
function tap(url: string, options?: ConnectOptions): ManagedConnection
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1025
1214
|
## trace
|
|
1026
1215
|
|
|
1027
1216
|
### `__flowStep`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IACtB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IACtB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0D7E"}
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { info, success, warn, error as logError } from '../output.js';
|
|
2
2
|
export async function generate(options) {
|
|
3
|
-
const { discoverInstance } = await import('../../core/config/discover.js');
|
|
3
|
+
const { discoverInstance, findConfig } = await import('../../core/config/discover.js');
|
|
4
4
|
const { generateForDatabase } = await import('../../data/migrate/generate.js');
|
|
5
5
|
const { loadInternalDefinitions } = await import('../../data/migrate/internal-definitions.js');
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
instance = await discoverInstance(options.cwd);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
6
|
+
if (findConfig(options.cwd) === null) {
|
|
11
7
|
logError('No pipework.config.ts found.\n\n' +
|
|
12
8
|
' Run `pipework init` to create one.\n');
|
|
13
9
|
process.exit(1);
|
|
14
10
|
}
|
|
11
|
+
// A config that exists but fails to load (missing env var, refused
|
|
12
|
+
// url/testUrl identity, …) must surface its real error, not be misreported
|
|
13
|
+
// as a missing config.
|
|
14
|
+
const instance = await discoverInstance(options.cwd);
|
|
15
15
|
const defaultDbName = instance.config.defaultDatabaseName();
|
|
16
16
|
try {
|
|
17
17
|
if (options.db !== undefined) {
|
|
18
18
|
const dbConfig = instance.config.database(options.db);
|
|
19
19
|
info(`Generating migration for database: ${options.db}`);
|
|
20
|
-
|
|
20
|
+
// Passed as a thunk so queue registrations made by glob-imported schema
|
|
21
|
+
// modules (inside generateForDatabase) are visible when it runs.
|
|
22
|
+
const internal = () => loadInternalDefinitions(instance.config.raw, {
|
|
21
23
|
isDefaultDatabase: options.db === defaultDbName,
|
|
22
24
|
});
|
|
23
|
-
const result = await generateForDatabase(dbConfig, options.
|
|
25
|
+
const result = await generateForDatabase(dbConfig, options.name, internal);
|
|
24
26
|
if (result.migrationFile === null) {
|
|
25
27
|
warn(`${options.db}: no schema changes detected`);
|
|
26
28
|
}
|
|
@@ -34,10 +36,10 @@ export async function generate(options) {
|
|
|
34
36
|
for (const [, dbConfig] of instance.config.databases()) {
|
|
35
37
|
if (dbConfig.schema === undefined)
|
|
36
38
|
continue;
|
|
37
|
-
const internal =
|
|
39
|
+
const internal = () => loadInternalDefinitions(instance.config.raw, {
|
|
38
40
|
isDefaultDatabase: dbConfig.name === defaultDbName,
|
|
39
41
|
});
|
|
40
|
-
const result = await generateForDatabase(dbConfig, options.
|
|
42
|
+
const result = await generateForDatabase(dbConfig, options.name, internal);
|
|
41
43
|
if (result.migrationFile === null) {
|
|
42
44
|
warn(`${dbConfig.name}: no schema changes detected`);
|
|
43
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAQrE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAA+B;IAC5D,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function setConfigBaseDir(dir: string): void;
|
|
2
|
+
/** Read-and-clear. Undefined when no config import is in flight — directly-constructed configs resolve against process.cwd(). */
|
|
3
|
+
export declare function consumeConfigBaseDir(): string | undefined;
|
|
4
|
+
/** Cleared in the importer's finally: an ESM cache hit skips loadConfig, and the stale slot must not leak into a later directly-constructed loadConfig. */
|
|
5
|
+
export declare function clearConfigBaseDir(): void;
|
|
6
|
+
//# sourceMappingURL=base-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-dir.d.ts","sourceRoot":"","sources":["../../../src/core/config/base-dir.ts"],"names":[],"mappings":"AASA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,iIAAiI;AACjI,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAIzD;AAED,2JAA2J;AAC3J,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The directory relative `migrations`/`schema` paths resolve against (D1,
|
|
2
|
+
// subseam audit 2026-07-16). Set from the config file's directory immediately
|
|
3
|
+
// before the config module is imported; consumed exactly once by loadConfig.
|
|
4
|
+
// A module-level slot rather than a parameter because the config file itself
|
|
5
|
+
// calls createManifold() during its own import — there is no call chain to
|
|
6
|
+
// thread the value through.
|
|
7
|
+
let configBaseDir;
|
|
8
|
+
export function setConfigBaseDir(dir) {
|
|
9
|
+
configBaseDir = dir;
|
|
10
|
+
}
|
|
11
|
+
/** Read-and-clear. Undefined when no config import is in flight — directly-constructed configs resolve against process.cwd(). */
|
|
12
|
+
export function consumeConfigBaseDir() {
|
|
13
|
+
const dir = configBaseDir;
|
|
14
|
+
configBaseDir = undefined;
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
/** Cleared in the importer's finally: an ESM cache hit skips loadConfig, and the stale slot must not leak into a later directly-constructed loadConfig. */
|
|
18
|
+
export function clearConfigBaseDir() {
|
|
19
|
+
configBaseDir = undefined;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=base-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-dir.js","sourceRoot":"","sources":["../../../src/core/config/base-dir.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,2EAA2E;AAC3E,4BAA4B;AAE5B,IAAI,aAAiC,CAAA;AAErC,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,aAAa,GAAG,GAAG,CAAA;AACrB,CAAC;AAED,iIAAiI;AACjI,MAAM,UAAU,oBAAoB;IAClC,MAAM,GAAG,GAAG,aAAa,CAAA;IACzB,aAAa,GAAG,SAAS,CAAA;IACzB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,2JAA2J;AAC3J,MAAM,UAAU,kBAAkB;IAChC,aAAa,GAAG,SAAS,CAAA;AAC3B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/core/config/discover.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/core/config/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAM9C,wBAAgB,sBAAsB,IAAI,IAAI,CAsD7C;AAED,wBAAsB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CActE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYtD"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolve, dirname } from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
3
|
import { register } from 'node:module';
|
|
5
4
|
import { ConfigError } from './errors.js';
|
|
5
|
+
import { importConfig } from './import-config.js';
|
|
6
6
|
const CONFIG_FILENAME = 'pipework.config.ts';
|
|
7
7
|
export function registerTsResolveHooks() {
|
|
8
8
|
{
|
|
@@ -66,20 +66,7 @@ export async function discoverInstance(cwd) {
|
|
|
66
66
|
throw new ConfigError(`No ${CONFIG_FILENAME} found in ${start} or any parent directory.`, `Create one with \`pipework init\`, or see the getting-started guide.`);
|
|
67
67
|
}
|
|
68
68
|
registerTsResolveHooks();
|
|
69
|
-
|
|
70
|
-
const mod = await import(configUrl);
|
|
71
|
-
const exported = mod['default'] ?? mod;
|
|
72
|
-
if (!isManifold(exported)) {
|
|
73
|
-
throw new ConfigError(`${CONFIG_FILENAME} must export a Pipework instance as its default export.`, `Your config should look like:\n\n` +
|
|
74
|
-
` import { createManifold } from 'pipework'\n\n` +
|
|
75
|
-
` export default createManifold({\n` +
|
|
76
|
-
` databases: { app: { url: 'DATABASE_URL' } },\n` +
|
|
77
|
-
` })\n`);
|
|
78
|
-
}
|
|
79
|
-
return exported;
|
|
80
|
-
}
|
|
81
|
-
function isManifold(v) {
|
|
82
|
-
return typeof v === 'object' && v !== null && 'pool' in v && 'config' in v;
|
|
69
|
+
return importConfig(configPath);
|
|
83
70
|
}
|
|
84
71
|
export function findConfig(from) {
|
|
85
72
|
let dir = resolve(from);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../src/core/config/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../src/core/config/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAE5C,MAAM,UAAU,sBAAsB;IACpC,CAAC;QACC,+EAA+E;QAC/E,qEAAqE;QACrE,8EAA8E;QAC9E,+EAA+E;QAC/E,+DAA+D;QAC/D,gFAAgF;QAChF,qEAAqE;QACrE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0CZ,CAAA;QACD,QAAQ,CAAC,wBAAwB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAY;IACjD,MAAM,KAAK,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IAClC,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAEpC,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,WAAW,CACnB,MAAM,eAAe,aAAa,KAAK,2BAA2B,EAClE,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,sBAAsB,EAAE,CAAA;IAExB,OAAO,YAAY,CAAC,UAAU,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,OAAO,GAAG,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;QAC/C,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAA;QAC3C,GAAG,GAAG,MAAM,CAAA;QACZ,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IAC1C,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -1,31 +1,100 @@
|
|
|
1
1
|
import type { Environment } from './schema.js';
|
|
2
2
|
interface EnvVarBase {
|
|
3
|
+
/** Redact the resolved value from logs and error messages (tokens, secrets). */
|
|
3
4
|
readonly sensitive?: boolean | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Per-environment fallback values, keyed by environment name. Always given
|
|
7
|
+
* as strings (they take the same parse path as a real environment variable);
|
|
8
|
+
* a variable that is set in the environment wins over any default here.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* PORT: { type: 'number', defaults: { development: '3000', test: '0' } }
|
|
12
|
+
*/
|
|
4
13
|
readonly defaults?: Partial<Record<Environment, string>> | undefined;
|
|
5
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A string environment variable — resolved verbatim.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* S3_BUCKET: { type: 'string', required: true }
|
|
20
|
+
*/
|
|
6
21
|
export interface StringEnvVar extends EnvVarBase {
|
|
7
22
|
readonly type: 'string';
|
|
23
|
+
/** Fail startup when unset and no default applies. */
|
|
8
24
|
readonly required?: boolean | undefined;
|
|
25
|
+
/** Environment-independent fallback; `defaults` (per-environment) wins over this. */
|
|
9
26
|
readonly default?: string | undefined;
|
|
10
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A number environment variable — parsed with a loud error on a non-numeric value.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* PORT: { type: 'number', default: 3000 }
|
|
33
|
+
*/
|
|
11
34
|
export interface NumberEnvVar extends EnvVarBase {
|
|
12
35
|
readonly type: 'number';
|
|
36
|
+
/** Fail startup when unset and no default applies. */
|
|
13
37
|
readonly required?: boolean | undefined;
|
|
38
|
+
/** Environment-independent fallback; `defaults` (per-environment) wins over this. */
|
|
14
39
|
readonly default?: number | undefined;
|
|
15
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A boolean environment variable — accepts true/false/1/0, loud error otherwise.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* FEATURE_GRAPH: { type: 'boolean', default: false }
|
|
46
|
+
*/
|
|
16
47
|
export interface BooleanEnvVar extends EnvVarBase {
|
|
17
48
|
readonly type: 'boolean';
|
|
49
|
+
/** Fail startup when unset and no default applies. */
|
|
18
50
|
readonly required?: boolean | undefined;
|
|
51
|
+
/** Environment-independent fallback; `defaults` (per-environment) wins over this. */
|
|
19
52
|
readonly default?: boolean | undefined;
|
|
20
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A string-array environment variable — the raw value split on `separator`
|
|
56
|
+
* (default `,`), each element trimmed.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* CORS_ORIGINS: { type: 'string[]', separator: ',', default: [] }
|
|
60
|
+
*/
|
|
21
61
|
export interface StringArrayEnvVar extends EnvVarBase {
|
|
22
62
|
readonly type: 'string[]';
|
|
63
|
+
/** Split character for the raw value (default ','). */
|
|
23
64
|
readonly separator?: string | undefined;
|
|
65
|
+
/** Fail startup when unset and no default applies. */
|
|
24
66
|
readonly required?: boolean | undefined;
|
|
67
|
+
/** Environment-independent fallback; `defaults` (per-environment) wins over this. */
|
|
25
68
|
readonly default?: readonly string[] | undefined;
|
|
26
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* One declared environment variable: its type, whether it is required, its
|
|
72
|
+
* fallbacks, and whether the value is sensitive. Declared under the `env`
|
|
73
|
+
* blueprint field; resolved once at config load and exposed typed on
|
|
74
|
+
* `manifold.env` — the canonical replacement for ad-hoc `process.env` reads.
|
|
75
|
+
*/
|
|
27
76
|
export type EnvVarDef = StringEnvVar | NumberEnvVar | BooleanEnvVar | StringArrayEnvVar;
|
|
77
|
+
/**
|
|
78
|
+
* The `env` blueprint field: environment-variable declarations keyed by
|
|
79
|
+
* variable name. Passing this map to `createManifold` is what types
|
|
80
|
+
* `manifold.env`.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* export default createManifold({
|
|
84
|
+
* databases: { app: { url: 'DATABASE_URL', testUrl: 'DATABASE_URL_TEST' } },
|
|
85
|
+
* env: {
|
|
86
|
+
* PORT: { type: 'number', defaults: { development: '3000', test: '0' } },
|
|
87
|
+
* S3_BUCKET: { type: 'string', required: true },
|
|
88
|
+
* API_TOKEN: { type: 'string', required: true, sensitive: true },
|
|
89
|
+
* CORS_ORIGINS: { type: 'string[]', default: [] },
|
|
90
|
+
* },
|
|
91
|
+
* })
|
|
92
|
+
* // manifold.env.PORT → number
|
|
93
|
+
* // manifold.env.S3_BUCKET → string
|
|
94
|
+
* // manifold.env.CORS_ORIGINS → string[]
|
|
95
|
+
*/
|
|
28
96
|
export type EnvDefs = Record<string, EnvVarDef>;
|
|
97
|
+
/** The resolved runtime type of one declaration: 'string' → string, 'number' → number, 'boolean' → boolean, 'string[]' → string[]. */
|
|
29
98
|
export type ResolvedEnvType<T extends EnvVarDef> = T extends {
|
|
30
99
|
readonly type: 'string';
|
|
31
100
|
} ? string : T extends {
|
|
@@ -35,6 +104,7 @@ export type ResolvedEnvType<T extends EnvVarDef> = T extends {
|
|
|
35
104
|
} ? boolean : T extends {
|
|
36
105
|
readonly type: 'string[]';
|
|
37
106
|
} ? string[] : never;
|
|
107
|
+
/** Maps a declaration map to the shape of `manifold.env` — each key typed by its declared `type`. */
|
|
38
108
|
export type ResolveEnvDefs<T extends EnvDefs> = {
|
|
39
109
|
readonly [K in keyof T]: ResolvedEnvType<T[K]>;
|
|
40
110
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-types.d.ts","sourceRoot":"","sources":["../../../src/core/config/env-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,UAAU,UAAU;IAClB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;CACrE;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACvC;AAED,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CACjD;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAEvF,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAE/C,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,SAAS,IAC7C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,MAAM,GAC9C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,MAAM,GAC9C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,OAAO,GAChD,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,MAAM,EAAE,GAClD,KAAK,CAAA;AAEP,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,OAAO,IAAI;IAC9C,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAA"}
|
|
1
|
+
{"version":3,"file":"env-types.d.ts","sourceRoot":"","sources":["../../../src/core/config/env-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,UAAU,UAAU;IAClB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;CACrE;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACvC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,uDAAuD;IACvD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CACjD;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAE/C,sIAAsI;AACtI,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,SAAS,IAC7C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,MAAM,GAC9C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,MAAM,GAC9C,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,OAAO,GAChD,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,MAAM,EAAE,GAClD,KAAK,CAAA;AAEP,qGAAqG;AACrG,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,OAAO,IAAI;IAC9C,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/core/config/env.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/core/config/env.ts"],"names":[],"mappings":"AAKA,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAU/C"}
|
package/dist/core/config/env.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { resolve, dirname } from 'node:path';
|
|
3
|
+
const ENV_FILES = ['.env.test.local', '.env.test', '.env.local', '.env'];
|
|
3
4
|
export function loadEnvFiles(cwd) {
|
|
4
5
|
const start = cwd ?? process.cwd();
|
|
5
6
|
const envDir = findEnvDir(start) ?? start;
|
|
6
|
-
loadFile
|
|
7
|
-
|
|
7
|
+
// First-set wins (loadFile never overwrites), so the machine-local,
|
|
8
|
+
// never-committed `.local` variant of each tier takes precedence over the
|
|
9
|
+
// committed file — the standard dotenv convention.
|
|
10
|
+
for (const file of ENV_FILES) {
|
|
11
|
+
loadFile(resolve(envDir, file));
|
|
12
|
+
}
|
|
8
13
|
}
|
|
9
14
|
function findEnvDir(from) {
|
|
15
|
+
const hasEnvFile = (dir) => ENV_FILES.some(f => existsSync(resolve(dir, f)));
|
|
10
16
|
let dir = resolve(from);
|
|
11
17
|
let parent = dirname(dir);
|
|
12
18
|
while (dir !== parent) {
|
|
13
|
-
if (
|
|
19
|
+
if (hasEnvFile(dir))
|
|
14
20
|
return dir;
|
|
15
|
-
}
|
|
16
21
|
dir = parent;
|
|
17
22
|
parent = dirname(dir);
|
|
18
23
|
}
|
|
19
|
-
if (
|
|
24
|
+
if (hasEnvFile(dir))
|
|
20
25
|
return dir;
|
|
21
|
-
}
|
|
22
26
|
return null;
|
|
23
27
|
}
|
|
24
28
|
function loadFile(path) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/core/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE5C,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,KAAK,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IAClC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAA;IAEzC,
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/core/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE5C,MAAM,SAAS,GAAG,CAAC,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,CAAA;AAExE,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,KAAK,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IAClC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAA;IAEzC,oEAAoE;IACpE,0EAA0E;IAC1E,mDAAmD;IACnD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;IACjC,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,UAAU,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7F,IAAI,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,OAAO,GAAG,KAAK,MAAM,EAAE,CAAC;QACtB,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAA;QAC/B,GAAG,GAAG,MAAM,CAAA;QACZ,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IACD,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/B,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAM;IAE7B,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAEvD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,SAAQ;QAE1B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAC1C,IAAI,GAAG,KAAK,EAAE;YAAE,SAAQ;QAExB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,SAAQ;QAE5C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAC3C,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACnD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;IAC1B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-config.d.ts","sourceRoot":"","sources":["../../../src/core/config/import-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAS9C,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBxE"}
|