@@ -0,0 +1,70 @@
|
||||
// What the catalog module does, as pure TypeScript over the port in ports.ts.
|
||||
//
|
||||
// Products are the organization's: every project of the app sees the same ones (a multi-tenant app keeps
|
||||
// all its tenants' products in its deployment's one organization; partitioning by app tenant is an open
|
||||
// owner decision, B-wave contract §11.4).
|
||||
|
||||
import type { ProjectScope } from "@conusai-kit/core"
|
||||
import {
|
||||
type ImportOutcomeView,
|
||||
type ImportRow,
|
||||
type ImportView,
|
||||
PAGE_LIMIT,
|
||||
type ProductSearchField,
|
||||
type ProductView,
|
||||
} from "../contract/index.js"
|
||||
import type { CatalogReads, CatalogService, Page } from "./ports.js"
|
||||
|
||||
/** The service's idempotency key of a request: one project's request id never answers another's. */
|
||||
export const requestKey = (scope: Pick<ProjectScope, "projectId">, requestId: string): string =>
|
||||
`app-project:${scope.projectId}:${requestId}`
|
||||
|
||||
const cursor = (after: string | undefined) => (after === undefined ? {} : { after })
|
||||
|
||||
export function listProducts(
|
||||
reads: CatalogReads,
|
||||
query: { after?: string; limit?: number },
|
||||
): Promise<Page<ProductView>> {
|
||||
return reads.listProducts({ ...cursor(query.after), limit: query.limit ?? PAGE_LIMIT })
|
||||
}
|
||||
|
||||
export function searchProducts(
|
||||
reads: CatalogReads,
|
||||
query: { q: string; by?: ProductSearchField; after?: string; limit?: number },
|
||||
): Promise<Page<ProductView>> {
|
||||
const q = query.q.trim()
|
||||
const by = query.by ?? "name"
|
||||
const limit = query.limit ?? PAGE_LIMIT
|
||||
if (by === "name") return reads.searchProducts({ q, ...cursor(query.after), limit })
|
||||
return reads.listProducts({ search: { by, q }, ...cursor(query.after), limit })
|
||||
}
|
||||
|
||||
export function listImports(reads: CatalogReads, query: { after?: string; limit?: number }): Promise<Page<ImportView>> {
|
||||
return reads.listImports({ ...cursor(query.after), limit: query.limit ?? PAGE_LIMIT })
|
||||
}
|
||||
|
||||
export function applyImport(
|
||||
service: CatalogService,
|
||||
scope: ProjectScope,
|
||||
input: {
|
||||
fileHash: string
|
||||
sourceName: string
|
||||
format: "csv" | "xlsx" | "json"
|
||||
chunk: number
|
||||
finalChunk: boolean
|
||||
revision?: string
|
||||
rows: readonly ImportRow[]
|
||||
requestId: string
|
||||
},
|
||||
): Promise<ImportOutcomeView> {
|
||||
return service.applyImport({
|
||||
fileHash: input.fileHash,
|
||||
sourceName: input.sourceName.trim(),
|
||||
format: input.format,
|
||||
chunk: input.chunk,
|
||||
finalChunk: input.finalChunk,
|
||||
...(input.revision === undefined ? {} : { revision: input.revision }),
|
||||
rows: input.rows,
|
||||
key: requestKey(scope, input.requestId),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user