'use strict'; var zod = require('zod'); var jose = require('jose'); // src/schemas/authors.ts var ghostIdentitySchema = zod.z.object({ slug: zod.z.string(), id: zod.z.string() }); var ghostIdentityInputSchema = zod.z.object({ slug: zod.z.string().optional(), id: zod.z.string().optional(), email: zod.z.string().email().optional() }); var ghostMetaSchema = zod.z.object({ pagination: zod.z.object({ pages: zod.z.number(), page: zod.z.number(), limit: zod.z.union([zod.z.number(), zod.z.literal("all")]), total: zod.z.number(), prev: zod.z.number().nullable(), next: zod.z.number().nullable() }) }); var ghostExcerptSchema = zod.z.object({ excerpt: zod.z.string().optional(), custom_excerpt: zod.z.string().optional() }); var ghostCodeInjectionSchema = zod.z.object({ codeinjection_head: zod.z.string().nullable(), codeinjection_foot: zod.z.string().nullable() }); var ghostFacebookSchema = zod.z.object({ og_image: zod.z.string().nullable(), og_title: zod.z.string().nullable(), og_description: zod.z.string().nullable() }); var ghostTwitterSchema = zod.z.object({ twitter_image: zod.z.string().nullable(), twitter_title: zod.z.string().nullable(), twitter_description: zod.z.string().nullable() }); var ghostSocialMediaSchema = zod.z.object({ ...ghostFacebookSchema.shape, ...ghostTwitterSchema.shape }); var ghostMetadataSchema = zod.z.object({ meta_title: zod.z.string().nullable(), meta_description: zod.z.string().nullable() }); var ghostVisibilitySchema = zod.z.union([ zod.z.literal("public"), zod.z.literal("members"), zod.z.literal("none"), zod.z.literal("internal"), zod.z.literal("paid"), zod.z.literal("tiers") ]); var apiVersionsSchema = zod.z.string().regex(/^v5\.\d+/).default("v5.0"); var contentAPICredentialsSchema = zod.z.object({ key: zod.z.string().regex(/[0-9a-f]{26}/, { message: "'key' must have 26 hex characters" }), version: apiVersionsSchema, url: zod.z.string().url() }); var adminAPICredentialsSchema = zod.z.object({ key: zod.z.string().regex(/[0-9a-f]{24}:[0-9a-f]{64}/, { message: "'key' must have the following format {A}:{B}, where A is 24 hex characters and B is 64 hex characters" }), version: apiVersionsSchema, url: zod.z.string().url() }); var slugOrIdSchema = zod.z.union([zod.z.object({ slug: zod.z.string() }), zod.z.object({ id: zod.z.string() })]); var emailOrIdSchema = zod.z.union([ zod.z.object({ email: zod.z.string().email() }), zod.z.object({ id: zod.z.string() }) ]); var identitySchema = zod.z.union([ zod.z.object({ email: zod.z.string().email() }), zod.z.object({ id: zod.z.string() }), zod.z.object({ slug: zod.z.string() }) ]); // src/schemas/authors.ts var baseAuthorsSchema = zod.z.object({ ...ghostIdentitySchema.shape, ...ghostMetadataSchema.shape, name: zod.z.string(), profile_image: zod.z.string().nullable(), cover_image: zod.z.string().nullable(), bio: zod.z.string().nullable(), website: zod.z.string().nullable(), location: zod.z.string().nullable(), facebook: zod.z.string().nullable(), twitter: zod.z.string().nullable(), count: zod.z.object({ posts: zod.z.number() }).optional(), url: zod.z.string() }); var baseTagsSchema = zod.z.object({ ...ghostIdentitySchema.shape, ...ghostMetadataSchema.shape, ...ghostCodeInjectionSchema.shape, ...ghostSocialMediaSchema.shape, name: zod.z.string(), description: zod.z.string().nullable(), feature_image: zod.z.string().nullable(), visibility: ghostVisibilitySchema, canonical_url: zod.z.string().nullable(), accent_color: zod.z.string().nullable(), url: zod.z.string(), created_at: zod.z.string().nullish(), updated_at: zod.z.string().nullish(), count: zod.z.object({ posts: zod.z.number() }).optional() }); // src/schemas/pages.ts var postsAuthorSchema = baseAuthorsSchema.extend({ url: zod.z.string().nullish() }); var basePagesSchema = zod.z.object({ ...ghostIdentitySchema.shape, ...ghostMetadataSchema.shape, title: zod.z.string(), html: zod.z.string().nullish(), plaintext: zod.z.string().nullish(), comment_id: zod.z.string().nullable(), feature_image: zod.z.string().nullable(), feature_image_alt: zod.z.string().nullable(), feature_image_caption: zod.z.string().nullable(), featured: zod.z.boolean(), custom_excerpt: zod.z.string().nullable(), ...ghostCodeInjectionSchema.shape, ...ghostSocialMediaSchema.shape, visibility: ghostVisibilitySchema, custom_template: zod.z.string().nullable(), canonical_url: zod.z.string().nullable(), authors: zod.z.array(postsAuthorSchema).optional(), tags: zod.z.array(baseTagsSchema).optional(), primary_author: postsAuthorSchema.nullish(), primary_tag: baseTagsSchema.nullish(), url: zod.z.string(), excerpt: zod.z.string().nullish(), reading_time: zod.z.number().optional().default(0), created_at: zod.z.string(), updated_at: zod.z.string().nullish(), published_at: zod.z.string().nullable(), email_subject: zod.z.string().nullish(), is_page: zod.z.boolean().default(true) }); var postsAuthorSchema2 = baseAuthorsSchema.extend({ url: zod.z.string().nullish() }); var basePostsSchema = zod.z.object({ ...ghostIdentitySchema.shape, ...ghostMetadataSchema.shape, title: zod.z.string(), html: zod.z.string().nullish(), plaintext: zod.z.string().nullish(), comment_id: zod.z.string().nullable(), feature_image: zod.z.string().nullable(), feature_image_alt: zod.z.string().nullable(), feature_image_caption: zod.z.string().nullable(), featured: zod.z.boolean(), custom_excerpt: zod.z.string().nullable(), ...ghostCodeInjectionSchema.shape, ...ghostSocialMediaSchema.shape, visibility: ghostVisibilitySchema, custom_template: zod.z.string().nullable(), canonical_url: zod.z.string().nullable(), authors: zod.z.array(postsAuthorSchema2).optional(), tags: zod.z.array(baseTagsSchema).optional(), primary_author: postsAuthorSchema2.nullish(), primary_tag: baseTagsSchema.nullish(), url: zod.z.string(), excerpt: zod.z.string().nullable(), reading_time: zod.z.number().optional().default(0), created_at: zod.z.string(), updated_at: zod.z.string().nullish(), published_at: zod.z.string().nullable(), email_subject: zod.z.string().nullish(), is_page: zod.z.boolean().default(false) }); var baseSettingsSchema = zod.z.object({ title: zod.z.string(), description: zod.z.string(), logo: zod.z.string().nullable(), icon: zod.z.string().nullable(), accent_color: zod.z.string().nullable(), cover_image: zod.z.string().nullable(), facebook: zod.z.string().nullable(), twitter: zod.z.string().nullable(), lang: zod.z.string(), timezone: zod.z.string(), codeinjection_head: zod.z.string().nullable(), codeinjection_foot: zod.z.string().nullable(), navigation: zod.z.array( zod.z.object({ label: zod.z.string(), url: zod.z.string() }) ), secondary_navigation: zod.z.array( zod.z.object({ label: zod.z.string(), url: zod.z.string() }) ), meta_title: zod.z.string().nullable(), meta_description: zod.z.string().nullable(), og_image: zod.z.string().nullable(), og_title: zod.z.string().nullable(), og_description: zod.z.string().nullable(), twitter_image: zod.z.string().nullable(), twitter_title: zod.z.string().nullable(), twitter_description: zod.z.string().nullable(), members_support_address: zod.z.string(), url: zod.z.string() }); var baseTiersSchema = zod.z.object({ ...ghostIdentitySchema.shape, name: zod.z.string(), description: zod.z.string().nullable(), active: zod.z.boolean(), type: zod.z.union([zod.z.literal("free"), zod.z.literal("paid")]), welcome_page_url: zod.z.string().nullable(), created_at: zod.z.string(), updated_at: zod.z.string().nullable(), stripe_prices: zod.z.array(zod.z.number()).optional().transform((v) => v?.length ? v : []), monthly_price: zod.z.number().nullable().optional().transform((v) => v ? v : null), yearly_price: zod.z.number().nullable().optional().transform((v) => v ? v : null), benefits: zod.z.array(zod.z.string()).optional(), visibility: ghostVisibilitySchema, currency: zod.z.string().nullish(), trial_days: zod.z.number().default(0) }); var baseEmailSchema = zod.z.object({ id: zod.z.string(), uuid: zod.z.string(), status: zod.z.string(), recipient_filter: zod.z.string(), error: zod.z.string().nullish(), error_data: zod.z.any().nullable(), email_count: zod.z.number(), delivered_count: zod.z.number(), opened_count: zod.z.number(), failed_count: zod.z.number(), subject: zod.z.string(), from: zod.z.string(), reply_to: zod.z.string().nullable(), source: zod.z.string(), // lexical format html: zod.z.string().nullable(), plaintext: zod.z.string().nullable(), track_opens: zod.z.boolean(), submitted_at: zod.z.string(), created_at: zod.z.string(), updated_at: zod.z.string() }); var baseOffersSchema = zod.z.object({ id: zod.z.string(), name: zod.z.string({ description: "Internal name for an offer, must be unique" }).default(""), code: zod.z.string({ description: "Shortcode for the offer, for example: https://yoursite.com/black-friday" }), display_title: zod.z.string({ description: "Name displayed in the offer window" }).nullish(), display_description: zod.z.string({ description: "Text displayed in the offer window" }).nullish(), type: zod.z.union([zod.z.literal("percent"), zod.z.literal("fixed"), zod.z.literal("trial")]), cadence: zod.z.union([zod.z.literal("month"), zod.z.literal("year")]), amount: zod.z.number({ 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: zod.z.union([zod.z.literal("once"), zod.z.literal("forever"), zod.z.literal("repeating"), zod.z.literal("trial")], { description: "once/forever/repeating. repeating duration is only available when cadence is month" }), duration_in_months: zod.z.number({ description: "Number of months offer should be repeated when duration is repeating" }).nullish(), currency_restriction: zod.z.boolean({ description: "Denotes whether the offer `currency` is restricted. If so, changing the currency invalidates the offer" }).nullish(), currency: zod.z.string({ description: "fixed type offers only - specifies tier's currency as three letter ISO currency code" }).nullish(), status: zod.z.union([zod.z.literal("active"), zod.z.literal("archived")], { description: "active or archived - denotes if the offer is active or archived" }), redemption_count: zod.z.number({ description: "Number of times the offer has been redeemed" }).nullish(), tier: zod.z.object( { id: zod.z.string(), name: zod.z.string().nullish() }, { description: "Tier on which offer is applied" } ) }); var baseNewsletterSchema = zod.z.object({ ...ghostIdentitySchema.shape, name: zod.z.string({ description: "Public name for the newsletter" }), description: zod.z.string({ description: "(nullable) Public description of the newsletter" }).nullish(), sender_name: zod.z.string({ description: "(nullable) The sender name of the emails" }).nullish(), sender_email: zod.z.string({ description: "(nullable) The email from which to send emails. Requires validation." }).nullish(), sender_reply_to: zod.z.string({ 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: zod.z.union([zod.z.literal("active"), zod.z.literal("archived")], { description: "active or archived - denotes if the newsletter is active or archived" }), visibility: zod.z.union([zod.z.literal("public"), zod.z.literal("members")]), subscribe_on_signup: zod.z.boolean({ description: "true/false. Whether members should automatically subscribe to this newsletter on signup" }), sort_order: zod.z.number({ description: "The order in which newsletters are displayed in the Portal" }), header_image: zod.z.string({ description: "(nullable) Path to an image to show at the top of emails. Recommended size 1200x600" }).nullish(), show_header_icon: zod.z.boolean({ description: "true/false. Show the site icon in emails" }), show_header_title: zod.z.boolean({ description: "true/false. Show the site name in emails" }), title_font_category: zod.z.union([zod.z.literal("serif"), zod.z.literal("sans_serif")], { description: "Title font style. Either serif or sans_serif" }), title_alignment: zod.z.string().nullish(), show_feature_image: zod.z.boolean({ description: "true/false. Show the post's feature image in emails" }), body_font_category: zod.z.union([zod.z.literal("serif"), zod.z.literal("sans_serif")], { description: "Body font style. Either serif or sans_serif" }), footer_content: zod.z.string({ description: "(nullable) Extra information or legal text to show in the footer of emails. Should contain valid HTML." }).nullish(), show_badge: zod.z.boolean({ description: "true/false. Show you\u2019re a part of the indie publishing movement by adding a small Ghost badge in the footer" }), created_at: zod.z.string(), updated_at: zod.z.string().nullish(), show_header_name: zod.z.boolean({ description: "true/false. Show the newsletter name in emails" }), uuid: zod.z.string() }); var baseSubscriptionsSchema = zod.z.object({ id: zod.z.string({ description: "Stripe subscription ID sub_XXXX" }), customer: zod.z.object( { id: zod.z.string(), name: zod.z.string().nullable(), email: zod.z.string() }, { description: "Stripe customer attached to the subscription" } ), status: zod.z.string({ description: "Subscription status" }), start_date: zod.z.string({ description: "Subscription start date" }), default_payment_card_last4: zod.z.string({ description: "Last 4 digits of the card" }).nullable(), cancel_at_period_end: zod.z.boolean({ description: "If the subscription should be canceled or renewed at period end" }), cancellation_reason: zod.z.string({ description: "Reason for subscription cancellation" }).nullable(), current_period_end: zod.z.string({ description: "Subscription end date" }), price: zod.z.object({ id: zod.z.string({ description: "Stripe price ID" }), price_id: zod.z.string({ description: "Ghost price ID" }), nickname: zod.z.string({ description: "Price nickname" }), amount: zod.z.number({ description: "Price amount" }), interval: zod.z.string({ description: "Price interval" }), type: zod.z.string({ description: "Price type" }), currency: zod.z.string({ description: "Price currency" }) }), tier: baseTiersSchema.nullish(), offer: baseOffersSchema.nullish() }); // src/schemas/members.ts var baseMembersSchema = zod.z.object({ id: zod.z.string(), email: zod.z.string({ description: "The email address of the member" }), name: zod.z.string({ description: "The name of the member" }).nullable(), note: zod.z.string({ description: "(nullable) A note about the member" }).nullish(), geolocation: zod.z.string({ description: "(nullable) The geolocation of the member" }).nullish(), created_at: zod.z.string({ description: "The date and time the member was created" }), updated_at: zod.z.string({ description: "(nullable) The date and time the member was last updated" }).nullish(), labels: zod.z.array( zod.z.object({ id: zod.z.string({ description: "The ID of the label" }), name: zod.z.string({ description: "The name of the label" }), slug: zod.z.string({ description: "The slug of the label" }), created_at: zod.z.string({ description: "The date and time the label was created" }), updated_at: zod.z.string({ description: "(nullable) The date and time the label was last updated" }).nullish() }), { description: "The labels associated with the member" } ), subscriptions: zod.z.array(baseSubscriptionsSchema, { description: "The subscriptions associated with the member" }), avatar_image: zod.z.string({ description: "The URL of the member's avatar image" }), email_count: zod.z.number({ description: "The number of emails sent to the member" }), email_opened_count: zod.z.number({ description: "The number of emails opened by the member" }), email_open_rate: zod.z.number({ description: "(nullable) The open rate of the member" }).nullish(), status: zod.z.string({ description: "The status of the member" }), last_seen_at: zod.z.string({ description: "(nullable) The date and time the member was last seen" }).nullish(), newsletters: zod.z.array(baseNewsletterSchema) }); var baseSiteSchema = zod.z.object({ title: zod.z.string(), description: zod.z.string(), logo: zod.z.string().nullable(), version: zod.z.string(), url: zod.z.string() }); // src/fetchers/formats.ts var contentFormats = ["html", "mobiledoc", "plaintext", "lexical"]; // src/fetchers/browse-fetcher.ts var BrowseFetcher = class _BrowseFetcher { constructor(resource, config, _params = { browseParams: {}, include: [], fields: {} }, httpClient) { this.resource = resource; this.config = config; this._params = _params; this.httpClient = httpClient; this._urlParams = {}; this._urlSearchParams = void 0; this._includeFields = []; this._buildUrlParams(); } /** * Lets you choose output format for the content of Post and Pages resources * The choices are html, mobiledoc or plaintext. It will transform the output of the fetcher to a new shape * with the selected formats required. * * @param formats html, mobiledoc or plaintext * @returns A new Fetcher with the fixed output shape and the formats specified */ formats(formats) { const params = { ...this._params, formats: Object.keys(formats).filter((key) => contentFormats.includes(key)) }; return new _BrowseFetcher( this.resource, { schema: this.config.schema, output: this.config.output.required(formats), include: this.config.include }, params, this.httpClient ); } /** * Let's you include special keys into the Ghost API Query to retrieve complimentary info * The available keys are defined by the Resource include schema, will not care about unknown keys. * Returns a new Fetcher with an Output shape modified with the include keys required. * * @param include Include specific keys from the include shape * @returns A new Fetcher with the fixed output shape and the formats specified */ include(include) { const params = { ...this._params, include: Object.keys(this.config.include.parse(include)) }; return new _BrowseFetcher( this.resource, { schema: this.config.schema, output: this.config.output.required(include), include: this.config.include }, params, this.httpClient ); } /** * Let's you strip the output to only the specified keys of your choice that are in the config Schema * Will not care about unknown keys and return a new Fetcher with an Output shape with only the selected keys. * * @param fields Any keys from the resource Schema * @returns A new Fetcher with the fixed output shape having only the selected Fields */ fields(fields) { const newOutput = this.config.output.pick(fields); return new _BrowseFetcher( this.resource, { schema: this.config.schema, output: newOutput, include: this.config.include }, this._params, this.httpClient ); } getResource() { return this.resource; } getParams() { return this._params; } getOutputFields() { return this.config.output.keyof().options; } getURLSearchParams() { return this._urlSearchParams; } getIncludes() { return this._params?.include || []; } getFormats() { return this._params?.formats || []; } _buildUrlParams() { const inputKeys = this.config.schema.keyof().options; const outputKeys = this.config.output.keyof().options; this._urlParams = { ...this._urlBrowseParams() }; if (inputKeys.length !== outputKeys.length && outputKeys.length > 0) { this._urlParams.fields = outputKeys.join(","); } if (this._params.include && this._params.include.length > 0) { this._urlParams.include = this._params.include.join(","); } if (this._params.formats && this._params.formats.length > 0) { this._urlParams.formats = this._params.formats.join(","); } this._urlSearchParams = new URLSearchParams(); for (const [key, value] of Object.entries(this._urlParams)) { this._urlSearchParams.append(key, value); } } _urlBrowseParams() { let urlBrowseParams = {}; if (this._params.browseParams === void 0) return {}; const { limit, page, ...params } = this._params.browseParams; urlBrowseParams = { ...params }; if (limit) { urlBrowseParams.limit = limit.toString(); } if (page) { urlBrowseParams.page = page.toString(); } return urlBrowseParams; } _getResultSchema() { return zod.z.discriminatedUnion("success", [ zod.z.object({ success: zod.z.literal(true), meta: ghostMetaSchema, data: zod.z.array(this.config.output) }), zod.z.object({ success: zod.z.literal(false), errors: zod.z.array( zod.z.object({ type: zod.z.string(), message: zod.z.string() }) ) }) ]); } async fetch(options) { const resultSchema = this._getResultSchema(); const result = await this.httpClient.fetch({ resource: this.resource, searchParams: this._urlSearchParams, options }); let data = {}; if (result.errors) { data.success = false; data.errors = result.errors; } else { data = { success: true, meta: result.meta || { pagination: { pages: 0, page: 0, limit: 15, total: 0, prev: null, next: null } }, data: result[this.resource] }; } return resultSchema.parse(data); } async paginate(options) { if (!this._params.browseParams?.page) { this._params.browseParams = { ...this._params.browseParams, page: 1 }; this._buildUrlParams(); } const resultSchema = this._getResultSchema(); const result = await this.httpClient.fetch({ resource: this.resource, searchParams: this._urlSearchParams, options }); let data = {}; if (result.errors) { data.success = false; data.errors = result.errors; } else { data = { success: true, meta: result.meta || { pagination: { pages: 0, page: 0, limit: 15, total: 0, prev: null, next: null } }, data: result[this.resource] }; } const response = { current: resultSchema.parse(data), next: void 0 }; if (response.current.success === false) return response; const { meta } = response.current; if (meta.pagination.pages <= 1 || meta.pagination.page === meta.pagination.pages) return response; const params = { ...this._params, browseParams: { ...this._params.browseParams, page: meta.pagination.page + 1 } }; const next = new _BrowseFetcher(this.resource, this.config, params, this.httpClient); response.next = next; return response; } }; var ReadFetcher = class _ReadFetcher { constructor(resource, config, _params, httpClient) { this.resource = resource; this.config = config; this._params = _params; this.httpClient = httpClient; this._urlParams = {}; this._urlSearchParams = void 0; this._pathnameIdentity = void 0; this._includeFields = []; this._buildUrlParams(); } /** * Lets you choose output format for the content of Post and Pages resources * The choices are html, mobiledoc or plaintext. It will transform the output of the fetcher to a new shape * with the selected formats required. * * @param formats html, mobiledoc or plaintext * @returns A new Fetcher with the fixed output shape and the formats specified */ formats(formats) { const params = { ...this._params, formats: Object.keys(formats).filter((key) => contentFormats.includes(key)) }; return new _ReadFetcher( this.resource, { schema: this.config.schema, output: this.config.output.required(formats), include: this.config.include }, params, this.httpClient ); } /** * Let's you include special keys into the Ghost API Query to retrieve complimentary info * The available keys are defined by the Resource include schema, will not care about unknown keys. * Returns a new Fetcher with an Output shape modified with the include keys required. * * @param include Include specific keys from the include shape * @returns A new Fetcher with the fixed output shape and the formats specified */ include(include) { const params = { ...this._params, include: Object.keys(this.config.include.parse(include)) }; return new _ReadFetcher( this.resource, { schema: this.config.schema, output: this.config.output.required(include), include: this.config.include }, params, this.httpClient ); } /** * Let's you strip the output to only the specified keys of your choice that are in the config Schema * Will not care about unknown keys and return a new Fetcher with an Output shape with only the selected keys. * * @param fields Any keys from the resource Schema * @returns A new Fetcher with the fixed output shape having only the selected Fields */ fields(fields) { const newOutput = this.config.output.pick(fields); return new _ReadFetcher( this.resource, { schema: this.config.schema, output: newOutput, include: this.config.include }, this._params, this.httpClient ); } getResource() { return this.resource; } getParams() { return this._params; } getOutputFields() { return this.config.output.keyof().options; } getIncludes() { return this._params?.include || []; } getFormats() { return this._params?.formats || []; } _buildUrlParams() { const inputKeys = this.config.schema.keyof().options; const outputKeys = this.config.output.keyof().options; if (inputKeys.length !== outputKeys.length && outputKeys.length > 0) { this._urlParams.fields = outputKeys.join(","); } if (this._params.include && this._params.include.length > 0) { this._urlParams.include = this._params.include.join(","); } if (this._params.formats && this._params.formats.length > 0) { this._urlParams.formats = this._params.formats.join(","); } if (this._params.identity.id) { this._pathnameIdentity = `${this._params.identity.id}`; } else if (this._params.identity.slug) { this._pathnameIdentity = `slug/${this._params.identity.slug}`; } else if (this._params.identity.email) { this._pathnameIdentity = `email/${this._params.identity.email}`; } else { throw new Error("Identity is not defined"); } this._urlSearchParams = new URLSearchParams(); for (const [key, value] of Object.entries(this._urlParams)) { this._urlSearchParams.append(key, value); } } async fetch(options) { const res = zod.z.discriminatedUnion("success", [ zod.z.object({ success: zod.z.literal(true), data: this.config.output }), zod.z.object({ success: zod.z.literal(false), errors: zod.z.array( zod.z.object({ type: zod.z.string(), message: zod.z.string() }) ) }) ]); const result = await this.httpClient.fetch({ resource: this.resource, pathnameIdentity: this._pathnameIdentity, searchParams: this._urlSearchParams, options }); let data = {}; if (result.errors) { data.success = false; data.errors = result.errors; } else { data = { success: true, data: result[this.resource][0] }; } return res.parse(data); } }; var BasicFetcher = class { constructor(resource, config, httpClient) { this.resource = resource; this.config = config; this.httpClient = httpClient; } getResource() { return this.resource; } async fetch(options) { const res = zod.z.discriminatedUnion("success", [ zod.z.object({ success: zod.z.literal(true), data: this.config.output }), zod.z.object({ success: zod.z.literal(false), errors: zod.z.array( zod.z.object({ type: zod.z.string(), message: zod.z.string() }) ) }) ]); const result = await this.httpClient.fetch({ options, resource: this.resource }); let data = {}; if (result.errors) { data.success = false; data.errors = result.errors; } else { data = { success: true, data: result[this.resource] }; } return res.parse(data); } }; var MutationFetcher = class { constructor(resource, config, _params, _options, httpClient) { this.resource = resource; this.config = config; this._params = _params; this._options = _options; this.httpClient = httpClient; this._urlParams = {}; this._urlSearchParams = void 0; this._pathnameIdentity = void 0; this._buildUrlParams(); } getResource() { return this.resource; } getParams() { return this._params; } _buildUrlParams() { if (this._params) { for (const [key, value] of Object.entries(this._params)) { if (key !== "id") { this._urlParams[key] = value; } } } this._urlSearchParams = new URLSearchParams(); if (this._params?.id) { this._pathnameIdentity = `${this._params.id}`; } for (const [key, value] of Object.entries(this._urlParams)) { this._urlSearchParams.append(key, value); } } async submit() { const schema = zod.z.discriminatedUnion("success", [ zod.z.object({ success: zod.z.literal(true), data: this.config.output }), zod.z.object({ success: zod.z.literal(false), errors: zod.z.array( zod.z.object({ type: zod.z.string(), message: zod.z.string(), context: zod.z.string().nullish() }) ) }) ]); const createData = { [this.resource]: [this._options.body] }; const response = await this.httpClient.fetch({ resource: this.resource, searchParams: this._urlSearchParams, pathnameIdentity: this._pathnameIdentity, options: { method: this._options.method, body: JSON.stringify(createData) } }); let result = {}; if (response.errors) { result.success = false; result.errors = response.errors; } else { result = { success: true, data: response[this.resource][0] }; } return schema.parse(result); } }; var DeleteFetcher = class { constructor(resource, _params, httpClient) { this.resource = resource; this._params = _params; this.httpClient = httpClient; this._pathnameIdentity = void 0; this._buildPathnameIdentity(); } getResource() { return this.resource; } getParams() { return this._params; } _buildPathnameIdentity() { if (!this._params.id) { throw new Error("Missing id in params"); } this._pathnameIdentity = this._params.id; } async submit() { const schema = zod.z.discriminatedUnion("success", [ zod.z.object({ success: zod.z.literal(true) }), zod.z.object({ success: zod.z.literal(false), errors: zod.z.array( zod.z.object({ type: zod.z.string(), message: zod.z.string(), context: zod.z.string().nullish() }) ) }) ]); let result = {}; try { const response = await this.httpClient.fetchRawResponse({ resource: this.resource, pathnameIdentity: this._pathnameIdentity, options: { method: "DELETE" } }); if (response.status === 204) { result = { success: true }; } else { const res = await response.json(); if (res.errors) { result.success = false; result.errors = res.errors; } } } catch (e) { result = { success: false, errors: [ { type: "FetchError", message: e.toString() } ] }; } return schema.parse(result); } }; var browseParamsSchema = zod.z.object({ order: zod.z.string().optional(), limit: zod.z.union([ zod.z.literal("all"), zod.z.number().refine((n) => n && n > 0 && n <= 15, { message: "Limit must be between 1 and 15" }) ]).optional(), page: zod.z.number().refine((n) => n && n >= 1, { message: "Page must be greater than 1" }).optional(), filter: zod.z.string().optional() }); var parseBrowseParams = (args, schema, includeSchema) => { const keys = [ ...schema.keyof().options, ...includeSchema && includeSchema.keyof().options || [] ]; const augmentedSchema = browseParamsSchema.merge( zod.z.object({ order: zod.z.string().superRefine((val, ctx) => { const orderPredicates = val.split(","); for (const orderPredicate of orderPredicates) { const [field, direction] = orderPredicate.split(" "); if (!keys.includes(field)) { ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: `Field "${field}" is not a valid field`, fatal: true }); } if (direction && !(direction.toUpperCase() === "ASC" || direction.toUpperCase() === "DESC")) { ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: "Order direction must be ASC or DESC", fatal: true }); } } }).optional(), filter: zod.z.string().superRefine((val, ctx) => { const filterPredicates = val.replace(/ *\[[^)]*\] */g, "").split(/[+(,]+/); for (const filterPredicate of filterPredicates) { const field = filterPredicate.split(":")[0].split(".")[0]; if (!keys.includes(field)) { ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: `Field "${field}" is not a valid field`, fatal: true }); } } }).optional() }) ); return augmentedSchema.parse(args); }; // src/api-composer.ts function isZodObject(schema) { return schema.partial !== void 0; } var APIComposer = class { constructor(resource, config, httpClient) { this.resource = resource; this.config = config; this.httpClient = httpClient; } /** * Browse function that accepts browse params order, filter, page and limit. Will return an instance * of BrowseFetcher class. */ browse(options) { return new BrowseFetcher( this.resource, { schema: this.config.schema, output: this.config.schema, include: this.config.include }, { browseParams: options && parseBrowseParams(options, this.config.schema, this.config.include) || void 0 }, this.httpClient ); } /** * Read function that accepts Identify fields like id, slug or email. Will return an instance * of ReadFetcher class. */ read(options) { return new ReadFetcher( this.resource, { schema: this.config.schema, output: this.config.schema, include: this.config.include }, { identity: this.config.identitySchema.parse(options) }, this.httpClient ); } async add(data, options) { if (!this.config.createSchema) { throw new Error("No createSchema defined"); } const parsedData = this.config.createSchema.parse(data); const parsedOptions = this.config.createOptionsSchema && options ? this.config.createOptionsSchema.parse(options) : void 0; const fetcher = new MutationFetcher( this.resource, { output: this.config.schema, paramsShape: this.config.createOptionsSchema }, parsedOptions, { method: "POST", body: parsedData }, this.httpClient ); return fetcher.submit(); } async edit(id, data, options) { let updateSchema = this.config.updateSchema; if (!this.config.updateSchema && this.config.createSchema && isZodObject(this.config.createSchema)) { updateSchema = this.config.createSchema.partial(); } if (!updateSchema) { throw new Error("No updateSchema defined"); } const cleanId = zod.z.string().nonempty().parse(id); const parsedData = updateSchema.parse(data); const parsedOptions = this.config.updateOptionsSchema && options ? this.config.updateOptionsSchema.parse(options) : {}; if (Object.keys(parsedData).length === 0) { throw new Error("No data to edit"); } const fetcher = new MutationFetcher( this.resource, { output: this.config.schema, paramsShape: this.config.updateOptionsSchema }, { id: cleanId, ...parsedOptions }, { method: "PUT", body: parsedData }, this.httpClient ); return fetcher.submit(); } async delete(id) { const cleanId = zod.z.string().nonempty().parse(id); const fetcher = new DeleteFetcher(this.resource, { id: cleanId }, this.httpClient); return fetcher.submit(); } access(keys) { const d = {}; keys.forEach((key) => { d[key] = this[key].bind(this); }); return d; } }; // src/helpers/fields.ts var schemaWithPickedFields = (schema, fields) => { return schema.pick(fields || {}); }; var HTTPClient = class { constructor(config) { this.config = config; this._baseURL = void 0; let prefixPath = new URL(config.url).pathname; if (prefixPath.slice(-1) === "/") { prefixPath = prefixPath.slice(0, -1); } this._baseURL = new URL(`${prefixPath}/ghost/api/${config.endpoint}/`, config.url); } get baseURL() { return this._baseURL; } get jwt() { return this._jwt; } async generateJWT(key) { const [id, _secret] = key.split(":"); this._jwtExpiresAt = Date.now() + 5 * 60 * 1e3; return new jose.SignJWT({}).setProtectedHeader({ kid: id, alg: "HS256" }).setExpirationTime("5m").setIssuedAt().setAudience("/admin/").sign( Uint8Array.from(_secret.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))) ); } async genHeaders() { const headers = { "Content-Type": "application/json", "Accept-Version": this.config.version }; if (this.config.endpoint === "admin") { if (this._jwt === void 0 || this._jwtExpiresAt === void 0 || this._jwtExpiresAt < Date.now()) { this._jwt = await this.generateJWT(this.config.key); } headers["Authorization"] = `Ghost ${this.jwt}`; } return headers; } async fetch({ resource, searchParams, options, pathnameIdentity }) { if (this._baseURL === void 0) throw new Error("URL is undefined"); let path = `${resource}/`; if (pathnameIdentity !== void 0) { path += `${pathnameIdentity}/`; } const url = new URL(path, this._baseURL); if (searchParams !== void 0) { for (const [key, value] of searchParams.entries()) { url.searchParams.append(key, value); } } if (this.config.endpoint === "content") { url.searchParams.append("key", this.config.key); } let result = void 0; const headers = await this.genHeaders(); try { result = await (await fetch(url.toString(), { ...options, headers })).json(); } catch (e) { return { status: "error", errors: [ { type: "FetchError", message: e.toString() } ] }; } return result; } async fetchRawResponse({ resource, searchParams, options, pathnameIdentity }) { if (this._baseURL === void 0) throw new Error("URL is undefined"); this._baseURL.pathname += `${resource}/`; if (pathnameIdentity !== void 0) { this._baseURL.pathname += `${pathnameIdentity}/`; } if (searchParams !== void 0) { for (const [key, value] of searchParams.entries()) { this._baseURL.searchParams.append(key, value); } } if (this.config.endpoint === "content") { this._baseURL.searchParams.append("key", this.config.key); } const headers = await this.genHeaders(); return await fetch(this._baseURL.toString(), { ...options, headers }); } }; exports.APIComposer = APIComposer; exports.BasicFetcher = BasicFetcher; exports.BrowseFetcher = BrowseFetcher; exports.DeleteFetcher = DeleteFetcher; exports.HTTPClient = HTTPClient; exports.MutationFetcher = MutationFetcher; exports.ReadFetcher = ReadFetcher; exports.adminAPICredentialsSchema = adminAPICredentialsSchema; exports.apiVersionsSchema = apiVersionsSchema; exports.baseAuthorsSchema = baseAuthorsSchema; exports.baseEmailSchema = baseEmailSchema; exports.baseMembersSchema = baseMembersSchema; exports.baseNewsletterSchema = baseNewsletterSchema; exports.baseOffersSchema = baseOffersSchema; exports.basePagesSchema = basePagesSchema; exports.basePostsSchema = basePostsSchema; exports.baseSettingsSchema = baseSettingsSchema; exports.baseSiteSchema = baseSiteSchema; exports.baseTagsSchema = baseTagsSchema; exports.baseTiersSchema = baseTiersSchema; exports.browseParamsSchema = browseParamsSchema; exports.contentAPICredentialsSchema = contentAPICredentialsSchema; exports.emailOrIdSchema = emailOrIdSchema; exports.ghostCodeInjectionSchema = ghostCodeInjectionSchema; exports.ghostExcerptSchema = ghostExcerptSchema; exports.ghostFacebookSchema = ghostFacebookSchema; exports.ghostIdentityInputSchema = ghostIdentityInputSchema; exports.ghostIdentitySchema = ghostIdentitySchema; exports.ghostMetaSchema = ghostMetaSchema; exports.ghostMetadataSchema = ghostMetadataSchema; exports.ghostSocialMediaSchema = ghostSocialMediaSchema; exports.ghostTwitterSchema = ghostTwitterSchema; exports.ghostVisibilitySchema = ghostVisibilitySchema; exports.identitySchema = identitySchema; exports.parseBrowseParams = parseBrowseParams; exports.schemaWithPickedFields = schemaWithPickedFields; exports.slugOrIdSchema = slugOrIdSchema; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.js.map