From 9d38b30ebbc26f05d5da546fc6837d6fdab55ecb Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sat, 3 Jan 2026 20:19:26 +0400 Subject: [PATCH 01/11] update zod imports --- docs/src/app/docs/core-api/page.mdx | 4 ++-- packages/ts-ghost-admin-api/src/admin-api.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/authors.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/members.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/newsletters.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/offers.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/pages.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/posts.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/tags.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/tiers.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/users.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/webhooks.ts | 2 +- packages/ts-ghost-content-api/src/authors/schemas.ts | 2 +- packages/ts-ghost-content-api/src/pages/schemas.ts | 2 +- packages/ts-ghost-content-api/src/posts/schemas.ts | 2 +- packages/ts-ghost-content-api/src/settings/schemas.ts | 2 +- packages/ts-ghost-content-api/src/tags/schemas.ts | 2 +- packages/ts-ghost-content-api/src/tiers/schemas.ts | 2 +- packages/ts-ghost-core-api/CHANGELOG.md | 9 +++------ packages/ts-ghost-core-api/README.md | 4 ++-- packages/ts-ghost-core-api/src/api-composer.test.ts | 2 +- packages/ts-ghost-core-api/src/api-composer.ts | 2 +- .../ts-ghost-core-api/src/fetchers/basic-fetcher.test.ts | 2 +- packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts | 2 +- .../src/fetchers/browse-fetcher.test.ts | 2 +- .../ts-ghost-core-api/src/fetchers/browse-fetcher.ts | 2 +- .../ts-ghost-core-api/src/fetchers/delete-fetcher.ts | 2 +- .../src/fetchers/mutation-fetcher.test.ts | 2 +- .../ts-ghost-core-api/src/fetchers/mutation-fetcher.ts | 2 +- .../ts-ghost-core-api/src/fetchers/read-fetcher.test.ts | 2 +- packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts | 2 +- .../ts-ghost-core-api/src/helpers/browse-params.test.ts | 2 +- packages/ts-ghost-core-api/src/helpers/browse-params.ts | 2 +- packages/ts-ghost-core-api/src/helpers/fields.test.ts | 2 +- packages/ts-ghost-core-api/src/helpers/fields.ts | 2 +- packages/ts-ghost-core-api/src/schemas/authors.ts | 2 +- packages/ts-ghost-core-api/src/schemas/email.ts | 2 +- packages/ts-ghost-core-api/src/schemas/members.ts | 2 +- packages/ts-ghost-core-api/src/schemas/newsletter.ts | 2 +- packages/ts-ghost-core-api/src/schemas/offers.ts | 2 +- packages/ts-ghost-core-api/src/schemas/pages.ts | 2 +- packages/ts-ghost-core-api/src/schemas/posts.ts | 2 +- packages/ts-ghost-core-api/src/schemas/settings.ts | 2 +- packages/ts-ghost-core-api/src/schemas/shared.ts | 2 +- packages/ts-ghost-core-api/src/schemas/site.ts | 2 +- packages/ts-ghost-core-api/src/schemas/subscriptions.ts | 2 +- packages/ts-ghost-core-api/src/schemas/tags.ts | 2 +- packages/ts-ghost-core-api/src/schemas/tiers.ts | 2 +- 48 files changed, 52 insertions(+), 55 deletions(-) diff --git a/docs/src/app/docs/core-api/page.mdx b/docs/src/app/docs/core-api/page.mdx index d91d4222..d4540a87 100644 --- a/docs/src/app/docs/core-api/page.mdx +++ b/docs/src/app/docs/core-api/page.mdx @@ -43,7 +43,7 @@ All these methods like `read` and `browse` gives you back the appropriate `Fetch ### Instantiation ```ts -import { z } from "zod/v3"; +import { z } from "zod"; import { APIComposer, HTTPClient, type HTTPClientOptions } from "@ts-ghost/core-api"; const credentials: HTTPClientOptions = { @@ -109,7 +109,7 @@ After instantiation you can use the `APIComposer` to build your queries with 2 a The `browse` and `read` methods accept a config object with 2 properties: `input` and an `output`. These params mimic the way Ghost API Content is built but with the power of Zod and TypeScript they are type-safe here. ```ts -import { z } from "zod/v3"; +import { z } from "zod"; import { APIComposer, HTTPClient, type HTTPClientOptions } from "@ts-ghost/core-api"; const credentials: HTTPClientOptions = { diff --git a/packages/ts-ghost-admin-api/src/admin-api.ts b/packages/ts-ghost-admin-api/src/admin-api.ts index cabd6617..956ea501 100644 --- a/packages/ts-ghost-admin-api/src/admin-api.ts +++ b/packages/ts-ghost-admin-api/src/admin-api.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { adminAPICredentialsSchema, APIComposer, diff --git a/packages/ts-ghost-admin-api/src/schemas/authors.ts b/packages/ts-ghost-admin-api/src/schemas/authors.ts index b63ef724..8ba8f688 100644 --- a/packages/ts-ghost-admin-api/src/schemas/authors.ts +++ b/packages/ts-ghost-admin-api/src/schemas/authors.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseAuthorsSchema } from "@ts-ghost/core-api"; export const adminAuthorsSchema = baseAuthorsSchema.merge( diff --git a/packages/ts-ghost-admin-api/src/schemas/members.ts b/packages/ts-ghost-admin-api/src/schemas/members.ts index 74077cc9..5e874ac6 100644 --- a/packages/ts-ghost-admin-api/src/schemas/members.ts +++ b/packages/ts-ghost-admin-api/src/schemas/members.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseMembersSchema } from "@ts-ghost/core-api"; export const adminMembersCreateSchema = z.object({ diff --git a/packages/ts-ghost-admin-api/src/schemas/newsletters.ts b/packages/ts-ghost-admin-api/src/schemas/newsletters.ts index 1e889a65..76b1a77d 100644 --- a/packages/ts-ghost-admin-api/src/schemas/newsletters.ts +++ b/packages/ts-ghost-admin-api/src/schemas/newsletters.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const adminNewsletterCreateSchema = z.object({ name: z.string(), diff --git a/packages/ts-ghost-admin-api/src/schemas/offers.ts b/packages/ts-ghost-admin-api/src/schemas/offers.ts index cc55a7aa..d435e2a6 100644 --- a/packages/ts-ghost-admin-api/src/schemas/offers.ts +++ b/packages/ts-ghost-admin-api/src/schemas/offers.ts @@ -1,5 +1,5 @@ import isSlug from "validator/lib/isSlug"; -import { z } from "zod/v3"; +import { z } from "zod"; const baseOffersCreateSchema = z.object({ name: z.string(), diff --git a/packages/ts-ghost-admin-api/src/schemas/pages.ts b/packages/ts-ghost-admin-api/src/schemas/pages.ts index d5d939fa..a92b001b 100644 --- a/packages/ts-ghost-admin-api/src/schemas/pages.ts +++ b/packages/ts-ghost-admin-api/src/schemas/pages.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { basePagesSchema } from "@ts-ghost/core-api"; import { adminAuthorsSchema } from "./authors"; diff --git a/packages/ts-ghost-admin-api/src/schemas/posts.ts b/packages/ts-ghost-admin-api/src/schemas/posts.ts index 49474e3e..15c42158 100644 --- a/packages/ts-ghost-admin-api/src/schemas/posts.ts +++ b/packages/ts-ghost-admin-api/src/schemas/posts.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseEmailSchema, baseNewsletterSchema, basePostsSchema } from "@ts-ghost/core-api"; import { adminAuthorsSchema } from "./authors"; diff --git a/packages/ts-ghost-admin-api/src/schemas/tags.ts b/packages/ts-ghost-admin-api/src/schemas/tags.ts index 47cdb686..39a279a5 100644 --- a/packages/ts-ghost-admin-api/src/schemas/tags.ts +++ b/packages/ts-ghost-admin-api/src/schemas/tags.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const adminTagsCreateSchema = z.object({ name: z.string().min(1).max(191), diff --git a/packages/ts-ghost-admin-api/src/schemas/tiers.ts b/packages/ts-ghost-admin-api/src/schemas/tiers.ts index 1af9643d..e0a88bc5 100644 --- a/packages/ts-ghost-admin-api/src/schemas/tiers.ts +++ b/packages/ts-ghost-admin-api/src/schemas/tiers.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseTiersSchema } from "@ts-ghost/core-api"; export const adminTiersSchema = baseTiersSchema.merge( diff --git a/packages/ts-ghost-admin-api/src/schemas/users.ts b/packages/ts-ghost-admin-api/src/schemas/users.ts index 2828b2c5..651ea541 100644 --- a/packages/ts-ghost-admin-api/src/schemas/users.ts +++ b/packages/ts-ghost-admin-api/src/schemas/users.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseAuthorsSchema } from "@ts-ghost/core-api"; export const adminUsersSchema = baseAuthorsSchema.merge( diff --git a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts index 7e09d5a5..6d8d7570 100644 --- a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts +++ b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const ghostEventTypes = z.union([ z.literal("site.changed", { diff --git a/packages/ts-ghost-content-api/src/authors/schemas.ts b/packages/ts-ghost-content-api/src/authors/schemas.ts index e67a7a47..a51a851b 100644 --- a/packages/ts-ghost-content-api/src/authors/schemas.ts +++ b/packages/ts-ghost-content-api/src/authors/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostIdentitySchema, ghostMetadataSchema, ghostMetaSchema } from "@ts-ghost/core-api"; export const authorsSchema = z.object({ diff --git a/packages/ts-ghost-content-api/src/pages/schemas.ts b/packages/ts-ghost-content-api/src/pages/schemas.ts index 34ab096d..de05f19f 100644 --- a/packages/ts-ghost-content-api/src/pages/schemas.ts +++ b/packages/ts-ghost-content-api/src/pages/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostCodeInjectionSchema, ghostIdentitySchema, diff --git a/packages/ts-ghost-content-api/src/posts/schemas.ts b/packages/ts-ghost-content-api/src/posts/schemas.ts index 03268056..226f793e 100644 --- a/packages/ts-ghost-content-api/src/posts/schemas.ts +++ b/packages/ts-ghost-content-api/src/posts/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostCodeInjectionSchema, ghostIdentitySchema, diff --git a/packages/ts-ghost-content-api/src/settings/schemas.ts b/packages/ts-ghost-content-api/src/settings/schemas.ts index c7b2bf7b..4455e559 100644 --- a/packages/ts-ghost-content-api/src/settings/schemas.ts +++ b/packages/ts-ghost-content-api/src/settings/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const settingsSchema = z.object({ title: z.string(), diff --git a/packages/ts-ghost-content-api/src/tags/schemas.ts b/packages/ts-ghost-content-api/src/tags/schemas.ts index 08d2a49a..62abbb20 100644 --- a/packages/ts-ghost-content-api/src/tags/schemas.ts +++ b/packages/ts-ghost-content-api/src/tags/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostCodeInjectionSchema, ghostIdentitySchema, diff --git a/packages/ts-ghost-content-api/src/tiers/schemas.ts b/packages/ts-ghost-content-api/src/tiers/schemas.ts index fba65641..81caf128 100644 --- a/packages/ts-ghost-content-api/src/tiers/schemas.ts +++ b/packages/ts-ghost-content-api/src/tiers/schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostIdentitySchema, ghostVisibilitySchema } from "@ts-ghost/core-api"; export const tiersSchema = z.object({ diff --git a/packages/ts-ghost-core-api/CHANGELOG.md b/packages/ts-ghost-core-api/CHANGELOG.md index b7c4cc3e..e9d54601 100644 --- a/packages/ts-ghost-core-api/CHANGELOG.md +++ b/packages/ts-ghost-core-api/CHANGELOG.md @@ -472,7 +472,7 @@ - `output` option was removed because it is no longer necessary at the QueryBuilder level (kept on Fetchers) ```ts - import { z } from "zod/v3"; + import { z } from "zod"; import { QueryBuilder, type ContentAPICredentials } from "@ts-ghost/core-api"; const api: ContentAPICredentials = { @@ -489,10 +489,7 @@ }); // the "identity" schema is used to validate the inputs of the `read`method of the QueryBuilder - const identitySchema = z.union([ - z.object({ slug: z.string() }), - z.object({ id: z.string() }), - ]); + const identitySchema = z.union([z.object({ slug: z.string() }), z.object({ id: z.string() })]); const simplifiedIncludeSchema = z.object({ count: z.literal(true).optional(), @@ -514,7 +511,7 @@ Example: ```ts - import { z } from "zod/v3"; + import { z } from "zod"; import { adminMembersSchema, QueryBuilder } from "@ts-ghost/core-api"; const membersIncludeSchema = z.object({}); diff --git a/packages/ts-ghost-core-api/README.md b/packages/ts-ghost-core-api/README.md index 742911e3..5f2b4001 100644 --- a/packages/ts-ghost-core-api/README.md +++ b/packages/ts-ghost-core-api/README.md @@ -55,7 +55,7 @@ All these methods like `read` and `browse` gives you back the appropriate `Fetch ### Instantiation ```ts -import { z } from "zod/v3"; +import { z } from "zod"; import { APIComposer, type ContentAPICredentials } from "@ts-ghost/core-api"; const api: ContentAPICredentials = { @@ -115,7 +115,7 @@ After instantiation you can use the `APIComposer` to build your queries with 2 a The `browse` and `read` methods accept a config object with 2 properties: `input` and an `output`. These params mimic the way Ghost API Content is built but with the power of Zod and TypeScript they are type-safe here. ```ts -import { z } from "zod/v3"; +import { z } from "zod"; import { APIComposer, type ContentAPICredentials } from "@ts-ghost/core-api"; const api: ContentAPICredentials = { diff --git a/packages/ts-ghost-core-api/src/api-composer.test.ts b/packages/ts-ghost-core-api/src/api-composer.test.ts index 546c5c2e..97e70d21 100644 --- a/packages/ts-ghost-core-api/src/api-composer.test.ts +++ b/packages/ts-ghost-core-api/src/api-composer.test.ts @@ -1,6 +1,6 @@ import createFetchMock from "vitest-fetch-mock"; import { describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { APIComposer } from "./api-composer"; import { BrowseFetcher, ReadFetcher } from "./fetchers"; diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index eccdc3ea..3987faeb 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -1,4 +1,4 @@ -import { z, ZodRawShape, ZodTypeAny } from "zod/v3"; +import { z, ZodRawShape, ZodTypeAny } from "zod"; import { DeleteFetcher } from "./fetchers"; import { BrowseFetcher } from "./fetchers/browse-fetcher"; diff --git a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.test.ts index 4dbd81bb..8bc74210 100644 --- a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.test.ts @@ -1,6 +1,6 @@ import createFetchMock from "vitest-fetch-mock"; import { afterEach, assert, describe, expect, test, vi } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { HTTPClient, HTTPClientOptions } from "../helpers/http-client"; import { BasicFetcher } from "./basic-fetcher"; diff --git a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts index 1c62b9b7..84e5eac2 100644 --- a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts @@ -1,4 +1,4 @@ -import { z, ZodTypeAny } from "zod/v3"; +import { z, ZodTypeAny } from "zod"; import type { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; diff --git a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts index b0074955..983893f7 100644 --- a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts @@ -1,6 +1,6 @@ import createFetchMock from "vitest-fetch-mock"; import { assert, describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { HTTPClient, type HTTPClientOptions } from "../helpers"; import { BrowseFetcher } from "./browse-fetcher"; diff --git a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts index da132086..e52829cf 100644 --- a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts @@ -1,4 +1,4 @@ -import { z, ZodRawShape } from "zod/v3"; +import { z, ZodRawShape } from "zod"; import { BrowseParamsSchema } from "../helpers/browse-params"; import type { HTTPClient } from "../helpers/http-client"; diff --git a/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts index c7f194af..811c0b22 100644 --- a/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import type { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; diff --git a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.test.ts index 91580f4e..e7e33403 100644 --- a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.test.ts @@ -1,6 +1,6 @@ import createFetchMock from "vitest-fetch-mock"; import { describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { HTTPClient, type HTTPClientOptions } from "../helpers/http-client"; import { MutationFetcher } from "./mutation-fetcher"; diff --git a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts index bc04eae6..2a9118fb 100644 --- a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts @@ -1,4 +1,4 @@ -import { z, ZodRawShape, ZodTypeAny } from "zod/v3"; +import { z, ZodRawShape, ZodTypeAny } from "zod"; import { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts index c36c7816..21bf7f9a 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts @@ -1,6 +1,6 @@ import createFetchMock from "vitest-fetch-mock"; import { assert, describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { HTTPClient, HTTPClientOptions } from "../helpers/http-client"; import { ReadFetcher } from "./read-fetcher"; diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts index 3c6be857..36e7636e 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts @@ -1,4 +1,4 @@ -import { z, ZodRawShape } from "zod/v3"; +import { z, ZodRawShape } from "zod"; import type { HTTPClient } from "../helpers/http-client"; import { type APIResource, type GhostIdentityInput } from "../schemas/shared"; diff --git a/packages/ts-ghost-core-api/src/helpers/browse-params.test.ts b/packages/ts-ghost-core-api/src/helpers/browse-params.test.ts index 8831008c..dac8af55 100644 --- a/packages/ts-ghost-core-api/src/helpers/browse-params.test.ts +++ b/packages/ts-ghost-core-api/src/helpers/browse-params.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { parseBrowseParams } from "./browse-params"; diff --git a/packages/ts-ghost-core-api/src/helpers/browse-params.ts b/packages/ts-ghost-core-api/src/helpers/browse-params.ts index 40e941d4..0e747150 100644 --- a/packages/ts-ghost-core-api/src/helpers/browse-params.ts +++ b/packages/ts-ghost-core-api/src/helpers/browse-params.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; type Split = Str extends `${infer Start}${Separator}${infer Rest}` ? [Start, ...Split] diff --git a/packages/ts-ghost-core-api/src/helpers/fields.test.ts b/packages/ts-ghost-core-api/src/helpers/fields.test.ts index 7628241f..83313f7f 100644 --- a/packages/ts-ghost-core-api/src/helpers/fields.test.ts +++ b/packages/ts-ghost-core-api/src/helpers/fields.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { z } from "zod/v3"; +import { z } from "zod"; import { schemaWithPickedFields } from "./fields"; diff --git a/packages/ts-ghost-core-api/src/helpers/fields.ts b/packages/ts-ghost-core-api/src/helpers/fields.ts index 7c070f19..d0d6b0e7 100644 --- a/packages/ts-ghost-core-api/src/helpers/fields.ts +++ b/packages/ts-ghost-core-api/src/helpers/fields.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import type { Exactly, Mask } from "../utils"; diff --git a/packages/ts-ghost-core-api/src/schemas/authors.ts b/packages/ts-ghost-core-api/src/schemas/authors.ts index 80a1fbf1..a05fcda3 100644 --- a/packages/ts-ghost-core-api/src/schemas/authors.ts +++ b/packages/ts-ghost-core-api/src/schemas/authors.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostIdentitySchema, ghostMetadataSchema } from "./shared"; diff --git a/packages/ts-ghost-core-api/src/schemas/email.ts b/packages/ts-ghost-core-api/src/schemas/email.ts index a0b5a73b..a2a879d1 100644 --- a/packages/ts-ghost-core-api/src/schemas/email.ts +++ b/packages/ts-ghost-core-api/src/schemas/email.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const baseEmailSchema = z.object({ id: z.string(), diff --git a/packages/ts-ghost-core-api/src/schemas/members.ts b/packages/ts-ghost-core-api/src/schemas/members.ts index a8199022..abcbefbc 100644 --- a/packages/ts-ghost-core-api/src/schemas/members.ts +++ b/packages/ts-ghost-core-api/src/schemas/members.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseNewsletterSchema } from "./newsletter"; import { baseSubscriptionsSchema } from "./subscriptions"; diff --git a/packages/ts-ghost-core-api/src/schemas/newsletter.ts b/packages/ts-ghost-core-api/src/schemas/newsletter.ts index 9625989a..db250fc7 100644 --- a/packages/ts-ghost-core-api/src/schemas/newsletter.ts +++ b/packages/ts-ghost-core-api/src/schemas/newsletter.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostIdentitySchema } from "./shared"; diff --git a/packages/ts-ghost-core-api/src/schemas/offers.ts b/packages/ts-ghost-core-api/src/schemas/offers.ts index 3c3e93f9..c7464cbb 100644 --- a/packages/ts-ghost-core-api/src/schemas/offers.ts +++ b/packages/ts-ghost-core-api/src/schemas/offers.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const baseOffersSchema = z.object({ id: z.string(), diff --git a/packages/ts-ghost-core-api/src/schemas/pages.ts b/packages/ts-ghost-core-api/src/schemas/pages.ts index 46275f23..ff3f127e 100644 --- a/packages/ts-ghost-core-api/src/schemas/pages.ts +++ b/packages/ts-ghost-core-api/src/schemas/pages.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseAuthorsSchema } from "./authors"; import { diff --git a/packages/ts-ghost-core-api/src/schemas/posts.ts b/packages/ts-ghost-core-api/src/schemas/posts.ts index dafceed3..e3e52914 100644 --- a/packages/ts-ghost-core-api/src/schemas/posts.ts +++ b/packages/ts-ghost-core-api/src/schemas/posts.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseAuthorsSchema } from "./authors"; import { diff --git a/packages/ts-ghost-core-api/src/schemas/settings.ts b/packages/ts-ghost-core-api/src/schemas/settings.ts index 1267a3b0..962815c7 100644 --- a/packages/ts-ghost-core-api/src/schemas/settings.ts +++ b/packages/ts-ghost-core-api/src/schemas/settings.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const baseSettingsSchema = z.object({ title: z.string(), diff --git a/packages/ts-ghost-core-api/src/schemas/shared.ts b/packages/ts-ghost-core-api/src/schemas/shared.ts index 5bb61a61..1e47b4d2 100644 --- a/packages/ts-ghost-core-api/src/schemas/shared.ts +++ b/packages/ts-ghost-core-api/src/schemas/shared.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import type { HTTPClient } from "../helpers/http-client"; diff --git a/packages/ts-ghost-core-api/src/schemas/site.ts b/packages/ts-ghost-core-api/src/schemas/site.ts index 197ceea1..8ff8cdfc 100644 --- a/packages/ts-ghost-core-api/src/schemas/site.ts +++ b/packages/ts-ghost-core-api/src/schemas/site.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; export const baseSiteSchema = z.object({ title: z.string(), diff --git a/packages/ts-ghost-core-api/src/schemas/subscriptions.ts b/packages/ts-ghost-core-api/src/schemas/subscriptions.ts index f4508956..5277e57e 100644 --- a/packages/ts-ghost-core-api/src/schemas/subscriptions.ts +++ b/packages/ts-ghost-core-api/src/schemas/subscriptions.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { baseOffersSchema } from "./offers"; import { baseTiersSchema } from "./tiers"; diff --git a/packages/ts-ghost-core-api/src/schemas/tags.ts b/packages/ts-ghost-core-api/src/schemas/tags.ts index e9303a2b..6d1732cf 100644 --- a/packages/ts-ghost-core-api/src/schemas/tags.ts +++ b/packages/ts-ghost-core-api/src/schemas/tags.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostCodeInjectionSchema, diff --git a/packages/ts-ghost-core-api/src/schemas/tiers.ts b/packages/ts-ghost-core-api/src/schemas/tiers.ts index f5e7a7d6..e91b4c67 100644 --- a/packages/ts-ghost-core-api/src/schemas/tiers.ts +++ b/packages/ts-ghost-core-api/src/schemas/tiers.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v3"; +import { z } from "zod"; import { ghostIdentitySchema, ghostVisibilitySchema } from "./shared"; From 07126a429b2c3e39b2821b22b5ea5f3561c3b02e Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sat, 3 Jan 2026 20:22:26 +0400 Subject: [PATCH 02/11] migrate schemas, types and tests due to some breaking changes --- .../ts-ghost-admin-api/src/schemas/authors.ts | 4 +- .../ts-ghost-admin-api/src/schemas/members.ts | 52 +++--- .../src/schemas/newsletters.ts | 2 +- .../ts-ghost-admin-api/src/schemas/offers.ts | 5 +- .../ts-ghost-admin-api/src/schemas/pages.ts | 36 ++-- .../ts-ghost-admin-api/src/schemas/posts.ts | 40 ++--- .../ts-ghost-admin-api/src/schemas/tags.ts | 2 +- .../ts-ghost-admin-api/src/schemas/tiers.ts | 22 +-- .../ts-ghost-admin-api/src/schemas/users.ts | 4 +- .../src/schemas/webhooks.ts | 108 ++++++------ .../src/authors/authors.test.ts | 4 +- packages/ts-ghost-content-api/src/index.ts | 8 +- .../src/pages/pages.integration.test.ts | 3 +- .../src/posts/posts.integration.test.ts | 3 +- .../src/posts/posts.test.ts | 16 +- .../src/settings/schemas.ts | 4 +- .../ts-ghost-core-api/src/api-composer.ts | 47 ++--- .../src/fetchers/browse-fetcher.test-d.ts | 161 ++++++++++++++++++ .../src/fetchers/browse-fetcher.test.ts | 52 +++--- .../src/fetchers/browse-fetcher.ts | 29 +++- .../src/fetchers/delete-fetcher.ts | 4 +- .../src/fetchers/mutation-fetcher.ts | 11 +- .../src/fetchers/read-fetcher.test-d.ts | 121 +++++++++++++ .../src/fetchers/read-fetcher.test.ts | 46 ++--- .../src/fetchers/read-fetcher.ts | 21 ++- .../ts-ghost-core-api/src/helpers/fields.ts | 4 +- .../src/helpers/http-client.ts | 5 +- .../ts-ghost-core-api/src/schemas/members.ts | 56 +++--- .../src/schemas/newsletter.ts | 39 +++-- .../ts-ghost-core-api/src/schemas/offers.ts | 45 +++-- .../ts-ghost-core-api/src/schemas/settings.ts | 4 +- .../ts-ghost-core-api/src/schemas/shared.ts | 4 +- .../src/schemas/subscriptions.ts | 37 ++-- packages/ts-ghost-core-api/src/utils.d.ts | 4 + 34 files changed, 679 insertions(+), 324 deletions(-) create mode 100644 packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test-d.ts create mode 100644 packages/ts-ghost-core-api/src/fetchers/read-fetcher.test-d.ts diff --git a/packages/ts-ghost-admin-api/src/schemas/authors.ts b/packages/ts-ghost-admin-api/src/schemas/authors.ts index 8ba8f688..7b651a2e 100644 --- a/packages/ts-ghost-admin-api/src/schemas/authors.ts +++ b/packages/ts-ghost-admin-api/src/schemas/authors.ts @@ -23,10 +23,10 @@ export const adminAuthorsSchema = baseAuthorsSchema.merge( description: z.string(), created_at: z.string().nullish(), updated_at: z.string().nullish(), - }), + }) ) .optional(), - }), + }) ); export type Author = z.infer; diff --git a/packages/ts-ghost-admin-api/src/schemas/members.ts b/packages/ts-ghost-admin-api/src/schemas/members.ts index 5e874ac6..b802d831 100644 --- a/packages/ts-ghost-admin-api/src/schemas/members.ts +++ b/packages/ts-ghost-admin-api/src/schemas/members.ts @@ -2,63 +2,64 @@ import { z } from "zod"; import { baseMembersSchema } from "@ts-ghost/core-api"; export const adminMembersCreateSchema = z.object({ - email: z.string({ description: "The email address of the member" }).email(), - name: z.string({ description: "The name of the member" }).optional(), - note: z.string({ description: "(nullable) A note about the member" }).optional(), - geolocation: z.string({ description: "(nullable) The geolocation of the member" }).optional(), + email: z.email().meta({ description: "The email address of the member" }), + name: z.string().meta({ description: "The name of the member" }).optional(), + note: z.string().meta({ description: "(nullable) A note about the member" }).optional(), + geolocation: z.string().meta({ description: "(nullable) The geolocation of the member" }).optional(), labels: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the label" }), + id: z.string().meta({ description: "The ID of the label" }), }), z.object({ - name: z.string({ description: "The name of the label" }), + name: z.string().meta({ description: "The name of the label" }), }), z.object({ - slug: z.string({ description: "The slug of the label" }), + slug: z.string().meta({ description: "The slug of the label" }), }), ]), - { description: "The labels associated with the member" }, ) + .meta({ description: "The labels associated with the member" }) .optional(), products: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the subscription" }), + id: z.string().meta({ description: "The ID of the subscription" }), }), z.object({ - name: z.string({ description: "The name of the subscription" }), + name: z.string().meta({ description: "The name of the subscription" }), }), z.object({ - slug: z.string({ description: "The slug of the subscription" }), + slug: z.string().meta({ description: "The slug of the subscription" }), }), ]), - { - description: `The products associated with the member, they correspond to a stripe product. - If given the member status will be 'comped' as given away a subscription.`, - }, ) + .meta({ + description: `The products associated with the member, they correspond to a stripe product. + If given the member status will be 'comped' as given away a subscription.`, + }) .optional(), // newsletters and subscribed exclude each other. `subscribed` newsletters: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the newsletter" }), + id: z.string().meta({ description: "The ID of the newsletter" }), }), z.object({ - name: z.string({ description: "The name of the newsletter" }), + name: z.string().meta({ description: "The name of the newsletter" }), }), ]), - { - description: `Specifing newsletter to subscribe to via id or name, incompatible with the \`subscribed\` property`, - }, ) + .meta({ + description: `Specifing newsletter to subscribe to via id or name, incompatible with the \`subscribed\` property`, + }) .optional(), subscribed: z - .boolean({ + .boolean() + .meta({ description: "Will subscribe the user to the default Newsletter, incompatible with the `newsletters` property", }) @@ -81,9 +82,12 @@ export const adminMembersSchema = baseMembersSchema.merge( newsletters: z.array( z.object({ id: z.string(), - name: z.string({ description: "Public name for the newsletter" }), - description: z.string({ description: "(nullable) Public description of the newsletter" }).nullish(), - status: z.union([z.literal("active"), z.literal("archived")], { + name: z.string().meta({ description: "Public name for the newsletter" }), + description: z + .string() + .meta({ description: "(nullable) Public description of the newsletter" }) + .nullish(), + status: z.union([z.literal("active"), z.literal("archived")]).meta({ description: "active or archived - denotes if the newsletter is active or archived", }), }), diff --git a/packages/ts-ghost-admin-api/src/schemas/newsletters.ts b/packages/ts-ghost-admin-api/src/schemas/newsletters.ts index 76b1a77d..9dcbdac4 100644 --- a/packages/ts-ghost-admin-api/src/schemas/newsletters.ts +++ b/packages/ts-ghost-admin-api/src/schemas/newsletters.ts @@ -4,7 +4,7 @@ export const adminNewsletterCreateSchema = z.object({ name: z.string(), description: z.string().max(3000).optional(), sender_name: z.string(), - sender_email: z.string().email().nullish(), + sender_email: z.email().nullish(), sender_reply_to: z.string().optional(), status: z.union([z.literal("active"), z.literal("archived")]).optional(), subscribe_on_signup: z.boolean().optional(), diff --git a/packages/ts-ghost-admin-api/src/schemas/offers.ts b/packages/ts-ghost-admin-api/src/schemas/offers.ts index d435e2a6..e7603491 100644 --- a/packages/ts-ghost-admin-api/src/schemas/offers.ts +++ b/packages/ts-ghost-admin-api/src/schemas/offers.ts @@ -9,12 +9,13 @@ const baseOffersCreateSchema = z.object({ display_title: z.string().optional(), display_description: z.string().optional(), cadence: z.union([z.literal("year"), z.literal("month")]), - amount: z.number({ + amount: z.number().meta({ description: "Amount of the percent or fixed amount in the smallest unit of the currency", }), duration: z.union([z.literal("once"), z.literal("forever"), z.literal("repeating")]), duration_in_months: z - .number({ + .number() + .meta({ description: "Number of months offer should be repeated when duration is repeating", }) .nullish(), diff --git a/packages/ts-ghost-admin-api/src/schemas/pages.ts b/packages/ts-ghost-admin-api/src/schemas/pages.ts index a92b001b..7a50dd4b 100644 --- a/packages/ts-ghost-admin-api/src/schemas/pages.ts +++ b/packages/ts-ghost-admin-api/src/schemas/pages.ts @@ -82,55 +82,55 @@ export const adminPagesCreateSchema = z.object({ .array( z.union([ z.object({ - id: z.string({ description: "The ID of the tags" }), + id: z.string().meta({ description: "The ID of the tags" }), }), z.object({ - name: z.string({ description: "The name of the tags" }), + name: z.string().meta({ description: "The name of the tags" }), }), z.object({ - slug: z.string({ description: "The slug of the tags" }), + slug: z.string().meta({ description: "The slug of the tags" }), }), ]), - { - description: `The tags associated with the post array of either slug, id or name`, - }, ) + .meta({ + description: `The tags associated with the post array of either slug, id or name`, + }) .optional(), tiers: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the tiers" }), + id: z.string().meta({ description: "The ID of the tiers" }), }), z.object({ - name: z.string({ description: "The name of the tiers" }), + name: z.string().meta({ description: "The name of the tiers" }), }), z.object({ - slug: z.string({ description: "The slug of the tiers" }), + slug: z.string().meta({ description: "The slug of the tiers" }), }), ]), - { - description: `The tiers associated with the post array of either slug, id or name`, - }, ) + .meta({ + description: `The tiers associated with the post array of either slug, id or name`, + }) .optional(), authors: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the author" }), + id: z.string().meta({ description: "The ID of the author" }), }), z.object({ - name: z.string({ description: "The name of the author" }), + name: z.string().meta({ description: "The name of the author" }), }), z.object({ - email: z.string({ description: "The email of the author" }), + email: z.string().meta({ description: "The email of the author" }), }), ]), - { - description: `Specifing author via id, name or slug.`, - }, ) + .meta({ + description: `Specifing author via id, name or slug.`, + }) .optional(), }); diff --git a/packages/ts-ghost-admin-api/src/schemas/posts.ts b/packages/ts-ghost-admin-api/src/schemas/posts.ts index 15c42158..b901f8ae 100644 --- a/packages/ts-ghost-admin-api/src/schemas/posts.ts +++ b/packages/ts-ghost-admin-api/src/schemas/posts.ts @@ -88,63 +88,63 @@ const basePostsCreateSchema = z.object({ .array( z.union([ z.object({ - id: z.string({ description: "The ID of the tags" }), + id: z.string().meta({ description: "The ID of the tags" }), }), z.object({ - name: z.string({ description: "The name of the tags" }), + name: z.string().meta({ description: "The name of the tags" }), }), z.object({ - slug: z.string({ description: "The slug of the tags" }), + slug: z.string().meta({ description: "The slug of the tags" }), }), ]), - { - description: `The tags associated with the post array of either slug, id or name`, - }, ) + .meta({ + description: `The tags associated with the post array of either slug, id or name`, + }) .optional(), tiers: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the tiers" }), + id: z.string().meta({ description: "The ID of the tiers" }), }), z.object({ - name: z.string({ description: "The name of the tiers" }), + name: z.string().meta({ description: "The name of the tiers" }), }), z.object({ - slug: z.string({ description: "The slug of the tiers" }), + slug: z.string().meta({ description: "The slug of the tiers" }), }), ]), - { - description: `The tiers associated with the post array of either slug, id or name`, - }, ) + .meta({ + description: `The tiers associated with the post array of either slug, id or name`, + }) .optional(), authors: z .array( z.union([ z.object({ - id: z.string({ description: "The ID of the author" }), + id: z.string().meta({ description: "The ID of the author" }), }), z.object({ - name: z.string({ description: "The name of the author" }), + name: z.string().meta({ description: "The name of the author" }), }), z.object({ - email: z.string({ description: "The email of the author" }), + email: z.string().meta({ description: "The email of the author" }), }), ]), - { - description: `Specifing author via id, name or slug.`, - }, ) + .meta({ + description: `Specifing author via id, name or slug.`, + }) .optional(), newsletter: z .union([ z.object({ - id: z.string({ description: "The ID of the newsletter" }), + id: z.string().meta({ description: "The ID of the newsletter" }), }), z.object({ - slug: z.string({ description: "The slug of the newsletter" }), + slug: z.string().meta({ description: "The slug of the newsletter" }), }), ]) .optional(), diff --git a/packages/ts-ghost-admin-api/src/schemas/tags.ts b/packages/ts-ghost-admin-api/src/schemas/tags.ts index 39a279a5..831d545e 100644 --- a/packages/ts-ghost-admin-api/src/schemas/tags.ts +++ b/packages/ts-ghost-admin-api/src/schemas/tags.ts @@ -4,7 +4,7 @@ export const adminTagsCreateSchema = z.object({ name: z.string().min(1).max(191), slug: z.string().max(191).optional(), description: z.string().max(500).optional(), - feature_image: z.string().url().optional(), + feature_image: z.url().optional(), visibility: z.union([z.literal("public"), z.literal("internal")]).optional(), meta_title: z.string().max(300).optional(), meta_description: z.string().max(500).optional(), diff --git a/packages/ts-ghost-admin-api/src/schemas/tiers.ts b/packages/ts-ghost-admin-api/src/schemas/tiers.ts index e0a88bc5..65de369a 100644 --- a/packages/ts-ghost-admin-api/src/schemas/tiers.ts +++ b/packages/ts-ghost-admin-api/src/schemas/tiers.ts @@ -3,22 +3,22 @@ import { baseTiersSchema } from "@ts-ghost/core-api"; export const adminTiersSchema = baseTiersSchema.merge( z.object({ - monthly_price_id: z.string({ description: "Monthly Stripe price id" }).nullish(), - yearly_price_id: z.string({ description: "Yearly Stripe price id" }).nullish(), + monthly_price_id: z.string().meta({ description: "Monthly Stripe price id" }).nullish(), + yearly_price_id: z.string().meta({ description: "Yearly Stripe price id" }).nullish(), }), ); export type Tiers = z.infer; export const adminTiersCreateSchema = z.object({ - name: z.string({ description: "Name of the tier" }).min(1).max(2000), - description: z.string({ description: "Description of the tier" }).optional(), - welcome_page_url: z.string({ description: "Welcome page URL of the tier" }).optional(), + name: z.string().meta({ description: "Name of the tier" }).min(1).max(2000), + description: z.string().meta({ description: "Description of the tier" }).optional(), + welcome_page_url: z.string().meta({ description: "Welcome page URL of the tier" }).optional(), visibility: z.union([z.literal("public"), z.literal("none")]).optional(), - monthly_price: z.number({ description: "Monthly price of the tier" }).optional(), - yearly_price: z.number({ description: "Yearly price of the tier" }).optional(), - trial_days: z.number({ description: "Trial days of the tier" }).optional(), - benefits: z.array(z.string({ description: "Benefits of the tier" })).optional(), - currency: z.string({ description: "Currency of the tier" }).optional(), - active: z.boolean({ description: "Active of the tier" }).optional(), + monthly_price: z.number().meta({ description: "Monthly price of the tier" }).optional(), + yearly_price: z.number().meta({ description: "Yearly price of the tier" }).optional(), + trial_days: z.number().meta({ description: "Trial days of the tier" }).optional(), + benefits: z.array(z.string().meta({ description: "Benefits of the tier" })).optional(), + currency: z.string().meta({ description: "Currency of the tier" }).optional(), + active: z.boolean().meta({ description: "Active of the tier" }).optional(), }); diff --git a/packages/ts-ghost-admin-api/src/schemas/users.ts b/packages/ts-ghost-admin-api/src/schemas/users.ts index 651ea541..07b24604 100644 --- a/packages/ts-ghost-admin-api/src/schemas/users.ts +++ b/packages/ts-ghost-admin-api/src/schemas/users.ts @@ -24,10 +24,10 @@ export const adminUsersSchema = baseAuthorsSchema.merge( description: z.string(), created_at: z.string().nullish(), updated_at: z.string().nullish(), - }), + }) ) .optional(), - }), + }) ); export type User = z.infer; diff --git a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts index 6d8d7570..6034e1fe 100644 --- a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts +++ b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts @@ -1,89 +1,99 @@ import { z } from "zod"; export const ghostEventTypes = z.union([ - z.literal("site.changed", { + z.literal("site.changed").meta({ description: "Triggered whenever any content changes in your site data or settings", }), - z.literal("post.added", { description: "Triggered whenever a post is added to Ghost" }), - z.literal("post.deleted", { description: "Triggered whenever a post is deleted from Ghost" }), - z.literal("post.edited", { description: "Triggered whenever a post is edited in Ghost" }), - z.literal("post.published", { description: "Triggered whenever a post is published to Ghost" }), - z.literal("post.published.edited", { + z.literal("post.added").meta({ description: "Triggered whenever a post is added to Ghost" }), + z.literal("post.deleted").meta({ description: "Triggered whenever a post is deleted from Ghost" }), + z.literal("post.edited").meta({ description: "Triggered whenever a post is edited in Ghost" }), + z.literal("post.published").meta({ description: "Triggered whenever a post is published to Ghost" }), + z.literal("post.published.edited").meta({ description: "Triggered whenever a published post is edited in Ghost", }), - z.literal("post.unpublished", { description: "Triggered whenever a post is unpublished from Ghost" }), - z.literal("post.scheduled", { + z.literal("post.unpublished").meta({ description: "Triggered whenever a post is unpublished from Ghost" }), + z.literal("post.scheduled").meta({ description: "Triggered whenever a post is scheduled to be published in Ghost", }), - z.literal("post.unscheduled", { + z.literal("post.unscheduled").meta({ description: "Triggered whenever a post is unscheduled from publishing in Ghost", }), - z.literal("post.rescheduled", { + z.literal("post.rescheduled").meta({ description: "Triggered whenever a post is rescheduled to publish in Ghost", }), - z.literal("page.added", { description: "Triggered whenever a page is added to Ghost" }), - z.literal("page.deleted", { description: "Triggered whenever a page is deleted from Ghost" }), - z.literal("page.edited", { description: "Triggered whenever a page is edited in Ghost" }), - z.literal("page.published", { description: "Triggered whenever a page is published to Ghost" }), - z.literal("page.published.edited", { + z.literal("page.added").meta({ description: "Triggered whenever a page is added to Ghost" }), + z.literal("page.deleted").meta({ description: "Triggered whenever a page is deleted from Ghost" }), + z.literal("page.edited").meta({ description: "Triggered whenever a page is edited in Ghost" }), + z.literal("page.published").meta({ description: "Triggered whenever a page is published to Ghost" }), + z.literal("page.published.edited").meta({ description: "Triggered whenever a published page is edited in Ghost", }), - z.literal("page.unpublished", { description: "Triggered whenever a page is unpublished from Ghost" }), - z.literal("page.scheduled", { + z.literal("page.unpublished").meta({ description: "Triggered whenever a page is unpublished from Ghost" }), + z.literal("page.scheduled").meta({ description: "Triggered whenever a page is scheduled to be published in Ghost", }), - z.literal("page.unscheduled", { + z.literal("page.unscheduled").meta({ description: "Triggered whenever a page is unscheduled from publishing in Ghost", }), - z.literal("page.rescheduled", { + z.literal("page.rescheduled").meta({ description: "Triggered whenever a page is rescheduled to publish in Ghost", }), - z.literal("tag.added", { description: "Triggered whenever a tag is added to Ghost" }), - z.literal("tag.edited", { description: "Triggered whenever a tag is edited in Ghost" }), - z.literal("tag.deleted", { description: "Triggered whenever a tag is deleted from Ghost" }), - z.literal("post.tag.attached", { description: "Triggered whenever a tag is attached to a post in Ghost" }), - z.literal("post.tag.detached", { + z.literal("tag.added").meta({ description: "Triggered whenever a tag is added to Ghost" }), + z.literal("tag.edited").meta({ description: "Triggered whenever a tag is edited in Ghost" }), + z.literal("tag.deleted").meta({ description: "Triggered whenever a tag is deleted from Ghost" }), + z + .literal("post.tag.attached") + .meta({ description: "Triggered whenever a tag is attached to a post in Ghost" }), + z.literal("post.tag.detached").meta({ description: "Triggered whenever a tag is detached from a post in Ghost", }), - z.literal("page.tag.attached", { description: "Triggered whenever a tag is attached to a page in Ghost" }), - z.literal("page.tag.detached", { + z + .literal("page.tag.attached") + .meta({ description: "Triggered whenever a tag is attached to a page in Ghost" }), + z.literal("page.tag.detached").meta({ description: "Triggered whenever a tag is detached from a page in Ghost", }), - z.literal("member.added", { description: "Triggered whenever a member is added to Ghost" }), - z.literal("member.edited", { description: "Triggered whenever a member is edited in Ghost" }), - z.literal("member.deleted", { description: "Triggered whenever a member is deleted from Ghost" }), + z.literal("member.added").meta({ description: "Triggered whenever a member is added to Ghost" }), + z.literal("member.edited").meta({ description: "Triggered whenever a member is edited in Ghost" }), + z.literal("member.deleted").meta({ description: "Triggered whenever a member is deleted from Ghost" }), ]); export type GhostWebhookEventTypes = z.infer; export const adminWebhookSchema = z.object({ - id: z.string({ description: "The ID of the webhook" }), + id: z.string().meta({ description: "The ID of the webhook" }), event: ghostEventTypes, - target_url: z.string({ description: "The URL of the webhook" }).url(), - name: z.string({ description: "The name of the webhook" }).nullish(), - secret: z.string({ description: "The secret of the webhook" }).nullish(), - api_version: z.string({ description: "The API version of the webhook" }).nullish(), - integration_id: z.string({ description: "The ID of the integration" }).nullish(), - status: z.string({ description: "The status of the webhook" }).nullish(), - last_triggered_at: z.string({ description: "The date and time of the last webhook trigger" }).nullish(), - last_triggered_status: z.string({ description: "The status of the last webhook trigger" }).nullish(), - last_triggered_error: z.string({ description: "The error of the last webhook trigger" }).nullish(), - created_at: z.string({ description: "The date and time of the webhook creation" }), - updated_at: z.string({ description: "The date and time of the webhook update" }).nullish(), + target_url: z.string().meta({ description: "The URL of the webhook" }).url(), + name: z.string().meta({ description: "The name of the webhook" }).nullish(), + secret: z.string().meta({ description: "The secret of the webhook" }).nullish(), + api_version: z.string().meta({ description: "The API version of the webhook" }).nullish(), + integration_id: z.string().meta({ description: "The ID of the integration" }).nullish(), + status: z.string().meta({ description: "The status of the webhook" }).nullish(), + last_triggered_at: z + .string() + .meta({ description: "The date and time of the last webhook trigger" }) + .nullish(), + last_triggered_status: z + .string() + .meta({ description: "The status of the last webhook trigger" }) + .nullish(), + last_triggered_error: z.string().meta({ description: "The error of the last webhook trigger" }).nullish(), + created_at: z.string().meta({ description: "The date and time of the webhook creation" }), + updated_at: z.string().meta({ description: "The date and time of the webhook update" }).nullish(), }); export const adminWebhookCreateSchema = z.object({ event: ghostEventTypes, - target_url: z.string({ description: "The URL of the webhook" }).url(), - name: z.string({ description: "The name of the webhook" }).optional(), - secret: z.string({ description: "The secret of the webhook" }).nullish(), - api_version: z.string({ description: "The API version of the webhook" }).nullish(), - integration_id: z.string({ description: "The ID of the integration" }).nullish(), + target_url: z.url().meta({ description: "The URL of the webhook" }), + name: z.string().meta({ description: "The name of the webhook" }).optional(), + secret: z.string().meta({ description: "The secret of the webhook" }).nullish(), + api_version: z.string().meta({ description: "The API version of the webhook" }).nullish(), + integration_id: z.string().meta({ description: "The ID of the integration" }).nullish(), }); export const adminWebhookUpdateSchema = z.object({ event: ghostEventTypes.optional(), - target_url: z.string({ description: "The URL of the webhook" }).url().optional(), - name: z.string({ description: "The name of the webhook" }).optional(), - api_version: z.string({ description: "The API version of the webhook" }).nullish(), + target_url: z.string().meta({ description: "The URL of the webhook" }).url().optional(), + name: z.string().meta({ description: "The name of the webhook" }).optional(), + api_version: z.string().meta({ description: "The API version of the webhook" }).nullish(), }); diff --git a/packages/ts-ghost-content-api/src/authors/authors.test.ts b/packages/ts-ghost-content-api/src/authors/authors.test.ts index 1d915f80..6c4c8d77 100644 --- a/packages/ts-ghost-content-api/src/authors/authors.test.ts +++ b/packages/ts-ghost-content-api/src/authors/authors.test.ts @@ -62,7 +62,7 @@ describe("authors api .browse() Args Type-safety", () => { }); test(".browse 'fields' argument should ony accept valid fields", () => { - expect( + expect(() => api.authors .browse() .fields({ @@ -70,7 +70,7 @@ describe("authors api .browse() Args Type-safety", () => { foo: true, }) .getOutputFields(), - ).toEqual([]); + ).toThrow(); expect(api.authors.browse().fields({ location: true }).getOutputFields()).toEqual(["location"]); expect(api.authors.browse().fields({ name: true, website: true }).getOutputFields()).toEqual([ diff --git a/packages/ts-ghost-content-api/src/index.ts b/packages/ts-ghost-content-api/src/index.ts index 1d08e003..5f29f12a 100644 --- a/packages/ts-ghost-content-api/src/index.ts +++ b/packages/ts-ghost-content-api/src/index.ts @@ -6,4 +6,10 @@ export * from "./tags"; export * from "./tiers"; export * from "./settings"; export * from "./helpers"; -export type { InferFetcherDataShape, InferResponseDataShape, BrowseParams } from "@ts-ghost/core-api"; +export type { + InferFetcherDataShape, + InferResponseDataShape, + BrowseParams, + BrowseFetcher, + ReadFetcher, +} from "@ts-ghost/core-api"; diff --git a/packages/ts-ghost-content-api/src/pages/pages.integration.test.ts b/packages/ts-ghost-content-api/src/pages/pages.integration.test.ts index 368f127e..9bacbd1d 100644 --- a/packages/ts-ghost-content-api/src/pages/pages.integration.test.ts +++ b/packages/ts-ghost-content-api/src/pages/pages.integration.test.ts @@ -155,7 +155,7 @@ describe("pages integration tests browse", () => { test("pages.browse() with mix of incude and fields... this is mostly broken on Ghost side", async () => { const result = await api.pages .browse() - .fields({ slug: true, title: true, primary_author: true }) + .fields({ slug: true, title: true, primary_author: true, authors: true }) .include({ authors: true }) .fetch(); expect(result).not.toBeUndefined(); @@ -173,7 +173,6 @@ describe("pages integration tests browse", () => { expect(page.primary_author?.slug).toBe("phildl"); // @ts-expect-error expect(page.id).toBeUndefined(); - // @ts-expect-error expect(page.authors).toBeUndefined(); } }); diff --git a/packages/ts-ghost-content-api/src/posts/posts.integration.test.ts b/packages/ts-ghost-content-api/src/posts/posts.integration.test.ts index e9a8a376..bd6fa1ef 100644 --- a/packages/ts-ghost-content-api/src/posts/posts.integration.test.ts +++ b/packages/ts-ghost-content-api/src/posts/posts.integration.test.ts @@ -152,7 +152,7 @@ describe("posts integration tests browse", () => { test("posts.browse() with mix of incude and fields... this is mostly broken on Ghost side", async () => { const result = await api.posts .browse() - .fields({ slug: true, title: true, primary_author: true }) + .fields({ slug: true, title: true, primary_author: true, authors: true }) .include({ authors: true }) .fetch(); expect(result).not.toBeUndefined(); @@ -170,7 +170,6 @@ describe("posts integration tests browse", () => { expect(post.primary_author?.slug).toBe("phildl"); // @ts-expect-error expect(post.id).toBeUndefined(); - // @ts-expect-error expect(post.authors).toBeUndefined(); } }); diff --git a/packages/ts-ghost-content-api/src/posts/posts.test.ts b/packages/ts-ghost-content-api/src/posts/posts.test.ts index c815be70..519a9c03 100644 --- a/packages/ts-ghost-content-api/src/posts/posts.test.ts +++ b/packages/ts-ghost-content-api/src/posts/posts.test.ts @@ -20,11 +20,13 @@ describe("posts api .browse() Args Type-safety", () => { foo: true, } satisfies { [k in keyof Post]?: true | undefined }; - let test = api.posts - .browse() - // @ts-expect-error - shouldnt accept invalid params - .fields(outputFields); - expect(test.getOutputFields()).toEqual(["slug", "title"]); + expect(() => + api.posts + .browse() + // @ts-expect-error - shouldnt accept invalid params + .fields(outputFields), + ).toThrow(); + // expect(test.getOutputFields()).toEqual(["slug", "title"]); const fields = ["slug", "title", "foo"] as const; const unknownOriginFields = fields.reduce( @@ -34,8 +36,8 @@ describe("posts api .browse() Args Type-safety", () => { }, {} as { [k in keyof Post]?: true | undefined }, ); - const result = api.posts.browse().fields(unknownOriginFields); - expect(result.getOutputFields()).toEqual(["slug", "title"]); + expect(() => api.posts.browse().fields(unknownOriginFields)).toThrow(); + // expect(result.getOutputFields()).toEqual(["slug", "title"]); }); test(".browse() params, output fields declare const", () => { const outputFields = { diff --git a/packages/ts-ghost-content-api/src/settings/schemas.ts b/packages/ts-ghost-content-api/src/settings/schemas.ts index 4455e559..6fb4ab9c 100644 --- a/packages/ts-ghost-content-api/src/settings/schemas.ts +++ b/packages/ts-ghost-content-api/src/settings/schemas.ts @@ -17,13 +17,13 @@ export const settingsSchema = z.object({ z.object({ label: z.string(), url: z.string(), - }), + }) ), secondary_navigation: z.array( z.object({ label: z.string(), url: z.string(), - }), + }) ), meta_title: z.string().nullable(), meta_description: z.string().nullable(), diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index 3987faeb..8ef8f2b5 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -1,4 +1,5 @@ -import { z, ZodRawShape, ZodTypeAny } from "zod"; +import { z, ZodObject as ZodObjectV4, ZodRawShape } from "zod"; +import * as z4 from "zod/v4/core"; import { DeleteFetcher } from "./fetchers"; import { BrowseFetcher } from "./fetchers/browse-fetcher"; @@ -9,8 +10,8 @@ import type { HTTPClientFactory } from "./helpers/http-client"; import type { APIResource } from "./schemas"; import type { IsAny } from "./utils"; -function isZodObject(schema: z.ZodObject | z.ZodTypeAny): schema is z.ZodObject { - return (schema as z.ZodObject).partial !== undefined; +function isZodObject(schema: z4.$ZodType): schema is ZodObjectV4 { + return (schema as ZodObjectV4).partial !== undefined; } /** @@ -19,12 +20,12 @@ function isZodObject(schema: z.ZodObject | z.ZodTypeAny): schema is z.ZodOb export class APIComposer< const Resource extends APIResource = any, Shape extends ZodRawShape = any, - IdentityShape extends z.ZodTypeAny = any, + IdentityShape extends z4.$ZodType<{ slug?: string; id?: string; email?: string }> = any, IncludeShape extends ZodRawShape = any, - CreateShape extends ZodTypeAny = any, - CreateOptions extends ZodTypeAny = any, - UpdateShape extends ZodTypeAny = any, - UpdateOptions extends ZodTypeAny = any, + CreateSchema extends z4.$ZodType = any, + CreateOptions extends z4.$ZodType = any, + UpdateSchema extends z4.$ZodObject = any, + UpdateOptions extends z4.$ZodObject = any, > { constructor( protected resource: Resource, @@ -32,9 +33,9 @@ export class APIComposer< schema: z.ZodObject; identitySchema: IdentityShape; include: z.ZodObject; - createSchema?: CreateShape; + createSchema?: CreateSchema; createOptionsSchema?: CreateOptions; - updateSchema?: UpdateShape; + updateSchema?: UpdateSchema; updateOptionsSchema?: UpdateOptions; }, protected httpClientFactory: HTTPClientFactory, @@ -73,7 +74,7 @@ export class APIComposer< * Read function that accepts Identify fields like id, slug or email. Will return an instance * of ReadFetcher class. */ - public read(options: z.infer) { + public read(options: z4.infer) { return new ReadFetcher( this.resource, { @@ -82,20 +83,20 @@ export class APIComposer< include: this.config.include, }, { - identity: this.config.identitySchema.parse(options), + identity: z4.parse(this.config.identitySchema, options), }, this.httpClientFactory.create(), ); } - public async add(data: z.input, options?: z.infer) { + public async add(data: z4.input, options?: z4.infer) { if (!this.config.createSchema) { throw new Error("No createSchema defined"); } - const parsedData = this.config.createSchema.parse(data); + const parsedData = z.parse(this.config.createSchema, data); const parsedOptions = this.config.createOptionsSchema && options - ? this.config.createOptionsSchema.parse(options) + ? z4.parse(this.config.createOptionsSchema, options) : undefined; const fetcher = new MutationFetcher( this.resource, @@ -103,8 +104,8 @@ export class APIComposer< output: this.config.schema, paramsShape: this.config.createOptionsSchema, }, - parsedOptions, - { method: "POST", body: parsedData }, + parsedOptions as ({ id?: string } & z4.output) | undefined, + { method: "POST", body: parsedData as any }, this.httpClientFactory.create(), ); return fetcher.submit(); @@ -112,10 +113,10 @@ export class APIComposer< public async edit( id: string, - data: IsAny extends true ? Partial> : z.input, - options?: z.infer, + data: IsAny extends true ? Partial> : z4.input, + options?: z4.infer, ) { - let updateSchema: z.ZodTypeAny | z.ZodObject | undefined = this.config.updateSchema; + let updateSchema: z4.$ZodObject | undefined = this.config.updateSchema; if (!this.config.updateSchema && this.config.createSchema && isZodObject(this.config.createSchema)) { updateSchema = this.config.createSchema.partial(); } @@ -123,9 +124,9 @@ export class APIComposer< throw new Error("No updateSchema defined"); } const cleanId = z.string().nonempty().parse(id); - const parsedData = updateSchema.parse(data); + const parsedData = z.parse(updateSchema, data); const parsedOptions = - this.config.updateOptionsSchema && options ? this.config.updateOptionsSchema.parse(options) : {}; + this.config.updateOptionsSchema && options ? z.parse(this.config.updateOptionsSchema, options) : {}; if (Object.keys(parsedData).length === 0) { throw new Error("No data to edit"); @@ -136,7 +137,7 @@ export class APIComposer< output: this.config.schema, paramsShape: this.config.updateOptionsSchema, }, - { id: cleanId, ...parsedOptions }, + { id: cleanId, ...parsedOptions } as ({ id?: string } & z4.output) | undefined, { method: "PUT", body: parsedData }, this.httpClientFactory.create(), ); diff --git a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test-d.ts b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test-d.ts new file mode 100644 index 00000000..1f896a1c --- /dev/null +++ b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test-d.ts @@ -0,0 +1,161 @@ +import createFetchMock from "vitest-fetch-mock"; +import { describe, expectTypeOf, test } from "vitest"; +import { z } from "zod"; + +import { HTTPClient, HTTPClientOptions } from "../helpers/http-client"; +import { BrowseFetcher } from "./browse-fetcher"; + +const fetchMocker = createFetchMock(vi); + +const fixture = JSON.stringify({ + posts: [ + { + title: "title", + slug: "this-is-a-slug-test", + includeMe: 1, + html: "html", + }, + ], + meta: { + pagination: { + pages: 1, + page: 1, + limit: 15, + total: 1, + prev: null, + next: null, + }, + }, +}); + +describe("BrowseFetcher", () => { + const credentials: HTTPClientOptions = { + url: "https://ghost.org", + key: "1234", + version: "v6.0", + endpoint: "content", + }; + let httpClient: HTTPClient; + + const simplifiedSchema = z.object({ + title: z.string(), + slug: z.string(), + published: z.boolean().optional(), + includeMe: z.number().optional(), + html: z.string().optional(), + }); + + const simplifiedIncludeSchema = z.object({ + includeMe: z.literal(true).optional(), + }); + + beforeEach(() => { + httpClient = new HTTPClient(credentials); + fetchMocker.enableMocks(); + }); + afterEach(() => { + fetchMocker.resetMocks(); + vi.restoreAllMocks(); + }); + + test("that BrowseFetcher returns the correct types", async () => { + const browseFetcher = new BrowseFetcher( + "posts", + { + schema: simplifiedSchema, + output: simplifiedSchema, + include: simplifiedIncludeSchema, + }, + { + browseParams: {}, + formats: ["html"], + }, + httpClient, + ); + fetchMocker.doMock(fixture); + expectTypeOf(await browseFetcher.fetch()).toEqualTypeOf< + | { + success: true; + meta: { + pagination: { + pages: number; + page: number; + limit: number | "all"; + total: number; + prev: number | null; + next: number | null; + }; + }; + data: { + title: string; + slug: string; + published?: boolean | undefined; + includeMe?: number | undefined; + html?: string | undefined; + }[]; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + expectTypeOf(await browseFetcher.fields({ title: true, html: true }).fetch()).toEqualTypeOf< + | { + success: true; + meta: { + pagination: { + pages: number; + page: number; + limit: number | "all"; + total: number; + prev: number | null; + next: number | null; + }; + }; + data: { + title: string; + html?: string | undefined; + }[]; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + + expectTypeOf( + await browseFetcher.fields({ title: true, html: true }).formats({ html: true }).fetch(), + ).toEqualTypeOf< + | { + success: true; + meta: { + pagination: { + pages: number; + page: number; + limit: number | "all"; + total: number; + prev: number | null; + next: number | null; + }; + }; + data: { + title: string; + html: string; + }[]; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + }); +}); diff --git a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts index 983893f7..3b481449 100644 --- a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.test.ts @@ -247,7 +247,7 @@ describe("BrowseFetcher", () => { ); const result = await browseFetcher.fetch(); expect(fetchMocker).toHaveBeenCalledWith( - "https://ghost.org/ghost/api/content/posts/?order=title+DESC&limit=10&fields=title%2Cslug%2Ccount&include=count&key=1234", + "https://ghost.org/ghost/api/content/posts/?order=title+DESC&limit=10&fields=title%2Cslug&include=count&key=1234", { headers: { "Content-Type": "application/json", @@ -436,7 +436,7 @@ describe("BrowseFetcher", () => { fetchMocker.doMockOnce(postsStub); await browseFetcher.fetch(); expect(fetchMocker).toHaveBeenCalledWith( - "https://ghost.org/ghost/api/admin/posts/?order=title+DESC&limit=10&fields=title%2Cslug%2Ccount&include=count", + "https://ghost.org/ghost/api/admin/posts/?order=title+DESC&limit=10&fields=title%2Cslug&include=count", { headers: { "Content-Type": "application/json", @@ -756,7 +756,7 @@ describe("BrowseFetcher output tests suite", () => { ); const res = fetcher .formats({ html: true }) - .include({ count: true, "nested.key": true }) + .include({ "nested.key": true, count: true }) .fields({ html: true, published: true, count: true }); expect(res.getIncludes()).toStrictEqual(["count", "nested.key"]); expect(res.getOutputFields()).toStrictEqual(["html", "published", "count"]); @@ -765,7 +765,7 @@ describe("BrowseFetcher output tests suite", () => { fetchMocker.doMockOnce(fixture); await res.fetch(); expect(fetchMocker).toHaveBeenCalledWith( - "https://ghost.org/ghost/api/content/posts/?fields=html%2Cpublished%2Ccount&include=count%2Cnested.key&formats=html&key=1234", + "https://ghost.org/ghost/api/content/posts/?fields=html%2Cpublished&include=count%2Cnested.key&formats=html&key=1234", { headers: { "Content-Type": "application/json", @@ -785,26 +785,28 @@ describe("BrowseFetcher output tests suite", () => { {}, httpClient, ); - const res = fetcher - // @ts-expect-error - foobar is not defined - .formats({ html: true, foobar: true }) - // @ts-expect-error - foo is not in the include schema - .include({ count: true, foo: true }) - // @ts-expect-error - barbaz is not in the output schema schema - .fields({ html: true, published: true, count: true, barbaz: true }); - expect(res.getIncludes()).toStrictEqual(["count"]); - expect(res.getOutputFields()).toStrictEqual(["html", "published", "count"]); - expect(res.getFormats()).toStrictEqual(["html"]); - fetchMocker.doMockOnce(fixture); - await res.fetch(); - expect(fetchMocker).toHaveBeenCalledWith( - "https://ghost.org/ghost/api/content/posts/?fields=html%2Cpublished%2Ccount&include=count&formats=html&key=1234", - { - headers: { - "Content-Type": "application/json", - "Accept-Version": "v6.0", - }, - }, - ); + expect(() => + fetcher + // @ts-expect-error - foobar is not defined + .formats({ html: true, foobar: true }) + // @ts-expect-error - foo is not in the include schema + .include({ count: true, foo: true }) + // @ts-expect-error - barbaz is not in the output schema schema + .fields({ html: true, published: true, count: true, barbaz: true }), + ).toThrow(); + // expect(res.getIncludes()).toStrictEqual(["count"]); + // expect(res.getOutputFields()).toStrictEqual(["html", "published", "count"]); + // expect(res.getFormats()).toStrictEqual(["html"]); + // fetchMocker.doMockOnce(fixture); + // await res.fetch(); + // expect(fetchMocker).toHaveBeenCalledWith( + // "https://ghost.org/ghost/api/content/posts/?fields=html%2Cpublished&include=count&formats=html&key=1234", + // { + // headers: { + // "Content-Type": "application/json", + // "Accept-Version": "v6.0", + // }, + // }, + // ); }); }); diff --git a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts index e52829cf..bfe44b6c 100644 --- a/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts @@ -3,7 +3,7 @@ import { z, ZodRawShape } from "zod"; import { BrowseParamsSchema } from "../helpers/browse-params"; import type { HTTPClient } from "../helpers/http-client"; import { ghostMetaSchema, type APIResource } from "../schemas/shared"; -import type { Exactly, Mask } from "../utils"; +import type { Exactly, Mask, NoUnrecognizedKeys } from "../utils"; import { contentFormats, type ContentFormats } from "./formats"; export class BrowseFetcher< @@ -30,7 +30,7 @@ export class BrowseFetcher< include?: (keyof IncludeShape)[]; fields?: Fields; formats?: string[]; - } = { browseParams: {} as Params, include: [], fields: {} as z.noUnrecognized }, + } = { browseParams: {} as Params, include: [], fields: {} as NoUnrecognizedKeys }, protected httpClient: HTTPClient, ) { this._buildUrlParams(); @@ -45,7 +45,7 @@ export class BrowseFetcher< * @returns A new Fetcher with the fixed output shape and the formats specified */ public formats>>( - formats: z.noUnrecognized, + formats: NoUnrecognizedKeys, ) { const params = { ...this._params, @@ -71,16 +71,23 @@ export class BrowseFetcher< * @param include Include specific keys from the include shape * @returns A new Fetcher with the fixed output shape and the formats specified */ - public include>(include: z.noUnrecognized) { + public include>(include: NoUnrecognizedKeys) { const params = { ...this._params, include: Object.keys(this.config.include.parse(include)), }; + // remove dot-notation from the include object key + const requiredIncludeKeys = Object.fromEntries( + Object.keys(include) + .filter((key) => !key.includes(".")) + .map((key) => [key, include[key]]), + ); + return new BrowseFetcher( this.resource, { schema: this.config.schema, - output: this.config.output.required(include as Exactly), + output: this.config.output.required(requiredIncludeKeys as Exactly), include: this.config.include, }, params, @@ -95,7 +102,7 @@ export class BrowseFetcher< * @param fields Any keys from the resource Schema * @returns A new Fetcher with the fixed output shape having only the selected Fields */ - public fields>(fields: z.noUnrecognized) { + public fields>(fields: NoUnrecognizedKeys) { const newOutput = this.config.output.pick(fields as Exactly); return new BrowseFetcher( this.resource, @@ -141,7 +148,7 @@ export class BrowseFetcher< }; if (inputKeys.length !== outputKeys.length && outputKeys.length > 0) { - this._urlParams.fields = outputKeys.join(","); + this._urlParams.fields = outputKeys.filter((key) => key !== "count").join(","); } if (this._params.include && this._params.include.length > 0) { this._urlParams.include = this._params.include.join(","); @@ -190,13 +197,19 @@ export class BrowseFetcher< ]); } - public async fetch(options?: RequestInit) { + public async fetch(options?: RequestInit & { debug?: boolean }) { const resultSchema = this._getResultSchema(); + if (options?.debug) { + console.log("_urlSearchParams", this._urlSearchParams); + } const result = await this.httpClient.fetch({ resource: this.resource, searchParams: this._urlSearchParams, options, }); + if (options?.debug) { + console.log("result", result); + } let data: any = {}; if (result.errors) { data.success = false; diff --git a/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts index 811c0b22..169f2a24 100644 --- a/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/delete-fetcher.ts @@ -9,7 +9,7 @@ export class DeleteFetcher { constructor( protected resource: Resource, private _params: { id: string }, - protected httpClient: HTTPClient, + protected httpClient: HTTPClient ) { this._buildPathnameIdentity(); } @@ -41,7 +41,7 @@ export class DeleteFetcher { type: z.string(), message: z.string(), context: z.string().nullish(), - }), + }) ), }), ]); diff --git a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts index 2a9118fb..bbeef7d3 100644 --- a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts @@ -1,12 +1,13 @@ -import { z, ZodRawShape, ZodTypeAny } from "zod"; +import { z } from "zod"; +import * as z4 from "zod/v4/core"; import { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; export class MutationFetcher< const Resource extends APIResource = any, - OutputShape extends ZodRawShape = any, - ParamsShape extends ZodTypeAny = any, + OutputShape extends z4.$ZodType = any, + ParamsShape extends z4.$ZodType = any, const HTTPVerb extends "POST" | "PUT" = "POST", > { protected _urlParams: Record = {}; @@ -16,10 +17,10 @@ export class MutationFetcher< constructor( protected resource: Resource, protected config: { - output: z.ZodObject; + output: OutputShape; paramsShape?: ParamsShape; }, - private _params: ({ id?: string } & ParamsShape["_output"]) | undefined, + private _params: ({ id?: string } & z4.output) | undefined, protected _options: { method: HTTPVerb; body: Record; diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test-d.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test-d.ts new file mode 100644 index 00000000..fe17c71e --- /dev/null +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test-d.ts @@ -0,0 +1,121 @@ +import createFetchMock from "vitest-fetch-mock"; +import { describe, expectTypeOf, test } from "vitest"; +import { z } from "zod"; + +import { HTTPClient, HTTPClientOptions } from "../helpers/http-client"; +import { ReadFetcher } from "./read-fetcher"; + +const fetchMocker = createFetchMock(vi); + +const fixture = JSON.stringify({ + posts: [ + { + title: "title", + slug: "this-is-a-slug-test", + includeMe: 1, + html: "html", + }, + ], +}); + +describe("ReadFetcher", () => { + const credentials: HTTPClientOptions = { + url: "https://ghost.org", + key: "1234", + version: "v6.0", + endpoint: "content", + }; + let httpClient: HTTPClient; + + const simplifiedSchema = z.object({ + title: z.string(), + slug: z.string(), + published: z.boolean().optional(), + includeMe: z.number().optional(), + html: z.string().optional(), + }); + + const simplifiedIncludeSchema = z.object({ + includeMe: z.literal(true).optional(), + }); + + beforeEach(() => { + httpClient = new HTTPClient(credentials); + fetchMocker.enableMocks(); + }); + afterEach(() => { + fetchMocker.resetMocks(); + vi.restoreAllMocks(); + }); + + test("that ReadFetcher returns the correct types", async () => { + const readFetcher = new ReadFetcher( + "posts", + { + schema: simplifiedSchema, + output: simplifiedSchema, + include: simplifiedIncludeSchema, + }, + { + identity: { id: "eh873jdLsnaUDj7149DSASJhdqsdj" }, + formats: ["html"], + }, + httpClient, + ); + fetchMocker.doMock(fixture); + expectTypeOf(await readFetcher.fetch()).toEqualTypeOf< + | { + success: true; + data: { + title: string; + slug: string; + published?: boolean | undefined; + includeMe?: number | undefined; + html?: string | undefined; + }; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + expectTypeOf(await readFetcher.fields({ title: true, html: true }).fetch()).toEqualTypeOf< + | { + success: true; + data: { + title: string; + html?: string | undefined; + }; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + + expectTypeOf( + await readFetcher.fields({ title: true, html: true }).formats({ html: true }).fetch(), + ).toEqualTypeOf< + | { + success: true; + data: { + title: string; + html: string; + }; + } + | { + success: false; + errors: { + type: string; + message: string; + }[]; + } + >(); + }); +}); diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts index 21bf7f9a..a3ced0a7 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.test.ts @@ -491,27 +491,29 @@ describe("ReadFetcherFetcher outputs test suite", () => { { identity: { slug: "this-is-a-slug" } }, httpClient, ); - const res = fetcher - // @ts-expect-error - foobar is not defined - .formats({ html: true, foobar: true }) - // @ts-expect-error - foo is not in the include schema - .include({ count: true, foo: true }) - // @ts-expect-error - barbaz is not in the output schema schema - .fields({ html: true, published: true, count: true, barbaz: true }); - expect(res.getIncludes()).toStrictEqual(["count"]); - expect(res.getOutputFields()).toStrictEqual(["html", "published", "count"]); - expect(res.getFormats()).toStrictEqual(["html"]); - fetchMocker.doMockOnce(fixture); - await res.fetch(); - expect(fetchMocker).toHaveBeenCalledTimes(1); - expect(fetchMocker).toHaveBeenCalledWith( - "https://ghost.org/ghost/api/content/posts/slug/this-is-a-slug/?fields=html%2Cpublished%2Ccount&include=count&formats=html&key=1234", - { - headers: { - "Content-Type": "application/json", - "Accept-Version": "v6.0", - }, - }, - ); + expect(() => + fetcher + // @ts-expect-error - foobar is not defined + .formats({ html: true, foobar: true }) + // @ts-expect-error - foo is not in the include schema + .include({ count: true, foo: true }) + // @ts-expect-error - barbaz is not in the output schema schema + .fields({ html: true, published: true, count: true, barbaz: true }), + ).toThrow(); + // expect(res.getIncludes()).toStrictEqual(["count"]); + // expect(res.getOutputFields()).toStrictEqual(["html", "published", "count"]); + // expect(res.getFormats()).toStrictEqual(["html"]); + // fetchMocker.doMockOnce(fixture); + // await res.fetch(); + // expect(fetchMocker).toHaveBeenCalledTimes(1); + // expect(fetchMocker).toHaveBeenCalledWith( + // "https://ghost.org/ghost/api/content/posts/slug/this-is-a-slug/?fields=html%2Cpublished%2Ccount&include=count&formats=html&key=1234", + // { + // headers: { + // "Content-Type": "application/json", + // "Accept-Version": "v6.0", + // }, + // }, + // ); }); }); diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts index 36e7636e..7c53d844 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts @@ -1,8 +1,8 @@ -import { z, ZodRawShape } from "zod"; +import { z, ZodRawShape } from "zod/v4"; import type { HTTPClient } from "../helpers/http-client"; import { type APIResource, type GhostIdentityInput } from "../schemas/shared"; -import type { Exactly, Mask } from "../utils"; +import type { Exactly, Mask, NoUnrecognizedKeys } from "../utils"; import { contentFormats, type ContentFormats } from "./formats"; export class ReadFetcher< @@ -44,8 +44,9 @@ export class ReadFetcher< * @returns A new Fetcher with the fixed output shape and the formats specified */ public formats>>( - formats: z.noUnrecognized, + formats: NoUnrecognizedKeys, ) { + const newOutput = this.config.output.required(formats as Exactly); const params = { ...this._params, formats: Object.keys(formats).filter((key) => contentFormats.includes(key)), @@ -54,7 +55,7 @@ export class ReadFetcher< this.resource, { schema: this.config.schema, - output: this.config.output.required(formats as Exactly), + output: newOutput, include: this.config.include, }, params, @@ -70,16 +71,22 @@ export class ReadFetcher< * @param include Include specific keys from the include shape * @returns A new Fetcher with the fixed output shape and the formats specified */ - public include>(include: z.noUnrecognized) { + public include>(include: NoUnrecognizedKeys) { const params = { ...this._params, include: Object.keys(this.config.include.parse(include)), }; + // remove dot-notation from the include object key + const requiredIncludeKeys = Object.fromEntries( + Object.keys(include) + .filter((key) => !key.includes(".")) + .map((key) => [key, include[key]]), + ); return new ReadFetcher( this.resource, { schema: this.config.schema, - output: this.config.output.required(include as Exactly), + output: this.config.output.required(requiredIncludeKeys as Exactly), include: this.config.include, }, params, @@ -94,7 +101,7 @@ export class ReadFetcher< * @param fields Any keys from the resource Schema * @returns A new Fetcher with the fixed output shape having only the selected Fields */ - public fields>(fields: z.noUnrecognized) { + public fields>(fields: NoUnrecognizedKeys) { const newOutput = this.config.output.pick(fields as Exactly); return new ReadFetcher( this.resource, diff --git a/packages/ts-ghost-core-api/src/helpers/fields.ts b/packages/ts-ghost-core-api/src/helpers/fields.ts index d0d6b0e7..2661f6ff 100644 --- a/packages/ts-ghost-core-api/src/helpers/fields.ts +++ b/packages/ts-ghost-core-api/src/helpers/fields.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import type { Exactly, Mask } from "../utils"; +import type { Exactly, Mask, NoUnrecognizedKeys } from "../utils"; /** * Parse a Fields object and generate a new Output Schema @@ -13,5 +13,5 @@ export const schemaWithPickedFields = , fields?: Fields, ) => { - return schema.pick((fields as Exactly) || ({} as z.noUnrecognized)); + return schema.pick((fields as Exactly) || ({} as NoUnrecognizedKeys)); }; diff --git a/packages/ts-ghost-core-api/src/helpers/http-client.ts b/packages/ts-ghost-core-api/src/helpers/http-client.ts index 60292436..3a9669f7 100644 --- a/packages/ts-ghost-core-api/src/helpers/http-client.ts +++ b/packages/ts-ghost-core-api/src/helpers/http-client.ts @@ -107,7 +107,7 @@ export class HTTPClient implement }: { resource: APIResource; searchParams?: URLSearchParams; - options?: RequestInit; + options?: RequestInit & { debug?: boolean }; pathnameIdentity?: string; }) { if (this._baseURL === undefined) throw new Error("URL is undefined"); @@ -126,6 +126,9 @@ export class HTTPClient implement } let result = undefined; const headers = await this.genHeaders(); + if (options?.debug) { + console.log("url", url.toString(), "headers", headers, "options", options); + } try { result = await ( await fetch(url.toString(), { diff --git a/packages/ts-ghost-core-api/src/schemas/members.ts b/packages/ts-ghost-core-api/src/schemas/members.ts index abcbefbc..d6a224d0 100644 --- a/packages/ts-ghost-core-api/src/schemas/members.ts +++ b/packages/ts-ghost-core-api/src/schemas/members.ts @@ -5,34 +5,40 @@ import { baseSubscriptionsSchema } from "./subscriptions"; export const baseMembersSchema = z.object({ id: z.string(), - email: z.string({ description: "The email address of the member" }), - name: z.string({ description: "The name of the member" }).nullable(), - note: z.string({ description: "(nullable) A note about the member" }).nullish(), - geolocation: z.string({ description: "(nullable) The geolocation of the member" }).nullish(), - created_at: z.string({ description: "The date and time the member was created" }), + email: z.string().meta({ description: "The email address of the member" }), + name: z.string().meta({ description: "The name of the member" }).nullable(), + note: z.string().meta({ description: "(nullable) A note about the member" }).nullish(), + geolocation: z.string().meta({ description: "(nullable) The geolocation of the member" }).nullish(), + created_at: z.string().meta({ description: "The date and time the member was created" }), updated_at: z - .string({ description: "(nullable) The date and time the member was last updated" }) + .string() + .meta({ description: "(nullable) The date and time the member was last updated" }) .nullish(), labels: z.array( - z.object({ - id: z.string({ description: "The ID of the label" }), - name: z.string({ description: "The name of the label" }), - slug: z.string({ description: "The slug of the label" }), - created_at: z.string({ description: "The date and time the label was created" }), - updated_at: z - .string({ description: "(nullable) The date and time the label was last updated" }) - .nullish(), - }), - { description: "The labels associated with the member" }, + z + .object({ + id: z.string().meta({ description: "The ID of the label" }), + name: z.string().meta({ description: "The name of the label" }), + slug: z.string().meta({ description: "The slug of the label" }), + created_at: z.string().meta({ description: "The date and time the label was created" }), + updated_at: z + .string() + .meta({ description: "(nullable) The date and time the label was last updated" }) + .nullish(), + }) + .meta({ description: "The labels associated with the member" }), ), - subscriptions: z.array(baseSubscriptionsSchema, { - description: "The subscriptions associated with the member", - }), - avatar_image: z.string({ description: "The URL of the member's avatar image" }), - email_count: z.number({ description: "The number of emails sent to the member" }), - email_opened_count: z.number({ description: "The number of emails opened by the member" }), - email_open_rate: z.number({ description: "(nullable) The open rate of the member" }).nullish(), - status: z.string({ description: "The status of the member" }), - last_seen_at: z.string({ description: "(nullable) The date and time the member was last seen" }).nullish(), + subscriptions: z + .array(baseSubscriptionsSchema) + .meta({ description: "The subscriptions associated with the member" }), + avatar_image: z.string().meta({ description: "The URL of the member's avatar image" }), + email_count: z.number().meta({ description: "The number of emails sent to the member" }), + email_opened_count: z.number().meta({ description: "The number of emails opened by the member" }), + email_open_rate: z.number().meta({ description: "(nullable) The open rate of the member" }).nullish(), + status: z.string().meta({ description: "The status of the member" }), + last_seen_at: z + .string() + .meta({ description: "(nullable) The date and time the member was last seen" }) + .nullish(), newsletters: z.array(baseNewsletterSchema), }); diff --git a/packages/ts-ghost-core-api/src/schemas/newsletter.ts b/packages/ts-ghost-core-api/src/schemas/newsletter.ts index db250fc7..dc2d4412 100644 --- a/packages/ts-ghost-core-api/src/schemas/newsletter.ts +++ b/packages/ts-ghost-core-api/src/schemas/newsletter.ts @@ -4,51 +4,56 @@ import { ghostIdentitySchema } from "./shared"; export const baseNewsletterSchema = z.object({ ...ghostIdentitySchema.shape, - name: z.string({ description: "Public name for the newsletter" }), - description: z.string({ description: "(nullable) Public description of the newsletter" }).nullish(), - sender_name: z.string({ description: "(nullable) The sender name of the emails" }).nullish(), + name: z.string().meta({ description: "Public name for the newsletter" }), + description: z.string().meta({ description: "(nullable) Public description of the newsletter" }).nullish(), + sender_name: z.string().meta({ description: "(nullable) The sender name of the emails" }).nullish(), sender_email: z - .string({ description: "(nullable) The email from which to send emails. Requires validation." }) + .string() + .meta({ description: "(nullable) The email from which to send emails. Requires validation." }) .nullish(), - sender_reply_to: z.string({ + sender_reply_to: z.string().meta({ description: "The reply-to email address for sent emails. Can be either newsletter (= use sender_email) or support (use support email from Portal settings).", }), - status: z.union([z.literal("active"), z.literal("archived")], { + status: z.union([z.literal("active"), z.literal("archived")]).meta({ description: "active or archived - denotes if the newsletter is active or archived", }), visibility: z.union([z.literal("public"), z.literal("members")]), - subscribe_on_signup: z.boolean({ + subscribe_on_signup: z.boolean().meta({ description: "true/false. Whether members should automatically subscribe to this newsletter on signup", }), - sort_order: z.number({ description: "The order in which newsletters are displayed in the Portal" }), + sort_order: z.number().meta({ description: "The order in which newsletters are displayed in the Portal" }), header_image: z - .string({ + .string() + .meta({ description: "(nullable) Path to an image to show at the top of emails. Recommended size 1200x600", }) .nullish(), - show_header_icon: z.boolean({ description: "true/false. Show the site icon in emails" }), - show_header_title: z.boolean({ description: "true/false. Show the site name in emails" }), - title_font_category: z.union([z.literal("serif"), z.literal("sans_serif")], { + show_header_icon: z.boolean().meta({ description: "true/false. Show the site icon in emails" }), + show_header_title: z.boolean().meta({ description: "true/false. Show the site name in emails" }), + title_font_category: z.union([z.literal("serif"), z.literal("sans_serif")]).meta({ description: "Title font style. Either serif or sans_serif", }), title_alignment: z.string().nullish(), - show_feature_image: z.boolean({ description: "true/false. Show the post's feature image in emails" }), - body_font_category: z.union([z.literal("serif"), z.literal("sans_serif")], { + show_feature_image: z + .boolean() + .meta({ description: "true/false. Show the post's feature image in emails" }), + body_font_category: z.union([z.literal("serif"), z.literal("sans_serif")]).meta({ description: "Body font style. Either serif or sans_serif", }), footer_content: z - .string({ + .string() + .meta({ description: "(nullable) Extra information or legal text to show in the footer of emails. Should contain valid HTML.", }) .nullish(), - show_badge: z.boolean({ + show_badge: z.boolean().meta({ description: "true/false. Show you’re a part of the indie publishing movement by adding a small Ghost badge in the footer", }), created_at: z.string(), updated_at: z.string().nullish(), - show_header_name: z.boolean({ description: "true/false. Show the newsletter name in emails" }), + show_header_name: z.boolean().meta({ description: "true/false. Show the newsletter name in emails" }), uuid: z.string(), }); diff --git a/packages/ts-ghost-core-api/src/schemas/offers.ts b/packages/ts-ghost-core-api/src/schemas/offers.ts index c7464cbb..d6110d6e 100644 --- a/packages/ts-ghost-core-api/src/schemas/offers.ts +++ b/packages/ts-ghost-core-api/src/schemas/offers.ts @@ -2,43 +2,52 @@ import { z } from "zod"; export const baseOffersSchema = z.object({ id: z.string(), - name: z.string({ description: "Internal name for an offer, must be unique" }).default(""), - code: z.string({ description: "Shortcode for the offer, for example: https://yoursite.com/black-friday" }), - display_title: z.string({ description: "Name displayed in the offer window" }).nullish(), - display_description: z.string({ description: "Text displayed in the offer window" }).nullish(), + name: z.string().meta({ description: "Internal name for an offer, must be unique" }).default(""), + code: z + .string() + .meta({ description: "Shortcode for the offer, for example: https://yoursite.com/black-friday" }), + display_title: z.string().meta({ description: "Name displayed in the offer window" }).nullish(), + display_description: z.string().meta({ description: "Text displayed in the offer window" }).nullish(), type: z.union([z.literal("percent"), z.literal("fixed"), z.literal("trial")]), cadence: z.union([z.literal("month"), z.literal("year")]), - amount: z.number({ + amount: z.number().meta({ description: `Offer discount amount, as a percentage or fixed value as set in type. Amount is always denoted by the smallest currency unit (e.g., 100 cents instead of $1.00 in USD)`, }), - duration: z.union([z.literal("once"), z.literal("forever"), z.literal("repeating"), z.literal("trial")], { - description: "once/forever/repeating. repeating duration is only available when cadence is month", - }), + duration: z + .union([z.literal("once"), z.literal("forever"), z.literal("repeating"), z.literal("trial")]) + .meta({ + description: "once/forever/repeating. repeating duration is only available when cadence is month", + }), duration_in_months: z - .number({ description: "Number of months offer should be repeated when duration is repeating" }) + .number() + .meta({ description: "Number of months offer should be repeated when duration is repeating" }) .nullish(), currency_restriction: z - .boolean({ + .boolean() + .meta({ description: "Denotes whether the offer `currency` is restricted. If so, changing the currency invalidates the offer", }) .nullish(), currency: z - .string({ + .string() + .meta({ description: "fixed type offers only - specifies tier's currency as three letter ISO currency code", }) .nullish(), - status: z.union([z.literal("active"), z.literal("archived")], { + status: z.union([z.literal("active"), z.literal("archived")]).meta({ description: "active or archived - denotes if the offer is active or archived", }), - redemption_count: z.number({ description: "Number of times the offer has been redeemed" }).nullish(), - tier: z.object( - { + redemption_count: z + .number() + .meta({ description: "Number of times the offer has been redeemed" }) + .nullish(), + tier: z + .object({ id: z.string(), name: z.string().nullish(), - }, - { description: "Tier on which offer is applied" }, - ), + }) + .meta({ description: "Tier on which offer is applied" }), }); diff --git a/packages/ts-ghost-core-api/src/schemas/settings.ts b/packages/ts-ghost-core-api/src/schemas/settings.ts index 962815c7..5000d37b 100644 --- a/packages/ts-ghost-core-api/src/schemas/settings.ts +++ b/packages/ts-ghost-core-api/src/schemas/settings.ts @@ -17,13 +17,13 @@ export const baseSettingsSchema = z.object({ z.object({ label: z.string(), url: z.string(), - }), + }) ), secondary_navigation: z.array( z.object({ label: z.string(), url: z.string(), - }), + }) ), meta_title: z.string().nullable(), meta_description: z.string().nullable(), diff --git a/packages/ts-ghost-core-api/src/schemas/shared.ts b/packages/ts-ghost-core-api/src/schemas/shared.ts index 1e47b4d2..8c70aba2 100644 --- a/packages/ts-ghost-core-api/src/schemas/shared.ts +++ b/packages/ts-ghost-core-api/src/schemas/shared.ts @@ -10,10 +10,10 @@ export const ghostIdentitySchema = z.object({ export const ghostIdentityInputSchema = z.object({ slug: z.string().optional(), id: z.string().optional(), - email: z.string().email().optional(), + email: z.email().optional(), }); -export type GhostIdentityInput = z.infer; +export type GhostIdentityInput = z.output; export type GhostIdentity = z.infer; diff --git a/packages/ts-ghost-core-api/src/schemas/subscriptions.ts b/packages/ts-ghost-core-api/src/schemas/subscriptions.ts index 5277e57e..28a0a8e0 100644 --- a/packages/ts-ghost-core-api/src/schemas/subscriptions.ts +++ b/packages/ts-ghost-core-api/src/schemas/subscriptions.ts @@ -4,31 +4,30 @@ import { baseOffersSchema } from "./offers"; import { baseTiersSchema } from "./tiers"; export const baseSubscriptionsSchema = z.object({ - id: z.string({ description: "Stripe subscription ID sub_XXXX" }), - customer: z.object( - { + id: z.string().meta({ description: "Stripe subscription ID sub_XXXX" }), + customer: z + .object({ id: z.string(), name: z.string().nullable(), email: z.string(), - }, - { description: "Stripe customer attached to the subscription" }, - ), - status: z.string({ description: "Subscription status" }), - start_date: z.string({ description: "Subscription start date" }), - default_payment_card_last4: z.string({ description: "Last 4 digits of the card" }).nullable(), - cancel_at_period_end: z.boolean({ + }) + .meta({ description: "Stripe customer attached to the subscription" }), + status: z.string().meta({ description: "Subscription status" }), + start_date: z.string().meta({ description: "Subscription start date" }), + default_payment_card_last4: z.string().meta({ description: "Last 4 digits of the card" }).nullable(), + cancel_at_period_end: z.boolean().meta({ description: "If the subscription should be canceled or renewed at period end", }), - cancellation_reason: z.string({ description: "Reason for subscription cancellation" }).nullable(), - current_period_end: z.string({ description: "Subscription end date" }), + cancellation_reason: z.string().meta({ description: "Reason for subscription cancellation" }).nullable(), + current_period_end: z.string().meta({ description: "Subscription end date" }), price: z.object({ - id: z.string({ description: "Stripe price ID" }), - price_id: z.string({ description: "Ghost price ID" }), - nickname: z.string({ description: "Price nickname" }), - amount: z.number({ description: "Price amount" }), - interval: z.string({ description: "Price interval" }), - type: z.string({ description: "Price type" }), - currency: z.string({ description: "Price currency" }), + id: z.string().meta({ description: "Stripe price ID" }), + price_id: z.string().meta({ description: "Ghost price ID" }), + nickname: z.string().meta({ description: "Price nickname" }), + amount: z.number().meta({ description: "Price amount" }), + interval: z.string().meta({ description: "Price interval" }), + type: z.string().meta({ description: "Price type" }), + currency: z.string().meta({ description: "Price currency" }), }), tier: baseTiersSchema.nullish(), offer: baseOffersSchema.nullish(), diff --git a/packages/ts-ghost-core-api/src/utils.d.ts b/packages/ts-ghost-core-api/src/utils.d.ts index 7564aa10..1d9e8c8f 100644 --- a/packages/ts-ghost-core-api/src/utils.d.ts +++ b/packages/ts-ghost-core-api/src/utils.d.ts @@ -11,3 +11,7 @@ export declare type InferFetcherDataShape Promise >; export type IsAny = 0 extends 1 & T ? true : false; + +export type NoUnrecognizedKeys = { + [k in keyof Obj]: k extends keyof Shape ? Obj[k] : never; +}; From 36398dafb23e827e3bfd08c4fc68dce69ebb22bd Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sat, 3 Jan 2026 18:57:09 +0100 Subject: [PATCH 03/11] Update packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts index 7c53d844..3c39c9bc 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts @@ -1,4 +1,4 @@ -import { z, ZodRawShape } from "zod/v4"; +import { z, ZodRawShape } from "zod"; import type { HTTPClient } from "../helpers/http-client"; import { type APIResource, type GhostIdentityInput } from "../schemas/shared"; From 678e773df5b6a592741f9fd544e8ec722d961275 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sat, 3 Jan 2026 21:58:12 +0400 Subject: [PATCH 04/11] remove the as any --- packages/ts-ghost-core-api/src/api-composer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index 8ef8f2b5..db0b15ae 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -105,7 +105,7 @@ export class APIComposer< paramsShape: this.config.createOptionsSchema, }, parsedOptions as ({ id?: string } & z4.output) | undefined, - { method: "POST", body: parsedData as any }, + { method: "POST", body: parsedData as Record }, this.httpClientFactory.create(), ); return fetcher.submit(); From 48b1d289b8fdc0026c4f9c72ae71ed2b2334bfed Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 04:57:51 +0100 Subject: [PATCH 05/11] Update packages/ts-ghost-core-api/src/api-composer.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/ts-ghost-core-api/src/api-composer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index db0b15ae..e2a97513 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -123,7 +123,7 @@ export class APIComposer< if (!updateSchema) { throw new Error("No updateSchema defined"); } - const cleanId = z.string().nonempty().parse(id); + const cleanId = z.string().min(1).parse(id); const parsedData = z.parse(updateSchema, data); const parsedOptions = this.config.updateOptionsSchema && options ? z.parse(this.config.updateOptionsSchema, options) : {}; From 59caa9c8b4fc0e0e5f18b63b18aed3a488849ce6 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 08:01:25 +0400 Subject: [PATCH 06/11] use z.url() instead of deprecated z.string().url() --- packages/ts-ghost-admin-api/src/schemas/webhooks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts index 6034e1fe..8053d104 100644 --- a/packages/ts-ghost-admin-api/src/schemas/webhooks.ts +++ b/packages/ts-ghost-admin-api/src/schemas/webhooks.ts @@ -63,7 +63,7 @@ export type GhostWebhookEventTypes = z.infer; export const adminWebhookSchema = z.object({ id: z.string().meta({ description: "The ID of the webhook" }), event: ghostEventTypes, - target_url: z.string().meta({ description: "The URL of the webhook" }).url(), + target_url: z.url().meta({ description: "The URL of the webhook" }), name: z.string().meta({ description: "The name of the webhook" }).nullish(), secret: z.string().meta({ description: "The secret of the webhook" }).nullish(), api_version: z.string().meta({ description: "The API version of the webhook" }).nullish(), @@ -93,7 +93,7 @@ export const adminWebhookCreateSchema = z.object({ export const adminWebhookUpdateSchema = z.object({ event: ghostEventTypes.optional(), - target_url: z.string().meta({ description: "The URL of the webhook" }).url().optional(), + target_url: z.url().meta({ description: "The URL of the webhook" }).optional(), name: z.string().meta({ description: "The name of the webhook" }).optional(), api_version: z.string().meta({ description: "The API version of the webhook" }).nullish(), }); From 730ff7c88077c8e4463dffd50e15f38ca7f0b7e0 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 08:02:05 +0400 Subject: [PATCH 07/11] fix typo "SpecifYing" --- packages/ts-ghost-admin-api/src/schemas/members.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/pages.ts | 2 +- packages/ts-ghost-admin-api/src/schemas/posts.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ts-ghost-admin-api/src/schemas/members.ts b/packages/ts-ghost-admin-api/src/schemas/members.ts index b802d831..7ff322fa 100644 --- a/packages/ts-ghost-admin-api/src/schemas/members.ts +++ b/packages/ts-ghost-admin-api/src/schemas/members.ts @@ -54,7 +54,7 @@ export const adminMembersCreateSchema = z.object({ ]), ) .meta({ - description: `Specifing newsletter to subscribe to via id or name, incompatible with the \`subscribed\` property`, + description: `Specifying newsletter to subscribe to via id or name, incompatible with the \`subscribed\` property`, }) .optional(), subscribed: z diff --git a/packages/ts-ghost-admin-api/src/schemas/pages.ts b/packages/ts-ghost-admin-api/src/schemas/pages.ts index 7a50dd4b..6273f596 100644 --- a/packages/ts-ghost-admin-api/src/schemas/pages.ts +++ b/packages/ts-ghost-admin-api/src/schemas/pages.ts @@ -129,7 +129,7 @@ export const adminPagesCreateSchema = z.object({ ]), ) .meta({ - description: `Specifing author via id, name or slug.`, + description: `Specifying author via id, name or slug.`, }) .optional(), }); diff --git a/packages/ts-ghost-admin-api/src/schemas/posts.ts b/packages/ts-ghost-admin-api/src/schemas/posts.ts index b901f8ae..a77b7de7 100644 --- a/packages/ts-ghost-admin-api/src/schemas/posts.ts +++ b/packages/ts-ghost-admin-api/src/schemas/posts.ts @@ -135,7 +135,7 @@ const basePostsCreateSchema = z.object({ ]), ) .meta({ - description: `Specifing author via id, name or slug.`, + description: `Specifying author via id, name or slug.`, }) .optional(), newsletter: z From 9033d9bcd3a818ef4d0800cccc34e5308e850dd6 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 08:03:49 +0400 Subject: [PATCH 08/11] use min(1) instead of deprecated nonempty() --- packages/ts-ghost-core-api/src/api-composer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index e2a97513..5ce92415 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -145,7 +145,7 @@ export class APIComposer< } public async delete(id: string) { - const cleanId = z.string().nonempty().parse(id); + const cleanId = z.string().min(1).parse(id); const fetcher = new DeleteFetcher(this.resource, { id: cleanId }, this.httpClientFactory.create()); return fetcher.submit(); } From 69ebfb27110d952a969a18505c3c506674094746 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 08:56:47 +0400 Subject: [PATCH 09/11] fix inconsistent use of z.parse and z4.parse --- packages/ts-ghost-core-api/src/api-composer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ts-ghost-core-api/src/api-composer.ts b/packages/ts-ghost-core-api/src/api-composer.ts index 5ce92415..e8120f79 100644 --- a/packages/ts-ghost-core-api/src/api-composer.ts +++ b/packages/ts-ghost-core-api/src/api-composer.ts @@ -83,7 +83,7 @@ export class APIComposer< include: this.config.include, }, { - identity: z4.parse(this.config.identitySchema, options), + identity: z.parse(this.config.identitySchema, options), }, this.httpClientFactory.create(), ); @@ -96,7 +96,7 @@ export class APIComposer< const parsedData = z.parse(this.config.createSchema, data); const parsedOptions = this.config.createOptionsSchema && options - ? z4.parse(this.config.createOptionsSchema, options) + ? z.parse(this.config.createOptionsSchema, options) : undefined; const fetcher = new MutationFetcher( this.resource, From 61cdb78d9ceb6d5261b2e7cd142f5c4f77ee7a68 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 09:29:50 +0400 Subject: [PATCH 10/11] add more debugging options like choosing our logger --- .../ts-ghost-core-api/src/fetchers/basic-fetcher.ts | 3 ++- .../src/fetchers/browse-fetcher.ts | 11 +++-------- .../src/fetchers/delete-fetcher.ts | 8 +++++--- .../src/fetchers/mutation-fetcher.ts | 8 +++----- .../ts-ghost-core-api/src/fetchers/read-fetcher.ts | 3 ++- packages/ts-ghost-core-api/src/helpers/debug.ts | 11 +++++++++++ .../ts-ghost-core-api/src/helpers/http-client.ts | 13 +++++++------ 7 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 packages/ts-ghost-core-api/src/helpers/debug.ts diff --git a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts index 84e5eac2..85aba001 100644 --- a/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/basic-fetcher.ts @@ -1,5 +1,6 @@ import { z, ZodTypeAny } from "zod"; +import { DebugOption } from "../helpers/debug"; import type { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; @@ -16,7 +17,7 @@ export class BasicFetcher { constructor( protected resource: Resource, private _params: { id: string }, - protected httpClient: HTTPClient + protected httpClient: HTTPClient, ) { this._buildPathnameIdentity(); } @@ -29,7 +30,7 @@ export class DeleteFetcher { this._pathnameIdentity = this._params.id; } - public async submit() { + public async submit(options?: RequestInit & DebugOption) { const schema = z.discriminatedUnion("success", [ z.object({ success: z.literal(true), @@ -41,7 +42,7 @@ export class DeleteFetcher { type: z.string(), message: z.string(), context: z.string().nullish(), - }) + }), ), }), ]); @@ -51,6 +52,7 @@ export class DeleteFetcher { resource: this.resource, pathnameIdentity: this._pathnameIdentity, options: { + ...options, method: "DELETE", }, }); diff --git a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts index bbeef7d3..852888e4 100644 --- a/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/mutation-fetcher.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import * as z4 from "zod/v4/core"; +import { DebugOption } from "../helpers/debug"; import { HTTPClient } from "../helpers/http-client"; import type { APIResource } from "../schemas/shared"; @@ -56,7 +57,7 @@ export class MutationFetcher< } } - public async submit() { + public async submit(options?: RequestInit & DebugOption) { const schema = z.discriminatedUnion("success", [ z.object({ success: z.literal(true), @@ -85,10 +86,7 @@ export class MutationFetcher< resource: this.resource, searchParams: this._urlSearchParams, pathnameIdentity: this._pathnameIdentity, - options: { - method: this._options.method, - body: JSON.stringify(createData), - }, + options: { ...options, method: this._options.method, body: JSON.stringify(createData) }, }); let result: any = {}; if (response.errors) { diff --git a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts index 3c39c9bc..c3ef9543 100644 --- a/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts +++ b/packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts @@ -1,5 +1,6 @@ import { z, ZodRawShape } from "zod"; +import { DebugOption } from "../helpers/debug"; import type { HTTPClient } from "../helpers/http-client"; import { type APIResource, type GhostIdentityInput } from "../schemas/shared"; import type { Exactly, Mask, NoUnrecognizedKeys } from "../utils"; @@ -164,7 +165,7 @@ export class ReadFetcher< } } - public async fetch(options?: RequestInit) { + public async fetch(options?: RequestInit & DebugOption) { const res = z.discriminatedUnion("success", [ z.object({ success: z.literal(true), diff --git a/packages/ts-ghost-core-api/src/helpers/debug.ts b/packages/ts-ghost-core-api/src/helpers/debug.ts new file mode 100644 index 00000000..8251ff59 --- /dev/null +++ b/packages/ts-ghost-core-api/src/helpers/debug.ts @@ -0,0 +1,11 @@ +export type DebugOption = { + debug?: boolean; + logger?: (message?: any, ...optionalParams: any[]) => void; +}; + +export const resolveDebugLogger = (options?: DebugOption) => { + if (options?.debug) { + return options.logger ? options.logger : console.log; + } + return () => {}; +}; diff --git a/packages/ts-ghost-core-api/src/helpers/http-client.ts b/packages/ts-ghost-core-api/src/helpers/http-client.ts index 3a9669f7..7082e831 100644 --- a/packages/ts-ghost-core-api/src/helpers/http-client.ts +++ b/packages/ts-ghost-core-api/src/helpers/http-client.ts @@ -1,6 +1,7 @@ import { SignJWT } from "jose"; import type { APICredentials, APIResource } from "../schemas"; +import { DebugOption, resolveDebugLogger } from "./debug"; export type HTTPClientOptions = { key: string; @@ -22,7 +23,7 @@ export interface IHTTPClient { }: { resource: APIResource; searchParams?: URLSearchParams; - options?: RequestInit; + options?: RequestInit & DebugOption; pathnameIdentity?: string; }): Promise; fetchRawResponse({ @@ -33,7 +34,7 @@ export interface IHTTPClient { }: { resource: APIResource; searchParams?: URLSearchParams; - options?: RequestInit; + options?: RequestInit & DebugOption; pathnameIdentity?: string; }): Promise; } @@ -107,9 +108,10 @@ export class HTTPClient implement }: { resource: APIResource; searchParams?: URLSearchParams; - options?: RequestInit & { debug?: boolean }; + options?: RequestInit & DebugOption; pathnameIdentity?: string; }) { + const debug = resolveDebugLogger(options); if (this._baseURL === undefined) throw new Error("URL is undefined"); let path = `${resource}/`; if (pathnameIdentity !== undefined) { @@ -126,9 +128,7 @@ export class HTTPClient implement } let result = undefined; const headers = await this.genHeaders(); - if (options?.debug) { - console.log("url", url.toString(), "headers", headers, "options", options); - } + debug("url", url.toString(), "headers", headers, "options", options); try { result = await ( await fetch(url.toString(), { @@ -137,6 +137,7 @@ export class HTTPClient implement }) ).json(); } catch (e) { + debug("error", e); return { status: "error", errors: [ From 5d7f81c3d0a23501c8a9d68733402be4e2db7d91 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Sun, 4 Jan 2026 10:24:18 +0400 Subject: [PATCH 11/11] debug options is now accessible from instantiation of the API but also possible... ... to override it at the .fetch() level --- packages/ts-ghost-admin-api/src/admin-api.ts | 3 +++ packages/ts-ghost-content-api/src/content-api.ts | 3 +++ packages/ts-ghost-core-api/src/helpers/http-client.ts | 5 +++-- packages/ts-ghost-core-api/src/helpers/index.ts | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/ts-ghost-admin-api/src/admin-api.ts b/packages/ts-ghost-admin-api/src/admin-api.ts index 956ea501..6aaf446a 100644 --- a/packages/ts-ghost-admin-api/src/admin-api.ts +++ b/packages/ts-ghost-admin-api/src/admin-api.ts @@ -7,6 +7,7 @@ import { baseSiteSchema, baseTagsSchema, BasicFetcher, + DebugOption, emailOrIdSchema, HTTPClientFactory, slugOrIdSchema, @@ -31,6 +32,7 @@ export class TSGhostAdminAPI implement options?: RequestInit & DebugOption; pathnameIdentity?: string; }) { - const debug = resolveDebugLogger(options); + const debug = resolveDebugLogger({ ...this.config, ...options }); if (this._baseURL === undefined) throw new Error("URL is undefined"); let path = `${resource}/`; if (pathnameIdentity !== undefined) { @@ -136,6 +136,7 @@ export class HTTPClient implement headers, }) ).json(); + debug("result", result, "status", result.status); } catch (e) { debug("error", e); return { diff --git a/packages/ts-ghost-core-api/src/helpers/index.ts b/packages/ts-ghost-core-api/src/helpers/index.ts index 91321b20..7f1c45bd 100644 --- a/packages/ts-ghost-core-api/src/helpers/index.ts +++ b/packages/ts-ghost-core-api/src/helpers/index.ts @@ -1,3 +1,4 @@ export * from "./browse-params"; export * from "./fields"; export * from "./http-client"; +export * from "./debug";