Customer
Source: src/data/customer/
Service
createCustomerService / CustomerService
Factory: createCustomerService(repo: CustomerRepository) — returns CustomerService.
All business logic for customer lifecycle (create/merge/link/address management) lives here.
| Method | Parameters | Returns | Description |
|---|---|---|---|
| findOrCreateCustomer | (ctx: RequestContext, input: FindOrCreateCustomerInput) | Promise<FindOrCreateResult> | Finds existing customer by Discord id or creates a new one |
| getCustomer | (ctx: RequestContext, id: string) | Promise<CustomerRecord | null> | Returns a customer by id |
| listCustomers | (ctx: RequestContext) | Promise<CustomerRecord[]> | Lists all customers for the tenant |
| updateCustomer | (ctx: RequestContext, id: string, input: UpdateCustomerInput) | Promise<CustomerRecord> | Patches an existing customer profile |
| deactivateCustomer | (ctx: RequestContext, id: string) | Promise<CustomerRecord> | Marks a customer as inactive |
| linkCustomerToUser | (ctx: RequestContext, customerId: string, userId: string) | Promise<CustomerRecord> | Links a customer record to a user account |
| mergeCustomers | (ctx: RequestContext, sourceId: string, targetId: string, reassignOrders: fn) | Promise<MergeResult> | Merges source customer into target: combines addresses, reassigns orders, deactivates source |
| addAddress | (ctx: RequestContext, customerId: string, address: AddressWithId) | Promise<CustomerRecord> | Appends an address to the customer; auto-sets default if first |
| removeAddress | (ctx: RequestContext, customerId: string, addressId: string) | Promise<CustomerRecord> | Removes an address; recalculates default if needed |
| setDefaultAddress | (ctx: RequestContext, customerId: string, addressId: string) | Promise<CustomerRecord> | Sets the default address for the customer |
| setDmPreference | (ctx: RequestContext, customerId: string, key: keyof CustomerDmPreferences, value: boolean) | Promise<CustomerRecord> | Patches one DM notification preference; throws NOT_FOUND when customer doesn't exist |
| setEmailPreference | (ctx: RequestContext, customerId: string, key: keyof CustomerEmailPreferences, value: boolean) | Promise<CustomerRecord> | Patches one email notification preference; throws NOT_FOUND when customer doesn't exist |
Repository
CustomerRepository
| Method | Parameters | Returns |
|---|---|---|
| getById | (ctx: RequestContext, id: string) | Promise<CustomerRecord | null> |
| listByTenant | (ctx: RequestContext) | Promise<CustomerRecord[]> |
| create | (ctx: RequestContext, input: Omit<CustomerRecord, 'id' | 'createdAt' | 'updatedAt'>) | Promise<CustomerRecord> |
| update | (ctx: RequestContext, id: string, patch: CustomerPatch) | Promise<CustomerRecord> |
| delete | (ctx: RequestContext, id: string) | Promise<void> |
| findByDiscordId | (ctx: RequestContext, discordId: string) | Promise<CustomerRecord | null> |
| findByUserId | (ctx: RequestContext, userId: string) | Promise<CustomerRecord | null> |
| findByEmail | (ctx: RequestContext, email: string) | Promise<CustomerRecord | null> |
| listByCustomerIds | (ctx: RequestContext, ids: string[]) | Promise<CustomerRecord[]> |
CustomerRecord
| Field | Type |
|---|---|
| id | string |
| tenantId | string |
| userId | string (optional) |
| discordId | string |
| discordUsername | string |
| discordDisplayName | string |
| source | CustomerSource |
string (optional) | |
| phone | string (optional) |
| addresses | AddressWithId[] |
| defaultAddressId | string (optional) |
| notes | string (optional) |
| isActive | boolean |
| dmPreferences | CustomerDmPreferences — { orderReceived, paymentConfirmed, orderShipped, refunds } all default true |
| emailPreferences | CustomerEmailPreferences — { orderReceived, paymentConfirmed, orderShipped, refunds } all default true |
| createdAt | Date |
| updatedAt | Date |
Validators
customerSchema
| Field | Type | Required | Default |
|---|---|---|---|
| tenantId | string | Yes | — |
| userId | string | No | — |
| discordId | string | Yes | — |
| discordUsername | string | Yes | — |
| discordDisplayName | string | Yes | — |
| source | enum('DISCORD', 'EMAIL', 'MANUAL') | No | 'DISCORD' |
string (email) | No | — | |
| phone | string | No | — |
| addresses | AddressWithId[] | No | [] |
| defaultAddressId | string | No | — |
| notes | string | No | — |
| isActive | boolean | No | true |
| createdAt | Date (coerced) | Yes | — |
| updatedAt | Date (coerced) | Yes | — |
findOrCreateCustomerSchema
| Field | Type | Required | Default |
|---|---|---|---|
| discordId | string | Yes | — |
| discordUsername | string | Yes | — |
| discordDisplayName | string | Yes | — |
| source | enum('DISCORD', 'EMAIL', 'MANUAL') | No | 'DISCORD' |
string (email) | No | — | |
| phone | string | No | — |
| userId | string | No | — |
updateCustomerSchema
| Field | Type | Required | Default |
|---|---|---|---|
| discordUsername | string | No | — |
| discordDisplayName | string | No | — |
string (email) | No | — | |
| phone | string | No | — |
| notes | string | No | — |
| userId | string | No | — |
Inferred Types
Customer—z.infer<typeof customerSchema>