diff --git a/api-client/src/client.ts b/api-client/src/client.ts index 3e942bc..3e5afb5 100644 --- a/api-client/src/client.ts +++ b/api-client/src/client.ts @@ -22,10 +22,41 @@ import { ValidationError, } from "./errors"; +/** + * Configuration options for the LittleHorse User Tasks API client + * @interface + */ +export interface ClientConfig { + /** Base URL of the API endpoint (e.g., "https://api.example.com/v1") */ + baseUrl: string; + /** Tenant identifier for multi-tenant environments */ + tenantId: string; + /** OAuth access token for authentication (Bearer token) */ + accessToken: string; +} + /** * Client for interacting with the LittleHorse UserTasks API. - * Provides methods for managing UserTasks, including claiming, canceling, and completing tasks, - * as well as administrative functions. + * + * This client provides a comprehensive interface for managing user tasks, including: + * - Basic task operations (claim, cancel, complete) + * - Task listing and filtering + * - Administrative functions + * - User and group management + * + * All methods automatically handle authentication and error handling. + * + * @example + * ```typescript + * const client = new LittleHorseUserTasksApiClient({ + * baseUrl: 'https://api.example.com', + * tenantId: 'tenant1', + * accessToken: 'oauth-token' + * }); + * + * // List available tasks + * const tasks = await client.listUserTasks({ limit: 10 }); + * ``` */ export class LittleHorseUserTasksApiClient { private baseUrl: string; @@ -34,13 +65,11 @@ export class LittleHorseUserTasksApiClient { /** * Creates a new instance of the LittleHorse UserTasks API client - * @param config Configuration object containing baseUrl, tenantId, and accessToken + * @param config - Configuration object containing connection details + * @throws {ValidationError} If required configuration parameters are missing or invalid + * @throws {UnauthorizedError} If initial connection test fails */ - constructor(config: { - baseUrl: string; - tenantId: string; - accessToken: string; - }) { + constructor(config: ClientConfig) { this.baseUrl = config.baseUrl.replace(/\/$/, ""); // Remove trailing slash if present this.tenantId = config.tenantId; this.accessToken = config.accessToken; @@ -49,9 +78,15 @@ export class LittleHorseUserTasksApiClient { /** * Internal method to make authenticated HTTP requests to the API - * @param path API endpoint path - * @param init Optional fetch configuration + * @param path - API endpoint path (without base URL and tenant) + * @param init - Optional fetch configuration including method, headers, and body * @returns Promise resolving to the JSON response or void + * @throws {UnauthorizedError} If authentication fails + * @throws {ForbiddenError} If the user lacks necessary permissions + * @throws {TaskStateError} If attempting to modify a task that is DONE or CANCELLED + * @throws {ValidationError} If the request is malformed + * @throws {AssignmentError} If task assignment/claim conditions aren't met + * @throws {PreconditionFailedError} If other preconditions aren't met * @private */ private async fetch(path: string, init?: RequestInit): Promise { @@ -131,8 +166,22 @@ export class LittleHorseUserTasksApiClient { // User Methods /** - * Claims a UserTask for the authenticated user - * @param userTask The UserTask to claim + * Claims a UserTask for the authenticated user. + * Once claimed, the task is assigned exclusively to the claiming user. + * + * @param userTask - The UserTask to claim, must contain wfRunId and id + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have permission to claim the task + * @throws {TaskStateError} If the task is already completed or cancelled + * @throws {AssignmentError} If the task cannot be claimed (e.g., already claimed by another user) + * + * @example + * ```typescript + * await client.claimUserTask({ + * id: 'task-123', + * wfRunId: 'workflow-456' + * }); + * ``` */ async claimUserTask(userTask: UserTask): Promise { await this.fetch(`/tasks/${userTask.wfRunId}/${userTask.id}/claim`, { @@ -141,8 +190,22 @@ export class LittleHorseUserTasksApiClient { } /** - * Cancels a UserTask - * @param userTask The UserTask to cancel + * Cancels a UserTask, preventing it from being completed. + * Once cancelled, a task cannot be claimed or completed. + * + * @param userTask - The UserTask to cancel, must contain wfRunId and id + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have permission to cancel the task + * @throws {TaskStateError} If the task is already completed or cancelled + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * await client.cancelUserTask({ + * id: 'task-123', + * wfRunId: 'workflow-456' + * }); + * ``` */ async cancelUserTask(userTask: UserTask): Promise { await this.fetch(`/tasks/${userTask.wfRunId}/${userTask.id}/cancel`, { @@ -151,9 +214,30 @@ export class LittleHorseUserTasksApiClient { } /** - * Lists UserTasks based on search criteria - * @param search Search parameters (excluding user_task_def_name) - * @returns Promise resolving to the list of UserTasks + * Lists UserTasks based on search criteria. + * Results are paginated and can be filtered by various parameters. + * + * @param search - Search parameters for filtering tasks + * @param search.limit - Maximum number of results to return + * @param search.earliest_start_date - Optional ISO 8601 timestamp for earliest task start + * @param search.latest_start_date - Optional ISO 8601 timestamp for latest task start + * @param search.status - Optional task status filter + * @param search.user_id - Optional filter by assigned user + * @param search.user_group_id - Optional filter by assigned group + * @param search.bookmark - Optional pagination token from previous response + * + * @returns Promise resolving to paginated list of UserTasks + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ValidationError} If search parameters are invalid + * + * @example + * ```typescript + * const result = await client.listUserTasks({ + * limit: 10, + * status: 'ASSIGNED', + * user_id: 'user-123' + * }); + * ``` */ async listUserTasks( search: Omit, @@ -173,26 +257,66 @@ export class LittleHorseUserTasksApiClient { } /** - * Retrieves all user groups + * Retrieves all user groups available in the system. + * * @returns Promise resolving to the list of user groups + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have permission to list groups + * + * @example + * ```typescript + * const groups = await client.listUserGroups(); + * console.log(groups.groups); // Array of UserGroup objects + * ``` */ async listUserGroups(): Promise { return await this.fetch("/groups"); } /** - * Retrieves a specific UserTask by ID - * @param userTask The UserTask to retrieve - * @returns Promise resolving to the UserTask details + * Retrieves detailed information about a specific UserTask. + * + * @param userTask - The UserTask to retrieve, must contain wfRunId and id + * @returns Promise resolving to the detailed UserTask information + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have permission to view the task + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * const taskDetails = await client.getUserTask({ + * id: 'task-123', + * wfRunId: 'workflow-456' + * }); + * console.log(taskDetails.status); // Current task status + * console.log(taskDetails.fields); // Array of field definitions + * ``` */ async getUserTask(userTask: UserTask): Promise { return await this.fetch(`/tasks/${userTask.wfRunId}/${userTask.id}`); } /** - * Completes a UserTask with the provided values - * @param userTask The UserTask to complete - * @param values The result values for the task + * Completes a UserTask by submitting the required result values. + * + * @param userTask - The UserTask to complete, must contain wfRunId and id + * @param values - The result values for the task fields + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have permission to complete the task + * @throws {TaskStateError} If the task is already completed or cancelled + * @throws {ValidationError} If the provided values don't match the task's field definitions + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * await client.completeUserTask( + * { id: 'task-123', wfRunId: 'workflow-456' }, + * { + * approved: { type: 'BOOLEAN', value: true }, + * comment: { type: 'STRING', value: 'Looks good!' } + * } + * ); + * ``` */ async completeUserTask( userTask: UserTask, @@ -209,8 +333,20 @@ export class LittleHorseUserTasksApiClient { // Admin Methods /** - * Administrative method to cancel a UserTask - * @param userTask The UserTask to cancel + * Administrative method to cancel any UserTask, regardless of its current state or assignment. + * + * @param userTask - The UserTask to cancel + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * await client.adminCancelUserTask({ + * id: 'task-123', + * wfRunId: 'workflow-456' + * }); + * ``` */ async adminCancelUserTask(userTask: UserTask): Promise { await this.fetch(`/admin/tasks/${userTask.wfRunId}/${userTask.id}/cancel`, { @@ -219,9 +355,26 @@ export class LittleHorseUserTasksApiClient { } /** - * Administrative method to assign a UserTask to a specific user or group - * @param userTask The UserTask to assign - * @param assignTo Object containing userId and/or userGroupId + * Administrative method to assign a UserTask to a specific user or group. + * If both userId and userGroupId are provided, the user must be a member of the group but + * the task will be assigned to the user. + * + * @param userTask - The UserTask to assign + * @param assignTo - Object containing userId and/or userGroupId + * @param assignTo.userId - Optional user ID to assign the task to + * @param assignTo.userGroupId - Optional user group ID to assign the task to + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * @throws {ValidationError} If neither userId nor userGroupId is provided + * @throws {NotFoundError} If the task, user, or group doesn't exist + * + * @example + * ```typescript + * await client.adminAssignUserTask( + * { id: 'task-123', wfRunId: 'workflow-456' }, + * { userId: 'user-789', userGroupId: 'group-101' } + * ); + * ``` */ async adminAssignUserTask( userTask: UserTask, @@ -240,25 +393,57 @@ export class LittleHorseUserTasksApiClient { } /** - * Administrative method to list all users - * @returns Promise resolving to the list of users + * Administrative method to list all users in the system. + * + * @returns Promise resolving to the list of all users + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * + * @example + * ```typescript + * const users = await client.adminListUsers(); + * console.log(users.users); // Array of User objects + * ``` */ async adminListUsers(): Promise { return await this.fetch("/admin/users"); } /** - * Administrative method to list all user groups - * @returns Promise resolving to the list of user groups + * Administrative method to list all user groups in the system. + * + * @returns Promise resolving to the list of all user groups + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * + * @example + * ```typescript + * const groups = await client.adminListUserGroups(); + * console.log(groups.groups); // Array of UserGroup objects + * ``` */ async adminListUserGroups(): Promise { return await this.fetch("/admin/groups"); } /** - * Administrative method to list UserTask definition names - * @param search Search parameters for task definitions + * Administrative method to list all available UserTask definition names. + * + * @param search - Search parameters for filtering task definitions + * @param search.limit - Maximum number of results to return + * @param search.bookmark - Optional pagination token from previous response * @returns Promise resolving to the list of task definition names + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * + * @example + * ```typescript + * const taskDefs = await client.adminListUserTaskDefNames({ + * limit: 20, + * bookmark: 'next-page-token' + * }); + * console.log(taskDefs.userTaskDefNames); // Array of task definition names + * ``` */ async adminListUserTaskDefNames( search: ListUserTaskDefNamesRequest, @@ -277,9 +462,25 @@ export class LittleHorseUserTasksApiClient { } /** - * Administrative method to get a UserTask - * @param userTask The UserTask to get the details of - * @returns Promise resolving to the UserTask details + * Administrative method to get detailed information about a UserTask. + * This method provides additional information not available in the regular getUserTask method, + * including task events history. + * + * @param userTask - The UserTask to retrieve details for + * @returns Promise resolving to the detailed UserTask information including events history + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * const taskDetails = await client.adminGetUserTask({ + * id: 'task-123', + * wfRunId: 'workflow-456' + * }); + * console.log(taskDetails.events); // Array of task events + * console.log(taskDetails.status); // Current task status + * ``` */ async adminGetUserTask( userTask: UserTask, @@ -288,9 +489,34 @@ export class LittleHorseUserTasksApiClient { } /** - * Administrative method to list all UserTasks - * @param search Search parameters for tasks - * @returns Promise resolving to the list of UserTasks + * Administrative method to list all UserTasks in the system. + * Provides comprehensive access to all tasks regardless of assignment or state. + * + * @param search - Search parameters for filtering tasks + * @param search.limit - Maximum number of results to return + * @param search.type - Task definition type to filter by + * @param search.earliest_start_date - Optional ISO 8601 timestamp for earliest task start + * @param search.latest_start_date - Optional ISO 8601 timestamp for latest task start + * @param search.status - Optional task status filter + * @param search.user_id - Optional filter by assigned user + * @param search.user_group_id - Optional filter by assigned group + * @param search.bookmark - Optional pagination token + * @returns Promise resolving to the paginated list of UserTasks + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * @throws {ValidationError} If search parameters are invalid + * + * @example + * ```typescript + * const tasks = await client.adminListUserTasks({ + * limit: 50, + * type: 'approval', + * status: 'DONE', + * earliest_start_date: '2024-01-01T00:00:00Z' + * }); + * console.log(tasks.userTasks); // Array of UserTask objects + * console.log(tasks.bookmark); // Pagination token for next page + * ``` */ async adminListUserTasks( search: ListUserTasksRequest, @@ -300,7 +526,6 @@ export class LittleHorseUserTasksApiClient { .filter(([_, value]) => value !== undefined && value !== null) .map(([key, value]) => [ key, - // Ensure value is properly encoded for URLs encodeURIComponent(value.toString().trim()), ]), ); @@ -310,9 +535,26 @@ export class LittleHorseUserTasksApiClient { } /** - * Administrative method to complete a UserTask - * @param userTask The UserTask to complete - * @param values The result values for the task + * Administrative method to complete any UserTask. + * Allows administrators to complete tasks regardless of their current state or assignment. + * + * @param userTask - The UserTask to complete + * @param values - The result values for the task fields + * @throws {UnauthorizedError} If the user is not authenticated + * @throws {ForbiddenError} If the user doesn't have administrative permissions + * @throws {ValidationError} If the provided values don't match the task's field definitions + * @throws {NotFoundError} If the task doesn't exist + * + * @example + * ```typescript + * await client.adminCompleteUserTask( + * { id: 'task-123', wfRunId: 'workflow-456' }, + * { + * approved: { type: 'BOOLEAN', value: true }, + * reason: { type: 'STRING', value: 'Administrative override' } + * } + * ); + * ``` */ async adminCompleteUserTask( userTask: UserTask, diff --git a/api-client/src/errors.ts b/api-client/src/errors.ts index 8972bd4..2ca81ba 100644 --- a/api-client/src/errors.ts +++ b/api-client/src/errors.ts @@ -1,4 +1,13 @@ +/** + * Base error class for LH User Tasks API errors. + * Provides common functionality and proper stack trace capture for all derived error classes. + * @extends Error + */ export class LHUserTasksError extends Error { + /** + * Creates a new LHUserTasksError instance + * @param message - The error message + */ constructor(message: string) { super(message); this.name = this.constructor.name; @@ -9,6 +18,11 @@ export class LHUserTasksError extends Error { } } +/** + * Thrown when the request is malformed or contains invalid parameters. + * Typically corresponds to HTTP 400 Bad Request responses. + * @extends LHUserTasksError + */ export class BadRequestError extends LHUserTasksError { constructor( message: string = "Bad request - The request was malformed or contained invalid parameters", @@ -17,6 +31,11 @@ export class BadRequestError extends LHUserTasksError { } } +/** + * Thrown when authentication is required but not provided or is invalid. + * Typically corresponds to HTTP 401 Unauthorized responses. + * @extends LHUserTasksError + */ export class UnauthorizedError extends LHUserTasksError { constructor( message: string = "Unauthorized - Authentication is required to access this resource", @@ -25,6 +44,11 @@ export class UnauthorizedError extends LHUserTasksError { } } +/** + * Thrown when the authenticated user lacks necessary permissions. + * Typically corresponds to HTTP 403 Forbidden responses. + * @extends LHUserTasksError + */ export class ForbiddenError extends LHUserTasksError { constructor( message: string = "Forbidden - You do not have permission to perform this action", @@ -33,6 +57,11 @@ export class ForbiddenError extends LHUserTasksError { } } +/** + * Thrown when the requested resource cannot be found. + * Typically corresponds to HTTP 404 Not Found responses. + * @extends LHUserTasksError + */ export class NotFoundError extends LHUserTasksError { constructor( message: string = "Not found - The requested resource does not exist", @@ -41,6 +70,11 @@ export class NotFoundError extends LHUserTasksError { } } +/** + * Thrown when a precondition for the request has not been met. + * Typically corresponds to HTTP 412 Precondition Failed responses. + * @extends LHUserTasksError + */ export class PreconditionFailedError extends LHUserTasksError { constructor( message: string = "Precondition failed - The request cannot be completed in the current state", @@ -50,6 +84,12 @@ export class PreconditionFailedError extends LHUserTasksError { } // Business Logic Errors + +/** + * Thrown when input validation fails. + * Used for both client-side and server-side validation errors. + * @extends LHUserTasksError + */ export class ValidationError extends LHUserTasksError { constructor( message: string = "Validation error - The provided data is invalid", @@ -58,6 +98,11 @@ export class ValidationError extends LHUserTasksError { } } +/** + * Thrown when attempting to perform an action on a task in an invalid state. + * For example, trying to complete an already cancelled task. + * @extends LHUserTasksError + */ export class TaskStateError extends LHUserTasksError { constructor( message: string = "Task state error - The task is in an invalid state for this operation", @@ -66,6 +111,11 @@ export class TaskStateError extends LHUserTasksError { } } +/** + * Thrown when task assignment operations fail. + * This could be due to conflicts, permissions, or invalid state transitions. + * @extends LHUserTasksError + */ export class AssignmentError extends LHUserTasksError { constructor( message: string = "Assignment error - The task cannot be assigned in its current state", @@ -73,8 +123,3 @@ export class AssignmentError extends LHUserTasksError { super(message); } } - -// Optional: Helper function to determine if an error is a LHUserTasksError -export function isLHUserTasksError(error: unknown): error is LHUserTasksError { - return error instanceof LHUserTasksError; -} diff --git a/api-client/src/types.ts b/api-client/src/types.ts index 65cefeb..b4946ea 100644 --- a/api-client/src/types.ts +++ b/api-client/src/types.ts @@ -1,33 +1,68 @@ +/** + * Response type for listing task type definitions + */ export type ListTaskTypesResponse = { + /** Array of user task definition names */ userTaskDefNames: string[]; + /** Pagination bookmark token, null if no more results */ bookmark: string | null; }; +/** + * Response type for listing user tasks + */ export type ListUserTasksResponse = { + /** Array of user tasks */ userTasks: UserTask[]; + /** Pagination bookmark token, null if no more results */ bookmark: string | null; }; +/** + * Response type for listing users + */ export type ListUsersResponse = { + /** Array of users */ users: User[]; }; +/** + * Response type for listing user groups + */ export type ListUserGroupsResponse = { + /** Array of user groups */ groups: UserGroup[]; }; +/** + * Represents a user task in the system + */ export type UserTask = { + /** Unique identifier for the task */ id: string; + /** Workflow run identifier associated with this task */ wfRunId: string; + /** Name of the user task definition */ userTaskDefName: string; + /** User group assigned to this task, if any */ userGroup?: UserGroup; + /** User assigned to this task, if any */ user?: User; + /** Current status of the task */ status: Status; + /** Additional notes or description for the task */ notes: string; + /** ISO 8601 timestamp when the task was scheduled */ scheduledTime: string; }; +/** Possible states of a user task */ export type Status = "UNASSIGNED" | "ASSIGNED" | "DONE" | "CANCELLED"; + +/** Supported field types for task inputs/outputs + * UNRECOGNIZED is used when the server returns an unknown field type + * that wasn't available when this client was built + */ export type FieldType = | "DOUBLE" | "BOOLEAN" @@ -35,51 +70,95 @@ export type FieldType = | "INTEGER" | "UNRECOGNIZED"; +/** Valid field values based on FieldType */ export type FieldValue = number | boolean | string; +/** + * Represents a user group in the system + */ export type UserGroup = { + /** Unique identifier for the group */ id: string; + /** Display name of the group */ name: string; + /** Whether the group exists in the configured OIDC provider */ valid: boolean; }; +/** + * Represents a user in the system + */ export type User = { + /** Unique identifier for the user */ id: string; + /** User's email address */ email: string | null; + /** Username, if different from email */ username: string | null; + /** User's first name */ firstName: string | null; + /** User's last name */ lastName: string | null; + /** Whether the user exists in the configured OIDC provider */ valid: boolean; }; +/** + * Request parameters for listing user tasks + */ export type ListUserTasksRequest = { + /** Maximum number of results to return */ limit: number; + /** Task definition type to filter by */ type: string; + /** ISO 8601 timestamp for earliest start date filter */ earliest_start_date?: string; + /** ISO 8601 timestamp for latest start date filter */ latest_start_date?: string; + /** Filter by task status */ status?: Status; + /** Filter by assigned user ID */ user_id?: string; + /** Filter by assigned user group ID */ user_group_id?: string; + /** Pagination bookmark token */ bookmark?: string; }; +/** + * Request parameters for listing task definition names + */ export type ListUserTaskDefNamesRequest = { + /** Maximum number of results to return */ limit: number; + /** Pagination bookmark token */ bookmark?: string; }; +/** + * Response type for listing task definition names + */ export type ListUserTaskDefNamesResponse = { + /** Array of task definition names */ userTaskDefNames?: string[]; }; +/** + * Response type for getting user task details + */ export type GetUserTaskResponse = { + /** Unique identifier for the task */ id: string; + /** Workflow run identifier */ wfRunId: string; + /** Task definition name */ userTaskDefName: string; + /** Assigned user group information */ userGroup: { id: string; name: string; }; + /** Assigned user information */ user: { id: string; email: string; @@ -87,47 +166,90 @@ export type GetUserTaskResponse = { firstName: string; lastName: string; }; + /** Current task status */ status: Status; + /** Task notes/description */ notes: string; + /** ISO 8601 timestamp when task was scheduled */ scheduledTime: string; + /** Array of field definitions for this task */ fields: { + /** Field identifier */ name: string; + /** Human-readable field name */ displayName: string; + /** Field description */ description: string; + /** Field data type */ type: FieldType; + /** Whether the field is required */ required: boolean; }[]; + /** Task result values */ results: UserTaskResult; }; +/** + * Extended response type for administrative task views + */ export type AdminGetUserTaskResponse = GetUserTaskResponse & { + /** Array of events that occurred on this task */ events: UserTaskEvent[]; }; +/** + * Event representing task execution + */ export type UserTaskExecutedEvent = { + /** Workflow run identifier */ wfRunId: string; + /** Task identifier */ userTaskGuid: string; }; +/** + * Event representing task assignment changes + */ export type UserTaskAssignedEvent = { + /** Previous assigned user ID */ oldUserId?: string; + /** Previous assigned group ID */ oldUserGroup?: string; + /** New assigned user ID */ newUserId?: string; + /** New assigned group ID */ newUserGroup?: string; }; +/** + * Event representing task cancellation + */ export type UserTaskCancelledEvent = { + /** Cancellation reason/message */ message: string; }; +/** + * Event representing changes to a task's lifecycle + * Events are discriminated by their structure: + * - UserTaskExecutedEvent: Contains wfRunId and userTaskGuid + * - UserTaskAssignedEvent: Contains old/new user/group IDs + * - UserTaskCancelledEvent: Contains a message + */ export type UserTaskEvent = | UserTaskExecutedEvent | UserTaskAssignedEvent | UserTaskCancelledEvent; +/** + * Structure for task results + */ export type UserTaskResult = { + /** Map of field names to their values and types */ [key: string]: { + /** Field data type */ type: FieldType; + /** Field value */ value: FieldValue; }; };