/** * Clients Resource * * CRUD operations, filtering, pagination, and batch operations for clients. * * This is the base (unscoped) resource. For scope-scoped SDKs, use: * - MexClientsResource - Mexico-scoped with flattened MX fields * - AVIClientsResource - Mexico AVI-scoped with flattened AVI fields * - JYSClientsResource - Mexico JYS-scoped with flattened JYS fields * - TSCClientsResource - Mexico TSC-scoped with flattened TSC fields */ import type { AtomicBatchResult, Checklist, ClientFilter, ClientRelationshipType, ClientSort, CreateAddressInput, CreateBankAccountInput, CreateClientInput, CreateContactMethodInput, LinkedClient as LinkedClientInput, ListOptions, MigrationPreview, MigrationResult, migrateClientInputSchema, PaginatedResponse, PartialBatchResult, ResourceTagResponse, RiskAssessmentInfo, Scope, SyncCreateDocumentInput, SyncItem, SyncLinkedClientInput, SyncResult, SyncUpdateDocumentInput, UpdateAddressInput, UpdateBankAccountInput, UpdateClientInput, UpdateContactMethodInput, UpsertClientInput, UpsertResult } from "@artu-ai/shared"; import { z } from "zod"; import { Address, BankAccount, Client, type ClientData, ContactMethod, Document, LinkedClient } from "../models"; import { BaseResource, type ResourceConfig } from "./base"; import { type RelatedPartyParent, type RelatedPartySubresource } from "./related-parties/base"; /** * Options for batch operations */ export interface BatchOptions { /** * If true (default), all items must succeed or entire operation fails. * If false, partial success is allowed with succeeded/failed arrays. */ atomic?: boolean; } export interface ClientIncludeMap { primaryContacts?: boolean; addresses?: boolean; contactMethods?: boolean; bankAccounts?: boolean; documents?: boolean; linkedClients?: boolean; } /** * Options for `clients.migrate` / `clients.migratePreview`. The source-row * `id` is supplied as the first argument, so it is omitted here. */ export type MigrateClientOptions = Omit, "id">; /** * Base resource manager for client operations. * * Provides: * - CRUD operations (create, retrieve, update, delete) * - Filtering and pagination * - Batch operations (createMany, updateMany) * * @example * ```typescript * // Create a client with full nested format * const client = await sdk.clients.create({ * type: "individual", * name: "Juan García", * scopes: ["MX"], * scopeData: { * MX: { rfc: "GAJL850101ABC" }, * }, * }); * * // List with filtering * const { data, pagination } = await sdk.clients.list({ * filter: { type: "individual" }, * limit: 20, * }); * * // Iterate all clients * for await (const client of sdk.clients.iterate()) { * console.log(client.name); * } * ``` */ export declare class ClientsResource extends BaseResource { protected scopeFilterKey: "scope" | "scopes" | null; /** * Typed subresource for legal representatives. A legal rep is a * non-primary client linked to a parent client via the * `legal_representative` relationship type. * * ```ts * await sdk.clients.legalRepresentatives.create(companyId, { * type: "individual", * name: "Juan García", * scopes: ["MX"], * scopeData: { MX: { rfc: "GAJL850101ABC" } }, * }); * ``` */ readonly legalRepresentatives: RelatedPartySubresource; /** * Typed subresource for controlling beneficiaries. A controlling beneficiary * is a non-primary client linked to a parent client via the * `controlling_beneficiary` relationship type. */ readonly controllingBeneficiaries: RelatedPartySubresource; constructor(config: ResourceConfig); /** * Wires the typed subresources (`legalRepresentatives`, * `controllingBeneficiaries`) to this resource's methods. Scoped * subclasses override this so their typed subresources get scope-narrowed * input/output types. Method dispatch resolves at call time, so the * factory called in the base constructor still picks up subclass * overrides. */ protected relatedPartyParent(): RelatedPartyParent; /** * Creates a non-primary client and links it to a parent client in one * atomic call, with the relationship type chosen at call time. Use this * when the type is dynamic (e.g. UI flows that show a relationship-type * picker). When the type is fixed, prefer the typed subresources * (`legalRepresentatives.create`, `controllingBeneficiaries.create`). */ createRelatedClient(parentClientId: string, relationshipType: ClientRelationshipType, input: CreateClientInput, metadata?: Record): Promise>; /** * Builds a `RelatedPartyParent` for the typed subresources. Scoped * subclasses pass a `prepareChild` function that validates and (if * needed) transforms the scope-specific input into the base * `CreateClientInput` shape before it reaches the API. All other plumbing * (instantiate, list/retrieve/update/unlink) reuses the dynamically * dispatched methods on `this` so subclasses get the right model types * for free. */ protected makeRelatedPartyParent(prepareChild: (input: TCreateInput) => CreateClientInput): RelatedPartyParent; /** * Creates a new client. * * @param data - Client creation data in nested format * @returns The created client model * * @example * ```typescript * const client = await sdk.clients.create({ * type: ClientType.Person, * name: "Juan García", * scopes: ["MX"], * scopeData: { * MX: { rfc: "GAJL850101ABC" }, * }, * }); * ``` */ create(data: CreateClientInput): Promise; /** * Retrieves a client by ID. * * @param id - Client ID * @param options - Optional retrieve options: * - `include`: relations / primary contacts to populate * - `crossScope`: skip this scoped SDK's scope enforcement * @returns The client model (with optional fields populated if included) */ retrieve(id: string, options?: { include?: ClientIncludeMap; crossScope?: boolean; }): Promise; /** * Retrieves the raw, un-instantiated client payload by ID. * * The API call is identical across every scope; only the client-side * reshape differs. The React SDK caches this single scope-agnostic payload * and lets each scope derive its own shape via `materialize`, avoiding N * redundant fetches in nested scope-overview components. * * Unlike `retrieve`/`retrieveByMetadata`/`retrieveByExternalId`, this method * intentionally does NOT pass `requireScope`, and that is safe: `requireScope` * is a client-supplied OPTIONAL narrowing filter, never the authorization * boundary. Tenant isolation (organization + environment) is enforced * server-side by row-level security on the request's tenant context and * applies identically here — `retrieveRaw` hits the very same * `clients.retrieve` endpoint. Omitting `requireScope` only declines to * narrow to a sub-scope (AVI vs CNBV, etc.), which the caller can already do * via the base SDK or `crossScope: true`; it cannot expose another tenant's * data. This is required for the dedup: the payload is keyed by id alone and * fetched once for all scopes, so a per-scope filter would leak one scope's * narrowing onto every other scope sharing the cache entry. * * @param id - Client ID * @param include - Optional includes (relations, primary contacts) * @returns The raw API payload (`ClientData`) — not a model instance */ retrieveRaw(id: string, include?: ClientIncludeMap): Promise; /** * Retrieves a client by metadata key/value. */ retrieveByMetadata(key: string, value: string, opts?: { crossScope?: boolean; }): Promise; /** * Retrieves a client by externalId. * * @param externalId - External identifier assigned by your system * @returns The client model * * @example * ```typescript * const client = await sdk.clients.retrieveByExternalId("external-123"); * console.log(client.name); * ``` */ retrieveByExternalId(externalId: string, opts?: { crossScope?: boolean; }): Promise; /** * Get the compliance checklist for a client in a given scope. * * The checklist lists per-field, per-resource compliance items that * indicate what is missing or invalid for the client to be ready under * the requested scope. Items can be scoped to specific levels via * `levels` (omitted = required by every level the scope declares); * per-level completeness is in `complete`. * * @param id - The client's UUID * @param opts.scope - The scope to evaluate (e.g., "MX:AV:AVI") * @param opts.includeChildren - When true, rollup items include * per-child item arrays. Defaults to true; pass false to receive * only the rollup summaries (useful for list views). */ checklist(id: string, opts: { scope: Scope; includeChildren?: boolean; }): Promise; /** * Updates a client. * * @param id - Client ID * @param data - Update data (partial) * @returns The updated client model * * @example * ```typescript * const client = await sdk.clients.update("client_123", { * email: "new@example.com", * }); * ``` */ update(id: string, data: UpdateClientInput): Promise; /** * Deletes a client (soft delete). * * @param id - Client ID * * @example * ```typescript * await sdk.clients.delete("client_123"); * ``` */ delete(id: string): Promise; /** * Lists clients with optional filtering and pagination. * * @param options - List options (filter, cursor, limit) * @param options.includePrimaryContacts - Include primary email/phone * @returns Paginated response with client models * * @example * ```typescript * const { data, pagination } = await sdk.clients.list({ * filter: { type: "individual" }, * limit: 20, * }); * * if (pagination.hasMore) { * const nextPage = await sdk.clients.list({ * cursor: pagination.nextCursor, * }); * } * ``` */ list(options?: ListOptions & { includePrimaryContacts?: boolean; /** * When true, includes non-primary clients (legal reps, controlling * beneficiaries) in the results. Default behavior filters to * primary clients only. */ includeNonPrimary?: boolean; crossScope?: boolean; }): Promise>; /** * Counts clients matching the given filter. * * @param filter - Optional filter criteria * @param options - Optional flags * @param options.includeNonPrimary - When true, counts non-primary * clients as well. Defaults to primary-only. * @returns The count of matching clients * * @example * ```typescript * const count = await sdk.clients.count({ type: "individual" }); * console.log(`Found ${count} individual clients`); * ``` */ count(filter?: ClientFilter, options?: { includeNonPrimary?: boolean; crossScope?: boolean; }): Promise; /** * Promotes a non-primary client (a rep or BO created via a typed * subresource) to a primary client. Idempotent — calling on an * already-primary client returns the existing client unchanged. * * @param id - Client ID to promote * @returns The (now-primary) client model */ promote(id: string): Promise; /** * Copies this client into the other environment (test ↔ live), upserting it * and the selected subresources by externalId. * * The whole graph is migrated atomically: nothing is written unless every * resource can be matched by externalId. When any resource in the graph * lacks an externalId the call throws {@link MigrationBlockedError} (whose * `blockers` lists the offending resources) and nothing is written — assign * the missing externalIds and retry. * * @param id - Source-environment client id * @param options - Target environment, precedence, and subresource selection * @returns The completed migration result (target id, action, per-resource counts) * @throws {MigrationBlockedError} when resources in the graph lack an externalId * * @example * ```typescript * const result = await sdk.clients.migrate(clientId, { to: "live" }); * console.log(result.action, result.targetId); * ``` */ migrate(id: string, options: MigrateClientOptions): Promise>; /** * Dry-run of {@link migrate}: reports what would be created/updated in the * target environment and any blockers, without writing anything. * * @param id - Source-environment client id * @param options - Target environment, precedence, and subresource selection * @returns The migration preview (target action, blockers, per-resource counts) */ migratePreview(id: string, options: MigrateClientOptions): Promise; /** * Returns distinct risk assessment names and their display settings. * * @param query - Optional search string to filter assessment names * @returns Array of risk assessment info objects */ getRiskAssessments(query?: string): Promise; /** * Returns an async iterator over all clients matching the filter. * Automatically fetches subsequent pages as you iterate. * * @param options - Optional iteration options (filter, sort, pageSize) * @returns Async generator yielding client models * * @example * ```typescript * for await (const client of sdk.clients.iterate({ filter: { type: "individual" } })) { * console.log(client.name); * } * ``` */ iterate(options?: { filter?: ClientFilter; sort?: TSort; pageSize?: number; crossScope?: boolean; }): AsyncGenerator; /** * Creates multiple clients in a single request. * * @param clients - Array of client creation data * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed * * @example * ```typescript * // Atomic mode (default) - all or nothing * const result = await sdk.clients.createMany([...]); * if (result.atomic) { * console.log("All created:", result.data); * } * * // Non-atomic mode - partial success allowed * const result = await sdk.clients.createMany([...], { atomic: false }); * if (!result.atomic) { * console.log("Succeeded:", result.succeeded.length); * console.log("Failed:", result.failed.length); * } * ``` */ createMany(clients: CreateClientInput[], options?: { atomic?: true; }): Promise>; createMany(clients: CreateClientInput[], options: { atomic: false; }): Promise>; /** * Updates multiple clients in a single request. * * @param updates - Array of { id, data } updates * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed * * @example * ```typescript * // Atomic mode (default) - all or nothing * const result = await sdk.clients.updateMany([...]); * if (result.atomic) { * console.log("All updated:", result.data); * } * * // Non-atomic mode - partial success allowed * const result = await sdk.clients.updateMany([...], { atomic: false }); * if (!result.atomic) { * console.log("Succeeded:", result.succeeded.length); * console.log("Failed:", result.failed.length); * } * ``` */ updateMany(updates: { id: string; data: UpdateClientInput; }[], options?: { atomic?: true; }): Promise>; updateMany(updates: { id: string; data: UpdateClientInput; }[], options: { atomic: false; }): Promise>; /** * Creates or updates a client by externalId. * * If a client with the given externalId exists, it will be updated. * Otherwise, a new client will be created. * * @param data - Client data with required externalId * @returns Result containing the client and action taken (created/updated) * * @example * ```typescript * const result = await sdk.clients.upsertByExternalId({ * externalId: "crm-123", * type: ClientType.Individual, * name: "Juan García", * scopes: ["MX"], * }); * * if (result.action === "created") { * console.log("New client created:", result.data.id); * } else { * console.log("Existing client updated:", result.data.id); * } * ``` */ upsertByExternalId(data: UpsertClientInput): Promise>; /** * Creates or updates multiple clients by externalId in a single request. * * @param clients - Array of client data with required externalId * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed * * @example * ```typescript * // Atomic mode (default) - all or nothing * const result = await sdk.clients.upsertManyByExternalId([ * { externalId: "crm-1", type: "individual", name: "Client 1", ... }, * { externalId: "crm-2", type: "company", name: "Client 2", ... }, * ]); * * // Non-atomic mode - partial success allowed * const result = await sdk.clients.upsertManyByExternalId([...], { atomic: false }); * ``` */ upsertManyByExternalId(clients: UpsertClientInput[], options?: { atomic?: true; }): Promise>>; upsertManyByExternalId(clients: UpsertClientInput[], options: { atomic: false; }): Promise>>; /** * Links a client to another client with a relationship type. * * @param parentClientId - The ID of the parent client (e.g., a company) * @param childClientId - The ID of the child client (e.g., a legal rep) * @param relationshipType - The type of relationship * @param metadata - Optional metadata for the relationship * @returns The updated parent client * * @example * ```typescript * // Link a legal representative to a company * await sdk.clients.linkClient( * companyId, * legalRepId, * ClientRelationshipType.LegalRepresentative * ); * * // Link a controlling beneficiary with metadata * await sdk.clients.linkClient( * companyId, * beneficiaryId, * ClientRelationshipType.ControllingBeneficiary, * { ownershipPercentage: 51 } * ); * ``` */ linkClient(parentClientId: string, childClientId: string, relationshipType: ClientRelationshipType, metadata?: Record): Promise; /** * Unlinks a client from another client. * * @param parentClientId - The ID of the parent client * @param childClientId - The ID of the child client to unlink * @param relationshipType - The type of relationship to remove * @returns The updated parent client * * @example * ```typescript * // Remove a legal representative * await sdk.clients.unlinkClient( * companyId, * legalRepId, * ClientRelationshipType.LegalRepresentative * ); * ``` */ unlinkClient(parentClientId: string, childClientId: string, relationshipType: ClientRelationshipType): Promise; /** * Lists all clients linked to a parent client. * * @param parentClientId - The ID of the parent client * @param relationshipType - Optional filter by relationship type * @returns Array of LinkedClient models * * @example * ```typescript * // List all linked clients * const links = await sdk.clients.listLinkedClients(companyId); * * // List only legal representatives * const legalReps = await sdk.clients.listLinkedClients( * companyId, * ClientRelationshipType.LegalRepresentative * ); * * // Access relationship metadata * for (const link of links) { * console.log(link.id, link.relationshipType, link.metadata); * } * ``` */ listLinkedClients(parentClientId: string, relationshipType?: ClientRelationshipType): Promise; /** * Retrieves a single linked client relationship by ID. * * @param id - The ID of the relationship * @returns The LinkedClient model * * @example * ```typescript * const relationship = await sdk.clients.retrieveLinkedClient(relationshipId); * console.log(relationship.relationshipType, relationship.metadata); * ``` */ retrieveLinkedClient(id: string): Promise; /** * Updates a linked client relationship's metadata. * * @param id - The ID of the relationship * @param metadata - New metadata for the relationship * @returns The updated LinkedClient model * * @example * ```typescript * const updated = await sdk.clients.updateLinkedClient(relationshipId, { * ownershipPercentage: 51, * votingRights: true, * }); * ``` */ updateLinkedClient(id: string, metadata?: Record): Promise; /** * Replaces all linked clients for a parent client. * * @param parentClientId - The ID of the parent client * @param linkedClients - Array of linked client definitions * @returns The updated parent client * * @example * ```typescript * // Replace all linked clients * await sdk.clients.setLinkedClients(companyId, [ * { clientId: legalRepId, relationshipType: "legal_representative" }, * { clientId: beneficiaryId, relationshipType: "controlling_beneficiary" }, * ]); * ``` */ setLinkedClients(parentClientId: string, linkedClients: LinkedClientInput[]): Promise; /** * Atomically creates or updates a client and all related resources in a single transaction. * * All operations succeed or fail together - if any fails, the entire * transaction is rolled back. * * Supports two modes: * - Edit mode: When clientId is provided, validates the client exists and optionally updates it * - Create mode: When clientId is undefined, creates a new client from the client data * * @param clientId - The client ID to update (undefined for create mode) * @param data - Sync data with optional client, addresses, contactMethods, bankAccounts, linkedClients * @param options - Sync options * @param options.replaceMode - When true, items not in the arrays are deleted (default: false) * @returns Result with created/updated client and sync results for each resource type * * @example * ```typescript * // Create mode - create a new client with addresses atomically * const result = await sdk.clients.syncAll(undefined, { * client: { type: "individual", name: "Juan García" }, * addresses: [ * { street: "123 Main St", city: "Mexico City", country: "MX" }, * ], * }); * * // Edit mode - update existing client and sync related resources * const result = await sdk.clients.syncAll("client_123", { * client: { name: "Updated Name" }, * addresses: [ * { street: "123 New St", city: "Mexico City", country: "MX" }, // create * { id: "addr_456", street: "Updated St" }, // update * { id: "addr_789", _delete: true }, // delete * ], * }); * * // Replace mode - provided arrays become the complete set * const result = await sdk.clients.syncAll("client_123", { * addresses: currentAddresses, * }, { replaceMode: true }); * * console.log(result.client); // created/updated client * console.log(result.addresses.created); // newly created addresses * ``` */ syncAll(clientId: undefined, data: { client: CreateClientInput; addresses?: SyncItem[]; contactMethods?: SyncItem[]; bankAccounts?: SyncItem[]; documents?: SyncItem[]; linkedClients?: SyncLinkedClientInput[]; }, options?: { replaceMode?: boolean; }): Promise<{ client: Client; addresses: SyncResult
; contactMethods: SyncResult; bankAccounts: SyncResult; documents: SyncResult; }>; syncAll(clientId: string, data: { client?: UpdateClientInput; addresses?: SyncItem[]; contactMethods?: SyncItem[]; bankAccounts?: SyncItem[]; documents?: SyncItem[]; linkedClients?: SyncLinkedClientInput[]; }, options: { replaceMode: true; }): Promise<{ client: Client; addresses: SyncResult
; contactMethods: SyncResult; bankAccounts: SyncResult; documents: SyncResult; }>; syncAll(clientId: string, data: { client?: UpdateClientInput; addresses?: SyncItem[]; contactMethods?: SyncItem[]; bankAccounts?: SyncItem[]; documents?: SyncItem[]; linkedClients?: SyncLinkedClientInput[]; }, options?: { replaceMode?: boolean; }): Promise<{ client: Client; addresses: SyncResult
; contactMethods: SyncResult; bankAccounts: SyncResult; documents: SyncResult; }>; /** * Adds tags to a client. */ addTags(id: string, tagIds: string[]): Promise; /** * Removes tags from a client. */ removeTags(id: string, tagIds: string[]): Promise; /** * Replaces all tags on a client. */ setTags(id: string, tagIds: string[]): Promise; /** * Lists all tags on a client. */ listTags(id: string): Promise; /** * Instantiates a Client model from API response data. * Subclasses override this to return their specific model type. */ protected instantiate(data: ClientData): Client; /** * Public, pure transform from a raw client payload to this scope's model. * * Wraps the protected `instantiate` so the React SDK can apply it inside a * TanStack Query `select`. Subclasses override `instantiate` (and override * `materialize` to narrow the return type), so each scope automatically * materializes its own model type. */ materialize(data: ClientData): Client; }