feat(indexer/distributions): define distributions database schema#44
feat(indexer/distributions): define distributions database schema#44Zeemnew wants to merge 1 commit into
Conversation
Add DistributionBatch and ClaimAction tables with pause/resume status fields, integer-safe token amounts, SQL migration, and schema tests. Closes Fundable-Protocol#36
|
@Zeemnew Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
📝 WalkthroughWalkthroughAdds distributions database support: new TypeORM entities for distribution batches and claim actions, shared status utilities and exports, an idempotent SQL migration that creates the tables and indexes, test coverage for entity and migration metadata, and README references to the schema files. ChangesDistributions schema
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. 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. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
indexer/tsconfig.base.json (1)
23-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider scoping
vitest/globalsto a test tsconfig.Placing
"types": ["vitest/globals"]in the shared base config injects Vitest test globals into production source compilation for every package, and explicitly settingtypesalso disables automatic inclusion of other@types/*. Decorator options here are correct; only the test-globals placement is worth reconsidering (e.g., atsconfig.test.jsonoverride).🤖 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 `@indexer/tsconfig.base.json` around lines 23 - 25, Move the vitest/globals type entry out of the shared tsconfig.base.json so production compilation is not polluted with test-only globals and automatic `@types` inclusion is preserved. Keep the decorator settings in the base config, and scope vitest/globals to a dedicated test tsconfig override such as tsconfig.test.json used by the test setup. Use the shared base config symbol tsconfig.base.json as the place to remove the test-only types and the test tsconfig as the new location for them.
🤖 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 `@indexer/distributions/package.json`:
- Around line 18-19: Add the missing reflect-metadata setup for TypeORM
decorators: the `indexer/distributions` package currently depends on `typeorm`
but never installs or loads `reflect-metadata`, so decorator-based entity
metadata can fail at runtime. Update the relevant package manifest(s) in the
`indexer/` workspace to include `reflect-metadata`, and import it once at the
application entrypoint before any TypeORM entities or DataSource initialization
run. Use the package entry bootstrap and the TypeORM entity-loading path as the
places to verify the import happens first.
In `@indexer/distributions/src/db/entity/ClaimAction.ts`:
- Around line 22-30: The ClaimAction relation is mismatched with the database
column naming, causing TypeORM to look for the wrong foreign key column. Update
the scalar column and the relation in ClaimAction so the `batchId` property
explicitly maps to the snake_case database column used by the migration, and
change the `@JoinColumn` on the `DistributionBatch` relation to reference that
same database column name. Keep the mapping consistent with the naming strategy
so the relationship can resolve correctly.
In `@indexer/distributions/src/db/entity/DistributionBatch.ts`:
- Around line 27-96: The DistributionBatch entity uses camelCase property names,
but the database schema is snake_case, so TypeORM will generate mismatched
column names unless the datasource is configured correctly. Update the
AppDataSource setup in data-source.ts to use SnakeNamingStrategy so entities
like DistributionBatch, contractId, totalAmount, and createdAt map to the
existing snake_case columns in the migration.
---
Nitpick comments:
In `@indexer/tsconfig.base.json`:
- Around line 23-25: Move the vitest/globals type entry out of the shared
tsconfig.base.json so production compilation is not polluted with test-only
globals and automatic `@types` inclusion is preserved. Keep the decorator settings
in the base config, and scope vitest/globals to a dedicated test tsconfig
override such as tsconfig.test.json used by the test setup. Use the shared base
config symbol tsconfig.base.json as the place to remove the test-only types and
the test tsconfig as the new location for them.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 054e836c-ac0f-4c7e-89c8-563795dc5df4
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
indexer/README.mdindexer/common/src/db/migrations/0002_create_distributions_schema.sqlindexer/distributions/package.jsonindexer/distributions/src/db/entity/ClaimAction.tsindexer/distributions/src/db/entity/DistributionBatch.tsindexer/distributions/src/db/schema.test.tsindexer/distributions/src/db/status.tsindexer/distributions/src/index.tsindexer/tsconfig.base.json
| "@fundable-indexer/common": "workspace:*", | ||
| "typeorm": "^0.3.20" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Is reflect-metadata declared anywhere?
fd -t f 'package.json' indexer --exec sh -c 'echo "== {} =="; cat {}' | rg -n 'reflect-metadata' || echo "reflect-metadata NOT declared in any indexer package.json"
# Is it imported anywhere?
rg -nP '''(import|require)\([^)]*reflect-metadata|import\s+["'\'']reflect-metadata''' indexer || echo "reflect-metadata NOT imported anywhere"Repository: Fundable-Protocol/Backend
Length of output: 260
Missing reflect-metadata dependency for decorator-based TypeORM entities.
TypeORM 0.3.x entities using decorators require reflect-metadata to be installed and imported once at startup. Verification confirms it is missing from all package.json files in the indexer/ workspace and is not imported anywhere.
🛠️ Proposed dependency addition
"`@fundable-indexer/common`": "workspace:*",
+ "reflect-metadata": "^0.2.2",
"typeorm": "^0.3.20"Add import "reflect-metadata"; at the application entrypoint before loading any entity metadata to prevent runtime failures in metadata resolution.
📝 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.
| "@fundable-indexer/common": "workspace:*", | |
| "typeorm": "^0.3.20" | |
| "`@fundable-indexer/common`": "workspace:*", | |
| "reflect-metadata": "^0.2.2", | |
| "typeorm": "^0.3.20" |
🤖 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 `@indexer/distributions/package.json` around lines 18 - 19, Add the missing
reflect-metadata setup for TypeORM decorators: the `indexer/distributions`
package currently depends on `typeorm` but never installs or loads
`reflect-metadata`, so decorator-based entity metadata can fail at runtime.
Update the relevant package manifest(s) in the `indexer/` workspace to include
`reflect-metadata`, and import it once at the application entrypoint before any
TypeORM entities or DataSource initialization run. Use the package entry
bootstrap and the TypeORM entity-loading path as the places to verify the import
happens first.
| @Column({ type: "varchar", comment: "Distribution batch this claim belongs to" }) | ||
| batchId!: string; | ||
|
|
||
| @ManyToOne( | ||
| () => DistributionBatch, | ||
| (batch) => batch.claims, | ||
| { onDelete: "CASCADE" }, | ||
| ) | ||
| @JoinColumn({ name: "batchId" }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
fd --extension ts --glob '*migration*' --type f | grep -i claimRepository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
fd --extension ts --type f "src/db/migrations" | head -20Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
find . -type f -name "*migration*.ts" -path "*/src/*" | head -20Repository: Fundable-Protocol/Backend
Length of output: 199
🏁 Script executed:
find . -type d -name "migrations" | head -5Repository: Fundable-Protocol/Backend
Length of output: 215
🏁 Script executed:
grep -r "batch_id\|batchId" ./src/migrations --include="*.ts" | head -20Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
grep -r "batch_id\|batchId" ./indexer/common/src/db/migrations --include="*.ts" | head -20Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
ls -la ./src/migrations/Repository: Fundable-Protocol/Backend
Length of output: 438
🏁 Script executed:
grep -r "batch_id\|batchId" ./src/migrations ./indexer/common/src/db/migrations --include="*.js" --include="*.ts" 2>/dev/null | head -20Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
cat ./indexer/distributions/src/db/entity/ClaimAction.tsRepository: Fundable-Protocol/Backend
Length of output: 1542
🏁 Script executed:
find ./indexer -type f \( -name "*.js" -o -name "*.ts" \) | xargs grep -l "claim_action" | grep -i migration | head -10Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
find ./indexer -type f \( -name "*migration*" -o -name "*Migrate*" \) | head -10Repository: Fundable-Protocol/Backend
Length of output: 163
🏁 Script executed:
grep -r "claim_action" --include="*.js" --include="*.ts" . | grep -i "createTable\|addColumn" | head -10Repository: Fundable-Protocol/Backend
Length of output: 163
Align the @JoinColumn name with the database column to prevent foreign key lookup failures.
The current code explicitly sets @JoinColumn({ name: "batchId" }), which forces TypeORM to look for a column named batchId in the database. However, the database schema (created via the migration) uses the snake_case batch_id following the global naming strategy. This mismatch causes the ORM to fail in resolving the relationship.
Also update the scalar @Column definition to explicitly map to batch_id for clarity and consistency.
Suggested fix:
Detailed Change
`@Index`("claim_action_batch_id_idx", ["batchId"])
`@Index`("claim_action_claimant_idx", ["claimant"])
`@Index`("claim_action_tx_hash_idx", ["txHash"])
`@Index`("claim_action_batch_claimant_idx", ["batchId", "claimant"])
export class ClaimAction {
`@PrimaryGeneratedColumn`("uuid")
id!: string;
- `@Column`({ type: "varchar", comment: "Distribution batch this claim belongs to" })
+ `@Column`({ name: "batch_id", type: "varchar", comment: "Distribution batch this claim belongs to" })
batchId!: string;
`@ManyToOne`(
() => DistributionBatch,
(batch) => batch.claims,
{ onDelete: "CASCADE" },
)
- `@JoinColumn`({ name: "batchId" })
+ `@JoinColumn`({ name: "batch_id" })
batch!: DistributionBatch;📝 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.
| @Column({ type: "varchar", comment: "Distribution batch this claim belongs to" }) | |
| batchId!: string; | |
| @ManyToOne( | |
| () => DistributionBatch, | |
| (batch) => batch.claims, | |
| { onDelete: "CASCADE" }, | |
| ) | |
| @JoinColumn({ name: "batchId" }) | |
| `@Column`({ name: "batch_id", type: "varchar", comment: "Distribution batch this claim belongs to" }) | |
| batchId!: string; | |
| `@ManyToOne`( | |
| () => DistributionBatch, | |
| (batch) => batch.claims, | |
| { onDelete: "CASCADE" }, | |
| ) | |
| `@JoinColumn`({ name: "batch_id" }) | |
| batch!: DistributionBatch; |
🤖 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 `@indexer/distributions/src/db/entity/ClaimAction.ts` around lines 22 - 30, The
ClaimAction relation is mismatched with the database column naming, causing
TypeORM to look for the wrong foreign key column. Update the scalar column and
the relation in ClaimAction so the `batchId` property explicitly maps to the
snake_case database column used by the migration, and change the `@JoinColumn`
on the `DistributionBatch` relation to reference that same database column name.
Keep the mapping consistent with the naming strategy so the relationship can
resolve correctly.
| @Column({ type: "varchar", comment: "Soroban contract that emitted the batch" }) | ||
| contractId!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Address that created the distribution batch" }) | ||
| distributor!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Token asset contract address" }) | ||
| token!: string; | ||
|
|
||
| @Column({ | ||
| type: "numeric", | ||
| precision: 78, | ||
| scale: 0, | ||
| comment: "Total allocated amount in the token's smallest unit", | ||
| }) | ||
| totalAmount!: string; | ||
|
|
||
| @Column({ | ||
| type: "numeric", | ||
| precision: 78, | ||
| scale: 0, | ||
| default: "0", | ||
| comment: "Total amount claimed so far in the token's smallest unit", | ||
| }) | ||
| claimedAmount!: string; | ||
|
|
||
| @Column({ type: "integer", default: 0, comment: "Number of intended recipients" }) | ||
| recipientCount!: number; | ||
|
|
||
| @Column({ | ||
| type: "varchar", | ||
| default: "active", | ||
| comment: "Batch lifecycle status for pause/resume behavior", | ||
| }) | ||
| status!: DistributionBatchStatus; | ||
|
|
||
| @Column({ | ||
| type: "bigint", | ||
| nullable: true, | ||
| comment: "Unix timestamp when the batch was paused", | ||
| }) | ||
| pausedAt!: string | null; | ||
|
|
||
| @Column({ | ||
| type: "bigint", | ||
| nullable: true, | ||
| comment: "Unix timestamp when the batch was last resumed", | ||
| }) | ||
| resumedAt!: string | null; | ||
|
|
||
| @Column({ type: "varchar", comment: "Contract-unique reference for the batch" }) | ||
| uniqueRef!: string; | ||
|
|
||
| @Column({ type: "bigint", comment: "Ledger where the batch was created" }) | ||
| ledgerNumber!: string; | ||
|
|
||
| @Column({ type: "varchar", comment: "Transaction hash that created the batch" }) | ||
| txHash!: string; | ||
|
|
||
| @OneToMany( | ||
| () => ClaimAction, | ||
| (claim) => claim.batch, | ||
| ) | ||
| claims!: ClaimAction[]; | ||
|
|
||
| @CreateDateColumn() | ||
| createdAt!: Date; | ||
|
|
||
| @UpdateDateColumn() | ||
| updatedAt!: Date; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect datasource / TypeORM config for an explicit snake_case naming strategy.
fd -i 'data-source|datasource|ormconfig' .
rg -n -C2 'namingStrategy|SnakeNamingStrategy|snake' .Repository: Fundable-Protocol/Backend
Length of output: 203
🏁 Script executed:
cat -n src/config/persistence/data-source.tsRepository: Fundable-Protocol/Backend
Length of output: 3452
Critical mismatch between TypeORM config and database schema: Missing SnakeNamingStrategy
The DistributionBatch entity defines properties in camelCase (e.g., contractId, totalAmount, created_at), while the migration 0002_create_distributions_schema.sql creates columns in snake_case (e.g., contract_id, total_amount, created_at).
The datasource configuration in src/config/persistence/data-source.ts (lines 39-64) does not define a namingStrategy. Without explicitly setting namingStrategy: new SnakeNamingStrategy(), TypeORM defaults to camelCase column names. This will cause runtime errors (column "contractid" does not exist) whenever the application attempts to query or persist DistributionBatch records.
Change required in src/config/persistence/data-source.ts:
import { SnakeNamingStrategy } from 'typeorm';
// ... inside AppDataSource options:
namingStrategy: new SnakeNamingStrategy(),🤖 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 `@indexer/distributions/src/db/entity/DistributionBatch.ts` around lines 27 -
96, The DistributionBatch entity uses camelCase property names, but the database
schema is snake_case, so TypeORM will generate mismatched column names unless
the datasource is configured correctly. Update the AppDataSource setup in
data-source.ts to use SnakeNamingStrategy so entities like DistributionBatch,
contractId, totalAmount, and createdAt map to the existing snake_case columns in
the migration.
Overview
This PR defines the distributions domain database schema for the Soroban indexer. It introduces
distribution_batchandclaim_actiontables so distribution handlers and future API resolvers can persist and query indexed data. Token amounts useNUMERIC(78, 0)to avoid floating-point precision loss, and batch status fields support pause/resume indexing behavior.Related Issue
Closes #36
Changes
📦 DistributionBatch Table
[ADD]
indexer/distributions/src/db/entity/DistributionBatch.tsAdded the core
distribution_batchTypeORM entity.Tracks contract ID, distributor, token, total/claimed amounts, recipient count, and on-chain metadata (
uniqueRef,ledgerNumber,txHash).Added pause/resume lifecycle fields:
status,pausedAt, andresumedAt.Uses
NUMERIC(78, 0)columns for integer-safe token amounts.[ADD]
indexer/distributions/src/db/status.tsDefined supported batch statuses:
active,paused,completed,cancelled.Added a type guard for validating status values.
🧾 ClaimAction Table
indexer/distributions/src/db/entity/ClaimAction.tsclaim_actionTypeORM entity linked todistribution_batchviabatchId.NUMERIC(78, 0)for claim amounts.🗄️ Migration & Indexes
indexer/common/src/db/migrations/0002_create_distributions_schema.sqlschema_migrationsguard.distribution_batchandclaim_actiontables.🔧 Package & Config
[MODIFY]
indexer/distributions/package.jsonAdded
typeormdependency for entity definitions.[MODIFY]
indexer/distributions/src/index.tsExported distribution entities, status helpers, and
distributionsEntitiesregistry.[MODIFY]
indexer/tsconfig.base.jsonEnabled TypeORM decorator support (
experimentalDecorators,emitDecoratorMetadata).🧪 Tests
indexer/distributions/src/db/schema.test.ts📚 Documentation
indexer/README.mdVerification Results
DistributionBatchtableClaimActiontablebun run type-checkpassesbun run testpassesbun run lintpassesbun run indexer:type-checkpassesbun run indexer:testpassesbun run indexer:lintpassesSummary by CodeRabbit
New Features
Documentation