Skip to content

combined branch for Improve performance, stability and memory use for large databases#280

Draft
Abhinegi2 wants to merge 16 commits into
masterfrom
test/combined-261
Draft

combined branch for Improve performance, stability and memory use for large databases#280
Abhinegi2 wants to merge 16 commits into
masterfrom
test/combined-261

Conversation

@Abhinegi2

@Abhinegi2 Abhinegi2 commented Jun 27, 2026

Copy link
Copy Markdown
Member

Combines all PRs from the #261 perf stack onto one branch so the integrated behavior can be tested in CI and on staging (Tier-3 real-replication / memory test). The actual changes merge via the individual PRs #263#270.

  • Unit 231 passed, e2e 50 passed, lint clean.
  • Manual Tier-2 vs real CouchDB 3.5.2: permission filtering, _changes doc-strip + lostPermissions, not_found passthrough, streamed 200 reads, gzip compression, clearLocal, timeout fail-fast.
  • Observed: brief fail-closed window if a request hits before Config:Permissions finishes loading at startup (self-corrects); likely pre-existing.

Summary by CodeRabbit

  • New Features

    • Added support for configurable CouchDB timeout and connection limits.
    • Improved performance with cached login and permission checks.
    • Large CouchDB responses can now be streamed and compressed more efficiently.
  • Bug Fixes

    • Reduced failures during bulk document and changes requests by handling large responses incrementally.
    • Improved stability when deleting local documents and processing partial failures.
    • Change notifications now use a lighter payload and better tolerate retries and missing data.

sleidig and others added 13 commits June 27, 2026 14:58
BasicAuthStrategy called CouchDB _session for every request — an extra
round trip plus a password hash verification inside CouchDB, amplified
by replication bursts of hundreds of requests.

Successful logins are now cached for 60s, keyed by a SHA-256 digest of
the credentials (plaintext is never stored). Failed logins are never
cached, and a wrong password never matches a cached entry since the
digest differs. A max-entries cap bounds memory.

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clearLocal fetched all _local docs at once (one checkpoint doc exists
per client replication, so this can be large) and fired every DELETE
in parallel via Promise.all - momentarily flooding CouchDB on big
deployments, and rejecting wholesale on the first failure while the
remaining deletes kept running detached.

Now:
- _local_docs is fetched in pages of 500 (deleted docs disappear from
  the index, so re-fetching pages through the remainder)
- deletes run with bounded concurrency (10 in flight)
- failures are logged, all docs are still attempted, and a summary
  error is thrown at the end (endpoint still reports failure)

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No axios timeout was configured anywhere, so a hung CouchDB connection
(e.g. half-open socket) would hang client requests - and the internal
changes feed - indefinitely. There was also no explicit http agent, so
request bursts could open an unbounded number of connections.

The shared CouchDB HTTP client is now configured with:
- a 60s request timeout (DATABASE_TIMEOUT_MS), safely above the 50s
  longpoll window of the internal changes feed
- keep-alive agents capped at 50 sockets (DATABASE_MAX_SOCKETS)

Both are documented in README and .env.template.

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the compression middleware so that large JSON responses (e.g.
_bulk_get / _all_docs payloads during initial sync) are gzipped for
clients on slow links. Responses that already carry a Content-Encoding
(proxied CouchDB responses) and non-compressible content types
(attachments) are skipped by the middleware's default filter, as is
anything below the 1kb threshold.

Registered in RestrictedEndpointsModule so the behavior is covered by
the e2e suite.

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- filterBulkDocsRequest: index existing docs in a Map instead of
  scanning all response rows per requested doc (was O(n²) — ~10⁶
  comparisons for a 1000-doc _bulk_docs push)
- _changes: drop doc bodies per fetch iteration when include_docs is
  not requested, instead of accumulating all doc bodies in memory and
  copying every result object again in a final mapping pass
- filterAllDocsResponse: pass through error rows for unknown keys
  ({key, error: "not_found"} rows have no id) instead of crashing with
  a TypeError that turned the whole request into a 500

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shared longpoll _changes feed (DocumentChangesService) requested
include_docs=true, so every document written to the database was
transferred to and parsed by the backend continuously — although its
only consumers just compare document IDs:

- UserIdentityService only invalidates cache entries by id
- RulesService only needs the body of the single Config:Permissions doc

The feed now emits a narrowed DocumentChangeEvent (id/seq/deleted) so
the compiler enforces that no consumer relies on doc bodies, and
RulesService fetches the permission document on demand when its ID
appears in the feed (serialized via concatMap; fetch failures keep the
previous in-memory permissions and are logged).

This removes constant O(write volume × doc size) overhead on systems
with large, active databases.

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Building a DocumentAbility runs on every request: all rules are
deep-cloned via JSON.parse(JSON.stringify(...)) for user variable
injection and then compiled by CASL. The result only changes when the
permission config or the user's identity changes.

Abilities are now cached per user identity (id, name, roles, projects
- everything that influences computed rules), guarded by:
- a new RulesService.configVersion counter that increments whenever
  the in-memory permission config is swapped (initial load, bootstrap
  mode, live config updates)
- a 5-minute TTL aligned with the user identity cache
- a max-entries cap as memory bound for very many distinct users

Part of #261

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #109 for the read-heavy replication endpoints:
_all_docs (GET/POST), _bulk_get, _find and _changes.

Previously every response was fully buffered by axios, JSON.parsed
into an object graph, filtered into a copy and re-serialized as one
giant string - a peak memory cost of roughly 4-5x the raw payload per
request, multiplied by concurrent syncing clients. An unbounded
GET /db/_all_docs?include_docs=true could pull the whole database
into heap.

Now:
- CouchdbService gains getStream/postStream (axios responseType
  stream, Accept-Encoding identity on the internal hop, errors mapped
  to the same HttpExceptions as buffered methods)
- JsonArrayFilterTransform incrementally re-serializes a JSON object
  from stream-json tokens, filtering/transforming the items of one
  top-level array while passing envelope fields through - without
  holding the document or the array in memory
- _all_docs/_bulk_get/_find pipe CouchDB responses through this
  filter directly into the client response with backpressure
  (stream.pipeline)
- _changes keeps its iterative fetch loop and time budget but writes
  each batch's permitted results to the client immediately instead of
  accumulating them
- per-item permission predicates are shared between the buffered
  service methods and the streaming endpoints

Error semantics: failures before the first byte yield regular error
responses; failures mid-stream abort the connection so clients see a
truncated response (PouchDB retries) instead of valid-looking partial
JSON. Truncated upstream bodies are detected by the transform and
never produce complete-looking output.

Behavior change: the streamed POST endpoints now return 200 (matching
CouchDB) instead of NestJS's default 201.

Closes #109

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	src/restricted-endpoints/replication/bulk-document/bulk-document.service.ts
#	src/restricted-endpoints/replication/changes/changes.controller.ts
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Abhinegi2, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66c18bc3-6b44-4f71-ba9d-52c55b7e6d24

📥 Commits

Reviewing files that changed from the base of the PR and between de39217 and a958992.

📒 Files selected for processing (13)
  • src/admin/admin.service.ts
  • src/auth/guards/basic-auth/basic-auth.strategy.ts
  • src/common/json-array-filter.ts
  • src/common/ttl-cache.spec.ts
  • src/common/ttl-cache.ts
  • src/couchdb/couchdb.module.spec.ts
  • src/couchdb/couchdb.module.ts
  • src/permissions/permission/permission.service.ts
  • src/permissions/rules/rules.service.spec.ts
  • src/permissions/rules/rules.service.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-document.service.spec.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-document.service.ts
  • src/restricted-endpoints/replication/changes/changes.controller.ts
📝 Walkthrough

Walkthrough

This PR converts bulk-document and changes replication endpoints from Observable-based full-response buffering to Node stream pipelines using a new JsonArrayFilterTransform. It adds in-memory caches for Basic Auth logins and CASL abilities, refactors DocumentChangesService to emit lightweight DocumentChangeEvent objects, rewrites AdminService.clearLocal with pagination and bounded concurrency, and adds configurable CouchDB HTTP timeout/socket limits with compression middleware.

Changes

Streaming Replication, Caching, and CouchDB Infrastructure

Layer / File(s) Summary
New dependencies and config docs
package.json, .env.template, README.md
Adds compression and stream-json runtime packages plus their types, and documents DATABASE_TIMEOUT_MS/DATABASE_MAX_SOCKETS optional env vars.
CouchDB HTTP module: timeout and keep-alive agents
src/couchdb/couchdb.module.ts, src/couchdb/couchdb.module.spec.ts
Exports env-var names and defaults, adds couchdbHttpOptions factory building keep-alive HTTP/HTTPS agents, and switches to HttpModule.registerAsync with ConfigService injection.
CouchdbService: getStream/postStream methods
src/couchdb/couchdb.service.ts
Adds getStream and postStream delegating to a new requestStream helper that forces responseType:'stream' and Accept-Encoding:identity; adds toBufferedError to buffer streamed error bodies.
DocumentChangesService: lightweight DocumentChangeEvent
src/couchdb/document-changes.service.ts, src/couchdb/document-changes.service.spec.ts
Introduces DocumentChangeEvent interface with id/seq/deleted, removes include_docs from _changes requests, maps raw results to the new shape.
JsonArrayFilterTransform: streaming JSON array filter
src/common/json-array-filter.ts, src/common/json-array-filter.spec.ts
Implements jsonTokenParser() and JsonArrayFilterTransform that incrementally re-serializes a root JSON object while filtering a named top-level array field via mapItem callback using a token state machine and Assembler.
BulkDocumentService: per-item filter helpers
src/restricted-endpoints/replication/bulk-document/bulk-document.service.ts, ...service.spec.ts
Extracts bulkGetResultMapper, allDocsRowFilter, and findDocFilter helpers from inline filter logic; adds pass-through for CouchDB error rows missing id.
Bulk-document endpoints: Observable→streaming pipeline
src/restricted-endpoints/replication/bulk-document/bulk-doc-endpoints.controller.ts, ...controller.spec.ts
Converts _find, _bulk_get, _all_docs POST/GET from Observable-based full-response mapping to async handlers that call postStream/getStream and pipe through JsonArrayFilterTransform; adds mid-stream error handling that destroys the connection if headers were already sent.
ChangesController: in-memory assembly→chunked streaming
src/restricted-endpoints/replication/changes/changes.controller.ts, ...controller.spec.ts
Rewrites changes endpoint to iteratively write JSON chunks with backpressure via writeChunk; sets headers on first iteration, writes results incrementally, finalizes envelope fields, and destroys connection on mid-stream errors.
Response compression middleware
src/restricted-endpoints/restricted-endpoints.module.ts, test/compression.e2e-spec.ts
Inserts compression() middleware for all routes before body-parser; validated with an E2E suite covering large/small threshold and _all_docs compression.
AdminService.clearLocal: paged batching and bounded concurrency
src/admin/admin.service.ts, src/admin/admin.service.spec.ts
Rewrites clearLocal to page _local_docs with CLEAR_LOCAL_BATCH_SIZE, delete with Promise.allSettled up to CLEAR_LOCAL_CONCURRENCY in flight, log per-failure warnings, and throw an aggregated error.
RulesService: configVersion counter and on-demand permission fetch
src/permissions/rules/rules.service.ts, src/permissions/rules/rules.service.spec.ts
Adds _configVersion/configVersion and setPermission(), refactors watchPermissionChanges to filter by permission doc ID and fetch on demand via concatMap, extracts applyUpdatedPermission to validate, version, clear caches, and schedule clearLocal.
PermissionService: CASL ability caching
src/permissions/permission/permission.service.ts, ...service.spec.ts
Adds abilityCache map with TTL and max-entry eviction; getAbilityFor returns cached abilities when configVersion and TTL match, otherwise rebuilds and stores.
BasicAuthStrategy: SHA-256 keyed login cache
src/auth/guards/basic-auth/basic-auth.strategy.ts, ...strategy.spec.ts
Adds loginCache map keyed by SHA-256 digest of username:password; validate() returns cached UserInfo within TTL, caches successes, never caches failures, evicts entire cache at max entries.
UserIdentityService spec and E2E alignment
src/permissions/user-identity/user-identity.service.spec.ts, test/replication.e2e-spec.ts, test/session-documents.e2e-spec.ts, test/utils/mock-couchdb.ts
Updates DocumentChangeEvent types across specs, corrects expected HTTP status from 201 to 200 for streaming endpoints, adds truncateNextAllDocs mock flag, and adds basic-auth caching E2E tests.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • Improve performance, stability and memory use for large databases #261: This PR directly implements the improvements described in that issue: streaming bulk-document endpoints, DocumentChangesService lightweight events, RulesService on-demand fetch, PermissionService ability caching, BasicAuthStrategy login caching, CouchDB HTTP config hardening, AdminService.clearLocal batching, and compression middleware.

Possibly related PRs

  • Aam-Digital/replication-backend#221: Both PRs modify DocumentChangesService and its downstream consumers in RulesService and UserIdentityService to react to CouchDB _changes feed events.
  • Aam-Digital/replication-backend#240: Both PRs modify RulesService startup, permission config lifecycle, and the permission-change watcher in src/permissions/rules/rules.service.ts.
  • Aam-Digital/replication-backend#262: Both PRs exercise AdminService.clearLocal triggered by RulesService permission-change detection, including the _local checkpoint deletion e2e behavior.

Poem

🐇 Hop, hop through the JSON stream,
No more buffering — what a dream!
Caches tucked under my fluffy ear,
Auth and abilities always near.
Sockets capped and bytes compressed,
This rabbit's pipeline is thoroughly blessed!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is broad but accurately reflects the PR’s performance, stability, and memory improvements for large-database behavior.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/combined-261

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Abhinegi2 Abhinegi2 changed the title test: combined branch for #261 PR stack (#263–#270) — DO NOT MERGE test: combined branch for #261 PR stack (#263–#270) Jun 27, 2026
@Abhinegi2 Abhinegi2 self-assigned this Jun 27, 2026
Comment thread src/common/json-array-filter.spec.ts
Comment thread src/common/json-array-filter.spec.ts
Comment thread src/common/json-array-filter.ts
Comment thread src/common/json-array-filter.ts
Comment thread src/couchdb/couchdb.module.spec.ts
Comment thread src/restricted-endpoints/replication/changes/changes.controller.ts
Comment thread src/restricted-endpoints/replication/changes/changes.controller.ts
@Abhinegi2
Abhinegi2 marked this pull request as ready for review June 29, 2026 05:28
@Abhinegi2 Abhinegi2 changed the title test: combined branch for #261 PR stack (#263–#270) combined branch for Improve performance, stability and memory use for large databases Jun 29, 2026
@Abhinegi2 Abhinegi2 linked an issue Jun 29, 2026 that may be closed by this pull request
@Abhinegi2

Abhinegi2 commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

Manual UI testing steps (dev site)

A. Initial sync / load — streaming (#270, #263, #267)

  • Fresh login (incognito / cleared site data) → full initial replication: all lists populate, no console/network errors, completes in reasonable time
  • Reload app → fast incremental sync (_changes since last_seq), data intact

B. Permission filtering — must not regress

  • Limited-role user sees only permitted entity types/records
  • Admin sees everything
  • Revoke a permission / change a role → next sync purges now-forbidden records locally (lostPermissions)

C. Write path — _bulk_docs + audit (#267)

  • Create a record → saves, appears, persists after reload (synced to server)
  • Edit a record → change persists + syncs
  • Delete a record → removed + syncs (tombstone)
  • Limited user edits a type they cannot write → denied gracefully (no crash)

D. Bulk / large data (item 5, streaming)

  • CSV / bulk import of many records → all imported, no timeout/hang

E. Resilience — auth cache (#265), timeouts (#266)

  • Log out / log in repeatedly → login always works (60s auth cache)
  • App stays responsive, no infinite spinners

@george-neha

Copy link
Copy Markdown
  1. Under Recurring Activities- the participant list is blank for some records check for record Class 6B
Screenshot 2026-06-29 at 11 23 22 AM 2. There is mismatch in the participant list shown under recurring Attendance and Record Attendance- check for driving class. under record attendance, attendance is being captured for 36 participants image 3. In import, in step 4- review and edit data, the data takes some time to load image

@Abhinegi2

Copy link
Copy Markdown
Member Author

@george-neha

  1. I checked this issue, and it seems to be caused by a corrupted database record, which is why the participant list is broken for that recurring activity. I also created a new recurring activity and couldn't reproduce the issue there, so it doesn't appear to affect newly created recurring activities.

  2. I checked this as well. It looks like there are two different records named "Driving Class"
    one belongs to the Recurring Activity entity and the other to the Event entity. To verify this, I renamed the Event record from "Driving Class" to "Driving Class 1", and the difference became visible. Since this is the dev site, it appears to be a data inconsistency rather than an application issue.

# Conflicts:
#	package-lock.json
#	package.json
#	src/restricted-endpoints/replication/bulk-document/bulk-doc-endpoints.controller.ts
@Abhinegi2

Copy link
Copy Markdown
Member Author

also investigated the import loading issue and found that it wasn't actually a bug. the csv I was importing contained the Address field, and while mapping the columns, I didn't check the Skip Address Lookup checkbox. Because of that, the import was taking 2–3 minutes, depending on the number of rows. Once I enabled that option, the import completed much faster.
I think this wasn't an actual issue after all. @george-neha, could you confirm by checking with your sheet as well?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/restricted-endpoints/replication/changes/changes.controller.ts (1)

67-75: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the _changes doc-body contract comment.

Line 70 now contradicts Line 120: include_docs=true returns doc, while other requests strip it. Please align the comment with the actual streaming behavior.

Proposed comment fix
-   * Even if `include_docs: true` is set, the stream will not return the document content.
+   * Document bodies are included only when `include_docs=true`; otherwise `doc`
+   * is stripped after permission filtering.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/restricted-endpoints/replication/changes/changes.controller.ts` around
lines 67 - 75, The `_changes` doc-body contract comment in `ChangesController`
is outdated and contradicts the actual behavior handled later in the stream
logic. Update the comment near the changes feed description to match the current
`include_docs` behavior in `changes.controller.ts`, where `include_docs=true`
can return `doc` and other requests strip it, and keep the wording aligned with
the streaming flow in the `getChanges`/changes feed handler.
🧹 Nitpick comments (1)
src/restricted-endpoints/replication/changes/changes.controller.spec.ts (1)

79-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add one test path where write() returns false.

The response mock always takes the fast path, so the new writeChunk() backpressure branch (drain/close listener cleanup) can regress unnoticed. Consider adding a focused mock variant that returns false once and invokes the registered drain callback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/restricted-endpoints/replication/changes/changes.controller.spec.ts`
around lines 79 - 89, Add a focused test case for the backpressure path in the
response mock used by changes.controller.spec.ts so `writeChunk()` exercises the
`drain`/`close` listener cleanup branch. Keep the existing mock for the fast
path, but add a variant where `write()` in the response mock returns false once,
then trigger the registered `drain` callback and assert the listeners are
cleaned up via the `writeChunk` flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/admin/admin.service.ts`:
- Around line 44-64: In AdminService’s `_local_docs` scan loop, the current
`ids.length === 0` break can stop pagination on a page that only contains
CouchDB-internal or already-attempted docs, so later checkpoints are missed.
Update the `firstValueFrom(this.couchdbService.get(...))` call in `AdminService`
to use `_local_docs` cursor pagination with `startkey_docid` plus `skip: 1`, and
keep advancing the cursor even when a page yields no usable IDs so the loop
continues through all documents.

In `@src/auth/guards/basic-auth/basic-auth.strategy.ts`:
- Around line 19-22: The Basic Auth cache is reusing completed logins for too
long, which effectively turns successful auth into a short-lived session and
delays password/role revocations. Update the logic in BasicAuthStrategy around
LOGIN_CACHE_TTL_MS, LOGIN_CACHE_MAX_ENTRIES, and validate() so it does not cache
authenticated results for 60 seconds; instead, cache only in-flight login
attempts or remove the completed-login reuse entirely. Keep
couchdbService.login() and the roles passed to PermissionService.getAbilityFor()
in sync with the latest CouchDB state so authorization changes take effect
immediately.

In `@src/couchdb/couchdb.module.ts`:
- Around line 33-35: The timeout parsing in the CouchDB module currently accepts
any numeric value from DATABASE_TIMEOUT_ENV, which can break longpoll _changes
requests used by document-changes.service. Update the timeout handling in the
module initialization so undersized values are either rejected or clamped to the
documented minimum before being passed into the CouchDB client, and keep
DEFAULT_DATABASE_TIMEOUT_MS as the fallback when the config is missing or
invalid.

In `@src/permissions/permission/permission.service.ts`:
- Around line 93-102: The ability cache key in abilityCacheKey() only covers a
subset of user fields, so cached CASL abilities can be reused across users whose
other rule-injectable properties differ. Update the key derivation in
PermissionService to include every user field that RulesService can interpolate
into `${user...}` conditions, not just id, name, roles, and projects, and keep
the anonymous fallback unchanged so each distinct user state maps to a unique
cache entry.

In `@src/permissions/rules/rules.service.ts`:
- Around line 188-197: The permission refresh flow in RulesService currently
swallows a 404 from couchdbService.get and leaves stale in-memory rules active.
Update the observable chain around the Permission.DOC_ID fetch in RulesService
to inspect the lightweight event’s deleted flag before calling get, and when a
Config:Permissions doc is deleted switch the in-memory state to
bootstrap/fail-closed permissions instead of returning EMPTY. Also invalidate
any permission caches in the same deletion path so the previous rules are not
retained.

In
`@src/restricted-endpoints/replication/bulk-document/bulk-doc-endpoints.controller.ts`:
- Around line 169-199: The restricted `_all_docs` handlers in `allDocs` and
`allDocsGet` currently allow requests without `include_docs=true`, which lets
`streamAllDocs` leak metadata when `BulkDocumentService.allDocsRowFilter()` sees
rows without `doc`. Add a guard in these endpoints before calling
`couchdbService.postStream`/`getStream` to require `include_docs=true` for
restricted access, or otherwise fetch documents needed for permission checks;
use the `allDocs`, `allDocsGet`, and `streamAllDocs` flow as the entry point for
the fix.

---

Outside diff comments:
In `@src/restricted-endpoints/replication/changes/changes.controller.ts`:
- Around line 67-75: The `_changes` doc-body contract comment in
`ChangesController` is outdated and contradicts the actual behavior handled
later in the stream logic. Update the comment near the changes feed description
to match the current `include_docs` behavior in `changes.controller.ts`, where
`include_docs=true` can return `doc` and other requests strip it, and keep the
wording aligned with the streaming flow in the `getChanges`/changes feed
handler.

---

Nitpick comments:
In `@src/restricted-endpoints/replication/changes/changes.controller.spec.ts`:
- Around line 79-89: Add a focused test case for the backpressure path in the
response mock used by changes.controller.spec.ts so `writeChunk()` exercises the
`drain`/`close` listener cleanup branch. Keep the existing mock for the fast
path, but add a variant where `write()` in the response mock returns false once,
then trigger the registered `drain` callback and assert the listeners are
cleaned up via the `writeChunk` flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa88144b-9290-4452-8f97-dcfda31c47f7

📥 Commits

Reviewing files that changed from the base of the PR and between 82c6251 and de39217.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (30)
  • .env.template
  • README.md
  • package.json
  • src/admin/admin.service.spec.ts
  • src/admin/admin.service.ts
  • src/auth/guards/basic-auth/basic-auth.strategy.spec.ts
  • src/auth/guards/basic-auth/basic-auth.strategy.ts
  • src/common/json-array-filter.spec.ts
  • src/common/json-array-filter.ts
  • src/couchdb/couchdb.module.spec.ts
  • src/couchdb/couchdb.module.ts
  • src/couchdb/couchdb.service.ts
  • src/couchdb/document-changes.service.spec.ts
  • src/couchdb/document-changes.service.ts
  • src/permissions/permission/permission.service.spec.ts
  • src/permissions/permission/permission.service.ts
  • src/permissions/rules/rules.service.spec.ts
  • src/permissions/rules/rules.service.ts
  • src/permissions/user-identity/user-identity.service.spec.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-doc-endpoints.controller.spec.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-doc-endpoints.controller.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-document.service.spec.ts
  • src/restricted-endpoints/replication/bulk-document/bulk-document.service.ts
  • src/restricted-endpoints/replication/changes/changes.controller.spec.ts
  • src/restricted-endpoints/replication/changes/changes.controller.ts
  • src/restricted-endpoints/restricted-endpoints.module.ts
  • test/compression.e2e-spec.ts
  • test/replication.e2e-spec.ts
  • test/session-documents.e2e-spec.ts
  • test/utils/mock-couchdb.ts

Comment thread src/admin/admin.service.ts Outdated
Comment thread src/auth/guards/basic-auth/basic-auth.strategy.ts
Comment thread src/couchdb/couchdb.module.ts Outdated
Comment thread src/permissions/permission/permission.service.ts Outdated
Comment thread src/permissions/rules/rules.service.ts Outdated
- extract shared TtlCache (LRU + expiry) for the ability and basic-auth
  login caches instead of duplicating TTL-Map logic; key ability cache on
  the whole user to avoid collisions on injectable ${user.*} fields
- enforce a MIN_DATABASE_TIMEOUT_MS floor so a too-low DATABASE_TIMEOUT_MS
  cannot abort the 50s longpoll changes feed
- retry the on-demand permission-document fetch on transient errors, and
  fail closed to bootstrap permissions when the permission doc is deleted
- harden _changes writeChunk with an 'error' listener + listener cleanup
- paginate clearLocal by startkey_docid so undeletable docs cannot block
  later pages (drops the unbounded attemptedIds set)
- remove dead filter*Response wrappers (retarget specs to the mappers) and
  the unused inTargetArray field
Comment thread src/admin/admin.service.ts
Comment thread src/common/json-array-filter.ts
@sleidig

sleidig commented Jul 2, 2026

Copy link
Copy Markdown
Member

@Abhinegi2 , sorry for being picky 😊 But could you apply the cleanups back to the individual branches, please? Replication backend is mission-critical and clear, isolated git commits are super helpful in case we need to troubleshoot regressions or undo any changes later.

@Abhinegi2
Abhinegi2 marked this pull request as draft July 3, 2026 06:23
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.

Improve performance, stability and memory use for large databases

3 participants