@databehave/schema 0.1.3 → 0.2.0

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 (123) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +12 -1
  3. package/dist/dataset/dataset.d.ts +6 -0
  4. package/dist/dataset/dataset.d.ts.map +1 -0
  5. package/dist/dataset/dataset.js +6 -0
  6. package/dist/dataset/dataset.js.map +1 -0
  7. package/dist/dataset/generate.d.ts +49 -0
  8. package/dist/dataset/generate.d.ts.map +1 -0
  9. package/dist/dataset/generate.js +96 -0
  10. package/dist/dataset/generate.js.map +1 -0
  11. package/dist/dataset/relate.d.ts +32 -0
  12. package/dist/dataset/relate.d.ts.map +1 -0
  13. package/dist/dataset/relate.js +46 -0
  14. package/dist/dataset/relate.js.map +1 -0
  15. package/dist/foundation/axes.d.ts +96 -0
  16. package/dist/foundation/axes.d.ts.map +1 -0
  17. package/dist/foundation/axes.js +16 -0
  18. package/dist/foundation/axes.js.map +1 -0
  19. package/dist/foundation/errors.d.ts +101 -0
  20. package/dist/foundation/errors.d.ts.map +1 -0
  21. package/dist/foundation/errors.js +91 -0
  22. package/dist/foundation/errors.js.map +1 -0
  23. package/dist/foundation/hash.d.ts +12 -0
  24. package/dist/foundation/hash.d.ts.map +1 -0
  25. package/dist/foundation/hash.js +21 -0
  26. package/dist/foundation/hash.js.map +1 -0
  27. package/dist/foundation/ir.d.ts +99 -0
  28. package/dist/foundation/ir.d.ts.map +1 -0
  29. package/dist/foundation/ir.js +16 -0
  30. package/dist/foundation/ir.js.map +1 -0
  31. package/dist/foundation/prng.d.ts +27 -0
  32. package/dist/foundation/prng.d.ts.map +1 -0
  33. package/dist/foundation/prng.js +56 -0
  34. package/dist/foundation/prng.js.map +1 -0
  35. package/dist/foundation/types.d.ts +116 -0
  36. package/dist/foundation/types.d.ts.map +1 -0
  37. package/dist/foundation/types.js +155 -0
  38. package/dist/foundation/types.js.map +1 -0
  39. package/dist/foundation/walk.d.ts +8 -0
  40. package/dist/foundation/walk.d.ts.map +1 -0
  41. package/dist/foundation/walk.js +9 -0
  42. package/dist/foundation/walk.js.map +1 -0
  43. package/dist/generator/engine.d.ts +69 -0
  44. package/dist/generator/engine.d.ts.map +1 -0
  45. package/dist/generator/engine.js +4 -0
  46. package/dist/generator/engine.js.map +1 -0
  47. package/dist/generator/replay.d.ts +38 -0
  48. package/dist/generator/replay.d.ts.map +1 -0
  49. package/dist/generator/replay.js +44 -0
  50. package/dist/generator/replay.js.map +1 -0
  51. package/dist/generator/trace.d.ts +54 -0
  52. package/dist/generator/trace.d.ts.map +1 -0
  53. package/dist/generator/trace.js +38 -0
  54. package/dist/generator/trace.js.map +1 -0
  55. package/dist/index.d.ts +37 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +40 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal.d.ts +22 -0
  60. package/dist/internal.d.ts.map +1 -0
  61. package/dist/internal.js +25 -0
  62. package/dist/internal.js.map +1 -0
  63. package/dist/plugin.d.ts +7 -0
  64. package/dist/plugin.d.ts.map +1 -0
  65. package/dist/plugin.js +14 -0
  66. package/dist/plugin.js.map +1 -0
  67. package/dist/runtime/contracts.d.ts +101 -0
  68. package/dist/runtime/contracts.d.ts.map +1 -0
  69. package/dist/runtime/contracts.js +67 -0
  70. package/dist/runtime/contracts.js.map +1 -0
  71. package/dist/runtime/runtime.d.ts +56 -0
  72. package/dist/runtime/runtime.d.ts.map +1 -0
  73. package/dist/runtime/runtime.js +392 -0
  74. package/dist/runtime/runtime.js.map +1 -0
  75. package/dist/runtime.d.ts +7 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +5 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/schema/composites.d.ts +104 -0
  80. package/dist/schema/composites.d.ts.map +1 -0
  81. package/dist/schema/composites.js +134 -0
  82. package/dist/schema/composites.js.map +1 -0
  83. package/dist/schema/conditional.d.ts +28 -0
  84. package/dist/schema/conditional.d.ts.map +1 -0
  85. package/dist/schema/conditional.js +52 -0
  86. package/dist/schema/conditional.js.map +1 -0
  87. package/dist/schema/decimal.d.ts +33 -0
  88. package/dist/schema/decimal.d.ts.map +1 -0
  89. package/dist/schema/decimal.js +39 -0
  90. package/dist/schema/decimal.js.map +1 -0
  91. package/dist/schema/primitives.d.ts +47 -0
  92. package/dist/schema/primitives.d.ts.map +1 -0
  93. package/dist/schema/primitives.js +78 -0
  94. package/dist/schema/primitives.js.map +1 -0
  95. package/dist/standard/behaviors.d.ts +6 -0
  96. package/dist/standard/behaviors.d.ts.map +1 -0
  97. package/dist/standard/behaviors.js +358 -0
  98. package/dist/standard/behaviors.js.map +1 -0
  99. package/dist/standard/generation.d.ts +53 -0
  100. package/dist/standard/generation.d.ts.map +1 -0
  101. package/dist/standard/generation.js +359 -0
  102. package/dist/standard/generation.js.map +1 -0
  103. package/dist/standard/kinds.d.ts +6 -0
  104. package/dist/standard/kinds.d.ts.map +1 -0
  105. package/dist/standard/kinds.js +178 -0
  106. package/dist/standard/kinds.js.map +1 -0
  107. package/dist/standard/runtime.d.ts +3 -0
  108. package/dist/standard/runtime.d.ts.map +1 -0
  109. package/dist/standard/runtime.js +9 -0
  110. package/dist/standard/runtime.js.map +1 -0
  111. package/dist/standard/validation.d.ts +44 -0
  112. package/dist/standard/validation.d.ts.map +1 -0
  113. package/dist/standard/validation.js +353 -0
  114. package/dist/standard/validation.js.map +1 -0
  115. package/dist/standard.d.ts +5 -0
  116. package/dist/standard.d.ts.map +1 -0
  117. package/dist/standard.js +5 -0
  118. package/dist/standard.js.map +1 -0
  119. package/dist/validator/parse.d.ts +13 -0
  120. package/dist/validator/parse.d.ts.map +1 -0
  121. package/dist/validator/parse.js +4 -0
  122. package/dist/validator/parse.js.map +1 -0
  123. package/package.json +26 -4
package/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ Upcoming package version: **0.2.0**. This section describes planned release contents, not a published release.
11
+
12
+ ### Breaking changes
13
+
14
+ - Require Node.js 24 or 26; support and test majors 24 and 26. Drop Node.js 18, 20 and 22.
15
+ - Replace the closed IR node union with open `SchemaNode` and built-in `StandardSchemaNode` contracts. Serialized IR
16
+ version 2 records exact kind/behavior contracts; version 1 requires explicit migration.
17
+ - Allow plugin-defined issue codes and enforce primitive invariants during validation.
18
+
19
+ ### Added
20
+
21
+ - Sealed instance-local kind and behavior registries, typed descriptors, scoped sampling and runtime methods for
22
+ nested generation, validation, datasets, traversal, replay and serialization.
23
+ - `/runtime`, `/standard` and optional `/plugin` entry points. Standard convenience builders/functions remain available.
24
+ - Public plugin, runtime support and migration guides, plus runnable public API examples.
25
+
26
+ See [migration](../../MIGRATION.md) and [release procedures](../../docs/releasing.md).
27
+
10
28
  ## [0.1.0] - 2026-06-03
11
29
 
12
30
  Initial public release of `@databehave/schema`.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@databehave/schema.svg)](https://www.npmjs.com/package/@databehave/schema)
10
10
  [![CI](https://github.com/ochairo/databehave/actions/workflows/ci.yml/badge.svg)](https://github.com/ochairo/databehave/actions/workflows/ci.yml)
11
- [![Node.js](https://img.shields.io/badge/node-%E2%89%A518-brightgreen)](https://nodejs.org/)
11
+ [![Node.js](https://img.shields.io/badge/node-24%20%7C%2026-brightgreen)](https://nodejs.org/)
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
13
13
 
14
14
  </div>
@@ -17,6 +17,9 @@ Most schema libraries describe what shape your data has. @databehave/schema desc
17
17
 
18
18
  ## Install
19
19
 
20
+ The upcoming release requires Node.js 24 or 26; supported majors are 24 and 26.
21
+ See the [workspace documentation](../../docs/index.md) and [runnable schema example](../../examples/schema.mjs).
22
+
20
23
  ```sh
21
24
  npm install @databehave/schema
22
25
  # or: pnpm add @databehave/schema
@@ -108,3 +111,11 @@ const Tier = enum_(['A', 'B', 'C']).weighted([['A', 0.7], ['B', 0.2], ['C', 0.1]
108
111
  © 2026-present ochairo. See [LICENSE](LICENSE)
109
112
 
110
113
  </div>
114
+
115
+ ### Composable schema runtime
116
+
117
+ Use `createSchemaRuntime({ kinds, behaviors })` for explicit plugin composition. The `standardKinds` and
118
+ `standardBehaviors` groups use the same public descriptors as external kinds and behaviors. Runtime operations,
119
+ including nested generation, validation, traversal, datasets, replay and versioned serialization, retain the selected
120
+ registry. See [schema plugins](docs/extending.md) for authoring and migration details. The standalone runtime needs no
121
+ application host or HTTP transport.
@@ -0,0 +1,6 @@
1
+ import type { Infer, Schema } from '../foundation/types.js';
2
+ import { type DatasetOptions } from './generate.js';
3
+ export { identityFor, type DatasetOptions } from './generate.js';
4
+ /** Standard convenience composition; runtime.mockDataset binds a selected registry. */
5
+ export declare const mockDataset: <S extends Schema>(options: DatasetOptions<S>) => Infer<S>[];
6
+ //# sourceMappingURL=dataset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset.d.ts","sourceRoot":"","sources":["../../src/dataset/dataset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAE3D,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AAChE,uFAAuF;AACvF,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,KAAK,CAAC,CAAC,CAAC,EAAoC,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { mock } from '../generator/engine.js';
2
+ import { generateDataset } from './generate.js';
3
+ export { identityFor } from './generate.js';
4
+ /** Standard convenience composition; runtime.mockDataset binds a selected registry. */
5
+ export const mockDataset = (options) => generateDataset(options, mock);
6
+ //# sourceMappingURL=dataset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset.js","sourceRoot":"","sources":["../../src/dataset/dataset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAuB,MAAM,eAAe,CAAA;AACpE,OAAO,EAAE,WAAW,EAAuB,MAAM,eAAe,CAAA;AAChE,uFAAuF;AACvF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAmB,OAA0B,EAAc,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Dataset abstraction.
3
+ *
4
+ * A Dataset is a collection of records that share:
5
+ * - a per-record schema `S`
6
+ * - identity keys `K` (same identity → same value, cross-endpoint)
7
+ * - a record count `N`
8
+ * - aggregate invariants `J` (predicates over the full record list)
9
+ *
10
+ * Identity uniqueness contract:
11
+ * The dataset will not silently return two rows with the same identity
12
+ * tuple. When generation produces a colliding identity, the row is
13
+ * re-sampled with a fresh sub-seed up to `DEDUP_ATTEMPTS_PER_ROW` times;
14
+ * if still colliding, a `SchemaConflictError` is thrown (your identity
15
+ * domain is too small for `n`).
16
+ */
17
+ import type { SchemaRuntime } from '../runtime/runtime.js';
18
+ import type { Infer, Schema } from '../foundation/types.js';
19
+ export type DatasetOptions<S extends Schema> = {
20
+ /** Unique dataset name (participates in the identity seed). */
21
+ readonly name: string;
22
+ /** Record-level schema. */
23
+ readonly schema: S;
24
+ /** Field names whose tuple defines a record's identity. */
25
+ readonly identity: readonly string[];
26
+ /** Number of records to generate per `mockDataset` call. */
27
+ readonly n: number;
28
+ /** Aggregate invariants — applied to the full record list, with bounded retries. */
29
+ readonly invariants?: readonly ((rows: readonly Infer<S>[]) => boolean)[];
30
+ /** Caller-supplied context channel exposed to derived/invariants as `ctx.input`. */
31
+ readonly input?: Readonly<Record<string, unknown>>;
32
+ /** Optional seed prefix; if omitted, the dataset name is used. */
33
+ readonly seedPrefix?: string;
34
+ };
35
+ /**
36
+ * Generate `n` records that conform to the schema and satisfy all aggregate
37
+ * invariants. Each row carries a unique identity tuple (collisions are
38
+ * re-sampled; if the identity domain is exhausted, `SchemaConflictError`
39
+ * is thrown).
40
+ */
41
+ /** Dataset policy receives the selected runtime's generator. */
42
+ export declare const generateDataset: <S extends Schema>(opts: DatasetOptions<S>, generate: SchemaRuntime["mock"]) => Infer<S>[];
43
+ /**
44
+ * Produce the deterministic identity key for a record. Two records with the
45
+ * same identity values produce the same key. `null` and `undefined` are
46
+ * distinguished from each other.
47
+ */
48
+ export declare const identityFor: (datasetName: string, identityKeys: readonly string[], row: Record<string, unknown>) => string;
49
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/dataset/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAE3D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,4DAA4D;IAC5D,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAA;IACzE,oFAAoF;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAClD,kEAAkE;IAClE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAYD;;;;;GAKG;AACH,gEAAgE;AAChE,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU,aAAa,CAAC,MAAM,CAAC,KAAG,KAAK,CAAC,CAAC,CAAC,EA6CpH,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,GACtB,aAAa,MAAM,EACnB,cAAc,SAAS,MAAM,EAAE,EAC/B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,MAUF,CAAA"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Dataset abstraction.
3
+ *
4
+ * A Dataset is a collection of records that share:
5
+ * - a per-record schema `S`
6
+ * - identity keys `K` (same identity → same value, cross-endpoint)
7
+ * - a record count `N`
8
+ * - aggregate invariants `J` (predicates over the full record list)
9
+ *
10
+ * Identity uniqueness contract:
11
+ * The dataset will not silently return two rows with the same identity
12
+ * tuple. When generation produces a colliding identity, the row is
13
+ * re-sampled with a fresh sub-seed up to `DEDUP_ATTEMPTS_PER_ROW` times;
14
+ * if still colliding, a `SchemaConflictError` is thrown (your identity
15
+ * domain is too small for `n`).
16
+ */
17
+ import { SchemaConflictError } from '../foundation/errors.js';
18
+ import { identityKey } from '../foundation/hash.js';
19
+ /**
20
+ * Number of full dataset re-rolls on top of the initial attempt. Each retry
21
+ * uses a *different* per-row seed (the loop variable is folded into the seed
22
+ * string), so retries can absorb aggregate-invariant rejection without the
23
+ * user having to widen the schema. Raise this only after verifying that the
24
+ * declared invariants are satisfiable for the chosen `n`.
25
+ */
26
+ const MAX_DATASET_RETRIES = 1;
27
+ const DEDUP_ATTEMPTS_PER_ROW = 8;
28
+ /**
29
+ * Generate `n` records that conform to the schema and satisfy all aggregate
30
+ * invariants. Each row carries a unique identity tuple (collisions are
31
+ * re-sampled; if the identity domain is exhausted, `SchemaConflictError`
32
+ * is thrown).
33
+ */
34
+ /** Dataset policy receives the selected runtime's generator. */
35
+ export const generateDataset = (opts, generate) => {
36
+ const totalAttempts = MAX_DATASET_RETRIES + 1;
37
+ for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
38
+ const seen = new Set();
39
+ const rows = [];
40
+ let collided = false;
41
+ for (let i = 0; i < opts.n; i += 1) {
42
+ let row;
43
+ let key = '';
44
+ for (let dedup = 0; dedup < DEDUP_ATTEMPTS_PER_ROW; dedup += 1) {
45
+ const rowSeed = `${opts.seedPrefix ?? opts.name}:row:${i}:attempt:${attempt}:dedup:${dedup}`;
46
+ const candidate = generate(opts.schema, {
47
+ seed: rowSeed,
48
+ index: i,
49
+ ...(opts.input !== undefined ? { input: opts.input } : {}),
50
+ });
51
+ const k = identityFor(opts.name, opts.identity, candidate);
52
+ if (!seen.has(k)) {
53
+ row = candidate;
54
+ key = k;
55
+ break;
56
+ }
57
+ }
58
+ if (row === undefined) {
59
+ collided = true;
60
+ break;
61
+ }
62
+ seen.add(key);
63
+ rows.push(row);
64
+ }
65
+ if (collided) {
66
+ // Try next dataset-level attempt with a different seed family.
67
+ continue;
68
+ }
69
+ const invariants = opts.invariants ?? [];
70
+ if (invariants.every((j) => j(rows)))
71
+ return rows;
72
+ }
73
+ throw new SchemaConflictError(`dataset "${opts.name}": could not satisfy identity uniqueness or aggregate invariants ` +
74
+ `after ${MAX_DATASET_RETRIES + 1} dataset attempt(s) ` +
75
+ `× ${DEDUP_ATTEMPTS_PER_ROW} dedup retries`, [opts.name], 'widen the identity domain, lower n, or relax aggregate invariants');
76
+ };
77
+ /**
78
+ * Produce the deterministic identity key for a record. Two records with the
79
+ * same identity values produce the same key. `null` and `undefined` are
80
+ * distinguished from each other.
81
+ */
82
+ export const identityFor = (datasetName, identityKeys, row) => {
83
+ const parts = {};
84
+ for (const k of identityKeys) {
85
+ const v = row[k];
86
+ // Distinguish "missing" / "explicit null" / "explicit undefined".
87
+ if (v === undefined)
88
+ parts[k] = '__undef__';
89
+ else if (v === null)
90
+ parts[k] = '__null__';
91
+ else
92
+ parts[k] = JSON.stringify(v);
93
+ }
94
+ return identityKey('DATASET', datasetName, parts);
95
+ };
96
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/dataset/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Cross-dataset FK identity — declarative foreign-key lookup.
3
+ *
4
+ * `relate(rows, field)` returns a `DerivedFn` suitable for `.derivedFrom(...)`
5
+ * that picks one row from a previously-generated dataset and returns its
6
+ * `field` value. Selection is deterministic, driven by the generator seed,
7
+ * so re-runs with the same seed produce the same FK assignments.
8
+ *
9
+ * ```ts
10
+ * const Groups = mockDataset({ name: 'groups', schema: Group, identity: ['group_code'], n: 5 })
11
+ *
12
+ * const Item = obj({
13
+ * item_id: str(),
14
+ * group_code: str().derivedFrom(relate(Groups, 'group_code')), // FK
15
+ * })
16
+ * ```
17
+ *
18
+ * For ad-hoc cross-row selection (e.g. by index or condition), use the
19
+ * `pickBy` option.
20
+ */
21
+ import type { DerivedFn, GenContext } from '../foundation/axes.js';
22
+ export type RelateOptions = {
23
+ /**
24
+ * Strategy to pick a row.
25
+ * - `'random'` (default): seeded uniform sample
26
+ * - `'index'` : `rows[ctx.index % rows.length]`
27
+ * - `(ctx) => number` : custom index resolver
28
+ */
29
+ readonly pickBy?: 'random' | 'index' | ((ctx: GenContext) => number);
30
+ };
31
+ export declare const relate: <R extends Record<string, unknown>, K extends Extract<keyof R, string>>(rows: readonly R[], field: K, opts?: RelateOptions) => DerivedFn<R[K]>;
32
+ //# sourceMappingURL=relate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relate.d.ts","sourceRoot":"","sources":["../../src/dataset/relate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAGlE,MAAM,MAAM,aAAa,GAAG;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,UAAU,KAAK,MAAM,CAAC,CAAA;CACrE,CAAA;AAED,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAC1F,MAAM,SAAS,CAAC,EAAE,EAClB,OAAO,CAAC,EACR,OAAM,aAAkB,KACvB,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAqBhB,CAAA"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cross-dataset FK identity — declarative foreign-key lookup.
3
+ *
4
+ * `relate(rows, field)` returns a `DerivedFn` suitable for `.derivedFrom(...)`
5
+ * that picks one row from a previously-generated dataset and returns its
6
+ * `field` value. Selection is deterministic, driven by the generator seed,
7
+ * so re-runs with the same seed produce the same FK assignments.
8
+ *
9
+ * ```ts
10
+ * const Groups = mockDataset({ name: 'groups', schema: Group, identity: ['group_code'], n: 5 })
11
+ *
12
+ * const Item = obj({
13
+ * item_id: str(),
14
+ * group_code: str().derivedFrom(relate(Groups, 'group_code')), // FK
15
+ * })
16
+ * ```
17
+ *
18
+ * For ad-hoc cross-row selection (e.g. by index or condition), use the
19
+ * `pickBy` option.
20
+ */
21
+ import { mulberry32, seedFromString } from '../foundation/prng.js';
22
+ export const relate = (rows, field, opts = {}) => {
23
+ if (rows.length === 0) {
24
+ throw new RangeError(`relate: empty dataset (cannot pick field "${field}")`);
25
+ }
26
+ const strategy = opts.pickBy ?? 'random';
27
+ return (ctx) => {
28
+ let idx;
29
+ if (strategy === 'index') {
30
+ idx = (ctx.index ?? 0) % rows.length;
31
+ }
32
+ else if (typeof strategy === 'function') {
33
+ // `((x % n) + n) % n` normalises negative results into [0, n)
34
+ // without the cost of `Math.abs` (which also changes magnitude).
35
+ const n = rows.length;
36
+ idx = ((strategy(ctx) % n) + n) % n;
37
+ }
38
+ else {
39
+ // Deterministic uniform: derive a sub-RNG from the path-bound seed.
40
+ const rng = mulberry32(seedFromString(`${ctx.seed}|relate|${field}`));
41
+ idx = Math.floor(rng.next() * rows.length);
42
+ }
43
+ return rows[idx][field];
44
+ };
45
+ };
46
+ //# sourceMappingURL=relate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relate.js","sourceRoot":"","sources":["../../src/dataset/relate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAYlE,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,IAAkB,EAClB,KAAQ,EACR,OAAsB,EAAE,EACP,EAAE;IACnB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,UAAU,CAAC,6CAA6C,KAAK,IAAI,CAAC,CAAA;IAC9E,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAA;IACxC,OAAO,CAAC,GAAe,EAAQ,EAAE;QAC/B,IAAI,GAAW,CAAA;QACf,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzB,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;QACtC,CAAC;aAAM,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC1C,8DAA8D;YAC9D,iEAAiE;YACjE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;YACrB,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;QACrC,CAAC;aAAM,CAAC;YACN,oEAAoE;YACpE,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,KAAK,EAAE,CAAC,CAAC,CAAA;YACrE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5C,CAAC;QACD,OAAQ,IAAI,CAAC,GAAG,CAAO,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Data-schema axes.
3
+ *
4
+ * Each axis is optional metadata attached to a schema node via `Modifiers.axes`.
5
+ * The generator inspects axes during sampling; the validator inspects only
6
+ * `domain` (since distribution is a sampling hint, not a conformance constraint).
7
+ *
8
+ * Generation order: modifiers; derived; eventually; occasionally; bounded
9
+ * invariants around domain/distribution/kind sampling. Short-circuit values
10
+ * bypass invariants. Dataset identity is handled by the dataset policy.
11
+ */
12
+ /** A generation context exposed to derived functions and invariants. */
13
+ export type GenContext = {
14
+ /** Root value being built, accessible by JSON-pointer-like path. */
15
+ readonly root: unknown;
16
+ /** Sibling object for the current field (the immediate parent object's data). */
17
+ readonly parent: Readonly<Record<string, unknown>>;
18
+ /** Nearest enclosing array index. `undefined` when no array is in scope. */
19
+ readonly index?: number;
20
+ /** Caller-supplied context channel (free-form key/value, never sampled). */
21
+ readonly input?: Readonly<Record<string, unknown>>;
22
+ /** Stable seed string for this node (derived from path + identity). */
23
+ readonly seed: string;
24
+ };
25
+ /** A single-record predicate. Returns true if the value is acceptable. */
26
+ export type InvariantFn = (value: unknown, ctx: GenContext) => boolean;
27
+ /**
28
+ * A pure function computing a field's value from sibling/root data.
29
+ *
30
+ * The generic parameter is the produced value type. It defaults to
31
+ * `unknown` so call sites that do not annotate (most plugin code) keep
32
+ * working unchanged. Builders such as `relate(rows, field)` propagate
33
+ * the inferred field type through this parameter so that
34
+ * `int().derivedFrom(relate(stringRows, 'sku'))` becomes a TS error
35
+ * (`string` is not assignable to `number`).
36
+ */
37
+ export type DerivedFn<T = unknown> = (ctx: GenContext) => T;
38
+ /** A weighted distribution over discrete enum values. */
39
+ export type WeightedDistribution = {
40
+ readonly kind: 'weighted';
41
+ /** Map of value → weight (relative; need not sum to 1). */
42
+ readonly weights: ReadonlyArray<readonly [string | number | boolean, number]>;
43
+ };
44
+ /** A normal (Gaussian) distribution for numbers. */
45
+ export type NormalDistribution = {
46
+ readonly kind: 'normal';
47
+ readonly mean: number;
48
+ readonly stddev: number;
49
+ };
50
+ /** A typical-range distribution (uniform within [from, to], reachable subset of [min, max]). */
51
+ export type TypicalDistribution = {
52
+ readonly kind: 'typical';
53
+ readonly from: number;
54
+ readonly to: number;
55
+ };
56
+ /**
57
+ * An "occasionally" override: with probability `p`, the produced value is
58
+ * forced to `value`. Stacks before the base distribution.
59
+ */
60
+ export type OccasionalOverride = {
61
+ readonly value: unknown;
62
+ readonly p: number;
63
+ };
64
+ /**
65
+ * An "eventually" override: every `every` rows the produced value is
66
+ * forced to `value`, deterministically driven by `ctx.index`.
67
+ *
68
+ * Stacks before `occasionally`. Skipped when `ctx.index` is undefined.
69
+ */
70
+ export type EventuallyOverride = {
71
+ readonly value: unknown;
72
+ readonly every: number;
73
+ readonly offset?: number;
74
+ };
75
+ export type Distribution = WeightedDistribution | NormalDistribution | TypicalDistribution;
76
+ /** A closed candidate set the value must belong to (`D` in the formal model). */
77
+ export type DomainConstraint = {
78
+ readonly kind: 'values';
79
+ readonly values: readonly unknown[];
80
+ } | {
81
+ readonly kind: 'lookup';
82
+ readonly fromField: string;
83
+ readonly map: Readonly<Record<string, readonly unknown[]>>;
84
+ };
85
+ /** All axis metadata attached to a schema node. */
86
+ export type Axes = {
87
+ readonly distribution?: Distribution;
88
+ readonly occasionally?: readonly OccasionalOverride[];
89
+ readonly eventually?: readonly EventuallyOverride[];
90
+ readonly derived?: DerivedFn;
91
+ readonly invariants?: readonly InvariantFn[];
92
+ readonly domain?: DomainConstraint;
93
+ };
94
+ /** Merge standard axes through their owning behavior descriptors. */
95
+ export declare const mergeAxes: (a: Axes | undefined, b: Axes) => Axes;
96
+ //# sourceMappingURL=axes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axes.d.ts","sourceRoot":"","sources":["../../src/foundation/axes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { standardBehaviors } from '../standard/behaviors.js';
2
+ /** Merge standard axes through their owning behavior descriptors. */
3
+ export const mergeAxes = (a, b) => {
4
+ if (a === undefined)
5
+ return b;
6
+ let node = { kind: 'metadata', mods: { axes: a ?? {} } };
7
+ for (const [id, incoming] of Object.entries(b)) {
8
+ const descriptor = standardBehaviors.find((behavior) => behavior.id === id);
9
+ if (descriptor === undefined)
10
+ throw new TypeError(`missing schema behavior ${id}`);
11
+ const previous = descriptor.read(node);
12
+ node = descriptor.write(node, previous === undefined ? incoming : descriptor.merge(previous, incoming));
13
+ }
14
+ return node.mods?.axes ?? {};
15
+ };
16
+ //# sourceMappingURL=axes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axes.js","sourceRoot":"","sources":["../../src/foundation/axes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAqG5D,qEAAqE;AACrE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAmB,EAAE,CAAO,EAAQ,EAAE;IAC9D,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAiC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAA;IACtF,KAAK,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3E,IAAI,UAAU,KAAK,SAAS;YAAE,MAAM,IAAI,SAAS,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAA;QAClF,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;IACzG,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAA;AAC9B,CAAC,CAAA"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * databehave error hierarchy. All errors carry a JSON-pointer style `path` so that
3
+ * deeply nested failures can be reported precisely.
4
+ *
5
+ * ```
6
+ * Error
7
+ * └── DataBehaveError (abstract)
8
+ * ├── ConformError (runtime input did not conform)
9
+ * └── SchemaConflictError (schema is unsatisfiable / misused)
10
+ * ```
11
+ *
12
+ * Catch `DataBehaveError` to filter library-originated failures from generic
13
+ * `Error`s (e.g. callback bugs in `derivedFrom`/`invariant` are not wrapped).
14
+ */
15
+ /**
16
+ * Stable, machine-matchable identifier for a conformance failure.
17
+ *
18
+ * The standard catalog is **snapshot-locked** by
19
+ * `test/foundation/issue-codes.test.ts`. Each value is a kebab-case,
20
+ * dot-segmented identifier (`'<bucket>.<reason>'` or a bare token
21
+ * for cross-cutting issues) and is part of the SemVer contract:
22
+ *
23
+ * Plugins may add namespaced string codes through their registered validators.
24
+ *
25
+ * - Adding a new standard code is `MINOR` (the snapshot test must be updated
26
+ * in the same commit).
27
+ * - Renaming, removing, or changing the meaning of an existing code
28
+ * is `MAJOR`.
29
+ *
30
+ * `Issue.message` is **not** part of the contract — wording may
31
+ * change in any release. See `docs/stability.md` ("Issue messages
32
+ * are diagnostic-only").
33
+ */
34
+ export type IssueCode = 'array.expected' | 'array.length-mismatch' | 'array.too-long' | 'array.too-short' | 'boolean.expected' | 'decimal.expected' | 'decimal.not-numeric' | 'decimal.scale-exceeded' | 'derived.callback-threw' | 'derived.mismatch' | 'discriminated.expected-object' | 'discriminated.missing-tag' | 'discriminated.unknown-tag' | 'domain.lookup-mismatch' | 'domain.not-in-values' | 'enum.not-member' | 'invariant.callback-threw' | 'invariant.failed' | 'literal.not-equal' | 'null.expected' | 'number.expected' | 'number.not-integer' | 'number.too-large' | 'number.too-small' | 'object.expected' | 'required' | 'string.expected' | 'string.pattern-mismatch' | 'string.too-long' | 'string.too-short' | 'tuple.expected-array' | 'tuple.length-mismatch' | 'unexpected-null' | 'union.no-match' | (string & {});
35
+ export type Issue = {
36
+ readonly path: readonly (string | number)[];
37
+ /**
38
+ * Stable, machine-matchable identifier. Use this — together with
39
+ * {@link Issue.path} — for programmatic assertions and runtime
40
+ * branching. See {@link IssueCode} for the standard catalog and extension contract.
41
+ */
42
+ readonly code: IssueCode;
43
+ /**
44
+ * Human-readable diagnostic. **Not** part of the SemVer contract:
45
+ * wording may change in any release. For machine matching, use
46
+ * {@link Issue.code} and {@link Issue.path}.
47
+ */
48
+ readonly message: string;
49
+ readonly expected?: string;
50
+ readonly received?: unknown;
51
+ };
52
+ /**
53
+ * Abstract root of all errors thrown by databehave itself.
54
+ *
55
+ * @remarks
56
+ * Callbacks supplied by the user (e.g. `derivedFrom`, `invariant`) are
57
+ * reported as {@link Issue}s rather than wrapped: the original `Error` is
58
+ * surfaced in the issue's `message` and re-thrown only at the boundary.
59
+ */
60
+ export declare abstract class DataBehaveError extends Error {
61
+ /** JSON-pointer style location where the error originated. */
62
+ abstract readonly path: readonly (string | number)[];
63
+ /** Optional recovery hint, surfaced in the message and as a property. */
64
+ abstract readonly hint?: string;
65
+ }
66
+ /**
67
+ * Thrown by {@link parse} when a value does not conform to a schema.
68
+ * Use {@link safeParse} to receive a result rather than an exception.
69
+ */
70
+ export declare class ConformError extends DataBehaveError {
71
+ readonly issues: readonly Issue[];
72
+ /** Root-relative — issues carry their own per-field paths. */
73
+ readonly path: readonly (string | number)[];
74
+ readonly hint?: string;
75
+ constructor(issues: readonly Issue[]);
76
+ }
77
+ /**
78
+ * Thrown when an axis combination is unsatisfiable
79
+ * (e.g. invariants conflict with distribution, derived returns an
80
+ * out-of-domain value, or primitive bounds are impossible such as
81
+ * `int().min(10).max(5)`).
82
+ *
83
+ * Also thrown at schema-build time when a modifier does not apply to the
84
+ * schema kind (e.g. `obj({...}).weighted([...])`). See the capability
85
+ * matrix in `docs/stability.md`.
86
+ */
87
+ export declare class SchemaConflictError extends DataBehaveError {
88
+ readonly path: readonly (string | number)[];
89
+ readonly hint?: string;
90
+ /**
91
+ * Optional, machine-matchable identifier for cross-cutting failures
92
+ * raised outside the conform pipeline (e.g. `'serialize.closure-axis'`
93
+ * thrown by `serializeSchema` when an axis carries a non-JSON
94
+ * function value). Free-form `'<bucket>.<reason>'` slugs; not part of
95
+ * the closed {@link IssueCode} catalog because they describe builder /
96
+ * codec misuse rather than runtime conformance.
97
+ */
98
+ readonly code?: string;
99
+ constructor(message: string, path?: readonly (string | number)[], hint?: string, code?: string);
100
+ }
101
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/foundation/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,SAAS,GACjB,gBAAgB,GAChB,uBAAuB,GACvB,gBAAgB,GAChB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,qBAAqB,GACrB,wBAAwB,GACxB,wBAAwB,GACxB,kBAAkB,GAClB,+BAA+B,GAC/B,2BAA2B,GAC3B,2BAA2B,GAC3B,wBAAwB,GACxB,sBAAsB,GACtB,iBAAiB,GACjB,0BAA0B,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,eAAe,GACf,iBAAiB,GACjB,oBAAoB,GACpB,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,UAAU,GACV,iBAAiB,GACjB,yBAAyB,GACzB,iBAAiB,GACjB,kBAAkB,GAClB,sBAAsB,GACtB,uBAAuB,GACvB,iBAAiB,GACjB,gBAAgB,GAChB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjB,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED;;;;;;;GAOG;AACH,8BAAsB,eAAgB,SAAQ,KAAK;IACjD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IACpD,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChC;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAA;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAK;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;gBAEV,MAAM,EAAE,SAAS,KAAK,EAAE;CASrC;AAED;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACtD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;gBAGpB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAO,EACvC,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM;CAQhB"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * databehave error hierarchy. All errors carry a JSON-pointer style `path` so that
3
+ * deeply nested failures can be reported precisely.
4
+ *
5
+ * ```
6
+ * Error
7
+ * └── DataBehaveError (abstract)
8
+ * ├── ConformError (runtime input did not conform)
9
+ * └── SchemaConflictError (schema is unsatisfiable / misused)
10
+ * ```
11
+ *
12
+ * Catch `DataBehaveError` to filter library-originated failures from generic
13
+ * `Error`s (e.g. callback bugs in `derivedFrom`/`invariant` are not wrapped).
14
+ */
15
+ /**
16
+ * Abstract root of all errors thrown by databehave itself.
17
+ *
18
+ * @remarks
19
+ * Callbacks supplied by the user (e.g. `derivedFrom`, `invariant`) are
20
+ * reported as {@link Issue}s rather than wrapped: the original `Error` is
21
+ * surfaced in the issue's `message` and re-thrown only at the boundary.
22
+ */
23
+ export class DataBehaveError extends Error {
24
+ }
25
+ /**
26
+ * Thrown by {@link parse} when a value does not conform to a schema.
27
+ * Use {@link safeParse} to receive a result rather than an exception.
28
+ */
29
+ export class ConformError extends DataBehaveError {
30
+ issues;
31
+ /** Root-relative — issues carry their own per-field paths. */
32
+ path = [];
33
+ hint;
34
+ constructor(issues) {
35
+ const summary = issues.length === 1
36
+ ? formatIssue(issues[0])
37
+ : `${issues.length} issues:\n${issues.map((i) => ' - ' + formatIssue(i)).join('\n')}`;
38
+ super(summary);
39
+ this.name = 'ConformError';
40
+ this.issues = issues;
41
+ }
42
+ }
43
+ /**
44
+ * Thrown when an axis combination is unsatisfiable
45
+ * (e.g. invariants conflict with distribution, derived returns an
46
+ * out-of-domain value, or primitive bounds are impossible such as
47
+ * `int().min(10).max(5)`).
48
+ *
49
+ * Also thrown at schema-build time when a modifier does not apply to the
50
+ * schema kind (e.g. `obj({...}).weighted([...])`). See the capability
51
+ * matrix in `docs/stability.md`.
52
+ */
53
+ export class SchemaConflictError extends DataBehaveError {
54
+ path;
55
+ hint;
56
+ /**
57
+ * Optional, machine-matchable identifier for cross-cutting failures
58
+ * raised outside the conform pipeline (e.g. `'serialize.closure-axis'`
59
+ * thrown by `serializeSchema` when an axis carries a non-JSON
60
+ * function value). Free-form `'<bucket>.<reason>'` slugs; not part of
61
+ * the closed {@link IssueCode} catalog because they describe builder /
62
+ * codec misuse rather than runtime conformance.
63
+ */
64
+ code;
65
+ constructor(message, path = [], hint, code) {
66
+ super(hint ? `${message} (hint: ${hint})` : message);
67
+ this.name = 'SchemaConflictError';
68
+ this.path = path;
69
+ if (hint !== undefined)
70
+ this.hint = hint;
71
+ if (code !== undefined)
72
+ this.code = code;
73
+ }
74
+ }
75
+ const formatIssue = (i) => {
76
+ const p = i.path.length === 0 ? '(root)' : i.path.map((s) => String(s)).join('.');
77
+ const expected = i.expected !== undefined ? ` expected=${i.expected}` : '';
78
+ const received = 'received' in i
79
+ ? ` received=${safeStringify(i.received)}`
80
+ : '';
81
+ return `${p}: ${i.message}${expected}${received}`;
82
+ };
83
+ const safeStringify = (v) => {
84
+ try {
85
+ return JSON.stringify(v);
86
+ }
87
+ catch {
88
+ return String(v);
89
+ }
90
+ };
91
+ //# sourceMappingURL=errors.js.map