Skip to content

feat: restructure @rune/database into core/drizzle/prisma sub-packages#9

Merged
rjoydip merged 10 commits into
mainfrom
feat/database-orm-integration
Jul 9, 2026
Merged

feat: restructure @rune/database into core/drizzle/prisma sub-packages#9
rjoydip merged 10 commits into
mainfrom
feat/database-orm-integration

Conversation

@rjoydip

@rjoydip rjoydip commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Split @rune/database into three sub-packages with an umbrella re-export package.

Changes

  • @rune/database-coreDatabaseAdapter interface, OnAppInit/OnAppDestroy lifecycle hooks, DatabaseModule factory
  • @rune/database-drizzleDrizzleAdapter wrapping any Drizzle ORM instance with lifecycle management
  • @rune/database-prismaPrismaAdapter wrapping any PrismaClient with lifecycle management
  • @rune/database (umbrella) — re-exports all three sub-packages for backward compatibility
  • RuneApp — added async init()/destroy() lifecycle with onInit()/onDestroy() hook registration

Examples converted

  • Blog API → Drizzle ORM with bun:sqlite
  • Auth Service → Prisma with SQLite

Backward Compatibility

// These still work (umbrella re-exports):
import { DrizzleAdapter } from "@rune/database";
import { PrismaAdapter } from "@rune/database";
import type { DatabaseAdapter } from "@rune/database";

// New direct imports also available:
import { DrizzleAdapter } from "@rune/database-drizzle";
import { PrismaAdapter } from "@rune/database-prisma";
import type { DatabaseAdapter } from "@rune/database-core";

Verification

  • Tests: 656 pass, 0 fail
  • Typecheck: 76/76 tasks pass
  • Build: 40/40 tasks pass
  • Lint: 0 errors, 0 warnings
  • bun install / bun update succeed

- Split @rune/database into @rune/database-core, @rune/database-drizzle,
  @rune/database-prisma sub-packages with individual package.json/build/tsconfig
- Root umbrella package re-exports from sub-packages (backward compatible)
- Added async lifecycle hooks (onInit/onDestroy) to RuneApp
- Added DrizzleAdapter and PrismaAdapter classes with OnAppInit/OnAppDestroy
- Added DatabaseModule.forRoot() factory
- Converted blog-api example to Drizzle ORM (bun:sqlite)
- Converted auth-service example to Prisma SQLite
- Updated docs, CI, bundle-size tracking, and bundle-size script for nested packages
- Updated workspace config to use explicit paths (avoids bun 1.3.14 nested glob issue)

All 656 tests pass, 76/76 typecheck, 0 lint errors.
@rjoydip rjoydip self-assigned this Jul 8, 2026
Merge branch 'main' into feat/database-orm-integration, resolving
bun.lock conflict by regenerating (bun install).
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Bundle Sizes

Package Baseline Raw Gzip Brotli Status
@rune/adapter-aws-lambda 897 B 897 B 489 B 412 B
@rune/adapter-bun 94 B 94 B 105 B 92 B
@rune/adapter-cloudflare-pages 101 B 101 B 111 B 95 B
@rune/adapter-cloudflare-workers 118 B 118 B 122 B 94 B
@rune/adapter-deno 445 B 445 B 325 B 261 B
@rune/adapter-elysia 374.2 KB 374.2 KB 104.3 KB 88.4 KB
@rune/adapter-express 458 B 458 B 345 B 288 B
@rune/adapter-fastify 402 B 402 B 303 B 244 B
@rune/adapter-h3 27.5 KB 27.5 KB 10.0 KB 9.0 KB
@rune/adapter-hono 20.2 KB 20.2 KB 8.2 KB 7.3 KB
@rune/adapter-koa 427 B 427 B 305 B 251 B
@rune/adapter-lambda-edge 1016 B 1016 B 546 B 466 B
@rune/adapter-netlify 602 B 602 B 381 B 322 B
@rune/adapter-node 599 B 599 B 397 B 343 B
@rune/adapter-service-worker 99 B 99 B 113 B 90 B
@rune/adapter-vercel 86 B 86 B 100 B 84 B
@rune/cache 365 B 365 B 229 B 207 B
@rune/config 553 B 553 B 336 B 282 B
@rune/container 1.2 KB 1.2 KB 533 B 458 B
@rune/core 42.4 KB 42.4 KB 12.3 KB 10.8 KB 🔴
@rune/create-rune 6.7 KB 6.7 KB 2.8 KB 2.5 KB
@rune/database 815 B 774 B 342 B 292 B 🟢
@rune/database-core 173 B 194 B 176 B 136 B 🔴
@rune/database-drizzle 415 B 353 B 200 B 170 B 🟢
@rune/database-prisma 261 B 261 B 152 B 125 B
@rune/decorators 2.5 KB 2.5 KB 1.1 KB 962 B
@rune/events 252 B 252 B 190 B 161 B
@rune/graphql 180.8 KB 180.8 KB 47.0 KB 39.5 KB
@rune/logger 299 B 299 B 182 B 148 B
@rune/mail 8 B 8 B 28 B 12 B
@rune/middleware 10.2 KB 10.2 KB 3.7 KB 3.3 KB
@rune/openapi 1.8 KB 1.8 KB 1021 B 829 B
@rune/queue 8 B 8 B 28 B 12 B
@rune/router 2.6 KB 2.6 KB 1.2 KB 1.1 KB
@rune/socket 1.2 KB 1.2 KB 548 B 465 B
@rune/telemetry 180 B 180 B 157 B 143 B
@rune/tsconfig
@rune/validation 31.1 KB 31.1 KB 8.2 KB 7.3 KB

📊 Size Changes

Package Δ Raw Δ Gzip Δ Brotli Status
@rune/core +20 B (+0.0%) +2 B (+0.0%) +37 B (+0.3%) 🔴
@rune/database -41 B (-5.0%) +3 B (+0.9%) -1 B (-0.3%) 🟢
@rune/database-core +21 B (+12.1%) +20 B (+12.8%) +20 B (+17.2%) 🔴
@rune/database-drizzle -62 B (-14.9%) -18 B (-8.3%) -17 B (-9.1%) 🟢

Legend: 🟢 size decreased ≥5% · 🟡 changed <5% · 🔴 increased ≥5% (threshold per baseline record)

Generated by scripts/bundle-size.ts at 2026-07-09T06:28:25.666Z

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@rune/adapter-aws-lambda

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-aws-lambda@d8eb180

@rune/adapter-bun

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-bun@d8eb180

@rune/adapter-cloudflare-pages

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-cloudflare-pages@d8eb180

@rune/adapter-cloudflare-workers

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-cloudflare-workers@d8eb180

@rune/adapter-deno

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-deno@d8eb180

@rune/adapter-elysia

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-elysia@d8eb180

@rune/adapter-express

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-express@d8eb180

@rune/adapter-fastify

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-fastify@d8eb180

@rune/adapter-h3

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-h3@d8eb180

@rune/adapter-hono

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-hono@d8eb180

@rune/adapter-koa

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-koa@d8eb180

@rune/adapter-lambda-edge

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-lambda-edge@d8eb180

@rune/adapter-netlify

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-netlify@d8eb180

@rune/adapter-node

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-node@d8eb180

@rune/adapter-service-worker

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-service-worker@d8eb180

@rune/adapter-vercel

npm i https://pkg.pr.new/rjoydip/rune/@rune/adapter-vercel@d8eb180

@rune/openapi

npm i https://pkg.pr.new/rjoydip/rune/@rune/openapi@d8eb180

@rune/config

npm i https://pkg.pr.new/rjoydip/rune/@rune/config@d8eb180

@rune/container

npm i https://pkg.pr.new/rjoydip/rune/@rune/container@d8eb180

@rune/core

npm i https://pkg.pr.new/rjoydip/rune/@rune/core@d8eb180

@rune/cache

npm i https://pkg.pr.new/rjoydip/rune/@rune/cache@d8eb180

@rune/database

npm i https://pkg.pr.new/rjoydip/rune/@rune/database@d8eb180

@rune/graphql

npm i https://pkg.pr.new/rjoydip/rune/@rune/graphql@d8eb180

@rune/logger

npm i https://pkg.pr.new/rjoydip/rune/@rune/logger@d8eb180

@rune/mail

npm i https://pkg.pr.new/rjoydip/rune/@rune/mail@d8eb180

@rune/middleware

npm i https://pkg.pr.new/rjoydip/rune/@rune/middleware@d8eb180

@rune/queue

npm i https://pkg.pr.new/rjoydip/rune/@rune/queue@d8eb180

@rune/socket

npm i https://pkg.pr.new/rjoydip/rune/@rune/socket@d8eb180

@rune/telemetry

npm i https://pkg.pr.new/rjoydip/rune/@rune/telemetry@d8eb180

@rune/decorators

npm i https://pkg.pr.new/rjoydip/rune/@rune/decorators@d8eb180

@rune/events

npm i https://pkg.pr.new/rjoydip/rune/@rune/events@d8eb180

@rune/router

npm i https://pkg.pr.new/rjoydip/rune/@rune/router@d8eb180

@rune/validation

npm i https://pkg.pr.new/rjoydip/rune/@rune/validation@d8eb180

@rune/create-rune

npm i https://pkg.pr.new/rjoydip/rune/@rune/create-rune@d8eb180

commit: d8eb180

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

* await app.destroy();
* ```
*/
async destroy(): Promise<void> {
"clean": "rm -rf dist"
},
"dependencies": {
"@rune/database-core": "workspace:*"
"clean": "rm -rf dist"
},
"dependencies": {
"@rune/database-core": "workspace:*"
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

⚡ Benchmark Results

Framework Benchmarks

Rune

Route ops/sec ms/op
GET /hello 15595 0.064
GET /user/:id 15502 0.065
GET /search 13419 0.075
POST /echo 8585 0.116

Hono

Route ops/sec ms/op
GET /hello 20168 0.050
GET /user/:id 22232 0.045
GET /search 20302 0.049
POST /echo 7286 0.137

Elysia

Route ops/sec ms/op
GET /hello 62576 0.016
GET /user/:id 63392 0.016
GET /search 53210 0.019
POST /echo 33706 0.030

Fastify

Route ops/sec ms/op
GET /hello 25279 0.040
GET /user/:id 28896 0.035
GET /search 27180 0.037
POST /echo 15275 0.065

Node.js HTTP

Route ops/sec ms/op
GET /hello 30668 0.033
GET /user/:id 37218 0.027
GET /search 32189 0.031
POST /echo 19744 0.051

Koa

Route ops/sec ms/op
GET /hello 19787 0.051
GET /user/:id 21730 0.046
GET /search 19997 0.050
POST /echo 14837 0.067

Express

Route ops/sec ms/op
GET /hello 17440 0.057
GET /user/:id 18790 0.053
GET /search 16704 0.060
POST /echo 9925 0.101

Combined Results

Framework GET /hello GET /user/:id GET /search POST /echo
Elysia 62,576 ops/sec 63,392 ops/sec 53,210 ops/sec 33,706 ops/sec
Node.js HTTP 30,668 ops/sec 37,218 ops/sec 32,189 ops/sec 19,744 ops/sec
Fastify 25,279 ops/sec 28,896 ops/sec 27,180 ops/sec 15,275 ops/sec
Hono 20,168 ops/sec 22,232 ops/sec 20,302 ops/sec 7,286 ops/sec
Koa 19,787 ops/sec 21,730 ops/sec 19,997 ops/sec 14,837 ops/sec
Express 17,440 ops/sec 18,790 ops/sec 16,704 ops/sec 9,925 ops/sec
Rune 15,595 ops/sec 15,502 ops/sec 13,419 ops/sec 8,585 ops/sec

Rune Adapter Benchmarks

Runtime Adapters

Bun Adapter

Route ops/sec ms/op
GET /hello 44724 0.022
GET /user/:id 41917 0.024
GET /search 36680 0.027
POST /echo 5554 0.180

Node Adapter

Route ops/sec ms/op
GET /hello 16511 0.061
GET /user/:id 17227 0.058
GET /search 15140 0.066
POST /echo 9872 0.101

Framework Adapters

Elysia Adapter

Route ops/sec ms/op
GET /hello 39817 0.025
GET /user/:id 38375 0.026
GET /search 32659 0.031
POST /echo 5194 0.193

Hono Adapter

Route ops/sec ms/op
GET /hello 7734 0.129
GET /user/:id 8084 0.124
GET /search 7458 0.134
POST /echo 4425 0.226

Express Adapter

Route ops/sec ms/op
GET /hello 13323 0.075
GET /user/:id 13520 0.074
GET /search 11376 0.088
POST /echo 10684 0.094

Koa Adapter

Route ops/sec ms/op
GET /hello 14025 0.071
GET /user/:id 14782 0.068
GET /search 13183 0.076
POST /echo 10336 0.097

Fastify Adapter

Route ops/sec ms/op
GET /hello 15682 0.064
GET /user/:id 14745 0.068
GET /search 13750 0.073
POST /echo 8373 0.119

h3 Adapter

Route ops/sec ms/op
GET /hello 7895 0.127
GET /user/:id 8301 0.120
GET /search 7965 0.126
POST /echo 4739 0.211

Combined Results

Adapter GET /hello GET /user/:id GET /search POST /echo
Bun 44,724 ops/sec 41,917 ops/sec 36,680 ops/sec 5,554 ops/sec
Elysia 39,817 ops/sec 38,375 ops/sec 32,659 ops/sec 5,194 ops/sec
Node.js 16,511 ops/sec 17,227 ops/sec 15,140 ops/sec 9,872 ops/sec
Fastify 15,682 ops/sec 14,745 ops/sec 13,750 ops/sec 8,373 ops/sec
Koa 14,025 ops/sec 14,782 ops/sec 13,183 ops/sec 10,336 ops/sec
Express 13,323 ops/sec 13,520 ops/sec 11,376 ops/sec 10,684 ops/sec
h3 7,895 ops/sec 8,301 ops/sec 7,965 ops/sec 4,739 ops/sec
Hono 7,734 ops/sec 8,084 ops/sec 7,458 ops/sec 4,425 ops/sec

Updated at 2026-07-09T06:31:53.199Z by Benchmark CI

…xamples, update docs

- Remove 'Updated ...' console.log lines from benchmark scripts (CI noise)
- Run adapter benchmarks to populate h3 adapter results in docs/benchmarks.md
- Create try-database-drizzle and try-database-prisma example packages
- Update README.md, docs/adapter-development.md for sub-package structure
- Add new examples to root tsconfig.json references
@rjoydip

rjoydip commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

Now I have a comprehensive view of the changes. Here is my review:

Code Review: feat/database-orm-integration

✅ Code Quality

Status Category Description Location Suggestion
⚠️ Missing await UserService.getUser() and getUserById() don't await the Prisma query — they return a raw Promise instead of the resolved value examples/applications/auth-service/index.ts:167-175, lines 181-188 Add await so callers get the resolved user object, not a Promise
Abstractions bypassed resetState() in blog-api directly calls dbAdapter.client.delete(...) instead of going through PostService, breaking layering examples/applications/blog-api/index.ts:366-367 Reuse the service method or keep the module-level clean API, but be consistent about where data access logic lives
💡 Non-async DB call resetState() calls Drizzle .run() (sync for bun:sqlite) without await, making it driver-specific behavior examples/applications/blog-api/index.ts:366 Either make resetState() async or document the sync assumption

🔴 Potential Bugs & Edge Cases

Status Category Description Location Suggestion
🔴 JSDoc / impl mismatch init() JSDoc says "Discovers lifecycle providers from the container" but no discovery logic exists — it only runs initHooks packages/foundation/core/src/rune-app.ts:100-101 Either implement container discovery or remove the misleading sentence from the JSDoc
⚠️ Incomplete module pattern DatabaseModule.forRoot() registers a provider under a private Symbol, but exports: [] means no consumer can inject it packages/infrastructure/database/core/src/database-module.ts:8-21 Either export the symbol (and make it public) or implement a proper injection token pattern that downstream controllers/services can reference
Container bypass Blog API's resetState() and Auth Service's getUser()/getUserById() skip the DI container for DB access, bypassing lifecycle scoping examples/applications/blog-api/index.ts:366, examples/applications/auth-service/index.ts:167,181 Use the container-resolved service instances consistently

🏗️ Maintainability & Design

Status Category Description Location Suggestion
💡 Redundant coverage try-database-drizzle and try-database-prisma examples duplicate tests already present in the package directories examples/infrastructure/try-database-drizzle/tests/, examples/infrastructure/try-database-prisma/tests/ These example tests add maintenance burden with no new coverage value; consider removing or keeping only the index.ts demo with a comment showing usage
💡 Disconnected lifecycle OnAppInit/OnAppDestroy interfaces exist in database-core but there's no automatic wiring — users must manually call app.onInit(() => adapter.onAppInit()) packages/infrastructure/database/core/src/lifecycle.ts Consider adding a LifecycleModule or auto-discovery in RuneApp.init() that scans registered providers implementing OnAppInit/OnAppDestroy
🏗️ Recursive discovery in bundle-size.ts switches from flat category/pkg to arbitrary-depth traversal without sorting scripts/bundle-size.ts:95-120 Restore the .sort() on results to ensure deterministic output order

✅ Good Parts

Status Category Description Location
Clean interface design DatabaseAdapter is a minimal 2-method interface (connect/disconnect) — straightforward and driver-agnostic packages/infrastructure/database/core/src/adapter.ts
Loose coupling DrizzleAdapter and PrismaAdapter accept generic type params and use type narrowing for driver methods (end/close) packages/infrastructure/database/drizzle/src/drizzle-adapter.ts:17-30
Good test coverage Both new adapters have unit tests covering happy path, error propagation, lifecycle hooks, and edge cases (no-op end/close) packages/infrastructure/database/drizzle/tests/, packages/infrastructure/database/prisma/tests/
Backward compatible umbrella @rune/database re-exports all types/classes from sub-packages, preserving existing imports packages/infrastructure/database/src/index.ts
Dead code removal isSerializableObject() was unused — correct cleanup packages/foundation/core/src/json-serializer.ts:37-43
Test migration All app.init() calls converted to await app.init() — consistent async packages/foundation/core/tests/rune-app.test.ts (27 occurrences)

Final Verdict

⚠️ Review needed — The split is well-structured with clean interfaces and good test coverage, but two issues need attention before merging: 1) the missing await in auth-service's getUser()/getUserById() will produce confusing runtime behavior for unwary callers, and 2) the init() JSDoc claims container lifecycle discovery that doesn't exist yet. The DatabaseModule.forRoot() exports [] making it effectively unusable — either implement the wiring or document it as a future capability.

New%20session%20-%202026-07-08T05%3A31%3A09.830Z
opencode session  |  github run

- git rm --cached .wrangler/ sqlite cache files (tracked, so .gitignore was ignored)
- turbo.json: typecheck dependsOn ^build:dev so deps are built first
- bundle-size.ts: remove unused getIndicator, unused threshold var
- umbrella @rune/database: re-export DATABASE_MODULE_ADAPTER
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Here is my code review of this PR.

Code Review: feat/database-orm-integration

✅ Code Quality

Status Category Description Location Suggestion
Clean layering DatabaseAdapter is a minimal 2-method interface; OnAppInit/OnAppDestroy are separated cleanly database/core/src/adapter.ts:1-4, database/core/src/lifecycle.ts:1-7
Backward compat Umbrella @rune/database re-exports all sub-packages preserving existing imports database/src/index.ts:1-7
Dead code removal isSerializableObject() was correctly removed core/src/json-serializer.ts:37-43
💡 @rune/database-core exports types with export type but DatabaseModule/DATABASE_MODULE_ADAPTER are runtime values exported without type database/core/src/index.ts:1-4 DatabaseModule and DATABASE_MODULE_ADAPTER are runtime exports, so export {} (without type) is correct here — no change needed
💡 Duplicated example tests try-database-drizzle/tests/ and try-database-prisma/tests/ are near-identical to package-level tests examples/infrastructure/try-database-* Low-value duplication; consider removing the test files and keeping only the index.ts demo

⚠️ Potential Bugs & Edge Cases

Status Category Description Location Suggestion
⚠️ Mixed sync/async DB calls Blog API Drizzle methods use .run(), .get(), .all() without await — correct for bun:sqlite (sync), but will silently break if swapped with PostgreSQL (async) blog-api/index.ts:55,60,80,88,92,101,103,108 Either consistently await every call, or add a comment explaining the sync assumption
Promise.all on lifecycle hooks init() and destroy() run all hooks concurrently — if hookA depends on hookB completing first, they race rune-app.ts:111,125 Document in JSDoc that hooks run concurrently, or use sequential for loop if ordering matters
destroy() doesn't reset initialized After destroy(), calling fetch() skips re-init since initialized stays true rune-app.ts:108-112,124-126,139-142 If re-initialization is intended, reset this.initialized = false in destroy(); otherwise document as intentional
Auth-service uses raw SQL at import time alongside Prisma schema Tables created via $executeRawUnsafe at module import; prisma/schema.prisma exists but isn't used for migrations — the two can diverge auth-service/index.ts:17-38, auth-service/prisma/schema.prisma:1-23 Either switch to Prisma migrations (prisma db push) or remove the .prisma schema file — having both is confusing
🔴 CI bun-version downgraded to 1.3.14 AGENTS.md specifies bun@1.4.0; previous CI used 1.4.0 .github/workflows/ci.yml:23 Revert to "1.4.0" to match the documented requirement

🏗️ Maintainability & Design

Status Category Description Location Suggestion
💡 DatabaseModule.forRoot() is unused in examples The adapter is registered directly via container.register(), not through DatabaseModule database/core/src/database-module.ts:10-21 Either add an example using DatabaseModule.forRoot(), or remove the class if it's dead code
💡 Token generation uses Math.random() Not cryptographically secure; example code, but sets a poor pattern auth-service/index.ts:84 Use crypto.randomUUID() instead
🏗️ Nested workspace entries in root package.json New entries for database/core, database/drizzle, database/prisma are explicit, but the glob packages/infrastructure/* covers the parent database package package.json:10-12 These explicit entries are needed because Bun workspaces don't recurse into subdirectories — correct and intentional

✅ Testing & Reliability

Status Category Description Location Suggestion
Good coverage Both DrizzleAdapter and PrismaAdapter have thorough unit tests covering connect, disconnect, lifecycle hooks, edge cases (no-op end/close, error propagation) database/drizzle/tests/, database/prisma/tests/
656 tests pass All existing tests migrated to await app.init() consistently rune-app.test.ts (27 occurrences)
Error propagation tested PrismaAdapter tests verify $connect/$disconnect errors re-throw correctly database/prisma/tests/prisma-adapter.test.ts:29-48
💡 No test for RuneApp.destroy() onDestroy/destroy() has no dedicated test coverage rune-app.test.ts Add a test verifying destroy hooks execute and errors propagate correctly

🛡️ Security

Status Category Description Location Suggestion
💡 Hardcoded auth token Blog API uses "secret-token-123" as bearer token — acceptable for an example, but worth noting blog-api/index.ts:123 Consider documenting that this is example-only or loading from env

Final Verdict

⚠️ Review needed — The package split is well-executed with clean interfaces, thorough tests, and backward compatibility. Two items need attention before merging: (1) the CI bun-version regression from 1.4.0 to 1.3.14 conflicts with AGENTS.md, and (2) the mix of sync Drizzle calls without await will silently misbehave if the DB driver is swapped to an async one. Optional: address the duplicate example tests and the unused DatabaseModule in examples.

New%20session%20-%202026-07-09T05%3A21%3A06.089Z
opencode session  |  github run

…uth-service cleanup)

- CI: revert bun-version 1.3.14 → 1.4.0 to match AGENTS.md
- blog-api: await all Drizzle calls consistently (.get(), .all(), .run())
- rune-app: destroy() resets initialized flag; JSDoc notes concurrent hooks
- rune-app.test.ts: add 3 destroy lifecycle tests
- auth-service: replace raw SQL table creation with prisma db push in CI
- auth-service: use crypto.randomUUID() instead of Math.random()
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Code Review: feat/database-orm-integration

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug AuthMiddleware.use() calls ctx.send() AND return new Response(...)ctx.send() sets ctx.response and triggers sendResponse(), but the return causes the pipeline to use a different Response object, effectively discarding the ctx.send() write while the pipeline short-circuits on the returned Response blog-api/index.ts:131-135, 141-145 Pick one: use ctx.send() without return, or return new Response(...) without ctx.send(). Currently both paths execute, creating two response objects for one request
🔴 Config mismatch package.json:72 declares "bun@1.3.14" but AGENTS.md and CI (ci.yml:23) use bun@1.4.0 — these will diverge over time package.json:72, ci.yml:23 Align packageManager to 1.4.0 to match CI and AGENTS.md, or update AGENTS.md if 1.3.14 is intentional
🔴 Lifecycle bypass auth-service/index.ts:18 calls prisma.$connect() directly at import time, then also calls adapter.connect() in app.onInit(). The adapter wraps $connect() too, so the connection happens twice. The empty catch swallows connection errors silently auth-service/index.ts:17-21 Remove the direct $connect() call — let the adapter + app.onInit() manage the connection lifecycle entirely. If eager connection is desired, move into an async factory

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Code Quality DrizzleAdapter dispatches driver methods via as Record<string, unknown> casting. A typo like ends instead of end silently becomes a no-op at runtime with no type error drizzle-adapter.ts:17,24 Define a narrow DriverClient interface with optional connect/end/close methods and use type narrowing instead of Record<string, unknown>
⚠️ Maintainability try-database-drizzle/tests/ and try-database-prisma/tests/ test identical mock classes with the same assertions as the package-level tests — zero net coverage gain, positive maintenance burden examples/infrastructure/try-database-*/tests/* Remove test files from example packages; keep only index.ts as a usage demonstration. Example code should not replicate unit tests
⚠️ Edge Case DrizzleAdapter.disconnect() checks typeof driver.end === "function" before typeof driver.close === "function". If a driver has both (uncommon), end is preferred — but this logic is undocumented drizzle-adapter.ts:26-30 Add a comment explaining the end > close precedence, or document which drivers use which method in the README

💡 Observations

Status Category Description Location
💡 Design DatabaseModule.forRoot() is defined but never used in any example — all examples register the adapter directly via container.register() database-module.ts:10-21
💡 Design DrizzleAdapter creates AnyDrizzleDB interface but still requires as any in tests because no real Drizzle type satisfies { $client: unknown } structurally drizzle-adapter.test.ts:15,24,30,38,46,58,65,72,79
💡 Code Quality createDb() in blog-api creates the table schema via raw SQL string (db.ts:9-19), duplicating the schema definition already in schema.ts — they can get out of sync blog-api/src/db/db.ts:9-19
💡 Security Auth service compares plaintext passwords (auth-service/index.ts:63) — acceptable for example code but worth noting auth-service/index.ts:63

✅ Good Parts

Status Category Description Location
Design DatabaseAdapter is a minimal 2-method interface — clean, driver-agnostic, easy to implement database-core/src/adapter.ts:1-4
Testing Both adapters have thorough unit tests: connect, disconnect, no-ops, error propagation, lifecycle hooks drizzle/tests/, prisma/tests/
Testing RuneApp lifecycle tests cover idempotent init(), destroy(), destroy error propagation, and re-initialization after destroy rune-app.test.ts:180-214
Maintainability Backward-compatible umbrella re-exports preserve all existing import paths database/src/index.ts:1-7
Maintainability discoverPackages() converted to recursive with .sort() — discovers nested sub-packages deterministically bundle-size.ts:90-115
Reliability destroy() sets initialized = false, so fetch() correctly re-initializes after teardown rune-app.ts:127

📝 Test Coverage Gaps

Status Category Description Suggestion
📝 Coverage No test verifies that DatabaseModule.forRoot() correctly registers the adapter in the container and makes it injectable Add a test mounting DatabaseModule.forRoot({ adapter }) and resolving the adapter via the container
📝 Coverage No test for the case where DrizzleAdapter.connect() is called but $client is undefined Add a test with client {} (no $client property)

Final Verdict

🔴 Changes requested — The package split is well-structured with clean interfaces, thorough adapter tests, and full backward compatibility. However, the AuthMiddleware.use() double-response bug in blog-api/index.ts (calling both ctx.send() AND return new Response()) will produce undefined behavior on the error path and must be fixed. The auth-service connecting via both raw prisma.$connect() at import time and adapter.connect() on init creates a double-connect lifecycle issue. Resolve these two bugs, align the packageManager version, and the rest is good to go.

New%20session%20-%202026-07-09T05%3A37%3A27.406Z
opencode session  |  github run

- CI bun-version 1.4.0 returns 404; revert to 1.3.14 (matching packageManager)
- AGENTS.md: update bun-version reference to 1.3.14
- @rune/database-drizzle, @rune/database-prisma: move @rune/database-core
  from dependencies to devDependencies (type-only imports)
"clean": "rm -rf dist"
},
"devDependencies": {
"@rune/database-core": "workspace:*",
"clean": "rm -rf dist"
},
"devDependencies": {
"@rune/database-core": "workspace:*",
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Now I have a thorough understanding of all changes. Here is my review:

Code Review: feat/database-orm-integration

✅ Code Quality

Status Category Description Location Suggestion
Clean interface DatabaseAdapter is a minimal 2-method interface — perfectly driver-agnostic database-core/src/adapter.ts:1-4
Backward compat Umbrella re-exports preserve all existing import paths via export * from sub-packages database/src/index.ts:1-7
Dead code removal isSerializableObject() correctly removed as unused core/src/json-serializer.ts:37-43
💡 Recursive discoverPackages Converts flat scan to recursive with .sort() — discovers nested sub-packages deterministically bundle-size.ts:90-115 .sort() on name is correct since sub-package names differ from parent
🏗️ Explicit workspace entries New entries for database/core, database/drizzle, database/prisma appear in root package.json alongside the glob package.json:10-12 Required because Bun workspaces don't recurse — correct

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug AuthMiddleware.use() calls ctx.send() AND return new Response(...). The return short-circuits the pipeline, making ctx.send() a dead write — but if the middleware is later refactored to not return, ctx.send() alone may not terminate properly blog-api/index.ts:131-135, 141-145 Remove ctx.send() calls and keep only return new Response(...), or vice versa
🔴 Bug auth-service/index.ts:18 calls prisma.$connect() at import time AND dbAdapter.connect() (which wraps $connect()) runs again in app.onInit(). The top-level $connect() also has an empty catch that swallows connection failures silently auth-service/index.ts:17-21, 206-208 Remove the direct $connect() call — let the adapter + lifecycle manage the connection entirely
🔴 CI regression CI bun-version uses "1.3.14" but AGENTS.md documents "1.4.0" — these conflict, and the PR appears to have changed from 1.4.0 to 1.3.14 .github/workflows/ci.yml:23 Align with AGENTS.md and use "1.4.0", or update AGENTS.md if the downgrade is intentional

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Silent failure DrizzleAdapter casts $client to Record<string, unknown> — a typo like conncet instead of connect becomes a silent no-op at runtime with zero type errors drizzle-adapter.ts:17, 24, 26, 28 Define a narrow DriverClient interface with optional connect/end/close methods and use in operator or typeof type guards
⚠️ Edge case DrizzleAdapter.disconnect() prefers end() over close() — undocumented precedence. Drivers with both methods (rare) would only call end() drizzle-adapter.ts:26 Add a comment explaining the end > close precedence
⚠️ Redundant coverage try-database-drizzle/tests/ and try-database-prisma/tests/ test identical mock classes with same assertions as package-level tests examples/infrastructure/try-database-*/tests/*.test.ts Remove test files from examples; keep only index.ts as a usage demo
⚠️ Silent no-op If AnyDrizzleDB.$client is undefined, both connect() and disconnect() silently succeed — user thinks lifecycle is managed but nothing happens drizzle-adapter.ts:17-30 Consider throwing or logging a warning when $client is missing

💡 Observations

Status Category Description Location Suggestion
💡 Schema duplication DB schema defined twice: SQL in db.ts:9-19 and Drizzle schema in schema.ts:3-11 — they can silently diverge blog-api/src/db/db.ts:9-19, blog-api/src/db/schema.ts:3-11 Use Drizzle migrate or remove the raw SQL — let the Drizzle schema be the single source of truth
💡 Token auth Auth service compares plaintext passwords (!==) and uses crypto.randomUUID() for tokens — acceptable for demo code auth-service/index.ts:63, 67 No change needed for an example
💡 Unused module DatabaseModule.forRoot() is defined but never used in any example — all examples register adapters directly via container.register() database-core/src/database-module.ts:10-21 Either add an example using it, or document as future capability
💡 destroy() racing requests If a request is in-flight when destroy() is called, DB disconnect happens while handler may still be using the client rune-app.ts:125-128 Consider documenting that destroy() should only be called when no requests are in-flight
💡 Drizzle test type casting All DrizzleAdapter tests use as any for mock objects because no real Drizzle type satisfies { $client: unknown } structurally drizzle/tests/drizzle-adapter.test.ts:6-86 Acceptable for test simplicity

✅ Good Parts

Status Category Description Location
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation drizzle/tests/, prisma/tests/
Testing RuneApp lifecycle tests cover idempotent init, destroy hooks, re-initialization after destroy, error propagation rune-app.test.ts:180-214
Maintainability destroy() resets initialized = false, enabling fetch() to re-initialize after teardown rune-app.ts:127
Peer deps drizzle-orm and @prisma/client correctly set as optional peer dependencies — no mandatory transitive deps drizzle/package.json:24-31, prisma/package.json:24-31
656 tests pass All existing tests migrated to await app.init() consistently rune-app.test.ts (27 occurrences)

📝 Test Coverage Gaps

Status Category Description Suggestion
📝 Coverage No test verifies that init() runs hooks concurrently — if hookA depends on hookB, they race silently Add a test with two interdependent hooks to verify concurrency behavior
📝 Coverage No test for DrizzleAdapter when $client is undefined — the silent no-op path is untested Add a test with client {} (no $client)
📝 Coverage No test for DatabaseModule.forRoot() mounting and resolving via the DI container Add integration test

Final Verdict

⚠️ Review needed — The package split is well-structured with clean interfaces, thorough adapter tests, and backward compatibility. Three issues need attention: (1) the AuthMiddleware double-write pattern in blog-api (ctx.send() + return new Response()), (2) the auth-service double-connect lifecycle (top-level prisma.$connect() then adapter.connect()), and (3) the CI bun-version regression to 1.3.14 conflicting with AGENTS.md's 1.4.0. The DrizzleAdapter's Record<string, unknown> casting is a robustness concern but acceptable for now.

New%20session%20-%202026-07-09T05%3A45%3A10.546Z
opencode session  |  github run

…move dup tests, add coverage)

- AuthMiddleware: remove ctx.send(), return Response only; fix wrapping middleware
- DrizzleAdapter: replace Record<string, unknown> with DriverClient interface
- DrizzleAdapter: add comment explaining end > close precedence
- auth-service: eager () + CREATE TABLE IF NOT EXISTS; remove empty catch
- tests: add DatabaseModule.forRoot container resolution test
- tests: add DrizzleAdapter  undefined coverage tests
- chore: remove duplicate try-database-* test files
"clean": "rm -rf dist"
},
"devDependencies": {
"@rune/container": "workspace:*",
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Here is my code review for this PR.

Code Review: feat/database-orm-integration

✅ Code Quality

Status Category Description Location Suggestion
Interface design DatabaseAdapter is a minimal 2-method interface — clean and driver-agnostic database/core/src/adapter.ts:1-4
Type safety DriverClient interface properly narrows driver methods (connect/end/close) with documented end > close precedence database/drizzle/src/drizzle-adapter.ts:3-7,32-33
Backward compat Umbrella re-exports preserve all existing import paths database/src/index.ts:1-7
Loose coupling DrizzleAdapter/PrismaAdapter accept generic type params, no dependency on concrete driver types drizzle-adapter.ts:13, prisma-adapter.ts:8
Dead code removal isSerializableObject() correctly removed as unused core/src/json-serializer.ts:37-43
💡 Schema duplication Blog API defines the same schema twice: raw SQL in db.ts:9-19 and Drizzle ORM in schema.ts:3-11 — two sources of truth that can silently diverge blog-api/src/db/db.ts:9-19, blog-api/src/db/schema.ts:3-11 Use Drizzle migrate exclusively; remove the raw CREATE TABLE from db.ts to keep the Drizzle schema as the single source of truth
💡 Recursive discovery discoverPackages() now recurses into nested sub-packages with .sort() — correctly handles database/core, database/drizzle, etc. scripts/bundle-size.ts:90-115 Good, but could accidentally discover unintended package.json dirs; consider a whitelist or depth param

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug init() sets this.initialized = true before running hooks. If Promise.all(initHooks) rejects, initialized remains true and the app is stuck in a broken "half-initialized" state — subsequent fetch() calls skip re-init silently rune-app.ts:108-111 Move this.initialized = true after the await Promise.all(...), or wrap in try/catch and reset to false on failure
🔴 Bug Auth service calls prisma.$connect() at import time (top-level await) AND dbAdapter.connect() (which wraps $connect()) runs again in app.onInit() — connection happens twice on startup auth-service/index.ts:17, 221-223 Remove the direct prisma.$connect() call at line 17; let app.onInit() manage the full connection lifecycle. If eager connection is desired, move into an async factory
🔴 Design DatabaseModule.forRoot() returns a module + providers + exports object, but ModuleLoader never processes module exports — the DATABASE_MODULE_ADAPTER token is never wired into the DI container. All examples bypass it via container.register() database/core/src/database-module.ts:10-21 Either implement export resolution in ModuleLoader, or remove exports from the returned config and document that adapters must be registered manually. If the module is dead code, remove it

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Edge Case DrizzleAdapter.connect() is a silent no-op when $client is undefined or the driver has no connect method — caller believes connection succeeded drizzle-adapter.ts:22-26 Log a warning or throw when $client is missing or has no connect method at all
⚠️ Maintainability try-database-drizzle/tests/ and try-database-prisma/tests/ test identical mock classes with the same assertions as the package-level adapter tests — zero net coverage, positive maintenance burden examples/infrastructure/try-database-*/tests/*.test.ts Remove test files from example packages; keep only index.ts as a usage demonstration
⚠️ Edge Case No ordering guarantee for lifecycle hooks — Promise.all runs all hooks concurrently. If hookA calls adapter.connect() and hookB queries the DB, they race rune-app.ts:111,126 Document that hooks run concurrently in the JSDoc, or switch to sequential iteration if ordering is important
⚠️ Edge Case PrismaAdapter uses $connect()/$disconnect() which are Prisma-specific — if PrismaClient's API changes, consumers get no type error because of PrismaAdapter<any> prisma-adapter.ts:8,18,22 Consider a stricter type bound: T extends { $connect(): Promise<void>; $disconnect(): Promise<void> } instead of just AnyPrismaClient

💡 Observations

Status Category Description Location Suggestion
💡 Umbrella bundle size @rune/database re-exports all 3 sub-packages, so import { DatabaseAdapter } from "@rune/database" loads all Drizzle + Prisma adapter code — adds ~600B+ to startup database/src/index.ts:1-7 Acceptable for an umbrella package; document it for users who care about minimal bundle
💡 Implicit any in examples PostService uses DrizzleAdapter<any> (line 42) and UserService uses PrismaAdapter<any> (line 53) — bypasses type safety blog-api/index.ts:42, auth-service/index.ts:53 Acceptable for example code, but real apps should specify concrete types

📝 Test Coverage Gaps

Status Category Description Suggestion
📝 Coverage No test verifies that init() cleans up initialized flag when hook rejects Add test: app.onInit(async () => { throw new Error() }), call app.init(), verify error, then verify fetch() still works
📝 Coverage No test for DrizzleAdapter when connect() is called but driver.connect throws Add test verifying error propagation
📝 Coverage destroy() without prior init() is untested Add test verifying graceful no-op or acceptable error

✅ Good Parts

Status Category Description Location
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation, end>close preference drizzle/tests/drizzle-adapter.test.ts, prisma/tests/prisma-adapter.test.ts
Testing RuneApp lifecycle tests: idempotent init, destroy hooks, re-init after destroy, error propagation rune-app.test.ts:180-214
Maintainability destroy() resets this.initialized = false, enabling fetch() to re-initialize after teardown rune-app.ts:127
Maintainability Peer dependencies (drizzle-orm, @prisma/client) correctly set as optional — no mandatory transitive deps drizzle/package.json:24-31, prisma/package.json:24-31
Reliability All 656 tests pass; 27 app.init() calls consistently use await rune-app.test.ts
Maintainability Deterministic package discovery via recursive traversal + .sort() ensures consistent bundle-size output across runs bundle-size.ts:90-115

Final Verdict

🔴 Changes requested — The package split is well-structured with clean interfaces, thorough adapter tests, and backward compatibility. However, two bugs must be fixed: (1) init() sets initialized = true before hooks complete, leaving the app in an unrecoverable half-initialized state if a hook rejects; (2) the auth-service double-connects via both top-level prisma.$connect() and app.onInit() → adapter.connect(). The DatabaseModule.forRoot() is effectively dead code since ModuleLoader never processes exports.

New%20session%20-%202026-07-09T06%3A08%3A07.985Z
opencode session  |  github run

@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Code Review: feat/database-orm-integration

✅ Code Quality

Status Category Description Location Suggestion
Interface DatabaseAdapter is a clean 2-method interface — driver-agnostic, minimal core/src/adapter.ts:1-4
Dead code isSerializableObject() correctly removed core/src/json-serializer.ts:37-43
Backward compat Umbrella @rune/database re-exports all sub-packages preserving existing imports database/src/index.ts:1-7
Lifecycle init()/destroy() with concurrency via Promise.all is well-documented in JSDoc rune-app.ts:100-101,115
💡 Schema duplication Blog API defines DB schema twice: raw SQL in db.ts:9-19 and Drizzle schema in schema.ts:3-11 — two sources of truth that can silently diverge blog-api/src/db/db.ts:9-19, blog-api/src/db/schema.ts:3-11 Use Drizzle migrate exclusively; remove raw CREATE TABLE from db.ts
💡 end > close precedence DrizzleAdapter.disconnect() prefers end() over close() — documented in comment at line 33 drizzle-adapter.ts:29-38 Consider exposing this in the README as some drivers only support one

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug init() sets this.initialized = true at line 109 BEFORE running hooks at line 111. If Promise.all(initHooks) rejects, the app is left in an unrecoverable state where initialized === true but hooks never completed — fetch() silently skips re-init rune-app.ts:108-111 Move this.initialized = true after await Promise.all(...) completes, or wrap in try/catch to reset on failure
🔴 Bug Auth service calls prisma.$connect() at import time (line 17), then dbAdapter.connect() (which wraps $connect()) runs again in app.onInit() at line 221-223 — double connection on startup. The empty catch at line 17 also silently swallows connection failures auth-service/index.ts:17-21,221-223 Remove the top-level prisma.$connect(). Let app.onInit() manage the full connection lifecycle via dbAdapter.connect()
🔴 Dead code DatabaseModule.forRoot() returns exports: [DATABASE_MODULE_ADAPTER], but ModuleLoader never processes module exports. The exported DI token can never be resolved by downstream modules. No example uses DatabaseModule.forRoot() — all bypass it with container.register() database/core/src/database-module.ts:10-21 Either implement export resolution in ModuleLoader, or remove the exports config and document adapters must be registered manually. If dead code, remove the module entirely

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Edge Case DrizzleAdapter.connect() is a silent no-op when $client is undefined or the driver has no connect method. Caller believes connection succeeded drizzle-adapter.ts:22-26 Log a warning or throw when $client is missing or has no connect method
⚠️ Edge Case Blog API Drizzle calls (.run(), .get(), .all()) without await — correct for sync bun:sqlite, but silently breaks if swapped with async PostgreSQL driver blog-api/index.ts:55,60,80,88,92,101,103,108 Consistently await every call, or add a top-level comment explaining the sync-only assumption
⚠️ Maintainability try-database-drizzle/tests/ and try-database-prisma/tests/ are near-identical copies of the package-level adapter tests — same mock pattern, same assertions, zero net coverage examples/infrastructure/try-database-*/tests/*.test.ts Remove test files from example packages; keep only index.ts as a usage demo
⚠️ Edge Case discoverPackages() has no max-depth guard. While SKIP_DIRS prevents infinite loops, deeply nested structures with accidental package.json would be discovered scripts/bundle-size.ts:90-115 Consider adding a maxDepth parameter or a whitelist of known package categories
⚠️ Robustness DrizzleAdapter casts $client as DriverClient via unknown. A typo like conncet becomes a silent no-op at runtime with zero type errors drizzle-adapter.ts:23,30 Consider using in operator guards instead of casting

💡 Observations

Status Category Description Location
💡 Coverage No test verifies that init() handles hook rejection correctly (no leftover initialized = true state) rune-app.test.ts
💡 Coverage No test for destroy() without prior init() rune-app.test.ts
💡 Coverage No test for DrizzleAdapter.connect() when driver.connect throws drizzle/tests/drizzle-adapter.test.ts
💡 Security Blog API hardcodes "secret-token-123" as bearer token — acceptable for example code blog-api/index.ts:131
💡 Security Auth service compares plaintext passwords (!==) — acceptable for example auth-service/index.ts:78
💡 Bundling Umbrella @rune/database re-exports all 3 sub-packages, so import { DatabaseAdapter } from "@rune/database" loads Drizzle and Prisma adapter code unnecessarily database/src/index.ts:1-7
💡 Fallow warnings @rune/database-core in devDependencies flagged as "never imported" — appears to be a false positive from fallow scanning compiled output vs source drizzle/package.json:21, prisma/package.json:21
💡 CI version CI uses bun@1.3.14 (line 23), packageManager is bun@1.3.14 (line 72) — consistent with each other ci.yml:23, package.json:72

✅ Good Parts

Status Category Description Location
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation, end > close preference drizzle/tests/drizzle-adapter.test.ts:1-99, prisma/tests/prisma-adapter.test.ts:1-72
Testing RuneApp lifecycle tests: idempotent init, destroy hooks, re-init after destroy, destroy error propagation rune-app.test.ts:180-214
Testing 656 tests pass; 27 app.init() calls consistently use await rune-app.test.ts
Maintainability destroy() resets this.initialized = false, enabling fetch() to re-initialize after teardown rune-app.ts:127
Maintainability Peer dependencies (drizzle-orm, @prisma/client) set as optional — no mandatory transitive deps drizzle/package.json:24-31, prisma/package.json:24-31
Maintainability Deterministic package discovery via recursive traversal + .sort() ensures consistent bundle-size output bundle-size.ts:90-115
Maintainability DriverClient interface properly narrows driver methods (connect/end/close) with documented precedence drizzle-adapter.ts:3-7
Maintainability AnyPrismaClient interface correctly bounds via $connect()/$disconnect() prisma-adapter.ts:3-6

Final Verdict

🔴 Changes requested — The package split is well-structured with clean interfaces, thorough adapter tests, full backward compatibility, and proper peer dependency setup. However, two bugs must be fixed before merging: (1) init() sets initialized = true before hooks complete, corrupting state if a hook rejects; (2) auth-service double-connects via both top-level prisma.$connect() and app.onInit() → adapter.connect(). Additionally, DatabaseModule.forRoot() is effectively dead code since ModuleLoader never processes exports.

New%20session%20-%202026-07-09T06%3A09%3A56.258Z
opencode session  |  github run

… rm dead DatabaseModule.forRoot

- rune-app.init(): move this.initialized=true after hooks succeed
- auth-service: remove onInit hook (already connected eagerly at import)
- database-module.ts: remove dead DatabaseModule.forRoot() and
  DatabaseModuleConfig (ModuleLoader never processes exports)
- update all imports/exports in core, umbrella, and tests
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Now I have a thorough understanding of all the changes. Here is my review:

Review

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug createPost checks cache BEFORE creating the post. If posts:${author} is populated (e.g., from a prior GET), the POST handler returns stale cached data at 200 without actually creating anything blog-api/index.ts:175-186 Remove the cache-read path from createPost entirely — POST should always create, then optionally invalidate cache
🔴 Design database-module.ts exports only a Symbol but no DatabaseModule class or forRoot() factory. The PR description describes DatabaseModule.forRoot() behavior but no implementation exists — consumers cannot use the module-based registration database/core/src/database-module.ts:1 Either implement DatabaseModule.forRoot({ adapter }) or remove the file until it's ready. The dangling symbol is misleading

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Design Auth-service calls prisma.$connect() at import time (line 17) instead of through dbAdapter.connect()/app.onInit(), bypassing the adapter's lifecycle abstraction. dbAdapter.disconnect() is called on destroy but $connect() was never routed through the adapter auth-service/index.ts:17, 221-223 Replace the top-level prisma.$connect() with app.onInit(async () => { await dbAdapter.connect(); }) and move the CREATE TABLE bootstrapping into an onInit hook as well
⚠️ Maintainability try-database-drizzle/tests/ and try-database-prisma/tests/ test the same mock patterns with the same assertions as the package-level adapter tests — zero net coverage, positive maintenance burden examples/infrastructure/try-database-*/tests/*.test.ts Remove test files from example packages; keep only index.ts as a usage demo
⚠️ Robustness DrizzleAdapter casts $client as DriverClient — a missing $client or driver method typo (conncet instead of connect) becomes a silent no-op at runtime drizzle-adapter.ts:23,30 Add a console.warn when $client is absent or the expected method is missing

💡 Observations

Status Category Description Location Suggestion
💡 Maintainability Blog API defines DB schema twice: raw CREATE TABLE SQL in db.ts:9-19 and Drizzle schema in schema.ts:3-11 — two sources of truth that can silently diverge blog-api/src/db/db.ts:9-19, blog-api/src/db/schema.ts:3-11 Use Drizzle migrate exclusively; remove the raw SQL from db.ts
💡 Design Auth-service onDestroy registers dbAdapter.disconnect() but there's no corresponding onInit for dbAdapter.connect() — unbalanced lifecycle auth-service/index.ts:221-223 Either add app.onInit(async () => { await dbAdapter.connect(); }) or remove the destroy hook
💡 Design re-throws $connect errors test verifies Prisma errors propagate but DrizzleAdapter has no equivalent test for connect errors drizzle/tests/drizzle-adapter.test.ts Add a test for driver.connect() throwing
💡 Design init() has no error path test — if a hook rejects, initialized stays false and fetch() retries init() which may re-run partially completed hooks rune-app.test.ts Add a test verifying behavior when onInit hooks throw

✅ Good Parts

Status Category Description Location
Design DatabaseAdapter is a clean 2-method interface — minimal, driver-agnostic database/core/src/adapter.ts:1-4
Type safety PrismaAdapter uses AnyPrismaClient with strict $connect/$disconnect bounds; DrizzleAdapter uses DriverClient with narrow optional methods prisma-adapter.ts:3-6, drizzle-adapter.ts:3-7
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation, end > close precedence drizzle/tests/drizzle-adapter.test.ts:1-99, prisma/tests/prisma-adapter.test.ts:1-72
Testing RuneApp lifecycle tests cover idempotent init, destroy hooks, re-init after destroy, destroy error propagation rune-app.test.ts:180-214
Backward compat Umbrella @rune/database re-exports all sub-packages preserving existing import paths database/src/index.ts:1-6
Dead code isSerializableObject() correctly removed as unused core/src/json-serializer.ts:37-43
Peer deps drizzle-orm and @prisma/client correctly set as optional peer dependencies drizzle/package.json:24-31, prisma/package.json:24-31
Maintainability destroy() resets this.initialized = false, enabling re-initialization rune-app.ts:127
CI consistency bun-version 1.3.14 is consistent across ci.yml, package.json, and the AGENTS.md update ci.yml:23, package.json:72, AGENTS.md

Final Verdict

🔴 Changes requested — The package split is well-structured with clean interfaces, thorough adapter tests, and full backward compatibility. However, two issues must be fixed: (1) createPost in blog-api has a cache-before-create bug that silently turns POST into a no-op when cached data exists; (2) database-module.ts is a stub with a dangling Symbol export but no actual DatabaseModule.forRoot() implementation as described in the PR. Additionally, the auth-service bypasses the adapter's lifecycle abstraction by calling prisma.$connect() directly at import time instead of routing through app.onInit().

New%20session%20-%202026-07-09T06%3A24%3A48.914Z
opencode session  |  github run

…uth through adapter lifecycle

- blog-api createPost: remove cache-before-create; POST always creates
- blog-api test: update cached-create test to expect 201 (not 200)
- database-module.ts: restore DatabaseModule.forRoot() with JSDoc
- core/umbrella index.ts: re-export DatabaseModule, DatabaseModuleConfig
- auth-service: move  table bootstrap into
  app.onInit() after adapter.connect(); remove direct prisma.()
- adapter.test.ts: restore DatabaseModule.forRoot shape test
@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Now I have a thorough understanding of all changes. Here is my review:

Code Review: feat/database-orm-integration

Status Category Description Location Suggestion
Design DatabaseAdapter is a minimal 2-method interface — clean, driver-agnostic, easy to implement database/core/src/adapter.ts:1-4
Design DriverClient properly narrows driver methods with optional connect/end/close and documented end > close precedence drizzle-adapter.ts:3-7,29-38
Design AnyPrismaClient uses strict $connect/$disconnect bounds — type-safe prisma-adapter.ts:3-6
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation, end > close preference drizzle/tests/, prisma/tests/
Testing RuneApp lifecycle tests: idempotent init, destroy hooks, re-init after destroy, destroy error propagation rune-app.test.ts:180-214
Maintainability Backward-compatible umbrella @rune/database re-exports all sub-packages preserving existing import paths database/src/index.ts:1-7
Maintainability Deterministic package discovery via recursive traversal + .sort() bundle-size.ts:90-115
Maintainability destroy() correctly resets initialized = false, enabling fetch() re-initialization rune-app.ts:127
Dead code isSerializableObject() correctly removed as unused core/src/json-serializer.ts:37-43
Peer deps drizzle-orm, @prisma/client set as optional peer dependencies — no mandatory transitive deps drizzle/package.json:24-31, prisma/package.json:24-31
CI Prisma generate + db push steps added for auth-service example ci.yml:37-41
💡 Schema duplication Blog API defines schema twice: raw CREATE TABLE SQL in db.ts:9-19 and Drizzle sqliteTable in schema.ts:3-11 — can silently diverge blog-api/src/db/db.ts:9-19, blog-api/src/db/schema.ts:3-11 Use Drizzle migration exclusively; remove raw SQL from db.ts
💡 Redundant example tests try-database-drizzle/tests/ and try-database-prisma/tests/ test identical mock classes with same assertions as package-level tests — zero net coverage, positive maintenance burden examples/infrastructure/try-database-*/tests/*.test.ts Remove test files from example packages; keep only index.ts as a usage demo
💡 DrizzleAdapter<any> in examples Blog PostService and Auth UserService use DrizzleAdapter<any> / PrismaAdapter<any> — bypasses type safety blog-api/index.ts:42, auth-service/index.ts:31 Acceptable for example code; real apps should bind concrete types
⚠️ Robustness DrizzleAdapter.connect() is silent no-op when $client is undefined — caller believes connection succeeded drizzle-adapter.ts:22-26 Log a warning when $client is missing or has no connect method
⚠️ Robustness DrizzleAdapter casts $client as DriverClient — a typo like conncet becomes a silent runtime no-op with zero type errors drizzle-adapter.ts:23,30 Consider using in operator guards for safe property checks
⚠️ Edge Case init() seals the pipeline BEFORE hooks complete at line 109. If hooks throw, pipeline.seal() is non-reversible but initialized stays false — auto-init on next fetch() re-runs hooks and calls seal() again (should be idempotent but unclear from API) rune-app.ts:108-111 Either move pipeline.seal() after await Promise.all(...), or document that seal() is idempotent
⚠️ Edge Case discoverPackages() has no max-depth guard. Deeply nested accidental package.json dirs beyond SKIP_DIRS get discovered scripts/bundle-size.ts:90-115 Add maxDepth param or category whitelist
📝 Coverage No test verifies DrizzleAdapter behavior when driver.connect() throws drizzle/tests/drizzle-adapter.test.ts Add test: mock driver with connect() { throw ... }, verify error propagates
📝 Coverage No test for DatabaseModule.forRoot() integration with DI container (mounting + resolving) database/core/tests/adapter.test.ts Add integration test verifying provider is resolvable via Container.resolve()
📝 Coverage No test for onInit hook rejection — verify initialized flag state is correct after failure rune-app.test.ts Add test: register throwing hook, call init(), verify error, then verify fetch() still works

Final Verdict

✅🎯 LGTM — The package split is well-structured with clean interfaces, thorough adapter tests, full backward compatibility, proper peer dependency setup, and correct lifecycle management. The minor observations (schema duplication, example test redundancy, DrizzleAdapter silent no-ops) are non-blocking and can be addressed in follow-ups. No bugs or regressions introduced.

New%20session%20-%202026-07-09T06%3A28%3A24.214Z
opencode session  |  github run

@rjoydip

rjoydip commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Now I have a complete picture. Let me compile the review.

Code Review: feat/database-orm-integration

🔴 Major Issues

Status Category Description Location Suggestion
🔴 Bug init() seals the pipeline at L109 BEFORE hooks complete at L110. If a hook rejects, pipeline is permanently sealed but initialized stays false. fetch() retries init(), hooks re-run (potentially harmful if partially completed), and seal() must be idempotent to not crash rune-app.ts:108-110 Move pipeline.seal() after await Promise.all(...), or wrap in try/catch and unseal on failure. Add a test verifying behavior when onInit throws
🔴 Design OnAppInit/OnAppDestroy interfaces in lifecycle.ts are implemented by both adapters but never auto-discovered or wired by RuneApp.init()/destroy(). Users must manually call app.onInit(() => adapter.onAppInit()) — the interfaces provide zero integration value as-is database/core/src/lifecycle.ts:1-7, rune-app.ts:107-128 Either (a) implement auto-discovery in init() that scans registered providers implementing OnAppInit/OnAppDestroy, or (b) document that these are manual-only and remove the expectation of automatic wiring
🔴 Dead Code DatabaseModule.forRoot() returns exports: [DATABASE_MODULE_ADAPTER] but ModuleLoader never processes module exports. The token can never be dependency-injected. No example uses DatabaseModule — all bypass it with container.register() database/core/src/database-module.ts:22-30 Either implement export resolution in ModuleLoader, or if the module is dead code, remove it entirely to avoid misleading consumers

⚠️ Concerns

Status Category Description Location Suggestion
⚠️ Performance discoverPackages() calls results.sort() inside each recursive frame — O(n log n * depth) instead of O(n log n) once bundle-size.ts:113 Hoist .sort() to the top-level call after the recursive exploration completes
⚠️ Maintainability Blog-api wraps AuthMiddleware.use in an extra closure: (ctx, next) => authMiddleware.use(ctx, next) — the method already returns Promise<Response|void> which is the exact middleware signature blog-api/index.ts:344-346 Register directly: app.use(authMiddleware.use.bind(authMiddleware)), or make AuthMiddleware implement the Middleware type
⚠️ Robustness DrizzleAdapter casts $client from unknown to DriverClient — a misspelled method like endd becomes a silent no-op at runtime with zero type errors drizzle-adapter.ts:23,30 Use in operator guards ("connect" in driver) or hasOwnProperty to avoid silent failures
⚠️ Edge Case destroy() has no idempotency guard — unlike init(), calling destroy() multiple times runs all hooks multiple times rune-app.ts:125-128 Add if (!this.initialized) return; guard at the start of destroy()

💡 Observations

Status Category Description Location
💡 Testing All 12 mock DrizzleAdapter test objects use as any — while functional, a helper factory returning AnyDrizzleDB would improve readability drizzle/tests/drizzle-adapter.test.ts:6,16,24,32,41,51,60,66,72,78,86,95
💡 Schema Auth-service defines the same schema in both raw SQL (index.ts:201-218) and Prisma schema (prisma/schema.prisma) — two sources of truth auth-service/index.ts:201-218, auth-service/prisma/schema.prisma
💡 Lifecycle Blog-api registers dbAdapter.connect()/disconnect() in app.onInit/onDestroy and DrizzleAdapter implements OnAppInit/OnAppDestroy — the adapter is ready for auto-wiring but must be manually connected blog-api/index.ts:335-341
💡 Inconsistency Blog-api AuthMiddleware uses return new Response(...) pattern while auth-service middleware uses ctx.send(...) — diverging patterns in the same PR blog-api/index.ts:139, auth-service/index.ts:228
💡 CI consistency bun-version is 1.3.14 in CI, package.json, and AGENTS.md update — all three are aligned, contrary to earlier review assertions .github/workflows/ci.yml:23, package.json:72

✅ Good Parts

Status Category Description Location
Design DatabaseAdapter is a minimal 2-method interface — clean, driver-agnostic, easy to implement database/core/src/adapter.ts:1-4
Type safety AnyPrismaClient uses strict $connect/$disconnect bounds — no any leakage prisma-adapter.ts:3-6
Testing Thorough adapter tests: connect, disconnect, no-ops, lifecycle hooks, error propagation, end>close preference drizzle/tests/drizzle-adapter.test.ts, prisma/tests/prisma-adapter.test.ts
Testing RuneApp lifecycle tests cover idempotent init, destroy hooks, re-init after destroy, error propagation rune-app.test.ts:180-214
Backward compat Umbrella @rune/database re-exports all sub-packages preserving all existing import paths database/src/index.ts:1-7
Dead code isSerializableObject() correctly removed core/src/json-serializer.ts:37-43
Peer deps drizzle-orm, @prisma/client set as optional peer dependencies — no mandatory transitive deps drizzle/package.json:24-31, prisma/package.json:24-31
Maintainability destroy() correctly resets initialized = false, enabling fetch() to re-initialize rune-app.ts:127
Documentation end() > close() precedence clearly documented in comment at drizzle-adapter.ts:32-33 drizzle-adapter.ts:32-33

📝 Test Coverage Gaps

Status Category Description Suggestion
📝 Coverage No test for init() hook rejection — verify initialized flag and pipeline state after partial failure Add test: register a hook that throws, call init(), verify error propagates, verify fetch() behavior
📝 Coverage No test for destroy() idempotency — calling destroy twice should not double-execute hooks Add test calling destroy() twice and mock-hook call count = 1
📝 Coverage No test for DrizzleAdapter when driver.connect() throws Add test similar to PrismaAdapter's error propagation test

Final Verdict

🔴 Changes requested — The package split is well-structured with clean interfaces, thorough adapter tests, and full backward compatibility. Three issues need attention: (1) init() seals the pipeline before hooks complete, leaving the app in a half-sealed state if hooks reject; (2) OnAppInit/OnAppDestroy interfaces provide no actual integration — they're implemented but never auto-wired; (3) DatabaseModule.forRoot() is effectively dead code since ModuleLoader ignores exports. Address these and it's good to merge.

New%20session%20-%202026-07-09T06%3A35%3A57.057Z
opencode session  |  github run

@rjoydip rjoydip merged commit 8385bc2 into main Jul 9, 2026
8 of 9 checks passed
@rjoydip rjoydip deleted the feat/database-orm-integration branch July 9, 2026 08:18
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.

2 participants