@oh-my-pi/omptype 18.2.0 → 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
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.2.0",
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>);