@naturalcycles/js-lib 15.20.0 → 15.22.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.
@@ -59,9 +59,9 @@
59
59
  "jsx": "preserve",
60
60
  "pretty": true,
61
61
  "newLine": "lf",
62
- "experimentalDecorators": true,
62
+ "experimentalDecorators": true
63
63
  // "emitDecoratorMetadata": true // use if needed
64
- },
64
+ }
65
65
  // Need to be specified in the project tsconfig
66
66
  // "include": ["src"],
67
67
  // "exclude": ["**/__exclude", "**/@linked"]
@@ -291,7 +291,7 @@ declare class LocalTimeFactory {
291
291
  /**
292
292
  * Throws on invalid value.
293
293
  */
294
- private validateDateTimeObject;
294
+ validateDateTimeObject(o: DateTimeObject): void;
295
295
  isDateTimeObjectValid(o: DateTimeObject): boolean;
296
296
  isTimeObjectValid({ hour, minute, second }: TimeObject): boolean;
297
297
  fromDate(date: Date): LocalTime;
@@ -17,6 +17,16 @@ import type { AppError } from '../error/error.util.js';
17
17
  export type ValidationFunction<T, ERR extends AppError> = (input: T, opt?: ValidationFunctionOptions) => ValidationFunctionResult<T, ERR>;
18
18
  export type ValidationFunctionResult<T, ERR extends AppError> = [err: ERR | null, output: T];
19
19
  export interface ValidationFunctionOptions {
20
+ /**
21
+ * Defaults to false,
22
+ * which means that the ValidationFunction IS NOT ALLOWED to mutate the input.
23
+ * If set to true - the ValidationFunction HAS TO mutate the input
24
+ * if it needs to apply transformations, such as:
25
+ * - stripping unknown properties
26
+ * - converting types (e.g. string to number)
27
+ * - applying transformations (which as string trim, toLowerCase, etc)
28
+ */
29
+ mutateInput?: boolean;
20
30
  /**
21
31
  * E.g User
22
32
  * Used for error message printing.
@@ -1,44 +1,47 @@
1
+ import type { ZodString } from 'zod';
1
2
  import { z } from 'zod';
2
3
  import type { IsoDate, UnixTimestamp, UnixTimestampMillis } from '../types.js';
3
4
  type ZodBranded<T, B> = T & Record<'_zod', Record<'output', number & B>>;
4
5
  export type ZodBrandedString<B> = ZodBranded<z.ZodString, B>;
5
6
  export type ZodBrandedInt<B> = ZodBranded<z.ZodInt, B>;
6
7
  export type ZodBrandedNumber<B> = ZodBranded<z.ZodNumber, B>;
7
- export declare const TS_2500 = 16725225600;
8
- export declare const TS_2000 = 946684800;
9
- export declare const zUnixTimestamp: () => ZodBrandedInt<UnixTimestamp>;
10
- export declare const zUnixTimestamp2000: () => ZodBrandedInt<UnixTimestamp>;
11
- export declare const zUnixTimestampMillis: () => ZodBranded<z.ZodNumber, UnixTimestampMillis>;
12
- export declare const zUnixTimestampMillis2000: () => ZodBrandedInt<UnixTimestampMillis>;
13
- export declare const zSemVer: () => z.ZodString;
14
- export declare const zIsoDate: () => ZodBrandedString<IsoDate>;
15
- export declare const zEmail: () => z.ZodEmail;
16
- export declare const BASE62_REGEX: RegExp;
17
- export declare const BASE64_REGEX: RegExp;
18
- export declare const BASE64URL_REGEX: RegExp;
19
- export declare const zBase62: () => z.ZodString;
20
- export declare const zBase64: () => z.ZodString;
21
- export declare const zBase64Url: () => z.ZodString;
22
- export declare const JWT_REGEX: RegExp;
23
- export declare const zJwt: () => z.ZodString;
8
+ declare function unixTimestamp(): ZodBrandedInt<UnixTimestamp>;
9
+ declare function unixTimestamp2000(): ZodBrandedInt<UnixTimestamp>;
10
+ declare function unixTimestampMillis(): ZodBranded<z.ZodNumber, UnixTimestampMillis>;
11
+ declare function unixTimestampMillis2000(): ZodBrandedInt<UnixTimestampMillis>;
12
+ declare function semVer(): z.ZodString;
13
+ declare function isoDate(): ZodBrandedString<IsoDate>;
14
+ declare function email(): z.ZodEmail;
15
+ declare function base62(): z.ZodString;
16
+ declare function base64(): z.ZodString;
17
+ declare function base64Url(): z.ZodString;
18
+ declare function jwt(): z.ZodString;
24
19
  /**
25
20
  * "Slug" - a valid URL, filename, etc.
26
21
  */
27
- export declare const zSlug: () => z.ZodString;
28
- export declare const zIanaTimezone: () => z.ZodEnum;
22
+ declare function slug(): z.ZodString;
23
+ declare function ianaTimezone(): z.ZodEnum;
24
+ type BaseDBEntityZodShape = {
25
+ id: ZodString;
26
+ created: ZodBrandedInt<UnixTimestamp>;
27
+ updated: ZodBrandedInt<UnixTimestamp>;
28
+ };
29
+ declare function dbEntity(): z.ZodObject<BaseDBEntityZodShape>;
30
+ declare function dbEntity<T extends z.ZodRawShape>(shape: T): z.ZodObject<BaseDBEntityZodShape & T>;
29
31
  export declare const customZodSchemas: {
30
- base62: () => z.ZodString;
31
- base64: () => z.ZodString;
32
- base64Url: () => z.ZodString;
33
- email: () => z.ZodEmail;
34
- ianaTimezone: () => z.ZodEnum;
35
- isoDate: () => ZodBrandedString<IsoDate>;
36
- jwt: () => z.ZodString;
37
- slug: () => z.ZodString;
38
- semver: () => z.ZodString;
39
- unixTimestamp: () => ZodBrandedInt<UnixTimestamp>;
40
- unixTimestamp2000: () => ZodBrandedInt<UnixTimestamp>;
41
- unixTimestampMillis: () => ZodBranded<z.ZodNumber, UnixTimestampMillis>;
42
- unixTimestampMillis2000: () => ZodBrandedInt<UnixTimestampMillis>;
32
+ base62: typeof base62;
33
+ base64: typeof base64;
34
+ base64Url: typeof base64Url;
35
+ dbEntity: typeof dbEntity;
36
+ email: typeof email;
37
+ ianaTimezone: typeof ianaTimezone;
38
+ isoDate: typeof isoDate;
39
+ jwt: typeof jwt;
40
+ slug: typeof slug;
41
+ semver: typeof semVer;
42
+ unixTimestamp: typeof unixTimestamp;
43
+ unixTimestamp2000: typeof unixTimestamp2000;
44
+ unixTimestampMillis: typeof unixTimestampMillis;
45
+ unixTimestampMillis2000: typeof unixTimestampMillis2000;
43
46
  };
44
47
  export {};
@@ -1,71 +1,106 @@
1
1
  import { z } from 'zod';
2
- export const TS_2500 = 16725225600; // 2500-01-01
3
- export const TS_2000 = 946684800; // 2000-01-01
4
- export const zUnixTimestamp = () => z
5
- .number()
6
- .int()
7
- .min(0)
8
- .max(TS_2500, 'Must be a UnixTimestamp number')
9
- .describe('UnixTimestamp');
10
- export const zUnixTimestamp2000 = () => z
11
- .number()
12
- .int()
13
- .min(TS_2000)
14
- .max(TS_2500, 'Must be a UnixTimestamp number after 2000-01-01')
15
- .describe('UnixTimestamp2000');
16
- export const zUnixTimestampMillis = () => z
17
- .number()
18
- .int()
19
- .min(0)
20
- .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number')
21
- .describe('UnixTimestampMillis');
22
- export const zUnixTimestampMillis2000 = () => z
23
- .number()
24
- .int()
25
- .min(TS_2000 * 1000)
26
- .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number after 2000-01-01')
27
- .describe('UnixTimestampMillis2000');
28
- export const zSemVer = () => z
29
- .string()
30
- .regex(/^[0-9]+\.[0-9]+\.[0-9]+$/, 'Must be a SemVer string')
31
- .describe('SemVer');
32
- export const zIsoDate = () => z
33
- .string()
34
- .refine(v => {
35
- return /^\d{4}-\d{2}-\d{2}$/.test(v);
36
- }, 'Must be an IsoDateString')
37
- .describe('IsoDateString');
38
- export const zEmail = () => z.email().describe('Email');
39
- export const BASE62_REGEX = /^[a-zA-Z0-9]+$/;
40
- export const BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/;
41
- export const BASE64URL_REGEX = /^[\w\-/]+$/;
42
- export const zBase62 = () => z.string().regex(BASE62_REGEX, 'Must be a base62 string').describe('Base62String');
43
- export const zBase64 = () => z.string().regex(BASE64_REGEX, 'Must be a base64 string').describe('Base64String');
44
- export const zBase64Url = () => z.string().regex(BASE64URL_REGEX, 'Must be a base64url string').describe('Base64UrlString');
45
- export const JWT_REGEX = /^[\w-]+\.[\w-]+\.[\w-]+$/;
46
- export const zJwt = () => z.string().regex(JWT_REGEX, 'Must be a JWT string').describe('JWTString');
2
+ const TS_2500 = 16725225600; // 2500-01-01
3
+ const TS_2000 = 946684800; // 2000-01-01
4
+ function unixTimestamp() {
5
+ return z
6
+ .number()
7
+ .int()
8
+ .min(0)
9
+ .max(TS_2500, 'Must be a UnixTimestamp number')
10
+ .describe('UnixTimestamp');
11
+ }
12
+ function unixTimestamp2000() {
13
+ return z
14
+ .number()
15
+ .int()
16
+ .min(TS_2000)
17
+ .max(TS_2500, 'Must be a UnixTimestamp number after 2000-01-01')
18
+ .describe('UnixTimestamp2000');
19
+ }
20
+ function unixTimestampMillis() {
21
+ return z
22
+ .number()
23
+ .int()
24
+ .min(0)
25
+ .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number')
26
+ .describe('UnixTimestampMillis');
27
+ }
28
+ function unixTimestampMillis2000() {
29
+ return z
30
+ .number()
31
+ .int()
32
+ .min(TS_2000 * 1000)
33
+ .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number after 2000-01-01')
34
+ .describe('UnixTimestampMillis2000');
35
+ }
36
+ function semVer() {
37
+ return z
38
+ .string()
39
+ .regex(/^[0-9]+\.[0-9]+\.[0-9]+$/, 'Must be a SemVer string')
40
+ .describe('SemVer');
41
+ }
42
+ function isoDate() {
43
+ return z
44
+ .string()
45
+ .refine(v => {
46
+ return /^\d{4}-\d{2}-\d{2}$/.test(v);
47
+ }, 'Must be an IsoDateString')
48
+ .describe('IsoDateString');
49
+ }
50
+ function email() {
51
+ return z.email().describe('Email');
52
+ }
53
+ const BASE62_REGEX = /^[a-zA-Z0-9]+$/;
54
+ const BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/;
55
+ const BASE64URL_REGEX = /^[\w\-/]+$/;
56
+ function base62() {
57
+ return z.string().regex(BASE62_REGEX, 'Must be a base62 string').describe('Base62String');
58
+ }
59
+ function base64() {
60
+ return z.string().regex(BASE64_REGEX, 'Must be a base64 string').describe('Base64String');
61
+ }
62
+ function base64Url() {
63
+ return z.string().regex(BASE64URL_REGEX, 'Must be a base64url string').describe('Base64UrlString');
64
+ }
65
+ const JWT_REGEX = /^[\w-]+\.[\w-]+\.[\w-]+$/;
66
+ function jwt() {
67
+ return z.string().regex(JWT_REGEX, 'Must be a JWT string').describe('JWTString');
68
+ }
47
69
  /**
48
70
  * "Slug" - a valid URL, filename, etc.
49
71
  */
50
- export const zSlug = () => z
51
- .string()
52
- .regex(/^[a-z0-9-]{1,255}$/, 'Must be a slug string')
53
- .describe('Slug');
54
- export const zIanaTimezone = () => z
55
- // UTC is added to assist unit-testing, which uses UTC by default (not technically a valid Iana timezone identifier)
56
- .enum([...Intl.supportedValuesOf('timeZone'), 'UTC']);
72
+ function slug() {
73
+ return z
74
+ .string()
75
+ .regex(/^[a-z0-9-]{1,255}$/, 'Must be a slug string')
76
+ .describe('Slug');
77
+ }
78
+ function ianaTimezone() {
79
+ return (z
80
+ // UTC is added to assist unit-testing, which uses UTC by default (not technically a valid Iana timezone identifier)
81
+ .enum([...Intl.supportedValuesOf('timeZone'), 'UTC']));
82
+ }
83
+ const baseDBEntitySchema = z.object({
84
+ id: z.string(),
85
+ created: unixTimestamp2000(),
86
+ updated: unixTimestamp2000(),
87
+ });
88
+ function dbEntity(shape) {
89
+ return baseDBEntitySchema.extend(shape ?? {});
90
+ }
57
91
  export const customZodSchemas = {
58
- base62: zBase62,
59
- base64: zBase64,
60
- base64Url: zBase64Url,
61
- email: zEmail,
62
- ianaTimezone: zIanaTimezone,
63
- isoDate: zIsoDate,
64
- jwt: zJwt,
65
- slug: zSlug,
66
- semver: zSemVer,
67
- unixTimestamp: zUnixTimestamp,
68
- unixTimestamp2000: zUnixTimestamp2000,
69
- unixTimestampMillis: zUnixTimestampMillis,
70
- unixTimestampMillis2000: zUnixTimestampMillis2000,
92
+ base62,
93
+ base64,
94
+ base64Url,
95
+ dbEntity,
96
+ email,
97
+ ianaTimezone,
98
+ isoDate,
99
+ jwt,
100
+ slug,
101
+ semver: semVer,
102
+ unixTimestamp,
103
+ unixTimestamp2000,
104
+ unixTimestampMillis,
105
+ unixTimestampMillis2000,
71
106
  };
@@ -1,7 +1,8 @@
1
1
  import type { ZodError, ZodType } from 'zod';
2
2
  import type { ErrorData } from '../error/error.model.js';
3
3
  import { AppError } from '../error/error.util.js';
4
- import type { ValidationFunctionResult } from '../validation/validation.js';
4
+ import type { ValidationFunction, ValidationFunctionResult } from '../validation/validation.js';
5
+ export declare function getZodValidationFunction<T>(schema: ZodType<T>): ValidationFunction<T, ZodValidationError>;
5
6
  export declare function zIsValid<T>(value: T, schema: ZodType<T>): boolean;
6
7
  export declare function zValidate<T>(value: T, schema: ZodType<T>): T;
7
8
  export declare function zSafeValidate<T>(input: T, schema: ZodType<T>): ValidationFunctionResult<T, ZodValidationError>;
@@ -1,5 +1,12 @@
1
+ import { _assert } from '../error/assert.js';
1
2
  import { AppError } from '../error/error.util.js';
2
3
  import { _stringify } from '../string/stringify.js';
4
+ export function getZodValidationFunction(schema) {
5
+ return (input, opt) => {
6
+ _assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Zod');
7
+ return zSafeValidate(input, schema);
8
+ };
9
+ }
3
10
  export function zIsValid(value, schema) {
4
11
  const { success } = schema.safeParse(value);
5
12
  return success;
@@ -27,9 +34,9 @@ export class ZodValidationError extends AppError {
27
34
  function createZodErrorMessage(err, schema, value) {
28
35
  let objectTitle = schema.description;
29
36
  if (typeof value === 'object' && value) {
30
- const objectName = schema.description || value.constructor?.name;
31
- const objectId = value['id'];
32
- objectTitle = [objectName, objectId].filter(Boolean).join('.');
37
+ const inputName = schema.description || value.constructor?.name;
38
+ const inputId = value['id'];
39
+ objectTitle = [inputName, inputId].filter(Boolean).join('.');
33
40
  }
34
41
  objectTitle ||= 'data';
35
42
  return [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
3
  "type": "module",
4
- "version": "15.20.0",
4
+ "version": "15.22.0",
5
5
  "dependencies": {
6
6
  "tslib": "^2",
7
7
  "zod": "^4"
@@ -12,7 +12,7 @@
12
12
  "@types/semver": "^7",
13
13
  "crypto-js": "^4",
14
14
  "dayjs": "^1",
15
- "@naturalcycles/dev-lib": "19.14.0"
15
+ "@naturalcycles/dev-lib": "19.22.0"
16
16
  },
17
17
  "exports": {
18
18
  ".": "./dist/index.js",
@@ -932,7 +932,7 @@ class LocalTimeFactory {
932
932
  /**
933
933
  * Throws on invalid value.
934
934
  */
935
- private validateDateTimeObject(o: DateTimeObject): void {
935
+ validateDateTimeObject(o: DateTimeObject): void {
936
936
  _assert(
937
937
  this.isDateTimeObjectValid(o),
938
938
  `Cannot construct LocalTime from: ${o.year}-${o.month}-${o.day} ${o.hour}:${o.minute}:${o.second}`,
@@ -23,6 +23,16 @@ export type ValidationFunction<T, ERR extends AppError> = (
23
23
  export type ValidationFunctionResult<T, ERR extends AppError> = [err: ERR | null, output: T]
24
24
 
25
25
  export interface ValidationFunctionOptions {
26
+ /**
27
+ * Defaults to false,
28
+ * which means that the ValidationFunction IS NOT ALLOWED to mutate the input.
29
+ * If set to true - the ValidationFunction HAS TO mutate the input
30
+ * if it needs to apply transformations, such as:
31
+ * - stripping unknown properties
32
+ * - converting types (e.g. string to number)
33
+ * - applying transformations (which as string trim, toLowerCase, etc)
34
+ */
35
+ mutateInput?: boolean
26
36
  /**
27
37
  * E.g User
28
38
  * Used for error message printing.
@@ -1,3 +1,4 @@
1
+ import type { ZodString } from 'zod'
1
2
  import { z } from 'zod'
2
3
  import type { IsoDate, UnixTimestamp, UnixTimestampMillis } from '../types.js'
3
4
 
@@ -6,100 +7,138 @@ export type ZodBrandedString<B> = ZodBranded<z.ZodString, B>
6
7
  export type ZodBrandedInt<B> = ZodBranded<z.ZodInt, B>
7
8
  export type ZodBrandedNumber<B> = ZodBranded<z.ZodNumber, B>
8
9
 
9
- export const TS_2500 = 16725225600 // 2500-01-01
10
- export const TS_2000 = 946684800 // 2000-01-01
10
+ const TS_2500 = 16725225600 // 2500-01-01
11
+ const TS_2000 = 946684800 // 2000-01-01
11
12
 
12
- export const zUnixTimestamp = (): ZodBrandedInt<UnixTimestamp> =>
13
- z
13
+ function unixTimestamp(): ZodBrandedInt<UnixTimestamp> {
14
+ return z
14
15
  .number()
15
16
  .int()
16
17
  .min(0)
17
18
  .max(TS_2500, 'Must be a UnixTimestamp number')
18
19
  .describe('UnixTimestamp') as ZodBrandedInt<UnixTimestamp>
20
+ }
19
21
 
20
- export const zUnixTimestamp2000 = (): ZodBrandedInt<UnixTimestamp> =>
21
- z
22
+ function unixTimestamp2000(): ZodBrandedInt<UnixTimestamp> {
23
+ return z
22
24
  .number()
23
25
  .int()
24
26
  .min(TS_2000)
25
27
  .max(TS_2500, 'Must be a UnixTimestamp number after 2000-01-01')
26
28
  .describe('UnixTimestamp2000') as ZodBrandedInt<UnixTimestamp>
29
+ }
27
30
 
28
- export const zUnixTimestampMillis = (): ZodBranded<z.ZodNumber, UnixTimestampMillis> =>
29
- z
31
+ function unixTimestampMillis(): ZodBranded<z.ZodNumber, UnixTimestampMillis> {
32
+ return z
30
33
  .number()
31
34
  .int()
32
35
  .min(0)
33
36
  .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number')
34
37
  .describe('UnixTimestampMillis') as ZodBrandedInt<UnixTimestampMillis>
38
+ }
35
39
 
36
- export const zUnixTimestampMillis2000 = (): ZodBrandedInt<UnixTimestampMillis> =>
37
- z
40
+ function unixTimestampMillis2000(): ZodBrandedInt<UnixTimestampMillis> {
41
+ return z
38
42
  .number()
39
43
  .int()
40
44
  .min(TS_2000 * 1000)
41
45
  .max(TS_2500 * 1000, 'Must be a UnixTimestampMillis number after 2000-01-01')
42
46
  .describe('UnixTimestampMillis2000') as ZodBrandedInt<UnixTimestampMillis>
47
+ }
43
48
 
44
- export const zSemVer = (): z.ZodString =>
45
- z
49
+ function semVer(): z.ZodString {
50
+ return z
46
51
  .string()
47
52
  .regex(/^[0-9]+\.[0-9]+\.[0-9]+$/, 'Must be a SemVer string')
48
53
  .describe('SemVer')
54
+ }
49
55
 
50
- export const zIsoDate = (): ZodBrandedString<IsoDate> =>
51
- z
56
+ function isoDate(): ZodBrandedString<IsoDate> {
57
+ return z
52
58
  .string()
53
59
  .refine(v => {
54
60
  return /^\d{4}-\d{2}-\d{2}$/.test(v)
55
61
  }, 'Must be an IsoDateString')
56
62
  .describe('IsoDateString') as ZodBrandedString<IsoDate>
63
+ }
57
64
 
58
- export const zEmail = (): z.ZodEmail => z.email().describe('Email')
65
+ function email(): z.ZodEmail {
66
+ return z.email().describe('Email')
67
+ }
59
68
 
60
- export const BASE62_REGEX = /^[a-zA-Z0-9]+$/
61
- export const BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/
62
- export const BASE64URL_REGEX = /^[\w\-/]+$/
69
+ const BASE62_REGEX = /^[a-zA-Z0-9]+$/
70
+ const BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/
71
+ const BASE64URL_REGEX = /^[\w\-/]+$/
63
72
 
64
- export const zBase62 = (): z.ZodString =>
65
- z.string().regex(BASE62_REGEX, 'Must be a base62 string').describe('Base62String')
73
+ function base62(): z.ZodString {
74
+ return z.string().regex(BASE62_REGEX, 'Must be a base62 string').describe('Base62String')
75
+ }
66
76
 
67
- export const zBase64 = (): z.ZodString =>
68
- z.string().regex(BASE64_REGEX, 'Must be a base64 string').describe('Base64String')
77
+ function base64(): z.ZodString {
78
+ return z.string().regex(BASE64_REGEX, 'Must be a base64 string').describe('Base64String')
79
+ }
69
80
 
70
- export const zBase64Url = (): z.ZodString =>
71
- z.string().regex(BASE64URL_REGEX, 'Must be a base64url string').describe('Base64UrlString')
81
+ function base64Url(): z.ZodString {
82
+ return z.string().regex(BASE64URL_REGEX, 'Must be a base64url string').describe('Base64UrlString')
83
+ }
72
84
 
73
- export const JWT_REGEX = /^[\w-]+\.[\w-]+\.[\w-]+$/
74
- export const zJwt = (): z.ZodString =>
75
- z.string().regex(JWT_REGEX, 'Must be a JWT string').describe('JWTString')
85
+ const JWT_REGEX = /^[\w-]+\.[\w-]+\.[\w-]+$/
86
+
87
+ function jwt(): z.ZodString {
88
+ return z.string().regex(JWT_REGEX, 'Must be a JWT string').describe('JWTString')
89
+ }
76
90
 
77
91
  /**
78
92
  * "Slug" - a valid URL, filename, etc.
79
93
  */
80
- export const zSlug = (): z.ZodString =>
81
- z
94
+ function slug(): z.ZodString {
95
+ return z
82
96
  .string()
83
97
  .regex(/^[a-z0-9-]{1,255}$/, 'Must be a slug string')
84
98
  .describe('Slug')
99
+ }
100
+
101
+ function ianaTimezone(): z.ZodEnum {
102
+ return (
103
+ z
104
+ // UTC is added to assist unit-testing, which uses UTC by default (not technically a valid Iana timezone identifier)
105
+ .enum([...Intl.supportedValuesOf('timeZone'), 'UTC'])
106
+ )
107
+ }
85
108
 
86
- export const zIanaTimezone = (): z.ZodEnum =>
87
- z
88
- // UTC is added to assist unit-testing, which uses UTC by default (not technically a valid Iana timezone identifier)
89
- .enum([...Intl.supportedValuesOf('timeZone'), 'UTC'])
109
+ const baseDBEntitySchema = z.object({
110
+ id: z.string(),
111
+ created: unixTimestamp2000(),
112
+ updated: unixTimestamp2000(),
113
+ })
114
+
115
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
116
+ type BaseDBEntityZodShape = {
117
+ id: ZodString
118
+ created: ZodBrandedInt<UnixTimestamp>
119
+ updated: ZodBrandedInt<UnixTimestamp>
120
+ }
121
+
122
+ function dbEntity(): z.ZodObject<BaseDBEntityZodShape>
123
+ function dbEntity<T extends z.ZodRawShape>(shape: T): z.ZodObject<BaseDBEntityZodShape & T>
124
+
125
+ function dbEntity<T extends z.ZodRawShape>(shape?: T): z.ZodObject<BaseDBEntityZodShape & T> {
126
+ return baseDBEntitySchema.extend(shape ?? {}) as z.ZodObject<BaseDBEntityZodShape & T>
127
+ }
90
128
 
91
129
  export const customZodSchemas = {
92
- base62: zBase62,
93
- base64: zBase64,
94
- base64Url: zBase64Url,
95
- email: zEmail,
96
- ianaTimezone: zIanaTimezone,
97
- isoDate: zIsoDate,
98
- jwt: zJwt,
99
- slug: zSlug,
100
- semver: zSemVer,
101
- unixTimestamp: zUnixTimestamp,
102
- unixTimestamp2000: zUnixTimestamp2000,
103
- unixTimestampMillis: zUnixTimestampMillis,
104
- unixTimestampMillis2000: zUnixTimestampMillis2000,
130
+ base62,
131
+ base64,
132
+ base64Url,
133
+ dbEntity,
134
+ email,
135
+ ianaTimezone,
136
+ isoDate,
137
+ jwt,
138
+ slug,
139
+ semver: semVer,
140
+ unixTimestamp,
141
+ unixTimestamp2000,
142
+ unixTimestampMillis,
143
+ unixTimestampMillis2000,
105
144
  }
@@ -1,8 +1,18 @@
1
1
  import type { ZodError, ZodType } from 'zod'
2
+ import { _assert } from '../error/assert.js'
2
3
  import type { ErrorData } from '../error/error.model.js'
3
4
  import { AppError } from '../error/error.util.js'
4
5
  import { _stringify } from '../string/stringify.js'
5
- import type { ValidationFunctionResult } from '../validation/validation.js'
6
+ import type { ValidationFunction, ValidationFunctionResult } from '../validation/validation.js'
7
+
8
+ export function getZodValidationFunction<T>(
9
+ schema: ZodType<T>,
10
+ ): ValidationFunction<T, ZodValidationError> {
11
+ return (input, opt) => {
12
+ _assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Zod')
13
+ return zSafeValidate(input, schema)
14
+ }
15
+ }
6
16
 
7
17
  export function zIsValid<T>(value: T, schema: ZodType<T>): boolean {
8
18
  const { success } = schema.safeParse(value)
@@ -18,7 +28,7 @@ export function zValidate<T>(value: T, schema: ZodType<T>): T {
18
28
  export function zSafeValidate<T>(
19
29
  input: T,
20
30
  schema: ZodType<T>,
21
- // objectName?: string,
31
+ // inputName?: string,
22
32
  ): ValidationFunctionResult<T, ZodValidationError> {
23
33
  const r = schema.safeParse(input)
24
34
  if (r.success) {
@@ -28,19 +38,7 @@ export function zSafeValidate<T>(
28
38
  return [new ZodValidationError(r.error, input, schema), r.data ?? input]
29
39
  }
30
40
 
31
- export interface ZodValidationErrorData extends ErrorData {
32
- // issues: $ZodIssue[]
33
- // joiValidationObjectName?: string
34
- // joiValidationObjectId?: string
35
- /**
36
- * Error "annotation" is stripped in Error.message.
37
- * This field contains the "full" annotation.
38
- *
39
- * This field is non-enumerable, won't be printed or included in JSON by default,
40
- * but still accessible programmatically (via `err.data.annotation`) when needed!
41
- */
42
- // annotation?: string
43
- }
41
+ export interface ZodValidationErrorData extends ErrorData {}
44
42
 
45
43
  export class ZodValidationError extends AppError<ZodValidationErrorData> {
46
44
  constructor(zodError: ZodError, value: any, schema: ZodType) {
@@ -54,9 +52,9 @@ function createZodErrorMessage<T>(err: ZodError<T>, schema: ZodType<T>, value: T
54
52
  let objectTitle = schema.description
55
53
 
56
54
  if (typeof value === 'object' && value) {
57
- const objectName = schema.description || value.constructor?.name
58
- const objectId = (value as any)['id'] as string
59
- objectTitle = [objectName, objectId].filter(Boolean).join('.')
55
+ const inputName = schema.description || value.constructor?.name
56
+ const inputId = (value as any)['id'] as string
57
+ objectTitle = [inputName, inputId].filter(Boolean).join('.')
60
58
  }
61
59
 
62
60
  objectTitle ||= 'data'