From 0fa522ab734a9b5adf41883abf25fa8600aaf6a9 Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:30:18 -0400 Subject: [PATCH] feat(davinci-client): implement protect collector --- .changeset/dull-rockets-give.md | 5 ++ e2e/davinci-app/components/protect.ts | 3 +- e2e/davinci-app/main.ts | 36 ++++++++- e2e/davinci-app/package.json | 3 +- e2e/davinci-app/tsconfig.app.json | 3 + e2e/davinci-app/tsconfig.json | 3 + .../davinci-client/src/lib/client.store.ts | 14 +++- .../davinci-client/src/lib/collector.types.ts | 67 ++++++++++++++++- .../davinci-client/src/lib/collector.utils.ts | 74 +++++++++++++++++++ .../davinci-client/src/lib/davinci.types.ts | 9 ++- .../davinci-client/src/lib/davinci.utils.ts | 3 +- .../davinci-client/src/lib/node.reducer.ts | 12 ++- .../src/lib/node.types.test-d.ts | 2 + packages/davinci-client/src/lib/node.types.ts | 2 + packages/davinci-client/src/types.ts | 1 + pnpm-lock.yaml | 25 ++++--- 16 files changed, 233 insertions(+), 29 deletions(-) create mode 100644 .changeset/dull-rockets-give.md diff --git a/.changeset/dull-rockets-give.md b/.changeset/dull-rockets-give.md new file mode 100644 index 000000000..5ffef93d8 --- /dev/null +++ b/.changeset/dull-rockets-give.md @@ -0,0 +1,5 @@ +--- +'@forgerock/davinci-client': minor +--- + +Implemented Ping Protect collector diff --git a/e2e/davinci-app/components/protect.ts b/e2e/davinci-app/components/protect.ts index 992c06e4f..f6edea6b0 100644 --- a/e2e/davinci-app/components/protect.ts +++ b/e2e/davinci-app/components/protect.ts @@ -10,7 +10,7 @@ import type { Updater, } from '@forgerock/davinci-client/types'; -export default function ( +export default function protectComponent( formEl: HTMLFormElement, collector: TextCollector | ValidatedTextCollector, updater: Updater, @@ -20,7 +20,6 @@ export default function ( p.innerText = collector.output.label; formEl?.appendChild(p); - const error = updater('fakeprofile'); if (error && 'error' in error) { console.error(error.error.message); diff --git a/e2e/davinci-app/main.ts b/e2e/davinci-app/main.ts index 4c05d6523..b1621899a 100644 --- a/e2e/davinci-app/main.ts +++ b/e2e/davinci-app/main.ts @@ -13,13 +13,15 @@ import type { DaVinciConfig, DavinciClient, GetClient, + ProtectCollector, RequestMiddleware, } from '@forgerock/davinci-client/types'; +import { protect } from '@pingidentity/protect'; import textComponent from './components/text.js'; import passwordComponent from './components/password.js'; import submitButtonComponent from './components/submit-button.js'; -import protect from './components/protect.js'; +import protectComponent from './components/protect.js'; import flowLinkComponent from './components/flow-link.js'; import socialLoginButtonComponent from './components/social-login-button.js'; import { serverConfigs } from './server-configs.js'; @@ -77,10 +79,14 @@ const urlParams = new URLSearchParams(window.location.search); (async () => { const davinciClient: DavinciClient = await davinci({ config, logger, requestMiddleware }); + const protectAPI = await protect({ envId: '02fb4743-189a-4bc7-9d6c-a919edfe6447' }); const continueToken = urlParams.get('continueToken'); const formEl = document.getElementById('form') as HTMLFormElement; let resumed: any; + // Initialize Protect + await protectAPI.start(); + if (continueToken) { resumed = await davinciClient.resume({ continueToken }); } else { @@ -186,11 +192,12 @@ const urlParams = new URLSearchParams(window.location.search); } const collectors = davinciClient.getCollectors(); + collectors.forEach((collector) => { if (collector.type === 'TextCollector' && collector.name === 'protectsdk') { // eslint-disable-next-line @typescript-eslint/no-unused-expressions collector; - protect( + protectComponent( formEl, // You can ignore this; it's just for rendering collector, // This is the plain object of the collector davinciClient.update(collector), // Returns an update function for this collector @@ -238,7 +245,6 @@ const urlParams = new URLSearchParams(window.location.search); submitForm, ); } else if (collector.type === 'IdpCollector') { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions socialLoginButtonComponent(formEl, collector, davinciClient.externalIdp()); } else if (collector.type === 'FlowCollector') { flowLinkComponent( @@ -257,11 +263,24 @@ const urlParams = new URLSearchParams(window.location.search); } }); - if (davinciClient.getCollectors().find((collector) => collector.name === 'protectsdk')) { + if ( + davinciClient + .getCollectors() + .find((collector) => collector.type === 'TextCollector' && collector.name === 'protectsdk') + ) { submitForm(); } } + async function updateProtectCollector(protectCollector: ProtectCollector) { + const data = await protectAPI.getData(); + const updater = davinciClient.update(protectCollector); + const error = updater(data); + if (error && 'error' in error) { + console.error(error.error.message); + } + } + async function submitForm() { const newNode = await davinciClient.next(); @@ -311,6 +330,15 @@ const urlParams = new URLSearchParams(window.location.search); formEl.addEventListener('submit', async (event) => { event.preventDefault(); + + // Evaluate Protect data + const protectCollector = davinciClient + .getCollectors() + .find((collector) => collector.type === 'ProtectCollector'); + if (protectCollector) { + await updateProtectCollector(protectCollector); + } + /** * We can just call `next` here and not worry about passing any arguments */ diff --git a/e2e/davinci-app/package.json b/e2e/davinci-app/package.json index f6dcad5cd..87783c44f 100644 --- a/e2e/davinci-app/package.json +++ b/e2e/davinci-app/package.json @@ -16,7 +16,8 @@ "dependencies": { "@forgerock/davinci-client": "workspace:*", "@forgerock/javascript-sdk": "4.7.0", - "@forgerock/sdk-logger": "workspace:*" + "@forgerock/sdk-logger": "workspace:*", + "@pingidentity/protect": "workspace:*" }, "devDependencies": {} } diff --git a/e2e/davinci-app/tsconfig.app.json b/e2e/davinci-app/tsconfig.app.json index f8cf9430f..de6679e94 100644 --- a/e2e/davinci-app/tsconfig.app.json +++ b/e2e/davinci-app/tsconfig.app.json @@ -12,6 +12,9 @@ "components/**/*.ts" ], "references": [ + { + "path": "../../packages/protect/tsconfig.lib.json" + }, { "path": "../../packages/sdk-effects/logger/tsconfig.lib.json" }, diff --git a/e2e/davinci-app/tsconfig.json b/e2e/davinci-app/tsconfig.json index 0fd5ea596..65085f023 100644 --- a/e2e/davinci-app/tsconfig.json +++ b/e2e/davinci-app/tsconfig.json @@ -14,6 +14,9 @@ "skipLibCheck": true }, "references": [ + { + "path": "../../packages/protect" + }, { "path": "../../packages/sdk-effects/logger" }, diff --git a/packages/davinci-client/src/lib/client.store.ts b/packages/davinci-client/src/lib/client.store.ts index df05ad668..6277229ae 100644 --- a/packages/davinci-client/src/lib/client.store.ts +++ b/packages/davinci-client/src/lib/client.store.ts @@ -32,6 +32,7 @@ import type { MultiSelectCollector, ObjectValueCollectors, PhoneNumberInputValue, + AutoCollectors, } from './collector.types.js'; import type { InitFlow, @@ -227,11 +228,15 @@ export async function davinci({ /** * @method update - Exclusive method for updating the current node with user provided values - * @param {SingleValueCollector} collector - the collector to update + * @param {SingleValueCollector | MultiSelectCollector | ObjectValueCollectors | AutoCollectors} collector - the collector to update * @returns {function} - a function to call for updating collector value */ update: ( - collector: SingleValueCollectors | MultiSelectCollector | ObjectValueCollectors, + collector: + | SingleValueCollectors + | MultiSelectCollector + | ObjectValueCollectors + | AutoCollectors, ): Updater => { if (!collector.id) { return handleUpdateValidateError( @@ -259,10 +264,11 @@ export async function davinci({ collectorToUpdate.category !== 'MultiValueCollector' && collectorToUpdate.category !== 'SingleValueCollector' && collectorToUpdate.category !== 'ValidatedSingleValueCollector' && - collectorToUpdate.category !== 'ObjectValueCollector' + collectorToUpdate.category !== 'ObjectValueCollector' && + collectorToUpdate.category !== 'SingleValueAutoCollector' ) { return handleUpdateValidateError( - 'Collector is not a MultiValueCollector, SingleValueCollector or ValidatedSingleValueCollector and cannot be updated', + 'Collector is not a MultiValueCollector, SingleValueCollector, ValidatedSingleValueCollector, ObjectValueCollector, or SingleValueAutoCollector and cannot be updated', 'state_error', log.error, ); diff --git a/packages/davinci-client/src/lib/collector.types.ts b/packages/davinci-client/src/lib/collector.types.ts index 8cd64f5b3..e7a47b10c 100644 --- a/packages/davinci-client/src/lib/collector.types.ts +++ b/packages/davinci-client/src/lib/collector.types.ts @@ -182,7 +182,7 @@ export type ValidatedTextCollector = ValidatedSingleValueCollectorWithValue<'Tex */ /** - * @interface MultiValueCollector - Represents a request to collect a single value from the user, like email or password. + * @interface MultiValueCollector - Represents a request to collect multiple values from the user. */ export type MultiValueCollectorTypes = 'MultiSelectCollector' | 'MultiValueCollector'; @@ -468,7 +468,7 @@ export type SubmitCollector = ActionCollectorNoUrl<'SubmitCollector'>; */ /** - * @interface NoValueCollector - Represents a collect that collects no value; text only for display. + * @interface NoValueCollector - Represents a collector that collects no value; text only for display. */ export type NoValueCollectorTypes = 'ReadOnlyCollector' | 'NoValueCollector'; @@ -487,7 +487,7 @@ export interface NoValueCollectorBase { /** * Type to help infer the collector based on the collector type - * Used specifically in the returnMultiValueCollector wrapper function. + * Used specifically in the returnNoValueCollector wrapper function. * When given a type, it can narrow which type it is returning * * Note: You can see this type in action in the test file or in the collector.utils file. @@ -517,3 +517,64 @@ export type UnknownCollector = { type: string; }; }; + +/** ********************************************************************* + * AUTOMATED COLLECTORS + */ + +/** + * @interface AutoCollector - Represents a collector that collects a value programmatically without user intervention. + */ + +export type AutoCollectorCategories = 'SingleValueAutoCollector'; +export type AutoCollectorTypes = AutoCollectorCategories | 'ProtectCollector'; + +export interface AutoCollector< + C extends AutoCollectorCategories, + T extends AutoCollectorTypes, + V = string, +> { + category: C; + error: string | null; + type: T; + id: string; + name: string; + input: { + key: string; + value: V; + type: string; + }; + output: { + key: string; + type: string; + config: Record; + }; +} + +export type ProtectCollector = AutoCollector< + 'SingleValueAutoCollector', + 'ProtectCollector', + string +>; +export type SingleValueAutoCollector = AutoCollector< + 'SingleValueAutoCollector', + 'SingleValueAutoCollector', + string +>; + +export type AutoCollectors = ProtectCollector | SingleValueAutoCollector; + +/** + * Type to help infer the collector based on the collector type + * Used specifically in the returnAutoCollector wrapper function. + * When given a type, it can narrow which type it is returning + * + * Note: You can see this type in action in the test file or in the collector.utils file. + */ +export type InferAutoCollectorType = T extends 'ProtectCollector' + ? ProtectCollector + : /** + * At this point, we have not passed in a collector type + * so we can return a SingleValueAutoCollector + **/ + SingleValueAutoCollector; diff --git a/packages/davinci-client/src/lib/collector.utils.ts b/packages/davinci-client/src/lib/collector.utils.ts index 145a48c6d..83ab01c89 100644 --- a/packages/davinci-client/src/lib/collector.utils.ts +++ b/packages/davinci-client/src/lib/collector.utils.ts @@ -21,13 +21,16 @@ import type { ValidatedTextCollector, InferValueObjectCollectorType, ObjectValueCollectorTypes, + AutoCollectorTypes, UnknownCollector, + InferAutoCollectorType, } from './collector.types.js'; import type { DeviceAuthenticationField, DeviceRegistrationField, MultiSelectField, PhoneNumberField, + ProtectField, ReadOnlyField, RedirectField, SingleSelectField, @@ -253,6 +256,66 @@ export function returnSingleValueCollector< } } +/** + * @function returnAutoCollector - Creates an AutoCollector object based on the provided field, index, and optional collector type. + * @param {DaVinciField} field - The field object containing key, label, type, and links. + * @param {number} idx - The index to be used in the id of the AutoCollector. + * @param {AutoCollectorTypes} [collectorType] - Optional type of the AutoCollector. + * @returns {AutoCollector} The constructed AutoCollector object. + */ +export function returnAutoCollector< + Field extends ProtectField, + CollectorType extends AutoCollectorTypes = 'SingleValueAutoCollector', +>(field: Field, idx: number, collectorType: CollectorType, data?: string) { + let error = ''; + if (!('key' in field)) { + error = `${error}Key is not found in the field object. `; + } + if (!('type' in field)) { + error = `${error}Type is not found in the field object. `; + } + + if (collectorType === 'ProtectCollector') { + return { + category: 'SingleValueAutoCollector', + error: error || null, + type: collectorType, + id: `${field?.key}-${idx}`, + name: field.key, + input: { + key: field.key, + value: data || '', + type: field.type, + }, + output: { + key: field.key, + type: field.type, + config: { + behavioralDataCollection: field.behavioralDataCollection, + universalDeviceIdentification: field.universalDeviceIdentification, + }, + }, + } as InferAutoCollectorType<'ProtectCollector'>; + } else { + return { + category: 'SingleValueAutoCollector', + error: error || null, + type: collectorType || 'SingleValueAutoCollector', + id: `${field.key}-${idx}`, + name: field.key, + input: { + key: field.key, + value: data || '', + type: field.type, + }, + output: { + key: field.key, + type: field.type, + }, + } as InferAutoCollectorType; + } +} + /** * @function returnPasswordCollector - Creates a PasswordCollector object based on the provided field and index. * @param {DaVinciField} field - The field object containing key, label, type, and links. @@ -272,6 +335,7 @@ export function returnPasswordCollector(field: StandardField, idx: number) { export function returnTextCollector(field: StandardField, idx: number, data: string) { return returnSingleValueCollector(field, idx, 'TextCollector', data); } + /** * @function returnSingleSelectCollector - Creates a SingleCollector object based on the provided field and index. * @param {DaVinciField} field - The field object containing key, label, type, and links. @@ -282,6 +346,16 @@ export function returnSingleSelectCollector(field: SingleSelectField, idx: numbe return returnSingleValueCollector(field, idx, 'SingleSelectCollector', data); } +/** + * @function returnProtectCollector - Creates a ProtectCollector object based on the provided field and index. + * @param {DaVinciField} field - The field object containing key, label, type, and links. + * @param {number} idx - The index to be used in the id of the ProtectCollector. + * @returns {ProtectCollector} The constructed ProtectCollector object. + */ +export function returnProtectCollector(field: ProtectField, idx: number, data: string) { + return returnAutoCollector(field, idx, 'ProtectCollector', data); +} + /** * @function returnMultiValueCollector - Creates a MultiValueCollector object based on the provided field, index, and optional collector type. * @param {DaVinciField} field - The field object containing key, label, type, and links. diff --git a/packages/davinci-client/src/lib/davinci.types.ts b/packages/davinci-client/src/lib/davinci.types.ts index 18d0208c1..17dfabdbd 100644 --- a/packages/davinci-client/src/lib/davinci.types.ts +++ b/packages/davinci-client/src/lib/davinci.types.ts @@ -152,6 +152,13 @@ export type PhoneNumberField = { required: boolean; }; +export type ProtectField = { + type: 'PROTECT'; + key: string; + behavioralDataCollection: boolean; + universalDeviceIdentification: boolean; +}; + export type UnknownField = Record; export type ComplexValueFields = @@ -161,7 +168,7 @@ export type ComplexValueFields = export type MultiValueFields = MultiSelectField; export type ReadOnlyFields = ReadOnlyField; export type RedirectFields = RedirectField; -export type SingleValueFields = StandardField | ValidatedField | SingleSelectField; +export type SingleValueFields = StandardField | ValidatedField | SingleSelectField | ProtectField; export type DaVinciField = | ComplexValueFields diff --git a/packages/davinci-client/src/lib/davinci.utils.ts b/packages/davinci-client/src/lib/davinci.utils.ts index 27f489157..a91b202c6 100644 --- a/packages/davinci-client/src/lib/davinci.utils.ts +++ b/packages/davinci-client/src/lib/davinci.utils.ts @@ -38,7 +38,8 @@ export function transformSubmitRequest( collector.category === 'MultiValueCollector' || collector.category === 'SingleValueCollector' || collector.category === 'ValidatedSingleValueCollector' || - collector.category === 'ObjectValueCollector', + collector.category === 'ObjectValueCollector' || + collector.category === 'SingleValueAutoCollector', ); const formData = collectors?.reduce<{ diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts index 4fd8aeaa9..018012d1e 100644 --- a/packages/davinci-client/src/lib/node.reducer.ts +++ b/packages/davinci-client/src/lib/node.reducer.ts @@ -24,6 +24,7 @@ import { returnReadOnlyCollector, returnObjectSelectCollector, returnObjectValueCollector, + returnProtectCollector, returnUnknownCollector, } from './collector.utils.js'; import type { DaVinciField, UnknownField } from './davinci.types.js'; @@ -44,6 +45,7 @@ import { PhoneNumberCollector, PhoneNumberInputValue, UnknownCollector, + ProtectCollector, } from './collector.types.js'; /** @@ -81,6 +83,7 @@ const initialCollectorValues: ( | ReadOnlyCollector | ValidatedTextCollector | UnknownCollector + | ProtectCollector )[] = []; /** @@ -159,6 +162,10 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build // No data to send return returnSubmitCollector(field, idx); } + case 'PROTECT': { + const str = data as string; + return returnProtectCollector(field, idx, str); + } default: // Default is handled below } @@ -196,10 +203,11 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build if ( collector.category === 'SingleValueCollector' || - collector.category === 'ValidatedSingleValueCollector' + collector.category === 'ValidatedSingleValueCollector' || + collector.category === 'SingleValueAutoCollector' ) { if (typeof action.payload.value !== 'string') { - throw new Error('SingleValueCollector does not accept an array'); + throw new Error('Value argument must be a string'); } collector.input.value = action.payload.value; return; diff --git a/packages/davinci-client/src/lib/node.types.test-d.ts b/packages/davinci-client/src/lib/node.types.test-d.ts index a3ee6644d..3beda1d0e 100644 --- a/packages/davinci-client/src/lib/node.types.test-d.ts +++ b/packages/davinci-client/src/lib/node.types.test-d.ts @@ -32,6 +32,7 @@ import { DeviceAuthenticationCollector, PhoneNumberCollector, UnknownCollector, + ProtectCollector, } from './collector.types.js'; // ErrorDetail and Links are used as part of the DaVinciError and server._links types respectively @@ -230,6 +231,7 @@ describe('Node Types', () => { | ReadOnlyCollector | SingleSelectCollector | ValidatedTextCollector + | ProtectCollector | UnknownCollector >(); diff --git a/packages/davinci-client/src/lib/node.types.ts b/packages/davinci-client/src/lib/node.types.ts index 05450541e..0ebac0f15 100644 --- a/packages/davinci-client/src/lib/node.types.ts +++ b/packages/davinci-client/src/lib/node.types.ts @@ -19,6 +19,7 @@ import type { ReadOnlyCollector, ValidatedTextCollector, PhoneNumberCollector, + ProtectCollector, UnknownCollector, } from './collector.types.js'; import type { Links } from './davinci.types.js'; @@ -39,6 +40,7 @@ export type Collectors = | PhoneNumberCollector | ReadOnlyCollector | ValidatedTextCollector + | ProtectCollector | UnknownCollector; export interface CollectorErrors { diff --git a/packages/davinci-client/src/types.ts b/packages/davinci-client/src/types.ts index 66e7dca19..6b410b673 100644 --- a/packages/davinci-client/src/types.ts +++ b/packages/davinci-client/src/types.ts @@ -48,6 +48,7 @@ export type SingleSelectCollector = collectors.SingleSelectCollector; export type DeviceRegistrationCollector = collectors.DeviceRegistrationCollector; export type DeviceAuthenticationCollector = collectors.DeviceAuthenticationCollector; export type PhoneNumberCollector = collectors.PhoneNumberCollector; +export type ProtectCollector = collectors.ProtectCollector; export type InternalErrorResponse = client.InternalErrorResponse; export type { RequestMiddleware }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aac0caf3b..8373806ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,7 +220,7 @@ importers: version: 6.2.6(@types/node@22.14.1)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0) vitest: specifier: 3.0.5 - version: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + version: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) vitest-canvas-mock: specifier: ^0.3.3 version: 0.3.3(vitest@3.0.5) @@ -236,6 +236,9 @@ importers: '@forgerock/sdk-logger': specifier: workspace:* version: link:../../packages/sdk-effects/logger + '@pingidentity/protect': + specifier: workspace:* + version: link:../../packages/protect e2e/davinci-suites: {} @@ -281,7 +284,7 @@ importers: devDependencies: '@effect/vitest': specifier: ^0.19.0 - version: 0.19.10(effect@3.16.0)(vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0)) + version: 0.19.10(effect@3.16.0)(vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0)) e2e/oidc-app: dependencies: @@ -328,7 +331,7 @@ importers: devDependencies: vitest: specifier: ^3.0.4 - version: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + version: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) packages/device-client: dependencies: @@ -8460,15 +8463,15 @@ snapshots: dependencies: effect: 3.16.0 - '@effect/vitest@0.19.10(effect@3.16.0)(vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0))': + '@effect/vitest@0.19.10(effect@3.16.0)(vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0))': dependencies: effect: 3.16.0 - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) '@effect/vitest@0.6.12(effect@3.16.0)(vitest@3.0.5)': dependencies: effect: 3.16.0 - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) '@emnapi/core@1.4.3': dependencies: @@ -9166,7 +9169,7 @@ snapshots: semver: 7.7.2 tsconfig-paths: 4.2.0 vite: 6.2.6(@types/node@22.14.1)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0) - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -10026,7 +10029,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -10082,7 +10085,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) '@vitest/utils@3.0.4': dependencies: @@ -14942,9 +14945,9 @@ snapshots: vitest-canvas-mock@0.3.3(vitest@3.0.5): dependencies: jest-canvas-mock: 2.5.2 - vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) + vitest: 3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0) - vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4(vitest@3.0.5))(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0): + vitest@3.0.5(@types/node@22.14.1)(@vitest/ui@3.0.4)(jiti@2.4.2)(jsdom@26.1.0)(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.42.0)(yaml@2.8.0): dependencies: '@vitest/expect': 3.0.5 '@vitest/mocker': 3.0.5(msw@2.8.5(@types/node@22.14.1)(typescript@5.8.3))(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))