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
+70
View File
@@ -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),
})
}
+54
View File
@@ -0,0 +1,54 @@
// The port the catalog module's use cases are written against: the platform's Catalog service, in this
// module's own words. adapters/service.ts is the one place that knows `@conusai-sdk/catalog` exists, and
// it turns every refusal into the kit's app errors (`businessAppError` of
// `@conusai-kit/business`) before core sees it.
import { token } from "@conusai-kit/core"
import type { ImportOutcomeView, ImportRow, ImportView, ProductSearchField, ProductView } from "../contract/index.js"
export interface Page<T> {
readonly rows: T[]
readonly after?: string
}
/** What a `read` procedure may ask the service: nothing that changes a row. */
export interface CatalogReads {
listProducts(query: {
search?: { by: Exclude<ProductSearchField, "name">; q: string }
after?: string
limit: number
}): Promise<Page<ProductView>>
/** The service's ranked search: the exact code first, then most shared words. */
searchProducts(query: { q: string; after?: string; limit: number }): Promise<Page<ProductView>>
getProduct(productId: string): Promise<ProductView>
listImports(query: { after?: string; limit: number }): Promise<Page<ImportView>>
}
export interface CatalogService extends CatalogReads {
applyImport(input: {
fileHash: string
sourceName: string
format: "csv" | "xlsx" | "json"
chunk: number
finalChunk: boolean
revision?: string
rows: readonly ImportRow[]
key: string
}): Promise<ImportOutcomeView>
}
/** The read view a `read` or `write` handler receives: the kind is a capability, not a label. */
export const catalogReadsOf = (service: CatalogReads): CatalogReads => ({
listProducts: (query) => service.listProducts(query),
searchProducts: (query) => service.searchProducts(query),
getProduct: (productId) => service.getProduct(productId),
listImports: (query) => service.listImports(query),
})
/**
* Bound by a composition root or a test that brings its own client (another organization, the
* business-services fake). With nothing bound, the module calls this deployment's service.
*/
export const CATALOG_SERVICE = token<CatalogService>("catalog.service", {
views: { read: catalogReadsOf, write: catalogReadsOf },
})