From 608fd6b49fd48ab782be29541184ba9aa5197516 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 20:20:08 +0000 Subject: [PATCH 1/7] chore(tests): remove redundant File import --- tests/uploads.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts index 6a964a1..d28a246 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -1,7 +1,6 @@ import fs from 'fs'; import type { ResponseLike } from 'postgrid-node/internal/to-file'; import { toFile } from 'postgrid-node/core/uploads'; -import { File } from 'node:buffer'; class MyClass { name: string = 'foo'; From 0e7287bebc5f0eeea9be3c2aa222df39fa585de1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:15:50 +0000 Subject: [PATCH 2/7] fix(typescript): upgrade tsc-multi so that it works with Node 26 --- package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f2c15f0..3d3a9c5 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "publint": "^0.2.12", "ts-jest": "^29.1.0", "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "tslib": "^2.8.1", "typescript": "5.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0540538..6497094 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,8 +56,8 @@ importers: specifier: ^10.5.0 version: 10.7.0(@swc/core@1.4.16)(@types/node@20.19.11)(typescript@5.8.3) tsc-multi: - specifier: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz - version: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz(typescript@5.8.3) + specifier: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz + version: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz(typescript@5.8.3) tsconfig-paths: specifier: ^4.0.0 version: 4.2.0 @@ -3119,13 +3119,13 @@ packages: '@swc/wasm': optional: true - tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz: + tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz: resolution: { - integrity: sha512-tWyCXnx0WqCkVlo5s+4KMj7HC0/0YrCZY0PustUwX9F2lNwd8Kp07q/Q56uGvV9q80XaSDrhy0YqBmrX5TDNpQ==, - tarball: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz, + integrity: sha512-LrjLRdfDnJ6UcZPSsxxY8QDnZmS3ZpPyvzgjUlNMjjRoTAUVqeL+IWrIzEU3Z+CwVrpVI97PePRLenEfCtR/UQ==, + tarball: https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz, } - version: 1.1.9 + version: 1.1.11 engines: { node: '>=14' } hasBin: true peerDependencies: @@ -5399,7 +5399,7 @@ snapshots: optionalDependencies: '@swc/core': 1.4.16 - tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz(typescript@5.8.3): + tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.11/tsc-multi.tgz(typescript@5.8.3): dependencies: debug: 4.4.1 fast-glob: 3.3.2 From d21b5def4f3c93fc7fa6e7c1955e727ea8f0a795 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 14:15:25 +0000 Subject: [PATCH 3/7] feat: Update available premium paper IDs --- .github/workflows/publish-npm.yml | 5 +- .github/workflows/release-doctor.yml | 2 + .gitignore | 2 +- .stats.yml | 6 +- bin/check-release-environment | 4 + bin/publish-npm | 12 +- src/resources/print-mail/campaigns.ts | 66 +++++- src/resources/print-mail/letters.ts | 191 +++++++++++++++++ src/resources/print-mail/postcards.ts | 199 ++++++++++++++++-- src/resources/print-mail/self-mailers.ts | 76 +++++++ .../print-mail/campaigns.test.ts | 3 +- .../api-resources/print-mail/letters.test.ts | 1 + .../print-mail/postcards.test.ts | 2 +- 13 files changed, 526 insertions(+), 43 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 4475864..c756ef3 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -12,9 +12,6 @@ jobs: publish: name: publish runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -34,3 +31,5 @@ jobs: - name: Publish to NPM run: | bash ./bin/publish-npm + env: + NPM_TOKEN: ${{ secrets.POSTGRID_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index c3adfe0..f5dbb6f 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -17,3 +17,5 @@ jobs: - name: Check release environment run: | bash ./bin/check-release-environment + env: + NPM_TOKEN: ${{ secrets.POSTGRID_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 9487e48..c85fe68 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ dist-deno /*.tgz .idea/ .eslintcache -oidc + diff --git a/.stats.yml b/.stats.yml index 468c6e3..beca239 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-3c7d57bbfb43bd794b71e33005d57e8e05469854cd30340b491df0819d145adc.yml -openapi_spec_hash: 3562a11137635f5a513f3c1ae8421fda -config_hash: c97dc4db121f63642b688d1ec0923a6d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-d00ba06c82004a1a431d4c1992fc13c65ed4ffc6bb4dcd06f4b1e10037b1cf60.yml +openapi_spec_hash: 45820a4d38f466f15dcbff2d011b098f +config_hash: 4429a41bb9161a59679a1a95b1ec7053 diff --git a/bin/check-release-environment b/bin/check-release-environment index 6b43775..e4b6d58 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,6 +2,10 @@ errors=() +if [ -z "${NPM_TOKEN}" ]; then + errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + lenErrors=${#errors[@]} if [[ lenErrors -gt 0 ]]; then diff --git a/bin/publish-npm b/bin/publish-npm index 4e094d7..a609989 100644 --- a/bin/publish-npm +++ b/bin/publish-npm @@ -2,12 +2,7 @@ set -eux -if [[ ${NPM_TOKEN:-} ]]; then - npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" -elif [[ ! ${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-} ]]; then - echo "ERROR: NPM_TOKEN must be set if not running in a Github Action with id-token permission" - exit 1 -fi +npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" pnpm build cd dist @@ -62,8 +57,5 @@ else TAG="latest" fi -# Install OIDC compatible npm version -npm install --prefix ../oidc/ npm@11.6.2 - # Publish with the appropriate tag -pnpm publish --npm-path "$(cd ../ && pwd)/oidc/node_modules/.bin/npm" --no-git-checks --tag "$TAG" +pnpm publish --no-git-checks --tag "$TAG" diff --git a/src/resources/print-mail/campaigns.ts b/src/resources/print-mail/campaigns.ts index 533eb62..c809427 100644 --- a/src/resources/print-mail/campaigns.ts +++ b/src/resources/print-mail/campaigns.ts @@ -469,6 +469,16 @@ export namespace Campaign { */ metadata?: { [key: string]: string }; + /** + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * Which page number should be perforated (if any). */ @@ -557,10 +567,16 @@ export namespace Campaign { metadata?: { [key: string]: string }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". */ - paper?: string; + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * Enum representing the supported postcard sizes. @@ -993,6 +1009,16 @@ export namespace CampaignCreateParams { */ metadata?: { [key: string]: string }; + /** + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * A PDF file or URL for the letter content. Cannot be used with `template`. */ @@ -1081,10 +1107,16 @@ export namespace CampaignCreateParams { metadata?: { [key: string]: string }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". */ - paper?: string; + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * A 2-page PDF file for the postcard content (front and back). Cannot be used with @@ -1501,6 +1533,16 @@ export namespace CampaignUpdateParams { */ metadata?: { [key: string]: string }; + /** + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * A PDF file or URL for the letter content. Cannot be used with `template`. */ @@ -1589,10 +1631,16 @@ export namespace CampaignUpdateParams { metadata?: { [key: string]: string }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. + * Premium paper selection ("standard" or a premium paper ID). If omitted, org + * default is used when configured; otherwise "standard". */ - paper?: string; + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * A 2-page PDF file for the postcard content (front and back). Cannot be used with diff --git a/src/resources/print-mail/letters.ts b/src/resources/print-mail/letters.ts index fc596c4..c3132d1 100644 --- a/src/resources/print-mail/letters.ts +++ b/src/resources/print-mail/letters.ts @@ -22,6 +22,7 @@ export class Letters extends APIResource { * from: 'contact_123', * html: 'Content', * to: 'contact_123', + * paper: 'standard', * }); * ``` */ @@ -309,6 +310,24 @@ export interface Letter { */ metadata?: { [key: string]: unknown }; + /** + * Premium paper selection used for this letter. + * + * Available values include: + * + * - `standard` + * - `premium_paper_letter_standard_white_70lb` + * - `premium_paper_letter_standard_white_80lb` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default letter paper is used when configured; otherwise `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * The ID of the PDF workflow run that created the letter, if any. */ @@ -583,6 +602,24 @@ export declare namespace LetterCreateParams { */ metadata?: { [key: string]: unknown }; + /** + * Premium paper selection used for this letter. + * + * Available values include: + * + * - `standard` + * - `premium_paper_letter_standard_white_70lb` + * - `premium_paper_letter_standard_white_80lb` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default letter paper is used when configured; otherwise `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * If specified, indicates which letter page is perforated. Currently, only the * first page can be perforated. @@ -612,11 +649,147 @@ export declare namespace LetterCreateParams { } export interface LetterCreateWithTemplate { + /** + * The contact information of the sender. You can pass contact information inline + * here just like you can for the `to`. + */ + from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + /** * The template ID for the letter. You can supply _either_ this or `html` but not * both. */ template: string; + + /** + * The recipient of this order. You can either supply the contact information + * inline here or provide a contact ID. PostGrid will automatically deduplicate + * contacts regardless of whether you provide the information inline here or call + * the contact creation endpoint. + */ + to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + + /** + * Enum representing the placement of the address on the letter. + */ + addressPlacement?: AddressPlacement; + + /** + * Model representing an attached PDF. + */ + attachedPDF?: AttachedPdf; + + /** + * Indicates if the letter is in color. + */ + color?: boolean; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * Indicates if the letter is double-sided. + */ + doubleSided?: boolean; + + /** + * The envelope (ID) for the letter. You can either specify a custom envelope ID or + * use the default `standard` envelope. + */ + envelope?: string; + + /** + * The mailing class of this order. If not provided, automatically set to + * `first_class`. + */ + mailingClass?: + | 'first_class' + | 'standard_class' + | 'express' + | 'certified' + | 'certified_return_receipt' + | 'registered' + | 'usps_first_class' + | 'usps_standard_class' + | 'usps_eddm' + | 'usps_express_2_day' + | 'usps_express_3_day' + | 'usps_first_class_certified' + | 'usps_first_class_certified_return_receipt' + | 'usps_first_class_registered' + | 'usps_express_3_day_signature_confirmation' + | 'usps_express_3_day_certified' + | 'usps_express_3_day_certified_return_receipt' + | 'ca_post_lettermail' + | 'ca_post_personalized' + | 'ca_post_neighbourhood_mail' + | 'ups_express_overnight' + | 'ups_express_2_day' + | 'ups_express_3_day' + | 'royal_mail_first_class' + | 'royal_mail_second_class' + | 'au_post_second_class'; + + /** + * These will be merged with the variables in the template or HTML you create this + * order with. The keys in this object should match the variable names in the + * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to + * PDFs uploaded with the order. + */ + mergeVariables?: { [key: string]: unknown }; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * Premium paper selection used for this letter. + * + * Available values include: + * + * - `standard` + * - `premium_paper_letter_standard_white_70lb` + * - `premium_paper_letter_standard_white_80lb` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default letter paper is used when configured; otherwise `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + + /** + * If specified, indicates which letter page is perforated. Currently, only the + * first page can be perforated. + */ + perforatedPage?: 1; + + /** + * Model representing a plastic card. + */ + plasticCard?: PlasticCard; + + /** + * The return envelope (ID) sent out with the letter, if any. + */ + returnEnvelope?: string; + + /** + * This order will transition from `ready` to `printing` on the day after this + * date. You can use this parameter to schedule orders for a future date. + */ + sendDate?: string; + + /** + * Enum representing the supported letter sizes. + */ + size?: LetterSize; } export interface LetterCreateWithPdf { @@ -716,6 +889,24 @@ export declare namespace LetterCreateParams { */ metadata?: { [key: string]: unknown }; + /** + * Premium paper selection used for this letter. + * + * Available values include: + * + * - `standard` + * - `premium_paper_letter_standard_white_70lb` + * - `premium_paper_letter_standard_white_80lb` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default letter paper is used when configured; otherwise `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_letter_standard_white_70lb' + | 'premium_paper_letter_standard_white_80lb' + | (string & {}); + /** * If specified, indicates which letter page is perforated. Currently, only the * first page can be perforated. diff --git a/src/resources/print-mail/postcards.ts b/src/resources/print-mail/postcards.ts index f8ecc0d..333ea86 100644 --- a/src/resources/print-mail/postcards.ts +++ b/src/resources/print-mail/postcards.ts @@ -25,6 +25,7 @@ export class Postcards extends APIResource { * size: '6x4', * to: 'contact_456', * from: 'contact_123', + * paper: 'standard', * }); * ``` */ @@ -264,10 +265,27 @@ export interface Postcard { metadata?: { [key: string]: unknown }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. + * Premium paper selection used for this postcard. + * + * Available values include: + * + * - `standard` + * - `premium_paper_heavy_1_glossy` + * - `premium_paper_postcard_uv_glossy_ss` + * - `premium_paper_postcard_uv_glossy_ss_120lb` + * - `premium_paper_postcard_satin_ds` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default postcard paper is used when configured; otherwise + * `standard`. */ - paper?: string; + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * The tracking number of this order. Populated after an express/certified order @@ -418,10 +436,27 @@ export declare namespace PostcardCreateParams { metadata?: { [key: string]: unknown }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. - */ - paper?: string; + * Premium paper selection used for this postcard. + * + * Available values include: + * + * - `standard` + * - `premium_paper_heavy_1_glossy` + * - `premium_paper_postcard_uv_glossy_ss` + * - `premium_paper_postcard_uv_glossy_ss_120lb` + * - `premium_paper_postcard_satin_ds` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default postcard paper is used when configured; otherwise + * `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * This order will transition from `ready` to `printing` on the day after this @@ -442,6 +477,106 @@ export declare namespace PostcardCreateParams { * `frontHTML` but not both. */ frontTemplate: string; + + /** + * Enum representing the supported postcard sizes. + */ + size: '6x4' | '9x6' | '11x6'; + + /** + * The recipient of this order. You can either supply the contact information + * inline here or provide a contact ID. PostGrid will automatically deduplicate + * contacts regardless of whether you provide the information inline here or call + * the contact creation endpoint. + */ + to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * The contact information of the sender. You can pass contact information inline + * here just like you can for the `to`. Unlike other order types, the sender + * address is optional for postcards. + */ + from?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + + /** + * The mailing class of this order. If not provided, automatically set to + * `first_class`. + */ + mailingClass?: + | 'first_class' + | 'standard_class' + | 'express' + | 'certified' + | 'certified_return_receipt' + | 'registered' + | 'usps_first_class' + | 'usps_standard_class' + | 'usps_eddm' + | 'usps_express_2_day' + | 'usps_express_3_day' + | 'usps_first_class_certified' + | 'usps_first_class_certified_return_receipt' + | 'usps_first_class_registered' + | 'usps_express_3_day_signature_confirmation' + | 'usps_express_3_day_certified' + | 'usps_express_3_day_certified_return_receipt' + | 'ca_post_lettermail' + | 'ca_post_personalized' + | 'ca_post_neighbourhood_mail' + | 'ups_express_overnight' + | 'ups_express_2_day' + | 'ups_express_3_day' + | 'royal_mail_first_class' + | 'royal_mail_second_class' + | 'au_post_second_class'; + + /** + * These will be merged with the variables in the template or HTML you create this + * order with. The keys in this object should match the variable names in the + * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to + * PDFs uploaded with the order. + */ + mergeVariables?: { [key: string]: unknown }; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * Premium paper selection used for this postcard. + * + * Available values include: + * + * - `standard` + * - `premium_paper_heavy_1_glossy` + * - `premium_paper_postcard_uv_glossy_ss` + * - `premium_paper_postcard_uv_glossy_ss_120lb` + * - `premium_paper_postcard_satin_ds` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default postcard paper is used when configured; otherwise + * `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); + + /** + * This order will transition from `ready` to `printing` on the day after this + * date. You can use this parameter to schedule orders for a future date. + */ + sendDate?: string; } export interface PostcardCreateWithPdfurl { @@ -523,10 +658,27 @@ export declare namespace PostcardCreateParams { metadata?: { [key: string]: unknown }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. - */ - paper?: string; + * Premium paper selection used for this postcard. + * + * Available values include: + * + * - `standard` + * - `premium_paper_heavy_1_glossy` + * - `premium_paper_postcard_uv_glossy_ss` + * - `premium_paper_postcard_uv_glossy_ss_120lb` + * - `premium_paper_postcard_satin_ds` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default postcard paper is used when configured; otherwise + * `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * This order will transition from `ready` to `printing` on the day after this @@ -614,10 +766,27 @@ export declare namespace PostcardCreateParams { metadata?: { [key: string]: unknown }; /** - * Premium paper identifier. Use "standard" for regular stock or a premium*paper*\* - * ID. - */ - paper?: string; + * Premium paper selection used for this postcard. + * + * Available values include: + * + * - `standard` + * - `premium_paper_heavy_1_glossy` + * - `premium_paper_postcard_uv_glossy_ss` + * - `premium_paper_postcard_uv_glossy_ss_120lb` + * - `premium_paper_postcard_satin_ds` + * + * Not all premium paper options are enabled for all organizations. If omitted, the + * organization default postcard paper is used when configured; otherwise + * `standard`. + */ + paper?: + | 'standard' + | 'premium_paper_heavy_1_glossy' + | 'premium_paper_postcard_uv_glossy_ss' + | 'premium_paper_postcard_uv_glossy_ss_120lb' + | 'premium_paper_postcard_satin_ds' + | (string & {}); /** * This order will transition from `ready` to `printing` on the day after this diff --git a/src/resources/print-mail/self-mailers.ts b/src/resources/print-mail/self-mailers.ts index 2126fd1..671e93f 100644 --- a/src/resources/print-mail/self-mailers.ts +++ b/src/resources/print-mail/self-mailers.ts @@ -403,6 +403,12 @@ export declare namespace SelfMailerCreateParams { } export interface SelfMailerCreateWithTemplate { + /** + * The contact information of the sender. You can pass contact information inline + * here just like you can for the `to`. + */ + from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + /** * The template ID for the inside of the self-mailer. You can supply _either_ this * or `insideHTML` but not both. @@ -414,6 +420,76 @@ export declare namespace SelfMailerCreateParams { * or `outsideHTML` but not both. */ outsideTemplate: string; + + /** + * Enum representing the supported self-mailer sizes. + */ + size: '8.5x11_bifold' | '8.5x11_trifold' | '9.5x16_trifold'; + + /** + * The recipient of this order. You can either supply the contact information + * inline here or provide a contact ID. PostGrid will automatically deduplicate + * contacts regardless of whether you provide the information inline here or call + * the contact creation endpoint. + */ + to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * The mailing class of this order. If not provided, automatically set to + * `first_class`. + */ + mailingClass?: + | 'first_class' + | 'standard_class' + | 'express' + | 'certified' + | 'certified_return_receipt' + | 'registered' + | 'usps_first_class' + | 'usps_standard_class' + | 'usps_eddm' + | 'usps_express_2_day' + | 'usps_express_3_day' + | 'usps_first_class_certified' + | 'usps_first_class_certified_return_receipt' + | 'usps_first_class_registered' + | 'usps_express_3_day_signature_confirmation' + | 'usps_express_3_day_certified' + | 'usps_express_3_day_certified_return_receipt' + | 'ca_post_lettermail' + | 'ca_post_personalized' + | 'ca_post_neighbourhood_mail' + | 'ups_express_overnight' + | 'ups_express_2_day' + | 'ups_express_3_day' + | 'royal_mail_first_class' + | 'royal_mail_second_class' + | 'au_post_second_class'; + + /** + * These will be merged with the variables in the template or HTML you create this + * order with. The keys in this object should match the variable names in the + * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to + * PDFs uploaded with the order. + */ + mergeVariables?: { [key: string]: unknown }; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * This order will transition from `ready` to `printing` on the day after this + * date. You can use this parameter to schedule orders for a future date. + */ + sendDate?: string; } export interface SelfMailerCreateWithPdfurl { diff --git a/tests/api-resources/print-mail/campaigns.test.ts b/tests/api-resources/print-mail/campaigns.test.ts index 7828398..8e15fe3 100644 --- a/tests/api-resources/print-mail/campaigns.test.ts +++ b/tests/api-resources/print-mail/campaigns.test.ts @@ -55,6 +55,7 @@ describe('resource campaigns', () => { mailingClass: 'first_class', mergeVariables: { foo: 'bar' }, metadata: { foo: 'string' }, + paper: 'standard', pdf: 'https://example.com', perforatedPage: 1, returnEnvelope: 'returnEnvelope', @@ -69,7 +70,7 @@ describe('resource campaigns', () => { mailingClass: 'first_class', mergeVariables: { foo: 'bar' }, metadata: { foo: 'string' }, - paper: 'premium_paper_L6fw2k_N_j', + paper: 'standard', pdf: 'https://example.com', size: '6x4', }, diff --git a/tests/api-resources/print-mail/letters.test.ts b/tests/api-resources/print-mail/letters.test.ts index 7961fed..d649189 100644 --- a/tests/api-resources/print-mail/letters.test.ts +++ b/tests/api-resources/print-mail/letters.test.ts @@ -82,6 +82,7 @@ describe('resource letters', () => { mailingClass: 'first_class', mergeVariables: { foo: 'bar' }, metadata: { foo: 'bar' }, + paper: 'standard', perforatedPage: 1, plasticCard: { size: 'standard', diff --git a/tests/api-resources/print-mail/postcards.test.ts b/tests/api-resources/print-mail/postcards.test.ts index f0d13ec..bf2f144 100644 --- a/tests/api-resources/print-mail/postcards.test.ts +++ b/tests/api-resources/print-mail/postcards.test.ts @@ -76,7 +76,7 @@ describe('resource postcards', () => { mailingClass: 'first_class', mergeVariables: { foo: 'bar' }, metadata: { foo: 'bar' }, - paper: 'premium_paper_L6fw2k_N_j', + paper: 'standard', sendDate: '2019-12-27T18:11:19.117Z', }); }); From 76f555ade3f03eae1fbf31e8972b5761c77c8223 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:37:54 +0000 Subject: [PATCH 4/7] feat(api): update api with events and webhook capabilities --- .stats.yml | 8 +- api.md | 27 + src/client.ts | 3 - src/internal/qs/LICENSE.md | 13 + src/internal/qs/README.md | 3 + src/internal/qs/formats.ts | 10 + src/internal/qs/index.ts | 13 + src/internal/qs/stringify.ts | 385 +++ src/internal/qs/types.ts | 71 + src/internal/qs/utils.ts | 265 ++ src/internal/utils/query.ts | 20 +- src/resources/print-mail/events.ts | 124 + src/resources/print-mail/index.ts | 13 + src/resources/print-mail/print-mail.ts | 39 + src/resources/print-mail/webhooks.ts | 451 ++++ tests/api-resources/print-mail/events.test.ts | 38 + .../api-resources/print-mail/webhooks.test.ts | 129 + tests/qs/empty-keys-cases.ts | 271 ++ tests/qs/stringify.test.ts | 2232 +++++++++++++++++ tests/qs/utils.test.ts | 169 ++ tests/stringifyQuery.test.ts | 6 - 21 files changed, 4259 insertions(+), 31 deletions(-) create mode 100644 src/internal/qs/LICENSE.md create mode 100644 src/internal/qs/README.md create mode 100644 src/internal/qs/formats.ts create mode 100644 src/internal/qs/index.ts create mode 100644 src/internal/qs/stringify.ts create mode 100644 src/internal/qs/types.ts create mode 100644 src/internal/qs/utils.ts create mode 100644 src/resources/print-mail/events.ts create mode 100644 src/resources/print-mail/webhooks.ts create mode 100644 tests/api-resources/print-mail/events.test.ts create mode 100644 tests/api-resources/print-mail/webhooks.test.ts create mode 100644 tests/qs/empty-keys-cases.ts create mode 100644 tests/qs/stringify.test.ts create mode 100644 tests/qs/utils.test.ts diff --git a/.stats.yml b/.stats.yml index beca239..dd2b394 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-d00ba06c82004a1a431d4c1992fc13c65ed4ffc6bb4dcd06f4b1e10037b1cf60.yml -openapi_spec_hash: 45820a4d38f466f15dcbff2d011b098f -config_hash: 4429a41bb9161a59679a1a95b1ec7053 +configured_endpoints: 126 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-01345c918ca89a9697dad4fb9ef469d65f00a9443bc1046fcda4ab08d9e9224d.yml +openapi_spec_hash: 2bffa15225d43f8f9ae6d6ea5dccc6b4 +config_hash: 174a107082cfb0cbf120e23621f4345c diff --git a/api.md b/api.md index 58089d7..ea04f85 100644 --- a/api.md +++ b/api.md @@ -98,6 +98,33 @@ Methods: - client.printMail.trackers.delete(id) -> TrackerDeleteResponse - client.printMail.trackers.retrieveVisits(id, { ...params }) -> TrackerRetrieveVisitsResponsesSkipLimit +## Webhooks + +Types: + +- Webhook +- WebhookInvocation +- WebhookDeleteResponse + +Methods: + +- client.printMail.webhooks.create({ ...params }) -> Webhook +- client.printMail.webhooks.retrieve(id) -> Webhook +- client.printMail.webhooks.update(id, { ...params }) -> Webhook +- client.printMail.webhooks.list({ ...params }) -> WebhooksSkipLimit +- client.printMail.webhooks.delete(id) -> WebhookDeleteResponse +- client.printMail.webhooks.listInvocations(id, { ...params }) -> WebhookInvocationsSkipLimit + +## Events + +Types: + +- Event + +Methods: + +- client.printMail.events.list({ ...params }) -> EventsSkipLimit + ## Letters Types: diff --git a/src/client.ts b/src/client.ts index 17d4dc2..db47396 100644 --- a/src/client.ts +++ b/src/client.ts @@ -300,9 +300,6 @@ export class PostGrid { return buildHeaders([{ 'X-API-Key': this.printMailAPIKey }]); } - /** - * Basic re-implementation of `qs.stringify` for primitive types. - */ protected stringifyQuery(query: object | Record): string { return stringifyQuery(query); } diff --git a/src/internal/qs/LICENSE.md b/src/internal/qs/LICENSE.md new file mode 100644 index 0000000..3fda157 --- /dev/null +++ b/src/internal/qs/LICENSE.md @@ -0,0 +1,13 @@ +BSD 3-Clause License + +Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/puruvj/neoqs/graphs/contributors) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/internal/qs/README.md b/src/internal/qs/README.md new file mode 100644 index 0000000..67ae04e --- /dev/null +++ b/src/internal/qs/README.md @@ -0,0 +1,3 @@ +# qs + +This is a vendored version of [neoqs](https://github.com/PuruVJ/neoqs) which is a TypeScript rewrite of [qs](https://github.com/ljharb/qs), a query string library. diff --git a/src/internal/qs/formats.ts b/src/internal/qs/formats.ts new file mode 100644 index 0000000..e76a742 --- /dev/null +++ b/src/internal/qs/formats.ts @@ -0,0 +1,10 @@ +import type { Format } from './types'; + +export const default_format: Format = 'RFC3986'; +export const default_formatter = (v: PropertyKey) => String(v); +export const formatters: Record string> = { + RFC1738: (v: PropertyKey) => String(v).replace(/%20/g, '+'), + RFC3986: default_formatter, +}; +export const RFC1738 = 'RFC1738'; +export const RFC3986 = 'RFC3986'; diff --git a/src/internal/qs/index.ts b/src/internal/qs/index.ts new file mode 100644 index 0000000..c3a3620 --- /dev/null +++ b/src/internal/qs/index.ts @@ -0,0 +1,13 @@ +import { default_format, formatters, RFC1738, RFC3986 } from './formats'; + +const formats = { + formatters, + RFC1738, + RFC3986, + default: default_format, +}; + +export { stringify } from './stringify'; +export { formats }; + +export type { DefaultDecoder, DefaultEncoder, Format, ParseOptions, StringifyOptions } from './types'; diff --git a/src/internal/qs/stringify.ts b/src/internal/qs/stringify.ts new file mode 100644 index 0000000..7e71387 --- /dev/null +++ b/src/internal/qs/stringify.ts @@ -0,0 +1,385 @@ +import { encode, is_buffer, maybe_map, has } from './utils'; +import { default_format, default_formatter, formatters } from './formats'; +import type { NonNullableProperties, StringifyOptions } from './types'; +import { isArray } from '../utils/values'; + +const array_prefix_generators = { + brackets(prefix: PropertyKey) { + return String(prefix) + '[]'; + }, + comma: 'comma', + indices(prefix: PropertyKey, key: string) { + return String(prefix) + '[' + key + ']'; + }, + repeat(prefix: PropertyKey) { + return String(prefix); + }, +}; + +const push_to_array = function (arr: any[], value_or_array: any) { + Array.prototype.push.apply(arr, isArray(value_or_array) ? value_or_array : [value_or_array]); +}; + +let toISOString; + +const defaults = { + addQueryPrefix: false, + allowDots: false, + allowEmptyArrays: false, + arrayFormat: 'indices', + charset: 'utf-8', + charsetSentinel: false, + delimiter: '&', + encode: true, + encodeDotInKeys: false, + encoder: encode, + encodeValuesOnly: false, + format: default_format, + formatter: default_formatter, + /** @deprecated */ + indices: false, + serializeDate(date) { + return (toISOString ??= Function.prototype.call.bind(Date.prototype.toISOString))(date); + }, + skipNulls: false, + strictNullHandling: false, +} as NonNullableProperties; + +function is_non_nullish_primitive(v: unknown): v is string | number | boolean | symbol | bigint { + return ( + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' || + typeof v === 'symbol' || + typeof v === 'bigint' + ); +} + +const sentinel = {}; + +function inner_stringify( + object: any, + prefix: PropertyKey, + generateArrayPrefix: StringifyOptions['arrayFormat'] | ((prefix: string, key: string) => string), + commaRoundTrip: boolean, + allowEmptyArrays: boolean, + strictNullHandling: boolean, + skipNulls: boolean, + encodeDotInKeys: boolean, + encoder: StringifyOptions['encoder'], + filter: StringifyOptions['filter'], + sort: StringifyOptions['sort'], + allowDots: StringifyOptions['allowDots'], + serializeDate: StringifyOptions['serializeDate'], + format: StringifyOptions['format'], + formatter: StringifyOptions['formatter'], + encodeValuesOnly: boolean, + charset: StringifyOptions['charset'], + sideChannel: WeakMap, +) { + let obj = object; + + let tmp_sc = sideChannel; + let step = 0; + let find_flag = false; + while ((tmp_sc = tmp_sc.get(sentinel)) !== void undefined && !find_flag) { + // Where object last appeared in the ref tree + const pos = tmp_sc.get(object); + step += 1; + if (typeof pos !== 'undefined') { + if (pos === step) { + throw new RangeError('Cyclic object value'); + } else { + find_flag = true; // Break while + } + } + if (typeof tmp_sc.get(sentinel) === 'undefined') { + step = 0; + } + } + + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate?.(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = maybe_map(obj, function (value) { + if (value instanceof Date) { + return serializeDate?.(value); + } + return value; + }); + } + + if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? + // @ts-expect-error + encoder(prefix, defaults.encoder, charset, 'key', format) + : prefix; + } + + obj = ''; + } + + if (is_non_nullish_primitive(obj) || is_buffer(obj)) { + if (encoder) { + const key_value = + encodeValuesOnly ? prefix + // @ts-expect-error + : encoder(prefix, defaults.encoder, charset, 'key', format); + return [ + formatter?.(key_value) + + '=' + + // @ts-expect-error + formatter?.(encoder(obj, defaults.encoder, charset, 'value', format)), + ]; + } + return [formatter?.(prefix) + '=' + formatter?.(String(obj))]; + } + + const values: string[] = []; + + if (typeof obj === 'undefined') { + return values; + } + + let obj_keys; + if (generateArrayPrefix === 'comma' && isArray(obj)) { + // we need to join elements in + if (encodeValuesOnly && encoder) { + // @ts-expect-error values only + obj = maybe_map(obj, encoder); + } + obj_keys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; + } else if (isArray(filter)) { + obj_keys = filter; + } else { + const keys = Object.keys(obj); + obj_keys = sort ? keys.sort(sort) : keys; + } + + const encoded_prefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix); + + const adjusted_prefix = + commaRoundTrip && isArray(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix; + + if (allowEmptyArrays && isArray(obj) && obj.length === 0) { + return adjusted_prefix + '[]'; + } + + for (let j = 0; j < obj_keys.length; ++j) { + const key = obj_keys[j]; + const value = + // @ts-ignore + typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key as any]; + + if (skipNulls && value === null) { + continue; + } + + // @ts-ignore + const encoded_key = allowDots && encodeDotInKeys ? (key as any).replace(/\./g, '%2E') : key; + const key_prefix = + isArray(obj) ? + typeof generateArrayPrefix === 'function' ? + generateArrayPrefix(adjusted_prefix, encoded_key) + : adjusted_prefix + : adjusted_prefix + (allowDots ? '.' + encoded_key : '[' + encoded_key + ']'); + + sideChannel.set(object, step); + const valueSideChannel = new WeakMap(); + valueSideChannel.set(sentinel, sideChannel); + push_to_array( + values, + inner_stringify( + value, + key_prefix, + generateArrayPrefix, + commaRoundTrip, + allowEmptyArrays, + strictNullHandling, + skipNulls, + encodeDotInKeys, + // @ts-ignore + generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder, + filter, + sort, + allowDots, + serializeDate, + format, + formatter, + encodeValuesOnly, + charset, + valueSideChannel, + ), + ); + } + + return values; +} + +function normalize_stringify_options( + opts: StringifyOptions = defaults, +): NonNullableProperties> & { indices?: boolean } { + if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') { + throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided'); + } + + if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') { + throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided'); + } + + if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + const charset = opts.charset || defaults.charset; + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + + let format = default_format; + if (typeof opts.format !== 'undefined') { + if (!has(formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; + } + const formatter = formatters[format]; + + let filter = defaults.filter; + if (typeof opts.filter === 'function' || isArray(opts.filter)) { + filter = opts.filter; + } + + let arrayFormat: StringifyOptions['arrayFormat']; + if (opts.arrayFormat && opts.arrayFormat in array_prefix_generators) { + arrayFormat = opts.arrayFormat; + } else if ('indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = defaults.arrayFormat; + } + + if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + + const allowDots = + typeof opts.allowDots === 'undefined' ? + !!opts.encodeDotInKeys === true ? + true + : defaults.allowDots + : !!opts.allowDots; + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + // @ts-ignore + allowDots: allowDots, + allowEmptyArrays: + typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, + arrayFormat: arrayFormat, + charset: charset, + charsetSentinel: + typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + commaRoundTrip: !!opts.commaRoundTrip, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encodeDotInKeys: + typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: + typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + format: format, + formatter: formatter, + serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate, + skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls, + // @ts-ignore + sort: typeof opts.sort === 'function' ? opts.sort : null, + strictNullHandling: + typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling, + }; +} + +export function stringify(object: any, opts: StringifyOptions = {}) { + let obj = object; + const options = normalize_stringify_options(opts); + + let obj_keys: PropertyKey[] | undefined; + let filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (isArray(options.filter)) { + filter = options.filter; + obj_keys = filter; + } + + const keys: string[] = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + const generateArrayPrefix = array_prefix_generators[options.arrayFormat]; + const commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip; + + if (!obj_keys) { + obj_keys = Object.keys(obj); + } + + if (options.sort) { + obj_keys.sort(options.sort); + } + + const sideChannel = new WeakMap(); + for (let i = 0; i < obj_keys.length; ++i) { + const key = obj_keys[i]!; + + if (options.skipNulls && obj[key] === null) { + continue; + } + push_to_array( + keys, + inner_stringify( + obj[key], + key, + // @ts-expect-error + generateArrayPrefix, + commaRoundTrip, + options.allowEmptyArrays, + options.strictNullHandling, + options.skipNulls, + options.encodeDotInKeys, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.format, + options.formatter, + options.encodeValuesOnly, + options.charset, + sideChannel, + ), + ); + } + + const joined = keys.join(options.delimiter); + let prefix = options.addQueryPrefix === true ? '?' : ''; + + if (options.charsetSentinel) { + if (options.charset === 'iso-8859-1') { + // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark + prefix += 'utf8=%26%2310003%3B&'; + } else { + // encodeURIComponent('✓') + prefix += 'utf8=%E2%9C%93&'; + } + } + + return joined.length > 0 ? prefix + joined : ''; +} diff --git a/src/internal/qs/types.ts b/src/internal/qs/types.ts new file mode 100644 index 0000000..7c28dbb --- /dev/null +++ b/src/internal/qs/types.ts @@ -0,0 +1,71 @@ +export type Format = 'RFC1738' | 'RFC3986'; + +export type DefaultEncoder = (str: any, defaultEncoder?: any, charset?: string) => string; +export type DefaultDecoder = (str: string, decoder?: any, charset?: string) => string; + +export type BooleanOptional = boolean | undefined; + +export type StringifyBaseOptions = { + delimiter?: string; + allowDots?: boolean; + encodeDotInKeys?: boolean; + strictNullHandling?: boolean; + skipNulls?: boolean; + encode?: boolean; + encoder?: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format?: Format, + ) => string; + filter?: Array | ((prefix: PropertyKey, value: any) => any); + arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma'; + indices?: boolean; + sort?: ((a: PropertyKey, b: PropertyKey) => number) | null; + serializeDate?: (d: Date) => string; + format?: 'RFC1738' | 'RFC3986'; + formatter?: (str: PropertyKey) => string; + encodeValuesOnly?: boolean; + addQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + allowEmptyArrays?: boolean; + commaRoundTrip?: boolean; +}; + +export type StringifyOptions = StringifyBaseOptions; + +export type ParseBaseOptions = { + comma?: boolean; + delimiter?: string | RegExp; + depth?: number | false; + decoder?: (str: string, defaultDecoder: DefaultDecoder, charset: string, type: 'key' | 'value') => any; + arrayLimit?: number; + parseArrays?: boolean; + plainObjects?: boolean; + allowPrototypes?: boolean; + allowSparse?: boolean; + parameterLimit?: number; + strictDepth?: boolean; + strictNullHandling?: boolean; + ignoreQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + interpretNumericEntities?: boolean; + allowEmptyArrays?: boolean; + duplicates?: 'combine' | 'first' | 'last'; + allowDots?: boolean; + decodeDotInKeys?: boolean; +}; + +export type ParseOptions = ParseBaseOptions; + +export type ParsedQs = { + [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; +}; + +// Type to remove null or undefined union from each property +export type NonNullableProperties = { + [K in keyof T]-?: Exclude; +}; diff --git a/src/internal/qs/utils.ts b/src/internal/qs/utils.ts new file mode 100644 index 0000000..4cd5657 --- /dev/null +++ b/src/internal/qs/utils.ts @@ -0,0 +1,265 @@ +import { RFC1738 } from './formats'; +import type { DefaultEncoder, Format } from './types'; +import { isArray } from '../utils/values'; + +export let has = (obj: object, key: PropertyKey): boolean => ( + (has = (Object as any).hasOwn ?? Function.prototype.call.bind(Object.prototype.hasOwnProperty)), + has(obj, key) +); + +const hex_table = /* @__PURE__ */ (() => { + const array = []; + for (let i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +})(); + +function compact_queue>(queue: Array<{ obj: T; prop: string }>) { + while (queue.length > 1) { + const item = queue.pop(); + if (!item) continue; + + const obj = item.obj[item.prop]; + + if (isArray(obj)) { + const compacted: unknown[] = []; + + for (let j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + // @ts-ignore + item.obj[item.prop] = compacted; + } + } +} + +function array_to_object(source: any[], options: { plainObjects: boolean }) { + const obj = options && options.plainObjects ? Object.create(null) : {}; + for (let i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +} + +export function merge( + target: any, + source: any, + options: { plainObjects?: boolean; allowPrototypes?: boolean } = {}, +) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (isArray(target)) { + target.push(source); + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (!target || typeof target !== 'object') { + return [target].concat(source); + } + + let mergeTarget = target; + if (isArray(target) && !isArray(source)) { + // @ts-ignore + mergeTarget = array_to_object(target, options); + } + + if (isArray(target) && isArray(source)) { + source.forEach(function (item, i) { + if (has(target, i)) { + const targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + const value = source[key]; + + if (has(acc, key)) { + acc[key] = merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +} + +export function assign_single_source(target: any, source: any) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +} + +export function decode(str: string, _: any, charset: string) { + const strWithoutPlus = str.replace(/\+/g, ' '); + if (charset === 'iso-8859-1') { + // unescape never throws, no try...catch needed: + return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape); + } + // utf-8 + try { + return decodeURIComponent(strWithoutPlus); + } catch (e) { + return strWithoutPlus; + } +} + +const limit = 1024; + +export const encode: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format: Format, +) => string = (str, _defaultEncoder, charset, _kind, format: Format) => { + // This code was originally written by Brian White for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + let string = str; + if (typeof str === 'symbol') { + string = Symbol.prototype.toString.call(str); + } else if (typeof str !== 'string') { + string = String(str); + } + + if (charset === 'iso-8859-1') { + return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { + return '%26%23' + parseInt($0.slice(2), 16) + '%3B'; + }); + } + + let out = ''; + for (let j = 0; j < string.length; j += limit) { + const segment = string.length >= limit ? string.slice(j, j + limit) : string; + const arr = []; + + for (let i = 0; i < segment.length; ++i) { + let c = segment.charCodeAt(i); + if ( + c === 0x2d || // - + c === 0x2e || // . + c === 0x5f || // _ + c === 0x7e || // ~ + (c >= 0x30 && c <= 0x39) || // 0-9 + (c >= 0x41 && c <= 0x5a) || // a-z + (c >= 0x61 && c <= 0x7a) || // A-Z + (format === RFC1738 && (c === 0x28 || c === 0x29)) // ( ) + ) { + arr[arr.length] = segment.charAt(i); + continue; + } + + if (c < 0x80) { + arr[arr.length] = hex_table[c]; + continue; + } + + if (c < 0x800) { + arr[arr.length] = hex_table[0xc0 | (c >> 6)]! + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + if (c < 0xd800 || c >= 0xe000) { + arr[arr.length] = + hex_table[0xe0 | (c >> 12)]! + hex_table[0x80 | ((c >> 6) & 0x3f)] + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3ff) << 10) | (segment.charCodeAt(i) & 0x3ff)); + + arr[arr.length] = + hex_table[0xf0 | (c >> 18)]! + + hex_table[0x80 | ((c >> 12) & 0x3f)] + + hex_table[0x80 | ((c >> 6) & 0x3f)] + + hex_table[0x80 | (c & 0x3f)]; + } + + out += arr.join(''); + } + + return out; +}; + +export function compact(value: any) { + const queue = [{ obj: { o: value }, prop: 'o' }]; + const refs = []; + + for (let i = 0; i < queue.length; ++i) { + const item = queue[i]; + // @ts-ignore + const obj = item.obj[item.prop]; + + const keys = Object.keys(obj); + for (let j = 0; j < keys.length; ++j) { + const key = keys[j]!; + const val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + compact_queue(queue); + + return value; +} + +export function is_regexp(obj: any) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +} + +export function is_buffer(obj: any) { + if (!obj || typeof obj !== 'object') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +} + +export function combine(a: any, b: any) { + return [].concat(a, b); +} + +export function maybe_map(val: T[], fn: (v: T) => T) { + if (isArray(val)) { + const mapped = []; + for (let i = 0; i < val.length; i += 1) { + mapped.push(fn(val[i]!)); + } + return mapped; + } + return fn(val); +} diff --git a/src/internal/utils/query.ts b/src/internal/utils/query.ts index 3003d8a..0139cac 100644 --- a/src/internal/utils/query.ts +++ b/src/internal/utils/query.ts @@ -1,23 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { PostGridError } from '../../core/error'; +import * as qs from '../qs/stringify'; -/** - * Basic re-implementation of `qs.stringify` for primitive types. - */ export function stringifyQuery(query: object | Record) { - return Object.entries(query) - .filter(([_, value]) => typeof value !== 'undefined') - .map(([key, value]) => { - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } - if (value === null) { - return `${encodeURIComponent(key)}=`; - } - throw new PostGridError( - `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, - ); - }) - .join('&'); + return qs.stringify(query, { arrayFormat: 'comma' }); } diff --git a/src/resources/print-mail/events.ts b/src/resources/print-mail/events.ts new file mode 100644 index 0000000..e0635c4 --- /dev/null +++ b/src/resources/print-mail/events.ts @@ -0,0 +1,124 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; + +/** + * View Events related to your orders. + * + * An event is created whenever a webhook is triggered. For example, if a webhook + * is created that listens to `letter.updated` events and the delivery status of a + * letter is updated, an event detailing the updated fields will get created. + */ +export class Events extends APIResource { + /** + * Retrieve a paginated list of Events. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const event of client.printMail.events.list()) { + * // ... + * } + * ``` + */ + list( + query: EventListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/print-mail/v1/events', SkipLimit, { query, ...options }); + } +} + +export type EventsSkipLimit = SkipLimit; + +export interface Event { + /** + * A unique ID prefixed with `event_`. + */ + id: string; + + /** + * The UTC time at which this event was created. + */ + createdAt: string; + + /** + * `true` if this is a live mode event else `false`. + */ + live: boolean; + + /** + * Always `event`. + */ + object: 'event'; + + /** + * The type of event that a Webhook can listen for and that an Event represents. + */ + type: + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created'; + + /** + * The data of the resource associated with this event. + */ + data?: { [key: string]: unknown }; + + /** + * A record containing the updated fields of the resource associated with this + * event. + */ + updatedFields?: { [key: string]: unknown }; +} + +export interface EventListParams extends SkipLimitParams { + /** + * An optional list of event types to filter the results by. + */ + type?: Array< + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created' + >; +} + +export declare namespace Events { + export { + type Event as Event, + type EventsSkipLimit as EventsSkipLimit, + type EventListParams as EventListParams, + }; +} diff --git a/src/resources/print-mail/index.ts b/src/resources/print-mail/index.ts index 4a40b58..83fc2c4 100644 --- a/src/resources/print-mail/index.ts +++ b/src/resources/print-mail/index.ts @@ -52,6 +52,7 @@ export { type ContactListParams, type ContactsSkipLimit, } from './contacts'; +export { Events, type Event, type EventListParams, type EventsSkipLimit } from './events'; export { Letters, type AddressPlacement, @@ -195,3 +196,15 @@ export { type VirtualMailboxListParams, type VirtualMailboxListResponsesSkipLimit, } from './virtual-mailboxes/index'; +export { + Webhooks, + type Webhook, + type WebhookInvocation, + type WebhookDeleteResponse, + type WebhookCreateParams, + type WebhookUpdateParams, + type WebhookListParams, + type WebhookListInvocationsParams, + type WebhooksSkipLimit, + type WebhookInvocationsSkipLimit, +} from './webhooks'; diff --git a/src/resources/print-mail/print-mail.ts b/src/resources/print-mail/print-mail.ts index c707dfd..85ea6dd 100644 --- a/src/resources/print-mail/print-mail.ts +++ b/src/resources/print-mail/print-mail.ts @@ -58,6 +58,8 @@ import { Contacts, ContactsSkipLimit, } from './contacts'; +import * as EventsAPI from './events'; +import { Event, EventListParams, Events, EventsSkipLimit } from './events'; import * as LettersAPI from './letters'; import { AddressPlacement, @@ -177,6 +179,19 @@ import { TrackerUpdateResponse, Trackers, } from './trackers'; +import * as WebhooksAPI from './webhooks'; +import { + Webhook, + WebhookCreateParams, + WebhookDeleteResponse, + WebhookInvocation, + WebhookInvocationsSkipLimit, + WebhookListInvocationsParams, + WebhookListParams, + WebhookUpdateParams, + Webhooks, + WebhooksSkipLimit, +} from './webhooks'; import * as ReportsAPI from './reports/reports'; import { DeletedResponse, @@ -218,6 +233,8 @@ export class PrintMail extends APIResource { contacts: ContactsAPI.Contacts = new ContactsAPI.Contacts(this._client); templates: TemplatesAPI.Templates = new TemplatesAPI.Templates(this._client); trackers: TrackersAPI.Trackers = new TrackersAPI.Trackers(this._client); + webhooks: WebhooksAPI.Webhooks = new WebhooksAPI.Webhooks(this._client); + events: EventsAPI.Events = new EventsAPI.Events(this._client); letters: LettersAPI.Letters = new LettersAPI.Letters(this._client); postcards: PostcardsAPI.Postcards = new PostcardsAPI.Postcards(this._client); bankAccounts: BankAccountsAPI.BankAccounts = new BankAccountsAPI.BankAccounts(this._client); @@ -247,6 +264,8 @@ export class PrintMail extends APIResource { PrintMail.Contacts = Contacts; PrintMail.Templates = Templates; PrintMail.Trackers = Trackers; +PrintMail.Webhooks = Webhooks; +PrintMail.Events = Events; PrintMail.Letters = Letters; PrintMail.Postcards = Postcards; PrintMail.BankAccounts = BankAccounts; @@ -302,6 +321,26 @@ export declare namespace PrintMail { type TrackerRetrieveVisitsParams as TrackerRetrieveVisitsParams, }; + export { + Webhooks as Webhooks, + type Webhook as Webhook, + type WebhookInvocation as WebhookInvocation, + type WebhookDeleteResponse as WebhookDeleteResponse, + type WebhooksSkipLimit as WebhooksSkipLimit, + type WebhookInvocationsSkipLimit as WebhookInvocationsSkipLimit, + type WebhookCreateParams as WebhookCreateParams, + type WebhookUpdateParams as WebhookUpdateParams, + type WebhookListParams as WebhookListParams, + type WebhookListInvocationsParams as WebhookListInvocationsParams, + }; + + export { + Events as Events, + type Event as Event, + type EventsSkipLimit as EventsSkipLimit, + type EventListParams as EventListParams, + }; + export { Letters as Letters, type AddressPlacement as AddressPlacement, diff --git a/src/resources/print-mail/webhooks.ts b/src/resources/print-mail/webhooks.ts new file mode 100644 index 0000000..fd8a19c --- /dev/null +++ b/src/resources/print-mail/webhooks.ts @@ -0,0 +1,451 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +/** + * Create and manage Webhooks. + * + * Webhooks can be used to notify your application when events occur in PostGrid. + * For example, you may use a `letter.updated` webhook to receive a notification + * when a letter has been processed for delivery. + * + * Every webhook has a `secret` and this is used to sign the payload of the event. + * + * You can choose what format you want the payload to be delivered in. By default, + * the webhook payload will be delivered as a [JSON Web Token](https://jwt.io/). + * When you receive the event, you can verify it using a JWT library available for + * your particular language (using the HMAC SHA256 Algorithm). There are + * [many](https://jwt.io/#libraries-io) off-the-shelf solutions you can use. + * + * You can alternatively choose to receive a JSON payload. In this case, you'll + * also receive a `PostGrid-Signature` HTTP header along with the payload. + * + * You must respond with a `200` status from your webhook. Otherwise, PostGrid + * will retry the webhook up to 3 times. First, after 1 hour, then 2 hours, then + * 4 hours. We will also keep track of every invocation and its response status. + * You can retrieve data about prior invocations using the webhook invocations + * list endpoint below. + */ +export class Webhooks extends APIResource { + /** + * Create a Webhook. + * + * @example + * ```ts + * const webhook = await client.printMail.webhooks.create({ + * enabledEvents: ['letter.created'], + * url: 'https://example.com/postgrid-webhook', + * description: 'Letter Created', + * }); + * ``` + */ + create(body: WebhookCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/print-mail/v1/webhooks', { body, ...options }); + } + + /** + * Retrieve a Webhook by ID. + * + * @example + * ```ts + * const webhook = await client.printMail.webhooks.retrieve( + * 'id', + * ); + * ``` + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/print-mail/v1/webhooks/${id}`, options); + } + + /** + * Update a Webhook by ID. + * + * @example + * ```ts + * const webhook = await client.printMail.webhooks.update( + * 'id', + * { + * description: 'Letter creates and updates', + * enabled: true, + * enabledEvents: ['letter.created', 'letter.updated'], + * url: 'https://example.com/postgrid-webhook', + * }, + * ); + * ``` + */ + update(id: string, body: WebhookUpdateParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/print-mail/v1/webhooks/${id}`, { body, ...options }); + } + + /** + * Retrieve a paginated list of Webhooks. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const webhook of client.printMail.webhooks.list()) { + * // ... + * } + * ``` + */ + list( + query: WebhookListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/print-mail/v1/webhooks', SkipLimit, { query, ...options }); + } + + /** + * Delete a Webhook by ID. Note that this operation cannot be undone. + * + * @example + * ```ts + * const webhook = await client.printMail.webhooks.delete( + * 'id', + * ); + * ``` + */ + delete(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/print-mail/v1/webhooks/${id}`, options); + } + + /** + * Retrieve a paginated list of invocations for a Webhook. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const webhookInvocation of client.printMail.webhooks.listInvocations( + * 'id', + * )) { + * // ... + * } + * ``` + */ + listInvocations( + id: string, + query: WebhookListInvocationsParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList( + path`/print-mail/v1/webhooks/${id}/invocations`, + SkipLimit, + { query, ...options }, + ); + } +} + +export type WebhooksSkipLimit = SkipLimit; + +export type WebhookInvocationsSkipLimit = SkipLimit; + +export interface Webhook { + /** + * A unique ID prefixed with webhook\_ + */ + id: string; + + /** + * The UTC time at which this resource was created. + */ + createdAt: string; + + /** + * Whether this webhook is enabled. Disabled webhooks are not triggered. + */ + enabled: boolean; + + /** + * The list of event types this webhook listens for. + */ + enabledEvents: Array< + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created' + >; + + /** + * `true` if this is a live mode resource else `false`. + */ + live: boolean; + + /** + * Always `webhook`. + */ + object: 'webhook'; + + /** + * The UTC time at which this resource was last updated. + */ + updatedAt: string; + + /** + * An HTTPS URL that PostGrid can invoke for webhook deliveries. + */ + url: string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * The format in which a Webhook's event payload is delivered. + */ + payloadFormat?: 'jwt' | 'json'; + + /** + * A webhook signing secret with at least 20 non-whitespace characters. + */ + secret?: string; +} + +export interface WebhookInvocation { + /** + * A unique ID prefixed with `webhook_invocation_`. + */ + id: string; + + /** + * The UTC time at which this invocation was created. + */ + createdAt: string; + + /** + * The ID of the event that was delivered in this invocation. + */ + event: string; + + /** + * Always `webhook_invocation`. + */ + object: 'webhook_invocation'; + + /** + * The HTTP status code returned by your endpoint for this invocation. + */ + statusCode: number; + + /** + * The type of event that a Webhook can listen for and that an Event represents. + */ + type: + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created'; + + /** + * The UTC time at which this invocation was last updated. + */ + updatedAt: string; + + /** + * The ID of the webhook that was invoked. + */ + webhook: string; + + /** + * The ID of the order associated with this invocation, if the event was + * order-related. + */ + orderID?: string; +} + +export interface WebhookDeleteResponse { + /** + * A unique ID prefixed with webhook\_ + */ + id: string; + + deleted: true; + + /** + * Always `webhook`. + */ + object: 'webhook'; +} + +export interface WebhookCreateParams { + /** + * The list of event types this webhook listens for. + */ + enabledEvents: Array< + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created' + >; + + /** + * An HTTPS URL that PostGrid can invoke for webhook deliveries. + */ + url: string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * The format in which a Webhook's event payload is delivered. + */ + payloadFormat?: 'jwt' | 'json'; + + /** + * A webhook signing secret with at least 20 non-whitespace characters. + */ + secret?: string; +} + +export interface WebhookUpdateParams { + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * Whether this webhook is enabled. Disabled webhooks are not triggered. + */ + enabled?: boolean; + + /** + * The list of event types this webhook listens for. + */ + enabledEvents?: Array< + | 'letter.created' + | 'letter.updated' + | 'postcard.created' + | 'postcard.updated' + | 'self_mailer.created' + | 'self_mailer.updated' + | 'cheque.created' + | 'cheque.updated' + | 'box.created' + | 'box.updated' + | 'snap_pack.created' + | 'snap_pack.updated' + | 'return_envelope_order.created' + | 'return_envelope_order.updated' + | 'tracker.visited' + | 'campaign.created' + | 'campaign.updated' + | 'virtual_mailbox_item.created' + >; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * The format in which a Webhook's event payload is delivered. + */ + payloadFormat?: 'jwt' | 'json'; + + /** + * A webhook signing secret with at least 20 non-whitespace characters. + */ + secret?: string; + + /** + * An HTTPS URL that PostGrid can invoke for webhook deliveries. + */ + url?: string; +} + +export interface WebhookListParams extends SkipLimitParams { + /** + * You can supply any string to help narrow down the list of resources. For + * example, if you pass `"New York"` (quoted), it will return resources that have + * that string present somewhere in their response. Alternatively, you can supply a + * structured search query. See the documentation on `StructuredSearchQuery` for + * more details. + */ + search?: string; +} + +export interface WebhookListInvocationsParams extends SkipLimitParams { + /** + * You can supply any string to help narrow down the list of resources. For + * example, if you pass `"New York"` (quoted), it will return resources that have + * that string present somewhere in their response. Alternatively, you can supply a + * structured search query. See the documentation on `StructuredSearchQuery` for + * more details. + */ + search?: string; +} + +export declare namespace Webhooks { + export { + type Webhook as Webhook, + type WebhookInvocation as WebhookInvocation, + type WebhookDeleteResponse as WebhookDeleteResponse, + type WebhooksSkipLimit as WebhooksSkipLimit, + type WebhookInvocationsSkipLimit as WebhookInvocationsSkipLimit, + type WebhookCreateParams as WebhookCreateParams, + type WebhookUpdateParams as WebhookUpdateParams, + type WebhookListParams as WebhookListParams, + type WebhookListInvocationsParams as WebhookListInvocationsParams, + }; +} diff --git a/tests/api-resources/print-mail/events.test.ts b/tests/api-resources/print-mail/events.test.ts new file mode 100644 index 0000000..49b6b8f --- /dev/null +++ b/tests/api-resources/print-mail/events.test.ts @@ -0,0 +1,38 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import PostGrid from 'postgrid-node'; + +const client = new PostGrid({ + addressVerificationAPIKey: 'My Address Verification API Key', + printMailAPIKey: 'My Print Mail API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource events', () => { + // Mock server tests are disabled + test.skip('list', async () => { + const responsePromise = client.printMail.events.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.printMail.events.list( + { + limit: 0, + skip: 0, + type: ['letter.created'], + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(PostGrid.NotFoundError); + }); +}); diff --git a/tests/api-resources/print-mail/webhooks.test.ts b/tests/api-resources/print-mail/webhooks.test.ts new file mode 100644 index 0000000..70ec100 --- /dev/null +++ b/tests/api-resources/print-mail/webhooks.test.ts @@ -0,0 +1,129 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import PostGrid from 'postgrid-node'; + +const client = new PostGrid({ + addressVerificationAPIKey: 'My Address Verification API Key', + printMailAPIKey: 'My Print Mail API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource webhooks', () => { + // Mock server tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.printMail.webhooks.create({ + enabledEvents: ['letter.created'], + url: 'https://example.com/postgrid-webhook', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.printMail.webhooks.create({ + enabledEvents: ['letter.created'], + url: 'https://example.com/postgrid-webhook', + description: 'Letter Created', + metadata: { foo: 'bar' }, + payloadFormat: 'jwt', + secret: 'xxxxxxxxxxxxxxxxxxxx', + }); + }); + + // Mock server tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.printMail.webhooks.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('update', async () => { + const responsePromise = client.printMail.webhooks.update('id', {}); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list', async () => { + const responsePromise = client.printMail.webhooks.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.printMail.webhooks.list( + { + limit: 0, + search: 'search', + skip: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(PostGrid.NotFoundError); + }); + + // Mock server tests are disabled + test.skip('delete', async () => { + const responsePromise = client.printMail.webhooks.delete('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('listInvocations', async () => { + const responsePromise = client.printMail.webhooks.listInvocations('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('listInvocations: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.printMail.webhooks.listInvocations( + 'id', + { + limit: 0, + search: 'search', + skip: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(PostGrid.NotFoundError); + }); +}); diff --git a/tests/qs/empty-keys-cases.ts b/tests/qs/empty-keys-cases.ts new file mode 100644 index 0000000..ea2c1b0 --- /dev/null +++ b/tests/qs/empty-keys-cases.ts @@ -0,0 +1,271 @@ +export const empty_test_cases = [ + { + input: '&', + with_empty_keys: {}, + stringify_output: { + brackets: '', + indices: '', + repeat: '', + }, + no_empty_keys: {}, + }, + { + input: '&&', + with_empty_keys: {}, + stringify_output: { + brackets: '', + indices: '', + repeat: '', + }, + no_empty_keys: {}, + }, + { + input: '&=', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '&=&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '&=&=', + with_empty_keys: { '': ['', ''] }, + stringify_output: { + brackets: '[]=&[]=', + indices: '[0]=&[1]=', + repeat: '=&=', + }, + no_empty_keys: {}, + }, + { + input: '&=&=&', + with_empty_keys: { '': ['', ''] }, + stringify_output: { + brackets: '[]=&[]=', + indices: '[0]=&[1]=', + repeat: '=&=', + }, + no_empty_keys: {}, + }, + { + input: '=', + with_empty_keys: { '': '' }, + no_empty_keys: {}, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + }, + { + input: '=&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '=&&&', + with_empty_keys: { '': '' }, + stringify_output: { + brackets: '=', + indices: '=', + repeat: '=', + }, + no_empty_keys: {}, + }, + { + input: '=&=&=&', + with_empty_keys: { '': ['', '', ''] }, + stringify_output: { + brackets: '[]=&[]=&[]=', + indices: '[0]=&[1]=&[2]=', + repeat: '=&=&=', + }, + no_empty_keys: {}, + }, + { + input: '=&a[]=b&a[1]=c', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: '=a', + with_empty_keys: { '': 'a' }, + no_empty_keys: {}, + stringify_output: { + brackets: '=a', + indices: '=a', + repeat: '=a', + }, + }, + { + input: 'a==a', + with_empty_keys: { a: '=a' }, + no_empty_keys: { a: '=a' }, + stringify_output: { + brackets: 'a==a', + indices: 'a==a', + repeat: 'a==a', + }, + }, + { + input: '=&a[]=b', + with_empty_keys: { '': '', a: ['b'] }, + stringify_output: { + brackets: '=&a[]=b', + indices: '=&a[0]=b', + repeat: '=&a=b', + }, + no_empty_keys: { a: ['b'] }, + }, + { + input: '=&a[]=b&a[]=c&a[2]=d', + with_empty_keys: { '': '', a: ['b', 'c', 'd'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c&a[]=d', + indices: '=&a[0]=b&a[1]=c&a[2]=d', + repeat: '=&a=b&a=c&a=d', + }, + no_empty_keys: { a: ['b', 'c', 'd'] }, + }, + { + input: '=a&=b', + with_empty_keys: { '': ['a', 'b'] }, + stringify_output: { + brackets: '[]=a&[]=b', + indices: '[0]=a&[1]=b', + repeat: '=a&=b', + }, + no_empty_keys: {}, + }, + { + input: '=a&foo=b', + with_empty_keys: { '': 'a', foo: 'b' }, + no_empty_keys: { foo: 'b' }, + stringify_output: { + brackets: '=a&foo=b', + indices: '=a&foo=b', + repeat: '=a&foo=b', + }, + }, + { + input: 'a[]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a[]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a[0]=b&a=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a=b&a[]=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: 'a=b&a[0]=c&=', + with_empty_keys: { '': '', a: ['b', 'c'] }, + stringify_output: { + brackets: '=&a[]=b&a[]=c', + indices: '=&a[0]=b&a[1]=c', + repeat: '=&a=b&a=c', + }, + no_empty_keys: { a: ['b', 'c'] }, + }, + { + input: '[]=a&[]=b& []=1', + with_empty_keys: { '': ['a', 'b'], ' ': ['1'] }, + stringify_output: { + brackets: '[]=a&[]=b& []=1', + indices: '[0]=a&[1]=b& [0]=1', + repeat: '=a&=b& =1', + }, + no_empty_keys: { 0: 'a', 1: 'b', ' ': ['1'] }, + }, + { + input: '[0]=a&[1]=b&a[0]=1&a[1]=2', + with_empty_keys: { '': ['a', 'b'], a: ['1', '2'] }, + no_empty_keys: { 0: 'a', 1: 'b', a: ['1', '2'] }, + stringify_output: { + brackets: '[]=a&[]=b&a[]=1&a[]=2', + indices: '[0]=a&[1]=b&a[0]=1&a[1]=2', + repeat: '=a&=b&a=1&a=2', + }, + }, + { + input: '[deep]=a&[deep]=2', + with_empty_keys: { '': { deep: ['a', '2'] } }, + stringify_output: { + brackets: '[deep][]=a&[deep][]=2', + indices: '[deep][0]=a&[deep][1]=2', + repeat: '[deep]=a&[deep]=2', + }, + no_empty_keys: { deep: ['a', '2'] }, + }, + { + input: '%5B0%5D=a&%5B1%5D=b', + with_empty_keys: { '': ['a', 'b'] }, + stringify_output: { + brackets: '[]=a&[]=b', + indices: '[0]=a&[1]=b', + repeat: '=a&=b', + }, + no_empty_keys: { 0: 'a', 1: 'b' }, + }, +] satisfies { + input: string; + with_empty_keys: Record; + stringify_output: { + brackets: string; + indices: string; + repeat: string; + }; + no_empty_keys: Record; +}[]; diff --git a/tests/qs/stringify.test.ts b/tests/qs/stringify.test.ts new file mode 100644 index 0000000..d4a3849 --- /dev/null +++ b/tests/qs/stringify.test.ts @@ -0,0 +1,2232 @@ +import iconv from 'iconv-lite'; +import { stringify } from 'postgrid-node/internal/qs'; +import { encode } from 'postgrid-node/internal/qs/utils'; +import { StringifyOptions } from 'postgrid-node/internal/qs/types'; +import { empty_test_cases } from './empty-keys-cases'; +import assert from 'assert'; + +describe('stringify()', function () { + test('stringifies a querystring object', function () { + expect(stringify({ a: 'b' })).toBe('a=b'); + expect(stringify({ a: 1 })).toBe('a=1'); + expect(stringify({ a: 1, b: 2 })).toBe('a=1&b=2'); + expect(stringify({ a: 'A_Z' })).toBe('a=A_Z'); + expect(stringify({ a: '€' })).toBe('a=%E2%82%AC'); + expect(stringify({ a: '' })).toBe('a=%EE%80%80'); + expect(stringify({ a: 'א' })).toBe('a=%D7%90'); + expect(stringify({ a: '𐐷' })).toBe('a=%F0%90%90%B7'); + }); + + test('stringifies falsy values', function () { + expect(stringify(undefined)).toBe(''); + expect(stringify(null)).toBe(''); + expect(stringify(null, { strictNullHandling: true })).toBe(''); + expect(stringify(false)).toBe(''); + expect(stringify(0)).toBe(''); + }); + + test('stringifies symbols', function () { + expect(stringify(Symbol.iterator)).toBe(''); + expect(stringify([Symbol.iterator])).toBe('0=Symbol%28Symbol.iterator%29'); + expect(stringify({ a: Symbol.iterator })).toBe('a=Symbol%28Symbol.iterator%29'); + expect(stringify({ a: [Symbol.iterator] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=Symbol%28Symbol.iterator%29', + ); + }); + + test('stringifies bigints', function () { + var three = BigInt(3); + // @ts-expect-error + var encodeWithN = function (value, defaultEncoder, charset) { + var result = defaultEncoder(value, defaultEncoder, charset); + return typeof value === 'bigint' ? result + 'n' : result; + }; + + expect(stringify(three)).toBe(''); + expect(stringify([three])).toBe('0=3'); + expect(stringify([three], { encoder: encodeWithN })).toBe('0=3n'); + expect(stringify({ a: three })).toBe('a=3'); + expect(stringify({ a: three }, { encoder: encodeWithN })).toBe('a=3n'); + expect(stringify({ a: [three] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a[]=3'); + expect( + stringify({ a: [three] }, { encodeValuesOnly: true, encoder: encodeWithN, arrayFormat: 'brackets' }), + ).toBe('a[]=3n'); + }); + + test('encodes dot in key of object when encodeDotInKeys and allowDots is provided', function () { + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: false, encodeDotInKeys: false }), + ).toBe('name.obj%5Bfirst%5D=John&name.obj%5Blast%5D=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: false }), + ).toBe('name.obj.first=John&name.obj.last=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: false, encodeDotInKeys: true }), + ).toBe('name%252Eobj%5Bfirst%5D=John&name%252Eobj%5Blast%5D=Doe'); + expect( + stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: true }), + ).toBe('name%252Eobj.first=John&name%252Eobj.last=Doe'); + + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: false, encodeDotInKeys: false }, + // ), + // 'name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe', + // 'with allowDots false and encodeDotInKeys false', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: false }, + // ), + // 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe', + // 'with allowDots false and encodeDotInKeys false', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: false, encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe', + // 'with allowDots false and encodeDotInKeys true', + // ); + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', + // 'with allowDots true and encodeDotInKeys true', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: false, encodeDotInKeys: false }, + ), + ).toBe('name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: false }, + ), + ).toBe('name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: false, encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe'); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe'); + }); + + test('should encode dot in key of object, and automatically set allowDots to `true` when encodeDotInKeys is true and allowDots in undefined', function () { + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { encodeDotInKeys: true }, + // ), + // 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', + // 'with allowDots undefined and encodeDotInKeys true', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { encodeDotInKeys: true }, + ), + ).toBe('name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe'); + }); + + test('should encode dot in key of object when encodeDotInKeys and allowDots is provided, and nothing else when encodeValuesOnly is provided', function () { + // st.equal( + // stringify( + // { 'name.obj': { first: 'John', last: 'Doe' } }, + // { + // encodeDotInKeys: true, + // allowDots: true, + // encodeValuesOnly: true, + // }, + // ), + // 'name%2Eobj.first=John&name%2Eobj.last=Doe', + // ); + expect( + stringify( + { 'name.obj': { first: 'John', last: 'Doe' } }, + { + encodeDotInKeys: true, + allowDots: true, + encodeValuesOnly: true, + }, + ), + ).toBe('name%2Eobj.first=John&name%2Eobj.last=Doe'); + + // st.equal( + // stringify( + // { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + // { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }, + // ), + // 'name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe', + // ); + expect( + stringify( + { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, + { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }, + ), + ).toBe('name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe'); + }); + + test('throws when `commaRoundTrip` is not a boolean', function () { + // st['throws']( + // function () { + // stringify({}, { commaRoundTrip: 'not a boolean' }); + // }, + // TypeError, + // 'throws when `commaRoundTrip` is not a boolean', + // ); + expect(() => { + // @ts-expect-error + stringify({}, { commaRoundTrip: 'not a boolean' }); + }).toThrow(TypeError); + }); + + test('throws when `encodeDotInKeys` is not a boolean', function () { + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); + }).toThrow(TypeError); + }); + + test('adds query prefix', function () { + // st.equal(stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); + expect(stringify({ a: 'b' }, { addQueryPrefix: true })).toBe('?a=b'); + }); + + test('with query prefix, outputs blank string given an empty object', function () { + // st.equal(stringify({}, { addQueryPrefix: true }), ''); + expect(stringify({}, { addQueryPrefix: true })).toBe(''); + }); + + test('stringifies nested falsy values', function () { + // st.equal(stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D='); + // st.equal( + // stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), + // 'a%5Bb%5D%5Bc%5D', + // ); + // st.equal(stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false'); + expect(stringify({ a: { b: { c: null } } })).toBe('a%5Bb%5D%5Bc%5D='); + expect(stringify({ a: { b: { c: null } } }, { strictNullHandling: true })).toBe('a%5Bb%5D%5Bc%5D'); + expect(stringify({ a: { b: { c: false } } })).toBe('a%5Bb%5D%5Bc%5D=false'); + }); + + test('stringifies a nested object', function () { + // st.equal(stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); + // st.equal(stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e'); + expect(stringify({ a: { b: 'c' } })).toBe('a%5Bb%5D=c'); + expect(stringify({ a: { b: { c: { d: 'e' } } } })).toBe('a%5Bb%5D%5Bc%5D%5Bd%5D=e'); + }); + + test('`allowDots` option: stringifies a nested object with dots notation', function () { + // st.equal(stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c'); + // st.equal(stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e'); + expect(stringify({ a: { b: 'c' } }, { allowDots: true })).toBe('a.b=c'); + expect(stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true })).toBe('a.b.c.d=e'); + }); + + test('stringifies an array value', function () { + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }), + // 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }), + // 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' }), + // 'a=b%2Cc%2Cd', + // 'comma => comma', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true }), + // 'a=b%2Cc%2Cd', + // 'comma round trip => comma', + // ); + // st.equal( + // stringify({ a: ['b', 'c', 'd'] }), + // 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + // 'default => indices', + // ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' })).toBe( + 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', + ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' })).toBe( + 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d', + ); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' })).toBe('a=b%2Cc%2Cd'); + expect(stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true })).toBe( + 'a=b%2Cc%2Cd', + ); + expect(stringify({ a: ['b', 'c', 'd'] })).toBe('a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d'); + }); + + test('`skipNulls` option', function () { + // st.equal( + // stringify({ a: 'b', c: null }, { skipNulls: true }), + // 'a=b', + // 'omits nulls when asked', + // ); + expect(stringify({ a: 'b', c: null }, { skipNulls: true })).toBe('a=b'); + + // st.equal( + // stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), + // 'a%5Bb%5D=c', + // 'omits nested nulls when asked', + // ); + expect(stringify({ a: { b: 'c', d: null } }, { skipNulls: true })).toBe('a%5Bb%5D=c'); + }); + + test('omits array indices when asked', function () { + // st.equal(stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d'); + expect(stringify({ a: ['b', 'c', 'd'] }, { indices: false })).toBe('a=b&a=c&a=d'); + }); + + test('omits object key/value pair when value is empty array', function () { + // st.equal(stringify({ a: [], b: 'zz' }), 'b=zz'); + expect(stringify({ a: [], b: 'zz' })).toBe('b=zz'); + }); + + test('should not omit object key/value pair when value is empty array and when asked', function () { + // st.equal(stringify({ a: [], b: 'zz' }), 'b=zz'); + // st.equal(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false }), 'b=zz'); + // st.equal(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true }), 'a[]&b=zz'); + expect(stringify({ a: [], b: 'zz' })).toBe('b=zz'); + expect(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false })).toBe('b=zz'); + expect(stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true })).toBe('a[]&b=zz'); + }); + + test('should throw when allowEmptyArrays is not of type boolean', function () { + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); + }).toThrow(TypeError); + + // st['throws'](function () { + // stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); + // }, TypeError); + expect(() => { + // @ts-expect-error + stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); + }).toThrow(TypeError); + }); + + test('allowEmptyArrays + strictNullHandling', function () { + // st.equal( + // stringify({ testEmptyArray: [] }, { strictNullHandling: true, allowEmptyArrays: true }), + // 'testEmptyArray[]', + // ); + expect(stringify({ testEmptyArray: [] }, { strictNullHandling: true, allowEmptyArrays: true })).toBe( + 'testEmptyArray[]', + ); + }); + + describe('stringifies an array value with one item vs multiple items', function () { + test('non-array item', function () { + // s2t.equal( + // stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a=c', + // ); + // s2t.equal( + // stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a=c', + // ); + // s2t.equal(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c'); + // s2t.equal(stringify({ a: 'c' }, { encodeValuesOnly: true }), 'a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c'); + expect(stringify({ a: 'c' }, { encodeValuesOnly: true })).toBe('a=c'); + }); + + test('array with a single item', function () { + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0]=c', + // ); + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=c', + // ); + // s2t.equal( + // stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c', + // ); + // s2t.equal( + // stringify( + // { a: ['c'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a[]=c', + // ); // so it parses back as an array + // s2t.equal(stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe('a[0]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe('a[]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c'); + expect( + stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('a[]=c'); + expect(stringify({ a: ['c'] }, { encodeValuesOnly: true })).toBe('a[0]=c'); + }); + + test('array with multiple items', function () { + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0]=c&a[1]=d', + // ); + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=c&a[]=d', + // ); + // s2t.equal( + // stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c,d', + // ); + // s2t.equal( + // stringify( + // { a: ['c', 'd'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a=c,d', + // ); + // s2t.equal(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d'); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[0]=c&a[1]=d', + ); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=c&a[]=d', + ); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe('a=c,d'); + expect( + stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('a=c,d'); + expect(stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true })).toBe('a[0]=c&a[1]=d'); + }); + + test('array with multiple items with a comma inside', function () { + // s2t.equal( + // stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // 'a=c%2Cd,e', + // ); + // s2t.equal(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce'); + expect(stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe( + 'a=c%2Cd,e', + ); + expect(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' })).toBe('a=c%2Cd%2Ce'); + + // s2t.equal( + // stringify( + // { a: ['c,d', 'e'] }, + // { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'a=c%2Cd,e', + // ); + // s2t.equal( + // stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true }), + // 'a=c%2Cd%2Ce', + // ); + expect( + stringify( + { a: ['c,d', 'e'] }, + { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }, + ), + ).toBe('a=c%2Cd,e'); + expect(stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true })).toBe( + 'a=c%2Cd%2Ce', + ); + }); + }); + + test('stringifies a nested array value', function () { + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[b][0]=c&a[b][1]=d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[b][]=c&a[b][]=d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' })).toBe( + 'a[b]=c,d', + ); + expect(stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true })).toBe('a[b][0]=c&a[b][1]=d'); + }); + + test('stringifies comma and empty array values', function () { + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), + // 'a[0]=,&a[1]=&a[2]=c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), + // 'a[]=,&a[]=&a[]=c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), + // 'a=,,,c,d%', + // ); + // st.equal( + // stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), + // 'a=,&a=&a=c,d%', + // ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' })).toBe( + 'a[0]=,&a[1]=&a[2]=c,d%', + ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'a[]=,&a[]=&a[]=c,d%', + ); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' })).toBe('a=,,,c,d%'); + expect(stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' })).toBe( + 'a=,&a=&a=c,d%', + ); + + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[]=%2C&a[]=&a[]=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a=%2C,,c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&a=&a=c%2Cd%25', + // ); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), + ).toBe('a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[]=%2C&a[]=&a[]=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), + ).toBe('a=%2C%2C%2Cc%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + // ), + // 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + // ), + // 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + // ), + // 'a=%2C%2C%2Cc%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: [',', '', 'c,d%'] }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&a=&a=c%2Cd%25', + // ); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), + ).toBe('a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[]=%2C&a[]=&a[]=c%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), + ).toBe('a=%2C%2C%2Cc%2Cd%25'); + expect( + stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), + ).toBe('a=%2C&a=&a=c%2Cd%25'); + }); + + test('stringifies comma and empty non-array values', function () { + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), + // 'a=,&b=&c=c,d%', + // ); + // st.equal( + // stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), + // 'a=,&b=&c=c,d%', + // ); + expect(stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' })).toBe( + 'a=,&b=&c=c,d%', + ); + expect(stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'a=,&b=&c=c,d%', + ); + + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + // st.equal( + // stringify( + // { a: ',', b: '', c: 'c,d%' }, + // { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + // ), + // 'a=%2C&b=&c=c%2Cd%25', + // ); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + expect( + stringify( + { a: ',', b: '', c: 'c,d%' }, + { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }, + ), + ).toBe('a=%2C&b=&c=c%2Cd%25'); + }); + + test('stringifies a nested array value with dots notation', function () { + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a.b[0]=c&a.b[1]=d', + // 'indices: stringifies with dots + indices', + // ); + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a.b[]=c&a.b[]=d', + // 'brackets: stringifies with dots + brackets', + // ); + // st.equal( + // stringify( + // { a: { b: ['c', 'd'] } }, + // { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }, + // ), + // 'a.b=c,d', + // 'comma: stringifies with dots + comma', + // ); + // st.equal( + // stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true }), + // 'a.b[0]=c&a.b[1]=d', + // 'default: stringifies with dots + indices', + // ); + expect( + stringify( + { a: { b: ['c', 'd'] } }, + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a.b[0]=c&a.b[1]=d'); + expect( + stringify( + { a: { b: ['c', 'd'] } }, + { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a.b[]=c&a.b[]=d'); + expect( + stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }), + ).toBe('a.b=c,d'); + expect(stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encodeValuesOnly: true })).toBe( + 'a.b[0]=c&a.b[1]=d', + ); + }); + + test('stringifies an object inside an array', function () { + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), + // 'a[0][b]=c', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), + // 'a[b]=c', + // 'repeat => repeat', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), + // 'a[][b]=c', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true }), + // 'a[0][b]=c', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true })).toBe( + 'a[0][b]=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true })).toBe('a[b]=c'); + expect(stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true })).toBe( + 'a[][b]=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true })).toBe('a[0][b]=c'); + + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), + // 'a[0][b][c][0]=1', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), + // 'a[b][c]=1', + // 'repeat => repeat', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), + // 'a[][b][c][]=1', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true }), + // 'a[0][b][c][0]=1', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true })).toBe( + 'a[0][b][c][0]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true })).toBe( + 'a[b][c]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true })).toBe( + 'a[][b][c][]=1', + ); + expect(stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true })).toBe('a[0][b][c][0]=1'); + }); + + test('stringifies an array with mixed objects and primitives', function () { + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[0][b]=1&a[1]=2&a[2]=3', + // 'indices => indices', + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[][b]=1&a[]=2&a[]=3', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // '???', + // 'brackets => brackets', + // { skip: 'TODO: figure out what this should do' }, + // ); + // st.equal( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }), + // 'a[0][b]=1&a[1]=2&a[2]=3', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[0][b]=1&a[1]=2&a[2]=3', + ); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[][b]=1&a[]=2&a[]=3', + ); + // !Skipped: Figure out what this should do + // expect( + // stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), + // ).toBe('???'); + expect(stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true })).toBe('a[0][b]=1&a[1]=2&a[2]=3'); + }); + + test('stringifies an object inside an array with dots notation', function () { + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'indices' }), + // 'a[0].b=c', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: [{ b: 'c' }] }, + // { allowDots: true, encode: false, arrayFormat: 'brackets' }, + // ), + // 'a[].b=c', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false }), + // 'a[0].b=c', + // 'default => indices', + // ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'indices' })).toBe( + 'a[0].b=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false, arrayFormat: 'brackets' })).toBe( + 'a[].b=c', + ); + expect(stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false })).toBe('a[0].b=c'); + + // st.equal( + // stringify( + // { a: [{ b: { c: [1] } }] }, + // { allowDots: true, encode: false, arrayFormat: 'indices' }, + // ), + // 'a[0].b.c[0]=1', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: [{ b: { c: [1] } }] }, + // { allowDots: true, encode: false, arrayFormat: 'brackets' }, + // ), + // 'a[].b.c[]=1', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false }), + // 'a[0].b.c[0]=1', + // 'default => indices', + // ); + expect( + stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false, arrayFormat: 'indices' }), + ).toBe('a[0].b.c[0]=1'); + expect( + stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false, arrayFormat: 'brackets' }), + ).toBe('a[].b.c[]=1'); + expect(stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false })).toBe('a[0].b.c[0]=1'); + }); + + test('does not omit object keys when indices = false', function () { + // st.equal(stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c'); + expect(stringify({ a: [{ b: 'c' }] }, { indices: false })).toBe('a%5Bb%5D=c'); + }); + + test('uses indices notation for arrays when indices=true', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { indices: true })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses indices notation for arrays when no arrayFormat is specified', function () { + // st.equal(stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses indices notation for arrays when arrayFormat=indices', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })).toBe('a%5B0%5D=b&a%5B1%5D=c'); + }); + + test('uses repeat notation for arrays when arrayFormat=repeat', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })).toBe('a=b&a=c'); + }); + + test('uses brackets notation for arrays when arrayFormat=brackets', function () { + // st.equal(stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c'); + expect(stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })).toBe('a%5B%5D=b&a%5B%5D=c'); + }); + + test('stringifies a complicated object', function () { + // st.equal(stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e'); + expect(stringify({ a: { b: 'c', d: 'e' } })).toBe('a%5Bb%5D=c&a%5Bd%5D=e'); + }); + + test('stringifies an empty value', function () { + // st.equal(stringify({ a: '' }), 'a='); + // st.equal(stringify({ a: null }, { strictNullHandling: true }), 'a'); + expect(stringify({ a: '' })).toBe('a='); + expect(stringify({ a: null }, { strictNullHandling: true })).toBe('a'); + + // st.equal(stringify({ a: '', b: '' }), 'a=&b='); + // st.equal(stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b='); + expect(stringify({ a: '', b: '' })).toBe('a=&b='); + expect(stringify({ a: null, b: '' }, { strictNullHandling: true })).toBe('a&b='); + + // st.equal(stringify({ a: { b: '' } }), 'a%5Bb%5D='); + // st.equal(stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D'); + // st.equal(stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D='); + expect(stringify({ a: { b: '' } })).toBe('a%5Bb%5D='); + expect(stringify({ a: { b: null } }, { strictNullHandling: true })).toBe('a%5Bb%5D'); + expect(stringify({ a: { b: null } }, { strictNullHandling: false })).toBe('a%5Bb%5D='); + }); + + test('stringifies an empty array in different arrayFormat', function () { + // st.equal(stringify({ a: [], b: [null], c: 'c' }, { encode: false }), 'b[0]=&c=c'); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false })).toBe('b[0]=&c=c'); + // arrayFormat default + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), + // 'b[0]=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), + // 'b[]=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), + // 'b=&c=c', + // ); + // st.equal( + // stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), + // 'b=&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', commaRoundTrip: true }, + // ), + // 'b[]=&c=c', + // ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' })).toBe( + 'b[0]=&c=c', + ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' })).toBe( + 'b[]=&c=c', + ); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' })).toBe('b=&c=c'); + expect(stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' })).toBe('b=&c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', commaRoundTrip: true }), + ).toBe('b[]=&c=c'); + + // with strictNullHandling + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'indices', strictNullHandling: true }, + // ), + // 'b[0]&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'brackets', strictNullHandling: true }, + // ), + // 'b[]&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'repeat', strictNullHandling: true }, + // ), + // 'b&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', strictNullHandling: true }, + // ), + // 'b&c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }, + // ), + // 'b[]&c=c', + // ); + + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'indices', strictNullHandling: true }, + ), + ).toBe('b[0]&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'brackets', strictNullHandling: true }, + ), + ).toBe('b[]&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'repeat', strictNullHandling: true }, + ), + ).toBe('b&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'comma', strictNullHandling: true }, + ), + ).toBe('b&c=c'); + expect( + stringify( + { a: [], b: [null], c: 'c' }, + { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }, + ), + ).toBe('b[]&c=c'); + + // with skipNulls + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'indices', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'brackets', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'repeat', skipNulls: true }, + // ), + // 'c=c', + // ); + // st.equal( + // stringify( + // { a: [], b: [null], c: 'c' }, + // { encode: false, arrayFormat: 'comma', skipNulls: true }, + // ), + // 'c=c', + // ); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', skipNulls: true }), + ).toBe('c=c'); + expect( + stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', skipNulls: true }), + ).toBe('c=c'); + }); + + test('stringifies a null object', function () { + var obj = Object.create(null); + obj.a = 'b'; + // st.equal(stringify(obj), 'a=b'); + expect(stringify(obj)).toBe('a=b'); + }); + + test('returns an empty string for invalid input', function () { + // st.equal(stringify(undefined), ''); + // st.equal(stringify(false), ''); + // st.equal(stringify(null), ''); + // st.equal(stringify(''), ''); + expect(stringify(undefined)).toBe(''); + expect(stringify(false)).toBe(''); + expect(stringify(null)).toBe(''); + expect(stringify('')).toBe(''); + }); + + test('stringifies an object with a null object as a child', function () { + var obj = { a: Object.create(null) }; + + obj.a.b = 'c'; + // st.equal(stringify(obj), 'a%5Bb%5D=c'); + expect(stringify(obj)).toBe('a%5Bb%5D=c'); + }); + + test('drops keys with a value of undefined', function () { + // st.equal(stringify({ a: undefined }), ''); + expect(stringify({ a: undefined })).toBe(''); + + // st.equal( + // stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), + // 'a%5Bc%5D', + // ); + // st.equal( + // stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), + // 'a%5Bc%5D=', + // ); + // st.equal(stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D='); + expect(stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true })).toBe('a%5Bc%5D'); + expect(stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false })).toBe('a%5Bc%5D='); + expect(stringify({ a: { b: undefined, c: '' } })).toBe('a%5Bc%5D='); + }); + + test('url encodes values', function () { + // st.equal(stringify({ a: 'b c' }), 'a=b%20c'); + expect(stringify({ a: 'b c' })).toBe('a=b%20c'); + }); + + test('stringifies a date', function () { + var now = new Date(); + var str = 'a=' + encodeURIComponent(now.toISOString()); + // st.equal(stringify({ a: now }), str); + expect(stringify({ a: now })).toBe(str); + }); + + test('stringifies the weird object from qs', function () { + // st.equal( + // stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' }), + // 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F', + // ); + expect(stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' })).toBe( + 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F', + ); + }); + + // TODO: Investigate how to to intercept in vitest + // TODO(rob) + test('skips properties that are part of the object prototype', function () { + // st.intercept(Object.prototype, 'crash', { value: 'test' }); + // @ts-expect-error + Object.prototype.crash = 'test'; + + // st.equal(stringify({ a: 'b' }), 'a=b'); + // st.equal(stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); + expect(stringify({ a: 'b' })).toBe('a=b'); + expect(stringify({ a: { b: 'c' } })).toBe('a%5Bb%5D=c'); + }); + + test('stringifies boolean values', function () { + // st.equal(stringify({ a: true }), 'a=true'); + // st.equal(stringify({ a: { b: true } }), 'a%5Bb%5D=true'); + // st.equal(stringify({ b: false }), 'b=false'); + // st.equal(stringify({ b: { c: false } }), 'b%5Bc%5D=false'); + expect(stringify({ a: true })).toBe('a=true'); + expect(stringify({ a: { b: true } })).toBe('a%5Bb%5D=true'); + expect(stringify({ b: false })).toBe('b=false'); + expect(stringify({ b: { c: false } })).toBe('b%5Bc%5D=false'); + }); + + test('stringifies buffer values', function () { + // st.equal(stringify({ a: Buffer.from('test') }), 'a=test'); + // st.equal(stringify({ a: { b: Buffer.from('test') } }), 'a%5Bb%5D=test'); + }); + + test('stringifies an object using an alternative delimiter', function () { + // st.equal(stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d'); + expect(stringify({ a: 'b', c: 'd' }, { delimiter: ';' })).toBe('a=b;c=d'); + }); + + // We dont target environments which dont even have Buffer + // test('does not blow up when Buffer global is missing', function () { + // var restore = mockProperty(global, 'Buffer', { delete: true }); + + // var result = stringify({ a: 'b', c: 'd' }); + + // restore(); + + // st.equal(result, 'a=b&c=d'); + // st.end(); + // }); + + test('does not crash when parsing circular references', function () { + var a: any = {}; + a.b = a; + + // st['throws']( + // function () { + // stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); + // }, + // /RangeError: Cyclic object value/, + // 'cyclic values throw', + // ); + expect(() => { + stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); + }).toThrow('Cyclic object value'); + + var circular: any = { + a: 'value', + }; + circular.a = circular; + // st['throws']( + // function () { + // stringify(circular); + // }, + // /RangeError: Cyclic object value/, + // 'cyclic values throw', + // ); + expect(() => { + stringify(circular); + }).toThrow('Cyclic object value'); + + var arr = ['a']; + // st.doesNotThrow(function () { + // stringify({ x: arr, y: arr }); + // }, 'non-cyclic values do not throw'); + expect(() => { + stringify({ x: arr, y: arr }); + }).not.toThrow(); + }); + + test('non-circular duplicated references can still work', function () { + var hourOfDay = { + function: 'hour_of_day', + }; + + var p1 = { + function: 'gte', + arguments: [hourOfDay, 0], + }; + var p2 = { + function: 'lte', + arguments: [hourOfDay, 23], + }; + + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23', + // ); + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23', + // ); + // st.equal( + // stringify( + // { filters: { $and: [p1, p2] } }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23', + // ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe( + 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23', + ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe( + 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23', + ); + expect( + stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe( + 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23', + ); + }); + + test('selects properties when filter=array', function () { + // st.equal(stringify({ a: 'b' }, { filter: ['a'] }), 'a=b'); + // st.equal(stringify({ a: 1 }, { filter: [] }), ''); + expect(stringify({ a: 'b' }, { filter: ['a'] })).toBe('a=b'); + expect(stringify({ a: 1 }, { filter: [] })).toBe(''); + + // st.equal( + // stringify( + // { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + // { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }, + // ), + // 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + // 'indices => indices', + // ); + // st.equal( + // stringify( + // { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + // { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }, + // ), + // 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3', + // 'brackets => brackets', + // ); + // st.equal( + // stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2] }), + // 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + // 'default => indices', + // ); + expect(stringify({ a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, { filter: ['a', 'b', 0, 2] })).toBe( + 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3', + ); + expect( + stringify( + { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }, + ), + ).toBe('a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3'); + expect( + stringify( + { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' }, + { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }, + ), + ).toBe('a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3'); + }); + + test('supports custom representations when filter=function', function () { + var calls = 0; + var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } }; + var filterFunc: StringifyOptions['filter'] = function (prefix, value) { + calls += 1; + if (calls === 1) { + // st.equal(prefix, '', 'prefix is empty'); + // st.equal(value, obj); + expect(prefix).toBe(''); + expect(value).toBe(obj); + } else if (prefix === 'c') { + return void 0; + } else if (value instanceof Date) { + // st.equal(prefix, 'e[f]'); + expect(prefix).toBe('e[f]'); + return value.getTime(); + } + return value; + }; + + // st.equal(stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000'); + // st.equal(calls, 5); + expect(stringify(obj, { filter: filterFunc })).toBe('a=b&e%5Bf%5D=1257894000000'); + expect(calls).toBe(5); + }); + + test('can disable uri encoding', function () { + // st.equal(stringify({ a: 'b' }, { encode: false }), 'a=b'); + // st.equal(stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c'); + // st.equal( + // stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), + // 'a=b&c', + // ); + expect(stringify({ a: 'b' }, { encode: false })).toBe('a=b'); + expect(stringify({ a: { b: 'c' } }, { encode: false })).toBe('a[b]=c'); + expect(stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false })).toBe('a=b&c'); + }); + + test('can sort the keys', function () { + // @ts-expect-error + var sort: NonNullable = function (a: string, b: string) { + return a.localeCompare(b); + }; + // st.equal(stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y'); + // st.equal( + // stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), + // 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a', + // ); + expect(stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort })).toBe('a=c&b=f&z=y'); + expect(stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort })).toBe( + 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a', + ); + }); + + test('can sort the keys at depth 3 or more too', function () { + // @ts-expect-error + var sort: NonNullable = function (a: string, b: string) { + return a.localeCompare(b); + }; + // st.equal( + // stringify( + // { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + // { sort: sort, encode: false }, + // ), + // 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb', + // ); + // st.equal( + // stringify( + // { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + // { sort: null, encode: false }, + // ), + // 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b', + // ); + expect( + stringify( + { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + { sort: sort, encode: false }, + ), + ).toBe('a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'); + expect( + stringify( + { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' }, + { sort: null, encode: false }, + ), + ).toBe('a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'); + }); + + test('can stringify with custom encoding', function () { + // st.equal( + // stringify( + // { 県: '大阪府', '': '' }, + // { + // encoder: function (str) { + // if (str.length === 0) { + // return ''; + // } + // var buf = iconv.encode(str, 'shiftjis'); + // var result = []; + // for (var i = 0; i < buf.length; ++i) { + // result.push(buf.readUInt8(i).toString(16)); + // } + // return '%' + result.join('%'); + // }, + // }, + // ), + // '%8c%a7=%91%e5%8d%e3%95%7b&=', + // ); + expect( + stringify( + { 県: '大阪府', '': '' }, + { + encoder: function (str) { + if (str.length === 0) { + return ''; + } + var buf = iconv.encode(str, 'shiftjis'); + var result = []; + for (var i = 0; i < buf.length; ++i) { + result.push(buf.readUInt8(i).toString(16)); + } + return '%' + result.join('%'); + }, + }, + ), + ).toBe('%8c%a7=%91%e5%8d%e3%95%7b&='); + }); + + test('receives the default encoder as a second argument', function () { + // stringify( + // { a: 1, b: new Date(), c: true, d: [1] }, + // { + // encoder: function (str) { + // st.match(typeof str, /^(?:string|number|boolean)$/); + // return ''; + // }, + // }, + // ); + + stringify( + { a: 1, b: new Date(), c: true, d: [1] }, + { + encoder: function (str) { + // st.match(typeof str, /^(?:string|number|boolean)$/); + assert.match(typeof str, /^(?:string|number|boolean)$/); + return ''; + }, + }, + ); + }); + + test('receives the default encoder as a second argument', function () { + // stringify( + // { a: 1 }, + // { + // encoder: function (str, defaultEncoder) { + // st.equal(defaultEncoder, utils.encode); + // }, + // }, + // ); + + stringify( + { a: 1 }, + { + // @ts-ignore + encoder: function (_str, defaultEncoder) { + expect(defaultEncoder).toBe(encode); + }, + }, + ); + }); + + test('throws error with wrong encoder', function () { + // st['throws'](function () { + // stringify({}, { encoder: 'string' }); + // }, new TypeError('Encoder has to be a function.')); + // st.end(); + expect(() => { + // @ts-expect-error + stringify({}, { encoder: 'string' }); + }).toThrow(TypeError); + }); + + (typeof Buffer === 'undefined' ? test.skip : test)( + 'can use custom encoder for a buffer object', + function () { + // st.equal( + // stringify( + // { a: Buffer.from([1]) }, + // { + // encoder: function (buffer) { + // if (typeof buffer === 'string') { + // return buffer; + // } + // return String.fromCharCode(buffer.readUInt8(0) + 97); + // }, + // }, + // ), + // 'a=b', + // ); + expect( + stringify( + { a: Buffer.from([1]) }, + { + encoder: function (buffer) { + if (typeof buffer === 'string') { + return buffer; + } + return String.fromCharCode(buffer.readUInt8(0) + 97); + }, + }, + ), + ).toBe('a=b'); + + // st.equal( + // stringify( + // { a: Buffer.from('a b') }, + // { + // encoder: function (buffer) { + // return buffer; + // }, + // }, + // ), + // 'a=a b', + // ); + expect( + stringify( + { a: Buffer.from('a b') }, + { + encoder: function (buffer) { + return buffer; + }, + }, + ), + ).toBe('a=a b'); + }, + ); + + test('serializeDate option', function () { + var date = new Date(); + // st.equal( + // stringify({ a: date }), + // 'a=' + date.toISOString().replace(/:/g, '%3A'), + // 'default is toISOString', + // ); + expect(stringify({ a: date })).toBe('a=' + date.toISOString().replace(/:/g, '%3A')); + + var mutatedDate = new Date(); + mutatedDate.toISOString = function () { + throw new SyntaxError(); + }; + // st['throws'](function () { + // mutatedDate.toISOString(); + // }, SyntaxError); + expect(() => { + mutatedDate.toISOString(); + }).toThrow(SyntaxError); + // st.equal( + // stringify({ a: mutatedDate }), + // 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'), + // 'toISOString works even when method is not locally present', + // ); + expect(stringify({ a: mutatedDate })).toBe( + 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'), + ); + + var specificDate = new Date(6); + // st.equal( + // stringify( + // { a: specificDate }, + // { + // serializeDate: function (d) { + // return d.getTime() * 7; + // }, + // }, + // ), + // 'a=42', + // 'custom serializeDate function called', + // ); + expect( + stringify( + { a: specificDate }, + { + // @ts-ignore + serializeDate: function (d) { + return d.getTime() * 7; + }, + }, + ), + ).toBe('a=42'); + + // st.equal( + // stringify( + // { a: [date] }, + // { + // serializeDate: function (d) { + // return d.getTime(); + // }, + // arrayFormat: 'comma', + // }, + // ), + // 'a=' + date.getTime(), + // 'works with arrayFormat comma', + // ); + // st.equal( + // stringify( + // { a: [date] }, + // { + // serializeDate: function (d) { + // return d.getTime(); + // }, + // arrayFormat: 'comma', + // commaRoundTrip: true, + // }, + // ), + // 'a%5B%5D=' + date.getTime(), + // 'works with arrayFormat comma', + // ); + expect( + stringify( + { a: [date] }, + { + // @ts-expect-error + serializeDate: function (d) { + return d.getTime(); + }, + arrayFormat: 'comma', + }, + ), + ).toBe('a=' + date.getTime()); + expect( + stringify( + { a: [date] }, + { + // @ts-expect-error + serializeDate: function (d) { + return d.getTime(); + }, + arrayFormat: 'comma', + commaRoundTrip: true, + }, + ), + ).toBe('a%5B%5D=' + date.getTime()); + }); + + test('RFC 1738 serialization', function () { + // st.equal(stringify({ a: 'b c' }, { format: formats.RFC1738 }), 'a=b+c'); + // st.equal(stringify({ 'a b': 'c d' }, { format: formats.RFC1738 }), 'a+b=c+d'); + // st.equal( + // stringify({ 'a b': Buffer.from('a b') }, { format: formats.RFC1738 }), + // 'a+b=a+b', + // ); + expect(stringify({ a: 'b c' }, { format: 'RFC1738' })).toBe('a=b+c'); + expect(stringify({ 'a b': 'c d' }, { format: 'RFC1738' })).toBe('a+b=c+d'); + expect(stringify({ 'a b': Buffer.from('a b') }, { format: 'RFC1738' })).toBe('a+b=a+b'); + + // st.equal(stringify({ 'foo(ref)': 'bar' }, { format: formats.RFC1738 }), 'foo(ref)=bar'); + expect(stringify({ 'foo(ref)': 'bar' }, { format: 'RFC1738' })).toBe('foo(ref)=bar'); + }); + + test('RFC 3986 spaces serialization', function () { + // st.equal(stringify({ a: 'b c' }, { format: formats.RFC3986 }), 'a=b%20c'); + // st.equal(stringify({ 'a b': 'c d' }, { format: formats.RFC3986 }), 'a%20b=c%20d'); + // st.equal( + // stringify({ 'a b': Buffer.from('a b') }, { format: formats.RFC3986 }), + // 'a%20b=a%20b', + // ); + expect(stringify({ a: 'b c' }, { format: 'RFC3986' })).toBe('a=b%20c'); + expect(stringify({ 'a b': 'c d' }, { format: 'RFC3986' })).toBe('a%20b=c%20d'); + expect(stringify({ 'a b': Buffer.from('a b') }, { format: 'RFC3986' })).toBe('a%20b=a%20b'); + }); + + test('Backward compatibility to RFC 3986', function () { + // st.equal(stringify({ a: 'b c' }), 'a=b%20c'); + // st.equal(stringify({ 'a b': Buffer.from('a b') }), 'a%20b=a%20b'); + expect(stringify({ a: 'b c' })).toBe('a=b%20c'); + expect(stringify({ 'a b': Buffer.from('a b') })).toBe('a%20b=a%20b'); + }); + + test('Edge cases and unknown formats', function () { + ['UFO1234', false, 1234, null, {}, []].forEach(function (format) { + // st['throws'](function () { + // stringify({ a: 'b c' }, { format: format }); + // }, new TypeError('Unknown format option provided.')); + expect(() => { + // @ts-expect-error + stringify({ a: 'b c' }, { format: format }); + }).toThrow(TypeError); + }); + }); + + test('encodeValuesOnly', function () { + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h', + // 'encodeValuesOnly + indices', + // ); + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h', + // 'encodeValuesOnly + brackets', + // ); + // st.equal( + // stringify( + // { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a=b&c=d&c=e%3Df&f=g&f=h', + // 'encodeValuesOnly + repeat', + // ); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'indices' }, + ), + ).toBe('a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'brackets' }, + ), + ).toBe('a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h'); + expect( + stringify( + { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, + { encodeValuesOnly: true, arrayFormat: 'repeat' }, + ), + ).toBe('a=b&c=d&c=e%3Df&f=g&f=h'); + + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'indices' }), + // 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h', + // 'no encodeValuesOnly + indices', + // ); + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'brackets' }), + // 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h', + // 'no encodeValuesOnly + brackets', + // ); + // st.equal( + // stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'repeat' }), + // 'a=b&c=d&c=e&f=g&f=h', + // 'no encodeValuesOnly + repeat', + // ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'indices' })).toBe( + 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h', + ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'brackets' })).toBe( + 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h', + ); + expect(stringify({ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, { arrayFormat: 'repeat' })).toBe( + 'a=b&c=d&c=e&f=g&f=h', + ); + }); + + test('encodeValuesOnly - strictNullHandling', function () { + // st.equal( + // stringify({ a: { b: null } }, { encodeValuesOnly: true, strictNullHandling: true }), + // 'a[b]', + // ); + expect(stringify({ a: { b: null } }, { encodeValuesOnly: true, strictNullHandling: true })).toBe('a[b]'); + }); + + test('throws if an invalid charset is specified', function () { + // st['throws'](function () { + // stringify({ a: 'b' }, { charset: 'foobar' }); + // }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined')); + expect(() => { + // @ts-expect-error + stringify({ a: 'b' }, { charset: 'foobar' }); + }).toThrow(TypeError); + }); + + test('respects a charset of iso-8859-1', function () { + // st.equal(stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6'); + expect(stringify({ æ: 'æ' }, { charset: 'iso-8859-1' })).toBe('%E6=%E6'); + }); + + test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function () { + // st.equal(stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B'); + expect(stringify({ a: '☺' }, { charset: 'iso-8859-1' })).toBe('a=%26%239786%3B'); + }); + + test('respects an explicit charset of utf-8 (the default)', function () { + // st.equal(stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6'); + expect(stringify({ a: 'æ' }, { charset: 'utf-8' })).toBe('a=%C3%A6'); + }); + + test('`charsetSentinel` option', function () { + // st.equal( + // stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), + // 'utf8=%E2%9C%93&a=%C3%A6', + // 'adds the right sentinel when instructed to and the charset is utf-8', + // ); + expect(stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' })).toBe( + 'utf8=%E2%9C%93&a=%C3%A6', + ); + + // st.equal( + // stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), + // 'utf8=%26%2310003%3B&a=%E6', + // 'adds the right sentinel when instructed to and the charset is iso-8859-1', + // ); + expect(stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' })).toBe( + 'utf8=%26%2310003%3B&a=%E6', + ); + }); + + test('does not mutate the options argument', function () { + var options = {}; + stringify({}, options); + // st.deepEqual(options, {}); + expect(options).toEqual({}); + }); + + test('strictNullHandling works with custom filter', function () { + // @ts-expect-error + var filter = function (_prefix, value) { + return value; + }; + + var options = { strictNullHandling: true, filter: filter }; + // st.equal(stringify({ key: null }, options), 'key'); + expect(stringify({ key: null }, options)).toBe('key'); + }); + + test('strictNullHandling works with null serializeDate', function () { + var serializeDate = function () { + return null; + }; + var options = { strictNullHandling: true, serializeDate: serializeDate }; + var date = new Date(); + // st.equal(stringify({ key: date }, options), 'key'); + // @ts-expect-error + expect(stringify({ key: date }, options)).toBe('key'); + }); + + test('allows for encoding keys and values differently', function () { + // @ts-expect-error + var encoder = function (str, defaultEncoder, charset, type) { + if (type === 'key') { + return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase(); + } + if (type === 'value') { + return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase(); + } + throw 'this should never happen! type: ' + type; + }; + + // st.deepEqual(stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE'); + expect(stringify({ KeY: 'vAlUe' }, { encoder: encoder })).toBe('key=VALUE'); + }); + + test('objects inside arrays', function () { + var obj = { a: { b: { c: 'd', e: 'f' } } }; + var withArray = { a: { b: [{ c: 'd', e: 'f' }] } }; + + // st.equal( + // stringify(obj, { encode: false }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, no arrayFormat', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'brackets' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, bracket', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'indices' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, indices', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'repeat' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, repeat', + // ); + // st.equal( + // stringify(obj, { encode: false, arrayFormat: 'comma' }), + // 'a[b][c]=d&a[b][e]=f', + // 'no array, comma', + // ); + expect(stringify(obj, { encode: false })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'brackets' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'indices' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'repeat' })).toBe('a[b][c]=d&a[b][e]=f'); + expect(stringify(obj, { encode: false, arrayFormat: 'comma' })).toBe('a[b][c]=d&a[b][e]=f'); + + // st.equal( + // stringify(withArray, { encode: false }), + // 'a[b][0][c]=d&a[b][0][e]=f', + // 'array, no arrayFormat', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'brackets' }), + // 'a[b][][c]=d&a[b][][e]=f', + // 'array, bracket', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'indices' }), + // 'a[b][0][c]=d&a[b][0][e]=f', + // 'array, indices', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'repeat' }), + // 'a[b][c]=d&a[b][e]=f', + // 'array, repeat', + // ); + // st.equal( + // stringify(withArray, { encode: false, arrayFormat: 'comma' }), + // '???', + // 'array, comma', + // { skip: 'TODO: figure out what this should do' }, + // ); + expect(stringify(withArray, { encode: false })).toBe('a[b][0][c]=d&a[b][0][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'brackets' })).toBe('a[b][][c]=d&a[b][][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'indices' })).toBe('a[b][0][c]=d&a[b][0][e]=f'); + expect(stringify(withArray, { encode: false, arrayFormat: 'repeat' })).toBe('a[b][c]=d&a[b][e]=f'); + // !TODo: Figure out what this should do + // expect(stringify(withArray, { encode: false, arrayFormat: 'comma' })).toBe( + // 'a[b][c]=d&a[b][e]=f', + // ); + }); + + test('stringifies sparse arrays', function () { + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + // 'a[1]=2&a[4]=1', + // ); + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + // 'a[]=2&a[]=1', + // ); + // st.equal( + // stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + // 'a=2&a=1', + // ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' })).toBe( + 'a[1]=2&a[4]=1', + ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' })).toBe( + 'a[]=2&a[]=1', + ); + expect(stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' })).toBe( + 'a=2&a=1', + ); + + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][b][2][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][b][][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, { b: [, , { c: '1' }] }] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[b][c]=1', + // ); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][b][2][c]=1'); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][b][][c]=1'); + expect( + stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[b][c]=1'); + + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][2][3][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][][][c]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: '1' }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[c]=1', + // ); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][2][3][c]=1'); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][][][c]=1'); + expect( + stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[c]=1'); + + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'indices' }, + // ), + // 'a[1][2][3][c][1]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'brackets' }, + // ), + // 'a[][][][c][]=1', + // ); + // st.equal( + // stringify( + // { a: [, [, , [, , , { c: [, '1'] }]]] }, + // { encodeValuesOnly: true, arrayFormat: 'repeat' }, + // ), + // 'a[c]=1', + // ); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), + ).toBe('a[1][2][3][c][1]=1'); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), + ).toBe('a[][][][c][]=1'); + expect( + stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), + ).toBe('a[c]=1'); + }); + + test('encodes a very long string', function () { + var chars = []; + var expected = []; + for (var i = 0; i < 5e3; i++) { + chars.push(' ' + i); + + expected.push('%20' + i); + } + + var obj = { + foo: chars.join(''), + }; + + // st.equal( + // stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' }), + // 'foo=' + expected.join(''), + // ); + // @ts-expect-error + expect(stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' })).toBe('foo=' + expected.join('')); + }); +}); + +describe('stringifies empty keys', function () { + empty_test_cases.forEach(function (testCase) { + test('stringifies an object with empty string key with ' + testCase.input, function () { + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'indices' }), + // testCase.stringifyOutput.indices, + // 'test case: ' + testCase.input + ', indices', + // ); + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'brackets' }), + // testCase.stringifyOutput.brackets, + // 'test case: ' + testCase.input + ', brackets', + // ); + // st.deepEqual( + // stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'repeat' }), + // testCase.stringifyOutput.repeat, + // 'test case: ' + testCase.input + ', repeat', + // ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'indices' })).toBe( + testCase.stringify_output.indices, + ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'brackets' })).toBe( + testCase.stringify_output.brackets, + ); + expect(stringify(testCase.with_empty_keys, { encode: false, arrayFormat: 'repeat' })).toBe( + testCase.stringify_output.repeat, + ); + }); + }); + + test('edge case with object/arrays', function () { + // st.deepEqual(stringify({ '': { '': [2, 3] } }, { encode: false }), '[][0]=2&[][1]=3'); + // st.deepEqual( + // stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), + // '[][0]=2&[][1]=3&[a]=2', + // ); + // st.deepEqual( + // stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' }), + // '[][0]=2&[][1]=3', + // ); + // st.deepEqual( + // stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' }), + // '[][0]=2&[][1]=3&[a]=2', + // ); + expect(stringify({ '': { '': [2, 3] } }, { encode: false })).toBe('[][0]=2&[][1]=3'); + expect(stringify({ '': { '': [2, 3], a: 2 } }, { encode: false })).toBe('[][0]=2&[][1]=3&[a]=2'); + expect(stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' })).toBe( + '[][0]=2&[][1]=3', + ); + expect(stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' })).toBe( + '[][0]=2&[][1]=3&[a]=2', + ); + }); +}); diff --git a/tests/qs/utils.test.ts b/tests/qs/utils.test.ts new file mode 100644 index 0000000..c95327f --- /dev/null +++ b/tests/qs/utils.test.ts @@ -0,0 +1,169 @@ +import { combine, merge, is_buffer, assign_single_source } from 'postgrid-node/internal/qs/utils'; + +describe('merge()', function () { + // t.deepEqual(merge(null, true), [null, true], 'merges true into null'); + expect(merge(null, true)).toEqual([null, true]); + + // t.deepEqual(merge(null, [42]), [null, 42], 'merges null into an array'); + expect(merge(null, [42])).toEqual([null, 42]); + + // t.deepEqual( + // merge({ a: 'b' }, { a: 'c' }), + // { a: ['b', 'c'] }, + // 'merges two objects with the same key', + // ); + expect(merge({ a: 'b' }, { a: 'c' })).toEqual({ a: ['b', 'c'] }); + + var oneMerged = merge({ foo: 'bar' }, { foo: { first: '123' } }); + // t.deepEqual( + // oneMerged, + // { foo: ['bar', { first: '123' }] }, + // 'merges a standalone and an object into an array', + // ); + expect(oneMerged).toEqual({ foo: ['bar', { first: '123' }] }); + + var twoMerged = merge({ foo: ['bar', { first: '123' }] }, { foo: { second: '456' } }); + // t.deepEqual( + // twoMerged, + // { foo: { 0: 'bar', 1: { first: '123' }, second: '456' } }, + // 'merges a standalone and two objects into an array', + // ); + expect(twoMerged).toEqual({ foo: { 0: 'bar', 1: { first: '123' }, second: '456' } }); + + var sandwiched = merge({ foo: ['bar', { first: '123', second: '456' }] }, { foo: 'baz' }); + // t.deepEqual( + // sandwiched, + // { foo: ['bar', { first: '123', second: '456' }, 'baz'] }, + // 'merges an object sandwiched by two standalones into an array', + // ); + expect(sandwiched).toEqual({ foo: ['bar', { first: '123', second: '456' }, 'baz'] }); + + var nestedArrays = merge({ foo: ['baz'] }, { foo: ['bar', 'xyzzy'] }); + // t.deepEqual(nestedArrays, { foo: ['baz', 'bar', 'xyzzy'] }); + expect(nestedArrays).toEqual({ foo: ['baz', 'bar', 'xyzzy'] }); + + var noOptionsNonObjectSource = merge({ foo: 'baz' }, 'bar'); + // t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true }); + expect(noOptionsNonObjectSource).toEqual({ foo: 'baz', bar: true }); + + (typeof Object.defineProperty !== 'function' ? test.skip : test)( + 'avoids invoking array setters unnecessarily', + function () { + var setCount = 0; + var getCount = 0; + var observed: any[] = []; + Object.defineProperty(observed, 0, { + get: function () { + getCount += 1; + return { bar: 'baz' }; + }, + set: function () { + setCount += 1; + }, + }); + merge(observed, [null]); + // st.equal(setCount, 0); + // st.equal(getCount, 1); + expect(setCount).toEqual(0); + expect(getCount).toEqual(1); + observed[0] = observed[0]; + // st.equal(setCount, 1); + // st.equal(getCount, 2); + expect(setCount).toEqual(1); + expect(getCount).toEqual(2); + }, + ); +}); + +test('assign()', function () { + var target = { a: 1, b: 2 }; + var source = { b: 3, c: 4 }; + var result = assign_single_source(target, source); + + expect(result).toEqual(target); + expect(target).toEqual({ a: 1, b: 3, c: 4 }); + expect(source).toEqual({ b: 3, c: 4 }); +}); + +describe('combine()', function () { + test('both arrays', function () { + var a = [1]; + var b = [2]; + var combined = combine(a, b); + + // st.deepEqual(a, [1], 'a is not mutated'); + // st.deepEqual(b, [2], 'b is not mutated'); + // st.notEqual(a, combined, 'a !== combined'); + // st.notEqual(b, combined, 'b !== combined'); + // st.deepEqual(combined, [1, 2], 'combined is a + b'); + expect(a).toEqual([1]); + expect(b).toEqual([2]); + expect(combined).toEqual([1, 2]); + expect(a).not.toEqual(combined); + expect(b).not.toEqual(combined); + }); + + test('one array, one non-array', function () { + var aN = 1; + var a = [aN]; + var bN = 2; + var b = [bN]; + + var combinedAnB = combine(aN, b); + // st.deepEqual(b, [bN], 'b is not mutated'); + // st.notEqual(aN, combinedAnB, 'aN + b !== aN'); + // st.notEqual(a, combinedAnB, 'aN + b !== a'); + // st.notEqual(bN, combinedAnB, 'aN + b !== bN'); + // st.notEqual(b, combinedAnB, 'aN + b !== b'); + // st.deepEqual([1, 2], combinedAnB, 'first argument is array-wrapped when not an array'); + expect(b).toEqual([bN]); + expect(combinedAnB).not.toEqual(aN); + expect(combinedAnB).not.toEqual(a); + expect(combinedAnB).not.toEqual(bN); + expect(combinedAnB).not.toEqual(b); + expect(combinedAnB).toEqual([1, 2]); + + var combinedABn = combine(a, bN); + // st.deepEqual(a, [aN], 'a is not mutated'); + // st.notEqual(aN, combinedABn, 'a + bN !== aN'); + // st.notEqual(a, combinedABn, 'a + bN !== a'); + // st.notEqual(bN, combinedABn, 'a + bN !== bN'); + // st.notEqual(b, combinedABn, 'a + bN !== b'); + // st.deepEqual([1, 2], combinedABn, 'second argument is array-wrapped when not an array'); + expect(a).toEqual([aN]); + expect(combinedABn).not.toEqual(aN); + expect(combinedABn).not.toEqual(a); + expect(combinedABn).not.toEqual(bN); + expect(combinedABn).not.toEqual(b); + expect(combinedABn).toEqual([1, 2]); + }); + + test('neither is an array', function () { + var combined = combine(1, 2); + // st.notEqual(1, combined, '1 + 2 !== 1'); + // st.notEqual(2, combined, '1 + 2 !== 2'); + // st.deepEqual([1, 2], combined, 'both arguments are array-wrapped when not an array'); + expect(combined).not.toEqual(1); + expect(combined).not.toEqual(2); + expect(combined).toEqual([1, 2]); + }); +}); + +test('is_buffer()', function () { + for (const x of [null, undefined, true, false, '', 'abc', 42, 0, NaN, {}, [], function () {}, /a/g]) { + // t.equal(is_buffer(x), false, inspect(x) + ' is not a buffer'); + expect(is_buffer(x)).toEqual(false); + } + + var fakeBuffer = { constructor: Buffer }; + // t.equal(is_buffer(fakeBuffer), false, 'fake buffer is not a buffer'); + expect(is_buffer(fakeBuffer)).toEqual(false); + + var saferBuffer = Buffer.from('abc'); + // t.equal(is_buffer(saferBuffer), true, 'SaferBuffer instance is a buffer'); + expect(is_buffer(saferBuffer)).toEqual(true); + + var buffer = Buffer.from('abc'); + // t.equal(is_buffer(buffer), true, 'real Buffer instance is a buffer'); + expect(is_buffer(buffer)).toEqual(true); +}); diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts index 0f35edd..eadcc64 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -18,10 +18,4 @@ describe(stringifyQuery, () => { expect(stringifyQuery(input)).toEqual(expected); }); } - - for (const value of [[], {}, new Date()]) { - it(`${JSON.stringify(value)} -> `, () => { - expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); - }); - } }); From 763499e0c794e44eb5ff4372e76fefed195eefef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:51:00 +0000 Subject: [PATCH 5/7] feat: New endpoints, fixed and aligned schemas --- .stats.yml | 8 +- README.md | 49 ++ api.md | 41 ++ src/client.ts | 40 ++ src/resources/address-verification.ts | 12 +- src/resources/bulk-verification.ts | 441 ++++++++++++++++++ src/resources/index.ts | 9 + src/resources/intl-address-verification.ts | 44 +- src/resources/print-mail/bank-accounts.ts | 13 + src/resources/print-mail/boxes.ts | 3 + src/resources/print-mail/cheques.ts | 97 +++- src/resources/print-mail/contacts.ts | 53 +++ src/resources/print-mail/index.ts | 7 + src/resources/print-mail/letters.ts | 3 + src/resources/print-mail/postcards.ts | 3 + src/resources/print-mail/print-mail.ts | 18 + src/resources/print-mail/return-envelopes.ts | 3 + .../print-mail/return-envelopes/index.ts | 19 + .../print-mail/return-envelopes/orders.ts | 273 +++++++++++ .../return-envelopes/return-envelopes.ts | 320 +++++++++++++ src/resources/print-mail/self-mailers.ts | 3 + src/resources/print-mail/templates.ts | 5 + tests/api-resources/bulk-verification.test.ts | 84 ++++ .../api-resources/print-mail/cheques.test.ts | 13 +- .../api-resources/print-mail/contacts.test.ts | 1 + .../api-resources/print-mail/letters.test.ts | 2 + .../print-mail/postcards.test.ts | 2 + .../return-envelopes/orders.test.ts | 117 +++++ .../return-envelopes/return-envelopes.test.ts | 72 +++ .../print-mail/self-mailers.test.ts | 2 + .../print-mail/snap-packs.test.ts | 2 + 31 files changed, 1733 insertions(+), 26 deletions(-) create mode 100644 src/resources/bulk-verification.ts create mode 100644 src/resources/print-mail/return-envelopes.ts create mode 100644 src/resources/print-mail/return-envelopes/index.ts create mode 100644 src/resources/print-mail/return-envelopes/orders.ts create mode 100644 src/resources/print-mail/return-envelopes/return-envelopes.ts create mode 100644 tests/api-resources/bulk-verification.test.ts create mode 100644 tests/api-resources/print-mail/return-envelopes/orders.test.ts create mode 100644 tests/api-resources/print-mail/return-envelopes/return-envelopes.test.ts diff --git a/.stats.yml b/.stats.yml index dd2b394..86116e6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 126 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-01345c918ca89a9697dad4fb9ef469d65f00a9443bc1046fcda4ab08d9e9224d.yml -openapi_spec_hash: 2bffa15225d43f8f9ae6d6ea5dccc6b4 -config_hash: 174a107082cfb0cbf120e23621f4345c +configured_endpoints: 137 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-32126b0df875e75e2a33a809e6fc25ea55cedc0d2505450361e71f4f2cedecf3.yml +openapi_spec_hash: 2cc8c911382f942c07b624e45c8db026 +config_hash: 6398155ff41b9bcbc09dc6066a1bf113 diff --git a/README.md b/README.md index fa4aa77..3cec123 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,55 @@ const response: PostGrid.AddressVerificationVerifyResponse = Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. +## File uploads + +Request parameters that correspond to file uploads can be passed in many different forms: + +- `File` (or an object with the same structure) +- a `fetch` `Response` (or an object with the same structure) +- an `fs.ReadStream` +- the return value of our `toFile` helper + +```ts +import fs from 'fs'; +import PostGrid, { toFile } from 'postgrid-node'; + +const client = new PostGrid(); + +// If you have access to Node `fs` we recommend using `fs.createReadStream()`: +await client.bulkVerification.upload({ + file: fs.createReadStream('/path/to/file'), + mappings: { line1: 'line1' }, + name: 'name', +}); + +// Or if you have the web `File` API you can pass a `File` instance: +await client.bulkVerification.upload({ + file: new File(['my bytes'], 'file'), + mappings: { line1: 'line1' }, + name: 'name', +}); + +// You can also pass a `fetch` `Response`: +await client.bulkVerification.upload({ + file: await fetch('https://somesite/file'), + mappings: { line1: 'line1' }, + name: 'name', +}); + +// Finally, if none of the above are convenient, you can use our `toFile` helper: +await client.bulkVerification.upload({ + file: await toFile(Buffer.from('my bytes'), 'file'), + mappings: { line1: 'line1' }, + name: 'name', +}); +await client.bulkVerification.upload({ + file: await toFile(new Uint8Array([0, 1, 2]), 'file'), + mappings: { line1: 'line1' }, + name: 'name', +}); +``` + ## Handling errors When the library is unable to connect to the API, diff --git a/api.md b/api.md index ea04f85..cb08c6a 100644 --- a/api.md +++ b/api.md @@ -44,6 +44,21 @@ Methods: - client.intlAddressVerification.getAutocompletePreviews({ ...params }) -> IntlAddressVerificationGetAutocompletePreviewsResponse - client.intlAddressVerification.verify({ ...params }) -> IntlAddressVerificationVerifyResponse +# BulkVerification + +Types: + +- AddverList +- BulkVerificationRetrieveResponse +- BulkVerificationListResponse +- BulkVerificationUploadResponse + +Methods: + +- client.bulkVerification.retrieve(id) -> BulkVerificationRetrieveResponse +- client.bulkVerification.list({ ...params }) -> BulkVerificationListResponse +- client.bulkVerification.upload({ ...params }) -> BulkVerificationUploadResponse + # PrintMail ## Contacts @@ -214,6 +229,32 @@ Methods: - client.printMail.selfMailers.progress(id) -> SelfMailer - client.printMail.selfMailers.retrieveURL(id) -> SelfMailerRetrieveURLResponse +## ReturnEnvelopes + +Types: + +- ReturnEnvelope + +Methods: + +- client.printMail.returnEnvelopes.create({ ...params }) -> ReturnEnvelope +- client.printMail.returnEnvelopes.retrieve(id) -> ReturnEnvelope +- client.printMail.returnEnvelopes.list({ ...params }) -> ReturnEnvelopesSkipLimit + +### Orders + +Types: + +- ReturnEnvelopeOrder + +Methods: + +- client.printMail.returnEnvelopes.orders.create(id, { ...params }) -> ReturnEnvelopeOrder +- client.printMail.returnEnvelopes.orders.retrieve(orderID, { ...params }) -> ReturnEnvelopeOrder +- client.printMail.returnEnvelopes.orders.list(id, { ...params }) -> ReturnEnvelopeOrdersSkipLimit +- client.printMail.returnEnvelopes.orders.cancel(orderID, { ...params }) -> ReturnEnvelopeOrder +- client.printMail.returnEnvelopes.orders.fill(orderID, { ...params }) -> ReturnEnvelopeOrder + ## Campaigns Types: diff --git a/src/client.ts b/src/client.ts index db47396..3a5e0b8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -41,6 +41,15 @@ import { Errors as AddressVerificationAPIErrors, Status, } from './resources/address-verification'; +import { + AddverList, + BulkVerification, + BulkVerificationListParams, + BulkVerificationListResponse, + BulkVerificationRetrieveResponse, + BulkVerificationUploadParams, + BulkVerificationUploadResponse, +} from './resources/bulk-verification'; import { IntlAddressVerification, IntlAddressVerificationAutocompleteParams, @@ -858,11 +867,32 @@ export class PostGrid { * */ intlAddressVerification: API.IntlAddressVerification = new API.IntlAddressVerification(this); + /** + * **Note: For verifying batches of addresses in real-time via JSON, please use + * the "Batch Verify Addresses" endpoint.** + * + * The bulk verification API allows you to submit CSV files to be processed + * through our address verification engine. Each file can contain up to 250,000 + * addresses, and the output lines up with what is returned from our batch + * verification API. + * + * Note that you will be invoiced for every list that processes successfully. + * You can pre-purchase bulk verification credits from our + * [dashboard](https://app.postgrid.com/dashboard/upgrade) to prevent this. + * However, these cannot be used for geocoded lists, and you must individually + * pay for every list that you process with those flags. + * + * **Also note that in order to access bulk geocoding you must contact** + * [support@postgrid.com](mailto:support@postgrid.com) **to enable the feature.** + * + */ + bulkVerification: API.BulkVerification = new API.BulkVerification(this); printMail: API.PrintMail = new API.PrintMail(this); } PostGrid.AddressVerification = AddressVerification; PostGrid.IntlAddressVerification = IntlAddressVerification; +PostGrid.BulkVerification = BulkVerification; PostGrid.PrintMail = PrintMail; export declare namespace PostGrid { @@ -908,5 +938,15 @@ export declare namespace PostGrid { type IntlAddressVerificationVerifyParams as IntlAddressVerificationVerifyParams, }; + export { + BulkVerification as BulkVerification, + type AddverList as AddverList, + type BulkVerificationRetrieveResponse as BulkVerificationRetrieveResponse, + type BulkVerificationListResponse as BulkVerificationListResponse, + type BulkVerificationUploadResponse as BulkVerificationUploadResponse, + type BulkVerificationListParams as BulkVerificationListParams, + type BulkVerificationUploadParams as BulkVerificationUploadParams, + }; + export { PrintMail as PrintMail }; } diff --git a/src/resources/address-verification.ts b/src/resources/address-verification.ts index 0b0f5be..a8c91ff 100644 --- a/src/resources/address-verification.ts +++ b/src/resources/address-verification.ts @@ -73,11 +73,15 @@ export class AddressVerification extends APIResource { * be freeform or structured, matching the same input formats accepted by the * single verification endpoint. * + * - Accepts up to 2,000 addresses per request. * - Uses 1 lookup per address (plus 1 more per address if geocoding). * - Requires a secret API key. * - Returns results in the same order as the input addresses. * - If an individual address fails, its result will contain an `error` field * rather than a `verifiedAddress`. + * - If you are not subscribed and the batch would exceed your remaining free + * lookups, the entire batch fails (nothing is verified). Size your batch to the + * number of lookups you have left. * * @example * ```ts @@ -129,7 +133,8 @@ export class AddressVerification extends APIResource { /** * Returns your organization's current lookup usage and plan information. Useful * for checking how many lookups you have consumed and whether you are on a paid - * plan. + * plan. If you are not subscribed, any lookup past your free limit will fail — use + * this endpoint to check your remaining lookups. * * @example * ```ts @@ -1369,6 +1374,11 @@ export namespace AddressVerificationGetLookupInfoResponse { */ freeLimit: number | null; + /** + * Whether the organization is on a paid (subscribed) plan. + */ + subscribed: boolean; + /** * The number of lookups consumed in the current billing period. */ diff --git a/src/resources/bulk-verification.ts b/src/resources/bulk-verification.ts new file mode 100644 index 0000000..e284e16 --- /dev/null +++ b/src/resources/bulk-verification.ts @@ -0,0 +1,441 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as BulkVerificationAPI from './bulk-verification'; +import { APIPromise } from '../core/api-promise'; +import { type Uploadable } from '../core/uploads'; +import { RequestOptions } from '../internal/request-options'; +import { multipartFormRequestOptions } from '../internal/uploads'; +import { path } from '../internal/utils/path'; + +/** + * **Note: For verifying batches of addresses in real-time via JSON, please use + * the "Batch Verify Addresses" endpoint.** + * + * The bulk verification API allows you to submit CSV files to be processed + * through our address verification engine. Each file can contain up to 250,000 + * addresses, and the output lines up with what is returned from our batch + * verification API. + * + * Note that you will be invoiced for every list that processes successfully. + * You can pre-purchase bulk verification credits from our + * [dashboard](https://app.postgrid.com/dashboard/upgrade) to prevent this. + * However, these cannot be used for geocoded lists, and you must individually + * pay for every list that you process with those flags. + * + * **Also note that in order to access bulk geocoding you must contact** + * [support@postgrid.com](mailto:support@postgrid.com) **to enable the feature.** + */ +export class BulkVerification extends APIResource { + /** + * Retrieve a single bulk verification list by ID, including its processing status + * and — once processed — a link to the output CSV. + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/addver_lists/${id}`, options); + } + + /** + * Retrieve a list of your bulk verification lists. + */ + list( + query: BulkVerificationListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/v1/addver_lists', { query, ...options }); + } + + /** + * Upload a CSV file of addresses to be verified in bulk. Supply a `mappings` + * object describing which CSV columns correspond to which address fields. + */ + upload( + body: BulkVerificationUploadParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post( + '/v1/addver_lists', + multipartFormRequestOptions({ body, ...options }, this._client), + ); + } +} + +/** + * A bulk address verification list — an uploaded CSV file of addresses and its + * processing state. + */ +export interface AddverList { + /** + * A unique ID prefixed with `addver_list_`. + */ + id: string; + + /** + * The cost charged for processing this list. + */ + cost: number; + + /** + * The number of addresses in the uploaded file. + */ + count: number; + + /** + * The UTC time at which this list was created. + */ + createdAt: string; + + /** + * A signed URL to the uploaded input CSV file. + */ + file: string; + + /** + * The mapping of your CSV column names to PostGrid address fields. Each value is + * the name of a column in your uploaded file. + */ + mappings: AddverList.Mappings; + + /** + * The name supplied for the list. This only affects what is displayed in the + * dashboard. + */ + name: string; + + /** + * The ID of the organization that owns this list. + */ + organization: string; + + /** + * The processing status of the list, e.g. `pending`, `processing`, or `processed`. + */ + status: string; + + /** + * The UTC time at which this list was last updated. + */ + updatedAt: string; + + /** + * Whether geocoding (latitude/longitude) output was requested. + */ + useGeocode: boolean; + + /** + * Whether international (outside US & Canada) verification was requested. + */ + useIntlVerification: boolean; + + /** + * Whether Proper Case output was requested. + */ + useProperCase: boolean; + + /** + * The ID of the user that created this list. + */ + user: string; + + /** + * The ISO 2-letter country code used as the fallback when a row is missing a + * country. Not returned for lists uploaded without one, e.g. lists which map the + * entire address into `line1`. + */ + defaultCountry?: string; + + /** + * Additional metadata about the list, including a count of each status. + */ + metadata?: AddverList.Metadata; + + /** + * The number of invalid or skipped rows in the uploaded file. May be omitted on + * lists created before this field was introduced. + */ + numInvalidRows?: number; + + /** + * A signed URL to the processed output CSV file. Present once the list has + * finished processing. + */ + result?: string; + + /** + * Whether CCOA (Canada Post change of address) was requested. May be omitted on + * lists created before COA support was introduced. + */ + runCCOA?: boolean; + + /** + * Whether NCOA (US National Change of Address) was requested. May be omitted on + * lists created before COA support was introduced. + */ + runNCOA?: boolean; +} + +export namespace AddverList { + /** + * The mapping of your CSV column names to PostGrid address fields. Each value is + * the name of a column in your uploaded file. + */ + export interface Mappings { + /** + * The column containing the first line of each address. If your entire address is + * in a single column, specify only this mapping. + */ + line1: string; + + /** + * The column containing the city of each address. + */ + city?: string; + + /** + * The column containing the 2-letter ISO country code of each address (e.g. `US`, + * not `United States`). + */ + country?: string; + + /** + * The column containing the first name of the person at each address. Only used + * when NCOA is run. + */ + firstName?: string; + + /** + * The column containing the full name of the person at each address. Can be + * supplied instead of `firstName` and `lastName`. Only used when NCOA or CCOA is + * run. + */ + fullName?: string; + + /** + * The column containing the last name of the person at each address. Only used + * when NCOA is run. + */ + lastName?: string; + + /** + * The column containing the second line of each address. + */ + line2?: string; + + /** + * The column containing the postal or ZIP code of each address. + */ + postalOrZip?: string; + + /** + * The column containing the province or state of each address. + */ + provinceOrState?: string; + } + + /** + * Additional metadata about the list, including a count of each status. + */ + export interface Metadata { + /** + * The number of addresses by resulting verification status. + */ + statusCount?: Metadata.StatusCount; + } + + export namespace Metadata { + /** + * The number of addresses by resulting verification status. + */ + export interface StatusCount { + corrected?: number; + + failed?: number; + + verified?: number; + } + } +} + +export interface BulkVerificationRetrieveResponse { + /** + * A bulk address verification list — an uploaded CSV file of addresses and its + * processing state. + */ + data: AddverList; + + message: string; + + status: 'success' | 'error'; +} + +export interface BulkVerificationListResponse { + /** + * A list of bulk verification lists. + */ + data: BulkVerificationListResponse.Data; + + message: string; + + status: 'success' | 'error'; +} + +export namespace BulkVerificationListResponse { + /** + * A list of bulk verification lists. + */ + export interface Data { + /** + * The total number of lists. + */ + count: number; + + /** + * The requested lists. + */ + data: Array; + } +} + +export interface BulkVerificationUploadResponse { + /** + * A bulk address verification list — an uploaded CSV file of addresses and its + * processing state. + */ + data: AddverList; + + message: string; + + status: 'success' | 'error'; +} + +export interface BulkVerificationListParams { + /** + * The maximum number of lists to return. + */ + limit?: number; + + /** + * The number of lists to skip past, for pagination. + */ + skip?: number; +} + +export interface BulkVerificationUploadParams { + file: Uploadable; + + /** + * The mapping of your CSV column names to PostGrid address fields. Each value is + * the name of a column in your uploaded file. + */ + mappings: BulkVerificationUploadParams.Mappings; + + /** + * A name for the uploaded list. This only affects what is displayed in the + * dashboard. + */ + name: string; + + /** + * An ISO 2-letter country code used as the fallback when a row is missing a value + * in the `country` column. + */ + defaultCountry?: string; + + /** + * Whether to run CCOA (Canada Post change of address) on the list. Note that a + * list cannot run both NCOA and CCOA — split mixed US/Canadian files into separate + * lists. + */ + runCCOA?: boolean; + + /** + * Whether to run NCOA (US National Change of Address) on the list. + */ + runNCOA?: boolean; + + /** + * Whether to append geographical location information (latitude, longitude) to + * your output. Bulk geocoding must be enabled by contacting support. + */ + useGeocode?: boolean; + + /** + * Whether to perform international (outside US & Canada) verification. + */ + useIntlVerification?: boolean; + + /** + * Whether to return addresses in Proper Case. + */ + useProperCase?: boolean; +} + +export namespace BulkVerificationUploadParams { + /** + * The mapping of your CSV column names to PostGrid address fields. Each value is + * the name of a column in your uploaded file. + */ + export interface Mappings { + /** + * The column containing the first line of each address. If your entire address is + * in a single column, specify only this mapping. + */ + line1: string; + + /** + * The column containing the city of each address. + */ + city?: string; + + /** + * The column containing the 2-letter ISO country code of each address (e.g. `US`, + * not `United States`). + */ + country?: string; + + /** + * The column containing the first name of the person at each address. Only used + * when NCOA is run. + */ + firstName?: string; + + /** + * The column containing the full name of the person at each address. Can be + * supplied instead of `firstName` and `lastName`. Only used when NCOA or CCOA is + * run. + */ + fullName?: string; + + /** + * The column containing the last name of the person at each address. Only used + * when NCOA is run. + */ + lastName?: string; + + /** + * The column containing the second line of each address. + */ + line2?: string; + + /** + * The column containing the postal or ZIP code of each address. + */ + postalOrZip?: string; + + /** + * The column containing the province or state of each address. + */ + provinceOrState?: string; + } +} + +export declare namespace BulkVerification { + export { + type AddverList as AddverList, + type BulkVerificationRetrieveResponse as BulkVerificationRetrieveResponse, + type BulkVerificationListResponse as BulkVerificationListResponse, + type BulkVerificationUploadResponse as BulkVerificationUploadResponse, + type BulkVerificationListParams as BulkVerificationListParams, + type BulkVerificationUploadParams as BulkVerificationUploadParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index 35e765a..22e5cfa 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -22,6 +22,15 @@ export { type AddressVerificationSuggestAddressesParams, type AddressVerificationVerifyParams, } from './address-verification'; +export { + BulkVerification, + type AddverList, + type BulkVerificationRetrieveResponse, + type BulkVerificationListResponse, + type BulkVerificationUploadResponse, + type BulkVerificationListParams, + type BulkVerificationUploadParams, +} from './bulk-verification'; export { IntlAddressVerification, type IntlAddressVerificationAutocompleteResponse, diff --git a/src/resources/intl-address-verification.ts b/src/resources/intl-address-verification.ts index 72c0376..ed59c88 100644 --- a/src/resources/intl-address-verification.ts +++ b/src/resources/intl-address-verification.ts @@ -47,11 +47,15 @@ export class IntlAddressVerification extends APIResource { * be freeform or structured, matching the same input formats accepted by the * single verification endpoint. * + * - Accepts up to 2,000 addresses per request. * - Uses 1 lookup per address. * - Requires a secret API key. * - Returns results in the same order as the input addresses. * - If an individual address fails, its result will contain an `error` field * rather than a `verifiedAddress`. + * - If you are not subscribed and the batch would exceed your remaining free + * lookups, the entire batch fails (nothing is verified). Size your batch to the + * number of lookups you have left. * * @example * ```ts @@ -87,16 +91,19 @@ export class IntlAddressVerification extends APIResource { * populating an autocomplete dropdown. * * **Regular mode** — supply `partialStreet` to search by partial street address. - * Results may include `Address` types (resolvable directly) and `Container` types - * (buildings/complexes that require a follow-up call). + * Results may include directly-resolvable `Address` results and non-`Address` + * results (e.g. `BuildingNumber`) that represent buildings/complexes requiring a + * follow-up call. * - * **Advanced mode** — supply `advanced=true` and a `container` ID (from a previous - * regular call) to drill into a building or complex and retrieve individual unit - * addresses. + * **Advanced mode** — supply `advanced=true` and a `container` ID (the `id` of a + * non-`Address` result from a previous regular call) to drill into a building or + * complex and retrieve individual unit addresses. * * Results with `type: "Address"` can be fully resolved by passing their `id` to * `POST /completions`. * + * - Results are biased by the caller's IP address by default; pass + * `disableIPBiasing=true` to turn this off. * - Does not consume a lookup. * * @example @@ -117,16 +124,19 @@ export class IntlAddressVerification extends APIResource { * populating an autocomplete dropdown. * * **Regular mode** — supply `partialStreet` to search by partial street address. - * Results may include `Address` types (resolvable directly) and `Container` types - * (buildings/complexes that require a follow-up call). + * Results may include directly-resolvable `Address` results and non-`Address` + * results (e.g. `BuildingNumber`) that represent buildings/complexes requiring a + * follow-up call. * - * **Advanced mode** — supply `advanced=true` and a `container` ID (from a previous - * regular call) to drill into a building or complex and retrieve individual unit - * addresses. + * **Advanced mode** — supply `advanced=true` and a `container` ID (the `id` of a + * non-`Address` result from a previous regular call) to drill into a building or + * complex and retrieve individual unit addresses. * * Results with `type: "Address"` can be fully resolved by passing their `id` to * `POST /completions`. * + * - Results are biased by the caller's IP address by default; pass + * `disableIPBiasing=true` to turn this off. * - Does not consume a lookup. * * @example @@ -969,9 +979,10 @@ export namespace IntlAddressVerificationGetAutocompleteAdvancedPreviewsResponse */ export interface Data { /** - * The unique identifier for this result. Pass this to `POST /completions` to - * retrieve the full address. If the `type` is `Container`, pass it as the - * `container` parameter to `GET /completions` to drill down further. + * The unique identifier for this result. If the result is a fully resolvable + * address (`type` is `Address`), pass this to `POST /completions` to retrieve the + * full address. Otherwise, pass it as the `container` query parameter to + * `GET /completions` to drill down further. */ id?: string; @@ -1020,9 +1031,10 @@ export namespace IntlAddressVerificationGetAutocompletePreviewsResponse { */ export interface Data { /** - * The unique identifier for this result. Pass this to `POST /completions` to - * retrieve the full address. If the `type` is `Container`, pass it as the - * `container` parameter to `GET /completions` to drill down further. + * The unique identifier for this result. If the result is a fully resolvable + * address (`type` is `Address`), pass this to `POST /completions` to retrieve the + * full address. Otherwise, pass it as the `container` query parameter to + * `GET /completions` to drill down further. */ id?: string; diff --git a/src/resources/print-mail/bank-accounts.ts b/src/resources/print-mail/bank-accounts.ts index 7d49120..dd4d3ce 100644 --- a/src/resources/print-mail/bank-accounts.ts +++ b/src/resources/print-mail/bank-accounts.ts @@ -6,6 +6,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Manage bank accounts that will be used for mailing cheques. + */ export class BankAccounts extends APIResource { /** * Create a bank account. A US bank account is created by setting `bankCountryCode` @@ -16,6 +19,10 @@ export class BankAccounts extends APIResource { * You must specify _either_ `signatureImage` or `signatureText`. The image can be * supplied as either a URL or a multipart file upload. * + * Note that the reasonable character limit for `signatureText` is 6 capital + * letters or 20 lowercase letters — anything exceeding that will likely overflow + * onto a new line. + * * @example * ```ts * const bankAccount = @@ -37,6 +44,9 @@ export class BankAccounts extends APIResource { /** * Retrieve a bank account by ID. * + * Note that we do not return the complete account number or the signature image + * for security reasons. + * * @example * ```ts * const bankAccount = @@ -50,6 +60,9 @@ export class BankAccounts extends APIResource { /** * Get a list of bank accounts. * + * Note that we do not return the complete account number or the signature image + * for security reasons. + * * @example * ```ts * // Automatically fetches more pages as needed. diff --git a/src/resources/print-mail/boxes.ts b/src/resources/print-mail/boxes.ts index a1e40f2..815dc11 100644 --- a/src/resources/print-mail/boxes.ts +++ b/src/resources/print-mail/boxes.ts @@ -7,6 +7,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage box orders. + */ export class Boxes extends APIResource { /** * This endpoint allows you to create a box containing up to 100 cheques. A Box is diff --git a/src/resources/print-mail/cheques.ts b/src/resources/print-mail/cheques.ts index 225272d..69309f9 100644 --- a/src/resources/print-mail/cheques.ts +++ b/src/resources/print-mail/cheques.ts @@ -7,6 +7,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage cheque orders. + */ export class Cheques extends APIResource { /** * Create a cheque. @@ -15,7 +18,9 @@ export class Cheques extends APIResource { * * If you would like to create a digitalOnly cheque, the digitalOnly object with * the watermark will need to be passed in. Feature is available on request, e-mail - * support@postgrid.com for access. + * support@postgrid.com for access. Digital-only cheques are not sent out — they + * are created with a `cancelled` status and a cancellation reason of + * `digital_only`. * * Example request body: * @@ -319,12 +324,29 @@ export interface Cheque { */ imbZIPCode?: string; + /** + * The raw HTML content for a letter attached to the cheque, if any. You can supply + * _either_ this, `letterTemplate`, or `letterPDF`, but not more than one. + */ + letterHTML?: string; + + /** + * A Template ID for the letter attached to the cheque, if any. + */ + letterTemplate?: string; + + /** + * A signed URL pointing to the original PDF of the letter attached to the cheque, + * if any. + */ + letterUploadedPDF?: string; + /** * An optional logo URL for the cheque. This will be placed next to the recipient * address at the top left corner of the cheque. This needs to be a public link to * an image file (e.g. a PNG or JPEG file). */ - logoURL?: string; + logo?: string; /** * The memo of the cheque. @@ -356,6 +378,12 @@ export interface Cheque { */ number?: number; + /** + * The return envelope (ID) sent out with the cheque, if any. Note that you must + * first order return envelopes using the Return Envelopes API. + */ + returnEnvelope?: string; + /** * The tracking number of this order. Populated after an express/certified order * has been processed for delivery. @@ -408,6 +436,27 @@ export interface DigitalOnly { * Text to be displayed as a watermark on the digital cheque. */ watermark: string; + + /** + * The payee of the digital cheque. Supplying `payee.name` lets you create a + * digital-only cheque without a `to` contact — when it is provided, the top-level + * `to` field may be omitted. + */ + payee?: DigitalOnly.Payee; +} + +export namespace DigitalOnly { + /** + * The payee of the digital cheque. Supplying `payee.name` lets you create a + * digital-only cheque without a `to` contact — when it is provided, the top-level + * `to` field may be omitted. + */ + export interface Payee { + /** + * The name of the payee. + */ + name: string; + } } export interface ChequeRetrieveURLResponse { @@ -476,12 +525,35 @@ export interface ChequeCreateParams { */ envelope?: 'standard' | (string & {}); + /** + * The raw HTML content for a letter attached to the cheque, if any. You can supply + * _either_ this, `letterTemplate`, or `letterPDF`, but not more than one. + */ + letterHTML?: string; + + /** + * A URL pointing to a PDF for the letter attached to the cheque, or the PDF file + * itself when uploaded via a multipart form request. You can supply _either_ this, + * `letterHTML`, or `letterTemplate`, but not more than one. + */ + letterPDF?: string; + + /** + * Settings for a letter attached to a cheque. + */ + letterSettings?: ChequeCreateParams.LetterSettings; + + /** + * A Template ID for the letter attached to the cheque, if any. + */ + letterTemplate?: string; + /** * An optional logo URL for the cheque. This will be placed next to the recipient * address at the top left corner of the cheque. This needs to be a public link to * an image file (e.g. a PNG or JPEG file). */ - logoURL?: string; + logo?: string; /** * The mailing class of this order. If not provided, automatically set to @@ -554,6 +626,12 @@ export interface ChequeCreateParams { */ redirectTo?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + /** + * The return envelope (ID) sent out with the cheque, if any. Note that you must + * first order return envelopes using the Return Envelopes API. + */ + returnEnvelope?: string; + /** * This order will transition from `ready` to `printing` on the day after this * date. You can use this parameter to schedule orders for a future date. @@ -566,6 +644,19 @@ export interface ChequeCreateParams { size?: ChequeSize; } +export namespace ChequeCreateParams { + /** + * Settings for a letter attached to a cheque. + */ + export interface LetterSettings { + /** + * Enum representing where a letter attached to a cheque is placed relative to the + * cheque page. + */ + placement?: 'before_cheque' | 'after_cheque'; + } +} + export interface ChequeListParams extends SkipLimitParams { /** * You can supply any string to help narrow down the list of resources. For diff --git a/src/resources/print-mail/contacts.ts b/src/resources/print-mail/contacts.ts index fc060be..cd930f1 100644 --- a/src/resources/print-mail/contacts.ts +++ b/src/resources/print-mail/contacts.ts @@ -6,6 +6,24 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Manage contacts that you can mail to. Test mode addresses will always have a + * `verified` status. In live mode, they may be `verified`, `corrected`, or + * `failed`. Addresses that fail to be corrected are likely undeliverable, but + * you can still send to them if you want to. + * + * For test mode contacts, you have the ability to assert the `addressStatus` of + * the contact by passing specific values to the `description` field. To receive + * an `addressStatus` of `failed`, the description of the contact should be a + * string with the exact value `test failed`. For an `addressStatus` value of + * `corrected`, the description of the contact should be a string with the exact + * value `test corrected`. + * + * Our address correction engine will often be able to fix missing postal/ZIP + * codes, city names, and also append ZIP+4. It is SERP (Canada Post) and CASS + * (USPS) certified, so you can rest assured that if an address is verified, we + * can deliver to it. + */ export class Contacts extends APIResource { /** * Creates a contact. This will also verify the contact's address **if you create @@ -200,6 +218,13 @@ export interface Contact { */ provinceOrState?: string; + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + /** * If `true`, PostGrid will skip running this contact's address through our address * verification system. @@ -284,6 +309,13 @@ export interface ContactCreateWithCompanyName { */ provinceOrState?: string; + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + /** * If `true`, PostGrid will skip running this contact's address through our address * verification system. @@ -366,6 +398,13 @@ export interface ContactCreateWithFirstName { */ provinceOrState?: string; + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + /** * If `true`, PostGrid will skip running this contact's address through our address * verification system. @@ -467,6 +506,13 @@ export declare namespace ContactCreateParams { */ provinceOrState?: string; + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + /** * If `true`, PostGrid will skip running this contact's address through our address * verification system. @@ -549,6 +595,13 @@ export declare namespace ContactCreateParams { */ provinceOrState?: string; + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + /** * If `true`, PostGrid will skip running this contact's address through our address * verification system. diff --git a/src/resources/print-mail/index.ts b/src/resources/print-mail/index.ts index 83fc2c4..b475437 100644 --- a/src/resources/print-mail/index.ts +++ b/src/resources/print-mail/index.ts @@ -108,6 +108,13 @@ export { type ReportSampleParams, type ReportsSkipLimit, } from './reports/index'; +export { + ReturnEnvelopes, + type ReturnEnvelope, + type ReturnEnvelopeCreateParams, + type ReturnEnvelopeListParams, + type ReturnEnvelopesSkipLimit, +} from './return-envelopes/index'; export { SelfMailers, type SelfMailer, diff --git a/src/resources/print-mail/letters.ts b/src/resources/print-mail/letters.ts index c3132d1..3df2a87 100644 --- a/src/resources/print-mail/letters.ts +++ b/src/resources/print-mail/letters.ts @@ -7,6 +7,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage letter orders. + */ export class Letters extends APIResource { /** * Create a letter. Note that you can supply one of the following: diff --git a/src/resources/print-mail/postcards.ts b/src/resources/print-mail/postcards.ts index 333ea86..e0d83e6 100644 --- a/src/resources/print-mail/postcards.ts +++ b/src/resources/print-mail/postcards.ts @@ -7,6 +7,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage postcard mailings. + */ export class Postcards extends APIResource { /** * Create a postcard. Note that you can supply one of the following: diff --git a/src/resources/print-mail/print-mail.ts b/src/resources/print-mail/print-mail.ts index 85ea6dd..6d1a2ba 100644 --- a/src/resources/print-mail/print-mail.ts +++ b/src/resources/print-mail/print-mail.ts @@ -203,6 +203,14 @@ import { Reports, ReportsSkipLimit, } from './reports/reports'; +import * as ReturnEnvelopesAPI from './return-envelopes/return-envelopes'; +import { + ReturnEnvelope, + ReturnEnvelopeCreateParams, + ReturnEnvelopeListParams, + ReturnEnvelopes, + ReturnEnvelopesSkipLimit, +} from './return-envelopes/return-envelopes'; import * as TargetedListBuildsAPI from './targeted-list-builds/targeted-list-builds'; import { TargetedListBuildConfirmResponse, @@ -240,6 +248,7 @@ export class PrintMail extends APIResource { bankAccounts: BankAccountsAPI.BankAccounts = new BankAccountsAPI.BankAccounts(this._client); cheques: ChequesAPI.Cheques = new ChequesAPI.Cheques(this._client); selfMailers: SelfMailersAPI.SelfMailers = new SelfMailersAPI.SelfMailers(this._client); + returnEnvelopes: ReturnEnvelopesAPI.ReturnEnvelopes = new ReturnEnvelopesAPI.ReturnEnvelopes(this._client); campaigns: CampaignsAPI.Campaigns = new CampaignsAPI.Campaigns(this._client); mailingListImports: MailingListImportsAPI.MailingListImports = new MailingListImportsAPI.MailingListImports( this._client, @@ -271,6 +280,7 @@ PrintMail.Postcards = Postcards; PrintMail.BankAccounts = BankAccounts; PrintMail.Cheques = Cheques; PrintMail.SelfMailers = SelfMailers; +PrintMail.ReturnEnvelopes = ReturnEnvelopes; PrintMail.Campaigns = Campaigns; PrintMail.MailingListImports = MailingListImports; PrintMail.MailingLists = MailingLists; @@ -396,6 +406,14 @@ export declare namespace PrintMail { type SelfMailerListParams as SelfMailerListParams, }; + export { + ReturnEnvelopes as ReturnEnvelopes, + type ReturnEnvelope as ReturnEnvelope, + type ReturnEnvelopesSkipLimit as ReturnEnvelopesSkipLimit, + type ReturnEnvelopeCreateParams as ReturnEnvelopeCreateParams, + type ReturnEnvelopeListParams as ReturnEnvelopeListParams, + }; + export { Campaigns as Campaigns, type Campaign as Campaign, diff --git a/src/resources/print-mail/return-envelopes.ts b/src/resources/print-mail/return-envelopes.ts new file mode 100644 index 0000000..0b220eb --- /dev/null +++ b/src/resources/print-mail/return-envelopes.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './return-envelopes/index'; diff --git a/src/resources/print-mail/return-envelopes/index.ts b/src/resources/print-mail/return-envelopes/index.ts new file mode 100644 index 0000000..fd67aa9 --- /dev/null +++ b/src/resources/print-mail/return-envelopes/index.ts @@ -0,0 +1,19 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { + Orders, + type ReturnEnvelopeOrder, + type OrderCreateParams, + type OrderRetrieveParams, + type OrderListParams, + type OrderCancelParams, + type OrderFillParams, + type ReturnEnvelopeOrdersSkipLimit, +} from './orders'; +export { + ReturnEnvelopes, + type ReturnEnvelope, + type ReturnEnvelopeCreateParams, + type ReturnEnvelopeListParams, + type ReturnEnvelopesSkipLimit, +} from './return-envelopes'; diff --git a/src/resources/print-mail/return-envelopes/orders.ts b/src/resources/print-mail/return-envelopes/orders.ts new file mode 100644 index 0000000..52755b1 --- /dev/null +++ b/src/resources/print-mail/return-envelopes/orders.ts @@ -0,0 +1,273 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../../core/resource'; +import * as ReturnEnvelopesAPI from './return-envelopes'; +import { APIPromise } from '../../../core/api-promise'; +import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination'; +import { RequestOptions } from '../../../internal/request-options'; +import { path } from '../../../internal/utils/path'; + +/** + * You can use the return envelopes API to create and manage return envelopes. + * These are envelopes that are sent along with your mail (if specified) and + * allow your recipients to send mail to a particular address without having to + * purchase their own envelopes/stamps. + * + * Note that you must order return envelopes and wait for the order to be + * filled before you can use them. You can manage these return envelope orders + * via the API as well as the dashboard. + */ +export class Orders extends APIResource { + /** + * Creates a batch order of return envelopes. The minimum order quantity is 5000. + * + * @example + * ```ts + * const returnEnvelopeOrder = + * await client.printMail.returnEnvelopes.orders.create( + * 'id', + * { + * quantityOrdered: 5000, + * description: 'A batch of 5000', + * }, + * ); + * ``` + */ + create(id: string, body: OrderCreateParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/print-mail/v1/return_envelopes/${id}/orders`, { body, ...options }); + } + + /** + * Gets a specific return envelope order by return envelope ID as `id` and return + * envelope order ID as `orderID`. + * + * @example + * ```ts + * const returnEnvelopeOrder = + * await client.printMail.returnEnvelopes.orders.retrieve( + * 'orderID', + * { id: 'id' }, + * ); + * ``` + */ + retrieve( + orderID: string, + params: OrderRetrieveParams, + options?: RequestOptions, + ): APIPromise { + const { id, ...query } = params; + return this._client.get(path`/print-mail/v1/return_envelopes/${id}/orders/${orderID}`, { + query, + ...options, + }); + } + + /** + * Gets a list of orders for the return envelope by `id`. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const returnEnvelopeOrder of client.printMail.returnEnvelopes.orders.list( + * 'id', + * )) { + * // ... + * } + * ``` + */ + list( + id: string, + query: OrderListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList( + path`/print-mail/v1/return_envelopes/${id}/orders`, + SkipLimit, + { query, ...options }, + ); + } + + /** + * Cancels the return envelope order by `orderID` for the return envelope by `id`. + * Note that this operation cannot be undone. + * + * @example + * ```ts + * const returnEnvelopeOrder = + * await client.printMail.returnEnvelopes.orders.cancel( + * 'orderID', + * { id: 'id' }, + * ); + * ``` + */ + cancel( + orderID: string, + params: OrderCancelParams, + options?: RequestOptions, + ): APIPromise { + const { id, expand } = params; + return this._client.delete(path`/print-mail/v1/return_envelopes/${id}/orders/${orderID}`, { + query: { expand }, + ...options, + }); + } + + /** + * Fills the return envelope order by `orderID` for the return envelope by `id`. + * This is only available in test mode and can be used to simulate how a live order + * would be filled. + * + * Note: this will fail with a `return_envelope_order_cannot_fill_error` if the + * order's status is not `placed`. + * + * @example + * ```ts + * const returnEnvelopeOrder = + * await client.printMail.returnEnvelopes.orders.fill( + * 'orderID', + * { id: 'id' }, + * ); + * ``` + */ + fill(orderID: string, params: OrderFillParams, options?: RequestOptions): APIPromise { + const { id } = params; + return this._client.post(path`/print-mail/v1/return_envelopes/${id}/orders/${orderID}/fills`, options); + } +} + +export type ReturnEnvelopeOrdersSkipLimit = SkipLimit; + +export interface ReturnEnvelopeOrder { + /** + * A unique ID prefixed with return*envelope_order* + */ + id: string; + + /** + * The UTC time at which this resource was created. + */ + createdAt: string; + + /** + * `true` if this is a live mode resource else `false`. + */ + live: boolean; + + /** + * Always `return_envelope_order`. + */ + object: 'return_envelope_order'; + + /** + * The quantity of return envelopes ordered. Minimum 5000. + */ + quantityOrdered: number; + + /** + * The ID of the return envelope that this order replenishes. Expanded into the + * full return envelope object on the individual order retrieval and cancellation + * endpoints when `expand[]=returnEnvelope` is supplied. + */ + returnEnvelope: string | ReturnEnvelopesAPI.ReturnEnvelope; + + /** + * The status of a return envelope order. + */ + status: 'placed' | 'filled' | 'cancelled'; + + /** + * The UTC time at which this resource was last updated. + */ + updatedAt: string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * The quantity of return envelopes that were filled for this order. Only returned + * once the order's status is `filled`. + */ + quantityFilled?: number; +} + +export interface OrderCreateParams { + /** + * The quantity of return envelopes ordered. Minimum 5000. + */ + quantityOrdered: number; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; +} + +export interface OrderRetrieveParams { + /** + * Path param: The ID of the return envelope. + */ + id: string; + + /** + * Query param: Pass `expand[]=returnEnvelope` to expand the order's + * `returnEnvelope` field into the full return envelope object. + */ + expand?: Array<'returnEnvelope'>; +} + +export interface OrderListParams extends SkipLimitParams { + /** + * You can supply any string to help narrow down the list of resources. For + * example, if you pass `"New York"` (quoted), it will return resources that have + * that string present somewhere in their response. Alternatively, you can supply a + * structured search query. See the documentation on `StructuredSearchQuery` for + * more details. + */ + search?: string; +} + +export interface OrderCancelParams { + /** + * Path param: The ID of the return envelope. + */ + id: string; + + /** + * Query param: Pass `expand[]=returnEnvelope` to expand the order's + * `returnEnvelope` field into the full return envelope object. + */ + expand?: Array<'returnEnvelope'>; +} + +export interface OrderFillParams { + /** + * The ID of the return envelope. + */ + id: string; +} + +export declare namespace Orders { + export { + type ReturnEnvelopeOrder as ReturnEnvelopeOrder, + type ReturnEnvelopeOrdersSkipLimit as ReturnEnvelopeOrdersSkipLimit, + type OrderCreateParams as OrderCreateParams, + type OrderRetrieveParams as OrderRetrieveParams, + type OrderListParams as OrderListParams, + type OrderCancelParams as OrderCancelParams, + type OrderFillParams as OrderFillParams, + }; +} diff --git a/src/resources/print-mail/return-envelopes/return-envelopes.ts b/src/resources/print-mail/return-envelopes/return-envelopes.ts new file mode 100644 index 0000000..e2e62f3 --- /dev/null +++ b/src/resources/print-mail/return-envelopes/return-envelopes.ts @@ -0,0 +1,320 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../../core/resource'; +import * as ContactsAPI from '../contacts'; +import * as OrdersAPI from './orders'; +import { + OrderCancelParams, + OrderCreateParams, + OrderFillParams, + OrderListParams, + OrderRetrieveParams, + Orders, + ReturnEnvelopeOrder, + ReturnEnvelopeOrdersSkipLimit, +} from './orders'; +import { APIPromise } from '../../../core/api-promise'; +import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination'; +import { buildHeaders } from '../../../internal/headers'; +import { RequestOptions } from '../../../internal/request-options'; +import { path } from '../../../internal/utils/path'; + +/** + * You can use the return envelopes API to create and manage return envelopes. + * These are envelopes that are sent along with your mail (if specified) and + * allow your recipients to send mail to a particular address without having to + * purchase their own envelopes/stamps. + * + * Note that you must order return envelopes and wait for the order to be + * filled before you can use them. You can manage these return envelope orders + * via the API as well as the dashboard. + */ +export class ReturnEnvelopes extends APIResource { + orders: OrdersAPI.Orders = new OrdersAPI.Orders(this._client); + + /** + * Creates a new return envelope. Note that if there is already a return envelope + * for the destination contact, this will fail with a + * `return_envelope_already_exists_error`. + * + * @example + * ```ts + * const returnEnvelope = + * await client.printMail.returnEnvelopes.create({ + * to: 'contact_kFjQtFqJtRXgahx5vgc9mA', + * }); + * ``` + */ + create(params: ReturnEnvelopeCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post('/print-mail/v1/return_envelopes', { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }); + } + + /** + * Gets the information for a return envelope by `id`. This should be a unique + * identifying string starting with `return_envelope_`. + * + * @example + * ```ts + * const returnEnvelope = + * await client.printMail.returnEnvelopes.retrieve('id'); + * ``` + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/print-mail/v1/return_envelopes/${id}`, options); + } + + /** + * Gets a list of return envelopes for the user. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const returnEnvelope of client.printMail.returnEnvelopes.list()) { + * // ... + * } + * ``` + */ + list( + query: ReturnEnvelopeListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/print-mail/v1/return_envelopes', SkipLimit, { + query, + ...options, + }); + } +} + +export type ReturnEnvelopesSkipLimit = SkipLimit; + +export interface ReturnEnvelope { + /** + * A unique ID prefixed with return*envelope* + */ + id: string; + + /** + * The number of return envelopes available to use in your orders immediately. This + * increases when a return envelope order is filled and decreases as you send + * orders which include this return envelope. + */ + available: number; + + /** + * The UTC time at which this resource was created. + */ + createdAt: string; + + /** + * `true` if this is a live mode resource else `false`. + */ + live: boolean; + + /** + * Always `return_envelope`. + */ + object: 'return_envelope'; + + /** + * The contact denormalized onto a return envelope when it is created. Unlike a + * full contact it is not a standalone resource, so it has no `object`, `live`, + * `createdAt`, or `updatedAt` fields. + */ + to: ReturnEnvelope.To; + + /** + * The UTC time at which this resource was last updated. + */ + updatedAt: string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; +} + +export namespace ReturnEnvelope { + /** + * The contact denormalized onto a return envelope when it is created. Unlike a + * full contact it is not a standalone resource, so it has no `object`, `live`, + * `createdAt`, or `updatedAt` fields. + */ + export interface To { + /** + * A unique ID prefixed with contact\_ + */ + id: string; + + /** + * The first line of the contact's address. + */ + addressLine1: string; + + /** + * One of `verified`, `corrected`, or `failed`. + */ + addressStatus: 'verified' | 'corrected' | 'failed'; + + /** + * The ISO 3611-1 country code of the contact's address. + */ + countryCode: string; + + /** + * A series of human-readable errors/warnings that were raised when running the + * provided address through our address verification. + */ + addressErrors?: string; + + /** + * Second line of the contact's address, if applicable. + */ + addressLine2?: string; + + /** + * The city of the contact's address. + */ + city?: string; + + /** + * Company name of the contact. + */ + companyName?: string; + + /** + * An optional string describing this resource. Will be visible in the API and the + * dashboard. + */ + description?: string; + + /** + * Email of the contact. + */ + email?: string; + + /** + * First name of the contact. + */ + firstName?: string; + + /** + * If `true`, PostGrid will force this contact to have an `addressStatus` of + * `verified` even if our address verification system says otherwise. + */ + forceVerifiedStatus?: boolean; + + /** + * Job title of the contact. + */ + jobTitle?: string; + + /** + * Last name of the contact. + */ + lastName?: string; + + /** + * See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * Phone number of the contact. + */ + phoneNumber?: string; + + /** + * The postal or ZIP code of the contact's address. + */ + postalOrZip?: string; + + /** + * Province or state of the contact's address. + */ + provinceOrState?: string; + + /** + * If `true`, the contact's details are hidden from the dashboard and API responses + * apart from the final print. The contact ID can then be used as a token for + * sending mail without giving access to the underlying data. + */ + secret?: boolean; + + /** + * If `true`, PostGrid will skip running this contact's address through our address + * verification system. + */ + skipVerification?: boolean; + } +} + +export interface ReturnEnvelopeCreateParams { + /** + * Body param: A contact ID or a contact object containing the address that will be + * printed onto the return envelope. + */ + to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; + + /** + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. + */ + description?: string; + + /** + * Body param: See the section on Metadata. + */ + metadata?: { [key: string]: unknown }; + + /** + * Header param + */ + 'idempotency-key'?: string; +} + +export interface ReturnEnvelopeListParams extends SkipLimitParams { + /** + * You can supply any string to help narrow down the list of resources. For + * example, if you pass `"New York"` (quoted), it will return resources that have + * that string present somewhere in their response. Alternatively, you can supply a + * structured search query. See the documentation on `StructuredSearchQuery` for + * more details. + */ + search?: string; +} + +ReturnEnvelopes.Orders = Orders; + +export declare namespace ReturnEnvelopes { + export { + type ReturnEnvelope as ReturnEnvelope, + type ReturnEnvelopesSkipLimit as ReturnEnvelopesSkipLimit, + type ReturnEnvelopeCreateParams as ReturnEnvelopeCreateParams, + type ReturnEnvelopeListParams as ReturnEnvelopeListParams, + }; + + export { + Orders as Orders, + type ReturnEnvelopeOrder as ReturnEnvelopeOrder, + type ReturnEnvelopeOrdersSkipLimit as ReturnEnvelopeOrdersSkipLimit, + type OrderCreateParams as OrderCreateParams, + type OrderRetrieveParams as OrderRetrieveParams, + type OrderListParams as OrderListParams, + type OrderCancelParams as OrderCancelParams, + type OrderFillParams as OrderFillParams, + }; +} diff --git a/src/resources/print-mail/self-mailers.ts b/src/resources/print-mail/self-mailers.ts index 671e93f..6e62d14 100644 --- a/src/resources/print-mail/self-mailers.ts +++ b/src/resources/print-mail/self-mailers.ts @@ -7,6 +7,9 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage self mailers. + */ export class SelfMailers extends APIResource { /** * Create a self-mailer. Note that you can supply one of the following: diff --git a/src/resources/print-mail/templates.ts b/src/resources/print-mail/templates.ts index a6fdf40..25c47b4 100644 --- a/src/resources/print-mail/templates.ts +++ b/src/resources/print-mail/templates.ts @@ -6,6 +6,11 @@ import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/paginat import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; +/** + * Create and manage reusable HTML templates. A template's HTML can include + * merge variables (e.g. `{{firstName}}`) and be referenced by ID when creating + * letters, postcards, cheques, and self mailers. + */ export class Templates extends APIResource { /** * Create a template. Note that if you want to create a template that works with diff --git a/tests/api-resources/bulk-verification.test.ts b/tests/api-resources/bulk-verification.test.ts new file mode 100644 index 0000000..d0009e6 --- /dev/null +++ b/tests/api-resources/bulk-verification.test.ts @@ -0,0 +1,84 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import PostGrid, { toFile } from 'postgrid-node'; + +const client = new PostGrid({ + addressVerificationAPIKey: 'My Address Verification API Key', + printMailAPIKey: 'My Print Mail API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource bulkVerification', () => { + // Mock server tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.bulkVerification.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list', async () => { + const responsePromise = client.bulkVerification.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.bulkVerification.list({ limit: 0, skip: 0 }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(PostGrid.NotFoundError); + }); + + // Mock server tests are disabled + test.skip('upload: only required params', async () => { + const responsePromise = client.bulkVerification.upload({ + file: await toFile(Buffer.from('Example data'), 'README.md'), + mappings: { line1: 'line1' }, + name: 'name', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('upload: required and optional params', async () => { + const response = await client.bulkVerification.upload({ + file: await toFile(Buffer.from('Example data'), 'README.md'), + mappings: { + line1: 'line1', + city: 'city', + country: 'country', + firstName: 'firstName', + fullName: 'fullName', + lastName: 'lastName', + line2: 'line2', + postalOrZip: 'postalOrZip', + provinceOrState: 'provinceOrState', + }, + name: 'name', + defaultCountry: 'defaultCountry', + runCCOA: true, + runNCOA: true, + useGeocode: true, + useIntlVerification: true, + useProperCase: true, + }); + }); +}); diff --git a/tests/api-resources/print-mail/cheques.test.ts b/tests/api-resources/print-mail/cheques.test.ts index 5d1d4b9..ee8db21 100644 --- a/tests/api-resources/print-mail/cheques.test.ts +++ b/tests/api-resources/print-mail/cheques.test.ts @@ -35,9 +35,16 @@ describe('resource cheques', () => { to: 'contact_123', currencyCode: 'USD', description: 'description', - digitalOnly: { watermark: 'watermark' }, + digitalOnly: { + watermark: 'watermark', + payee: { name: 'name' }, + }, envelope: 'standard', - logoURL: 'https://example.com', + letterHTML: 'letterHTML', + letterPDF: 'U3RhaW5sZXNzIHJvY2tz', + letterSettings: { placement: 'before_cheque' }, + letterTemplate: 'letterTemplate', + logo: 'https://example.com', mailingClass: 'first_class', memo: 'memo', mergeVariables: { foo: 'bar' }, @@ -60,8 +67,10 @@ describe('resource cheques', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, + returnEnvelope: 'returnEnvelope', sendDate: '2019-12-27T18:11:19.117Z', size: 'us_letter', }); diff --git a/tests/api-resources/print-mail/contacts.test.ts b/tests/api-resources/print-mail/contacts.test.ts index ad3bac7..99afbee 100644 --- a/tests/api-resources/print-mail/contacts.test.ts +++ b/tests/api-resources/print-mail/contacts.test.ts @@ -43,6 +43,7 @@ describe('resource contacts', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }); }); diff --git a/tests/api-resources/print-mail/letters.test.ts b/tests/api-resources/print-mail/letters.test.ts index d649189..2e1fd8d 100644 --- a/tests/api-resources/print-mail/letters.test.ts +++ b/tests/api-resources/print-mail/letters.test.ts @@ -52,6 +52,7 @@ describe('resource letters', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, html: 'html', @@ -71,6 +72,7 @@ describe('resource letters', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, addressPlacement: 'top_first_page', diff --git a/tests/api-resources/print-mail/postcards.test.ts b/tests/api-resources/print-mail/postcards.test.ts index bf2f144..197d527 100644 --- a/tests/api-resources/print-mail/postcards.test.ts +++ b/tests/api-resources/print-mail/postcards.test.ts @@ -52,6 +52,7 @@ describe('resource postcards', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, description: 'description', @@ -71,6 +72,7 @@ describe('resource postcards', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, mailingClass: 'first_class', diff --git a/tests/api-resources/print-mail/return-envelopes/orders.test.ts b/tests/api-resources/print-mail/return-envelopes/orders.test.ts new file mode 100644 index 0000000..573b933 --- /dev/null +++ b/tests/api-resources/print-mail/return-envelopes/orders.test.ts @@ -0,0 +1,117 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import PostGrid from 'postgrid-node'; + +const client = new PostGrid({ + addressVerificationAPIKey: 'My Address Verification API Key', + printMailAPIKey: 'My Print Mail API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource orders', () => { + // Mock server tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.printMail.returnEnvelopes.orders.create('id', { quantityOrdered: 5000 }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.printMail.returnEnvelopes.orders.create('id', { + quantityOrdered: 5000, + description: 'A batch of 5000', + metadata: { foo: 'bar' }, + }); + }); + + // Mock server tests are disabled + test.skip('retrieve: only required params', async () => { + const responsePromise = client.printMail.returnEnvelopes.orders.retrieve('orderID', { id: 'id' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('retrieve: required and optional params', async () => { + const response = await client.printMail.returnEnvelopes.orders.retrieve('orderID', { + id: 'id', + expand: ['returnEnvelope'], + }); + }); + + // Mock server tests are disabled + test.skip('list', async () => { + const responsePromise = client.printMail.returnEnvelopes.orders.list('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.printMail.returnEnvelopes.orders.list( + 'id', + { + limit: 0, + search: 'search', + skip: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(PostGrid.NotFoundError); + }); + + // Mock server tests are disabled + test.skip('cancel: only required params', async () => { + const responsePromise = client.printMail.returnEnvelopes.orders.cancel('orderID', { id: 'id' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('cancel: required and optional params', async () => { + const response = await client.printMail.returnEnvelopes.orders.cancel('orderID', { + id: 'id', + expand: ['returnEnvelope'], + }); + }); + + // Mock server tests are disabled + test.skip('fill: only required params', async () => { + const responsePromise = client.printMail.returnEnvelopes.orders.fill('orderID', { id: 'id' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('fill: required and optional params', async () => { + const response = await client.printMail.returnEnvelopes.orders.fill('orderID', { id: 'id' }); + }); +}); diff --git a/tests/api-resources/print-mail/return-envelopes/return-envelopes.test.ts b/tests/api-resources/print-mail/return-envelopes/return-envelopes.test.ts new file mode 100644 index 0000000..8962558 --- /dev/null +++ b/tests/api-resources/print-mail/return-envelopes/return-envelopes.test.ts @@ -0,0 +1,72 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import PostGrid from 'postgrid-node'; + +const client = new PostGrid({ + addressVerificationAPIKey: 'My Address Verification API Key', + printMailAPIKey: 'My Print Mail API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource returnEnvelopes', () => { + // Mock server tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.printMail.returnEnvelopes.create({ to: 'contact_kFjQtFqJtRXgahx5vgc9mA' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.printMail.returnEnvelopes.create({ + to: 'contact_kFjQtFqJtRXgahx5vgc9mA', + description: 'description', + metadata: { foo: 'bar' }, + 'idempotency-key': 'idempotency-key', + }); + }); + + // Mock server tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.printMail.returnEnvelopes.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list', async () => { + const responsePromise = client.printMail.returnEnvelopes.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.printMail.returnEnvelopes.list( + { + limit: 0, + search: 'search', + skip: 0, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(PostGrid.NotFoundError); + }); +}); diff --git a/tests/api-resources/print-mail/self-mailers.test.ts b/tests/api-resources/print-mail/self-mailers.test.ts index f59b633..cafcd7d 100644 --- a/tests/api-resources/print-mail/self-mailers.test.ts +++ b/tests/api-resources/print-mail/self-mailers.test.ts @@ -54,6 +54,7 @@ describe('resource selfMailers', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, insideHTML: 'insideHTML', @@ -75,6 +76,7 @@ describe('resource selfMailers', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, description: 'description', diff --git a/tests/api-resources/print-mail/snap-packs.test.ts b/tests/api-resources/print-mail/snap-packs.test.ts index 202d5d1..5246d49 100644 --- a/tests/api-resources/print-mail/snap-packs.test.ts +++ b/tests/api-resources/print-mail/snap-packs.test.ts @@ -54,6 +54,7 @@ describe('resource snapPacks', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, insideHTML: 'insideHTML', @@ -75,6 +76,7 @@ describe('resource snapPacks', () => { phoneNumber: 'phoneNumber', postalOrZip: 'postalOrZip', provinceOrState: 'provinceOrState', + secret: true, skipVerification: true, }, description: 'description', From 086b129ea75f1c609397dfb5ef57abd09a71771a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:08:37 +0000 Subject: [PATCH 6/7] feat: PE-6131 HOTFIX: Fix idempotency key header for create endpoints --- .stats.yml | 6 +- api.md | 22 +- src/resources/print-mail/campaigns.ts | 44 +- src/resources/print-mail/cheques.ts | 130 +- src/resources/print-mail/index.ts | 10 +- src/resources/print-mail/letters.ts | 240 ++-- src/resources/print-mail/postcards.ts | 244 ++-- src/resources/print-mail/print-mail.ts | 20 +- src/resources/print-mail/self-mailers.ts | 227 ++-- src/resources/print-mail/snap-packs.ts | 1046 +++-------------- .../print-mail/campaigns.test.ts | 2 +- .../api-resources/print-mail/cheques.test.ts | 3 +- .../api-resources/print-mail/letters.test.ts | 1 + .../print-mail/postcards.test.ts | 1 + .../print-mail/self-mailers.test.ts | 1 + .../print-mail/snap-packs.test.ts | 1 + 16 files changed, 700 insertions(+), 1298 deletions(-) diff --git a/.stats.yml b/.stats.yml index 86116e6..a18e8eb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 137 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-32126b0df875e75e2a33a809e6fc25ea55cedc0d2505450361e71f4f2cedecf3.yml -openapi_spec_hash: 2cc8c911382f942c07b624e45c8db026 -config_hash: 6398155ff41b9bcbc09dc6066a1bf113 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/postgrid/postgrid-a1e82d54f9c6456622ef353b6864dd9ccc47c9b5cf6b5fbe98c123f2bdf30f34.yml +openapi_spec_hash: cd682fda35a9733c4bcd508b8d8e6eab +config_hash: f16e8e5b59ab878eee700d0f2dee5645 diff --git a/api.md b/api.md index cb08c6a..117083e 100644 --- a/api.md +++ b/api.md @@ -149,11 +149,12 @@ Types: - Letter - LetterSize - PlasticCard +- LetterCreateResponse - LetterRetrieveURLResponse Methods: -- client.printMail.letters.create({ ...params }) -> Letter +- client.printMail.letters.create({ ...params }) -> LetterCreateResponse - client.printMail.letters.retrieve(id) -> Letter - client.printMail.letters.list({ ...params }) -> LettersSkipLimit - client.printMail.letters.delete(id) -> Letter @@ -166,11 +167,12 @@ Methods: Types: - Postcard +- PostcardCreateResponse - PostcardRetrieveURLResponse Methods: -- client.printMail.postcards.create({ ...params }) -> Postcard +- client.printMail.postcards.create({ ...params }) -> PostcardCreateResponse - client.printMail.postcards.retrieve(id) -> Postcard - client.printMail.postcards.list({ ...params }) -> PostcardsSkipLimit - client.printMail.postcards.delete(id) -> Postcard @@ -218,11 +220,12 @@ Methods: Types: - SelfMailer +- SelfMailerCreateResponse - SelfMailerRetrieveURLResponse Methods: -- client.printMail.selfMailers.create({ ...params }) -> SelfMailer +- client.printMail.selfMailers.create({ ...params }) -> SelfMailerCreateResponse - client.printMail.selfMailers.retrieve(id) -> SelfMailer - client.printMail.selfMailers.list({ ...params }) -> SelfMailersSkipLimit - client.printMail.selfMailers.delete(id) -> SelfMailer @@ -382,20 +385,17 @@ Methods: Types: +- SnapPack - SnapPackCreateResponse -- SnapPackRetrieveResponse -- SnapPackListResponse -- SnapPackDeleteResponse -- SnapPackProgressionsResponse - SnapPackRetrieveCapabilitiesResponse Methods: - client.printMail.snapPacks.create({ ...params }) -> SnapPackCreateResponse -- client.printMail.snapPacks.retrieve(id) -> SnapPackRetrieveResponse -- client.printMail.snapPacks.list({ ...params }) -> SnapPackListResponsesSkipLimit -- client.printMail.snapPacks.delete(id) -> SnapPackDeleteResponse -- client.printMail.snapPacks.progressions(id) -> SnapPackProgressionsResponse +- client.printMail.snapPacks.retrieve(id) -> SnapPack +- client.printMail.snapPacks.list({ ...params }) -> SnapPacksSkipLimit +- client.printMail.snapPacks.delete(id) -> SnapPack +- client.printMail.snapPacks.progressions(id) -> SnapPack - client.printMail.snapPacks.retrieveCapabilities({ ...params }) -> SnapPackRetrieveCapabilitiesResponse ## TargetedListBuilds diff --git a/src/resources/print-mail/campaigns.ts b/src/resources/print-mail/campaigns.ts index c809427..eb36b2d 100644 --- a/src/resources/print-mail/campaigns.ts +++ b/src/resources/print-mail/campaigns.ts @@ -5,8 +5,10 @@ import * as ChequesAPI from './cheques'; import * as LettersAPI from './letters'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -33,14 +35,20 @@ export class Campaigns extends APIResource { */ create(params: CampaignCreateParams, options?: RequestOptions): APIPromise { const { 'idempotency-key': idempotencyKey, ...body } = params; - return this._client.post('/print-mail/v1/campaigns', { - body, - ...options, - headers: buildHeaders([ - { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, - options?.headers, - ]), - }); + return this._client.post( + '/print-mail/v1/campaigns', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -836,7 +844,7 @@ export namespace CampaignCreateParams { /** * PDF file for an optional attached letter. Cannot be used with `letterTemplate`. */ - letterPDF?: string; + letterPDF?: string | Uploadable; /** * Settings for the attached letter (e.g., color printing). @@ -1022,7 +1030,7 @@ export namespace CampaignCreateParams { /** * A PDF file or URL for the letter content. Cannot be used with `template`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Which page number should be perforated (if any). @@ -1122,7 +1130,7 @@ export namespace CampaignCreateParams { * A 2-page PDF file for the postcard content (front and back). Cannot be used with * `frontTemplate`/`backTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported postcard sizes. @@ -1195,7 +1203,7 @@ export namespace CampaignCreateParams { * A 2-page PDF file for the self-mailer content. Cannot be used with * `insideTemplate`/`outsideTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported self-mailer sizes. @@ -1268,7 +1276,7 @@ export namespace CampaignCreateParams { * A 2-page PDF file for the snap pack content. Cannot be used with * `insideTemplate`/`outsideTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported snap pack sizes. @@ -1360,7 +1368,7 @@ export namespace CampaignUpdateParams { /** * PDF file for an optional attached letter. Cannot be used with `letterTemplate`. */ - letterPDF?: string; + letterPDF?: string | Uploadable; /** * Settings for the attached letter (e.g., color printing). @@ -1546,7 +1554,7 @@ export namespace CampaignUpdateParams { /** * A PDF file or URL for the letter content. Cannot be used with `template`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Which page number should be perforated (if any). @@ -1646,7 +1654,7 @@ export namespace CampaignUpdateParams { * A 2-page PDF file for the postcard content (front and back). Cannot be used with * `frontTemplate`/`backTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported postcard sizes. @@ -1719,7 +1727,7 @@ export namespace CampaignUpdateParams { * A 2-page PDF file for the self-mailer content. Cannot be used with * `insideTemplate`/`outsideTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported self-mailer sizes. @@ -1792,7 +1800,7 @@ export namespace CampaignUpdateParams { * A 2-page PDF file for the snap pack content. Cannot be used with * `insideTemplate`/`outsideTemplate`. */ - pdf?: string; + pdf?: string | Uploadable; /** * Enum representing the supported snap pack sizes. diff --git a/src/resources/print-mail/cheques.ts b/src/resources/print-mail/cheques.ts index 69309f9..867753d 100644 --- a/src/resources/print-mail/cheques.ts +++ b/src/resources/print-mail/cheques.ts @@ -4,7 +4,10 @@ import { APIResource } from '../../core/resource'; import * as ContactsAPI from './contacts'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -51,8 +54,22 @@ export class Cheques extends APIResource { * }); * ``` */ - create(body: ChequeCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/print-mail/v1/cheques', { body, ...options }); + create(params: ChequeCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post( + '/print-mail/v1/cheques', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -476,88 +493,89 @@ export interface ChequeRetrieveURLResponse { export interface ChequeCreateParams { /** - * The amount of the cheque in cents. + * Body param: The amount of the cheque in cents. */ amount: number; /** - * The bank account (ID) associated with the cheque. + * Body param: The bank account (ID) associated with the cheque. */ bankAccount: string; /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The currency code of the cheque. This will be set to the default currency of the - * bank account (`USD` for US bank accounts and `CAD` for Canadian bank accounts) - * if not provided. You can set this value to `USD` if you want to draw USD from a - * Canadian bank account or vice versa. + * Body param: The currency code of the cheque. This will be set to the default + * currency of the bank account (`USD` for US bank accounts and `CAD` for Canadian + * bank accounts) if not provided. You can set this value to `USD` if you want to + * draw USD from a Canadian bank account or vice versa. */ currencyCode?: 'USD' | 'CAD'; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The digitalOnly object contains data for digital-only cheques. A watermark must - * be provided. + * Body param: The digitalOnly object contains data for digital-only cheques. A + * watermark must be provided. */ digitalOnly?: DigitalOnly; /** - * The envelope of the cheque. If a custom envelope ID is not specified, defaults - * to `standard`. + * Body param: The envelope of the cheque. If a custom envelope ID is not + * specified, defaults to `standard`. */ envelope?: 'standard' | (string & {}); /** - * The raw HTML content for a letter attached to the cheque, if any. You can supply - * _either_ this, `letterTemplate`, or `letterPDF`, but not more than one. + * Body param: The raw HTML content for a letter attached to the cheque, if any. + * You can supply _either_ this, `letterTemplate`, or `letterPDF`, but not more + * than one. */ letterHTML?: string; /** - * A URL pointing to a PDF for the letter attached to the cheque, or the PDF file - * itself when uploaded via a multipart form request. You can supply _either_ this, - * `letterHTML`, or `letterTemplate`, but not more than one. + * Body param: A URL pointing to a PDF for the letter attached to the cheque, or + * the PDF file itself when uploaded via a multipart form request. You can supply + * _either_ this, `letterHTML`, or `letterTemplate`, but not more than one. */ - letterPDF?: string; + letterPDF?: string | Uploadable; /** - * Settings for a letter attached to a cheque. + * Body param: Settings for a letter attached to a cheque. */ letterSettings?: ChequeCreateParams.LetterSettings; /** - * A Template ID for the letter attached to the cheque, if any. + * Body param: A Template ID for the letter attached to the cheque, if any. */ letterTemplate?: string; /** - * An optional logo URL for the cheque. This will be placed next to the recipient - * address at the top left corner of the cheque. This needs to be a public link to - * an image file (e.g. a PNG or JPEG file). + * Body param: An optional logo URL for the cheque. This will be placed next to the + * recipient address at the top left corner of the cheque. This needs to be a + * public link to an image file (e.g. a PNG or JPEG file). */ logo?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -588,60 +606,66 @@ export interface ChequeCreateParams { | 'au_post_second_class'; /** - * The memo of the cheque. + * Body param: The memo of the cheque. */ memo?: string; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * The message of the cheque. + * Body param: The message of the cheque. */ message?: string; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * The number of the cheque. If you don't provide this, it will automatically be - * set to an incrementing number starting from 1 across your entire account, - * ensuring that every cheque has a unique number. + * Body param: The number of the cheque. If you don't provide this, it will + * automatically be set to an incrementing number starting from 1 across your + * entire account, ensuring that every cheque has a unique number. */ number?: number; /** - * Providing this inserts a blank page at the start of the cheque with the - * recipient you provide here. This leaves the cheque that follows intact, which - * means you can use this to intercept at cheque at the redirected address and then - * mail it forward to the final recipient yourself. One use case for this is - * signing cheques at your office before mailing them out yourself. + * Body param: Providing this inserts a blank page at the start of the cheque with + * the recipient you provide here. This leaves the cheque that follows intact, + * which means you can use this to intercept at cheque at the redirected address + * and then mail it forward to the final recipient yourself. One use case for this + * is signing cheques at your office before mailing them out yourself. */ redirectTo?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The return envelope (ID) sent out with the cheque, if any. Note that you must - * first order return envelopes using the Return Envelopes API. + * Body param: The return envelope (ID) sent out with the cheque, if any. Note that + * you must first order return envelopes using the Return Envelopes API. */ returnEnvelope?: string; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; /** - * Enum representing the supported cheque sizes. + * Body param: Enum representing the supported cheque sizes. */ size?: ChequeSize; + + /** + * Header param + */ + 'idempotency-key'?: string; } export namespace ChequeCreateParams { diff --git a/src/resources/print-mail/index.ts b/src/resources/print-mail/index.ts index b475437..ae24d82 100644 --- a/src/resources/print-mail/index.ts +++ b/src/resources/print-mail/index.ts @@ -60,6 +60,7 @@ export { type Letter, type LetterSize, type PlasticCard, + type LetterCreateResponse, type LetterRetrieveURLResponse, type LetterCreateParams, type LetterListParams, @@ -91,6 +92,7 @@ export { export { Postcards, type Postcard, + type PostcardCreateResponse, type PostcardRetrieveURLResponse, type PostcardCreateParams, type PostcardListParams, @@ -118,6 +120,7 @@ export { export { SelfMailers, type SelfMailer, + type SelfMailerCreateResponse, type SelfMailerRetrieveURLResponse, type SelfMailerCreateParams, type SelfMailerListParams, @@ -125,16 +128,13 @@ export { } from './self-mailers'; export { SnapPacks, + type SnapPack, type SnapPackCreateResponse, - type SnapPackRetrieveResponse, - type SnapPackListResponse, - type SnapPackDeleteResponse, - type SnapPackProgressionsResponse, type SnapPackRetrieveCapabilitiesResponse, type SnapPackCreateParams, type SnapPackListParams, type SnapPackRetrieveCapabilitiesParams, - type SnapPackListResponsesSkipLimit, + type SnapPacksSkipLimit, } from './snap-packs'; export { SubOrganizations, diff --git a/src/resources/print-mail/letters.ts b/src/resources/print-mail/letters.ts index 3df2a87..eea2965 100644 --- a/src/resources/print-mail/letters.ts +++ b/src/resources/print-mail/letters.ts @@ -4,7 +4,10 @@ import { APIResource } from '../../core/resource'; import * as ContactsAPI from './contacts'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -16,8 +19,10 @@ export class Letters extends APIResource { * * - HTML content for the letter * - A template ID for the letter - * - A URL or file for a PDF for the letter - * - Upload the aforementioned PDF file via a multipart form upload request + * - A URL for a PDF for the letter Create a letter via a multipart/form-data + * request. Accepts the same fields as the JSON create body (nested objects are + * bracket-encoded form fields, e.g. `to[firstName]`); use this content type to + * upload the PDF file directly. * * @example * ```ts @@ -29,8 +34,22 @@ export class Letters extends APIResource { * }); * ``` */ - create(body: LetterCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/print-mail/v1/letters', { body, ...options }); + create(params: LetterCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post( + '/print-mail/v1/letters', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -145,7 +164,7 @@ export interface AttachedPdf { /** * The file (multipart form upload) or URL pointing to a PDF for the attached PDF. */ - file: string; + file: string | Uploadable; /** * Enum representing the placement of the attached PDF. @@ -460,7 +479,7 @@ export namespace PlasticCard { * A URL pointing to a PDF file for the double-sided plastic card or the file * itself. */ - pdf?: string; + pdf?: string | Uploadable; } /** @@ -477,7 +496,7 @@ export namespace PlasticCard { * A URL pointing to a PDF file for the single-sided plastic card or the PDF file * itself. */ - pdf?: string; + pdf?: string | Uploadable; /** * The template ID for the single-sided plastic card. @@ -486,6 +505,8 @@ export namespace PlasticCard { } } +export type LetterCreateResponse = Letter | Letter; + export interface LetterRetrieveURLResponse { /** * A unique ID prefixed with letter\_ @@ -509,60 +530,60 @@ export type LetterCreateParams = export declare namespace LetterCreateParams { export interface LetterCreateWithHTML { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The HTML content for the letter. You can supply _either_ this or `template` but - * not both. + * Body param: The HTML content for the letter. You can supply _either_ this or + * `template` but not both. */ html: string; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * Enum representing the placement of the address on the letter. + * Body param: Enum representing the placement of the address on the letter. */ addressPlacement?: AddressPlacement; /** - * Model representing an attached PDF. + * Body param: Model representing an attached PDF. */ attachedPDF?: AttachedPdf; /** - * Indicates if the letter is in color. + * Body param: Indicates if the letter is in color. */ color?: boolean; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * Indicates if the letter is double-sided. + * Body param: Indicates if the letter is double-sided. */ doubleSided?: boolean; /** - * The envelope (ID) for the letter. You can either specify a custom envelope ID or - * use the default `standard` envelope. + * Body param: The envelope (ID) for the letter. You can either specify a custom + * envelope ID or use the default `standard` envelope. */ envelope?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -593,20 +614,20 @@ export declare namespace LetterCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this letter. + * Body param: Premium paper selection used for this letter. * * Available values include: * @@ -624,89 +645,95 @@ export declare namespace LetterCreateParams { | (string & {}); /** - * If specified, indicates which letter page is perforated. Currently, only the - * first page can be perforated. + * Body param: If specified, indicates which letter page is perforated. Currently, + * only the first page can be perforated. */ perforatedPage?: 1; /** - * Model representing a plastic card. + * Body param: Model representing a plastic card. */ plasticCard?: PlasticCard; /** - * The return envelope (ID) sent out with the letter, if any. + * Body param: The return envelope (ID) sent out with the letter, if any. */ returnEnvelope?: string; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; /** - * Enum representing the supported letter sizes. + * Body param: Enum representing the supported letter sizes. */ size?: LetterSize; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface LetterCreateWithTemplate { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The template ID for the letter. You can supply _either_ this or `html` but not - * both. + * Body param: The template ID for the letter. You can supply _either_ this or + * `html` but not both. */ template: string; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * Enum representing the placement of the address on the letter. + * Body param: Enum representing the placement of the address on the letter. */ addressPlacement?: AddressPlacement; /** - * Model representing an attached PDF. + * Body param: Model representing an attached PDF. */ attachedPDF?: AttachedPdf; /** - * Indicates if the letter is in color. + * Body param: Indicates if the letter is in color. */ color?: boolean; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * Indicates if the letter is double-sided. + * Body param: Indicates if the letter is double-sided. */ doubleSided?: boolean; /** - * The envelope (ID) for the letter. You can either specify a custom envelope ID or - * use the default `standard` envelope. + * Body param: The envelope (ID) for the letter. You can either specify a custom + * envelope ID or use the default `standard` envelope. */ envelope?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -737,20 +764,20 @@ export declare namespace LetterCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this letter. + * Body param: Premium paper selection used for this letter. * * Available values include: * @@ -768,88 +795,94 @@ export declare namespace LetterCreateParams { | (string & {}); /** - * If specified, indicates which letter page is perforated. Currently, only the - * first page can be perforated. + * Body param: If specified, indicates which letter page is perforated. Currently, + * only the first page can be perforated. */ perforatedPage?: 1; /** - * Model representing a plastic card. + * Body param: Model representing a plastic card. */ plasticCard?: PlasticCard; /** - * The return envelope (ID) sent out with the letter, if any. + * Body param: The return envelope (ID) sent out with the letter, if any. */ returnEnvelope?: string; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; /** - * Enum representing the supported letter sizes. + * Body param: Enum representing the supported letter sizes. */ size?: LetterSize; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface LetterCreateWithPdf { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * A URL pointing to a PDF file for the letter or the PDF file itself. + * Body param: A URL pointing to a PDF file for the letter or the PDF file itself. */ - pdf: string; + pdf: string | Uploadable; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * Enum representing the placement of the address on the letter. + * Body param: Enum representing the placement of the address on the letter. */ addressPlacement?: AddressPlacement; /** - * Model representing an attached PDF. + * Body param: Model representing an attached PDF. */ attachedPDF?: AttachedPdf; /** - * Indicates if the letter is in color. + * Body param: Indicates if the letter is in color. */ color?: boolean; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * Indicates if the letter is double-sided. + * Body param: Indicates if the letter is double-sided. */ doubleSided?: boolean; /** - * The envelope (ID) for the letter. You can either specify a custom envelope ID or - * use the default `standard` envelope. + * Body param: The envelope (ID) for the letter. You can either specify a custom + * envelope ID or use the default `standard` envelope. */ envelope?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -880,20 +913,20 @@ export declare namespace LetterCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this letter. + * Body param: Premium paper selection used for this letter. * * Available values include: * @@ -911,31 +944,37 @@ export declare namespace LetterCreateParams { | (string & {}); /** - * If specified, indicates which letter page is perforated. Currently, only the - * first page can be perforated. + * Body param: If specified, indicates which letter page is perforated. Currently, + * only the first page can be perforated. */ perforatedPage?: 1; /** - * Model representing a plastic card. + * Body param: Model representing a plastic card. */ plasticCard?: PlasticCard; /** - * The return envelope (ID) sent out with the letter, if any. + * Body param: The return envelope (ID) sent out with the letter, if any. */ returnEnvelope?: string; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; /** - * Enum representing the supported letter sizes. + * Body param: Enum representing the supported letter sizes. */ size?: LetterSize; + + /** + * Header param + */ + 'idempotency-key'?: string; } } @@ -961,6 +1000,7 @@ export declare namespace Letters { type Letter as Letter, type LetterSize as LetterSize, type PlasticCard as PlasticCard, + type LetterCreateResponse as LetterCreateResponse, type LetterRetrieveURLResponse as LetterRetrieveURLResponse, type LettersSkipLimit as LettersSkipLimit, type LetterCreateParams as LetterCreateParams, diff --git a/src/resources/print-mail/postcards.ts b/src/resources/print-mail/postcards.ts index e0d83e6..bb94896 100644 --- a/src/resources/print-mail/postcards.ts +++ b/src/resources/print-mail/postcards.ts @@ -4,7 +4,10 @@ import { APIResource } from '../../core/resource'; import * as ContactsAPI from './contacts'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -16,9 +19,11 @@ export class Postcards extends APIResource { * * - HTML content for the front and back of the postcard * - A template ID for the front and back of the postcard - * - A URL or file for a 2 page PDF where the first page is the front of the - * postcard and the second page is the back - * - Upload the aforementioned PDF file via a multipart form upload request + * - A URL for a 2 page PDF where the first page is the front of the postcard and + * the second page is the back Create a postcard via a multipart/form-data + * request. Accepts the same fields as the JSON create body (nested objects are + * bracket-encoded form fields, e.g. `to[firstName]`); use this content type to + * upload the PDF file directly. * * @example * ```ts @@ -32,8 +37,22 @@ export class Postcards extends APIResource { * }); * ``` */ - create(body: PostcardCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/print-mail/v1/postcards', { body, ...options }); + create(params: PostcardCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post( + '/print-mail/v1/postcards', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -332,6 +351,8 @@ export namespace Postcard { } } +export type PostcardCreateResponse = Postcard | Postcard; + export interface PostcardRetrieveURLResponse { /** * A unique ID prefixed with postcard\_ @@ -356,46 +377,46 @@ export type PostcardCreateParams = export declare namespace PostcardCreateParams { export interface PostcardCreateWithHTML { /** - * The HTML content for the back of the postcard. You can supply _either_ this or - * `backTemplate` but not both. + * Body param: The HTML content for the back of the postcard. You can supply + * _either_ this or `backTemplate` but not both. */ backHTML: string; /** - * The HTML content for the front of the postcard. You can supply _either_ this or - * `frontTemplate` but not both. + * Body param: The HTML content for the front of the postcard. You can supply + * _either_ this or `frontTemplate` but not both. */ frontHTML: string; /** - * Enum representing the supported postcard sizes. + * Body param: Enum representing the supported postcard sizes. */ size: '6x4' | '9x6' | '11x6'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. Unlike other order types, the sender - * address is optional for postcards. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. Unlike other order + * types, the sender address is optional for postcards. */ from?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -426,20 +447,20 @@ export declare namespace PostcardCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this postcard. + * Body param: Premium paper selection used for this postcard. * * Available values include: * @@ -462,54 +483,60 @@ export declare namespace PostcardCreateParams { | (string & {}); /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface PostcardCreateWithTemplate { /** - * The template ID for the back of the postcard. You can supply _either_ this or - * `backHTML` but not both. + * Body param: The template ID for the back of the postcard. You can supply + * _either_ this or `backHTML` but not both. */ backTemplate: string; /** - * The template ID for the front of the postcard. You can supply _either_ this or - * `frontHTML` but not both. + * Body param: The template ID for the front of the postcard. You can supply + * _either_ this or `frontHTML` but not both. */ frontTemplate: string; /** - * Enum representing the supported postcard sizes. + * Body param: Enum representing the supported postcard sizes. */ size: '6x4' | '9x6' | '11x6'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. Unlike other order types, the sender - * address is optional for postcards. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. Unlike other order + * types, the sender address is optional for postcards. */ from?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -540,20 +567,20 @@ export declare namespace PostcardCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this postcard. + * Body param: Premium paper selection used for this postcard. * * Available values include: * @@ -576,48 +603,55 @@ export declare namespace PostcardCreateParams { | (string & {}); /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface PostcardCreateWithPdfurl { /** - * A URL pointing to a 2 page PDF file. The first page is the front of the postcard - * and the second page is the back (where the address will be stamped on). + * Body param: A URL pointing to a 2 page PDF file. The first page is the front of + * the postcard and the second page is the back (where the address will be stamped + * on). */ pdf: string; /** - * Enum representing the supported postcard sizes. + * Body param: Enum representing the supported postcard sizes. */ size: '6x4' | '9x6' | '11x6'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. Unlike other order types, the sender - * address is optional for postcards. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. Unlike other order + * types, the sender address is optional for postcards. */ from?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -648,20 +682,20 @@ export declare namespace PostcardCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this postcard. + * Body param: Premium paper selection used for this postcard. * * Available values include: * @@ -684,48 +718,55 @@ export declare namespace PostcardCreateParams { | (string & {}); /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface PostcardCreateWithPdfFile { /** - * A 2 page PDF file. The first page is the front of the postcard and the second - * page is the back (where the address will be stamped on). + * Body param: Represents a raw file upload. Sending the actual file requires a + * `multipart/form-data` request; in `application/json` request bodies, supply a + * URL instead. */ - pdf: string; + pdf: Uploadable; /** - * Enum representing the supported postcard sizes. + * Body param: Enum representing the supported postcard sizes. */ size: '6x4' | '9x6' | '11x6'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. Unlike other order types, the sender - * address is optional for postcards. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. Unlike other order + * types, the sender address is optional for postcards. */ from?: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -756,20 +797,20 @@ export declare namespace PostcardCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * Premium paper selection used for this postcard. + * Body param: Premium paper selection used for this postcard. * * Available values include: * @@ -792,10 +833,16 @@ export declare namespace PostcardCreateParams { | (string & {}); /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } } @@ -817,6 +864,7 @@ export interface PostcardCancelParams { export declare namespace Postcards { export { type Postcard as Postcard, + type PostcardCreateResponse as PostcardCreateResponse, type PostcardRetrieveURLResponse as PostcardRetrieveURLResponse, type PostcardsSkipLimit as PostcardsSkipLimit, type PostcardCreateParams as PostcardCreateParams, diff --git a/src/resources/print-mail/print-mail.ts b/src/resources/print-mail/print-mail.ts index 6d1a2ba..b453f82 100644 --- a/src/resources/print-mail/print-mail.ts +++ b/src/resources/print-mail/print-mail.ts @@ -67,6 +67,7 @@ import { Letter, LetterCancelParams, LetterCreateParams, + LetterCreateResponse, LetterListParams, LetterRetrieveURLResponse, LetterSize, @@ -103,6 +104,7 @@ import { Postcard, PostcardCancelParams, PostcardCreateParams, + PostcardCreateResponse, PostcardListParams, PostcardRetrieveURLResponse, Postcards, @@ -112,6 +114,7 @@ import * as SelfMailersAPI from './self-mailers'; import { SelfMailer, SelfMailerCreateParams, + SelfMailerCreateResponse, SelfMailerListParams, SelfMailerRetrieveURLResponse, SelfMailers, @@ -119,17 +122,14 @@ import { } from './self-mailers'; import * as SnapPacksAPI from './snap-packs'; import { + SnapPack, SnapPackCreateParams, SnapPackCreateResponse, - SnapPackDeleteResponse, SnapPackListParams, - SnapPackListResponse, - SnapPackListResponsesSkipLimit, - SnapPackProgressionsResponse, SnapPackRetrieveCapabilitiesParams, SnapPackRetrieveCapabilitiesResponse, - SnapPackRetrieveResponse, SnapPacks, + SnapPacksSkipLimit, } from './snap-packs'; import * as SubOrganizationsAPI from './sub-organizations'; import { @@ -358,6 +358,7 @@ export declare namespace PrintMail { type Letter as Letter, type LetterSize as LetterSize, type PlasticCard as PlasticCard, + type LetterCreateResponse as LetterCreateResponse, type LetterRetrieveURLResponse as LetterRetrieveURLResponse, type LettersSkipLimit as LettersSkipLimit, type LetterCreateParams as LetterCreateParams, @@ -368,6 +369,7 @@ export declare namespace PrintMail { export { Postcards as Postcards, type Postcard as Postcard, + type PostcardCreateResponse as PostcardCreateResponse, type PostcardRetrieveURLResponse as PostcardRetrieveURLResponse, type PostcardsSkipLimit as PostcardsSkipLimit, type PostcardCreateParams as PostcardCreateParams, @@ -400,6 +402,7 @@ export declare namespace PrintMail { export { SelfMailers as SelfMailers, type SelfMailer as SelfMailer, + type SelfMailerCreateResponse as SelfMailerCreateResponse, type SelfMailerRetrieveURLResponse as SelfMailerRetrieveURLResponse, type SelfMailersSkipLimit as SelfMailersSkipLimit, type SelfMailerCreateParams as SelfMailerCreateParams, @@ -486,13 +489,10 @@ export declare namespace PrintMail { export { SnapPacks as SnapPacks, + type SnapPack as SnapPack, type SnapPackCreateResponse as SnapPackCreateResponse, - type SnapPackRetrieveResponse as SnapPackRetrieveResponse, - type SnapPackListResponse as SnapPackListResponse, - type SnapPackDeleteResponse as SnapPackDeleteResponse, - type SnapPackProgressionsResponse as SnapPackProgressionsResponse, type SnapPackRetrieveCapabilitiesResponse as SnapPackRetrieveCapabilitiesResponse, - type SnapPackListResponsesSkipLimit as SnapPackListResponsesSkipLimit, + type SnapPacksSkipLimit as SnapPacksSkipLimit, type SnapPackCreateParams as SnapPackCreateParams, type SnapPackListParams as SnapPackListParams, type SnapPackRetrieveCapabilitiesParams as SnapPackRetrieveCapabilitiesParams, diff --git a/src/resources/print-mail/self-mailers.ts b/src/resources/print-mail/self-mailers.ts index 6e62d14..af0da90 100644 --- a/src/resources/print-mail/self-mailers.ts +++ b/src/resources/print-mail/self-mailers.ts @@ -4,7 +4,10 @@ import { APIResource } from '../../core/resource'; import * as ContactsAPI from './contacts'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -16,9 +19,11 @@ export class SelfMailers extends APIResource { * * - HTML content for the inside and outside of the self-mailer * - A template ID for the inside and outside of the self-mailer - * - A URL or file for a 2 page PDF where the first page is the outside of the - * self-mailer and the second page is the inside - * - Upload the aforementioned PDF file via a multipart form upload request + * - A URL for a 2 page PDF where the first page is the outside of the self-mailer + * and the second page is the inside Create a self-mailer via a + * multipart/form-data request. Accepts the same fields as the JSON create body + * (nested objects are bracket-encoded form fields, e.g. `to[firstName]`); use + * this content type to upload the PDF file directly. * * @example * ```ts @@ -32,8 +37,22 @@ export class SelfMailers extends APIResource { * }); * ``` */ - create(body: SelfMailerCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/print-mail/v1/self_mailers', { body, ...options }); + create(params: SelfMailerCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post( + '/print-mail/v1/self_mailers', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -293,6 +312,8 @@ export namespace SelfMailer { } } +export type SelfMailerCreateResponse = SelfMailer | SelfMailer; + export interface SelfMailerRetrieveURLResponse { /** * A unique ID prefixed with self*mailer* @@ -317,45 +338,45 @@ export type SelfMailerCreateParams = export declare namespace SelfMailerCreateParams { export interface SelfMailerCreateWithHTML { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The HTML content for the inside of the self-mailer. You can supply _either_ this - * or `insideTemplate` but not both. + * Body param: The HTML content for the inside of the self-mailer. You can supply + * _either_ this or `insideTemplate` but not both. */ insideHTML: string; /** - * The HTML content for the outside of the self-mailer. You can supply _either_ - * this or `outsideTemplate` but not both. + * Body param: The HTML content for the outside of the self-mailer. You can supply + * _either_ this or `outsideTemplate` but not both. */ outsideHTML: string; /** - * Enum representing the supported self-mailer sizes. + * Body param: Enum representing the supported self-mailer sizes. */ size: '8.5x11_bifold' | '8.5x11_trifold' | '9.5x16_trifold'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -386,66 +407,72 @@ export declare namespace SelfMailerCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface SelfMailerCreateWithTemplate { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The template ID for the inside of the self-mailer. You can supply _either_ this - * or `insideHTML` but not both. + * Body param: The template ID for the inside of the self-mailer. You can supply + * _either_ this or `insideHTML` but not both. */ insideTemplate: string; /** - * The template ID for the outside of the self-mailer. You can supply _either_ this - * or `outsideHTML` but not both. + * Body param: The template ID for the outside of the self-mailer. You can supply + * _either_ this or `outsideHTML` but not both. */ outsideTemplate: string; /** - * Enum representing the supported self-mailer sizes. + * Body param: Enum representing the supported self-mailer sizes. */ size: '8.5x11_bifold' | '8.5x11_trifold' | '9.5x16_trifold'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -476,61 +503,67 @@ export declare namespace SelfMailerCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface SelfMailerCreateWithPdfurl { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * A URL pointing to a 2 page PDF file. The first page is the inside of the - * self-mailer and the second page is the outside (where the address will be + * Body param: A URL pointing to a 2 page PDF file. The first page is the inside of + * the self-mailer and the second page is the outside (where the address will be * stamped on). */ pdf: string; /** - * Enum representing the supported self-mailer sizes. + * Body param: Enum representing the supported self-mailer sizes. */ size: '8.5x11_bifold' | '8.5x11_trifold' | '9.5x16_trifold'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -561,60 +594,67 @@ export declare namespace SelfMailerCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface SelfMailerCreateWithPdfFile { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to`. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to`. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * A 2 page PDF file. The first page is the inside of the self-mailer and the - * second page is the outside (where the address will be stamped on). + * Body param: Represents a raw file upload. Sending the actual file requires a + * `multipart/form-data` request; in `application/json` request bodies, supply a + * URL instead. */ - pdf: string; + pdf: Uploadable; /** - * Enum representing the supported self-mailer sizes. + * Body param: Enum representing the supported self-mailer sizes. */ size: '8.5x11_bifold' | '8.5x11_trifold' | '9.5x16_trifold'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -645,23 +685,29 @@ export declare namespace SelfMailerCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } } @@ -679,6 +725,7 @@ export interface SelfMailerListParams extends SkipLimitParams { export declare namespace SelfMailers { export { type SelfMailer as SelfMailer, + type SelfMailerCreateResponse as SelfMailerCreateResponse, type SelfMailerRetrieveURLResponse as SelfMailerRetrieveURLResponse, type SelfMailersSkipLimit as SelfMailersSkipLimit, type SelfMailerCreateParams as SelfMailerCreateParams, diff --git a/src/resources/print-mail/snap-packs.ts b/src/resources/print-mail/snap-packs.ts index 7ab91d5..d0f9785 100644 --- a/src/resources/print-mail/snap-packs.ts +++ b/src/resources/print-mail/snap-packs.ts @@ -4,7 +4,10 @@ import { APIResource } from '../../core/resource'; import * as ContactsAPI from './contacts'; import { APIPromise } from '../../core/api-promise'; import { PagePromise, SkipLimit, type SkipLimitParams } from '../../core/pagination'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; +import { maybeMultipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; /** @@ -23,7 +26,10 @@ export class SnapPacks extends APIResource { * * - HTML content for the inside and outside of the snap pack * - Template IDs for the inside and outside of the snap pack - * - A URL or file upload for a two-page PDF that matches the snap pack layout + * - A URL for a two-page PDF that matches the snap pack layout Create a snap pack + * via a multipart/form-data request. Accepts the same fields as the JSON create + * body (nested objects are bracket-encoded form fields, e.g. `to[firstName]`); + * use this content type to upload the PDF file directly. * * @example * ```ts @@ -36,8 +42,22 @@ export class SnapPacks extends APIResource { * }); * ``` */ - create(body: SnapPackCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/print-mail/v1/snap_packs', { body, ...options }); + create(params: SnapPackCreateParams, options?: RequestOptions): APIPromise { + const { 'idempotency-key': idempotencyKey, ...body } = params; + return this._client.post( + '/print-mail/v1/snap_packs', + maybeMultipartFormRequestOptions( + { + body, + ...options, + headers: buildHeaders([ + { ...(idempotencyKey != null ? { 'idempotency-key': idempotencyKey } : undefined) }, + options?.headers, + ]), + }, + this._client, + ), + ); } /** @@ -50,7 +70,7 @@ export class SnapPacks extends APIResource { * ); * ``` */ - retrieve(id: string, options?: RequestOptions): APIPromise { + retrieve(id: string, options?: RequestOptions): APIPromise { return this._client.get(path`/print-mail/v1/snap_packs/${id}`, options); } @@ -60,7 +80,7 @@ export class SnapPacks extends APIResource { * @example * ```ts * // Automatically fetches more pages as needed. - * for await (const snapPackListResponse of client.printMail.snapPacks.list()) { + * for await (const snapPack of client.printMail.snapPacks.list()) { * // ... * } * ``` @@ -68,11 +88,8 @@ export class SnapPacks extends APIResource { list( query: SnapPackListParams | null | undefined = {}, options?: RequestOptions, - ): PagePromise { - return this._client.getAPIList('/print-mail/v1/snap_packs', SkipLimit, { - query, - ...options, - }); + ): PagePromise { + return this._client.getAPIList('/print-mail/v1/snap_packs', SkipLimit, { query, ...options }); } /** @@ -86,7 +103,7 @@ export class SnapPacks extends APIResource { * ); * ``` */ - delete(id: string, options?: RequestOptions): APIPromise { + delete(id: string, options?: RequestOptions): APIPromise { return this._client.delete(path`/print-mail/v1/snap_packs/${id}`, options); } @@ -100,840 +117,36 @@ export class SnapPacks extends APIResource { * * @example * ```ts - * const response = - * await client.printMail.snapPacks.progressions('id'); - * ``` - */ - progressions(id: string, options?: RequestOptions): APIPromise { - return this._client.post(path`/print-mail/v1/snap_packs/${id}/progressions`, options); - } - - /** - * Provides sizes and mailing classes available for the destination. - * - * @example - * ```ts - * const response = - * await client.printMail.snapPacks.retrieveCapabilities({ - * returnCountryCode: 'returnCountryCode', - * }); - * ``` - */ - retrieveCapabilities( - query: SnapPackRetrieveCapabilitiesParams, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/print-mail/v1/snap_packs/capabilities', { query, ...options }); - } -} - -export type SnapPackListResponsesSkipLimit = SkipLimit; - -export interface SnapPackCreateResponse { - /** - * A unique ID prefixed with snap*pack* - */ - id: string; - - /** - * The UTC time at which this resource was created. - */ - createdAt: string; - - /** - * The contact information of the sender. - */ - from: ContactsAPI.Contact; - - /** - * `true` if this is a live mode resource else `false`. - */ - live: boolean; - - /** - * The mailing class of this order. This determines the speed and cost of delivery. - * See `OrderMailingClass` for more details. - */ - mailingClass: - | 'first_class' - | 'standard_class' - | 'express' - | 'certified' - | 'certified_return_receipt' - | 'registered' - | 'usps_first_class' - | 'usps_standard_class' - | 'usps_eddm' - | 'usps_express_2_day' - | 'usps_express_3_day' - | 'usps_first_class_certified' - | 'usps_first_class_certified_return_receipt' - | 'usps_first_class_registered' - | 'usps_express_3_day_signature_confirmation' - | 'usps_express_3_day_certified' - | 'usps_express_3_day_certified_return_receipt' - | 'ca_post_lettermail' - | 'ca_post_personalized' - | 'ca_post_neighbourhood_mail' - | 'ups_express_overnight' - | 'ups_express_2_day' - | 'ups_express_3_day' - | 'royal_mail_first_class' - | 'royal_mail_second_class' - | 'au_post_second_class'; - - /** - * Always `snap_pack`. - */ - object: 'snap_pack'; - - /** - * This order will transition from `ready` to `printing` on the day after this - * date. For example, if this is a date on Tuesday, the order will transition to - * `printing` on Wednesday at midnight eastern time. - */ - sendDate: string; - - /** - * Enum representing the supported snap pack sizes. - */ - size: '8.5x11_bifold_v'; - - /** - * See `OrderStatus` for more details on the status of this order. - */ - status: 'ready' | 'printing' | 'processed_for_delivery' | 'completed' | 'cancelled'; - - /** - * The recipient of this order. This will be provided even if you delete the - * underlying contact. - */ - to: ContactsAPI.Contact; - - /** - * The UTC time at which this resource was last updated. - */ - updatedAt: string; - - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - cancellation?: SnapPackCreateResponse.Cancellation; - - /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. - */ - description?: string; - - /** - * The last date that the IMB status was updated. See `imbStatus` for more details. - */ - imbDate?: string; - - /** - * The Intelligent Mail Barcode (IMB) status of this order. Only populated for - * US-printed and US-destined orders. This is the most detailed way to track - * non-express/certified orders. - */ - imbStatus?: 'entered_mail_stream' | 'out_for_delivery' | 'returned_to_sender'; - - /** - * The most recent ZIP code of the USPS facility that the order has been processed - * through. Only populated when an `imbStatus` is present. - */ - imbZIPCode?: string; - - /** - * The HTML content for the inside of the snap pack, when provided instead of a - * template or PDF. - */ - insideHTML?: string; - - /** - * The template ID for the inside of the snap pack, when provided instead of HTML - * or PDF. - */ - insideTemplate?: string; - - /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. - */ - mergeVariables?: { [key: string]: unknown }; - - /** - * See the section on Metadata. - */ - metadata?: { [key: string]: unknown }; - - /** - * The HTML content for the outside of the snap pack, when provided instead of a - * template or PDF. - */ - outsideHTML?: string; - - /** - * The template ID for the outside of the snap pack, when provided instead of HTML - * or PDF. - */ - outsideTemplate?: string; - - /** - * The tracking number of this order. Populated after an express/certified order - * has been processed for delivery. - */ - trackingNumber?: string; - - /** - * A signed URL to the uploaded PDF provided at creation time, if a PDF was - * supplied. - */ - uploadedPDF?: string; - - /** - * PostGrid renders a PDF preview for all orders. This should be inspected to - * ensure that the order is correct before it is sent out because it shows what - * will be printed and mailed to the recipient. Once the PDF preview is generated, - * this field will be returned by all `GET` endpoints which produce this order. - * - * This URL is a signed link to the PDF preview. It will expire after a short - * period of time. If you need to access this URL after it has expired, you can - * regenerate it by calling the `GET` endpoint again. - */ - url?: string; -} - -export namespace SnapPackCreateResponse { - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - export interface Cancellation { - /** - * The reason for the cancellation. - */ - reason: 'user_initiated' | 'invalid_content' | 'invalid_order_mailing_class'; - - /** - * The user ID who cancelled the order. - */ - cancelledByUser?: string; - - /** - * An optional note provided by the user who cancelled the order. - */ - note?: string; - } -} - -export interface SnapPackRetrieveResponse { - /** - * A unique ID prefixed with snap*pack* - */ - id: string; - - /** - * The UTC time at which this resource was created. - */ - createdAt: string; - - /** - * The contact information of the sender. - */ - from: ContactsAPI.Contact; - - /** - * `true` if this is a live mode resource else `false`. - */ - live: boolean; - - /** - * The mailing class of this order. This determines the speed and cost of delivery. - * See `OrderMailingClass` for more details. - */ - mailingClass: - | 'first_class' - | 'standard_class' - | 'express' - | 'certified' - | 'certified_return_receipt' - | 'registered' - | 'usps_first_class' - | 'usps_standard_class' - | 'usps_eddm' - | 'usps_express_2_day' - | 'usps_express_3_day' - | 'usps_first_class_certified' - | 'usps_first_class_certified_return_receipt' - | 'usps_first_class_registered' - | 'usps_express_3_day_signature_confirmation' - | 'usps_express_3_day_certified' - | 'usps_express_3_day_certified_return_receipt' - | 'ca_post_lettermail' - | 'ca_post_personalized' - | 'ca_post_neighbourhood_mail' - | 'ups_express_overnight' - | 'ups_express_2_day' - | 'ups_express_3_day' - | 'royal_mail_first_class' - | 'royal_mail_second_class' - | 'au_post_second_class'; - - /** - * Always `snap_pack`. - */ - object: 'snap_pack'; - - /** - * This order will transition from `ready` to `printing` on the day after this - * date. For example, if this is a date on Tuesday, the order will transition to - * `printing` on Wednesday at midnight eastern time. - */ - sendDate: string; - - /** - * Enum representing the supported snap pack sizes. - */ - size: '8.5x11_bifold_v'; - - /** - * See `OrderStatus` for more details on the status of this order. - */ - status: 'ready' | 'printing' | 'processed_for_delivery' | 'completed' | 'cancelled'; - - /** - * The recipient of this order. This will be provided even if you delete the - * underlying contact. - */ - to: ContactsAPI.Contact; - - /** - * The UTC time at which this resource was last updated. - */ - updatedAt: string; - - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - cancellation?: SnapPackRetrieveResponse.Cancellation; - - /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. - */ - description?: string; - - /** - * The last date that the IMB status was updated. See `imbStatus` for more details. - */ - imbDate?: string; - - /** - * The Intelligent Mail Barcode (IMB) status of this order. Only populated for - * US-printed and US-destined orders. This is the most detailed way to track - * non-express/certified orders. - */ - imbStatus?: 'entered_mail_stream' | 'out_for_delivery' | 'returned_to_sender'; - - /** - * The most recent ZIP code of the USPS facility that the order has been processed - * through. Only populated when an `imbStatus` is present. - */ - imbZIPCode?: string; - - /** - * The HTML content for the inside of the snap pack, when provided instead of a - * template or PDF. - */ - insideHTML?: string; - - /** - * The template ID for the inside of the snap pack, when provided instead of HTML - * or PDF. - */ - insideTemplate?: string; - - /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. - */ - mergeVariables?: { [key: string]: unknown }; - - /** - * See the section on Metadata. - */ - metadata?: { [key: string]: unknown }; - - /** - * The HTML content for the outside of the snap pack, when provided instead of a - * template or PDF. - */ - outsideHTML?: string; - - /** - * The template ID for the outside of the snap pack, when provided instead of HTML - * or PDF. - */ - outsideTemplate?: string; - - /** - * The tracking number of this order. Populated after an express/certified order - * has been processed for delivery. - */ - trackingNumber?: string; - - /** - * A signed URL to the uploaded PDF provided at creation time, if a PDF was - * supplied. - */ - uploadedPDF?: string; - - /** - * PostGrid renders a PDF preview for all orders. This should be inspected to - * ensure that the order is correct before it is sent out because it shows what - * will be printed and mailed to the recipient. Once the PDF preview is generated, - * this field will be returned by all `GET` endpoints which produce this order. - * - * This URL is a signed link to the PDF preview. It will expire after a short - * period of time. If you need to access this URL after it has expired, you can - * regenerate it by calling the `GET` endpoint again. - */ - url?: string; -} - -export namespace SnapPackRetrieveResponse { - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - export interface Cancellation { - /** - * The reason for the cancellation. - */ - reason: 'user_initiated' | 'invalid_content' | 'invalid_order_mailing_class'; - - /** - * The user ID who cancelled the order. - */ - cancelledByUser?: string; - - /** - * An optional note provided by the user who cancelled the order. - */ - note?: string; - } -} - -export interface SnapPackListResponse { - /** - * A unique ID prefixed with snap*pack* - */ - id: string; - - /** - * The UTC time at which this resource was created. - */ - createdAt: string; - - /** - * The contact information of the sender. - */ - from: ContactsAPI.Contact; - - /** - * `true` if this is a live mode resource else `false`. - */ - live: boolean; - - /** - * The mailing class of this order. This determines the speed and cost of delivery. - * See `OrderMailingClass` for more details. - */ - mailingClass: - | 'first_class' - | 'standard_class' - | 'express' - | 'certified' - | 'certified_return_receipt' - | 'registered' - | 'usps_first_class' - | 'usps_standard_class' - | 'usps_eddm' - | 'usps_express_2_day' - | 'usps_express_3_day' - | 'usps_first_class_certified' - | 'usps_first_class_certified_return_receipt' - | 'usps_first_class_registered' - | 'usps_express_3_day_signature_confirmation' - | 'usps_express_3_day_certified' - | 'usps_express_3_day_certified_return_receipt' - | 'ca_post_lettermail' - | 'ca_post_personalized' - | 'ca_post_neighbourhood_mail' - | 'ups_express_overnight' - | 'ups_express_2_day' - | 'ups_express_3_day' - | 'royal_mail_first_class' - | 'royal_mail_second_class' - | 'au_post_second_class'; - - /** - * Always `snap_pack`. - */ - object: 'snap_pack'; - - /** - * This order will transition from `ready` to `printing` on the day after this - * date. For example, if this is a date on Tuesday, the order will transition to - * `printing` on Wednesday at midnight eastern time. - */ - sendDate: string; - - /** - * Enum representing the supported snap pack sizes. - */ - size: '8.5x11_bifold_v'; - - /** - * See `OrderStatus` for more details on the status of this order. - */ - status: 'ready' | 'printing' | 'processed_for_delivery' | 'completed' | 'cancelled'; - - /** - * The recipient of this order. This will be provided even if you delete the - * underlying contact. - */ - to: ContactsAPI.Contact; - - /** - * The UTC time at which this resource was last updated. - */ - updatedAt: string; - - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - cancellation?: SnapPackListResponse.Cancellation; - - /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. - */ - description?: string; - - /** - * The last date that the IMB status was updated. See `imbStatus` for more details. - */ - imbDate?: string; - - /** - * The Intelligent Mail Barcode (IMB) status of this order. Only populated for - * US-printed and US-destined orders. This is the most detailed way to track - * non-express/certified orders. - */ - imbStatus?: 'entered_mail_stream' | 'out_for_delivery' | 'returned_to_sender'; - - /** - * The most recent ZIP code of the USPS facility that the order has been processed - * through. Only populated when an `imbStatus` is present. - */ - imbZIPCode?: string; - - /** - * The HTML content for the inside of the snap pack, when provided instead of a - * template or PDF. - */ - insideHTML?: string; - - /** - * The template ID for the inside of the snap pack, when provided instead of HTML - * or PDF. - */ - insideTemplate?: string; - - /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. - */ - mergeVariables?: { [key: string]: unknown }; - - /** - * See the section on Metadata. - */ - metadata?: { [key: string]: unknown }; - - /** - * The HTML content for the outside of the snap pack, when provided instead of a - * template or PDF. - */ - outsideHTML?: string; - - /** - * The template ID for the outside of the snap pack, when provided instead of HTML - * or PDF. - */ - outsideTemplate?: string; - - /** - * The tracking number of this order. Populated after an express/certified order - * has been processed for delivery. - */ - trackingNumber?: string; - - /** - * A signed URL to the uploaded PDF provided at creation time, if a PDF was - * supplied. - */ - uploadedPDF?: string; - - /** - * PostGrid renders a PDF preview for all orders. This should be inspected to - * ensure that the order is correct before it is sent out because it shows what - * will be printed and mailed to the recipient. Once the PDF preview is generated, - * this field will be returned by all `GET` endpoints which produce this order. - * - * This URL is a signed link to the PDF preview. It will expire after a short - * period of time. If you need to access this URL after it has expired, you can - * regenerate it by calling the `GET` endpoint again. - */ - url?: string; -} - -export namespace SnapPackListResponse { - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. + * const snapPack = + * await client.printMail.snapPacks.progressions('id'); + * ``` */ - export interface Cancellation { - /** - * The reason for the cancellation. - */ - reason: 'user_initiated' | 'invalid_content' | 'invalid_order_mailing_class'; - - /** - * The user ID who cancelled the order. - */ - cancelledByUser?: string; - - /** - * An optional note provided by the user who cancelled the order. - */ - note?: string; + progressions(id: string, options?: RequestOptions): APIPromise { + return this._client.post(path`/print-mail/v1/snap_packs/${id}/progressions`, options); } -} - -export interface SnapPackDeleteResponse { - /** - * A unique ID prefixed with snap*pack* - */ - id: string; - - /** - * The UTC time at which this resource was created. - */ - createdAt: string; - - /** - * The contact information of the sender. - */ - from: ContactsAPI.Contact; - - /** - * `true` if this is a live mode resource else `false`. - */ - live: boolean; - - /** - * The mailing class of this order. This determines the speed and cost of delivery. - * See `OrderMailingClass` for more details. - */ - mailingClass: - | 'first_class' - | 'standard_class' - | 'express' - | 'certified' - | 'certified_return_receipt' - | 'registered' - | 'usps_first_class' - | 'usps_standard_class' - | 'usps_eddm' - | 'usps_express_2_day' - | 'usps_express_3_day' - | 'usps_first_class_certified' - | 'usps_first_class_certified_return_receipt' - | 'usps_first_class_registered' - | 'usps_express_3_day_signature_confirmation' - | 'usps_express_3_day_certified' - | 'usps_express_3_day_certified_return_receipt' - | 'ca_post_lettermail' - | 'ca_post_personalized' - | 'ca_post_neighbourhood_mail' - | 'ups_express_overnight' - | 'ups_express_2_day' - | 'ups_express_3_day' - | 'royal_mail_first_class' - | 'royal_mail_second_class' - | 'au_post_second_class'; - - /** - * Always `snap_pack`. - */ - object: 'snap_pack'; - - /** - * This order will transition from `ready` to `printing` on the day after this - * date. For example, if this is a date on Tuesday, the order will transition to - * `printing` on Wednesday at midnight eastern time. - */ - sendDate: string; - - /** - * Enum representing the supported snap pack sizes. - */ - size: '8.5x11_bifold_v'; - - /** - * See `OrderStatus` for more details on the status of this order. - */ - status: 'ready' | 'printing' | 'processed_for_delivery' | 'completed' | 'cancelled'; - - /** - * The recipient of this order. This will be provided even if you delete the - * underlying contact. - */ - to: ContactsAPI.Contact; - - /** - * The UTC time at which this resource was last updated. - */ - updatedAt: string; - - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. - */ - cancellation?: SnapPackDeleteResponse.Cancellation; - - /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. - */ - description?: string; - - /** - * The last date that the IMB status was updated. See `imbStatus` for more details. - */ - imbDate?: string; - - /** - * The Intelligent Mail Barcode (IMB) status of this order. Only populated for - * US-printed and US-destined orders. This is the most detailed way to track - * non-express/certified orders. - */ - imbStatus?: 'entered_mail_stream' | 'out_for_delivery' | 'returned_to_sender'; - - /** - * The most recent ZIP code of the USPS facility that the order has been processed - * through. Only populated when an `imbStatus` is present. - */ - imbZIPCode?: string; - - /** - * The HTML content for the inside of the snap pack, when provided instead of a - * template or PDF. - */ - insideHTML?: string; - - /** - * The template ID for the inside of the snap pack, when provided instead of HTML - * or PDF. - */ - insideTemplate?: string; - - /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. - */ - mergeVariables?: { [key: string]: unknown }; - - /** - * See the section on Metadata. - */ - metadata?: { [key: string]: unknown }; - - /** - * The HTML content for the outside of the snap pack, when provided instead of a - * template or PDF. - */ - outsideHTML?: string; - - /** - * The template ID for the outside of the snap pack, when provided instead of HTML - * or PDF. - */ - outsideTemplate?: string; - - /** - * The tracking number of this order. Populated after an express/certified order - * has been processed for delivery. - */ - trackingNumber?: string; - - /** - * A signed URL to the uploaded PDF provided at creation time, if a PDF was - * supplied. - */ - uploadedPDF?: string; /** - * PostGrid renders a PDF preview for all orders. This should be inspected to - * ensure that the order is correct before it is sent out because it shows what - * will be printed and mailed to the recipient. Once the PDF preview is generated, - * this field will be returned by all `GET` endpoints which produce this order. + * Provides sizes and mailing classes available for the destination. * - * This URL is a signed link to the PDF preview. It will expire after a short - * period of time. If you need to access this URL after it has expired, you can - * regenerate it by calling the `GET` endpoint again. - */ - url?: string; -} - -export namespace SnapPackDeleteResponse { - /** - * The cancellation details of this order. Populated if the order has been - * cancelled. + * @example + * ```ts + * const response = + * await client.printMail.snapPacks.retrieveCapabilities({ + * returnCountryCode: 'returnCountryCode', + * }); + * ``` */ - export interface Cancellation { - /** - * The reason for the cancellation. - */ - reason: 'user_initiated' | 'invalid_content' | 'invalid_order_mailing_class'; - - /** - * The user ID who cancelled the order. - */ - cancelledByUser?: string; - - /** - * An optional note provided by the user who cancelled the order. - */ - note?: string; + retrieveCapabilities( + query: SnapPackRetrieveCapabilitiesParams, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/print-mail/v1/snap_packs/capabilities', { query, ...options }); } } -export interface SnapPackProgressionsResponse { +export type SnapPacksSkipLimit = SkipLimit; + +export interface SnapPack { /** * A unique ID prefixed with snap*pack* */ @@ -1023,7 +236,7 @@ export interface SnapPackProgressionsResponse { * The cancellation details of this order. Populated if the order has been * cancelled. */ - cancellation?: SnapPackProgressionsResponse.Cancellation; + cancellation?: SnapPack.Cancellation; /** * An optional string describing this resource. Will be visible in the API and the @@ -1111,7 +324,7 @@ export interface SnapPackProgressionsResponse { url?: string; } -export namespace SnapPackProgressionsResponse { +export namespace SnapPack { /** * The cancellation details of this order. Populated if the order has been * cancelled. @@ -1134,6 +347,8 @@ export namespace SnapPackProgressionsResponse { } } +export type SnapPackCreateResponse = SnapPack | SnapPack; + export interface SnapPackRetrieveCapabilitiesResponse { mailingClasses: Array< | 'first_class' @@ -1175,45 +390,45 @@ export type SnapPackCreateParams = export declare namespace SnapPackCreateParams { export interface SnapPackCreateWithHTML { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to` contact. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to` contact. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The HTML content for the inside of the snap pack. You can supply _either_ this - * or `insideTemplate` but not both. + * Body param: The HTML content for the inside of the snap pack. You can supply + * _either_ this or `insideTemplate` but not both. */ insideHTML: string; /** - * The HTML content for the outside of the snap pack. You can supply _either_ this - * or `outsideTemplate` but not both. + * Body param: The HTML content for the outside of the snap pack. You can supply + * _either_ this or `outsideTemplate` but not both. */ outsideHTML: string; /** - * Enum representing the supported snap pack sizes. + * Body param: Enum representing the supported snap pack sizes. */ size: '8.5x11_bifold_v'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -1244,66 +459,72 @@ export declare namespace SnapPackCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface SnapPackCreateWithTemplate { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to` contact. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to` contact. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * The template ID for the inside of the snap pack. You can supply _either_ this or - * `insideHTML` but not both. + * Body param: The template ID for the inside of the snap pack. You can supply + * _either_ this or `insideHTML` but not both. */ insideTemplate: string; /** - * The template ID for the outside of the snap pack. You can supply _either_ this - * or `outsideHTML` but not both. + * Body param: The template ID for the outside of the snap pack. You can supply + * _either_ this or `outsideHTML` but not both. */ outsideTemplate: string; /** - * Enum representing the supported snap pack sizes. + * Body param: Enum representing the supported snap pack sizes. */ size: '8.5x11_bifold_v'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -1334,60 +555,66 @@ export declare namespace SnapPackCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } export interface SnapPackCreateWithPdf { /** - * The contact information of the sender. You can pass contact information inline - * here just like you can for the `to` contact. + * Body param: The contact information of the sender. You can pass contact + * information inline here just like you can for the `to` contact. */ from: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * A URL or a multipart-uploaded two-page PDF (first page is the outside, second - * page is the inside) that matches the selected snap pack size. + * Body param: A URL or a multipart-uploaded two-page PDF (first page is the + * outside, second page is the inside) that matches the selected snap pack size. */ - pdf: string; + pdf: string | Uploadable; /** - * Enum representing the supported snap pack sizes. + * Body param: Enum representing the supported snap pack sizes. */ size: '8.5x11_bifold_v'; /** - * The recipient of this order. You can either supply the contact information - * inline here or provide a contact ID. PostGrid will automatically deduplicate - * contacts regardless of whether you provide the information inline here or call - * the contact creation endpoint. + * Body param: The recipient of this order. You can either supply the contact + * information inline here or provide a contact ID. PostGrid will automatically + * deduplicate contacts regardless of whether you provide the information inline + * here or call the contact creation endpoint. */ to: ContactsAPI.ContactCreateWithFirstName | ContactsAPI.ContactCreateWithCompanyName | string; /** - * An optional string describing this resource. Will be visible in the API and the - * dashboard. + * Body param: An optional string describing this resource. Will be visible in the + * API and the dashboard. */ description?: string; /** - * The mailing class of this order. If not provided, automatically set to - * `first_class`. + * Body param: The mailing class of this order. If not provided, automatically set + * to `first_class`. */ mailingClass?: | 'first_class' @@ -1418,23 +645,29 @@ export declare namespace SnapPackCreateParams { | 'au_post_second_class'; /** - * These will be merged with the variables in the template or HTML you create this - * order with. The keys in this object should match the variable names in the - * template _exactly_ as they are case-sensitive. Note that these _do not_ apply to - * PDFs uploaded with the order. + * Body param: These will be merged with the variables in the template or HTML you + * create this order with. The keys in this object should match the variable names + * in the template _exactly_ as they are case-sensitive. Note that these _do not_ + * apply to PDFs uploaded with the order. */ mergeVariables?: { [key: string]: unknown }; /** - * See the section on Metadata. + * Body param: See the section on Metadata. */ metadata?: { [key: string]: unknown }; /** - * This order will transition from `ready` to `printing` on the day after this - * date. You can use this parameter to schedule orders for a future date. + * Body param: This order will transition from `ready` to `printing` on the day + * after this date. You can use this parameter to schedule orders for a future + * date. */ sendDate?: string; + + /** + * Header param + */ + 'idempotency-key'?: string; } } @@ -1470,13 +703,10 @@ export interface SnapPackRetrieveCapabilitiesParams { export declare namespace SnapPacks { export { + type SnapPack as SnapPack, type SnapPackCreateResponse as SnapPackCreateResponse, - type SnapPackRetrieveResponse as SnapPackRetrieveResponse, - type SnapPackListResponse as SnapPackListResponse, - type SnapPackDeleteResponse as SnapPackDeleteResponse, - type SnapPackProgressionsResponse as SnapPackProgressionsResponse, type SnapPackRetrieveCapabilitiesResponse as SnapPackRetrieveCapabilitiesResponse, - type SnapPackListResponsesSkipLimit as SnapPackListResponsesSkipLimit, + type SnapPacksSkipLimit as SnapPacksSkipLimit, type SnapPackCreateParams as SnapPackCreateParams, type SnapPackListParams as SnapPackListParams, type SnapPackRetrieveCapabilitiesParams as SnapPackRetrieveCapabilitiesParams, diff --git a/tests/api-resources/print-mail/campaigns.test.ts b/tests/api-resources/print-mail/campaigns.test.ts index 8e15fe3..14cbe82 100644 --- a/tests/api-resources/print-mail/campaigns.test.ts +++ b/tests/api-resources/print-mail/campaigns.test.ts @@ -30,7 +30,7 @@ describe('resource campaigns', () => { currencyCode: 'CAD', description: 'description', envelope: 'envelope', - letterPDF: 'U3RhaW5sZXNzIHJvY2tz', + letterPDF: 'https://example.com', letterSettings: { color: true }, letterTemplate: 'letterTemplate', logo: 'https://example.com', diff --git a/tests/api-resources/print-mail/cheques.test.ts b/tests/api-resources/print-mail/cheques.test.ts index ee8db21..b9905a6 100644 --- a/tests/api-resources/print-mail/cheques.test.ts +++ b/tests/api-resources/print-mail/cheques.test.ts @@ -41,7 +41,7 @@ describe('resource cheques', () => { }, envelope: 'standard', letterHTML: 'letterHTML', - letterPDF: 'U3RhaW5sZXNzIHJvY2tz', + letterPDF: 'https://example.com', letterSettings: { placement: 'before_cheque' }, letterTemplate: 'letterTemplate', logo: 'https://example.com', @@ -73,6 +73,7 @@ describe('resource cheques', () => { returnEnvelope: 'returnEnvelope', sendDate: '2019-12-27T18:11:19.117Z', size: 'us_letter', + 'idempotency-key': 'idempotency-key', }); }); diff --git a/tests/api-resources/print-mail/letters.test.ts b/tests/api-resources/print-mail/letters.test.ts index 2e1fd8d..bba17a0 100644 --- a/tests/api-resources/print-mail/letters.test.ts +++ b/tests/api-resources/print-mail/letters.test.ts @@ -104,6 +104,7 @@ describe('resource letters', () => { returnEnvelope: 'returnEnvelope', sendDate: '2019-12-27T18:11:19.117Z', size: 'us_letter', + 'idempotency-key': 'idempotency-key', }); }); diff --git a/tests/api-resources/print-mail/postcards.test.ts b/tests/api-resources/print-mail/postcards.test.ts index 197d527..7558677 100644 --- a/tests/api-resources/print-mail/postcards.test.ts +++ b/tests/api-resources/print-mail/postcards.test.ts @@ -80,6 +80,7 @@ describe('resource postcards', () => { metadata: { foo: 'bar' }, paper: 'standard', sendDate: '2019-12-27T18:11:19.117Z', + 'idempotency-key': 'idempotency-key', }); }); diff --git a/tests/api-resources/print-mail/self-mailers.test.ts b/tests/api-resources/print-mail/self-mailers.test.ts index cafcd7d..1b441cd 100644 --- a/tests/api-resources/print-mail/self-mailers.test.ts +++ b/tests/api-resources/print-mail/self-mailers.test.ts @@ -84,6 +84,7 @@ describe('resource selfMailers', () => { mergeVariables: { foo: 'bar' }, metadata: { foo: 'bar' }, sendDate: '2019-12-27T18:11:19.117Z', + 'idempotency-key': 'idempotency-key', }); }); diff --git a/tests/api-resources/print-mail/snap-packs.test.ts b/tests/api-resources/print-mail/snap-packs.test.ts index 5246d49..934d6df 100644 --- a/tests/api-resources/print-mail/snap-packs.test.ts +++ b/tests/api-resources/print-mail/snap-packs.test.ts @@ -84,6 +84,7 @@ describe('resource snapPacks', () => { mergeVariables: { foo: 'bar' }, metadata: { foo: 'bar' }, sendDate: '2019-12-27T18:11:19.117Z', + 'idempotency-key': 'idempotency-key', }); }); From 62e42474b5904556d9416c9ead34ea26afff13d3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:09:05 +0000 Subject: [PATCH 7/7] release: 0.7.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5d02000..e7ca613 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.6.1" + ".": "0.7.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 691543a..5b652ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.7.0 (2026-06-12) + +Full Changelog: [v0.6.1...v0.7.0](https://github.com/postgrid/postgrid-node/compare/v0.6.1...v0.7.0) + +### Features + +* **api:** update api with events and webhook capabilities ([76f555a](https://github.com/postgrid/postgrid-node/commit/76f555ade3f03eae1fbf31e8972b5761c77c8223)) +* New endpoints, fixed and aligned schemas ([763499e](https://github.com/postgrid/postgrid-node/commit/763499e0c794e44eb5ff4372e76fefed195eefef)) +* PE-6131 HOTFIX: Fix idempotency key header for create endpoints ([086b129](https://github.com/postgrid/postgrid-node/commit/086b129ea75f1c609397dfb5ef57abd09a71771a)) +* Update available premium paper IDs ([d21b5de](https://github.com/postgrid/postgrid-node/commit/d21b5def4f3c93fc7fa6e7c1955e727ea8f0a795)) + + +### Bug Fixes + +* **typescript:** upgrade tsc-multi so that it works with Node 26 ([0e7287b](https://github.com/postgrid/postgrid-node/commit/0e7287bebc5f0eeea9be3c2aa222df39fa585de1)) + + +### Chores + +* **tests:** remove redundant File import ([608fd6b](https://github.com/postgrid/postgrid-node/commit/608fd6b49fd48ab782be29541184ba9aa5197516)) + ## 0.6.1 (2026-05-14) Full Changelog: [v0.6.0...v0.6.1](https://github.com/postgrid/postgrid-node/compare/v0.6.0...v0.6.1) diff --git a/package.json b/package.json index 3d3a9c5..027f083 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgrid-node", - "version": "0.6.1", + "version": "0.7.0", "description": "The official TypeScript library for the PostGrid API", "author": "PostGrid ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index 3998d8e..d9da9f7 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.6.1'; // x-release-please-version +export const VERSION = '0.7.0'; // x-release-please-version