feat: add convex state functions for snapshot persistence

This commit is contained in:
Codex
2026-02-18 14:25:46 +00:00
parent 42684125f9
commit 77f7f10921
5 changed files with 79 additions and 0 deletions

View File

@@ -451,5 +451,8 @@ Use `.env.example` as the source of truth.
1. Replace `/auth/dev-login` with direct Better Auth UI/OAuth sign-in for public launch. 1. Replace `/auth/dev-login` with direct Better Auth UI/OAuth sign-in for public launch.
2. Populate integration keys in Coolify environment for X, Polar, Qwen3 TTS, MinIO, and Convex. 2. Populate integration keys in Coolify environment for X, Polar, Qwen3 TTS, MinIO, and Convex.
3. Implement Convex functions named by `CONVEX_STATE_QUERY` and `CONVEX_STATE_MUTATION`. 3. Implement Convex functions named by `CONVEX_STATE_QUERY` and `CONVEX_STATE_MUTATION`.
- This repository includes `convex/state.ts` and `convex/schema.ts` for defaults:
- `state:getLatestSnapshot`
- `state:saveSnapshot`
4. Move Better Auth from memory adapter to a persistent production adapter. 4. Move Better Auth from memory adapter to a persistent production adapter.
5. Add tracing and external alerting. 5. Add tracing and external alerting.

9
convex/schema.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
state_snapshots: defineTable({
snapshot: v.any(),
updatedAt: v.string(),
}),
});

45
convex/state.ts Normal file
View File

@@ -0,0 +1,45 @@
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
export const getLatestSnapshot = query({
args: {},
handler: async (ctx) => {
const latest = await ctx.db
.query("state_snapshots")
.order("desc")
.first();
return latest
? {
snapshot: latest.snapshot,
updatedAt: latest.updatedAt,
}
: null;
},
});
export const saveSnapshot = mutation({
args: {
snapshot: v.any(),
updatedAt: v.string(),
},
handler: async (ctx, args) => {
const latest = await ctx.db
.query("state_snapshots")
.order("desc")
.first();
if (latest) {
await ctx.db.patch(latest._id, {
snapshot: args.snapshot,
updatedAt: args.updatedAt,
});
return latest._id;
}
return ctx.db.insert("state_snapshots", {
snapshot: args.snapshot,
updatedAt: args.updatedAt,
});
},
});

View File

@@ -0,0 +1,14 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
test("convex state functions are present for configured function names", () => {
const schema = fs.readFileSync("convex/schema.ts", "utf8");
const state = fs.readFileSync("convex/state.ts", "utf8");
assert.match(schema, /state_snapshots/);
assert.match(state, /export const getLatestSnapshot = query/);
assert.match(state, /export const saveSnapshot = mutation/);
});

View File

@@ -35,3 +35,11 @@ test("env example includes required webhook and credit settings", () => {
assert.match(envFile, /INCLUDED_CHARS=/); assert.match(envFile, /INCLUDED_CHARS=/);
assert.match(envFile, /WEBHOOK_RPM=/); assert.match(envFile, /WEBHOOK_RPM=/);
}); });
test("convex state function files exist", () => {
const schema = fs.readFileSync("convex/schema.ts", "utf8");
const state = fs.readFileSync("convex/state.ts", "utf8");
assert.match(schema, /defineSchema/);
assert.match(state, /getLatestSnapshot/);
assert.match(state, /saveSnapshot/);
});