each feature declares its own exports; the barrel is one line per feature

src/index.ts is 47 lines and reads as a list: `export * from './agent-panels';`
and 22 more, under `db` and `schema`. It was 297 lines of hand-written named
exports.

What a feature exports now lives in <feature>/index.ts, beside the schema and
queries it describes. Adding a query function is one file in one directory rather
than that file plus a list three levels up that nothing enforces — a function
missing from that list was invisible to all 107 consumers while existing and
compiling perfectly.

The old file had drifted in the ways a hand-maintained list does: twelve features
were listed twice because values and types were separate statements repeating the
path, soulseek three times, two features used an inline `type` specifier instead,
and `db` and `schema` — the package's most fundamental exports — sat at line 270
with notify, types and app-store appended after them.

The surface is byte-for-byte the same set. Checked rather than asserted: 247
exported names before, 247 after, no missing and no extra. The per-feature index
files carry the same named lists the barrel did, so `export *` widens nothing.

`operations` is deliberately not in the list, and the root file says why: it has a
schema and no queries, its task_logs is reached as `schema.taskLogs` from
src/servers past this package's boundary, and its other two tables are read by
nothing at all.

Verified: every file in the package parses, every relative import resolves to a
real file, and all 107 consumers still parse. Not typechecked — empty
node_modules, frozen installs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-13 01:45:31 +00:00
co-authored by Claude Opus 5
parent 68f2c55ecf
commit 288b4bf683
25 changed files with 349 additions and 293 deletions
+8 -1
View File
@@ -10,17 +10,24 @@ describing a different codebase.
src/databases/officer_db/ src/databases/officer_db/
├── src/ ├── src/
│ ├── db.ts # the connection │ ├── db.ts # the connection
│ ├── index.ts # public surface: re-exports every feature's queries │ ├── index.ts # public surface: one `export * from './<feature>'` per line
│ ├── schema.ts # what db:push creates — see below │ ├── schema.ts # what db:push creates — see below
│ ├── types.ts # every type export (Select / Insert / extended) │ ├── types.ts # every type export (Select / Insert / extended)
│ ├── crypto.ts # at-rest encryption, one key per purpose │ ├── crypto.ts # at-rest encryption, one key per purpose
│ ├── secret-store.ts # the key store itself (SQLite, outside Postgres) │ ├── secret-store.ts # the key store itself (SQLite, outside Postgres)
│ └── <feature>/ │ └── <feature>/
│ ├── index.ts # what this feature exports — declared here, not in a list three levels up
│ ├── schema.ts # its tables │ ├── schema.ts # its tables
│ └── queries.ts # everything that reads or writes them │ └── queries.ts # everything that reads or writes them
└── package.json # exports ".", "./types", "./db", "./schema", "./secret-store", "./*" └── package.json # exports ".", "./types", "./db", "./schema", "./secret-store", "./*"
``` ```
**A feature owns its own public surface.** `src/index.ts` is one `export *` per feature and nothing
else; what a feature exports is declared in its own `index.ts`, beside the code it describes. Adding a
query function is one file in one directory, rather than that file plus a hand-written list of every
symbol in the package. That list was 297 lines until 2026-08-13 and it had already drifted — twelve
features listed twice, `db` and `schema` buried at line 270 with three feature blocks after them.
**One directory per feature, holding both halves.** Restructured 2026-08-13 from parallel `schema/` and **One directory per feature, holding both halves.** Restructured 2026-08-13 from parallel `schema/` and
`queries/` trees, where the two sides had drifted: four features were named differently on each side `queries/` trees, where the two sides had drifted: four features were named differently on each side
(`app-store`/`sidecar-installs`, `email`/`email-accounts`, `server`/`server-config`), `operations` had no (`app-store`/`sidecar-installs`, `email`/`email-accounts`, `server`/`server-config`), `operations` had no
@@ -0,0 +1,13 @@
export {
listAgentPanels,
getAgentPanelByPanelId,
getAgentPanelByName,
getAgentPanelByHandoffToken,
createAgentPanel,
updateAgentPanel,
markAgentPanelIntroduced,
deleteAgentPanel,
toAgentPanelView,
} from './queries';
export type { AgentPanel, AgentPanelView, CreateAgentPanelInput, UpdateAgentPanelInput } from './queries';
@@ -0,0 +1,8 @@
export {
findLiveApiKeyByHash,
createApiKey,
listApiKeys,
revokeApiKey,
touchApiKey,
type ApiKeyIdentity,
} from './queries';
@@ -0,0 +1,13 @@
// App store — what the owner has installed, and whether it should be running.
export {
listSidecarInstalls,
getSidecarInstall,
beginInstall,
recordSteps,
markInstalled,
markFailed,
markBlocked,
setEnabled,
removeInstall,
type SidecarInstall,
} from './queries';
@@ -0,0 +1,27 @@
export {
getUsers,
getUserById,
getUserByEmail,
getUserByUsername,
getOwnerUser,
getUserCount,
createUser,
updateUser,
deleteUser,
getPasskeysByUserId,
getPasskeysByUserIdAndOrigin,
getPasskeyByCredentialId,
createPasskey,
updatePasskey,
storeChallenge,
consumeChallenge,
blacklistToken,
isTokenBlacklisted,
cleanupExpiredTokens,
} from './queries';
// Exported as a value, not just a type: the API and the UI need to enumerate the roles, and the
// column definition is the only place that list should exist.
export { USER_ROLES, OWNER_USER_ID } from './schema';
export type { UserRole } from './schema';
@@ -0,0 +1,11 @@
export {
getAllRoleGrants,
getRoleGrants,
setRoleGrant,
revokeRoleGrant,
replaceRoleGrants,
} from './queries';
export type { RoleGrant } from './queries';
export type { CapabilityLevelValue } from './schema';
@@ -0,0 +1,6 @@
export {
appendChatEvent,
getChatEventsSince,
getLastChatEventSeq,
pruneChatEventsOlderThan,
} from './queries';
@@ -0,0 +1,11 @@
export {
getAllDashboardState,
upsertDashboard,
updateDashboard,
deleteDashboard,
setDashboardPanelState,
upsertScreen,
deleteScreen,
upsertDefaults,
setDefaultsPanelState,
} from './queries';
@@ -0,0 +1,9 @@
export {
listDavAppPasswords,
createDavAppPassword,
revokeDavAppPassword,
deleteDavAppPassword,
verifyDavAppPassword,
} from './queries';
export type { DavAppPassword, DavAppPasswordView } from './queries';
@@ -0,0 +1,9 @@
export {
getEmailAccounts,
getEmailAccount,
createEmailAccount,
deleteEmailAccount,
updateEmailAccountStatus,
updateEmailAccountSyncMeta,
getAllSyncedAccounts,
} from './queries';
@@ -0,0 +1,12 @@
export {
listHeadscaleServers,
getActiveHeadscaleCredentials,
getHeadscaleCredentials,
createHeadscaleServer,
updateHeadscaleServer,
setActiveHeadscaleServer,
deleteHeadscaleServer,
recordHeadscaleProbe,
} from './queries';
export type { HeadscaleServer, HeadscaleServerCredentials } from './queries';
+42 -292
View File
@@ -1,297 +1,47 @@
export { // The package's public surface.
getUsers, //
getUserById, // One line per feature, and nothing else. What each feature exports is stated in its own index.ts,
getUserByEmail, // beside the schema and queries it exports — so adding a query function means editing one file in one
getUserByUsername, // directory, not that file plus a list three levels up that nobody remembers to update.
getOwnerUser, //
getUserCount, // This was 297 lines of hand-written named exports until 2026-08-13. Every symbol was listed here, twice
createUser, // for most features (values, then types, repeating the path), and `db` and `schema` sat at line 270 with
updateUser, // three feature blocks appended after them.
deleteUser, //
getPasskeysByUserId, // The surface is unchanged: the same names are exported, they are just declared next to what they
getPasskeysByUserIdAndOrigin, // describe. 107 files import from 'officerdb' and none of them notice.
getPasskeyByCredentialId,
createPasskey,
updatePasskey,
storeChallenge,
consumeChallenge,
blacklistToken,
isTokenBlacklisted,
cleanupExpiredTokens,
} from './auth/queries';
export {
findLiveApiKeyByHash,
createApiKey,
listApiKeys,
revokeApiKey,
touchApiKey,
type ApiKeyIdentity,
} from './api-keys/queries';
export { readServerSettings, writeServerSettings, readConfigValue, writeConfigValue } from './server/queries';
export {
getUserSettings,
setUserSettings,
getUserState,
patchUserState,
getDockPaths,
setDockPaths,
} from './user-data/queries';
export {
getServerIntegrations,
getServerIntegration,
upsertServerIntegration,
deleteServerIntegration,
getUserIntegrations,
getUserIntegration,
getIntegrationsByProvider,
upsertUserIntegration,
deleteUserIntegration,
findUserByIntegrationConfig,
} from './integrations/queries';
export {
getEmailAccounts,
getEmailAccount,
createEmailAccount,
deleteEmailAccount,
updateEmailAccountStatus,
updateEmailAccountSyncMeta,
getAllSyncedAccounts,
} from './email/queries';
export {
getAllDashboardState,
upsertDashboard,
updateDashboard,
deleteDashboard,
setDashboardPanelState,
upsertScreen,
deleteScreen,
upsertDefaults,
setDefaultsPanelState,
} from './dashboards/queries';
export {
createPipelineJob,
getPipelineJob,
updatePipelineJob,
getPipelineJobsForUser,
getOldestPendingJob,
getPendingJobs,
countPendingJobs,
deletePipelineJob,
deleteTerminalJobsForUser,
markInterruptedJobs,
} from './pipeline-jobs/queries';
export {
appendChatEvent,
getChatEventsSince,
getLastChatEventSeq,
pruneChatEventsOlderThan,
} from './chat-events/queries';
export {
listAgentPanels,
getAgentPanelByPanelId,
getAgentPanelByName,
getAgentPanelByHandoffToken,
createAgentPanel,
updateAgentPanel,
markAgentPanelIntroduced,
deleteAgentPanel,
toAgentPanelView,
} from './agent-panels/queries';
export type { AgentPanel, AgentPanelView, CreateAgentPanelInput, UpdateAgentPanelInput } from './agent-panels/queries';
export {
getMusicFavorites,
addMusicFavorite,
removeMusicFavorite,
getNowPlaying,
setNowPlaying,
clearNowPlaying,
getPlaylists,
getPlaylist,
createPlaylist,
renamePlaylist,
deletePlaylist,
addPlaylistItems,
setPlaylistItems,
} from './music/queries';
export type {
FavoriteKind,
GroupedFavorites,
NowPlaying,
NowPlayingInput,
PlaylistSummary,
Playlist,
} from './music/queries';
export { getSoulseekFavorites, addSoulseekFavorite, removeSoulseekFavorite } from './soulseek/queries';
export {
getSoulseekBrowseSnapshots,
getSoulseekBrowseSnapshot,
startSoulseekBrowse,
finishSoulseekBrowse,
failSoulseekBrowse,
resetStaleSoulseekBrowses,
getSoulseekBrowseLevel,
searchSoulseekBrowseTree,
getSoulseekBrowseDirFiles,
getSoulseekBrowseDownload,
deleteSoulseekBrowse,
} from './soulseek/queries';
export type {
BrowseDownloadFile,
BrowsedFile,
BrowseDirInput,
BrowseDirRow,
BrowseTreeNode,
BrowseLevel,
BrowseTreeSearch,
SoulseekBrowseSnapshot,
} from './soulseek/queries';
export {
listHeadscaleServers,
getActiveHeadscaleCredentials,
getHeadscaleCredentials,
createHeadscaleServer,
updateHeadscaleServer,
setActiveHeadscaleServer,
deleteHeadscaleServer,
recordHeadscaleProbe,
} from './headscale/queries';
export type { HeadscaleServer, HeadscaleServerCredentials } from './headscale/queries';
export {
listInvoiceshelfAccounts,
getActiveInvoiceshelfCredentials,
getInvoiceshelfCredentials,
createInvoiceshelfAccount,
updateInvoiceshelfAccount,
setActiveInvoiceshelfAccount,
deleteInvoiceshelfAccount,
recordInvoiceshelfProbe,
} from './invoiceshelf/queries';
export type { InvoiceshelfAccount, InvoiceshelfCredentials } from './invoiceshelf/queries';
export {
listJellyfinServers,
getActiveJellyfinCredentials,
getJellyfinCredentials,
createJellyfinServer,
updateJellyfinServer,
setActiveJellyfinServer,
deleteJellyfinServer,
recordJellyfinProbe,
} from './jellyfin/queries';
export type { JellyfinServer, JellyfinCredentials } from './jellyfin/queries';
export {
listPhotosAccounts,
getActivePhotosCredentials,
getPhotosCredentials,
createPhotosAccount,
updatePhotosAccount,
setActivePhotosAccount,
deletePhotosAccount,
recordPhotosProbe,
} from './photos/queries';
export type { PhotosAccount, PhotosCredentials } from './photos/queries';
export {
listDavAppPasswords,
createDavAppPassword,
revokeDavAppPassword,
deleteDavAppPassword,
verifyDavAppPassword,
} from './dav/queries';
export type { DavAppPassword, DavAppPasswordView } from './dav/queries';
export {
getServiceConnection,
getServiceCredentials,
saveServiceConnection,
deleteServiceConnection,
recordServiceProbe,
getServiceInstanceUrl,
getResolvedServiceCredentials,
} from './service-connections/queries';
export type { ServiceName, ServiceConnection, ServiceCredentials } from './service-connections/queries';
export {
getAllRoleGrants,
getRoleGrants,
setRoleGrant,
revokeRoleGrant,
replaceRoleGrants,
} from './capabilities/queries';
export type { RoleGrant } from './capabilities/queries';
export type { CapabilityLevelValue } from './capabilities/schema';
export {
getVaultTokens,
setVaultTokens,
updateVaultAccess,
clearVaultTokens,
getVaultUnlockKey,
setVaultUnlockKey,
clearVaultUnlockKey,
} from './vault/queries';
export type { VaultTokenSet } from './vault/queries';
export {
listWallets,
getWallet,
getActiveWallet,
getWalletSecrets,
getSealedSeed,
createWallet,
updateWallet,
replaceSealedSeed,
setActiveWallet,
deleteWallet,
getWalletLabels,
setWalletLabel,
getFrozenOutpoints,
setUtxoFrozen,
getWalletChainCache,
saveWalletChainCache,
recordWalletChainError,
} from './wallet/queries';
export type {
WalletKind,
WalletSummary,
WalletSecrets,
WalletLabel,
CreateWalletParams,
WalletChainCache,
} from './wallet/queries';
export type { WalletChainSnapshot } from './wallet/schema';
// Exported as a value, not just a type: the API and the UI need to enumerate the roles, and the
// column definition is the only place that list should exist.
export { USER_ROLES, OWNER_USER_ID } from './auth/schema';
export type { UserRole } from './auth/schema';
// The connection, and drizzle-kit's view of the schema. See ./schema.ts for the core/plugin split.
export { db } from './db'; export { db } from './db';
export * as schema from './schema'; export * as schema from './schema';
export {
upsertPushDevice,
getPushDevices,
deletePushDevice,
recordPushFailure,
markPushDeviceSeen,
} from './notify/queries';
export type { PushDeviceSelect, PushDeviceInsert } from './types';
// App store — what the owner has installed, and whether it should be running. export * from './agent-panels';
export { export * from './api-keys';
listSidecarInstalls, export * from './app-store';
getSidecarInstall, export * from './auth';
beginInstall, export * from './capabilities';
recordSteps, export * from './chat-events';
markInstalled, export * from './dashboards';
markFailed, export * from './dav';
markBlocked, export * from './email';
setEnabled, export * from './headscale';
removeInstall, export * from './integrations';
type SidecarInstall, export * from './invoiceshelf';
} from './app-store/queries'; export * from './jellyfin';
export * from './music';
export * from './notify';
export * from './photos';
export * from './pipeline-jobs';
export * from './server';
export * from './service-connections';
export * from './soulseek';
export * from './user-data';
export * from './vault';
export * from './wallet';
// `operations` is deliberately absent: it has a schema and no queries. Its `task_logs` is reached as
// `schema.taskLogs` from src/servers/api/task-logger.ts, which reaches past this package's own boundary.
// Its other two tables, `queue_jobs` and `terminal_containers`, are read by nothing at all — the queue
// engine works on files (src/servers/queue/storage.ts) and terminal containers are from an architecture
// that is gone. Give it a queries.ts and it earns a line here.
@@ -0,0 +1,12 @@
export {
getServerIntegrations,
getServerIntegration,
upsertServerIntegration,
deleteServerIntegration,
getUserIntegrations,
getUserIntegration,
getIntegrationsByProvider,
upsertUserIntegration,
deleteUserIntegration,
findUserByIntegrationConfig,
} from './queries';
@@ -0,0 +1,12 @@
export {
listInvoiceshelfAccounts,
getActiveInvoiceshelfCredentials,
getInvoiceshelfCredentials,
createInvoiceshelfAccount,
updateInvoiceshelfAccount,
setActiveInvoiceshelfAccount,
deleteInvoiceshelfAccount,
recordInvoiceshelfProbe,
} from './queries';
export type { InvoiceshelfAccount, InvoiceshelfCredentials } from './queries';
@@ -0,0 +1,12 @@
export {
listJellyfinServers,
getActiveJellyfinCredentials,
getJellyfinCredentials,
createJellyfinServer,
updateJellyfinServer,
setActiveJellyfinServer,
deleteJellyfinServer,
recordJellyfinProbe,
} from './queries';
export type { JellyfinServer, JellyfinCredentials } from './queries';
@@ -0,0 +1,24 @@
export {
getMusicFavorites,
addMusicFavorite,
removeMusicFavorite,
getNowPlaying,
setNowPlaying,
clearNowPlaying,
getPlaylists,
getPlaylist,
createPlaylist,
renamePlaylist,
deletePlaylist,
addPlaylistItems,
setPlaylistItems,
} from './queries';
export type {
FavoriteKind,
GroupedFavorites,
NowPlaying,
NowPlayingInput,
PlaylistSummary,
Playlist,
} from './queries';
@@ -0,0 +1,9 @@
export {
upsertPushDevice,
getPushDevices,
deletePushDevice,
recordPushFailure,
markPushDeviceSeen,
} from './queries';
export type { PushDeviceSelect, PushDeviceInsert } from '../types';
@@ -0,0 +1,12 @@
export {
listPhotosAccounts,
getActivePhotosCredentials,
getPhotosCredentials,
createPhotosAccount,
updatePhotosAccount,
setActivePhotosAccount,
deletePhotosAccount,
recordPhotosProbe,
} from './queries';
export type { PhotosAccount, PhotosCredentials } from './queries';
@@ -0,0 +1,12 @@
export {
createPipelineJob,
getPipelineJob,
updatePipelineJob,
getPipelineJobsForUser,
getOldestPendingJob,
getPendingJobs,
countPendingJobs,
deletePipelineJob,
deleteTerminalJobsForUser,
markInterruptedJobs,
} from './queries';
@@ -0,0 +1 @@
export { readServerSettings, writeServerSettings, readConfigValue, writeConfigValue } from './queries';
@@ -0,0 +1,11 @@
export {
getServiceConnection,
getServiceCredentials,
saveServiceConnection,
deleteServiceConnection,
recordServiceProbe,
getServiceInstanceUrl,
getResolvedServiceCredentials,
} from './queries';
export type { ServiceName, ServiceConnection, ServiceCredentials } from './queries';
@@ -0,0 +1,26 @@
export { getSoulseekFavorites, addSoulseekFavorite, removeSoulseekFavorite } from './queries';
export {
getSoulseekBrowseSnapshots,
getSoulseekBrowseSnapshot,
startSoulseekBrowse,
finishSoulseekBrowse,
failSoulseekBrowse,
resetStaleSoulseekBrowses,
getSoulseekBrowseLevel,
searchSoulseekBrowseTree,
getSoulseekBrowseDirFiles,
getSoulseekBrowseDownload,
deleteSoulseekBrowse,
} from './queries';
export type {
BrowseDownloadFile,
BrowsedFile,
BrowseDirInput,
BrowseDirRow,
BrowseTreeNode,
BrowseLevel,
BrowseTreeSearch,
SoulseekBrowseSnapshot,
} from './queries';
@@ -0,0 +1,8 @@
export {
getUserSettings,
setUserSettings,
getUserState,
patchUserState,
getDockPaths,
setDockPaths,
} from './queries';
@@ -0,0 +1,11 @@
export {
getVaultTokens,
setVaultTokens,
updateVaultAccess,
clearVaultTokens,
getVaultUnlockKey,
setVaultUnlockKey,
clearVaultUnlockKey,
} from './queries';
export type { VaultTokenSet } from './queries';
@@ -0,0 +1,30 @@
export {
listWallets,
getWallet,
getActiveWallet,
getWalletSecrets,
getSealedSeed,
createWallet,
updateWallet,
replaceSealedSeed,
setActiveWallet,
deleteWallet,
getWalletLabels,
setWalletLabel,
getFrozenOutpoints,
setUtxoFrozen,
getWalletChainCache,
saveWalletChainCache,
recordWalletChainError,
} from './queries';
export type {
WalletKind,
WalletSummary,
WalletSecrets,
WalletLabel,
CreateWalletParams,
WalletChainCache,
} from './queries';
export type { WalletChainSnapshot } from './schema';