diff --git a/package-lock.json b/package-lock.json index 8be4b29f9a9..b70b6efe2ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46716,7 +46716,8 @@ "bson": "^6.2.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "yargs-parser": "^21.1.1" + "yargs-parser": "^21.1.1", + "zod": "^3.22.3" }, "devDependencies": { "@mongodb-js/compass-user-data": "^0.1.14", @@ -71883,7 +71884,8 @@ "mocha": "^10.2.0", "react": "^17.0.2", "sinon": "^9.2.3", - "yargs-parser": "^21.1.1" + "yargs-parser": "^21.1.1", + "zod": "^3.22.3" }, "dependencies": { "argparse": { diff --git a/packages/compass-preferences-model/package.json b/packages/compass-preferences-model/package.json index 2ba9f2a1abc..03860260338 100644 --- a/packages/compass-preferences-model/package.json +++ b/packages/compass-preferences-model/package.json @@ -48,12 +48,13 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-user-data": "^0.1.14", "@mongodb-js/compass-logging": "^1.2.11", + "@mongodb-js/compass-user-data": "^0.1.14", "bson": "^6.2.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "yargs-parser": "^21.1.1" + "yargs-parser": "^21.1.1", + "zod": "^3.22.3" }, "devDependencies": { "@mongodb-js/compass-user-data": "^0.1.14", diff --git a/packages/compass-preferences-model/src/global-config.ts b/packages/compass-preferences-model/src/global-config.ts index 30d287c2f72..94362d8a612 100644 --- a/packages/compass-preferences-model/src/global-config.ts +++ b/packages/compass-preferences-model/src/global-config.ts @@ -5,9 +5,9 @@ import yaml from 'js-yaml'; import type { Options as YargsOptions } from 'yargs-parser'; import yargsParser from 'yargs-parser'; import { kebabCase } from 'lodash'; -import type { AllPreferences } from './preferences'; -import { allPreferencesProps } from './preferences'; -import type { z } from '@mongodb-js/compass-user-data'; +import type { AllPreferences } from './preferences-schema'; +import { allPreferencesProps } from './preferences-schema'; +import type { z } from 'zod'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; const { log, mongoLogId } = createLoggerAndTelemetry('COMPASS-PREFERENCES'); diff --git a/packages/compass-preferences-model/src/index.ts b/packages/compass-preferences-model/src/index.ts index 281366d744a..1e1644242b9 100644 --- a/packages/compass-preferences-model/src/index.ts +++ b/packages/compass-preferences-model/src/index.ts @@ -1,5 +1,5 @@ -export type { THEMES } from './preferences'; -export { getSettingDescription } from './preferences'; +export type { THEMES } from './preferences-schema'; +export { getSettingDescription } from './preferences-schema'; export { featureFlags } from './feature-flags'; import type { @@ -7,8 +7,8 @@ import type { UserConfigurablePreferences, PreferenceStateInformation, AllPreferences, - Preferences, -} from './preferences'; +} from './preferences-schema'; +import type { Preferences } from './preferences'; export type { UserPreferences, UserConfigurablePreferences, diff --git a/packages/compass-preferences-model/src/preferences-schema.ts b/packages/compass-preferences-model/src/preferences-schema.ts new file mode 100644 index 00000000000..7318adf22bc --- /dev/null +++ b/packages/compass-preferences-model/src/preferences-schema.ts @@ -0,0 +1,877 @@ +import { z } from 'zod'; +import { + type FeatureFlagDefinition, + type FeatureFlags, + featureFlags, +} from './feature-flags'; +import { parseRecord } from './parse-record'; + +export const THEMES_VALUES = ['DARK', 'LIGHT', 'OS_THEME'] as const; +export type THEMES = typeof THEMES_VALUES[number]; + +export type PermanentFeatureFlags = { + showDevFeatureFlags?: boolean; + enableDebugUseCsfleSchemaMap?: boolean; +}; + +type AllFeatureFlags = PermanentFeatureFlags & FeatureFlags; + +export type UserConfigurablePreferences = PermanentFeatureFlags & + FeatureFlags & { + // User-facing preferences + autoUpdates: boolean; + enableGenAIFeatures: boolean; + enableMaps: boolean; + trackUsageStatistics: boolean; + enableFeedbackPanel: boolean; + networkTraffic: boolean; + readOnly: boolean; + enableShell: boolean; + protectConnectionStrings?: boolean; + forceConnectionOptions?: [key: string, value: string][]; + showKerberosPasswordField: boolean; + showOIDCDeviceAuthFlow: boolean; + browserCommandForOIDCAuth?: string; + persistOIDCTokens?: boolean; + enableDevTools: boolean; + theme: THEMES; + maxTimeMS?: number; + installURLHandlers: boolean; + protectConnectionStringsForNewConnections: boolean; + // This preference is not a great fit for user preferences, but everything + // except for user preferences doesn't allow required preferences to be + // defined, so we are sticking it here + atlasServiceBackendPreset: + | 'compass-dev' + | 'compass' + | 'atlas-local' + | 'atlas-dev' + | 'atlas'; + // Features that are enabled by default in Compass, but are disabled in Data + // Explorer + enableExplainPlan: boolean; + enableImportExport: boolean; + enableAggregationBuilderRunPipeline: boolean; + enableAggregationBuilderExtraOptions: boolean; + enableSavedAggregationsQueries: boolean; + }; + +export type InternalUserPreferences = { + // These are internally used preferences that are not configurable + // by users. + showedNetworkOptIn: boolean; // Has the settings dialog been shown before. + id: string; + cloudFeatureRolloutAccess?: { + GEN_AI_COMPASS?: boolean; + }; + lastKnownVersion: string; + currentUserId?: string; + telemetryAnonymousId?: string; +}; + +// UserPreferences contains all preferences stored to disk. +export type UserPreferences = UserConfigurablePreferences & + InternalUserPreferences; + +export type CliOnlyPreferences = { + exportConnections?: string; + importConnections?: string; + passphrase?: string; + version?: boolean; + help?: boolean; + showExampleConfig?: boolean; +}; + +export type NonUserPreferences = { + ignoreAdditionalCommandLineFlags?: boolean; + positionalArguments?: string[]; + file?: string; + username?: string; + password?: string; +}; + +export type AllPreferences = UserPreferences & + CliOnlyPreferences & + NonUserPreferences & + PermanentFeatureFlags; + +// Types related to PreferenceDefinition +type PostProcessFunction = ( + input: unknown, + error: (message: string) => void +) => T; + +type PreferenceType = T extends string + ? 'string' + : T extends boolean + ? 'boolean' + : T extends number + ? 'number' + : T extends unknown[] + ? 'array' + : T extends Date + ? 'date' + : T extends object + ? 'object' + : never; + +/* Identifies a source from which the preference was set */ +export type PreferenceState = + | 'set-cli' // Can be set directly or derived from a preference set via cli args. + | 'set-global' // Can be set directly or derived from a preference set via global config. + | 'hardcoded' + | 'derived' // Derived from a preference set by a user via setting UI. + | undefined; + +export type DeriveValueFunction = ( + /** Get a preference's value from the current set of preferences */ + getValue: (key: K) => AllPreferences[K], + /** Get a preference's state from the current set of preferences */ + getState: (key: K) => PreferenceState +) => { value: T; state: PreferenceState }; + +type PreferenceDefinition = { + /** Whether the preference can be modified through the Settings UI */ + ui: K extends keyof UserConfigurablePreferences ? true : false; + /** Whether the preference can be set on the command line */ + cli: K extends keyof Omit + ? false + : K extends keyof CliOnlyPreferences + ? true + : boolean; + /** Whether the preference can be set in the global config file */ + global: K extends keyof InternalUserPreferences + ? false + : K extends keyof CliOnlyPreferences + ? false + : boolean; + /** A description used for the --help text and the Settings UI */ + description: K extends keyof InternalUserPreferences + ? null + : { short: string; long?: string }; + /** A method for deriving the current semantic value of this option, even if it differs from the stored value */ + deriveValue?: DeriveValueFunction; + /** A method for cleaning up/normalizing input from the command line or global config file */ + customPostProcess?: PostProcessFunction; + /** Specify that this option should not be listed in --help output */ + omitFromHelp?: K extends keyof (UserConfigurablePreferences & + CliOnlyPreferences) + ? K extends keyof AllFeatureFlags + ? boolean + : false + : boolean; + validator: z.Schema< + AllPreferences[K], + z.ZodTypeDef, + AllPreferences[K] | undefined + >; + type: PreferenceType; +}; + +export type PreferenceStateInformation = Partial< + Record +>; + +// Preference definitions +const featureFlagsProps: Required<{ + [K in keyof FeatureFlags]: PreferenceDefinition; +}> = Object.fromEntries( + Object.entries(featureFlags).map(([key, value]) => [ + key as keyof FeatureFlags, + featureFlagToPreferenceDefinition(value), + ]) +) as unknown as Required<{ + [K in keyof FeatureFlags]: PreferenceDefinition; +}>; + +const allFeatureFlagsProps: Required<{ + [K in keyof AllFeatureFlags]: PreferenceDefinition; +}> = { + /** Meta-feature-flag! Whether to show the dev flags of the feature flag settings modal */ + showDevFeatureFlags: { + ui: true, + cli: true, + global: true, + omitFromHelp: true, + description: { + short: 'Show Developer Feature Flags', + }, + validator: z + .boolean() + .optional() + .default(process.env.HADRON_CHANNEL === 'dev'), + type: 'boolean', + }, + + /** + * Permanent feature flag for debugging. + * We want to encourage user to use Queryable Encryption, not CSFLE, so we do not + * officially support the CSFLE schemaMap property. + */ + enableDebugUseCsfleSchemaMap: { + ui: true, + cli: true, + global: true, + description: { + short: 'CSFLE Schema Map Debugging', + }, + validator: z.boolean().optional(), + type: 'boolean', + }, + + ...featureFlagsProps, +}; + +export const storedUserPreferencesProps: Required<{ + [K in keyof UserPreferences]: PreferenceDefinition; +}> = { + /** + * String identifier for this set of preferences. Default is `General`. + */ + id: { + ui: false, + cli: false, + global: false, + description: null, + validator: z.string().default('General'), + type: 'string', + }, + /** + * Stores the last version compass was run as, e.g. `1.0.5`. + */ + lastKnownVersion: { + ui: false, + cli: false, + global: false, + description: null, + validator: z.string().default('0.0.0'), + type: 'string', + }, + /** + * Stores whether or not the network opt-in screen has been shown to + * the user already. + */ + showedNetworkOptIn: { + ui: false, + cli: true, + global: false, + description: null, + omitFromHelp: true, + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Stores the theme preference for the user. + */ + theme: { + ui: true, + cli: true, + global: true, + description: { + short: 'Compass UI Theme', + }, + validator: z + .effect(z.enum(THEMES_VALUES), { + type: 'preprocess', + transform: (val) => + typeof val !== 'string' ? val : (val || 'light').toUpperCase(), + }) + .optional() + .default('LIGHT'), + type: 'string', + }, + /** + * Stores a unique MongoDB ID for the current user. + * Initially, we used this field as telemetry user identifier, + * but this usage is being deprecated. + * The telemetryAnonymousId should be used instead. + */ + currentUserId: { + ui: false, + cli: false, + global: false, + description: null, + validator: z.string().optional(), + type: 'string', + }, + /** + * Stores a unique telemetry anonymous ID (uuid) for the current user. + */ + telemetryAnonymousId: { + ui: false, + cli: false, + global: false, + description: null, + validator: z.string().uuid().optional(), + type: 'string', + }, + /** + * Enable/disable the AI services. This is currently set + * in the atlas-service initialization where we make a request to the + * ai endpoint to check what's enabled for the user (incremental rollout). + */ + cloudFeatureRolloutAccess: { + ui: false, + cli: false, + global: false, + description: null, + validator: z + .object({ + GEN_AI_COMPASS: z.boolean().optional(), + }) + .optional(), + type: 'object', + }, + /** + * Master switch to disable all network traffic + * and make Compass behave like Isolated edition always, + * i.e. no network traffic other than the one to the db server + * (which includes maps, telemetry, auto-updates). + */ + networkTraffic: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable network traffic other than to the MongoDB database', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + /** + * Removes features that write to the database from the UI. + */ + readOnly: { + ui: true, + cli: true, + global: true, + description: { + short: 'Set Read-Only Mode', + long: 'Limit Compass strictly to read operations, with all write and delete capabilities removed.', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to enable/disable the embedded shell. + */ + enableShell: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable MongoDB Shell', + long: 'Allow Compass to interact with MongoDB deployments via the embedded shell.', + }, + deriveValue: deriveReadOnlyOptionState('enableShell'), + validator: z.boolean().default(true), + type: 'boolean', + }, + /** + * Switch to enable/disable maps rendering. + */ + enableMaps: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Geographic Visualizations', + long: 'Allow Compass to make requests to a 3rd party mapping service.', + }, + deriveValue: deriveNetworkTrafficOptionState('enableMaps'), + validator: z.boolean().default(false), + type: 'boolean', + }, + enableGenAIFeatures: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable AI Features', + long: 'Allow the use of AI features in Compass which make requests to 3rd party services. These features are currently experimental and offered as a preview to only a limited number of users.', + }, + deriveValue: deriveNetworkTrafficOptionState('enableGenAIFeatures'), + validator: z.boolean().default(true), + type: 'boolean', + }, + /** + * Switch to enable/disable Intercom panel (renamed from `intercom`). + */ + enableFeedbackPanel: { + ui: true, + cli: true, + global: true, + description: { + short: 'Give Product Feedback', + long: 'Enables a tool that our Product team can use to occasionally reach out for feedback about Compass.', + }, + deriveValue: deriveNetworkTrafficOptionState('enableFeedbackPanel'), + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to enable/disable usage statistics collection + * (renamed from `googleAnalytics`). + */ + trackUsageStatistics: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Usage Statistics', + long: 'Allow Compass to send anonymous usage statistics.', + }, + deriveValue: deriveNetworkTrafficOptionState('trackUsageStatistics'), + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to enable/disable automatic updates. + */ + autoUpdates: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Automatic Updates', + long: 'Allow Compass to periodically check for new updates.', + }, + deriveValue: deriveNetworkTrafficOptionState('autoUpdates'), + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to hide credentials in connection strings from users. + */ + protectConnectionStrings: { + ui: true, + cli: true, + global: true, + description: { + short: 'Protect Connection String Secrets', + long: 'Hide credentials in connection strings from users.', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to enable DevTools in Electron. + */ + enableDevTools: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable DevTools', + long: `Enable the Chromium Developer Tools that can be used to debug Electron's process.`, + }, + deriveValue: deriveFeatureRestrictingOptionsState('enableDevTools'), + validator: z.boolean().default(process.env.APP_ENV === 'webdriverio'), + type: 'boolean', + }, + /** + * Switch to show the Kerberos password field in the connection form. + */ + showKerberosPasswordField: { + ui: true, + cli: true, + global: true, + description: { + short: 'Show Kerberos Password Field', + long: 'Show a password field for Kerberos authentication. Typically only useful when attempting to authenticate as another user than the current system user.', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Switch to show the OIDC device auth flow option in the connection form. + */ + showOIDCDeviceAuthFlow: { + ui: true, + cli: true, + global: true, + description: { + short: 'Show Device Auth Flow Checkbox', + long: 'Show a checkbox on the connection form to enable device auth flow authentication for MongoDB server OIDC Authentication. This enables a less secure authentication flow that can be used as a fallback when browser-based authentication is unavailable.', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + /** + * Input to change the browser command used for OIDC authentication. + */ + browserCommandForOIDCAuth: { + ui: true, + cli: true, + global: true, + description: { + short: 'Browser command to use for authentication', + long: 'Specify a shell command that is run to start the browser for authenticating with the OIDC identity provider for the server connection or when logging in to your Atlas Cloud account. Leave this empty for default browser.', + }, + validator: z.string().optional(), + type: 'string', + }, + /** + * Input to change the browser command used for OIDC authentication. + */ + persistOIDCTokens: { + ui: true, + cli: true, + global: true, + description: { + short: 'Stay logged in with OIDC', + long: 'Remain logged in when using the MONGODB-OIDC authentication mechanism for MongoDB server connection. Access tokens are encrypted using the system keychain before being stored.', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + /** + * Override certain connection string properties. + */ + forceConnectionOptions: { + ui: true, + cli: true, + global: true, + description: { + short: 'Override Connection String Properties', + long: 'Force connection string properties to take specific values', + }, + customPostProcess: parseRecord, + validator: z.array(z.tuple([z.string(), z.string()])).optional(), + type: 'array', + }, + /** + * Set an upper limit for maxTimeMS for operations started by Compass. + */ + maxTimeMS: { + ui: true, + cli: true, + global: true, + description: { + short: 'Upper Limit for maxTimeMS for Compass Database Operations', + }, + validator: z.number().optional(), + type: 'number', + }, + /** + * Do not handle mongodb:// and mongodb+srv:// URLs via Compass + */ + installURLHandlers: { + ui: true, + cli: true, + global: true, + description: { + short: 'Install Compass as URL Protocol Handler', + long: 'Register Compass as a handler for mongodb:// and mongodb+srv:// URLs', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + /** + * Determines if the toggle to edit connection string for new connections + * should be in the off state or in the on state by default + */ + protectConnectionStringsForNewConnections: { + ui: true, + cli: true, + global: true, + description: { + short: + 'If true, "Edit connection string" is disabled for new connections by default', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + + /** + * Chooses atlas service backend configuration from preset + * - compass-dev: locally running compass kanopy backend (localhost) + * - compass: compass kanopy backend (compass.mongodb.com) + * - atlas-local: local mms backend (http://localhost:8080) + * - atlas-dev: dev mms backend (cloud-dev.mongodb.com) + * - atlas: mms backend (cloud.mongodb.com) + */ + atlasServiceBackendPreset: { + ui: true, + cli: true, + global: true, + description: { + short: 'Configuration used by atlas service', + }, + validator: z + .enum(['compass-dev', 'compass', 'atlas-dev', 'atlas-local', 'atlas']) + .default('atlas'), + type: 'string', + }, + + enableImportExport: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable import / export feature', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + + enableAggregationBuilderRunPipeline: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable "Run Pipeline" feature in aggregation builder', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + + enableExplainPlan: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable explain plan feature in CRUD and aggregation view', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + + enableAggregationBuilderExtraOptions: { + ui: true, + cli: true, + global: true, + description: { + short: + 'Enable preview input limit and collation options in aggregation view', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + + enableSavedAggregationsQueries: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable saving and opening saved aggregations and queries', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + + ...allFeatureFlagsProps, +}; + +const cliOnlyPreferencesProps: Required<{ + [K in keyof CliOnlyPreferences]: PreferenceDefinition; +}> = { + exportConnections: { + ui: false, + cli: true, + global: false, + description: { + short: 'Export Favorite Connections', + long: 'Export Compass favorite connections. Can be used with --passphrase.', + }, + validator: z.string().optional(), + type: 'string', + }, + importConnections: { + ui: false, + cli: true, + global: false, + description: { + short: 'Import Favorite Connections', + long: 'Import Compass favorite connections. Can be used with --passphrase.', + }, + validator: z.string().optional(), + type: 'string', + }, + passphrase: { + ui: false, + cli: true, + global: false, + description: { + short: 'Connection Export/Import Passphrase', + long: 'Specify a passphrase for encrypting/decrypting secrets.', + }, + validator: z.string().optional(), + type: 'string', + }, + help: { + ui: false, + cli: true, + global: false, + description: { + short: 'Show Compass Options', + }, + validator: z.boolean().optional(), + type: 'boolean', + }, + version: { + ui: false, + cli: true, + global: false, + description: { + short: 'Show Compass Version', + }, + validator: z.boolean().optional(), + type: 'boolean', + }, + showExampleConfig: { + ui: false, + cli: true, + global: false, + description: { + short: 'Show Example Config File', + }, + validator: z.boolean().optional(), + type: 'boolean', + }, +}; + +const nonUserPreferences: Required<{ + [K in keyof NonUserPreferences]: PreferenceDefinition; +}> = { + ignoreAdditionalCommandLineFlags: { + ui: false, + cli: true, + global: true, + description: { + short: 'Allow Additional CLI Flags', + long: 'Allow specifying command-line flags that Compass does not understand, e.g. Electron or Chromium flags', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + positionalArguments: { + ui: false, + cli: true, + global: false, + description: { + short: + 'Specify a Connection String or Connection ID to Automatically Connect', + }, + omitFromHelp: true, + validator: z.array(z.string()).optional(), + type: 'array', + }, + file: { + ui: false, + cli: true, + global: true, + description: { + short: 'Specify a List of Connections for Automatically Connecting', + }, + validator: z.string().optional(), + type: 'string', + }, + username: { + ui: false, + cli: true, + global: true, + description: { + short: 'Specify a Username for Automatically Connecting', + }, + validator: z.string().optional(), + type: 'string', + }, + password: { + ui: false, + cli: true, + global: true, + description: { + short: 'Specify a Password for Automatically Connecting', + }, + validator: z.string().optional(), + type: 'string', + }, +}; + +export const allPreferencesProps: Required<{ + [K in keyof AllPreferences]: PreferenceDefinition; +}> = { + ...storedUserPreferencesProps, + ...cliOnlyPreferencesProps, + ...nonUserPreferences, +}; + +/** Helper for defining how to derive value/state for networkTraffic-affected preferences */ +function deriveNetworkTrafficOptionState( + property: K +): DeriveValueFunction { + return (v, s) => ({ + value: v(property) && v('networkTraffic'), + state: + s(property) ?? + (v('networkTraffic') ? undefined : s('networkTraffic') ?? 'derived'), + }); +} + +/** Helper for defining how to derive value/state for feature-restricting preferences */ +function deriveFeatureRestrictingOptionsState( + property: K +): DeriveValueFunction { + return (v, s) => ({ + value: + v(property) && + v('enableShell') && + !v('maxTimeMS') && + !v('protectConnectionStrings') && + !v('readOnly'), + state: + s(property) ?? + (v('protectConnectionStrings') + ? s('protectConnectionStrings') ?? 'derived' + : undefined) ?? + (v('readOnly') ? s('readOnly') ?? 'derived' : undefined) ?? + (v('enableShell') ? undefined : s('enableShell') ?? 'derived') ?? + (v('maxTimeMS') ? s('maxTimeMS') ?? 'derived' : undefined), + }); +} + +/** Helper for defining how to derive value/state for readOnly-affected preferences */ +function deriveReadOnlyOptionState( + property: K +): DeriveValueFunction { + return (v, s) => ({ + value: v(property) && !v('readOnly'), + state: + s(property) ?? (v('readOnly') ? s('readOnly') ?? 'derived' : undefined), + }); +} + +// Helper to convert feature flag definitions to preference definitions +function featureFlagToPreferenceDefinition( + featureFlag: FeatureFlagDefinition +): PreferenceDefinition { + return { + cli: true, + global: true, + ui: true, + description: featureFlag.description, + // if a feature flag is 'released' it will always return true + // regardless of any persisted value. + deriveValue: + featureFlag.stage === 'released' + ? () => ({ value: true, state: 'hardcoded' }) + : undefined, + validator: z.boolean().default(false), + type: 'boolean', + }; +} + +export function getSettingDescription< + Name extends Exclude +>( + name: Name +): Pick, 'description'> & { type: unknown } { + const { description, type } = allPreferencesProps[ + name + ] as PreferenceDefinition; + return { + description, + type, + }; +} diff --git a/packages/compass-preferences-model/src/preferences.ts b/packages/compass-preferences-model/src/preferences.ts index 1eea8cc0b03..c06bf5a963d 100644 --- a/packages/compass-preferences-model/src/preferences.ts +++ b/packages/compass-preferences-model/src/preferences.ts @@ -5,884 +5,23 @@ import { type BasePreferencesStorage, } from './storage'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; -import { parseRecord } from './parse-record'; -import type { FeatureFlagDefinition, FeatureFlags } from './feature-flags'; -import { featureFlags } from './feature-flags'; -import { z } from '@mongodb-js/compass-user-data'; +import { z } from 'zod'; +import { + type AllPreferences, + type PreferenceState, + type UserPreferences, + type DeriveValueFunction, + type UserConfigurablePreferences, + type PreferenceStateInformation, + allPreferencesProps, +} from './preferences-schema'; const { log, mongoLogId } = createLoggerAndTelemetry('COMPASS-PREFERENCES'); -export const THEMES_VALUES = ['DARK', 'LIGHT', 'OS_THEME'] as const; -export type THEMES = typeof THEMES_VALUES[number]; - -export type PermanentFeatureFlags = { - showDevFeatureFlags?: boolean; - enableDebugUseCsfleSchemaMap?: boolean; -}; - -type AllFeatureFlags = PermanentFeatureFlags & FeatureFlags; - -export type UserConfigurablePreferences = PermanentFeatureFlags & - FeatureFlags & { - // User-facing preferences - autoUpdates: boolean; - enableGenAIFeatures: boolean; - enableMaps: boolean; - trackUsageStatistics: boolean; - enableFeedbackPanel: boolean; - networkTraffic: boolean; - readOnly: boolean; - enableShell: boolean; - protectConnectionStrings?: boolean; - forceConnectionOptions?: [key: string, value: string][]; - showKerberosPasswordField: boolean; - showOIDCDeviceAuthFlow: boolean; - browserCommandForOIDCAuth?: string; - persistOIDCTokens?: boolean; - enableDevTools: boolean; - theme: THEMES; - maxTimeMS?: number; - installURLHandlers: boolean; - protectConnectionStringsForNewConnections: boolean; - // This preference is not a great fit for user preferences, but everything - // except for user preferences doesn't allow required preferences to be - // defined, so we are sticking it here - atlasServiceBackendPreset: - | 'compass-dev' - | 'compass' - | 'atlas-local' - | 'atlas-dev' - | 'atlas'; - // Features that are enabled by default in Compass, but are disabled in Data - // Explorer - enableExplainPlan: boolean; - enableImportExport: boolean; - enableAggregationBuilderRunPipeline: boolean; - enableAggregationBuilderExtraOptions: boolean; - enableSavedAggregationsQueries: boolean; - }; - -export type InternalUserPreferences = { - // These are internally used preferences that are not configurable - // by users. - showedNetworkOptIn: boolean; // Has the settings dialog been shown before. - id: string; - cloudFeatureRolloutAccess?: { - GEN_AI_COMPASS?: boolean; - }; - lastKnownVersion: string; - currentUserId?: string; - telemetryAnonymousId?: string; -}; - -// UserPreferences contains all preferences stored to disk. -export type UserPreferences = UserConfigurablePreferences & - InternalUserPreferences; - -export type CliOnlyPreferences = { - exportConnections?: string; - importConnections?: string; - passphrase?: string; - version?: boolean; - help?: boolean; - showExampleConfig?: boolean; -}; - -export type NonUserPreferences = { - ignoreAdditionalCommandLineFlags?: boolean; - positionalArguments?: string[]; - file?: string; - username?: string; - password?: string; -}; - -export type AllPreferences = UserPreferences & - CliOnlyPreferences & - NonUserPreferences & - PermanentFeatureFlags; - type OnPreferencesChangedCallback = ( changedPreferencesValues: Partial ) => void; -type PostProcessFunction = ( - input: unknown, - error: (message: string) => void -) => T; - -type PreferenceType = T extends string - ? 'string' - : T extends boolean - ? 'boolean' - : T extends number - ? 'number' - : T extends unknown[] - ? 'array' - : T extends Date - ? 'date' - : T extends object - ? 'object' - : never; - -type PreferenceDefinition = { - /** Whether the preference can be modified through the Settings UI */ - ui: K extends keyof UserConfigurablePreferences ? true : false; - /** Whether the preference can be set on the command line */ - cli: K extends keyof Omit - ? false - : K extends keyof CliOnlyPreferences - ? true - : boolean; - /** Whether the preference can be set in the global config file */ - global: K extends keyof InternalUserPreferences - ? false - : K extends keyof CliOnlyPreferences - ? false - : boolean; - /** A description used for the --help text and the Settings UI */ - description: K extends keyof InternalUserPreferences - ? null - : { short: string; long?: string }; - /** A method for deriving the current semantic value of this option, even if it differs from the stored value */ - deriveValue?: DeriveValueFunction; - /** A method for cleaning up/normalizing input from the command line or global config file */ - customPostProcess?: PostProcessFunction; - /** Specify that this option should not be listed in --help output */ - omitFromHelp?: K extends keyof (UserConfigurablePreferences & - CliOnlyPreferences) - ? K extends keyof AllFeatureFlags - ? boolean - : false - : boolean; - validator: z.Schema< - AllPreferences[K], - z.ZodTypeDef, - AllPreferences[K] | undefined - >; - type: PreferenceType; -}; - -type DeriveValueFunction = ( - /** Get a preference's value from the current set of preferences */ - getValue: (key: K) => AllPreferences[K], - /** Get a preference's state from the current set of preferences */ - getState: (key: K) => PreferenceState -) => { value: T; state: PreferenceState }; - -/** Helper for defining how to derive value/state for networkTraffic-affected preferences */ -function deriveNetworkTrafficOptionState( - property: K -): DeriveValueFunction { - return (v, s) => ({ - value: v(property) && v('networkTraffic'), - state: - s(property) ?? - (v('networkTraffic') ? undefined : s('networkTraffic') ?? 'derived'), - }); -} - -/** Helper for defining how to derive value/state for feature-restricting preferences */ -function deriveFeatureRestrictingOptionsState( - property: K -): DeriveValueFunction { - return (v, s) => ({ - value: - v(property) && - v('enableShell') && - !v('maxTimeMS') && - !v('protectConnectionStrings') && - !v('readOnly'), - state: - s(property) ?? - (v('protectConnectionStrings') - ? s('protectConnectionStrings') ?? 'derived' - : undefined) ?? - (v('readOnly') ? s('readOnly') ?? 'derived' : undefined) ?? - (v('enableShell') ? undefined : s('enableShell') ?? 'derived') ?? - (v('maxTimeMS') ? s('maxTimeMS') ?? 'derived' : undefined), - }); -} - -/** Helper for defining how to derive value/state for readOnly-affected preferences */ -function deriveReadOnlyOptionState( - property: K -): DeriveValueFunction { - return (v, s) => ({ - value: v(property) && !v('readOnly'), - state: - s(property) ?? (v('readOnly') ? s('readOnly') ?? 'derived' : undefined), - }); -} - -function featureFlagToPreferenceDefinition( - featureFlag: FeatureFlagDefinition -): PreferenceDefinition { - return { - cli: true, - global: true, - ui: true, - description: featureFlag.description, - // if a feature flag is 'released' it will always return true - // regardless of any persisted value. - deriveValue: - featureFlag.stage === 'released' - ? () => ({ value: true, state: 'hardcoded' }) - : undefined, - validator: z.boolean().default(false), - type: 'boolean', - }; -} - -const featureFlagsProps: Required<{ - [K in keyof FeatureFlags]: PreferenceDefinition; -}> = Object.fromEntries( - Object.entries(featureFlags).map(([key, value]) => [ - key as keyof FeatureFlags, - featureFlagToPreferenceDefinition(value), - ]) -) as unknown as Required<{ - [K in keyof FeatureFlags]: PreferenceDefinition; -}>; - -const allFeatureFlagsProps: Required<{ - [K in keyof AllFeatureFlags]: PreferenceDefinition; -}> = { - /** Meta-feature-flag! Whether to show the dev flags of the feature flag settings modal */ - showDevFeatureFlags: { - ui: true, - cli: true, - global: true, - omitFromHelp: true, - description: { - short: 'Show Developer Feature Flags', - }, - validator: z - .boolean() - .optional() - .default(process.env.HADRON_CHANNEL === 'dev'), - type: 'boolean', - }, - - /** - * Permanent feature flag for debugging. - * We want to encourage user to use Queryable Encryption, not CSFLE, so we do not - * officially support the CSFLE schemaMap property. - */ - enableDebugUseCsfleSchemaMap: { - ui: true, - cli: true, - global: true, - description: { - short: 'CSFLE Schema Map Debugging', - }, - validator: z.boolean().optional(), - type: 'boolean', - }, - - ...featureFlagsProps, -}; - -export const storedUserPreferencesProps: Required<{ - [K in keyof UserPreferences]: PreferenceDefinition; -}> = { - /** - * String identifier for this set of preferences. Default is `General`. - */ - id: { - ui: false, - cli: false, - global: false, - description: null, - validator: z.string().default('General'), - type: 'string', - }, - /** - * Stores the last version compass was run as, e.g. `1.0.5`. - */ - lastKnownVersion: { - ui: false, - cli: false, - global: false, - description: null, - validator: z.string().default('0.0.0'), - type: 'string', - }, - /** - * Stores whether or not the network opt-in screen has been shown to - * the user already. - */ - showedNetworkOptIn: { - ui: false, - cli: true, - global: false, - description: null, - omitFromHelp: true, - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Stores the theme preference for the user. - */ - theme: { - ui: true, - cli: true, - global: true, - description: { - short: 'Compass UI Theme', - }, - validator: z - .effect(z.enum(THEMES_VALUES), { - type: 'preprocess', - transform: (val) => - typeof val !== 'string' ? val : (val || 'light').toUpperCase(), - }) - .optional() - .default('LIGHT'), - type: 'string', - }, - /** - * Stores a unique MongoDB ID for the current user. - * Initially, we used this field as telemetry user identifier, - * but this usage is being deprecated. - * The telemetryAnonymousId should be used instead. - */ - currentUserId: { - ui: false, - cli: false, - global: false, - description: null, - validator: z.string().optional(), - type: 'string', - }, - /** - * Stores a unique telemetry anonymous ID (uuid) for the current user. - */ - telemetryAnonymousId: { - ui: false, - cli: false, - global: false, - description: null, - validator: z.string().uuid().optional(), - type: 'string', - }, - /** - * Enable/disable the AI services. This is currently set - * in the atlas-service initialization where we make a request to the - * ai endpoint to check what's enabled for the user (incremental rollout). - */ - cloudFeatureRolloutAccess: { - ui: false, - cli: false, - global: false, - description: null, - validator: z - .object({ - GEN_AI_COMPASS: z.boolean().optional(), - }) - .optional(), - type: 'object', - }, - /** - * Master switch to disable all network traffic - * and make Compass behave like Isolated edition always, - * i.e. no network traffic other than the one to the db server - * (which includes maps, telemetry, auto-updates). - */ - networkTraffic: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable network traffic other than to the MongoDB database', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - /** - * Removes features that write to the database from the UI. - */ - readOnly: { - ui: true, - cli: true, - global: true, - description: { - short: 'Set Read-Only Mode', - long: 'Limit Compass strictly to read operations, with all write and delete capabilities removed.', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to enable/disable the embedded shell. - */ - enableShell: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable MongoDB Shell', - long: 'Allow Compass to interact with MongoDB deployments via the embedded shell.', - }, - deriveValue: deriveReadOnlyOptionState('enableShell'), - validator: z.boolean().default(true), - type: 'boolean', - }, - /** - * Switch to enable/disable maps rendering. - */ - enableMaps: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable Geographic Visualizations', - long: 'Allow Compass to make requests to a 3rd party mapping service.', - }, - deriveValue: deriveNetworkTrafficOptionState('enableMaps'), - validator: z.boolean().default(false), - type: 'boolean', - }, - enableGenAIFeatures: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable AI Features', - long: 'Allow the use of AI features in Compass which make requests to 3rd party services. These features are currently experimental and offered as a preview to only a limited number of users.', - }, - deriveValue: deriveNetworkTrafficOptionState('enableGenAIFeatures'), - validator: z.boolean().default(true), - type: 'boolean', - }, - /** - * Switch to enable/disable Intercom panel (renamed from `intercom`). - */ - enableFeedbackPanel: { - ui: true, - cli: true, - global: true, - description: { - short: 'Give Product Feedback', - long: 'Enables a tool that our Product team can use to occasionally reach out for feedback about Compass.', - }, - deriveValue: deriveNetworkTrafficOptionState('enableFeedbackPanel'), - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to enable/disable usage statistics collection - * (renamed from `googleAnalytics`). - */ - trackUsageStatistics: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable Usage Statistics', - long: 'Allow Compass to send anonymous usage statistics.', - }, - deriveValue: deriveNetworkTrafficOptionState('trackUsageStatistics'), - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to enable/disable automatic updates. - */ - autoUpdates: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable Automatic Updates', - long: 'Allow Compass to periodically check for new updates.', - }, - deriveValue: deriveNetworkTrafficOptionState('autoUpdates'), - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to hide credentials in connection strings from users. - */ - protectConnectionStrings: { - ui: true, - cli: true, - global: true, - description: { - short: 'Protect Connection String Secrets', - long: 'Hide credentials in connection strings from users.', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to enable DevTools in Electron. - */ - enableDevTools: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable DevTools', - long: `Enable the Chromium Developer Tools that can be used to debug Electron's process.`, - }, - deriveValue: deriveFeatureRestrictingOptionsState('enableDevTools'), - validator: z.boolean().default(process.env.APP_ENV === 'webdriverio'), - type: 'boolean', - }, - /** - * Switch to show the Kerberos password field in the connection form. - */ - showKerberosPasswordField: { - ui: true, - cli: true, - global: true, - description: { - short: 'Show Kerberos Password Field', - long: 'Show a password field for Kerberos authentication. Typically only useful when attempting to authenticate as another user than the current system user.', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Switch to show the OIDC device auth flow option in the connection form. - */ - showOIDCDeviceAuthFlow: { - ui: true, - cli: true, - global: true, - description: { - short: 'Show Device Auth Flow Checkbox', - long: 'Show a checkbox on the connection form to enable device auth flow authentication for MongoDB server OIDC Authentication. This enables a less secure authentication flow that can be used as a fallback when browser-based authentication is unavailable.', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - /** - * Input to change the browser command used for OIDC authentication. - */ - browserCommandForOIDCAuth: { - ui: true, - cli: true, - global: true, - description: { - short: 'Browser command to use for authentication', - long: 'Specify a shell command that is run to start the browser for authenticating with the OIDC identity provider for the server connection or when logging in to your Atlas Cloud account. Leave this empty for default browser.', - }, - validator: z.string().optional(), - type: 'string', - }, - /** - * Input to change the browser command used for OIDC authentication. - */ - persistOIDCTokens: { - ui: true, - cli: true, - global: true, - description: { - short: 'Stay logged in with OIDC', - long: 'Remain logged in when using the MONGODB-OIDC authentication mechanism for MongoDB server connection. Access tokens are encrypted using the system keychain before being stored.', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - /** - * Override certain connection string properties. - */ - forceConnectionOptions: { - ui: true, - cli: true, - global: true, - description: { - short: 'Override Connection String Properties', - long: 'Force connection string properties to take specific values', - }, - customPostProcess: parseRecord, - validator: z.array(z.tuple([z.string(), z.string()])).optional(), - type: 'array', - }, - /** - * Set an upper limit for maxTimeMS for operations started by Compass. - */ - maxTimeMS: { - ui: true, - cli: true, - global: true, - description: { - short: 'Upper Limit for maxTimeMS for Compass Database Operations', - }, - validator: z.number().optional(), - type: 'number', - }, - /** - * Do not handle mongodb:// and mongodb+srv:// URLs via Compass - */ - installURLHandlers: { - ui: true, - cli: true, - global: true, - description: { - short: 'Install Compass as URL Protocol Handler', - long: 'Register Compass as a handler for mongodb:// and mongodb+srv:// URLs', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - /** - * Determines if the toggle to edit connection string for new connections - * should be in the off state or in the on state by default - */ - protectConnectionStringsForNewConnections: { - ui: true, - cli: true, - global: true, - description: { - short: - 'If true, "Edit connection string" is disabled for new connections by default', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - - /** - * Chooses atlas service backend configuration from preset - * - compass-dev: locally running compass kanopy backend (localhost) - * - compass: compass kanopy backend (compass.mongodb.com) - * - atlas-local: local mms backend (http://localhost:8080) - * - atlas-dev: dev mms backend (cloud-dev.mongodb.com) - * - atlas: mms backend (cloud.mongodb.com) - */ - atlasServiceBackendPreset: { - ui: true, - cli: true, - global: true, - description: { - short: 'Configuration used by atlas service', - }, - validator: z - .enum(['compass-dev', 'compass', 'atlas-dev', 'atlas-local', 'atlas']) - .default('atlas'), - type: 'string', - }, - - enableImportExport: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable import / export feature', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - - enableAggregationBuilderRunPipeline: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable "Run Pipeline" feature in aggregation builder', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - - enableExplainPlan: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable explain plan feature in CRUD and aggregation view', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - - enableAggregationBuilderExtraOptions: { - ui: true, - cli: true, - global: true, - description: { - short: - 'Enable preview input limit and collation options in aggregation view', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - - enableSavedAggregationsQueries: { - ui: true, - cli: true, - global: true, - description: { - short: 'Enable saving and opening saved aggregations and queries', - }, - validator: z.boolean().default(true), - type: 'boolean', - }, - - ...allFeatureFlagsProps, -}; - -const cliOnlyPreferencesProps: Required<{ - [K in keyof CliOnlyPreferences]: PreferenceDefinition; -}> = { - exportConnections: { - ui: false, - cli: true, - global: false, - description: { - short: 'Export Favorite Connections', - long: 'Export Compass favorite connections. Can be used with --passphrase.', - }, - validator: z.string().optional(), - type: 'string', - }, - importConnections: { - ui: false, - cli: true, - global: false, - description: { - short: 'Import Favorite Connections', - long: 'Import Compass favorite connections. Can be used with --passphrase.', - }, - validator: z.string().optional(), - type: 'string', - }, - passphrase: { - ui: false, - cli: true, - global: false, - description: { - short: 'Connection Export/Import Passphrase', - long: 'Specify a passphrase for encrypting/decrypting secrets.', - }, - validator: z.string().optional(), - type: 'string', - }, - help: { - ui: false, - cli: true, - global: false, - description: { - short: 'Show Compass Options', - }, - validator: z.boolean().optional(), - type: 'boolean', - }, - version: { - ui: false, - cli: true, - global: false, - description: { - short: 'Show Compass Version', - }, - validator: z.boolean().optional(), - type: 'boolean', - }, - showExampleConfig: { - ui: false, - cli: true, - global: false, - description: { - short: 'Show Example Config File', - }, - validator: z.boolean().optional(), - type: 'boolean', - }, -}; - -const nonUserPreferences: Required<{ - [K in keyof NonUserPreferences]: PreferenceDefinition; -}> = { - ignoreAdditionalCommandLineFlags: { - ui: false, - cli: true, - global: true, - description: { - short: 'Allow Additional CLI Flags', - long: 'Allow specifying command-line flags that Compass does not understand, e.g. Electron or Chromium flags', - }, - validator: z.boolean().default(false), - type: 'boolean', - }, - positionalArguments: { - ui: false, - cli: true, - global: false, - description: { - short: - 'Specify a Connection String or Connection ID to Automatically Connect', - }, - omitFromHelp: true, - validator: z.array(z.string()).optional(), - type: 'array', - }, - file: { - ui: false, - cli: true, - global: true, - description: { - short: 'Specify a List of Connections for Automatically Connecting', - }, - validator: z.string().optional(), - type: 'string', - }, - username: { - ui: false, - cli: true, - global: true, - description: { - short: 'Specify a Username for Automatically Connecting', - }, - validator: z.string().optional(), - type: 'string', - }, - password: { - ui: false, - cli: true, - global: true, - description: { - short: 'Specify a Password for Automatically Connecting', - }, - validator: z.string().optional(), - type: 'string', - }, -}; - -export const allPreferencesProps: Required<{ - [K in keyof AllPreferences]: PreferenceDefinition; -}> = { - ...storedUserPreferencesProps, - ...cliOnlyPreferencesProps, - ...nonUserPreferences, -}; - -export function getSettingDescription< - Name extends Exclude ->( - name: Name -): Pick, 'description'> & { type: unknown } { - const { description, type } = allPreferencesProps[ - name - ] as PreferenceDefinition; - return { - description, - type, - }; -} - -/* Identifies a source from which the preference was set */ -export type PreferenceState = - | 'set-cli' // Can be set directly or derived from a preference set via cli args. - | 'set-global' // Can be set directly or derived from a preference set via global config. - | 'hardcoded' - | 'derived' // Derived from a preference set by a user via setting UI. - | undefined; - -export type PreferenceStateInformation = Partial< - Record ->; - export type PreferenceSandboxProperties = string; // Internal to the Preferences class, so PreferenceSandboxProperties is an opaque string type PreferenceSandboxPropertiesImpl = { diff --git a/packages/compass-preferences-model/src/renderer-ipc.ts b/packages/compass-preferences-model/src/renderer-ipc.ts index 3e700302500..ea50b598a99 100644 --- a/packages/compass-preferences-model/src/renderer-ipc.ts +++ b/packages/compass-preferences-model/src/renderer-ipc.ts @@ -3,11 +3,11 @@ import { ipcRenderer } from 'hadron-ipc'; import type { PreferencesAccess } from '.'; import type { AllPreferences, - PreferenceSandboxProperties, PreferenceStateInformation, UserConfigurablePreferences, UserPreferences, -} from './preferences'; +} from './preferences-schema'; +import type { PreferenceSandboxProperties } from './preferences'; import { createSandboxAccessFromProps } from './setup-preferences'; /** diff --git a/packages/compass-preferences-model/src/setup-preferences.ts b/packages/compass-preferences-model/src/setup-preferences.ts index d75a6968311..61f80d9bc51 100644 --- a/packages/compass-preferences-model/src/setup-preferences.ts +++ b/packages/compass-preferences-model/src/setup-preferences.ts @@ -5,8 +5,8 @@ import type { PreferenceStateInformation, UserConfigurablePreferences, UserPreferences, - PreferenceSandboxProperties, -} from './preferences'; +} from './preferences-schema'; +import type { PreferenceSandboxProperties } from './preferences'; import type { ParsedGlobalPreferencesResult } from './global-config'; import type { PreferencesAccess } from '.'; diff --git a/packages/compass-preferences-model/src/storage.spec.ts b/packages/compass-preferences-model/src/storage.spec.ts index 9fe04ffe6fe..8425a6aeacb 100644 --- a/packages/compass-preferences-model/src/storage.spec.ts +++ b/packages/compass-preferences-model/src/storage.spec.ts @@ -9,7 +9,7 @@ import { UserStorageImpl, } from './storage'; import { expect } from 'chai'; -import { z } from '@mongodb-js/compass-user-data'; +import { z } from 'zod'; import { users as UserFixtures } from './../test/fixtures'; const getPreferencesFolder = (tmpDir: string) => { diff --git a/packages/compass-preferences-model/src/storage.ts b/packages/compass-preferences-model/src/storage.ts index 297440278fa..7b094f6e29b 100644 --- a/packages/compass-preferences-model/src/storage.ts +++ b/packages/compass-preferences-model/src/storage.ts @@ -1,6 +1,7 @@ import { UUID } from 'bson'; -import { storedUserPreferencesProps } from './preferences'; -import { UserData, z } from '@mongodb-js/compass-user-data'; +import { storedUserPreferencesProps } from './preferences-schema'; +import { z } from 'zod'; +import { UserData } from '@mongodb-js/compass-user-data'; type PreferencesValidator = ReturnType; export type StoredPreferences = z.output;