@cqrs-ddd/pipeline-zod 0.5.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/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +33 -0
- package/dist/create-zod-mapper.d.ts +37 -0
- package/dist/create-zod-mapper.d.ts.map +1 -0
- package/dist/create-zod-mapper.js +37 -0
- package/dist/create-zod-mapper.js.map +1 -0
- package/dist/create-zod-request.d.ts +142 -0
- package/dist/create-zod-request.d.ts.map +1 -0
- package/dist/create-zod-request.js +175 -0
- package/dist/create-zod-request.js.map +1 -0
- package/dist/errors/zod-validation.error.d.ts +17 -0
- package/dist/errors/zod-validation.error.d.ts.map +1 -0
- package/dist/errors/zod-validation.error.js +21 -0
- package/dist/errors/zod-validation.error.js.map +1 -0
- package/dist/helpers/request-output.d.ts +15 -0
- package/dist/helpers/request-output.d.ts.map +1 -0
- package/dist/helpers/request-output.js +34 -0
- package/dist/helpers/request-output.js.map +1 -0
- package/dist/helpers/validated.intent.d.ts +17 -0
- package/dist/helpers/validated.intent.d.ts.map +1 -0
- package/dist/helpers/validated.intent.js +18 -0
- package/dist/helpers/validated.intent.js.map +1 -0
- package/dist/helpers/zod-data.helpers.d.ts +40 -0
- package/dist/helpers/zod-data.helpers.d.ts.map +1 -0
- package/dist/helpers/zod-data.helpers.js +195 -0
- package/dist/helpers/zod-data.helpers.js.map +1 -0
- package/dist/http.d.ts +19 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +24 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/updatable.d.ts +44 -0
- package/dist/updatable.d.ts.map +1 -0
- package/dist/updatable.js +76 -0
- package/dist/updatable.js.map +1 -0
- package/dist/zod-validation.behavior.d.ts +85 -0
- package/dist/zod-validation.behavior.d.ts.map +1 -0
- package/dist/zod-validation.behavior.js +115 -0
- package/dist/zod-validation.behavior.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
export const ZOD_RAW_INPUT_KEY = Symbol.for('@cqrs-ddd/pipeline-zod:raw-input');
|
|
3
|
+
export const ZOD_VALIDATED_DATA_KEY = Symbol.for('@cqrs-ddd/pipeline-zod:validated-data');
|
|
4
|
+
const requestRawInputMap = new WeakMap();
|
|
5
|
+
const requestValidationMap = new WeakMap();
|
|
6
|
+
/** Records constructor input, including explicitly supplied null or undefined. */
|
|
7
|
+
export function setRawInput(request, rawInput) {
|
|
8
|
+
requestRawInputMap.set(request, rawInput);
|
|
9
|
+
}
|
|
10
|
+
/** Returns original constructor input by reference, or the request when no input was recorded. */
|
|
11
|
+
export function getRawInput(request) {
|
|
12
|
+
if (request && typeof request === 'object') {
|
|
13
|
+
if (requestRawInputMap.has(request))
|
|
14
|
+
return requestRawInputMap.get(request);
|
|
15
|
+
const raw = request[ZOD_RAW_INPUT_KEY];
|
|
16
|
+
if (raw !== undefined)
|
|
17
|
+
return raw;
|
|
18
|
+
}
|
|
19
|
+
return request;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Records the parsed payload a request now carries, detached from the live
|
|
23
|
+
* instance. The snapshot covers exactly the keys the schema produced, so fields
|
|
24
|
+
* the request class owns itself are neither tracked nor removed later.
|
|
25
|
+
*/
|
|
26
|
+
export function setValidatedData(request, schema, data) {
|
|
27
|
+
requestValidationMap.set(request, {
|
|
28
|
+
schema,
|
|
29
|
+
snapshot: Object.freeze(cloneData(data)),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Internal validation provenance; caller-supplied symbol metadata cannot establish it. */
|
|
33
|
+
export function getValidationState(request) {
|
|
34
|
+
return requestValidationMap.get(request);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns a detached copy of the parsed payload for inspection. Its top level is
|
|
38
|
+
* frozen and every call returns a fresh copy, so changing it cannot change the
|
|
39
|
+
* validation state the behavior relies on. Without an internal validation
|
|
40
|
+
* record, it falls back to a value stored under `ZOD_VALIDATED_DATA_KEY`; that
|
|
41
|
+
* value is returned as data but never establishes a trusted validation result.
|
|
42
|
+
*/
|
|
43
|
+
export function getValidatedData(request) {
|
|
44
|
+
if (!request || typeof request !== 'object')
|
|
45
|
+
return undefined;
|
|
46
|
+
const snapshot = requestValidationMap.get(request)?.snapshot ??
|
|
47
|
+
request[ZOD_VALIDATED_DATA_KEY];
|
|
48
|
+
return snapshot === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: Object.freeze(cloneData(snapshot));
|
|
51
|
+
}
|
|
52
|
+
/** Clones enumerable data, rich collections and binary values, preserving cycles and shared references. */
|
|
53
|
+
export function cloneData(data) {
|
|
54
|
+
return cloneValue(data, new WeakMap());
|
|
55
|
+
}
|
|
56
|
+
function isBinary(value) {
|
|
57
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
58
|
+
}
|
|
59
|
+
function binaryBytes(value) {
|
|
60
|
+
return value instanceof ArrayBuffer
|
|
61
|
+
? new Uint8Array(value)
|
|
62
|
+
: new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
63
|
+
}
|
|
64
|
+
function enumerableKeys(value) {
|
|
65
|
+
const keys = Object.keys(value);
|
|
66
|
+
for (const symbol of Object.getOwnPropertySymbols(value)) {
|
|
67
|
+
if (Object.getOwnPropertyDescriptor(value, symbol)?.enumerable)
|
|
68
|
+
keys.push(symbol);
|
|
69
|
+
}
|
|
70
|
+
return keys;
|
|
71
|
+
}
|
|
72
|
+
function cloneValue(data, seen) {
|
|
73
|
+
if (data === null || typeof data !== 'object')
|
|
74
|
+
return data;
|
|
75
|
+
if (seen.has(data))
|
|
76
|
+
return seen.get(data);
|
|
77
|
+
if (isBinary(data)) {
|
|
78
|
+
// Contents copy wholesale; walking typed-array indices would be O(bytes).
|
|
79
|
+
const binary = Buffer.isBuffer(data)
|
|
80
|
+
? Buffer.from(data)
|
|
81
|
+
: structuredClone(data);
|
|
82
|
+
seen.set(data, binary);
|
|
83
|
+
return binary;
|
|
84
|
+
}
|
|
85
|
+
let copy;
|
|
86
|
+
if (data instanceof Date)
|
|
87
|
+
copy = new Date(data.getTime());
|
|
88
|
+
else if (data instanceof RegExp) {
|
|
89
|
+
const expression = new RegExp(data.source, data.flags);
|
|
90
|
+
expression.lastIndex = data.lastIndex;
|
|
91
|
+
copy = expression;
|
|
92
|
+
}
|
|
93
|
+
else if (data instanceof Map)
|
|
94
|
+
copy = new Map();
|
|
95
|
+
else if (data instanceof Set)
|
|
96
|
+
copy = new Set();
|
|
97
|
+
else if (Array.isArray(data))
|
|
98
|
+
copy = new Array(data.length);
|
|
99
|
+
else
|
|
100
|
+
copy = Object.create(Object.getPrototypeOf(data) === null ? null : Object.prototype);
|
|
101
|
+
seen.set(data, copy);
|
|
102
|
+
if (data instanceof Map) {
|
|
103
|
+
for (const [key, value] of data)
|
|
104
|
+
copy.set(cloneValue(key, seen), cloneValue(value, seen));
|
|
105
|
+
}
|
|
106
|
+
else if (data instanceof Set) {
|
|
107
|
+
for (const value of data)
|
|
108
|
+
copy.add(cloneValue(value, seen));
|
|
109
|
+
}
|
|
110
|
+
for (const key of enumerableKeys(data)) {
|
|
111
|
+
Object.defineProperty(copy, key, {
|
|
112
|
+
value: cloneValue(Reflect.get(data, key), seen),
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true,
|
|
115
|
+
writable: true,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return copy;
|
|
119
|
+
}
|
|
120
|
+
/** Structural data equality with one-to-one collection matching and cycle detection. */
|
|
121
|
+
export function deepEqual(a, b) {
|
|
122
|
+
return equalData(a, b, new Map());
|
|
123
|
+
}
|
|
124
|
+
function equalData(a, b, pairs) {
|
|
125
|
+
if (Object.is(a, b))
|
|
126
|
+
return true;
|
|
127
|
+
if (typeof a !== 'object' ||
|
|
128
|
+
a === null ||
|
|
129
|
+
typeof b !== 'object' ||
|
|
130
|
+
b === null)
|
|
131
|
+
return false;
|
|
132
|
+
if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b))
|
|
133
|
+
return false;
|
|
134
|
+
if (isBinary(a) && isBinary(b)) {
|
|
135
|
+
const left = binaryBytes(a);
|
|
136
|
+
const right = binaryBytes(b);
|
|
137
|
+
return (left.length === right.length &&
|
|
138
|
+
left.every((byte, index) => byte === right[index]));
|
|
139
|
+
}
|
|
140
|
+
if (a instanceof Date &&
|
|
141
|
+
b instanceof Date &&
|
|
142
|
+
!Object.is(a.getTime(), b.getTime()))
|
|
143
|
+
return false;
|
|
144
|
+
if (a instanceof RegExp &&
|
|
145
|
+
b instanceof RegExp &&
|
|
146
|
+
(a.source !== b.source ||
|
|
147
|
+
a.flags !== b.flags ||
|
|
148
|
+
a.lastIndex !== b.lastIndex))
|
|
149
|
+
return false;
|
|
150
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length)
|
|
151
|
+
return false;
|
|
152
|
+
// Both directions of the current comparison path, so a cycle only matches the
|
|
153
|
+
// partner it was first paired with.
|
|
154
|
+
if (pairs.has(a))
|
|
155
|
+
return pairs.get(a) === b;
|
|
156
|
+
if (pairs.has(b))
|
|
157
|
+
return false;
|
|
158
|
+
pairs.set(a, b);
|
|
159
|
+
pairs.set(b, a);
|
|
160
|
+
try {
|
|
161
|
+
if ((a instanceof Map && b instanceof Map) ||
|
|
162
|
+
(a instanceof Set && b instanceof Set)) {
|
|
163
|
+
if (a.size !== b.size)
|
|
164
|
+
return false;
|
|
165
|
+
const remaining = [...b];
|
|
166
|
+
for (const item of a) {
|
|
167
|
+
const index = remaining.findIndex((other) => equalData(item, other, pairs));
|
|
168
|
+
if (index < 0)
|
|
169
|
+
return false;
|
|
170
|
+
remaining.splice(index, 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const keysA = enumerableKeys(a);
|
|
174
|
+
const keysB = enumerableKeys(b);
|
|
175
|
+
if (keysA.length !== keysB.length)
|
|
176
|
+
return false;
|
|
177
|
+
const presentInB = new Set(keysB);
|
|
178
|
+
return keysA.every((key) => presentInB.has(key) &&
|
|
179
|
+
equalData(Reflect.get(a, key), Reflect.get(b, key), pairs));
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
// A rejected collection candidate must not leave path state for the next one.
|
|
183
|
+
pairs.delete(a);
|
|
184
|
+
pairs.delete(b);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Reports whether the request still carries the payload it was validated with.
|
|
189
|
+
* Only the schema-produced fields are compared: class-owned fields and
|
|
190
|
+
* non-enumerable CQRS/session metadata are outside the validated payload.
|
|
191
|
+
*/
|
|
192
|
+
export function hasBeenMutated(request, snapshot) {
|
|
193
|
+
return Object.keys(snapshot).some((key) => !Object.hasOwn(request, key) || !deepEqual(request[key], snapshot[key]));
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=zod-data.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod-data.helpers.js","sourceRoot":"","sources":["../../src/helpers/zod-data.helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ZodValidationError } from './errors/zod-validation.error.js';
|
|
2
|
+
/** An HTTP answer for an error: status, JSON body and headers. */
|
|
3
|
+
export interface HttpResponse {
|
|
4
|
+
status: number;
|
|
5
|
+
body: Record<string, unknown>;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The HTTP answer for a {@link ZodValidationError}: `400 Bad Request` with the issue
|
|
10
|
+
* details.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { status, body } = toHttpResponse(error);
|
|
15
|
+
* res.status(status).json(body);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function toHttpResponse(error: ZodValidationError): HttpResponse;
|
|
19
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,CAWtE"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* The HTTP answer for a {@link ZodValidationError}: `400 Bad Request` with the issue
|
|
4
|
+
* details.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const { status, body } = toHttpResponse(error);
|
|
9
|
+
* res.status(status).json(body);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function toHttpResponse(error) {
|
|
13
|
+
return {
|
|
14
|
+
status: 400,
|
|
15
|
+
body: {
|
|
16
|
+
statusCode: 400,
|
|
17
|
+
error: 'Bad Request',
|
|
18
|
+
message: error.message,
|
|
19
|
+
details: error.details,
|
|
20
|
+
},
|
|
21
|
+
headers: {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAWtE;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI,EAAE;YACJ,UAAU,EAAE,GAAG;YACf,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB;QACD,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation: `validated()`, the behavior, request classes that carry their schema, and `createZodMapper()` for the HTTP edge. The HTTP mapping of its errors is the `@cqrs-ddd/pipeline-zod/http` entry point.
|
|
3
|
+
*
|
|
4
|
+
* @module main
|
|
5
|
+
*/
|
|
6
|
+
export { createZodMapper, type ZodMapper } from './create-zod-mapper.js';
|
|
7
|
+
export { type AbstractConstructor, createCommand, createQuery, createZodRequest, type InferInput, type InferOutput, type ZodCommandClass, type ZodQueryClass, type ZodRequestClass, } from './create-zod-request.js';
|
|
8
|
+
export { ZodValidationError } from './errors/zod-validation.error.js';
|
|
9
|
+
export { validated } from './helpers/validated.intent.js';
|
|
10
|
+
export { updatable, updatableFieldsOf } from './updatable.js';
|
|
11
|
+
export { getRawInput, getValidatedData, ZOD_RAW_INPUT_KEY, ZOD_SCHEMA_KEY, ZOD_VALIDATED_DATA_KEY, ZodValidationBehavior, type ZodValidationOptions, } from './zod-validation.behavior.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EACL,KAAK,mBAAmB,EACxB,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,oBAAoB,GAC1B,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* Zod validation: `validated()`, the behavior, request classes that carry their schema, and `createZodMapper()` for the HTTP edge. The HTTP mapping of its errors is the `@cqrs-ddd/pipeline-zod/http` entry point.
|
|
4
|
+
*
|
|
5
|
+
* @module main
|
|
6
|
+
*/
|
|
7
|
+
export { createZodMapper } from './create-zod-mapper.js';
|
|
8
|
+
export { createCommand, createQuery, createZodRequest, } from './create-zod-request.js';
|
|
9
|
+
export { ZodValidationError } from './errors/zod-validation.error.js';
|
|
10
|
+
export { validated } from './helpers/validated.intent.js';
|
|
11
|
+
export { updatable, updatableFieldsOf } from './updatable.js';
|
|
12
|
+
export { getRawInput, getValidatedData, ZOD_RAW_INPUT_KEY, ZOD_SCHEMA_KEY, ZOD_VALIDATED_DATA_KEY, ZodValidationBehavior, } from './zod-validation.behavior.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAkB,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAEL,aAAa,EACb,WAAW,EACX,gBAAgB,GAMjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GAEtB,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ZodType } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Marks a command field as updatable: a change the command requests, which
|
|
4
|
+
* field-level authorization must check.
|
|
5
|
+
*
|
|
6
|
+
* {@link createCommand} lists the marked fields of the top-level object as
|
|
7
|
+
* the class's static `updatableFields`, the list of fields to pass to
|
|
8
|
+
* field-level authorization. A field without the mark is never in that list,
|
|
9
|
+
* so mark every field the handler writes.
|
|
10
|
+
*
|
|
11
|
+
* The mark survives the checks and wrappers chained after it (`.min()`,
|
|
12
|
+
* `.optional()`, `.nullable()`, `.default()`, `.transform()`) and
|
|
13
|
+
* `.partial()` on the object. It is set on a copy, so a schema shared with
|
|
14
|
+
* other commands stays unmarked.
|
|
15
|
+
*
|
|
16
|
+
* @param schema - The field schema to mark.
|
|
17
|
+
* @returns A marked copy of `schema`, with the same type.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* export class UpdateRoleCommand extends createCommand(
|
|
22
|
+
* z.object({
|
|
23
|
+
* id: z.uuid(),
|
|
24
|
+
* name: z.string().trim().apply(updatable).min(3),
|
|
25
|
+
* }),
|
|
26
|
+
* ) {}
|
|
27
|
+
*
|
|
28
|
+
* UpdateRoleCommand.updatableFields; // ['name']
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function updatable<TSchema extends ZodType>(schema: TSchema): TSchema;
|
|
32
|
+
/**
|
|
33
|
+
* Lists the fields of an object schema marked with {@link updatable}, in
|
|
34
|
+
* shape order.
|
|
35
|
+
*
|
|
36
|
+
* Only the top-level object is read, also through a top-level `.transform()`
|
|
37
|
+
* or `.pipe()`: a mark inside a nested object, an array or a union is not
|
|
38
|
+
* listed. Any other schema has no updatable fields.
|
|
39
|
+
*
|
|
40
|
+
* @param schema - A command schema, usually a `z.object()`.
|
|
41
|
+
* @returns The marked field names, frozen.
|
|
42
|
+
*/
|
|
43
|
+
export declare function updatableFieldsOf(schema: ZodType): readonly string[];
|
|
44
|
+
//# sourceMappingURL=updatable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updatable.d.ts","sourceRoot":"","sources":["../src/updatable.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AAmB7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAI3E;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAKpE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { registry } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* The fields marked with {@link updatable}. A private registry rather than
|
|
5
|
+
* `.meta()`, so the mark never appears in `z.toJSONSchema()` output and no
|
|
6
|
+
* other library can set or clear it.
|
|
7
|
+
*/
|
|
8
|
+
const updatableFields = registry();
|
|
9
|
+
const defOf = (schema) => schema._zod.def;
|
|
10
|
+
/**
|
|
11
|
+
* Marks a command field as updatable: a change the command requests, which
|
|
12
|
+
* field-level authorization must check.
|
|
13
|
+
*
|
|
14
|
+
* {@link createCommand} lists the marked fields of the top-level object as
|
|
15
|
+
* the class's static `updatableFields`, the list of fields to pass to
|
|
16
|
+
* field-level authorization. A field without the mark is never in that list,
|
|
17
|
+
* so mark every field the handler writes.
|
|
18
|
+
*
|
|
19
|
+
* The mark survives the checks and wrappers chained after it (`.min()`,
|
|
20
|
+
* `.optional()`, `.nullable()`, `.default()`, `.transform()`) and
|
|
21
|
+
* `.partial()` on the object. It is set on a copy, so a schema shared with
|
|
22
|
+
* other commands stays unmarked.
|
|
23
|
+
*
|
|
24
|
+
* @param schema - The field schema to mark.
|
|
25
|
+
* @returns A marked copy of `schema`, with the same type.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* export class UpdateRoleCommand extends createCommand(
|
|
30
|
+
* z.object({
|
|
31
|
+
* id: z.uuid(),
|
|
32
|
+
* name: z.string().trim().apply(updatable).min(3),
|
|
33
|
+
* }),
|
|
34
|
+
* ) {}
|
|
35
|
+
*
|
|
36
|
+
* UpdateRoleCommand.updatableFields; // ['name']
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function updatable(schema) {
|
|
40
|
+
const marked = schema.clone();
|
|
41
|
+
updatableFields.add(marked, { updatable: true });
|
|
42
|
+
return marked;
|
|
43
|
+
}
|
|
44
|
+
function isMarked(schema) {
|
|
45
|
+
if (updatableFields.get(schema)?.updatable)
|
|
46
|
+
return true;
|
|
47
|
+
const def = defOf(schema);
|
|
48
|
+
if (def.type === 'pipe') {
|
|
49
|
+
return isMarked(def.in) || isMarked(def.out);
|
|
50
|
+
}
|
|
51
|
+
return def.innerType !== undefined && isMarked(def.innerType);
|
|
52
|
+
}
|
|
53
|
+
function objectShape(schema) {
|
|
54
|
+
const def = defOf(schema);
|
|
55
|
+
if (def.type === 'object')
|
|
56
|
+
return def.shape;
|
|
57
|
+
if (def.type === 'pipe')
|
|
58
|
+
return objectShape(def.in);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Lists the fields of an object schema marked with {@link updatable}, in
|
|
63
|
+
* shape order.
|
|
64
|
+
*
|
|
65
|
+
* Only the top-level object is read, also through a top-level `.transform()`
|
|
66
|
+
* or `.pipe()`: a mark inside a nested object, an array or a union is not
|
|
67
|
+
* listed. Any other schema has no updatable fields.
|
|
68
|
+
*
|
|
69
|
+
* @param schema - A command schema, usually a `z.object()`.
|
|
70
|
+
* @returns The marked field names, frozen.
|
|
71
|
+
*/
|
|
72
|
+
export function updatableFieldsOf(schema) {
|
|
73
|
+
const shape = objectShape(schema) ?? {};
|
|
74
|
+
return Object.freeze(Object.keys(shape).filter((key) => isMarked(shape[key])));
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=updatable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updatable.js","sourceRoot":"","sources":["../src/updatable.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,QAAQ,EAAgB,MAAM,KAAK,CAAC;AAE7C;;;;GAIG;AACH,MAAM,eAAe,GAAG,QAAQ,EAAgC,CAAC;AAUjE,MAAM,KAAK,GAAG,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAA4B,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,SAAS,CAA0B,MAAe;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;IAC9B,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,MAAe;IAC/B,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,QAAQ,CAAC,GAAG,CAAC,EAAa,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAc,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,WAAW,CAAC,MAAe;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC;IAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,WAAW,CAAC,GAAG,CAAC,EAAa,CAAC,CAAC;IAC/D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,OAAO,MAAM,CAAC,MAAM,CAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC,CAAC,CACpE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { IPipelineBehavior, IPipelineContext, NextDelegate } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
import { getRawInput, getValidatedData, ZOD_RAW_INPUT_KEY, ZOD_VALIDATED_DATA_KEY } from './helpers/zod-data.helpers.js';
|
|
4
|
+
export { getRawInput, getValidatedData, ZOD_RAW_INPUT_KEY, ZOD_VALIDATED_DATA_KEY, };
|
|
5
|
+
/**
|
|
6
|
+
* Conventional property key used to attach a Zod schema to a command, query, or event class.
|
|
7
|
+
*
|
|
8
|
+
* Classes built with `createCommand()`, `createQuery()`, or `createZodRequest()` automatically receive this property, so
|
|
9
|
+
* {@link ZodValidationBehavior} can introspect and validate without extra wiring.
|
|
10
|
+
*
|
|
11
|
+
* For manually-written event classes you can attach the schema yourself:
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { ZOD_SCHEMA_KEY } from '@cqrs-ddd/pipeline-zod';
|
|
16
|
+
* import { z } from 'zod';
|
|
17
|
+
*
|
|
18
|
+
* const userCreatedSchema = z.object({
|
|
19
|
+
* userId: z.uuid(),
|
|
20
|
+
* username: z.string().min(1),
|
|
21
|
+
* email: z.email(),
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* export class UserCreatedEvent {
|
|
25
|
+
* static readonly [ZOD_SCHEMA_KEY] = userCreatedSchema;
|
|
26
|
+
*
|
|
27
|
+
* constructor(
|
|
28
|
+
* public readonly userId: string,
|
|
29
|
+
* public readonly username: string,
|
|
30
|
+
* public readonly email: string,
|
|
31
|
+
* ) {}
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare const ZOD_SCHEMA_KEY: '_zodSchema';
|
|
36
|
+
/** Options of {@link ZodValidationBehavior}. */
|
|
37
|
+
export interface ZodValidationOptions {
|
|
38
|
+
/**
|
|
39
|
+
* The schema of the input. Given, the input is parsed into a copy that the handler
|
|
40
|
+
* receives, so the caller's value never changes; this is how a wrapped function is
|
|
41
|
+
* validated (its arguments as a `z.tuple` when it takes several). Absent, the schema
|
|
42
|
+
* attached to the request class applies, in place.
|
|
43
|
+
*/
|
|
44
|
+
schema?: ZodType;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pipeline behavior that parses the incoming request (command, query, or event)
|
|
48
|
+
* with a Zod schema when one is attached to the request class via the `_zodSchema`
|
|
49
|
+
* static property (set automatically by `createCommand()`, `createQuery()`, or `createZodRequest()`).
|
|
50
|
+
*
|
|
51
|
+
* **How it works:**
|
|
52
|
+
* - If `context.requestType._zodSchema` is a `ZodType`, the behavior runs
|
|
53
|
+
* `schema.safeParseAsync(context.request)`.
|
|
54
|
+
* - On failure it throws {@link ZodValidationError}; `toHttpResponse()` from
|
|
55
|
+
* `@cqrs-ddd/pipeline-zod/http` maps it to an HTTP 400.
|
|
56
|
+
* - On success, the parsed result must be a plain object because pipeline
|
|
57
|
+
* request identity is preserved in-place. Keys omitted by the schema are
|
|
58
|
+
* deleted and parsed/coerced/defaulted values are assigned before the handler
|
|
59
|
+
* runs. A top-level transform to an array, primitive, Date, or other
|
|
60
|
+
* non-record shape is rejected rather than corrupting the request instance.
|
|
61
|
+
* - If no schema is attached (e.g. a plain event class), the behavior is a transparent
|
|
62
|
+
* no-op and simply calls `next()`.
|
|
63
|
+
*
|
|
64
|
+
* **Registration — globally for all request kinds:**
|
|
65
|
+
* ```ts
|
|
66
|
+
* createPipeline({
|
|
67
|
+
* globalBehaviors: {
|
|
68
|
+
* scope: 'all',
|
|
69
|
+
* before: [ZodValidationBehavior],
|
|
70
|
+
* },
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* **Registration — per handler only:**
|
|
75
|
+
* ```ts
|
|
76
|
+
* class CreateUserHandler {
|
|
77
|
+
* @pipeline.wrap({ kind: 'command' }, ZodValidationBehavior)
|
|
78
|
+
* async handle(command: CreateUserCommand) {}
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare class ZodValidationBehavior implements IPipelineBehavior {
|
|
83
|
+
handle(context: IPipelineContext, next: NextDelegate): Promise<unknown>;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=zod-validation.behavior.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod-validation.behavior.d.ts","sourceRoot":"","sources":["../src/zod-validation.behavior.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EAEb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAM9B,OAAO,EACL,WAAW,EACX,gBAAgB,EAIhB,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,cAAc,EAAG,YAAqB,CAAC;AAEpD,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,qBAAsB,YAAW,iBAAiB;IACvD,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CA0ClB;CACF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { replaceRequest, } from '@cqrs-ddd/pipeline';
|
|
3
|
+
import { untyped } from '@cqrs-ddd/untyped';
|
|
4
|
+
import { ZodValidationError } from './errors/zod-validation.error.js';
|
|
5
|
+
import { assertPlainRequestOutput, defineEnumerableDataProperties, } from './helpers/request-output.js';
|
|
6
|
+
import { getRawInput, getValidatedData, getValidationState, hasBeenMutated, setValidatedData, ZOD_RAW_INPUT_KEY, ZOD_VALIDATED_DATA_KEY, } from './helpers/zod-data.helpers.js';
|
|
7
|
+
export { getRawInput, getValidatedData, ZOD_RAW_INPUT_KEY, ZOD_VALIDATED_DATA_KEY, };
|
|
8
|
+
/**
|
|
9
|
+
* Conventional property key used to attach a Zod schema to a command, query, or event class.
|
|
10
|
+
*
|
|
11
|
+
* Classes built with `createCommand()`, `createQuery()`, or `createZodRequest()` automatically receive this property, so
|
|
12
|
+
* {@link ZodValidationBehavior} can introspect and validate without extra wiring.
|
|
13
|
+
*
|
|
14
|
+
* For manually-written event classes you can attach the schema yourself:
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { ZOD_SCHEMA_KEY } from '@cqrs-ddd/pipeline-zod';
|
|
19
|
+
* import { z } from 'zod';
|
|
20
|
+
*
|
|
21
|
+
* const userCreatedSchema = z.object({
|
|
22
|
+
* userId: z.uuid(),
|
|
23
|
+
* username: z.string().min(1),
|
|
24
|
+
* email: z.email(),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* export class UserCreatedEvent {
|
|
28
|
+
* static readonly [ZOD_SCHEMA_KEY] = userCreatedSchema;
|
|
29
|
+
*
|
|
30
|
+
* constructor(
|
|
31
|
+
* public readonly userId: string,
|
|
32
|
+
* public readonly username: string,
|
|
33
|
+
* public readonly email: string,
|
|
34
|
+
* ) {}
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export const ZOD_SCHEMA_KEY = '_zodSchema';
|
|
39
|
+
/**
|
|
40
|
+
* Pipeline behavior that parses the incoming request (command, query, or event)
|
|
41
|
+
* with a Zod schema when one is attached to the request class via the `_zodSchema`
|
|
42
|
+
* static property (set automatically by `createCommand()`, `createQuery()`, or `createZodRequest()`).
|
|
43
|
+
*
|
|
44
|
+
* **How it works:**
|
|
45
|
+
* - If `context.requestType._zodSchema` is a `ZodType`, the behavior runs
|
|
46
|
+
* `schema.safeParseAsync(context.request)`.
|
|
47
|
+
* - On failure it throws {@link ZodValidationError}; `toHttpResponse()` from
|
|
48
|
+
* `@cqrs-ddd/pipeline-zod/http` maps it to an HTTP 400.
|
|
49
|
+
* - On success, the parsed result must be a plain object because pipeline
|
|
50
|
+
* request identity is preserved in-place. Keys omitted by the schema are
|
|
51
|
+
* deleted and parsed/coerced/defaulted values are assigned before the handler
|
|
52
|
+
* runs. A top-level transform to an array, primitive, Date, or other
|
|
53
|
+
* non-record shape is rejected rather than corrupting the request instance.
|
|
54
|
+
* - If no schema is attached (e.g. a plain event class), the behavior is a transparent
|
|
55
|
+
* no-op and simply calls `next()`.
|
|
56
|
+
*
|
|
57
|
+
* **Registration — globally for all request kinds:**
|
|
58
|
+
* ```ts
|
|
59
|
+
* createPipeline({
|
|
60
|
+
* globalBehaviors: {
|
|
61
|
+
* scope: 'all',
|
|
62
|
+
* before: [ZodValidationBehavior],
|
|
63
|
+
* },
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* **Registration — per handler only:**
|
|
68
|
+
* ```ts
|
|
69
|
+
* class CreateUserHandler {
|
|
70
|
+
* @pipeline.wrap({ kind: 'command' }, ZodValidationBehavior)
|
|
71
|
+
* async handle(command: CreateUserCommand) {}
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class ZodValidationBehavior {
|
|
76
|
+
async handle(context, next) {
|
|
77
|
+
const declared = context.getBehaviorOptions(ZodValidationBehavior)?.schema;
|
|
78
|
+
if (declared) {
|
|
79
|
+
const parsed = await declared.safeParseAsync(context.request);
|
|
80
|
+
if (!parsed.success)
|
|
81
|
+
throw new ZodValidationError(parsed.error);
|
|
82
|
+
replaceRequest(context, parsed.data);
|
|
83
|
+
return next();
|
|
84
|
+
}
|
|
85
|
+
const schema = context.requestType
|
|
86
|
+
? untyped(context.requestType)[ZOD_SCHEMA_KEY]
|
|
87
|
+
: undefined;
|
|
88
|
+
if (!schema)
|
|
89
|
+
return next();
|
|
90
|
+
if (!context.request || typeof context.request !== 'object') {
|
|
91
|
+
throw new TypeError('ZodValidationBehavior requires the pipeline request to be an object when a schema is attached.');
|
|
92
|
+
}
|
|
93
|
+
const request = context.request;
|
|
94
|
+
const state = getValidationState(request);
|
|
95
|
+
if (state?.schema === schema && !hasBeenMutated(request, state.snapshot))
|
|
96
|
+
return next();
|
|
97
|
+
const result = await schema.safeParseAsync(request);
|
|
98
|
+
if (!result.success)
|
|
99
|
+
throw new ZodValidationError(result.error);
|
|
100
|
+
assertPlainRequestOutput(result.data, 'behavior');
|
|
101
|
+
// A previously parsed request only gives up the fields its schema produced;
|
|
102
|
+
// an unvalidated one gives up every field the schema does not keep.
|
|
103
|
+
const removable = state
|
|
104
|
+
? Object.keys(state.snapshot)
|
|
105
|
+
: Object.keys(request);
|
|
106
|
+
for (const key of removable) {
|
|
107
|
+
if (!Object.hasOwn(result.data, key))
|
|
108
|
+
delete request[key];
|
|
109
|
+
}
|
|
110
|
+
defineEnumerableDataProperties(request, result.data);
|
|
111
|
+
setValidatedData(request, schema, result.data);
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=zod-validation.behavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod-validation.behavior.js","sourceRoot":"","sources":["../src/zod-validation.behavior.ts"],"names":[],"mappings":"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"}
|