@zipbul/baker 3.3.1 → 3.4.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 (50) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/index.js +1 -10
  3. package/dist/src/collect.js +1 -26
  4. package/dist/src/configure.js +1 -43
  5. package/dist/src/create-rule.js +1 -41
  6. package/dist/src/decorators/field.js +1 -277
  7. package/dist/src/decorators/index.js +1 -2
  8. package/dist/src/decorators/recipe.js +1 -23
  9. package/dist/src/errors.js +1 -52
  10. package/dist/src/functions/check-call-options.js +1 -51
  11. package/dist/src/functions/deserialize.js +1 -57
  12. package/dist/src/functions/serialize.js +1 -52
  13. package/dist/src/functions/validate.js +1 -49
  14. package/dist/src/interfaces.js +0 -4
  15. package/dist/src/meta-access.js +1 -75
  16. package/dist/src/registry.js +1 -8
  17. package/dist/src/rule-metadata.js +1 -17
  18. package/dist/src/rule-plan.js +1 -117
  19. package/dist/src/rules/array.js +1 -96
  20. package/dist/src/rules/binary.d.ts +3 -0
  21. package/dist/src/rules/binary.js +3 -0
  22. package/dist/src/rules/combinators.js +1 -111
  23. package/dist/src/rules/common.js +1 -77
  24. package/dist/src/rules/date.js +1 -35
  25. package/dist/src/rules/index.d.ts +1 -0
  26. package/dist/src/rules/index.js +1 -9
  27. package/dist/src/rules/locales.js +1 -249
  28. package/dist/src/rules/number.js +1 -79
  29. package/dist/src/rules/object.js +1 -49
  30. package/dist/src/rules/string.js +10 -2033
  31. package/dist/src/rules/typechecker.js +5 -171
  32. package/dist/src/seal/circular-analyzer.js +1 -63
  33. package/dist/src/seal/codegen-utils.js +1 -18
  34. package/dist/src/seal/deserialize-builder.js +265 -1564
  35. package/dist/src/seal/expose-validator.js +1 -65
  36. package/dist/src/seal/seal-state.js +1 -18
  37. package/dist/src/seal/seal.js +1 -431
  38. package/dist/src/seal/serialize-builder.js +66 -370
  39. package/dist/src/seal/validate-meta.js +1 -61
  40. package/dist/src/symbols.js +1 -13
  41. package/dist/src/transformers/collection.transformer.js +1 -25
  42. package/dist/src/transformers/date.transformer.js +1 -18
  43. package/dist/src/transformers/index.js +1 -6
  44. package/dist/src/transformers/luxon.transformer.js +1 -34
  45. package/dist/src/transformers/moment.transformer.js +1 -32
  46. package/dist/src/transformers/number.transformer.js +1 -8
  47. package/dist/src/transformers/string.transformer.js +1 -12
  48. package/dist/src/types.js +0 -1
  49. package/dist/src/utils.js +1 -10
  50. package/package.json +2 -2
@@ -1,52 +1 @@
1
- import { BakerError } from '../errors.js';
2
- import { ensureSealed } from '../seal/seal.js';
3
- import { checkCallOptions } from './check-call-options.js';
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // serialize — Public API (§5.2)
6
- // ─────────────────────────────────────────────────────────────────────────────
7
- /** Boundary check shared by serialize / serializeSync / serializeAsync. */
8
- function resolveSerializer(instance, fnName) {
9
- if (instance == null || typeof instance !== 'object') {
10
- throw new BakerError(`${fnName}: expected a class instance, got ${instance === null ? 'null' : typeof instance}`);
11
- }
12
- const Class = instance.constructor;
13
- if (typeof Class !== 'function') {
14
- throw new BakerError(`${fnName}: instance has no constructor`);
15
- }
16
- // Reject plain objects and forged ones (e.g. `{ constructor: SomeDto }`): a real instance is
17
- // `instanceof` its own constructor via the prototype chain; the `constructor` property alone
18
- // (which anyone can set) is not trusted.
19
- if (Class === Object || !(instance instanceof Class)) {
20
- throw new BakerError(`${fnName}: received a plain object. Pass an instance of a DTO class decorated with @Field.`);
21
- }
22
- return ensureSealed(Class);
23
- }
24
- export function serialize(instance, options) {
25
- const checkedOpts = checkCallOptions(options);
26
- const sealed = resolveSerializer(instance, 'serialize');
27
- return sealed.isSerializeAsync
28
- ? sealed.serialize(instance, checkedOpts)
29
- : sealed.serialize(instance, checkedOpts);
30
- }
31
- /**
32
- * Sync-asserted serialize. Throws `BakerError` if Class has any async transform on the serialize side.
33
- */
34
- export function serializeSync(instance, options) {
35
- const checkedOpts = checkCallOptions(options);
36
- const sealed = resolveSerializer(instance, 'serializeSync');
37
- if (sealed.isSerializeAsync) {
38
- const className = instance.constructor.name;
39
- throw new BakerError(`serializeSync(${className}): DTO has async serialize transforms. Use serializeAsync() instead.`);
40
- }
41
- return sealed.serialize(instance, checkedOpts);
42
- }
43
- /**
44
- * Async-asserted serialize. Always returns Promise (sync DTOs are wrapped via Promise.resolve).
45
- */
46
- export function serializeAsync(instance, options) {
47
- const checkedOpts = checkCallOptions(options);
48
- const sealed = resolveSerializer(instance, 'serializeAsync');
49
- return sealed.isSerializeAsync
50
- ? sealed.serialize(instance, checkedOpts)
51
- : Promise.resolve(sealed.serialize(instance, checkedOpts));
52
- }
1
+ import{BakerError as x}from"../errors.js";import{ensureSealed as I}from"../seal/seal.js";import{checkCallOptions as F}from"./check-call-options.js";function G(b,q){if(b==null||typeof b!=="object")throw new x(`${q}: expected a class instance, got ${b===null?"null":typeof b}`);const g=b.constructor;if(typeof g!=="function")throw new x(`${q}: instance has no constructor`);if(g===Object||!(b instanceof g))throw new x(`${q}: received a plain object. Pass an instance of a DTO class decorated with @Field.`);return I(g)}export function serialize(b,q){const g=F(q),j=G(b,"serialize");return j.isSerializeAsync?j.serialize(b,g):j.serialize(b,g)}export function serializeSync(b,q){const g=F(q),j=G(b,"serializeSync");if(j.isSerializeAsync){const H=b.constructor.name;throw new x(`serializeSync(${H}): DTO has async serialize transforms. Use serializeAsync() instead.`)}return j.serialize(b,g)}export function serializeAsync(b,q){const g=F(q),j=G(b,"serializeAsync");return j.isSerializeAsync?j.serialize(b,g):Promise.resolve(j.serialize(b,g))}
@@ -1,49 +1 @@
1
- import { toBakerIssueSet, BakerError } from '../errors.js';
2
- import { ensureSealed } from '../seal/seal.js';
3
- import { checkCallOptions } from './check-call-options.js';
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // validate — Public API (§5.3)
6
- // ─────────────────────────────────────────────────────────────────────────────
7
- /**
8
- * DTO-level validation — validates `input` against a decorated class's schema.
9
- * Sync DTOs return directly; async DTOs return Promise. To validate a single primitive without a
10
- * DTO, call the rule directly (e.g. `isEmail()(value)`).
11
- */
12
- function validate(Class, input, options) {
13
- const checkedOpts = checkCallOptions(options);
14
- const sealed = ensureSealed(Class);
15
- if (sealed.isAsync) {
16
- return sealed.validate(input, checkedOpts).then((result) => result === null ? true : toBakerIssueSet(result));
17
- }
18
- const result = sealed.validate(input, checkedOpts);
19
- return result === null ? true : toBakerIssueSet(result);
20
- }
21
- // ─────────────────────────────────────────────────────────────────────────────
22
- // W14: strict sync/async variants — explicit intent at call site
23
- // ─────────────────────────────────────────────────────────────────────────────
24
- /**
25
- * Sync-asserted validate. Throws `BakerError` if Class has any async rule/transform
26
- * on the deserialize/validate side. Use when caller code assumes sync return.
27
- */
28
- function validateSync(Class, input, options) {
29
- const checkedOpts = checkCallOptions(options);
30
- const sealed = ensureSealed(Class);
31
- if (sealed.isAsync) {
32
- throw new BakerError(`validateSync(${Class.name}): DTO has async rules/transforms. Use validateAsync() instead.`);
33
- }
34
- const result = sealed.validate(input, checkedOpts);
35
- return result === null ? true : toBakerIssueSet(result);
36
- }
37
- /**
38
- * Async-asserted validate. Always returns Promise (sync DTOs are wrapped via Promise.resolve).
39
- */
40
- function validateAsync(Class, input, options) {
41
- const checkedOpts = checkCallOptions(options);
42
- const sealed = ensureSealed(Class);
43
- if (sealed.isAsync) {
44
- return sealed.validate(input, checkedOpts).then((r) => r === null ? true : toBakerIssueSet(r));
45
- }
46
- const result = sealed.validate(input, checkedOpts);
47
- return Promise.resolve(result === null ? true : toBakerIssueSet(result));
48
- }
49
- export { validate, validateSync, validateAsync };
1
+ import{toBakerIssueSet as F,BakerError as J}from"../errors.js";import{ensureSealed as G}from"../seal/seal.js";import{checkCallOptions as H}from"./check-call-options.js";function validate(q,g,x){const j=H(x),b=G(q);if(b.isAsync)return b.validate(g,j).then((z)=>z===null?!0:F(z));const f=b.validate(g,j);return f===null?!0:F(f)}function validateSync(q,g,x){const j=H(x),b=G(q);if(b.isAsync)throw new J(`validateSync(${q.name}): DTO has async rules/transforms. Use validateAsync() instead.`);const f=b.validate(g,j);return f===null?!0:F(f)}function validateAsync(q,g,x){const j=H(x),b=G(q);if(b.isAsync)return b.validate(g,j).then((z)=>z===null?!0:F(z));const f=b.validate(g,j);return Promise.resolve(f===null?!0:F(f))}export{validate,validateSync,validateAsync};
@@ -1,4 +0,0 @@
1
- // ─────────────────────────────────────────────────────────────────────────────
2
- // SealOptions — seal() global options (§1.4)
3
- // ─────────────────────────────────────────────────────────────────────────────
4
- export {};
@@ -1,75 +1 @@
1
- import { RAW, SEALED } from './symbols.js';
2
- /** Returns the metadata object visible on cls (own or inherited via the class prototype chain). */
3
- function metaOf(cls) {
4
- return cls[Symbol.metadata] ?? undefined;
5
- }
6
- /** Returns the class's own metadata object, creating one if absent. */
7
- function ensureOwnMeta(cls) {
8
- if (!Object.hasOwn(cls, Symbol.metadata)) {
9
- Object.defineProperty(cls, Symbol.metadata, {
10
- value: {},
11
- writable: true,
12
- configurable: true,
13
- enumerable: false,
14
- });
15
- }
16
- return cls[Symbol.metadata];
17
- }
18
- export function getSealed(cls) {
19
- return cls[SEALED];
20
- }
21
- /** Same as getSealed but throws if the class is not sealed — for callers that establish the invariant elsewhere. */
22
- export function requireSealed(cls) {
23
- const v = cls[SEALED];
24
- if (v === undefined) {
25
- throw new Error(`${cls.name || '<anonymous>'}: class is not sealed`);
26
- }
27
- return v;
28
- }
29
- export function setSealed(cls, exec) {
30
- cls[SEALED] = exec;
31
- }
32
- export function hasSealedOwn(cls) {
33
- return Object.hasOwn(cls, SEALED);
34
- }
35
- export function deleteSealed(cls) {
36
- delete cls[SEALED];
37
- }
38
- export function deleteRaw(cls) {
39
- if (Object.hasOwn(cls, Symbol.metadata)) {
40
- delete cls[Symbol.metadata][RAW];
41
- }
42
- }
43
- export function getRaw(cls) {
44
- return metaOf(cls)?.[RAW];
45
- }
46
- /** Same as getRaw but throws if the class has no @Field decorators — for callers that establish the invariant elsewhere. */
47
- export function requireRaw(cls) {
48
- const v = getRaw(cls);
49
- if (v === undefined) {
50
- throw new Error(`${cls.name || '<anonymous>'}: class has no @Field decorators`);
51
- }
52
- return v;
53
- }
54
- export function setRaw(cls, raw) {
55
- ensureOwnMeta(cls)[RAW] = raw;
56
- }
57
- /**
58
- * True only when cls has its OWN RAW slot. A subclass without its own @Field decorators
59
- * resolves Class[Symbol.metadata] to the parent's metadata via the class prototype chain;
60
- * the dual own-check keeps mergeInheritance from double-counting inherited fields.
61
- */
62
- export function hasRawOwn(cls) {
63
- if (!Object.hasOwn(cls, Symbol.metadata)) {
64
- return false;
65
- }
66
- const meta = cls[Symbol.metadata];
67
- return meta != null && Object.hasOwn(meta, RAW);
68
- }
69
- export function freezeRaw(cls) {
70
- // Guard on own RAW: an inherited-only subclass must not freeze the parent's RAW.
71
- if (!hasRawOwn(cls)) {
72
- return;
73
- }
74
- Object.freeze(getRaw(cls));
75
- }
1
+ import{RAW as B,SEALED as q}from"./symbols.js";function C(j){return j[Symbol.metadata]??void 0}function F(j){if(!Object.hasOwn(j,Symbol.metadata))Object.defineProperty(j,Symbol.metadata,{value:{},writable:!0,configurable:!0,enumerable:!1});return j[Symbol.metadata]}export function getSealed(j){return j[q]}export function requireSealed(j){const k=j[q];if(k===void 0)throw Error(`${j.name||"<anonymous>"}: class is not sealed`);return k}export function setSealed(j,k){j[q]=k}export function hasSealedOwn(j){return Object.hasOwn(j,q)}export function deleteSealed(j){delete j[q]}export function deleteRaw(j){if(Object.hasOwn(j,Symbol.metadata))delete j[Symbol.metadata][B]}export function getRaw(j){return C(j)?.[B]}export function requireRaw(j){const k=getRaw(j);if(k===void 0)throw Error(`${j.name||"<anonymous>"}: class has no @Field decorators`);return k}export function setRaw(j,k){F(j)[B]=k}export function hasRawOwn(j){if(!Object.hasOwn(j,Symbol.metadata))return!1;const k=j[Symbol.metadata];return k!=null&&Object.hasOwn(k,B)}export function freezeRaw(j){if(!hasRawOwn(j))return;Object.freeze(getRaw(j))}
@@ -1,8 +1 @@
1
- /**
2
- * Global registry — automatically registers classes with at least one decorator attached
3
- *
4
- * - Automatically called from ensureMeta()
5
- * - seal() iterates this Set to seal all DTOs
6
- * - Metadata is not stored here — used only as an index (which classes are registered)
7
- */
8
- export const globalRegistry = new Set();
1
+ export const globalRegistry=new Set;
@@ -1,17 +1 @@
1
- export function defineRuleMetadata(fn, meta) {
2
- const target = fn;
3
- target.emit = meta.emit;
4
- target.ruleName = meta.ruleName;
5
- if (meta.requiresType !== undefined) {
6
- target.requiresType = meta.requiresType;
7
- }
8
- if (meta.constraints !== undefined) {
9
- target.constraints = meta.constraints;
10
- }
11
- if (meta.isAsync !== undefined) {
12
- target.isAsync = meta.isAsync;
13
- }
14
- if (meta.plan) {
15
- target.plan = meta.plan;
16
- }
17
- }
1
+ export function defineRuleMetadata(q,b){const j=q;j.emit=b.emit;j.ruleName=b.ruleName;if(b.requiresType!==void 0)j.requiresType=b.requiresType;if(b.constraints!==void 0)j.constraints=b.constraints;if(b.isAsync!==void 0)j.isAsync=b.isAsync;if(b.plan)j.plan=b.plan}
@@ -1,117 +1 @@
1
- import { defineRuleMetadata } from './rule-metadata.js';
2
- const planValue = () => ({ kind: 'value' });
3
- const planLength = (object = planValue()) => ({
4
- kind: 'member',
5
- object,
6
- property: 'length',
7
- });
8
- const planTime = (object = planValue()) => ({
9
- kind: 'call0',
10
- object,
11
- method: 'getTime',
12
- });
13
- const planLiteral = (value) => ({ kind: 'literal', value });
14
- const planCompare = (left, op, right) => ({
15
- kind: 'compare',
16
- left,
17
- op,
18
- right: typeof right === 'number' ? planLiteral(right) : right,
19
- });
20
- const planOr = (...checks) => ({ kind: 'or', checks });
21
- function makePlannedRule(options) {
22
- const inner = {
23
- name: options.name,
24
- requiresType: options.requiresType,
25
- plan: options.plan,
26
- validate: options.validate,
27
- emit: (varName, ctx) => emitRulePlan(varName, ctx, options.name, options.plan, undefined, ctx.insideTypeGate),
28
- };
29
- if (options.constraints !== undefined) {
30
- inner.constraints = options.constraints;
31
- }
32
- return makeRule(inner);
33
- }
34
- function makeRule(options) {
35
- const fn = ((value) => options.validate(value));
36
- const meta = {
37
- emit: options.emit,
38
- ruleName: options.name,
39
- constraints: options.constraints ?? {},
40
- };
41
- if (options.requiresType !== undefined) {
42
- meta.requiresType = options.requiresType;
43
- }
44
- if (options.isAsync !== undefined) {
45
- meta.isAsync = options.isAsync;
46
- }
47
- if (options.plan !== undefined) {
48
- meta.plan = options.plan;
49
- }
50
- defineRuleMetadata(fn, meta);
51
- return fn;
52
- }
53
- function emitRulePlan(varName, ctx, ruleName, plan, cache, insideTypeGate) {
54
- const failure = insideTypeGate ? stripSelfComparison(plan.failure) : plan.failure;
55
- return `if (${emitPlanCheck(failure, varName, cache)}) ${ctx.fail(ruleName)};`;
56
- }
57
- /** Strip `x !== x` / `getTime() !== getTime()` self-comparison guards — redundant inside type gate */
58
- function stripSelfComparison(check) {
59
- if (check.kind === 'compare') {
60
- return check;
61
- }
62
- const filtered = check.checks.filter(c => !isSelfComparison(c));
63
- if (filtered.length === 0) {
64
- return check;
65
- } // safety: don't strip everything
66
- if (filtered.length === 1) {
67
- return filtered[0];
68
- }
69
- return { kind: check.kind, checks: filtered };
70
- }
71
- function isSelfComparison(check) {
72
- if (check.kind !== 'compare' || check.op !== '!==') {
73
- return false;
74
- }
75
- return exprEqual(check.left, check.right);
76
- }
77
- function exprEqual(a, b) {
78
- if (a.kind !== b.kind) {
79
- return false;
80
- }
81
- switch (a.kind) {
82
- case 'value':
83
- return true;
84
- case 'literal':
85
- return a.value === b.value;
86
- case 'member':
87
- return exprEqual(a.object, b.object);
88
- case 'call0':
89
- return a.method === b.method && exprEqual(a.object, b.object);
90
- default:
91
- // Compile-time exhaustiveness: adding a RulePlanExpr.kind without a case fails to compile here.
92
- return a;
93
- }
94
- }
95
- function emitPlanCheck(check, varName, cache) {
96
- if (check.kind === 'compare') {
97
- return `${emitPlanExpr(check.left, varName, cache)} ${check.op} ${emitPlanExpr(check.right, varName, cache)}`;
98
- }
99
- const joiner = check.kind === 'and' ? ' && ' : ' || ';
100
- return `(${check.checks.map(part => emitPlanCheck(part, varName, cache)).join(joiner)})`;
101
- }
102
- function emitPlanExpr(expr, varName, cache) {
103
- switch (expr.kind) {
104
- case 'value':
105
- return varName;
106
- case 'literal':
107
- return String(expr.value);
108
- case 'member':
109
- return cache?.length ?? `${emitPlanExpr(expr.object, varName, cache)}.length`;
110
- case 'call0':
111
- return cache?.time ?? `${emitPlanExpr(expr.object, varName, cache)}.getTime()`;
112
- default:
113
- // Compile-time exhaustiveness: adding a RulePlanExpr.kind without a case fails to compile here.
114
- return expr;
115
- }
116
- }
117
- export { planValue, planLength, planTime, planLiteral, planCompare, planOr, makePlannedRule, makeRule, emitRulePlan };
1
+ import{defineRuleMetadata as U}from"./rule-metadata.js";const planValue=()=>({kind:"value"});const planLength=(z=planValue())=>({kind:"member",object:z,property:"length"});const planTime=(z=planValue())=>({kind:"call0",object:z,method:"getTime"});const planLiteral=(z)=>({kind:"literal",value:z});const planCompare=(z,A,B)=>({kind:"compare",left:z,op:A,right:typeof B==="number"?planLiteral(B):B});const planOr=(...z)=>({kind:"or",checks:z});function makePlannedRule(z){const A={name:z.name,requiresType:z.requiresType,plan:z.plan,validate:z.validate,emit:(B,D)=>emitRulePlan(B,D,z.name,z.plan,void 0,D.insideTypeGate)};if(z.constraints!==void 0)A.constraints=z.constraints;return makeRule(A)}function makeRule(z){const A=(D)=>z.validate(D),B={emit:z.emit,ruleName:z.name,constraints:z.constraints??{}};if(z.requiresType!==void 0)B.requiresType=z.requiresType;if(z.isAsync!==void 0)B.isAsync=z.isAsync;if(z.plan!==void 0)B.plan=z.plan;U(A,B);return A}function emitRulePlan(z,A,B,D,H,K){const Q=K?W(D.failure):D.failure;return`if (${J(Q,z,H)}) ${A.fail(B)};`}function W(z){if(z.kind==="compare")return z;const A=z.checks.filter((B)=>!X(B));if(A.length===0)return z;if(A.length===1)return A[0];return{kind:z.kind,checks:A}}function X(z){if(z.kind!=="compare"||z.op!=="!==")return!1;return I(z.left,z.right)}function I(z,A){if(z.kind!==A.kind)return!1;switch(z.kind){case"value":return!0;case"literal":return z.value===A.value;case"member":return I(z.object,A.object);case"call0":return z.method===A.method&&I(z.object,A.object);default:return z}}function J(z,A,B){if(z.kind==="compare")return`${F(z.left,A,B)} ${z.op} ${F(z.right,A,B)}`;const D=z.kind==="and"?" && ":" || ";return`(${z.checks.map((H)=>J(H,A,B)).join(D)})`}function F(z,A,B){switch(z.kind){case"value":return A;case"literal":return String(z.value);case"member":return B?.length??`${F(z.object,A,B)}.length`;case"call0":return B?.time??`${F(z.object,A,B)}.getTime()`;default:return z}}export{planValue,planLength,planTime,planLiteral,planCompare,planOr,makePlannedRule,makeRule,emitRulePlan};
@@ -1,96 +1 @@
1
- import { makePlannedRule, makeRule, planCompare, planLength } from '../rule-plan.js';
2
- // ─────────────────────────────────────────────────────────────────────────────
3
- // arrayContains(values) — array contains all specified values
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- function arrayContains(values) {
6
- return makeRule({
7
- name: 'arrayContains',
8
- requiresType: 'array',
9
- constraints: { values },
10
- validate: value => Array.isArray(value) && values.every(v => value.includes(v)),
11
- emit: (varName, ctx) => {
12
- const i = ctx.addRef(values);
13
- return `if (!refs[${i}].every(function(v){return ${varName}.includes(v);})) ${ctx.fail('arrayContains')};`;
14
- },
15
- });
16
- }
17
- // ─────────────────────────────────────────────────────────────────────────────
18
- // arrayNotContains(values) — array does not contain any of the specified values
19
- // ─────────────────────────────────────────────────────────────────────────────
20
- function arrayNotContains(values) {
21
- return makeRule({
22
- name: 'arrayNotContains',
23
- requiresType: 'array',
24
- constraints: { values },
25
- validate: value => Array.isArray(value) && values.every(v => !value.includes(v)),
26
- emit: (varName, ctx) => {
27
- const i = ctx.addRef(values);
28
- return `if (refs[${i}].some(function(v){return ${varName}.includes(v);})) ${ctx.fail('arrayNotContains')};`;
29
- },
30
- });
31
- }
32
- // ─────────────────────────────────────────────────────────────────────────────
33
- // arrayMinSize(min) — minimum array length
34
- // ─────────────────────────────────────────────────────────────────────────────
35
- function arrayMinSize(min) {
36
- const plan = { cacheKey: 'length', failure: planCompare(planLength(), '<', min) };
37
- return makePlannedRule({
38
- name: 'arrayMinSize',
39
- requiresType: 'array',
40
- constraints: { min },
41
- plan,
42
- validate: value => Array.isArray(value) && value.length >= min,
43
- });
44
- }
45
- // ─────────────────────────────────────────────────────────────────────────────
46
- // arrayMaxSize(max) — maximum array length
47
- // ─────────────────────────────────────────────────────────────────────────────
48
- function arrayMaxSize(max) {
49
- const plan = { cacheKey: 'length', failure: planCompare(planLength(), '>', max) };
50
- return makePlannedRule({
51
- name: 'arrayMaxSize',
52
- requiresType: 'array',
53
- constraints: { max },
54
- plan,
55
- validate: value => Array.isArray(value) && value.length <= max,
56
- });
57
- }
58
- // ─────────────────────────────────────────────────────────────────────────────
59
- // arrayUnique(identifier?) — no duplicates in array
60
- // ─────────────────────────────────────────────────────────────────────────────
61
- function arrayUnique(identifier) {
62
- return makeRule({
63
- name: 'arrayUnique',
64
- requiresType: 'array',
65
- constraints: {},
66
- validate: value => {
67
- if (!Array.isArray(value)) {
68
- return false;
69
- }
70
- if (identifier) {
71
- const keys = value.map(identifier);
72
- return new Set(keys).size === keys.length;
73
- }
74
- return new Set(value).size === value.length;
75
- },
76
- emit: (varName, ctx) => {
77
- if (identifier) {
78
- const i = ctx.addRef(identifier);
79
- return `{var keys=${varName}.map(refs[${i}]);if(new Set(keys).size!==keys.length)${ctx.fail('arrayUnique')};}`;
80
- }
81
- return `if(new Set(${varName}).size!==${varName}.length)${ctx.fail('arrayUnique')};`;
82
- },
83
- });
84
- }
85
- // ─────────────────────────────────────────────────────────────────────────────
86
- // arrayNotEmpty — array is not empty (singleton)
87
- // ─────────────────────────────────────────────────────────────────────────────
88
- const arrayNotEmptyPlan = { cacheKey: 'length', failure: planCompare(planLength(), '===', 0) };
89
- const arrayNotEmpty = makePlannedRule({
90
- name: 'arrayNotEmpty',
91
- requiresType: 'array',
92
- constraints: {},
93
- plan: arrayNotEmptyPlan,
94
- validate: value => Array.isArray(value) && value.length > 0,
95
- });
96
- export { arrayContains, arrayNotContains, arrayMinSize, arrayMaxSize, arrayUnique, arrayNotEmpty };
1
+ import{makePlannedRule as B,makeRule as D,planCompare as F,planLength as G}from"../rule-plan.js";function arrayContains(b){return D({name:"arrayContains",requiresType:"array",constraints:{values:b},validate:(j)=>Array.isArray(j)&&b.every((w)=>j.includes(w)),emit:(j,w)=>{return`if (!refs[${w.addRef(b)}].every(function(v){return ${j}.includes(v);})) ${w.fail("arrayContains")};`}})}function arrayNotContains(b){return D({name:"arrayNotContains",requiresType:"array",constraints:{values:b},validate:(j)=>Array.isArray(j)&&b.every((w)=>!j.includes(w)),emit:(j,w)=>{return`if (refs[${w.addRef(b)}].some(function(v){return ${j}.includes(v);})) ${w.fail("arrayNotContains")};`}})}function arrayMinSize(b){const j={cacheKey:"length",failure:F(G(),"<",b)};return B({name:"arrayMinSize",requiresType:"array",constraints:{min:b},plan:j,validate:(w)=>Array.isArray(w)&&w.length>=b})}function arrayMaxSize(b){const j={cacheKey:"length",failure:F(G(),">",b)};return B({name:"arrayMaxSize",requiresType:"array",constraints:{max:b},plan:j,validate:(w)=>Array.isArray(w)&&w.length<=b})}function arrayUnique(b){return D({name:"arrayUnique",requiresType:"array",constraints:{},validate:(j)=>{if(!Array.isArray(j))return!1;if(b){const w=j.map(b);return new Set(w).size===w.length}return new Set(j).size===j.length},emit:(j,w)=>{if(b){const A=w.addRef(b);return`{var keys=${j}.map(refs[${A}]);if(new Set(keys).size!==keys.length)${w.fail("arrayUnique")};}`}return`if(new Set(${j}).size!==${j}.length)${w.fail("arrayUnique")};`}})}const H={cacheKey:"length",failure:F(G(),"===",0)};const arrayNotEmpty=B({name:"arrayNotEmpty",requiresType:"array",constraints:{},plan:H,validate:(b)=>Array.isArray(b)&&b.length>0});export{arrayContains,arrayNotContains,arrayMinSize,arrayMaxSize,arrayUnique,arrayNotEmpty};
@@ -0,0 +1,3 @@
1
+ import type { EmittableRule } from '../types';
2
+ export declare const isUint8Array: import("../types").InternalRule;
3
+ export declare function isByteSize(min: number, max?: number): EmittableRule;
@@ -0,0 +1,3 @@
1
+ import{makeRule as A}from"../rule-plan.js";export const isUint8Array=A({name:"isUint8Array",constraints:{},validate:(f)=>f instanceof Uint8Array,emit:(f,j)=>`if (!(${f} instanceof Uint8Array)) ${j.fail("isUint8Array")};`});export function isByteSize(f,j){return A({name:"isByteSize",constraints:{min:f,max:j},validate:(q)=>{if(!ArrayBuffer.isView(q))return!1;const w=q.byteLength;if(w<f)return!1;if(j!==void 0&&w>j)return!1;return!0},emit:(q,w)=>{let z=`if (!ArrayBuffer.isView(${q})) ${w.fail("isByteSize")};`;z+=`
2
+ else if (${q}.byteLength < ${f}) ${w.fail("isByteSize")};`;if(j!==void 0)z+=`
3
+ else if (${q}.byteLength > ${j}) ${w.fail("isByteSize")};`;return z}})}
@@ -1,111 +1 @@
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
+ import{BakerError as H}from"../errors.js";import{makeRule as F}from"../rule-plan.js";function J(q,D){if(typeof q==="function"&&typeof q.emit==="function"&&typeof q.ruleName==="string")return;throw new H(`${D}: every argument must be a baker rule (function with .emit and .ruleName). Use createRule() or import a rule from @zipbul/baker/rules.`)}function oneOf(...q){if(q.length===0)throw new H("oneOf requires at least one rule.");for(const z of q)J(z,"oneOf");const D={oneOf:q.map((z)=>z.ruleName)};if(q.some((z)=>z.isAsync===!0)){const z=async(j)=>{for(const w of q)if(await w(j))return!0;return!1};return F({name:"oneOf",constraints:D,isAsync:!0,validate:z,emit:(j,w)=>{return`if (!(await refs[${w.addRef(z)}](${j}))) ${w.fail("oneOf")};`}})}return F({name:"oneOf",constraints:D,validate:(z)=>q.some((j)=>j(z)),emit:(z,j)=>{return`if (!(${q.map((C)=>`refs[${j.addRef(C)}](${z})`).join(" || ")})) ${j.fail("oneOf")};`}})}function arrayEvery(...q){if(q.length===0)throw new H("arrayEvery requires at least one rule.");for(const j of q)J(j,"arrayEvery");const D={arrayEvery:q.map((j)=>j.ruleName)};if(q.some((j)=>j.isAsync===!0)){const j=async(w)=>{if(!Array.isArray(w))return!1;for(const C of w)for(const I of q)if(!await I(C))return!1;return!0};return F({name:"arrayEvery",constraints:D,isAsync:!0,validate:j,emit:(w,C)=>{return`if (!(await refs[${C.addRef(j)}](${w}))) ${C.fail("arrayEvery")};`}})}const G=(j)=>q.every((w)=>w(j));return F({name:"arrayEvery",constraints:D,validate:(j)=>Array.isArray(j)&&j.every(G),emit:(j,w)=>{const C=w.addRef(G);return`if (!(Array.isArray(${j}) && ${j}.every(refs[${C}]))) ${w.fail("arrayEvery")};`}})}export{oneOf,arrayEvery};