Skip to content

Conversation

petermasking
Copy link
Member

Fixes #436

Changes proposed in this pull request:

  • updated tenant caching strategy
  • boy-scout: cleaned up worker tear-down

@MaskingTechnology/comify

@petermasking petermasking linked an issue Sep 18, 2025 that may be closed by this pull request
Copy link

coderabbitai bot commented Sep 18, 2025

Summary by CodeRabbit

  • New Features

    • None
  • Refactor

    • Reworked tenant middleware to resolve and cache tenants per origin and expose a default, no-arg setup.
    • Authentication middleware now uses a fixed whitelist entry for tenant-by-origin access.
    • Removed an unused domain definition.
  • Performance

    • Faster tenant lookups due to per-origin caching.
  • Chores

    • Simplified worker shutdown by removing unused service disconnections, reducing teardown overhead.

Walkthrough

Removed TENANT_BY_ORIGIN_PATH; adjusted authentication whitelist to a string literal. Refactored TenantMiddleware to cache Tenant objects, expose a get-by-origin FQN handler, and drop the constructor argument. Updated the tenant middleware export accordingly. Simplified tearDownWorker by removing event broker and notification service disconnections.

Changes

Cohort / File(s) Summary
Domain constants
src/domain/definitions.ts
Removed exported constant TENANT_BY_ORIGIN_PATH. No other changes.
Runtime auth whitelist
src/integrations/runtime/authenticationMiddleware.ts
Replaced import of TENANT_BY_ORIGIN_PATH; whitelist now hard-coded to 'domain/tenant/getByOriginConverted'. Public API unchanged.
Tenant middleware core refactor
src/integrations/runtime/middlewares/TenantMiddleware.ts
Introduced Tenant caching Map<string, Tenant>. Added FQN guard for origin-based retrieval; returns 200 Response with Tenant. Constructor now no-arg. Resolved origin via header. Adjusted internal methods: #resolveTenant, #getTenant, #handleRequest.
Tenant middleware export wiring
src/integrations/runtime/tenantMiddleware.ts
Dropped dependency on removed constant and path argument. Default export now new TenantMiddleware() (no args).
Runtime teardown simplification
src/integrations/runtime/tearDownWorker.ts
Removed eventBroker and notificationService disconnect logic; now only disconnects database and fileStore when connected.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Runtime as Runtime
  participant TenantMW as TenantMiddleware
  participant Cache as Tenant Cache
  participant Conv as getByOrigin converter
  participant Next as Next Handler

  rect rgb(245,248,255)
  note right of TenantMW: Normal request flow
  Client->>Runtime: Request (fqn != getByOrigin)
  Runtime->>TenantMW: handle(request)
  TenantMW->>Cache: lookup(origin)
  alt cache miss
    TenantMW->>Conv: getByOrigin(origin)
    Conv-->>TenantMW: Tenant
    TenantMW->>Cache: cache Tenant
  end
  TenantMW->>Next: set TENANT_PARAMETER, next()
  Next-->>Runtime: Response
  Runtime-->>Client: Response
  end

  rect rgb(243,255,245)
  note right of TenantMW: getByOrigin FQN path
  Client->>Runtime: Request (fqn == getByOrigin)
  Runtime->>TenantMW: handle(request)
  TenantMW->>Cache: lookup(origin)
  alt cache miss
    TenantMW->>Conv: getByOrigin(origin)
    Conv-->>TenantMW: Tenant
    TenantMW->>Cache: cache Tenant
  end
  TenantMW-->>Runtime: 200 Response(Tenant)
  Runtime-->>Client: 200 Response(Tenant)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A whisk of bytes, I hop through code,
New paths resolved, a lighter load.
Tenants cached, no dawn refresh,
Middleware’s ears now keen and fresh.
Teardown trims the extra thread—
I twitch, approve, and bound ahead! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ❓ Inconclusive Most edits align with the stated objectives (tenant caching and small worker teardown cleanup), however the removal of the exported TENANT_BY_ORIGIN_PATH constant and the change to TenantMiddleware's constructor/default export are potentially breaking public API changes that cannot be fully validated from the provided summaries alone, and the removal of eventBroker/notificationService disconnects should be confirmed as intentional. Perform a repository-wide search for TENANT_BY_ORIGIN_PATH and references to the old TenantMiddleware constructor to ensure no other modules or external consumers rely on them, consider re-exporting a deprecated constant or adding a migration note to avoid breakage, and confirm the tear-down removals are intentional and safe.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: tanent caching" is concise and correctly targets the main change (tenant caching) but contains a spelling error ("tanent" → "tenant") and could be slightly clearer by referencing the issue or the caching approach.
Linked Issues Check ✅ Passed The changes implement a per-origin Tenant cache and perform on-demand tenant resolution via getByOrigin, caching results server-side instead of relying solely on client-populated requests, which addresses the linked issue's root cause by preventing TenantNotFound when the client is not refreshed after a restart; the cache remains in-memory (so it will still clear on restart) but the middleware now proactively fetches tenants on first request so a client refresh is no longer required.
Description Check ✅ Passed The PR description follows the repository template, includes "Fixes #436", lists the main proposed changes, and mentions the team; it is largely complete for reviewers to understand intent but contains a stray empty bullet and lacks a few implementation details (notably the constructor/default-export change and removal of TENANT_BY_ORIGIN_PATH).
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 436-tanent-caching-improvement

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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 and usage tips.

Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/integrations/runtime/authenticationMiddleware.ts (1)

14-16: Avoid string literal drift: import a shared FQN constant instead of hardcoding.

Hardcoding 'domain/tenant/getByOriginConverted' risks divergence from the middleware comparison. Centralize the FQN and import it here.

Apply this diff locally (uses a shared constant GET_BY_ORIGIN_FQN):

+import { GET_BY_ORIGIN_FQN } from '^/domain/tenant/fqns';
 
-const whiteList: string[] = [
-    'domain/tenant/getByOriginConverted'
-];
+const whiteList: string[] = [GET_BY_ORIGIN_FQN];

New shared constant (add this file):

// src/domain/tenant/fqns.ts
export const GET_BY_ORIGIN_FQN = 'domain/tenant/getByOriginConverted';
src/integrations/runtime/middlewares/TenantMiddleware.ts (3)

8-8: Typo and constant reuse: rename GEY_BY_ORIGIN_FQN → GET_BY_ORIGIN_FQN and reuse shared constant.

Minor typo; also prefer importing the shared FQN to keep it in sync with the auth whitelist.

Apply this diff:

-import getByOrigin from '^/domain/tenant/getByOriginConverted';
-
-const GEY_BY_ORIGIN_FQN = 'domain/tenant/getByOriginConverted';
+import getByOrigin from '^/domain/tenant/getByOriginConverted';
+import { GET_BY_ORIGIN_FQN } from '^/domain/tenant/fqns';
@@
-        return request.fqn === GEY_BY_ORIGIN_FQN
+        return request.fqn === GET_BY_ORIGIN_FQN

Also applies to: 17-17


27-34: Shadowed variable 'tenant' reduces clarity; rename inner binding.

The inner const tenant masks the outer variable. Rename to avoid shadowing.

-        if (tenant === undefined)
-        {
-            const tenant = await getByOrigin(origin);
-
-            this.#tenants.set(origin, tenant);
-
-            return tenant;
-        }
+        if (tenant === undefined) {
+            const resolvedTenant = await getByOrigin(origin);
+            this.#tenants.set(origin, resolvedTenant);
+            return resolvedTenant;
+        }

22-36: Optional: dedupe concurrent lookups to the same origin.

Two simultaneous cache misses for the same origin will trigger duplicate getByOrigin calls. Track in‑flight promises per origin to coalesce.

Sketch:

// field
#inflight = new Map<string, Promise<Tenant>>();

async #resolveTenant(request: Request): Promise<Tenant> {
  const origin = /* validated/normalized origin */;
  const cached = this.#tenants.get(origin);
  if (cached) return cached;

  const existing = this.#inflight.get(origin);
  if (existing) return existing;

  const p = getByOrigin(origin).then(t => {
    this.#tenants.set(origin, t);
    this.#inflight.delete(origin);
    return t;
  }).catch(err => {
    this.#inflight.delete(origin);
    throw err;
  });

  this.#inflight.set(origin, p);
  return p;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c075d4 and 52b60aa.

📒 Files selected for processing (5)
  • src/domain/definitions.ts (0 hunks)
  • src/integrations/runtime/authenticationMiddleware.ts (1 hunks)
  • src/integrations/runtime/middlewares/TenantMiddleware.ts (1 hunks)
  • src/integrations/runtime/tearDownWorker.ts (0 hunks)
  • src/integrations/runtime/tenantMiddleware.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/domain/definitions.ts
  • src/integrations/runtime/tearDownWorker.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: basmasking
PR: MaskingTechnology/comify#428
File: segments/bff.json:48-48
Timestamp: 2025-07-22T21:11:15.729Z
Learning: The tenant domain uses a two-tier data model: the database `DataModel` contains an `id` and an array of valid `origins`, while the public `Tenant` type returned by `getByOriginConverted` contains only `id` and `origin` (singular). This conversion serves as data minimization, where `id` is needed for stylesheet retrieval and `origin` is the validated browser-sent origin. This architecture implements proper security through data filtering at the public interface level.
📚 Learning: 2025-07-22T21:11:15.729Z
Learnt from: basmasking
PR: MaskingTechnology/comify#428
File: segments/bff.json:48-48
Timestamp: 2025-07-22T21:11:15.729Z
Learning: The tenant domain uses a two-tier data model: the database `DataModel` contains an `id` and an array of valid `origins`, while the public `Tenant` type returned by `getByOriginConverted` contains only `id` and `origin` (singular). This conversion serves as data minimization, where `id` is needed for stylesheet retrieval and `origin` is the validated browser-sent origin. This architecture implements proper security through data filtering at the public interface level.

Applied to files:

  • src/integrations/runtime/tenantMiddleware.ts
  • src/integrations/runtime/middlewares/TenantMiddleware.ts
📚 Learning: 2025-07-22T20:29:33.605Z
Learnt from: basmasking
PR: MaskingTechnology/comify#428
File: src/webui/features/hooks/usePostsFollowing.ts:6-6
Timestamp: 2025-07-22T20:29:33.605Z
Learning: In Jitar applications, static imports of context objects like `tenant` from `^/domain/tenant` and `requester` from `^/domain/authentication` are intentionally static placeholders that serve as type-safe function signature matches. At runtime, these are dynamically replaced through Jitar's parameter injection mechanism via middleware (TenantMiddleware and AuthenticationMiddleware) using special parameter keys like `*tenant`. This allows for compile-time type safety while providing runtime flexibility for multi-tenant and authentication contexts.

Applied to files:

  • src/integrations/runtime/middlewares/TenantMiddleware.ts
🧬 Code graph analysis (2)
src/integrations/runtime/tenantMiddleware.ts (1)
src/integrations/runtime/middlewares/TenantMiddleware.ts (1)
  • TenantMiddleware (11-54)
src/integrations/runtime/middlewares/TenantMiddleware.ts (1)
src/domain/tenant/getByOrigin/getByOrigin.ts (1)
  • getByOrigin (9-24)
🔇 Additional comments (1)
src/integrations/runtime/tenantMiddleware.ts (1)

4-4: Constructor change looks good — no callers rely on the old signature.
Search found only the self-export at src/integrations/runtime/tenantMiddleware.ts:4 — export default new TenantMiddleware();

Comment on lines +22 to 26
async #resolveTenant(request: Request): Promise<Tenant>
{
const origin = this.#getOrigin(request);
const cached = this.#cache.get(origin);
const origin = request.getHeader('origin') as string;
const tenant = this.#tenants.get(origin);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard and normalize the Origin header; current cast may pass undefined through.

request.getHeader('origin') can be missing or 'null'. The forced cast risks passing undefined into the cache and data layer.

Apply this minimal guard (optional: add normalization):

-        const origin = request.getHeader('origin') as string;
+        const rawOrigin = request.getHeader('origin');
+        if (typeof rawOrigin !== 'string' || rawOrigin.trim() === '' || rawOrigin === 'null') {
+            // Consider mapping to a 400 response upstream or throwing a typed error the global handler maps to 400.
+            throw new Error('Missing or invalid Origin header');
+        }
+        const origin = rawOrigin.trim();

Optional normalization helper (to avoid case/port variance):

// add as a private static method if desired
static #normalizeOrigin(raw: string): string {
  try {
    const u = new URL(raw);
    const isDefault = (u.protocol === 'https:' && (u.port === '' || u.port === '443')) ||
                      (u.protocol === 'http:'  && (u.port === '' || u.port === '80'));
    const host = isDefault ? u.hostname : `${u.hostname}:${u.port}`;
    return `${u.protocol}//${host}`.toLowerCase();
  } catch {
    return raw.toLowerCase();
  }
}
🤖 Prompt for AI Agents
In src/integrations/runtime/middlewares/TenantMiddleware.ts around lines 22-26,
the code force-casts request.getHeader('origin') to string which may yield
undefined or 'null' and pollute the tenant cache; update the method to first
guard against missing/invalid origin (e.g., check for falsy or the literal
'null' and handle by throwing or returning a default tenant), then normalize the
origin before using it as a cache key (add the provided #normalizeOrigin helper
as a private static method or inline equivalent), and finally use the
normalizedOrigin when calling this.#tenants.get(...) so only valid, normalized
origin strings reach the cache and data layer.

@basmasking basmasking merged commit 1dff537 into main Sep 19, 2025
7 checks passed
@basmasking basmasking deleted the 436-tanent-caching-improvement branch September 19, 2025 04:26
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.

Tanent caching improvement
2 participants