@prairielearn/zod 1.3.2 → 1.3.4

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
@@ -1,5 +1,17 @@
1
1
  # @prairielearn/zod
2
2
 
3
+ ## 1.3.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 23adb05: Upgrade all JavaScript dependencies
8
+
9
+ ## 1.3.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 32e4f7b: Support passing in milliseconds into the IntervalSchema
14
+
3
15
  ## 1.3.2
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -31,15 +31,17 @@ export declare const IdSchema: z.ZodEffects<z.ZodString, string, string>;
31
31
  /**
32
32
  * A Zod schema for a PostgreSQL interval.
33
33
  *
34
- * This handles two representations of an interval:
34
+ * This handles three representations of an interval:
35
35
  *
36
36
  * - A string like "1 year 2 days", which is how intervals will be represented
37
37
  * if they go through `to_jsonb` in a query.
38
38
  * - A {@link PostgresIntervalSchema} object, which is what we'll get if a
39
39
  * query directly returns an interval column. The interval will already be
40
40
  * parsed by `postgres-interval` by way of `pg-types`.
41
+ * - A number of milliseconds, which is possible if you want to feed the output of IntervalSchema.parse()
42
+ * back through this schema.
41
43
  *
42
- * In either case, we convert the interval to a number of milliseconds.
44
+ * In all cases, we convert the interval to a number of milliseconds.
43
45
  */
44
46
  export declare const IntervalSchema: z.ZodEffects<z.ZodUnion<[z.ZodString, z.ZodObject<{
45
47
  years: z.ZodDefault<z.ZodNumber>;
@@ -65,7 +67,7 @@ export declare const IntervalSchema: z.ZodEffects<z.ZodUnion<[z.ZodString, z.Zod
65
67
  minutes?: number | undefined;
66
68
  seconds?: number | undefined;
67
69
  milliseconds?: number | undefined;
68
- }>]>, number, string | {
70
+ }>, z.ZodNumber]>, number, string | number | {
69
71
  years?: number | undefined;
70
72
  months?: number | undefined;
71
73
  days?: number | undefined;
package/dist/index.js CHANGED
@@ -56,22 +56,27 @@ const PostgresIntervalSchema = z.object({
56
56
  /**
57
57
  * A Zod schema for a PostgreSQL interval.
58
58
  *
59
- * This handles two representations of an interval:
59
+ * This handles three representations of an interval:
60
60
  *
61
61
  * - A string like "1 year 2 days", which is how intervals will be represented
62
62
  * if they go through `to_jsonb` in a query.
63
63
  * - A {@link PostgresIntervalSchema} object, which is what we'll get if a
64
64
  * query directly returns an interval column. The interval will already be
65
65
  * parsed by `postgres-interval` by way of `pg-types`.
66
+ * - A number of milliseconds, which is possible if you want to feed the output of IntervalSchema.parse()
67
+ * back through this schema.
66
68
  *
67
- * In either case, we convert the interval to a number of milliseconds.
69
+ * In all cases, we convert the interval to a number of milliseconds.
68
70
  */
69
71
  export const IntervalSchema = z
70
- .union([z.string(), PostgresIntervalSchema])
72
+ .union([z.string(), PostgresIntervalSchema, z.number()])
71
73
  .transform((interval) => {
72
74
  if (typeof interval === 'string') {
73
75
  interval = parsePostgresInterval(interval);
74
76
  }
77
+ if (typeof interval === 'number') {
78
+ return interval;
79
+ }
75
80
  // This calculation matches Postgres's behavior when computing the number of
76
81
  // milliseconds in an interval with `EXTRACT(epoch from '...'::interval) * 1000`.
77
82
  // The noteworthy parts of this conversion are that 1 year = 365.25 days and
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import parsePostgresInterval from 'postgres-interval';\nimport { type ZodTypeAny, z } from 'zod';\n\nconst INTERVAL_MS_PER_SECOND = 1000;\nconst INTERVAL_MS_PER_MINUTE = 60 * INTERVAL_MS_PER_SECOND;\nconst INTERVAL_MS_PER_HOUR = 60 * INTERVAL_MS_PER_MINUTE;\nconst INTERVAL_MS_PER_DAY = 24 * INTERVAL_MS_PER_HOUR;\nconst INTERVAL_MS_PER_MONTH = 30 * INTERVAL_MS_PER_DAY;\nconst INTERVAL_MS_PER_YEAR = 365.25 * INTERVAL_MS_PER_DAY;\n\n/**\n * A schema type on which `.optional()` cannot be called.\n */\ntype NoOptional<S extends ZodTypeAny> = S & {\n optional: never;\n};\n\n/**\n * Wrap any Zod schema so that calling `.optional()` is illegal in TypeScript.\n * Runtime behavior is untouched.\n */\nfunction required<S extends ZodTypeAny>(schema: S): NoOptional<S> {\n return schema as unknown as NoOptional<S>;\n}\n\n/**\n * A Zod schema for a boolean from a single checkbox input in the body\n * parameters from a form. This will return a boolean with a value of `true` if\n * the checkbox is checked (the input is present) and `false` if it is not\n * checked.\n *\n * Note that this will not behave sensibly if `.optional()` is called on the schema,\n * as it will turn a missing checkbox into `undefined` instead of `false`. We use\n * some TypeScript magic to ensure that `.optional()` cannot be called on this schema.\n */\nexport const BooleanFromCheckboxSchema = required(\n z\n .string()\n .optional()\n .transform((s) => !!s),\n);\n\n/**\n * A Zod schema for a PostgreSQL ID.\n *\n * We store IDs as BIGINT in PostgreSQL, which are passed to JavaScript as\n * either strings (if the ID is fetched directly) or numbers (if passed via\n * `to_jsonb()`). This schema coerces the ID to a string to ensure consistent\n * handling.\n *\n * The `refine` step is important to ensure that the thing we've coerced to a\n * string is actually a number. If it's not, we want to fail quickly.\n */\nexport const IdSchema = z\n .string({ coerce: true })\n .refine((val) => /^\\d+$/.test(val), { message: 'ID is not a non-negative integer' });\n\n/**\n * A Zod schema for the objects produced by the `postgres-interval` library.\n */\nconst PostgresIntervalSchema = z.object({\n years: z.number().default(0),\n months: z.number().default(0),\n days: z.number().default(0),\n hours: z.number().default(0),\n minutes: z.number().default(0),\n seconds: z.number().default(0),\n milliseconds: z.number().default(0),\n});\n\n/**\n * A Zod schema for a PostgreSQL interval.\n *\n * This handles two representations of an interval:\n *\n * - A string like \"1 year 2 days\", which is how intervals will be represented\n * if they go through `to_jsonb` in a query.\n * - A {@link PostgresIntervalSchema} object, which is what we'll get if a\n * query directly returns an interval column. The interval will already be\n * parsed by `postgres-interval` by way of `pg-types`.\n *\n * In either case, we convert the interval to a number of milliseconds.\n */\nexport const IntervalSchema = z\n .union([z.string(), PostgresIntervalSchema])\n .transform((interval) => {\n if (typeof interval === 'string') {\n interval = parsePostgresInterval(interval);\n }\n\n // This calculation matches Postgres's behavior when computing the number of\n // milliseconds in an interval with `EXTRACT(epoch from '...'::interval) * 1000`.\n // The noteworthy parts of this conversion are that 1 year = 365.25 days and\n // 1 month = 30 days.\n return (\n interval.years * INTERVAL_MS_PER_YEAR +\n interval.months * INTERVAL_MS_PER_MONTH +\n interval.days * INTERVAL_MS_PER_DAY +\n interval.hours * INTERVAL_MS_PER_HOUR +\n interval.minutes * INTERVAL_MS_PER_MINUTE +\n interval.seconds * INTERVAL_MS_PER_SECOND +\n interval.milliseconds\n );\n });\n\n/**\n * A Zod schema for a date string in ISO format.\n *\n * Accepts either a string or a Date object. If a string is passed, it is\n * validated and parsed as an ISO date string.\n *\n * Useful for parsing dates from JSON, which are always strings.\n */\nexport const DateFromISOString = z\n .union([z.string(), z.date()])\n .refine(\n (s) => {\n const date = new Date(s);\n return !Number.isNaN(date.getTime());\n },\n {\n message: 'must be a valid ISO date string',\n },\n )\n .transform((s) => new Date(s));\n\n/**\n * A Zod schema that coerces a non-empty string to an integer or an empty string to null.\n * This is useful for form number inputs that are not required but we do not want to\n * use an empty string to compute values.\n */\nexport const IntegerFromStringOrEmptySchema = z.preprocess(\n (value) => (value === '' ? null : value),\n z.union([z.null(), z.coerce.number().int()]),\n);\n\n/**\n * A Zod schema for an arrray of string values from either a string or an array of\n * strings.\n */\nexport const ArrayFromStringOrArraySchema = z\n .union([z.string(), z.array(z.string())])\n .transform((s) => {\n if (s === null) {\n return [];\n } else if (Array.isArray(s)) {\n return s;\n } else {\n return [s];\n }\n });\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import parsePostgresInterval from 'postgres-interval';\nimport { type ZodTypeAny, z } from 'zod';\n\nconst INTERVAL_MS_PER_SECOND = 1000;\nconst INTERVAL_MS_PER_MINUTE = 60 * INTERVAL_MS_PER_SECOND;\nconst INTERVAL_MS_PER_HOUR = 60 * INTERVAL_MS_PER_MINUTE;\nconst INTERVAL_MS_PER_DAY = 24 * INTERVAL_MS_PER_HOUR;\nconst INTERVAL_MS_PER_MONTH = 30 * INTERVAL_MS_PER_DAY;\nconst INTERVAL_MS_PER_YEAR = 365.25 * INTERVAL_MS_PER_DAY;\n\n/**\n * A schema type on which `.optional()` cannot be called.\n */\ntype NoOptional<S extends ZodTypeAny> = S & {\n optional: never;\n};\n\n/**\n * Wrap any Zod schema so that calling `.optional()` is illegal in TypeScript.\n * Runtime behavior is untouched.\n */\nfunction required<S extends ZodTypeAny>(schema: S): NoOptional<S> {\n return schema as unknown as NoOptional<S>;\n}\n\n/**\n * A Zod schema for a boolean from a single checkbox input in the body\n * parameters from a form. This will return a boolean with a value of `true` if\n * the checkbox is checked (the input is present) and `false` if it is not\n * checked.\n *\n * Note that this will not behave sensibly if `.optional()` is called on the schema,\n * as it will turn a missing checkbox into `undefined` instead of `false`. We use\n * some TypeScript magic to ensure that `.optional()` cannot be called on this schema.\n */\nexport const BooleanFromCheckboxSchema = required(\n z\n .string()\n .optional()\n .transform((s) => !!s),\n);\n\n/**\n * A Zod schema for a PostgreSQL ID.\n *\n * We store IDs as BIGINT in PostgreSQL, which are passed to JavaScript as\n * either strings (if the ID is fetched directly) or numbers (if passed via\n * `to_jsonb()`). This schema coerces the ID to a string to ensure consistent\n * handling.\n *\n * The `refine` step is important to ensure that the thing we've coerced to a\n * string is actually a number. If it's not, we want to fail quickly.\n */\nexport const IdSchema = z\n .string({ coerce: true })\n .refine((val) => /^\\d+$/.test(val), { message: 'ID is not a non-negative integer' });\n\n/**\n * A Zod schema for the objects produced by the `postgres-interval` library.\n */\nconst PostgresIntervalSchema = z.object({\n years: z.number().default(0),\n months: z.number().default(0),\n days: z.number().default(0),\n hours: z.number().default(0),\n minutes: z.number().default(0),\n seconds: z.number().default(0),\n milliseconds: z.number().default(0),\n});\n\n/**\n * A Zod schema for a PostgreSQL interval.\n *\n * This handles three representations of an interval:\n *\n * - A string like \"1 year 2 days\", which is how intervals will be represented\n * if they go through `to_jsonb` in a query.\n * - A {@link PostgresIntervalSchema} object, which is what we'll get if a\n * query directly returns an interval column. The interval will already be\n * parsed by `postgres-interval` by way of `pg-types`.\n * - A number of milliseconds, which is possible if you want to feed the output of IntervalSchema.parse()\n * back through this schema.\n *\n * In all cases, we convert the interval to a number of milliseconds.\n */\nexport const IntervalSchema = z\n .union([z.string(), PostgresIntervalSchema, z.number()])\n .transform((interval) => {\n if (typeof interval === 'string') {\n interval = parsePostgresInterval(interval);\n }\n\n if (typeof interval === 'number') {\n return interval;\n }\n\n // This calculation matches Postgres's behavior when computing the number of\n // milliseconds in an interval with `EXTRACT(epoch from '...'::interval) * 1000`.\n // The noteworthy parts of this conversion are that 1 year = 365.25 days and\n // 1 month = 30 days.\n return (\n interval.years * INTERVAL_MS_PER_YEAR +\n interval.months * INTERVAL_MS_PER_MONTH +\n interval.days * INTERVAL_MS_PER_DAY +\n interval.hours * INTERVAL_MS_PER_HOUR +\n interval.minutes * INTERVAL_MS_PER_MINUTE +\n interval.seconds * INTERVAL_MS_PER_SECOND +\n interval.milliseconds\n );\n });\n\n/**\n * A Zod schema for a date string in ISO format.\n *\n * Accepts either a string or a Date object. If a string is passed, it is\n * validated and parsed as an ISO date string.\n *\n * Useful for parsing dates from JSON, which are always strings.\n */\nexport const DateFromISOString = z\n .union([z.string(), z.date()])\n .refine(\n (s) => {\n const date = new Date(s);\n return !Number.isNaN(date.getTime());\n },\n {\n message: 'must be a valid ISO date string',\n },\n )\n .transform((s) => new Date(s));\n\n/**\n * A Zod schema that coerces a non-empty string to an integer or an empty string to null.\n * This is useful for form number inputs that are not required but we do not want to\n * use an empty string to compute values.\n */\nexport const IntegerFromStringOrEmptySchema = z.preprocess(\n (value) => (value === '' ? null : value),\n z.union([z.null(), z.coerce.number().int()]),\n);\n\n/**\n * A Zod schema for an arrray of string values from either a string or an array of\n * strings.\n */\nexport const ArrayFromStringOrArraySchema = z\n .union([z.string(), z.array(z.string())])\n .transform((s) => {\n if (s === null) {\n return [];\n } else if (Array.isArray(s)) {\n return s;\n } else {\n return [s];\n }\n });\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/zod",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,11 +15,11 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "postgres-interval": "^4.0.2",
18
- "zod": "^3.25.67"
18
+ "zod": "^3.25.76"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@prairielearn/tsconfig": "^0.0.0",
22
- "@types/node": "^22.15.34",
22
+ "@types/node": "^22.17.0",
23
23
  "@vitest/coverage-v8": "^3.2.4",
24
24
  "tsx": "^4.20.3",
25
25
  "typescript": "^5.8.3",
package/src/index.ts CHANGED
@@ -71,23 +71,29 @@ const PostgresIntervalSchema = z.object({
71
71
  /**
72
72
  * A Zod schema for a PostgreSQL interval.
73
73
  *
74
- * This handles two representations of an interval:
74
+ * This handles three representations of an interval:
75
75
  *
76
76
  * - A string like "1 year 2 days", which is how intervals will be represented
77
77
  * if they go through `to_jsonb` in a query.
78
78
  * - A {@link PostgresIntervalSchema} object, which is what we'll get if a
79
79
  * query directly returns an interval column. The interval will already be
80
80
  * parsed by `postgres-interval` by way of `pg-types`.
81
+ * - A number of milliseconds, which is possible if you want to feed the output of IntervalSchema.parse()
82
+ * back through this schema.
81
83
  *
82
- * In either case, we convert the interval to a number of milliseconds.
84
+ * In all cases, we convert the interval to a number of milliseconds.
83
85
  */
84
86
  export const IntervalSchema = z
85
- .union([z.string(), PostgresIntervalSchema])
87
+ .union([z.string(), PostgresIntervalSchema, z.number()])
86
88
  .transform((interval) => {
87
89
  if (typeof interval === 'string') {
88
90
  interval = parsePostgresInterval(interval);
89
91
  }
90
92
 
93
+ if (typeof interval === 'number') {
94
+ return interval;
95
+ }
96
+
91
97
  // This calculation matches Postgres's behavior when computing the number of
92
98
  // milliseconds in an interval with `EXTRACT(epoch from '...'::interval) * 1000`.
93
99
  // The noteworthy parts of this conversion are that 1 year = 365.25 days and