diff --git a/.changeset/neat-apes-brake.md b/.changeset/neat-apes-brake.md new file mode 100644 index 0000000..5ec88e3 --- /dev/null +++ b/.changeset/neat-apes-brake.md @@ -0,0 +1,5 @@ +--- +"temporal-zod": patch +--- + +More docs diff --git a/.changeset/plain-jars-sell.md b/.changeset/plain-jars-sell.md new file mode 100644 index 0000000..036e92e --- /dev/null +++ b/.changeset/plain-jars-sell.md @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8077e06..6c5613a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 @@ -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 }} diff --git a/package.json b/package.json index cf69a3b..68a23fe 100644 --- a/package.json +++ b/package.json @@ -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 ", diff --git a/packages/temporal-zod/README.md b/packages/temporal-zod/README.md index bbb97b0..55c4744 100644 --- a/packages/temporal-zod/README.md +++ b/packages/temporal-zod/README.md @@ -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. diff --git a/packages/temporal-zod/src/base/index.ts b/packages/temporal-zod/src/base/index.ts index 593b800..0ea3492 100644 --- a/packages/temporal-zod/src/base/index.ts +++ b/packages/temporal-zod/src/base/index.ts @@ -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"; diff --git a/packages/temporal-zod/src/index.ts b/packages/temporal-zod/src/index.ts index b72235c..dbdba6d 100644 --- a/packages/temporal-zod/src/index.ts +++ b/packages/temporal-zod/src/index.ts @@ -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"; diff --git a/packages/temporal-zod/src/json-schemas.ts b/packages/temporal-zod/src/json-schemas.ts index 06ab058..59b83d5 100644 --- a/packages/temporal-zod/src/json-schemas.ts +++ b/packages/temporal-zod/src/json-schemas.ts @@ -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 { @@ -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; @@ -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 = _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 = _instant[1]; const _plainDate = registerJSONSchema( @@ -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 = _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 = _plainDate[1]; const _plainTime = registerJSONSchema( @@ -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 = _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 = _plainTime[1]; const _plainDateTime = registerJSONSchema( @@ -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 = _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 = _plainDateTime[1]; @@ -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 = _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 = _plainYearMonth[1]; @@ -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 = _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 = _plainMonthDay[1]; @@ -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 = _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 = _zonedDateTime[1]; @@ -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 = _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 = _duration[1]; export { diff --git a/packages/temporal-zod/typedoc.json b/packages/temporal-zod/typedoc.json new file mode 100644 index 0000000..36d2fc1 --- /dev/null +++ b/packages/temporal-zod/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts", "src/base/index.ts"] +} diff --git a/typedoc.json b/typedoc.json index 75efccd..135c73c 100644 --- a/typedoc.json +++ b/typedoc.json @@ -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" + } } }