Skip to content

feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007]#151

Merged
vertz-dev-dx[bot] merged 2 commits intofeat/db-v1from
feat/db-v1-phase-2-errors-connection
Feb 11, 2026
Merged

feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007]#151
vertz-dev-dx[bot] merged 2 commits intofeat/db-v1from
feat/db-v1-phase-2-errors-connection

Conversation

@vertz-dev-core
Copy link
Copy Markdown
Contributor

Summary

Phase 2 of @vertz/db: Error Hierarchy + Connection Management.

Tickets: DB-006, DB-007

DB-006: Typed Error Hierarchy + PG Error Parser

  • Abstract DbError base + 7 subclasses (UniqueConstraint, ForeignKey, NotNull, CheckConstraint, NotFound, Connection, PoolExhausted)
  • PostgreSQL error code parser — maps PG codes to typed errors with extracted metadata
  • dbErrorToHttpError() adapter — maps DB errors to HTTP status codes
  • 42 tests

DB-007: createDb() + Tenant Graph

  • createDb() factory returning typed Database<TTables>
  • Tenant graph computation — classifies tables as root, directlyScoped, indirectlyScoped, shared
  • Startup notices for tables missing tenant paths
  • db.close() and db.isHealthy() stubs
  • 16 tests

Quality gates

  • 116 total tests passing
  • TypeScript strict typecheck clean
  • Biome lint + format clean

Test plan

  • All DbError subclasses extend DbError base
  • toJSON() produces structured output
  • PG error codes map to correct error types
  • dbErrorToHttpError() maps all types to HTTP status
  • createDb() returns typed Database instance
  • Tenant graph classifies tables correctly
  • Startup notices for unscoped tables

vertz-dev-core[bot] and others added 2 commits February 11, 2026 02:36
- Abstract DbError base with code, name, query, table, toJSON()
- UniqueConstraintError (23505), ForeignKeyError (23503),
  NotNullError (23502), CheckConstraintError (23514)
- NotFoundError, ConnectionError, ConnectionPoolExhaustedError
- PG error code parser with column/constraint/detail extraction
- dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings
- 42 new tests covering all error types, parser, and HTTP adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- createDb() factory accepts URL, pool config, table registry, casing, log
- Returns typed DatabaseInstance<TTables> with $tenantGraph
- Tenant graph computed at createDb() time from d.tenant() metadata
- Traverses .references() chains for indirect tenant scoping
- Classifies tables as root, directlyScoped, indirectlyScoped, shared
- Logs notices for tables without tenant path and not .shared()
- db.close() and db.isHealthy() stub implementations
- 16 new tests for tenant graph and database factory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@vertz-dev-dx vertz-dev-dx Bot left a comment

Choose a reason for hiding this comment

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

Adversarial Review — PR #151: Phase 2 error hierarchy + connection management (DB-006, DB-007)

Verdict: Approve

Solid work. Clean abstractions, good test coverage, no @ts-ignore or as any. A few findings below — one medium concern and several nits/suggestions. Nothing blocking.


DB-006 Acceptance Criteria Verification

Criterion Status
All DbError subclasses extend DbError abstract base PassUniqueConstraintError, ForeignKeyError, NotNullError, CheckConstraintError, NotFoundError, ConnectionError, ConnectionPoolExhaustedError all extend DbError. Verified via tests with toBeInstanceOf(DbError).
toJSON() produces { error, code, message, table?, column? } Pass — Base class toJSON() returns the shape. Subclasses override to include table and column where appropriate.
PG error code 23505 maps to UniqueConstraintError with extracted column name PassparsePgError handles 23505, extracts column from detail via regex when column field not present.
PG error code 23503 maps to ForeignKeyError Pass — with constraint name extracted.
PG error code 23502 maps to NotNullError Pass — with column name extracted from PG error message when column not provided.
Error messages include table name and column name Pass — all constraint error messages interpolate table and column/constraint.
dbErrorToHttpError() maps all error types to correct HTTP status codes Pass — Unique→409, FK/NotNull/Check→422, NotFound→404, Connection→503, Unknown→500.

DB-007 Acceptance Criteria Verification

Criterion Status
createDb() returns a typed Database<TTables> instance Pass — returns DatabaseInstance<TTables> with generic preservation.
Tenant graph correctly identifies root, directly scoped, indirectly scoped, and shared tables Pass — Multi-hop indirect resolution via iterative while (changed) loop works correctly. Tests cover 2-hop chains (tasks→projects→organizations).
Startup notice logged for tables without tenant path and not .shared() Passaudit_logs correctly triggers log notice.
db.close() and db.isHealthy() exist with correct types Pass — Stubbed implementations return Promise<void> and Promise<boolean> respectively.

Findings

[Medium] ConnectionPoolExhaustedError — fragile name reset (db-error.ts:217)

constructor(poolSize: number) {
    super(`Connection pool exhausted (max: ${poolSize})`);
    // Reset name since ConnectionError constructor sets it to ConnectionError
    this.name = 'ConnectionPoolExhaustedError';
}

The base class DbError uses new.target.name to set this.name, which should correctly resolve to 'ConnectionPoolExhaustedError' when constructed directly. The problem is that ConnectionError's constructor calls super(message) on DbError, and at that point new.target is ConnectionPoolExhaustedError, so DbError's constructor sets this.name = 'ConnectionPoolExhaustedError'. Then ConnectionError's constructor body runs — but it doesn't reassign this.name. So the manual reset in ConnectionPoolExhaustedError is actually redundant. It works correctly, but the comment is misleading — it claims ConnectionError's constructor sets name to ConnectionError, which it does not (it's DbError's constructor that sets name via new.target.name).

Not a bug, but the misleading comment should be cleaned up to avoid confusion in future maintenance.

[Low] Mutable cast in DbError.toJSON() (db-error.ts:30)

(json as { table: string }).table = this.table;

The DbErrorJson interface has readonly fields, but toJSON() casts to a mutable type to set table. This works, but it's a pattern that could be cleaner — constructing the full object conditionally instead:

return {
    error: this.name,
    code: this.code,
    message: this.message,
    ...(this.table !== undefined && { table: this.table }),
};

Not blocking — the current approach is correct.

[Low] UnknownDbError not exported (pg-parser.ts:59)

UnknownDbError is a concrete subclass of DbError defined only in pg-parser.ts but not exported. This means consumers cannot instanceof check against it. The http-adapter test uses require() to get parsePgError and create an unknown error for the 500 status test — this is fine, but if consumers ever need to distinguish unknown PG errors from known ones, they would have no way to do so. Consider exporting it or providing a type guard.

[Low] extractKeyDetail regex does not handle composite keys (pg-parser.ts:30)

const match = detail.match(/^Key \(([^)]+)\)=\(([^)]*)\)/);

PG composite unique constraints produce detail strings like Key (col_a, col_b)=(val_a, val_b) already exists. This regex would capture col_a, col_b as the column name and val_a, val_b as the value — both as a single string. Not necessarily wrong (it still extracts something useful), but worth a note or test for this edge case. The UniqueConstraintError column field would contain a comma-separated string rather than a clean column name.

[Nit] http-adapter test uses require() (http-adapter.test.ts:84)

const { parsePgError } = require('../pg-parser');

This is the only require() in the test suite. Presumably done to avoid a circular type issue or to keep the import out of the top-level. Works fine, but it breaks ESM consistency. Consider using dynamic import() instead.

[Nit] Database vs DatabaseInstance naming

The schema/inference.ts already exports a Database<TTables> interface with readonly _tables: TTables. The new code introduces DatabaseInstance<TTables> which has _tables, $tenantGraph, close(), and isHealthy(). These are conceptually the same thing at different phases of implementation. It would be worth a TODO comment noting that DatabaseInstance should eventually replace or extend the Database type from inference.ts to avoid confusion.


Test Coverage Assessment

Tests are comprehensive. Good coverage of:

  • All error class constructors, properties, and toJSON() shapes
  • PG parser extraction from detail, message, and constraint fields
  • Fallback behavior for missing fields
  • HTTP adapter mapping for all error types + unknown fallback
  • Tenant graph: root, directly scoped, indirectly scoped, shared, multi-hop, null root
  • createDb: table passthrough, graph computation, logging, pool/casing options
  • close() and isHealthy() stub behavior

Missing (not blocking):

  • No test for Error.captureStackTrace or stack trace correctness
  • No test for ConnectionError.toJSON() confirming table field is absent (it inherits base toJSON() which skips table when undefined — implicitly covered)
  • PGlite integration tests mentioned in DB-007 are not present (expected for stub phase)

LGTM. Clean, well-tested, meets acceptance criteria. The stub approach for pool management is appropriate for this phase.

-- ava (vertz-dev-dx)

@vertz-dev-dx vertz-dev-dx Bot merged commit fee99d9 into feat/db-v1 Feb 11, 2026
3 checks passed
@vertz-dev-dx vertz-dev-dx Bot deleted the feat/db-v1-phase-2-errors-connection branch February 11, 2026 02:43
@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Core (packages/core)

Status Category Percentage Covered / Total
🔵 Lines 82.21% 763 / 928
🔵 Statements 82.21% 763 / 928
🔵 Functions 97.77% 88 / 90
🔵 Branches 95.34% 246 / 258
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/core/src/internals.ts 0% 100% 100% 0% 4-9
packages/core/src/vertz.ts 100% 100% 100% 100%
packages/core/src/app/app-builder.ts 100% 85.71% 100% 100%
packages/core/src/app/app-runner.ts 100% 94.73% 100% 100%
packages/core/src/app/bun-adapter.ts 5.55% 100% 0% 5.55% 12-29
packages/core/src/app/detect-adapter.ts 81.81% 75% 100% 81.81% 16-17
packages/core/src/app/index.ts 100% 100% 100% 100%
packages/core/src/app/__tests__/middleware-ctx-inference.test-d.ts 0% 100% 100% 0% 9-115
packages/core/src/context/ctx-builder.ts 100% 100% 100% 100%
packages/core/src/context/deps-builder.ts 100% 100% 100% 100%
packages/core/src/context/index.ts 0% 100% 100% 0% 2-4
packages/core/src/di/boot-executor.ts 100% 100% 100% 100%
packages/core/src/di/index.ts 0% 0% 0% 0% 1
packages/core/src/env/env-validator.ts 100% 100% 100% 100%
packages/core/src/env/index.ts 100% 100% 100% 100%
packages/core/src/exceptions/http-exceptions.ts 100% 100% 100% 100%
packages/core/src/exceptions/index.ts 100% 100% 100% 100%
packages/core/src/exceptions/vertz-exception.ts 100% 100% 100% 100%
packages/core/src/immutability/dev-proxy.ts 100% 100% 100% 100%
packages/core/src/immutability/freeze.ts 100% 100% 100% 100%
packages/core/src/immutability/index.ts 100% 100% 100% 100%
packages/core/src/immutability/make-immutable.ts 100% 100% 100% 100%
packages/core/src/middleware/index.ts 100% 100% 100% 100%
packages/core/src/middleware/middleware-def.ts 100% 100% 100% 100%
packages/core/src/middleware/middleware-runner.ts 100% 100% 100% 100%
packages/core/src/module/index.ts 100% 100% 100% 100%
packages/core/src/module/module-def.ts 100% 100% 100% 100%
packages/core/src/module/module.ts 100% 100% 100% 100%
packages/core/src/module/router-def.ts 100% 100% 100% 100%
packages/core/src/module/service.ts 100% 100% 100% 100%
packages/core/src/module/__tests__/router-type-inference.test-d.ts 0% 100% 100% 0% 8-112
packages/core/src/router/index.ts 0% 100% 100% 0% 2
packages/core/src/router/trie.ts 97.16% 92% 100% 97.16% 91-93
packages/core/src/server/cors.ts 87.27% 87.5% 100% 87.27% 12-16, 61-62
packages/core/src/server/index.ts 0% 100% 100% 0% 2-5
packages/core/src/server/request-utils.ts 100% 100% 100% 100%
packages/core/src/server/response-utils.ts 100% 100% 100% 100%
packages/core/src/types/app.ts 100% 100% 100% 100%
packages/core/src/types/boot-sequence.ts 100% 100% 100% 100%
packages/core/src/types/context.ts 100% 100% 100% 100%
packages/core/src/types/deep-readonly.ts 100% 100% 100% 100%
packages/core/src/types/env.ts 100% 100% 100% 100%
packages/core/src/types/http.ts 100% 100% 100% 100%
packages/core/src/types/index.ts 100% 100% 100% 100%
packages/core/src/types/middleware.ts 100% 100% 100% 100%
packages/core/src/types/module.ts 100% 100% 100% 100%
packages/core/src/types/schema-infer.ts 100% 100% 100% 100%
packages/core/src/types/server-adapter.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Schema (packages/schema)

Status Category Percentage Covered / Total
🔵 Lines 91.48% 2116 / 2313
🔵 Statements 91.48% 2116 / 2313
🔵 Functions 76.8% 288 / 375
🔵 Branches 95.54% 622 / 651
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/schema/src/core/errors.ts 100% 100% 100% 100%
packages/schema/src/core/parse-context.ts 100% 100% 100% 100%
packages/schema/src/core/registry.ts 96% 87.5% 100% 96% 20
packages/schema/src/core/schema.ts 84.35% 95.61% 68.96% 84.35% 57-61, 201-202, 214-215, 228-229, 245-246, 249-250, 262-263, 282-283, 323-324, 327-328, 331-337, 358-359, 362-363, 366-367, 395-396, 399-400, 403-404, 424-425, 428-429, 432-433, 456-457, 460-461, 468-469, 485-486, 493-494, 515-516, 519-520, 523-524
packages/schema/src/core/types.ts 100% 100% 100% 100%
packages/schema/src/introspection/json-schema.ts 100% 100% 100% 100%
packages/schema/src/schemas/array.ts 94.11% 95.83% 88.88% 94.11% 72-73, 83-85
packages/schema/src/schemas/bigint.ts 83.33% 100% 50% 83.33% 20-21, 28-29
packages/schema/src/schemas/boolean.ts 83.33% 100% 50% 83.33% 20-21, 28-29
packages/schema/src/schemas/coerced.ts 88.67% 83.33% 70% 88.67% 35-36, 51-52, 62-63
packages/schema/src/schemas/custom.ts 77.77% 100% 50% 77.77% 25-26, 29-30, 33-34
packages/schema/src/schemas/date.ts 96.72% 88.88% 85.71% 96.72% 55-56
packages/schema/src/schemas/discriminated-union.ts 94.28% 90.47% 71.42% 94.28% 82-83, 93-94
packages/schema/src/schemas/enum.ts 89.74% 100% 75% 89.74% 26-27, 47-48
packages/schema/src/schemas/file.ts 71.42% 100% 25% 71.42% 17-18, 21-22, 25-26
packages/schema/src/schemas/instanceof.ts 79.31% 100% 50% 79.31% 30-31, 34-35, 38-39
packages/schema/src/schemas/intersection.ts 89.36% 88.88% 66.66% 89.36% 40, 44-45, 57-58
packages/schema/src/schemas/lazy.ts 85.71% 100% 71.42% 85.71% 27-28, 35-36
packages/schema/src/schemas/literal.ts 87.09% 100% 71.42% 87.09% 32-33, 40-41
packages/schema/src/schemas/map.ts 92.15% 100% 66.66% 92.15% 39-40, 57-58
packages/schema/src/schemas/nan.ts 80.95% 100% 50% 80.95% 17-18, 25-26
packages/schema/src/schemas/number.ts 98.87% 98.68% 94.73% 98.87% 166-167
packages/schema/src/schemas/object.ts 98.76% 98.21% 94.73% 98.76% 172-173
packages/schema/src/schemas/record.ts 87.71% 87.5% 71.42% 87.71% 55-56, 66-70
packages/schema/src/schemas/set.ts 97.72% 100% 88.88% 97.72% 74-75
packages/schema/src/schemas/special.ts 77.57% 100% 50% 77.57% 15-16, 23-24, 33-34, 41-42, 58-59, 66-67, 83-84, 91-92, 106-107, 114-115, 125-126, 133-134
packages/schema/src/schemas/string.ts 98.94% 100% 94.44% 98.94% 179-180
packages/schema/src/schemas/symbol.ts 83.33% 100% 50% 83.33% 20-21, 28-29
packages/schema/src/schemas/tuple.ts 89.23% 95.45% 85.71% 89.23% 36-40, 64-65
packages/schema/src/schemas/union.ts 88.23% 100% 66.66% 88.23% 37-38, 47-48
packages/schema/src/schemas/formats/base64.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/cuid.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/email.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/format-schema.ts 91.66% 88.88% 75% 91.66% 12-13
packages/schema/src/schemas/formats/hex.ts 81.81% 100% 66.66% 81.81% 13-14
packages/schema/src/schemas/formats/hostname.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/index.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/ipv4.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/ipv6.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/iso.ts 100% 76.47% 100% 100%
packages/schema/src/schemas/formats/jwt.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/nanoid.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/ulid.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/url.ts 100% 100% 100% 100%
packages/schema/src/schemas/formats/uuid.ts 100% 100% 100% 100%
packages/schema/src/transforms/preprocess.ts 83.33% 85.71% 57.14% 83.33% 32-33, 36-37, 40-41
packages/schema/src/utils/type-inference.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Compiler (packages/compiler)

Status Category Percentage Covered / Total
🔵 Lines 97.92% 3163 / 3230
🔵 Statements 97.92% 3163 / 3230
🔵 Functions 97.9% 187 / 191
🔵 Branches 89.59% 964 / 1076
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/compiler/src/compiler.ts 100% 100% 100% 100%
packages/compiler/src/config.ts 100% 96.72% 100% 100%
packages/compiler/src/errors.ts 100% 100% 100% 100%
packages/compiler/src/incremental.ts 100% 97.95% 100% 100%
packages/compiler/src/typecheck.ts 87.64% 76.47% 100% 87.64% 57-63, 86-87, 109-110
packages/compiler/src/__tests__/codegen-poc/spike.ts 96.18% 88.37% 100% 96.18% 97-98, 132-133, 139-140, 153-156
packages/compiler/src/analyzers/app-analyzer.ts 99.35% 81.81% 100% 99.35% 189
packages/compiler/src/analyzers/base-analyzer.ts 100% 100% 100% 100%
packages/compiler/src/analyzers/dependency-graph-analyzer.ts 99.29% 90.36% 91.66% 99.29% 25-26
packages/compiler/src/analyzers/env-analyzer.ts 100% 92.3% 100% 100%
packages/compiler/src/analyzers/middleware-analyzer.ts 99.06% 95.45% 100% 99.06% 129
packages/compiler/src/analyzers/module-analyzer.ts 100% 74.07% 100% 100%
packages/compiler/src/analyzers/route-analyzer.ts 98.65% 87.5% 91.66% 98.65% 40-41, 181, 301
packages/compiler/src/analyzers/schema-analyzer.ts 95.95% 76.19% 100% 95.95% 88-89, 118-119
packages/compiler/src/analyzers/service-analyzer.ts 92.92% 63.88% 87.5% 92.92% 20-21, 81, 119-120, 132-133
packages/compiler/src/generators/base-generator.ts 100% 100% 100% 100%
packages/compiler/src/generators/boot-generator.ts 100% 95% 100% 100%
packages/compiler/src/generators/index.ts 0% 100% 100% 0% 2-46
packages/compiler/src/generators/manifest-generator.ts 100% 97.56% 100% 100%
packages/compiler/src/generators/openapi-generator.ts 95.47% 82.6% 100% 95.47% 143-144, 330-331, 335-336, 340-343
packages/compiler/src/generators/route-table-generator.ts 100% 100% 100% 100%
packages/compiler/src/generators/schema-registry-generator.ts 100% 100% 100% 100%
packages/compiler/src/ir/builder.ts 100% 100% 100% 100%
packages/compiler/src/ir/merge.ts 100% 100% 100% 100%
packages/compiler/src/ir/types.ts 100% 100% 100% 100%
packages/compiler/src/utils/ast-helpers.ts 100% 100% 100% 100%
packages/compiler/src/utils/import-resolver.ts 91.07% 74.07% 100% 91.07% 34, 47-48, 77-78
packages/compiler/src/utils/schema-executor.ts 100% 90.9% 100% 100%
packages/compiler/src/validators/completeness-validator.ts 99.43% 94.05% 100% 99.43% 403-404
packages/compiler/src/validators/index.ts 0% 0% 0% 0% 1-5
packages/compiler/src/validators/module-validator.ts 100% 100% 100% 100%
packages/compiler/src/validators/naming-validator.ts 100% 94.11% 100% 100%
packages/compiler/src/validators/placement-validator.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Codegen (packages/codegen)

Status Category Percentage Covered / Total
🔵 Lines 98.73% 1169 / 1184
🔵 Statements 98.73% 1169 / 1184
🔵 Functions 100% 60 / 60
🔵 Branches 94.89% 353 / 372
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/codegen/src/config.ts 91.48% 90.9% 100% 91.48% 117-118, 120-121
packages/codegen/src/format.ts 96.55% 75% 100% 96.55% 54-55, 107
packages/codegen/src/generate.ts 100% 92.3% 100% 100%
packages/codegen/src/hasher.ts 100% 100% 100% 100%
packages/codegen/src/incremental.ts 96.72% 94.11% 100% 96.72% 35-36
packages/codegen/src/ir-adapter.ts 100% 94.28% 100% 100%
packages/codegen/src/json-schema-converter.ts 98.03% 94.02% 100% 98.03% 139-140
packages/codegen/src/pipeline.ts 100% 100% 100% 100%
packages/codegen/src/types.ts 100% 100% 100% 100%
packages/codegen/src/generators/typescript/emit-cli.ts 100% 97.36% 100% 100%
packages/codegen/src/generators/typescript/emit-client.ts 100% 97.87% 100% 100%
packages/codegen/src/generators/typescript/emit-sdk.ts 100% 100% 100% 100%
packages/codegen/src/generators/typescript/emit-types.ts 97.56% 95.12% 100% 97.56% 69-70, 91-92
packages/codegen/src/utils/imports.ts 100% 100% 100% 100%
packages/codegen/src/utils/naming.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for CLI (packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 94.03% 347 / 369
🔵 Statements 93.54% 362 / 387
🔵 Functions 87% 87 / 100
🔵 Branches 77.62% 170 / 219
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/cli/src/cli.ts 100% 100% 100% 100%
packages/cli/src/commands/build.ts 95.65% 85.71% 66.66% 100% 27
packages/cli/src/commands/check.ts 100% 100% 100% 100%
packages/cli/src/commands/codegen.ts 100% 84.61% 100% 100%
packages/cli/src/commands/deploy.ts 100% 100% 100% 100%
packages/cli/src/commands/generate.ts 100% 100% 100% 100%
packages/cli/src/commands/routes.ts 94.28% 71.42% 90.9% 92.85% 47, 57
packages/cli/src/config/defaults.ts 100% 100% 100% 100%
packages/cli/src/config/loader.ts 45.83% 15.38% 66.66% 45.83% 19-40, 58-68
packages/cli/src/deploy/detector.ts 100% 100% 100% 100%
packages/cli/src/deploy/dockerfile.ts 100% 100% 100% 100%
packages/cli/src/deploy/fly.ts 100% 100% 100% 100%
packages/cli/src/deploy/railway.ts 100% 100% 100% 100%
packages/cli/src/dev-server/dev-loop.ts 100% 100% 100% 100%
packages/cli/src/dev-server/process-manager.ts 91.42% 66.66% 92.3% 94.11% 45, 50-51
packages/cli/src/dev-server/watcher.ts 95.65% 87.5% 100% 100% 27
packages/cli/src/generators/module.ts 100% 100% 100% 100%
packages/cli/src/generators/naming.ts 100% 100% 100% 100%
packages/cli/src/generators/router.ts 100% 100% 100% 100%
packages/cli/src/generators/schema.ts 100% 100% 100% 100%
packages/cli/src/generators/service.ts 100% 100% 100% 100%
packages/cli/src/ui/diagnostic-formatter.ts 100% 76.31% 100% 100%
packages/cli/src/ui/task-runner.ts 55.55% 100% 25% 55.55% 39-48
packages/cli/src/ui/theme.ts 100% 100% 100% 100%
packages/cli/src/ui/components/Banner.tsx 100% 100% 100% 100%
packages/cli/src/ui/components/DiagnosticDisplay.tsx 100% 81.25% 100% 100%
packages/cli/src/ui/components/DiagnosticSummary.tsx 100% 100% 100% 100%
packages/cli/src/ui/components/Message.tsx 100% 100% 100% 100%
packages/cli/src/ui/components/SelectList.tsx 100% 100% 100% 100%
packages/cli/src/ui/components/Task.tsx 100% 100% 100% 100%
packages/cli/src/ui/components/TaskList.tsx 100% 100% 100% 100%
packages/cli/src/utils/format.ts 100% 91.66% 100% 100%
packages/cli/src/utils/paths.ts 100% 83.33% 100% 100%
packages/cli/src/utils/prompt.ts 100% 100% 100% 100%
packages/cli/src/utils/runtime-detect.ts 66.66% 75% 100% 66.66% 5
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for CLI Runtime (packages/cli-runtime)

Status Category Percentage Covered / Total
🔵 Lines 84.21% 491 / 583
🔵 Statements 84.21% 491 / 583
🔵 Functions 80% 28 / 35
🔵 Branches 82.82% 135 / 163
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/cli-runtime/src/args.ts 100% 95.23% 100% 100%
packages/cli-runtime/src/auth.ts 87.03% 93.54% 77.77% 87.03% 65-66, 69, 72, 179-184, 245-256
packages/cli-runtime/src/cli.ts 51.81% 58.33% 50% 51.81% 48-50, 79-86, 92-126, 131, 134-139, 144-150
packages/cli-runtime/src/help.ts 100% 93.75% 100% 100%
packages/cli-runtime/src/output.ts 92.77% 75.6% 100% 92.77% 16-17, 37-38, 68, 93
packages/cli-runtime/src/resolver.ts 87.36% 86.2% 80% 87.36% 17-24, 27-28, 125-126
packages/cli-runtime/src/types.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Fetch (packages/fetch)

Status Category Percentage Covered / Total
🔵 Lines 97.03% 327 / 337
🔵 Statements 97.03% 327 / 337
🔵 Functions 100% 28 / 28
🔵 Branches 90.29% 121 / 134
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/fetch/src/client.ts 95.96% 88.98% 100% 95.96% 91, 101-104, 135-136, 185-186, 223
packages/fetch/src/errors.ts 100% 100% 100% 100%
packages/fetch/src/types.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Testing (packages/testing)

Status Category Percentage Covered / Total
🔵 Lines 97.15% 239 / 246
🔵 Statements 97.15% 239 / 246
🔵 Functions 100% 19 / 19
🔵 Branches 89.65% 52 / 58
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Unchanged Files
packages/testing/src/test-app.ts 96.74% 87.5% 100% 96.74% 160-165, 233
packages/testing/src/test-service.ts 100% 100% 100% 100%
packages/testing/src/types.ts 100% 100% 100% 100%
Generated in workflow #198 for commit 6cedd0b by the Vitest Coverage Report Action

vertz-tech-lead Bot pushed a commit that referenced this pull request Feb 11, 2026
…DB-007] (#151)

* feat(db): add typed error hierarchy and PG error parser [DB-006]

- Abstract DbError base with code, name, query, table, toJSON()
- UniqueConstraintError (23505), ForeignKeyError (23503),
  NotNullError (23502), CheckConstraintError (23514)
- NotFoundError, ConnectionError, ConnectionPoolExhaustedError
- PG error code parser with column/constraint/detail extraction
- dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings
- 42 new tests covering all error types, parser, and HTTP adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add createDb(), tenant graph computation [DB-007]

- createDb() factory accepts URL, pool config, table registry, casing, log
- Returns typed DatabaseInstance<TTables> with $tenantGraph
- Tenant graph computed at createDb() time from d.tenant() metadata
- Traverses .references() chains for indirect tenant scoping
- Classifies tables as root, directlyScoped, indirectlyScoped, shared
- Logs notices for tables without tenant path and not .shared()
- db.close() and db.isHealthy() stub implementations
- 16 new tests for tenant graph and database factory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
viniciusdacal pushed a commit that referenced this pull request Feb 11, 2026
* feat(db): add type inference engine (FindResult, filters, includes) [DB-005]

Implement the type inference layer that powers all query type safety:

- FilterType<TColumns>: typed where filters with eq/ne/gt/gte/lt/lte/in/notIn
  operators, string-specific contains/startsWith/endsWith, nullable isNull,
  and direct value shorthand
- OrderByType<TColumns>: constrained to column names with 'asc' | 'desc'
- SelectOption<TColumns>: mutually exclusive not:'sensitive'|'hidden' vs
  explicit field selection, enforced via never mapped keys
- SelectNarrow<TColumns, TSelect>: narrows result based on select clause
- IncludeResolve<TRelations, TInclude>: resolves relation includes with
  depth cap at 2, supports select sub-clauses for narrowing included relations
- FindResult<TTable, TOptions, TRelations>: combines select + include
- InsertInput/UpdateInput: standalone type utilities delegating to $insert/$update
- Database<TTables>/TableEntry: registry types for typed query methods
- Comprehensive type-level tests with @ts-expect-error negative cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007] (#151)

* feat(db): add typed error hierarchy and PG error parser [DB-006]

- Abstract DbError base with code, name, query, table, toJSON()
- UniqueConstraintError (23505), ForeignKeyError (23503),
  NotNullError (23502), CheckConstraintError (23514)
- NotFoundError, ConnectionError, ConnectionPoolExhaustedError
- PG error code parser with column/constraint/detail extraction
- dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings
- 42 new tests covering all error types, parser, and HTTP adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add createDb(), tenant graph computation [DB-007]

- createDb() factory accepts URL, pool config, table registry, casing, log
- Returns typed DatabaseInstance<TTables> with $tenantGraph
- Tenant graph computed at createDb() time from d.tenant() metadata
- Traverses .references() chains for indirect tenant scoping
- Classifies tables as root, directlyScoped, indirectlyScoped, shared
- Logs notices for tables without tenant path and not .shared()
- db.close() and db.isHealthy() stub implementations
- 16 new tests for tenant graph and database factory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 3 — SQL Generator (db-008, db-009) (#152)

* feat(db): add SQL statement builders (SELECT, INSERT, UPDATE, DELETE) [DB-008]

Implements Phase 3 SQL generator builders:
- SELECT builder: column selection with camelCase->snake_case aliasing,
  WHERE, ORDER BY, LIMIT/OFFSET, COUNT(*) OVER() for findManyAndCount
- INSERT builder: single row, batch (multi-row VALUES), RETURNING,
  ON CONFLICT (upsert with DO NOTHING / DO UPDATE SET)
- UPDATE builder: SET clause from data object, WHERE, RETURNING
- DELETE builder: WHERE, RETURNING
- WHERE builder: all 13+ filter operators (eq, ne, gt, gte, lt, lte,
  contains, startsWith, endsWith, in, notIn, isNull, isNotNull),
  nested OR/AND/NOT, JSONB operators (->/->>), array operators (@>, <@, &&)
- Casing module: camelToSnake / snakeToCamel conversion
- All values parameterized ($1, $2, ...) for SQL injection prevention
- Handles 'now' sentinel for timestamp columns via NOW()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add sql tagged template and escape hatch [DB-009]

Implements the sql tagged template literal for composable raw SQL:
- sql`...${value}...` auto-parameterizes values as $1, $2, ...
- sql.raw() inserts trusted SQL strings without parameterization
- Nested sql`` fragments compose with automatic param renumbering
- CTE (WITH ... AS) syntax works through tagged template composition
- SqlFragment type with _tag discriminant for runtime identification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address 7 blocking review issues on Phase 3 SQL generator [DB-008, DB-009]

1. Empty IN/NOT IN arrays now produce FALSE/TRUE instead of invalid SQL
2. LIKE values escape %, _, and \ metacharacters before wrapping
3. Empty OR/AND arrays produce FALSE/TRUE (standard SQL logic)
4. JSONB path segments sanitize single quotes to prevent injection
5. LIMIT/OFFSET values are parameterized ($N) instead of inlined
6. Added PGlite integration test (DB-008 acceptance criterion)
7. Added db.query<T>() stub to DatabaseInstance (DB-009 acceptance criterion)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 4 — Query Builder + Relations + Aggregation (db-010/011/012) (#156)

* feat(db): add CRUD query methods [DB-010]

Implements typed find, create, update, upsert, and delete methods on
the Database instance. All methods use the Phase 3 SQL builders,
parameterized queries, and snake_case -> camelCase result mapping.

- findOne, findOneOrThrow, findMany, findManyAndCount
- create, createMany, createManyAndReturn
- update, updateMany
- upsert (INSERT ON CONFLICT with explicit update values)
- deleteOne, deleteMany
- Query executor with PG error mapping
- Row mapper with snakeToCamel conversion
- Helper utilities for column resolution

Includes comprehensive PGlite integration tests covering all
acceptance criteria: full CRUD cycle, pagination, select narrowing,
UniqueConstraintError, ForeignKeyError, NotFoundError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add relation loading with include option [DB-011]

Implements the include option for find queries using a batched
loading strategy (N+1 prevention via IN queries).

- belongsTo (one) relations: loads single object by FK
- hasMany (many) relations: loads array by parent PK
- Batched loading: single IN query per relation
- Select narrowing on included relations
- Nested includes supported up to depth 2
- Integration with findOne, findMany, findManyAndCount

PGlite integration tests cover belongsTo, hasMany, batched
loading, include with select, and findManyAndCount with include.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add aggregation queries (count, aggregate, groupBy) [DB-012]

Implements typed aggregation query methods:

- count(table, { where? }): returns row count as number
- aggregate(table, { _avg, _sum, _min, _max, _count, where? }):
  computes aggregation functions with typed results
- groupBy(table, { by, _count?, _avg?, orderBy? }):
  groups rows with per-group aggregations

All methods support where filters and generate parameterized SQL.
Results use camelCase keys consistent with the rest of the query layer.

PGlite integration tests cover count with filter, aggregate with
all functions, groupBy with ordering, multi-column grouping, and
combined aggregations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address PR #156 review — SQL injection, manyToMany, nested includes [db-010/011/012]

B1: Validate orderBy columns in groupBy aggregation to prevent SQL injection.
    Underscore-prefixed columns are now validated against requested aggregation
    aliases; direction values are restricted to 'asc'/'desc' only.

B2: Implement manyToMany relation loading via join tables (_through).
    Queries the join table first, collects target IDs, then batch-loads
    target rows and maps them back to parent rows.

B3: Implement recursive nested includes (depth-2).
    Pass tablesRegistry through loadRelations so target table relations
    can be resolved for nested include: { posts: { include: { comments: true } } }.

All fixes follow TDD: failing tests written first, then minimal implementation.

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 5 — Migration Differ + SQL Generator + Runner (db-013/014/015) (#157)

* feat(db): add JSON snapshot format for schema state capture [db-013]

* feat(db): add schema differ with rename detection and confidence scoring [db-013]

* feat(db): add SQL migration generator with rollback support [db-014]

* feat(db): add migration runner with history, drift detection, and ordering [db-015]

* feat(db): add file management, module exports, and integration tests [db-015]

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>

* feat(db): Phase 6 — CLI + Cache-Readiness Primitives (db-016, db-017) (#158)

* feat(db): Phase 6 — CLI commands + cache-readiness primitives (db-016/db-017)

- CLI: migrateDev, migrateDeploy, push, migrateStatus
- Dry-run mode for migrateDev
- Event bus for mutation events
- Deterministic query fingerprinting
- Plugin runner with beforeQuery/afterQuery hooks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address PR #158 review — status crash, snapshot return, exports

- B1: migrateStatus now calls createHistoryTable before querying applied
- B2: migrateDev returns snapshot in result for caller persistence
- B3: cli and plugin modules re-exported from main barrel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 7 — E2E acceptance test + type error quality (db-018/db-019) (#159)

- Full E2E test from design doc Section 7
- Type inference assertions with @ts-expect-error
- Branded error types for readable compiler messages
- @vertz/db/diagnostic export
- Performance validation under 100k instantiation budget

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* docs(db): add v1.0 retrospective and changeset

- Post-implementation review covering all 7 phases
- Changeset for @vertz/db minor release

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): add @types/node to resolve node:crypto in CI typecheck

The @vertz/db package imports from node:crypto (runner.ts, fingerprint.ts)
but was missing @types/node in devDependencies. This worked locally because
Bun hoists @types/node from sibling packages, but failed in CI where tsc
runs in an isolated Dagger container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): increase test/hook timeouts for PGlite in CI

PGlite uses WebAssembly which is slower in Dagger's container environment.
Tests were timing out at the default 5s/10s limits. Set testTimeout and
hookTimeout to 30s to accommodate CI's constrained resources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(db): update design doc to reflect implementation deviations

Comprehensive update to align the design doc with the actual v1.0
implementation. Documents all breaking changes, missing features,
and additions discovered during adversarial API diff review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): stabilize PGlite tests in CI

Disable file parallelism to prevent concurrent WASM instances from
crashing, add retry:2 as safety net, fix PGlite version constraint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): escape enum values and column defaults in DDL generation

Enum values and column default values were interpolated directly into
DDL strings without escaping single quotes. Malicious values like
'); DROP TABLE users;-- would produce injectable SQL.

Added escapeSqlString() helper that doubles internal single quotes,
applied to enum_added, enum_altered, and column default expressions.

Follow-up #22.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): guard deleteMany/updateMany against empty where clause

Passing an empty where object ({}) to deleteMany or updateMany would
generate SQL with no WHERE clause, silently affecting all rows. Added
a runtime check that throws if the where object has no keys.

Follow-up #19.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): preserve previous result when afterQuery plugin returns undefined

If a plugin's afterQuery hook returned undefined (e.g. a logging-only
plugin that observes but doesn't transform), the chain would pass
undefined to subsequent plugins, causing runtime errors.

Now defaults to the previous result when a plugin returns undefined:
  result = pluginResult !== undefined ? pluginResult : result;

Follow-up #29.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): use dynamic PK column resolution in relation loader

loadOneRelation, loadManyRelation, and loadManyToManyRelation all
hard-coded 'id' as the primary key column name. Tables with non-standard
PKs (e.g. 'code', 'slug') would silently fail to load relations.

Now uses getPrimaryKeyColumns() to dynamically resolve the PK column
name for both primary and target tables. Falls back to 'id' when no
PK metadata is found for backward compatibility.

Follow-up #14.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(db): split barrel export into tiered sub-paths

Primary API at @vertz/db, SQL utilities at @vertz/db/sql,
internal helpers at @vertz/db/internals, plugin system at @vertz/db/plugin.
Reduces autocomplete noise and clarifies API surface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add semantic error code enum (DbErrorCode)

Replace raw PG numeric error codes ('23505', '23503', etc.) with
developer-friendly semantic names on error classes. This makes error
handling more readable and enables future switch/case exhaustiveness
checking.

Changes:
- Add DbErrorCode const object mapping semantic names to PG SQLSTATE codes
- Add PgCodeToName reverse lookup and resolveErrorCode helper
- Change .code on constraint errors to semantic names (e.g., 'UNIQUE_VIOLATION')
- Add .pgCode property on error classes for raw PG code access
- Export DbErrorCode, DbErrorCodeName, DbErrorCodeValue, PgCodeToName,
  resolveErrorCode from @vertz/db
- Update all tests to assert semantic codes instead of numeric strings
- Fix http-adapter test to use ESM import instead of CJS require

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address review — types condition ordering, remove duplicate exports

- Reorder package.json exports to put types before import
- Remove camelToSnake/snakeToCamel from internals (already in sql)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add OR/NOT filter operators with proper parenthesization [#41]

Fix OR and AND branch parenthesization so multi-condition branches are
wrapped in parens, preventing operator precedence issues in generated SQL.

Add comprehensive tests:
- OR/AND/NOT with multi-condition branches
- Nested composition (OR>AND>NOT, NOT>OR, etc.)
- Operator-based conditions in logical branches
- Parameter numbering across nested operators
- PGlite integration tests for OR, NOT, nested queries
- SQL injection prevention test with parameterized OR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add createRegistry() and d.entry() for ergonomic relations [VER-40]

Reduce boilerplate for defining table registries with type-safe relations.

- d.entry(table) helper: reduces `{ table, relations: {} }` to a single call
- d.entry(table, relations): wraps table with relations in one call
- createRegistry(tables, callback): typed ref factory with per-table FK validation
  - ref.TABLE.one('target', 'fkColumn') validates both table name and column at compile time
  - ref.TABLE.many('target', 'fkColumn') validates FK against target table columns
  - Tables without explicit relations are auto-wrapped (no boilerplate needed)
- Full test coverage: runtime tests, type-level tests (@ts-expect-error negatives)
- E2E test updated to use createRegistry() instead of manual wrapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add cursor-based pagination to findMany [#15] (#168)

Implements cursor-based pagination as specified in design doc Section 1.7.

- Add `cursor` and `take` options to SelectOptions, FindManyArgs, and
  TypedFindManyOptions interfaces
- Single-column cursor generates `WHERE "col" > $N ORDER BY "col" ASC LIMIT $M`
- Composite cursor uses row-value comparison: `(col1, col2) > ($1, $2)`
- Cursor respects orderBy direction (> for asc, < for desc)
- Cursor conditions AND with existing where filters
- `take` aliases `limit` when paired with cursor
- When no explicit orderBy, cursor columns are used for ORDER BY (default ASC)

Tested with PGlite integration tests verifying multi-page traversal,
cursor+where combinations, and desc ordering.

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add column-type-specific metadata to DefaultMeta extensions (#169)

Downstream consumers (migration generators, query builders) need
column-type-specific fields at the type level, not just at runtime.
Previously DefaultMeta only carried boolean flags, while fields like
length, precision, scale, enumName, enumValues, and format only existed
in the runtime ColumnMetadata interface.

Add four typed metadata extensions:
- VarcharMeta<TLength> — carries length as a literal type
- DecimalMeta<TPrecision, TScale> — carries precision and scale
- EnumMeta<TName, TValues> — carries enumName and enumValues tuple
- FormatMeta<TSqlType, TFormat> — carries format (e.g. 'email')

Update d.varchar(), d.decimal(), d.enum(), and d.email() signatures
to return these specific meta types instead of plain DefaultMeta.
The metadata flows through modifier chains (nullable, unique, etc.)
via the existing Omit & intersection pattern.

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add dry-run mode to migration runner (#167)

* feat(db): add dry-run mode to migration runner and deploy CLI [VER-21]

- Add ApplyOptions and ApplyResult types to runner.apply()
- When dryRun: true, return SQL statements without executing
- Add dryRun option to migrateDeploy() with per-migration details
- Update migration index exports with new types
- Tests: dry-run returns SQL without modifying DB, output matches
  what non-dry-run would execute, CLI respects the flag

Closes VER-21

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): skip createHistoryTable during dry-run in migrateDeploy

Guard createHistoryTable behind !dryRun so dry-run mode is truly
side-effect-free. Wrap getApplied in try/catch during dry-run to
gracefully handle missing history table (returns empty applied list).

Updated tests to assert that no DDL is executed during dry-run and
added coverage for dry-run with an existing history table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): wire generic type inference from schema to query results [DB-035] (#170)

Thread TTables generic through DatabaseInstance method signatures so
that findOne, findMany, create, update, upsert, delete, and other
CRUD methods return properly typed results instead of Promise<unknown>.

- Add TOptions generic to capture literal option shapes
- Compute return types via FindResult<TTable, TOptions, TRelations>
- Type data inputs using InsertInput/UpdateInput from table definition
- Type where filters using FilterType<TColumns>
- Add comprehensive .test-d.ts type flow verification tests
- Remove 27 'as Record<string, unknown>' casts from E2E test

Closes DB-035

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-core[bot] <260431274+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com>
Co-authored-by: vertz-devops[bot] <2832183+vertz-devops[bot]@users.noreply.github.com>
Co-authored-by: vertz-devops[bot] <260554958+vertz-devops[bot]@users.noreply.github.com>
Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>
viniciusdacal pushed a commit that referenced this pull request Feb 22, 2026
* feat(db): add type inference engine (FindResult, filters, includes) [DB-005]

Implement the type inference layer that powers all query type safety:

- FilterType<TColumns>: typed where filters with eq/ne/gt/gte/lt/lte/in/notIn
  operators, string-specific contains/startsWith/endsWith, nullable isNull,
  and direct value shorthand
- OrderByType<TColumns>: constrained to column names with 'asc' | 'desc'
- SelectOption<TColumns>: mutually exclusive not:'sensitive'|'hidden' vs
  explicit field selection, enforced via never mapped keys
- SelectNarrow<TColumns, TSelect>: narrows result based on select clause
- IncludeResolve<TRelations, TInclude>: resolves relation includes with
  depth cap at 2, supports select sub-clauses for narrowing included relations
- FindResult<TTable, TOptions, TRelations>: combines select + include
- InsertInput/UpdateInput: standalone type utilities delegating to $insert/$update
- Database<TTables>/TableEntry: registry types for typed query methods
- Comprehensive type-level tests with @ts-expect-error negative cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007] (#151)

* feat(db): add typed error hierarchy and PG error parser [DB-006]

- Abstract DbError base with code, name, query, table, toJSON()
- UniqueConstraintError (23505), ForeignKeyError (23503),
  NotNullError (23502), CheckConstraintError (23514)
- NotFoundError, ConnectionError, ConnectionPoolExhaustedError
- PG error code parser with column/constraint/detail extraction
- dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings
- 42 new tests covering all error types, parser, and HTTP adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add createDb(), tenant graph computation [DB-007]

- createDb() factory accepts URL, pool config, table registry, casing, log
- Returns typed DatabaseInstance<TTables> with $tenantGraph
- Tenant graph computed at createDb() time from d.tenant() metadata
- Traverses .references() chains for indirect tenant scoping
- Classifies tables as root, directlyScoped, indirectlyScoped, shared
- Logs notices for tables without tenant path and not .shared()
- db.close() and db.isHealthy() stub implementations
- 16 new tests for tenant graph and database factory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 3 — SQL Generator (db-008, db-009) (#152)

* feat(db): add SQL statement builders (SELECT, INSERT, UPDATE, DELETE) [DB-008]

Implements Phase 3 SQL generator builders:
- SELECT builder: column selection with camelCase->snake_case aliasing,
  WHERE, ORDER BY, LIMIT/OFFSET, COUNT(*) OVER() for findManyAndCount
- INSERT builder: single row, batch (multi-row VALUES), RETURNING,
  ON CONFLICT (upsert with DO NOTHING / DO UPDATE SET)
- UPDATE builder: SET clause from data object, WHERE, RETURNING
- DELETE builder: WHERE, RETURNING
- WHERE builder: all 13+ filter operators (eq, ne, gt, gte, lt, lte,
  contains, startsWith, endsWith, in, notIn, isNull, isNotNull),
  nested OR/AND/NOT, JSONB operators (->/->>), array operators (@>, <@, &&)
- Casing module: camelToSnake / snakeToCamel conversion
- All values parameterized ($1, $2, ...) for SQL injection prevention
- Handles 'now' sentinel for timestamp columns via NOW()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add sql tagged template and escape hatch [DB-009]

Implements the sql tagged template literal for composable raw SQL:
- sql`...${value}...` auto-parameterizes values as $1, $2, ...
- sql.raw() inserts trusted SQL strings without parameterization
- Nested sql`` fragments compose with automatic param renumbering
- CTE (WITH ... AS) syntax works through tagged template composition
- SqlFragment type with _tag discriminant for runtime identification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address 7 blocking review issues on Phase 3 SQL generator [DB-008, DB-009]

1. Empty IN/NOT IN arrays now produce FALSE/TRUE instead of invalid SQL
2. LIKE values escape %, _, and \ metacharacters before wrapping
3. Empty OR/AND arrays produce FALSE/TRUE (standard SQL logic)
4. JSONB path segments sanitize single quotes to prevent injection
5. LIMIT/OFFSET values are parameterized ($N) instead of inlined
6. Added PGlite integration test (DB-008 acceptance criterion)
7. Added db.query<T>() stub to DatabaseInstance (DB-009 acceptance criterion)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 4 — Query Builder + Relations + Aggregation (db-010/011/012) (#156)

* feat(db): add CRUD query methods [DB-010]

Implements typed find, create, update, upsert, and delete methods on
the Database instance. All methods use the Phase 3 SQL builders,
parameterized queries, and snake_case -> camelCase result mapping.

- findOne, findOneOrThrow, findMany, findManyAndCount
- create, createMany, createManyAndReturn
- update, updateMany
- upsert (INSERT ON CONFLICT with explicit update values)
- deleteOne, deleteMany
- Query executor with PG error mapping
- Row mapper with snakeToCamel conversion
- Helper utilities for column resolution

Includes comprehensive PGlite integration tests covering all
acceptance criteria: full CRUD cycle, pagination, select narrowing,
UniqueConstraintError, ForeignKeyError, NotFoundError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add relation loading with include option [DB-011]

Implements the include option for find queries using a batched
loading strategy (N+1 prevention via IN queries).

- belongsTo (one) relations: loads single object by FK
- hasMany (many) relations: loads array by parent PK
- Batched loading: single IN query per relation
- Select narrowing on included relations
- Nested includes supported up to depth 2
- Integration with findOne, findMany, findManyAndCount

PGlite integration tests cover belongsTo, hasMany, batched
loading, include with select, and findManyAndCount with include.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add aggregation queries (count, aggregate, groupBy) [DB-012]

Implements typed aggregation query methods:

- count(table, { where? }): returns row count as number
- aggregate(table, { _avg, _sum, _min, _max, _count, where? }):
  computes aggregation functions with typed results
- groupBy(table, { by, _count?, _avg?, orderBy? }):
  groups rows with per-group aggregations

All methods support where filters and generate parameterized SQL.
Results use camelCase keys consistent with the rest of the query layer.

PGlite integration tests cover count with filter, aggregate with
all functions, groupBy with ordering, multi-column grouping, and
combined aggregations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address PR #156 review — SQL injection, manyToMany, nested includes [db-010/011/012]

B1: Validate orderBy columns in groupBy aggregation to prevent SQL injection.
    Underscore-prefixed columns are now validated against requested aggregation
    aliases; direction values are restricted to 'asc'/'desc' only.

B2: Implement manyToMany relation loading via join tables (_through).
    Queries the join table first, collects target IDs, then batch-loads
    target rows and maps them back to parent rows.

B3: Implement recursive nested includes (depth-2).
    Pass tablesRegistry through loadRelations so target table relations
    can be resolved for nested include: { posts: { include: { comments: true } } }.

All fixes follow TDD: failing tests written first, then minimal implementation.

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 5 — Migration Differ + SQL Generator + Runner (db-013/014/015) (#157)

* feat(db): add JSON snapshot format for schema state capture [db-013]

* feat(db): add schema differ with rename detection and confidence scoring [db-013]

* feat(db): add SQL migration generator with rollback support [db-014]

* feat(db): add migration runner with history, drift detection, and ordering [db-015]

* feat(db): add file management, module exports, and integration tests [db-015]

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>

* feat(db): Phase 6 — CLI + Cache-Readiness Primitives (db-016, db-017) (#158)

* feat(db): Phase 6 — CLI commands + cache-readiness primitives (db-016/db-017)

- CLI: migrateDev, migrateDeploy, push, migrateStatus
- Dry-run mode for migrateDev
- Event bus for mutation events
- Deterministic query fingerprinting
- Plugin runner with beforeQuery/afterQuery hooks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address PR #158 review — status crash, snapshot return, exports

- B1: migrateStatus now calls createHistoryTable before querying applied
- B2: migrateDev returns snapshot in result for caller persistence
- B3: cli and plugin modules re-exported from main barrel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): Phase 7 — E2E acceptance test + type error quality (db-018/db-019) (#159)

- Full E2E test from design doc Section 7
- Type inference assertions with @ts-expect-error
- Branded error types for readable compiler messages
- @vertz/db/diagnostic export
- Performance validation under 100k instantiation budget

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* docs(db): add v1.0 retrospective and changeset

- Post-implementation review covering all 7 phases
- Changeset for @vertz/db minor release

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): add @types/node to resolve node:crypto in CI typecheck

The @vertz/db package imports from node:crypto (runner.ts, fingerprint.ts)
but was missing @types/node in devDependencies. This worked locally because
Bun hoists @types/node from sibling packages, but failed in CI where tsc
runs in an isolated Dagger container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): increase test/hook timeouts for PGlite in CI

PGlite uses WebAssembly which is slower in Dagger's container environment.
Tests were timing out at the default 5s/10s limits. Set testTimeout and
hookTimeout to 30s to accommodate CI's constrained resources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(db): update design doc to reflect implementation deviations

Comprehensive update to align the design doc with the actual v1.0
implementation. Documents all breaking changes, missing features,
and additions discovered during adversarial API diff review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): stabilize PGlite tests in CI

Disable file parallelism to prevent concurrent WASM instances from
crashing, add retry:2 as safety net, fix PGlite version constraint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): escape enum values and column defaults in DDL generation

Enum values and column default values were interpolated directly into
DDL strings without escaping single quotes. Malicious values like
'); DROP TABLE users;-- would produce injectable SQL.

Added escapeSqlString() helper that doubles internal single quotes,
applied to enum_added, enum_altered, and column default expressions.

Follow-up #22.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): guard deleteMany/updateMany against empty where clause

Passing an empty where object ({}) to deleteMany or updateMany would
generate SQL with no WHERE clause, silently affecting all rows. Added
a runtime check that throws if the where object has no keys.

Follow-up #19.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): preserve previous result when afterQuery plugin returns undefined

If a plugin's afterQuery hook returned undefined (e.g. a logging-only
plugin that observes but doesn't transform), the chain would pass
undefined to subsequent plugins, causing runtime errors.

Now defaults to the previous result when a plugin returns undefined:
  result = pluginResult !== undefined ? pluginResult : result;

Follow-up #29.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): use dynamic PK column resolution in relation loader

loadOneRelation, loadManyRelation, and loadManyToManyRelation all
hard-coded 'id' as the primary key column name. Tables with non-standard
PKs (e.g. 'code', 'slug') would silently fail to load relations.

Now uses getPrimaryKeyColumns() to dynamically resolve the PK column
name for both primary and target tables. Falls back to 'id' when no
PK metadata is found for backward compatibility.

Follow-up #14.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(db): split barrel export into tiered sub-paths

Primary API at @vertz/db, SQL utilities at @vertz/db/sql,
internal helpers at @vertz/db/internals, plugin system at @vertz/db/plugin.
Reduces autocomplete noise and clarifies API surface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add semantic error code enum (DbErrorCode)

Replace raw PG numeric error codes ('23505', '23503', etc.) with
developer-friendly semantic names on error classes. This makes error
handling more readable and enables future switch/case exhaustiveness
checking.

Changes:
- Add DbErrorCode const object mapping semantic names to PG SQLSTATE codes
- Add PgCodeToName reverse lookup and resolveErrorCode helper
- Change .code on constraint errors to semantic names (e.g., 'UNIQUE_VIOLATION')
- Add .pgCode property on error classes for raw PG code access
- Export DbErrorCode, DbErrorCodeName, DbErrorCodeValue, PgCodeToName,
  resolveErrorCode from @vertz/db
- Update all tests to assert semantic codes instead of numeric strings
- Fix http-adapter test to use ESM import instead of CJS require

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): address review — types condition ordering, remove duplicate exports

- Reorder package.json exports to put types before import
- Remove camelToSnake/snakeToCamel from internals (already in sql)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add OR/NOT filter operators with proper parenthesization [#41]

Fix OR and AND branch parenthesization so multi-condition branches are
wrapped in parens, preventing operator precedence issues in generated SQL.

Add comprehensive tests:
- OR/AND/NOT with multi-condition branches
- Nested composition (OR>AND>NOT, NOT>OR, etc.)
- Operator-based conditions in logical branches
- Parameter numbering across nested operators
- PGlite integration tests for OR, NOT, nested queries
- SQL injection prevention test with parameterized OR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add createRegistry() and d.entry() for ergonomic relations [VER-40]

Reduce boilerplate for defining table registries with type-safe relations.

- d.entry(table) helper: reduces `{ table, relations: {} }` to a single call
- d.entry(table, relations): wraps table with relations in one call
- createRegistry(tables, callback): typed ref factory with per-table FK validation
  - ref.TABLE.one('target', 'fkColumn') validates both table name and column at compile time
  - ref.TABLE.many('target', 'fkColumn') validates FK against target table columns
  - Tables without explicit relations are auto-wrapped (no boilerplate needed)
- Full test coverage: runtime tests, type-level tests (@ts-expect-error negatives)
- E2E test updated to use createRegistry() instead of manual wrapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add cursor-based pagination to findMany [#15] (#168)

Implements cursor-based pagination as specified in design doc Section 1.7.

- Add `cursor` and `take` options to SelectOptions, FindManyArgs, and
  TypedFindManyOptions interfaces
- Single-column cursor generates `WHERE "col" > $N ORDER BY "col" ASC LIMIT $M`
- Composite cursor uses row-value comparison: `(col1, col2) > ($1, $2)`
- Cursor respects orderBy direction (> for asc, < for desc)
- Cursor conditions AND with existing where filters
- `take` aliases `limit` when paired with cursor
- When no explicit orderBy, cursor columns are used for ORDER BY (default ASC)

Tested with PGlite integration tests verifying multi-page traversal,
cursor+where combinations, and desc ordering.

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add column-type-specific metadata to DefaultMeta extensions (#169)

Downstream consumers (migration generators, query builders) need
column-type-specific fields at the type level, not just at runtime.
Previously DefaultMeta only carried boolean flags, while fields like
length, precision, scale, enumName, enumValues, and format only existed
in the runtime ColumnMetadata interface.

Add four typed metadata extensions:
- VarcharMeta<TLength> — carries length as a literal type
- DecimalMeta<TPrecision, TScale> — carries precision and scale
- EnumMeta<TName, TValues> — carries enumName and enumValues tuple
- FormatMeta<TSqlType, TFormat> — carries format (e.g. 'email')

Update d.varchar(), d.decimal(), d.enum(), and d.email() signatures
to return these specific meta types instead of plain DefaultMeta.
The metadata flows through modifier chains (nullable, unique, etc.)
via the existing Omit & intersection pattern.

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): add dry-run mode to migration runner (#167)

* feat(db): add dry-run mode to migration runner and deploy CLI [VER-21]

- Add ApplyOptions and ApplyResult types to runner.apply()
- When dryRun: true, return SQL statements without executing
- Add dryRun option to migrateDeploy() with per-migration details
- Update migration index exports with new types
- Tests: dry-run returns SQL without modifying DB, output matches
  what non-dry-run would execute, CLI respects the flag

Closes VER-21

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): skip createHistoryTable during dry-run in migrateDeploy

Guard createHistoryTable behind !dryRun so dry-run mode is truly
side-effect-free. Wrap getApplied in try/catch during dry-run to
gracefully handle missing history table (returns empty applied list).

Updated tests to assert that no DDL is executed during dry-run and
added coverage for dry-run with an existing history table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): wire generic type inference from schema to query results [DB-035] (#170)

Thread TTables generic through DatabaseInstance method signatures so
that findOne, findMany, create, update, upsert, delete, and other
CRUD methods return properly typed results instead of Promise<unknown>.

- Add TOptions generic to capture literal option shapes
- Compute return types via FindResult<TTable, TOptions, TRelations>
- Type data inputs using InsertInput/UpdateInput from table definition
- Type where filters using FilterType<TColumns>
- Add comprehensive .test-d.ts type flow verification tests
- Remove 27 'as Record<string, unknown>' casts from E2E test

Closes DB-035

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>

---------

Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vertz-dev-core[bot] <260431274+vertz-dev-core[bot]@users.noreply.github.com>
Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com>
Co-authored-by: vertz-devops[bot] <2832183+vertz-devops[bot]@users.noreply.github.com>
Co-authored-by: vertz-devops[bot] <260554958+vertz-devops[bot]@users.noreply.github.com>
Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>
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.

0 participants