Commit 0f2320d
authored
feat(billing): compute layer foundation - PR-N1 (no behavior change) (#3535)
* feat(billing): compute layer foundation - pr-n1 (no behavior change)
lays the foundational data layer for compute-based pricing behind
the billing.computeMode: false feature flag (default off).
all new code paths are dormant for existing downstream projects.
new models: billing.plan.model.mongoose.js + schema, billing.processedStripeEvent.model.mongoose.js
modified models (additive, sparse, backward-compat): billing.usage (weekKey,
computeUsed/Quota, computeBreakdown, resetAt, alertedAt80/100, consumedHistoryIds),
billing.subscription (planVersion, currentPeriodStart, pastDueSince)
new service: billing.plan.service.js - getActivePlan (1h cache), getPlanByVersion,
bumpVersion (mongo transaction), invalidateCache; billing.plans.service.js kept as legacy facade
config: billing.computeMode (default false), billing.compute.*, billing.packs,
stripe.prices.packs (all downstream override slots)
migrations: 20260501000000 (weekKey sparse index), 20260501000100 (plan/event indexes) - idempotent
tests: +43 tests across 3 new suites + 3 extended existing files
closes checklist item pr-n1 in #3533
* fix(billing): remove duplicate index creation from migrations
Migrations were creating indexes with custom names (planId_version_unique,
organizationId_weekKey_unique_sparse) that conflicted with the auto-names
Mongoose assigns at bootstrap (planId_1_version_1, organizationId_1_weekKey_1).
MongoDB code 85 IndexOptionsConflict was crashing the app on first run in
each fresh test DB, causing all integration tests to fail.
These indexes are owned by the Mongoose schemas and auto-synced at connect
time — the migration up() functions must not duplicate them.
* fix(billing): make migrations pure no-ops - all indexes are Mongoose-managed
The subscriptions.planVersion sparse index and processedstripeevents.processedAt
TTL index are both defined in Mongoose schemas with autoIndex:true. When
migrations run post-connect, Mongoose has already created them with auto-names
(planVersion_1, processedAt_1). The migrations tried to create the same key
patterns with different names → IndexOptionsConflict code 85.
Both migrations are now pure markers (up/down no-ops). The schema + index
lifecycle is fully owned by Mongoose model definitions.
* test(billing): remove done callbacks from synchronous Zod schema tests
New Subscription compute-field tests used the done callback pattern from
surrounding legacy tests, but the operations are synchronous (safeParse).
Using done with sync code masks exceptions as timeouts rather than failures.
Replaced with plain synchronous test functions to fix 6 Codacy ErrorProne
high findings.
* fix(billing): address CodeRabbit major + minor findings on billing.plan.service
Major fixes:
- bumpVersion: remove withTransaction/startSession (requires replica set).
Now uses updateMany + countDocuments + create without sessions, matching
the standalone-MongoDB pattern in organizations.membership.service.js.
- getActivePlan: do not cache null results (plan not found) — a null miss
should not block visibility of a newly-created plan until TTL expiry.
Reduce CACHE_TTL from 1h to 5min to limit stale-read window.
- billing.plan.model: add Mongoose validator on ratios (must be object with
finite non-negative values). Mirror in Zod schema with .min(0).
Minor fixes:
- Add JSDoc header to makeDoc test helper.
- Fix misleading test title for fetchPlansFromStripe (not exported).
- Add test coverage for negative ratio rejection.
- Fix BillingPlanBump JSDoc: ratios is optional, not required.
- Update bumpVersion tests to reflect no-session implementation.
* fix(billing): suppress Codacy false-positive useQwikValidLexicalScope on Node service
Codacy applies Biome's useQwikValidLexicalScope rule to Node.js arrow
function declarations, producing 6 ErrorProne high findings. This rule
is designed for Qwik SSR serialization and is a false positive in a
Node.js context. Add biome-ignore suppressions to the affected
top-level function declarations in billing.plan.service.js and its
unit test file. No behavior change.
* refactor(billing): apply critical-review fixes — repository pattern + meter rename
- Extract BillingPlanRepository (findActive, findByVersion, deactivateAll, count, create)
so billing.plan.service.js no longer imports mongoose directly (ERRORS.md arch rule)
- Rename compute → meter throughout billing module for devkit neutrality:
computeMode → meterMode, compute.* → meter.*, computeQuota → meterQuota,
computeUsed/computeQuota/computeBreakdown → meterUsed/meterQuota/meterBreakdown
- git mv migration to 20260501000000-add-meter-fields.js (history preserved)
- Update unit tests to mock repository instead of mongoose; add lean() pass-through test
- Add JSDoc: bumpVersion concurrency/activation-gap guidance, getActivePlan null semantics,
dollarsToUnitRatio downstream-override warning, multi-pod cache stale-window TODO
- Backward compat: meterMode default false, zero behavior change for non-meter downstream
* fix(billing): add biome-ignore suppressions for useQwikValidLexicalScope on repository + service
Codacy erroneously applies the Qwik rule on Node.js module-level arrow
functions. Suppress in billing.plan.repository.js (6 declarations) and
restore suppressions removed from billing.plan.service.js (4 declarations)
during the refactor → 0 high ErrorProne issues expected.
* fix(billing): address CodeRabbit Major/Critical findings on indexes + schema
1. BillingPlanMongoose: make (planId, active, effectiveUntil) a partial unique
index (active=true, effectiveUntil=null) — enforces single active plan per
planId at the DB level, guards concurrent bumpVersion() races.
2. BillingUsage: scope (organizationId, month) unique index to non-meter docs
only (partialFilterExpression: weekKey $exists false) — allows multiple
weekly records per month in meter mode.
3. BillingPlanBump schema: add .min(0) to ratios values — matches BillingPlan
validation contract, prevents negative ratio overrides bypassing Zod.
4. billing.plan.repository.js: add JSDoc to BillingPlan() model accessor.
5. Add test: BillingPlanBump.ratios rejects negative values.
* test(billing): add unit tests for billing.plan.repository.js
Add 10 tests covering all 5 repository methods (findActive, findByVersion,
deactivateAll, count, create) to fix Codecov patch coverage below 80%
threshold on the new repository file.
* docs(billing): align stale 'Compute fields' comment with meter rename1 parent a229bcb commit 0f2320d
19 files changed
Lines changed: 1487 additions & 2 deletions
File tree
- modules/billing
- config
- migrations
- models
- repositories
- services
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
28 | 60 | | |
29 | 61 | | |
30 | 62 | | |
| |||
38 | 70 | | |
39 | 71 | | |
40 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
41 | 78 | | |
42 | 79 | | |
43 | 80 | | |
| |||
Lines changed: 28 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
Lines changed: 22 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
Lines changed: 61 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
Lines changed: 27 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
47 | 74 | | |
48 | 75 | | |
49 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
| |||
0 commit comments