/** * Base Resource Class * * Abstract base class for all resource managers. * Provides common patterns: validation, error mapping, pagination. */ import { type IterateOptions, type ListOptions, type PaginatedResponse, type Scope } from "@artu-ai/shared"; import type { ZodType } from "zod"; import type { Environment } from "../sdk/types"; /** * Configuration for creating a resource manager */ export interface ResourceConfig { /** * Optional SDK scope for scope/activity scoping */ scope?: Scope; /** * SDK environment (test or live) */ environment?: Environment; /** * When true, validates postal code format against country rules client-side. */ enablePostalCodeValidation?: boolean; } /** * Abstract base class for all resource managers. * * Provides: * - Input validation with Zod schemas * - Automatic error mapping from tRPC to SDK errors * - Pagination iterator factory * * @typeParam TModel - The model type returned by this resource * @typeParam TCreateInput - Input type for create operations * @typeParam TUpdateInput - Input type for update operations * @typeParam TFilter - Filter type for list operations * * @example * ```typescript * class ClientsResource extends BaseResource< * Client, * CreateClientInput, * UpdateClientInput, * ClientFilter * > { * async create(data: CreateClientInput): Promise { * const validated = this.validate(createClientInputSchema, data); * return this.execute(() => * this.trpc.clients.create.mutate(validated) * ); * } * } * ``` */ export declare abstract class BaseResource, TSort = unknown, TCreateResult = TModel> { /** * SDK scope */ protected readonly scope: Scope | undefined; /** * Which filter key carries this resource's scope, enabling auto-scoping. * - "scopes": resource has a scope-array column and is auto-scoped * (clients, transactions, products). * - "scope": resource has a single scope column and is auto-scoped (alerts). * - null (default): auto-scoping is skipped. * * Only resources that are independently scope-partitioned set this. Notably * addresses and bank-accounts have scope columns but are intentionally left * `null` — they're owned by a parent client/transaction and aren't queried * by scope on their own (see the auto-scope design doc). * * Set on each auto-scoped resource's base class; inherited by scoped subclasses. */ protected scopeFilterKey: "scope" | "scopes" | null; /** * Whether client-side postal code format validation is enabled */ protected readonly enablePostalCodeValidation: boolean; constructor(config: ResourceConfig); /** * Whether this resource is scoped to a scope */ protected get isScoped(): boolean; /** * Gets the scope for this resource * @returns The scope for this resource */ protected getScope(): Scope; /** * Creates a new resource * @param data - Creation data * @returns The created model (or custom result type) */ abstract create(data: TCreateInput): Promise; /** * Retrieves a resource by ID * @param id - Resource ID * @returns The retrieved model */ abstract retrieve(id: string): Promise; /** * Updates an existing resource * @param id - Resource ID * @param data - Update data * @returns The updated model */ abstract update(id: string, data: TUpdateInput): Promise; /** * Deletes a resource by ID * @param id - Resource ID */ abstract delete(id: string): Promise; /** * Lists resources with optional filtering and pagination * @param options - List options * @returns Paginated response */ abstract list(options?: ListOptions): Promise>; /** * Counts resources matching the given filter * @param filter - Optional filter criteria * @returns The count of matching resources */ abstract count(filter?: TFilter): Promise; /** * Validates input against a Zod schema. * Throws ValidationError if validation fails. * * @param schema - Zod schema to validate against * @param data - Data to validate * @returns Validated and typed data * @throws ValidationError if validation fails * * @example * ```typescript * const validated = this.validate(createClientSchema, data); * // validated is now typed as CreateClientInput * ``` */ protected validate(schema: ZodType, data: unknown): T; /** * Returns this SDK instance's scope plus all descendant scopes. * Empty when the SDK is unscoped (base SDK). */ protected expandScope(): Scope[]; /** * Injects this resource's scope filter into `filter`, enforcing strict * scoping. Behaviour: * - unscoped SDK, `scopeFilterKey === null`, or `crossScope === true`: * returns `filter` unchanged. * - user already set the scope key: throws `ScopeViolationError` (unless * `crossScope` is true, in which case their value is preserved). * - otherwise: returns `filter` with `{ [scopeFilterKey]: { in: expandScope() } }`. * * NOTE: this is a client-side convenience, not a server-enforced * authorization boundary. The injected filter is an ordinary query filter * the server applies like any other; a raw API call to `list`/`count` * without it still returns every record the API key can read. The real * isolation boundary remains organization + environment + RLS. (Retrieve* * calls additionally pass `requireScope` so the server hard-scopes single * lookups — see {@link buildRequireScope}.) */ protected applyAutoScope>(filter: TFilterArg | undefined, crossScope: boolean | undefined): TFilterArg | undefined; /** * Builds the `requireScope` argument for retrieve* calls. * Returns the expanded scopes, or `undefined` when unscoped, when the * resource has no scope column, or when `crossScope` is true. */ protected buildRequireScope(crossScope: boolean | undefined): Scope[] | undefined; /** * Wraps a tRPC operation with error mapping. * Converts tRPC errors to SDK-specific errors. * * @param operation - Async operation to execute * @returns Result of the operation * @throws SDK-specific error if operation fails * * @example * ```typescript * const result = await this.execute(() => * this.trpc.clients.create.mutate(data) * ); * ``` */ protected execute(operation: () => Promise): Promise; /** * Creates an async iterator for paginated results. * Automatically fetches subsequent pages as you iterate. * * @typeParam T - The model type to return (defaults to TModel, can be overridden by subclasses) * @param options - Iterator options * @returns Async generator yielding individual items * * @example * ```typescript * for await (const client of this.iterate({ filter: { type: "individual" } })) { * console.log(client.name); * } * ``` */ protected createIterator(options?: IterateOptions & { crossScope?: boolean; }): AsyncGenerator; } /** * Base class for read-only resources (no create/update/delete). * Useful for resources that are managed elsewhere. * * @typeParam TModel - The model type returned by this resource * @typeParam TFilter - Filter type for list operations * @typeParam TSort - Sort type for list operations */ export declare abstract class ReadOnlyResource, TSort = unknown> { constructor(config: ResourceConfig); /** * Retrieves a resource by ID */ abstract retrieve(id: string): Promise; /** * Lists resources with optional filtering and pagination */ abstract list(options?: ListOptions): Promise>; /** * Counts resources matching the given filter */ abstract count(filter?: TFilter): Promise; /** * Wraps a tRPC operation with error mapping */ protected execute(operation: () => Promise): Promise; /** * Creates an async iterator for paginated results */ protected createIterator(options?: IterateOptions & { crossScope?: boolean; }): AsyncGenerator; }