103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
// The knowledge jobs and the subscribers that enqueue them.
|
|
//
|
|
// A web request writes the document row and publishes the outbox event in one transaction; the
|
|
// module's own subscriber (running in the worker's outbox relay) turns the event into the job,
|
|
// with the (collection, file) pair as the singleton key — a redelivered event adds no second job.
|
|
// The tenant comes from the container, never from the payload (decision AT-F6).
|
|
|
|
import { type EventContext, internal, type JobDef, subscribe } from "@conusai-kit/core"
|
|
import { JOB_QUEUE } from "@conusai-kit/data"
|
|
import { z } from "zod"
|
|
import { documentIndexRequested, documentRemoveRequested, INDEX_JOB, REMOVE_JOB } from "../contract/index.js"
|
|
import { indexDocument, removeDocument } from "../core/index-job.js"
|
|
import { KNOWLEDGE_SCOPE, type KnowledgeScope } from "../core/ports.js"
|
|
import { knowledgeSearchDeps } from "./procedures.js"
|
|
|
|
export const documentPayload = z.object({
|
|
documentId: z.string(),
|
|
collectionName: z.string(),
|
|
fileId: z.string(),
|
|
})
|
|
export type DocumentJobPayload = z.output<typeof documentPayload>
|
|
|
|
export const indexJobKey = (collectionName: string, fileId: string): string =>
|
|
`knowledge.index.${collectionName}.${fileId}`
|
|
export const removeJobKey = (collectionName: string, fileId: string): string =>
|
|
`knowledge.remove.${collectionName}.${fileId}`
|
|
|
|
function projectScopeOf(ctx: EventContext, payload: { workspaceId: string; projectId: string }): KnowledgeScope {
|
|
const ambient = ctx.container.resolve(KNOWLEDGE_SCOPE)
|
|
if (ambient.level !== "project") throw internal("a knowledge event resolves to a project scope")
|
|
return { ...ambient, workspaceId: payload.workspaceId, projectId: payload.projectId }
|
|
}
|
|
|
|
export const indexJob: JobDef<DocumentJobPayload> = {
|
|
payload: documentPayload,
|
|
async handler(payload, ctx) {
|
|
const scope = ctx.container.resolve(KNOWLEDGE_SCOPE)
|
|
if (scope.level !== "project") throw internal(`a knowledge job runs in a project scope, not a ${scope.level} one`)
|
|
await indexDocument(knowledgeSearchDeps(ctx.container), scope, payload.documentId, {
|
|
...(ctx.signal === undefined ? {} : { signal: ctx.signal }),
|
|
})
|
|
},
|
|
}
|
|
|
|
export const removeJob: JobDef<DocumentJobPayload> = {
|
|
payload: documentPayload,
|
|
async handler(payload, ctx) {
|
|
const scope = ctx.container.resolve(KNOWLEDGE_SCOPE)
|
|
if (scope.level !== "project") throw internal(`a knowledge job runs in a project scope, not a ${scope.level} one`)
|
|
await removeDocument(knowledgeSearchDeps(ctx.container), scope, payload.documentId)
|
|
},
|
|
}
|
|
|
|
async function onDocumentIndexRequested(
|
|
event: {
|
|
name: string
|
|
payload: { workspaceId: string; projectId: string; documentId: string; collectionName: string; fileId: string }
|
|
},
|
|
ctx: EventContext,
|
|
): Promise<void> {
|
|
const scope = projectScopeOf(ctx, event.payload)
|
|
await ctx.container.resolve(JOB_QUEUE).enqueue(
|
|
scope,
|
|
INDEX_JOB,
|
|
{
|
|
documentId: event.payload.documentId,
|
|
collectionName: event.payload.collectionName,
|
|
fileId: event.payload.fileId,
|
|
},
|
|
{
|
|
key: indexJobKey(event.payload.collectionName, event.payload.fileId),
|
|
},
|
|
)
|
|
}
|
|
|
|
async function onDocumentRemoveRequested(
|
|
event: {
|
|
name: string
|
|
payload: { workspaceId: string; projectId: string; documentId: string; collectionName: string; fileId: string }
|
|
},
|
|
ctx: EventContext,
|
|
): Promise<void> {
|
|
const scope = projectScopeOf(ctx, event.payload)
|
|
await ctx.container.resolve(JOB_QUEUE).enqueue(
|
|
scope,
|
|
REMOVE_JOB,
|
|
{
|
|
documentId: event.payload.documentId,
|
|
collectionName: event.payload.collectionName,
|
|
fileId: event.payload.fileId,
|
|
},
|
|
{
|
|
key: removeJobKey(event.payload.collectionName, event.payload.fileId),
|
|
},
|
|
)
|
|
}
|
|
|
|
export const KNOWLEDGE_JOBS = { [INDEX_JOB]: indexJob, [REMOVE_JOB]: removeJob }
|
|
export const KNOWLEDGE_SUBSCRIPTIONS = [
|
|
subscribe(documentIndexRequested, onDocumentIndexRequested),
|
|
subscribe(documentRemoveRequested, onDocumentRemoveRequested),
|
|
]
|