@kaged/storage
SQLite adapter via bun:sqlite with schema migrations, CRUD for sessions/messages/providers/compactions/task instances, and transcript persistence
5
source files
8
test files
~11.2k
lines
✓ 312 pass
tests
pass
typecheck
clean
lint
Test results 312
sqlite storage issues > createIssue + getIssue round-trip
[5.59ms]
sqlite storage issues > getIssue returns null for nonexistent id
[3.86ms]
sqlite storage issues > getIssueByNumber round-trip
[3.59ms]
sqlite storage issues > getIssueByNumber returns null when absent
[3.53ms]
sqlite storage issues > createIssue duplicate id throws ConstraintError
[3.95ms]
sqlite storage issues > createIssue duplicate project/number throws ConstraintError
[3.67ms]
sqlite storage issues > listIssues by projectId
[4.09ms]
sqlite storage issues > listIssues filtered by status
[3.79ms]
sqlite storage issues > listIssues filtered by createdBy
[4.10ms]
sqlite storage issues > listIssues orders by updatedAt descending
[3.72ms]
sqlite storage issues > listIssues paginates with cursor + limit
[3.97ms]
sqlite storage issues > updateIssue updates title
[3.74ms]
sqlite storage issues > updateIssue updates body
[3.90ms]
sqlite storage issues > updateIssue updates status assignment resolution fields
[3.64ms]
sqlite storage issues > updateIssue preserves originalBody on first edit
[3.64ms]
sqlite storage issues > updateIssue does not overwrite originalBody when omitted later
[3.72ms]
sqlite storage issues > updateIssue can clear assignment
[3.60ms]
sqlite storage issues > updateIssue nonexistent throws NotFoundError
[3.62ms]
sqlite storage issues > deleteIssue success
[3.67ms]
sqlite storage issues > deleteIssue nonexistent throws NotFoundError
[3.59ms]
sqlite storage issues > deleteIssue cascades to issue_updates
[3.84ms]
sqlite storage issues > nextIssueNumber starts at 1
[3.54ms]
sqlite storage issues > nextIssueNumber increments per project
[3.72ms]
sqlite storage issues > countIssues counts by project
[3.69ms]
sqlite storage issues > createIssueUpdate + getIssueUpdate round-trip
[3.89ms]
sqlite storage issues > getIssueUpdate returns null for nonexistent id
[3.53ms]
sqlite storage issues > createIssueUpdate duplicate id throws ConstraintError
[3.69ms]
sqlite storage issues > createIssueUpdate missing issue throws ConstraintError
[5.12ms]
sqlite storage issues > listIssueUpdates ordered by created_at ASC
[3.86ms]
sqlite storage issues > listIssueUpdates paginates
[3.96ms]
sqlite storage issues > listIssueUpdates only returns rows for requested issue
[3.75ms]
sqlite storage issues > deleteIssueUpdate success
[3.62ms]
sqlite storage issues > deleteIssueUpdate nonexistent throws NotFoundError
[3.57ms]
sqlite storage issues > metadata round-trip JSON
[3.77ms]
sqlite storage issues > visibility values persist for all and operator_only
[4.14ms]
sqlite storage guest activity > listGuestActivity returns issues opened by the guest
[5.66ms]
sqlite storage guest activity > listGuestActivity returns comments made by the guest
[2.93ms]
sqlite storage guest activity > listGuestActivity returns grant events for the guest
[3.02ms]
sqlite storage guest activity > listGuestActivity filters by projectId
[2.96ms]
sqlite storage guest activity > listGuestActivity paginates with cursor + limit
[3.29ms]
sqlite storage users > migrates v14 guest schema into v15 user schema
[11.83ms]
sqlite storage users > createUser and lookup methods round-trip new fields
[5.45ms]
sqlite storage users > listUsers filters by status and role
[5.53ms]
sqlite storage users > updateUser updates email avatar provider and role
[5.35ms]
sqlite storage users > lookupUsers fetches users in requested order and skips missing ids
[5.57ms]
sqlite storage users > listPendingUsers returns only pending users
[5.37ms]
sqlite storage users > reapPendingSsoUsers deletes only stale pending sso users without invites
[5.85ms]
sqlite storage compactions > createCompaction + getCompaction round-trip
[5.22ms]
sqlite storage compactions > getCompaction returns null for nonexistent id
[3.20ms]
sqlite storage compactions > createCompaction duplicate id throws ConstraintError
[3.34ms]
sqlite storage compactions > round-trips all trigger types
[3.44ms]
sqlite storage compactions > round-trips all strategy types
[3.44ms]
sqlite storage compactions > round-trips supersededMessageIds as JSON array
[3.22ms]
sqlite storage compactions > round-trips empty supersededMessageIds
[3.18ms]
sqlite storage compactions > round-trips pluginsFired with entries
[3.27ms]
sqlite storage compactions > round-trips pluginCost when present
[3.21ms]
sqlite storage compactions > round-trips pluginCost as null
[3.20ms]
sqlite storage compactions > round-trips fallbackOccurred boolean
[3.18ms]
sqlite storage compactions > round-trips summary fields
[3.21ms]
sqlite storage compactions > round-trips checkpoint linkage
[3.23ms]
sqlite storage compactions > round-trips operator feedback fields
[3.21ms]
sqlite storage compactions — listCompactions > lists compactions for a session ordered by created_at
[3.41ms]
sqlite storage compactions — listCompactions > returns empty list for session with no compactions
[3.24ms]
sqlite storage compactions — listCompactions > paginates with cursor
[3.63ms]
sqlite storage compactions — listCompactions > does not return compactions from other sessions
[3.35ms]
sqlite storage compactions — updateCompaction > updates afterEstimate
[3.39ms]
sqlite storage compactions — updateCompaction > updates operator feedback
[3.32ms]
sqlite storage compactions — updateCompaction > updates fallback fields
[3.87ms]
sqlite storage compactions — updateCompaction > updates supersededMessageIds
[3.26ms]
sqlite storage compactions — updateCompaction > updates summary fields
[3.24ms]
sqlite storage compactions — updateCompaction > updates checkpointId
[3.24ms]
sqlite storage compactions — updateCompaction > no-op update does nothing
[3.20ms]
sqlite storage compactions — updateCompaction > throws NotFoundError for nonexistent id
[3.15ms]
sqlite storage compactions — updateCompaction > preserves unchanged fields on partial update
[3.24ms]
sqlite storage compactions — migration v9 > schema version is 9
[3.23ms]
session boundIssue > sqlite storage issue todos and links > create session with boundIssue and getSession returns it
[6.36ms]
session boundIssue > sqlite storage issue todos and links > update session boundIssue persists new issue id
[5.36ms]
session boundIssue > sqlite storage issue todos and links > update session boundIssue can clear to null
[5.24ms]
issue_todos > sqlite storage issue todos and links > create and get issue todo
[5.66ms]
issue_todos > sqlite storage issue todos and links > get non-existent issue todo returns null
[5.42ms]
issue_todos > sqlite storage issue todos and links > duplicate issue todo id throws ConstraintError
[5.62ms]
issue_todos > sqlite storage issue todos and links > list issue todos returns position order ascending
[5.83ms]
issue_todos > sqlite storage issue todos and links > list issue todos breaks position ties by id
[5.66ms]
issue_todos > sqlite storage issue todos and links > list issue todos filters by status
[5.74ms]
issue_todos > sqlite storage issue todos and links > list issue todos filters by kind
[5.61ms]
issue_todos > sqlite storage issue todos and links > list issue todos filters by phase
[5.57ms]
issue_todos > sqlite storage issue todos and links > list issue todos combines status kind and phase filters
[5.50ms]
issue_todos > sqlite storage issue todos and links > update issue todo persists mutable fields
[21.22ms]
issue_todos > sqlite storage issue todos and links > update non-existent issue todo throws NotFoundError
[5.34ms]
issue_todos > sqlite storage issue todos and links > delete issue todo removes it
[5.45ms]
issue_todos > sqlite storage issue todos and links > delete non-existent issue todo throws NotFoundError
[5.26ms]
issue_todos > sqlite storage issue todos and links > count issue todos counts only matching issue
[5.56ms]
issue_todos > sqlite storage issue todos and links > nextIssueTodoPosition starts at one for empty issue
[5.29ms]
issue_todos > sqlite storage issue todos and links > nextIssueTodoPosition uses max position plus one
[6.24ms]
issue_todos > sqlite storage issue todos and links > storage allows multiple in_progress (handler enforces single-in_progress)
[5.61ms]
issue_todos > sqlite storage issue todos and links > another todo can become in_progress after current one completes
[5.54ms]
issue_links > sqlite storage issue todos and links > create and get issue link
[5.75ms]
issue_links > sqlite storage issue todos and links > get non-existent issue link returns null
[5.43ms]
issue_links > sqlite storage issue todos and links > duplicate issue link id throws ConstraintError
[5.59ms]
issue_links > sqlite storage issue todos and links > listIssueLinksFrom returns links ordered by createdAt
[5.80ms]
issue_links > sqlite storage issue todos and links > listIssueLinksTo returns links ordered by createdAt
[5.73ms]
issue_links > sqlite storage issue todos and links > listIssueLinksForProject returns only matching project links
[5.83ms]
issue_links > sqlite storage issue todos and links > listIssueLinksForProject paginates by cursor
[5.92ms]
issue_links > sqlite storage issue todos and links > delete issue link removes it
[5.68ms]
issue_links > sqlite storage issue todos and links > delete non-existent issue link throws NotFoundError
[5.52ms]
cascade delete > sqlite storage issue todos and links > deleting an issue cascades to issue_todos
[5.72ms]
cascade delete > sqlite storage issue todos and links > deleting an issue cascades to outgoing and incoming issue_links
[5.86ms]
session kind filter > createSession stores kind and getSession returns it
[9.90ms]
session kind filter > createSession defaults kind to chat
[5.14ms]
session kind filter > listSessions filters by kind
[6.15ms]
session kind filter > listSessions without kind filter returns all sessions
[5.25ms]
workflow invocations > createWorkflowInvocation and getWorkflowInvocation round-trip
[6.12ms]
workflow invocations > createWorkflowInvocation with no steps
[5.32ms]
workflow invocations > createWorkflowInvocation with session FK
[5.34ms]
workflow invocations > createWorkflowInvocation duplicate id throws ConstraintError
[5.39ms]
workflow invocations > createWorkflowInvocation is transactional — duplicate step PK rolls back invocation
[5.32ms]
workflow invocations > getWorkflowInvocation returns null for missing id
[5.07ms]
workflow invocations > getWorkflowInvocation returns steps ordered by idx
[5.35ms]
workflow invocations > listWorkflowInvocations returns invocations for project
[8.95ms]
workflow invocations > listWorkflowInvocations filters by workflow name
[5.43ms]
workflow invocations > listWorkflowInvocations filters by invokerKind
[5.32ms]
workflow invocations > listWorkflowInvocations filters by invokerId
[5.44ms]
workflow invocations > listWorkflowInvocations filters by state
[5.36ms]
workflow invocations > listWorkflowInvocations paginates
[5.69ms]
workflow invocations > listWorkflowInvocations returns empty for unknown project
[5.16ms]
workflow invocations > listWorkflowInvocations combines filters
[5.50ms]
workflow invocations > updateWorkflowInvocation updates state
[5.80ms]
workflow invocations > updateWorkflowInvocation updates all fields
[5.39ms]
workflow invocations > updateWorkflowInvocation can clear nullable fields
[5.39ms]
workflow invocations > updateWorkflowInvocation with empty updates is a no-op
[5.20ms]
workflow invocations > updateWorkflowInvocation throws NotFoundError for missing id
[5.19ms]
workflow invocations > updateWorkflowInvocationStep updates status
[5.44ms]
workflow invocations > updateWorkflowInvocationStep updates all fields
[5.37ms]
workflow invocations > updateWorkflowInvocationStep can set error
[5.30ms]
workflow invocations > updateWorkflowInvocationStep with empty updates is a no-op
[5.20ms]
workflow invocations > updateWorkflowInvocationStep throws NotFoundError for missing id
[5.12ms]
workflow invocations > countActiveInvocations counts non-terminal states
[5.86ms]
workflow invocations > countActiveInvocations scopes to project
[5.38ms]
workflow invocations > countActiveInvocations returns 0 for unknown project
[5.04ms]
workflow invocations > countActiveInvocationsByInvoker counts by invoker identity
[5.57ms]
workflow invocations > countActiveInvocationsByInvoker excludes all terminal states
[15.58ms]
workflow uploads > createWorkflowUpload and getWorkflowUpload round-trip
[5.46ms]
workflow uploads > createWorkflowUpload duplicate token throws ConstraintError
[5.31ms]
workflow uploads > getWorkflowUpload returns null for missing token
[5.18ms]
workflow uploads > consumeWorkflowUpload sets consumed_by
[5.40ms]
workflow uploads > consumeWorkflowUpload is idempotent-safe — already consumed throws NotFoundError
[5.48ms]
workflow uploads > consumeWorkflowUpload throws NotFoundError for missing token
[5.18ms]
workflow uploads > deleteExpiredWorkflowUploads removes expired unconsumed uploads
[5.69ms]
workflow uploads > deleteExpiredWorkflowUploads skips consumed uploads even if expired
[5.61ms]
workflow uploads > deleteExpiredWorkflowUploads returns 0 when nothing to delete
[5.19ms]
workflow uploads > deleteExpiredWorkflowUploads boundary — exactly at cutoff not deleted
[5.40ms]
listNonTerminalWorkflowInvocations > returns empty array when no invocations
[5.45ms]
listNonTerminalWorkflowInvocations > returns only non-terminal invocations
[6.07ms]
listNonTerminalWorkflowInvocations > returns invocations across projects
[5.90ms]
initialization > sqlite storage > creates database with WAL mode
[8.65ms]
initialization > sqlite storage > creates schema_version table with current version
[5.85ms]
initialization > sqlite storage > creates all 28 tables
[5.93ms]
initialization > sqlite storage > idempotent — opening same DB twice does not fail
[5.67ms]
initialization > sqlite storage > foreign keys are enforced
[5.39ms]
sessions > sqlite storage > create and get session
[5.25ms]
sessions > sqlite storage > get non-existent session returns null
[5.15ms]
sessions > sqlite storage > duplicate session id throws ConstraintError
[5.28ms]
sessions > sqlite storage > update session state
[5.24ms]
sessions > sqlite storage > update session endedAt
[5.24ms]
sessions > sqlite storage > update session name
[5.43ms]
sessions > sqlite storage > clear session name with null
[5.59ms]
sessions > sqlite storage > create session with name
[5.50ms]
sessions > sqlite storage > update non-existent session throws NotFoundError
[5.59ms]
sessions > sqlite storage > list sessions returns all
[5.64ms]
sessions > sqlite storage > list sessions filters by projectId
[5.73ms]
sessions > sqlite storage > list sessions filters by createdBy
[5.59ms]
sessions > sqlite storage > list sessions filters by state
[5.71ms]
sessions > sqlite storage > list sessions paginates with cursor
[5.90ms]
sessions > sqlite storage > count sessions by project excludes ended sessions
[5.95ms]
sessions > sqlite storage > count sessions by operator excludes ended sessions
[5.59ms]
sessions > sqlite storage > forkedFrom is persisted
[5.62ms]
runs > sqlite storage > create and get run
[5.65ms]
runs > sqlite storage > get non-existent run returns null
[5.43ms]
runs > sqlite storage > duplicate run id throws ConstraintError
[5.60ms]
runs > sqlite storage > update run state and completion
[7.93ms]
runs > sqlite storage > update run with error
[5.31ms]
runs > sqlite storage > update non-existent run throws NotFoundError
[5.34ms]
runs > sqlite storage > list runs for session
[5.36ms]
runs > sqlite storage > list runs paginates
[5.50ms]
runs > sqlite storage > count runs for session
[5.27ms]
runs > sqlite storage > run error round-trips through JSON
[5.15ms]
runs > sqlite storage > getProjectStats returns session aggregates, 24h activity, and recent runs
[7.00ms]
messages > sqlite storage > create and list messages
[5.94ms]
messages > sqlite storage > supersede messages after cursor
[6.02ms]
messages > sqlite storage > messages paginate with cursor
[5.98ms]
messages > sqlite storage > message metadata round-trips through JSON
[5.75ms]
messages > sqlite storage > message with runId
[5.59ms]
messages > sqlite storage > created_by round-trips for operator messages
[5.72ms]
messages > sqlite storage > created_by defaults to null when not set
[5.69ms]
checkpoints > sqlite storage > create and get checkpoint
[8.37ms]
checkpoints > sqlite storage > get non-existent checkpoint returns null
[5.29ms]
checkpoints > sqlite storage > update checkpoint resumed
[5.44ms]
checkpoints > sqlite storage > update checkpoint rolled back
[5.40ms]
checkpoints > sqlite storage > update checkpoint superseded
[5.34ms]
checkpoints > sqlite storage > update non-existent checkpoint throws NotFoundError
[5.27ms]
checkpoints > sqlite storage > list checkpoints for session
[5.48ms]
checkpoints > sqlite storage > count checkpoints
[5.36ms]
checkpoints > sqlite storage > rolledBack round-trips as boolean
[5.25ms]
checkpoints > sqlite storage > createdBy values persist correctly
[5.28ms]
subagent invocations > sqlite storage > create and get subagent invocation
[5.39ms]
subagent invocations > sqlite storage > get non-existent returns null
[5.35ms]
subagent invocations > sqlite storage > update subagent invocation state
[5.72ms]
subagent invocations > sqlite storage > update non-existent throws NotFoundError
[5.64ms]
subagent invocations > sqlite storage > list subagent invocations for run
[5.86ms]
subagent invocations > sqlite storage > cageId null when uncaged
[26.38ms]
tool calls > sqlite storage > create and get tool call
[5.77ms]
tool calls > sqlite storage > get non-existent returns null
[5.48ms]
tool calls > sqlite storage > update tool call completion
[5.77ms]
tool calls > sqlite storage > update non-existent throws NotFoundError
[5.56ms]
tool calls > sqlite storage > list tool calls for run
[8.51ms]
tool calls > sqlite storage > tool calls paginate
[5.62ms]
pty transcripts > sqlite storage > create and list pty transcripts
[5.51ms]
pty transcripts > sqlite storage > empty list for unknown run
[5.37ms]
pty transcripts > sqlite storage > multiple transcripts per run
[5.41ms]
prompt edits > sqlite storage > create and list prompt edits
[5.44ms]
prompt edits > sqlite storage > empty list for unknown checkpoint
[5.29ms]
prompt edits > sqlite storage > multiple edits per checkpoint
[5.39ms]
edge cases > sqlite storage > empty updates are no-ops (runs)
[5.26ms]
edge cases > sqlite storage > empty updates are no-ops (checkpoints)
[5.27ms]
edge cases > sqlite storage > page limit clamped to max 200
[5.20ms]
edge cases > sqlite storage > page limit minimum 1
[5.22ms]
edge cases > sqlite storage > close is idempotent
[5.41ms]
edge cases > sqlite storage > supersedeMessagesAfter returns 0 when no messages match
[5.51ms]
errors module > sqlite storage > StorageError has correct name
[5.50ms]
errors module > sqlite storage > NotFoundError includes entity info
[5.39ms]
errors module > sqlite storage > ConstraintError preserves cause
[5.45ms]
task instances > sqlite storage > create and get task instance
[5.72ms]
task instances > sqlite storage > get returns null for missing instance
[5.50ms]
task instances > sqlite storage > duplicate create throws ConstraintError
[5.60ms]
task instances > sqlite storage > update task instance state and exit
[5.68ms]
task instances > sqlite storage > update nonexistent instance throws NotFoundError
[5.59ms]
task instances > sqlite storage > update with empty changes is no-op
[5.50ms]
task instances > sqlite storage > update tmux fields
[5.67ms]
task instances > sqlite storage > delete task instance
[5.60ms]
task instances > sqlite storage > delete nonexistent instance throws NotFoundError
[5.63ms]
task instances > sqlite storage > list task instances by project
[7.53ms]
task instances > sqlite storage > list task instances by state
[5.27ms]
task instances > sqlite storage > list task instances respects pagination limit
[5.40ms]
task instances > sqlite storage > count task instances by project
[5.36ms]
task instances > sqlite storage > stores and retrieves all fields including nulls
[5.18ms]
task instances > sqlite storage > stores and retrieves instance with all null optional fields
[5.22ms]
task transcripts > sqlite storage > create and list task transcript
[5.34ms]
task transcripts > sqlite storage > empty list for unknown task instance
[5.23ms]
task transcripts > sqlite storage > multiple transcripts per task instance
[5.63ms]
task transcripts > sqlite storage > duplicate id throws ConstraintError
[5.88ms]
task transcripts > sqlite storage > foreign key to nonexistent task instance fails
[5.54ms]
task transcripts > sqlite storage > large transcript blob round-trips correctly
[7.95ms]
guests > sqlite storage > createGuest + getGuest round-trip
[5.66ms]
guests > sqlite storage > getGuestByHandle
[5.60ms]
guests > sqlite storage > listGuests with status filter and pagination
[5.93ms]
guests > sqlite storage > updateGuest handle, displayName, passwordHash, status changes
[5.61ms]
guests > sqlite storage > updateGuest throws ConstraintError on duplicate handle
[5.69ms]
guests > sqlite storage > deleteGuest + cascade to invites, sessions, grants
[6.09ms]
guests > sqlite storage > createGuest throws ConstraintError on duplicate userId
[5.60ms]
guests > sqlite storage > createGuest throws ConstraintError on duplicate handle
[5.47ms]
guests > sqlite storage > getGuest returns null for missing
[5.34ms]
guest invites > sqlite storage > createGuestInvite + getGuestInvite round-trip
[5.52ms]
guest invites > sqlite storage > deleteGuestInvite
[5.57ms]
guest invites > sqlite storage > deleteGuestInvitesByUser
[5.75ms]
guest invites > sqlite storage > deleteExpiredGuestInvites returns count of deleted
[5.80ms]
guest invites > sqlite storage > getGuestInvite returns null for missing
[5.37ms]
guest sessions > sqlite storage > createGuestSession + getGuestSession round-trip
[5.56ms]
guest sessions > sqlite storage > updateGuestSession lastActiveAt
[5.54ms]
guest sessions > sqlite storage > deleteGuestSession
[5.52ms]
guest sessions > sqlite storage > deleteGuestSessionsByUser
[5.81ms]
guest sessions > sqlite storage > updateGuestSession throws NotFoundError for missing
[5.50ms]
guest sessions > sqlite storage > getGuestSession returns null for missing
[5.41ms]
project guest grants > sqlite storage > createProjectGuestGrant + getProjectGuestGrant round-trip
[5.72ms]
project guest grants > sqlite storage > JSON round-trip for permissionSet
[5.57ms]
project guest grants > sqlite storage > listProjectGuestGrants by project
[6.03ms]
project guest grants > sqlite storage > listGuestGrants by user
[5.74ms]
project guest grants > sqlite storage > updateProjectGuestGrant new permissionSet, notes
[5.84ms]
project guest grants > sqlite storage > deleteProjectGuestGrant
[5.64ms]
project guest grants > sqlite storage > deleteProjectGuestGrant throws NotFoundError for missing
[5.40ms]
project guest grants > sqlite storage > getProjectGuestGrant returns null for missing
[5.40ms]
model overrides > sqlite storage > getModelOverrides returns empty for no overrides
[5.47ms]
model overrides > sqlite storage > upsert and get single override
[5.60ms]
model overrides > sqlite storage > upsert multiple overrides for same model
[5.52ms]
model overrides > sqlite storage > upsert replaces existing override (idempotent)
[5.49ms]
model overrides > sqlite storage > overrides are isolated per provider
[5.45ms]
model overrides > sqlite storage > overrides are isolated per model
[5.53ms]
model overrides > sqlite storage > deleteModelOverride removes single field
[5.66ms]
model overrides > sqlite storage > deleteModelOverride throws NotFoundError for missing
[5.42ms]
model overrides > sqlite storage > deleteAllModelOverrides removes all fields for a model
[5.64ms]
model overrides > sqlite storage > deleteAllModelOverrides returns 0 when none exist
[5.40ms]
provider spend limits > sqlite storage > getSpendLimit returns null when not set
[5.41ms]
provider spend limits > sqlite storage > upsert and get spend limit
[5.58ms]
provider spend limits > sqlite storage > upsert replaces existing spend limit
[5.53ms]
provider spend limits > sqlite storage > spend limit with percentage-based windows
[5.64ms]
provider spend limits > sqlite storage > deleteSpendLimit removes the limit
[5.63ms]
provider spend limits > sqlite storage > deleteSpendLimit throws NotFoundError for missing
[5.39ms]
provider spend limits > sqlite storage > limits are isolated per provider
[5.55ms]
provider spend events > sqlite storage > createSpendEvent and sumSpendByWindow
[5.67ms]
provider spend events > sqlite storage > sumSpendByWindow returns null when no events
[5.46ms]
provider spend events > sqlite storage > sumSpendByWindow isolates by provider
[5.58ms]
provider spend events > sqlite storage > sumSpendByWindow isolates by window key
[5.63ms]
provider spend events > sqlite storage > sumSpendByWindow7d with 7-day window
[5.51ms]
provider spend events > sqlite storage > sumSpendByWindow7d returns null when no events
[5.42ms]
provider usage cache > sqlite storage > getUsageCache returns null when not cached
[5.38ms]
provider usage cache > sqlite storage > setUsageCache and getUsageCache
[5.57ms]
provider usage cache > sqlite storage > setUsageCache replaces existing cache
[5.49ms]
provider usage cache > sqlite storage > deleteUsageCache removes the cache
[5.52ms]
provider usage cache > sqlite storage > deleteUsageCache is idempotent for missing provider
[5.34ms]
provider usage cache > sqlite storage > usage cache is isolated per provider
[5.49ms]
notification storage helpers > stores and lists push subscriptions without exposing secrets
[12.26ms]
notification storage helpers > deletes and reaps subscriptions by owner
[5.53ms]
notification storage helpers > persists per-session bell values
[5.46ms]