Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-apes-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"temporal-zod": patch
---

More docs
10 changes: 10 additions & 0 deletions .changeset/plain-jars-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"temporal-zod": patch
"format-temporal": patch
"interval-temporal": patch
"parse-temporal": patch
"superjson-temporal": patch
"temporal-quarter-fns": patch
---

Update publish config
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
jobs:
version:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down Expand Up @@ -35,3 +39,5 @@ jobs:
publish: bun run ci:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:cov": "bun test src/ --coverage",
"typedoc": "typedoc",
"ci:version": "changeset version && bun update",
"ci:publish": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag"
"ci:publish": "for dir in packages/*; do (cd \"$dir\" && bun pm pack --filename package.tgz && bunx npm publish package.tgz --provenance || true); done && changeset tag"
},
"keywords": [],
"author": "Ian Macalinao <me@ianm.com>",
Expand Down
28 changes: 28 additions & 0 deletions packages/temporal-zod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ const result = schema.parse(input);

You may view the [tests](https://github.com/macalinao/temporal-utils/blob/master/packages/temporal-zod/src/index.test.ts) for more examples.

### JSON Schema Support

The default `temporal-zod` export registers JSON Schema metadata on every validator via Zod's `.meta()`, so `z.toJSONSchema()` works out of the box:

```typescript
import * as z from "zod";
import { zPlainDate, zInstant } from "temporal-zod";

const schema = z.object({
date: zPlainDate,
instant: zInstant,
});

const jsonSchema = z.toJSONSchema(schema);
// Produces a JSON Schema with $defs for Temporal.PlainDate, Temporal.Instant,
// including type, description, pattern, and format where applicable.
```

### Base Export (No JSON Schema)

If you don't need JSON Schema support, you can import from `temporal-zod/base` for a smaller bundle. This gives you the same validators without the JSON Schema metadata registration side effect:

```typescript
import { zPlainDate, zInstant } from "temporal-zod/base";
```

This is backwards-compatible with the pre-JSON Schema versions of `temporal-zod`.

### With tRPC

If you are using [tRPC](https://trpc.io/), you likely use Zod to validate your inputs and outputs. However, when using it with [Tanstack Query](https://tanstack.com/query), since the Temporal types get mapped to an object, you should ensure that you are using the instance of the Temporal type rather than the one with type coercion. Otherwise, the query cache will not work as expected.
Expand Down
18 changes: 18 additions & 0 deletions packages/temporal-zod/src/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/**
* Base Temporal Zod validators **without** JSON Schema metadata.
*
* Import from `temporal-zod/base` for a smaller bundle when you don't need
* `z.toJSONSchema()` support. The validators are functionally identical to
* the main `temporal-zod` export — they just lack the `.meta()` registration.
*
* @example
* ```typescript
* import { zPlainDate, zInstant } from "temporal-zod/base";
*
* const result = zPlainDate.parse("2023-01-15");
* // result is a Temporal.PlainDate instance
* ```
*
* @module
* @see {@link https://github.com/macalinao/temporal-utils/tree/master/packages/temporal-zod | temporal-zod on GitHub}
*/
export type { ZodTemporal } from "./temporal-validator.js";
export * from "./duration.js";
export * from "./instant.js";
Expand Down
26 changes: 26 additions & 0 deletions packages/temporal-zod/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
/**
* Zod validators for TC39 Temporal types with full JSON Schema support.
*
* @example
* ```typescript
* import * as z from "zod";
* import { zPlainDate, zInstant } from "temporal-zod";
*
* const schema = z.object({
* date: zPlainDate,
* instant: zInstant,
* });
*
* // Parse strings into Temporal objects
* const result = schema.parse({
* date: "2023-01-15",
* instant: "2023-01-15T13:45:30Z",
* });
*
* // Generate JSON Schema with proper Temporal type metadata
* const jsonSchema = z.toJSONSchema(schema);
* ```
*
* @module
* @see {@link https://github.com/macalinao/temporal-utils/tree/master/packages/temporal-zod | temporal-zod on GitHub}
*/
export * from "./json-schemas.js";
153 changes: 153 additions & 0 deletions packages/temporal-zod/src/json-schemas.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
/**
* Temporal Zod validators with JSON Schema metadata.
*
* This is the main entry point of `temporal-zod`. Every validator exported here
* is a clone of the corresponding base validator with JSON Schema metadata
* attached via Zod's `.meta()`, so `z.toJSONSchema()` works out of the box.
*
* If you don't need JSON Schema support, import from `temporal-zod/base` instead
* for a smaller bundle without the metadata registration side effect.
*
* @module
* @see {@link https://github.com/macalinao/temporal-utils/tree/master/packages/temporal-zod | temporal-zod on GitHub}
*/
import type { Temporal } from "temporal-polyfill";
import type * as z from "zod";
import {
Expand Down Expand Up @@ -27,6 +40,9 @@ import {
zZonedDateTimeInstance as zZonedDateTimeInstanceBase,
} from "./base/index.js";

/**
* Shape of the JSON Schema metadata attached to each Temporal validator.
*/
interface TemporalJSONSchema {
type: "string";
id: string;
Expand Down Expand Up @@ -77,7 +93,24 @@ const _instant = registerJSONSchema([zInstantBase, zInstantInstanceBase], {
format: "date-time",
pattern: INSTANT_PATTERN,
});
/**
* Validates or coerces a string or `Date` to a {@link Temporal.Instant}.
*
* Accepts ISO 8601 instant strings with a required UTC offset
* (e.g. `2023-01-15T13:45:30Z`), `Date` objects, or existing `Instant` instances.
*
* Includes JSON Schema metadata (`format: "date-time"`, `pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zInstant: z.ZodType<Temporal.Instant> = _instant[0];
/**
* Validates that the value is an instance of {@link Temporal.Instant}.
*
* Unlike {@link zInstant}, this does **not** coerce strings or `Date` objects.
* Use this when you expect a pre-parsed `Temporal.Instant` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zInstantInstance: z.ZodType<Temporal.Instant> = _instant[1];

const _plainDate = registerJSONSchema(
Expand All @@ -90,7 +123,24 @@ const _plainDate = registerJSONSchema(
pattern: PLAIN_DATE_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.PlainDate}.
*
* Accepts ISO 8601 date strings without time (e.g. `2023-01-15`)
* or existing `PlainDate` instances.
*
* Includes JSON Schema metadata (`format: "date"`, `pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainDate: z.ZodType<Temporal.PlainDate> = _plainDate[0];
/**
* Validates that the value is an instance of {@link Temporal.PlainDate}.
*
* Unlike {@link zPlainDate}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.PlainDate` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainDateInstance: z.ZodType<Temporal.PlainDate> = _plainDate[1];

const _plainTime = registerJSONSchema(
Expand All @@ -103,7 +153,24 @@ const _plainTime = registerJSONSchema(
pattern: PLAIN_TIME_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.PlainTime}.
*
* Accepts ISO 8601 time strings without date or timezone
* (e.g. `13:45:30`, `13:45:30.123456789`) or existing `PlainTime` instances.
*
* Includes JSON Schema metadata (`pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainTime: z.ZodType<Temporal.PlainTime> = _plainTime[0];
/**
* Validates that the value is an instance of {@link Temporal.PlainTime}.
*
* Unlike {@link zPlainTime}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.PlainTime` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainTimeInstance: z.ZodType<Temporal.PlainTime> = _plainTime[1];

const _plainDateTime = registerJSONSchema(
Expand All @@ -116,7 +183,24 @@ const _plainDateTime = registerJSONSchema(
pattern: PLAIN_DATE_TIME_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.PlainDateTime}.
*
* Accepts ISO 8601 date-time strings without timezone
* (e.g. `2023-01-15T13:45:30`) or existing `PlainDateTime` instances.
*
* Includes JSON Schema metadata (`pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainDateTime: z.ZodType<Temporal.PlainDateTime> = _plainDateTime[0];
/**
* Validates that the value is an instance of {@link Temporal.PlainDateTime}.
*
* Unlike {@link zPlainDateTime}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.PlainDateTime` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainDateTimeInstance: z.ZodType<Temporal.PlainDateTime> =
_plainDateTime[1];

Expand All @@ -129,7 +213,24 @@ const _plainYearMonth = registerJSONSchema(
pattern: PLAIN_YEAR_MONTH_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.PlainYearMonth}.
*
* Accepts ISO 8601 year-month strings (e.g. `2023-01`)
* or existing `PlainYearMonth` instances.
*
* Includes JSON Schema metadata (`pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainYearMonth: z.ZodType<Temporal.PlainYearMonth> = _plainYearMonth[0];
/**
* Validates that the value is an instance of {@link Temporal.PlainYearMonth}.
*
* Unlike {@link zPlainYearMonth}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.PlainYearMonth` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainYearMonthInstance: z.ZodType<Temporal.PlainYearMonth> =
_plainYearMonth[1];

Expand All @@ -142,7 +243,24 @@ const _plainMonthDay = registerJSONSchema(
pattern: PLAIN_MONTH_DAY_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.PlainMonthDay}.
*
* Accepts ISO 8601 month-day strings (e.g. `--01-15` or `01-15`)
* or existing `PlainMonthDay` instances.
*
* Includes JSON Schema metadata (`pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainMonthDay: z.ZodType<Temporal.PlainMonthDay> = _plainMonthDay[0];
/**
* Validates that the value is an instance of {@link Temporal.PlainMonthDay}.
*
* Unlike {@link zPlainMonthDay}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.PlainMonthDay` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zPlainMonthDayInstance: z.ZodType<Temporal.PlainMonthDay> =
_plainMonthDay[1];

Expand All @@ -156,7 +274,25 @@ const _zonedDateTime = registerJSONSchema(
pattern: ZONED_DATE_TIME_PATTERN,
},
);
/**
* Validates or coerces a string to a {@link Temporal.ZonedDateTime}.
*
* Accepts ISO 8601 date-time strings with a timezone offset and IANA timezone
* annotation (e.g. `2023-01-15T13:45:30+08:00[Asia/Manila]`)
* or existing `ZonedDateTime` instances.
*
* Includes JSON Schema metadata (`pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zZonedDateTime: z.ZodType<Temporal.ZonedDateTime> = _zonedDateTime[0];
/**
* Validates that the value is an instance of {@link Temporal.ZonedDateTime}.
*
* Unlike {@link zZonedDateTime}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.ZonedDateTime` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zZonedDateTimeInstance: z.ZodType<Temporal.ZonedDateTime> =
_zonedDateTime[1];

Expand All @@ -167,7 +303,24 @@ const _duration = registerJSONSchema([zDurationBase, zDurationInstanceBase], {
format: "duration",
pattern: DURATION_PATTERN,
});
/**
* Validates or coerces a string to a {@link Temporal.Duration}.
*
* Accepts ISO 8601 duration strings (e.g. `PT1H30M`, `P1Y2M3D`)
* or existing `Duration` instances.
*
* Includes JSON Schema metadata (`format: "duration"`, `pattern`, `description`)
* so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zDuration: z.ZodType<Temporal.Duration> = _duration[0];
/**
* Validates that the value is an instance of {@link Temporal.Duration}.
*
* Unlike {@link zDuration}, this does **not** coerce strings.
* Use this when you expect a pre-parsed `Temporal.Duration` instance.
*
* Includes JSON Schema metadata so `z.toJSONSchema()` produces a correct JSON Schema.
*/
const zDurationInstance: z.ZodType<Temporal.Duration> = _duration[1];

export {
Expand Down
3 changes: 3 additions & 0 deletions packages/temporal-zod/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"entryPoints": ["src/index.ts", "src/base/index.ts"]
}
13 changes: 13 additions & 0 deletions typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,18 @@
"entryPointStrategy": "packages",
"packageOptions": {
"entryPoints": ["src/index.ts"]
},
"externalSymbolLinkMappings": {
"temporal-spec": {
"Temporal.Instant": "https://tc39.es/proposal-temporal/docs/instant.html",
"Temporal.PlainDate": "https://tc39.es/proposal-temporal/docs/plaindate.html",
"Temporal.PlainTime": "https://tc39.es/proposal-temporal/docs/plaintime.html",
"Temporal.PlainDateTime": "https://tc39.es/proposal-temporal/docs/plaindatetime.html",
"Temporal.PlainYearMonth": "https://tc39.es/proposal-temporal/docs/plainyearmonth.html",
"Temporal.PlainMonthDay": "https://tc39.es/proposal-temporal/docs/plainmonthday.html",
"Temporal.ZonedDateTime": "https://tc39.es/proposal-temporal/docs/zoneddatetime.html",
"Temporal.Duration": "https://tc39.es/proposal-temporal/docs/duration.html",
"Intl.DateTimeFormat": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat"
}
}
}