@zipbul/baker 3.4.0 → 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.
- package/CHANGELOG.md +15 -0
- package/dist/index.js +1 -10
- package/dist/src/collect.js +1 -26
- package/dist/src/configure.js +1 -43
- package/dist/src/create-rule.js +1 -41
- package/dist/src/decorators/field.js +1 -277
- package/dist/src/decorators/index.js +1 -2
- package/dist/src/decorators/recipe.js +1 -23
- package/dist/src/errors.js +1 -52
- package/dist/src/functions/check-call-options.js +1 -51
- package/dist/src/functions/deserialize.js +1 -57
- package/dist/src/functions/serialize.js +1 -52
- package/dist/src/functions/validate.js +1 -49
- package/dist/src/interfaces.js +0 -4
- package/dist/src/meta-access.js +1 -75
- package/dist/src/registry.js +1 -8
- package/dist/src/rule-metadata.js +1 -17
- package/dist/src/rule-plan.js +1 -117
- package/dist/src/rules/array.js +1 -96
- package/dist/src/rules/binary.js +3 -51
- package/dist/src/rules/combinators.js +1 -111
- package/dist/src/rules/common.js +1 -77
- package/dist/src/rules/date.js +1 -35
- package/dist/src/rules/index.js +1 -10
- package/dist/src/rules/locales.js +1 -249
- package/dist/src/rules/number.js +1 -79
- package/dist/src/rules/object.js +1 -49
- package/dist/src/rules/string.js +10 -2033
- package/dist/src/rules/typechecker.js +5 -171
- package/dist/src/seal/circular-analyzer.js +1 -63
- package/dist/src/seal/codegen-utils.js +1 -18
- package/dist/src/seal/deserialize-builder.js +265 -1564
- package/dist/src/seal/expose-validator.js +1 -65
- package/dist/src/seal/seal-state.js +1 -18
- package/dist/src/seal/seal.js +1 -431
- package/dist/src/seal/serialize-builder.js +66 -370
- package/dist/src/seal/validate-meta.js +1 -61
- package/dist/src/symbols.js +1 -13
- package/dist/src/transformers/collection.transformer.js +1 -25
- package/dist/src/transformers/date.transformer.js +1 -18
- package/dist/src/transformers/index.js +1 -6
- package/dist/src/transformers/luxon.transformer.js +1 -34
- package/dist/src/transformers/moment.transformer.js +1 -32
- package/dist/src/transformers/number.transformer.js +1 -8
- package/dist/src/transformers/string.transformer.js +1 -12
- package/dist/src/types.js +0 -1
- package/dist/src/utils.js +1 -10
- package/package.json +2 -2
|
@@ -1,52 +1 @@
|
|
|
1
|
-
import
|
|
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
|
|
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};
|
package/dist/src/interfaces.js
CHANGED
package/dist/src/meta-access.js
CHANGED
|
@@ -1,75 +1 @@
|
|
|
1
|
-
import
|
|
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))}
|
package/dist/src/registry.js
CHANGED
|
@@ -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(
|
|
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}
|
package/dist/src/rule-plan.js
CHANGED
|
@@ -1,117 +1 @@
|
|
|
1
|
-
import
|
|
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};
|
package/dist/src/rules/array.js
CHANGED
|
@@ -1,96 +1 @@
|
|
|
1
|
-
import
|
|
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};
|
package/dist/src/rules/binary.js
CHANGED
|
@@ -1,51 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
export const isUint8Array = makeRule({
|
|
6
|
-
name: 'isUint8Array',
|
|
7
|
-
constraints: {},
|
|
8
|
-
validate: value => value instanceof Uint8Array,
|
|
9
|
-
emit: (varName, ctx) => `if (!(${varName} instanceof Uint8Array)) ${ctx.fail('isUint8Array')};`,
|
|
10
|
-
});
|
|
11
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
-
// isByteSize(min, max?) — byte length of any ArrayBufferView (binary analogue of isByteLength)
|
|
13
|
-
//
|
|
14
|
-
// The ArrayBuffer.isView guard MUST short-circuit before any .byteLength read: reading .byteLength
|
|
15
|
-
// on a non-view yields undefined (and undefined < min is false → would wrongly pass), and on
|
|
16
|
-
// null/undefined it throws. The guard-first else-if chain prevents both. .byteLength is inlined
|
|
17
|
-
// (not aliased to a local like isByteLength) — it is a trivial getter, not an expensive call.
|
|
18
|
-
//
|
|
19
|
-
// min/max are dev-supplied constants; per "trust TS for dev inputs" they are not runtime-guarded,
|
|
20
|
-
// consistent with isByteLength.
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
export function isByteSize(min, max) {
|
|
23
|
-
return makeRule({
|
|
24
|
-
name: 'isByteSize',
|
|
25
|
-
constraints: { min, max },
|
|
26
|
-
// Fail-form mirrors emit exactly (same as isByteLength), so validate() and the generated code
|
|
27
|
-
// agree for ALL inputs — including degenerate NaN bounds, where pass-form (>= NaN) would reject
|
|
28
|
-
// but the emitted (< NaN) accepts, breaking validate/emit parity.
|
|
29
|
-
validate: value => {
|
|
30
|
-
if (!ArrayBuffer.isView(value)) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
const byteLen = value.byteLength;
|
|
34
|
-
if (byteLen < min) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
if (max !== undefined && byteLen > max) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
return true;
|
|
41
|
-
},
|
|
42
|
-
emit: (varName, ctx) => {
|
|
43
|
-
let code = `if (!ArrayBuffer.isView(${varName})) ${ctx.fail('isByteSize')};`;
|
|
44
|
-
code += `\nelse if (${varName}.byteLength < ${min}) ${ctx.fail('isByteSize')};`;
|
|
45
|
-
if (max !== undefined) {
|
|
46
|
-
code += `\nelse if (${varName}.byteLength > ${max}) ${ctx.fail('isByteSize')};`;
|
|
47
|
-
}
|
|
48
|
-
return code;
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
}
|
|
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
|
|
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};
|