@oh-my-pi/omptype 18.1.22 → 18.2.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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.1] - 2026-09-15
6
+
7
+ ### Added
8
+
9
+ - Added `trim()`, `superRefine()`, and one-argument `record()` support to the Zod compatibility facade ([#12011](https://github.com/can1357/oh-my-pi/pull/12011) by [@bnivanov](https://github.com/bnivanov)).
10
+
5
11
  ## [17.3.1] - 2026-08-13
6
12
 
7
13
  ### Fixed
@@ -10843,7 +10843,7 @@ RUST RUNTIME DEPENDENCY LICENSES
10843
10843
 
10844
10844
  Generated from Cargo.lock by cargo-about 0.8.2 in locked, offline, workspace, all-feature, all-target mode, then restricted using cargo metadata to normal and build edges reachable from the workspace (development-only edges are excluded). cargo-deny independently evaluates the complete all-target graph.
10845
10845
 
10846
- Native Opus (audiopus/audiopus_sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10846
+ Native Opus (opus/opusic-sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10847
10847
 
10848
10848
  -------------------------------------------------------------------------------
10849
10849
  License: Apache License 2.0 (Apache-2.0)
@@ -12509,6 +12509,58 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
12509
12509
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
12510
12510
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12511
12511
 
12512
+ -------------------------------------------------------------------------------
12513
+ License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
+ Used by:
12515
+ - opusic-sys 0.7.5 (https://github.com/DoumanAsh/opusic-sys)
12516
+
12517
+ Full license text:
12518
+
12519
+ Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
12520
+ Jean-Marc Valin, Timothy B. Terriberry,
12521
+ CSIRO, Gregory Maxwell, Mark Borgerding,
12522
+ Erik de Castro Lopo, Mozilla, Amazon
12523
+
12524
+ Redistribution and use in source and binary forms, with or without
12525
+ modification, are permitted provided that the following conditions
12526
+ are met:
12527
+
12528
+ - Redistributions of source code must retain the above copyright
12529
+ notice, this list of conditions and the following disclaimer.
12530
+
12531
+ - Redistributions in binary form must reproduce the above copyright
12532
+ notice, this list of conditions and the following disclaimer in the
12533
+ documentation and/or other materials provided with the distribution.
12534
+
12535
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the
12536
+ names of specific contributors, may be used to endorse or promote
12537
+ products derived from this software without specific prior written
12538
+ permission.
12539
+
12540
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12541
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12542
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12543
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
12544
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
12545
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
12546
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
12547
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
12548
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
12549
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
12550
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12551
+
12552
+ Opus is subject to the royalty-free patent licenses which are
12553
+ specified at:
12554
+
12555
+ Xiph.Org Foundation:
12556
+ https://datatracker.ietf.org/ipr/1524/
12557
+
12558
+ Microsoft Corporation:
12559
+ https://datatracker.ietf.org/ipr/1914/
12560
+
12561
+ Broadcom Corporation:
12562
+ https://datatracker.ietf.org/ipr/1526/
12563
+
12512
12564
  -------------------------------------------------------------------------------
12513
12565
  License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
12566
  Used by:
@@ -13290,29 +13342,6 @@ jurisdiction of the Federal Courts of the Northern District of
13290
13342
  California and the state courts of the State of California, with
13291
13343
  venue lying in Santa Clara County, California.
13292
13344
 
13293
- -------------------------------------------------------------------------------
13294
- License: ISC License (ISC)
13295
- Used by:
13296
- - audiopus_sys 0.2.2 (https://github.com/lakelezz/audiopus_sys.git)
13297
-
13298
- Full license text:
13299
-
13300
- ISC License
13301
-
13302
- Copyright (c) 2019, Lakelezz
13303
-
13304
- Permission to use, copy, modify, and/or distribute this software for any
13305
- purpose with or without fee is hereby granted, provided that the above
13306
- copyright notice and this permission notice appear in all copies.
13307
-
13308
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13309
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13310
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13312
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13313
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13314
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
13315
-
13316
13345
  -------------------------------------------------------------------------------
13317
13346
  License: ISC License (ISC)
13318
13347
  Used by:
@@ -18734,7 +18763,7 @@ DEALINGS IN THE SOFTWARE.
18734
18763
  -------------------------------------------------------------------------------
18735
18764
  License: MIT License (MIT)
18736
18765
  Used by:
18737
- - opus 0.3.1 (https://github.com/SpaceManiac/opus-rs)
18766
+ - opus 0.4.0 (https://github.com/SpaceManiac/opus-rs)
18738
18767
 
18739
18768
  Full license text:
18740
18769
 
package/dist/js/zod.js CHANGED
@@ -88,6 +88,25 @@ function decorate(schema, optional = false) {
88
88
  return next(restrictBase(schema, ir));
89
89
  return next(restrictBase(schema, { ...ir, min: bound, xmin: false }));
90
90
  }
91
+ if (ir.k === "morph" &&
92
+ !schema.hasSteps &&
93
+ ir.out !== undefined &&
94
+ (ir.out.k === "string" || ir.out.k === "array")) {
95
+ if (!Number.isSafeInteger(bound) || bound < 0)
96
+ throw new OmpTypeError("min length must be a nonnegative safe integer");
97
+ const out = { ...ir.out, min: ir.out.min === undefined ? bound : Math.max(ir.out.min, bound) };
98
+ return next(restrictBase(schema, { ...ir, out }));
99
+ }
100
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
101
+ if (!Number.isSafeInteger(bound) || bound < 0)
102
+ throw new OmpTypeError("min length must be a nonnegative safe integer");
103
+ return next(schema.narrow((value, ctx) => {
104
+ if (typeof value === "string" || Array.isArray(value)) {
105
+ return value.length >= bound || ctx.mustBe(`at least ${bound} characters`);
106
+ }
107
+ return ctx.mustBe("a string or array");
108
+ }));
109
+ }
91
110
  throw new OmpTypeError(`cannot apply min to ${ir.k}`);
92
111
  },
93
112
  max(bound) {
@@ -104,6 +123,25 @@ function decorate(schema, optional = false) {
104
123
  return next(restrictBase(schema, ir));
105
124
  return next(restrictBase(schema, { ...ir, max: bound, xmax: false }));
106
125
  }
126
+ if (ir.k === "morph" &&
127
+ !schema.hasSteps &&
128
+ ir.out !== undefined &&
129
+ (ir.out.k === "string" || ir.out.k === "array")) {
130
+ if (!Number.isSafeInteger(bound) || bound < 0)
131
+ throw new OmpTypeError("max length must be a nonnegative safe integer");
132
+ const out = { ...ir.out, max: ir.out.max === undefined ? bound : Math.min(ir.out.max, bound) };
133
+ return next(restrictBase(schema, { ...ir, out }));
134
+ }
135
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
136
+ if (!Number.isSafeInteger(bound) || bound < 0)
137
+ throw new OmpTypeError("max length must be a nonnegative safe integer");
138
+ return next(schema.narrow((value, ctx) => {
139
+ if (typeof value === "string" || Array.isArray(value)) {
140
+ return value.length <= bound || ctx.mustBe(`at most ${bound} characters`);
141
+ }
142
+ return ctx.mustBe("a string or array");
143
+ }));
144
+ }
107
145
  throw new OmpTypeError(`cannot apply max to ${ir.k}`);
108
146
  },
109
147
  int() {
@@ -125,10 +163,15 @@ function decorate(schema, optional = false) {
125
163
  return this.min(0);
126
164
  },
127
165
  regex(expression, message) {
128
- if (schema.ir.k !== "string")
129
- throw new OmpTypeError(`cannot apply regex to ${schema.ir.k}`);
166
+ const ir = schema.ir;
167
+ const isStringLike = ir.k === "string" ||
168
+ (ir.k === "morph" && (ir.out?.k === "string" || (ir.out === undefined && ir.input.k === "string")));
169
+ if (!isStringLike)
170
+ throw new OmpTypeError(`cannot apply regex to ${ir.k}`);
130
171
  const expectation = message ?? `matching ${expression}`;
131
172
  const narrowed = schema.narrow((value, ctx) => {
173
+ if (typeof value !== "string")
174
+ return ctx.mustBe("a string");
132
175
  expression.lastIndex = 0;
133
176
  const matches = expression.test(value);
134
177
  expression.lastIndex = 0;
@@ -137,9 +180,26 @@ function decorate(schema, optional = false) {
137
180
  return next(narrowed);
138
181
  },
139
182
  url() {
140
- if (schema.ir.k !== "string")
141
- throw new OmpTypeError(`cannot apply url to ${schema.ir.k}`);
142
- return next(restrictBase(schema, { ...schema.ir, url: true }));
183
+ const ir = schema.ir;
184
+ if (ir.k === "string")
185
+ return next(restrictBase(schema, { ...ir, url: true }));
186
+ if (ir.k === "morph" && !schema.hasSteps && ir.out !== undefined && ir.out.k === "string") {
187
+ return next(restrictBase(schema, { ...ir, out: { ...ir.out, url: true } }));
188
+ }
189
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
190
+ return next(schema.narrow((value, ctx) => {
191
+ if (typeof value !== "string")
192
+ return ctx.mustBe("a string");
193
+ try {
194
+ new URL(value);
195
+ return true;
196
+ }
197
+ catch {
198
+ return ctx.mustBe("a valid URL");
199
+ }
200
+ }));
201
+ }
202
+ throw new OmpTypeError(`cannot apply url to ${ir.k}`);
143
203
  },
144
204
  optional() {
145
205
  const widened = schema.or(type.raw("undefined"));
@@ -164,6 +224,54 @@ function decorate(schema, optional = false) {
164
224
  const expectation = refinementMessage(messageOrOptions);
165
225
  return next(schema.narrow((value, ctx) => Boolean(predicate(value)) || ctx.mustBe(expectation)));
166
226
  },
227
+ superRefine(refinement) {
228
+ return next(schema.narrow((value, ctx) => {
229
+ const proxy = {
230
+ addIssue(issue) {
231
+ ctx.error({
232
+ expected: issue.message,
233
+ path: issue.path ?? [],
234
+ ...(issue.actual !== undefined ? { actual: issue.actual } : {}),
235
+ });
236
+ },
237
+ };
238
+ refinement(value, proxy);
239
+ return true;
240
+ }));
241
+ },
242
+ trim() {
243
+ const ir = schema.ir;
244
+ if (ir.k === "string" && !schema.hasSteps) {
245
+ let trimmed = schemaFromIR({
246
+ k: "morph",
247
+ input: { k: "string" },
248
+ fn: v => v.trim(),
249
+ out: ir,
250
+ });
251
+ if (schema.hasDefault)
252
+ trimmed = trimmed.default(schema.defaultValue);
253
+ return next(trimmed);
254
+ }
255
+ if (ir.k === "morph" || schema.hasSteps) {
256
+ let trimmed = schemaFromIR({
257
+ k: "morph",
258
+ input: { k: "unknown" },
259
+ fn: v => {
260
+ const r = schema(v);
261
+ if (r instanceof type.errors)
262
+ return r;
263
+ if (typeof r !== "string")
264
+ throw new OmpTypeError("trim requires a string output");
265
+ return r.trim();
266
+ },
267
+ out: { k: "string" },
268
+ });
269
+ if (schema.hasDefault)
270
+ trimmed = trimmed.default(schema.defaultValue);
271
+ return next(trimmed);
272
+ }
273
+ throw new OmpTypeError(`cannot apply trim to ${ir.k}`);
274
+ },
167
275
  transform(transformer) {
168
276
  return decorate(schema.pipe(value => transformer(value)), optional);
169
277
  },
@@ -228,13 +336,15 @@ export { enumSchema as enum };
228
336
  export const union = (schemas) => decorate(schemaFromIR({ k: "union", members: schemas.map(schema => embed(schema)) }));
229
337
  export const array = (element) => decorate(schemaFromIR({ k: "array", el: embed(element) }));
230
338
  export const object = (shape) => objectSchema(shape);
231
- export const record = (keySchema, valueSchema) => {
339
+ export function record(keyOrValueSchema, valueSchema) {
340
+ const keySchema = (valueSchema === undefined ? string() : keyOrValueSchema);
341
+ const valSchema = (valueSchema === undefined ? keyOrValueSchema : valueSchema);
232
342
  if (!isStringKeyIR(keySchema.ir))
233
343
  throw new OmpTypeError("record keys must use a string schema");
234
344
  const base = schemaFromIR({
235
345
  k: "object",
236
346
  props: [],
237
- index: embed(valueSchema),
347
+ index: embed(valSchema),
238
348
  extras: "keep",
239
349
  });
240
350
  const checked = base.narrow((value, ctx) => {
@@ -245,7 +355,7 @@ export const record = (keySchema, valueSchema) => {
245
355
  return true;
246
356
  });
247
357
  return decorate(checked);
248
- };
358
+ }
249
359
  export const unknown = () => decorate(schemaFromIR(type.unknown.ir));
250
360
  export const any = () => decorate(schemaFromIR(type.unknown.ir));
251
361
  const nullSchema = () => decorate(type.raw("null"));
@@ -6,6 +6,15 @@ interface RefineOptions {
6
6
  message?: string;
7
7
  error?: string;
8
8
  }
9
+ export interface SuperRefineIssue {
10
+ code?: string;
11
+ path?: PropertyKey[];
12
+ message: string;
13
+ actual?: unknown;
14
+ }
15
+ export interface SuperRefineContext {
16
+ addIssue(issue: SuperRefineIssue): void;
17
+ }
9
18
  export interface ZodLikeIssue {
10
19
  path: PropertyKey[];
11
20
  message: string;
@@ -39,6 +48,8 @@ export interface ZodLikeSchema<out Out> extends Type<Out, unknown> {
39
48
  default(value: Exclude<Out, undefined> | (() => Exclude<Out, undefined>)): ZodLikeSchema<Exclude<Out, undefined>>;
40
49
  describe(description: string): ZodLikeSchema<Out>;
41
50
  refine(predicate: (value: Out) => unknown, messageOrOptions?: string | RefineOptions): ZodLikeSchema<Out>;
51
+ superRefine(refinement: (value: Out, ctx: SuperRefineContext) => void): ZodLikeSchema<Out>;
52
+ trim(): ZodLikeSchema<Out>;
42
53
  transform<Next>(transformer: (value: Out) => Next): ZodLikeSchema<Next>;
43
54
  catch(fallback: Out | (() => Out)): ZodLikeSchema<Out>;
44
55
  strict(): ZodLikeSchema<Out>;
@@ -71,7 +82,8 @@ export { enumSchema as enum };
71
82
  export declare const union: <const Schemas extends readonly [ZodLikeSchema<unknown>, ZodLikeSchema<unknown>, ...ZodLikeSchema<unknown>[]]>(schemas: Schemas) => ZodLikeSchema<UnionOutput<Schemas>>;
72
83
  export declare const array: <Element>(element: ZodLikeSchema<Element>) => ZodLikeSchema<Element[]>;
73
84
  export declare const object: <const S extends Shape>(shape: S) => ZodLikeSchema<Simplify<ObjectOutput<S>>>;
74
- export declare const record: <Key extends string, Value>(keySchema: ZodLikeSchema<Key>, valueSchema: ZodLikeSchema<Value>) => ZodLikeSchema<Record<string, Value>>;
85
+ export declare function record<Key extends string, Value>(keySchema: ZodLikeSchema<Key>, valueSchema: ZodLikeSchema<Value>): ZodLikeSchema<Record<string, Value>>;
86
+ export declare function record<Value>(valueSchema: ZodLikeSchema<Value>): ZodLikeSchema<Record<string, Value>>;
75
87
  export declare const unknown: () => ZodLikeSchema<unknown>;
76
88
  export declare const any: () => ZodLikeSchema<unknown>;
77
89
  declare const nullSchema: () => ZodLikeSchema<null>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/omptype",
4
- "version": "18.1.22",
4
+ "version": "18.2.1",
5
5
  "description": "ArkType-compatible runtime schema validation with lazy JIT compilation",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
package/src/zod.ts CHANGED
@@ -20,6 +20,17 @@ interface Decoratable<out Out> extends EmbeddableSchema {
20
20
  default(value: Out | (() => Out)): Decoratable<Out>;
21
21
  }
22
22
 
23
+ export interface SuperRefineIssue {
24
+ code?: string;
25
+ path?: PropertyKey[];
26
+ message: string;
27
+ actual?: unknown;
28
+ }
29
+
30
+ export interface SuperRefineContext {
31
+ addIssue(issue: SuperRefineIssue): void;
32
+ }
33
+
23
34
  export interface ZodLikeIssue {
24
35
  path: PropertyKey[];
25
36
  message: string;
@@ -48,6 +59,8 @@ export interface ZodLikeSchema<out Out> extends Type<Out, unknown> {
48
59
  default(value: Exclude<Out, undefined> | (() => Exclude<Out, undefined>)): ZodLikeSchema<Exclude<Out, undefined>>;
49
60
  describe(description: string): ZodLikeSchema<Out>;
50
61
  refine(predicate: (value: Out) => unknown, messageOrOptions?: string | RefineOptions): ZodLikeSchema<Out>;
62
+ superRefine(refinement: (value: Out, ctx: SuperRefineContext) => void): ZodLikeSchema<Out>;
63
+ trim(): ZodLikeSchema<Out>;
51
64
  transform<Next>(transformer: (value: Out) => Next): ZodLikeSchema<Next>;
52
65
  catch(fallback: Out | (() => Out)): ZodLikeSchema<Out>;
53
66
  strict(): ZodLikeSchema<Out>;
@@ -140,6 +153,29 @@ function decorate<Out>(schema: Decoratable<Out>, optional = false): ZodLikeSchem
140
153
  if (ir.min !== undefined && ir.min >= bound) return next(restrictBase(schema, ir));
141
154
  return next(restrictBase(schema, { ...ir, min: bound, xmin: false }));
142
155
  }
156
+ if (
157
+ ir.k === "morph" &&
158
+ !schema.hasSteps &&
159
+ ir.out !== undefined &&
160
+ (ir.out.k === "string" || ir.out.k === "array")
161
+ ) {
162
+ if (!Number.isSafeInteger(bound) || bound < 0)
163
+ throw new OmpTypeError("min length must be a nonnegative safe integer");
164
+ const out = { ...ir.out, min: ir.out.min === undefined ? bound : Math.max(ir.out.min, bound) };
165
+ return next(restrictBase(schema, { ...ir, out }));
166
+ }
167
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
168
+ if (!Number.isSafeInteger(bound) || bound < 0)
169
+ throw new OmpTypeError("min length must be a nonnegative safe integer");
170
+ return next(
171
+ schema.narrow((value, ctx) => {
172
+ if (typeof value === "string" || Array.isArray(value)) {
173
+ return value.length >= bound || ctx.mustBe(`at least ${bound} characters`);
174
+ }
175
+ return ctx.mustBe("a string or array");
176
+ }),
177
+ );
178
+ }
143
179
  throw new OmpTypeError(`cannot apply min to ${ir.k}`);
144
180
  },
145
181
  max(bound: number): ZodLikeSchema<Out> {
@@ -154,6 +190,29 @@ function decorate<Out>(schema: Decoratable<Out>, optional = false): ZodLikeSchem
154
190
  if (ir.max !== undefined && ir.max <= bound) return next(restrictBase(schema, ir));
155
191
  return next(restrictBase(schema, { ...ir, max: bound, xmax: false }));
156
192
  }
193
+ if (
194
+ ir.k === "morph" &&
195
+ !schema.hasSteps &&
196
+ ir.out !== undefined &&
197
+ (ir.out.k === "string" || ir.out.k === "array")
198
+ ) {
199
+ if (!Number.isSafeInteger(bound) || bound < 0)
200
+ throw new OmpTypeError("max length must be a nonnegative safe integer");
201
+ const out = { ...ir.out, max: ir.out.max === undefined ? bound : Math.min(ir.out.max, bound) };
202
+ return next(restrictBase(schema, { ...ir, out }));
203
+ }
204
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
205
+ if (!Number.isSafeInteger(bound) || bound < 0)
206
+ throw new OmpTypeError("max length must be a nonnegative safe integer");
207
+ return next(
208
+ schema.narrow((value, ctx) => {
209
+ if (typeof value === "string" || Array.isArray(value)) {
210
+ return value.length <= bound || ctx.mustBe(`at most ${bound} characters`);
211
+ }
212
+ return ctx.mustBe("a string or array");
213
+ }),
214
+ );
215
+ }
157
216
  throw new OmpTypeError(`cannot apply max to ${ir.k}`);
158
217
  },
159
218
  int(): ZodLikeSchema<Out> {
@@ -171,19 +230,41 @@ function decorate<Out>(schema: Decoratable<Out>, optional = false): ZodLikeSchem
171
230
  return this.min(0);
172
231
  },
173
232
  regex(expression: RegExp, message?: string): ZodLikeSchema<Out> {
174
- if (schema.ir.k !== "string") throw new OmpTypeError(`cannot apply regex to ${schema.ir.k}`);
233
+ const ir = schema.ir;
234
+ const isStringLike =
235
+ ir.k === "string" ||
236
+ (ir.k === "morph" && (ir.out?.k === "string" || (ir.out === undefined && ir.input.k === "string")));
237
+ if (!isStringLike) throw new OmpTypeError(`cannot apply regex to ${ir.k}`);
175
238
  const expectation = message ?? `matching ${expression}`;
176
239
  const narrowed = schema.narrow((value, ctx) => {
240
+ if (typeof value !== "string") return ctx.mustBe("a string");
177
241
  expression.lastIndex = 0;
178
- const matches = expression.test(value as string);
242
+ const matches = expression.test(value);
179
243
  expression.lastIndex = 0;
180
244
  return matches || ctx.mustBe(expectation);
181
245
  });
182
246
  return next(narrowed);
183
247
  },
184
248
  url(): ZodLikeSchema<Out> {
185
- if (schema.ir.k !== "string") throw new OmpTypeError(`cannot apply url to ${schema.ir.k}`);
186
- return next(restrictBase(schema, { ...schema.ir, url: true }));
249
+ const ir = schema.ir;
250
+ if (ir.k === "string") return next(restrictBase(schema, { ...ir, url: true }));
251
+ if (ir.k === "morph" && !schema.hasSteps && ir.out !== undefined && ir.out.k === "string") {
252
+ return next(restrictBase(schema, { ...ir, out: { ...ir.out, url: true } }));
253
+ }
254
+ if (ir.k === "morph" && (schema.hasSteps || ir.out === undefined)) {
255
+ return next(
256
+ schema.narrow((value, ctx) => {
257
+ if (typeof value !== "string") return ctx.mustBe("a string");
258
+ try {
259
+ new URL(value);
260
+ return true;
261
+ } catch {
262
+ return ctx.mustBe("a valid URL");
263
+ }
264
+ }),
265
+ );
266
+ }
267
+ throw new OmpTypeError(`cannot apply url to ${ir.k}`);
187
268
  },
188
269
  optional(): ZodLikeSchema<Out | undefined> & OptionalSchemaMarker {
189
270
  const widened = schema.or(type.raw("undefined")) as Decoratable<Out | undefined>;
@@ -210,6 +291,52 @@ function decorate<Out>(schema: Decoratable<Out>, optional = false): ZodLikeSchem
210
291
  const expectation = refinementMessage(messageOrOptions);
211
292
  return next(schema.narrow((value, ctx) => Boolean(predicate(value)) || ctx.mustBe(expectation)));
212
293
  },
294
+ superRefine(refinement: (value: Out, ctx: SuperRefineContext) => void): ZodLikeSchema<Out> {
295
+ return next(
296
+ schema.narrow((value, ctx) => {
297
+ const proxy: SuperRefineContext = {
298
+ addIssue(issue) {
299
+ ctx.error({
300
+ expected: issue.message,
301
+ path: issue.path ?? [],
302
+ ...(issue.actual !== undefined ? { actual: issue.actual } : {}),
303
+ });
304
+ },
305
+ };
306
+ refinement(value, proxy);
307
+ return true;
308
+ }),
309
+ );
310
+ },
311
+ trim(): ZodLikeSchema<Out> {
312
+ const ir = schema.ir;
313
+ if (ir.k === "string" && !schema.hasSteps) {
314
+ let trimmed = schemaFromIR<Out>({
315
+ k: "morph",
316
+ input: { k: "string" },
317
+ fn: v => (v as string).trim(),
318
+ out: ir,
319
+ });
320
+ if (schema.hasDefault) trimmed = trimmed.default(schema.defaultValue as Out | (() => Out));
321
+ return next(trimmed);
322
+ }
323
+ if (ir.k === "morph" || schema.hasSteps) {
324
+ let trimmed = schemaFromIR<Out>({
325
+ k: "morph",
326
+ input: { k: "unknown" },
327
+ fn: v => {
328
+ const r = schema(v);
329
+ if (r instanceof type.errors) return r;
330
+ if (typeof r !== "string") throw new OmpTypeError("trim requires a string output");
331
+ return r.trim();
332
+ },
333
+ out: { k: "string" },
334
+ });
335
+ if (schema.hasDefault) trimmed = trimmed.default(schema.defaultValue as Out | (() => Out));
336
+ return next(trimmed);
337
+ }
338
+ throw new OmpTypeError(`cannot apply trim to ${ir.k}`);
339
+ },
213
340
  transform<Next>(transformer: (value: Out) => Next): ZodLikeSchema<Next> {
214
341
  return decorate(
215
342
  schema.pipe(value => transformer(value)),
@@ -303,15 +430,22 @@ export const array = <Element>(element: ZodLikeSchema<Element>): ZodLikeSchema<E
303
430
  decorate(schemaFromIR({ k: "array", el: embed(element) }));
304
431
  export const object = <const S extends Shape>(shape: S): ZodLikeSchema<Simplify<ObjectOutput<S>>> =>
305
432
  objectSchema(shape);
306
- export const record = <Key extends string, Value>(
433
+ export function record<Key extends string, Value>(
307
434
  keySchema: ZodLikeSchema<Key>,
308
435
  valueSchema: ZodLikeSchema<Value>,
309
- ): ZodLikeSchema<Record<string, Value>> => {
436
+ ): ZodLikeSchema<Record<string, Value>>;
437
+ export function record<Value>(valueSchema: ZodLikeSchema<Value>): ZodLikeSchema<Record<string, Value>>;
438
+ export function record<Key extends string, Value>(
439
+ keyOrValueSchema: ZodLikeSchema<Key> | ZodLikeSchema<Value>,
440
+ valueSchema?: ZodLikeSchema<Value>,
441
+ ): ZodLikeSchema<Record<string, Value>> {
442
+ const keySchema = (valueSchema === undefined ? string() : keyOrValueSchema) as ZodLikeSchema<Key>;
443
+ const valSchema = (valueSchema === undefined ? keyOrValueSchema : valueSchema) as ZodLikeSchema<Value>;
310
444
  if (!isStringKeyIR(keySchema.ir)) throw new OmpTypeError("record keys must use a string schema");
311
445
  const base = schemaFromIR<Record<string, Value>>({
312
446
  k: "object",
313
447
  props: [],
314
- index: embed(valueSchema),
448
+ index: embed(valSchema),
315
449
  extras: "keep",
316
450
  });
317
451
  const checked = base.narrow((value, ctx: NarrowContext) => {
@@ -321,7 +455,7 @@ export const record = <Key extends string, Value>(
321
455
  return true;
322
456
  });
323
457
  return decorate(checked);
324
- };
458
+ }
325
459
  export const unknown = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
326
460
  export const any = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
327
461
  const nullSchema = (): ZodLikeSchema<null> => decorate(type.raw("null") as unknown as Decoratable<null>);