@modulify/validator 0.2.1 → 0.3.1
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.
- package/CHANGELOG.md +29 -1
- package/README.md +12 -2
- package/dist/assert.cjs +99 -63
- package/dist/assert.d.cts +37 -0
- package/dist/assert.d.mts +37 -0
- package/dist/assert.d.ts +24 -3
- package/dist/assert.mjs +94 -64
- package/dist/assertions.cjs +283 -178
- package/dist/assertions.d.cts +56 -0
- package/dist/assertions.d.mts +56 -0
- package/dist/assertions.d.ts +43 -45
- package/dist/assertions.mjs +274 -201
- package/dist/checkers.cjs +23 -0
- package/dist/checkers.d.cts +8 -0
- package/dist/checkers.d.mts +8 -0
- package/dist/checkers.d.ts +1 -1
- package/dist/checkers.mjs +16 -0
- package/dist/combinators.cjs +305 -304
- package/dist/combinators.d.cts +16 -0
- package/dist/combinators.d.mts +16 -0
- package/dist/combinators.d.ts +15 -16
- package/dist/combinators.mjs +305 -314
- package/dist/constraints.cjs +23 -0
- package/dist/constraints.d.cts +4 -0
- package/dist/constraints.d.mts +4 -0
- package/dist/constraints.d.ts +2 -2
- package/dist/constraints.mjs +21 -0
- package/dist/extractors.cjs +6 -0
- package/dist/extractors.d.cts +2 -0
- package/dist/extractors.d.mts +2 -0
- package/dist/extractors.mjs +5 -0
- package/dist/index.cjs +140 -219
- package/dist/index.d.cts +12 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +93 -222
- package/dist/json-schema.cjs +364 -489
- package/dist/json-schema.d.cts +14 -0
- package/dist/json-schema.d.mts +14 -0
- package/dist/json-schema.d.ts +3 -3
- package/dist/json-schema.mjs +365 -492
- package/dist/metadata.cjs +98 -7
- package/dist/metadata.d.cts +8 -0
- package/dist/metadata.d.mts +8 -0
- package/dist/metadata.d.ts +2 -2
- package/dist/metadata.mjs +93 -7
- package/dist/predicates.cjs +98 -41
- package/dist/predicates.d.cts +77 -0
- package/dist/predicates.d.mts +77 -0
- package/dist/predicates.d.ts +25 -4
- package/dist/predicates.mjs +92 -66
- package/dist/types/index.d.cts +984 -0
- package/dist/types/index.d.mts +984 -0
- package/dist/types/index.d.ts +984 -0
- package/dist/types/json-schema.d.cts +75 -0
- package/dist/types/json-schema.d.mts +75 -0
- package/dist/types/json-schema.d.ts +75 -0
- package/dist/violations.cjs +81 -0
- package/dist/violations.d.cts +29 -0
- package/dist/violations.d.mts +29 -0
- package/dist/violations.d.ts +1 -1
- package/dist/violations.mjs +80 -0
- package/docs/RELEASING.md +66 -0
- package/docs/en/00-index.md +2 -0
- package/docs/en/01-shape-api.md +35 -10
- package/docs/en/02-metadata-and-introspection.md +2 -1
- package/docs/en/03-violations.md +1 -1
- package/docs/en/04-json-schema-export.md +5 -0
- package/docs/en/05-public-api.md +35 -3
- package/docs/en/06-common-recipes.md +2 -1
- package/docs/en/07-ai-reference.md +5 -2
- package/docs/en/08-violation-code-types.md +28 -2
- package/docs/en/09-migration.md +75 -0
- package/docs/ru/00-index.md +2 -0
- package/docs/ru/01-shape-api.md +35 -10
- package/docs/ru/02-metadata-and-introspection.md +2 -1
- package/docs/ru/03-violations.md +1 -1
- package/docs/ru/04-json-schema-export.md +5 -0
- package/docs/ru/05-public-api.md +35 -3
- package/docs/ru/06-common-recipes.md +2 -1
- package/docs/ru/07-ai-reference.md +5 -2
- package/docs/ru/08-violation-code-types.md +28 -2
- package/docs/ru/09-migration.md +76 -0
- package/docs/ru/README.md +10 -2
- package/package.json +63 -37
- package/types/index.d.ts +275 -115
- package/dist/metadata.cjs.js +0 -130
- package/dist/metadata.es.js +0 -131
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Constraint,
|
|
3
|
+
ConstraintDescriptor,
|
|
4
|
+
MaybeMany,
|
|
5
|
+
} from './index.cjs'
|
|
6
|
+
|
|
7
|
+
/** JSON Schema primitive `type` values used by `toJsonSchema(...)`. */
|
|
8
|
+
export type JsonSchemaTypeName =
|
|
9
|
+
| 'string'
|
|
10
|
+
| 'number'
|
|
11
|
+
| 'integer'
|
|
12
|
+
| 'boolean'
|
|
13
|
+
| 'object'
|
|
14
|
+
| 'array'
|
|
15
|
+
| 'null'
|
|
16
|
+
|
|
17
|
+
/** Minimal JSON Schema object shape emitted by the built-in exporter. */
|
|
18
|
+
export interface JsonSchema {
|
|
19
|
+
readonly $schema?: string;
|
|
20
|
+
readonly $id?: string;
|
|
21
|
+
readonly $comment?: string;
|
|
22
|
+
readonly title?: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly format?: string;
|
|
25
|
+
readonly default?: unknown;
|
|
26
|
+
readonly examples?: readonly unknown[];
|
|
27
|
+
readonly deprecated?: boolean;
|
|
28
|
+
readonly readOnly?: boolean;
|
|
29
|
+
readonly writeOnly?: boolean;
|
|
30
|
+
readonly type?: JsonSchemaTypeName | readonly JsonSchemaTypeName[];
|
|
31
|
+
readonly const?: unknown;
|
|
32
|
+
readonly enum?: readonly unknown[];
|
|
33
|
+
readonly allOf?: readonly JsonSchema[];
|
|
34
|
+
readonly anyOf?: readonly JsonSchema[];
|
|
35
|
+
readonly oneOf?: readonly JsonSchema[];
|
|
36
|
+
readonly properties?: Readonly<Record<string, JsonSchema>>;
|
|
37
|
+
readonly required?: readonly string[];
|
|
38
|
+
readonly additionalProperties?: boolean | JsonSchema;
|
|
39
|
+
readonly items?: JsonSchema;
|
|
40
|
+
readonly prefixItems?: readonly JsonSchema[];
|
|
41
|
+
readonly minItems?: number;
|
|
42
|
+
readonly maxItems?: number;
|
|
43
|
+
readonly minLength?: number;
|
|
44
|
+
readonly maxLength?: number;
|
|
45
|
+
readonly pattern?: string;
|
|
46
|
+
readonly minimum?: number;
|
|
47
|
+
readonly maximum?: number;
|
|
48
|
+
readonly multipleOf?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Controls how `toJsonSchema(...)` handles constraints that cannot be represented faithfully. */
|
|
52
|
+
export type JsonSchemaExportMode = 'bestEffort' | 'strict'
|
|
53
|
+
|
|
54
|
+
/** Options for the JSON Schema exporter. */
|
|
55
|
+
export interface ToJsonSchemaOptions {
|
|
56
|
+
readonly mode?: JsonSchemaExportMode
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Error thrown by `toJsonSchema(...)` in strict mode when a node cannot be exported faithfully. */
|
|
60
|
+
export declare class JsonSchemaExportError extends Error {
|
|
61
|
+
constructor(message: string, options: {
|
|
62
|
+
descriptor: ConstraintDescriptor
|
|
63
|
+
reason: string
|
|
64
|
+
path?: readonly PropertyKey[]
|
|
65
|
+
})
|
|
66
|
+
readonly descriptor: ConstraintDescriptor
|
|
67
|
+
readonly reason: string
|
|
68
|
+
readonly path: readonly PropertyKey[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Derives a JSON Schema object from one or many public constraint descriptors. */
|
|
72
|
+
export declare const toJsonSchema: <const C extends MaybeMany<Constraint>>(
|
|
73
|
+
constraints: C,
|
|
74
|
+
options?: ToJsonSchemaOptions
|
|
75
|
+
) => JsonSchema
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Constraint,
|
|
3
|
+
ConstraintDescriptor,
|
|
4
|
+
MaybeMany,
|
|
5
|
+
} from './index.mjs'
|
|
6
|
+
|
|
7
|
+
/** JSON Schema primitive `type` values used by `toJsonSchema(...)`. */
|
|
8
|
+
export type JsonSchemaTypeName =
|
|
9
|
+
| 'string'
|
|
10
|
+
| 'number'
|
|
11
|
+
| 'integer'
|
|
12
|
+
| 'boolean'
|
|
13
|
+
| 'object'
|
|
14
|
+
| 'array'
|
|
15
|
+
| 'null'
|
|
16
|
+
|
|
17
|
+
/** Minimal JSON Schema object shape emitted by the built-in exporter. */
|
|
18
|
+
export interface JsonSchema {
|
|
19
|
+
readonly $schema?: string;
|
|
20
|
+
readonly $id?: string;
|
|
21
|
+
readonly $comment?: string;
|
|
22
|
+
readonly title?: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly format?: string;
|
|
25
|
+
readonly default?: unknown;
|
|
26
|
+
readonly examples?: readonly unknown[];
|
|
27
|
+
readonly deprecated?: boolean;
|
|
28
|
+
readonly readOnly?: boolean;
|
|
29
|
+
readonly writeOnly?: boolean;
|
|
30
|
+
readonly type?: JsonSchemaTypeName | readonly JsonSchemaTypeName[];
|
|
31
|
+
readonly const?: unknown;
|
|
32
|
+
readonly enum?: readonly unknown[];
|
|
33
|
+
readonly allOf?: readonly JsonSchema[];
|
|
34
|
+
readonly anyOf?: readonly JsonSchema[];
|
|
35
|
+
readonly oneOf?: readonly JsonSchema[];
|
|
36
|
+
readonly properties?: Readonly<Record<string, JsonSchema>>;
|
|
37
|
+
readonly required?: readonly string[];
|
|
38
|
+
readonly additionalProperties?: boolean | JsonSchema;
|
|
39
|
+
readonly items?: JsonSchema;
|
|
40
|
+
readonly prefixItems?: readonly JsonSchema[];
|
|
41
|
+
readonly minItems?: number;
|
|
42
|
+
readonly maxItems?: number;
|
|
43
|
+
readonly minLength?: number;
|
|
44
|
+
readonly maxLength?: number;
|
|
45
|
+
readonly pattern?: string;
|
|
46
|
+
readonly minimum?: number;
|
|
47
|
+
readonly maximum?: number;
|
|
48
|
+
readonly multipleOf?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Controls how `toJsonSchema(...)` handles constraints that cannot be represented faithfully. */
|
|
52
|
+
export type JsonSchemaExportMode = 'bestEffort' | 'strict'
|
|
53
|
+
|
|
54
|
+
/** Options for the JSON Schema exporter. */
|
|
55
|
+
export interface ToJsonSchemaOptions {
|
|
56
|
+
readonly mode?: JsonSchemaExportMode
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Error thrown by `toJsonSchema(...)` in strict mode when a node cannot be exported faithfully. */
|
|
60
|
+
export declare class JsonSchemaExportError extends Error {
|
|
61
|
+
constructor(message: string, options: {
|
|
62
|
+
descriptor: ConstraintDescriptor
|
|
63
|
+
reason: string
|
|
64
|
+
path?: readonly PropertyKey[]
|
|
65
|
+
})
|
|
66
|
+
readonly descriptor: ConstraintDescriptor
|
|
67
|
+
readonly reason: string
|
|
68
|
+
readonly path: readonly PropertyKey[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Derives a JSON Schema object from one or many public constraint descriptors. */
|
|
72
|
+
export declare const toJsonSchema: <const C extends MaybeMany<Constraint>>(
|
|
73
|
+
constraints: C,
|
|
74
|
+
options?: ToJsonSchemaOptions
|
|
75
|
+
) => JsonSchema
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Constraint,
|
|
3
|
+
ConstraintDescriptor,
|
|
4
|
+
MaybeMany,
|
|
5
|
+
} from './index.js'
|
|
6
|
+
|
|
7
|
+
/** JSON Schema primitive `type` values used by `toJsonSchema(...)`. */
|
|
8
|
+
export type JsonSchemaTypeName =
|
|
9
|
+
| 'string'
|
|
10
|
+
| 'number'
|
|
11
|
+
| 'integer'
|
|
12
|
+
| 'boolean'
|
|
13
|
+
| 'object'
|
|
14
|
+
| 'array'
|
|
15
|
+
| 'null'
|
|
16
|
+
|
|
17
|
+
/** Minimal JSON Schema object shape emitted by the built-in exporter. */
|
|
18
|
+
export interface JsonSchema {
|
|
19
|
+
readonly $schema?: string;
|
|
20
|
+
readonly $id?: string;
|
|
21
|
+
readonly $comment?: string;
|
|
22
|
+
readonly title?: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly format?: string;
|
|
25
|
+
readonly default?: unknown;
|
|
26
|
+
readonly examples?: readonly unknown[];
|
|
27
|
+
readonly deprecated?: boolean;
|
|
28
|
+
readonly readOnly?: boolean;
|
|
29
|
+
readonly writeOnly?: boolean;
|
|
30
|
+
readonly type?: JsonSchemaTypeName | readonly JsonSchemaTypeName[];
|
|
31
|
+
readonly const?: unknown;
|
|
32
|
+
readonly enum?: readonly unknown[];
|
|
33
|
+
readonly allOf?: readonly JsonSchema[];
|
|
34
|
+
readonly anyOf?: readonly JsonSchema[];
|
|
35
|
+
readonly oneOf?: readonly JsonSchema[];
|
|
36
|
+
readonly properties?: Readonly<Record<string, JsonSchema>>;
|
|
37
|
+
readonly required?: readonly string[];
|
|
38
|
+
readonly additionalProperties?: boolean | JsonSchema;
|
|
39
|
+
readonly items?: JsonSchema;
|
|
40
|
+
readonly prefixItems?: readonly JsonSchema[];
|
|
41
|
+
readonly minItems?: number;
|
|
42
|
+
readonly maxItems?: number;
|
|
43
|
+
readonly minLength?: number;
|
|
44
|
+
readonly maxLength?: number;
|
|
45
|
+
readonly pattern?: string;
|
|
46
|
+
readonly minimum?: number;
|
|
47
|
+
readonly maximum?: number;
|
|
48
|
+
readonly multipleOf?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Controls how `toJsonSchema(...)` handles constraints that cannot be represented faithfully. */
|
|
52
|
+
export type JsonSchemaExportMode = 'bestEffort' | 'strict'
|
|
53
|
+
|
|
54
|
+
/** Options for the JSON Schema exporter. */
|
|
55
|
+
export interface ToJsonSchemaOptions {
|
|
56
|
+
readonly mode?: JsonSchemaExportMode
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Error thrown by `toJsonSchema(...)` in strict mode when a node cannot be exported faithfully. */
|
|
60
|
+
export declare class JsonSchemaExportError extends Error {
|
|
61
|
+
constructor(message: string, options: {
|
|
62
|
+
descriptor: ConstraintDescriptor
|
|
63
|
+
reason: string
|
|
64
|
+
path?: readonly PropertyKey[]
|
|
65
|
+
})
|
|
66
|
+
readonly descriptor: ConstraintDescriptor
|
|
67
|
+
readonly reason: string
|
|
68
|
+
readonly path: readonly PropertyKey[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Derives a JSON Schema object from one or many public constraint descriptors. */
|
|
72
|
+
export declare const toJsonSchema: <const C extends MaybeMany<Constraint>>(
|
|
73
|
+
constraints: C,
|
|
74
|
+
options?: ToJsonSchemaOptions
|
|
75
|
+
) => JsonSchema
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/violations.ts
|
|
2
|
+
var ROOT_PATH = Object.freeze([]);
|
|
3
|
+
var normalizePath = (path) => path ?? ROOT_PATH;
|
|
4
|
+
var isSamePath = (left, right = ROOT_PATH) => {
|
|
5
|
+
const normalizedLeft = normalizePath(left);
|
|
6
|
+
if (normalizedLeft.length !== right.length) return false;
|
|
7
|
+
return normalizedLeft.every((part, index) => Object.is(part, right[index]));
|
|
8
|
+
};
|
|
9
|
+
var isPathPrefix = (path, prefix) => {
|
|
10
|
+
if (prefix.length > path.length) return false;
|
|
11
|
+
return prefix.every((part, index) => Object.is(part, path[index]));
|
|
12
|
+
};
|
|
13
|
+
var createTreeState = (path) => ({
|
|
14
|
+
path: Object.freeze([...path]),
|
|
15
|
+
self: [],
|
|
16
|
+
subtree: [],
|
|
17
|
+
children: /* @__PURE__ */ new Map()
|
|
18
|
+
});
|
|
19
|
+
var TreeNode = class TreeNode {
|
|
20
|
+
constructor(state) {
|
|
21
|
+
this.path = state.path;
|
|
22
|
+
this.self = new ViolationCollection(state.self);
|
|
23
|
+
this.subtree = new ViolationCollection(state.subtree);
|
|
24
|
+
this.children = new Map([...state.children.entries()].map(([key, child]) => [key, new TreeNode(child)]));
|
|
25
|
+
}
|
|
26
|
+
at(path) {
|
|
27
|
+
if (!isPathPrefix(path, this.path)) return;
|
|
28
|
+
if (path.length === this.path.length) return this;
|
|
29
|
+
return this.children.get(path[this.path.length])?.at(path);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var buildTree = (violations) => {
|
|
33
|
+
const root = createTreeState(ROOT_PATH);
|
|
34
|
+
violations.forEach((violation) => {
|
|
35
|
+
const path = normalizePath(violation.path);
|
|
36
|
+
root.subtree.push(violation);
|
|
37
|
+
if (path.length === 0) {
|
|
38
|
+
root.self.push(violation);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let current = root;
|
|
42
|
+
const currentPath = [];
|
|
43
|
+
path.forEach((segment, index) => {
|
|
44
|
+
currentPath.push(segment);
|
|
45
|
+
let child = current.children.get(segment);
|
|
46
|
+
if (!child) {
|
|
47
|
+
child = createTreeState(currentPath);
|
|
48
|
+
current.children.set(segment, child);
|
|
49
|
+
}
|
|
50
|
+
child.subtree.push(violation);
|
|
51
|
+
if (index === path.length - 1) child.self.push(violation);
|
|
52
|
+
current = child;
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return new TreeNode(root);
|
|
56
|
+
};
|
|
57
|
+
var ViolationCollection = class ViolationCollection {
|
|
58
|
+
constructor(violations) {
|
|
59
|
+
this.violations = [...violations];
|
|
60
|
+
this.size = this.violations.length;
|
|
61
|
+
}
|
|
62
|
+
[Symbol.iterator]() {
|
|
63
|
+
return this.violations[Symbol.iterator]();
|
|
64
|
+
}
|
|
65
|
+
forEach(callback) {
|
|
66
|
+
this.violations.forEach((violation, index) => callback(violation, index, this));
|
|
67
|
+
}
|
|
68
|
+
map(callback) {
|
|
69
|
+
return this.violations.map((violation, index) => callback(violation, index, this));
|
|
70
|
+
}
|
|
71
|
+
at(path) {
|
|
72
|
+
return new ViolationCollection(this.violations.filter((violation) => isSamePath(violation.path, path)));
|
|
73
|
+
}
|
|
74
|
+
tree() {
|
|
75
|
+
return buildTree(this.violations);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var collection = (violations) => new ViolationCollection(violations);
|
|
79
|
+
//#endregion
|
|
80
|
+
exports.ViolationCollection = ViolationCollection;
|
|
81
|
+
exports.collection = collection;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Violation } from './types/index.cjs';
|
|
2
|
+
type ViolationPath = readonly PropertyKey[];
|
|
3
|
+
type ViolationTreeState<V extends Violation = Violation> = {
|
|
4
|
+
readonly path: ViolationPath;
|
|
5
|
+
readonly self: V[];
|
|
6
|
+
readonly subtree: V[];
|
|
7
|
+
readonly children: Map<PropertyKey, ViolationTreeState<V>>;
|
|
8
|
+
};
|
|
9
|
+
declare class TreeNode<V extends Violation = Violation> {
|
|
10
|
+
readonly path: readonly PropertyKey[];
|
|
11
|
+
readonly self: ViolationCollection<V>;
|
|
12
|
+
readonly subtree: ViolationCollection<V>;
|
|
13
|
+
readonly children: ReadonlyMap<PropertyKey, TreeNode<V>>;
|
|
14
|
+
constructor(state: ViolationTreeState<V>);
|
|
15
|
+
at(path: readonly PropertyKey[]): TreeNode<V> | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare class ViolationCollection<V extends Violation = Violation> implements Iterable<V> {
|
|
18
|
+
readonly size: number;
|
|
19
|
+
private readonly violations;
|
|
20
|
+
constructor(violations: readonly V[]);
|
|
21
|
+
[Symbol.iterator](): Iterator<V>;
|
|
22
|
+
forEach(callback: (violation: V, index: number, collection: ViolationCollection<V>) => void): void;
|
|
23
|
+
map<T>(callback: (violation: V, index: number, collection: ViolationCollection<V>) => T): T[];
|
|
24
|
+
at(path: readonly PropertyKey[]): ViolationCollection<V>;
|
|
25
|
+
tree(): TreeNode<V>;
|
|
26
|
+
}
|
|
27
|
+
export type ViolationTreeNode<V extends Violation = Violation> = TreeNode<V>;
|
|
28
|
+
export declare const collection: <V extends Violation>(violations: readonly V[]) => ViolationCollection<V>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Violation } from './types/index.mjs';
|
|
2
|
+
type ViolationPath = readonly PropertyKey[];
|
|
3
|
+
type ViolationTreeState<V extends Violation = Violation> = {
|
|
4
|
+
readonly path: ViolationPath;
|
|
5
|
+
readonly self: V[];
|
|
6
|
+
readonly subtree: V[];
|
|
7
|
+
readonly children: Map<PropertyKey, ViolationTreeState<V>>;
|
|
8
|
+
};
|
|
9
|
+
declare class TreeNode<V extends Violation = Violation> {
|
|
10
|
+
readonly path: readonly PropertyKey[];
|
|
11
|
+
readonly self: ViolationCollection<V>;
|
|
12
|
+
readonly subtree: ViolationCollection<V>;
|
|
13
|
+
readonly children: ReadonlyMap<PropertyKey, TreeNode<V>>;
|
|
14
|
+
constructor(state: ViolationTreeState<V>);
|
|
15
|
+
at(path: readonly PropertyKey[]): TreeNode<V> | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare class ViolationCollection<V extends Violation = Violation> implements Iterable<V> {
|
|
18
|
+
readonly size: number;
|
|
19
|
+
private readonly violations;
|
|
20
|
+
constructor(violations: readonly V[]);
|
|
21
|
+
[Symbol.iterator](): Iterator<V>;
|
|
22
|
+
forEach(callback: (violation: V, index: number, collection: ViolationCollection<V>) => void): void;
|
|
23
|
+
map<T>(callback: (violation: V, index: number, collection: ViolationCollection<V>) => T): T[];
|
|
24
|
+
at(path: readonly PropertyKey[]): ViolationCollection<V>;
|
|
25
|
+
tree(): TreeNode<V>;
|
|
26
|
+
}
|
|
27
|
+
export type ViolationTreeNode<V extends Violation = Violation> = TreeNode<V>;
|
|
28
|
+
export declare const collection: <V extends Violation>(violations: readonly V[]) => ViolationCollection<V>;
|
|
29
|
+
export {};
|
package/dist/violations.d.ts
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/violations.ts
|
|
2
|
+
var ROOT_PATH = Object.freeze([]);
|
|
3
|
+
var normalizePath = (path) => path ?? ROOT_PATH;
|
|
4
|
+
var isSamePath = (left, right = ROOT_PATH) => {
|
|
5
|
+
const normalizedLeft = normalizePath(left);
|
|
6
|
+
if (normalizedLeft.length !== right.length) return false;
|
|
7
|
+
return normalizedLeft.every((part, index) => Object.is(part, right[index]));
|
|
8
|
+
};
|
|
9
|
+
var isPathPrefix = (path, prefix) => {
|
|
10
|
+
if (prefix.length > path.length) return false;
|
|
11
|
+
return prefix.every((part, index) => Object.is(part, path[index]));
|
|
12
|
+
};
|
|
13
|
+
var createTreeState = (path) => ({
|
|
14
|
+
path: Object.freeze([...path]),
|
|
15
|
+
self: [],
|
|
16
|
+
subtree: [],
|
|
17
|
+
children: /* @__PURE__ */ new Map()
|
|
18
|
+
});
|
|
19
|
+
var TreeNode = class TreeNode {
|
|
20
|
+
constructor(state) {
|
|
21
|
+
this.path = state.path;
|
|
22
|
+
this.self = new ViolationCollection(state.self);
|
|
23
|
+
this.subtree = new ViolationCollection(state.subtree);
|
|
24
|
+
this.children = new Map([...state.children.entries()].map(([key, child]) => [key, new TreeNode(child)]));
|
|
25
|
+
}
|
|
26
|
+
at(path) {
|
|
27
|
+
if (!isPathPrefix(path, this.path)) return;
|
|
28
|
+
if (path.length === this.path.length) return this;
|
|
29
|
+
return this.children.get(path[this.path.length])?.at(path);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var buildTree = (violations) => {
|
|
33
|
+
const root = createTreeState(ROOT_PATH);
|
|
34
|
+
violations.forEach((violation) => {
|
|
35
|
+
const path = normalizePath(violation.path);
|
|
36
|
+
root.subtree.push(violation);
|
|
37
|
+
if (path.length === 0) {
|
|
38
|
+
root.self.push(violation);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let current = root;
|
|
42
|
+
const currentPath = [];
|
|
43
|
+
path.forEach((segment, index) => {
|
|
44
|
+
currentPath.push(segment);
|
|
45
|
+
let child = current.children.get(segment);
|
|
46
|
+
if (!child) {
|
|
47
|
+
child = createTreeState(currentPath);
|
|
48
|
+
current.children.set(segment, child);
|
|
49
|
+
}
|
|
50
|
+
child.subtree.push(violation);
|
|
51
|
+
if (index === path.length - 1) child.self.push(violation);
|
|
52
|
+
current = child;
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return new TreeNode(root);
|
|
56
|
+
};
|
|
57
|
+
var ViolationCollection = class ViolationCollection {
|
|
58
|
+
constructor(violations) {
|
|
59
|
+
this.violations = [...violations];
|
|
60
|
+
this.size = this.violations.length;
|
|
61
|
+
}
|
|
62
|
+
[Symbol.iterator]() {
|
|
63
|
+
return this.violations[Symbol.iterator]();
|
|
64
|
+
}
|
|
65
|
+
forEach(callback) {
|
|
66
|
+
this.violations.forEach((violation, index) => callback(violation, index, this));
|
|
67
|
+
}
|
|
68
|
+
map(callback) {
|
|
69
|
+
return this.violations.map((violation, index) => callback(violation, index, this));
|
|
70
|
+
}
|
|
71
|
+
at(path) {
|
|
72
|
+
return new ViolationCollection(this.violations.filter((violation) => isSamePath(violation.path, path)));
|
|
73
|
+
}
|
|
74
|
+
tree() {
|
|
75
|
+
return buildTree(this.violations);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var collection = (violations) => new ViolationCollection(violations);
|
|
79
|
+
//#endregion
|
|
80
|
+
export { ViolationCollection, collection };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Releasing @modulify/validator
|
|
2
|
+
|
|
3
|
+
Use Node.js 22.14+ or 24+ and the Yarn version pinned in `package.json`.
|
|
4
|
+
Install dependencies with `yarn install --immutable`.
|
|
5
|
+
|
|
6
|
+
The repository uses `@modulify/conventional-release` to calculate the version,
|
|
7
|
+
update `package.json` and `CHANGELOG.md`, and create a release commit with an
|
|
8
|
+
annotated `v<version>` tag. npm publication and GitHub Releases belong to CI.
|
|
9
|
+
|
|
10
|
+
Preview a release without changing files, commits, or tags:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
yarn release:dry
|
|
14
|
+
yarn release:dry --prerelease rc
|
|
15
|
+
yarn release:dry --release-as minor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Automatic version recommendations below 1.0 follow pre-major semantics:
|
|
19
|
+
breaking changes advance the minor version; features and fixes advance the patch.
|
|
20
|
+
Explicit `--release-as major` produces 1.0.0. Release configuration lives in
|
|
21
|
+
`release.config.mjs`; dependencies are installed before releasing, so the version
|
|
22
|
+
bump does not rerun installation or update the lockfile.
|
|
23
|
+
|
|
24
|
+
## GitHub Actions
|
|
25
|
+
|
|
26
|
+
Start the `Release` workflow manually:
|
|
27
|
+
|
|
28
|
+
- `release`: `auto`, `patch`, `minor`, or `major`;
|
|
29
|
+
- `prerelease`: `none`, `alpha`, `beta`, or `rc`;
|
|
30
|
+
- `npm_tag`: `auto` selects `latest` for stable releases and the prerelease channel
|
|
31
|
+
otherwise; an explicit dist-tag can override it.
|
|
32
|
+
|
|
33
|
+
Stable releases run only from `main`. Prereleases may run from other branches,
|
|
34
|
+
but cannot publish to `latest`. Releases are serialized across branches.
|
|
35
|
+
|
|
36
|
+
CI runs lint, strict type checks, runtime and type tests, and packed consumer
|
|
37
|
+
checks before creating the release. It atomically pushes the release commit and
|
|
38
|
+
its tag. A separate job checks out that exact tag, repeats the checks, builds the
|
|
39
|
+
package, and publishes through npm OIDC. A GitHub Release is created after npm
|
|
40
|
+
publication succeeds; prereleases are marked accordingly.
|
|
41
|
+
|
|
42
|
+
If publication or GitHub Release creation fails after the tag was pushed, use
|
|
43
|
+
GitHub Actions **Re-run failed jobs** on the original run. Publication checks the
|
|
44
|
+
registry for the exact version and skips a version already published. Registry
|
|
45
|
+
lookup errors stop the job. Starting a new workflow run prepares another release.
|
|
46
|
+
|
|
47
|
+
## npm Trusted Publisher
|
|
48
|
+
|
|
49
|
+
The package already has this trusted publisher configured (verified 2026-09-26):
|
|
50
|
+
|
|
51
|
+
| Field | Value |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| Provider | GitHub Actions |
|
|
54
|
+
| Organization | `modulify` |
|
|
55
|
+
| Repository | `validator` |
|
|
56
|
+
| Workflow filename | `release.yml` |
|
|
57
|
+
| Allowed actions | `npm publish`, `npm stage publish` |
|
|
58
|
+
|
|
59
|
+
Keep the workflow filename aligned with this package-level configuration.
|
|
60
|
+
The publishing job uses a GitHub-hosted runner, Node.js 24, npm 11.5.1 or newer,
|
|
61
|
+
and `id-token: write`. No `NPM_TOKEN` or `NODE_AUTH_TOKEN` secret is used for
|
|
62
|
+
publication. npm generates provenance automatically for this public package
|
|
63
|
+
and repository. See [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
64
|
+
|
|
65
|
+
A local authenticated npm session is separate from CI OIDC. Release previews
|
|
66
|
+
and repository-local versioning do not require npm authentication.
|
package/docs/en/00-index.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
- [AI Reference](./07-ai-reference.md) - Compact contract summary for AI agents, tooling, and quick lookup of stable library semantics.
|
|
10
10
|
- [Violation Code Types](./08-violation-code-types.md) - Detailed guide to `ViolationCodeRegistry`, `ViolationCode`, literal code preservation in descriptors, and external registry augmentation.
|
|
11
11
|
|
|
12
|
+
- [Migration From 0.2.1](./09-migration.md) - Breaking changes, renamed types, and updated validation contracts.
|
|
13
|
+
|
|
12
14
|
## Translations
|
|
13
15
|
|
|
14
16
|
- [Russian](../ru/00-index.md)
|
package/docs/en/01-shape-api.md
CHANGED
|
@@ -49,6 +49,8 @@ Each field in the descriptor may contain:
|
|
|
49
49
|
- an array of constraints that run sequentially;
|
|
50
50
|
- another structural validator such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, or `discriminatedUnion(...)`.
|
|
51
51
|
|
|
52
|
+
For assertion arrays, sequencing is stage-aware: guard assertions establish the field domain first, and refinement assertions must be compatible with that domain.
|
|
53
|
+
|
|
52
54
|
That means object validation stays aligned with the rest of the library:
|
|
53
55
|
|
|
54
56
|
- field-level checks reuse the same assertions and combinators;
|
|
@@ -79,6 +81,11 @@ Runtime behavior:
|
|
|
79
81
|
- nested violations are reported on the field path;
|
|
80
82
|
- unknown keys are allowed by default.
|
|
81
83
|
|
|
84
|
+
Type-level behavior:
|
|
85
|
+
|
|
86
|
+
- `[isString, hasLength({ min: 8 })]` is valid;
|
|
87
|
+
- `[isNumber, hasLength({ min: 8 })]` is rejected by TypeScript before runtime.
|
|
88
|
+
|
|
82
89
|
## Unknown Keys
|
|
83
90
|
|
|
84
91
|
Shapes have two unknown-key modes:
|
|
@@ -190,13 +197,36 @@ Shapes can express cross-field invariants without introducing a second schema la
|
|
|
190
197
|
|
|
191
198
|
### `refine(...)`
|
|
192
199
|
|
|
193
|
-
`refine(...)` adds
|
|
200
|
+
`refine(...)` adds an async-first object-level rule that runs only after the base shape has already validated successfully as an object.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const registration = shape({
|
|
204
|
+
email: isString,
|
|
205
|
+
}).refine(async value => {
|
|
206
|
+
const taken = await users.has(value.email)
|
|
207
|
+
|
|
208
|
+
return taken
|
|
209
|
+
? [{ path: ['email'], code: 'user.email.taken' }]
|
|
210
|
+
: []
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`refine(...)` is intentionally thin:
|
|
215
|
+
|
|
216
|
+
- it accepts sync or async callbacks;
|
|
217
|
+
- it returns `[]`, `null`, or `undefined` when the rule passes;
|
|
218
|
+
- it returns one issue or an array of issues when it fails;
|
|
219
|
+
- `path` is relative to the current shape and defaults to `[]`;
|
|
220
|
+
- `value` is optional and defaults to the object value at that relative path;
|
|
221
|
+
- `code` stays machine-readable.
|
|
222
|
+
|
|
223
|
+
If you need an explicitly sync-safe object rule for `validate.sync(...)`, use `refine.sync(...)`:
|
|
194
224
|
|
|
195
225
|
```typescript
|
|
196
226
|
const registration = shape({
|
|
197
227
|
password: isString,
|
|
198
228
|
confirmPassword: isString,
|
|
199
|
-
}).refine(value => {
|
|
229
|
+
}).refine.sync(value => {
|
|
200
230
|
return value.password === value.confirmPassword
|
|
201
231
|
? []
|
|
202
232
|
: [{
|
|
@@ -207,14 +237,7 @@ const registration = shape({
|
|
|
207
237
|
})
|
|
208
238
|
```
|
|
209
239
|
|
|
210
|
-
`
|
|
211
|
-
|
|
212
|
-
- it is sync-only;
|
|
213
|
-
- it returns `[]`, `null`, or `undefined` when the rule passes;
|
|
214
|
-
- it returns one issue or an array of issues when it fails;
|
|
215
|
-
- `path` is relative to the current shape and defaults to `[]`;
|
|
216
|
-
- `value` is optional and defaults to the object value at that relative path;
|
|
217
|
-
- `code` stays machine-readable.
|
|
240
|
+
`validate.sync(...)`, `matches.sync(...)`, and `shape.check(...)` throw explicitly if they encounter an async `refine(...)` callback.
|
|
218
241
|
|
|
219
242
|
Produced violations use:
|
|
220
243
|
|
|
@@ -243,6 +266,8 @@ const registration = shape({
|
|
|
243
266
|
|
|
244
267
|
This shows up later in `describe(...)` under the shape `rules` array.
|
|
245
268
|
|
|
269
|
+
Rules registered through async-first `refine(...)` are marked with `async: true` in that descriptor array.
|
|
270
|
+
|
|
246
271
|
### `fieldsMatch(...)`
|
|
247
272
|
|
|
248
273
|
`fieldsMatch(...)` is a small helper for the common confirmation-field case.
|
|
@@ -182,7 +182,8 @@ Instead, shapes keep lightweight rule descriptors in `rules`.
|
|
|
182
182
|
Built-in examples:
|
|
183
183
|
|
|
184
184
|
- `fieldsMatch(...)` produces a compact `fieldsMatch` rule descriptor;
|
|
185
|
-
- `refine(...)` can accept a custom compact rule descriptor object
|
|
185
|
+
- `refine(...)` can accept a custom compact rule descriptor object;
|
|
186
|
+
- async-first `refine(...)` rules are marked with `async: true`.
|
|
186
187
|
|
|
187
188
|
This keeps the public descriptor tree stable and serializable enough for tooling, while avoiding the impossible task of serializing arbitrary callbacks.
|
|
188
189
|
|
package/docs/en/03-violations.md
CHANGED
|
@@ -120,7 +120,7 @@ This is one of the reasons the library can stay adapter-friendly without stringi
|
|
|
120
120
|
`validate(...)` and `validate.sync(...)` return:
|
|
121
121
|
|
|
122
122
|
```typescript
|
|
123
|
-
type
|
|
123
|
+
type ValidationResult<T> =
|
|
124
124
|
| [ok: true, validated: T, violations: []]
|
|
125
125
|
| [ok: false, validated: unknown, violations: Violation[]]
|
|
126
126
|
```
|
|
@@ -69,6 +69,9 @@ Supported practical mappings include:
|
|
|
69
69
|
|
|
70
70
|
- `isString` -> `type: 'string'`
|
|
71
71
|
- `isNumber` -> `type: 'number'`
|
|
72
|
+
- `isFiniteNumber` -> `type: 'number'`
|
|
73
|
+
- `isInteger` -> `type: 'integer'`
|
|
74
|
+
- `isSafeInteger` -> `type: 'integer'`, `minimum: Number.MIN_SAFE_INTEGER`, `maximum: Number.MAX_SAFE_INTEGER`
|
|
72
75
|
- `isBoolean` -> `type: 'boolean'`
|
|
73
76
|
- `isNull` -> `type: 'null'`
|
|
74
77
|
- `isEmail` -> `type: 'string'` plus `format: 'email'`
|
|
@@ -202,6 +205,8 @@ Important examples:
|
|
|
202
205
|
|
|
203
206
|
The exporter keeps these boundaries explicit instead of guessing.
|
|
204
207
|
|
|
208
|
+
`isValidDate`, `isError`, `isRegExp`, and `isPromiseLike` describe runtime values without a faithful JSON Schema representation. Strict mode throws `JsonSchemaExportError`; best-effort mode emits an unconstrained node. JSON numeric values are finite, so `isFiniteNumber` needs no additional schema keyword.
|
|
209
|
+
|
|
205
210
|
## Relationship To `describe(...)`
|
|
206
211
|
|
|
207
212
|
`toJsonSchema(...)` is downstream from `describe(...)`.
|