feat(selfhosted): add optional self-hosted variant with SQLite persistence#19
feat(selfhosted): add optional self-hosted variant with SQLite persistence#19kilo-code-bot[bot] wants to merge 1 commit intomainfrom
Conversation
…tence Add a separate self-hosted Next.js app at selfhosted/ that reuses the existing viewer/renderers while storing payloads in SQLite under UUID v4. Key additions: - SQLite database module with artifacts table (better-sqlite3) - REST API: POST/GET/DELETE/PUT /api/artifacts, POST /api/cleanup - 24-hour sliding TTL with lazy expiry on read - Server-rendered /artifact/[id] route reusing shared viewer components - SelfHostedViewerShell component accepting envelope prop - Docker Compose deployment config with persistent volume - selfhosted-agent-render skill documentation The static fragment-based app at the root is unchanged. Both apps pass lint, typecheck, tests, and build successfully. Updated docs: - README.md, docs/architecture.md, docs/deployment.md - docs/dependency-notes.md, AGENTS.md
Deploying agent-render with
|
| Latest commit: |
005903a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://23e6172c.agent-render.pages.dev |
| Branch Preview URL: | https://session-agent-7767eee6-1728.agent-render.pages.dev |
| return null; | ||
| } | ||
|
|
||
| if (new Date(row.expires_at + "Z").getTime() < Date.now()) { |
There was a problem hiding this comment.
WARNING: Timezone inconsistency in expiration check
This line treats expires_at (a SQLite datetime in local time) as UTC by appending "Z", but compares against Date.now() which is UTC. This can cause expiration logic to be off by the server's timezone offset. For example, if the server is in UTC+5, artifacts may appear to expire 5 hours early or late.
The cleanup function at line 93 correctly uses datetime('now') (SQLite local time) for comparison. Consider aligning this check to use the same timezone semantics.
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (27 files)
General Notes: The self-hosted variant implementation is well-structured. The database schema, CRUD operations, and API endpoints follow good practices. UUID validation, payload format validation, and the sliding TTL mechanism are properly implemented. The architecture correctly reuses viewer components from the parent app via webpack aliases. The only issue found is a timezone handling inconsistency in the artifact expiration check that could cause edge cases depending on the server's timezone configuration. Reviewed by minimax-m2.5-20260211 · 419,788 tokens |
Summary
selfhosted/that stores artifact payloads in SQLite and serves them via UUID links (/{uuid}). The static fragment-based app at the root is unchanged.better-sqlite3database withartifactstable storing payloads mapped by UUID v4, withcreated_at,updated_at,last_viewed_at, andexpires_atfields.POST /api/artifacts(create),GET /api/artifacts/:id(read + TTL refresh),DELETE /api/artifacts/:id,PUT /api/artifacts/:id(update),POST /api/cleanup(remove expired rows).expires_atby 24 hours. Expired entries return 404 and are cleaned up lazily on read.SelfHostedViewerShellcomponent (src/components/selfhosted-viewer-shell.tsx) accepts a pre-fetchedPayloadEnvelopeprop and renders the same artifact stage, toolbar, and renderer slots as the fragment-based viewer. Supports copy, download, markdown print-to-PDF, artifact bundle switching, and all 5 artifact kinds.selfhosted/src/app/artifact/[id]/page.tsxfetches the payload from SQLite, validates it, and passes it to the shell component.Dockerfile(standalone build) anddocker-compose.ymlwith persistent volume.skills/selfhosted-agent-render/SKILL.md— full deployment, API usage, TTL, cleanup, and auth guidance for agents.README.md,docs/architecture.md,docs/deployment.md,docs/dependency-notes.md, andAGENTS.mdto document the optional self-hosted mode.New files
selfhosted/package.jsonbetter-sqlite3,uuid)selfhosted/next.config.ts@shared/@webpack aliases to parentsrc/selfhosted/tsconfig.json@shared/*,@self/*selfhosted/src/lib/db.tsselfhosted/src/lib/artifacts.tscreateArtifact,getArtifact,deleteArtifact,updateArtifact,cleanupExpiredselfhosted/src/app/api/artifacts/route.tsselfhosted/src/app/api/artifacts/[id]/route.tsselfhosted/src/app/api/cleanup/route.tsselfhosted/src/app/artifact/[id]/page.tsxselfhosted/src/app/page.tsxselfhosted/src/app/layout.tsxselfhosted/styles/globals.cssselfhosted/Dockerfileselfhosted/docker-compose.ymlsrc/components/selfhosted-viewer-shell.tsxskills/selfhosted-agent-render/SKILL.mdModified files
README.mddocs/architecture.mddocs/deployment.mddocs/dependency-notes.mdAGENTS.mdtsconfig.jsonselfhosted/from root typecheckeslint.config.mjsselfhosted/**to eslint ignoresTesting
npm run lint— passednpm run typecheck— passednpm run test— 62 tests passednpm run build— static export succeededcd selfhosted && npm run typecheck— passedcd selfhosted && npm run build— standalone build succeededArchitecture
The selfhosted app reuses the parent app's viewer components via webpack aliases:
@sharedand@both resolve to../src/(parent's source)@selfresolves to./src/(selfhosted's own modules)src/app/globals.cssThis keeps the static app completely untouched while sharing rendering code.