@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.
Files changed (88) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +12 -2
  3. package/dist/assert.cjs +99 -63
  4. package/dist/assert.d.cts +37 -0
  5. package/dist/assert.d.mts +37 -0
  6. package/dist/assert.d.ts +24 -3
  7. package/dist/assert.mjs +94 -64
  8. package/dist/assertions.cjs +283 -178
  9. package/dist/assertions.d.cts +56 -0
  10. package/dist/assertions.d.mts +56 -0
  11. package/dist/assertions.d.ts +43 -45
  12. package/dist/assertions.mjs +274 -201
  13. package/dist/checkers.cjs +23 -0
  14. package/dist/checkers.d.cts +8 -0
  15. package/dist/checkers.d.mts +8 -0
  16. package/dist/checkers.d.ts +1 -1
  17. package/dist/checkers.mjs +16 -0
  18. package/dist/combinators.cjs +305 -304
  19. package/dist/combinators.d.cts +16 -0
  20. package/dist/combinators.d.mts +16 -0
  21. package/dist/combinators.d.ts +15 -16
  22. package/dist/combinators.mjs +305 -314
  23. package/dist/constraints.cjs +23 -0
  24. package/dist/constraints.d.cts +4 -0
  25. package/dist/constraints.d.mts +4 -0
  26. package/dist/constraints.d.ts +2 -2
  27. package/dist/constraints.mjs +21 -0
  28. package/dist/extractors.cjs +6 -0
  29. package/dist/extractors.d.cts +2 -0
  30. package/dist/extractors.d.mts +2 -0
  31. package/dist/extractors.mjs +5 -0
  32. package/dist/index.cjs +140 -219
  33. package/dist/index.d.cts +12 -0
  34. package/dist/index.d.mts +12 -0
  35. package/dist/index.d.ts +9 -9
  36. package/dist/index.mjs +93 -222
  37. package/dist/json-schema.cjs +364 -489
  38. package/dist/json-schema.d.cts +14 -0
  39. package/dist/json-schema.d.mts +14 -0
  40. package/dist/json-schema.d.ts +3 -3
  41. package/dist/json-schema.mjs +365 -492
  42. package/dist/metadata.cjs +98 -7
  43. package/dist/metadata.d.cts +8 -0
  44. package/dist/metadata.d.mts +8 -0
  45. package/dist/metadata.d.ts +2 -2
  46. package/dist/metadata.mjs +93 -7
  47. package/dist/predicates.cjs +98 -41
  48. package/dist/predicates.d.cts +77 -0
  49. package/dist/predicates.d.mts +77 -0
  50. package/dist/predicates.d.ts +25 -4
  51. package/dist/predicates.mjs +92 -66
  52. package/dist/types/index.d.cts +984 -0
  53. package/dist/types/index.d.mts +984 -0
  54. package/dist/types/index.d.ts +984 -0
  55. package/dist/types/json-schema.d.cts +75 -0
  56. package/dist/types/json-schema.d.mts +75 -0
  57. package/dist/types/json-schema.d.ts +75 -0
  58. package/dist/violations.cjs +81 -0
  59. package/dist/violations.d.cts +29 -0
  60. package/dist/violations.d.mts +29 -0
  61. package/dist/violations.d.ts +1 -1
  62. package/dist/violations.mjs +80 -0
  63. package/docs/RELEASING.md +66 -0
  64. package/docs/en/00-index.md +2 -0
  65. package/docs/en/01-shape-api.md +35 -10
  66. package/docs/en/02-metadata-and-introspection.md +2 -1
  67. package/docs/en/03-violations.md +1 -1
  68. package/docs/en/04-json-schema-export.md +5 -0
  69. package/docs/en/05-public-api.md +35 -3
  70. package/docs/en/06-common-recipes.md +2 -1
  71. package/docs/en/07-ai-reference.md +5 -2
  72. package/docs/en/08-violation-code-types.md +28 -2
  73. package/docs/en/09-migration.md +75 -0
  74. package/docs/ru/00-index.md +2 -0
  75. package/docs/ru/01-shape-api.md +35 -10
  76. package/docs/ru/02-metadata-and-introspection.md +2 -1
  77. package/docs/ru/03-violations.md +1 -1
  78. package/docs/ru/04-json-schema-export.md +5 -0
  79. package/docs/ru/05-public-api.md +35 -3
  80. package/docs/ru/06-common-recipes.md +2 -1
  81. package/docs/ru/07-ai-reference.md +5 -2
  82. package/docs/ru/08-violation-code-types.md +28 -2
  83. package/docs/ru/09-migration.md +76 -0
  84. package/docs/ru/README.md +10 -2
  85. package/package.json +63 -37
  86. package/types/index.d.ts +275 -115
  87. package/dist/metadata.cjs.js +0 -130
  88. 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 {};
@@ -1,4 +1,4 @@
1
- import { Violation } from '../types';
1
+ import { Violation } from './types/index.js';
2
2
  type ViolationPath = readonly PropertyKey[];
3
3
  type ViolationTreeState<V extends Violation = Violation> = {
4
4
  readonly path: ViolationPath;
@@ -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.
@@ -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)
@@ -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 a synchronous object-level rule that runs only after the base shape has already validated successfully as an object.
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
- `refine(...)` is intentionally thin:
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
 
@@ -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 ValidationTuple<T> =
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(...)`.