@linqapp/sdk 0.28.0 → 0.28.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/client.d.mts +59 -9
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +59 -9
  5. package/client.d.ts.map +1 -1
  6. package/client.js +63 -5
  7. package/client.js.map +1 -1
  8. package/client.mjs +64 -6
  9. package/client.mjs.map +1 -1
  10. package/internal/qs/formats.d.mts +7 -0
  11. package/internal/qs/formats.d.mts.map +1 -0
  12. package/internal/qs/formats.d.ts +7 -0
  13. package/internal/qs/formats.d.ts.map +1 -0
  14. package/internal/qs/formats.js +13 -0
  15. package/internal/qs/formats.js.map +1 -0
  16. package/internal/qs/formats.mjs +9 -0
  17. package/internal/qs/formats.mjs.map +1 -0
  18. package/internal/qs/index.d.mts +10 -0
  19. package/internal/qs/index.d.mts.map +1 -0
  20. package/internal/qs/index.d.ts +10 -0
  21. package/internal/qs/index.d.ts.map +1 -0
  22. package/internal/qs/index.js +14 -0
  23. package/internal/qs/index.js.map +1 -0
  24. package/internal/qs/index.mjs +10 -0
  25. package/internal/qs/index.mjs.map +1 -0
  26. package/internal/qs/stringify.d.mts +3 -0
  27. package/internal/qs/stringify.d.mts.map +1 -0
  28. package/internal/qs/stringify.d.ts +3 -0
  29. package/internal/qs/stringify.d.ts.map +1 -0
  30. package/internal/qs/stringify.js +277 -0
  31. package/internal/qs/stringify.js.map +1 -0
  32. package/internal/qs/stringify.mjs +274 -0
  33. package/internal/qs/stringify.mjs.map +1 -0
  34. package/internal/qs/types.d.mts +57 -0
  35. package/internal/qs/types.d.mts.map +1 -0
  36. package/internal/qs/types.d.ts +57 -0
  37. package/internal/qs/types.d.ts.map +1 -0
  38. package/internal/qs/types.js +3 -0
  39. package/internal/qs/types.js.map +1 -0
  40. package/internal/qs/types.mjs +2 -0
  41. package/internal/qs/types.mjs.map +1 -0
  42. package/internal/qs/utils.d.mts +15 -0
  43. package/internal/qs/utils.d.mts.map +1 -0
  44. package/internal/qs/utils.d.ts +15 -0
  45. package/internal/qs/utils.d.ts.map +1 -0
  46. package/internal/qs/utils.js +230 -0
  47. package/internal/qs/utils.js.map +1 -0
  48. package/internal/qs/utils.mjs +217 -0
  49. package/internal/qs/utils.mjs.map +1 -0
  50. package/internal/tslib.js +17 -17
  51. package/internal/utils/query.d.mts +0 -3
  52. package/internal/utils/query.d.mts.map +1 -1
  53. package/internal/utils/query.d.ts +0 -3
  54. package/internal/utils/query.d.ts.map +1 -1
  55. package/internal/utils/query.js +3 -16
  56. package/internal/utils/query.js.map +1 -1
  57. package/internal/utils/query.mjs +2 -16
  58. package/internal/utils/query.mjs.map +1 -1
  59. package/package.json +1 -1
  60. package/resources/attachments.d.mts +1 -1
  61. package/resources/attachments.d.ts +1 -1
  62. package/resources/attachments.js +1 -1
  63. package/resources/attachments.mjs +1 -1
  64. package/resources/available-number.d.mts +58 -0
  65. package/resources/available-number.d.mts.map +1 -0
  66. package/resources/available-number.d.ts +58 -0
  67. package/resources/available-number.d.ts.map +1 -0
  68. package/resources/available-number.js +36 -0
  69. package/resources/available-number.js.map +1 -0
  70. package/resources/available-number.mjs +32 -0
  71. package/resources/available-number.mjs.map +1 -0
  72. package/resources/chats/chats.d.mts +59 -11
  73. package/resources/chats/chats.d.mts.map +1 -1
  74. package/resources/chats/chats.d.ts +59 -11
  75. package/resources/chats/chats.d.ts.map +1 -1
  76. package/resources/chats/chats.js.map +1 -1
  77. package/resources/chats/chats.mjs.map +1 -1
  78. package/resources/chats/location.d.mts +9 -8
  79. package/resources/chats/location.d.mts.map +1 -1
  80. package/resources/chats/location.d.ts +9 -8
  81. package/resources/chats/location.d.ts.map +1 -1
  82. package/resources/chats/location.js +9 -8
  83. package/resources/chats/location.js.map +1 -1
  84. package/resources/chats/location.mjs +9 -8
  85. package/resources/chats/location.mjs.map +1 -1
  86. package/resources/chats/messages.d.mts +84 -6
  87. package/resources/chats/messages.d.mts.map +1 -1
  88. package/resources/chats/messages.d.ts +84 -6
  89. package/resources/chats/messages.d.ts.map +1 -1
  90. package/resources/chats/messages.js +40 -0
  91. package/resources/chats/messages.js.map +1 -1
  92. package/resources/chats/messages.mjs +40 -0
  93. package/resources/chats/messages.mjs.map +1 -1
  94. package/resources/contact-card.d.mts +1 -2
  95. package/resources/contact-card.d.mts.map +1 -1
  96. package/resources/contact-card.d.ts +1 -2
  97. package/resources/contact-card.d.ts.map +1 -1
  98. package/resources/index.d.mts +3 -2
  99. package/resources/index.d.mts.map +1 -1
  100. package/resources/index.d.ts +3 -2
  101. package/resources/index.d.ts.map +1 -1
  102. package/resources/index.js +3 -1
  103. package/resources/index.js.map +1 -1
  104. package/resources/index.mjs +2 -1
  105. package/resources/index.mjs.map +1 -1
  106. package/resources/messages.d.mts +400 -7
  107. package/resources/messages.d.mts.map +1 -1
  108. package/resources/messages.d.ts +400 -7
  109. package/resources/messages.d.ts.map +1 -1
  110. package/resources/messages.js +148 -0
  111. package/resources/messages.js.map +1 -1
  112. package/resources/messages.mjs +148 -0
  113. package/resources/messages.mjs.map +1 -1
  114. package/resources/phone-numbers.d.mts +68 -23
  115. package/resources/phone-numbers.d.mts.map +1 -1
  116. package/resources/phone-numbers.d.ts +68 -23
  117. package/resources/phone-numbers.d.ts.map +1 -1
  118. package/resources/phone-numbers.js +23 -0
  119. package/resources/phone-numbers.js.map +1 -1
  120. package/resources/phone-numbers.mjs +23 -0
  121. package/resources/phone-numbers.mjs.map +1 -1
  122. package/resources/webhooks.d.mts +14 -14
  123. package/resources/webhooks.d.ts +14 -14
  124. package/src/client.ts +94 -8
  125. package/src/internal/qs/LICENSE.md +13 -0
  126. package/src/internal/qs/README.md +3 -0
  127. package/src/internal/qs/formats.ts +10 -0
  128. package/src/internal/qs/index.ts +13 -0
  129. package/src/internal/qs/stringify.ts +385 -0
  130. package/src/internal/qs/types.ts +71 -0
  131. package/src/internal/qs/utils.ts +265 -0
  132. package/src/internal/utils/query.ts +2 -18
  133. package/src/resources/attachments.ts +1 -1
  134. package/src/resources/available-number.ts +72 -0
  135. package/src/resources/chats/chats.ts +64 -12
  136. package/src/resources/chats/location.ts +9 -8
  137. package/src/resources/chats/messages.ts +87 -6
  138. package/src/resources/contact-card.ts +1 -2
  139. package/src/resources/index.ts +15 -1
  140. package/src/resources/messages.ts +453 -6
  141. package/src/resources/phone-numbers.ts +85 -23
  142. package/src/resources/webhooks.ts +14 -14
  143. package/src/version.ts +1 -1
  144. package/version.d.mts +1 -1
  145. package/version.d.ts +1 -1
  146. package/version.js +1 -1
  147. package/version.mjs +1 -1
@@ -175,7 +175,7 @@ export declare namespace MessageEventV2 {
175
175
  */
176
176
  type: 'imessage_app';
177
177
  /**
178
- * The URL delivered to the iMessage app on tap.
178
+ * The URL the recipient's app opens when the user taps the card.
179
179
  */
180
180
  url: string;
181
181
  /**
@@ -321,7 +321,7 @@ export declare namespace MessagePayload {
321
321
  */
322
322
  type: 'imessage_app';
323
323
  /**
324
- * The URL delivered to the iMessage app on tap.
324
+ * The URL the recipient's app opens when the user taps the card.
325
325
  */
326
326
  url: string;
327
327
  /**
@@ -1777,13 +1777,13 @@ export declare namespace PhoneNumberStatusUpdatedWebhookEvent {
1777
1777
  */
1778
1778
  changed_at: string;
1779
1779
  /**
1780
- * @deprecated Current reputation of this phone line as assessed by risk-service.
1780
+ * @deprecated Current reputation of this phone line.
1781
1781
  *
1782
- * - `HEALTHY` — No elevated risk detected.
1783
- * - `AT_RISK` — Elevated risk indicators present; consider reducing send volume or
1784
- * reviewing messaging patterns.
1785
- * - `CRITICAL` — High risk; further sending may result in line flagging or
1786
- * restriction.
1782
+ * - `HEALTHY` — The line is in good standing. Send normally.
1783
+ * - `AT_RISK` — The line's overall engagement is trending down. Slow the line's
1784
+ * send pace and review your messaging patterns.
1785
+ * - `CRITICAL` — Strong signals that messages from this line aren't landing well.
1786
+ * Pause outbound on the line until it recovers.
1787
1787
  *
1788
1788
  * Defaults to `HEALTHY` for lines that have not yet been scored.
1789
1789
  */
@@ -1801,13 +1801,13 @@ export declare namespace PhoneNumberStatusUpdatedWebhookEvent {
1801
1801
  */
1802
1802
  phone_number: string;
1803
1803
  /**
1804
- * @deprecated Current reputation of this phone line as assessed by risk-service.
1804
+ * @deprecated Current reputation of this phone line.
1805
1805
  *
1806
- * - `HEALTHY` — No elevated risk detected.
1807
- * - `AT_RISK` — Elevated risk indicators present; consider reducing send volume or
1808
- * reviewing messaging patterns.
1809
- * - `CRITICAL` — High risk; further sending may result in line flagging or
1810
- * restriction.
1806
+ * - `HEALTHY` — The line is in good standing. Send normally.
1807
+ * - `AT_RISK` — The line's overall engagement is trending down. Slow the line's
1808
+ * send pace and review your messaging patterns.
1809
+ * - `CRITICAL` — Strong signals that messages from this line aren't landing well.
1810
+ * Pause outbound on the line until it recovers.
1811
1811
  *
1812
1812
  * Defaults to `HEALTHY` for lines that have not yet been scored.
1813
1813
  */
package/src/client.ts CHANGED
@@ -32,6 +32,11 @@ import {
32
32
  Attachments,
33
33
  SupportedContentType,
34
34
  } from './resources/attachments';
35
+ import {
36
+ AvailableNumber,
37
+ AvailableNumberRetrieveParams,
38
+ AvailableNumberRetrieveResponse,
39
+ } from './resources/available-number';
35
40
  import {
36
41
  Capability,
37
42
  CapabilityCheckIMessageParams,
@@ -51,14 +56,23 @@ import {
51
56
  Message,
52
57
  MessageAddReactionParams,
53
58
  MessageAddReactionResponse,
59
+ MessageCreateParams,
60
+ MessageCreateResponse,
54
61
  MessageEffect,
55
62
  MessageListMessagesThreadParams,
63
+ MessageUpdateAppCardParams,
64
+ MessageUpdateAppCardResponse,
56
65
  MessageUpdateParams,
57
66
  Messages,
58
67
  MessagesListMessagesPagination,
59
68
  ReplyTo,
60
69
  } from './resources/messages';
61
- import { PhoneNumberListResponse, PhoneNumbers } from './resources/phone-numbers';
70
+ import {
71
+ PhoneNumberListResponse,
72
+ PhoneNumberUpdateParams,
73
+ PhoneNumberUpdateResponse,
74
+ PhoneNumbers,
75
+ } from './resources/phone-numbers';
62
76
  import { PhonenumberListResponse, Phonenumbers } from './resources/phonenumbers';
63
77
  import { WebhookEventListResponse, WebhookEventType, WebhookEvents } from './resources/webhook-events';
64
78
  import {
@@ -141,7 +155,7 @@ export interface ClientOptions {
141
155
  * Standard Webhooks signature on incoming webhook requests.
142
156
  *
143
157
  * Format: a base64-encoded key, optionally with a `whsec_` prefix
144
- * (e.g. `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
158
+ * (e.g. `whsec_<your-webhook-signing-secret>`).
145
159
  *
146
160
  */
147
161
  webhookSecret?: string | null | undefined;
@@ -338,9 +352,6 @@ export class LinqAPIV3 {
338
352
  return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]);
339
353
  }
340
354
 
341
- /**
342
- * Basic re-implementation of `qs.stringify` for primitive types.
343
- */
344
355
  protected stringifyQuery(query: object | Record<string, unknown>): string {
345
356
  return stringifyQuery(query);
346
357
  }
@@ -805,11 +816,19 @@ export class LinqAPIV3 {
805
816
  return () => controller.abort();
806
817
  }
807
818
 
808
- private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): {
819
+ private buildBody({ options }: { options: FinalRequestOptions }): {
809
820
  bodyHeaders: HeadersLike;
810
821
  body: BodyInit | undefined;
811
822
  } {
823
+ const { body, headers: rawHeaders } = options;
812
824
  if (!body) {
825
+ // A resource method always passes a `body` key when its operation defines a
826
+ // request body, even if the caller omitted an optional body param. Keep the
827
+ // content-type for those, and only elide it for operations with no body at
828
+ // all (e.g. GET/DELETE).
829
+ if (body == null && 'body' in options) {
830
+ return this.#encoder({ body, headers: buildHeaders([rawHeaders]) });
831
+ }
813
832
  return { bodyHeaders: undefined, body: undefined };
814
833
  }
815
834
  const headers = buildHeaders([rawHeaders]);
@@ -890,6 +909,46 @@ export class LinqAPIV3 {
890
909
  * - A `link` part cannot be combined with other parts in the same message.
891
910
  * - Maximum URL length: 2,048 characters.
892
911
  *
912
+ * ## Ephemeral Messages (Privacy Tier)
913
+ *
914
+ * For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
915
+ *
916
+ * You can request it at two scopes:
917
+ *
918
+ * | Scope | Effect |
919
+ * |---|---|
920
+ * | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
921
+ * | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
922
+ *
923
+ * **Behavioral differences vs the standard default:**
924
+ *
925
+ * | Aspect | Standard | Ephemeral |
926
+ * |---|---|---|
927
+ * | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
928
+ * | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
929
+ * | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
930
+ * | Cross-partner isolation | Enforced | Enforced |
931
+ *
932
+ * **How the 24-hour window works:**
933
+ *
934
+ * - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
935
+ * - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
936
+ * - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
937
+ *
938
+ * **What you observe:**
939
+ *
940
+ * - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
941
+ * - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
942
+ * - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
943
+ *
944
+ * **When to choose ephemeral:**
945
+ *
946
+ * - You have a compliance requirement that the platform must not retain message content beyond a short window.
947
+ * - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
948
+ * - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
949
+ *
950
+ * **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
951
+ *
893
952
  */
894
953
  messages: API.Messages = new API.Messages(this);
895
954
  /**
@@ -1056,7 +1115,7 @@ export class LinqAPIV3 {
1056
1115
  * |---|---|---|
1057
1116
  * | Attachment bytes | Retained until you `DELETE` | **Auto-removed after 1 day**, also removable via `DELETE` |
1058
1117
  * | Attachment metadata (id, filename, mime type, size) | Retained until you `DELETE` | Removed alongside the bytes |
1059
- * | Message body & parts | Retained per message-retention policy | Retained per message-retention policy |
1118
+ * | Message body & parts | Retained per message-retention policy | Retained per message-retention policy — unless the line also has **ephemeral messages** enabled (see the Messages page), in which case the message and its parts are deleted 24 hours after creation |
1060
1119
  * | Audit log of deletions | Retained per platform retention policy | Retained per platform retention policy |
1061
1120
  *
1062
1121
  * **In transit:** TLS 1.2+ everywhere. **At rest:** AES-256 (server-side encryption).
@@ -1096,6 +1155,17 @@ export class LinqAPIV3 {
1096
1155
  *
1097
1156
  */
1098
1157
  phoneNumbers: API.PhoneNumbers = new API.PhoneNumbers(this);
1158
+ /**
1159
+ * Phone Numbers represent the phone numbers assigned to your partner account.
1160
+ *
1161
+ * Use the list phone numbers endpoint to discover which phone numbers are available
1162
+ * for sending messages.
1163
+ *
1164
+ * When creating chats, listing chats, or sending a voice memo, use one of your assigned phone numbers
1165
+ * in the `from` field.
1166
+ *
1167
+ */
1168
+ availableNumber: API.AvailableNumber = new API.AvailableNumber(this);
1099
1169
  /**
1100
1170
  * Webhook Subscriptions allow you to receive real-time notifications when events
1101
1171
  * occur on your account.
@@ -1380,6 +1450,7 @@ LinqAPIV3.Messages = Messages;
1380
1450
  LinqAPIV3.Attachments = Attachments;
1381
1451
  LinqAPIV3.Phonenumbers = Phonenumbers;
1382
1452
  LinqAPIV3.PhoneNumbers = PhoneNumbers;
1453
+ LinqAPIV3.AvailableNumber = AvailableNumber;
1383
1454
  LinqAPIV3.WebhookEvents = WebhookEvents;
1384
1455
  LinqAPIV3.WebhookSubscriptions = WebhookSubscriptions;
1385
1456
  LinqAPIV3.Capability = Capability;
@@ -1424,11 +1495,15 @@ export declare namespace LinqAPIV3 {
1424
1495
  type Message as Message,
1425
1496
  type MessageEffect as MessageEffect,
1426
1497
  type ReplyTo as ReplyTo,
1498
+ type MessageCreateResponse as MessageCreateResponse,
1427
1499
  type MessageAddReactionResponse as MessageAddReactionResponse,
1500
+ type MessageUpdateAppCardResponse as MessageUpdateAppCardResponse,
1428
1501
  type MessagesListMessagesPagination as MessagesListMessagesPagination,
1502
+ type MessageCreateParams as MessageCreateParams,
1429
1503
  type MessageListMessagesThreadParams as MessageListMessagesThreadParams,
1430
1504
  type MessageAddReactionParams as MessageAddReactionParams,
1431
1505
  type MessageUpdateParams as MessageUpdateParams,
1506
+ type MessageUpdateAppCardParams as MessageUpdateAppCardParams,
1432
1507
  };
1433
1508
 
1434
1509
  export {
@@ -1441,7 +1516,18 @@ export declare namespace LinqAPIV3 {
1441
1516
 
1442
1517
  export { Phonenumbers as Phonenumbers, type PhonenumberListResponse as PhonenumberListResponse };
1443
1518
 
1444
- export { PhoneNumbers as PhoneNumbers, type PhoneNumberListResponse as PhoneNumberListResponse };
1519
+ export {
1520
+ PhoneNumbers as PhoneNumbers,
1521
+ type PhoneNumberUpdateResponse as PhoneNumberUpdateResponse,
1522
+ type PhoneNumberListResponse as PhoneNumberListResponse,
1523
+ type PhoneNumberUpdateParams as PhoneNumberUpdateParams,
1524
+ };
1525
+
1526
+ export {
1527
+ AvailableNumber as AvailableNumber,
1528
+ type AvailableNumberRetrieveResponse as AvailableNumberRetrieveResponse,
1529
+ type AvailableNumberRetrieveParams as AvailableNumberRetrieveParams,
1530
+ };
1445
1531
 
1446
1532
  export {
1447
1533
  WebhookEvents as WebhookEvents,
@@ -0,0 +1,13 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/puruvj/neoqs/graphs/contributors) All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ 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.
@@ -0,0 +1,3 @@
1
+ # qs
2
+
3
+ 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.
@@ -0,0 +1,10 @@
1
+ import type { Format } from './types';
2
+
3
+ export const default_format: Format = 'RFC3986';
4
+ export const default_formatter = (v: PropertyKey) => String(v);
5
+ export const formatters: Record<Format, (str: PropertyKey) => string> = {
6
+ RFC1738: (v: PropertyKey) => String(v).replace(/%20/g, '+'),
7
+ RFC3986: default_formatter,
8
+ };
9
+ export const RFC1738 = 'RFC1738';
10
+ export const RFC3986 = 'RFC3986';
@@ -0,0 +1,13 @@
1
+ import { default_format, formatters, RFC1738, RFC3986 } from './formats';
2
+
3
+ const formats = {
4
+ formatters,
5
+ RFC1738,
6
+ RFC3986,
7
+ default: default_format,
8
+ };
9
+
10
+ export { stringify } from './stringify';
11
+ export { formats };
12
+
13
+ export type { DefaultDecoder, DefaultEncoder, Format, ParseOptions, StringifyOptions } from './types';
@@ -0,0 +1,385 @@
1
+ import { encode, is_buffer, maybe_map, has } from './utils';
2
+ import { default_format, default_formatter, formatters } from './formats';
3
+ import type { NonNullableProperties, StringifyOptions } from './types';
4
+ import { isArray } from '../utils/values';
5
+
6
+ const array_prefix_generators = {
7
+ brackets(prefix: PropertyKey) {
8
+ return String(prefix) + '[]';
9
+ },
10
+ comma: 'comma',
11
+ indices(prefix: PropertyKey, key: string) {
12
+ return String(prefix) + '[' + key + ']';
13
+ },
14
+ repeat(prefix: PropertyKey) {
15
+ return String(prefix);
16
+ },
17
+ };
18
+
19
+ const push_to_array = function (arr: any[], value_or_array: any) {
20
+ Array.prototype.push.apply(arr, isArray(value_or_array) ? value_or_array : [value_or_array]);
21
+ };
22
+
23
+ let toISOString;
24
+
25
+ const defaults = {
26
+ addQueryPrefix: false,
27
+ allowDots: false,
28
+ allowEmptyArrays: false,
29
+ arrayFormat: 'indices',
30
+ charset: 'utf-8',
31
+ charsetSentinel: false,
32
+ delimiter: '&',
33
+ encode: true,
34
+ encodeDotInKeys: false,
35
+ encoder: encode,
36
+ encodeValuesOnly: false,
37
+ format: default_format,
38
+ formatter: default_formatter,
39
+ /** @deprecated */
40
+ indices: false,
41
+ serializeDate(date) {
42
+ return (toISOString ??= Function.prototype.call.bind(Date.prototype.toISOString))(date);
43
+ },
44
+ skipNulls: false,
45
+ strictNullHandling: false,
46
+ } as NonNullableProperties<StringifyOptions & { formatter: (typeof formatters)['RFC1738'] }>;
47
+
48
+ function is_non_nullish_primitive(v: unknown): v is string | number | boolean | symbol | bigint {
49
+ return (
50
+ typeof v === 'string' ||
51
+ typeof v === 'number' ||
52
+ typeof v === 'boolean' ||
53
+ typeof v === 'symbol' ||
54
+ typeof v === 'bigint'
55
+ );
56
+ }
57
+
58
+ const sentinel = {};
59
+
60
+ function inner_stringify(
61
+ object: any,
62
+ prefix: PropertyKey,
63
+ generateArrayPrefix: StringifyOptions['arrayFormat'] | ((prefix: string, key: string) => string),
64
+ commaRoundTrip: boolean,
65
+ allowEmptyArrays: boolean,
66
+ strictNullHandling: boolean,
67
+ skipNulls: boolean,
68
+ encodeDotInKeys: boolean,
69
+ encoder: StringifyOptions['encoder'],
70
+ filter: StringifyOptions['filter'],
71
+ sort: StringifyOptions['sort'],
72
+ allowDots: StringifyOptions['allowDots'],
73
+ serializeDate: StringifyOptions['serializeDate'],
74
+ format: StringifyOptions['format'],
75
+ formatter: StringifyOptions['formatter'],
76
+ encodeValuesOnly: boolean,
77
+ charset: StringifyOptions['charset'],
78
+ sideChannel: WeakMap<any, any>,
79
+ ) {
80
+ let obj = object;
81
+
82
+ let tmp_sc = sideChannel;
83
+ let step = 0;
84
+ let find_flag = false;
85
+ while ((tmp_sc = tmp_sc.get(sentinel)) !== void undefined && !find_flag) {
86
+ // Where object last appeared in the ref tree
87
+ const pos = tmp_sc.get(object);
88
+ step += 1;
89
+ if (typeof pos !== 'undefined') {
90
+ if (pos === step) {
91
+ throw new RangeError('Cyclic object value');
92
+ } else {
93
+ find_flag = true; // Break while
94
+ }
95
+ }
96
+ if (typeof tmp_sc.get(sentinel) === 'undefined') {
97
+ step = 0;
98
+ }
99
+ }
100
+
101
+ if (typeof filter === 'function') {
102
+ obj = filter(prefix, obj);
103
+ } else if (obj instanceof Date) {
104
+ obj = serializeDate?.(obj);
105
+ } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
106
+ obj = maybe_map(obj, function (value) {
107
+ if (value instanceof Date) {
108
+ return serializeDate?.(value);
109
+ }
110
+ return value;
111
+ });
112
+ }
113
+
114
+ if (obj === null) {
115
+ if (strictNullHandling) {
116
+ return encoder && !encodeValuesOnly ?
117
+ // @ts-expect-error
118
+ encoder(prefix, defaults.encoder, charset, 'key', format)
119
+ : prefix;
120
+ }
121
+
122
+ obj = '';
123
+ }
124
+
125
+ if (is_non_nullish_primitive(obj) || is_buffer(obj)) {
126
+ if (encoder) {
127
+ const key_value =
128
+ encodeValuesOnly ? prefix
129
+ // @ts-expect-error
130
+ : encoder(prefix, defaults.encoder, charset, 'key', format);
131
+ return [
132
+ formatter?.(key_value) +
133
+ '=' +
134
+ // @ts-expect-error
135
+ formatter?.(encoder(obj, defaults.encoder, charset, 'value', format)),
136
+ ];
137
+ }
138
+ return [formatter?.(prefix) + '=' + formatter?.(String(obj))];
139
+ }
140
+
141
+ const values: string[] = [];
142
+
143
+ if (typeof obj === 'undefined') {
144
+ return values;
145
+ }
146
+
147
+ let obj_keys;
148
+ if (generateArrayPrefix === 'comma' && isArray(obj)) {
149
+ // we need to join elements in
150
+ if (encodeValuesOnly && encoder) {
151
+ // @ts-expect-error values only
152
+ obj = maybe_map(obj, encoder);
153
+ }
154
+ obj_keys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
155
+ } else if (isArray(filter)) {
156
+ obj_keys = filter;
157
+ } else {
158
+ const keys = Object.keys(obj);
159
+ obj_keys = sort ? keys.sort(sort) : keys;
160
+ }
161
+
162
+ const encoded_prefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix);
163
+
164
+ const adjusted_prefix =
165
+ commaRoundTrip && isArray(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix;
166
+
167
+ if (allowEmptyArrays && isArray(obj) && obj.length === 0) {
168
+ return adjusted_prefix + '[]';
169
+ }
170
+
171
+ for (let j = 0; j < obj_keys.length; ++j) {
172
+ const key = obj_keys[j];
173
+ const value =
174
+ // @ts-ignore
175
+ typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key as any];
176
+
177
+ if (skipNulls && value === null) {
178
+ continue;
179
+ }
180
+
181
+ // @ts-ignore
182
+ const encoded_key = allowDots && encodeDotInKeys ? (key as any).replace(/\./g, '%2E') : key;
183
+ const key_prefix =
184
+ isArray(obj) ?
185
+ typeof generateArrayPrefix === 'function' ?
186
+ generateArrayPrefix(adjusted_prefix, encoded_key)
187
+ : adjusted_prefix
188
+ : adjusted_prefix + (allowDots ? '.' + encoded_key : '[' + encoded_key + ']');
189
+
190
+ sideChannel.set(object, step);
191
+ const valueSideChannel = new WeakMap();
192
+ valueSideChannel.set(sentinel, sideChannel);
193
+ push_to_array(
194
+ values,
195
+ inner_stringify(
196
+ value,
197
+ key_prefix,
198
+ generateArrayPrefix,
199
+ commaRoundTrip,
200
+ allowEmptyArrays,
201
+ strictNullHandling,
202
+ skipNulls,
203
+ encodeDotInKeys,
204
+ // @ts-ignore
205
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
206
+ filter,
207
+ sort,
208
+ allowDots,
209
+ serializeDate,
210
+ format,
211
+ formatter,
212
+ encodeValuesOnly,
213
+ charset,
214
+ valueSideChannel,
215
+ ),
216
+ );
217
+ }
218
+
219
+ return values;
220
+ }
221
+
222
+ function normalize_stringify_options(
223
+ opts: StringifyOptions = defaults,
224
+ ): NonNullableProperties<Omit<StringifyOptions, 'indices'>> & { indices?: boolean } {
225
+ if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') {
226
+ throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided');
227
+ }
228
+
229
+ if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') {
230
+ throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided');
231
+ }
232
+
233
+ if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {
234
+ throw new TypeError('Encoder has to be a function.');
235
+ }
236
+
237
+ const charset = opts.charset || defaults.charset;
238
+ if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
239
+ throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
240
+ }
241
+
242
+ let format = default_format;
243
+ if (typeof opts.format !== 'undefined') {
244
+ if (!has(formatters, opts.format)) {
245
+ throw new TypeError('Unknown format option provided.');
246
+ }
247
+ format = opts.format;
248
+ }
249
+ const formatter = formatters[format];
250
+
251
+ let filter = defaults.filter;
252
+ if (typeof opts.filter === 'function' || isArray(opts.filter)) {
253
+ filter = opts.filter;
254
+ }
255
+
256
+ let arrayFormat: StringifyOptions['arrayFormat'];
257
+ if (opts.arrayFormat && opts.arrayFormat in array_prefix_generators) {
258
+ arrayFormat = opts.arrayFormat;
259
+ } else if ('indices' in opts) {
260
+ arrayFormat = opts.indices ? 'indices' : 'repeat';
261
+ } else {
262
+ arrayFormat = defaults.arrayFormat;
263
+ }
264
+
265
+ if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') {
266
+ throw new TypeError('`commaRoundTrip` must be a boolean, or absent');
267
+ }
268
+
269
+ const allowDots =
270
+ typeof opts.allowDots === 'undefined' ?
271
+ !!opts.encodeDotInKeys === true ?
272
+ true
273
+ : defaults.allowDots
274
+ : !!opts.allowDots;
275
+
276
+ return {
277
+ addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,
278
+ // @ts-ignore
279
+ allowDots: allowDots,
280
+ allowEmptyArrays:
281
+ typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays,
282
+ arrayFormat: arrayFormat,
283
+ charset: charset,
284
+ charsetSentinel:
285
+ typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
286
+ commaRoundTrip: !!opts.commaRoundTrip,
287
+ delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
288
+ encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
289
+ encodeDotInKeys:
290
+ typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys,
291
+ encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
292
+ encodeValuesOnly:
293
+ typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,
294
+ filter: filter,
295
+ format: format,
296
+ formatter: formatter,
297
+ serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,
298
+ skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
299
+ // @ts-ignore
300
+ sort: typeof opts.sort === 'function' ? opts.sort : null,
301
+ strictNullHandling:
302
+ typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling,
303
+ };
304
+ }
305
+
306
+ export function stringify(object: any, opts: StringifyOptions = {}) {
307
+ let obj = object;
308
+ const options = normalize_stringify_options(opts);
309
+
310
+ let obj_keys: PropertyKey[] | undefined;
311
+ let filter;
312
+
313
+ if (typeof options.filter === 'function') {
314
+ filter = options.filter;
315
+ obj = filter('', obj);
316
+ } else if (isArray(options.filter)) {
317
+ filter = options.filter;
318
+ obj_keys = filter;
319
+ }
320
+
321
+ const keys: string[] = [];
322
+
323
+ if (typeof obj !== 'object' || obj === null) {
324
+ return '';
325
+ }
326
+
327
+ const generateArrayPrefix = array_prefix_generators[options.arrayFormat];
328
+ const commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip;
329
+
330
+ if (!obj_keys) {
331
+ obj_keys = Object.keys(obj);
332
+ }
333
+
334
+ if (options.sort) {
335
+ obj_keys.sort(options.sort);
336
+ }
337
+
338
+ const sideChannel = new WeakMap();
339
+ for (let i = 0; i < obj_keys.length; ++i) {
340
+ const key = obj_keys[i]!;
341
+
342
+ if (options.skipNulls && obj[key] === null) {
343
+ continue;
344
+ }
345
+ push_to_array(
346
+ keys,
347
+ inner_stringify(
348
+ obj[key],
349
+ key,
350
+ // @ts-expect-error
351
+ generateArrayPrefix,
352
+ commaRoundTrip,
353
+ options.allowEmptyArrays,
354
+ options.strictNullHandling,
355
+ options.skipNulls,
356
+ options.encodeDotInKeys,
357
+ options.encode ? options.encoder : null,
358
+ options.filter,
359
+ options.sort,
360
+ options.allowDots,
361
+ options.serializeDate,
362
+ options.format,
363
+ options.formatter,
364
+ options.encodeValuesOnly,
365
+ options.charset,
366
+ sideChannel,
367
+ ),
368
+ );
369
+ }
370
+
371
+ const joined = keys.join(options.delimiter);
372
+ let prefix = options.addQueryPrefix === true ? '?' : '';
373
+
374
+ if (options.charsetSentinel) {
375
+ if (options.charset === 'iso-8859-1') {
376
+ // encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
377
+ prefix += 'utf8=%26%2310003%3B&';
378
+ } else {
379
+ // encodeURIComponent('✓')
380
+ prefix += 'utf8=%E2%9C%93&';
381
+ }
382
+ }
383
+
384
+ return joined.length > 0 ? prefix + joined : '';
385
+ }