* feat(db): install into a configurable PostgreSQL schema
Add DB_SCHEMA (and dbOptions.schema / createDatabase({ schema }) /
runMigrations(db, { schema })) so notifkit can live in a schema other than
public. Defaults to public, where behaviour is unchanged.
For a custom schema, runMigrations creates the schema, retargets the
bundled migrations at it (rewrites the "public". qualifiers drizzle-kit
emits and sets search_path for the unqualified objects with SET LOCAL,
inside the migration transaction), refuses any migration that still
mentions public afterwards, and keeps the drizzle journal in the same
schema. Afterwards it checks via information_schema that every table of
the package's Drizzle schema exists in the target schema and throws with
the names of any that are missing. createDatabase sets search_path on
every connection.
Add a notifkit-migrate bin (next to notifkit-create-admin) that reads
DATABASE_URL and DB_SCHEMA, applies the migrations, closes the connection
and exits non-zero on failure. The README now uses it for production
migrations instead of drizzle-kit, which ignores DB_SCHEMA, and warns
that switching an existing public install to DB_SCHEMA without moving
its tables first makes it look empty.
Closes#4
* test(db): fix typecheck and lint, move schema tests under tests/
---------
Co-authored-by: Alexander Savchenko <savchenko_av@nlmk.com>
Co-authored-by: coo1estguy <70365929+coolestcodeguy@users.noreply.github.com>
z.coerce.boolean() evaluates Boolean(value), coercing 'false' and '0' to true. Setting TRUST_PROXY=false inadvertently enabled proxy trust, allowing spoofed X-Forwarded-For headers to bypass login brute-force throttling and unsubscribe limits.
Align parsing with PIPELINE_FUSED: only 'true' or '1' enable proxy trust, defaulting to false when unset.
- Delete stream messages with XDEL immediately after XACK to free Redis memory and prevent re-processing on recreated consumer groups
- Add IDEMPOTENCY_TTL_SECONDS config (default 1h) for internal stage deduplication
- Preserve 24h idempotency window specifically for client-provided X-Idempotency-Key
- Update queue and pipeline tests to assert deletion on ack and verify TTL behavior
Workspace packages import 'notifkit' which resolves to root dist/index.d.mts. Running build first ensures declaration files exist before workspace typecheck runs.
- Scope root tsconfig to exclude workspace packages and restore declaration resolution
- Align MCP package zod dependency to ^4.6.5 and update record schemas to explicit key/value syntax
- Upgrade ai dependency to ^7.0.118 to restore bundled dist/index.d.ts declarations
- Fix rate limiter inFlightLease promise initialization in TS strict mode
- Add typecheck:workspaces script and wire into CI workflow
- Run k6 inside Docker bridge network to eliminate host networking and Node loop overhead
- Add dual-scenario execution for 45s warm-up and 180s steady-state isolation
- Track custom k6 Trend metrics and parse steady-state latency percentiles
- Support --warmup and --vus CLI flags in test runner
- Buffer concurrent publish and publishBatch calls into a single pipeline flush per event loop tick
- Apply MAXLEN ~ trimming only to the last command in each batch to reduce scan overhead
- Throttle periodic stream length monitoring to once every 5 seconds
- Key in-memory authCache by raw token instead of tokenHash
- Compute SHA-256 only on cache misses when querying database
- Store structured auth record to avoid string splitting and parseInt
- Maintain tokenHash-to-token reverse map for targeted cache invalidations
- Replace per-request synchronous Redis check with ApiRateLimiter in request pipeline
- Replace eager new URL() allocation with indexOf query pathname slice and lazy getters
- Invalidate in-memory rate limiter leases on API key broadcast
- Upgrade zod to ^4.6.5 along with compatible dependency updates across ai, dotenv, drizzle-orm, ioredis, pino, postgres, and dev tools.
- Adjust tests/workers-ai.test.ts mock UUIDs to adhere to RFC 4122 v4 validation in Zod 4.
- Update tests/http.test.ts validation message expectations for Zod 4.
- Replace generic server container names with role-based definitions (api-server-1, api-server-2, enricher-worker, engine-worker, delivery-worker).
- Add serverNodes option in distributed profiling harness to route and health-check designated worker and API nodes.
- Add multi-run statistical metrics (mean, median, min, max, stdDev) reporting to money-scaling benchmark runner.
- Support budget tier arguments with or without leading dollar signs in test:money
- Tune worker concurrency and provider mock latency defaults for scaling tests
- Add two-phase idempotency with acquireLease and markProcessed using a single-key SET NX fast path
- Return { ack: false } on lock collisions to leave messages pending in the stream instead of acknowledging them
- Bound provider dispatch timeout within the lock lease window to prevent duplicate delivery under slow responses
- Replace per-message Redis MULTI/INCR retry keys with Redis Stream PEL delivery counts
- Handle LockHeldError in BaseWorker to skip message acknowledgement when another worker holds the lock
Eliminate dynamic imports on execution hot paths and module startup:
- Import drizzle-orm sql statically in delivery worker
- Import node:crypto and transport registry statically in API handlers
- Import provider SDKs statically in Twilio and FCM packages
- Replace root barrel imports in config and db with direct submodules to prevent circular evaluation
- Update workflow tests to preserve redis mock exports via importOriginal
- Register custom Lua scripts during RedisClient initialization via defineCommand
- Pre-compile checkApiRateLimit, throttleProvider, throttleUser, releaseLock, renewLock, and schedulerPoll
- Augment ioredis Redis and ChainableCommander type interfaces for custom commands
- Maintain eval fallbacks across callers for compatibility with test mocks
- Add test suite in tests/redis.test.ts verifying registration, execution, and fallbacks
- Replace O(N) ZSET with O(1) sliding-window counter across two buckets
- Define LUA_SLIDING_WINDOW_COUNTER at module level to avoid per-request string allocations
- Eliminate crypto randomBytes generation on hot path
- Use Redis hash tags for cluster-safe multi-key evaluation
- Add tests for window weighting, TTL, and limit rejection
- Cap Redis stream retention at 100,000 messages (down from 10,000,000) in
src/config/index.ts and .env.example to prevent unbounded memory growth.
- In src/services/api/handlers.ts, update getSystemMetrics to resolve stream
queue depths using consumer group lag and in-flight pending entries (via
XINFO GROUPS) instead of raw XLEN. This prevents acknowledged and delivered
notifications from persisting as ghost backlog in the dashboard.
- Retain XLEN measurement for DEAD_LETTER queue where messages persist until
replayed or deleted.
- Adjust dashboard capacity scaling to 100,000 items in AnalyticsPage.tsx and
update backpressure warning threshold to 80,000 in DashboardHeader.tsx.
- Ensure 0-depth queues render empty (0% width) in the backlog gauge meter.
- Update queue test expectations in tests/queue.test.ts and add unit tests
verifying consumer group lag resolution in tests/api-admin.test.ts.
- Provide Docker Compose test harness with CPU and memory resource limits\n- Add single-node CPU scaling benchmark suite (0.5 to 4.0 vCPU)\n- Add distributed multi-node scaling and budget benchmarks (, , /mo)\n- Support partitioned service topologies (dedicated API, pipeline worker, and delivery worker instances)\n- Implement configurable provider latency simulation with randomized network jitter\n- Support dynamic client and worker concurrency tuning
- Support latencyMs in ConsoleTransportOptions as a fixed number or range string (e.g. '60-150')\n- Apply randomized jitter between bounds to simulate real-world vendor network response times
- Collapse Enricher and Decision Engine into direct outbound dispatch: Enricher evaluates quiet hours, topic opt-outs, and renders templates in-memory before publishing directly to STREAMS.OUTBOUND_*, eliminating intermediate STREAMS.ENRICHED_* Redis stream hops
- Add transactional direct fast-path in /v1/notify: Critical priority notifications with inline user targets bypass inbound and decision queues, rendering templates from cache and dispatching directly to STREAMS.OUTBOUND_CRITICAL
- Reduce first-delivery latency from 6.2s to 0.38s and increase wall drain throughput by up to 58% on budget infrastructure
- Add unit and integration tests covering Enricher direct outbound dispatch and API transactional fast-path routing
- Implement stream-keyed ACK buffering in StreamConsumer with periodic and capacity-triggered flushes
- Consolidate delivery outbox persistence to a single post-dispatch write, cutting PostgreSQL outbox writes by 50%
- Make retry key deletion and idempotency TTL extension non-blocking on message success
- Add host-unique consumer identifiers across distributed delivery, engine, and enricher workers
- Add unit tests verifying buffered stream ACK flushing behavior