Skip to content

Cleaned up tier repository initializer#26766

Merged
EvanHahn merged 3 commits intomainfrom
speed-up-tier-repo-init
Mar 12, 2026
Merged

Cleaned up tier repository initializer#26766
EvanHahn merged 3 commits intomainfrom
speed-up-tier-repo-init

Conversation

@EvanHahn
Copy link
Copy Markdown
Contributor

no ref

This change should have no user impact. It:

  • Creates Tier models in parallel, for performance
  • Uses Set instead of an object

This should slightly improve boot time and makes the code a bit clearer.

no ref

This change should have no user impact. It:

- Creates `Tier` models in parallel, for performance
- Uses `Set` instead of an object

This should slightly improve boot time and makes the code a bit clearer.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 10, 2026

Walkthrough

The tier repository's internal caching was refactored: the #ids field was changed from a plain object to a Set<string>, and #store initialization was modified from an empty array to asynchronous population via Promise.all over mapped models. Save method existence checks switched from #ids[tier.id.toHexString()] to #ids.has(tier.id.toHexString()), and ID population now uses #ids.add() instead of setting object properties. Exported/public signatures remain unchanged.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Cleaned up tier repository initializer' accurately reflects the main change: refactoring the tier repository's initialization logic with improved data structures and parallel processing.
Description check ✅ Passed The description is directly related to the changeset, explaining the key improvements: parallel Tier model creation, Set usage instead of objects, and expected performance benefits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch speed-up-tier-repo-init
📝 Coding Plan for PR comments
  • Generate coding plan

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 Markdown
Contributor

@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.

🧹 Nitpick comments (1)
ghost/core/core/server/services/tiers/tier-repository.js (1)

33-41: Rebuild #ids and #store atomically inside init().

#ids is mutated during the Promise.all() mapper and never reset in init(). If this repository instance is ever re-initialized, stale ids can survive while #store is rebuilt, which makes Line 137 take the update branch for a tier that is no longer in #store; Line 144 then does splice(-1, 1, toSave) and overwrites the last cached tier.

Proposed change
 async init() {
     const models = await this.#ProductModel.findAll({
         withRelated: ['benefits']
     });
-    this.#store = await Promise.all(models.map(async (model) => {
+    const ids = new Set();
+    const store = await Promise.all(models.map(async (model) => {
         const tier = await Tier.create(this.mapToTier(model));
-        this.#ids.add(tier.id.toHexString());
+        ids.add(tier.id.toHexString());
         return tier;
     }));
+
+    this.#ids = ids;
+    this.#store = store;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/services/tiers/tier-repository.js` around lines 33 -
41, Reset and rebuild the repository state atomically in init(): do not mutate
this.#ids inside the Promise.all mapper; instead create local arrays (e.g.,
newStore and newIds) while mapping models -> Tier.create(this.mapToTier(model)),
push each created tier into newStore and its id (tier.id.toHexString()) into
newIds, await Promise.all, then after all creations succeed assign this.#store =
newStore and this.#ids = new Set(newIds) in one step so init() never leaves a
partially-updated `#ids` that diverges from `#store`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ghost/core/core/server/services/tiers/tier-repository.js`:
- Around line 33-41: Reset and rebuild the repository state atomically in
init(): do not mutate this.#ids inside the Promise.all mapper; instead create
local arrays (e.g., newStore and newIds) while mapping models ->
Tier.create(this.mapToTier(model)), push each created tier into newStore and its
id (tier.id.toHexString()) into newIds, await Promise.all, then after all
creations succeed assign this.#store = newStore and this.#ids = new Set(newIds)
in one step so init() never leaves a partially-updated `#ids` that diverges from
`#store`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0595d749-b43c-426c-a280-73f5fc4173fd

📥 Commits

Reviewing files that changed from the base of the PR and between 8df2622 and 0cf2ccd.

📒 Files selected for processing (1)
  • ghost/core/core/server/services/tiers/tier-repository.js

@ErisDS

This comment was marked as outdated.

@EvanHahn EvanHahn requested a review from cmraible March 11, 2026 19:09
Copy link
Copy Markdown
Collaborator

@cmraible cmraible left a comment

Choose a reason for hiding this comment

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

Approved with a non-blocking comment. Good find!


async init() {
this.#store = [];
this.#ids = {};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: if init() is called twice (as it is in test fixture utils here), this could leave stale ids in the Set since we're no longer explicitly clearing this.#ids.

Unlikely to cause any real problems I think, but a slight unintended behavioral change.

Adding a this.#ids.clear() at the beginning of init() would solve this, but I'm wouldn't consider this blocking

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Addressed in d8bad23.

@EvanHahn EvanHahn enabled auto-merge (squash) March 12, 2026 14:39
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/core/server/services/tiers/tier-repository.js`:
- Around line 37-42: init() mutates this.#ids inside the concurrent map which
can leave the repository half-updated if any Tier.create() rejects; instead,
collect results and ids into local temporaries and only swap them into the
instance fields after Promise.all resolves successfully. Specifically, stop
calling this.#ids.add(...) inside the mapped async tasks; build a local array
(e.g., tempStore) from Promise.all(models.map(...)) and a local Set (e.g.,
tempIds) from tempStore.map(t => t.id.toHexString()), then on success assign
this.#store = tempStore and this.#ids = tempIds so the repository state is
replaced atomically. Ensure you still use mapToTier(model) and Tier.create(...)
as before but avoid mutating instance state until all creates succeed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0cc63ac0-10ec-4f18-a455-283625b5f079

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf2ccd and d8bad23.

📒 Files selected for processing (1)
  • ghost/core/core/server/services/tiers/tier-repository.js

Comment on lines +37 to +42
this.#ids.clear();
this.#store = await Promise.all(models.map(async (model) => {
const tier = await Tier.create(this.mapToTier(model));
this.#store.push(tier);
this.#ids[tier.id.toHexString()] = true;
}
this.#ids.add(tier.id.toHexString());
return tier;
}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Populate the caches atomically after Promise.all() succeeds.

init() now mutates #ids inside the mapped tasks. If one Tier.create() rejects during a later init() call, the method exits with a partially rebuilt #ids set while #store still contains the previous snapshot. That leaves the repository in an inconsistent state and can send save() down the update path for tiers that are no longer in #store.

💡 Suggested change
     async init() {
         const models = await this.#ProductModel.findAll({
             withRelated: ['benefits']
         });
-        this.#ids.clear();
-        this.#store = await Promise.all(models.map(async (model) => {
-            const tier = await Tier.create(this.mapToTier(model));
-            this.#ids.add(tier.id.toHexString());
-            return tier;
-        }));
+        const store = await Promise.all(models.map((model) => {
+            return Tier.create(this.mapToTier(model));
+        }));
+
+        this.#store = store;
+        this.#ids = new Set(store.map(tier => tier.id.toHexString()));
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.#ids.clear();
this.#store = await Promise.all(models.map(async (model) => {
const tier = await Tier.create(this.mapToTier(model));
this.#store.push(tier);
this.#ids[tier.id.toHexString()] = true;
}
this.#ids.add(tier.id.toHexString());
return tier;
}));
const store = await Promise.all(models.map((model) => {
return Tier.create(this.mapToTier(model));
}));
this.#store = store;
this.#ids = new Set(store.map(tier => tier.id.toHexString()));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/services/tiers/tier-repository.js` around lines 37 -
42, init() mutates this.#ids inside the concurrent map which can leave the
repository half-updated if any Tier.create() rejects; instead, collect results
and ids into local temporaries and only swap them into the instance fields after
Promise.all resolves successfully. Specifically, stop calling this.#ids.add(...)
inside the mapped async tasks; build a local array (e.g., tempStore) from
Promise.all(models.map(...)) and a local Set (e.g., tempIds) from
tempStore.map(t => t.id.toHexString()), then on success assign this.#store =
tempStore and this.#ids = tempIds so the repository state is replaced
atomically. Ensure you still use mapToTier(model) and Tier.create(...) as before
but avoid mutating instance state until all creates succeed.

@EvanHahn EvanHahn merged commit ffa3598 into main Mar 12, 2026
30 checks passed
@EvanHahn EvanHahn deleted the speed-up-tier-repo-init branch March 12, 2026 15:10
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.

3 participants