Skip to content

feat: add @constructive-io/express-context + configurable pg-cache pool management#1213

Merged
pyramation merged 7 commits into
mainfrom
feat/express-context
May 23, 2026
Merged

feat: add @constructive-io/express-context + configurable pg-cache pool management#1213
pyramation merged 7 commits into
mainfrom
feat/express-context

Conversation

@pyramation
Copy link
Copy Markdown
Contributor

@pyramation pyramation commented May 22, 2026

Summary

Extracts shared Express middleware into @constructive-io/express-context, makes pg-cache configurable via env vars, aligns cache limits, expands pg-query-context with a callback-based API, and adds shared seed SQL fixtures for integration testing.

1. @constructive-io/express-context (new package)

  • buildPgSettings() — constructs pgSettings from JWT claims + request metadata
  • withPgClient() — re-exported from pg-query-context (no duplicate implementation)
  • requestIdMiddleware() — generates or forwards X-Request-Id
  • createContextMiddleware() — populates req.constructive with tenant-scoped DB context

2. pg-query-context expansion

  • New: withPgClient<T>(pool, context, fn, opts?) — callback-based API for multi-query RLS transactions
  • New: WithPgClientOptions interface with skipTransaction option
  • Existing: pgQueryContext() single-query API unchanged (still default export)
  • express-context now re-exports withPgClient from here — single source of truth

3. pg-cache configuration

Env var Default Was
PG_CACHE_MAX 50 10 (hardcoded)
PG_CACHE_TTL_MS 1 year 1 year (hardcoded)
PG_POOL_MAX 5 10 (node-postgres default)
PG_POOL_IDLE_TIMEOUT_MS 30000 none
PG_POOL_CONNECTION_TIMEOUT_MS 5000 none

Cache limit alignment: pgCache 10→50, graphileCache 15→50, svcCache 25→50

4. Server wiring

  • llm-api.ts uses req.constructive instead of manual getPgSettings()/withRlsClient()
  • Server types restored to original (no re-exports from express-context)

5. Shared seed fixtures (__fixtures__/seed/)

Composable SQL layers extracted from graphql/server-test/__fixtures__/seed/simple-seed-services/:

  • services/setup.sql — roles, extensions, stamps, metaschema tables, services tables, settings, modules, grants
  • services/test-data.sql — example database, APIs, domains, API→schema linkage
  • app-schemas/simple-pets/schema.sql — simple-pets schemas + animals table
  • app-schemas/simple-pets/test-data.sql — 5 test animals

Review & Testing Checklist for Human

  • Verify pg-query-context's new withPgClient signature matches usage in express-context and elsewhere
  • Verify req.constructive is populated correctly in the LLM routes
  • Verify pg-cache env var defaults are sensible for your deployment (PG_CACHE_MAX=50, PG_POOL_MAX=5)
  • Run server.integration.test.ts to confirm existing seeds still work

Notes

  • pg-query-context is now the single source of truth for RLS-scoped query execution (both single-query and callback APIs)
  • The shared seed fixtures are foundational for express-context integration tests (next step)
  • Planning issue: constructive-planning#917
  • Pool architecture review: constructive-planning#924

Link to Devin session: https://app.devin.ai/sessions/2b5a29d83d3f478e8d3d972653b4879c
Requested by: @pyramation

Extract reusable Express middleware for Constructive tenant context into
a new shared package (@constructive-io/express-context). This enables
standalone Express services (e.g., LLM sidecar) to reuse domain
resolution, JWT auth, pgSettings, and withPgClient without depending
on the full PostGraphile server.

New package exports:
- Shared types (ApiStructure, RlsModule, AuthSettings, etc.)
- buildPgSettings() — constructs SET LOCAL key-value pairs from API + token
- withPgClient() — tenant-scoped RLS transaction helper
- requestIdMiddleware() — UUID correlation ID (X-Request-Id or generated)
- createContextMiddleware() — composes all into req.constructive

Changes to graphql/server:
- types.ts now re-exports from express-context (backwards compatible)
- middleware/types.ts re-exports ConstructiveAPIToken from express-context
- server.ts wires requestIdMiddleware + createContextMiddleware
- llm-api.ts simplified to use req.constructive instead of manual
  getPgSettings/withRlsClient/getPgPool helpers (~100 lines removed)

Refs: constructive-io/constructive-planning#917
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

…he limits

- PgPoolCacheManager now reads PG_CACHE_MAX (default: 50, was hardcoded 10)
  and PG_CACHE_TTL_MS from environment, with constructor override support
- getPgPool() reads PG_POOL_MAX (default: 5), PG_POOL_IDLE_TIMEOUT_MS
  (default: 30000), PG_POOL_CONNECTION_TIMEOUT_MS (default: 5000) and
  passes them to new pg.Pool()
- New PgPoolConfig interface in pg-env for callers that want explicit control
- Align all three LRU caches to max=50: pgCache, graphileCache, svcCache
  (was 10/15/25 — mismatch caused Graphile instances to reference ended pools)
- Fix pre-existing test that expected close() to be final (it re-opens)
@devin-ai-integration devin-ai-integration Bot changed the title feat: add @constructive-io/express-context package + wire into server feat: add @constructive-io/express-context + configurable pg-cache pool management May 22, 2026
…ntext

Server types belong in graphql/server — nobody imports from there externally.
express-context keeps its own independent type definitions and only augments
Express.Request with the 'constructive' property it actually adds.
express-context's context.ts reads req.api, req.token, req.requestId,
and req.clientIp — these must be in its augmentation declaration.
…testing

Extract the metaschema/services DDL and test data from
graphql/server-test/__fixtures__/seed/simple-seed-services/ into
root-level __fixtures__/seed/ with composable layers:

- services/setup.sql: roles, extensions, stamps, metaschema tables,
  services tables, settings, modules, grants
- services/test-data.sql: example database, schemas, APIs, domains,
  API->schema linkage
- app-schemas/simple-pets/schema.sql: simple-pets schemas + animals table
- app-schemas/simple-pets/test-data.sql: 5 test animals

Tests compose what they need via seed.sqlfile([...paths]).
README documents layer dependencies and well-known IDs.
Expand pg-query-context with a withPgClient<T>(pool, context, fn, opts?)
function for multi-query RLS transactions. This is the callback-based
counterpart to the existing single-query pgQueryContext() API.

express-context now re-exports withPgClient from pg-query-context
instead of maintaining its own duplicate implementation.

Also exports ExecOptions and WithPgClientOptions interfaces.
Verifies that req.constructive is correctly populated when requests
flow through the full middleware stack:

  parseDomains → requestId → API resolver → auth → createContextMiddleware

Tests cover:
- Domain-based API resolution (app.test.constructive.io → 401 on LLM endpoint)
- Unknown/invalid domain rejection (404)
- Request ID propagation (X-Request-Id header)
- RLS-scoped query execution via withPgClient (reads 5 animals)
- Anonymous role pgSettings application
- Full chain verification (domain → pgSettings → PostGraphile → query)
@pyramation pyramation merged commit 6c59fbf into main May 23, 2026
37 checks passed
@pyramation pyramation deleted the feat/express-context branch May 23, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant