@stackframe/stack-shared 2.8.34 → 2.8.36
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.
- package/CHANGELOG.md +12 -0
- package/dist/config/schema.d.mts +249 -236
- package/dist/config/schema.d.ts +249 -236
- package/dist/config/schema.js +76 -54
- package/dist/config/schema.js.map +1 -1
- package/dist/esm/config/schema.js +76 -54
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/helpers/vault/client-side.js +46 -0
- package/dist/esm/helpers/vault/client-side.js.map +1 -0
- package/dist/esm/helpers/vault/server-side.js +92 -0
- package/dist/esm/helpers/vault/server-side.js.map +1 -0
- package/dist/esm/hooks/use-hover.js +71 -0
- package/dist/esm/hooks/use-hover.js.map +1 -0
- package/dist/esm/interface/admin-interface.js +30 -8
- package/dist/esm/interface/admin-interface.js.map +1 -1
- package/dist/esm/interface/client-interface.js +18 -4
- package/dist/esm/interface/client-interface.js.map +1 -1
- package/dist/esm/interface/server-interface.js +57 -10
- package/dist/esm/interface/server-interface.js.map +1 -1
- package/dist/esm/known-errors.js +38 -1
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.js +22 -9
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/utils/bytes.js +1 -2
- package/dist/esm/utils/bytes.js.map +1 -1
- package/dist/esm/utils/crypto.js +83 -2
- package/dist/esm/utils/crypto.js.map +1 -1
- package/dist/esm/utils/currencies.js +0 -38
- package/dist/esm/utils/currencies.js.map +1 -1
- package/dist/esm/utils/currency-constants.js +42 -0
- package/dist/esm/utils/currency-constants.js.map +1 -0
- package/dist/esm/utils/dates.js +30 -0
- package/dist/esm/utils/dates.js.map +1 -1
- package/dist/esm/utils/numbers.js.map +1 -1
- package/dist/esm/utils/react.js +7 -3
- package/dist/esm/utils/react.js.map +1 -1
- package/dist/esm/utils/strings.js +3 -0
- package/dist/esm/utils/strings.js.map +1 -1
- package/dist/esm/utils/types.js.map +1 -1
- package/dist/helpers/password.d.mts +5 -5
- package/dist/helpers/password.d.ts +5 -5
- package/dist/helpers/vault/client-side.d.mts +14 -0
- package/dist/helpers/vault/client-side.d.ts +14 -0
- package/dist/helpers/vault/client-side.js +73 -0
- package/dist/helpers/vault/client-side.js.map +1 -0
- package/dist/helpers/vault/server-side.d.mts +7 -0
- package/dist/helpers/vault/server-side.d.ts +7 -0
- package/dist/helpers/vault/server-side.js +111 -0
- package/dist/helpers/vault/server-side.js.map +1 -0
- package/dist/hooks/use-hover.d.mts +6 -0
- package/dist/hooks/use-hover.d.ts +6 -0
- package/dist/hooks/use-hover.js +96 -0
- package/dist/hooks/use-hover.js.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/interface/admin-interface.d.mts +22 -9
- package/dist/interface/admin-interface.d.ts +22 -9
- package/dist/interface/admin-interface.js +30 -8
- package/dist/interface/admin-interface.js.map +1 -1
- package/dist/interface/client-interface.d.mts +11 -6
- package/dist/interface/client-interface.d.ts +11 -6
- package/dist/interface/client-interface.js +18 -4
- package/dist/interface/client-interface.js.map +1 -1
- package/dist/interface/crud/current-user.d.mts +1 -1
- package/dist/interface/crud/current-user.d.ts +1 -1
- package/dist/interface/crud/project-api-keys.d.mts +2 -2
- package/dist/interface/crud/project-api-keys.d.ts +2 -2
- package/dist/interface/crud/team-member-profiles.d.mts +2 -2
- package/dist/interface/crud/team-member-profiles.d.ts +2 -2
- package/dist/interface/crud/users.d.mts +4 -4
- package/dist/interface/crud/users.d.ts +4 -4
- package/dist/interface/server-interface.d.mts +13 -2
- package/dist/interface/server-interface.d.ts +13 -2
- package/dist/interface/server-interface.js +57 -10
- package/dist/interface/server-interface.js.map +1 -1
- package/dist/known-errors.d.mts +12 -3
- package/dist/known-errors.d.ts +12 -3
- package/dist/known-errors.js +38 -1
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.mts +23 -7
- package/dist/schema-fields.d.ts +23 -7
- package/dist/schema-fields.js +25 -11
- package/dist/schema-fields.js.map +1 -1
- package/dist/utils/bytes.js +1 -2
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/crypto.d.mts +31 -1
- package/dist/utils/crypto.d.ts +31 -1
- package/dist/utils/crypto.js +87 -2
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/currencies.d.mts +3 -36
- package/dist/utils/currencies.d.ts +3 -36
- package/dist/utils/currencies.js +0 -39
- package/dist/utils/currencies.js.map +1 -1
- package/dist/utils/currency-constants.d.mts +37 -0
- package/dist/utils/currency-constants.d.ts +37 -0
- package/dist/utils/currency-constants.js +67 -0
- package/dist/utils/currency-constants.js.map +1 -0
- package/dist/utils/dates.d.mts +3 -1
- package/dist/utils/dates.d.ts +3 -1
- package/dist/utils/dates.js +32 -0
- package/dist/utils/dates.js.map +1 -1
- package/dist/utils/numbers.js.map +1 -1
- package/dist/utils/react.js +7 -3
- package/dist/utils/react.js.map +1 -1
- package/dist/utils/strings.js +3 -0
- package/dist/utils/strings.js.map +1 -1
- package/dist/utils/types.d.mts +5 -4
- package/dist/utils/types.d.ts +5 -4
- package/dist/utils/types.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/crypto.tsx"],"sourcesContent":["import { encodeBase32 } from \"./bytes\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\n\nexport function generateRandomValues(array: Uint8Array): typeof array {\n if (!globalVar.crypto) {\n throw new StackAssertionError(\"Crypto API is not available in this environment. Are you using an old browser?\");\n }\n if (!globalVar.crypto.getRandomValues) {\n throw new StackAssertionError(\"crypto.getRandomValues is not available in this environment. Are you using an old browser?\");\n }\n return globalVar.crypto.getRandomValues(array);\n}\n\n/**\n * Generates a secure alphanumeric string using the system's cryptographically secure\n * random number generator.\n */\nexport function generateSecureRandomString(minBitsOfEntropy: number = 224) {\n const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);\n const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);\n const randomBytes = generateRandomValues(new Uint8Array(bytesCount));\n const str = encodeBase32(randomBytes);\n return str.slice(str.length - base32CharactersCount).toLowerCase();\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAEnB,SAAS,qBAAqB,OAAiC;AACpE,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,oBAAoB,gFAAgF;AAAA,EAChH;AACA,MAAI,CAAC,UAAU,OAAO,iBAAiB;AACrC,UAAM,IAAI,oBAAoB,4FAA4F;AAAA,EAC5H;AACA,SAAO,UAAU,OAAO,gBAAgB,KAAK;AAC/C;AAMO,SAAS,2BAA2B,mBAA2B,KAAK;AACzE,QAAM,wBAAwB,KAAK,KAAK,mBAAmB,CAAC;AAC5D,QAAM,aAAa,KAAK,KAAK,wBAAwB,IAAI,CAAC;AAC1D,QAAM,cAAc,qBAAqB,IAAI,WAAW,UAAU,CAAC;AACnE,QAAM,MAAM,aAAa,WAAW;AACpC,SAAO,IAAI,MAAM,IAAI,SAAS,qBAAqB,EAAE,YAAY;AACnE;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/crypto.tsx"],"sourcesContent":["import { encodeBase32, encodeBase64 } from \"./bytes\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\nimport { Result } from \"./results\";\n\nexport function generateRandomValues(array: Uint8Array): typeof array {\n if (!globalVar.crypto) {\n throw new StackAssertionError(\"Crypto API is not available in this environment. Are you using an old browser?\");\n }\n if (!globalVar.crypto.getRandomValues) {\n throw new StackAssertionError(\"crypto.getRandomValues is not available in this environment. Are you using an old browser?\");\n }\n return globalVar.crypto.getRandomValues(array);\n}\n\n/**\n * Generates a secure alphanumeric string using the system's cryptographically secure\n * random number generator.\n */\nexport function generateSecureRandomString(minBitsOfEntropy: number = 224) {\n const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);\n const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);\n const randomBytes = generateRandomValues(new Uint8Array(bytesCount));\n const str = encodeBase32(randomBytes);\n return str.slice(str.length - base32CharactersCount).toLowerCase();\n}\n\nasync function getDerivedSymmetricKey(purpose: string, secret: string | Uint8Array, salt: Uint8Array) {\n const originalSecretKey = await crypto.subtle.importKey(\"raw\", typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret, \"HKDF\", false, [\"deriveKey\"]);\n return await crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n salt,\n hash: \"SHA-256\",\n info: new TextEncoder().encode(JSON.stringify([\n \"stack-crypto-helper-derived-symmetric-key\",\n purpose,\n typeof secret === \"string\" ? \"string-key\" : \"binary-key\",\n encodeBase64(salt),\n ])),\n },\n originalSecretKey,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"]\n );\n}\n\nexport async function encrypt({ purpose, secret, value }: { purpose: string, secret: string | Uint8Array, value: Uint8Array }) {\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const salt = crypto.getRandomValues(new Uint8Array(16));\n const derivedSecretKey = await getDerivedSymmetricKey(purpose, secret, salt);\n\n const cipher = await crypto.subtle.encrypt({\n name: \"AES-GCM\",\n iv,\n }, derivedSecretKey, value);\n\n const version = [0x01, 0x00];\n return new Uint8Array([...version, ...salt, ...iv, ...new Uint8Array(cipher)]);\n}\n\nexport async function decrypt({ purpose, secret, cipher }: { purpose: string, secret: string | Uint8Array, cipher: Uint8Array }) {\n const version = cipher.slice(0, 2);\n if (version[0] !== 0x01 || version[1] !== 0x00) throw new StackAssertionError(\"Invalid ciphertext version in decrypt(...); expected 0x0100\", { purpose });\n const salt = cipher.slice(2, 18);\n const iv = cipher.slice(18, 30);\n const cipherBytes = cipher.slice(30);\n const derivedSecretKey = await getDerivedSymmetricKey(purpose, secret, salt);\n\n try {\n const plaintext = await crypto.subtle.decrypt({\n name: \"AES-GCM\",\n iv,\n }, derivedSecretKey, cipherBytes);\n return Result.ok(new Uint8Array(plaintext));\n } catch (e) {\n if (e instanceof DOMException && e.name === \"OperationError\") {\n return Result.error(new Error(\"Invalid ciphertext or secret when decrypting encrypted value\", { cause: e }));\n }\n throw e;\n }\n}\n\nundefined?.test(\"encrypt & decrypt\", async ({ expect }) => {\n const encryptAndDecrypt = async (encryptPurpose: string, decryptPurpose: string, encryptSecret: string | Uint8Array, decryptSecret: string | Uint8Array, value: Uint8Array) => {\n const encrypted = await encrypt({ purpose: encryptPurpose, secret: encryptSecret, value });\n const decrypted = await decrypt({ purpose: decryptPurpose, secret: decryptSecret, cipher: encrypted });\n return decrypted;\n };\n\n const exampleBytes = new TextEncoder().encode(\"hello\");\n\n const exampleKey1 = crypto.getRandomValues(new Uint8Array(32));\n const exampleKey2 = crypto.getRandomValues(new Uint8Array(32));\n\n expect(await encryptAndDecrypt(\"p\", \"p\", \"secret\", \"secret\", exampleBytes)).toEqual(Result.ok(exampleBytes));\n expect(await encryptAndDecrypt(\"p\", \"p\", exampleKey1, exampleKey1, exampleBytes)).toEqual(Result.ok(exampleBytes));\n expect(await encryptAndDecrypt(\"p\", \"p\", exampleKey1, \"secret\", exampleBytes)).toEqual(Result.error(expect.objectContaining({ message: \"Invalid ciphertext or secret when decrypting encrypted value\" })));\n expect(await encryptAndDecrypt(\"p\", \"p\", exampleKey1, exampleKey2, exampleBytes)).toEqual(Result.error(expect.objectContaining({ message: \"Invalid ciphertext or secret when decrypting encrypted value\" })));\n expect(await encryptAndDecrypt(\"p\", \"not-p\", exampleKey1, exampleKey1, exampleBytes)).toEqual(Result.error(expect.objectContaining({ message: \"Invalid ciphertext or secret when decrypting encrypted value\" })));\n});\n\nexport type HashOptions = {\n purpose: string,\n salt?: string | Uint8Array,\n extra?: string | Uint8Array,\n value: string | Uint8Array,\n};\n\nexport async function hash(options: HashOptions) {\n return await iteratedHash({ ...options, iterations: 1 });\n}\n\nexport async function iteratedHash(options: HashOptions & { iterations: number }) {\n const stringOrUint8ArrayToUint8Array = (value: string | Uint8Array) => typeof value === \"string\" ? new TextEncoder().encode(value) : value;\n const stringOrUint8ArrayToBase64 = (value: string | Uint8Array) => encodeBase64(stringOrUint8ArrayToUint8Array(value));\n const input = await crypto.subtle.importKey(\n \"raw\",\n stringOrUint8ArrayToUint8Array(options.value),\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n return new Uint8Array(await crypto.subtle.deriveBits({\n name: \"PBKDF2\",\n salt: new TextEncoder().encode(JSON.stringify([\n \"stack-crypto-helper-iterated-hash\",\n options.purpose,\n stringOrUint8ArrayToBase64(options.salt ?? \"\"),\n stringOrUint8ArrayToBase64(options.extra ?? \"\"),\n ])),\n iterations: options.iterations,\n hash: \"SHA-256\",\n }, input, 256));\n}\n\nundefined?.test(\"iteratedHash\", async ({ expect }) => {\n const valueBytes = new TextEncoder().encode(\"hello\");\n const incrementBytes = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]);\n\n const hash = await iteratedHash({ purpose: \"purpose\", value: valueBytes, iterations: 100_000 });\n const hash2 = await iteratedHash({ purpose: \"purpose\", value: valueBytes, iterations: 100_000 });\n const hashWithDifferentPurpose = await iteratedHash({ purpose: \"different-purpose\", value: valueBytes, iterations: 100_000 });\n const hashWithEmptySalt = await iteratedHash({ purpose: \"purpose\", value: valueBytes, salt: new Uint8Array(0), iterations: 100_000 });\n const hashWithDifferentSalt = await iteratedHash({ purpose: \"purpose\", value: valueBytes, salt: incrementBytes, iterations: 100_000 });\n const hashWithEmptyExtra = await iteratedHash({ purpose: \"purpose\", value: valueBytes, extra: new Uint8Array(0), iterations: 100_000 });\n const hashWithDifferentExtra = await iteratedHash({ purpose: \"purpose\", value: valueBytes, extra: incrementBytes, iterations: 100_000 });\n const hashWithDifferentValue = await iteratedHash({ purpose: \"purpose\", value: new TextEncoder().encode(\"hello2\"), iterations: 100_000 });\n const hashWithDifferentSaltAndExtra = await iteratedHash({ purpose: \"purpose\", value: valueBytes, salt: incrementBytes, extra: incrementBytes, iterations: 100_000 });\n const hashWithDifferentIterations = await iteratedHash({ purpose: \"purpose\", value: valueBytes, iterations: 100_001 });\n\n\n expect(hash).toEqual(hash2);\n expect(hash).not.toEqual(hashWithDifferentPurpose);\n expect(hash).toEqual(hashWithEmptySalt);\n expect(hash).not.toEqual(hashWithDifferentSalt);\n expect(hash).toEqual(hashWithEmptyExtra);\n expect(hash).not.toEqual(hashWithDifferentExtra);\n expect(hash).not.toEqual(hashWithDifferentValue);\n expect(hash).not.toEqual(hashWithDifferentIterations);\n\n expect(hashWithDifferentSalt).not.toEqual(hashWithDifferentExtra);\n expect(hashWithDifferentSalt).not.toEqual(hashWithDifferentSaltAndExtra);\n expect(hashWithDifferentExtra).not.toEqual(hashWithDifferentSaltAndExtra);\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,43 +1,6 @@
|
|
|
1
1
|
// src/utils/currencies.tsx
|
|
2
2
|
import { moneyAmountSchema } from "../schema-fields.js";
|
|
3
3
|
import { StackAssertionError } from "./errors.js";
|
|
4
|
-
var SUPPORTED_CURRENCIES = [
|
|
5
|
-
{
|
|
6
|
-
code: "USD",
|
|
7
|
-
decimals: 2,
|
|
8
|
-
stripeDecimals: 2
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
code: "EUR",
|
|
12
|
-
decimals: 2,
|
|
13
|
-
stripeDecimals: 2
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
code: "GBP",
|
|
17
|
-
decimals: 2,
|
|
18
|
-
stripeDecimals: 2
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
code: "JPY",
|
|
22
|
-
decimals: 0,
|
|
23
|
-
stripeDecimals: 0
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
code: "INR",
|
|
27
|
-
decimals: 2,
|
|
28
|
-
stripeDecimals: 2
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
code: "AUD",
|
|
32
|
-
decimals: 2,
|
|
33
|
-
stripeDecimals: 2
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
code: "CAD",
|
|
37
|
-
decimals: 2,
|
|
38
|
-
stripeDecimals: 2
|
|
39
|
-
}
|
|
40
|
-
];
|
|
41
4
|
function moneyAmountToStripeUnits(amount, currency) {
|
|
42
5
|
const validated = moneyAmountSchema(currency).defined().validateSync(amount);
|
|
43
6
|
if (currency.stripeDecimals !== currency.decimals) {
|
|
@@ -46,7 +9,6 @@ function moneyAmountToStripeUnits(amount, currency) {
|
|
|
46
9
|
return Number.parseInt(validated.replace(".", ""), 10);
|
|
47
10
|
}
|
|
48
11
|
export {
|
|
49
|
-
SUPPORTED_CURRENCIES,
|
|
50
12
|
moneyAmountToStripeUnits
|
|
51
13
|
};
|
|
52
14
|
//# sourceMappingURL=currencies.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/currencies.tsx"],"sourcesContent":["import { moneyAmountSchema } from \"../schema-fields\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/currencies.tsx"],"sourcesContent":["import { moneyAmountSchema } from \"../schema-fields\";\nimport { SUPPORTED_CURRENCIES, type Currency, type MoneyAmount } from \"./currency-constants\";\nimport { StackAssertionError } from \"./errors\";\n\nexport type SupportedCurrency = (typeof SUPPORTED_CURRENCIES)[number];\n\nexport function moneyAmountToStripeUnits(amount: MoneyAmount, currency: Currency): number {\n const validated = moneyAmountSchema(currency).defined().validateSync(amount);\n if (currency.stripeDecimals !== currency.decimals) {\n throw new StackAssertionError(\"unimplemented: TODO support different decimal configurations\");\n }\n\n return Number.parseInt(validated.replace('.', ''), 10);\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAElC,SAAS,2BAA2B;AAI7B,SAAS,yBAAyB,QAAqB,UAA4B;AACxF,QAAM,YAAY,kBAAkB,QAAQ,EAAE,QAAQ,EAAE,aAAa,MAAM;AAC3E,MAAI,SAAS,mBAAmB,SAAS,UAAU;AACjD,UAAM,IAAI,oBAAoB,8DAA8D;AAAA,EAC9F;AAEA,SAAO,OAAO,SAAS,UAAU,QAAQ,KAAK,EAAE,GAAG,EAAE;AACvD;","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/utils/currency-constants.tsx
|
|
2
|
+
var SUPPORTED_CURRENCIES = [
|
|
3
|
+
{
|
|
4
|
+
code: "USD",
|
|
5
|
+
decimals: 2,
|
|
6
|
+
stripeDecimals: 2
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
code: "EUR",
|
|
10
|
+
decimals: 2,
|
|
11
|
+
stripeDecimals: 2
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: "GBP",
|
|
15
|
+
decimals: 2,
|
|
16
|
+
stripeDecimals: 2
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
code: "JPY",
|
|
20
|
+
decimals: 0,
|
|
21
|
+
stripeDecimals: 0
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: "INR",
|
|
25
|
+
decimals: 2,
|
|
26
|
+
stripeDecimals: 2
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: "AUD",
|
|
30
|
+
decimals: 2,
|
|
31
|
+
stripeDecimals: 2
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: "CAD",
|
|
35
|
+
decimals: 2,
|
|
36
|
+
stripeDecimals: 2
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
export {
|
|
40
|
+
SUPPORTED_CURRENCIES
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=currency-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/currency-constants.tsx"],"sourcesContent":["export type MoneyAmount = `${number}` | `${number}.${number}`;\n\nexport type Currency = {\n code: Uppercase<string>,\n decimals: number,\n stripeDecimals: number,\n};\n\nexport const SUPPORTED_CURRENCIES = [\n {\n code: 'USD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'EUR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'GBP',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'JPY',\n decimals: 0,\n stripeDecimals: 0,\n },\n {\n code: 'INR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'AUD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'CAD',\n decimals: 2,\n stripeDecimals: 2,\n },\n] as const satisfies Currency[];\n"],"mappings":";AAQO,IAAM,uBAAuB;AAAA,EAClC;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AACF;","names":[]}
|
package/dist/esm/utils/dates.js
CHANGED
|
@@ -107,11 +107,41 @@ function subtractInterval(date, interval) {
|
|
|
107
107
|
function addInterval(date, interval) {
|
|
108
108
|
return applyInterval(date, 1, interval);
|
|
109
109
|
}
|
|
110
|
+
var FAR_FUTURE_DATE = /* @__PURE__ */ new Date(864e13);
|
|
111
|
+
function getMsPerDayIntervalUnit(unit) {
|
|
112
|
+
if (unit === "day") {
|
|
113
|
+
return 24 * 60 * 60 * 1e3;
|
|
114
|
+
}
|
|
115
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
116
|
+
}
|
|
117
|
+
function getIntervalsElapsed(anchor, to, repeat) {
|
|
118
|
+
const [amount, unit] = repeat;
|
|
119
|
+
if (to <= anchor) return 0;
|
|
120
|
+
if (unit === "day" || unit === "week") {
|
|
121
|
+
const msPerUnit = getMsPerDayIntervalUnit(unit);
|
|
122
|
+
const diffMs = to.getTime() - anchor.getTime();
|
|
123
|
+
return Math.floor(diffMs / (msPerUnit * amount));
|
|
124
|
+
}
|
|
125
|
+
if (["month", "year"].includes(unit)) {
|
|
126
|
+
let count = 0;
|
|
127
|
+
let current = new Date(anchor);
|
|
128
|
+
for (; ; ) {
|
|
129
|
+
const next = addInterval(new Date(current), [amount, unit]);
|
|
130
|
+
if (next > to) break;
|
|
131
|
+
current = next;
|
|
132
|
+
count += 1;
|
|
133
|
+
}
|
|
134
|
+
return count;
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
110
138
|
export {
|
|
139
|
+
FAR_FUTURE_DATE,
|
|
111
140
|
addInterval,
|
|
112
141
|
fromNow,
|
|
113
142
|
fromNowDetailed,
|
|
114
143
|
getInputDatetimeLocalString,
|
|
144
|
+
getIntervalsElapsed,
|
|
115
145
|
isWeekend,
|
|
116
146
|
subtractInterval
|
|
117
147
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { StackAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nundefined?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nundefined?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n undefined?.vi.useFakeTimers();\n undefined?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nundefined?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n undefined?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(date: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new StackAssertionError(`Invalid interval`, { interval });\n }\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new StackAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { StackAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nundefined?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nundefined?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n undefined?.vi.useFakeTimers();\n undefined?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nundefined?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n undefined?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(date: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new StackAssertionError(`Invalid interval`, { interval });\n }\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new StackAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n\nexport const FAR_FUTURE_DATE = new Date(8640000000000000); // 13 Sep 275760 00:00:00 UTC\n\nfunction getMsPerDayIntervalUnit(unit: 'day' | 'week'): number {\n if (unit === 'day') {\n return 24 * 60 * 60 * 1000;\n }\n return 7 * 24 * 60 * 60 * 1000;\n}\n\n\nexport function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number {\n const [amount, unit] = repeat;\n if (to <= anchor) return 0;\n if (unit === 'day' || unit === 'week') {\n const msPerUnit = getMsPerDayIntervalUnit(unit);\n const diffMs = to.getTime() - anchor.getTime();\n return Math.floor(diffMs / (msPerUnit * amount));\n }\n if ([\"month\", \"year\"].includes(unit)) {\n let count = 0;\n let current = new Date(anchor);\n for (; ;) {\n const next = addInterval(new Date(current), [amount, unit]);\n if (next > to) break;\n current = next;\n count += 1;\n }\n return count;\n }\n return 0;\n}\n\nundefined?.test(\"getIntervalsElapsed\", ({ expect }) => {\n const anchor = new Date('2025-01-01T00:00:00.000Z');\n const to = new Date('2025-01-15T00:00:00.000Z');\n expect(getIntervalsElapsed(anchor, to, [1, 'week'])).toBe(2);\n expect(getIntervalsElapsed(anchor, to, [3, 'day'])).toBe(4);\n\n const mAnchor = new Date('2023-01-31T00:00:00.000Z');\n const mTo = new Date('2023-03-01T00:00:00.000Z');\n expect(getIntervalsElapsed(mAnchor, mTo, [1, 'month'])).toBe(0);\n\n const yAnchor = new Date('2020-01-01T00:00:00.000Z');\n const yTo = new Date('2022-06-01T00:00:00.000Z');\n expect(getIntervalsElapsed(yAnchor, yTo, [1, 'year'])).toBe(2);\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/numbers.tsx"],"sourcesContent":["const magnitudes = [\n [1_000_000_000_000_000, \"trln\"],\n [1_000_000_000_000, \"bln\"],\n [1_000_000_000, \"bn\"],\n [1_000_000, \"M\"],\n [1_000, \"k\"],\n] as const;\n\nexport function prettyPrintWithMagnitudes(num: number): string {\n if (typeof num !== \"number\") throw new Error(\"Expected a number\");\n if (Number.isNaN(num)) return \"NaN\";\n if (num < 0) return \"-\" + prettyPrintWithMagnitudes(-num);\n if (!Number.isFinite(num)) return \"∞\";\n\n for (const [magnitude, suffix] of magnitudes) {\n if (num >= magnitude) {\n return toFixedMax(num / magnitude, 1) + suffix;\n }\n }\n return toFixedMax(num, 1); // Handle numbers less than 1,000 without suffix.\n}\nundefined?.test(\"prettyPrintWithMagnitudes\", ({ expect }) => {\n // Test different magnitudes\n expect(prettyPrintWithMagnitudes(1000)).toBe(\"1k\");\n expect(prettyPrintWithMagnitudes(1500)).toBe(\"1.5k\");\n expect(prettyPrintWithMagnitudes(1000000)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1500000)).toBe(\"1.5M\");\n expect(prettyPrintWithMagnitudes(1000000000)).toBe(\"1bn\");\n expect(prettyPrintWithMagnitudes(1500000000)).toBe(\"1.5bn\");\n expect(prettyPrintWithMagnitudes(1000000000000)).toBe(\"1bln\");\n expect(prettyPrintWithMagnitudes(1500000000000)).toBe(\"1.5bln\");\n expect(prettyPrintWithMagnitudes(1000000000000000)).toBe(\"1trln\");\n expect(prettyPrintWithMagnitudes(1500000000000000)).toBe(\"1.5trln\");\n // Test small numbers\n expect(prettyPrintWithMagnitudes(100)).toBe(\"100\");\n expect(prettyPrintWithMagnitudes(0)).toBe(\"0\");\n expect(prettyPrintWithMagnitudes(0.5)).toBe(\"0.5\");\n // Test negative numbers\n expect(prettyPrintWithMagnitudes(-1000)).toBe(\"-1k\");\n expect(prettyPrintWithMagnitudes(-1500000)).toBe(\"-1.5M\");\n // Test special cases\n expect(prettyPrintWithMagnitudes(NaN)).toBe(\"NaN\");\n expect(prettyPrintWithMagnitudes(Infinity)).toBe(\"∞\");\n expect(prettyPrintWithMagnitudes(-Infinity)).toBe(\"-∞\");\n});\n\nexport function toFixedMax(num: number, maxDecimals: number): string {\n return num.toFixed(maxDecimals).replace(/\\.?0+$/, \"\");\n}\nundefined?.test(\"toFixedMax\", ({ expect }) => {\n expect(toFixedMax(1, 2)).toBe(\"1\");\n expect(toFixedMax(1.2, 2)).toBe(\"1.2\");\n expect(toFixedMax(1.23, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.234, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.0, 2)).toBe(\"1\");\n expect(toFixedMax(1.20, 2)).toBe(\"1.2\");\n expect(toFixedMax(0, 2)).toBe(\"0\");\n});\n\nexport function numberCompare(a: number, b: number): number {\n return Math.sign(a - b);\n}\nundefined?.test(\"numberCompare\", ({ expect }) => {\n expect(numberCompare(1, 2)).toBe(-1);\n expect(numberCompare(2, 1)).toBe(1);\n expect(numberCompare(1, 1)).toBe(0);\n expect(numberCompare(0, 0)).toBe(0);\n expect(numberCompare(-1, -2)).toBe(1);\n expect(numberCompare(-2, -1)).toBe(-1);\n expect(numberCompare(-1, 1)).toBe(-1);\n expect(numberCompare(1, -1)).toBe(1);\n});\n"],"mappings":";AAAA,IAAM,aAAa;AAAA,EACjB,CAAC,MAAuB,MAAM;AAAA,EAC9B,CAAC,MAAmB,KAAK;AAAA,EACzB,CAAC,KAAe,IAAI;AAAA,EACpB,CAAC,KAAW,GAAG;AAAA,EACf,CAAC,KAAO,GAAG;AACb;AAEO,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAChE,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,MAAM,EAAG,QAAO,MAAM,0BAA0B,CAAC,GAAG;AACxD,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAElC,aAAW,CAAC,WAAW,MAAM,KAAK,YAAY;AAC5C,QAAI,OAAO,WAAW;AACpB,aAAO,WAAW,MAAM,WAAW,CAAC,IAAI;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,WAAW,KAAK,CAAC;AAC1B;
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/numbers.tsx"],"sourcesContent":["const magnitudes = [\n [1_000_000_000_000_000, \"trln\"],\n [1_000_000_000_000, \"bln\"],\n [1_000_000_000, \"bn\"],\n [1_000_000, \"M\"],\n [1_000, \"k\"],\n] as const;\n\nexport function prettyPrintWithMagnitudes(num: number): string {\n if (typeof num !== \"number\") throw new Error(\"Expected a number\");\n if (Number.isNaN(num)) return \"NaN\";\n if (num < 0) return \"-\" + prettyPrintWithMagnitudes(-num);\n if (!Number.isFinite(num)) return \"∞\";\n\n for (const [magnitude, suffix] of magnitudes) {\n if (num >= magnitude) {\n return toFixedMax(num / magnitude, 1) + suffix;\n }\n }\n return toFixedMax(num, 1); // Handle numbers less than 1,000 without suffix.\n}\nundefined?.test(\"prettyPrintWithMagnitudes\", ({ expect }) => {\n // Test different magnitudes\n expect(prettyPrintWithMagnitudes(999)).toBe(\"999\");\n expect(prettyPrintWithMagnitudes(1000)).toBe(\"1k\");\n expect(prettyPrintWithMagnitudes(1500)).toBe(\"1.5k\");\n expect(prettyPrintWithMagnitudes(1000000)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1500000)).toBe(\"1.5M\");\n expect(prettyPrintWithMagnitudes(1000000000)).toBe(\"1bn\");\n expect(prettyPrintWithMagnitudes(1500000000)).toBe(\"1.5bn\");\n expect(prettyPrintWithMagnitudes(1000000000000)).toBe(\"1bln\");\n expect(prettyPrintWithMagnitudes(1500000000000)).toBe(\"1.5bln\");\n expect(prettyPrintWithMagnitudes(1000000000000000)).toBe(\"1trln\");\n expect(prettyPrintWithMagnitudes(1500000000000000)).toBe(\"1.5trln\");\n // Test small numbers\n expect(prettyPrintWithMagnitudes(100)).toBe(\"100\");\n expect(prettyPrintWithMagnitudes(0)).toBe(\"0\");\n expect(prettyPrintWithMagnitudes(0.5)).toBe(\"0.5\");\n // Test negative numbers\n expect(prettyPrintWithMagnitudes(-1000)).toBe(\"-1k\");\n expect(prettyPrintWithMagnitudes(-1500000)).toBe(\"-1.5M\");\n // Test special cases\n expect(prettyPrintWithMagnitudes(NaN)).toBe(\"NaN\");\n expect(prettyPrintWithMagnitudes(Infinity)).toBe(\"∞\");\n expect(prettyPrintWithMagnitudes(-Infinity)).toBe(\"-∞\");\n});\n\nexport function toFixedMax(num: number, maxDecimals: number): string {\n return num.toFixed(maxDecimals).replace(/\\.?0+$/, \"\");\n}\nundefined?.test(\"toFixedMax\", ({ expect }) => {\n expect(toFixedMax(1, 2)).toBe(\"1\");\n expect(toFixedMax(1.2, 2)).toBe(\"1.2\");\n expect(toFixedMax(1.23, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.234, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.0, 2)).toBe(\"1\");\n expect(toFixedMax(1.20, 2)).toBe(\"1.2\");\n expect(toFixedMax(0, 2)).toBe(\"0\");\n});\n\nexport function numberCompare(a: number, b: number): number {\n return Math.sign(a - b);\n}\nundefined?.test(\"numberCompare\", ({ expect }) => {\n expect(numberCompare(1, 2)).toBe(-1);\n expect(numberCompare(2, 1)).toBe(1);\n expect(numberCompare(1, 1)).toBe(0);\n expect(numberCompare(0, 0)).toBe(0);\n expect(numberCompare(-1, -2)).toBe(1);\n expect(numberCompare(-2, -1)).toBe(-1);\n expect(numberCompare(-1, 1)).toBe(-1);\n expect(numberCompare(1, -1)).toBe(1);\n});\n"],"mappings":";AAAA,IAAM,aAAa;AAAA,EACjB,CAAC,MAAuB,MAAM;AAAA,EAC9B,CAAC,MAAmB,KAAK;AAAA,EACzB,CAAC,KAAe,IAAI;AAAA,EACpB,CAAC,KAAW,GAAG;AAAA,EACf,CAAC,KAAO,GAAG;AACb;AAEO,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAChE,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,MAAM,EAAG,QAAO,MAAM,0BAA0B,CAAC,GAAG;AACxD,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAElC,aAAW,CAAC,WAAW,MAAM,KAAK,YAAY;AAC5C,QAAI,OAAO,WAAW;AACpB,aAAO,WAAW,MAAM,WAAW,CAAC,IAAI;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,WAAW,KAAK,CAAC;AAC1B;AA2BO,SAAS,WAAW,KAAa,aAA6B;AACnE,SAAO,IAAI,QAAQ,WAAW,EAAE,QAAQ,UAAU,EAAE;AACtD;AAWO,SAAS,cAAc,GAAW,GAAmB;AAC1D,SAAO,KAAK,KAAK,IAAI,CAAC;AACxB;","names":[]}
|
package/dist/esm/utils/react.js
CHANGED
|
@@ -53,13 +53,17 @@ function useRefState(initialValue) {
|
|
|
53
53
|
const ref = React.useRef(initialValue);
|
|
54
54
|
const setValue = React.useCallback((updater) => {
|
|
55
55
|
const value = typeof updater === "function" ? updater(ref.current) : updater;
|
|
56
|
-
|
|
56
|
+
console.log("setValue", ref.current);
|
|
57
57
|
ref.current = value;
|
|
58
|
+
console.log("setValue", ref.current);
|
|
59
|
+
setState(value);
|
|
58
60
|
}, []);
|
|
59
61
|
const res = React.useMemo(() => ({
|
|
60
|
-
current
|
|
62
|
+
get current() {
|
|
63
|
+
return ref.current;
|
|
64
|
+
},
|
|
61
65
|
set: setValue
|
|
62
|
-
}), [
|
|
66
|
+
}), [setValue]);
|
|
63
67
|
return res;
|
|
64
68
|
}
|
|
65
69
|
function mapRefState(refState, mapper, reverseMapper) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/react.tsx"],"sourcesContent":["import React, { SetStateAction } from \"react\";\nimport { isBrowserLike } from \"./env\";\nimport { neverResolve } from \"./promises\";\nimport { deindent } from \"./strings\";\n\nexport function componentWrapper<\n C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements,\n ExtraProps extends {} = {}\n>(displayName: string, render: React.ForwardRefRenderFunction<RefFromComponent<C>, React.ComponentPropsWithRef<C> & ExtraProps>) {\n const Component = forwardRefIfNeeded(render);\n Component.displayName = displayName;\n return Component;\n}\ntype RefFromComponent<C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements> = NonNullable<RefFromComponentDistCond<React.ComponentPropsWithRef<C>[\"ref\"]>>;\ntype RefFromComponentDistCond<A> = A extends React.RefObject<infer T> ? T : never; // distributive conditional type; see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types\n\nexport function forwardRefIfNeeded<T, P = {}>(render: React.ForwardRefRenderFunction<T, P>): React.FC<P & { ref?: React.Ref<T> }> {\n // TODO: when we drop support for react 18, remove this\n\n const version = React.version;\n const major = parseInt(version.split(\".\")[0]);\n if (major < 19) {\n return React.forwardRef<T, P>(render as any) as any;\n } else {\n return ((props: P) => render(props, (props as any).ref)) as any;\n }\n}\n\nexport function getNodeText(node: React.ReactNode): string {\n if ([\"number\", \"string\"].includes(typeof node)) {\n return `${node}`;\n }\n if (!node) {\n return \"\";\n }\n if (Array.isArray(node)) {\n return node.map(getNodeText).join(\"\");\n }\n if (typeof node === \"object\" && \"props\" in node) {\n return getNodeText(node.props.children);\n }\n throw new Error(`Unknown node type: ${typeof node}`);\n}\nundefined?.test(\"getNodeText\", ({ expect }) => {\n // Test with string\n expect(getNodeText(\"hello\")).toBe(\"hello\");\n\n // Test with number\n expect(getNodeText(42)).toBe(\"42\");\n\n // Test with null/undefined\n expect(getNodeText(null)).toBe(\"\");\n expect(getNodeText(undefined)).toBe(\"\");\n\n // Test with array\n expect(getNodeText([\"hello\", \" \", \"world\"])).toBe(\"hello world\");\n expect(getNodeText([1, 2, 3])).toBe(\"123\");\n\n // Test with mixed array\n expect(getNodeText([\"hello\", 42, null])).toBe(\"hello42\");\n\n // Test with React element (mocked)\n const mockElement = {\n props: {\n children: \"child text\"\n }\n } as React.ReactElement;\n expect(getNodeText(mockElement)).toBe(\"child text\");\n\n // Test with nested React elements\n const nestedElement = {\n props: {\n children: {\n props: {\n children: \"nested text\"\n }\n } as React.ReactElement\n }\n } as React.ReactElement;\n expect(getNodeText(nestedElement)).toBe(\"nested text\");\n\n // Test with array of React elements\n const arrayOfElements = [\n { props: { children: \"first\" } } as React.ReactElement,\n { props: { children: \"second\" } } as React.ReactElement\n ];\n expect(getNodeText(arrayOfElements)).toBe(\"firstsecond\");\n});\n\n/**\n * Suspends the currently rendered component indefinitely. Will not unsuspend unless the component rerenders.\n *\n * You can use this to translate older query- or AsyncResult-based code to new the Suspense system, for example: `if (query.isLoading) suspend();`\n */\nexport function suspend(): never {\n React.use(neverResolve());\n throw new Error(\"Somehow a Promise that never resolves was resolved?\");\n}\n\nexport function mapRef<T, R>(ref: ReadonlyRef<T>, mapper: (value: T) => R): ReadonlyRef<R> {\n let last: [T, R] | null = null;\n return {\n get current() {\n const input = ref.current;\n if (last === null || input !== last[0]) {\n last = [input, mapper(input)];\n }\n return last[1];\n },\n };\n}\n\nexport type ReadonlyRef<T> = {\n readonly current: T,\n};\n\nexport type RefState<T> = ReadonlyRef<T> & {\n set: (updater: SetStateAction<T>) => void,\n};\n\n/**\n * Like useState, but its value is immediately available on refState.current after being set.\n *\n * Like useRef, but setting the value will cause a rerender.\n *\n * Note that useRefState returns a new object every time a rerender happens due to a value change, which is intentional\n * as it allows you to specify it in a dependency array like this:\n *\n * ```tsx\n * useEffect(() => {\n * // do something with refState.current\n * }, [refState]); // instead of refState.current\n * ```\n *\n * If you don't want this, you can wrap the result in a useMemo call.\n */\nexport function useRefState<T>(initialValue: T): RefState<T> {\n const [, setState] = React.useState(initialValue);\n const ref = React.useRef(initialValue);\n const setValue = React.useCallback((updater: SetStateAction<T>) => {\n const value: T = typeof updater === \"function\" ? (updater as any)(ref.current) : updater;\n setState(value);\n ref.current = value;\n }, []);\n const res = React.useMemo(() => ({\n current: ref.current,\n set: setValue,\n }), [ref.current, setValue]);\n return res;\n}\n\nexport function mapRefState<T, R>(refState: RefState<T>, mapper: (value: T) => R, reverseMapper: (oldT: T, newR: R) => T): RefState<R> {\n let last: [T, R] | null = null;\n return {\n get current() {\n const input = refState.current;\n if (last === null || input !== last[0]) {\n last = [input, mapper(input)];\n }\n return last[1];\n },\n set(updater: SetStateAction<R>) {\n const value: R = typeof updater === \"function\" ? (updater as any)(this.current) : updater;\n refState.set(reverseMapper(refState.current, value));\n },\n };\n}\n\nexport class NoSuspenseBoundaryError extends Error {\n digest: string;\n reason: string;\n\n constructor(options: { caller?: string }) {\n super(deindent`\n ${options.caller ?? \"This code path\"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.\n \n The fix depends on which of the 3 scenarios caused it:\n \n 1. You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.\n\n 2. The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:\n\n - app\n - - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? \"this code path\"}\n - - loading.tsx // required for suspense\n - - (main)\n - - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? \"this code path\"}\n - - - route.tsx // your actual main page\n - - - the rest of your app\n\n For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups\n \n 3. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.\n\n See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n\n More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content\n `);\n\n this.name = \"NoSuspenseBoundaryError\";\n this.reason = options.caller ?? \"suspendIfSsr()\";\n\n // set the digest so nextjs doesn't log the error\n // https://github.com/vercel/next.js/blob/d01d6d9c35a8c2725b3d74c1402ab76d4779a6cf/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts#L14\n this.digest = \"BAILOUT_TO_CLIENT_SIDE_RENDERING\";\n }\n}\nundefined?.test(\"NoSuspenseBoundaryError\", ({ expect }) => {\n // Test with default options\n const defaultError = new NoSuspenseBoundaryError({});\n expect(defaultError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(defaultError.reason).toBe(\"suspendIfSsr()\");\n expect(defaultError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(defaultError.message).toContain(\"This code path attempted to display a loading indicator\");\n\n // Test with custom caller\n const customError = new NoSuspenseBoundaryError({ caller: \"CustomComponent\" });\n expect(customError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(customError.reason).toBe(\"CustomComponent\");\n expect(customError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(customError.message).toContain(\"CustomComponent attempted to display a loading indicator\");\n\n // Verify error message contains all the necessary information\n expect(customError.message).toContain(\"loading.tsx\");\n expect(customError.message).toContain(\"route groups\");\n expect(customError.message).toContain(\"https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\");\n});\n\n\n/**\n * Use this in a component or a hook to disable SSR. Should be wrapped in a Suspense boundary, or it will throw an error.\n */\nexport function suspendIfSsr(caller?: string) {\n if (!isBrowserLike()) {\n throw new NoSuspenseBoundaryError({ caller });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/react.tsx"],"sourcesContent":["import React, { SetStateAction } from \"react\";\nimport { isBrowserLike } from \"./env\";\nimport { neverResolve } from \"./promises\";\nimport { deindent } from \"./strings\";\n\nexport function componentWrapper<\n C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements,\n ExtraProps extends {} = {}\n>(displayName: string, render: React.ForwardRefRenderFunction<RefFromComponent<C>, React.ComponentPropsWithRef<C> & ExtraProps>) {\n const Component = forwardRefIfNeeded(render);\n Component.displayName = displayName;\n return Component;\n}\ntype RefFromComponent<C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements> = NonNullable<RefFromComponentDistCond<React.ComponentPropsWithRef<C>[\"ref\"]>>;\ntype RefFromComponentDistCond<A> = A extends React.RefObject<infer T> ? T : never; // distributive conditional type; see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types\n\nexport function forwardRefIfNeeded<T, P = {}>(render: React.ForwardRefRenderFunction<T, P>): React.FC<P & { ref?: React.Ref<T> }> {\n // TODO: when we drop support for react 18, remove this\n\n const version = React.version;\n const major = parseInt(version.split(\".\")[0]);\n if (major < 19) {\n return React.forwardRef<T, P>(render as any) as any;\n } else {\n return ((props: P) => render(props, (props as any).ref)) as any;\n }\n}\n\nexport function getNodeText(node: React.ReactNode): string {\n if ([\"number\", \"string\"].includes(typeof node)) {\n return `${node}`;\n }\n if (!node) {\n return \"\";\n }\n if (Array.isArray(node)) {\n return node.map(getNodeText).join(\"\");\n }\n if (typeof node === \"object\" && \"props\" in node) {\n return getNodeText(node.props.children);\n }\n throw new Error(`Unknown node type: ${typeof node}`);\n}\nundefined?.test(\"getNodeText\", ({ expect }) => {\n // Test with string\n expect(getNodeText(\"hello\")).toBe(\"hello\");\n\n // Test with number\n expect(getNodeText(42)).toBe(\"42\");\n\n // Test with null/undefined\n expect(getNodeText(null)).toBe(\"\");\n expect(getNodeText(undefined)).toBe(\"\");\n\n // Test with array\n expect(getNodeText([\"hello\", \" \", \"world\"])).toBe(\"hello world\");\n expect(getNodeText([1, 2, 3])).toBe(\"123\");\n\n // Test with mixed array\n expect(getNodeText([\"hello\", 42, null])).toBe(\"hello42\");\n\n // Test with React element (mocked)\n const mockElement = {\n props: {\n children: \"child text\"\n }\n } as React.ReactElement;\n expect(getNodeText(mockElement)).toBe(\"child text\");\n\n // Test with nested React elements\n const nestedElement = {\n props: {\n children: {\n props: {\n children: \"nested text\"\n }\n } as React.ReactElement\n }\n } as React.ReactElement;\n expect(getNodeText(nestedElement)).toBe(\"nested text\");\n\n // Test with array of React elements\n const arrayOfElements = [\n { props: { children: \"first\" } } as React.ReactElement,\n { props: { children: \"second\" } } as React.ReactElement\n ];\n expect(getNodeText(arrayOfElements)).toBe(\"firstsecond\");\n});\n\n/**\n * Suspends the currently rendered component indefinitely. Will not unsuspend unless the component rerenders.\n *\n * You can use this to translate older query- or AsyncResult-based code to new the Suspense system, for example: `if (query.isLoading) suspend();`\n */\nexport function suspend(): never {\n React.use(neverResolve());\n throw new Error(\"Somehow a Promise that never resolves was resolved?\");\n}\n\nexport function mapRef<T, R>(ref: ReadonlyRef<T>, mapper: (value: T) => R): ReadonlyRef<R> {\n let last: [T, R] | null = null;\n return {\n get current() {\n const input = ref.current;\n if (last === null || input !== last[0]) {\n last = [input, mapper(input)];\n }\n return last[1];\n },\n };\n}\n\nexport type ReadonlyRef<T> = {\n readonly current: T,\n};\n\nexport type RefState<T> = ReadonlyRef<T> & {\n set: (updater: SetStateAction<T>) => void,\n};\n\n/**\n * Like useState, but its value is immediately available on refState.current after being set.\n *\n * Like useRef, but setting the value will cause a rerender.\n *\n * Note that useRefState returns a new object every time a rerender happens due to a value change, which is intentional\n * as it allows you to specify it in a dependency array like this:\n *\n * ```tsx\n * useEffect(() => {\n * // do something with refState.current\n * }, [refState]); // instead of refState.current\n * ```\n *\n * If you don't want this, you can wrap the result in a useMemo call.\n */\nexport function useRefState<T>(initialValue: T): RefState<T> {\n const [, setState] = React.useState(initialValue);\n const ref = React.useRef(initialValue);\n const setValue = React.useCallback((updater: SetStateAction<T>) => {\n const value: T = typeof updater === \"function\" ? (updater as any)(ref.current) : updater;\n console.log(\"setValue\", ref.current);\n ref.current = value;\n console.log(\"setValue\", ref.current);\n setState(value);\n }, []);\n const res = React.useMemo(() => ({\n get current() {\n return ref.current;\n },\n set: setValue,\n }), [setValue]);\n return res;\n}\n\nexport function mapRefState<T, R>(refState: RefState<T>, mapper: (value: T) => R, reverseMapper: (oldT: T, newR: R) => T): RefState<R> {\n let last: [T, R] | null = null;\n return {\n get current() {\n const input = refState.current;\n if (last === null || input !== last[0]) {\n last = [input, mapper(input)];\n }\n return last[1];\n },\n set(updater: SetStateAction<R>) {\n const value: R = typeof updater === \"function\" ? (updater as any)(this.current) : updater;\n refState.set(reverseMapper(refState.current, value));\n },\n };\n}\n\nexport class NoSuspenseBoundaryError extends Error {\n digest: string;\n reason: string;\n\n constructor(options: { caller?: string }) {\n super(deindent`\n ${options.caller ?? \"This code path\"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.\n \n The fix depends on which of the 3 scenarios caused it:\n \n 1. You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.\n\n 2. The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:\n\n - app\n - - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? \"this code path\"}\n - - loading.tsx // required for suspense\n - - (main)\n - - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? \"this code path\"}\n - - - route.tsx // your actual main page\n - - - the rest of your app\n\n For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups\n \n 3. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.\n\n See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n\n More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content\n `);\n\n this.name = \"NoSuspenseBoundaryError\";\n this.reason = options.caller ?? \"suspendIfSsr()\";\n\n // set the digest so nextjs doesn't log the error\n // https://github.com/vercel/next.js/blob/d01d6d9c35a8c2725b3d74c1402ab76d4779a6cf/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts#L14\n this.digest = \"BAILOUT_TO_CLIENT_SIDE_RENDERING\";\n }\n}\nundefined?.test(\"NoSuspenseBoundaryError\", ({ expect }) => {\n // Test with default options\n const defaultError = new NoSuspenseBoundaryError({});\n expect(defaultError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(defaultError.reason).toBe(\"suspendIfSsr()\");\n expect(defaultError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(defaultError.message).toContain(\"This code path attempted to display a loading indicator\");\n\n // Test with custom caller\n const customError = new NoSuspenseBoundaryError({ caller: \"CustomComponent\" });\n expect(customError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(customError.reason).toBe(\"CustomComponent\");\n expect(customError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(customError.message).toContain(\"CustomComponent attempted to display a loading indicator\");\n\n // Verify error message contains all the necessary information\n expect(customError.message).toContain(\"loading.tsx\");\n expect(customError.message).toContain(\"route groups\");\n expect(customError.message).toContain(\"https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\");\n});\n\n\n/**\n * Use this in a component or a hook to disable SSR. Should be wrapped in a Suspense boundary, or it will throw an error.\n */\nexport function suspendIfSsr(caller?: string) {\n if (!isBrowserLike()) {\n throw new NoSuspenseBoundaryError({ caller });\n }\n}\n"],"mappings":";AAAA,OAAO,WAA+B;AACtC,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AAElB,SAAS,iBAGd,aAAqB,QAA0G;AAC/H,QAAM,YAAY,mBAAmB,MAAM;AAC3C,YAAU,cAAc;AACxB,SAAO;AACT;AAIO,SAAS,mBAA8B,QAAoF;AAGhI,QAAM,UAAU,MAAM;AACtB,QAAM,QAAQ,SAAS,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC;AAC5C,MAAI,QAAQ,IAAI;AACd,WAAO,MAAM,WAAiB,MAAa;AAAA,EAC7C,OAAO;AACL,WAAQ,CAAC,UAAa,OAAO,OAAQ,MAAc,GAAG;AAAA,EACxD;AACF;AAEO,SAAS,YAAY,MAA+B;AACzD,MAAI,CAAC,UAAU,QAAQ,EAAE,SAAS,OAAO,IAAI,GAAG;AAC9C,WAAO,GAAG,IAAI;AAAA,EAChB;AACA,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,WAAW,EAAE,KAAK,EAAE;AAAA,EACtC;AACA,MAAI,OAAO,SAAS,YAAY,WAAW,MAAM;AAC/C,WAAO,YAAY,KAAK,MAAM,QAAQ;AAAA,EACxC;AACA,QAAM,IAAI,MAAM,sBAAsB,OAAO,IAAI,EAAE;AACrD;AAoDO,SAAS,UAAiB;AAC/B,QAAM,IAAI,aAAa,CAAC;AACxB,QAAM,IAAI,MAAM,qDAAqD;AACvE;AAEO,SAAS,OAAa,KAAqB,QAAyC;AACzF,MAAI,OAAsB;AAC1B,SAAO;AAAA,IACL,IAAI,UAAU;AACZ,YAAM,QAAQ,IAAI;AAClB,UAAI,SAAS,QAAQ,UAAU,KAAK,CAAC,GAAG;AACtC,eAAO,CAAC,OAAO,OAAO,KAAK,CAAC;AAAA,MAC9B;AACA,aAAO,KAAK,CAAC;AAAA,IACf;AAAA,EACF;AACF;AA0BO,SAAS,YAAe,cAA8B;AAC3D,QAAM,CAAC,EAAE,QAAQ,IAAI,MAAM,SAAS,YAAY;AAChD,QAAM,MAAM,MAAM,OAAO,YAAY;AACrC,QAAM,WAAW,MAAM,YAAY,CAAC,YAA+B;AACjE,UAAM,QAAW,OAAO,YAAY,aAAc,QAAgB,IAAI,OAAO,IAAI;AACjF,YAAQ,IAAI,YAAY,IAAI,OAAO;AACnC,QAAI,UAAU;AACd,YAAQ,IAAI,YAAY,IAAI,OAAO;AACnC,aAAS,KAAK;AAAA,EAChB,GAAG,CAAC,CAAC;AACL,QAAM,MAAM,MAAM,QAAQ,OAAO;AAAA,IAC/B,IAAI,UAAU;AACZ,aAAO,IAAI;AAAA,IACb;AAAA,IACA,KAAK;AAAA,EACP,IAAI,CAAC,QAAQ,CAAC;AACd,SAAO;AACT;AAEO,SAAS,YAAkB,UAAuB,QAAyB,eAAqD;AACrI,MAAI,OAAsB;AAC1B,SAAO;AAAA,IACL,IAAI,UAAU;AACZ,YAAM,QAAQ,SAAS;AACvB,UAAI,SAAS,QAAQ,UAAU,KAAK,CAAC,GAAG;AACtC,eAAO,CAAC,OAAO,OAAO,KAAK,CAAC;AAAA,MAC9B;AACA,aAAO,KAAK,CAAC;AAAA,IACf;AAAA,IACA,IAAI,SAA4B;AAC9B,YAAM,QAAW,OAAO,YAAY,aAAc,QAAgB,KAAK,OAAO,IAAI;AAClF,eAAS,IAAI,cAAc,SAAS,SAAS,KAAK,CAAC;AAAA,IACrD;AAAA,EACF;AACF;AAEO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAIjD,YAAY,SAA8B;AACxC,UAAM;AAAA,QACF,QAAQ,UAAU,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kHASwE,QAAQ,UAAU,gBAAgB;AAAA;AAAA;AAAA,6GAGvC,QAAQ,UAAU,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAW1I;AAED,SAAK,OAAO;AACZ,SAAK,SAAS,QAAQ,UAAU;AAIhC,SAAK,SAAS;AAAA,EAChB;AACF;AA0BO,SAAS,aAAa,QAAiB;AAC5C,MAAI,CAAC,cAAc,GAAG;AACpB,UAAM,IAAI,wBAAwB,EAAE,OAAO,CAAC;AAAA,EAC9C;AACF;","names":[]}
|
|
@@ -191,6 +191,9 @@ ${currentIndent}`;
|
|
|
191
191
|
if (ArrayBuffer.isView(value)) {
|
|
192
192
|
return `${value.constructor.name}([${value.toString()}])`;
|
|
193
193
|
}
|
|
194
|
+
if (value instanceof ArrayBuffer) {
|
|
195
|
+
return `ArrayBuffer [${new Uint8Array(value).toString()}]`;
|
|
196
|
+
}
|
|
194
197
|
if (value instanceof Error) {
|
|
195
198
|
let stack = value.stack ?? "";
|
|
196
199
|
const toString = value.toString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/strings.tsx"],"sourcesContent":["import { findLastIndex, unique } from \"./arrays\";\nimport { StackAssertionError } from \"./errors\";\nimport { filterUndefined } from \"./objects\";\n\nexport type Join<T extends string[], Separator extends string> =\n T extends [] ? \"\"\n : T extends [infer U extends string, ...infer Rest extends string[]]\n ? `${U}${Rest extends [any, ...any[]] ? `${Separator}${Join<Rest, Separator>}` : \"\"}`\n : \"<error-joining-strings>\";\n\nexport function typedJoin<T extends string[], Separator extends string>(strings: T, separator: Separator): Join<T, Separator> {\n return strings.join(separator) as Join<T, Separator>;\n}\n\nexport function typedToLowercase<S extends string>(s: S): Lowercase<S> {\n if (typeof s !== \"string\") throw new StackAssertionError(\"Expected a string for typedToLowercase\", { s });\n return s.toLowerCase() as Lowercase<S>;\n}\nundefined?.test(\"typedToLowercase\", ({ expect }) => {\n expect(typedToLowercase(\"\")).toBe(\"\");\n expect(typedToLowercase(\"HELLO\")).toBe(\"hello\");\n expect(typedToLowercase(\"Hello World\")).toBe(\"hello world\");\n expect(typedToLowercase(\"hello\")).toBe(\"hello\");\n expect(typedToLowercase(\"123\")).toBe(\"123\");\n expect(typedToLowercase(\"MIXED123case\")).toBe(\"mixed123case\");\n expect(typedToLowercase(\"Special@Chars!\")).toBe(\"special@chars!\");\n expect(() => typedToLowercase(123 as any)).toThrow(\"Expected a string for typedToLowercase\");\n});\n\nexport function typedToUppercase<S extends string>(s: S): Uppercase<S> {\n if (typeof s !== \"string\") throw new StackAssertionError(\"Expected a string for typedToUppercase\", { s });\n return s.toUpperCase() as Uppercase<S>;\n}\nundefined?.test(\"typedToUppercase\", ({ expect }) => {\n expect(typedToUppercase(\"\")).toBe(\"\");\n expect(typedToUppercase(\"hello\")).toBe(\"HELLO\");\n expect(typedToUppercase(\"Hello World\")).toBe(\"HELLO WORLD\");\n expect(typedToUppercase(\"HELLO\")).toBe(\"HELLO\");\n expect(typedToUppercase(\"123\")).toBe(\"123\");\n expect(typedToUppercase(\"mixed123Case\")).toBe(\"MIXED123CASE\");\n expect(typedToUppercase(\"special@chars!\")).toBe(\"SPECIAL@CHARS!\");\n expect(() => typedToUppercase(123 as any)).toThrow(\"Expected a string for typedToUppercase\");\n});\n\nexport function typedCapitalize<S extends string>(s: S): Capitalize<S> {\n return s.charAt(0).toUpperCase() + s.slice(1) as Capitalize<S>;\n}\nundefined?.test(\"typedCapitalize\", ({ expect }) => {\n expect(typedCapitalize(\"\")).toBe(\"\");\n expect(typedCapitalize(\"hello\")).toBe(\"Hello\");\n expect(typedCapitalize(\"hello world\")).toBe(\"Hello world\");\n expect(typedCapitalize(\"HELLO\")).toBe(\"HELLO\");\n expect(typedCapitalize(\"123test\")).toBe(\"123test\");\n expect(typedCapitalize(\"already Capitalized\")).toBe(\"Already Capitalized\");\n expect(typedCapitalize(\"h\")).toBe(\"H\");\n});\n\n/**\n * Compares two strings in a way that is not dependent on the current locale.\n */\nexport function stringCompare(a: string, b: string): number {\n if (typeof a !== \"string\" || typeof b !== \"string\") throw new StackAssertionError(`Expected two strings for stringCompare, found ${typeof a} and ${typeof b}`, { a, b });\n const cmp = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;\n return cmp(a.toUpperCase(), b.toUpperCase()) || cmp(b, a);\n}\nundefined?.test(\"stringCompare\", ({ expect }) => {\n // Equal strings\n expect(stringCompare(\"a\", \"a\")).toBe(0);\n expect(stringCompare(\"\", \"\")).toBe(0);\n\n // Case comparison - note that this function is NOT case-insensitive\n // It compares uppercase versions first, then original strings\n expect(stringCompare(\"a\", \"A\")).toBe(-1); // lowercase comes after uppercase\n expect(stringCompare(\"A\", \"a\")).toBe(1); // uppercase comes before lowercase\n expect(stringCompare(\"abc\", \"ABC\")).toBe(-1);\n expect(stringCompare(\"ABC\", \"abc\")).toBe(1);\n\n // Different strings\n expect(stringCompare(\"a\", \"b\")).toBe(-1);\n expect(stringCompare(\"b\", \"a\")).toBe(1);\n\n // Strings with different lengths\n expect(stringCompare(\"abc\", \"abcd\")).toBe(-1);\n expect(stringCompare(\"abcd\", \"abc\")).toBe(1);\n\n // Strings with numbers\n expect(stringCompare(\"a1\", \"a2\")).toBe(-1);\n expect(stringCompare(\"a10\", \"a2\")).toBe(-1);\n\n // Strings with special characters\n expect(stringCompare(\"a\", \"a!\")).toBe(-1);\n expect(stringCompare(\"a!\", \"a\")).toBe(1);\n});\n\n/**\n * Returns all whitespace character at the start of the string.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function getWhitespacePrefix(s: string): string {\n return s.substring(0, s.length - s.trimStart().length);\n}\nundefined?.test(\"getWhitespacePrefix\", ({ expect }) => {\n expect(getWhitespacePrefix(\"\")).toBe(\"\");\n expect(getWhitespacePrefix(\"hello\")).toBe(\"\");\n expect(getWhitespacePrefix(\" hello\")).toBe(\" \");\n expect(getWhitespacePrefix(\" hello\")).toBe(\" \");\n expect(getWhitespacePrefix(\"\\thello\")).toBe(\"\\t\");\n expect(getWhitespacePrefix(\"\\n hello\")).toBe(\"\\n \");\n expect(getWhitespacePrefix(\" \")).toBe(\" \");\n expect(getWhitespacePrefix(\" \\t\\n\\r\")).toBe(\" \\t\\n\\r\");\n});\n\n/**\n * Returns all whitespace character at the end of the string.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function getWhitespaceSuffix(s: string): string {\n return s.substring(s.trimEnd().length);\n}\nundefined?.test(\"getWhitespaceSuffix\", ({ expect }) => {\n expect(getWhitespaceSuffix(\"\")).toBe(\"\");\n expect(getWhitespaceSuffix(\"hello\")).toBe(\"\");\n expect(getWhitespaceSuffix(\"hello \")).toBe(\" \");\n expect(getWhitespaceSuffix(\"hello \")).toBe(\" \");\n expect(getWhitespaceSuffix(\"hello\\t\")).toBe(\"\\t\");\n expect(getWhitespaceSuffix(\"hello \\n\")).toBe(\" \\n\");\n expect(getWhitespaceSuffix(\" \")).toBe(\" \");\n expect(getWhitespaceSuffix(\" \\t\\n\\r\")).toBe(\" \\t\\n\\r\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines at the start removed.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimEmptyLinesStart(s: string): string {\n const lines = s.split(\"\\n\");\n const firstNonEmptyLineIndex = lines.findIndex((line) => line.trim() !== \"\");\n // If all lines are empty or whitespace-only, return an empty string\n if (firstNonEmptyLineIndex === -1) return \"\";\n return lines.slice(firstNonEmptyLineIndex).join(\"\\n\");\n}\nundefined?.test(\"trimEmptyLinesStart\", ({ expect }) => {\n expect(trimEmptyLinesStart(\"\")).toBe(\"\");\n expect(trimEmptyLinesStart(\"hello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\n\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\" \\n\\t\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\n\\nhello\\nworld\")).toBe(\"hello\\nworld\");\n expect(trimEmptyLinesStart(\"hello\\n\\nworld\")).toBe(\"hello\\n\\nworld\");\n expect(trimEmptyLinesStart(\"hello\\nworld\\n\")).toBe(\"hello\\nworld\\n\");\n expect(trimEmptyLinesStart(\"\\n \\n\\nhello\\n \\nworld\")).toBe(\"hello\\n \\nworld\");\n // Edge case: all lines are empty\n expect(trimEmptyLinesStart(\"\\n\\n \\n\\t\")).toBe(\"\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines at the end removed.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimEmptyLinesEnd(s: string): string {\n const lines = s.split(\"\\n\");\n const lastNonEmptyLineIndex = findLastIndex(lines, (line) => line.trim() !== \"\");\n return lines.slice(0, lastNonEmptyLineIndex + 1).join(\"\\n\");\n}\nundefined?.test(\"trimEmptyLinesEnd\", ({ expect }) => {\n expect(trimEmptyLinesEnd(\"\")).toBe(\"\");\n expect(trimEmptyLinesEnd(\"hello\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n\\n\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n \\n\\t\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\nworld\\n\\n\")).toBe(\"hello\\nworld\");\n expect(trimEmptyLinesEnd(\"hello\\n\\nworld\")).toBe(\"hello\\n\\nworld\");\n expect(trimEmptyLinesEnd(\"\\nhello\\nworld\")).toBe(\"\\nhello\\nworld\");\n expect(trimEmptyLinesEnd(\"hello\\n \\nworld\\n\\n \")).toBe(\"hello\\n \\nworld\");\n // Edge case: all lines are empty\n expect(trimEmptyLinesEnd(\"\\n\\n \\n\\t\")).toBe(\"\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines trimmed at the start and end.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimLines(s: string): string {\n return trimEmptyLinesEnd(trimEmptyLinesStart(s));\n}\nundefined?.test(\"trimLines\", ({ expect }) => {\n expect(trimLines(\"\")).toBe(\"\");\n expect(trimLines(\" \")).toBe(\"\");\n expect(trimLines(\" \\n \")).toBe(\"\");\n expect(trimLines(\" abc \")).toBe(\" abc \");\n expect(trimLines(\"\\n \\nLine1\\nLine2\\n \\n\")).toBe(\"Line1\\nLine2\");\n expect(trimLines(\"Line1\\n \\nLine2\")).toBe(\"Line1\\n \\nLine2\");\n expect(trimLines(\" \\n \\n\\t\")).toBe(\"\");\n expect(trimLines(\" Hello World\")).toBe(\" Hello World\");\n expect(trimLines(\"\\n\")).toBe(\"\");\n expect(trimLines(\"\\t \\n\\t\\tLine1 \\n \\nLine2\\t\\t\\n\\t \")).toBe(\"\\t\\tLine1 \\n \\nLine2\\t\\t\");\n});\n\n\n/**\n * A template literal tag that returns the same string as the template literal without a tag.\n *\n * Useful for implementing your own template literal tags.\n */\nexport function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: string[]): string {\n if (values.length !== strings.length - 1) throw new StackAssertionError(\"Invalid number of values; must be one less than strings\", { strings, values });\n\n return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');\n}\nundefined?.test(\"templateIdentity\", ({ expect }) => {\n expect(templateIdentity`Hello World`).toBe(\"Hello World\");\n expect(templateIdentity`${\"Hello\"}`).toBe(\"Hello\");\n const greeting = \"Hello\";\n const subject = \"World\";\n expect(templateIdentity`${greeting}, ${subject}!`).toBe(\"Hello, World!\");\n expect(templateIdentity`${\"A\"}${\"B\"}${\"C\"}`).toBe(\"ABC\");\n expect(templateIdentity`Start${\"\"}Middle${\"\"}End`).toBe(\"StartMiddleEnd\");\n expect(templateIdentity``).toBe(\"\");\n expect(templateIdentity`Line1\nLine2`).toBe(\"Line1\\nLine2\");\n expect(templateIdentity([\"a \", \" scientific \", \"gun\"], \"certain\", \"rail\")).toBe(\"a certain scientific railgun\");\n expect(templateIdentity([\"only one part\"])).toBe(\"only one part\");\n expect(() => templateIdentity([\"a \", \"b\", \"c\"], \"only one\")).toThrow(\"Invalid number of values\");\n expect(() => templateIdentity([\"a\", \"b\"], \"x\", \"y\")).toThrow(\"Invalid number of values\");\n});\n\n\nexport function deindent(code: string): string;\nexport function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;\nexport function deindent(strings: string | readonly string[], ...values: any[]): string {\n if (typeof strings === \"string\") return deindent([strings]);\n return templateIdentity(...deindentTemplate(strings, ...values));\n}\n\nexport function deindentTemplate(strings: TemplateStringsArray | readonly string[], ...values: any[]): [string[], ...string[]] {\n if (values.length !== strings.length - 1) throw new StackAssertionError(\"Invalid number of values; must be one less than strings\", { strings, values });\n\n const trimmedStrings = [...strings];\n trimmedStrings[0] = trimEmptyLinesStart(trimmedStrings[0] + \"+\").slice(0, -1);\n trimmedStrings[trimmedStrings.length - 1] = trimEmptyLinesEnd(\"+\" + trimmedStrings[trimmedStrings.length - 1]).slice(1);\n\n const indentation = trimmedStrings\n .join(\"${SOME_VALUE}\")\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\")\n .map((line) => getWhitespacePrefix(line).length)\n .reduce((min, current) => Math.min(min, current), Infinity);\n\n const deindentedStrings = trimmedStrings\n .map((string, stringIndex) => {\n return string\n .split(\"\\n\")\n .map((line, lineIndex) => stringIndex !== 0 && lineIndex === 0 ? line : line.substring(indentation))\n .join(\"\\n\");\n });\n\n const indentedValues = values.map((value, i) => {\n const firstLineIndentation = getWhitespacePrefix(deindentedStrings[i].split(\"\\n\").at(-1)!);\n return `${value}`.replaceAll(\"\\n\", `\\n${firstLineIndentation}`);\n });\n\n return [deindentedStrings, ...indentedValues];\n}\nundefined?.test(\"deindent\", ({ expect }) => {\n // Test with string input\n expect(deindent(\" hello\")).toBe(\"hello\");\n expect(deindent(\" hello\\n world\")).toBe(\"hello\\nworld\");\n expect(deindent(\" hello\\n world\")).toBe(\"hello\\n world\");\n expect(deindent(\"\\n hello\\n world\\n\")).toBe(\"hello\\nworld\");\n\n // Test with empty input\n expect(deindent(\"\")).toBe(\"\");\n\n // Test with template literal\n expect(deindent`\n hello\n world\n `).toBe(\"hello\\nworld\");\n\n expect(deindent`\n hello\n world\n `).toBe(\"hello\\n world\");\n\n // Test with values\n const value = \"test\";\n expect(deindent`\n hello ${value}\n world\n `).toBe(`hello ${value}\\nworld`);\n\n // Test with multiline values\n expect(deindent`\n hello\n to ${\"line1\\n line2\"}\n world\n `).toBe(`hello\\n to line1\\n line2\\nworld`);\n\n // Leading whitespace values\n expect(deindent`\n ${\" \"}A\n ${\" \"}B\n ${\" \"}C\n `).toBe(` A\\n B\\n C`);\n\n // Trailing whitespaces (note: there are two whitespaces each after A and after C)\n expect(deindent`\n A \n B ${\" \"}\n C \n `).toBe(`A \\nB \\nC `);\n\n // Test with mixed indentation\n expect(deindent`\n hello\n world\n !\n `).toBe(\"hello\\n world\\n !\");\n\n // Test error cases\n expect(() => deindent([\"a\", \"b\", \"c\"], \"too\", \"many\", \"values\")).toThrow(\"Invalid number of values\");\n});\n\nexport function extractScopes(scope: string, removeDuplicates=true): string[] {\n // TODO what is this for? can we move this into the OAuth code in the backend?\n const trimmedString = scope.trim();\n const scopesArray = trimmedString.split(/\\s+/);\n const filtered = scopesArray.filter(scope => scope.length > 0);\n return removeDuplicates ? [...new Set(filtered)] : filtered;\n}\nundefined?.test(\"extractScopes\", ({ expect }) => {\n // Test with empty string\n expect(extractScopes(\"\")).toEqual([]);\n\n // Test with single scope\n expect(extractScopes(\"read\")).toEqual([\"read\"]);\n\n // Test with multiple scopes\n expect(extractScopes(\"read write\")).toEqual([\"read\", \"write\"]);\n\n // Test with extra whitespace\n expect(extractScopes(\" read write \")).toEqual([\"read\", \"write\"]);\n\n // Test with newlines and tabs\n expect(extractScopes(\"read\\nwrite\\tdelete\")).toEqual([\"read\", \"write\", \"delete\"]);\n\n // Test with duplicates (default behavior)\n expect(extractScopes(\"read write read\")).toEqual([\"read\", \"write\"]);\n\n // Test with duplicates (explicitly set to remove)\n expect(extractScopes(\"read write read\", true)).toEqual([\"read\", \"write\"]);\n\n // Test with duplicates (explicitly set to keep)\n expect(extractScopes(\"read write read\", false)).toEqual([\"read\", \"write\", \"read\"]);\n});\n\nexport function mergeScopeStrings(...scopes: string[]): string {\n // TODO what is this for? can we move this into the OAuth code in the backend?\n const allScope = scopes.map((s) => extractScopes(s)).flat().join(\" \");\n return extractScopes(allScope).join(\" \");\n}\nundefined?.test(\"mergeScopeStrings\", ({ expect }) => {\n // Test with empty input\n expect(mergeScopeStrings()).toBe(\"\");\n\n // Test with single scope string\n expect(mergeScopeStrings(\"read write\")).toBe(\"read write\");\n\n // Test with multiple scope strings\n expect(mergeScopeStrings(\"read\", \"write\")).toBe(\"read write\");\n\n // Test with overlapping scopes\n expect(mergeScopeStrings(\"read write\", \"write delete\")).toBe(\"read write delete\");\n\n // Test with extra whitespace\n expect(mergeScopeStrings(\" read write \", \" delete \")).toBe(\"read write delete\");\n\n // Test with duplicates across strings\n expect(mergeScopeStrings(\"read write\", \"write delete\", \"read\")).toBe(\"read write delete\");\n\n // Test with empty strings\n expect(mergeScopeStrings(\"read write\", \"\", \"delete\")).toBe(\"read write delete\");\n});\n\nexport function escapeTemplateLiteral(s: string): string {\n return s.replaceAll(\"`\", \"\\\\`\").replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"$\", \"\\\\$\");\n}\nundefined?.test(\"escapeTemplateLiteral\", ({ expect }) => {\n // Test with empty string\n expect(escapeTemplateLiteral(\"\")).toBe(\"\");\n\n // Test with normal string (no special characters)\n expect(escapeTemplateLiteral(\"hello world\")).toBe(\"hello world\");\n\n // Test with backtick\n const input1 = \"hello `world`\";\n const output1 = escapeTemplateLiteral(input1);\n // Verify backticks are escaped\n expect(output1.includes(\"\\\\`\")).toBe(true);\n expect(output1).not.toBe(input1);\n\n // Test with backslash\n const input2 = \"hello \\\\world\";\n const output2 = escapeTemplateLiteral(input2);\n // Verify backslashes are escaped\n expect(output2.includes(\"\\\\\\\\\")).toBe(true);\n expect(output2).not.toBe(input2);\n\n // Test with dollar sign\n const input3 = \"hello $world\";\n const output3 = escapeTemplateLiteral(input3);\n // Verify dollar signs are escaped\n expect(output3.includes(\"\\\\$\")).toBe(true);\n expect(output3).not.toBe(input3);\n\n // Test with multiple special characters\n const input4 = \"`hello` $world\\\\\";\n const output4 = escapeTemplateLiteral(input4);\n // Verify all special characters are escaped\n expect(output4.includes(\"\\\\`\")).toBe(true);\n expect(output4.includes(\"\\\\$\")).toBe(true);\n expect(output4.includes(\"\\\\\\\\\")).toBe(true);\n expect(output4).not.toBe(input4);\n\n // Test with already escaped characters\n const input5 = \"\\\\`hello\\\\`\";\n const output5 = escapeTemplateLiteral(input5);\n // Verify already escaped characters are properly escaped\n expect(output5).not.toBe(input5);\n});\n\n/**\n * Some classes have different constructor names in different environments (eg. `Headers` is sometimes called `_Headers`,\n * so we create an object of overrides to handle these cases.\n */\nconst nicifiableClassNameOverrides = new Map(Object.entries({\n Headers,\n} as Record<string, unknown>).map(([k, v]) => [v, k]));\nexport type Nicifiable = {\n getNicifiableKeys?(): PropertyKey[],\n getNicifiedObjectExtraLines?(): string[],\n};\nexport type NicifyOptions = {\n maxDepth: number,\n currentIndent: string,\n lineIndent: string,\n multiline: boolean,\n refs: Map<unknown, string>,\n path: string,\n parent: null | {\n options: NicifyOptions,\n value: unknown,\n },\n keyInParent: PropertyKey | null,\n hideFields: PropertyKey[],\n overrides: (...args: Parameters<typeof nicify>) => string | null,\n};\nexport function nicify(\n value: unknown,\n options: Partial<NicifyOptions> = {},\n): string {\n const fullOptions: NicifyOptions = {\n maxDepth: 5,\n currentIndent: \"\",\n lineIndent: \" \",\n multiline: true,\n refs: new Map(),\n path: \"value\",\n parent: null,\n overrides: () => null,\n keyInParent: null,\n hideFields: [],\n ...filterUndefined(options),\n };\n const {\n maxDepth,\n currentIndent,\n lineIndent,\n multiline,\n refs,\n path,\n overrides,\n hideFields,\n } = fullOptions;\n const nl = `\\n${currentIndent}`;\n\n const overrideResult = overrides(value, options);\n if (overrideResult !== null) return overrideResult;\n\n if ([\"function\", \"object\", \"symbol\"].includes(typeof value) && value !== null) {\n if (refs.has(value)) {\n return `Ref<${refs.get(value)}>`;\n }\n refs.set(value, path);\n }\n\n const newOptions: NicifyOptions = {\n maxDepth: maxDepth - 1,\n currentIndent,\n lineIndent,\n multiline,\n refs,\n path: path + \"->[unknown property]\",\n overrides,\n parent: { value, options: fullOptions },\n keyInParent: null,\n hideFields: [],\n };\n const nestedNicify = (newValue: unknown, newPath: string, keyInParent: PropertyKey | null, options: Partial<NicifyOptions> = {}) => {\n return nicify(newValue, {\n ...newOptions,\n path: newPath,\n currentIndent: currentIndent + lineIndent,\n keyInParent,\n ...options,\n });\n };\n\n switch (typeof value) {\n case \"boolean\": case \"number\": {\n return JSON.stringify(value);\n }\n case \"string\": {\n const isDeindentable = (v: string) => deindent(v) === v && v.includes(\"\\n\");\n const wrapInDeindent = (v: string) => deindent`\n deindent\\`\n ${currentIndent + lineIndent}${escapeTemplateLiteral(v).replaceAll(\"\\n\", nl + lineIndent)}\n ${currentIndent}\\`\n `;\n if (isDeindentable(value)) {\n return wrapInDeindent(value);\n } else if (value.endsWith(\"\\n\") && isDeindentable(value.slice(0, -1))) {\n return wrapInDeindent(value.slice(0, -1)) + ' + \"\\\\n\"';\n } else {\n return JSON.stringify(value);\n }\n }\n case \"undefined\": {\n return \"undefined\";\n }\n case \"symbol\": {\n return value.toString();\n }\n case \"bigint\": {\n return `${value}n`;\n }\n case \"function\": {\n if (value.name) return `function ${value.name}(...) { ... }`;\n return `(...) => { ... }`;\n }\n case \"object\": {\n if (value === null) return \"null\";\n if (Array.isArray(value)) {\n const extraLines = getNicifiedObjectExtraLines(value);\n const resValueLength = value.length + extraLines.length;\n if (maxDepth <= 0 && resValueLength === 0) return \"[...]\";\n const resValues = value.map((v, i) => nestedNicify(v, `${path}[${i}]`, i));\n resValues.push(...extraLines);\n if (resValues.length !== resValueLength) throw new StackAssertionError(\"nicify of object: resValues.length !== resValueLength\", { value, resValues, resValueLength });\n const shouldIndent = resValues.length > 4 || resValues.some(x => (resValues.length > 1 && x.length > 4) || x.includes(\"\\n\"));\n if (shouldIndent) {\n return `[${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join(\"\")}]`;\n } else {\n return `[${resValues.join(\", \")}]`;\n }\n }\n if (value instanceof URL) {\n return `URL(${nestedNicify(value.toString(), `${path}.toString()`, null)})`;\n }\n if (ArrayBuffer.isView(value)) {\n return `${value.constructor.name}([${value.toString()}])`;\n }\n if (value instanceof Error) {\n let stack = value.stack ?? \"\";\n const toString = value.toString();\n if (!stack.startsWith(toString)) stack = `${toString}\\n${stack}`; // some browsers don't include the error message in the stack, some do\n stack = stack.trimEnd();\n stack = stack.replace(/\\n\\s+/g, `\\n${lineIndent}${lineIndent}`);\n stack = stack.replace(\"\\n\", `\\n${lineIndent}Stack:\\n`);\n if (Object.keys(value).length > 0) {\n stack += `\\n${lineIndent}Extra properties: ${nestedNicify(Object.fromEntries(Object.entries(value)), path, null)}`;\n }\n if (value.cause) {\n stack += `\\n${lineIndent}Cause:\\n${lineIndent}${lineIndent}${nestedNicify(value.cause, path, null, { currentIndent: currentIndent + lineIndent + lineIndent })}`;\n }\n stack = stack.replaceAll(\"\\n\", `\\n${currentIndent}`);\n return stack;\n }\n\n const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);\n const constructorString = constructorName ? `${constructorName} ` : \"\";\n\n const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));\n const extraLines = [\n ...getNicifiedObjectExtraLines(value),\n ...hideFields.length > 0 ? [`<some fields may have been hidden>`] : [],\n ];\n const resValueLength = entries.length + extraLines.length;\n if (resValueLength === 0) return `${constructorString}{}`;\n if (maxDepth <= 0) return `${constructorString}{ ... }`;\n const resValues = entries.map(([k, v], keyIndex) => {\n const keyNicified = nestedNicify(k, `Object.keys(${path})[${keyIndex}]`, null);\n const keyInObjectLiteral = typeof k === \"string\" ? nicifyPropertyString(k) : `[${keyNicified}]`;\n if (typeof v === \"function\" && v.name === k) {\n return `${keyInObjectLiteral}(...): { ... }`;\n } else {\n return `${keyInObjectLiteral}: ${nestedNicify(v, `${path}[${keyNicified}]`, k)}`;\n }\n });\n resValues.push(...extraLines);\n if (resValues.length !== resValueLength) throw new StackAssertionError(\"nicify of object: resValues.length !== resValueLength\", { value, resValues, resValueLength });\n const shouldIndent = resValues.length > 1 || resValues.some(x => x.includes(\"\\n\"));\n\n if (resValues.length === 0) return `${constructorString}{}`;\n if (shouldIndent) {\n return `${constructorString}{${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join(\"\")}}`;\n } else {\n return `${constructorString}{ ${resValues.join(\", \")} }`;\n }\n }\n default: {\n return `${typeof value}<${value}>`;\n }\n }\n}\n\nexport function replaceAll(input: string, searchValue: string, replaceValue: string): string {\n if (searchValue === \"\") throw new StackAssertionError(\"replaceAll: searchValue is empty\");\n return input.split(searchValue).join(replaceValue);\n}\nundefined?.test(\"replaceAll\", ({ expect }) => {\n expect(replaceAll(\"hello world\", \"o\", \"x\")).toBe(\"hellx wxrld\");\n expect(replaceAll(\"aaa\", \"a\", \"b\")).toBe(\"bbb\");\n expect(replaceAll(\"\", \"a\", \"b\")).toBe(\"\");\n expect(replaceAll(\"abc\", \"b\", \"\")).toBe(\"ac\");\n expect(replaceAll(\"test.test.test\", \".\", \"_\")).toBe(\"test_test_test\");\n expect(replaceAll(\"a.b*c\", \".\", \"x\")).toBe(\"axb*c\");\n expect(replaceAll(\"a*b*c\", \"*\", \"x\")).toBe(\"axbxc\");\n expect(replaceAll(\"hello hello\", \"hello\", \"hi\")).toBe(\"hi hi\");\n});\n\nfunction nicifyPropertyString(str: string) {\n return JSON.stringify(str);\n}\nundefined?.test(\"nicifyPropertyString\", ({ expect }) => {\n // Test valid identifiers\n expect(nicifyPropertyString(\"validName\")).toBe('\"validName\"');\n expect(nicifyPropertyString(\"_validName\")).toBe('\"_validName\"');\n expect(nicifyPropertyString(\"valid123Name\")).toBe('\"valid123Name\"');\n\n // Test invalid identifiers\n expect(nicifyPropertyString(\"123invalid\")).toBe('\"123invalid\"');\n expect(nicifyPropertyString(\"invalid-name\")).toBe('\"invalid-name\"');\n expect(nicifyPropertyString(\"invalid space\")).toBe('\"invalid space\"');\n expect(nicifyPropertyString(\"$invalid\")).toBe('\"$invalid\"');\n expect(nicifyPropertyString(\"\")).toBe('\"\"');\n\n // Test with special characters\n expect(nicifyPropertyString(\"property!\")).toBe('\"property!\"');\n expect(nicifyPropertyString(\"property.name\")).toBe('\"property.name\"');\n\n // Test with escaped characters\n expect(nicifyPropertyString(\"\\\\\")).toBe('\"\\\\\\\\\"');\n expect(nicifyPropertyString('\"')).toBe('\"\\\\\"\"');\n});\n\nfunction getNicifiableKeys(value: Nicifiable | object) {\n const overridden = (\"getNicifiableKeys\" in value ? value.getNicifiableKeys?.bind(value) : null)?.();\n if (overridden != null) return overridden;\n if (value instanceof Response) {\n return ['status', 'headers'];\n }\n const keys = Object.keys(value).sort();\n return unique(keys);\n}\nundefined?.test(\"getNicifiableKeys\", ({ expect }) => {\n // Test regular object\n expect(getNicifiableKeys({ b: 1, a: 2, c: 3 })).toEqual([\"a\", \"b\", \"c\"]);\n\n // Test empty object\n expect(getNicifiableKeys({})).toEqual([]);\n\n\n expect(getNicifiableKeys(new Response())).toEqual([\"status\", \"headers\"]);\n\n // Test object with custom getNicifiableKeys\n const customObject = {\n a: 1,\n b: 2,\n getNicifiableKeys() {\n return [\"customKey1\", \"customKey2\"];\n }\n };\n expect(getNicifiableKeys(customObject)).toEqual([\"customKey1\", \"customKey2\"]);\n});\n\nfunction getNicifiableEntries(value: Nicifiable | object): [PropertyKey, unknown][] {\n const recordLikes = [Headers];\n function isRecordLike(value: unknown): value is InstanceType<typeof recordLikes[number]> {\n return recordLikes.some(x => value instanceof x);\n }\n\n if (isRecordLike(value)) {\n return [...value.entries()].sort(([a], [b]) => stringCompare(`${a}`, `${b}`));\n }\n const keys = getNicifiableKeys(value);\n return keys.map((k) => [k, value[k as never]] as [PropertyKey, unknown]);\n}\n\nfunction getNicifiedObjectExtraLines(value: Nicifiable | object) {\n return (\"getNicifiedObjectExtraLines\" in value ? value.getNicifiedObjectExtraLines : null)?.() ?? [];\n}\n"],"mappings":"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","names":["a","b","scope","options","extraLines","resValueLength","resValues","shouldIndent","value"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/strings.tsx"],"sourcesContent":["import { findLastIndex, unique } from \"./arrays\";\nimport { StackAssertionError } from \"./errors\";\nimport { filterUndefined } from \"./objects\";\n\nexport type Join<T extends string[], Separator extends string> =\n T extends [] ? \"\"\n : T extends [infer U extends string, ...infer Rest extends string[]]\n ? `${U}${Rest extends [any, ...any[]] ? `${Separator}${Join<Rest, Separator>}` : \"\"}`\n : \"<error-joining-strings>\";\n\nexport function typedJoin<T extends string[], Separator extends string>(strings: T, separator: Separator): Join<T, Separator> {\n return strings.join(separator) as Join<T, Separator>;\n}\n\nexport function typedToLowercase<S extends string>(s: S): Lowercase<S> {\n if (typeof s !== \"string\") throw new StackAssertionError(\"Expected a string for typedToLowercase\", { s });\n return s.toLowerCase() as Lowercase<S>;\n}\nundefined?.test(\"typedToLowercase\", ({ expect }) => {\n expect(typedToLowercase(\"\")).toBe(\"\");\n expect(typedToLowercase(\"HELLO\")).toBe(\"hello\");\n expect(typedToLowercase(\"Hello World\")).toBe(\"hello world\");\n expect(typedToLowercase(\"hello\")).toBe(\"hello\");\n expect(typedToLowercase(\"123\")).toBe(\"123\");\n expect(typedToLowercase(\"MIXED123case\")).toBe(\"mixed123case\");\n expect(typedToLowercase(\"Special@Chars!\")).toBe(\"special@chars!\");\n expect(() => typedToLowercase(123 as any)).toThrow(\"Expected a string for typedToLowercase\");\n});\n\nexport function typedToUppercase<S extends string>(s: S): Uppercase<S> {\n if (typeof s !== \"string\") throw new StackAssertionError(\"Expected a string for typedToUppercase\", { s });\n return s.toUpperCase() as Uppercase<S>;\n}\nundefined?.test(\"typedToUppercase\", ({ expect }) => {\n expect(typedToUppercase(\"\")).toBe(\"\");\n expect(typedToUppercase(\"hello\")).toBe(\"HELLO\");\n expect(typedToUppercase(\"Hello World\")).toBe(\"HELLO WORLD\");\n expect(typedToUppercase(\"HELLO\")).toBe(\"HELLO\");\n expect(typedToUppercase(\"123\")).toBe(\"123\");\n expect(typedToUppercase(\"mixed123Case\")).toBe(\"MIXED123CASE\");\n expect(typedToUppercase(\"special@chars!\")).toBe(\"SPECIAL@CHARS!\");\n expect(() => typedToUppercase(123 as any)).toThrow(\"Expected a string for typedToUppercase\");\n});\n\nexport function typedCapitalize<S extends string>(s: S): Capitalize<S> {\n return s.charAt(0).toUpperCase() + s.slice(1) as Capitalize<S>;\n}\nundefined?.test(\"typedCapitalize\", ({ expect }) => {\n expect(typedCapitalize(\"\")).toBe(\"\");\n expect(typedCapitalize(\"hello\")).toBe(\"Hello\");\n expect(typedCapitalize(\"hello world\")).toBe(\"Hello world\");\n expect(typedCapitalize(\"HELLO\")).toBe(\"HELLO\");\n expect(typedCapitalize(\"123test\")).toBe(\"123test\");\n expect(typedCapitalize(\"already Capitalized\")).toBe(\"Already Capitalized\");\n expect(typedCapitalize(\"h\")).toBe(\"H\");\n});\n\n/**\n * Compares two strings in a way that is not dependent on the current locale.\n */\nexport function stringCompare(a: string, b: string): number {\n if (typeof a !== \"string\" || typeof b !== \"string\") throw new StackAssertionError(`Expected two strings for stringCompare, found ${typeof a} and ${typeof b}`, { a, b });\n const cmp = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;\n return cmp(a.toUpperCase(), b.toUpperCase()) || cmp(b, a);\n}\nundefined?.test(\"stringCompare\", ({ expect }) => {\n // Equal strings\n expect(stringCompare(\"a\", \"a\")).toBe(0);\n expect(stringCompare(\"\", \"\")).toBe(0);\n\n // Case comparison - note that this function is NOT case-insensitive\n // It compares uppercase versions first, then original strings\n expect(stringCompare(\"a\", \"A\")).toBe(-1); // lowercase comes after uppercase\n expect(stringCompare(\"A\", \"a\")).toBe(1); // uppercase comes before lowercase\n expect(stringCompare(\"abc\", \"ABC\")).toBe(-1);\n expect(stringCompare(\"ABC\", \"abc\")).toBe(1);\n\n // Different strings\n expect(stringCompare(\"a\", \"b\")).toBe(-1);\n expect(stringCompare(\"b\", \"a\")).toBe(1);\n\n // Strings with different lengths\n expect(stringCompare(\"abc\", \"abcd\")).toBe(-1);\n expect(stringCompare(\"abcd\", \"abc\")).toBe(1);\n\n // Strings with numbers\n expect(stringCompare(\"a1\", \"a2\")).toBe(-1);\n expect(stringCompare(\"a10\", \"a2\")).toBe(-1);\n\n // Strings with special characters\n expect(stringCompare(\"a\", \"a!\")).toBe(-1);\n expect(stringCompare(\"a!\", \"a\")).toBe(1);\n});\n\n/**\n * Returns all whitespace character at the start of the string.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function getWhitespacePrefix(s: string): string {\n return s.substring(0, s.length - s.trimStart().length);\n}\nundefined?.test(\"getWhitespacePrefix\", ({ expect }) => {\n expect(getWhitespacePrefix(\"\")).toBe(\"\");\n expect(getWhitespacePrefix(\"hello\")).toBe(\"\");\n expect(getWhitespacePrefix(\" hello\")).toBe(\" \");\n expect(getWhitespacePrefix(\" hello\")).toBe(\" \");\n expect(getWhitespacePrefix(\"\\thello\")).toBe(\"\\t\");\n expect(getWhitespacePrefix(\"\\n hello\")).toBe(\"\\n \");\n expect(getWhitespacePrefix(\" \")).toBe(\" \");\n expect(getWhitespacePrefix(\" \\t\\n\\r\")).toBe(\" \\t\\n\\r\");\n});\n\n/**\n * Returns all whitespace character at the end of the string.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function getWhitespaceSuffix(s: string): string {\n return s.substring(s.trimEnd().length);\n}\nundefined?.test(\"getWhitespaceSuffix\", ({ expect }) => {\n expect(getWhitespaceSuffix(\"\")).toBe(\"\");\n expect(getWhitespaceSuffix(\"hello\")).toBe(\"\");\n expect(getWhitespaceSuffix(\"hello \")).toBe(\" \");\n expect(getWhitespaceSuffix(\"hello \")).toBe(\" \");\n expect(getWhitespaceSuffix(\"hello\\t\")).toBe(\"\\t\");\n expect(getWhitespaceSuffix(\"hello \\n\")).toBe(\" \\n\");\n expect(getWhitespaceSuffix(\" \")).toBe(\" \");\n expect(getWhitespaceSuffix(\" \\t\\n\\r\")).toBe(\" \\t\\n\\r\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines at the start removed.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimEmptyLinesStart(s: string): string {\n const lines = s.split(\"\\n\");\n const firstNonEmptyLineIndex = lines.findIndex((line) => line.trim() !== \"\");\n // If all lines are empty or whitespace-only, return an empty string\n if (firstNonEmptyLineIndex === -1) return \"\";\n return lines.slice(firstNonEmptyLineIndex).join(\"\\n\");\n}\nundefined?.test(\"trimEmptyLinesStart\", ({ expect }) => {\n expect(trimEmptyLinesStart(\"\")).toBe(\"\");\n expect(trimEmptyLinesStart(\"hello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\n\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\" \\n\\t\\nhello\")).toBe(\"hello\");\n expect(trimEmptyLinesStart(\"\\n\\nhello\\nworld\")).toBe(\"hello\\nworld\");\n expect(trimEmptyLinesStart(\"hello\\n\\nworld\")).toBe(\"hello\\n\\nworld\");\n expect(trimEmptyLinesStart(\"hello\\nworld\\n\")).toBe(\"hello\\nworld\\n\");\n expect(trimEmptyLinesStart(\"\\n \\n\\nhello\\n \\nworld\")).toBe(\"hello\\n \\nworld\");\n // Edge case: all lines are empty\n expect(trimEmptyLinesStart(\"\\n\\n \\n\\t\")).toBe(\"\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines at the end removed.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimEmptyLinesEnd(s: string): string {\n const lines = s.split(\"\\n\");\n const lastNonEmptyLineIndex = findLastIndex(lines, (line) => line.trim() !== \"\");\n return lines.slice(0, lastNonEmptyLineIndex + 1).join(\"\\n\");\n}\nundefined?.test(\"trimEmptyLinesEnd\", ({ expect }) => {\n expect(trimEmptyLinesEnd(\"\")).toBe(\"\");\n expect(trimEmptyLinesEnd(\"hello\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n\\n\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\n \\n\\t\")).toBe(\"hello\");\n expect(trimEmptyLinesEnd(\"hello\\nworld\\n\\n\")).toBe(\"hello\\nworld\");\n expect(trimEmptyLinesEnd(\"hello\\n\\nworld\")).toBe(\"hello\\n\\nworld\");\n expect(trimEmptyLinesEnd(\"\\nhello\\nworld\")).toBe(\"\\nhello\\nworld\");\n expect(trimEmptyLinesEnd(\"hello\\n \\nworld\\n\\n \")).toBe(\"hello\\n \\nworld\");\n // Edge case: all lines are empty\n expect(trimEmptyLinesEnd(\"\\n\\n \\n\\t\")).toBe(\"\");\n});\n\n/**\n * Returns a string with all empty or whitespace-only lines trimmed at the start and end.\n *\n * Uses the same definition for whitespace as `String.prototype.trim()`.\n */\nexport function trimLines(s: string): string {\n return trimEmptyLinesEnd(trimEmptyLinesStart(s));\n}\nundefined?.test(\"trimLines\", ({ expect }) => {\n expect(trimLines(\"\")).toBe(\"\");\n expect(trimLines(\" \")).toBe(\"\");\n expect(trimLines(\" \\n \")).toBe(\"\");\n expect(trimLines(\" abc \")).toBe(\" abc \");\n expect(trimLines(\"\\n \\nLine1\\nLine2\\n \\n\")).toBe(\"Line1\\nLine2\");\n expect(trimLines(\"Line1\\n \\nLine2\")).toBe(\"Line1\\n \\nLine2\");\n expect(trimLines(\" \\n \\n\\t\")).toBe(\"\");\n expect(trimLines(\" Hello World\")).toBe(\" Hello World\");\n expect(trimLines(\"\\n\")).toBe(\"\");\n expect(trimLines(\"\\t \\n\\t\\tLine1 \\n \\nLine2\\t\\t\\n\\t \")).toBe(\"\\t\\tLine1 \\n \\nLine2\\t\\t\");\n});\n\n\n/**\n * A template literal tag that returns the same string as the template literal without a tag.\n *\n * Useful for implementing your own template literal tags.\n */\nexport function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: string[]): string {\n if (values.length !== strings.length - 1) throw new StackAssertionError(\"Invalid number of values; must be one less than strings\", { strings, values });\n\n return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');\n}\nundefined?.test(\"templateIdentity\", ({ expect }) => {\n expect(templateIdentity`Hello World`).toBe(\"Hello World\");\n expect(templateIdentity`${\"Hello\"}`).toBe(\"Hello\");\n const greeting = \"Hello\";\n const subject = \"World\";\n expect(templateIdentity`${greeting}, ${subject}!`).toBe(\"Hello, World!\");\n expect(templateIdentity`${\"A\"}${\"B\"}${\"C\"}`).toBe(\"ABC\");\n expect(templateIdentity`Start${\"\"}Middle${\"\"}End`).toBe(\"StartMiddleEnd\");\n expect(templateIdentity``).toBe(\"\");\n expect(templateIdentity`Line1\nLine2`).toBe(\"Line1\\nLine2\");\n expect(templateIdentity([\"a \", \" scientific \", \"gun\"], \"certain\", \"rail\")).toBe(\"a certain scientific railgun\");\n expect(templateIdentity([\"only one part\"])).toBe(\"only one part\");\n expect(() => templateIdentity([\"a \", \"b\", \"c\"], \"only one\")).toThrow(\"Invalid number of values\");\n expect(() => templateIdentity([\"a\", \"b\"], \"x\", \"y\")).toThrow(\"Invalid number of values\");\n});\n\n\nexport function deindent(code: string): string;\nexport function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;\nexport function deindent(strings: string | readonly string[], ...values: any[]): string {\n if (typeof strings === \"string\") return deindent([strings]);\n return templateIdentity(...deindentTemplate(strings, ...values));\n}\n\nexport function deindentTemplate(strings: TemplateStringsArray | readonly string[], ...values: any[]): [string[], ...string[]] {\n if (values.length !== strings.length - 1) throw new StackAssertionError(\"Invalid number of values; must be one less than strings\", { strings, values });\n\n const trimmedStrings = [...strings];\n trimmedStrings[0] = trimEmptyLinesStart(trimmedStrings[0] + \"+\").slice(0, -1);\n trimmedStrings[trimmedStrings.length - 1] = trimEmptyLinesEnd(\"+\" + trimmedStrings[trimmedStrings.length - 1]).slice(1);\n\n const indentation = trimmedStrings\n .join(\"${SOME_VALUE}\")\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\")\n .map((line) => getWhitespacePrefix(line).length)\n .reduce((min, current) => Math.min(min, current), Infinity);\n\n const deindentedStrings = trimmedStrings\n .map((string, stringIndex) => {\n return string\n .split(\"\\n\")\n .map((line, lineIndex) => stringIndex !== 0 && lineIndex === 0 ? line : line.substring(indentation))\n .join(\"\\n\");\n });\n\n const indentedValues = values.map((value, i) => {\n const firstLineIndentation = getWhitespacePrefix(deindentedStrings[i].split(\"\\n\").at(-1)!);\n return `${value}`.replaceAll(\"\\n\", `\\n${firstLineIndentation}`);\n });\n\n return [deindentedStrings, ...indentedValues];\n}\nundefined?.test(\"deindent\", ({ expect }) => {\n // Test with string input\n expect(deindent(\" hello\")).toBe(\"hello\");\n expect(deindent(\" hello\\n world\")).toBe(\"hello\\nworld\");\n expect(deindent(\" hello\\n world\")).toBe(\"hello\\n world\");\n expect(deindent(\"\\n hello\\n world\\n\")).toBe(\"hello\\nworld\");\n\n // Test with empty input\n expect(deindent(\"\")).toBe(\"\");\n\n // Test with template literal\n expect(deindent`\n hello\n world\n `).toBe(\"hello\\nworld\");\n\n expect(deindent`\n hello\n world\n `).toBe(\"hello\\n world\");\n\n // Test with values\n const value = \"test\";\n expect(deindent`\n hello ${value}\n world\n `).toBe(`hello ${value}\\nworld`);\n\n // Test with multiline values\n expect(deindent`\n hello\n to ${\"line1\\n line2\"}\n world\n `).toBe(`hello\\n to line1\\n line2\\nworld`);\n\n // Leading whitespace values\n expect(deindent`\n ${\" \"}A\n ${\" \"}B\n ${\" \"}C\n `).toBe(` A\\n B\\n C`);\n\n // Trailing whitespaces (note: there are two whitespaces each after A and after C)\n expect(deindent`\n A \n B ${\" \"}\n C \n `).toBe(`A \\nB \\nC `);\n\n // Test with mixed indentation\n expect(deindent`\n hello\n world\n !\n `).toBe(\"hello\\n world\\n !\");\n\n // Test error cases\n expect(() => deindent([\"a\", \"b\", \"c\"], \"too\", \"many\", \"values\")).toThrow(\"Invalid number of values\");\n});\n\nexport function extractScopes(scope: string, removeDuplicates=true): string[] {\n // TODO what is this for? can we move this into the OAuth code in the backend?\n const trimmedString = scope.trim();\n const scopesArray = trimmedString.split(/\\s+/);\n const filtered = scopesArray.filter(scope => scope.length > 0);\n return removeDuplicates ? [...new Set(filtered)] : filtered;\n}\nundefined?.test(\"extractScopes\", ({ expect }) => {\n // Test with empty string\n expect(extractScopes(\"\")).toEqual([]);\n\n // Test with single scope\n expect(extractScopes(\"read\")).toEqual([\"read\"]);\n\n // Test with multiple scopes\n expect(extractScopes(\"read write\")).toEqual([\"read\", \"write\"]);\n\n // Test with extra whitespace\n expect(extractScopes(\" read write \")).toEqual([\"read\", \"write\"]);\n\n // Test with newlines and tabs\n expect(extractScopes(\"read\\nwrite\\tdelete\")).toEqual([\"read\", \"write\", \"delete\"]);\n\n // Test with duplicates (default behavior)\n expect(extractScopes(\"read write read\")).toEqual([\"read\", \"write\"]);\n\n // Test with duplicates (explicitly set to remove)\n expect(extractScopes(\"read write read\", true)).toEqual([\"read\", \"write\"]);\n\n // Test with duplicates (explicitly set to keep)\n expect(extractScopes(\"read write read\", false)).toEqual([\"read\", \"write\", \"read\"]);\n});\n\nexport function mergeScopeStrings(...scopes: string[]): string {\n // TODO what is this for? can we move this into the OAuth code in the backend?\n const allScope = scopes.map((s) => extractScopes(s)).flat().join(\" \");\n return extractScopes(allScope).join(\" \");\n}\nundefined?.test(\"mergeScopeStrings\", ({ expect }) => {\n // Test with empty input\n expect(mergeScopeStrings()).toBe(\"\");\n\n // Test with single scope string\n expect(mergeScopeStrings(\"read write\")).toBe(\"read write\");\n\n // Test with multiple scope strings\n expect(mergeScopeStrings(\"read\", \"write\")).toBe(\"read write\");\n\n // Test with overlapping scopes\n expect(mergeScopeStrings(\"read write\", \"write delete\")).toBe(\"read write delete\");\n\n // Test with extra whitespace\n expect(mergeScopeStrings(\" read write \", \" delete \")).toBe(\"read write delete\");\n\n // Test with duplicates across strings\n expect(mergeScopeStrings(\"read write\", \"write delete\", \"read\")).toBe(\"read write delete\");\n\n // Test with empty strings\n expect(mergeScopeStrings(\"read write\", \"\", \"delete\")).toBe(\"read write delete\");\n});\n\nexport function escapeTemplateLiteral(s: string): string {\n return s.replaceAll(\"`\", \"\\\\`\").replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"$\", \"\\\\$\");\n}\nundefined?.test(\"escapeTemplateLiteral\", ({ expect }) => {\n // Test with empty string\n expect(escapeTemplateLiteral(\"\")).toBe(\"\");\n\n // Test with normal string (no special characters)\n expect(escapeTemplateLiteral(\"hello world\")).toBe(\"hello world\");\n\n // Test with backtick\n const input1 = \"hello `world`\";\n const output1 = escapeTemplateLiteral(input1);\n // Verify backticks are escaped\n expect(output1.includes(\"\\\\`\")).toBe(true);\n expect(output1).not.toBe(input1);\n\n // Test with backslash\n const input2 = \"hello \\\\world\";\n const output2 = escapeTemplateLiteral(input2);\n // Verify backslashes are escaped\n expect(output2.includes(\"\\\\\\\\\")).toBe(true);\n expect(output2).not.toBe(input2);\n\n // Test with dollar sign\n const input3 = \"hello $world\";\n const output3 = escapeTemplateLiteral(input3);\n // Verify dollar signs are escaped\n expect(output3.includes(\"\\\\$\")).toBe(true);\n expect(output3).not.toBe(input3);\n\n // Test with multiple special characters\n const input4 = \"`hello` $world\\\\\";\n const output4 = escapeTemplateLiteral(input4);\n // Verify all special characters are escaped\n expect(output4.includes(\"\\\\`\")).toBe(true);\n expect(output4.includes(\"\\\\$\")).toBe(true);\n expect(output4.includes(\"\\\\\\\\\")).toBe(true);\n expect(output4).not.toBe(input4);\n\n // Test with already escaped characters\n const input5 = \"\\\\`hello\\\\`\";\n const output5 = escapeTemplateLiteral(input5);\n // Verify already escaped characters are properly escaped\n expect(output5).not.toBe(input5);\n});\n\n/**\n * Some classes have different constructor names in different environments (eg. `Headers` is sometimes called `_Headers`,\n * so we create an object of overrides to handle these cases.\n */\nconst nicifiableClassNameOverrides = new Map(Object.entries({\n Headers,\n} as Record<string, unknown>).map(([k, v]) => [v, k]));\nexport type Nicifiable = {\n getNicifiableKeys?(): PropertyKey[],\n getNicifiedObjectExtraLines?(): string[],\n};\nexport type NicifyOptions = {\n maxDepth: number,\n currentIndent: string,\n lineIndent: string,\n multiline: boolean,\n refs: Map<unknown, string>,\n path: string,\n parent: null | {\n options: NicifyOptions,\n value: unknown,\n },\n keyInParent: PropertyKey | null,\n hideFields: PropertyKey[],\n overrides: (...args: Parameters<typeof nicify>) => string | null,\n};\nexport function nicify(\n value: unknown,\n options: Partial<NicifyOptions> = {},\n): string {\n const fullOptions: NicifyOptions = {\n maxDepth: 5,\n currentIndent: \"\",\n lineIndent: \" \",\n multiline: true,\n refs: new Map(),\n path: \"value\",\n parent: null,\n overrides: () => null,\n keyInParent: null,\n hideFields: [],\n ...filterUndefined(options),\n };\n const {\n maxDepth,\n currentIndent,\n lineIndent,\n multiline,\n refs,\n path,\n overrides,\n hideFields,\n } = fullOptions;\n const nl = `\\n${currentIndent}`;\n\n const overrideResult = overrides(value, options);\n if (overrideResult !== null) return overrideResult;\n\n if ([\"function\", \"object\", \"symbol\"].includes(typeof value) && value !== null) {\n if (refs.has(value)) {\n return `Ref<${refs.get(value)}>`;\n }\n refs.set(value, path);\n }\n\n const newOptions: NicifyOptions = {\n maxDepth: maxDepth - 1,\n currentIndent,\n lineIndent,\n multiline,\n refs,\n path: path + \"->[unknown property]\",\n overrides,\n parent: { value, options: fullOptions },\n keyInParent: null,\n hideFields: [],\n };\n const nestedNicify = (newValue: unknown, newPath: string, keyInParent: PropertyKey | null, options: Partial<NicifyOptions> = {}) => {\n return nicify(newValue, {\n ...newOptions,\n path: newPath,\n currentIndent: currentIndent + lineIndent,\n keyInParent,\n ...options,\n });\n };\n\n switch (typeof value) {\n case \"boolean\": case \"number\": {\n return JSON.stringify(value);\n }\n case \"string\": {\n const isDeindentable = (v: string) => deindent(v) === v && v.includes(\"\\n\");\n const wrapInDeindent = (v: string) => deindent`\n deindent\\`\n ${currentIndent + lineIndent}${escapeTemplateLiteral(v).replaceAll(\"\\n\", nl + lineIndent)}\n ${currentIndent}\\`\n `;\n if (isDeindentable(value)) {\n return wrapInDeindent(value);\n } else if (value.endsWith(\"\\n\") && isDeindentable(value.slice(0, -1))) {\n return wrapInDeindent(value.slice(0, -1)) + ' + \"\\\\n\"';\n } else {\n return JSON.stringify(value);\n }\n }\n case \"undefined\": {\n return \"undefined\";\n }\n case \"symbol\": {\n return value.toString();\n }\n case \"bigint\": {\n return `${value}n`;\n }\n case \"function\": {\n if (value.name) return `function ${value.name}(...) { ... }`;\n return `(...) => { ... }`;\n }\n case \"object\": {\n if (value === null) return \"null\";\n if (Array.isArray(value)) {\n const extraLines = getNicifiedObjectExtraLines(value);\n const resValueLength = value.length + extraLines.length;\n if (maxDepth <= 0 && resValueLength === 0) return \"[...]\";\n const resValues = value.map((v, i) => nestedNicify(v, `${path}[${i}]`, i));\n resValues.push(...extraLines);\n if (resValues.length !== resValueLength) throw new StackAssertionError(\"nicify of object: resValues.length !== resValueLength\", { value, resValues, resValueLength });\n const shouldIndent = resValues.length > 4 || resValues.some(x => (resValues.length > 1 && x.length > 4) || x.includes(\"\\n\"));\n if (shouldIndent) {\n return `[${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join(\"\")}]`;\n } else {\n return `[${resValues.join(\", \")}]`;\n }\n }\n if (value instanceof URL) {\n return `URL(${nestedNicify(value.toString(), `${path}.toString()`, null)})`;\n }\n if (ArrayBuffer.isView(value)) {\n return `${value.constructor.name}([${value.toString()}])`;\n }\n if (value instanceof ArrayBuffer) {\n return `ArrayBuffer [${new Uint8Array(value).toString()}]`;\n }\n if (value instanceof Error) {\n let stack = value.stack ?? \"\";\n const toString = value.toString();\n if (!stack.startsWith(toString)) stack = `${toString}\\n${stack}`; // some browsers don't include the error message in the stack, some do\n stack = stack.trimEnd();\n stack = stack.replace(/\\n\\s+/g, `\\n${lineIndent}${lineIndent}`);\n stack = stack.replace(\"\\n\", `\\n${lineIndent}Stack:\\n`);\n if (Object.keys(value).length > 0) {\n stack += `\\n${lineIndent}Extra properties: ${nestedNicify(Object.fromEntries(Object.entries(value)), path, null)}`;\n }\n if (value.cause) {\n stack += `\\n${lineIndent}Cause:\\n${lineIndent}${lineIndent}${nestedNicify(value.cause, path, null, { currentIndent: currentIndent + lineIndent + lineIndent })}`;\n }\n stack = stack.replaceAll(\"\\n\", `\\n${currentIndent}`);\n return stack;\n }\n\n const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);\n const constructorString = constructorName ? `${constructorName} ` : \"\";\n\n const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));\n const extraLines = [\n ...getNicifiedObjectExtraLines(value),\n ...hideFields.length > 0 ? [`<some fields may have been hidden>`] : [],\n ];\n const resValueLength = entries.length + extraLines.length;\n if (resValueLength === 0) return `${constructorString}{}`;\n if (maxDepth <= 0) return `${constructorString}{ ... }`;\n const resValues = entries.map(([k, v], keyIndex) => {\n const keyNicified = nestedNicify(k, `Object.keys(${path})[${keyIndex}]`, null);\n const keyInObjectLiteral = typeof k === \"string\" ? nicifyPropertyString(k) : `[${keyNicified}]`;\n if (typeof v === \"function\" && v.name === k) {\n return `${keyInObjectLiteral}(...): { ... }`;\n } else {\n return `${keyInObjectLiteral}: ${nestedNicify(v, `${path}[${keyNicified}]`, k)}`;\n }\n });\n resValues.push(...extraLines);\n if (resValues.length !== resValueLength) throw new StackAssertionError(\"nicify of object: resValues.length !== resValueLength\", { value, resValues, resValueLength });\n const shouldIndent = resValues.length > 1 || resValues.some(x => x.includes(\"\\n\"));\n\n if (resValues.length === 0) return `${constructorString}{}`;\n if (shouldIndent) {\n return `${constructorString}{${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join(\"\")}}`;\n } else {\n return `${constructorString}{ ${resValues.join(\", \")} }`;\n }\n }\n default: {\n return `${typeof value}<${value}>`;\n }\n }\n}\n\nexport function replaceAll(input: string, searchValue: string, replaceValue: string): string {\n if (searchValue === \"\") throw new StackAssertionError(\"replaceAll: searchValue is empty\");\n return input.split(searchValue).join(replaceValue);\n}\nundefined?.test(\"replaceAll\", ({ expect }) => {\n expect(replaceAll(\"hello world\", \"o\", \"x\")).toBe(\"hellx wxrld\");\n expect(replaceAll(\"aaa\", \"a\", \"b\")).toBe(\"bbb\");\n expect(replaceAll(\"\", \"a\", \"b\")).toBe(\"\");\n expect(replaceAll(\"abc\", \"b\", \"\")).toBe(\"ac\");\n expect(replaceAll(\"test.test.test\", \".\", \"_\")).toBe(\"test_test_test\");\n expect(replaceAll(\"a.b*c\", \".\", \"x\")).toBe(\"axb*c\");\n expect(replaceAll(\"a*b*c\", \"*\", \"x\")).toBe(\"axbxc\");\n expect(replaceAll(\"hello hello\", \"hello\", \"hi\")).toBe(\"hi hi\");\n});\n\nfunction nicifyPropertyString(str: string) {\n return JSON.stringify(str);\n}\nundefined?.test(\"nicifyPropertyString\", ({ expect }) => {\n // Test valid identifiers\n expect(nicifyPropertyString(\"validName\")).toBe('\"validName\"');\n expect(nicifyPropertyString(\"_validName\")).toBe('\"_validName\"');\n expect(nicifyPropertyString(\"valid123Name\")).toBe('\"valid123Name\"');\n\n // Test invalid identifiers\n expect(nicifyPropertyString(\"123invalid\")).toBe('\"123invalid\"');\n expect(nicifyPropertyString(\"invalid-name\")).toBe('\"invalid-name\"');\n expect(nicifyPropertyString(\"invalid space\")).toBe('\"invalid space\"');\n expect(nicifyPropertyString(\"$invalid\")).toBe('\"$invalid\"');\n expect(nicifyPropertyString(\"\")).toBe('\"\"');\n\n // Test with special characters\n expect(nicifyPropertyString(\"property!\")).toBe('\"property!\"');\n expect(nicifyPropertyString(\"property.name\")).toBe('\"property.name\"');\n\n // Test with escaped characters\n expect(nicifyPropertyString(\"\\\\\")).toBe('\"\\\\\\\\\"');\n expect(nicifyPropertyString('\"')).toBe('\"\\\\\"\"');\n});\n\nfunction getNicifiableKeys(value: Nicifiable | object) {\n const overridden = (\"getNicifiableKeys\" in value ? value.getNicifiableKeys?.bind(value) : null)?.();\n if (overridden != null) return overridden;\n if (value instanceof Response) {\n return ['status', 'headers'];\n }\n const keys = Object.keys(value).sort();\n return unique(keys);\n}\nundefined?.test(\"getNicifiableKeys\", ({ expect }) => {\n // Test regular object\n expect(getNicifiableKeys({ b: 1, a: 2, c: 3 })).toEqual([\"a\", \"b\", \"c\"]);\n\n // Test empty object\n expect(getNicifiableKeys({})).toEqual([]);\n\n\n expect(getNicifiableKeys(new Response())).toEqual([\"status\", \"headers\"]);\n\n // Test object with custom getNicifiableKeys\n const customObject = {\n a: 1,\n b: 2,\n getNicifiableKeys() {\n return [\"customKey1\", \"customKey2\"];\n }\n };\n expect(getNicifiableKeys(customObject)).toEqual([\"customKey1\", \"customKey2\"]);\n});\n\nfunction getNicifiableEntries(value: Nicifiable | object): [PropertyKey, unknown][] {\n const recordLikes = [Headers];\n function isRecordLike(value: unknown): value is InstanceType<typeof recordLikes[number]> {\n return recordLikes.some(x => value instanceof x);\n }\n\n if (isRecordLike(value)) {\n return [...value.entries()].sort(([a], [b]) => stringCompare(`${a}`, `${b}`));\n }\n const keys = getNicifiableKeys(value);\n return keys.map((k) => [k, value[k as never]] as [PropertyKey, unknown]);\n}\n\nfunction getNicifiedObjectExtraLines(value: Nicifiable | object) {\n return (\"getNicifiedObjectExtraLines\" in value ? value.getNicifiedObjectExtraLines : null)?.() ?? [];\n}\n"],"mappings":"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","names":["a","b","scope","options","extraLines","resValueLength","resValues","shouldIndent","value"]}
|