Files
template-app/apps/web/src/server/dialect/stores.ts
T
2026-11-14 09:56:45 +00:00

68 lines
3.0 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The stores of the mariadb and mongodb profiles (spec §5.1; profiles/<name>/README.md), built once
// per web process beside the Postgres pair app.ts builds. One login, no row-level security to bind
// (AT-D20 is Postgres-only): the repository predicate is the tenant wall, which the adapters' shared
// contract suites prove. The queue is BullMQ on the linked Valkey, and a request only enqueues.
//
// `kit template:prune --profile` keeps this folder for mariadb and mongodb (and the chosen engine's
// folder); postgres and convex remove it with every `kit:profile-dialect` line.
import type { AppEnv } from "@app/config"
import type { AnyEntityDef, JobQueue } from "@conusai-kit/data"
import { createBullmqJobQueue } from "@conusai-kit/jobs-bullmq"
import { Redis } from "ioredis"
import type { EngineStore } from "../stores"
import { mariadbStore } from "./mariadb/index" // kit:profile-mariadb
import { mongodbStore } from "./mongodb/index" // kit:profile-mongodb
export interface DialectStores extends EngineStore {
/** The queue a request enqueues on; it connects on the first enqueue. */
queue(): JobQueue
close(): Promise<void>
}
/**
* The BullMQ key prefix of this app: `kit-jobs-<slug>`, from the project the platform injects, so
* two apps sharing one Valkey service never meet (the worker's `queueSlug` is the same function).
*/
export function queueSlug(env: Pick<AppEnv, "deployment">): string {
const id = env.deployment.CONUSAI_PROJECT_ID.toLowerCase().replace(/[^a-z0-9-]+/g, "-")
return `p-${id}`.replace(/-+$/, "")
}
type Collections = ReadonlyArray<{ readonly entity: string; readonly table: string }>
function engineStore(env: AppEnv, entities: readonly AnyEntityDef[], collections: Collections): EngineStore | null {
if (env.mariadb) return mariadbStore(env.mariadb.url, entities) // kit:profile-mariadb
if (env.mongodb) return mongodbStore(env.mongodb.url, entities, collections) // kit:profile-mongodb
return null
}
/** The stores of the mariadb or mongodb profile, or null on any other. */
export function dialectStores(
env: AppEnv,
entities: readonly AnyEntityDef[],
collections: Collections = [],
): DialectStores | null {
const store = engineStore(env, entities, collections)
if (!store) return null
const redisUrl = env.redis?.url
if (!redisUrl) throw new Error("the mariadb and mongodb profiles need REDIS_URL for their job queue")
let connection: Redis | undefined
let queue: ReturnType<typeof createBullmqJobQueue> | undefined
return {
...store,
queue: () => {
// BullMQ needs `maxRetriesPerRequest: null` on the connection it is given.
connection ??= new Redis(redisUrl, { maxRetriesPerRequest: null })
queue ??= createBullmqJobQueue({ connection, slug: queueSlug(env) })
return queue
},
close: async () => {
await queue?.close().catch(() => undefined)
await connection?.quit().catch(() => undefined)
await store.data.close()
},
}
}