@zipbul/baker 3.1.0 → 3.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @zipbul/baker
2
2
 
3
+ ## 3.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 317e536: Add the `isStatelessRegExp` type-checker rule: a value is valid if it is a `RegExp`
8
+ without the `g` (global) or `y` (sticky) flag. Those two flags make
9
+ `RegExp.prototype.test`/`exec` mutate `lastIndex` across calls, so a regex carrying them
10
+ produces order-dependent results when reused as a single-shot matcher. `isStatelessRegExp`
11
+ rejects them at validation time (all other flags — `d`, `i`, `m`, `s`, `u`, `v` — are
12
+ stateless and pass). It is the safe-form sibling of `isRegExp` (which is unchanged).
13
+ Exported from `@zipbul/baker/rules`.
14
+
15
+ ## 3.2.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 6b07d8d: Add the `oneOf` rule combinator and `arrayEvery`, `isRegExp`, `isFunction` rules.
20
+
21
+ - `oneOf(...rules)` — OR combinator: a value is valid if it matches at least one of the
22
+ given rules (first match wins, short-circuit). Fills baker's union-validation gap for
23
+ native-type unions (e.g. `boolean | string | RegExp | ...`) that the object discriminator
24
+ can't express. Sync branches compile to an inlined `||`; an async branch makes the rule
25
+ async. Note: semantics is "matches at least one" — not JSON-Schema `oneOf` (exactly-one).
26
+ - `arrayEvery(...rules)` — value is an array and every element satisfies all given rules.
27
+ Composable as a rule (e.g. `oneOf(isString, arrayEvery(isString))` for `string | string[]`).
28
+ - `isRegExp` / `isFunction` — the two missing type-checker primitives (`instanceof RegExp`,
29
+ `typeof === 'function'`).
30
+
31
+ All exported from `@zipbul/baker/rules`.
32
+
3
33
  ## 3.1.0
4
34
 
5
35
  ### Minor Changes
@@ -0,0 +1,4 @@
1
+ import type { EmittableRule } from '../types';
2
+ declare function oneOf(...branches: EmittableRule[]): EmittableRule;
3
+ declare function arrayEvery(...rules: EmittableRule[]): EmittableRule;
4
+ export { oneOf, arrayEvery };
@@ -0,0 +1,111 @@
1
+ import { BakerError } from '../errors.js';
2
+ import { makeRule } from '../rule-plan.js';
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Helpers
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ /** Assert that a value is a baker rule (callable with `.emit` fn + `.ruleName` string). */
7
+ function assertRuleArg(value, combinator) {
8
+ if (typeof value === 'function' &&
9
+ typeof value.emit === 'function' &&
10
+ typeof value.ruleName === 'string') {
11
+ return;
12
+ }
13
+ throw new BakerError(`${combinator}: every argument must be a baker rule (function with .emit and .ruleName). Use createRule() or import a rule from @zipbul/baker/rules.`);
14
+ }
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // oneOf — OR combinator: value matches at least one of the given rules.
17
+ // (Not JSON-Schema `oneOf`/exactly-one — semantics is "matches at least one",
18
+ // first matching branch wins, short-circuit.)
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ function oneOf(...branches) {
21
+ if (branches.length === 0) {
22
+ throw new BakerError('oneOf requires at least one rule.');
23
+ }
24
+ for (const b of branches) {
25
+ assertRuleArg(b, 'oneOf');
26
+ }
27
+ const constraints = { oneOf: branches.map(b => b.ruleName) };
28
+ const isAsync = branches.some(b => b.isAsync === true);
29
+ if (isAsync) {
30
+ const validate = async (value) => {
31
+ for (const b of branches) {
32
+ if (await b(value)) {
33
+ return true;
34
+ }
35
+ }
36
+ return false;
37
+ };
38
+ return makeRule({
39
+ name: 'oneOf',
40
+ constraints,
41
+ isAsync: true,
42
+ validate,
43
+ emit: (varName, ctx) => {
44
+ const i = ctx.addRef(validate);
45
+ return `if (!(await refs[${i}](${varName}))) ${ctx.fail('oneOf')};`;
46
+ },
47
+ });
48
+ }
49
+ const validate = (value) => branches.some(b => b(value));
50
+ return makeRule({
51
+ name: 'oneOf',
52
+ constraints,
53
+ validate,
54
+ emit: (varName, ctx) => {
55
+ const calls = branches.map(b => `refs[${ctx.addRef(b)}](${varName})`).join(' || ');
56
+ return `if (!(${calls})) ${ctx.fail('oneOf')};`;
57
+ },
58
+ });
59
+ }
60
+ // ─────────────────────────────────────────────────────────────────────────────
61
+ // arrayEvery — value is an array and every element satisfies all given rules
62
+ // (AND over the rules, applied per element). Arrays only — Set/Map element
63
+ // validation stays at the @Field level via arrayOf.
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ function arrayEvery(...rules) {
66
+ if (rules.length === 0) {
67
+ throw new BakerError('arrayEvery requires at least one rule.');
68
+ }
69
+ for (const r of rules) {
70
+ assertRuleArg(r, 'arrayEvery');
71
+ }
72
+ const constraints = { arrayEvery: rules.map(r => r.ruleName) };
73
+ const isAsync = rules.some(r => r.isAsync === true);
74
+ if (isAsync) {
75
+ const validate = async (value) => {
76
+ if (!Array.isArray(value)) {
77
+ return false;
78
+ }
79
+ for (const el of value) {
80
+ for (const r of rules) {
81
+ if (!(await r(el))) {
82
+ return false;
83
+ }
84
+ }
85
+ }
86
+ return true;
87
+ };
88
+ return makeRule({
89
+ name: 'arrayEvery',
90
+ constraints,
91
+ isAsync: true,
92
+ validate,
93
+ emit: (varName, ctx) => {
94
+ const i = ctx.addRef(validate);
95
+ return `if (!(await refs[${i}](${varName}))) ${ctx.fail('arrayEvery')};`;
96
+ },
97
+ });
98
+ }
99
+ const elementPredicate = (el) => rules.every(r => r(el));
100
+ const validate = (value) => Array.isArray(value) && value.every(elementPredicate);
101
+ return makeRule({
102
+ name: 'arrayEvery',
103
+ constraints,
104
+ validate,
105
+ emit: (varName, ctx) => {
106
+ const i = ctx.addRef(elementPredicate);
107
+ return `if (!(Array.isArray(${varName}) && ${varName}.every(refs[${i}]))) ${ctx.fail('arrayEvery')};`;
108
+ },
109
+ });
110
+ }
111
+ export { oneOf, arrayEvery };
@@ -1,5 +1,6 @@
1
- export { isString, isNumber, isBoolean, isDate, isEnum, isInt, isArray, isObject } from './typechecker';
1
+ export { isString, isNumber, isBoolean, isDate, isEnum, isInt, isArray, isObject, isRegExp, isFunction, isStatelessRegExp, } from './typechecker';
2
2
  export type { IsNumberOptions } from './typechecker';
3
+ export { oneOf, arrayEvery } from './combinators';
3
4
  export { min, max, isPositive, isNegative, isDivisibleBy } from './number';
4
5
  export { minDate, maxDate } from './date';
5
6
  export { equals, notEquals, isEmpty, isNotEmpty, isIn, isNotIn } from './common';
@@ -1,4 +1,5 @@
1
- export { isString, isNumber, isBoolean, isDate, isEnum, isInt, isArray, isObject } from './typechecker.js';
1
+ export { isString, isNumber, isBoolean, isDate, isEnum, isInt, isArray, isObject, isRegExp, isFunction, isStatelessRegExp, } from './typechecker.js';
2
+ export { oneOf, arrayEvery } from './combinators.js';
2
3
  export { min, max, isPositive, isNegative, isDivisibleBy } from './number.js';
3
4
  export { minDate, maxDate } from './date.js';
4
5
  export { equals, notEquals, isEmpty, isNotEmpty, isIn, isNotIn } from './common.js';
@@ -12,3 +12,6 @@ export declare function isEnum(entity: object): EmittableRule;
12
12
  export declare const isInt: import("../types").InternalRule;
13
13
  export declare const isArray: import("../types").InternalRule;
14
14
  export declare const isObject: import("../types").InternalRule;
15
+ export declare const isRegExp: import("../types").InternalRule;
16
+ export declare const isFunction: import("../types").InternalRule;
17
+ export declare const isStatelessRegExp: import("../types").InternalRule;
@@ -141,3 +141,31 @@ export const isObject = makeRule({
141
141
  validate: value => typeof value === 'object' && value !== null && !Array.isArray(value),
142
142
  emit: (varName, ctx) => `if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${ctx.fail('isObject')};`,
143
143
  });
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+ // isRegExp — instanceof RegExp (self-narrowing, no typeof gate needed)
146
+ // ─────────────────────────────────────────────────────────────────────────────
147
+ export const isRegExp = makeRule({
148
+ name: 'isRegExp',
149
+ constraints: {},
150
+ validate: value => value instanceof RegExp,
151
+ emit: (varName, ctx) => `if (!(${varName} instanceof RegExp)) ${ctx.fail('isRegExp')};`,
152
+ });
153
+ // ─────────────────────────────────────────────────────────────────────────────
154
+ // isFunction — typeof check (accepts arrow fns, unlike isInstance(Function))
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ export const isFunction = makeRule({
157
+ name: 'isFunction',
158
+ constraints: {},
159
+ validate: value => typeof value === 'function',
160
+ emit: (varName, ctx) => `if (typeof ${varName} !== 'function') ${ctx.fail('isFunction')};`,
161
+ });
162
+ // ─────────────────────────────────────────────────────────────────────────────
163
+ // isStatelessRegExp — RegExp without g/y (the only flags that mutate lastIndex
164
+ // across repeated test()/exec()). Safe for reuse as a single-shot matcher.
165
+ // ─────────────────────────────────────────────────────────────────────────────
166
+ export const isStatelessRegExp = makeRule({
167
+ name: 'isStatelessRegExp',
168
+ constraints: {},
169
+ validate: value => value instanceof RegExp && !value.global && !value.sticky,
170
+ emit: (varName, ctx) => `if (!(${varName} instanceof RegExp) || ${varName}.global || ${varName}.sticky) ${ctx.fail('isStatelessRegExp')};`,
171
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zipbul/baker",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Bun-only AOT decorator-based DTO validation & serialization. class-validator DX, sealed code generation, zero reflect-metadata.",
5
5
  "keywords": [
6
6
  "aot",