@owlmeans/postgres 0.1.15
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/LICENSE +21 -0
- package/README.md +139 -0
- package/agent-meta/instructions/postgres.instructions.md +57 -0
- package/agent-meta/manifest.json +23 -0
- package/agent-meta/skills/postgres/SKILL.md +121 -0
- package/build/bootstrap.d.ts +16 -0
- package/build/bootstrap.d.ts.map +1 -0
- package/build/bootstrap.js +106 -0
- package/build/bootstrap.js.map +1 -0
- package/build/consts.d.ts +29 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +30 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/middleware.d.ts +11 -0
- package/build/middleware.d.ts.map +1 -0
- package/build/middleware.js +21 -0
- package/build/middleware.js.map +1 -0
- package/build/service.d.ts +9 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +221 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +40 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/config.d.ts +27 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +96 -0
- package/build/utils/config.js.map +1 -0
- package/build/utils/connection.d.ts +15 -0
- package/build/utils/connection.d.ts.map +1 -0
- package/build/utils/connection.js +56 -0
- package/build/utils/connection.js.map +1 -0
- package/package.json +45 -0
- package/src/bootstrap.ts +126 -0
- package/src/consts.ts +36 -0
- package/src/index.ts +7 -0
- package/src/middleware.ts +26 -0
- package/src/service.ts +287 -0
- package/src/types.ts +42 -0
- package/src/utils/config.ts +109 -0
- package/src/utils/connection.ts +64 -0
- package/tests/bootstrap.spec.ts +142 -0
- package/tests/context.ts +204 -0
- package/tests/crud.spec.ts +227 -0
- package/tests/custom-sql.spec.ts +196 -0
- package/tests/migration.spec.ts +260 -0
- package/tests/sync.spec.ts +156 -0
- package/tsconfig.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @owlmeans/postgres
|
|
2
|
+
|
|
3
|
+
PostgreSQL service for OwlMeans server contexts — pooled connections, readiness probing, schema
|
|
4
|
+
provisioning, and an opt-in least-privilege bootstrap path.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
- `makePostgresDbService(alias?)` — creates a PostgreSQL connection service
|
|
9
|
+
- `appendPostgres(context, alias?)` — registers the service **and** its drain middleware in the context
|
|
10
|
+
- Reads connection config from `context.cfg.dbs` entries whose `service` is `'postgres'`
|
|
11
|
+
- Backs `@owlmeans/postgres-resource`; the resource layer owns all DDL
|
|
12
|
+
- `bootstrap()` provisions a role, database and schema from a separate superuser config alias
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @owlmeans/postgres @owlmeans/postgres-resource
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { appendPostgres, DEFAULT_ALIAS as POSTGRES_SERVICE } from '@owlmeans/postgres'
|
|
24
|
+
|
|
25
|
+
// In context setup (backend/src/context.ts)
|
|
26
|
+
appendPostgres<C, T>(context)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Config:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
cfg.dbs = [{
|
|
33
|
+
service: 'postgres',
|
|
34
|
+
alias: 'postgres',
|
|
35
|
+
host: '/etc/app-config/pg-host', // values starting with `/` are read as files
|
|
36
|
+
user: '/etc/app-config/pg-role-name',
|
|
37
|
+
secret: '/etc/master-secret/pg-app-password',
|
|
38
|
+
schema: 'app', // ← Postgres SCHEMA
|
|
39
|
+
meta: { database: '/etc/app-config/pg-db-name' }
|
|
40
|
+
}]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A whole connection string works too, and wins over `host`/`user`/`secret`:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
cfg.dbs = [{ service: 'postgres', alias: 'postgres', schema: 'app', meta: { url: process.env.DATABASE_URL } }]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`schema` is the Postgres **schema**, not the database — `dbName()` suffixes it per Entity/User layer,
|
|
50
|
+
so per-tenant namespaces stay cheap. The database comes from `meta.database` and is never suffixed.
|
|
51
|
+
|
|
52
|
+
At `init()` the service opens a pool, runs a `SELECT 1` readiness probe (30 attempts, 2s apart by
|
|
53
|
+
default — a Postgres sidecar routinely accepts TCP before it accepts queries), issues
|
|
54
|
+
`CREATE SCHEMA IF NOT EXISTS`, and installs a `SIGTERM` handler that drains the pool.
|
|
55
|
+
|
|
56
|
+
### Bootstrap (admin path)
|
|
57
|
+
|
|
58
|
+
Hold the superuser connection under a **separate** config alias so the application's own entry never
|
|
59
|
+
carries superuser credentials:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
cfg.dbs = [
|
|
63
|
+
{ service: 'postgres', alias: 'postgres', /* least privileged app role */ },
|
|
64
|
+
{ service: 'postgres', alias: 'pg-admin', /* superuser */ }
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const postgres = context.service<PostgresService>('postgres')
|
|
68
|
+
await postgres.bootstrap('pg-admin', {
|
|
69
|
+
role: 'app_role', password: appPassword, database: 'app_db', schema: 'app', leastPrivilege: true
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Idempotent by design — it probes `pg_roles` and `pg_database` before creating anything, rotates the
|
|
74
|
+
password of a role that already exists, and applies `REVOKE CONNECT … FROM PUBLIC`,
|
|
75
|
+
`REVOKE CREATE ON SCHEMA public FROM PUBLIC`, `GRANT CONNECT … TO <role>` and
|
|
76
|
+
`ALTER ROLE … SET search_path`. Returns a `BootstrapReport` saying what it actually changed.
|
|
77
|
+
Identifiers are validated and quoted; the password is the only literal and is escaped.
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `makePostgresDbService(alias?): PostgresService`
|
|
82
|
+
|
|
83
|
+
Creates the PostgreSQL service. `alias` defaults to `DEFAULT_ALIAS` (`'postgres'`).
|
|
84
|
+
|
|
85
|
+
### `appendPostgres<C, T>(context, alias?): T`
|
|
86
|
+
|
|
87
|
+
Registers the service and the drain middleware in the context.
|
|
88
|
+
|
|
89
|
+
### `PostgresService`
|
|
90
|
+
|
|
91
|
+
Extends `PostgresDbService` from `@owlmeans/postgres-resource`:
|
|
92
|
+
|
|
93
|
+
- `db(alias?): Promise<PostgresDb>` — `{ drizzle, pool, schema, database }`
|
|
94
|
+
- `client(alias?): Promise<Pool>` / `clients: Record<string, Pool>`
|
|
95
|
+
- `qualify(resourceAlias, configAlias?): string` — `"schema"."table"` of a registered resource
|
|
96
|
+
- `query(text, params?, configAlias?)` — parameterised query straight on the pool
|
|
97
|
+
- `transaction(fn, configAlias?)` — a `PostgresTx`
|
|
98
|
+
- `defer(configAlias, task)` / `drain(configAlias?)` — work held back until every resource has
|
|
99
|
+
initialized (foreign keys whose target table belongs to a resource that hasn't run `init()` yet)
|
|
100
|
+
- `lock` / `unlock` — AES field encryption via `config.encryptionKey`
|
|
101
|
+
- `bootstrap(configAlias, opts): Promise<BootstrapReport>`
|
|
102
|
+
|
|
103
|
+
### Helpers
|
|
104
|
+
|
|
105
|
+
- `parseUrl(url)` / `prepareConfig(config, overrides?)` / `poolDatabase(pool)`
|
|
106
|
+
- `probe(pool, meta, location)` / `ensureSchema(pool, schema)`
|
|
107
|
+
- `bootstrapDb(...)` — the bootstrap implementation, usable without a context
|
|
108
|
+
- `drainMiddleware(alias?)`
|
|
109
|
+
|
|
110
|
+
### Constants
|
|
111
|
+
|
|
112
|
+
- `DEFAULT_ALIAS` — `'postgres'`
|
|
113
|
+
- `DEF_ADMIN_ALIAS` — `'pg-admin'`
|
|
114
|
+
- `DEF_MAINTENANCE_DB` — `'postgres'`; `DEF_PORT` — `5432`; `DEF_POOL_SIZE` — `10`
|
|
115
|
+
- `DEF_RETRIES` — `30`; `DEF_RETRY_DELAY` — `2000`
|
|
116
|
+
- `TERMINAL_CONNECT_CODES` — auth/database/permission failures the probe treats as final
|
|
117
|
+
|
|
118
|
+
## Related Packages
|
|
119
|
+
|
|
120
|
+
- [`@owlmeans/postgres-resource`](../postgres-resource) — `makePostgresResource` uses this service
|
|
121
|
+
- [`@owlmeans/server-app`](../server-app) — `makeContext` in conjunction with `appendPostgres`
|
|
122
|
+
- [`@owlmeans/mongo`](../mongo) — the MongoDB counterpart
|
|
123
|
+
|
|
124
|
+
<!-- owlmeans:agent-guidance:start -->
|
|
125
|
+
## Agent guidance
|
|
126
|
+
|
|
127
|
+
This package ships embedded Claude Code skills and GitHub Copilot instructions under
|
|
128
|
+
`agent-meta/`. After installing your `@owlmeans/*` packages, run the OwlMeans
|
|
129
|
+
agent-skills installer to place them into your project's native locations
|
|
130
|
+
(`.claude/skills/` and `.github/instructions/`):
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
npx @owlmeans/agent-skills
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The embedded files are version-matched to this package release. Do not edit them
|
|
137
|
+
directly — they are regenerated on each publish. To contribute guidance edits,
|
|
138
|
+
open a PR against the source monorepo.
|
|
139
|
+
<!-- owlmeans:agent-guidance:end -->
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "How to use @owlmeans/postgres — PostgreSQL connection service (makePostgresDbService / appendPostgres) registered on a server context, plus the least-privilege bootstrap admin path."
|
|
3
|
+
applyTo: "**/context.ts, **/config.ts, **/*.ts, **/*.tsx"
|
|
4
|
+
---
|
|
5
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
6
|
+
|
|
7
|
+
# @owlmeans/postgres
|
|
8
|
+
|
|
9
|
+
**Layer:** Infra
|
|
10
|
+
**Install:** `"@owlmeans/postgres": "^0.1.15"` in `dependencies`
|
|
11
|
+
|
|
12
|
+
## Key Exports
|
|
13
|
+
|
|
14
|
+
| Export | Description |
|
|
15
|
+
|--------|-------------|
|
|
16
|
+
| `makePostgresDbService(alias?)` | PostgreSQL connection service factory |
|
|
17
|
+
| `appendPostgres(context, alias?)` | Registers the service **and** its drain middleware |
|
|
18
|
+
| `PostgresService`, `BootstrapOptions`, `BootstrapReport` | Service contract and the admin path |
|
|
19
|
+
| `bootstrapDb`, `drainMiddleware`, `parseUrl`, `prepareConfig`, `probe`, `ensureSchema` | Helpers |
|
|
20
|
+
| `DEFAULT_ALIAS` (`'postgres'`), `DEF_ADMIN_ALIAS` (`'pg-admin'`), `DEF_POOL_SIZE`, `DEF_RETRIES` | Constants |
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { appendPostgres } from '@owlmeans/postgres'
|
|
26
|
+
appendPostgres<C, T>(context)
|
|
27
|
+
|
|
28
|
+
cfg.dbs = [{
|
|
29
|
+
service: 'postgres', alias: 'postgres',
|
|
30
|
+
host: '/etc/app-config/pg-host', user: '/etc/app-config/pg-role-name',
|
|
31
|
+
secret: '/etc/master-secret/pg-app-password',
|
|
32
|
+
schema: 'app', meta: { database: '/etc/app-config/pg-db-name' }
|
|
33
|
+
}]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
|
|
38
|
+
- Use `appendPostgres`, not a bare `registerService` — the drain middleware is half the wiring.
|
|
39
|
+
- `DbConfig.schema` is the Postgres **SCHEMA** (layer-suffixed by `dbName()`); the **DATABASE** is
|
|
40
|
+
`meta.database` and is never suffixed. `meta.url` accepts a whole connection string and wins over
|
|
41
|
+
`host`/`user`/`secret`. A value starting with `/` is read as a file.
|
|
42
|
+
- `init()` already probes readiness (`SELECT 1`, 30×2s) and creates the schema — do not hand-roll a
|
|
43
|
+
retry loop. Auth / missing-database / permission codes fail fast instead of retrying.
|
|
44
|
+
- Keep the pool small; `max_connections` is a cluster-wide cap, so an oversized pool starves other
|
|
45
|
+
clients. Default `DEF_POOL_SIZE` is 10.
|
|
46
|
+
- All application-table DDL belongs to `@owlmeans/postgres-resource`. This service only creates the
|
|
47
|
+
schema.
|
|
48
|
+
- Superuser credentials go under a **separate** config alias (`pg-admin`), never the app's own entry.
|
|
49
|
+
- `bootstrap()` is idempotent and replaces hand-written `CREATE ROLE` / `CREATE DATABASE` / `GRANT`
|
|
50
|
+
SQL. If such SQL exists in a deployment script or doc, collapse it into a `bootstrap()` call.
|
|
51
|
+
- Use `defer()` for work that depends on another resource being initialized (foreign keys); it is
|
|
52
|
+
drained by the middleware once the context is up.
|
|
53
|
+
|
|
54
|
+
## Depends On
|
|
55
|
+
|
|
56
|
+
- `@owlmeans/postgres-resource`, `@owlmeans/resource`, `@owlmeans/context`,
|
|
57
|
+
`@owlmeans/server-context`, `@owlmeans/basic-keys`, `pg`, `drizzle-orm`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"package": "@owlmeans/postgres",
|
|
4
|
+
"version": "0.1.15",
|
|
5
|
+
"generatedAt": "2026-08-07T18:11:56.992Z",
|
|
6
|
+
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "skill",
|
|
10
|
+
"name": "postgres",
|
|
11
|
+
"category": "package-specific",
|
|
12
|
+
"file": "skills/postgres/SKILL.md",
|
|
13
|
+
"canonicalPath": ".claude/skills/postgres/SKILL.md"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"kind": "instruction",
|
|
17
|
+
"name": "postgres",
|
|
18
|
+
"category": "package-specific",
|
|
19
|
+
"file": "instructions/postgres.instructions.md",
|
|
20
|
+
"canonicalPath": ".github/instructions/postgres.instructions.md"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: postgres
|
|
3
|
+
description: How to use @owlmeans/postgres — PostgreSQL connection service (makePostgresDbService / appendPostgres) registered on a server context, plus the least-privilege bootstrap admin path. Auto-invoked when wiring PostgreSQL into a server app.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# @owlmeans/postgres
|
|
9
|
+
|
|
10
|
+
**Layer:** Infra
|
|
11
|
+
**Install:** `"@owlmeans/postgres": "^0.1.15"` in `dependencies`
|
|
12
|
+
|
|
13
|
+
Pooled `pg` connections for a server context, plus the admin path that provisions the role, database
|
|
14
|
+
and schema an app connects with. All DDL for application tables belongs to [[postgres-resource]] —
|
|
15
|
+
this package only creates the schema those tables live in.
|
|
16
|
+
|
|
17
|
+
## Key Exports
|
|
18
|
+
|
|
19
|
+
| Export | Description |
|
|
20
|
+
|--------|-------------|
|
|
21
|
+
| `makePostgresDbService(alias?)` | The connection service. `alias` defaults to `DEFAULT_ALIAS` (`'postgres'`). |
|
|
22
|
+
| `appendPostgres(context, alias?)` | Registers the service **and** its drain middleware. Use this, not a bare `registerService`. |
|
|
23
|
+
| `PostgresService` | `PostgresDbService` + `bootstrap(configAlias, opts)`. |
|
|
24
|
+
| `BootstrapOptions`, `BootstrapReport`, `bootstrapDb` | The admin path; `bootstrapDb` is usable without a context. |
|
|
25
|
+
| `drainMiddleware(alias?)` | Drains work deferred until every resource has initialized. |
|
|
26
|
+
| `parseUrl`, `prepareConfig`, `poolDatabase`, `probe`, `ensureSchema` | Config and connection helpers. |
|
|
27
|
+
| `DEFAULT_ALIAS`, `DEF_ADMIN_ALIAS`, `DEF_MAINTENANCE_DB`, `DEF_PORT`, `DEF_POOL_SIZE`, `DEF_RETRIES`, `DEF_RETRY_DELAY`, `TERMINAL_CONNECT_CODES` | Constants. |
|
|
28
|
+
|
|
29
|
+
## Wiring
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { appendPostgres } from '@owlmeans/postgres'
|
|
33
|
+
|
|
34
|
+
appendPostgres<C, T>(context)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
cfg.dbs = [{
|
|
39
|
+
service: 'postgres',
|
|
40
|
+
alias: 'postgres',
|
|
41
|
+
host: '/etc/app-config/pg-host', // a leading `/` means "read this file"
|
|
42
|
+
user: '/etc/app-config/pg-role-name',
|
|
43
|
+
secret: '/etc/master-secret/pg-app-password',
|
|
44
|
+
schema: 'app', // ← Postgres SCHEMA
|
|
45
|
+
meta: { database: '/etc/app-config/pg-db-name' }
|
|
46
|
+
}]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`meta.url` takes a whole connection string and wins over `host`/`user`/`secret` — that is the shape
|
|
50
|
+
for a `DATABASE_URL` env var. Because a leading `/` is auto-read, moving to file-mounted secrets
|
|
51
|
+
later is a config change, not a code change.
|
|
52
|
+
|
|
53
|
+
**`schema` is the SCHEMA, not the database.** `dbName()` suffixes it per Entity/User layer, so
|
|
54
|
+
per-tenant namespaces cost a `CREATE SCHEMA` rather than a `CREATE DATABASE`. The database comes
|
|
55
|
+
from `meta.database` and is never suffixed.
|
|
56
|
+
|
|
57
|
+
Other `meta` keys: `autoSync` (see [[postgres-resource]]), `ssl`, `max`, `idleTimeoutMillis`,
|
|
58
|
+
`connectionTimeoutMillis`, `statementTimeoutMillis`, `retries`, `retryDelayMillis`.
|
|
59
|
+
|
|
60
|
+
## What `init()` does
|
|
61
|
+
|
|
62
|
+
Opens a `pg.Pool` → `SELECT 1` readiness probe (30 attempts, 2s apart) → `CREATE SCHEMA IF NOT
|
|
63
|
+
EXISTS` → `SIGTERM` handler that drains the pool. Do **not** hand-roll the retry loop around it: a
|
|
64
|
+
Postgres sidecar routinely accepts TCP before it accepts queries, which is precisely what the probe
|
|
65
|
+
is for. Credential, missing-database and permission failures (`TERMINAL_CONNECT_CODES`) fail
|
|
66
|
+
immediately instead of burning the full retry budget on an error that will never clear.
|
|
67
|
+
|
|
68
|
+
Keep the pool small. Postgres caps connections cluster-wide (`max_connections`, 100 by default), so
|
|
69
|
+
an oversized per-process pool starves every other client of the same server — the opposite of the
|
|
70
|
+
Mongo driver's tuning instinct.
|
|
71
|
+
|
|
72
|
+
## Service surface
|
|
73
|
+
|
|
74
|
+
`db(alias?)` → `{ drizzle, pool, schema, database }` · `client(alias?)` / `clients` ·
|
|
75
|
+
`qualify(resourceAlias, configAlias?)` · `query(text, params?, configAlias?)` ·
|
|
76
|
+
`transaction(fn, configAlias?)` · `lock`/`unlock` (AES via `config.encryptionKey`) ·
|
|
77
|
+
`defer(configAlias, task)` / `drain(configAlias?)`.
|
|
78
|
+
|
|
79
|
+
`defer` exists for work that can't run during one resource's `init()` because it points at another
|
|
80
|
+
resource that hasn't initialized yet — foreign keys, above all. Queue it; the middleware
|
|
81
|
+
`appendPostgres` registers drains it once the context is up. Do not reorder resource registrations
|
|
82
|
+
to work around this.
|
|
83
|
+
|
|
84
|
+
## Bootstrap — the admin path
|
|
85
|
+
|
|
86
|
+
Superuser credentials belong to a **separate config alias**. The application's own entry connects as
|
|
87
|
+
the least-privileged role and must never carry them.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
await context.service<PostgresService>('postgres').bootstrap('pg-admin', {
|
|
91
|
+
role: 'app_role', password: appPassword, database: 'app_db', schema: 'app', leastPrivilege: true
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Idempotent by design — every OwlMeans deployment calls it on each start and each rebuild. It probes
|
|
96
|
+
`pg_roles` / `pg_database` before creating, rotates the password of an existing role (the caller
|
|
97
|
+
generated the password it just passed in, so the role has to accept it), and with `leastPrivilege`
|
|
98
|
+
applies `REVOKE CONNECT … FROM PUBLIC`, `REVOKE CREATE ON SCHEMA public FROM PUBLIC`,
|
|
99
|
+
`GRANT CONNECT … TO <role>`, `ALTER ROLE … SET search_path`. The returned `BootstrapReport` says
|
|
100
|
+
what actually changed. Identifiers are validated and quoted; the password is the only literal.
|
|
101
|
+
|
|
102
|
+
**This replaces hand-written bootstrap SQL.** If you find `CREATE ROLE` / `CREATE DATABASE` /
|
|
103
|
+
`GRANT` strings in a deployment script, a provisioner, or a `DEPLOYMENT.md`, they are a duplicate of
|
|
104
|
+
this function — collapse them into a `bootstrap()` call rather than keeping both in step.
|
|
105
|
+
|
|
106
|
+
## Tests
|
|
107
|
+
|
|
108
|
+
`bun test ./tests` in the package. Integration specs that build a real `ServerContext` live **here**,
|
|
109
|
+
not in `postgres-resource` — see [[testing-integration]]. Gated on `POSTGRES_URL`; the bootstrap
|
|
110
|
+
specs additionally probe for `CREATEROLE`/`CREATEDB` and self-skip without them.
|
|
111
|
+
|
|
112
|
+
## Depends On
|
|
113
|
+
|
|
114
|
+
- `@owlmeans/postgres-resource` · `@owlmeans/resource` · `@owlmeans/context` ·
|
|
115
|
+
`@owlmeans/server-context` · `@owlmeans/basic-keys`
|
|
116
|
+
- `pg` · `drizzle-orm`
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
|
|
120
|
+
- [[postgres-resource]] — the `Resource<T>` implementation this service backs
|
|
121
|
+
- [[mongo]] — the MongoDB counterpart · [[kluster]] — `kluster:` directives in `cfg.dbs`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BootstrapOptions, BootstrapReport, PostgresService } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Provision a least privileged role, its database and its schema.
|
|
4
|
+
*
|
|
5
|
+
* This is the one copy of a script every OwlMeans deployment used to carry inline. It runs
|
|
6
|
+
* on every start and every rebuild, so each step probes before it acts — "already exists"
|
|
7
|
+
* is the expected outcome, not an error.
|
|
8
|
+
*
|
|
9
|
+
* Two connections are unavoidable. `CREATE DATABASE` cannot run from inside the database
|
|
10
|
+
* it creates, so roles and databases are made over the maintenance connection the admin
|
|
11
|
+
* config points at, and the grants are applied over a short lived connection to the target.
|
|
12
|
+
*
|
|
13
|
+
* @throws {PostgresBootstrapError}
|
|
14
|
+
*/
|
|
15
|
+
export declare const bootstrapDb: (service: PostgresService, configAlias: string, opts: BootstrapOptions) => Promise<BootstrapReport>;
|
|
16
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AASpF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GACtB,SAAS,eAAe,EAAE,aAAa,MAAM,EAAE,MAAM,gBAAgB,KACpE,OAAO,CAAC,eAAe,CAwFzB,CAAA"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { assertSqlIdentifier, PostgresBootstrapError, quoteIdent, quoteLiteral } from '@owlmeans/postgres-resource';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { prepareConfig } from './utils/config.js';
|
|
4
|
+
const exists = async (client, text, value) => {
|
|
5
|
+
const result = await client.query(text, [value]);
|
|
6
|
+
return result.rowCount != null && result.rowCount > 0;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Provision a least privileged role, its database and its schema.
|
|
10
|
+
*
|
|
11
|
+
* This is the one copy of a script every OwlMeans deployment used to carry inline. It runs
|
|
12
|
+
* on every start and every rebuild, so each step probes before it acts — "already exists"
|
|
13
|
+
* is the expected outcome, not an error.
|
|
14
|
+
*
|
|
15
|
+
* Two connections are unavoidable. `CREATE DATABASE` cannot run from inside the database
|
|
16
|
+
* it creates, so roles and databases are made over the maintenance connection the admin
|
|
17
|
+
* config points at, and the grants are applied over a short lived connection to the target.
|
|
18
|
+
*
|
|
19
|
+
* @throws {PostgresBootstrapError}
|
|
20
|
+
*/
|
|
21
|
+
export const bootstrapDb = async (service, configAlias, opts) => {
|
|
22
|
+
const role = assertSqlIdentifier(opts.role, 'role');
|
|
23
|
+
const database = assertSqlIdentifier(opts.database ?? opts.role, 'database');
|
|
24
|
+
const schema = opts.schema != null ? assertSqlIdentifier(opts.schema, 'schema') : null;
|
|
25
|
+
const leastPrivilege = opts.leastPrivilege ?? true;
|
|
26
|
+
const rotate = opts.rotatePassword ?? true;
|
|
27
|
+
if (opts.password === '') {
|
|
28
|
+
throw new PostgresBootstrapError(`empty-password:${role}`);
|
|
29
|
+
}
|
|
30
|
+
const report = {
|
|
31
|
+
roleCreated: false,
|
|
32
|
+
passwordRotated: false,
|
|
33
|
+
databaseCreated: false,
|
|
34
|
+
schemaCreated: false,
|
|
35
|
+
grantsApplied: false
|
|
36
|
+
};
|
|
37
|
+
const config = service.config(configAlias);
|
|
38
|
+
const admin = await service.client(configAlias);
|
|
39
|
+
/**
|
|
40
|
+
* The password is the only value here that can't be bound: `CREATE ROLE` takes no
|
|
41
|
+
* parameters, so it reaches the server inside the statement text. Identifiers have
|
|
42
|
+
* already been asserted safe; the literal is escaped.
|
|
43
|
+
*/
|
|
44
|
+
const secret = quoteLiteral(opts.password);
|
|
45
|
+
const client = await admin.connect();
|
|
46
|
+
try {
|
|
47
|
+
if (await exists(client, 'SELECT 1 FROM pg_roles WHERE rolname = $1', role)) {
|
|
48
|
+
if (rotate) {
|
|
49
|
+
await client.query(`ALTER ROLE ${quoteIdent(role)} WITH LOGIN PASSWORD ${secret}`);
|
|
50
|
+
report.passwordRotated = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await client.query(`CREATE ROLE ${quoteIdent(role)} WITH LOGIN PASSWORD ${secret}`);
|
|
55
|
+
report.roleCreated = true;
|
|
56
|
+
}
|
|
57
|
+
if (!await exists(client, 'SELECT 1 FROM pg_database WHERE datname = $1', database)) {
|
|
58
|
+
/** Never inside a transaction — Postgres refuses `CREATE DATABASE` in one. */
|
|
59
|
+
await client.query(`CREATE DATABASE ${quoteIdent(database)} OWNER ${quoteIdent(role)}`);
|
|
60
|
+
report.databaseCreated = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw wrap(error, `${role}@${database}`);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
client.release();
|
|
68
|
+
}
|
|
69
|
+
const target = new Pool(prepareConfig(config, { database, max: 1 }));
|
|
70
|
+
try {
|
|
71
|
+
if (schema != null) {
|
|
72
|
+
/**
|
|
73
|
+
* The resource layer creates its own schema too, but only once an application with
|
|
74
|
+
* resources boots. Creating it here — owned by the role — is what lets that
|
|
75
|
+
* application connect as a role with no `CREATE` right on the database at all.
|
|
76
|
+
*/
|
|
77
|
+
await target.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdent(schema)} AUTHORIZATION ${quoteIdent(role)}`);
|
|
78
|
+
await target.query(`GRANT ALL ON SCHEMA ${quoteIdent(schema)} TO ${quoteIdent(role)}`);
|
|
79
|
+
await target.query(`ALTER ROLE ${quoteIdent(role)} SET search_path = ${quoteIdent(schema)}`);
|
|
80
|
+
report.schemaCreated = true;
|
|
81
|
+
}
|
|
82
|
+
if (leastPrivilege) {
|
|
83
|
+
await target.query(`REVOKE CONNECT ON DATABASE ${quoteIdent(database)} FROM PUBLIC`);
|
|
84
|
+
await target.query('REVOKE CREATE ON SCHEMA public FROM PUBLIC');
|
|
85
|
+
await target.query(`GRANT CONNECT ON DATABASE ${quoteIdent(database)} TO ${quoteIdent(role)}`);
|
|
86
|
+
report.grantsApplied = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw wrap(error, `${role}@${database}`);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await target.end().catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
console.log(`@owlmeans/postgres: bootstrap ${role}@${database} —`
|
|
96
|
+
+ ` role ${report.roleCreated ? 'created' : report.passwordRotated ? 'rotated' : 'present'},`
|
|
97
|
+
+ ` database ${report.databaseCreated ? 'created' : 'present'}`
|
|
98
|
+
+ (schema != null ? `, schema ${schema} ready` : ''));
|
|
99
|
+
return report;
|
|
100
|
+
};
|
|
101
|
+
const wrap = (error, subject) => {
|
|
102
|
+
const failure = new PostgresBootstrapError(`${subject}:${error?.message ?? error}`);
|
|
103
|
+
failure.cause = error;
|
|
104
|
+
return failure;
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const DEFAULT_ALIAS = "postgres";
|
|
2
|
+
/**
|
|
3
|
+
* Config alias conventionally holding the superuser connection the admin path needs.
|
|
4
|
+
* Never the same as {@link DEFAULT_ALIAS} — the application connects as a least
|
|
5
|
+
* privileged role and must not carry superuser credentials in the same config entry.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEF_ADMIN_ALIAS = "pg-admin";
|
|
8
|
+
/** The database `CREATE DATABASE` has to be issued from, since it can't create the one it's in. */
|
|
9
|
+
export declare const DEF_MAINTENANCE_DB = "postgres";
|
|
10
|
+
export declare const DEF_PORT = 5432;
|
|
11
|
+
/**
|
|
12
|
+
* `SELECT 1` readiness probe, generalized from the boot loop every OwlMeans deployment
|
|
13
|
+
* hand-rolled: a Postgres sidecar routinely accepts TCP before it accepts queries.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEF_RETRIES = 30;
|
|
16
|
+
export declare const DEF_RETRY_DELAY = 2000;
|
|
17
|
+
/**
|
|
18
|
+
* Postgres caps connections cluster wide (`max_connections`, 100 by default), so the
|
|
19
|
+
* per-process pool stays deliberately small — unlike a Mongo driver pool, an oversized
|
|
20
|
+
* one here starves every other client of the same server.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEF_POOL_SIZE = 10;
|
|
23
|
+
/**
|
|
24
|
+
* Failures the readiness probe treats as final rather than as "not up yet":
|
|
25
|
+
* `28P01`/`28000` wrong credentials, `3D000` no such database, `42501` no rights.
|
|
26
|
+
* Retrying any of them just delays the same error by a minute.
|
|
27
|
+
*/
|
|
28
|
+
export declare const TERMINAL_CONNECT_CODES: string[];
|
|
29
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,aAAmB,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAAa,CAAA;AAEzC,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,aAAa,CAAA;AAE5C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAE5B;;;GAGG;AACH,eAAO,MAAM,WAAW,KAAK,CAAA;AAC7B,eAAO,MAAM,eAAe,OAAO,CAAA;AAEnC;;;;GAIG;AACH,eAAO,MAAM,aAAa,KAAK,CAAA;AAE/B;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,UAAuC,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DEFAULT_DB_ALIAS } from '@owlmeans/postgres-resource';
|
|
2
|
+
export const DEFAULT_ALIAS = DEFAULT_DB_ALIAS;
|
|
3
|
+
/**
|
|
4
|
+
* Config alias conventionally holding the superuser connection the admin path needs.
|
|
5
|
+
* Never the same as {@link DEFAULT_ALIAS} — the application connects as a least
|
|
6
|
+
* privileged role and must not carry superuser credentials in the same config entry.
|
|
7
|
+
*/
|
|
8
|
+
export const DEF_ADMIN_ALIAS = 'pg-admin';
|
|
9
|
+
/** The database `CREATE DATABASE` has to be issued from, since it can't create the one it's in. */
|
|
10
|
+
export const DEF_MAINTENANCE_DB = 'postgres';
|
|
11
|
+
export const DEF_PORT = 5432;
|
|
12
|
+
/**
|
|
13
|
+
* `SELECT 1` readiness probe, generalized from the boot loop every OwlMeans deployment
|
|
14
|
+
* hand-rolled: a Postgres sidecar routinely accepts TCP before it accepts queries.
|
|
15
|
+
*/
|
|
16
|
+
export const DEF_RETRIES = 30;
|
|
17
|
+
export const DEF_RETRY_DELAY = 2000;
|
|
18
|
+
/**
|
|
19
|
+
* Postgres caps connections cluster wide (`max_connections`, 100 by default), so the
|
|
20
|
+
* per-process pool stays deliberately small — unlike a Mongo driver pool, an oversized
|
|
21
|
+
* one here starves every other client of the same server.
|
|
22
|
+
*/
|
|
23
|
+
export const DEF_POOL_SIZE = 10;
|
|
24
|
+
/**
|
|
25
|
+
* Failures the readiness probe treats as final rather than as "not up yet":
|
|
26
|
+
* `28P01`/`28000` wrong credentials, `3D000` no such database, `42501` no rights.
|
|
27
|
+
* Retrying any of them just delays the same error by a minute.
|
|
28
|
+
*/
|
|
29
|
+
export const TERMINAL_CONNECT_CODES = ['28P01', '28000', '3D000', '42501'];
|
|
30
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAE9D,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAgB,CAAA;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAA;AAEzC,mGAAmG;AACnG,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAA;AAE5C,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAA;AAE5B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAA;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AAEnC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAA;AAE/B;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type * from './types.js';
|
|
2
|
+
export * from './consts.js';
|
|
3
|
+
export * from './bootstrap.js';
|
|
4
|
+
export * from './middleware.js';
|
|
5
|
+
export * from './service.js';
|
|
6
|
+
export * from './utils/config.js';
|
|
7
|
+
export * from './utils/connection.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Middleware } from '@owlmeans/context';
|
|
2
|
+
/**
|
|
3
|
+
* Run the work resources held back until every one of them had initialized.
|
|
4
|
+
*
|
|
5
|
+
* Foreign keys are the reason this exists: a key points at a table another resource owns,
|
|
6
|
+
* and resource initialization order is registration order, not dependency order. The
|
|
7
|
+
* Loading stage of the Context middleware type runs immediately after the last resource's
|
|
8
|
+
* `init()` — the first moment at which every table is known to exist.
|
|
9
|
+
*/
|
|
10
|
+
export declare const drainMiddleware: (alias?: string) => Middleware;
|
|
11
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAKnD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,QAAO,MAAsB,KAAG,UAW9D,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MiddlewareStage, MiddlewareType } from '@owlmeans/context';
|
|
2
|
+
import { DEFAULT_ALIAS } from './consts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Run the work resources held back until every one of them had initialized.
|
|
5
|
+
*
|
|
6
|
+
* Foreign keys are the reason this exists: a key points at a table another resource owns,
|
|
7
|
+
* and resource initialization order is registration order, not dependency order. The
|
|
8
|
+
* Loading stage of the Context middleware type runs immediately after the last resource's
|
|
9
|
+
* `init()` — the first moment at which every table is known to exist.
|
|
10
|
+
*/
|
|
11
|
+
export const drainMiddleware = (alias = DEFAULT_ALIAS) => ({
|
|
12
|
+
type: MiddlewareType.Context,
|
|
13
|
+
stage: MiddlewareStage.Loading,
|
|
14
|
+
apply: async (context) => {
|
|
15
|
+
if (!context.hasService(alias)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await context.service(alias).drain();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGnE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAgB,aAAa,EAAc,EAAE,CAAC,CAAC;IAC7E,IAAI,EAAE,cAAc,CAAC,OAAO;IAC5B,KAAK,EAAE,eAAe,CAAC,OAAO;IAE9B,KAAK,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;QACrB,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAM;QACR,CAAC;QAED,MAAM,OAAO,CAAC,OAAO,CAAkB,KAAK,CAAC,CAAC,KAAK,EAAE,CAAA;IACvD,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
|
|
2
|
+
import type { PostgresService } from './types.js';
|
|
3
|
+
type Config = ServerConfig;
|
|
4
|
+
interface Context<C extends Config = Config> extends ServerContext<C> {
|
|
5
|
+
}
|
|
6
|
+
export declare const makePostgresDbService: (alias?: string) => PostgresService;
|
|
7
|
+
export declare const appendPostgres: <C extends Config, T extends Context<C> = Context<C>>(context: T, alias?: string) => T;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=service.d.ts.map
|