ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The account module on the Convex profile (joint contract §12): export my data and delete my
// account, with the same ledger, ordering, idempotency and partial-failure export as on Postgres.
//
// `account.data.export` and `account.deletion.request` are generated actions (lib/effects.ts): most
// modules' shares are calls to platform services, and tenancy's, which reads this app's own tables, is
// `tenancyShareRead` and `tenancyShareWrite` below. Their store is bound as ACCOUNT_STORE, and every
// store method is one of the two internal functions below -- `createAccountStore` itself, the very
// store the Postgres profile runs, over this function's ctx.db -- so each step is one Convex
// transaction. A deletion is a ledger of idempotent steps the ledger records as they complete, so a
// deletion interrupted between two transactions resumes from the ledger as it does on Postgres.
//
// storeRead / storeWrite the store's methods (internal: only this deployment's actions call them)
// tenancyShareRead / tenancyShareWrite
// tenancy's share of an account (internal), its own export and erasure over
// this function's ctx.db
// sweep the worker's DELETION_SWEEP_JOB, as a Convex cron (crons.ts)
// providerEvent the identity provider's account events (Better Auth's deletion or ban,
// from the web's revocation relay with the admin key)
// revocationRead / revocationWrite
// the sign-out revocation outbox's methods (internal), the Postgres
// profile's own store over ctx.db
// revokeSession a revoked app session (the web's sign-out, with the admin key): recorded
// in the outbox and delivered at once; never blocks the sign-out
// revocationSweep the worker's REVOCATION_JOB, as a one-minute Convex cron (crons.ts)
import { Container, toProblem } from "@conusai-kit/core"
import { DATA_SOURCE, SYSTEM_DATA } from "@conusai-kit/data"
import { createConvexDataSource } from "@conusai-kit/data-convex"
import { executorFor } from "@conusai-kit/data-convex/runtime"
import { notificationsAccountData } from "@conusai-kit/notifications"
import { type AccountDataContext, tenancyAccountData } from "@conusai-kit/tenancy"
import type { GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server"
import { ConvexError, type Value, v } from "convex/values"
import {
type AccountStore,
createAccountStore,
createRevocationStore,
DELETION_SWEEP_JOB,
type RevocationStore,
recordProviderAccountEvent,
} from "../modules/dist/account/index.js"
import { internalAction, internalMutation, internalQuery } from "./_generated/server"
import {
ACCOUNT_STORE_READS,
ACCOUNT_STORE_WRITES,
type EffectActionCtx,
effectContainer,
REVOCATION_STORE_READS,
REVOCATION_STORE_WRITES,
revokeSessionInBackend,
sweepRevocationsInBackend,
} from "./lib/effects"
import { convexEntities, convexLive, registry } from "./lib/runtime"
type FunctionContext<DataModel extends GenericDataModel> = {
db: GenericQueryCtx<DataModel>["db"] | GenericMutationCtx<DataModel>["db"]
}
const storeOver = <DataModel extends GenericDataModel>(ctx: FunctionContext<DataModel>): AccountStore => {
const data = createConvexDataSource({ entities: convexEntities, live: convexLive, executor: executorFor(ctx) })
return createAccountStore({ data, system: data })
}
const revocationsOver = <DataModel extends GenericDataModel>(ctx: FunctionContext<DataModel>): RevocationStore =>
createRevocationStore(
createConvexDataSource({ entities: convexEntities, live: convexLive, executor: executorFor(ctx) }),
)
/** What tenancy's share resolves, over this function's transaction: the context an action cannot give it. */
const shareContext = <DataModel extends GenericDataModel>(
ctx: FunctionContext<DataModel>,
subject: string,
): AccountDataContext => {
const data = createConvexDataSource({ entities: convexEntities, live: convexLive, executor: executorFor(ctx) })
const container = new Container()
container.value(DATA_SOURCE, data)
container.value(SYSTEM_DATA, data)
return { subject, container }
}
/** An answer as plain JSON (no `undefined` crosses into Convex), or the kernel's problem. */
async function answered(call: () => Promise<unknown>): Promise<Value> {
try {
return JSON.parse(JSON.stringify((await call()) ?? null)) as Value
} catch (error) {
throw new ConvexError(toProblem(error) as unknown as Value)
}
}
/** One store method. */
function run(store: AccountStore | RevocationStore, method: string, args: unknown[]): Promise<Value> {
return answered(async () => {
const call = (store as unknown as Record<string, (...input: unknown[]) => Promise<unknown>>)[method]
if (!call) throw new Error(`no store method ${method}`)
return call.apply(store, args)
})
}
const literals = <T extends string>(names: readonly T[]) =>
v.union(
...(names.map((name) => v.literal(name)) as [
ReturnType<typeof v.literal<T>>,
...ReturnType<typeof v.literal<T>>[],
]),
)
export const storeRead = internalQuery({
args: { method: literals(ACCOUNT_STORE_READS), args: v.array(v.any()) },
handler: (ctx, { method, args }) => run(storeOver(ctx), method, args),
})
export const storeWrite = internalMutation({
args: { method: literals(ACCOUNT_STORE_WRITES), args: v.array(v.any()) },
handler: (ctx, { method, args }) => run(storeOver(ctx), method, args),
})
export const tenancyShareRead = internalQuery({
args: { subject: v.string() },
handler: (ctx, { subject }) => answered(async () => tenancyAccountData.export?.(shareContext(ctx, subject))),
})
export const tenancyShareWrite = internalMutation({
args: { subject: v.string() },
handler: (ctx, { subject }) => answered(async () => tenancyAccountData.erase?.(shareContext(ctx, subject))),
})
export const notificationsShareRead = internalQuery({
args: { subject: v.string() },
handler: (ctx, { subject }) => answered(async () => notificationsAccountData.export?.(shareContext(ctx, subject))),
})
export const notificationsShareWrite = internalMutation({
args: { subject: v.string() },
handler: (ctx, { subject }) => answered(async () => notificationsAccountData.erase?.(shareContext(ctx, subject))),
})
export const revocationRead = internalQuery({
args: { method: literals(REVOCATION_STORE_READS), args: v.array(v.any()) },
handler: (ctx, { method, args }) => run(revocationsOver(ctx), method, args),
})
export const revocationWrite = internalMutation({
args: { method: literals(REVOCATION_STORE_WRITES), args: v.array(v.any()) },
handler: (ctx, { method, args }) => run(revocationsOver(ctx), method, args),
})
/**
* A revoked app session (joint contract §14), from the web's sign-out with the admin key: the
* revocation is written to the outbox and every module that minted derived service sessions for it
* is asked at once, bounded. What a platform did not confirm `revocationSweep` retries. Fails only
* when nothing was recorded; the web then asks the modules itself.
*/
export const revokeSession = internalAction({
args: { session: v.string() },
handler: async (ctx, { session }) => {
try {
await revokeSessionInBackend(ctx as unknown as EffectActionCtx, session)
} catch (error) {
throw new ConvexError(toProblem(error) as unknown as Value)
}
return null
},
})
/**
* Delivers every due revocation, as the Postgres worker's revocation job does every minute: each row
* is claimed with a versioned write and delivered once per module, with the row's backoff (30 s
* doubling, at most 5 min) between attempts, until 65 minutes after the sign-out, when no widget
* session it minted can still be alive and the row lapses. A run that leaves rows waiting fails with
* the kernel's `account_revocations_waiting` problem, which the backend logs.
*/
export const revocationSweep = internalAction({
args: {},
handler: async (ctx) => {
try {
await sweepRevocationsInBackend(ctx as unknown as EffectActionCtx)
} catch (error) {
throw new ConvexError(toProblem(error) as unknown as Value)
}
return null
},
})
/**
* Resumes every deletion a module's erasure (or the provider's account end) held up, as the Postgres
* worker's sweep does every five minutes: the module's own job handler, in this action's container.
* A run that leaves deletions waiting fails with the kernel's `account_deletion_waiting` problem,
* which the backend logs: that is the line an operator is alerted on.
*/
export const sweep = internalAction({
args: {},
handler: async (ctx) => {
const job = registry.jobs[DELETION_SWEEP_JOB]
if (!job) throw new Error("the account module declares no deletion sweep")
try {
await job.def.handler(null, {
container: effectContainer(ctx as unknown as EffectActionCtx),
correlationId: globalThis.crypto.randomUUID(),
attempt: 1,
})
} catch (error) {
throw new ConvexError(toProblem(error) as unknown as Value)
}
return null
},
})
/**
* An account event of the app's identity provider, delivered by the web's revocation relay with the
* admin key: `account-deleted` erases the account through the ledger, `user-disabled` revokes every
* credential and derived service session of it. Idempotent, so a replayed event is harmless.
*/
export const providerEvent = internalAction({
args: { reason: v.union(v.literal("account-deleted"), v.literal("user-disabled")), userId: v.string() },
handler: async (ctx, event) => {
try {
await recordProviderAccountEvent(effectContainer(ctx as unknown as EffectActionCtx), event)
} catch (error) {
throw new ConvexError(toProblem(error) as unknown as Value)
}
return null
},
})