// 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, 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> { 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> { 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> { 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 { 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), }) }