import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context'; import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder'; import { optionalStringComponentField, requireStringComponentField } from './_existing-component-fields'; /** * Initialization properties for {@link Category}. */ export interface CategoryProps { /** * Unique identifier for this category (e.g. `'gym__c'`). Must end with `__c`. * * Required. */ apiName: string; /** * Display name shown in the Rippling UI (e.g. `'Gym'`). * * Required. */ name: string; /** * Human-readable description of what this category groups. * * Required. The backend (`CustomCategoryConfig.description: str`) requires the * key to be present and rejects `null`. Pass an empty string only if you truly * have no meaningful copy. */ description: string; } /** * Defines a category and registers it with the manifest. * * A category is the navigation grouping that organizes related custom objects * in the Rippling sidebar. You would normally define one category per domain, * then pass it (or its api_name string) to each {@link CustomObject} in that domain. * * @example * ```ts * const manifest = new ManifestBuilder({ * key: 'gym_membership_management', * name: 'Gym Membership Management', * }); * * const gymCategory = new Category(manifest, { * apiName: 'gym__c', * name: 'Gym', * description: 'Memberships, staff, schedules, and attendance', * }); * * // Pass the instance to any CustomObject in this domain: * const memberObj = new CustomObject(manifest, { * apiName: 'gym_member__c', * name: 'Member', * category: gymCategory, * }); * ``` * * @see {@link CustomObject} — references this category via instance. */ export class Category { static readonly componentType = 'CUSTOM_CATEGORY' as const; static toDeletionIdentifier(apiName: string): ManifestComponentDeletion { return { type: Category.componentType, api_name: apiName, }; } private readonly _apiName: string; private readonly _name: string; private readonly _description: string; /** * @param scope - The manifest to register this category with. * @param props - Initialization properties. */ constructor(scope: ManifestScope, props: CategoryProps) { this._apiName = props.apiName; this._name = props.name; this._description = props.description; scope._register(this); } /** * Loads this category from the active existing-manifest JSON context. */ static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): Category { return getExistingManifestContext(options).loadCategory(apiName); } /** @internal Hydrates a category from existing manifest wire JSON. */ static _fromExistingComponent(scope: ManifestScope, component: Record): Category { return new Category(scope, { apiName: requireStringComponentField(component, 'api_name', Category.componentType), name: requireStringComponentField(component, 'name', Category.componentType), description: optionalStringComponentField(component, 'description'), }); } /** * Returns the api_name of this category (e.g. `'gym__c'`). */ getApiName(): string { return this._apiName; } /** * Returns the display name of this category (e.g. `'Gym'`). */ getName(): string { return this._name; } /** * Serializes this category to the wire format consumed by the manifest install endpoint. * * @returns A plain object with `type: 'CUSTOM_CATEGORY'` and all configured fields. */ toDict(): Record { return { type: Category.componentType, api_name: this._apiName, name: this._name, description: this._description, }; } }