Skip to content

Commit

Permalink
fix: fixed collection validations cleanup and not helper
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgarciaesgi committed Oct 20, 2023
1 parent 8d2264f commit ead7bde
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 62 deletions.
16 changes: 16 additions & 0 deletions packages/core/src/core/defaultValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,20 @@ export type DefaultValidators = {
maxLength: RegleRuleWithParamsDefinition<string, [count: number]>;
required: RegleRuleDefinition<unknown, []>;
requiredIf: RegleRuleWithParamsDefinition<unknown, [condition: boolean]>;
alpha: RegleRuleDefinition<string>;
alphaNum: RegleRuleDefinition<string | number>;
between: RegleRuleWithParamsDefinition<number, [min: number, max: number]>;
decimal: RegleRuleDefinition<number | string>;
email: RegleRuleDefinition<string>;
integer: RegleRuleDefinition<number | string>;
maxValue: RegleRuleWithParamsDefinition<number, [count: number]>;
minLength: RegleRuleWithParamsDefinition<
string | Record<PropertyKey, any> | any[],
[count: number]
>;
minValue: RegleRuleWithParamsDefinition<number, [count: number]>;
numeric: RegleRuleDefinition<number | string>;
requireUnless: RegleRuleWithParamsDefinition<unknown, [condition: boolean]>;
sameAs: RegleRuleWithParamsDefinition<unknown, [target: unknown]>;
url: RegleRuleDefinition<string>;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RequiredDeep } from 'type-fest';
import { Ref, reactive, ref, toRef, toRefs, watch } from 'vue';
import { Ref, nextTick, reactive, ref, toRaw, toRef, toRefs, watch } from 'vue';
import type {
$InternalFormPropertyTypes,
$InternalRegleCollectionRuleDecl,
Expand Down Expand Up @@ -41,13 +41,16 @@ function createCollectionElement({
options: DeepMaybeRef<RequiredDeep<RegleBehaviourOptions>>;
rules: $InternalFormPropertyTypes;
}): $InternalRegleStatusType | null {
const $path = `${path}.${index}`;
const $id = randomId();
const $path = `${path}.${$id}`;

if (!value[index].$id) {
Object.defineProperties(value[index], {
$id: {
value: $id,
enumerable: false,
configurable: false,
writable: false,
},
});
}
Expand Down Expand Up @@ -104,29 +107,45 @@ export function createReactiveCollectionStatus({
if (Array.isArray(state.value) && $each) {
$eachStatus.value = state.value
.map((value, index) => {
return createCollectionElement({
path,
rules: $each,
value: state.value as any[],
index,
options,
storage,
});
if (value.$id) {
const previousStatus = storage.getArrayStatus(value.$id);
if (previousStatus) {
return previousStatus;
}
} else {
return createCollectionElement({
path,
rules: $each,
value: state.value as any[],
index,
options,
storage,
});
}
})
.filter((f): f is $InternalRegleStatusType => !!f);
} else {
$eachStatus.value = [];
}
}

function updateChildrenStatus() {
async function updateChildrenStatus() {
const { $each } = rulesDef.value;
if (Array.isArray(state.value) && $eachStatus.value && $each) {
$unwatchState?.();
state.value.forEach((value, index) => {
if (value.$id) {
if (
Array.isArray(state.value) &&
!state.value.find((val) => val.$id === $eachStatus.value[index].$id)
) {
$eachStatus.value[index].$unwatch();
}
const previousStatus = storage.getArrayStatus(value.$id);
if (previousStatus) {
$eachStatus.value[index] = previousStatus;
} else {
$eachStatus.value[index].$unwatch();
}
} else {
const newElement = createCollectionElement({
Expand All @@ -143,16 +162,17 @@ export function createReactiveCollectionStatus({
}
}
});
}

// cleanup removed elements from array
// cleanup removed elements from array (only if elements are pushed)

if ($eachStatus.value) {
const deletedItems = $eachStatus.value.filter(($each) => {
return Array.isArray(state.value) && !state.value.find((val) => val.$id === $each.$id);
});

deletedItems.forEach((item) => item.$unwatch());
deletedItems.forEach((item) => {
storage.deleteArrayStatus(item.$id);
item.$unwatch();
});
$eachStatus.value.length = state.value.length;
nextTick($watch);
}
}

Expand All @@ -171,13 +191,9 @@ export function createReactiveCollectionStatus({
}

function $watch() {
$unwatchState = watch(
state,
() => {
updateChildrenStatus();
},
{ deep: true, flush: 'sync' }
);
$unwatchState = watch(() => (state.value as any).length, updateChildrenStatus, {
flush: 'sync',
});
}

function $touch(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export function createReactiveFieldStatus({

if (storeResult?.valid != null) {
$dirty.value = storage.getDirtyState(path);
if ($dirty.value) {
$commit();
}
}

storage.addRuleDeclEntry(path, declaredRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export function createReactiveRuleStatus({
const validator = scopeState.$validator.value;
let ruleResult = false;
const resultOrPromise = validator(state.value, ...scopeState.$params.value);

if (resultOrPromise instanceof Promise) {
if ($dirty.value && !$pending.value) {
try {
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/core/useStorage/useStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type RegleStorage = {
getCollectionsEntry($path: string): Ref<Array<$InternalRegleStatusType>>;
getArrayStatus($id: string): $InternalRegleStatusType | undefined;
addArrayStatus($id: string, value: $InternalRegleStatusType): void;
deleteArrayStatus($id?: string): void;
};

/**
Expand Down Expand Up @@ -70,6 +71,12 @@ export function useStorage(): RegleStorage {
return arrayStatusStorage.value.get($id);
}

function deleteArrayStatus($id?: string) {
if ($id) {
arrayStatusStorage.value.delete($id);
}
}

function setDirtyEntry($path: string, dirty: boolean) {
dirtyStorage.value.set($path, dirty);
}
Expand Down Expand Up @@ -148,5 +155,6 @@ export function useStorage(): RegleStorage {
getCollectionsEntry,
getArrayStatus,
addArrayStatus,
deleteArrayStatus,
};
}
15 changes: 3 additions & 12 deletions packages/validators/src/helpers/and.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
createRule,
FormRuleDeclaration,
InternalRuleType,
RegleRuleDefinition,
} from '@regle/core';
import { minValue } from 'validators/minValue';
import { createRule, FormRuleDeclaration, RegleRuleDefinition } from '@regle/core';
import { ExtractValueFormRules } from '../types';
import { maxLength } from '../validators';

export function and<TRules extends FormRuleDeclaration<any, any>[]>(
...rules: [...TRules]
Expand Down Expand Up @@ -55,14 +48,12 @@ export function and<TRules extends FormRuleDeclaration<any, any>[]>(
};

const newRule = createRule({
type: InternalRuleType.Async,
type: 'and',
validator: validator,
message: '',
});

newRule._params = params;

return newRule as any;
return newRule as RegleRuleDefinition;
}

const test = and(maxLength(1), minValue(2));
10 changes: 7 additions & 3 deletions packages/validators/src/helpers/applyIf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ export function applyIf<TValue extends any, TParams extends any[]>(
): RegleRuleWithParamsDefinition<TValue, TParams> | RegleRuleDefinition<TValue, TParams> {
let _type: string;
let validator: RegleRuleDefinitionProcessor<TValue, TParams, boolean | Promise<boolean>>;
let _active: boolean | RegleRuleDefinitionProcessor<TValue, TParams, boolean> | undefined;
let _params: any[] | undefined;
let _message: string | ((value: TValue | null | undefined, ...args: TParams) => string) = '';

if (typeof rule === 'function') {
_type = InternalRuleType.Inline;
validator = rule;
} else {
({ _type, validator, _active, _params, _message } = rule);
({ _type, validator, _params, _message } = rule);
_params?.push(_condition);
}

Expand All @@ -43,10 +42,15 @@ export function applyIf<TValue extends any, TParams extends any[]>(
return true;
}

function newActive(value: any, ...args: TParams) {
const [condition] = unwrapRuleParameters<[boolean]>([_condition]);
return condition;
}

const newRule = createRule<TValue, TParams>({
type: _type,
validator: newValidator,
active: _active,
active: newActive,
message: _message,
});

Expand Down
2 changes: 2 additions & 0 deletions packages/validators/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export { withAsync } from './withAsync';
export { applyIf } from './applyIf';
export { ruleHelpers } from './rulesHelpers';
export { and } from './and';
export { or } from './or';
export { not } from './not';
61 changes: 61 additions & 0 deletions packages/validators/src/helpers/not.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
FormRuleDeclaration,
RegleRuleDefinition,
RegleRuleDefinitionProcessor,
createRule,
} from '@regle/core';
import { ruleHelpers } from './rulesHelpers';

export function not<TValue, TParams extends any[] = any[]>(
rule: FormRuleDeclaration<TValue, TParams>,
message: string | RegleRuleDefinitionProcessor<TValue, TParams, string>
): RegleRuleDefinition<TValue> {
let _type = 'not';
let _active: boolean | RegleRuleDefinitionProcessor<any, [], boolean> | undefined;
let _params: any[] | undefined = [];
let _async = false;

if (typeof rule === 'function') {
_async = rule.constructor.name === 'AsyncFunction';
} else {
({ _type, _params, _async } = rule);
}

const validator = (() => {
if (_async) {
return async (value: any, ...params: any[]) => {
if (ruleHelpers.isFilled(value)) {
if (typeof rule === 'function') {
const result = await rule(value);
return !result;
} else {
const result = await rule._validator(value, ...(params as any));
return !result;
}
}
return true;
};
} else {
return (value: any, ...params: any[]) => {
if (ruleHelpers.isFilled(value)) {
if (typeof rule === 'function') {
return !rule(value);
} else {
return !rule._validator(value, ...(params as any));
}
}
return true;
};
}
})();

const newRule = createRule({
type: 'not',
validator: validator,
message,
});

newRule._params = _params as any;

return newRule as RegleRuleDefinition;
}
66 changes: 66 additions & 0 deletions packages/validators/src/helpers/or.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
createRule,
FormRuleDeclaration,
RegleRuleDefinition,
RegleRuleDefinitionProcessor,
} from '@regle/core';
import { minValue } from 'validators/minValue';
import { ExtractValueFormRules } from '../types';
import { maxLength } from '../validators';

export function or<TRules extends FormRuleDeclaration<any, any>[]>(
...rules: [...TRules]
): RegleRuleDefinition<ExtractValueFormRules<TRules>[number]> {
const isAnyRuleAsync = rules.some((rule) => {
if (typeof rule === 'function') {
return rule.constructor.name === 'AsyncFunction';
} else {
return rule._async;
}
});

const params = rules
.map((rule) => {
if (typeof rule === 'function') {
return null;
} else {
return rule._params;
}
})
.filter((param): param is any => !!param);

const validator = isAnyRuleAsync
? async (value: any | null | undefined, ...params: any[]) => {
const results = await Promise.all(
rules.map((rule) => {
if (typeof rule === 'function') {
return rule(value);
} else {
return rule.validator(value, ...params);
}
})
);
return results.some((result) => !!result);
}
: (value: any | null | undefined, ...params: any[]) => {
return rules
.map((rule) => {
if (typeof rule === 'function') {
return rule(value);
} else {
return rule.validator(value, ...params);
}
})
.some((result) => !!result);
};

const newRule = createRule({
type: 'or',
validator: validator,
message: '',
});

newRule._params = params;

return newRule as RegleRuleDefinition;
}
2 changes: 1 addition & 1 deletion packages/validators/src/helpers/withAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export function withAsync<TValue, TParams extends (Ref<unknown> | (() => unknown

newRule._params = depsArray as any;

return newRule as any;
return newRule as RegleRuleDefinition;
}
Loading

0 comments on commit ead7bde

Please sign in to comment.