feat: add @constructive-io/express-context + configurable pg-cache pool management#1213
Merged
Conversation
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
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…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)
…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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extracts shared Express middleware into
@constructive-io/express-context, makespg-cacheconfigurable via env vars, aligns cache limits, expandspg-query-contextwith 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 metadatawithPgClient()— re-exported frompg-query-context(no duplicate implementation)requestIdMiddleware()— generates or forwardsX-Request-IdcreateContextMiddleware()— populatesreq.constructivewith tenant-scoped DB context2.
pg-query-contextexpansionwithPgClient<T>(pool, context, fn, opts?)— callback-based API for multi-query RLS transactionsWithPgClientOptionsinterface withskipTransactionoptionpgQueryContext()single-query API unchanged (still default export)express-contextnow re-exportswithPgClientfrom here — single source of truth3.
pg-cacheconfigurationPG_CACHE_MAXPG_CACHE_TTL_MSPG_POOL_MAXPG_POOL_IDLE_TIMEOUT_MSPG_POOL_CONNECTION_TIMEOUT_MSCache limit alignment: pgCache 10→50, graphileCache 15→50, svcCache 25→50
4. Server wiring
llm-api.tsusesreq.constructiveinstead of manualgetPgSettings()/withRlsClient()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, grantsservices/test-data.sql— example database, APIs, domains, API→schema linkageapp-schemas/simple-pets/schema.sql— simple-pets schemas + animals tableapp-schemas/simple-pets/test-data.sql— 5 test animalsReview & Testing Checklist for Human
pg-query-context's newwithPgClientsignature matches usage inexpress-contextand elsewherereq.constructiveis populated correctly in the LLM routesserver.integration.test.tsto confirm existing seeds still workNotes
pg-query-contextis now the single source of truth for RLS-scoped query execution (both single-query and callback APIs)Link to Devin session: https://app.devin.ai/sessions/2b5a29d83d3f478e8d3d972653b4879c
Requested by: @pyramation