@blueprint-ts/core 4.1.0-beta.3 → 4.1.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ export interface PropertyAwareField<T> {
6
6
  model: WritableComputedRef<T>;
7
7
  errors: string[];
8
8
  dirty: boolean;
9
+ touched: boolean;
9
10
  }
10
11
  /**
11
12
  * Wandelt ein reguläres Interface in ein property-aware Interface um
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Opt-in object wrapper for nested property-aware fields.
3
+ * Fields declared as PropertyAwareObject are exposed as nested property-aware
4
+ * children instead of a single scalar field.
5
+ */
6
+ export declare const PROPERTY_AWARE_OBJECT_MARKER = "__propertyAwareObject";
7
+ export declare class PropertyAwareObject<T extends object = Record<string, unknown>> {
8
+ [key: string]: unknown;
9
+ private readonly __propertyAwareObjectBrand;
10
+ constructor(values: T);
11
+ static from<T extends object>(values: T): PropertyAwareObject<T>;
12
+ toJSON(): Record<string, unknown>;
13
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Opt-in object wrapper for nested property-aware fields.
3
+ * Fields declared as PropertyAwareObject are exposed as nested property-aware
4
+ * children instead of a single scalar field.
5
+ */
6
+ export const PROPERTY_AWARE_OBJECT_MARKER = '__propertyAwareObject';
7
+ export class PropertyAwareObject {
8
+ constructor(values) {
9
+ void this.__propertyAwareObjectBrand;
10
+ Object.assign(this, values);
11
+ }
12
+ static from(values) {
13
+ return new PropertyAwareObject(values);
14
+ }
15
+ toJSON() {
16
+ return Object.assign({ [PROPERTY_AWARE_OBJECT_MARKER]: true }, this);
17
+ }
18
+ }
@@ -5,5 +5,8 @@ import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDrive
5
5
  import { SessionStorageDriver } from '../../persistenceDrivers/SessionStorageDriver';
6
6
  import { type PersistenceDriver } from '../../persistenceDrivers/types/PersistenceDriver';
7
7
  import { PropertyAwareArray, type PropertyAwareField, type PropertyAware } from './PropertyAwareArray';
8
- export { BaseForm, propertyAwareToRaw, PropertyAwareArray, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver };
9
- export type { PersistedForm, PersistenceDriver, PropertyAwareField, PropertyAware };
8
+ import { PropertyAwareObject } from './PropertyAwareObject';
9
+ import { StrictPersistenceRestorePolicy } from './persistence';
10
+ import type { PersistenceDebugEvent, PersistenceRestoreContext, PersistenceRestorePolicy, PersistenceRestoreResult } from './persistence';
11
+ export { BaseForm, propertyAwareToRaw, PropertyAwareArray, PropertyAwareObject, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver, StrictPersistenceRestorePolicy };
12
+ export type { PersistedForm, PersistenceDriver, PropertyAwareField, PropertyAware, PersistenceDebugEvent, PersistenceRestoreContext, PersistenceRestorePolicy, PersistenceRestoreResult };
@@ -3,4 +3,6 @@ import { LocalStorageDriver } from '../../persistenceDrivers/LocalStorageDriver'
3
3
  import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDriver';
4
4
  import { SessionStorageDriver } from '../../persistenceDrivers/SessionStorageDriver';
5
5
  import { PropertyAwareArray } from './PropertyAwareArray';
6
- export { BaseForm, propertyAwareToRaw, PropertyAwareArray, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver };
6
+ import { PropertyAwareObject } from './PropertyAwareObject';
7
+ import { StrictPersistenceRestorePolicy } from './persistence';
8
+ export { BaseForm, propertyAwareToRaw, PropertyAwareArray, PropertyAwareObject, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver, StrictPersistenceRestorePolicy };
@@ -0,0 +1,4 @@
1
+ import { type PersistenceRestoreContext, type PersistenceRestorePolicy, type PersistenceRestoreResult } from './types';
2
+ export declare class StrictPersistenceRestorePolicy<FormBody extends object> implements PersistenceRestorePolicy<FormBody> {
3
+ resolve(context: PersistenceRestoreContext<FormBody>): PersistenceRestoreResult<FormBody>;
4
+ }
@@ -0,0 +1,42 @@
1
+ import { collectPropertyAwareMismatchPaths, propertyAwareDeepEqual } from './utils';
2
+ function isRecord(value) {
3
+ return !!value && typeof value === 'object' && !Array.isArray(value);
4
+ }
5
+ function isPersistedFormLike(value) {
6
+ if (!isRecord(value)) {
7
+ return false;
8
+ }
9
+ return 'state' in value && 'original' in value && 'dirty' in value;
10
+ }
11
+ export class StrictPersistenceRestorePolicy {
12
+ resolve(context) {
13
+ const { defaults, persisted } = context;
14
+ if (persisted === null) {
15
+ return {
16
+ action: 'ignore',
17
+ reason: 'no_persisted_state'
18
+ };
19
+ }
20
+ if (!isPersistedFormLike(persisted)) {
21
+ return {
22
+ action: 'discard',
23
+ reason: 'invalid_persisted_state'
24
+ };
25
+ }
26
+ if (propertyAwareDeepEqual(defaults, persisted.original)) {
27
+ return {
28
+ action: 'restore',
29
+ reason: 'defaults_match',
30
+ persisted
31
+ };
32
+ }
33
+ return {
34
+ action: 'discard',
35
+ reason: 'defaults_mismatch',
36
+ persisted,
37
+ details: {
38
+ mismatchPaths: collectPropertyAwareMismatchPaths(defaults, persisted.original)
39
+ }
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ import { StrictPersistenceRestorePolicy } from './StrictPersistenceRestorePolicy';
2
+ export { StrictPersistenceRestorePolicy };
3
+ export type { PersistenceDebugEvent, PersistenceRestoreContext, PersistenceRestorePolicy, PersistenceRestoreResult } from './types';
@@ -0,0 +1,2 @@
1
+ import { StrictPersistenceRestorePolicy } from './StrictPersistenceRestorePolicy';
2
+ export { StrictPersistenceRestorePolicy };
@@ -0,0 +1,23 @@
1
+ import { type PersistedForm } from '../types/PersistedForm';
2
+ export interface PersistenceRestoreContext<FormBody extends object> {
3
+ formName: string;
4
+ persistSuffix?: string | undefined;
5
+ defaults: FormBody;
6
+ persisted: PersistedForm<FormBody> | null;
7
+ }
8
+ export interface PersistenceRestoreResult<FormBody extends object> {
9
+ action: 'restore' | 'discard' | 'ignore';
10
+ reason: string;
11
+ persisted?: PersistedForm<FormBody> | undefined;
12
+ details?: Record<string, unknown> | undefined;
13
+ }
14
+ export interface PersistenceDebugEvent<FormBody extends object> {
15
+ formName: string;
16
+ persistSuffix?: string | undefined;
17
+ action: PersistenceRestoreResult<FormBody>['action'];
18
+ reason: string;
19
+ details?: Record<string, unknown> | undefined;
20
+ }
21
+ export interface PersistenceRestorePolicy<FormBody extends object> {
22
+ resolve(context: PersistenceRestoreContext<FormBody>): PersistenceRestoreResult<FormBody>;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function propertyAwareDeepEqual<T>(a: T, b: T): boolean;
2
+ export declare function collectPropertyAwareMismatchPaths(a: unknown, b: unknown, path?: string, maxPaths?: number): string[];
@@ -0,0 +1,77 @@
1
+ import { isEqual } from 'lodash-es';
2
+ import { PropertyAwareArray } from '../PropertyAwareArray';
3
+ import { PropertyAwareObject, PROPERTY_AWARE_OBJECT_MARKER } from '../PropertyAwareObject';
4
+ function isRecord(value) {
5
+ return !!value && typeof value === 'object' && !Array.isArray(value);
6
+ }
7
+ function isPropertyAwareObject(value) {
8
+ return value instanceof PropertyAwareObject;
9
+ }
10
+ function isSerializedPropertyAwareObject(value) {
11
+ return isRecord(value) && value[PROPERTY_AWARE_OBJECT_MARKER] === true;
12
+ }
13
+ function normalizePropertyAwareEqualityValue(value) {
14
+ if (value instanceof PropertyAwareArray) {
15
+ return Array.from(value, (item) => normalizePropertyAwareEqualityValue(item));
16
+ }
17
+ if (isPropertyAwareObject(value) || isSerializedPropertyAwareObject(value)) {
18
+ const normalized = {};
19
+ for (const [key, child] of Object.entries(value)) {
20
+ if (key === PROPERTY_AWARE_OBJECT_MARKER) {
21
+ continue;
22
+ }
23
+ normalized[key] = normalizePropertyAwareEqualityValue(child);
24
+ }
25
+ return normalized;
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return value.map((item) => normalizePropertyAwareEqualityValue(item));
29
+ }
30
+ if (isRecord(value)) {
31
+ const normalized = {};
32
+ for (const [key, child] of Object.entries(value)) {
33
+ normalized[key] = normalizePropertyAwareEqualityValue(child);
34
+ }
35
+ return normalized;
36
+ }
37
+ return value;
38
+ }
39
+ export function propertyAwareDeepEqual(a, b) {
40
+ return isEqual(normalizePropertyAwareEqualityValue(a), normalizePropertyAwareEqualityValue(b));
41
+ }
42
+ export function collectPropertyAwareMismatchPaths(a, b, path = '', maxPaths = 10) {
43
+ const mismatches = [];
44
+ const visit = (left, right, currentPath) => {
45
+ if (mismatches.length >= maxPaths) {
46
+ return;
47
+ }
48
+ const normalizedLeft = normalizePropertyAwareEqualityValue(left);
49
+ const normalizedRight = normalizePropertyAwareEqualityValue(right);
50
+ if (isEqual(normalizedLeft, normalizedRight)) {
51
+ return;
52
+ }
53
+ if (Array.isArray(normalizedLeft) && Array.isArray(normalizedRight)) {
54
+ if (normalizedLeft.length !== normalizedRight.length) {
55
+ mismatches.push(currentPath || '(root)');
56
+ return;
57
+ }
58
+ normalizedLeft.forEach((item, index) => {
59
+ visit(item, normalizedRight[index], currentPath ? `${currentPath}.${index}` : String(index));
60
+ });
61
+ return;
62
+ }
63
+ if (isRecord(normalizedLeft) && isRecord(normalizedRight)) {
64
+ const keys = new Set([...Object.keys(normalizedLeft), ...Object.keys(normalizedRight)]);
65
+ for (const key of keys) {
66
+ visit(normalizedLeft[key], normalizedRight[key], currentPath ? `${currentPath}.${key}` : key);
67
+ if (mismatches.length >= maxPaths) {
68
+ return;
69
+ }
70
+ }
71
+ return;
72
+ }
73
+ mismatches.push(currentPath || '(root)');
74
+ };
75
+ visit(a, b, path);
76
+ return mismatches;
77
+ }
@@ -5,8 +5,9 @@ import { JsonRule } from './rules/JsonRule';
5
5
  import { RequiredRule } from './rules/RequiredRule';
6
6
  import { UrlRule } from './rules/UrlRule';
7
7
  import { MinRule } from './rules/MinRule';
8
+ import { PrecognitiveRule } from './rules/PrecognitiveRule';
8
9
  import { ValidationMode } from './ValidationMode.enum';
9
10
  import { type BidirectionalRule } from './types/BidirectionalRule';
10
- import { type ValidationRules } from './types/ValidationRules';
11
- export { BaseRule, ConfirmedRule, EmailRule, JsonRule, RequiredRule, UrlRule, MinRule, ValidationMode };
12
- export type { BidirectionalRule, ValidationRules };
11
+ import { type ValidationGroups, type ValidationRules } from './types/ValidationRules';
12
+ export { BaseRule, ConfirmedRule, EmailRule, JsonRule, RequiredRule, UrlRule, MinRule, PrecognitiveRule, ValidationMode };
13
+ export type { BidirectionalRule, ValidationGroups, ValidationRules };
@@ -5,5 +5,6 @@ import { JsonRule } from './rules/JsonRule';
5
5
  import { RequiredRule } from './rules/RequiredRule';
6
6
  import { UrlRule } from './rules/UrlRule';
7
7
  import { MinRule } from './rules/MinRule';
8
+ import { PrecognitiveRule } from './rules/PrecognitiveRule';
8
9
  import { ValidationMode } from './ValidationMode.enum';
9
- export { BaseRule, ConfirmedRule, EmailRule, JsonRule, RequiredRule, UrlRule, MinRule, ValidationMode };
10
+ export { BaseRule, ConfirmedRule, EmailRule, JsonRule, RequiredRule, UrlRule, MinRule, PrecognitiveRule, ValidationMode };
@@ -1,5 +1,12 @@
1
+ export interface AsyncValidationContext {
2
+ field: string;
3
+ payload: Record<string, unknown>;
4
+ isSubmitting: boolean;
5
+ }
1
6
  export declare abstract class BaseRule<FormBody extends object> {
2
7
  dependsOn: Array<keyof FormBody>;
3
8
  abstract validate(value: unknown, state: FormBody): boolean;
4
9
  abstract getMessage(): string;
10
+ validateAsync(_value: unknown, _state: FormBody, _context: AsyncValidationContext): Promise<Record<string, string[]> | null>;
11
+ getAsyncValidationPaths(field: keyof FormBody, _state: FormBody): string[];
5
12
  }
@@ -1,5 +1,24 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  export class BaseRule {
2
11
  constructor() {
3
12
  this.dependsOn = [];
4
13
  }
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
+ validateAsync(_value, _state, _context) {
16
+ return __awaiter(this, void 0, void 0, function* () {
17
+ return null;
18
+ });
19
+ }
20
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
21
+ getAsyncValidationPaths(field, _state) {
22
+ return [String(field)];
23
+ }
5
24
  }
@@ -0,0 +1,22 @@
1
+ import { BaseRequest } from '../../../../requests/BaseRequest';
2
+ import { BaseResponse } from '../../../../requests/responses/BaseResponse';
3
+ import { BaseRule, type AsyncValidationContext } from './BaseRule';
4
+ export interface PrecognitiveValidationErrorBody {
5
+ errors?: Record<string, string[]>;
6
+ }
7
+ export interface PrecognitiveRuleOptions<FormBody extends object> {
8
+ validateOnly?: string[] | ((field: keyof FormBody, state: FormBody) => string[]);
9
+ acceptHeader?: string;
10
+ resolveErrors?: (body: PrecognitiveValidationErrorBody) => Record<string, string[]>;
11
+ }
12
+ type PrecognitiveRequestFactory = () => BaseRequest<unknown, PrecognitiveValidationErrorBody, unknown, BaseResponse<unknown>, object, object>;
13
+ export declare class PrecognitiveRule<FormBody extends object> extends BaseRule<FormBody> {
14
+ private readonly requestFactory;
15
+ private readonly options;
16
+ constructor(requestFactory: PrecognitiveRequestFactory, options?: PrecognitiveRuleOptions<FormBody>);
17
+ validate(): boolean;
18
+ getMessage(): string;
19
+ getAsyncValidationPaths(field: keyof FormBody, state: FormBody): string[];
20
+ validateAsync(_value: unknown, state: FormBody, context: AsyncValidationContext): Promise<Record<string, string[]> | null>;
21
+ }
22
+ export {};
@@ -0,0 +1,58 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { ValidationException } from '../../../../requests/exceptions/ValidationException';
11
+ import { BaseRule } from './BaseRule';
12
+ export class PrecognitiveRule extends BaseRule {
13
+ constructor(requestFactory, options = {}) {
14
+ super();
15
+ this.requestFactory = requestFactory;
16
+ this.options = options;
17
+ }
18
+ validate() {
19
+ return true;
20
+ }
21
+ getMessage() {
22
+ return '';
23
+ }
24
+ getAsyncValidationPaths(field, state) {
25
+ const validateOnly = this.options.validateOnly;
26
+ if (Array.isArray(validateOnly)) {
27
+ return [...validateOnly];
28
+ }
29
+ if (typeof validateOnly === 'function') {
30
+ return validateOnly(field, state);
31
+ }
32
+ return [String(field)];
33
+ }
34
+ validateAsync(_value, state, context) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ var _a, _b, _c, _d, _e;
37
+ const request = this.requestFactory();
38
+ const validateOnly = this.getAsyncValidationPaths(context.field, state);
39
+ request.setBody(context.payload);
40
+ request.setHeaders({
41
+ Accept: (_a = this.options.acceptHeader) !== null && _a !== void 0 ? _a : 'application/json',
42
+ Precognition: 'true',
43
+ 'Precognition-Validate-Only': validateOnly.join(',')
44
+ });
45
+ try {
46
+ yield request.send({ resolveBody: false });
47
+ return {};
48
+ }
49
+ catch (error) {
50
+ if (error instanceof ValidationException) {
51
+ const body = error.getBody();
52
+ return (_e = (_d = (_c = (_b = this.options).resolveErrors) === null || _c === void 0 ? void 0 : _c.call(_b, body)) !== null && _d !== void 0 ? _d : body.errors) !== null && _e !== void 0 ? _e : {};
53
+ }
54
+ throw error;
55
+ }
56
+ });
57
+ }
58
+ }
@@ -2,10 +2,13 @@ import { type BaseRule } from '../rules/BaseRule';
2
2
  import { type BidirectionalRule } from './BidirectionalRule';
3
3
  import { type ValidationMode } from '../ValidationMode.enum';
4
4
  type Rule<FormBody extends object> = BaseRule<FormBody> & Partial<BidirectionalRule<FormBody>>;
5
+ type ValidationGroupPath<FormBody extends object> = Extract<keyof FormBody, string> | string;
5
6
  export type ValidationRules<FormBody extends object> = Partial<Record<keyof FormBody, {
6
7
  rules: Rule<FormBody>[];
7
8
  options?: {
8
9
  mode?: ValidationMode;
10
+ asyncDebounceMs?: number;
9
11
  };
10
12
  }>>;
13
+ export type ValidationGroups<FormBody extends object> = Partial<Record<string, ValidationGroupPath<FormBody>[]>>;
11
14
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueprint-ts/core",
3
- "version": "4.1.0-beta.3",
3
+ "version": "4.1.0-beta.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"