// The procedure contracts of the catalog module: schemas and names, no handler and no port. // // The module is a client of the platform's Catalog service (B-wave contract ยง3.14): products and their // imports live there, in the deployment's organization, and every procedure is one service call. A read // reads; an import is an `effect` (it calls an external service and writes nothing of this app's own). // The service's int64 fields are plain values here, so what a page loads is JSON: a revision is its digits, // a count a number. import { procedure } from "@conusai-kit/core" import { z } from "zod" import { CATALOG_PERMISSIONS, IMPORT_CHUNK_ROWS, PRODUCT_SEARCH_FIELDS } from "./constants.js" const inProject = { org: z.string(), workspace: z.string(), project: z.string() } const page = { after: z.string().max(64).optional(), limit: z.number().int().min(1).max(200).optional() } /** What a form generates once per submission, so a retried request applies once. */ const requestId = z.string().uuid() const productId = z.string().min(1).max(64) /** A revision as its digits: the service's are 64-bit. */ const revision = z.string().regex(/^\d{1,19}$/) export const productViewSchema = z.object({ productId: z.string(), code: z.string(), name: z.string(), description: z.string(), /** A UN/CEFACT Recommendation 20 unit code. */ baseUnit: z.string(), status: z.enum(["active", "archived", "deleted"]), groupCode: z.string().nullable(), identifiers: z.array(z.object({ kind: z.string(), value: z.string() })), revision, }) export type ProductView = z.output export const importViewSchema = z.object({ importId: z.string(), sourceName: z.string(), format: z.string(), status: z.string(), revision, created: z.number().int(), updated: z.number().int(), unchanged: z.number().int(), refused: z.number().int(), }) export type ImportView = z.output export const importOutcomeSchema = z.object({ import: importViewSchema, rows: z.array( z.object({ sourceRow: z.number().int(), outcome: z.string(), productId: z.string().nullable(), reason: z.string().nullable(), }), ), }) export type ImportOutcomeView = z.output const importRowSchema = z.object({ /** The line of the source file, which the service keeps as the row's provenance. */ sourceRow: z.number().int().min(1), /** The revision the row replaces, for a product that exists. */ revision: revision.optional(), product: z.object({ code: z.string().min(1).max(64), name: z.string().min(1).max(200), baseUnit: z.string().min(1).max(3), description: z.string().max(4000).optional(), }), }) export type ImportRow = z.output const productPage = z.object({ rows: z.array(productViewSchema), after: z.string().optional() }) export const productList = procedure("catalog.product.list") .kind("read") .scope("project") .permission(CATALOG_PERMISSIONS.read) .input(z.object({ ...inProject, ...page })) .output(productPage) .contract() const searchField = z.enum(PRODUCT_SEARCH_FIELDS) export type ProductSearchField = z.output export const productSearch = procedure("catalog.product.search") .kind("read") .scope("project") .permission(CATALOG_PERMISSIONS.read) .input(z.object({ ...inProject, q: z.string().trim().min(1).max(200), by: searchField.optional(), ...page })) .output(productPage) .contract() export const productGet = procedure("catalog.product.get") .kind("read") .scope("project") .permission(CATALOG_PERMISSIONS.read) .input(z.object({ ...inProject, productId })) .output(productViewSchema) .contract() export const importList = procedure("catalog.import.list") .kind("read") .scope("project") .permission(CATALOG_PERMISSIONS.read) .input(z.object({ ...inProject, ...page })) .output(z.object({ rows: z.array(importViewSchema), after: z.string().optional() })) .contract() export const importApply = procedure("catalog.import.apply") .kind("effect") .scope("project") .permission(CATALOG_PERMISSIONS.write) .input( z.object({ ...inProject, /** The source file's lower-case SHA-256: one file is one import, however many chunks it takes. */ fileHash: z.string().regex(/^[0-9a-f]{64}$/), sourceName: z.string().trim().min(1).max(200), format: z.enum(["csv", "xlsx", "json"]), chunk: z.number().int().min(0), finalChunk: z.boolean(), /** The revision of the import, from the chunk before; absent on the first. */ revision: revision.optional(), rows: z.array(importRowSchema).min(1).max(IMPORT_CHUNK_ROWS), requestId, }), ) .output(importOutcomeSchema) .contract()