Skip to content

Sqlite wrapper joins#100

Open
Its4Nik wants to merge 10 commits intodevfrom
sqlite-wrapper-joins
Open

Sqlite wrapper joins#100
Its4Nik wants to merge 10 commits intodevfrom
sqlite-wrapper-joins

Conversation

@Its4Nik
Copy link
Copy Markdown
Owner

@Its4Nik Its4Nik commented Apr 16, 2026

Summary by CodeRabbit

  • New Features

    • Added accounts management UI with user, API key, and OAuth provider management sections.
    • Implemented custom authentication system supporting API keys and OAuth/OIDC providers.
    • Added type-safe JOIN operations to query builder with automatic result type inference.
  • Refactor

    • Replaced authentication framework with custom AuthHandler implementation.
    • Enhanced logging consistency across codebase with structured prefixes and context.
    • Reorganized middleware registration order.
  • Chores

    • Added tooling configuration files and workspace setup.
    • Updated TypeScript path aliases.
    • Bumped package versions and dependencies.

Its4Nik added 10 commits April 13, 2026 12:36
- Add database initialization for Better Auth tables (user, session, account, verification, apikey)
- Enable Better Auth admin and API key plugins in the backend
- Implement Accounts settings UI with management sections for users, API keys, and OAuth providers
- Add custom hooks and mutations for account management operations
- Update OpenAPI path prefix to /api/v2/auth for better route organization
- Add Zed editor configuration for Biome and TypeScript support
- Refactor authentication setup to expose a reusable `betterAuthPlugin` for Elysia
- Update API entry point to use the new authentication plugin and reorder middleware initialization
- Add @dockstat/api path alias to tsconfig.json for both api and dockstat apps
- Clean up unused import in elysia-plugins
- Improve readability of getSession call in auth plugin
- Export CommandResult type in docknode treaty
Implements a decorator-based routing system for Elysia JS with
full TypeScript type safety via TypeBox schema extraction. Includes
comprehensive documentation with examples and migration guides.

Features:
- Proper @controller() decorator pattern (fixes ts 1206 errors)
- Type extraction utilities for compile-time safety
- Support for @BodySchema, @QuerySchema, @ParamsSchema, etc.
- Dual pattern support (context destructuring and parameter decorators)
- typedRoute helper for functional style handlers
- Extensive documentation on type safety approaches
The new auth package introduces database schemas and handlers for users,
API tokens, and OIDC providers. The API application has been updated to
use the new AuthHandler for managing users and API keys.
- Implement join, innerJoin, leftJoin, rightJoin, fullJoin, and crossJoin methods
- Add generic type merging to allow IntelliSense for joined table columns
- Support join conditions via simple column mapping or raw SQL expressions
- Enable table aliasing for complex queries and self-joins
- Update README documentation with join examples and migration notes
- Add extensive type-safe JOIN unit tests in __test__/join-types.test.ts
- Refactor QueryBuilder architecture to support chained result type transformations
…no-ticket]

- Standardize log formatting across all query builders and database operations by adding bracketed prefixes (e.g., [INIT], [SELECT], [TRANSACTION]) for better traceability.
- Enhance logging detail with operational context and result summaries in query-builder modules.
- Refactor `BaseQueryBuilder` to include a `logWithTable` helper for consistent logging.
- Update `transformFromDb` and `transformToDb` to provide more granular debug information during data serialization.
- Correct boolean expectation in `join-types.test.ts` and update serialization test in `transformer.test.ts` to be more robust.
…ces for type-safe inference

- Update join methods (join, innerJoin, leftJoin, etc.) to accept a `QueryBuilder` instance instead of a string table name.
- Implement automatic parser merging for joined tables to ensure correct column transformation (e.g., boolean/date/json parsing) on joined data.
- Update type-safe documentation and examples to remove generic type parameters in join calls.
- Improve `BaseQueryBuilder` with public accessors for table names and parsers to facilitate cross-builder interactions.
- Cleanup unused `tableParsers` state in the main DB class.
@Its4Nik Its4Nik added this to the DockStat v1 - BETA milestone Apr 16, 2026
@Its4Nik Its4Nik self-assigned this Apr 16, 2026
@Its4Nik Its4Nik added enhancement New feature or request SQLite-Wrapper labels Apr 16, 2026
@Its4Nik Its4Nik added this to DockStat Apr 16, 2026
@github-project-automation github-project-automation Bot moved this to Todo in DockStat Apr 16, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @Its4Nik, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b0bdbbc-2eae-4721-b01a-7772ee005783

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sqlite-wrapper-joins

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
packages/sqlite-wrapper/src/utils/transformer.ts (1)

143-151: ⚠️ Potential issue | 🟠 Major

Handle invalid date strings explicitly; new Date() won't throw.

The new Date(value) call creates Invalid Date objects without throwing exceptions, so invalid date strings silently pass through the catch block and get stored with the "successfully transformed" marker. The try-catch is ineffective here.

Proposed fix
-      if (value !== null && value !== undefined && typeof value === "string") {
-        try {
-          transformed[columnKey] = new Date(value)
-          transformedColumns.push(`DATE:${columnKey}`)
-          logger.debug(`[FROM_DB] DATE column parsed ${value}`, columnKey)
-        } catch {
-          // Keep original value if DATE parsing fails
-          logger.warn(`[FROM_DB] Failed to parse DATE column Value: ${value}`, columnKey)
-        }
-      }
+      if (value !== null && value !== undefined && typeof value === "string") {
+        const parsed = new Date(value)
+        if (!Number.isNaN(parsed.getTime())) {
+          transformed[columnKey] = parsed
+          transformedColumns.push(`DATE:${columnKey}`)
+          logger.debug(`[FROM_DB] DATE column parsed`, columnKey)
+        } else {
+          // Keep original value if DATE parsing fails
+          logger.warn(`[FROM_DB] Failed to parse DATE column`, columnKey)
+        }
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/utils/transformer.ts` around lines 143 - 151, The
code assumes new Date(value) will throw on invalid input but it doesn't; replace
the try/catch with explicit validation: create const parsed = new Date(value)
and check if Number.isNaN(parsed.getTime()) before assigning to
transformed[columnKey] and pushing `DATE:${columnKey}` to transformedColumns; if
invalid, do not mutate transformed/transformedColumns and log a warning via
logger.warn(`[FROM_DB] Failed to parse DATE column Value: ${value}`, columnKey).
Ensure you reference the existing variables transformed, transformedColumns,
columnKey, logger and value in the updated logic (in the same block where typeof
value === "string" is checked).
apps/api/src/elysia-plugins.ts (2)

5-13: ⚠️ Potential issue | 🔴 Critical

OpenAPI does not exist in the imported module and will cause a startup failure.

Line 5 imports { OpenAPI } from "./auth", but apps/api/src/auth/index.ts only exports AuthHandler. The usage at lines 12–13 (OpenAPI.components, OpenAPI.getPaths()) will fail at runtime. Either implement OpenAPI in the auth module or correct the import path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/elysia-plugins.ts` around lines 5 - 13, The code imports and
uses OpenAPI (OpenAPI.components, OpenAPI.getPaths) but ./auth only exports
AuthHandler, causing a runtime failure; fix by either exporting a proper OpenAPI
object from the auth module (implement and export OpenAPI with components and
getPaths) or change the import in the DockStatElysiaPlugins setup to the correct
module that provides OpenAPI; update the import line and ensure the openapi({
documentation: { components: await OpenAPI.components, paths: await
OpenAPI.getPaths() } }) call references the exported symbol (OpenAPI) or replace
those calls with the correct functions/exports from the auth module
(AuthHandler) or the actual OpenAPI provider so DockStatElysiaPlugins and
openapi use valid symbols.

7-18: ⚠️ Potential issue | 🔴 Critical

Broken import: OpenAPI is not exported from auth module.

Line 5 imports OpenAPI from "./auth", but the auth module only exports AuthHandler (from index.ts) and getAuthTable (from db.ts). The OpenAPI export does not exist. This will cause a build or runtime error when elysia-plugins.ts attempts to use OpenAPI.components and OpenAPI.getPaths() on lines 12–13. The OpenAPI functionality needs to be either implemented in the auth module or imported from the correct location.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/elysia-plugins.ts` around lines 7 - 18, The import of OpenAPI
used by DockStatElysiaPlugins (openapi({ documentation: { components: await
OpenAPI.components, paths: await OpenAPI.getPaths() } })) is invalid because
OpenAPI is not exported from the auth module; update the import so OpenAPI comes
from the correct module that actually exports components/getPaths (or add an
OpenAPI export from the auth module), then ensure the file imports that symbol
instead of OpenAPI from "./auth"; verify DockStatElysiaPlugins and the
openapi(...) call use the corrected OpenAPI symbol name and methods.
packages/sqlite-wrapper/src/migration.ts (1)

248-354: ⚠️ Potential issue | 🔴 Critical

Restore foreign_keys in a finally.

If any migration step throws, control exits before the re-enable block runs and the shared connection keeps PRAGMA foreign_keys = OFF. That leaves every later write on this DB handle without FK enforcement.

Suggested fix
   const fkStatus = db.prepare("PRAGMA foreign_keys").get() as ForeignKeyStatus | null
-  if (fkStatus && fkStatus.foreign_keys === 1) {
+  const shouldRestoreForeignKeys = fkStatus?.foreign_keys === 1
+  if (shouldRestoreForeignKeys) {
     migrationLog.debug(`[${tableName}] Disabling foreign key constraints for migration`)
     db.run("PRAGMA foreign_keys = OFF")
   }
 
-  db.transaction(() => {
+  try {
+    db.transaction(() => {
       // existing migration body
-  })()
-
-  if (fkStatus && fkStatus.foreign_keys === 1) {
-    migrationLog.debug(`[${tableName}] Re-enabling foreign key constraints`)
-    db.run("PRAGMA foreign_keys = ON")
+    })()
+  } finally {
+    if (shouldRestoreForeignKeys) {
+      migrationLog.debug(`[${tableName}] Re-enabling foreign key constraints`)
+      db.run("PRAGMA foreign_keys = ON")
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/migration.ts` around lines 248 - 354, Summary:
Re-enable PRAGMA foreign_keys in a finally block so it always runs even if
migration throws. Fix: capture whether you disabled foreign keys (e.g. using
fkStatus and a boolean like fkWasDisabled) before calling db.transaction(...),
then wrap the transaction invocation in try { db.transaction(() => { ... }) }
finally { if (fkWasDisabled) { migrationLog.debug(`[${tableName}] Re-enabling
foreign key constraints`); db.run("PRAGMA foreign_keys = ON") } } so the PRAGMA
ON call executes regardless of errors; keep existing migrationLog/error handling
and rethrow the error from the catch inside the transaction to preserve
behavior.
packages/sqlite-wrapper/src/query-builder/select.ts (2)

306-358: ⚠️ Potential issue | 🔴 Critical

count() and exists() still ignore accumulated JOIN clauses.

Both methods rebuild SQL from FROM ${baseTable} plus the WHERE fragment only. Joined queries therefore return the wrong cardinality, and predicates that reference joined tables can fail because the joined table never appears in the statement. These paths need to include buildJoinClause() and prepend the join params, just like buildSelectQuery() does.

Proposed fix sketch
   count(): number {
     if (!this.hasRegexConditions()) {
+      const [joinClause, joinParams] = this.buildJoinClause()
       const [whereClause, whereParams] = this.buildWhereClause()
-      const query = `SELECT COUNT(*) AS __count FROM ${quoteIdentifier(this.getTableName())}${whereClause}`
+      const query = `SELECT COUNT(*) AS __count FROM ${quoteIdentifier(this.getTableName())}${joinClause}${whereClause}`

       const result = this.getDb()
         .prepare(query)
-        .get(...whereParams) as { __count: number } | null
+        .get(...joinParams, ...whereParams) as { __count: number } | null

Apply the same pattern in exists().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/query-builder/select.ts` around lines 306 - 358,
count() and exists() rebuild SQL using only the base FROM and WHERE fragments
and thus ignore accumulated JOINs, causing wrong counts and broken predicates;
update both count() and exists() to include the join clause and join params the
same way buildSelectQuery() does: call buildJoinClause() (or reuse the
join-building logic from buildSelectQuery()), prepend the join SQL to the FROM
clause and prepend the join params to whereParams before preparing/executing the
statement so JOINed tables appear in the SQL and their params are bound.

109-145: ⚠️ Potential issue | 🟠 Major

Default SELECT * is unsafe once joins are present.

With joins enabled, the fallback projection is still *. SQLite returns duplicate column names for common fields like id, name, or timestamps, but the row object can only keep one property per key, so one table's value overwrites the other and the merged parser becomes ambiguous. Please require an explicit projection after a join, or auto-alias joined columns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/query-builder/select.ts` around lines 109 - 145,
The current buildSelectQuery method allows a default "*" projection even when
joins exist, which causes duplicate column names to collide; update
buildSelectQuery (and related selectedColumns handling) to detect when
buildJoinClause returns a non-empty joinClause and the projection is "*" and
either (a) throw a clear error requiring the caller to supply an explicit
projection, or (b) auto-expand the projection by enumerating each table's
columns and aliasing them (e.g., prefix/alias via quoteIdentifier(table) + "." +
quoteIdentifier(col) or quoteIdentifier(table + "__" + col)) before assembling
the SELECT; implement the chosen behavior inside buildSelectQuery using
getTableName, buildJoinClause, selectedColumns and quoteIdentifier so joined
columns are uniquely named and keep the existing ORDER/LIMIT logic intact.
🟡 Minor comments (14)
packages/sqlite-wrapper/src/lib/backup/listBackups.ts-42-42 (1)

42-42: ⚠️ Potential issue | 🟡 Minor

Fix error interpolation in backup failure log.

Line 42 currently logs the literal ${error} instead of the real error value, which hides failure context during incident/debugging.

🛠️ Proposed fix
-    backupLog.error("[BACKUP] Failed to list backups | Error: ${error}", backupDir)
+    backupLog.error(`[BACKUP] Failed to list backups | Error: ${String(error)}`, backupDir)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/listBackups.ts` at line 42, In
listBackups (packages/sqlite-wrapper/src/lib/backup/listBackups.ts) the log call
uses a literal "${error}" instead of the thrown error; update the
backupLog.error invocation to include the actual error value (and still include
backupDir) by interpolating the error or passing it as a parameter to the logger
so the message reads the actual error details rather than the string "${error}".
packages/auth/src/index.ts-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Remove import-time console side effect from package entrypoint.

This log runs whenever the module is loaded and will leak noise into consumers/tests.

Suggested fix
-console.log("Hello via Bun!")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/index.ts` at line 1, Remove the import-time console
side-effect by deleting the top-level console.log("Hello via Bun!") in the
package entrypoint; if runtime debugging is needed, instead expose an explicit
function (e.g., export function logHello() or init()) that performs the
console.log and let callers invoke it, so loading the module no longer produces
noisy logs.
packages/auth/.gitignore-15-16 (1)

15-16: ⚠️ Potential issue | 🟡 Minor

Fix glob typos so logs/reports are actually ignored.

_.log and the report pattern with underscores are too restrictive and likely won’t match intended files.

Suggested fix
-_.log
-report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+*.log
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/.gitignore` around lines 15 - 16, Replace the incorrect glob
patterns in .gitignore: change "_.log" to "*.log" so all log files are ignored,
and replace "report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json" with a proper numeric/glob
pattern such as "report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json" (or "report.*.json" if
broader matching is acceptable) so generated report files are actually excluded.
packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts-52-53 (1)

52-53: ⚠️ Potential issue | 🟡 Minor

Fix non-interpolated interval/max-backups log message.

The log message uses double quotes instead of backticks, preventing template interpolation. The values will be logged literally as ${autoBackupOptions.intervalMs} instead of actual numeric values.

Suggested fix
-    "[AUTO_BACKUP] Enabled | Interval: ${autoBackupOptions.intervalMs}ms | Max backups: ${autoBackupOptions.maxBackups}",
+    `[AUTO_BACKUP] Enabled | Interval: ${autoBackupOptions.intervalMs}ms | Max backups: ${autoBackupOptions.maxBackups}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts` around lines 52 -
53, The log string in setupAutoBackup is using double quotes so template
expressions like ${autoBackupOptions.intervalMs} and
${autoBackupOptions.maxBackups} are not interpolated; update the logging call
that currently passes "[AUTO_BACKUP] Enabled | Interval:
${autoBackupOptions.intervalMs}ms | Max backups:
${autoBackupOptions.maxBackups}", dbPath to use a template literal (backticks)
so the values from autoBackupOptions are interpolated (refer to the
setupAutoBackup function and the variables autoBackupOptions and dbPath when
making the change).
packages/sqlite-wrapper/src/lib/backup/restore.ts-41-41 (1)

41-41: ⚠️ Potential issue | 🟡 Minor

Fix broken interpolation in restore warning log.

At Line 41, ${err} is inside double quotes, so the error value is never rendered. Change to backticks to enable template interpolation.

Suggested fix
-      backupLog.warn("[RESTORE] Error closing DB before restore | Error: ${err}", dbPath)
+      backupLog.warn(`[RESTORE] Error closing DB before restore | Error: ${String(err)}`, dbPath)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/restore.ts` at line 41, The warning
log in restore.ts uses double quotes so the ${err} placeholder isn't
interpolated; update the backupLog.warn call (the one invoking
backupLog.warn("[RESTORE] Error closing DB before restore | Error: ${err}",
dbPath)) to use backticks for template interpolation (i.e., change the string to
a template literal) so the err value is rendered, and keep the dbPath argument
as appropriate.
packages/sqlite-wrapper/__test__/join-types.test.ts-114-124 (1)

114-124: ⚠️ Potential issue | 🟡 Minor

Ambiguous id column in joined result.

Both User and Post have an id field. The comment "From User" on line 115 is misleading—when joining tables with overlapping column names, SQLite returns the last value (from posts) unless columns are explicitly aliased. This test may pass but could give a false sense of correctness.

Consider either:

  1. Using explicit column selection with aliases to avoid ambiguity
  2. Updating the comment to reflect the actual behavior
  3. Testing with a column unique to User (like email) to verify User data is present
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/__test__/join-types.test.ts` around lines 114 - 124,
The test is asserting a non-aliased "id" which is ambiguous between User and
Post; update the test (join-types.test.ts) to remove the ambiguous
expect(first).toHaveProperty("id") and instead either (A) modify the query that
produces `first` to explicitly select and alias columns (e.g. users.id AS
user_id, posts.id AS post_id) and assert those aliases, or (B) keep the query
but change assertions to rely only on unambiguous User fields (e.g. `email`) to
validate User data; reference the `first` variable and the User/Post models in
your change.
packages/auth/package.json-1-19 (1)

1-19: ⚠️ Potential issue | 🟡 Minor

Missing version field in package.json.

The package.json lacks a version field, which is required for npm packages and may cause issues with monorepo tooling.

Suggested fix
 {
   "dependencies": {
     "@dockstat/logger": "workspace:*"
   },
   "devDependencies": {
     "@dockstat/sqlite-wrapper": "workspace:*",
     "@types/bun": "latest"
   },
   "exports": {
     "/client": "./src/client/index.ts",
     "/server": "./src/server/index.ts"
   },
   "module": "src/index.ts",
   "name": "@dockstat/auth",
+  "version": "0.1.0",
   "peerDependencies": {
     "typescript": "^5"
   },
   "type": "module"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/package.json` around lines 1 - 19, The package.json for the
`@dockstat/auth` package is missing the top-level "version" field; add a
semantically versioned string (e.g., "1.0.0" or appropriate semver) as the
"version" property in the package.json to satisfy npm/monorepo tooling
requirements and keep it alongside existing keys like "name", "dependencies",
and "exports".
packages/sqlite-wrapper/src/lib/backup/backup.ts-44-52 (1)

44-52: ⚠️ Potential issue | 🟡 Minor

Log message timing is misleading.

Line 46 logs "[BACKUP] Creating backup" after the VACUUM INTO command has already completed. The message implies the backup is about to start, but at that point the backup file has already been created.

Consider moving this log before the db.run() call, or changing the message to reflect completion:

Proposed fix
  try {
+   backupLog.info(`[BACKUP] Creating backup`, backupPath)
    db.run(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`)
-   backupLog.info(`[BACKUP] Creating backup`, backupPath)

    if (autoBackupOptions) {
      applyRetentionPolicy(backupLog, autoBackupOptions)
    }

    backupLog.info(`[BACKUP] Backup completed successfully`, backupPath)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/backup.ts` around lines 44 - 52, The
log timing is misleading: move the "[BACKUP] Creating backup" log to before the
db.run(`VACUUM INTO ...`) call (or change its text to reflect completion) so the
message accurately represents the operation; update the call to
backupLog.info(`[BACKUP] Creating backup`, backupPath) to execute prior to
db.run(...) (functions involved: db.run and backupLog.info) and ensure
subsequent logs around applyRetentionPolicy(autoBackupOptions) and the final
backupLog.info(`[BACKUP] Backup completed successfully`, backupPath) remain
correct.
packages/auth/package.json-9-12 (1)

9-12: ⚠️ Potential issue | 🟡 Minor

Fix subpath export format to match Node.js ESM standard and monorepo convention.

Change /client and /server to ./client and ./server to align with the standard ./ prefix format used throughout the rest of the monorepo and required by Node.js ESM resolution.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/package.json` around lines 9 - 12, The package.json "exports"
entries use "/client" and "/server" subpaths which don't follow Node.js ESM and
the monorepo convention; update the exports object to use "./client" and
"./server" instead (i.e., replace the "/client" and "/server" keys with
"./client" and "./server") so the exports field in packages/auth/package.json
correctly maps to "./src/client/index.ts" and "./src/server/index.ts".
packages/sqlite-wrapper/src/index.ts-119-123 (1)

119-123: ⚠️ Potential issue | 🟡 Minor

Log success after the DB call returns.

These info messages are emitted before new Database(...), db.close(), and the COMMIT/ROLLBACK statements execute. If one of those throws, the logs still report a successful operation.

Also applies to: 217-220, 462-472

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/index.ts` around lines 119 - 123, The logs (calls
to this.dbLog.info) are emitted before the operations actually succeed, leading
to false success messages; move each success log to after the operation
completes without throwing (e.g., after this.db = new Database(path) succeeds,
after db.close() returns, and after COMMIT/ROLLBACK complete) so failures are
not logged as successes. Locate the early calls to this.dbLog.info around the
Database constructor (this.db = new Database(path)), close logic (db.close()),
and transaction commit/rollback sections and relocate or wrap them to run only
after the operation finishes successfully, optionally logging errors in the
catch blocks instead.
packages/sqlite-wrapper/src/query-builder/where.ts-167-171 (1)

167-171: ⚠️ Potential issue | 🟡 Minor

Normalize fallback values the same way as where().

The non-regex branch pushes value directly into whereParams, so booleans take a different path than where(). Reuse toSqliteValue() here too.

Suggested fix
-        this.state.whereParams.push(value as SQLQueryBindings)
+        this.state.whereParams.push(this.toSqliteValue(value))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/query-builder/where.ts` around lines 167 - 171,
The non-regex fallback in where.ts is pushing raw value into
this.state.whereParams causing booleans (and other types) to differ from where()
behavior; update the else branch in the method that builds conditions so it
calls toSqliteValue(value) and pushes that normalized result into
this.state.whereParams (while keeping the existing this.state.whereConditions
push using quoteIdentifier(column)); reference the functions/fields to change:
toSqliteValue(), where(), this.state.whereParams, this.state.whereConditions,
and quoteIdentifier(column) and keep the existing whereLog.debug message.
apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx-169-191 (1)

169-191: ⚠️ Potential issue | 🟡 Minor

Non-functional settings button.

The Settings button (gear icon) on line 169-176 has no onClick handler - it renders but does nothing when clicked. Either add functionality or remove the button if editing providers isn't supported yet.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx`
around lines 169 - 191, The Settings (gear) Button rendered in the actions cell
(uses the Settings icon component) has no onClick handler and is non-functional;
either remove that Button entirely or wire it to the provider-edit flow — e.g.,
add an onClick that calls the existing edit/open function (implement or reuse a
function like openEditOAuthProvider(providerId) or
showEditModal(record.providerId)) and ensure the handler is passed the
record.providerId so the correct provider is edited; if editing isn't supported
yet, remove the Button to avoid a dead control.
apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx-96-103 (1)

96-103: ⚠️ Potential issue | 🟡 Minor

Potential bug: formatDate receives stringified null.

When value (which is expiresAt: string | null) is null, String(value) produces the literal string "null", which is then passed to formatDate. The function only checks if (!dateString) which is falsy for empty string but not for "null". This results in new Date("null") producing an Invalid Date.

🐛 Fix the null handling
     {
       key: "expiresAt",
       render: (value) => (
-        <span className="text-sm text-muted-text">{formatDate(String(value))}</span>
+        <span className="text-sm text-muted-text">{formatDate(value as string | null)}</span>
       ),
       title: "Expires",
       width: "10%",
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx` around
lines 96 - 103, The render for the "expiresAt" column is stringifying null which
yields "null" and causes formatDate to produce an Invalid Date; update the
render in the expiresAt column (key "expiresAt") to check the raw value first
and only call formatDate when value is non-null/defined (e.g., render: (value)
=> value ? formatDate(value) : a sensible fallback like "-" or empty string), or
alternatively normalize to undefined instead of String(value) before passing
into formatDate so formatDate's falsy check works correctly.
apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx-114-122 (1)

114-122: ⚠️ Potential issue | 🟡 Minor

Missing delete confirmation.

The delete button immediately calls deleteApiKey without confirmation. Compare this to the OAuth providers section (line 181 in oauthProviders.tsx) which shows a confirmation dialog before removal. API key deletion is destructive and should have similar protection.

🛡️ Add confirmation before delete
           <Button
             className="h-7 px-2 text-destructive hover:text-destructive"
             noFocusRing
-            onClick={() => deleteApiKey(record.id)}
+            onClick={() => {
+              if (confirm(`Are you sure you want to delete this API key?`)) {
+                deleteApiKey(record.id)
+              }
+            }}
             size="xs"
             variant="ghost"
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx` around
lines 114 - 122, The delete Button currently calls deleteApiKey(record.id)
directly; change it to open a confirmation dialog/modal and only call
deleteApiKey(record.id) if the user confirms (match the behavior used in the
OAuth providers removal flow). Replace the onClick handler on the Button (the
Trash2 button) with a function that shows a confirmation prompt (reusing your
app's confirm/modal utility or the same pattern used in the oauth providers
section) and on confirm invoke deleteApiKey(record.id); keep the Button props
(className, noFocusRing, size, variant) unchanged so styling/behavior remains
identical.
🧹 Nitpick comments (17)
knip.json (1)

1-84: Consider adding ignore patterns for common artifacts.

The configuration works well as-is. Optionally, you might add ignore patterns to exclude common build outputs, test files, or configuration files that Knip shouldn't analyze. For example:

{
  "ignore": [
    "**/*.test.ts",
    "**/*.spec.ts",
    "**/dist/**",
    "**/build/**"
  ],
  "workspaces": {
    ...
  }
}

This can reduce noise in Knip's reports, but it's not required if the defaults work for your project.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@knip.json` around lines 1 - 84, Add a top-level "ignore" array to the
knip.json config (alongside the existing "workspaces" key) to exclude common
artifacts from analysis; add patterns such as "**/*.test.ts", "**/*.spec.ts",
"**/dist/**", and "**/build/**" so Knip skips test files and build outputs and
reduces noise in reports while keeping the existing workspace entries intact.
packages/bun-docker/src/docker.ts (1)

62-67: Consider adding JSDoc for the new public module fields.

For consistency with the rest of the Docker API surface, adding short doc blocks for plugins, secrets, and swarm would improve discoverability in editors/docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bun-docker/src/docker.ts` around lines 62 - 67, Add short JSDoc
one-line comments above the public readonly fields plugins, secrets, and swarm
on the Docker class to match the rest of the API surface; each comment should
briefly describe the module (e.g., "Access Docker plugins API", "Access Docker
secrets API", "Access Docker swarm API") and follow the existing doc style used
elsewhere in the file (single-line /** ... */ blocks so editors and generated
docs pick them up).
packages/sqlite-wrapper/src/utils/transformer.ts (1)

179-181: Avoid per-row info logs for normal transformations.

These completion logs will fire for each row and can flood production logs on large result sets. Prefer debug for routine transformation telemetry.

🧹 Proposed fix
-    logger.info(
+    logger.debug(
       `[FROM_DB] Completed | Transformed ${transformedColumns.length} columns: ${transformedColumns.join(", ")}`
     )
...
-    logger.info(
+    logger.debug(
       `[TO_DB] Completed | Transformed ${transformedColumns.length} columns: ${transformedColumns.join(", ")}`
     )

Also applies to: 285-287

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/utils/transformer.ts` around lines 179 - 181,
Change the per-row informational logs in transformer.ts to use debug-level
logging so routine transformations don't flood production logs: replace
logger.info calls that log transformedColumns (the log call that prints
`[FROM_DB] Completed | Transformed ${transformedColumns.length} columns:
${transformedColumns.join(", ")}` and the analogous call around the code that
starts at the other occurrence) with logger.debug (or guard them behind
logger.isDebugEnabled() if available) so the same message is emitted only at
debug verbosity; locate the calls by the transformedColumns variable and the
surrounding transformation completion text in the transformer utilities (both
the occurrence that logs the transformedColumns.join and the similar block at
the other occurrence).
packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts (1)

37-48: Include caught error object in failure logs.

Both catch blocks log only dbPath; the actual exception is dropped, which makes incident diagnosis harder.

Suggested fix
-  } catch (err) {
-    backupLog.error("[AUTO_BACKUP] Initial backup failed", dbPath)
+  } catch (err) {
+    backupLog.error("[AUTO_BACKUP] Initial backup failed", dbPath, err)
   }
...
-    } catch (err) {
-      backupLog.error("[AUTO_BACKUP] Scheduled backup run failed", dbPath)
+    } catch (err) {
+      backupLog.error("[AUTO_BACKUP] Scheduled backup run failed", dbPath, err)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts` around lines 37 -
48, The catch blocks in setupAutoBackup (the initial try/catch and the
setInterval callback around backup and applyRetentionPolicy) currently call
backupLog.error with only dbPath, dropping the caught error; update both catch
handlers to capture the caught error variable (err) and pass it or its
message/stack into backupLog.error alongside the existing context (e.g.,
"[AUTO_BACKUP] Initial backup failed" and "[AUTO_BACKUP] Scheduled backup run
failed"), referencing the same symbols (backupLog.error, backup,
applyRetentionPolicy, dbPath, autoBackupOptions) so logs include the actual
exception details for troubleshooting.
packages/auth/package.json (1)

7-7: Unpinned @types/bun version may cause reproducibility issues.

Using "latest" for @types/bun means builds could break unexpectedly when new versions are released. Consider pinning to a specific version or using a caret range.

Suggested fix
   "devDependencies": {
     "@dockstat/sqlite-wrapper": "workspace:*",
-    "@types/bun": "latest"
+    "@types/bun": "^1.0.0"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/package.json` at line 7, The dependency entry for "@types/bun"
in package.json is pinned to "latest", which hurts reproducibility; change it to
a specific semver (e.g., "x.y.z") or a caret range ("^x.y.z") instead of
"latest" so builds are deterministic—update the "@types/bun" value in
package.json and run your lockfile update (npm/yarn/pnpm install) to commit the
resulting lockfile changes.
packages/sqlite-wrapper/README.md (1)

502-512: Minor inconsistency: missing semicolon in code example.

Line 505 ends .all() without a semicolon while other examples in the README use semicolons consistently. This is a minor cosmetic issue in documentation.

Suggested fix
 const results = users
   .join(posts, { id: "user_id" })
-  .all()
+  .all();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/README.md` around lines 502 - 512, The README example
is missing a semicolon after the .all() call; update the snippet where results
is assigned (the line using users.join(posts, { id: "user_id" }).all()) to
terminate the statement with a semicolon so it matches the other examples and
maintains consistent punctuation for the results variable and the .join/.all()
usage.
packages/auth/src/server/handler/index.ts (1)

12-25: AuthHandler class has no methods.

The class initializes tables and Logger but provides no public methods to use them. This makes the class unusable in its current state. Consider adding methods for authentication operations, or exposing table accessors if this is intended as a data-holder.

Additionally, the Logger field uses PascalCase which typically indicates a class/type rather than an instance field. Consider renaming to logger for consistency with TypeScript conventions.

Suggested naming fix
 export class AuthHandler {
-  private Logger: Logger
+  private logger: Logger
   private tables: {
     UsersTable: QueryBuilder<UserTable>
     APITokensTable: QueryBuilder<ApiTokensTable>
     OIDCProvidersTable: QueryBuilder<OidcProvidersTable>
     OIDCIdentitiesTable: QueryBuilder<UserOidcIdentitiesTable>
   }

   constructor(db: DB, logger: Logger) {
-    this.Logger = logger.spawn("Auth")
+    this.logger = logger.spawn("Auth")
     this.tables = createAuthTables(db, logger)
   }
+
+  // TODO: Add authentication methods
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/server/handler/index.ts` around lines 12 - 25, AuthHandler
currently only stores Logger and tables and is unusable; rename the instance
field Logger to logger (update constructor and any references) to follow TS
conventions, and add at least one public API such as async
authenticate(credentials), async getUserById(id), or public getters for the
tables (e.g., get users() returning this.tables.UsersTable) so callers can
perform auth operations; ensure methods use this.logger and this.tables and are
implemented within the AuthHandler class (referencing AuthHandler,
createAuthTables, and the tables keys
UsersTable/APITokensTable/OIDCProvidersTable/OIDCIdentitiesTable).
packages/sqlite-wrapper/__test__/join-types.test.ts (1)

126-126: Remove debug comment.

// HALLO appears to be a leftover debug marker and should be removed.

Suggested fix
   })

-  // HALLO
   test("where(...) should work with merged type columns", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/__test__/join-types.test.ts` at line 126, Remove the
leftover debug comment "// HALLO" from the test file (join-types.test.ts) so no
stray debug markers remain; locate the commented line in the test block and
delete it, leaving the test code unchanged otherwise.
packages/auth/src/server/db/index.ts (1)

83-85: Preserve the original error as the cause.

new Error(String(error)) drops the original stack and SQLite metadata, which makes startup failures much harder to debug. Re-throw error when it's already an Error, or wrap it with { cause: error }.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/server/db/index.ts` around lines 83 - 85, The catch block
in auth/src/server/db/index.ts currently creates a new Error with String(error),
losing the original stack and SQLite metadata; instead, preserve the original
error by either re-throwing the caught error directly when it's already an Error
(throw error) or, if you must wrap it, create a new Error that includes the
original as the cause (new Error("Could not create Auth Tables", { cause: error
})), and update the logger.error call to include the error object so the
stack/metadata are logged (logger.error("Could not create Auth Tables!",
error)).
apps/dockstat/src/components/settings/accounts/index.tsx (2)

2-2: Unused import: Settings icon.

The Settings icon is imported but not used in this component. It appears to be used only in the child OAuthProvidersSection component which has its own import.

🧹 Remove unused import
-import { Key, Settings, Shield } from "lucide-react"
+import { Key, Shield } from "lucide-react"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/index.tsx` at line 2, Remove
the unused Settings import from the import statement that currently reads
"import { Key, Settings, Shield } from 'lucide-react'": edit the import to only
include the icons actually used in this file (e.g., Key and Shield) since the
Settings icon is not referenced here (it is imported and used inside
OAuthProvidersSection), so delete Settings from the named imports to eliminate
the unused import.

46-139: Consider extracting the repeated section header pattern.

Each of the three sections (Users, API Keys, OAuth Providers) follows the same nested Card header structure. This could be extracted into a reusable SectionCard component to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/index.tsx` around lines 46 -
139, The three repeated header blocks should be extracted into a reusable
SectionCard component: create a SectionCard(props) that accepts title: string,
icon: JSX.Element, children: ReactNode (or a prop for CardBody content) and any
Card props (variant/size/className), and renders the outer Card, the nested
header Card with icon + title, then a CardBody wrapper for children; then
replace the three manual header+CardBody blocks that wrap UsersSection,
ApiKeysSection and OAuthProvidersSection with SectionCard usage (pass
Shield/Key/Settings as icon, "User Management"/"API Keys"/"OAuth Providers" as
title, and move banUser/createUser/…/oauthProviders props into the children
components).
apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx (2)

7-12: Prop types don't reflect async nature of callbacks.

The callback props are typed as returning void, but the actual implementations from useAccounts are async functions returning Promise<void>. While this works due to how TypeScript handles void returns, updating the types to Promise<void> would be more accurate and allow consumers to await these operations if needed.

📝 More accurate typing
 interface ApiKeysSectionProps {
   apiKeys: ApiKey[]
-  createApiKey: (data: { name: string; referenceId: string }) => void
-  deleteApiKey: (id: string) => void
-  toggleApiKey: (id: string, enabled: boolean) => void
+  createApiKey: (data: { name: string; referenceId: string }) => Promise<void>
+  deleteApiKey: (id: string) => Promise<void>
+  toggleApiKey: (id: string, enabled: boolean) => Promise<void>
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx` around
lines 7 - 12, The prop types in ApiKeysSectionProps misrepresent the async
callbacks: update the signatures for createApiKey, deleteApiKey, and
toggleApiKey to return Promise<void> instead of void so they match the async
implementations from useAccounts; keep the same parameter shapes (createApiKey:
(data: { name: string; referenceId: string }) => Promise<void>, deleteApiKey:
(id: string) => Promise<void>, toggleApiKey: (id: string, enabled: boolean) =>
Promise<void>), and then adjust any local callers to await these promises where
needed.

32-34: Missing user feedback for clipboard copy.

The copyToClipboard function silently copies to clipboard without any visual feedback. Consider adding a toast notification or temporary visual indicator so users know the copy succeeded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx` around
lines 32 - 34, The copyToClipboard function in
apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx currently
writes silently to the clipboard; update copyToClipboard to handle the promise
from navigator.clipboard.writeText(text) and provide user feedback by showing a
success or error toast (or a temporary visual indicator state) when the copy
resolves or rejects. Locate the copyToClipboard function and any UI element that
triggers it, call the toast helper (or set a transient "copied" state) on
success and show an error toast on failure, and ensure the UI clears the
transient indicator after a short timeout.
packages/sqlite-wrapper/src/lib/backup/applyRetentionPolicy.ts (1)

32-37: Semantic mismatch: backupDir passed as requestId parameter.

The Logger's debug/error methods have signature (msg: string, requestId?: string). Passing backupDir as the second argument works but is semantically incorrect - it's a directory path, not a request ID. The logs will show the backup directory in the request ID field.

Consider embedding backupDir in the message string instead for clarity, or verify this is intentional for log correlation purposes.

💡 Suggested alternative
-        backupLog.debug(`[RETENTION] Removing old backup: ${file.name}`, backupDir)
+        backupLog.debug(`[RETENTION] Removing old backup: ${file.name} | Directory: ${backupDir}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/applyRetentionPolicy.ts` around lines
32 - 37, The log calls in applyRetentionPolicy are passing backupDir as the
second parameter to backupLog.debug/info/error (which is the requestId
parameter) causing a semantic mismatch; update calls that currently pass
backupDir (e.g., backupLog.debug(`[RETENTION] Removing old backup:
${file.name}`, backupDir), backupLog.info(..., backupDir) and
backupLog.error(`[RETENTION] Failed to apply retention policy | Error:
${error}`, backupDir)) to instead include backupDir inside the message string
(or omit the second argument) so the requestId field is not misused—search for
usages of backupLog.debug/backupLog.info/backupLog.error in applyRetentionPolicy
and change them accordingly.
apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx (2)

438-442: Hardcoded localhost URL in setup instructions.

The redirect URI shows http://localhost:3030/auth/callback/{provider} which is only valid during local development. In production, this would need to be the actual domain. Consider deriving this from the current window location or an environment variable.

💡 Use dynamic URL
                       <li>
                         Set the redirect URI to:{" "}
                         <code className="bg-muted/10 px-1 rounded">
-                          http://localhost:3030/auth/callback/{selectedProvider.id}
+                          {window.location.origin}/auth/callback/{selectedProvider.id}
                         </code>
                       </li>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx`
around lines 438 - 442, The hardcoded redirect URI should be built dynamically
instead of using http://localhost:3030; update the JSX that renders the redirect
URI (where it uses selectedProvider.id) to compute the base URL from a
config/env value (e.g., a VITE/REACT_APP style env var) with a fallback to
window.location.origin, and render
`${baseOrigin}/auth/callback/${selectedProvider.id}` so the displayed URI
matches production or local environments; ensure the symbol selectedProvider.id
is used unchanged and prefer an environment var like REACT_APP_BASE_URL or
VITE_BASE_URL for deployments.

52-58: Potential runtime error: OAUTH_SCOPES access for custom-oidc provider.

When selectedProvider.type === "oauth" but the provider is not in OAUTH_SCOPES (though current configs only have oauth providers that exist in OAUTH_SCOPES), the access OAUTH_SCOPES[selectedProvider.id as keyof typeof OAUTH_SCOPES] would return undefined. This is currently safe given the PROVIDER_CONFIGS, but adding a fallback would make it more robust.

🛡️ Add defensive fallback
     const scopes =
       selectedProvider.type === "oidc"
         ? selectedScopes.length > 0
           ? selectedScopes
           : ["openid", "profile", "email"]
-        : Array.from(OAUTH_SCOPES[selectedProvider.id as keyof typeof OAUTH_SCOPES])
+        : Array.from(OAUTH_SCOPES[selectedProvider.id as keyof typeof OAUTH_SCOPES] ?? [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx`
around lines 52 - 58, The current scopes computation can throw if
OAUTH_SCOPES[selectedProvider.id] is undefined for custom oauth providers;
update the ternary branch for the oauth case to safely fall back to an empty
array (or sensible default) before calling Array.from. Concretely, in the scopes
expression use a guarded lookup like const providerScopes =
OAUTH_SCOPES[selectedProvider.id as keyof typeof OAUTH_SCOPES] ?? []; then use
Array.from(providerScopes) (or providerScopes directly) so selectedProvider,
selectedScopes and OAUTH_SCOPES are referenced but undefined entries no longer
cause a runtime error.
apps/dockstat/src/components/settings/accounts/sections/useAccounts.tsx (1)

92-97: Unsafe type assertion from localStorage.

The value from localStorage.getItem() is cast directly to AdminModalOption without validation. If localStorage contains an unexpected value (corrupted data, manual tampering), this could lead to unexpected behavior.

🛡️ Add validation before casting
   useEffect(() => {
     const savedChoice = localStorage.getItem("dockstat-admin-modal-choice")
-    if (savedChoice) {
-      setAdminModalChoice(savedChoice as AdminModalOption)
+    if (savedChoice && ["yes", "no", "ask-later"].includes(savedChoice)) {
+      setAdminModalChoice(savedChoice as AdminModalOption)
     }
   }, [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/useAccounts.tsx`
around lines 92 - 97, The code reads
localStorage.getItem("dockstat-admin-modal-choice") and casts it directly to
AdminModalOption in the useEffect; instead validate the raw string against the
allowed AdminModalOption values before calling setAdminModalChoice to avoid
unsafe assertion. Implement or use a guard (e.g., isAdminModalOption or an array
of permitted values) to check the savedChoice is one of the enum/string union
members, and only then call setAdminModalChoice(savedChoice); otherwise ignore
it or set a safe default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0b42d9aa-6215-49b0-996d-8e26c614b0cb

📥 Commits

Reviewing files that changed from the base of the PR and between 71cd0e8 and 67412d8.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (53)
  • .zed/settings.json
  • apps/api/package.json
  • apps/api/src/auth/db.ts
  • apps/api/src/auth/index.ts
  • apps/api/src/database/index.ts
  • apps/api/src/elysia-plugins.ts
  • apps/api/src/index.ts
  • apps/api/tsconfig.json
  • apps/docknode/src/_treaty.ts
  • apps/dockstat/src/components/settings/accounts/index.tsx
  • apps/dockstat/src/components/settings/accounts/sections/adminModal.tsx
  • apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx
  • apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx
  • apps/dockstat/src/components/settings/accounts/sections/useAccounts.tsx
  • apps/dockstat/src/components/settings/accounts/sections/users.tsx
  • apps/dockstat/src/hooks/mutations/accounts.ts
  • apps/dockstat/src/hooks/mutations/index.ts
  • apps/dockstat/src/pages/settings.tsx
  • apps/dockstat/tsconfig.json
  • knip.json
  • packages/auth/.gitignore
  • packages/auth/README.md
  • packages/auth/package.json
  • packages/auth/src/client/index.ts
  • packages/auth/src/index.ts
  • packages/auth/src/server/db/index.ts
  • packages/auth/src/server/db/types.ts
  • packages/auth/src/server/db/utils.ts
  • packages/auth/src/server/handler/index.ts
  • packages/auth/src/server/index.ts
  • packages/auth/tsconfig.json
  • packages/bun-docker/src/docker.ts
  • packages/sqlite-wrapper/README.md
  • packages/sqlite-wrapper/__test__/join-types.test.ts
  • packages/sqlite-wrapper/__test__/transformer.test.ts
  • packages/sqlite-wrapper/package.json
  • packages/sqlite-wrapper/src/index.ts
  • packages/sqlite-wrapper/src/lib/backup/applyRetentionPolicy.ts
  • packages/sqlite-wrapper/src/lib/backup/backup.ts
  • packages/sqlite-wrapper/src/lib/backup/listBackups.ts
  • packages/sqlite-wrapper/src/lib/backup/restore.ts
  • packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts
  • packages/sqlite-wrapper/src/migration.ts
  • packages/sqlite-wrapper/src/query-builder/base.ts
  • packages/sqlite-wrapper/src/query-builder/delete.ts
  • packages/sqlite-wrapper/src/query-builder/index.ts
  • packages/sqlite-wrapper/src/query-builder/insert.ts
  • packages/sqlite-wrapper/src/query-builder/join.ts
  • packages/sqlite-wrapper/src/query-builder/select.ts
  • packages/sqlite-wrapper/src/query-builder/update.ts
  • packages/sqlite-wrapper/src/query-builder/where.ts
  • packages/sqlite-wrapper/src/types.ts
  • packages/sqlite-wrapper/src/utils/transformer.ts

Comment thread apps/api/src/auth/db.ts
@@ -0,0 +1,17 @@
import type DB from "@dockstat/sqlite-wrapper"
import { column } from "@dockstat/sqlite-wrapper"
import { DockStatDB } from "../database"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Cycle check (direct reciprocal imports):"
rg -n --type=ts -C1 'from "../database"' apps/api/src/auth/db.ts
rg -n --type=ts -C1 'from "../auth/db"' apps/api/src/database/index.ts

Repository: Its4Nik/DockStat

Length of output: 408


🏁 Script executed:

cat -n apps/api/src/auth/db.ts

Repository: Its4Nik/DockStat

Length of output: 637


🏁 Script executed:

grep -n "DockStatDB" apps/api/src/auth/db.ts

Repository: Its4Nik/DockStat

Length of output: 102


Remove unused import that creates circular module dependency.

DockStatDB is imported on line 3 but never used. This import creates a circular dependency: apps/api/src/auth/db.ts imports from ../database, while apps/api/src/database/index.ts imports getAuthTable from apps/api/src/auth/db.ts. Circular dependencies can cause partially initialized modules at runtime.

Suggested fix
-import { DockStatDB } from "../database"
📝 Committable suggestion

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

Suggested change
import { DockStatDB } from "../database"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/auth/db.ts` at line 3, Remove the unused import DockStatDB from
the top of the module to eliminate the circular dependency; specifically delete
the import statement that references DockStatDB (it is not used anywhere in this
file) and keep getAuthTable and related exports intact so other modules can
import them without causing partial initialization; run tests or a type-check to
confirm no remaining references and, if you need the type only, convert it to a
type-only import (e.g., using "import type") instead of a runtime import.

Comment thread apps/api/src/auth/db.ts
Comment on lines +12 to +13
pass: column.text(),
token: column.text(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not persist raw passwords or API tokens.

pass and token columns represent plaintext secret storage; compromise of the DB immediately compromises user credentials and API access.

Suggested schema direction
-      pass: column.text(),
-      token: column.text(),
+      passwordHash: column.text(),
+      tokenHash: column.text(),

Also update writers/verifiers to hash before insert and compare hash on authentication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/auth/db.ts` around lines 12 - 13, The schema currently persists
plaintext secrets in the columns named "pass" and "token"; change these to store
hashed values (rename to e.g., "pass_hash" and "token_hash" or document they
contain hashes) and keep their type as text, then update any insert/update logic
in the auth writers to hash secrets before saving and update
authentication/verifier code to compare using a secure hash verify call (use a
modern library such as bcrypt or argon2 for pass hashing and a suitable keyed
HMAC or one-way hash for API tokens). Ensure functions/methods that write to the
DB (writers) call the hashing routine before insert, and functions that
authenticate (verifiers) use the corresponding verify/compare routine (e.g.,
bcrypt.compare/argon2.verify) against "pass_hash"/"token_hash"; also plan a
migration/rotation strategy for existing plaintext entries.

Comment on lines +9 to +17
createApiKey(name: string) {
this.logger.info(`Creating API Key ${name}`)
const token = `DAPI-${Bun.randomUUIDv7("base64")}`
return this.table.insertAndGet({ name, token, type: "api-key" })
}

// biome-ignore lint/suspicious/noAssignInExpressions: From the better auth examples
const getSchema = async () => (_schema ??= auth.api.generateOpenAPISchema())
createUser(name: string, password: string) {
this.logger.info(`Creating user ${name}`)
return this.table.insertAndGet({ name, pass: password, type: "user" })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Never persist raw API keys or passwords.

createApiKey() stores the bearer token directly, and createUser() stores the password directly. A read-only DB leak becomes immediate credential compromise. Store a password hash plus a token digest instead, and return the raw API key only once at creation time. This also diverges from packages/auth/src/server/db/types.ts, which already models passwordHash and tokenHash.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/auth/index.ts` around lines 9 - 17, createApiKey and createUser
are persisting raw credentials; change them to hash secrets before storing and
return the raw API key only at creation: in createApiKey generate the raw token
(e.g. DAPI-...), compute a secure token digest (tokenHash) using a strong
KDF/HMAC, store { name, tokenHash, type: "api-key" } via
this.table.insertAndGet, and return the raw token to the caller only once; in
createUser compute a passwordHash (using bcrypt/argon2/scrypt) from the provided
password and store { name, passwordHash, type: "user" } instead of pass,
updating any field names to match packages/auth/src/server/db/types.ts and
keeping table.insertAndGet usage. Ensure error handling and consistent field
names (passwordHash/tokenHash) so raw secrets are never persisted.

Comment thread apps/api/tsconfig.json
Comment on lines +64 to +66
"paths": {
"@dockstat/api": ["./src/*"]
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For TypeScript paths, what is the correct configuration when you want both @dockstat/api(root module) and@dockstat/api/* (subpath modules) to resolve in the same project?

💡 Result:

The correct TypeScript paths configuration to resolve both @dockstat/api (root module, typically mapping to an index file like src/api/index.ts) and @dockstat/api/* (subpaths like @dockstat/api/utils) in the same project is to explicitly define both mappings in tsconfig.json. Assume your API code is in a src/api/ directory with an index.ts file re-exporting the main API, and submodules like src/api/utils.ts. { "compilerOptions": { "baseUrl": ".", "paths": { "@dockstat/api": ["src/api/index.ts"], "@dockstat/api/": ["src/api/"] } } } ### Key Points: - Both entries are required: TypeScript's path mapping algorithm uses longest prefix matching. - import { something } from "@dockstat/api" → exact match to "@dockstat/api" → resolves to src/api/index.ts - import { util } from "@dockstat/api/utils" → longest match "@dockstat/api/*" (wildcard captures "utils") → resolves to src/api/utils - Without the exact "@dockstat/api" entry, root imports fail because wildcards don't automatically resolve bare package names to index.ts. - baseUrl is mandatory for paths to work (set to "." for tsconfig root, or "src" if paths are relative to src). - Paths don't affect emitted JS (use bundlers like webpack/Vite or tsconfig-paths for runtime). ### Usage Examples: import { ApiClass } from "@dockstat/api"; // → src/api/index.ts import { helperFn } from "@dockstat/api/utils"; // → src/api/utils.ts ### Runtime Support: - Development: Use ts-node -r tsconfig-paths/register - Build tools: Configure webpack (tsconfig-paths-webpack-plugin), Vite (path: { '@dockstat/api': ... }), etc. - Node.js: Use package.json "imports" for native subpath support (TS 5.0+ with moduleResolution: "node16"). This pattern is confirmed across official docs, GitHub issues (#5039, #26859), and community examples.

Citations:


🏁 Script executed:

# Verify the file and lines in question
if [ -f "apps/api/tsconfig.json" ]; then
  echo "=== File exists ===" 
  sed -n '60,70p' apps/api/tsconfig.json
fi

Repository: Its4Nik/DockStat

Length of output: 1253


🏁 Script executed:

# Check if there's an entry point file
ls -la apps/api/src/ 2>/dev/null | head -20

Repository: Its4Nik/DockStat

Length of output: 1058


🏁 Script executed:

# Check how `@dockstat/api` is being imported in the codebase
rg "@dockstat/api" --type ts --type tsx -A 1 -B 1 | head -50

Repository: Its4Nik/DockStat

Length of output: 87


🏁 Script executed:

# Also check package.json to see if there's any related configuration
if [ -f "apps/api/package.json" ]; then
  cat apps/api/package.json
fi

Repository: Its4Nik/DockStat

Length of output: 1213


🏁 Script executed:

# Search for imports of `@dockstat/api` to see how it's used
rg "@dockstat/api" --type ts -A 1 -B 1 | head -80

Repository: Its4Nik/DockStat

Length of output: 218


🏁 Script executed:

# Also check the baseUrl setting in the tsconfig
grep -A 5 -B 5 "baseUrl" apps/api/tsconfig.json

Repository: Its4Nik/DockStat

Length of output: 947


Split the path alias into exact and wildcard entries to properly resolve root and subpath imports.

The current configuration "@dockstat/api": ["./src/*"] resolves root imports only by relying on implicit index file discovery. To correctly handle both @dockstat/api (root module) and @dockstat/api/* (subpaths), split into explicit mappings:

Suggested fix
 "paths": {
-  "@dockstat/api": ["./src/*"]
+  "@dockstat/api": ["./src/index.ts"],
+  "@dockstat/api/*": ["./src/*"]
 },
📝 Committable suggestion

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

Suggested change
"paths": {
"@dockstat/api": ["./src/*"]
},
"paths": {
"@dockstat/api": ["./src/index.ts"],
"@dockstat/api/*": ["./src/*"]
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/tsconfig.json` around lines 64 - 66, The path alias "@dockstat/api"
currently maps only to "./src/*" and therefore doesn't explicitly resolve the
package root import; update the tsconfig "paths" so you have two entries: an
exact key "@dockstat/api" mapped to the package root (e.g., "./src" or the root
entry file such as "./src/index.ts") and a wildcard key "@dockstat/api/*" mapped
to "./src/*" so both import "@dockstat/api" and "@dockstat/api/..." resolve
correctly.

Comment on lines +45 to +60
const handleCreate = () => {
if (!newUserName || !newUserEmail || !newUserPassword) return
createUser({ email: newUserEmail, name: newUserName, password: newUserPassword })
setNewUserName("")
setNewUserEmail("")
setNewUserPassword("")

setShowCreateForm(false)
}

const handleBan = () => {
if (!selectedUser || !banReason) return
banUser(selectedUser.id, banReason)
setShowBanModal(false)
setSelectedUser(null)
setBanReason("")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset the create/ban UI only after the mutation succeeds.

Both handlers clear local state immediately after calling createUser() / banUser(). If those callbacks fail asynchronously, the modal closes and the entered values are lost, leaving the admin with no retry path. Move the reset into an explicit success callback/promise resolution instead of doing it optimistically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/components/settings/accounts/sections/users.tsx` around
lines 45 - 60, handleCreate and handleBan currently clear form and close modals
immediately after calling createUser() / banUser(); change them to only reset
local state and close the UI after the mutation succeeds by awaiting the promise
or using the mutation's onSuccess callback. Specifically, in handleCreate use
the promise returned by createUser (or the createUser hook's onSuccess) to call
setNewUserName(""), setNewUserEmail(""), setNewUserPassword("") and
setShowCreateForm(false) only on success, and in handleBan call
banUser(...).then/onSuccess to call setShowBanModal(false),
setSelectedUser(null) and setBanReason("") only on success, and keep early
returns for invalid input unchanged. Ensure errors are still surfaced (do not
swallow promise rejections).


export const getUserByID = (id: string, table: QueryBuilder<UserTable>) =>
table.select(["*"]).where({ id: id }).first()
export const getUserByToken = (token: string, db: DB) => {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getUserByToken has an empty implementation.

This function accepts token and db parameters but has no implementation body. It will return undefined, which will likely cause issues when callers expect user data. Either implement the function or mark it as a stub with a TODO comment and appropriate return type.

Suggested stub with explicit TODO
-export const getUserByToken = (token: string, db: DB) => {}
+// TODO: Implement token-based user lookup
+export const getUserByToken = (token: string, db: DB): null => {
+  throw new Error("getUserByToken not implemented")
+}

Would you like me to help implement this function or open an issue to track it?

📝 Committable suggestion

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

Suggested change
export const getUserByToken = (token: string, db: DB) => {}
// TODO: Implement token-based user lookup
export const getUserByToken = (token: string, db: DB): never => {
throw new Error("getUserByToken not implemented")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/server/db/utils.ts` at line 6, getUserByToken currently has
an empty body; implement it to return the user associated with the given token
(or explicitly return null) instead of undefined. Locate the
getUserByToken(token: string, db: DB) function and either: 1) implement the
lookup by calling your DB's session/token methods (e.g.,
db.findSessionByToken(token) then db.getUserById(session.userId) or
db.findUserByToken(token)) and return the User or null (make the function
async/return Promise<User | null> if DB calls are async), or 2) if the
repository API is not available yet, add a clear TODO and return null with the
proper return type (or throw a NotImplementedError) so callers don't receive
undefined; ensure the function signature and JSDoc/type annotations reflect the
chosen return type.

Comment on lines 13 to 15
if (dbPath === ":memory:") {
backupLog.warn("Auto-backup is not available for in-memory databases")
backupLog.warn("[AUTO_BACKUP] Not available for in-memory databases", dbPath)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Short-circuit auto-backup for in-memory databases.

After warning, execution continues and still configures backup flow. For :memory:, return early with timer: null to avoid repeated failing work.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/lib/backup/setupAutoBackup.ts` around lines 13 -
15, The function currently logs and continues when dbPath === ":memory:",
causing the backup flow to still be configured; modify setupAutoBackup so after
calling backupLog.warn("[AUTO_BACKUP] Not available for in-memory databases",
dbPath) it immediately returns the expected shape with timer: null (and any
other required return fields) to short-circuit setup and avoid scheduling or
running backup work for in-memory databases; locate the check using dbPath and
backupLog.warn in setupAutoBackup and add the early return there.

Comment on lines +183 to +186
if (typeof condition === "string") {
// Raw expression condition
onClause = ` ON ${condition}`
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handle CROSS JOIN without an ON clause.

crossJoin() records condition: "", and this branch renders every string condition as ON ${condition}. The generated SQL becomes ... CROSS JOIN ... ON , which is invalid SQLite syntax. Special-case type === "CROSS" here, or reject empty string conditions for every non-cross join.

Proposed fix
-      if (typeof condition === "string") {
-        // Raw expression condition
-        onClause = ` ON ${condition}`
+      if (type === "CROSS") {
+        onClause = ""
+      } else if (typeof condition === "string") {
+        if (!condition.trim()) {
+          throw new Error("JOIN condition cannot be empty")
+        }
+        onClause = ` ON ${condition}`
       } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/query-builder/base.ts` around lines 183 - 186,
The code currently treats any string condition as an ON clause (setting onClause
= ` ON ${condition}`), which produces invalid SQL for crossJoin() which records
condition as "" — update the branch in the query builder that sets onClause so
that when condition is a string and either non-empty or the join type is not
"CROSS" you render ` ON ${condition}`, but if type === "CROSS" and condition ===
"" omit the ON clause entirely; alternatively validate/throw for empty string
conditions on non-CROSS joins. Refer to the condition variable, onClause
assignment, and the join type check (type === "CROSS") and the crossJoin()
call-site to implement this special-case.

Comment on lines +191 to +272
join<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
condition: JoinCondition,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.join<JT>(table, condition, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}

/**
* Add an INNER JOIN clause.
*
* @template JT - The joined table type
* @returns A new QueryBuilder with the merged result type (ResultType & JT)
*/
innerJoin<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
condition: JoinCondition,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.innerJoin<JT>(table, condition, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}

/**
* Add a LEFT JOIN clause.
*
* @template JT - The joined table type
* @returns A new QueryBuilder with the merged result type (ResultType & JT)
*/
leftJoin<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
condition: JoinCondition,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.leftJoin<JT>(table, condition, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}

/**
* Add a RIGHT JOIN clause.
*
* @template JT - The joined table type
* @returns A new QueryBuilder with the merged result type (ResultType & JT)
*/
rightJoin<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
condition: JoinCondition,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.rightJoin<JT>(table, condition, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}

/**
* Add a FULL JOIN clause.
*
* @template JT - The joined table type
* @returns A new QueryBuilder with the merged result type (ResultType & JT)
*/
fullJoin<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
condition: JoinCondition,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.fullJoin<JT>(table, condition, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}

/**
* Add a CROSS JOIN clause.
*
* @template JT - The joined table type
* @returns A new QueryBuilder with the merged result type (ResultType & JT)
*/
crossJoin<JT extends Record<string, unknown>>(
table: QueryBuilder<JT, JT>,
alias?: string
): QueryBuilder<T, ResultType & JT> {
this.selectBuilder.crossJoin<JT>(table, alias)
return this as unknown as QueryBuilder<T, ResultType & JT>
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These JOIN methods retype the same mutable instance instead of returning a new typed builder.

Each method mutates this.selectBuilder and then casts this to QueryBuilder<T, ResultType & JT>. That breaks the advertised type-safety: any older reference to the same builder keeps the old static type but now runs the joined query at runtime. Either clone the builder before adding the join, or keep the original generic and avoid claiming that a fresh merged-type builder was returned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/query-builder/index.ts` around lines 191 - 272,
The join methods (join, innerJoin, leftJoin, rightJoin, fullJoin, crossJoin)
currently mutate this.selectBuilder and then cast and return this as
QueryBuilder<T, ResultType & JT>, which lies about the instance's type; fix by
either (A) cloning the builder state before mutating: create a new QueryBuilder
instance (copying current selectBuilder, where clauses, params, etc.), call the
corresponding selectBuilder.*Join on the clone, and return that new
QueryBuilder<T, ResultType & JT>, or (B) preserve correctness by not changing
the generic result type and instead return this (remove the cast) so the
signature reflects the actual mutated instance; choose one approach and apply it
consistently to all listed methods (join, innerJoin, leftJoin, rightJoin,
fullJoin, crossJoin).

} catch {
// Keep original value if JSON parsing fails
logger.warn(`Failed to parse JSON column: ${columnKey}`)
logger.warn(`[FROM_DB] Failed to parse JSON column: ${value}`, columnKey)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not log raw DB values in warning paths.

Lines 67 and 150 include raw column values in warning logs. This can expose sensitive data/PII to log sinks.

🔒 Proposed fix
-          logger.warn(`[FROM_DB] Failed to parse JSON column: ${value}`, columnKey)
+          logger.warn(`[FROM_DB] Failed to parse JSON column`, columnKey)
...
-          logger.warn(`[FROM_DB] Failed to parse DATE column Value: ${value}`, columnKey)
+          logger.warn(`[FROM_DB] Failed to parse DATE column`, columnKey)

Also applies to: 150-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sqlite-wrapper/src/utils/transformer.ts` at line 67, The warning
logs in transformer.ts currently include raw DB values (the logger.warn call
that builds "[FROM_DB] Failed to parse JSON column: ${value}", columnKey and the
equivalent at the second occurrence), which can leak sensitive data; change both
logger.warn usages to avoid logging the raw value — log the columnKey and error
details (e.g. error.message) and, if useful, a non-sensitive artifact such as
the value length or a fixed "<REDACTED>" placeholder instead of the actual
value; update the two logger.warn calls that reference "[FROM_DB] Failed to
parse JSON column" so they never interpolate the DB value directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant