@tstdl/base 0.93.51 → 0.93.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/browser/utils.js +2 -2
  2. package/object-storage/google/google.object-storage-provider.d.ts +44 -0
  3. package/object-storage/google/google.object-storage-provider.js +81 -0
  4. package/object-storage/google/google.object-storage.d.ts +32 -0
  5. package/object-storage/google/google.object-storage.js +220 -0
  6. package/object-storage/google/google.object.d.ts +16 -0
  7. package/object-storage/google/google.object.js +69 -0
  8. package/object-storage/google/index.d.ts +3 -0
  9. package/object-storage/google/index.js +3 -0
  10. package/object-storage/object-storage.d.ts +7 -4
  11. package/object-storage/s3/s3.object-storage-provider.js +1 -1
  12. package/object-storage/s3/s3.object-storage.d.ts +3 -3
  13. package/object-storage/s3/s3.object-storage.js +21 -11
  14. package/object-storage/s3/s3.object.d.ts +1 -1
  15. package/object-storage/s3/s3.object.js +1 -1
  16. package/orm/sqls.js +3 -3
  17. package/package.json +2 -2
  18. package/schema/converters/zod-converter.d.ts +8 -2
  19. package/schema/converters/zod-converter.js +182 -136
  20. package/schema/schemas/bigint.js +2 -2
  21. package/schema/schemas/boolean.d.ts +1 -0
  22. package/schema/schemas/boolean.js +16 -16
  23. package/schema/schemas/constraint.d.ts +19 -0
  24. package/schema/schemas/constraint.js +36 -0
  25. package/schema/schemas/index.d.ts +1 -0
  26. package/schema/schemas/index.js +1 -0
  27. package/schema/schemas/object.d.ts +3 -1
  28. package/schema/schemas/object.js +57 -28
  29. package/schema/schemas/simple.d.ts +6 -1
  30. package/schema/schemas/simple.js +11 -2
  31. package/schema/schemas/string.d.ts +4 -2
  32. package/schema/schemas/string.js +10 -5
  33. package/schema/schemas/transform.d.ts +4 -0
  34. package/schema/schemas/transform.js +4 -0
  35. package/schema/schemas/uint8-array.d.ts +1 -0
  36. package/schema/schemas/uint8-array.js +10 -7
  37. package/templates/template.model.js +2 -2
  38. package/test6.js +112 -29
  39. package/types/types.d.ts +1 -1
  40. package/utils/base64.js +1 -2
  41. package/utils/encoding.js +2 -5
  42. package/utils/format-error.js +1 -1
  43. package/utils/object/object.js +7 -1
  44. package/utils/z-base32.js +2 -4
package/orm/sqls.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import { and, Column, eq, sql, isNotNull as sqlIsNotNull, Table } from 'drizzle-orm';
8
8
  import { match, P } from 'ts-pattern';
9
9
  import { mapObjectValues, objectEntries, objectValues } from '../utils/object/object.js';
10
- import { assertDefined, isDefined, isInstanceOf, isNotNull, isNull, isNumber, isString } from '../utils/type-guards.js';
10
+ import { assertDefined, isArray, isDefined, isInstanceOf, isNotNull, isNull, isNumber, isString } from '../utils/type-guards.js';
11
11
  import { getEnumName } from './enums.js';
12
12
  /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
13
13
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
@@ -268,7 +268,7 @@ export function tsRankCd(tsvector, tsquery, options) {
268
268
  export function tsHeadline(language, document, tsquery, options) {
269
269
  const documentSql = isString(document) ? sql `${document}` : document;
270
270
  if (isDefined(options)) {
271
- const optionsString = Object.entries(options).map(([key, value]) => `${key[0].toUpperCase()}${key.slice(1)}=${String(value)}`).join(', ');
271
+ const optionsString = objectEntries(options).map(([key, value]) => `${key[0].toUpperCase()}${key.slice(1)}=${String(value)}`).join(', ');
272
272
  return sql `ts_headline(${language}, ${documentSql}, ${tsquery}, '${sql.raw(optionsString)}')`;
273
273
  }
274
274
  return sql `ts_headline(${language}, ${documentSql}, ${tsquery})`;
@@ -322,7 +322,7 @@ export function strictWordDistance(left, right) {
322
322
  return sql `(${left} <<<-> ${right})`;
323
323
  }
324
324
  export function jsonbBuildObject(properties) {
325
- const entries = Array.isArray(properties) ? properties : Object.entries(properties);
325
+ const entries = isArray(properties) ? properties : objectEntries(properties);
326
326
  const chunks = entries
327
327
  .filter(([_, propValue]) => isDefined(propValue))
328
328
  .map(([propKey, propValue]) => sql `'${sql.raw(propKey)}', ${propValue}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.51",
3
+ "version": "0.93.54",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -152,7 +152,7 @@
152
152
  "nodemailer": "^7.0",
153
153
  "pg": "^8.16",
154
154
  "playwright": "^1.57",
155
- "preact": "^10.27",
155
+ "preact": "^10.28",
156
156
  "preact-render-to-string": "^6.6",
157
157
  "sharp": "^0.34",
158
158
  "undici": "^7.16",
@@ -1,3 +1,9 @@
1
- import * as z from 'zod/mini';
1
+ import { z } from 'zod';
2
2
  import type { SchemaTestable } from '../schema.js';
3
- export declare function convertToZodSchema<O, I = unknown>(testable: SchemaTestable<O>): z.ZodMiniType<O, I>;
3
+ export type SchemaConversionOptions = {
4
+ /** Default coerce option if not specified in schema */
5
+ coerce?: boolean;
6
+ /** Default mask option if not specified in schema */
7
+ mask?: boolean;
8
+ };
9
+ export declare function convertToZodSchema<O, I = unknown>(testable: SchemaTestable<O>, options?: SchemaConversionOptions): z.ZodType<O, I>;
@@ -1,172 +1,218 @@
1
- import * as z from 'zod/mini';
1
+ import { match, P } from 'ts-pattern';
2
+ import { z } from 'zod';
2
3
  import { NotSupportedError } from '../../errors/not-supported.error.js';
4
+ import { JsonPath } from '../../json-path/json-path.js';
3
5
  import { objectEntries, objectKeys } from '../../utils/object/object.js';
4
- import { isArray, isNotNull, isNumber, isString } from '../../utils/type-guards.js';
5
- import { any, AnySchema, ArraySchema, BigIntSchema, BooleanSchema, DateSchema, DefaultSchema, DeferredSchema, EnumerationSchema, InstanceSchema, LiteralSchema, NeverSchema, NullableSchema, NumberSchema, ObjectSchema, OneOrManySchema, OptionalSchema, ReadableStreamSchema, RegExpSchema, StringSchema, SymbolSchema, TransformSchema, Uint8ArraySchema, UnionSchema, UnknownSchema } from '../schemas/index.js';
6
+ import { isArray, isInstanceOf, isNotNull, isNumber, isString } from '../../utils/type-guards.js';
7
+ import { SchemaError } from '../schema.error.js';
8
+ import { any, AnySchema, ArraySchema, BigIntSchema, BooleanSchema, ConstraintSchema, DateSchema, DefaultSchema, DeferredSchema, EnumerationSchema, FunctionSchema, InstanceSchema, LiteralSchema, NeverSchema, NullableSchema, NumberSchema, ObjectSchema, OneOrManySchema, OptionalSchema, ReadableStreamSchema, RegExpSchema, StringSchema, SymbolSchema, TransformSchema, Uint8ArraySchema, UnionSchema, UnknownSchema } from '../schemas/index.js';
6
9
  import { schemaTestableToSchema } from '../testable.js';
7
- export function convertToZodSchema(testable) {
10
+ export function convertToZodSchema(testable, options) {
8
11
  const schema = schemaTestableToSchema(testable);
9
- return convertToZodSchemaBase(schema);
12
+ return convertToZodSchemaBase(schema, options);
10
13
  }
11
- function convertToZodSchemaBase(schema) {
12
- if (schema instanceof AnySchema) {
13
- return z.any();
14
- }
15
- if (schema instanceof UnknownSchema) {
16
- return z.unknown();
17
- }
18
- if (schema instanceof NeverSchema) {
19
- return z.never();
20
- }
21
- if (schema instanceof NullableSchema) {
22
- return z.nullable(convertToZodSchema(schema.schema));
23
- }
24
- if (schema instanceof OptionalSchema) {
25
- return z.optional(convertToZodSchema(schema.schema));
26
- }
27
- if (schema instanceof OneOrManySchema) {
28
- return convertToZodSchema(schema.schema);
29
- }
30
- if (schema instanceof StringSchema) {
31
- const checks = [];
32
- if (isNumber(schema.minimumLength)) {
33
- checks.push(z.minLength(schema.minimumLength));
14
+ function convertToZodSchemaBase(schema, options) {
15
+ let zodSchema = match(schema)
16
+ .with(P.instanceOf(AnySchema), () => z.any())
17
+ .with(P.instanceOf(UnknownSchema), () => z.unknown())
18
+ .with(P.instanceOf(NeverSchema), () => z.never())
19
+ .with(P.instanceOf(NullableSchema), (s) => convertToZodSchemaBase(s.schema, options).nullable())
20
+ .with(P.instanceOf(OptionalSchema), (s) => convertToZodSchemaBase(s.schema, options).optional())
21
+ .with(P.instanceOf(OneOrManySchema), (s) => {
22
+ const innerSchema = convertToZodSchemaBase(s.schema, options);
23
+ return innerSchema.or(innerSchema.array());
24
+ })
25
+ .with(P.instanceOf(StringSchema), (s) => {
26
+ let zodSchema = (s.coerce ?? options?.coerce ?? false) ? z.coerce.string() : z.string();
27
+ if (isNumber(s.minimumLength)) {
28
+ zodSchema = zodSchema.min(s.minimumLength);
34
29
  }
35
- if (isNumber(schema.maximumLength)) {
36
- checks.push(z.maxLength(schema.maximumLength));
30
+ if (isNumber(s.maximumLength)) {
31
+ zodSchema = zodSchema.max(s.maximumLength);
37
32
  }
38
- if (isNotNull(schema.pattern)) {
39
- checks.push(z.regex(isString(schema.pattern) ? new RegExp(schema.pattern) : schema.pattern));
33
+ if (isNotNull(s.pattern)) {
34
+ zodSchema = zodSchema.regex(s.pattern);
40
35
  }
41
- if (schema.lowercase) {
42
- checks.push(z.toLowerCase());
36
+ if (s.trim) {
37
+ zodSchema = zodSchema.trim();
43
38
  }
44
- const zodSchema = z.string();
45
- return (checks.length > 0) ? zodSchema.check(...checks) : zodSchema;
46
- }
47
- if (schema instanceof NumberSchema) {
48
- const checks = [];
49
- if (isNumber(schema.minimum)) {
50
- checks.push(z.gte(schema.minimum));
39
+ if (s.lowercase) {
40
+ zodSchema = zodSchema.toLowerCase();
51
41
  }
52
- if (isNumber(schema.maximum)) {
53
- checks.push(z.lte(schema.maximum));
42
+ return zodSchema;
43
+ })
44
+ .with(P.instanceOf(NumberSchema), (s) => {
45
+ let zodSchema = (s.coerce ?? options?.coerce ?? false) ? z.coerce.number() : z.number();
46
+ if (s.integer) {
47
+ zodSchema = zodSchema.int();
54
48
  }
55
- const zodSchema = (schema.integer) ? z.int() : z.number();
56
- return (checks.length > 0) ? zodSchema.check(...checks) : zodSchema;
57
- }
58
- if (schema instanceof BooleanSchema) {
59
- return z.boolean();
60
- }
61
- if (schema instanceof BigIntSchema) {
62
- return z.bigint();
63
- }
64
- if (schema instanceof SymbolSchema) {
65
- return z.symbol();
66
- }
67
- if (schema instanceof DateSchema) {
68
- const checks = [];
69
- if (isNotNull(schema.minimum)) {
70
- checks.push(z.gte(schema.minimum));
49
+ if (isNumber(s.minimum)) {
50
+ zodSchema = zodSchema.gte(s.minimum);
71
51
  }
72
- if (isNotNull(schema.maximum)) {
73
- checks.push(z.lte(schema.maximum));
52
+ if (isNumber(s.maximum)) {
53
+ zodSchema = zodSchema.lte(s.maximum);
74
54
  }
75
- const zodSchema = z.date();
76
- return (checks.length > 0) ? zodSchema.check(...checks) : zodSchema;
77
- }
78
- if (schema instanceof LiteralSchema) {
79
- return z.literal(schema.value);
80
- }
81
- if (schema instanceof ArraySchema) {
82
- const itemSchema = convertToZodSchema(schema.itemSchema);
83
- const checks = [];
84
- if (isNumber(schema.minimum)) {
85
- checks.push(z.minSize(schema.minimum));
55
+ return zodSchema;
56
+ })
57
+ .with(P.instanceOf(BooleanSchema), (s) => (s.coerce ?? options?.coerce ?? false) ? z.coerce.boolean() : z.boolean())
58
+ .with(P.instanceOf(BigIntSchema), (s) => (s.coerce ?? options?.coerce ?? false) ? z.coerce.bigint() : z.bigint())
59
+ .with(P.instanceOf(SymbolSchema), () => z.symbol())
60
+ .with(P.instanceOf(DateSchema), (s) => {
61
+ let zodSchema = (s.coerce ?? options?.coerce ?? false) ? z.coerce.date() : z.date();
62
+ if (isNotNull(s.minimum)) {
63
+ zodSchema = zodSchema.min(s.minimum);
86
64
  }
87
- if (isNumber(schema.maximum)) {
88
- checks.push(z.maxSize(schema.maximum));
65
+ if (isNotNull(s.maximum)) {
66
+ zodSchema = zodSchema.max(s.maximum);
89
67
  }
90
- const zodSchema = z.array(itemSchema);
91
- return (checks.length > 0) ? zodSchema.check(...checks) : zodSchema;
92
- }
93
- if (schema instanceof ObjectSchema) {
94
- const propertyEntries = objectEntries(schema.properties);
95
- if ((propertyEntries.length == 0) && (isNotNull(schema.unknownPropertiesKey) || isNotNull(schema.unknownProperties))) {
96
- return z.record(convertToZodSchema(schema.unknownPropertiesKey ?? any()), convertToZodSchema(schema.unknownProperties ?? any()));
68
+ return zodSchema;
69
+ })
70
+ .with(P.instanceOf(LiteralSchema), (s) => z.literal(s.value))
71
+ .with(P.instanceOf(ArraySchema), (s) => {
72
+ const itemSchema = convertToZodSchemaBase(s.itemSchema, options);
73
+ let zodSchema = z.array(itemSchema);
74
+ if (isNumber(s.minimum)) {
75
+ zodSchema = zodSchema.min(s.minimum);
76
+ }
77
+ if (isNumber(s.maximum)) {
78
+ zodSchema = zodSchema.max(s.maximum);
79
+ }
80
+ return zodSchema;
81
+ })
82
+ .with(P.instanceOf(ObjectSchema), (s) => {
83
+ const propertyEntries = objectEntries(s.properties);
84
+ // Handle generic records (no specific properties, but has unknownProperties/Key)
85
+ if ((propertyEntries.length == 0) && (isNotNull(s.unknownPropertiesKey) || isNotNull(s.unknownProperties))) {
86
+ return z.record(convertToZodSchema(s.unknownPropertiesKey ?? any()), convertToZodSchema(s.unknownProperties ?? any()));
97
87
  }
98
88
  const shape = {};
99
89
  for (const [key, propertySchema] of propertyEntries) {
100
- shape[key] = convertToZodSchema(propertySchema);
101
- }
102
- const loose = isNotNull(schema.unknownPropertiesKey) || isNotNull(schema.unknownProperties);
103
- const zodSchema = loose
104
- ? z.looseObject(shape)
105
- : schema.mask
106
- ? z.object(shape)
107
- : z.strictObject(shape);
108
- if (loose) {
109
- const propertiesSchema = convertToZodSchema(schema.unknownProperties ?? any());
110
- const propertiesKeySchema = convertToZodSchema(schema.unknownPropertiesKey ?? any());
90
+ shape[key] = convertToZodSchemaBase(propertySchema, options);
91
+ }
92
+ const loose = isNotNull(s.unknownPropertiesKey) || isNotNull(s.unknownProperties);
93
+ let zodSchema = loose ? z.looseObject(shape) : z.object(shape);
94
+ if (!loose && !(s.mask ?? options?.mask ?? false)) {
95
+ zodSchema = zodSchema.strict();
96
+ }
97
+ if (isNotNull(s.unknownProperties)) {
98
+ zodSchema = zodSchema.catchall(convertToZodSchemaBase(s.unknownProperties, options));
99
+ }
100
+ // Apply custom validation for unknown properties if they are defined
101
+ if (isNotNull(s.unknownPropertiesKey)) {
102
+ const propertiesKeySchema = convertToZodSchemaBase(s.unknownPropertiesKey, options);
111
103
  const knownPropertyKeys = new Set(objectKeys(shape));
112
- return zodSchema.check(z.superRefine((record, context) => {
113
- for (const [key, value] of objectEntries(record)) {
104
+ return zodSchema.check((context) => {
105
+ for (const key of objectKeys(context.value)) {
114
106
  if (knownPropertyKeys.has(key)) {
115
107
  continue;
116
108
  }
117
109
  const keyParseResult = propertiesKeySchema.safeParse(key);
118
- const valueParseResult = propertiesSchema.safeParse(value);
119
110
  if (!keyParseResult.success) {
120
- context.addIssue({
111
+ context.issues.push({
121
112
  code: 'invalid_key',
122
113
  origin: 'record',
123
114
  issues: keyParseResult.error.issues,
124
- });
125
- }
126
- if (!valueParseResult.success) {
127
- context.addIssue({
128
- code: 'custom',
129
- input: value,
130
- path: [key],
131
- message: valueParseResult.error.message,
115
+ input: key,
132
116
  });
133
117
  }
134
118
  }
135
- }));
119
+ });
120
+ }
121
+ if (isNotNull(s.minimumPropertiesCount)) {
122
+ zodSchema = zodSchema.refine((obj) => objectKeys(obj).length >= s.minimumPropertiesCount, {
123
+ message: `Object must have at least ${s.minimumPropertiesCount} properties.`,
124
+ });
125
+ }
126
+ if (isNotNull(s.maximumPropertiesCount)) {
127
+ zodSchema = zodSchema.refine((obj) => objectKeys(obj).length <= s.maximumPropertiesCount, {
128
+ message: `Object must have at most ${s.maximumPropertiesCount} properties.`,
129
+ });
136
130
  }
137
131
  return zodSchema;
138
- }
139
- if (schema instanceof UnionSchema) {
140
- const schemas = schema.schemas.map((s) => convertToZodSchema(s));
132
+ })
133
+ .with(P.instanceOf(UnionSchema), (s) => {
134
+ const schemas = s.schemas.map((subSchema) => convertToZodSchemaBase(subSchema, options));
141
135
  return z.union(schemas);
142
- }
143
- if (schema instanceof EnumerationSchema) {
144
- if (isArray(schema.enumeration) && schema.enumeration.some((v) => isNumber(v))) {
145
- const types = schema.allowedValues.map((value) => z.literal(value));
136
+ })
137
+ .with(P.instanceOf(EnumerationSchema), (s) => {
138
+ // Zod nativeEnum or enum doesn't support numbers directly without hacks or native TS enums.
139
+ // Use union of literals for mixed or number enums.
140
+ if (isArray(s.enumeration) && s.enumeration.some((v) => isNumber(v))) {
141
+ const types = s.allowedValues.map((value) => z.literal(value));
146
142
  return z.union(types);
147
143
  }
148
- return z.enum(schema.enumeration);
149
- }
150
- if (schema instanceof DefaultSchema) {
151
- return z._default(convertToZodSchema(schema.schema), schema.defaultValue);
152
- }
153
- if (schema instanceof TransformSchema) {
154
- return z.transform((input) => schema.transformFn(input));
155
- }
156
- if (schema instanceof DeferredSchema) {
157
- return z.lazy(() => convertToZodSchema(schema.schema));
158
- }
159
- if (schema instanceof Uint8ArraySchema) {
160
- return z.instanceof(Uint8Array);
161
- }
162
- if (schema instanceof ReadableStreamSchema) {
163
- return z.instanceof(ReadableStream);
164
- }
165
- if (schema instanceof RegExpSchema) {
166
- return z.instanceof(RegExp);
167
- }
168
- if (schema instanceof InstanceSchema) {
169
- return z.instanceof(schema.type);
170
- }
171
- throw new NotSupportedError(`Schema "${schema.name}" cannot be converted to Zod schema.`);
144
+ // enumeration is either EnumerationObject or string-only EnumerationArray, both is fine for z.enum
145
+ // using `as`, as z.enum is overloaded and only one type per overload
146
+ return z.enum(s.enumeration);
147
+ })
148
+ .with(P.instanceOf(FunctionSchema), (s) => {
149
+ let zodSchema = z.function();
150
+ if (isNotNull(s.parameterSchemas)) {
151
+ const args = s.parameterSchemas.map((parameterSchema) => convertToZodSchema(parameterSchema, options));
152
+ zodSchema = zodSchema.input(...args);
153
+ }
154
+ if (isNotNull(s.returnValueSchema)) {
155
+ zodSchema = zodSchema.output(convertToZodSchema(s.returnValueSchema, options));
156
+ }
157
+ return zodSchema;
158
+ })
159
+ .with(P.instanceOf(DefaultSchema), (s) => convertToZodSchemaBase(s.schema, options).default(s.defaultValue))
160
+ .with(P.instanceOf(TransformSchema), (s) => convertToZodSchemaBase(s.schema, options).transform((input) => s.transformFn(input)))
161
+ .with(P.instanceOf(DeferredSchema), (s) => z.lazy(() => convertToZodSchemaBase(s.schema, options)))
162
+ .with(P.instanceOf(Uint8ArraySchema), (s) => {
163
+ let zodSchema = z.instanceof(Uint8Array);
164
+ if (isNumber(s.minimumLength)) {
165
+ zodSchema = zodSchema.refine((value) => (value.byteLength >= s.minimumLength), {
166
+ message: `Size must be at least ${s.minimumLength} bytes.`,
167
+ });
168
+ }
169
+ if (isNumber(s.maximumLength)) {
170
+ zodSchema = zodSchema.refine((value) => (value.byteLength <= s.maximumLength), {
171
+ message: `Size must be at most ${s.maximumLength} bytes.`,
172
+ });
173
+ }
174
+ if ((s.coerce ?? options?.coerce ?? false) && isNotNull(s.coerceType)) {
175
+ zodSchema = z.preprocess((value, context) => {
176
+ if (isString(value)) {
177
+ try {
178
+ return Uint8ArraySchema.coerce(value, s.coerceType);
179
+ }
180
+ catch {
181
+ context.addIssue({ code: 'invalid_format', format: s.coerceType, input: value });
182
+ }
183
+ }
184
+ return value;
185
+ }, zodSchema);
186
+ }
187
+ return zodSchema;
188
+ })
189
+ .with(P.instanceOf(ReadableStreamSchema), () => z.instanceof(ReadableStream))
190
+ .with(P.instanceOf(RegExpSchema), (s) => {
191
+ let zodSchema = z.instanceof(RegExp);
192
+ if (s.coerce ?? options?.coerce ?? false) {
193
+ zodSchema = z.preprocess((value) => {
194
+ if (isString(value)) {
195
+ return new RegExp(value, 'u');
196
+ }
197
+ return value;
198
+ }, zodSchema);
199
+ }
200
+ return zodSchema;
201
+ })
202
+ .with(P.instanceOf(InstanceSchema), (s) => z.instanceof(s.type))
203
+ .with(P.instanceOf(ConstraintSchema), (s) => convertToZodSchemaBase(s.schema, options).superRefine((value, context) => {
204
+ const result = s.constraintFn(value, { path: JsonPath.ROOT, options: { coerce: options?.coerce, mask: options?.mask } });
205
+ if (result == true) {
206
+ return;
207
+ }
208
+ const message = isString(result) ? result : isInstanceOf(result, SchemaError) ? result.message : 'Constraint validation failed.';
209
+ context.addIssue({ code: 'custom', message, input: value });
210
+ }))
211
+ .otherwise(() => {
212
+ throw new NotSupportedError(`Schema "${schema.name}" cannot be converted to Zod schema.`);
213
+ });
214
+ if (isNotNull(schema.description)) {
215
+ zodSchema = zodSchema.describe(schema.description);
216
+ }
217
+ return zodSchema;
172
218
  }
@@ -24,8 +24,8 @@ export class BigIntSchema extends SimpleSchema {
24
24
  catch (error) {
25
25
  return { success: false, error: SchemaError.couldNotCoerce('bigint', 'string', path, { fast: coerceOptions.fastErrors, customMessage: error.message }) };
26
26
  }
27
- }
28
- }
27
+ },
28
+ },
29
29
  });
30
30
  }
31
31
  }
@@ -4,6 +4,7 @@ export type BooleanSchemaOptions = SimpleSchemaOptions<boolean>;
4
4
  export declare class BooleanSchema extends SimpleSchema<boolean> {
5
5
  readonly name = "boolean";
6
6
  constructor(options?: BooleanSchemaOptions);
7
+ static coerce(value: unknown): boolean | null;
7
8
  }
8
9
  export declare function boolean(options?: BooleanSchemaOptions): BooleanSchema;
9
10
  export declare function BooleanProperty(options?: BooleanSchemaOptions & SchemaDecoratorOptions): SchemaPropertyDecorator;
@@ -1,33 +1,33 @@
1
1
  import { isBoolean, isString } from '../../utils/type-guards.js';
2
2
  import { PropertySchema } from '../decorators/index.js';
3
3
  import { SimpleSchema } from './simple.js';
4
+ const truthyValues = new Set([1, 1n, true, 'true', '1', 'yes', 'on', 'y', 'enabled']);
5
+ const falsyValues = new Set([0, 0n, false, 'false', '0', 'no', 'off', 'n', 'disabled']);
4
6
  export class BooleanSchema extends SimpleSchema {
5
7
  name = 'boolean';
6
8
  constructor(options) {
7
9
  super('boolean', isBoolean, options, {
8
10
  coercers: {
9
11
  all: (value) => {
10
- const normalizedValue = isString(value) ? value.toLowerCase().trim() : value;
11
- switch (normalizedValue) {
12
- case 1:
13
- case 1n:
14
- case 'true':
15
- case '1':
16
- case 'yes':
17
- return { success: true, value: true, valid: true };
18
- case 0:
19
- case 0n:
20
- case 'false':
21
- case '0':
22
- case 'no':
23
- return { success: true, value: false, valid: true };
24
- default:
25
- return { success: false };
12
+ const coerceValue = BooleanSchema.coerce(value);
13
+ if (coerceValue === null) {
14
+ return { success: false };
26
15
  }
16
+ return { success: true, value: coerceValue, valid: true };
27
17
  },
28
18
  },
29
19
  });
30
20
  }
21
+ static coerce(value) {
22
+ const normalizedValue = isString(value) ? value.toLowerCase().trim() : value;
23
+ if (truthyValues.has(normalizedValue)) {
24
+ return true;
25
+ }
26
+ if (falsyValues.has(normalizedValue)) {
27
+ return false;
28
+ }
29
+ return null;
30
+ }
31
31
  }
32
32
  export function boolean(options) {
33
33
  return new BooleanSchema(options);
@@ -0,0 +1,19 @@
1
+ import type { JsonPath } from '../../json-path/json-path.js';
2
+ import { SchemaError } from '../schema.error.js';
3
+ import { Schema, type SchemaTestable, type SchemaTestOptions, type SchemaTestResult } from '../schema.js';
4
+ type ConstraintFnContext = {
5
+ path: JsonPath;
6
+ options: SchemaTestOptions;
7
+ };
8
+ /**
9
+ * Adds a constraint function to an existing schema.
10
+ */
11
+ export declare class ConstraintSchema<T> extends Schema<T> {
12
+ readonly name: string;
13
+ readonly schema: Schema<T>;
14
+ readonly constraintFn: (value: T, context: ConstraintFnContext) => boolean | string | SchemaError;
15
+ constructor(schema: SchemaTestable<T>, constraintFn: (value: T, context: ConstraintFnContext) => boolean | SchemaError);
16
+ _test(value: any, path: JsonPath, options: SchemaTestOptions): SchemaTestResult<T>;
17
+ }
18
+ export declare function constraint<T>(schema: Schema<T>, constraintFn: (value: T) => boolean | SchemaError): ConstraintSchema<T>;
19
+ export {};
@@ -0,0 +1,36 @@
1
+ import { lazyProperty } from '../../utils/object/lazy-property.js';
2
+ import { isInstanceOf, isString } from '../../utils/type-guards.js';
3
+ import { SchemaError } from '../schema.error.js';
4
+ import { Schema } from '../schema.js';
5
+ import { schemaTestableToSchema } from '../testable.js';
6
+ /**
7
+ * Adds a constraint function to an existing schema.
8
+ */
9
+ export class ConstraintSchema extends Schema {
10
+ name;
11
+ schema;
12
+ constraintFn;
13
+ constructor(schema, constraintFn) {
14
+ super();
15
+ this.schema = schemaTestableToSchema(schema);
16
+ this.constraintFn = constraintFn;
17
+ lazyProperty(this, 'name', () => `Constraint[${this.schema.name}]`);
18
+ }
19
+ _test(value, path, options) {
20
+ const result = this.schema._test(value, path, options);
21
+ if (!result.valid) {
22
+ return result;
23
+ }
24
+ const constraintResult = this.constraintFn(result.value, { path, options });
25
+ if (constraintResult == true) {
26
+ return { valid: true, value: result.value };
27
+ }
28
+ const error = isInstanceOf(constraintResult, SchemaError)
29
+ ? constraintResult
30
+ : new SchemaError(isString(constraintResult) ? constraintResult : `Value not passing constraint`, path, { fast: options.fastErrors });
31
+ return { valid: false, error };
32
+ }
33
+ }
34
+ export function constraint(schema, constraintFn) {
35
+ return new ConstraintSchema(schema, constraintFn);
36
+ }
@@ -2,6 +2,7 @@ export * from './any.js';
2
2
  export * from './array.js';
3
3
  export * from './bigint.js';
4
4
  export * from './boolean.js';
5
+ export * from './constraint.js';
5
6
  export * from './date.js';
6
7
  export * from './defaulted.js';
7
8
  export * from './deferred.js';
@@ -2,6 +2,7 @@ export * from './any.js';
2
2
  export * from './array.js';
3
3
  export * from './bigint.js';
4
4
  export * from './boolean.js';
5
+ export * from './constraint.js';
5
6
  export * from './date.js';
6
7
  export * from './defaulted.js';
7
8
  export * from './deferred.js';
@@ -35,8 +35,10 @@ export type ObjectSchemaPropertiesType<TP extends ObjectSchemaProperties> = Simp
35
35
  }>;
36
36
  export declare const tryGetSchemaFromReflection: typeof _tryGetSchemaFromReflection;
37
37
  export declare class ObjectSchema<T extends Record = Record> extends Schema<T> {
38
- private readonly propertyKeys;
38
+ private readonly propertyEntries;
39
+ private readonly knownPropertyKeys;
39
40
  private readonly allowUnknownProperties;
41
+ private readonly resultValueConstructor;
40
42
  readonly name: string;
41
43
  readonly properties: NormalizedObjectSchemaProperties<T>;
42
44
  readonly mask: boolean | null;