From 5285f8ad79c016f160290c080a247af35c650788 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 10 Jul 2026 04:09:04 +0000 Subject: [PATCH] feat(api)!: sync generated SDK from staging 9e250aba6f6ae8194f242258a900219af0acdbe9 BREAKING CHANGE: removes the google_calendar integration endpoints, the slack integration list endpoint, and the box/google_calendar per-source options on memories search. Source: hyperspell/node-sdk-staging@9e250aba6f6ae8194f242258a900219af0acdbe9 Promoted by: https://github.com/hyperspell/hyperspell/actions/runs/29068363046 --- .github/workflows/ci.yml | 60 +- .stats.yml | 2 +- MIGRATION.md | 1 - api.md | 18 - packages/mcp-server/src/code-tool-worker.ts | 2 - packages/mcp-server/src/local-docs-search.ts | 106 +- packages/mcp-server/src/methods.ts | 12 - packages/mcp-server/src/server.ts | 2 +- scripts/mock | 2 +- 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/actions.ts | 8 +- src/resources/auth.ts | 14 +- src/resources/connections.ts | 4 +- src/resources/integrations/google-calendar.ts | 53 - src/resources/integrations/index.ts | 3 +- src/resources/integrations/integrations.ts | 57 +- src/resources/integrations/slack.ts | 55 +- src/resources/integrations/web-crawler.ts | 11 +- src/resources/memories.ts | 77 +- src/resources/sessions.ts | 2 +- src/resources/shared.ts | 8 +- .../integrations/google-calendar.test.ts | 22 - .../api-resources/integrations/slack.test.ts | 38 - tests/api-resources/memories.test.ts | 2 - tests/qs/empty-keys-cases.ts | 271 -- tests/qs/stringify.test.ts | 2232 ----------------- tests/qs/utils.test.ts | 169 -- tests/stringifyQuery.test.ts | 6 + 36 files changed, 136 insertions(+), 3884 deletions(-) delete mode 100644 src/internal/qs/LICENSE.md delete mode 100644 src/internal/qs/README.md delete mode 100644 src/internal/qs/formats.ts delete mode 100644 src/internal/qs/index.ts delete mode 100644 src/internal/qs/stringify.ts delete mode 100644 src/internal/qs/types.ts delete mode 100644 src/internal/qs/utils.ts delete mode 100644 src/resources/integrations/google-calendar.ts delete mode 100644 tests/api-resources/integrations/google-calendar.test.ts delete mode 100644 tests/api-resources/integrations/slack.test.ts delete mode 100644 tests/qs/empty-keys-cases.ts delete mode 100644 tests/qs/stringify.test.ts delete mode 100644 tests/qs/utils.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9d1d58d..b7a9d229 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -42,7 +42,7 @@ jobs: build: timeout-minutes: 5 name: build - runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') permissions: contents: read @@ -65,59 +65,3 @@ jobs: - name: Check build run: ./scripts/build - - - name: Get GitHub OIDC Token - if: |- - github.repository == 'stainless-sdks/hyperspell-typescript' && - !startsWith(github.ref, 'refs/heads/stl/') - id: github-oidc - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: core.setOutput('github_token', await core.getIDToken()); - - - name: Upload tarball - if: |- - github.repository == 'stainless-sdks/hyperspell-typescript' && - !startsWith(github.ref, 'refs/heads/stl/') - env: - URL: https://pkg.stainless.com/s - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - run: ./scripts/utils/upload-artifact.sh - - - name: Upload MCP Server tarball - if: |- - github.repository == 'stainless-sdks/hyperspell-typescript' && - !startsWith(github.ref, 'refs/heads/stl/') - env: - URL: https://pkg.stainless.com/s?subpackage=mcp-server - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - BASE_PATH: packages/mcp-server - run: ./scripts/utils/upload-artifact.sh - test: - timeout-minutes: 10 - name: test - runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '22' - - - name: Set up pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 - with: - version: '10.30.1' - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Build - run: ./scripts/build - - - name: Run tests - run: ./scripts/test diff --git a/.stats.yml b/.stats.yml index 423e2f9a..3a1d4dfc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 31 +configured_endpoints: 29 diff --git a/MIGRATION.md b/MIGRATION.md index b08a80d2..92b1c781 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -90,7 +90,6 @@ This affects the following methods: - `client.folders.list()` - `client.integrations.connect()` -- `client.integrations.slack.list()` - `client.memories.list()` - `client.evaluate.listQueries()` - `client.vaults.list()` diff --git a/api.md b/api.md index 294b3ad6..6c88e04a 100644 --- a/api.md +++ b/api.md @@ -87,16 +87,6 @@ Methods: - client.integrations.list() -> IntegrationListResponse - client.integrations.connect(integrationID, { ...params }) -> IntegrationConnectResponse -## GoogleCalendar - -Types: - -- Calendar - -Methods: - -- client.integrations.googleCalendar.list() -> Calendar - ## WebCrawler Types: @@ -109,14 +99,6 @@ Methods: ## Slack -Types: - -- SlackListResponse - -Methods: - -- client.integrations.slack.list({ ...params }) -> unknown - # Memories Types: diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 6dae892c..99da78c1 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -116,9 +116,7 @@ const fuse = new Fuse( 'client.folders.setPolicies', 'client.integrations.connect', 'client.integrations.list', - 'client.integrations.googleCalendar.list', 'client.integrations.webCrawler.index', - 'client.integrations.slack.list', 'client.memories.add', 'client.memories.addBulk', 'client.memories.delete', diff --git a/packages/mcp-server/src/local-docs-search.ts b/packages/mcp-server/src/local-docs-search.ts index 0aaed090..02f354d7 100644 --- a/packages/mcp-server/src/local-docs-search.ts +++ b/packages/mcp-server/src/local-docs-search.ts @@ -304,9 +304,9 @@ const EMBEDDED_METHODS: MethodEntry[] = [ stainlessPath: '(resource) integrations > (method) list', qualified: 'client.integrations.list', response: - "{ integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; requires_channel_selection?: boolean; }[]; }", + "{ integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; channel_selection_required?: boolean; folder_selection_required?: boolean; requires_channel_selection?: boolean; supports_channel_selection?: boolean; supports_folder_selection?: boolean; }[]; }", markdown: - "## list\n\n`client.integrations.list(): { integrations: object[]; }`\n\n**get** `/integrations/list`\n\nList all integrations for the user.\n\n### Returns\n\n- `{ integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; requires_channel_selection?: boolean; }[]; }`\n\n - `integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; requires_channel_selection?: boolean; }[]`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst integrations = await client.integrations.list();\n\nconsole.log(integrations);\n```", + "## list\n\n`client.integrations.list(): { integrations: object[]; }`\n\n**get** `/integrations/list`\n\nList all integrations for the user.\n\n### Returns\n\n- `{ integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; channel_selection_required?: boolean; folder_selection_required?: boolean; requires_channel_selection?: boolean; supports_channel_selection?: boolean; supports_folder_selection?: boolean; }[]; }`\n\n - `integrations: { id: string; allow_multiple_connections: boolean; auth_provider: 'nango' | 'unified' | 'whitelabel'; icon: string; name: string; provider: string; actions_only?: boolean; channel_selection_required?: boolean; folder_selection_required?: boolean; requires_channel_selection?: boolean; supports_channel_selection?: boolean; supports_folder_selection?: boolean; }[]`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst integrations = await client.integrations.list();\n\nconsole.log(integrations);\n```", perLanguage: { go: { method: 'client.Integrations.List', @@ -372,44 +372,6 @@ const EMBEDDED_METHODS: MethodEntry[] = [ }, }, }, - { - name: 'list', - endpoint: '/integrations/google_calendar/list', - httpMethod: 'get', - summary: 'List available calendars', - description: - 'List available calendars for a user. This can be used to ie. populate a dropdown for the user to select a calendar.', - stainlessPath: '(resource) integrations.google_calendar > (method) list', - qualified: 'client.integrations.googleCalendar.list', - response: '{ items: { id: string; name: string; primary: boolean; timezone: string; }[]; }', - markdown: - "## list\n\n`client.integrations.googleCalendar.list(): { items: object[]; }`\n\n**get** `/integrations/google_calendar/list`\n\nList available calendars for a user. This can be used to ie. populate a dropdown for the user to select a calendar.\n\n### Returns\n\n- `{ items: { id: string; name: string; primary: boolean; timezone: string; }[]; }`\n\n - `items: { id: string; name: string; primary: boolean; timezone: string; }[]`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst calendar = await client.integrations.googleCalendar.list();\n\nconsole.log(calendar);\n```", - perLanguage: { - go: { - method: 'client.Integrations.GoogleCalendar.List', - example: - 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/hyperspell/hyperspell-go"\n\t"github.com/hyperspell/hyperspell-go/option"\n)\n\nfunc main() {\n\tclient := hyperspell.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tcalendar, err := client.Integrations.GoogleCalendar.List(context.TODO())\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", calendar.Items)\n}\n', - }, - cli: { - method: 'google_calendar list', - example: "hyperspell integrations:google-calendar list \\\n --api-key 'My API Key'", - }, - python: { - method: 'integrations.google_calendar.list', - example: - 'import os\nfrom hyperspell import Hyperspell\n\nclient = Hyperspell(\n api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted\n)\ncalendar = client.integrations.google_calendar.list()\nprint(calendar.items)', - }, - typescript: { - method: 'client.integrations.googleCalendar.list', - example: - "import Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell({\n apiKey: process.env['HYPERSPELL_API_KEY'], // This is the default and can be omitted\n});\n\nconst calendar = await client.integrations.googleCalendar.list();\n\nconsole.log(calendar.items);", - }, - http: { - example: - 'curl https://api.hyperspell.com/integrations/google_calendar/list \\\n -H "Authorization: Bearer $HYPERSPELL_API_KEY"', - }, - }, - }, { name: 'index', endpoint: '/integrations/web_crawler/index', @@ -449,51 +411,6 @@ const EMBEDDED_METHODS: MethodEntry[] = [ }, }, }, - { - name: 'list', - endpoint: '/integrations/slack/list', - httpMethod: 'get', - summary: 'List available Slack conversations', - description: - "List Slack conversations accessible to the user via the live Nango connection.\n\nReturns minimal channel metadata suitable for selection UIs. If required\nscopes are missing, Slack's error is propagated with details.\n\nSupports filtering by channels, including/excluding private channels, DMs,\ngroup DMs, and archived channels based on the provided search options.", - stainlessPath: '(resource) integrations.slack > (method) list', - qualified: 'client.integrations.slack.list', - params: [ - 'channels?: string[];', - 'exclude_archived?: boolean;', - 'include_dms?: boolean;', - 'include_group_dms?: boolean;', - 'include_private?: boolean;', - ], - response: 'object', - markdown: - "## list\n\n`client.integrations.slack.list(channels?: string[], exclude_archived?: boolean, include_dms?: boolean, include_group_dms?: boolean, include_private?: boolean): object`\n\n**get** `/integrations/slack/list`\n\nList Slack conversations accessible to the user via the live Nango connection.\n\nReturns minimal channel metadata suitable for selection UIs. If required\nscopes are missing, Slack's error is propagated with details.\n\nSupports filtering by channels, including/excluding private channels, DMs,\ngroup DMs, and archived channels based on the provided search options.\n\n### Parameters\n\n- `channels?: string[]`\n List of Slack channels to include (by id, name, or #name).\n\n- `exclude_archived?: boolean`\n If set, pass 'exclude_archived' to Slack. If None, omit the param.\n\n- `include_dms?: boolean`\n Include direct messages (im) when listing conversations.\n\n- `include_group_dms?: boolean`\n Include group DMs (mpim) when listing conversations.\n\n- `include_private?: boolean`\n Include private channels when constructing Slack 'types'.\n\n### Returns\n\n- `object`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst slacks = await client.integrations.slack.list();\n\nconsole.log(slacks);\n```", - perLanguage: { - go: { - method: 'client.Integrations.Slack.List', - example: - 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/hyperspell/hyperspell-go"\n\t"github.com/hyperspell/hyperspell-go/option"\n)\n\nfunc main() {\n\tclient := hyperspell.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tslacks, err := client.Integrations.Slack.List(context.TODO(), hyperspell.IntegrationSlackListParams{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", slacks)\n}\n', - }, - cli: { - method: 'slack list', - example: "hyperspell integrations:slack list \\\n --api-key 'My API Key'", - }, - python: { - method: 'integrations.slack.list', - example: - 'import os\nfrom hyperspell import Hyperspell\n\nclient = Hyperspell(\n api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted\n)\nslacks = client.integrations.slack.list()\nprint(slacks)', - }, - typescript: { - method: 'client.integrations.slack.list', - example: - "import Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell({\n apiKey: process.env['HYPERSPELL_API_KEY'], // This is the default and can be omitted\n});\n\nconst slacks = await client.integrations.slack.list();\n\nconsole.log(slacks);", - }, - http: { - example: - 'curl https://api.hyperspell.com/integrations/slack/list \\\n -H "Authorization: Bearer $HYPERSPELL_API_KEY"', - }, - }, - }, { name: 'add', endpoint: '/memories/add', @@ -810,14 +727,14 @@ const EMBEDDED_METHODS: MethodEntry[] = [ 'answer?: boolean;', "effort?: 'minimal' | 'low' | 'medium' | 'high' | 'very_high';", 'max_results?: number;', - "options?: { after?: string; answer_model?: string; before?: string; box?: { weight?: number; }; filter?: object; google_calendar?: { calendar_id?: string; weight?: number; }; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; };", + "options?: { after?: string; answer_model?: string; before?: string; filter?: object; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; };", 'provenance?: boolean;', 'sources?: string[];', ], response: "{ answer?: string; documents?: { document: document | website | task | person | message | event | file | conversation | trace | transcript | company | deal; resource_id: string; source: string; type: string; collection?: string; document_date?: string; highlights?: object[]; ingested_at?: string; last_modified_at?: string; metadata?: object; score?: number; status?: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; summary?: string; title?: string; }[]; errors?: object[]; provenance?: { entities?: provenance_entity[]; failed_sources?: string[]; sources?: provenance_source[]; steps?: provenance_step[]; }; query?: string; query_id?: string; score?: number; }", markdown: - "## search\n\n`client.memories.search(query: string, answer?: boolean, effort?: 'minimal' | 'low' | 'medium' | 'high' | 'very_high', max_results?: number, options?: { after?: string; answer_model?: string; before?: string; box?: { weight?: number; }; filter?: object; google_calendar?: { calendar_id?: string; weight?: number; }; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; }, provenance?: boolean, sources?: string[]): { answer?: string; documents?: scored_document_response[]; errors?: object[]; provenance?: provenance; query?: string; query_id?: string; score?: number; }`\n\n**post** `/memories/query`\n\nRetrieves documents matching the query.\n\n### Parameters\n\n- `query: string`\n Query to run.\n\n- `answer?: boolean`\n If true, the query will be answered along with matching source documents.\n\n- `effort?: 'minimal' | 'low' | 'medium' | 'high' | 'very_high'`\n How much compute to spend on retrieval. Mirrors the dial popularized by frontier-model APIs (OpenAI reasoning_effort, etc.). 'minimal' = verbatim single-shot retrieval (fastest). 'low' = LLM rewrites the query for better retrieval and extracts date filters. 'medium' = rewrite + agentic refinement loop (the answer LLM may request additional retrieval rounds, up to 3). 'high' = rewrite + extended refinement (up to 6 rounds). Higher = better recall, more latency, more cost.\n\n- `max_results?: number`\n Maximum number of results to return.\n\n- `options?: { after?: string; answer_model?: string; before?: string; box?: { weight?: number; }; filter?: object; google_calendar?: { calendar_id?: string; weight?: number; }; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; }`\n Search options for the query.\n - `after?: string`\n Only query documents created on or after this date.\n - `answer_model?: string`\n Model to use for answer generation when answer=True\n - `before?: string`\n Only query documents created before this date.\n - `box?: { weight?: number; }`\n Search options for Box\n - `filter?: object`\n Metadata filters using MongoDB-style operators. Example: {'status': 'published', 'priority': {'$gt': 3}}\n - `google_calendar?: { calendar_id?: string; weight?: number; }`\n Search options for Google Calendar\n - `google_drive?: { weight?: number; }`\n Search options for Google Drive\n - `google_mail?: { label_ids?: string[]; weight?: number; }`\n Search options for Gmail\n - `max_results?: number`\n Maximum number of results to return.\n - `memory_types?: 'procedure' | 'memory' | 'mood'[]`\n Filter by memory type. Defaults to generic memories only. Pass multiple types to include procedures, etc.\n - `notion?: { notion_page_ids?: string[]; weight?: number; }`\n Search options for Notion\n - `recency_half_life_days?: number`\n When set, multiplies each result's score by an exponential-decay factor based on the document's most recent activity timestamp (source-reported last_modified, falling back to document_date). A document one half-life old gets its score halved. Resources with no recency timestamp are passed through unchanged. Leave unset to disable.\n - `resource_ids?: string[]`\n Only return results from these specific resource IDs. Useful for scoping searches to specific documents (e.g., a specific email thread or uploaded file).\n - `slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }`\n Search options for Slack\n - `vault?: { weight?: number; }`\n Search options for vault\n - `web_crawler?: { max_depth?: number; url?: string; weight?: number; }`\n Search options for Web Crawler\n\n- `provenance?: boolean`\n If true (effort='very_high' only), attach a provenance record to the response: the source documents and entities the answer was grounded in, the agent's search trajectory, and any sources that failed. Adds one indexed lookup; intended for auditability / compliance use cases.\n\n- `sources?: string[]`\n Only query documents from these sources.\n\n### Returns\n\n- `{ answer?: string; documents?: { document: document | website | task | person | message | event | file | conversation | trace | transcript | company | deal; resource_id: string; source: string; type: string; collection?: string; document_date?: string; highlights?: object[]; ingested_at?: string; last_modified_at?: string; metadata?: object; score?: number; status?: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; summary?: string; title?: string; }[]; errors?: object[]; provenance?: { entities?: provenance_entity[]; failed_sources?: string[]; sources?: provenance_source[]; steps?: provenance_step[]; }; query?: string; query_id?: string; score?: number; }`\n\n - `answer?: string`\n - `documents?: { document: { id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; metadata?: metadata; text?: string; title?: string; type?: 'document'; } | { url: string; id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; description?: string; favicon?: string; image_url?: string; language?: string; metadata?: metadata; text?: string; title?: string; type?: 'website'; } | { id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; comments?: message[]; due_at?: string; metadata?: metadata; priority?: 'urgent' | 'high' | 'medium' | 'low'; status?: 'completed' | 'not_started' | 'in_progress' | 'cancelled'; text?: string; type?: 'task'; } | { id?: string; address?: string; alt_names?: string[]; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; company?: string; company_ids?: string[]; date_of_birth?: string; deal_ids?: string[]; email?: string; emails?: string[]; image_url?: string; job_title?: string; link_urls?: string[]; metadata?: metadata; name?: string; phone_numbers?: string[]; tags?: string[]; text?: string; type?: 'person'; username?: string; } | { date: string; sender: person; id?: string; channel?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; external_id?: string; is_self?: boolean; mentioned_users?: person[]; metadata?: metadata; num_replies?: number; replies?: message[]; text?: string; thread_id?: string; title?: string; type?: 'message'; updated_at?: string; upvotes?: number; } | { id?: string; attendees?: person[]; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; end_at?: string; location?: string; meeting_url?: string; metadata?: metadata; start_at?: string; text?: string; title?: string; type?: 'event'; } | { content_type: string; filename: string; id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; metadata?: metadata; path?: string[]; text?: string; title?: string; type?: 'file'; } | { id?: string; channel?: string; children?: message[]; metadata?: metadata; text?: string; title?: string; type?: 'conversation'; } | { id?: string; children?: trace_message | tool_call | tool_result[]; metadata?: metadata; text?: string; title?: string; type?: 'trace'; } | { id?: string; children?: utterance[]; ended_at?: string; metadata?: metadata; participants?: person[]; started_at?: string; text?: string; title?: string; type?: 'transcript'; } | { id?: string; address?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; contact_ids?: string[]; deal_ids?: string[]; description?: string; emails?: string[]; employees?: number; image_url?: string; industry?: string; is_active?: boolean; metadata?: metadata; name?: string; phone_numbers?: string[]; tags?: string[]; text?: string; timezone?: string; type?: 'company'; websites?: string[]; } | { id?: string; amount?: number; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; closed_at?: string; company_ids?: string[]; contact_ids?: string[]; currency?: string; deal_source?: string; lost_reason?: string; metadata?: metadata; name?: string; pipeline?: string; probability?: number; stage?: string; tags?: string[]; text?: string; type?: 'deal'; won_reason?: string; }; resource_id: string; source: string; type: string; collection?: string; document_date?: string; highlights?: object[]; ingested_at?: string; last_modified_at?: string; metadata?: object; score?: number; status?: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; summary?: string; title?: string; }[]`\n - `errors?: object[]`\n - `provenance?: { entities?: { id: string; name: string; type: string; }[]; failed_sources?: string[]; sources?: { resource_id: string; source: string; score?: number; title?: string; }[]; steps?: { iteration: number; status: string; tool: string; query?: string; result_count?: number; source?: string; }[]; }`\n - `query?: string`\n - `query_id?: string`\n - `score?: number`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst queryResult = await client.memories.search({ query: 'What does Hyperspell do?' });\n\nconsole.log(queryResult);\n```", + "## search\n\n`client.memories.search(query: string, answer?: boolean, effort?: 'minimal' | 'low' | 'medium' | 'high' | 'very_high', max_results?: number, options?: { after?: string; answer_model?: string; before?: string; filter?: object; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; }, provenance?: boolean, sources?: string[]): { answer?: string; documents?: scored_document_response[]; errors?: object[]; provenance?: provenance; query?: string; query_id?: string; score?: number; }`\n\n**post** `/memories/query`\n\nRetrieves documents matching the query.\n\n### Parameters\n\n- `query: string`\n Query to run.\n\n- `answer?: boolean`\n If true, the query will be answered along with matching source documents.\n\n- `effort?: 'minimal' | 'low' | 'medium' | 'high' | 'very_high'`\n How much compute to spend on retrieval. Mirrors the dial popularized by frontier-model APIs (OpenAI reasoning_effort, etc.). 'minimal' = verbatim single-shot retrieval (fastest). 'low' = LLM rewrites the query for better retrieval and extracts date filters. 'medium' = rewrite + agentic refinement loop (the answer LLM may request additional retrieval rounds, up to 3). 'high' = rewrite + extended refinement (up to 6 rounds). Higher = better recall, more latency, more cost.\n\n- `max_results?: number`\n Maximum number of results to return.\n\n- `options?: { after?: string; answer_model?: string; before?: string; filter?: object; google_drive?: { weight?: number; }; google_mail?: { label_ids?: string[]; weight?: number; }; max_results?: number; memory_types?: 'procedure' | 'memory' | 'mood'[]; notion?: { notion_page_ids?: string[]; weight?: number; }; recency_half_life_days?: number; resource_ids?: string[]; slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }; vault?: { weight?: number; }; web_crawler?: { max_depth?: number; url?: string; weight?: number; }; }`\n Search options for the query.\n - `after?: string`\n Only query documents created on or after this date.\n - `answer_model?: string`\n Model to use for answer generation when answer=True\n - `before?: string`\n Only query documents created before this date.\n - `filter?: object`\n Metadata filters using MongoDB-style operators. Example: {'status': 'published', 'priority': {'$gt': 3}}\n - `google_drive?: { weight?: number; }`\n Search options for Google Drive\n - `google_mail?: { label_ids?: string[]; weight?: number; }`\n Search options for Gmail\n - `max_results?: number`\n Maximum number of results to return.\n - `memory_types?: 'procedure' | 'memory' | 'mood'[]`\n Filter by memory type. Defaults to generic memories only. Pass multiple types to include procedures, etc.\n - `notion?: { notion_page_ids?: string[]; weight?: number; }`\n Search options for Notion\n - `recency_half_life_days?: number`\n When set, multiplies each result's score by an exponential-decay factor based on the document's most recent activity timestamp (source-reported last_modified, falling back to document_date). A document one half-life old gets its score halved. Resources with no recency timestamp are passed through unchanged. Leave unset to disable.\n - `resource_ids?: string[]`\n Only return results from these specific resource IDs. Useful for scoping searches to specific documents (e.g., a specific email thread or uploaded file).\n - `slack?: { channels?: string[]; exclude_archived?: boolean; include_dms?: boolean; include_group_dms?: boolean; include_private?: boolean; weight?: number; }`\n Search options for Slack\n - `vault?: { weight?: number; }`\n Search options for vault\n - `web_crawler?: { max_depth?: number; url?: string; weight?: number; }`\n Search options for Web Crawler\n\n- `provenance?: boolean`\n If true (effort='very_high' only), attach a provenance record to the response: the source documents and entities the answer was grounded in, the agent's search trajectory, and any sources that failed. Adds one indexed lookup; intended for auditability / compliance use cases.\n\n- `sources?: string[]`\n Only query documents from these sources.\n\n### Returns\n\n- `{ answer?: string; documents?: { document: document | website | task | person | message | event | file | conversation | trace | transcript | company | deal; resource_id: string; source: string; type: string; collection?: string; document_date?: string; highlights?: object[]; ingested_at?: string; last_modified_at?: string; metadata?: object; score?: number; status?: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; summary?: string; title?: string; }[]; errors?: object[]; provenance?: { entities?: provenance_entity[]; failed_sources?: string[]; sources?: provenance_source[]; steps?: provenance_step[]; }; query?: string; query_id?: string; score?: number; }`\n\n - `answer?: string`\n - `documents?: { document: { id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; metadata?: metadata; text?: string; title?: string; type?: 'document'; } | { url: string; id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; description?: string; favicon?: string; image_url?: string; language?: string; metadata?: metadata; text?: string; title?: string; type?: 'website'; } | { id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; comments?: message[]; due_at?: string; metadata?: metadata; priority?: 'urgent' | 'high' | 'medium' | 'low'; status?: 'completed' | 'not_started' | 'in_progress' | 'cancelled'; text?: string; type?: 'task'; } | { id?: string; address?: string; alt_names?: string[]; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; company?: string; company_ids?: string[]; date_of_birth?: string; deal_ids?: string[]; email?: string; emails?: string[]; image_url?: string; job_title?: string; link_urls?: string[]; metadata?: metadata; name?: string; phone_numbers?: string[]; tags?: string[]; text?: string; type?: 'person'; username?: string; } | { date: string; sender: person; id?: string; channel?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; external_id?: string; is_self?: boolean; mentioned_users?: person[]; metadata?: metadata; num_replies?: number; replies?: message[]; text?: string; thread_id?: string; title?: string; type?: 'message'; updated_at?: string; upvotes?: number; } | { id?: string; attendees?: person[]; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; end_at?: string; location?: string; meeting_url?: string; metadata?: metadata; start_at?: string; text?: string; title?: string; type?: 'event'; } | { content_type: string; filename: string; id?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; metadata?: metadata; path?: string[]; text?: string; title?: string; type?: 'file'; } | { id?: string; channel?: string; children?: message[]; metadata?: metadata; text?: string; title?: string; type?: 'conversation'; } | { id?: string; children?: trace_message | tool_call | tool_result[]; metadata?: metadata; text?: string; title?: string; type?: 'trace'; } | { id?: string; children?: utterance[]; ended_at?: string; metadata?: metadata; participants?: person[]; started_at?: string; text?: string; title?: string; type?: 'transcript'; } | { id?: string; address?: string; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; contact_ids?: string[]; deal_ids?: string[]; description?: string; emails?: string[]; employees?: number; image_url?: string; industry?: string; is_active?: boolean; metadata?: metadata; name?: string; phone_numbers?: string[]; tags?: string[]; text?: string; timezone?: string; type?: 'company'; websites?: string[]; } | { id?: string; amount?: number; children?: blob | callout | chunk | code | comment | divider | equation | footnote | heading | image | link | line_break | list | list_item | paragraph | quote | table | table_cell | table_row | text | to_do | tool_call | tool_result | trace_message | utterance[]; closed_at?: string; company_ids?: string[]; contact_ids?: string[]; currency?: string; deal_source?: string; lost_reason?: string; metadata?: metadata; name?: string; pipeline?: string; probability?: number; stage?: string; tags?: string[]; text?: string; type?: 'deal'; won_reason?: string; }; resource_id: string; source: string; type: string; collection?: string; document_date?: string; highlights?: object[]; ingested_at?: string; last_modified_at?: string; metadata?: object; score?: number; status?: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; summary?: string; title?: string; }[]`\n - `errors?: object[]`\n - `provenance?: { entities?: { id: string; name: string; type: string; }[]; failed_sources?: string[]; sources?: { resource_id: string; source: string; score?: number; title?: string; }[]; steps?: { iteration: number; status: string; tool: string; query?: string; result_count?: number; source?: string; }[]; }`\n - `query?: string`\n - `query_id?: string`\n - `score?: number`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst queryResult = await client.memories.search({ query: 'What does Hyperspell do?' });\n\nconsole.log(queryResult);\n```", perLanguage: { go: { method: 'client.Memories.Search', @@ -1230,7 +1147,7 @@ const EMBEDDED_METHODS: MethodEntry[] = [ params: ['user_id: string;', 'expires_in?: string;', 'origin?: string;'], response: '{ token: string; expires_at: string; }', markdown: - "## user_token\n\n`client.auth.userToken(user_id: string, expires_in?: string, origin?: string): { token: string; expires_at: string; }`\n\n**post** `/auth/user_token`\n\nUse this endpoint to create a user token for a specific user.\nThis token can be safely passed to your user-facing front-end.\n\n### Parameters\n\n- `user_id: string`\n\n- `expires_in?: string`\n Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided.\n\n- `origin?: string`\n Origin of the request, used for CSRF protection. If set, the token will only be valid for requests originating from this origin.\n\n### Returns\n\n- `{ token: string; expires_at: string; }`\n\n - `token: string`\n - `expires_at: string`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst token = await client.auth.userToken({ user_id: 'user_id' });\n\nconsole.log(token);\n```", + "## user_token\n\n`client.auth.userToken(user_id: string, expires_in?: string, origin?: string): { token: string; expires_at: string; }`\n\n**post** `/auth/user_token`\n\nUse this endpoint to create a user token for a specific user.\nThis token can be safely passed to your user-facing front-end.\n\n### Parameters\n\n- `user_id: string`\n\n- `expires_in?: string`\n Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided. Maximum 30 days.\n\n- `origin?: string`\n Origin of the request, used for CSRF protection. If set, the token will only be valid for requests originating from this origin.\n\n### Returns\n\n- `{ token: string; expires_at: string; }`\n\n - `token: string`\n - `expires_at: string`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst token = await client.auth.userToken({ user_id: 'user_id' });\n\nconsole.log(token);\n```", perLanguage: { go: { method: 'client.Auth.UserToken', @@ -1300,17 +1217,18 @@ const EMBEDDED_METHODS: MethodEntry[] = [ endpoint: '/auth/delete', httpMethod: 'delete', summary: 'Delete user', - description: 'Endpoint to delete user.', + description: + "Delete the calling user's data (GDPR erasure).\n\nThe purge — external connection revokes plus the multi-table cascade delete —\nruns in a background Temporal workflow; this returns ``202 Accepted`` + the\nworkflow id immediately, so a large account can't outrun the request timeout\nor stall the API event loop.", stainlessPath: '(resource) auth > (method) delete_user', qualified: 'client.auth.deleteUser', - response: '{ message: string; success: boolean; }', + response: '{ message: string; success: boolean; workflow_id: string; }', markdown: - "## delete_user\n\n`client.auth.deleteUser(): { message: string; success: boolean; }`\n\n**delete** `/auth/delete`\n\nEndpoint to delete user.\n\n### Returns\n\n- `{ message: string; success: boolean; }`\n\n - `message: string`\n - `success: boolean`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst response = await client.auth.deleteUser();\n\nconsole.log(response);\n```", + "## delete_user\n\n`client.auth.deleteUser(): { message: string; success: boolean; workflow_id: string; }`\n\n**delete** `/auth/delete`\n\nDelete the calling user's data (GDPR erasure).\n\nThe purge — external connection revokes plus the multi-table cascade delete —\nruns in a background Temporal workflow; this returns ``202 Accepted`` + the\nworkflow id immediately, so a large account can't outrun the request timeout\nor stall the API event loop.\n\n### Returns\n\n- `{ message: string; success: boolean; workflow_id: string; }`\n\n - `message: string`\n - `success: boolean`\n - `workflow_id: string`\n\n### Example\n\n```typescript\nimport Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell();\n\nconst response = await client.auth.deleteUser();\n\nconsole.log(response);\n```", perLanguage: { go: { method: 'client.Auth.DeleteUser', example: - 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/hyperspell/hyperspell-go"\n\t"github.com/hyperspell/hyperspell-go/option"\n)\n\nfunc main() {\n\tclient := hyperspell.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Auth.DeleteUser(context.TODO())\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Message)\n}\n', + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/hyperspell/hyperspell-go"\n\t"github.com/hyperspell/hyperspell-go/option"\n)\n\nfunc main() {\n\tclient := hyperspell.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Auth.DeleteUser(context.TODO())\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.WorkflowID)\n}\n', }, cli: { method: 'auth delete_user', @@ -1319,12 +1237,12 @@ const EMBEDDED_METHODS: MethodEntry[] = [ python: { method: 'auth.delete_user', example: - 'import os\nfrom hyperspell import Hyperspell\n\nclient = Hyperspell(\n api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.auth.delete_user()\nprint(response.message)', + 'import os\nfrom hyperspell import Hyperspell\n\nclient = Hyperspell(\n api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.auth.delete_user()\nprint(response.workflow_id)', }, typescript: { method: 'client.auth.deleteUser', example: - "import Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell({\n apiKey: process.env['HYPERSPELL_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.auth.deleteUser();\n\nconsole.log(response.message);", + "import Hyperspell from '@hyperspell/hyperspell';\n\nconst client = new Hyperspell({\n apiKey: process.env['HYPERSPELL_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.auth.deleteUser();\n\nconsole.log(response.workflow_id);", }, http: { example: diff --git a/packages/mcp-server/src/methods.ts b/packages/mcp-server/src/methods.ts index 3f2bedbd..5984ad51 100644 --- a/packages/mcp-server/src/methods.ts +++ b/packages/mcp-server/src/methods.ts @@ -58,24 +58,12 @@ export const sdkMethods: SdkMethod[] = [ httpMethod: 'get', httpPath: '/integrations/{integration_id}/connect', }, - { - clientCallName: 'client.integrations.googleCalendar.list', - fullyQualifiedName: 'integrations.googleCalendar.list', - httpMethod: 'get', - httpPath: '/integrations/google_calendar/list', - }, { clientCallName: 'client.integrations.webCrawler.index', fullyQualifiedName: 'integrations.webCrawler.index', httpMethod: 'get', httpPath: '/integrations/web_crawler/index', }, - { - clientCallName: 'client.integrations.slack.list', - fullyQualifiedName: 'integrations.slack.list', - httpMethod: 'get', - httpPath: '/integrations/slack/list', - }, { clientCallName: 'client.memories.add', fullyQualifiedName: 'memories.add', diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 969823d0..71ad3e16 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -29,7 +29,7 @@ export const newMcpServer = async ({ new McpServer( { name: 'hyperspell_hyperspell_api', - version: '0.39.0', + version: '0.40.0', }, { instructions: await getInstructions({ stainlessApiKey, customInstructionsPath }), diff --git a/scripts/mock b/scripts/mock index 1df910b9..23b76a37 100755 --- a/scripts/mock +++ b/scripts/mock @@ -11,7 +11,7 @@ elif [ -n "$STAINLESS_OPENAPI_SPEC_URL" ]; then URL="$STAINLESS_OPENAPI_SPEC_URL" else # Embedded OpenAPI spec (base64-encoded, gzipped) - EMBEDDED_SPEC="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" + EMBEDDED_SPEC="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" SPEC_TMPFILE="$(mktemp)" trap 'rm -f "$SPEC_TMPFILE"' EXIT echo "$EMBEDDED_SPEC" | base64 --decode | gunzip > "$SPEC_TMPFILE" diff --git a/src/client.ts b/src/client.ts index 4c19147b..3c6eeab6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -301,6 +301,9 @@ export class Hyperspell { return buildHeaders([{ 'X-As-User': this.userID }]); } + /** + * 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 deleted file mode 100644 index 3fda1573..00000000 --- a/src/internal/qs/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 67ae04ec..00000000 --- a/src/internal/qs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 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 deleted file mode 100644 index e76a742f..00000000 --- a/src/internal/qs/formats.ts +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index c3a3620d..00000000 --- a/src/internal/qs/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 7e71387f..00000000 --- a/src/internal/qs/stringify.ts +++ /dev/null @@ -1,385 +0,0 @@ -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 deleted file mode 100644 index 7c28dbb4..00000000 --- a/src/internal/qs/types.ts +++ /dev/null @@ -1,71 +0,0 @@ -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 deleted file mode 100644 index 4cd56579..00000000 --- a/src/internal/qs/utils.ts +++ /dev/null @@ -1,265 +0,0 @@ -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 0139cacb..26daecd6 100644 --- a/src/internal/utils/query.ts +++ b/src/internal/utils/query.ts @@ -1,7 +1,23 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import * as qs from '../qs/stringify'; +import { HyperspellError } from '../../core/error'; +/** + * Basic re-implementation of `qs.stringify` for primitive types. + */ export function stringifyQuery(query: object | Record) { - return qs.stringify(query, { arrayFormat: 'comma' }); + 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 HyperspellError( + `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('&'); } diff --git a/src/resources/actions.ts b/src/resources/actions.ts index dcacb818..9b51f629 100644 --- a/src/resources/actions.ts +++ b/src/resources/actions.ts @@ -75,7 +75,9 @@ export interface ActionSendMessageParams { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Message text @@ -135,7 +137,9 @@ export interface ActionAddReactionParams { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Message timestamp to react to diff --git a/src/resources/auth.ts b/src/resources/auth.ts index 7dc1bf02..eeb6f2c6 100644 --- a/src/resources/auth.ts +++ b/src/resources/auth.ts @@ -33,7 +33,12 @@ export class Auth extends APIResource { } /** - * Endpoint to delete user. + * Delete the calling user's data (GDPR erasure). + * + * The purge — external connection revokes plus the multi-table cascade delete — + * runs in a background Temporal workflow; this returns `202 Accepted` + the + * workflow id immediately, so a large account can't outrun the request timeout or + * stall the API event loop. * * @example * ```ts @@ -55,6 +60,8 @@ export interface AuthDeleteUserResponse { message: string; success: boolean; + + workflow_id: string; } export interface AuthMeResponse { @@ -95,6 +102,8 @@ export interface AuthMeResponse { | 'coda' | 'lightfield' | 'gong' + | 'pylon' + | 'clickup' >; /** @@ -124,6 +133,8 @@ export interface AuthMeResponse { | 'coda' | 'lightfield' | 'gong' + | 'pylon' + | 'clickup' >; /** @@ -164,6 +175,7 @@ export interface AuthUserTokenParams { /** * Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided. + * Maximum 30 days. */ expires_in?: string | null; diff --git a/src/resources/connections.ts b/src/resources/connections.ts index 748d6558..a6ac3634 100644 --- a/src/resources/connections.ts +++ b/src/resources/connections.ts @@ -75,7 +75,9 @@ export namespace ConnectionListResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Count of items in user_options.channels (Teams: workspaces selected; 0 means diff --git a/src/resources/integrations/google-calendar.ts b/src/resources/integrations/google-calendar.ts deleted file mode 100644 index d787927a..00000000 --- a/src/resources/integrations/google-calendar.ts +++ /dev/null @@ -1,53 +0,0 @@ -// 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 { RequestOptions } from '../../internal/request-options'; - -export class GoogleCalendar extends APIResource { - /** - * List available calendars for a user. This can be used to ie. populate a dropdown - * for the user to select a calendar. - * - * @example - * ```ts - * const calendar = - * await client.integrations.googleCalendar.list(); - * ``` - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/integrations/google_calendar/list', options); - } -} - -export interface Calendar { - items: Array; -} - -export namespace Calendar { - export interface Item { - /** - * The ID of the calendar - */ - id: string; - - /** - * The name of the calendar - */ - name: string; - - /** - * Whether the calendar is the primary calendar of the user - */ - primary: boolean; - - /** - * Default timezone of the calendar - */ - timezone: string | null; - } -} - -export declare namespace GoogleCalendar { - export { type Calendar as Calendar }; -} diff --git a/src/resources/integrations/index.ts b/src/resources/integrations/index.ts index ee806e5b..a5257dbd 100644 --- a/src/resources/integrations/index.ts +++ b/src/resources/integrations/index.ts @@ -1,11 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -export { GoogleCalendar, type Calendar } from './google-calendar'; export { Integrations, type IntegrationListResponse, type IntegrationConnectResponse, type IntegrationConnectParams, } from './integrations'; -export { Slack, type SlackListResponse, type SlackListParams } from './slack'; +export { Slack } from './slack'; export { WebCrawler, type WebCrawlerIndexResponse, type WebCrawlerIndexParams } from './web-crawler'; diff --git a/src/resources/integrations/integrations.ts b/src/resources/integrations/integrations.ts index cb41aeb1..d60e4ec5 100644 --- a/src/resources/integrations/integrations.ts +++ b/src/resources/integrations/integrations.ts @@ -1,10 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; -import * as GoogleCalendarAPI from './google-calendar'; -import { Calendar, GoogleCalendar } from './google-calendar'; import * as SlackAPI from './slack'; -import { Slack, SlackListParams, SlackListResponse } from './slack'; +import { Slack } from './slack'; import * as WebCrawlerAPI from './web-crawler'; import { WebCrawler, WebCrawlerIndexParams, WebCrawlerIndexResponse } from './web-crawler'; import { APIPromise } from '../../core/api-promise'; @@ -12,17 +10,11 @@ import { RequestOptions } from '../../internal/request-options'; import { path } from '../../internal/utils/path'; export class Integrations extends APIResource { - googleCalendar: GoogleCalendarAPI.GoogleCalendar = new GoogleCalendarAPI.GoogleCalendar(this._client); webCrawler: WebCrawlerAPI.WebCrawler = new WebCrawlerAPI.WebCrawler(this._client); slack: SlackAPI.Slack = new SlackAPI.Slack(this._client); /** * List all integrations for the user. - * - * @example - * ```ts - * const integrations = await client.integrations.list(); - * ``` */ list(options?: RequestOptions): APIPromise { return this._client.get('/integrations/list', options); @@ -30,13 +22,6 @@ export class Integrations extends APIResource { /** * Redirects to the connect URL to link an integration. - * - * @example - * ```ts - * const response = await client.integrations.connect( - * 'integration_id', - * ); - * ``` */ connect( integrationID: string, @@ -104,17 +89,46 @@ export namespace IntegrationListResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Whether this integration only supports write actions (no sync) */ actions_only?: boolean; + /** + * Effective per-app requirement: True when an empty selection indexes NOTHING (the + * user must pick channels). Combines the integration's requires_channel_selection + * with the app's index_all_by_default config. + */ + channel_selection_required?: boolean; + + /** + * Effective per-app requirement: True when the integration's + * require_folder_selection index option is on, i.e. new connections sync NOTHING + * until the user picks folders. + */ + folder_selection_required?: boolean; + /** * Whether the user must select channels before indexing starts */ requires_channel_selection?: boolean; + + /** + * Whether the integration offers an optional channel picker to narrow indexing. + * Empty selection indexes everything (opt-in narrowing). + */ + supports_channel_selection?: boolean; + + /** + * Whether the integration supports listing folders and per-folder sync policies + * (GET /connections/{id}/folders + folder-policies). Drives the folder picker in + * the connect flow. + */ + supports_folder_selection?: boolean; } } @@ -128,7 +142,6 @@ export interface IntegrationConnectParams { redirect_url?: string | null; } -Integrations.GoogleCalendar = GoogleCalendar; Integrations.WebCrawler = WebCrawler; Integrations.Slack = Slack; @@ -139,17 +152,11 @@ export declare namespace Integrations { type IntegrationConnectParams as IntegrationConnectParams, }; - export { GoogleCalendar as GoogleCalendar, type Calendar as Calendar }; - export { WebCrawler as WebCrawler, type WebCrawlerIndexResponse as WebCrawlerIndexResponse, type WebCrawlerIndexParams as WebCrawlerIndexParams, }; - export { - Slack as Slack, - type SlackListResponse as SlackListResponse, - type SlackListParams as SlackListParams, - }; + export { Slack as Slack }; } diff --git a/src/resources/integrations/slack.ts b/src/resources/integrations/slack.ts index 6a6da06b..a74621f9 100644 --- a/src/resources/integrations/slack.ts +++ b/src/resources/integrations/slack.ts @@ -1,58 +1,5 @@ // 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 { RequestOptions } from '../../internal/request-options'; -export class Slack extends APIResource { - /** - * List Slack conversations accessible to the user via the live Nango connection. - * - * Returns minimal channel metadata suitable for selection UIs. If required scopes - * are missing, Slack's error is propagated with details. - * - * Supports filtering by channels, including/excluding private channels, DMs, group - * DMs, and archived channels based on the provided search options. - * - * @example - * ```ts - * const slacks = await client.integrations.slack.list(); - * ``` - */ - list(query: SlackListParams | null | undefined = {}, options?: RequestOptions): APIPromise { - return this._client.get('/integrations/slack/list', { query, ...options }); - } -} - -export type SlackListResponse = unknown; - -export interface SlackListParams { - /** - * List of Slack channels to include (by id, name, or #name). - */ - channels?: Array; - - /** - * If set, pass 'exclude_archived' to Slack. If None, omit the param. - */ - exclude_archived?: boolean | null; - - /** - * Include direct messages (im) when listing conversations. - */ - include_dms?: boolean; - - /** - * Include group DMs (mpim) when listing conversations. - */ - include_group_dms?: boolean; - - /** - * Include private channels when constructing Slack 'types'. - */ - include_private?: boolean; -} - -export declare namespace Slack { - export { type SlackListResponse as SlackListResponse, type SlackListParams as SlackListParams }; -} +export class Slack extends APIResource {} diff --git a/src/resources/integrations/web-crawler.ts b/src/resources/integrations/web-crawler.ts index 79f7748e..2954ba31 100644 --- a/src/resources/integrations/web-crawler.ts +++ b/src/resources/integrations/web-crawler.ts @@ -7,13 +7,6 @@ import { RequestOptions } from '../../internal/request-options'; export class WebCrawler extends APIResource { /** * Recursively crawl a website to make it available for indexed search. - * - * @example - * ```ts - * const response = await client.integrations.webCrawler.index( - * { url: 'url' }, - * ); - * ``` */ index(query: WebCrawlerIndexParams, options?: RequestOptions): APIPromise { return this._client.get('/integrations/web_crawler/index', { query, ...options }); @@ -46,7 +39,9 @@ export interface WebCrawlerIndexResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; status: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; } diff --git a/src/resources/memories.ts b/src/resources/memories.ts index cd8ff777..a560ffe3 100644 --- a/src/resources/memories.ts +++ b/src/resources/memories.ts @@ -213,7 +213,9 @@ export interface MemoryStatus { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; status: 'pending' | 'processing' | 'completed' | 'failed' | 'pending_review' | 'skipped'; } @@ -265,7 +267,9 @@ export interface MemoryListResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Hyperdoc document type discriminator (document, message, file, event, ...). @@ -338,7 +342,9 @@ export interface MemoryDeleteResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; success: boolean; } @@ -407,7 +413,9 @@ export interface MemoryGetResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Hyperdoc document type discriminator (document, message, file, event, ...). @@ -587,7 +595,9 @@ export interface MemoryUpdateParams { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * @deprecated Body param: The collection to move the document to — deprecated, set @@ -658,6 +668,8 @@ export interface MemoryListParams extends CursorPageParams { | 'coda' | 'lightfield' | 'gong' + | 'pylon' + | 'clickup' | null; /** @@ -690,7 +702,9 @@ export interface MemoryGetParams { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; } export interface MemorySearchParams { @@ -760,6 +774,8 @@ export interface MemorySearchParams { | 'coda' | 'lightfield' | 'gong' + | 'pylon' + | 'clickup' >; } @@ -791,22 +807,12 @@ export namespace MemorySearchParams { */ before?: string | null; - /** - * Search options for Box - */ - box?: Options.Box; - /** * Metadata filters using MongoDB-style operators. Example: {'status': 'published', * 'priority': {'$gt': 3}} */ filter?: { [key: string]: unknown } | null; - /** - * Search options for Google Calendar - */ - google_calendar?: Options.GoogleCalendar; - /** * Search options for Google Drive */ @@ -826,7 +832,7 @@ export namespace MemorySearchParams { * Filter by memory type. Defaults to generic memories only. Pass multiple types to * include procedures, etc. */ - memory_types?: Array<'procedure' | 'memory'>; + memory_types?: Array<'procedure' | 'memory' | 'mood'>; /** * Search options for Notion @@ -865,39 +871,6 @@ export namespace MemorySearchParams { } export namespace Options { - /** - * Search options for Box - */ - export interface Box { - /** - * Weight of results from this source. A weight greater than 1.0 means more results - * from this source will be returned, a weight less than 1.0 means fewer results - * will be returned. This will only affect results if multiple sources are queried - * at the same time. - */ - weight?: number; - } - - /** - * Search options for Google Calendar - */ - export interface GoogleCalendar { - /** - * The ID of the calendar to search. If not provided, it will use the ID of the - * default calendar. You can get the list of calendars with the - * `/integrations/google_calendar/list` endpoint. - */ - calendar_id?: string | null; - - /** - * Weight of results from this source. A weight greater than 1.0 means more results - * from this source will be returned, a weight less than 1.0 means fewer results - * will be returned. This will only affect results if multiple sources are queried - * at the same time. - */ - weight?: number; - } - /** * Search options for Google Drive */ @@ -1052,7 +1025,9 @@ export interface MemoryDeleteParams { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; } export declare namespace Memories { diff --git a/src/resources/sessions.ts b/src/resources/sessions.ts index 70cfa3c9..4a789100 100644 --- a/src/resources/sessions.ts +++ b/src/resources/sessions.ts @@ -54,7 +54,7 @@ export interface SessionAddParams { /** * What kind of memories to extract from the trace */ - extract?: Array<'procedure' | 'memory'>; + extract?: Array<'procedure' | 'memory' | 'mood'>; /** * Trace format: 'vercel', 'hyperdoc', or 'openclaw'. Auto-detected if not set. diff --git a/src/resources/shared.ts b/src/resources/shared.ts index bffd529f..bd9d4850 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -1152,7 +1152,9 @@ export interface ProvenanceSource { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; score?: number | null; @@ -1318,7 +1320,9 @@ export interface ScoredDocumentResponse { | 'salesforce' | 'coda' | 'lightfield' - | 'gong'; + | 'gong' + | 'pylon' + | 'clickup'; /** * Hyperdoc document type discriminator (document, message, file, event, ...). diff --git a/tests/api-resources/integrations/google-calendar.test.ts b/tests/api-resources/integrations/google-calendar.test.ts deleted file mode 100644 index b9aa076e..00000000 --- a/tests/api-resources/integrations/google-calendar.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Hyperspell from '@hyperspell/hyperspell'; - -const client = new Hyperspell({ - apiKey: 'My API Key', - userID: 'My User ID', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource googleCalendar', () => { - test('list', async () => { - const responsePromise = client.integrations.googleCalendar.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); - }); -}); diff --git a/tests/api-resources/integrations/slack.test.ts b/tests/api-resources/integrations/slack.test.ts deleted file mode 100644 index 7367e5fc..00000000 --- a/tests/api-resources/integrations/slack.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Hyperspell from '@hyperspell/hyperspell'; - -const client = new Hyperspell({ - apiKey: 'My API Key', - userID: 'My User ID', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource slack', () => { - test('list', async () => { - const responsePromise = client.integrations.slack.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); - }); - - test('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.integrations.slack.list( - { - channels: ['string'], - exclude_archived: true, - include_dms: true, - include_group_dms: true, - include_private: true, - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Hyperspell.NotFoundError); - }); -}); diff --git a/tests/api-resources/memories.test.ts b/tests/api-resources/memories.test.ts index 6b221029..62d4c981 100644 --- a/tests/api-resources/memories.test.ts +++ b/tests/api-resources/memories.test.ts @@ -183,9 +183,7 @@ describe('resource memories', () => { after: '2019-12-27T18:11:19.117Z', answer_model: 'llama-3.1', before: '2019-12-27T18:11:19.117Z', - box: { weight: 0 }, filter: {}, - google_calendar: { calendar_id: 'calendar_id', weight: 0 }, google_drive: { weight: 0 }, google_mail: { label_ids: ['string'], weight: 0 }, max_results: 1, diff --git a/tests/qs/empty-keys-cases.ts b/tests/qs/empty-keys-cases.ts deleted file mode 100644 index ea2c1b0a..00000000 --- a/tests/qs/empty-keys-cases.ts +++ /dev/null @@ -1,271 +0,0 @@ -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 deleted file mode 100644 index 47d83763..00000000 --- a/tests/qs/stringify.test.ts +++ /dev/null @@ -1,2232 +0,0 @@ -import iconv from 'iconv-lite'; -import { stringify } from '@hyperspell/hyperspell/internal/qs'; -import { encode } from '@hyperspell/hyperspell/internal/qs/utils'; -import { StringifyOptions } from '@hyperspell/hyperspell/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 deleted file mode 100644 index 08f0e6da..00000000 --- a/tests/qs/utils.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { combine, merge, is_buffer, assign_single_source } from '@hyperspell/hyperspell/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 ce88c6f0..56a3842e 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -18,4 +18,10 @@ 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}`); + }); + } });