refactor(db): collapse repository rollout scaffolding into single factory

Repositories are now the only data path. Replaces the 41 current-*-repository
wrapper files, the DATABASE_LAYER_REPOSITORY_ROLLOUT flag/alias map and the
unused database/runtime adapter with repositories/factory.ts, a plain
DatabaseContext type and an in-memory TestSqliteDatabase test harness.
This commit is contained in:
LukeGus
2026-07-15 23:56:02 -05:00
parent 1e7c9ea53c
commit ef4969dd35
217 changed files with 843 additions and 4297 deletions
File diff suppressed because one or more lines are too long
-548
View File
@@ -1,548 +0,0 @@
# Database Layer Refactor Phase 0 Audit
Status: Draft
Branch: `feature/database-layer-refactor`
Purpose: Establish the current database access inventory, domain map, sensitive field map, and first implementation boundaries before changing runtime persistence.
## 1. Scope
Phase 0 does not change runtime behavior.
It produces the evidence needed for the next implementation phases:
- where database access currently happens
- which modules write directly to the database
- which tables belong to which product domains
- which fields are sensitive
- which direct writes are risky under the current in-memory snapshot model
- which domains should move first into repositories
## 2. Current Evidence
Commands used for the initial audit:
```bash
rg -n "getDb\\(|getSqlite\\(|DatabaseSaveTrigger|SimpleDBOps|db\\.\\$client|\\.prepare\\(" src/backend
rg -n "getDb\\(\\)\\.(insert|update|delete)|await db\\.(insert|update|delete)|db\\.(insert|update|delete)|\\.\\$client\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|DatabaseSaveTrigger\\.triggerSave|DatabaseSaveTrigger\\.forceSave" src/backend
rg -n "export const .* = sqliteTable\\(" src/backend/database/db/schema.ts
```
High-level findings:
- Database infrastructure is concentrated in `src/backend/database/db/index.ts`.
- Business database access is spread across route modules, SSH modules, utilities, and auth helpers.
- `SimpleDBOps` is not the only write path.
- There are many direct Drizzle writes and raw SQLite writes.
- Some direct writes manually trigger `DatabaseSaveTrigger`; many write paths do not.
- Schema is SQLite-specific through `sqliteTable` and manual `CREATE TABLE IF NOT EXISTS` / `addColumnIfNotExists`.
## 3. Database Access Hotspots
The most database-heavy files by audit hits:
| File | Approx. hits | Notes |
| ----------------------------------------------------- | -----------: | --------------------------------------------------------------- |
| `src/backend/database/db/index.ts` | 75 | database init, schema creation, ad-hoc migration, snapshot save |
| `src/backend/database/routes/alert-rules-routes.ts` | 64 | alert rules, channels, firings, raw SQL deletes |
| `src/backend/database/routes/users.ts` | 54 | users, settings, OIDC/GitHub settings, admin flows |
| `src/backend/database/database.ts` | 27 | import/export, legacy SQLite handling |
| `src/backend/ssh/host-metrics.ts` | 26 | metrics connection and settings reads |
| `src/backend/database/routes/user-settings-routes.ts` | 16 | user settings |
| `src/backend/dashboard.ts` | 15 | dashboard aggregation reads |
| `src/backend/ssh/docker.ts` | 14 | host/credential reads for Docker SSH |
| `src/backend/ssh/managers/health.ts` | 13 | host health checks |
| `src/backend/ssh/alert-engine.ts` | 13 | alert evaluation and firing state |
| `src/backend/utils/user-crypto.ts` | 12 | settings writes for crypto metadata |
| `src/backend/ssh/host-metrics-settings-routes.ts` | 12 | metrics settings |
| `src/backend/ssh/tmux-monitor.ts` | 11 | tmux session tags |
| `src/backend/guacamole/routes.ts` | 11 | Guacamole config/routes |
| `src/backend/database/routes/host.ts` | 10 | host operations and related rows |
This confirms the refactor must be domain-by-domain. A mechanical replacement of `getDb()` would be noisy and unsafe.
## 4. Direct Write Risk Inventory
Under the current architecture, a direct write is risky when it bypasses `SimpleDBOps` and does not trigger `DatabaseSaveTrigger`.
### 4.1 Direct Writes That Need Repository Ownership
These areas perform direct writes and should move behind repositories/services:
| Area | Representative files | Examples |
| --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
| users/settings/auth | `routes/users.ts`, `utils/auth-manager.ts`, `utils/user-crypto.ts` | sessions, trusted devices, OIDC settings, registration settings |
| RBAC/sharing | `routes/rbac.ts`, `utils/shared-credential-manager.ts` | roles, host access, snippet access, shared credentials |
| hosts/credentials | `routes/host.ts`, `routes/credentials.ts`, `host-resolver.ts` | host access cleanup, credential usage |
| file manager metadata | `host-file-manager-bookmark-routes.ts` | recent, pinned, shortcuts |
| alerts | `alert-rules-routes.ts`, `ssh/alert-engine.ts` | channels, rules, firings |
| metrics | `host-metrics-preferences-routes.ts`, `managers/health.ts` | preferences, health checks, history |
| terminal logs | `terminal-session-manager.ts` | session recording metadata |
| tmux | `tmux-monitor.ts` | tmux session tags |
| import/export | `database/database.ts` | SQLite import and forced save |
| open tabs | `routes/open-tabs.ts` | tab persistence and cleanup |
| API keys | `user-api-key-routes.ts`, `utils/auth-manager.ts` | API key create/delete/last-used |
| SSO and identity | `sso-provider-routes.ts`, `termix-id.ts` | providers, identity keys/CA |
### 4.2 Immediate Compatibility Rule
Until a domain is migrated to repositories:
- Every direct write must either be moved into a repository or explicitly trigger persistence in the old runtime.
- New code should not add direct `getDb()` writes outside infrastructure or repositories.
- The draft branch should add an enforcement check before Phase 8, not immediately, because the current codebase still violates the target rule widely.
## 5. Table Domain Map
### 5.1 Identity and Authentication
| Tables | Notes |
| ---------------------- | -------------------------------------------- |
| `users` | local/OIDC users, TOTP fields, password hash |
| `sessions` | JWT sessions |
| `trusted_devices` | remembered devices |
| `api_keys` | API token hashes/prefixes |
| `sso_providers` | configured SSO providers |
| `termix_identities` | Termix identity records |
| `termix_identity_keys` | public/private identity key metadata |
| `termix_identity_ca` | CA material, private key is sensitive |
Suggested repository:
- `userRepository`
- `sessionRepository`
- `trustedDeviceRepository`
- `apiKeyRepository`
- `ssoProviderRepository`
- `termixIdentityRepository`
### 5.2 Hosts and Credentials
| Tables | Notes |
| ---------------------- | ------------------------------------------------ |
| `ssh_data` | primary host table, many config JSON/text fields |
| `ssh_credentials` | reusable credentials |
| `ssh_credential_usage` | usage history |
| `ssh_folders` | folder metadata |
| `host_access` | sharing/RBAC access rows |
| `shared_credentials` | encrypted shared credential material |
| `network_topology` | topology graph/config |
Suggested repository:
- `hostRepository`
- `credentialRepository`
- `credentialUsageRepository`
- `hostAccessRepository`
- `sharedCredentialRepository`
- `hostFolderRepository`
- `networkTopologyRepository`
### 5.3 RBAC
| Tables | Notes |
| ---------------- | ------------------------ |
| `roles` | role definitions |
| `user_roles` | user to role assignments |
| `host_access` | host-level grants |
| `snippet_access` | snippet-level grants |
Suggested repository:
- `roleRepository`
- `accessRepository`
### 5.4 File Manager
| Tables | Notes |
| ------------------------ | ---------------------------------- |
| `file_manager_recent` | recently opened paths |
| `file_manager_pinned` | pinned paths |
| `file_manager_shortcuts` | saved shortcuts |
| `transfer_recent` | host-to-host transfer destinations |
Suggested repository:
- `fileManagerRepository`
- `transferRecentRepository`
### 5.5 Snippets
| Tables | Notes |
| ----------------- | ----------------------- |
| `snippets` | command snippets |
| `snippet_folders` | snippet folder metadata |
| `snippet_access` | snippet sharing |
Suggested repository:
- `snippetRepository`
### 5.6 Runtime Metadata and Audit
| Tables | Notes |
| -------------------- | ------------------------------------------------------ |
| `audit_logs` | append-only audit trail |
| `session_recordings` | terminal recording metadata, log content is file-based |
| `recent_activity` | host activity feed |
| `command_history` | terminal command history |
| `user_open_tabs` | UI restore state; currently cleared on startup |
| `user_preferences` | per-user UI/preferences |
| `settings` | global settings |
Suggested repository:
- `auditRepository`
- `sessionRecordingRepository`
- `activityRepository`
- `commandHistoryRepository`
- `openTabsRepository`
- `userPreferencesRepository`
- `settingsRepository`
### 5.7 Metrics and Alerts
| Tables | Notes |
| -------------------------- | -------------------------- |
| `host_metrics_preferences` | metrics layout/preferences |
| `host_health_checks` | health check definitions |
| `host_health_history` | health check results |
| `host_metrics_history` | metrics history |
| `alert_rules` | alert definitions |
| `notification_channels` | webhook/ntfy/etc config |
| `alert_rule_channels` | rule/channel joins |
| `alert_firings` | firing/ack state |
| `dismissed_alerts` | dismissed system alerts |
Suggested repository:
- `metricsRepository`
- `healthCheckRepository`
- `alertRepository`
- `notificationRepository`
### 5.8 Integrations and Feature Config
| Tables | Notes |
| ------------------------- | --------------------------------------- |
| `c2s_tunnel_presets` | tunnel preset config |
| `opkssh_tokens` | OPKSSH cert/private key cache |
| `vault_profiles` | Vault profile config, mostly non-secret |
| `vault_tokens` | Vault cert/private key cache |
| `dashboard_service_links` | dashboard links |
| `homepage_items` | homepage widgets/items |
| `homepage_layouts` | homepage layouts |
| `tmux_session_tags` | tmux tag metadata |
Suggested repository:
- `tunnelPresetRepository`
- `opksshTokenRepository`
- `vaultRepository`
- `dashboardRepository`
- `homepageRepository`
- `tmuxRepository`
## 6. Sensitive Field Map
The current explicit `FieldCrypto` map encrypts:
| Table | Fields |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `users` | `passwordHash`, `clientSecret`, `totpSecret`, `totpBackupCodes`, `oidcIdentifier` |
| `ssh_data` | `password`, `key`, `keyPassword`, `sudoPassword`, `autostartPassword`, `autostartKey`, `autostartKeyPassword`, `socks5Password`, `rdpPassword`, `vncPassword`, `telnetPassword` |
| `ssh_credentials` | `password`, `privateKey`, `keyPassword`, `key`, `publicKey` |
| `opkssh_tokens` | `sshCert`, `privateKey` |
| `termix_identity_ca` | `privateKey` |
| `vault_tokens` | `sshCert`, `privateKey` |
Additional sensitive fields by table semantics:
| Table | Fields / reason |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ssh_credentials` | `systemPassword`, `systemKey`, `systemKeyPassword` are system-key encrypted credential copies |
| `shared_credentials` | `encryptedUsername`, `encryptedAuthType`, `encryptedPassword`, `encryptedKey`, `encryptedKeyPassword`, `encryptedKeyType` are already encrypted payload fields |
| `api_keys` | `tokenHash` is not plaintext but is authentication material; `tokenPrefix` may remain plaintext for display |
| `settings` | some keys may hold provider secrets or reset codes; repository must classify by key |
| `notification_channels` | config may include webhook URLs/tokens; treat config as sensitive unless split |
| `alert_rules` | rule definitions are usually not secret but can include host/resource metadata |
| `homepage_items` | widget config can include URLs/API config; classify per widget type |
| `c2s_tunnel_presets` | config can include connection details; review before plaintext external DB storage |
| `termix_identity_keys` | inspect key material fields before migration; public keys are not secret but private material must never be plaintext |
| `vault_profiles` | current comments say profile fields are non-secret; keep that invariant explicit |
Open privacy decision:
- `ssh_data.ip`, domain fields, usernames, folders, and tags are queryable today.
- Encrypting them improves confidentiality but breaks search/filter/sort unless blind indexes are added.
- Recommended initial migration: keep them plaintext and document privacy implications; add optional privacy mode later.
## 7. Repository Migration Order
Use a vertical slice instead of broad replacement.
### 7.1 First Slice
1. `settingsRepository`
2. `userRepository`
3. `sessionRepository`
4. `hostRepository`
5. `credentialRepository`
Reasons:
- Covers the app's boot/login/core host management path.
- Exercises field encryption.
- Exercises per-user data unlock requirements.
- Exercises transaction and migration behavior.
- Builds reusable patterns for the rest of the backend.
### 7.2 Second Slice
1. `hostAccessRepository`
2. `roleRepository`
3. `sharedCredentialRepository`
4. `auditRepository`
5. `userPreferencesRepository`
Reasons:
- Completes permission and sharing boundaries.
- Removes high-risk direct writes in admin/RBAC flows.
- Moves audit to an append-only repository.
### 7.3 Third Slice
1. `snippetRepository`
2. `fileManagerRepository`
3. `metricsRepository`
4. `alertRepository`
5. `homepageRepository`
Reasons:
- These are broad feature domains with many routes.
- They should reuse patterns from the core slices.
### 7.4 Fourth Slice
1. `vaultRepository`
2. `opksshTokenRepository`
3. `termixIdentityRepository`
4. `tunnelPresetRepository`
5. `tmuxRepository`
Reasons:
- Sensitive token/key cache domains need careful encryption tests.
- Some data is transient and should have retention cleanup.
## 8. Adapter Design Notes
The adapter boundary should expose:
```ts
interface DatabaseAdapter {
dialect: "sqlite" | "postgres" | "mysql";
connect(): Promise<void>;
close(): Promise<void>;
migrate(): Promise<void>;
transaction<T>(fn: (tx: DatabaseTransaction) => Promise<T>): Promise<T>;
}
```
The repository layer should not depend on `better-sqlite3`.
For Drizzle, likely options:
- keep dialect-specific Drizzle clients internally
- expose repositories instead of exposing the raw Drizzle client
- keep schema definitions close to migrations, not route handlers
Important: do not make route modules import dialect-specific schema objects after migration.
## 9. Compatibility Shims
During the migration, avoid a flag day.
Add a compatibility database module that lets old code continue to run while new repositories are introduced:
```text
legacy getDb() path
new adapter/repository path
```
Rules:
- New modules use repositories only.
- Migrated modules must not fall back to `getDb()`.
- Legacy path is removed only after all domains move.
## 10. Phase 1 Entry Criteria
Before implementing the adapter skeleton:
- This audit document exists.
- The sensitive field map is reviewed.
- The first vertical slice is accepted.
- The draft PR remains draft.
- No runtime behavior has changed.
## 11. Phase 1 Deliverables
Recommended first implementation PR on this branch:
- `src/backend/database/runtime/config.ts`
- `src/backend/database/runtime/adapter.ts`
- `src/backend/database/runtime/sqlite-adapter.ts`
- `src/backend/database/repositories/settings-repository.ts`
- `src/backend/database/repositories/user-repository.ts`
- `src/backend/database/repositories/session-repository.ts`
- `src/backend/database/repositories/host-repository.ts`
- `src/backend/database/repositories/credential-repository.ts`
- `src/backend/database/repositories/field-encryption-boundary.ts`
- `src/backend/database/repositories/current-settings-repository.ts`
- tests for config parsing and SQLite adapter boot
Started:
- runtime config parser
- SQLite adapter skeleton
- migration metadata table bootstrap
- `SettingsRepository` skeleton and tests
- `UserRepository` and `SessionRepository` skeletons and tests
- `HostRepository` and `CredentialRepository` skeletons and tests
- `FieldEncryptionBoundary` skeleton and tests
- first settings route slice wired through `SettingsRepository`
- user settings route direct `settings` table access moved behind
`SettingsRepository`
- host metrics settings route direct `settings` table access moved behind
`SettingsRepository`
- ACME SSL settings route direct `settings` table access moved behind
`SettingsRepository`
- terminal route direct `settings` table access moved behind
`SettingsRepository`
- tailscale route direct `settings` table access moved behind
`SettingsRepository`
- Guacamole route and WebSocket server direct `settings` table access moved
behind the current settings repository boundary
- auth token expiry and terminal session timeout settings reads moved behind the
current settings repository boundary
- open tabs, TOTP, and LDAP auth route settings reads moved behind the current
settings repository boundary
- host metrics polling settings reads moved behind the current settings
repository boundary
- backend startup settings reads moved behind the current settings repository
boundary
- user deletion cleanup now removes per-user settings through
`SettingsRepository.deleteLike`
- password reset route reset code and temporary token settings access moved
behind `SettingsRepository`
- OIDC utility legacy config fallback reads `oidc_config` through
`SettingsRepository`
- user route registration/password flags and OIDC config administration moved
behind the current settings repository boundary
- OIDC authorize/callback temporary state and auto-provision reads moved behind
the current settings repository boundary
- user login settings reads moved behind the current settings repository
boundary, completing direct `settings` access cleanup in `routes/users.ts`
- user encryption metadata in `utils/user-crypto.ts` moved behind the current
settings repository boundary
- database startup and schema migration defaults in `database/db/index.ts`
moved to local raw settings helpers
- database import/export settings handling in `database/database.ts` moved to
local helper boundaries
- core `auth-manager.ts` session create/read/update/revoke/list paths started
using the current session repository boundary
- remaining `auth-manager.ts` session cleanup/middleware/logout paths and
`user-session-routes.ts` single-session lookup moved behind the current
session repository boundary
- current user repository factory/write-save hook added, and
`user-admin-routes.ts` list/admin promotion/admin removal/admin-create user
paths moved behind the current user repository boundary
- low-risk `routes/users.ts` current-user lookup and admin gate checks moved
behind the current user repository boundary
- user registration, self-delete, password change hash updates, and admin
delete-user lookup paths in `routes/users.ts` moved behind the current user
repository boundary, with first-user admin creation kept transactional inside
`UserRepository`
- traditional login username lookup and `auth-manager.ts` admin user checks
moved behind the current user repository boundary
- GitHub and standard OIDC callback user lookup/create/rollback/profile/admin
sync writes moved behind the current user repository boundary, removing direct
Drizzle `users` table access from `routes/users.ts`, `user-admin-routes.ts`,
and `auth-manager.ts`
- API key create/list/delete and API key authentication last-used updates moved
behind the current API key repository boundary
- trusted device check/add/remove and TOTP trusted-device cleanup moved behind
the current trusted device repository boundary
- user session routes moved user/admin lookups and admin session username
enrichment behind the current user repository boundary
- vault admin checks, Termix ID audit username lookup, permission manager admin
checks, and user data export user lookup moved behind the current user
repository boundary
- SSH credential OIDC username expansion and tmux monitor audit actor username
lookup moved behind the current user repository boundary
- user settings route admin checks and audit actor username lookups moved behind
the current user repository boundary
- ACME SSL route admin checks and audit actor username lookups moved behind the
current user repository boundary
- audit log route admin checks moved behind the current user repository boundary
- OIDC account link/unlink route user lookups and OIDC field updates moved
behind the current user repository boundary
- password reset route user lookups, password hash updates, and TOTP reset
fields moved behind the current user repository boundary
- user deletion helper now removes sessions through the current session
repository and the final user record through the current user repository
- snippet create/update/delete audit actor username lookups moved behind the
current user repository boundary
- LDAP login existing-user lookup, encryption rollback delete, admin sync, and
display-name sync moved behind the current user repository boundary
- TOTP setup/enable/disable/backup-code/login verification user updates and
session revocation moved behind the current user/session repository
boundaries
- RBAC host sharing, role assignment, and snippet sharing target-user existence
checks moved behind the current user repository boundary, with existing
RBAC/snippet owner username joins retained
- RBAC role list/create/update/delete, user-role assignment/removal/listing,
and shared host/snippet role-id lookups moved behind the current role
repository boundary, with existing RBAC/share credential joins retained
- permission manager role permission aggregation, role-id lookups for shared
host access, and admin role checks moved behind the current role repository
boundary
- RBAC host/snippet access-list read models moved behind the current RBAC access
repository boundary, and snippet route shared-access role-id lookups moved
behind the current role repository boundary
- RBAC shared host/shared snippet read models and the main snippet
shared-snippet read model moved behind the current RBAC access repository
boundary
- RBAC host/snippet access grant, revoke, and direct host-access credential
override writes moved behind the current RBAC access repository boundary,
with shared credential material creation retained in the existing manager
- permission manager host-access expiration cleanup, shared host-access lookup,
and last-access timestamp updates moved behind the current RBAC access
repository boundary
- repository rollout guard added through `DATABASE_LAYER_REPOSITORY_ROLLOUT`
for the migrated settings/users/sessions/API-key/trusted-device/role/RBAC-access
slice
Keep it small. Do not wire host or credential routes into the new repositories in
the same first implementation commit.
## 12. Current Unknowns
- Exact Drizzle multi-dialect strategy needs a spike.
- The project may need a migration generator or a custom migration runner.
- Some raw SQL in `routes/users.ts`, `alert-rules-routes.ts`, and `db/index.ts` must be rewritten or isolated.
- `settings` contains mixed public and sensitive values; key-level classification is required.
- `homepage_items.config`, `notification_channels.config`, and tunnel configs may contain embedded secrets.
- Legacy encrypted snapshot fixtures need to be created before migration implementation.
## 13. Decision Log
- Default upgrade target should be persistent SQLite, not PostgreSQL/MySQL.
- PostgreSQL/MySQL migration should be explicit and initially manual/experimental.
- Runtime SSH/WebSocket/tunnel state remains memory-only.
- Field-level encryption is mandatory for every database backend.
- Field-level encryption must use a stable record id; temporary encryption
contexts are forbidden for newly written repository data.
- Repository migration should start with settings/users/sessions/hosts/credentials.
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -5,10 +5,12 @@ import { dashboardLogger } from "./utils/logger.js";
import { AuthManager } from "./utils/auth-manager.js"; import { AuthManager } from "./utils/auth-manager.js";
import type { AuthenticatedRequest } from "../types/index.js"; import type { AuthenticatedRequest } from "../types/index.js";
import { dashboardServiceLinksRouter } from "./database/routes/dashboard-service-links-routes.js"; import { dashboardServiceLinksRouter } from "./database/routes/dashboard-service-links-routes.js";
import { createCurrentHostResolutionRepository } from "./database/repositories/current-host-resolution-repository.js"; import {
import { createCurrentRbacAccessRepository } from "./database/repositories/current-rbac-access-repository.js"; createCurrentHostResolutionRepository,
import { createCurrentRecentActivityRepository } from "./database/repositories/current-recent-activity-repository.js"; createCurrentRbacAccessRepository,
import { createCurrentRoleRepository } from "./database/repositories/current-role-repository.js"; createCurrentRecentActivityRepository,
createCurrentRoleRepository,
} from "./database/repositories/factory.js";
import { DataCrypto } from "./utils/data-crypto.js"; import { DataCrypto } from "./utils/data-crypto.js";
const app = express(); const app = express();
+10 -10
View File
@@ -34,15 +34,16 @@ import { DatabaseFileEncryption } from "../utils/database-file-encryption.js";
import { DatabaseMigration } from "../utils/database-migration.js"; import { DatabaseMigration } from "../utils/database-migration.js";
import { UserDataExport } from "../utils/user-data-export.js"; import { UserDataExport } from "../utils/user-data-export.js";
import { AutoSSLSetup } from "../utils/auto-ssl-setup.js"; import { AutoSSLSetup } from "../utils/auto-ssl-setup.js";
import { createCurrentCredentialRepository } from "./repositories/current-credential-repository.js"; import {
import { createCurrentDismissedAlertRepository } from "./repositories/current-dismissed-alert-repository.js"; createCurrentCredentialRepository,
import { createCurrentFileManagerBookmarkRepository } from "./repositories/current-file-manager-bookmark-repository.js"; createCurrentDismissedAlertRepository,
import { createCurrentHostRepository } from "./repositories/current-host-repository.js"; createCurrentFileManagerBookmarkRepository,
import { createCurrentSettingsRepository } from "./repositories/current-settings-repository.js"; createCurrentHostRepository,
import { createCurrentSshCredentialUsageRepository } from "./repositories/current-ssh-credential-usage-repository.js"; createCurrentSettingsRepository,
import { createCurrentUserRepository } from "./repositories/current-user-repository.js"; createCurrentSshCredentialUsageRepository,
import { getRepositoryRolloutStatus } from "./repositories/repository-rollout.js"; createCurrentUserRepository,
import { withCurrentSqliteForeignKeysDisabled } from "./runtime/sqlite-foreign-key-boundary.js"; } from "./repositories/factory.js";
import { withCurrentSqliteForeignKeysDisabled } from "./repositories/sqlite-foreign-keys.js";
import { parseUserAgent } from "../utils/user-agent-parser.js"; import { parseUserAgent } from "../utils/user-agent-parser.js";
import { getProxyAgent } from "../utils/proxy-agent.js"; import { getProxyAgent } from "../utils/proxy-agent.js";
import type { import type {
@@ -1895,7 +1896,6 @@ app.get(
res.json({ res.json({
migrationStatus: status, migrationStatus: status,
repositoryRollout: getRepositoryRolloutStatus(),
files: { files: {
unencryptedDbSize: unencryptedSize, unencryptedDbSize: unencryptedSize,
encryptedDbSize: encryptedSize, encryptedDbSize: encryptedSize,
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { AlertRepository } from "./alert-repository.js"; import { AlertRepository } from "./alert-repository.js";
describe("AlertRepository", () => { describe("AlertRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("AlertRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<AlertRepository> { ): Promise<AlertRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -6,7 +6,7 @@ import {
hosts, hosts,
notificationChannels, notificationChannels,
} from "../db/schema.js"; } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
type AlertRuleRecord = typeof alertRules.$inferSelect; type AlertRuleRecord = typeof alertRules.$inferSelect;
type NotificationChannelRecord = typeof notificationChannels.$inferSelect; type NotificationChannelRecord = typeof notificationChannels.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { ApiKeyRepository } from "./api-key-repository.js"; import { ApiKeyRepository } from "./api-key-repository.js";
describe("ApiKeyRepository", () => { describe("ApiKeyRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("ApiKeyRepository", () => {
async function createRepository(onWrite?: () => void): Promise<{ async function createRepository(onWrite?: () => void): Promise<{
apiKeys: ApiKeyRepository; apiKeys: ApiKeyRepository;
}> { }> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { apiKeys, users } from "../db/schema.js"; import { apiKeys, users } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type ApiKeyRecord = typeof apiKeys.$inferSelect; export type ApiKeyRecord = typeof apiKeys.$inferSelect;
export type NewApiKeyRecord = typeof apiKeys.$inferInsert; export type NewApiKeyRecord = typeof apiKeys.$inferInsert;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { AuditLogRepository } from "./audit-log-repository.js"; import { AuditLogRepository } from "./audit-log-repository.js";
describe("AuditLogRepository", () => { describe("AuditLogRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("AuditLogRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<AuditLogRepository> { ): Promise<AuditLogRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm"; import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
import { auditLogs } from "../db/schema.js"; import { auditLogs } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type AuditLogRecord = typeof auditLogs.$inferSelect; export type AuditLogRecord = typeof auditLogs.$inferSelect;
export type NewAuditLogRecord = typeof auditLogs.$inferInsert; export type NewAuditLogRecord = typeof auditLogs.$inferInsert;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js"; import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
describe("C2sTunnelPresetRepository", () => { describe("C2sTunnelPresetRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("C2sTunnelPresetRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<C2sTunnelPresetRepository> { ): Promise<C2sTunnelPresetRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, asc, eq, sql } from "drizzle-orm"; import { and, asc, eq, sql } from "drizzle-orm";
import { c2sTunnelPresets } from "../db/schema.js"; import { c2sTunnelPresets } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type C2sTunnelPresetRecord = typeof c2sTunnelPresets.$inferSelect; export type C2sTunnelPresetRecord = typeof c2sTunnelPresets.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { CommandHistoryRepository } from "./command-history-repository.js"; import { CommandHistoryRepository } from "./command-history-repository.js";
describe("CommandHistoryRepository", () => { describe("CommandHistoryRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("CommandHistoryRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<CommandHistoryRepository> { ): Promise<CommandHistoryRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, desc, eq, inArray, sql } from "drizzle-orm"; import { and, desc, eq, inArray, sql } from "drizzle-orm";
import { commandHistory } from "../db/schema.js"; import { commandHistory } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type CommandHistoryRecord = typeof commandHistory.$inferSelect; export type CommandHistoryRecord = typeof commandHistory.$inferSelect;
@@ -1,6 +1,6 @@
import { and, desc, eq, isNull, or, sql } from "drizzle-orm"; import { and, desc, eq, isNull, or, sql } from "drizzle-orm";
import { sshCredentials, sshCredentialUsage } from "../db/schema.js"; import { sshCredentials, sshCredentialUsage } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
import { SystemCrypto } from "../../utils/system-crypto.js"; import { SystemCrypto } from "../../utils/system-crypto.js";
@@ -1,15 +0,0 @@
import { AlertRepository } from "./alert-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentAlertRepository(): AlertRepository {
assertRepositoryRolloutDomainEnabled("alerts");
return new AlertRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("alert_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { ApiKeyRepository } from "./api-key-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentApiKeyRepository(): ApiKeyRepository {
assertRepositoryRolloutDomainEnabled("api_keys");
return new ApiKeyRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("api_key_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { AuditLogRepository } from "./audit-log-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentAuditLogRepository(): AuditLogRepository {
assertRepositoryRolloutDomainEnabled("audit_logs");
return new AuditLogRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("audit_log_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentC2sTunnelPresetRepository(): C2sTunnelPresetRepository {
assertRepositoryRolloutDomainEnabled("c2s_tunnel_presets");
return new C2sTunnelPresetRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("c2s_tunnel_preset_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { CommandHistoryRepository } from "./command-history-repository.js";
export function createCurrentCommandHistoryRepository(): CommandHistoryRepository {
assertRepositoryRolloutDomainEnabled("command_history");
return new CommandHistoryRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("command_history_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { CredentialRepository } from "./credential-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentCredentialRepository(): CredentialRepository {
assertRepositoryRolloutDomainEnabled("credentials");
return new CredentialRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("credential_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
export function createCurrentDashboardServiceLinkRepository(): DashboardServiceLinkRepository {
assertRepositoryRolloutDomainEnabled("dashboard_service_links");
return new DashboardServiceLinkRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("dashboard_service_link_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
export function createCurrentDismissedAlertRepository(): DismissedAlertRepository {
assertRepositoryRolloutDomainEnabled("dismissed_alerts");
return new DismissedAlertRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("dismissed_alert_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmarkRepository {
assertRepositoryRolloutDomainEnabled("file_manager_bookmarks");
return new FileManagerBookmarkRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("file_manager_bookmarks_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { HomepageItemRepository } from "./homepage-item-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentHomepageItemRepository(): HomepageItemRepository {
assertRepositoryRolloutDomainEnabled("homepage_items");
return new HomepageItemRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("homepage_item_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
export function createCurrentHomepageLayoutRepository(): HomepageLayoutRepository {
assertRepositoryRolloutDomainEnabled("homepage_layouts");
return new HomepageLayoutRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("homepage_layout_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { HostFolderRepository } from "./host-folder-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentHostFolderRepository(): HostFolderRepository {
assertRepositoryRolloutDomainEnabled("host_folders");
return new HostFolderRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_folder_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { HostHealthRepository } from "./host-health-repository.js";
export function createCurrentHostHealthRepository(): HostHealthRepository {
assertRepositoryRolloutDomainEnabled("host_health");
return new HostHealthRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_health_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
export function createCurrentHostMetricsHistoryRepository(): HostMetricsHistoryRepository {
assertRepositoryRolloutDomainEnabled("host_metrics_history");
return new HostMetricsHistoryRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_metrics_history_repository_write"),
);
}
@@ -1,17 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
export function createCurrentHostMetricsPreferenceRepository(): HostMetricsPreferenceRepository {
assertRepositoryRolloutDomainEnabled("host_metrics_preferences");
return new HostMetricsPreferenceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook(
"host_metrics_preference_repository_write",
),
);
}
@@ -1,15 +0,0 @@
import { HostRepository } from "./host-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentHostRepository(): HostRepository {
assertRepositoryRolloutDomainEnabled("hosts");
return new HostRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { HostResolutionRepository } from "./host-resolution-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentHostResolutionRepository(): HostResolutionRepository {
assertRepositoryRolloutDomainEnabled("host_resolution");
return new HostResolutionRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_resolution_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { NetworkTopologyRepository } from "./network-topology-repository.js";
export function createCurrentNetworkTopologyRepository(): NetworkTopologyRepository {
assertRepositoryRolloutDomainEnabled("network_topology");
return new NetworkTopologyRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("network_topology_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { OpenTabRepository } from "./open-tab-repository.js";
export function createCurrentOpenTabRepository(): OpenTabRepository {
assertRepositoryRolloutDomainEnabled("open_tabs");
return new OpenTabRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("open_tab_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
export function createCurrentOpksshTokenRepository(): OpksshTokenRepository {
assertRepositoryRolloutDomainEnabled("opkssh_tokens");
return new OpksshTokenRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("opkssh_token_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import { RbacAccessRepository } from "./rbac-access-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
export function createCurrentRbacAccessRepository(): RbacAccessRepository {
assertRepositoryRolloutDomainEnabled("rbac_access");
return new RbacAccessRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("rbac_access_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { RecentActivityRepository } from "./recent-activity-repository.js";
export function createCurrentRecentActivityRepository(): RecentActivityRepository {
assertRepositoryRolloutDomainEnabled("recent_activity");
return new RecentActivityRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("recent_activity_repository_write"),
);
}
@@ -1,21 +0,0 @@
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
import { getDb, getSqlite } from "../db/index.js";
import type { DatabaseContext } from "../runtime/adapter.js";
export function createCurrentRepositoryContext(): DatabaseContext {
return {
dialect: "sqlite",
drizzle: getDb(),
sqlite: getSqlite(),
};
}
export function createCurrentRepositoryWriteHook(
reason: string,
): () => Promise<void> {
return () => DatabaseSaveTrigger.forceSave(reason);
}
export function getCurrentRepositorySqlite() {
return getSqlite();
}
@@ -1,15 +0,0 @@
import { RoleRepository } from "./role-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentRoleRepository(): RoleRepository {
assertRepositoryRolloutDomainEnabled("roles");
return new RoleRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("role_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { SessionRecordingRepository } from "./session-recording-repository.js";
export function createCurrentSessionRecordingRepository(): SessionRecordingRepository {
assertRepositoryRolloutDomainEnabled("session_recordings");
return new SessionRecordingRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("session_recording_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { SessionRepository } from "./session-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentSessionRepository(): SessionRepository {
assertRepositoryRolloutDomainEnabled("sessions");
return new SessionRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("session_repository_write"),
);
}
@@ -1,26 +0,0 @@
import { SettingsRepository } from "./settings-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
getCurrentRepositorySqlite,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentSettingsRepository(): SettingsRepository {
assertRepositoryRolloutDomainEnabled("settings");
return new SettingsRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("settings_repository_write"),
);
}
export function getCurrentSettingValue(key: string): string | null {
assertRepositoryRolloutDomainEnabled("settings");
const row = getCurrentRepositorySqlite()
.prepare("SELECT value FROM settings WHERE key = ?")
.get(key) as { value?: string } | undefined;
return row?.value ?? null;
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { SharedCredentialRepository } from "./shared-credential-repository.js";
export function createCurrentSharedCredentialRepository(): SharedCredentialRepository {
assertRepositoryRolloutDomainEnabled("shared_credentials");
return new SharedCredentialRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("shared_credential_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { SnippetRepository } from "./snippet-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentSnippetRepository(): SnippetRepository {
assertRepositoryRolloutDomainEnabled("snippets");
return new SnippetRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("snippet_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js";
export function createCurrentSshCredentialUsageRepository(): SshCredentialUsageRepository {
assertRepositoryRolloutDomainEnabled("ssh_credential_usage");
return new SshCredentialUsageRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("ssh_credential_usage_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { SsoProviderRepository } from "./sso-provider-repository.js";
export function createCurrentSsoProviderRepository(): SsoProviderRepository {
assertRepositoryRolloutDomainEnabled("sso_providers");
return new SsoProviderRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("sso_provider_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentTermixIdentityCaRepository(): TermixIdentityCaRepository {
assertRepositoryRolloutDomainEnabled("termix_identity_ca");
return new TermixIdentityCaRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("termix_identity_ca_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { TermixIdentityRepository } from "./termix-identity-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentTermixIdentityRepository(): TermixIdentityRepository {
assertRepositoryRolloutDomainEnabled("termix_identity");
return new TermixIdentityRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("termix_identity_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js";
export function createCurrentTmuxSessionTagRepository(): TmuxSessionTagRepository {
assertRepositoryRolloutDomainEnabled("tmux_session_tags");
return new TmuxSessionTagRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("tmux_session_tag_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { TransferRecentRepository } from "./transfer-recent-repository.js";
export function createCurrentTransferRecentRepository(): TransferRecentRepository {
assertRepositoryRolloutDomainEnabled("transfer_recent");
return new TransferRecentRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("transfer_recent_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { TrustedDeviceRepository } from "./trusted-device-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentTrustedDeviceRepository(): TrustedDeviceRepository {
assertRepositoryRolloutDomainEnabled("trusted_devices");
return new TrustedDeviceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("trusted_device_repository_write"),
);
}
@@ -1,9 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import { createCurrentRepositoryContext } from "./current-repository-runtime.js";
import { UserDataExportRepository } from "./user-data-export-repository.js";
export function createCurrentUserDataExportRepository(): UserDataExportRepository {
assertRepositoryRolloutDomainEnabled("user_data_exports");
return new UserDataExportRepository(createCurrentRepositoryContext());
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { UserPreferenceRepository } from "./user-preference-repository.js";
export function createCurrentUserPreferenceRepository(): UserPreferenceRepository {
assertRepositoryRolloutDomainEnabled("user_preferences");
return new UserPreferenceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("user_preference_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { UserRepository } from "./user-repository.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
export function createCurrentUserRepository(): UserRepository {
assertRepositoryRolloutDomainEnabled("users");
return new UserRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("user_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { VaultProfileRepository } from "./vault-profile-repository.js";
export function createCurrentVaultProfileRepository(): VaultProfileRepository {
assertRepositoryRolloutDomainEnabled("vault_profiles");
return new VaultProfileRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("vault_profile_repository_write"),
);
}
@@ -1,15 +0,0 @@
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
import {
createCurrentRepositoryContext,
createCurrentRepositoryWriteHook,
} from "./current-repository-runtime.js";
import { VaultTokenRepository } from "./vault-token-repository.js";
export function createCurrentVaultTokenRepository(): VaultTokenRepository {
assertRepositoryRolloutDomainEnabled("vault_tokens");
return new VaultTokenRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("vault_token_repository_write"),
);
}
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js"; import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
describe("DashboardServiceLinkRepository", () => { describe("DashboardServiceLinkRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("DashboardServiceLinkRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<DashboardServiceLinkRepository> { ): Promise<DashboardServiceLinkRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, asc, eq } from "drizzle-orm"; import { and, asc, eq } from "drizzle-orm";
import { dashboardServiceLinks } from "../db/schema.js"; import { dashboardServiceLinks } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type DashboardServiceLinkRecord = export type DashboardServiceLinkRecord =
typeof dashboardServiceLinks.$inferSelect; typeof dashboardServiceLinks.$inferSelect;
@@ -0,0 +1,9 @@
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import type { Database as BetterSqliteDatabase } from "better-sqlite3";
import type * as schema from "../db/schema.js";
export interface DatabaseContext {
dialect: "sqlite";
drizzle: BetterSQLite3Database<typeof schema>;
sqlite?: BetterSqliteDatabase;
}
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DismissedAlertRepository } from "./dismissed-alert-repository.js"; import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
describe("DismissedAlertRepository", () => { describe("DismissedAlertRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("DismissedAlertRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<DismissedAlertRepository> { ): Promise<DismissedAlertRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { dismissedAlerts } from "../db/schema.js"; import { dismissedAlerts } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type DismissedAlertRecord = typeof dismissedAlerts.$inferSelect; export type DismissedAlertRecord = typeof dismissedAlerts.$inferSelect;
@@ -0,0 +1,348 @@
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
import { getDb, getSqlite } from "../db/index.js";
import type { DatabaseContext } from "./database-context.js";
import { AlertRepository } from "./alert-repository.js";
import { ApiKeyRepository } from "./api-key-repository.js";
import { AuditLogRepository } from "./audit-log-repository.js";
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
import { CommandHistoryRepository } from "./command-history-repository.js";
import { CredentialRepository } from "./credential-repository.js";
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
import { HomepageItemRepository } from "./homepage-item-repository.js";
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
import { HostFolderRepository } from "./host-folder-repository.js";
import { HostHealthRepository } from "./host-health-repository.js";
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
import { HostRepository } from "./host-repository.js";
import { HostResolutionRepository } from "./host-resolution-repository.js";
import { NetworkTopologyRepository } from "./network-topology-repository.js";
import { OpenTabRepository } from "./open-tab-repository.js";
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
import { RbacAccessRepository } from "./rbac-access-repository.js";
import { RecentActivityRepository } from "./recent-activity-repository.js";
import { RoleRepository } from "./role-repository.js";
import { SessionRecordingRepository } from "./session-recording-repository.js";
import { SessionRepository } from "./session-repository.js";
import { SettingsRepository } from "./settings-repository.js";
import { SharedCredentialRepository } from "./shared-credential-repository.js";
import { SnippetRepository } from "./snippet-repository.js";
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js";
import { SsoProviderRepository } from "./sso-provider-repository.js";
import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js";
import { TermixIdentityRepository } from "./termix-identity-repository.js";
import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js";
import { TransferRecentRepository } from "./transfer-recent-repository.js";
import { TrustedDeviceRepository } from "./trusted-device-repository.js";
import { UserDataExportRepository } from "./user-data-export-repository.js";
import { UserPreferenceRepository } from "./user-preference-repository.js";
import { UserRepository } from "./user-repository.js";
import { VaultProfileRepository } from "./vault-profile-repository.js";
import { VaultTokenRepository } from "./vault-token-repository.js";
export function createCurrentRepositoryContext(): DatabaseContext {
return {
dialect: "sqlite",
drizzle: getDb(),
sqlite: getSqlite(),
};
}
export function createCurrentRepositoryWriteHook(
reason: string,
): () => Promise<void> {
return () => DatabaseSaveTrigger.forceSave(reason);
}
export function getCurrentRepositorySqlite() {
return getSqlite();
}
export function getCurrentSettingValue(key: string): string | null {
const row = getCurrentRepositorySqlite()
.prepare("SELECT value FROM settings WHERE key = ?")
.get(key) as { value?: string } | undefined;
return row?.value ?? null;
}
export function createCurrentAlertRepository(): AlertRepository {
return new AlertRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("alert_repository_write"),
);
}
export function createCurrentApiKeyRepository(): ApiKeyRepository {
return new ApiKeyRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("api_key_repository_write"),
);
}
export function createCurrentAuditLogRepository(): AuditLogRepository {
return new AuditLogRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("audit_log_repository_write"),
);
}
export function createCurrentC2sTunnelPresetRepository(): C2sTunnelPresetRepository {
return new C2sTunnelPresetRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("c2s_tunnel_preset_repository_write"),
);
}
export function createCurrentCommandHistoryRepository(): CommandHistoryRepository {
return new CommandHistoryRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("command_history_repository_write"),
);
}
export function createCurrentCredentialRepository(): CredentialRepository {
return new CredentialRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("credential_repository_write"),
);
}
export function createCurrentDashboardServiceLinkRepository(): DashboardServiceLinkRepository {
return new DashboardServiceLinkRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("dashboard_service_link_repository_write"),
);
}
export function createCurrentDismissedAlertRepository(): DismissedAlertRepository {
return new DismissedAlertRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("dismissed_alert_repository_write"),
);
}
export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmarkRepository {
return new FileManagerBookmarkRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("file_manager_bookmarks_repository_write"),
);
}
export function createCurrentHomepageItemRepository(): HomepageItemRepository {
return new HomepageItemRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("homepage_item_repository_write"),
);
}
export function createCurrentHomepageLayoutRepository(): HomepageLayoutRepository {
return new HomepageLayoutRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("homepage_layout_repository_write"),
);
}
export function createCurrentHostFolderRepository(): HostFolderRepository {
return new HostFolderRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_folder_repository_write"),
);
}
export function createCurrentHostHealthRepository(): HostHealthRepository {
return new HostHealthRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_health_repository_write"),
);
}
export function createCurrentHostMetricsHistoryRepository(): HostMetricsHistoryRepository {
return new HostMetricsHistoryRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_metrics_history_repository_write"),
);
}
export function createCurrentHostMetricsPreferenceRepository(): HostMetricsPreferenceRepository {
return new HostMetricsPreferenceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook(
"host_metrics_preference_repository_write",
),
);
}
export function createCurrentHostRepository(): HostRepository {
return new HostRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_repository_write"),
);
}
export function createCurrentHostResolutionRepository(): HostResolutionRepository {
return new HostResolutionRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("host_resolution_repository_write"),
);
}
export function createCurrentNetworkTopologyRepository(): NetworkTopologyRepository {
return new NetworkTopologyRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("network_topology_repository_write"),
);
}
export function createCurrentOpenTabRepository(): OpenTabRepository {
return new OpenTabRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("open_tab_repository_write"),
);
}
export function createCurrentOpksshTokenRepository(): OpksshTokenRepository {
return new OpksshTokenRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("opkssh_token_repository_write"),
);
}
export function createCurrentRbacAccessRepository(): RbacAccessRepository {
return new RbacAccessRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("rbac_access_repository_write"),
);
}
export function createCurrentRecentActivityRepository(): RecentActivityRepository {
return new RecentActivityRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("recent_activity_repository_write"),
);
}
export function createCurrentRoleRepository(): RoleRepository {
return new RoleRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("role_repository_write"),
);
}
export function createCurrentSessionRecordingRepository(): SessionRecordingRepository {
return new SessionRecordingRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("session_recording_repository_write"),
);
}
export function createCurrentSessionRepository(): SessionRepository {
return new SessionRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("session_repository_write"),
);
}
export function createCurrentSettingsRepository(): SettingsRepository {
return new SettingsRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("settings_repository_write"),
);
}
export function createCurrentSharedCredentialRepository(): SharedCredentialRepository {
return new SharedCredentialRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("shared_credential_repository_write"),
);
}
export function createCurrentSnippetRepository(): SnippetRepository {
return new SnippetRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("snippet_repository_write"),
);
}
export function createCurrentSshCredentialUsageRepository(): SshCredentialUsageRepository {
return new SshCredentialUsageRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("ssh_credential_usage_repository_write"),
);
}
export function createCurrentSsoProviderRepository(): SsoProviderRepository {
return new SsoProviderRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("sso_provider_repository_write"),
);
}
export function createCurrentTermixIdentityCaRepository(): TermixIdentityCaRepository {
return new TermixIdentityCaRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("termix_identity_ca_repository_write"),
);
}
export function createCurrentTermixIdentityRepository(): TermixIdentityRepository {
return new TermixIdentityRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("termix_identity_repository_write"),
);
}
export function createCurrentTmuxSessionTagRepository(): TmuxSessionTagRepository {
return new TmuxSessionTagRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("tmux_session_tag_repository_write"),
);
}
export function createCurrentTransferRecentRepository(): TransferRecentRepository {
return new TransferRecentRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("transfer_recent_repository_write"),
);
}
export function createCurrentTrustedDeviceRepository(): TrustedDeviceRepository {
return new TrustedDeviceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("trusted_device_repository_write"),
);
}
export function createCurrentUserDataExportRepository(): UserDataExportRepository {
return new UserDataExportRepository(createCurrentRepositoryContext());
}
export function createCurrentUserPreferenceRepository(): UserPreferenceRepository {
return new UserPreferenceRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("user_preference_repository_write"),
);
}
export function createCurrentUserRepository(): UserRepository {
return new UserRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("user_repository_write"),
);
}
export function createCurrentVaultProfileRepository(): VaultProfileRepository {
return new VaultProfileRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("vault_profile_repository_write"),
);
}
export function createCurrentVaultTokenRepository(): VaultTokenRepository {
return new VaultTokenRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("vault_token_repository_write"),
);
}
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js"; import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
describe("FileManagerBookmarkRepository", () => { describe("FileManagerBookmarkRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("FileManagerBookmarkRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<FileManagerBookmarkRepository> { ): Promise<FileManagerBookmarkRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -4,7 +4,7 @@ import {
fileManagerRecent, fileManagerRecent,
fileManagerShortcuts, fileManagerShortcuts,
} from "../db/schema.js"; } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect; export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect;
export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect; export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HomepageItemRepository } from "./homepage-item-repository.js"; import { HomepageItemRepository } from "./homepage-item-repository.js";
describe("HomepageItemRepository", () => { describe("HomepageItemRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("HomepageItemRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HomepageItemRepository> { ): Promise<HomepageItemRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, asc, eq } from "drizzle-orm"; import { and, asc, eq } from "drizzle-orm";
import { homepageItems } from "../db/schema.js"; import { homepageItems } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HomepageItemRecord = typeof homepageItems.$inferSelect; export type HomepageItemRecord = typeof homepageItems.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HomepageLayoutRepository } from "./homepage-layout-repository.js"; import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
describe("HomepageLayoutRepository", () => { describe("HomepageLayoutRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("HomepageLayoutRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HomepageLayoutRepository> { ): Promise<HomepageLayoutRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { homepageLayouts } from "../db/schema.js"; import { homepageLayouts } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HomepageLayoutRecord = typeof homepageLayouts.$inferSelect; export type HomepageLayoutRecord = typeof homepageLayouts.$inferSelect;
@@ -1,12 +1,12 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { CredentialRepository } from "./credential-repository.js"; import { CredentialRepository } from "./credential-repository.js";
import { HostRepository } from "./host-repository.js"; import { HostRepository } from "./host-repository.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
import { SystemCrypto } from "../../utils/system-crypto.js"; import { SystemCrypto } from "../../utils/system-crypto.js";
describe("HostRepository and CredentialRepository", () => { describe("HostRepository and CredentialRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
vi.restoreAllMocks(); vi.restoreAllMocks();
@@ -23,14 +23,10 @@ describe("HostRepository and CredentialRepository", () => {
credentials: CredentialRepository; credentials: CredentialRepository;
hosts: HostRepository; hosts: HostRepository;
sqlite: NonNullable< sqlite: NonNullable<
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"] Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
>; >;
}> { }> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostFolderRepository } from "./host-folder-repository.js"; import { HostFolderRepository } from "./host-folder-repository.js";
describe("HostFolderRepository", () => { describe("HostFolderRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -17,14 +17,10 @@ describe("HostFolderRepository", () => {
): Promise<{ ): Promise<{
repository: HostFolderRepository; repository: HostFolderRepository;
sqlite: NonNullable< sqlite: NonNullable<
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"] Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
>; >;
}> { }> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,7 +1,7 @@
import { and, eq, like, or, sql } from "drizzle-orm"; import { and, eq, like, or, sql } from "drizzle-orm";
import type { SQLiteColumn } from "drizzle-orm/sqlite-core"; import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
import { hosts, sshCredentials, sshFolders } from "../db/schema.js"; import { hosts, sshCredentials, sshFolders } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HostFolderRecord = typeof sshFolders.$inferSelect; export type HostFolderRecord = typeof sshFolders.$inferSelect;
export type HostFolderHostRecord = typeof hosts.$inferSelect; export type HostFolderHostRecord = typeof hosts.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostHealthRepository } from "./host-health-repository.js"; import { HostHealthRepository } from "./host-health-repository.js";
describe("HostHealthRepository", () => { describe("HostHealthRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("HostHealthRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HostHealthRepository> { ): Promise<HostHealthRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, desc, eq } from "drizzle-orm"; import { and, desc, eq } from "drizzle-orm";
import { hostHealthChecks, hostHealthHistory } from "../db/schema.js"; import { hostHealthChecks, hostHealthHistory } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HostHealthCheckRecord = typeof hostHealthChecks.$inferSelect; export type HostHealthCheckRecord = typeof hostHealthChecks.$inferSelect;
export type HostHealthHistoryRecord = typeof hostHealthHistory.$inferSelect; export type HostHealthHistoryRecord = typeof hostHealthHistory.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js"; import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
describe("HostMetricsHistoryRepository", () => { describe("HostMetricsHistoryRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("HostMetricsHistoryRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HostMetricsHistoryRepository> { ): Promise<HostMetricsHistoryRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE hosts ( CREATE TABLE hosts (
@@ -1,6 +1,6 @@
import { and, asc, eq, gte, lte } from "drizzle-orm"; import { and, asc, eq, gte, lte } from "drizzle-orm";
import { hostMetricsHistory } from "../db/schema.js"; import { hostMetricsHistory } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HostMetricsHistoryRecord = typeof hostMetricsHistory.$inferSelect; export type HostMetricsHistoryRecord = typeof hostMetricsHistory.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js"; import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
describe("HostMetricsPreferenceRepository", () => { describe("HostMetricsPreferenceRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("HostMetricsPreferenceRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HostMetricsPreferenceRepository> { ): Promise<HostMetricsPreferenceRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { hostMetricsPreferences, hosts } from "../db/schema.js"; import { hostMetricsPreferences, hosts } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type HostMetricsPreferenceRecord = export type HostMetricsPreferenceRecord =
typeof hostMetricsPreferences.$inferSelect; typeof hostMetricsPreferences.$inferSelect;
@@ -1,6 +1,6 @@
import { and, eq, inArray } from "drizzle-orm"; import { and, eq, inArray } from "drizzle-orm";
import { hostAccess, hosts } from "../db/schema.js"; import { hostAccess, hosts } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
export type HostRecord = typeof hosts.$inferSelect; export type HostRecord = typeof hosts.$inferSelect;
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
import { HostResolutionRepository } from "./host-resolution-repository.js"; import { HostResolutionRepository } from "./host-resolution-repository.js";
@@ -11,7 +11,7 @@ vi.mock("../../utils/data-crypto.js", () => ({
})); }));
describe("HostResolutionRepository", () => { describe("HostResolutionRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
vi.mocked(DataCrypto.getUserDataKey).mockReset(); vi.mocked(DataCrypto.getUserDataKey).mockReset();
@@ -25,11 +25,7 @@ describe("HostResolutionRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<HostResolutionRepository> { ): Promise<HostResolutionRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq, inArray, isNotNull } from "drizzle-orm"; import { and, eq, inArray, isNotNull } from "drizzle-orm";
import { hostAccess, hosts, sshCredentials } from "../db/schema.js"; import { hostAccess, hosts, sshCredentials } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
export type HostResolutionHostRecord = typeof hosts.$inferSelect; export type HostResolutionHostRecord = typeof hosts.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { NetworkTopologyRepository } from "./network-topology-repository.js"; import { NetworkTopologyRepository } from "./network-topology-repository.js";
describe("NetworkTopologyRepository", () => { describe("NetworkTopologyRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("NetworkTopologyRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<NetworkTopologyRepository> { ): Promise<NetworkTopologyRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { networkTopology } from "../db/schema.js"; import { networkTopology } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type NetworkTopologyRecord = typeof networkTopology.$inferSelect; export type NetworkTopologyRecord = typeof networkTopology.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { OpenTabRepository } from "./open-tab-repository.js"; import { OpenTabRepository } from "./open-tab-repository.js";
describe("OpenTabRepository", () => { describe("OpenTabRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("OpenTabRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<OpenTabRepository> { ): Promise<OpenTabRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq, gt } from "drizzle-orm"; import { and, eq, gt } from "drizzle-orm";
import { userOpenTabs } from "../db/schema.js"; import { userOpenTabs } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type OpenTabRecord = typeof userOpenTabs.$inferSelect; export type OpenTabRecord = typeof userOpenTabs.$inferSelect;
export type NewOpenTabRecord = typeof userOpenTabs.$inferInsert; export type NewOpenTabRecord = typeof userOpenTabs.$inferInsert;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { OpksshTokenRepository } from "./opkssh-token-repository.js"; import { OpksshTokenRepository } from "./opkssh-token-repository.js";
describe("OpksshTokenRepository", () => { describe("OpksshTokenRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("OpksshTokenRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<OpksshTokenRepository> { ): Promise<OpksshTokenRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { opksshTokens } from "../db/schema.js"; import { opksshTokens } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type OpksshTokenRecord = typeof opksshTokens.$inferSelect; export type OpksshTokenRecord = typeof opksshTokens.$inferSelect;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RbacAccessRepository } from "./rbac-access-repository.js"; import { RbacAccessRepository } from "./rbac-access-repository.js";
describe("RbacAccessRepository", () => { describe("RbacAccessRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
const activeAccessTime = "2026-06-26T12:00:00.000Z"; const activeAccessTime = "2026-06-26T12:00:00.000Z";
afterEach(async () => { afterEach(async () => {
@@ -16,11 +16,7 @@ describe("RbacAccessRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<RbacAccessRepository> { ): Promise<RbacAccessRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -8,7 +8,7 @@ import {
snippets, snippets,
users, users,
} from "../db/schema.js"; } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type RbacAccessTargetType = "user" | "role"; export type RbacAccessTargetType = "user" | "role";
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RecentActivityRepository } from "./recent-activity-repository.js"; import { RecentActivityRepository } from "./recent-activity-repository.js";
describe("RecentActivityRepository", () => { describe("RecentActivityRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -17,14 +17,10 @@ describe("RecentActivityRepository", () => {
): Promise<{ ): Promise<{
repository: RecentActivityRepository; repository: RecentActivityRepository;
sqlite: NonNullable< sqlite: NonNullable<
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"] Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
>; >;
}> { }> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { desc, eq, inArray } from "drizzle-orm"; import { desc, eq, inArray } from "drizzle-orm";
import { recentActivity } from "../db/schema.js"; import { recentActivity } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type RecentActivityRecord = typeof recentActivity.$inferSelect; export type RecentActivityRecord = typeof recentActivity.$inferSelect;
export type NewRecentActivityRecord = typeof recentActivity.$inferInsert; export type NewRecentActivityRecord = typeof recentActivity.$inferInsert;
@@ -1,219 +0,0 @@
import { describe, expect, it } from "vitest";
import {
getRepositoryRolloutStatus,
getRepositoryRolloutWarnings,
isRepositoryRolloutDomainEnabled,
parseRepositoryRolloutConfig,
REPOSITORY_ROLLOUT_ENV,
} from "./repository-rollout.js";
describe("parseRepositoryRolloutConfig", () => {
it("defaults to the current migrated repository slice", () => {
const config = parseRepositoryRolloutConfig({});
expect(config).toEqual({
mode: "all",
enabledDomains: [
"settings",
"users",
"sessions",
"api_keys",
"trusted_devices",
"credentials",
"termix_identity",
"termix_identity_ca",
"hosts",
"snippets",
"roles",
"rbac_access",
"shared_credentials",
"sso_providers",
"audit_logs",
"user_preferences",
"open_tabs",
"dismissed_alerts",
"homepage_layouts",
"homepage_items",
"network_topology",
"dashboard_service_links",
"session_recordings",
"command_history",
"recent_activity",
"ssh_credential_usage",
"transfer_recent",
"file_manager_bookmarks",
"c2s_tunnel_presets",
"tmux_session_tags",
"opkssh_tokens",
"vault_tokens",
"vault_profiles",
"host_metrics_preferences",
"host_health",
"host_metrics_history",
"alerts",
"user_data_exports",
"host_folders",
"host_resolution",
],
explicit: false,
});
});
it("allows explicitly disabling all migrated repository domains", () => {
const config = parseRepositoryRolloutConfig({
[REPOSITORY_ROLLOUT_ENV]: "off",
});
expect(config).toEqual({
mode: "none",
enabledDomains: [],
explicit: true,
});
});
it("accepts a partial domain allowlist with aliases", () => {
const config = parseRepositoryRolloutConfig({
[REPOSITORY_ROLLOUT_ENV]:
"settings,user,api-key,credential,termix-id,termix-ca,host,dismissed,alerts,layout,items,topology,dashboard-link,recordings,history,activity,usage,transfer,user-data-export,ssh-folder,host-resolver,shared-credentials,bookmarks,c2s,tmux,opkssh,vault-token,vault-profile,metrics-preferences,host-health,metrics-history",
});
expect(config).toEqual({
mode: "partial",
enabledDomains: [
"settings",
"users",
"api_keys",
"credentials",
"termix_identity",
"termix_identity_ca",
"hosts",
"dismissed_alerts",
"alerts",
"homepage_layouts",
"homepage_items",
"network_topology",
"dashboard_service_links",
"session_recordings",
"command_history",
"recent_activity",
"ssh_credential_usage",
"transfer_recent",
"user_data_exports",
"host_folders",
"host_resolution",
"shared_credentials",
"file_manager_bookmarks",
"c2s_tunnel_presets",
"tmux_session_tags",
"opkssh_tokens",
"vault_tokens",
"vault_profiles",
"host_metrics_preferences",
"host_health",
"host_metrics_history",
],
explicit: true,
});
});
it("deduplicates allowlisted domains", () => {
const config = parseRepositoryRolloutConfig({
[REPOSITORY_ROLLOUT_ENV]: "users,user,users",
});
expect(config.enabledDomains).toEqual(["users"]);
});
it("rejects unknown domains", () => {
expect(() =>
parseRepositoryRolloutConfig({
[REPOSITORY_ROLLOUT_ENV]: "settings,unknown-domain",
}),
).toThrow("Unsupported DATABASE_LAYER_REPOSITORY_ROLLOUT domain");
});
it("checks whether an individual domain is enabled", () => {
const env = { [REPOSITORY_ROLLOUT_ENV]: "sessions" };
expect(isRepositoryRolloutDomainEnabled("sessions", env)).toBe(true);
expect(isRepositoryRolloutDomainEnabled("users", env)).toBe(false);
});
it("builds a status payload for admin visibility", () => {
const status = getRepositoryRolloutStatus({
[REPOSITORY_ROLLOUT_ENV]: "settings,sessions",
});
expect(status).toEqual({
mode: "partial",
enabledDomains: ["settings", "sessions"],
explicit: true,
envKey: REPOSITORY_ROLLOUT_ENV,
supportedDomains: [
"settings",
"users",
"sessions",
"api_keys",
"trusted_devices",
"credentials",
"termix_identity",
"termix_identity_ca",
"hosts",
"snippets",
"roles",
"rbac_access",
"shared_credentials",
"sso_providers",
"audit_logs",
"user_preferences",
"open_tabs",
"dismissed_alerts",
"homepage_layouts",
"homepage_items",
"network_topology",
"dashboard_service_links",
"session_recordings",
"command_history",
"recent_activity",
"ssh_credential_usage",
"transfer_recent",
"file_manager_bookmarks",
"c2s_tunnel_presets",
"tmux_session_tags",
"opkssh_tokens",
"vault_tokens",
"vault_profiles",
"host_metrics_preferences",
"host_health",
"host_metrics_history",
"alerts",
"user_data_exports",
"host_folders",
"host_resolution",
],
warnings: [
"Partial repository rollout enabled for domains: settings, sessions.",
],
});
});
it("warns when gray rollout is implicit", () => {
const warnings = getRepositoryRolloutWarnings(
parseRepositoryRolloutConfig({}),
);
expect(warnings).toEqual([
"DATABASE_LAYER_REPOSITORY_ROLLOUT is not explicitly set; gray targets should set it so rollout state is visible in deployment config.",
]);
});
it("warns when migrated repository domains are disabled", () => {
const warnings = getRepositoryRolloutWarnings(
parseRepositoryRolloutConfig({ [REPOSITORY_ROLLOUT_ENV]: "off" }),
);
expect(warnings).toEqual([
"All migrated repository domains are disabled; migrated auth/settings/session paths will fail closed.",
]);
});
});
@@ -1,381 +0,0 @@
import { databaseLogger } from "../../utils/logger.js";
export const REPOSITORY_ROLLOUT_ENV = "DATABASE_LAYER_REPOSITORY_ROLLOUT";
export const REPOSITORY_ROLLOUT_DOMAINS = [
"settings",
"users",
"sessions",
"api_keys",
"trusted_devices",
"credentials",
"termix_identity",
"termix_identity_ca",
"hosts",
"snippets",
"roles",
"rbac_access",
"shared_credentials",
"sso_providers",
"audit_logs",
"user_preferences",
"open_tabs",
"dismissed_alerts",
"homepage_layouts",
"homepage_items",
"network_topology",
"dashboard_service_links",
"session_recordings",
"command_history",
"recent_activity",
"ssh_credential_usage",
"transfer_recent",
"file_manager_bookmarks",
"c2s_tunnel_presets",
"tmux_session_tags",
"opkssh_tokens",
"vault_tokens",
"vault_profiles",
"host_metrics_preferences",
"host_health",
"host_metrics_history",
"alerts",
"user_data_exports",
"host_folders",
"host_resolution",
] as const;
export type RepositoryRolloutDomain =
(typeof REPOSITORY_ROLLOUT_DOMAINS)[number];
export interface RepositoryRolloutConfig {
mode: "all" | "none" | "partial";
enabledDomains: RepositoryRolloutDomain[];
explicit: boolean;
}
export interface RepositoryRolloutStatus extends RepositoryRolloutConfig {
envKey: typeof REPOSITORY_ROLLOUT_ENV;
supportedDomains: RepositoryRolloutDomain[];
warnings: string[];
}
type EnvLike = Record<string, string | undefined>;
const DOMAIN_ALIASES: Record<string, RepositoryRolloutDomain> = {
api: "api_keys",
api_key: "api_keys",
api_keys: "api_keys",
apikey: "api_keys",
apikeys: "api_keys",
audit: "audit_logs",
audit_log: "audit_logs",
audit_logs: "audit_logs",
auditlog: "audit_logs",
auditlogs: "audit_logs",
alert: "alerts",
alerts: "alerts",
dashboard_link: "dashboard_service_links",
dashboard_links: "dashboard_service_links",
dashboard_service_link: "dashboard_service_links",
dashboard_service_links: "dashboard_service_links",
dashboardlink: "dashboard_service_links",
dashboardlinks: "dashboard_service_links",
command_history: "command_history",
commandhistory: "command_history",
credential: "credentials",
credentials: "credentials",
ca: "termix_identity_ca",
history: "command_history",
ssh_credential: "credentials",
ssh_credentials: "credentials",
sshcredential: "credentials",
sshcredentials: "credentials",
terminal_history: "command_history",
termix_ca: "termix_identity_ca",
termix_id: "termix_identity",
termix_id_ca: "termix_identity_ca",
termix_identity: "termix_identity",
termix_identity_ca: "termix_identity_ca",
termix_identity_cas: "termix_identity_ca",
termix_identity_key: "termix_identity",
termix_identity_keys: "termix_identity",
termix_identity_public_keys: "termix_identity",
termix_identities: "termix_identity",
termixidca: "termix_identity_ca",
termixidentity: "termix_identity",
dismissed_alert: "dismissed_alerts",
dismissed_alerts: "dismissed_alerts",
dismissedalert: "dismissed_alerts",
dismissedalerts: "dismissed_alerts",
dismissed: "dismissed_alerts",
homepage_layout: "homepage_layouts",
homepage_layouts: "homepage_layouts",
homepagelayout: "homepage_layouts",
homepagelayouts: "homepage_layouts",
layout: "homepage_layouts",
layouts: "homepage_layouts",
homepage_item: "homepage_items",
homepage_items: "homepage_items",
homepageitem: "homepage_items",
homepageitems: "homepage_items",
host_folder: "host_folders",
host_folders: "host_folders",
hostfolder: "host_folders",
hostfolders: "host_folders",
host: "hosts",
hosts: "hosts",
snippet: "snippets",
snippets: "snippets",
host_resolution: "host_resolution",
host_resolver: "host_resolution",
hostresolution: "host_resolution",
hostresolver: "host_resolution",
resolver: "host_resolution",
ssh_folder: "host_folders",
ssh_folders: "host_folders",
sshfolder: "host_folders",
sshfolders: "host_folders",
item: "homepage_items",
items: "homepage_items",
recording: "session_recordings",
recordings: "session_recordings",
session_recording: "session_recordings",
session_recordings: "session_recordings",
sessionrecording: "session_recordings",
sessionrecordings: "session_recordings",
network_topologies: "network_topology",
network_topology: "network_topology",
networktopologies: "network_topology",
networktopology: "network_topology",
topology: "network_topology",
activity: "recent_activity",
recent_activities: "recent_activity",
recent_activity: "recent_activity",
recentactivity: "recent_activity",
credential_usage: "ssh_credential_usage",
ssh_credential_usage: "ssh_credential_usage",
sshcredentialusage: "ssh_credential_usage",
usage: "ssh_credential_usage",
transfer: "transfer_recent",
transfer_recent: "transfer_recent",
transferrecent: "transfer_recent",
export: "user_data_exports",
exports: "user_data_exports",
user_data_export: "user_data_exports",
user_data_exports: "user_data_exports",
userdataexport: "user_data_exports",
userdataexports: "user_data_exports",
bookmark: "file_manager_bookmarks",
bookmarks: "file_manager_bookmarks",
file_bookmarks: "file_manager_bookmarks",
file_manager_bookmark: "file_manager_bookmarks",
file_manager_bookmarks: "file_manager_bookmarks",
filemanagerbookmarks: "file_manager_bookmarks",
c2s: "c2s_tunnel_presets",
c2s_preset: "c2s_tunnel_presets",
c2s_presets: "c2s_tunnel_presets",
c2s_tunnel_preset: "c2s_tunnel_presets",
c2s_tunnel_presets: "c2s_tunnel_presets",
c2stunnelpresets: "c2s_tunnel_presets",
tmux: "tmux_session_tags",
tmux_tag: "tmux_session_tags",
tmux_tags: "tmux_session_tags",
tmux_session_tag: "tmux_session_tags",
tmux_session_tags: "tmux_session_tags",
tmuxsessiontags: "tmux_session_tags",
opkssh: "opkssh_tokens",
opkssh_token: "opkssh_tokens",
opkssh_tokens: "opkssh_tokens",
opksshtoken: "opkssh_tokens",
opksshtokens: "opkssh_tokens",
vault_token: "vault_tokens",
vault_tokens: "vault_tokens",
vaulttoken: "vault_tokens",
vaulttokens: "vault_tokens",
vault_profile: "vault_profiles",
vault_profiles: "vault_profiles",
vaultprofile: "vault_profiles",
vaultprofiles: "vault_profiles",
host_metrics_preference: "host_metrics_preferences",
host_metrics_preferences: "host_metrics_preferences",
hostmetricspreference: "host_metrics_preferences",
hostmetricspreferences: "host_metrics_preferences",
metrics_preferences: "host_metrics_preferences",
health: "host_health",
host_health: "host_health",
host_health_checks: "host_health",
host_health_history: "host_health",
hosthealth: "host_health",
host_metrics_history: "host_metrics_history",
hostmetricshistory: "host_metrics_history",
metrics_history: "host_metrics_history",
open_tab: "open_tabs",
open_tabs: "open_tabs",
opentab: "open_tabs",
opentabs: "open_tabs",
setting: "settings",
settings: "settings",
session: "sessions",
sessions: "sessions",
trusted_device: "trusted_devices",
trusted_devices: "trusted_devices",
trusteddevice: "trusted_devices",
trusteddevices: "trusted_devices",
preference: "user_preferences",
preferences: "user_preferences",
user_preference: "user_preferences",
user_preferences: "user_preferences",
userpreference: "user_preferences",
userpreferences: "user_preferences",
role: "roles",
roles: "roles",
rbac: "rbac_access",
rbac_access: "rbac_access",
rbacaccess: "rbac_access",
shared_credential: "shared_credentials",
shared_credentials: "shared_credentials",
sharedcredential: "shared_credentials",
sharedcredentials: "shared_credentials",
sso: "sso_providers",
sso_provider: "sso_providers",
sso_providers: "sso_providers",
ssoprovider: "sso_providers",
ssoproviders: "sso_providers",
user: "users",
users: "users",
};
const DISABLED_VALUES = new Set(["0", "false", "none", "off", "disabled"]);
const ENABLED_VALUES = new Set(["1", "true", "all", "on", "enabled"]);
function parseDomainList(value: string): RepositoryRolloutDomain[] {
const domains = value
.split(",")
.map((part) => part.trim().toLowerCase().replaceAll("-", "_"))
.filter(Boolean)
.map((part) => {
const domain = DOMAIN_ALIASES[part];
if (!domain) {
throw new Error(
`Unsupported ${REPOSITORY_ROLLOUT_ENV} domain '${part}'. Expected one of: ${REPOSITORY_ROLLOUT_DOMAINS.join(", ")}.`,
);
}
return domain;
});
return Array.from(new Set(domains));
}
export function parseRepositoryRolloutConfig(
env: EnvLike = process.env,
): RepositoryRolloutConfig {
const raw = env[REPOSITORY_ROLLOUT_ENV];
const normalized = raw?.trim().toLowerCase();
if (!normalized) {
return {
mode: "all",
enabledDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
explicit: false,
};
}
if (ENABLED_VALUES.has(normalized)) {
return {
mode: "all",
enabledDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
explicit: true,
};
}
if (DISABLED_VALUES.has(normalized)) {
return { mode: "none", enabledDomains: [], explicit: true };
}
const enabledDomains = parseDomainList(normalized);
return {
mode:
enabledDomains.length === REPOSITORY_ROLLOUT_DOMAINS.length
? "all"
: "partial",
enabledDomains,
explicit: true,
};
}
export function isRepositoryRolloutDomainEnabled(
domain: RepositoryRolloutDomain,
env: EnvLike = process.env,
): boolean {
return parseRepositoryRolloutConfig(env).enabledDomains.includes(domain);
}
export function getRepositoryRolloutStatus(
env: EnvLike = process.env,
): RepositoryRolloutStatus {
const config = parseRepositoryRolloutConfig(env);
return {
...config,
envKey: REPOSITORY_ROLLOUT_ENV,
supportedDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
warnings: getRepositoryRolloutWarnings(config),
};
}
export function getRepositoryRolloutWarnings(
config: RepositoryRolloutConfig,
): string[] {
const warnings: string[] = [];
if (!config.explicit) {
warnings.push(
`${REPOSITORY_ROLLOUT_ENV} is not explicitly set; gray targets should set it so rollout state is visible in deployment config.`,
);
}
if (config.mode === "none") {
warnings.push(
"All migrated repository domains are disabled; migrated auth/settings/session paths will fail closed.",
);
}
if (config.mode === "partial") {
warnings.push(
`Partial repository rollout enabled for domains: ${config.enabledDomains.join(", ")}.`,
);
}
return warnings;
}
export function assertRepositoryRolloutDomainEnabled(
domain: RepositoryRolloutDomain,
): void {
if (isRepositoryRolloutDomainEnabled(domain)) return;
throw new Error(
`Repository domain '${domain}' is disabled by ${REPOSITORY_ROLLOUT_ENV}.`,
);
}
export function logRepositoryRolloutConfig(env: EnvLike = process.env): void {
const config = getRepositoryRolloutStatus(env);
databaseLogger.info("Database repository rollout configuration loaded", {
operation: "repository_rollout_config",
mode: config.mode,
enabledDomains: config.enabledDomains,
explicit: config.explicit,
envKey: REPOSITORY_ROLLOUT_ENV,
});
for (const warning of config.warnings) {
databaseLogger.warn(warning, {
operation: "repository_rollout_warning",
mode: config.mode,
enabledDomains: config.enabledDomains,
explicit: config.explicit,
envKey: REPOSITORY_ROLLOUT_ENV,
});
}
}
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RoleRepository } from "./role-repository.js"; import { RoleRepository } from "./role-repository.js";
describe("RoleRepository", () => { describe("RoleRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("RoleRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<RoleRepository> { ): Promise<RoleRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, eq, inArray } from "drizzle-orm"; import { and, eq, inArray } from "drizzle-orm";
import { hostAccess, roles, userRoles } from "../db/schema.js"; import { hostAccess, roles, userRoles } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type RoleRecord = typeof roles.$inferSelect; export type RoleRecord = typeof roles.$inferSelect;
export type NewRoleRecord = typeof roles.$inferInsert; export type NewRoleRecord = typeof roles.$inferInsert;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SessionRecordingRepository } from "./session-recording-repository.js"; import { SessionRecordingRepository } from "./session-recording-repository.js";
describe("SessionRecordingRepository", () => { describe("SessionRecordingRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -15,11 +15,7 @@ describe("SessionRecordingRepository", () => {
async function createRepository( async function createRepository(
onWrite?: () => void | Promise<void>, onWrite?: () => void | Promise<void>,
): Promise<SessionRecordingRepository> { ): Promise<SessionRecordingRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE users ( CREATE TABLE users (
@@ -1,6 +1,6 @@
import { and, desc, eq, inArray, lt } from "drizzle-orm"; import { and, desc, eq, inArray, lt } from "drizzle-orm";
import { hosts, sessionRecordings } from "../db/schema.js"; import { hosts, sessionRecordings } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect; export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect;
@@ -74,9 +74,7 @@ export class SessionRecordingRepository {
return rows[0] ?? null; return rows[0] ?? null;
} }
async findPathById( async findPathById(id: number): Promise<
id: number,
): Promise<
| (SessionRecordingPathRecord & { | (SessionRecordingPathRecord & {
userId: string; userId: string;
format?: string | null; format?: string | null;
@@ -1,6 +1,6 @@
import { and, eq, lte, ne } from "drizzle-orm"; import { and, eq, lte, ne } from "drizzle-orm";
import { sessions } from "../db/schema.js"; import { sessions } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export type SessionRecord = typeof sessions.$inferSelect; export type SessionRecord = typeof sessions.$inferSelect;
export type NewSessionRecord = typeof sessions.$inferInsert; export type NewSessionRecord = typeof sessions.$inferInsert;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SettingsRepository } from "./settings-repository.js"; import { SettingsRepository } from "./settings-repository.js";
describe("SettingsRepository", () => { describe("SettingsRepository", () => {
let adapter: SqliteDatabaseAdapter | null = null; let adapter: TestSqliteDatabase | null = null;
afterEach(async () => { afterEach(async () => {
if (adapter) { if (adapter) {
@@ -13,11 +13,7 @@ describe("SettingsRepository", () => {
}); });
async function createRepository(): Promise<SettingsRepository> { async function createRepository(): Promise<SettingsRepository> {
adapter = new SqliteDatabaseAdapter({ adapter = new TestSqliteDatabase();
dialect: "sqlite",
url: ":memory:",
sqlitePath: ":memory:",
});
const context = await adapter.connect(); const context = await adapter.connect();
context.sqlite?.exec(` context.sqlite?.exec(`
CREATE TABLE settings ( CREATE TABLE settings (
@@ -1,6 +1,6 @@
import { eq, like } from "drizzle-orm"; import { eq, like } from "drizzle-orm";
import { settings } from "../db/schema.js"; import { settings } from "../db/schema.js";
import type { DatabaseContext } from "../runtime/adapter.js"; import type { DatabaseContext } from "./database-context.js";
export class SettingsRepository { export class SettingsRepository {
constructor( constructor(

Some files were not shown because too many files have changed in this diff Show More