diff --git a/apps/browser-extension/src/entrypoints/contentScript/Popup.ts b/apps/browser-extension/src/entrypoints/contentScript/Popup.ts index a7e2703f4..4e32f8443 100644 --- a/apps/browser-extension/src/entrypoints/contentScript/Popup.ts +++ b/apps/browser-extension/src/entrypoints/contentScript/Popup.ts @@ -6,7 +6,7 @@ import { DISABLED_SITES_KEY, TEMPORARY_DISABLED_SITES_KEY, GLOBAL_AUTOFILL_POPUP import { AutofillMatchingMode } from '@/utils/credentialMatcher/CredentialMatcher'; import { CreateIdentityGenerator, IdentityHelperUtils } from '@/utils/dist/core/identity-generator'; import type { Item, ItemField } from '@/utils/dist/core/models/vault'; -import { ItemTypes, FieldTypes, FieldKey } from '@/utils/dist/core/models/vault'; +import { ItemTypes, FieldKey, createSystemField } from '@/utils/dist/core/models/vault'; import { CreatePasswordGenerator, PasswordGenerator, PasswordSettings } from '@/utils/dist/core/password-generator'; import { ClickValidator } from '@/utils/security/ClickValidator'; import { ServiceDetectionUtility } from '@/utils/serviceDetection/ServiceDetectionUtility'; @@ -249,64 +249,21 @@ export async function createAutofillPopup(input: HTMLInputElement, items: Item[] if (result.isCustomCredential) { // Create custom item with information provided by user in popup. const faviconBytes = await getFaviconBytes(document); - const fields: ItemField[] = []; - let displayOrder = 0; - - // Add URL field const serviceUrl = getValidServiceUrl(); + + // Build fields using factory - only include non-empty values + const fields: ItemField[] = []; if (serviceUrl) { - fields.push({ - FieldKey: FieldKey.LoginUrl, - Label: 'URL', - FieldType: FieldTypes.URL, - Value: serviceUrl, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); + fields.push(createSystemField(FieldKey.LoginUrl, { value: serviceUrl })); } - - // Add username if provided if (result.customUsername) { - fields.push({ - FieldKey: FieldKey.LoginUsername, - Label: 'Username', - FieldType: FieldTypes.Text, - Value: result.customUsername, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); + fields.push(createSystemField(FieldKey.LoginUsername, { value: result.customUsername })); } - - // Add email if provided if (result.customEmail) { - fields.push({ - FieldKey: FieldKey.LoginEmail, - Label: 'Email', - FieldType: FieldTypes.Email, - Value: result.customEmail, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); + fields.push(createSystemField(FieldKey.LoginEmail, { value: result.customEmail })); } - - // Add password if provided if (result.customPassword) { - fields.push({ - FieldKey: FieldKey.LoginPassword, - Label: 'Password', - FieldType: FieldTypes.Password, - Value: result.customPassword, - IsHidden: true, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: true - }); + fields.push(createSystemField(FieldKey.LoginPassword, { value: result.customPassword })); } newItem = { @@ -340,106 +297,20 @@ export async function createAutofillPopup(input: HTMLInputElement, items: Item[] // Extract favicon from page and get the bytes const faviconBytes = await getFaviconBytes(document); - - // Build fields array for the Alias item type - const fields: ItemField[] = []; - let displayOrder = 0; - - // Add URL field const serviceUrl = getValidServiceUrl(); + + // Build fields using factory - metadata comes from SystemFieldRegistry + const fields: ItemField[] = []; if (serviceUrl) { - fields.push({ - FieldKey: FieldKey.LoginUrl, - Label: 'URL', - FieldType: FieldTypes.URL, - Value: serviceUrl, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); + fields.push(createSystemField(FieldKey.LoginUrl, { value: serviceUrl })); } - - // Add username - fields.push({ - FieldKey: FieldKey.LoginUsername, - Label: 'Username', - FieldType: FieldTypes.Text, - Value: identity.nickName, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); - - // Add email - fields.push({ - FieldKey: FieldKey.LoginEmail, - Label: 'Email', - FieldType: FieldTypes.Email, - Value: `${identity.emailPrefix}@${domain}`, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); - - // Add password - fields.push({ - FieldKey: FieldKey.LoginPassword, - Label: 'Password', - FieldType: FieldTypes.Password, - Value: password, - IsHidden: true, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: true - }); - - // Add alias fields - fields.push({ - FieldKey: FieldKey.AliasFirstName, - Label: 'First Name', - FieldType: FieldTypes.Text, - Value: identity.firstName, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); - - fields.push({ - FieldKey: FieldKey.AliasLastName, - Label: 'Last Name', - FieldType: FieldTypes.Text, - Value: identity.lastName, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); - - fields.push({ - FieldKey: FieldKey.AliasBirthdate, - Label: 'Birth Date', - FieldType: FieldTypes.Date, - Value: IdentityHelperUtils.normalizeBirthDate(identity.birthDate.toISOString()), - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); - - fields.push({ - FieldKey: FieldKey.AliasGender, - Label: 'Gender', - FieldType: FieldTypes.Text, - Value: identity.gender, - IsHidden: false, - DisplayOrder: displayOrder++, - IsCustomField: false, - EnableHistory: false - }); + fields.push(createSystemField(FieldKey.LoginUsername, { value: identity.nickName })); + fields.push(createSystemField(FieldKey.LoginEmail, { value: `${identity.emailPrefix}@${domain}` })); + fields.push(createSystemField(FieldKey.LoginPassword, { value: password })); + fields.push(createSystemField(FieldKey.AliasFirstName, { value: identity.firstName })); + fields.push(createSystemField(FieldKey.AliasLastName, { value: identity.lastName })); + fields.push(createSystemField(FieldKey.AliasBirthdate, { value: IdentityHelperUtils.normalizeBirthDate(identity.birthDate.toISOString()) })); + fields.push(createSystemField(FieldKey.AliasGender, { value: identity.gender })); newItem = { Id: '', diff --git a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx index a62885218..6e78679af 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx @@ -17,7 +17,7 @@ import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate'; import { PASSKEY_DISABLED_SITES_KEY } from '@/utils/Constants'; import { extractDomain, extractRootDomain, filterItems, AutofillMatchingMode } from '@/utils/credentialMatcher/CredentialMatcher'; import type { Item, Passkey } from '@/utils/dist/core/models/vault'; -import { FieldKey, FieldTypes, ItemTypes, getFieldValue } from '@/utils/dist/core/models/vault'; +import { FieldKey, ItemTypes, getFieldValue, createSystemField } from '@/utils/dist/core/models/vault'; import { PasskeyAuthenticator } from '@/utils/passkey/PasskeyAuthenticator'; import { PasskeyHelper } from '@/utils/passkey/PasskeyHelper'; import type { CreateRequest, PasskeyCreateCredentialResponse, PendingPasskeyCreateRequest } from '@/utils/passkey/types'; @@ -325,8 +325,8 @@ const PasskeyCreate: React.FC = () => { Logo: faviconLogo ?? existingItem.Logo, Fields: [ ...(existingItem.Fields || []).filter((f) => f.FieldKey !== FieldKey.LoginUrl && f.FieldKey !== FieldKey.LoginUsername), - { FieldKey: FieldKey.LoginUrl, Label: 'URL', FieldType: FieldTypes.URL, Value: request.origin, IsHidden: false, DisplayOrder: 0, IsCustomField: false, EnableHistory: false }, - { FieldKey: FieldKey.LoginUsername, Label: 'Username', FieldType: FieldTypes.Text, Value: request.publicKey.user.name, IsHidden: false, DisplayOrder: 1, IsCustomField: false, EnableHistory: false } + createSystemField(FieldKey.LoginUrl, { value: request.origin }), + createSystemField(FieldKey.LoginUsername, { value: request.publicKey.user.name }), ] }, [], @@ -398,8 +398,8 @@ const PasskeyCreate: React.FC = () => { ItemType: ItemTypes.Login, Logo: faviconLogo, Fields: [ - { FieldKey: FieldKey.LoginUrl, Label: 'URL', FieldType: FieldTypes.URL, Value: request.origin, IsHidden: false, DisplayOrder: 0, IsCustomField: false, EnableHistory: false }, - { FieldKey: FieldKey.LoginUsername, Label: 'Username', FieldType: FieldTypes.Text, Value: request.publicKey.user.name, IsHidden: false, DisplayOrder: 1, IsCustomField: false, EnableHistory: false } + createSystemField(FieldKey.LoginUrl, { value: request.origin }), + createSystemField(FieldKey.LoginUsername, { value: request.publicKey.user.name }), ], CreatedAt: new Date().toISOString(), UpdatedAt: new Date().toISOString() diff --git a/apps/browser-extension/src/utils/dist/core/models/vault/index.d.ts b/apps/browser-extension/src/utils/dist/core/models/vault/index.d.ts index e5d3247f4..a0e413239 100644 --- a/apps/browser-extension/src/utils/dist/core/models/vault/index.d.ts +++ b/apps/browser-extension/src/utils/dist/core/models/vault/index.d.ts @@ -361,6 +361,53 @@ declare function groupFieldsByCategory(item: Item): Record; * @deprecated Use Item model directly. This is a temporary compatibility layer. */ declare function itemToCredential(item: Item): Credential; +/** + * Options for creating a system field. + * Only `value` is required; metadata is derived from SystemFieldRegistry. + */ +type CreateSystemFieldOptions = { + /** The value for the field (string or string[] for multi-value) */ + value: string | string[]; + /** Override display order (optional, defaults from registry) */ + displayOrder?: number; + /** Override label (optional, normally derived from FieldKey for translation) */ + label?: string; +}; +/** + * Options for creating a custom field. + */ +type CreateCustomFieldOptions = { + /** Unique identifier for the custom field (UUID) */ + fieldKey: string; + /** Display label for the field */ + label: string; + /** The value for the field */ + value: string | string[]; + /** Field type for rendering */ + fieldType?: FieldType; + /** Whether the field is hidden/masked */ + isHidden?: boolean; + /** Display order */ + displayOrder?: number; + /** Whether to track history (defaults to false for custom fields) */ + enableHistory?: boolean; +}; +/** + * Create a system field (ItemField) by FieldKey with metadata derived from SystemFieldRegistry. + * + * @param fieldKey - The system field key (e.g., 'login.username', FieldKey.LoginPassword) + * @param options - Field creation options (value required, displayOrder optional) + * @returns ItemField with proper metadata from SystemFieldRegistry + * @throws Error if fieldKey is not found in SystemFieldRegistry + */ +declare function createSystemField(fieldKey: string, options: CreateSystemFieldOptions): ItemField; +/** + * Create a custom field (ItemField) with sensible defaults. + * + * @param options - Custom field options + * @returns ItemField configured as a custom field + */ +declare function createCustomField(options: CreateCustomFieldOptions): ItemField; /** * Per-item-type configuration for a system field. @@ -499,4 +546,4 @@ type FieldHistory = { */ declare const MAX_FIELD_HISTORY_RECORDS = 10; -export { type Alias, type Attachment, type Credential, type EncryptionKey, FieldCategories, type FieldCategory, type FieldHistory, FieldKey, type FieldKeyValue, type FieldType, FieldTypes, type Item, type ItemField, type ItemTag, type ItemTagRef, type ItemType, type ItemTypeFieldConfig, ItemTypes, MAX_FIELD_HISTORY_RECORDS, type Passkey, type PasswordSettings, type SystemFieldDefinition, SystemFieldRegistry, type Tag, type TotpCode, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; +export { type Alias, type Attachment, type CreateCustomFieldOptions, type CreateSystemFieldOptions, type Credential, type EncryptionKey, FieldCategories, type FieldCategory, type FieldHistory, FieldKey, type FieldKeyValue, type FieldType, FieldTypes, type Item, type ItemField, type ItemTag, type ItemTagRef, type ItemType, type ItemTypeFieldConfig, ItemTypes, MAX_FIELD_HISTORY_RECORDS, type Passkey, type PasswordSettings, type SystemFieldDefinition, SystemFieldRegistry, type Tag, type TotpCode, createCustomField, createSystemField, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; diff --git a/apps/browser-extension/src/utils/dist/core/models/vault/index.js b/apps/browser-extension/src/utils/dist/core/models/vault/index.js index e55e3203c..91369aa2b 100644 --- a/apps/browser-extension/src/utils/dist/core/models/vault/index.js +++ b/apps/browser-extension/src/utils/dist/core/models/vault/index.js @@ -405,10 +405,40 @@ function itemToCredential(item) { HasAttachment: item.HasAttachment }; } +function createSystemField(fieldKey, options) { + const systemField = getSystemField(fieldKey); + if (!systemField) { + throw new Error(`Unknown system field: ${fieldKey}. Use createCustomField for custom fields.`); + } + return { + FieldKey: fieldKey, + Label: options.label ?? fieldKey, + // UI layer translates via fieldLabels.* + FieldType: systemField.FieldType, + Value: options.value, + IsHidden: systemField.IsHidden, + DisplayOrder: options.displayOrder ?? systemField.DefaultDisplayOrder, + IsCustomField: false, + EnableHistory: systemField.EnableHistory + }; +} +function createCustomField(options) { + return { + FieldKey: options.fieldKey, + Label: options.label, + FieldType: options.fieldType ?? FieldTypes.Text, + Value: options.value, + IsHidden: options.isHidden ?? false, + DisplayOrder: options.displayOrder ?? 0, + IsCustomField: true, + EnableHistory: options.enableHistory ?? false + // Custom fields don't track history by default + }; +} // src/vault/FieldHistory.ts var MAX_FIELD_HISTORY_RECORDS = 10; -export { FieldCategories, FieldKey, FieldTypes, ItemTypes, MAX_FIELD_HISTORY_RECORDS, SystemFieldRegistry, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; +export { FieldCategories, FieldKey, FieldTypes, ItemTypes, MAX_FIELD_HISTORY_RECORDS, SystemFieldRegistry, createCustomField, createSystemField, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/apps/mobile-app/utils/dist/core/models/vault/index.d.ts b/apps/mobile-app/utils/dist/core/models/vault/index.d.ts index e5d3247f4..a0e413239 100644 --- a/apps/mobile-app/utils/dist/core/models/vault/index.d.ts +++ b/apps/mobile-app/utils/dist/core/models/vault/index.d.ts @@ -361,6 +361,53 @@ declare function groupFieldsByCategory(item: Item): Record; * @deprecated Use Item model directly. This is a temporary compatibility layer. */ declare function itemToCredential(item: Item): Credential; +/** + * Options for creating a system field. + * Only `value` is required; metadata is derived from SystemFieldRegistry. + */ +type CreateSystemFieldOptions = { + /** The value for the field (string or string[] for multi-value) */ + value: string | string[]; + /** Override display order (optional, defaults from registry) */ + displayOrder?: number; + /** Override label (optional, normally derived from FieldKey for translation) */ + label?: string; +}; +/** + * Options for creating a custom field. + */ +type CreateCustomFieldOptions = { + /** Unique identifier for the custom field (UUID) */ + fieldKey: string; + /** Display label for the field */ + label: string; + /** The value for the field */ + value: string | string[]; + /** Field type for rendering */ + fieldType?: FieldType; + /** Whether the field is hidden/masked */ + isHidden?: boolean; + /** Display order */ + displayOrder?: number; + /** Whether to track history (defaults to false for custom fields) */ + enableHistory?: boolean; +}; +/** + * Create a system field (ItemField) by FieldKey with metadata derived from SystemFieldRegistry. + * + * @param fieldKey - The system field key (e.g., 'login.username', FieldKey.LoginPassword) + * @param options - Field creation options (value required, displayOrder optional) + * @returns ItemField with proper metadata from SystemFieldRegistry + * @throws Error if fieldKey is not found in SystemFieldRegistry + */ +declare function createSystemField(fieldKey: string, options: CreateSystemFieldOptions): ItemField; +/** + * Create a custom field (ItemField) with sensible defaults. + * + * @param options - Custom field options + * @returns ItemField configured as a custom field + */ +declare function createCustomField(options: CreateCustomFieldOptions): ItemField; /** * Per-item-type configuration for a system field. @@ -499,4 +546,4 @@ type FieldHistory = { */ declare const MAX_FIELD_HISTORY_RECORDS = 10; -export { type Alias, type Attachment, type Credential, type EncryptionKey, FieldCategories, type FieldCategory, type FieldHistory, FieldKey, type FieldKeyValue, type FieldType, FieldTypes, type Item, type ItemField, type ItemTag, type ItemTagRef, type ItemType, type ItemTypeFieldConfig, ItemTypes, MAX_FIELD_HISTORY_RECORDS, type Passkey, type PasswordSettings, type SystemFieldDefinition, SystemFieldRegistry, type Tag, type TotpCode, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; +export { type Alias, type Attachment, type CreateCustomFieldOptions, type CreateSystemFieldOptions, type Credential, type EncryptionKey, FieldCategories, type FieldCategory, type FieldHistory, FieldKey, type FieldKeyValue, type FieldType, FieldTypes, type Item, type ItemField, type ItemTag, type ItemTagRef, type ItemType, type ItemTypeFieldConfig, ItemTypes, MAX_FIELD_HISTORY_RECORDS, type Passkey, type PasswordSettings, type SystemFieldDefinition, SystemFieldRegistry, type Tag, type TotpCode, createCustomField, createSystemField, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; diff --git a/apps/mobile-app/utils/dist/core/models/vault/index.js b/apps/mobile-app/utils/dist/core/models/vault/index.js index e55e3203c..91369aa2b 100644 --- a/apps/mobile-app/utils/dist/core/models/vault/index.js +++ b/apps/mobile-app/utils/dist/core/models/vault/index.js @@ -405,10 +405,40 @@ function itemToCredential(item) { HasAttachment: item.HasAttachment }; } +function createSystemField(fieldKey, options) { + const systemField = getSystemField(fieldKey); + if (!systemField) { + throw new Error(`Unknown system field: ${fieldKey}. Use createCustomField for custom fields.`); + } + return { + FieldKey: fieldKey, + Label: options.label ?? fieldKey, + // UI layer translates via fieldLabels.* + FieldType: systemField.FieldType, + Value: options.value, + IsHidden: systemField.IsHidden, + DisplayOrder: options.displayOrder ?? systemField.DefaultDisplayOrder, + IsCustomField: false, + EnableHistory: systemField.EnableHistory + }; +} +function createCustomField(options) { + return { + FieldKey: options.fieldKey, + Label: options.label, + FieldType: options.fieldType ?? FieldTypes.Text, + Value: options.value, + IsHidden: options.isHidden ?? false, + DisplayOrder: options.displayOrder ?? 0, + IsCustomField: true, + EnableHistory: options.enableHistory ?? false + // Custom fields don't track history by default + }; +} // src/vault/FieldHistory.ts var MAX_FIELD_HISTORY_RECORDS = 10; -export { FieldCategories, FieldKey, FieldTypes, ItemTypes, MAX_FIELD_HISTORY_RECORDS, SystemFieldRegistry, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; +export { FieldCategories, FieldKey, FieldTypes, ItemTypes, MAX_FIELD_HISTORY_RECORDS, SystemFieldRegistry, createCustomField, createSystemField, fieldAppliesToType, getAllSystemFieldKeys, getDefaultFieldsForItemType, getFieldConfigForType, getFieldValue, getFieldValues, getOptionalFieldsForItemType, getSystemField, getSystemFieldsForItemType, groupFields, groupFieldsByCategory, hasField, isFieldShownByDefault, isSystemField, isSystemFieldPrefix, itemToCredential }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm b/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm index d86832e86..570ce299c 100644 Binary files a/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm and b/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm differ diff --git a/core/models/src/vault/ItemMethods.ts b/core/models/src/vault/ItemMethods.ts index 9613ec648..24d221c39 100644 --- a/core/models/src/vault/ItemMethods.ts +++ b/core/models/src/vault/ItemMethods.ts @@ -1,7 +1,8 @@ -import type { Item, ItemField } from './Item'; +import type { Item, ItemField, FieldType } from './Item'; +import { FieldTypes } from './Item'; import type { Credential } from './Credential'; import { FieldKey } from './FieldKey'; -import { FieldCategories } from './SystemFieldRegistry'; +import { FieldCategories, getSystemField } from './SystemFieldRegistry'; /** * Helper functions for working with Item model @@ -105,3 +106,81 @@ export function itemToCredential(item: Item): Credential { HasAttachment: item.HasAttachment }; } + +/** + * Options for creating a system field. + * Only `value` is required; metadata is derived from SystemFieldRegistry. + */ +export type CreateSystemFieldOptions = { + /** The value for the field (string or string[] for multi-value) */ + value: string | string[]; + /** Override display order (optional, defaults from registry) */ + displayOrder?: number; + /** Override label (optional, normally derived from FieldKey for translation) */ + label?: string; +}; + +/** + * Options for creating a custom field. + */ +export type CreateCustomFieldOptions = { + /** Unique identifier for the custom field (UUID) */ + fieldKey: string; + /** Display label for the field */ + label: string; + /** The value for the field */ + value: string | string[]; + /** Field type for rendering */ + fieldType?: FieldType; + /** Whether the field is hidden/masked */ + isHidden?: boolean; + /** Display order */ + displayOrder?: number; + /** Whether to track history (defaults to false for custom fields) */ + enableHistory?: boolean; +}; + +/** + * Create a system field (ItemField) by FieldKey with metadata derived from SystemFieldRegistry. + * + * @param fieldKey - The system field key (e.g., 'login.username', FieldKey.LoginPassword) + * @param options - Field creation options (value required, displayOrder optional) + * @returns ItemField with proper metadata from SystemFieldRegistry + * @throws Error if fieldKey is not found in SystemFieldRegistry + */ +export function createSystemField(fieldKey: string, options: CreateSystemFieldOptions): ItemField { + const systemField = getSystemField(fieldKey); + if (!systemField) { + throw new Error(`Unknown system field: ${fieldKey}. Use createCustomField for custom fields.`); + } + + return { + FieldKey: fieldKey, + Label: options.label ?? fieldKey, // UI layer translates via fieldLabels.* + FieldType: systemField.FieldType, + Value: options.value, + IsHidden: systemField.IsHidden, + DisplayOrder: options.displayOrder ?? systemField.DefaultDisplayOrder, + IsCustomField: false, + EnableHistory: systemField.EnableHistory, + }; +} + +/** + * Create a custom field (ItemField) with sensible defaults. + * + * @param options - Custom field options + * @returns ItemField configured as a custom field + */ +export function createCustomField(options: CreateCustomFieldOptions): ItemField { + return { + FieldKey: options.fieldKey, + Label: options.label, + FieldType: options.fieldType ?? FieldTypes.Text, + Value: options.value, + IsHidden: options.isHidden ?? false, + DisplayOrder: options.displayOrder ?? 0, + IsCustomField: true, + EnableHistory: options.enableHistory ?? false, // Custom fields don't track history by default + }; +}