@stackframe/stack-shared 2.8.35 → 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.
Files changed (74) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/config/schema.d.mts +167 -107
  3. package/dist/config/schema.d.ts +167 -107
  4. package/dist/config/schema.js +39 -25
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/esm/config/schema.js +39 -25
  7. package/dist/esm/config/schema.js.map +1 -1
  8. package/dist/esm/helpers/vault/client-side.js +46 -0
  9. package/dist/esm/helpers/vault/client-side.js.map +1 -0
  10. package/dist/esm/helpers/vault/server-side.js +92 -0
  11. package/dist/esm/helpers/vault/server-side.js.map +1 -0
  12. package/dist/esm/hooks/use-hover.js +71 -0
  13. package/dist/esm/hooks/use-hover.js.map +1 -0
  14. package/dist/esm/interface/admin-interface.js +21 -3
  15. package/dist/esm/interface/admin-interface.js.map +1 -1
  16. package/dist/esm/interface/server-interface.js +38 -0
  17. package/dist/esm/interface/server-interface.js.map +1 -1
  18. package/dist/esm/known-errors.js +38 -1
  19. package/dist/esm/known-errors.js.map +1 -1
  20. package/dist/esm/utils/bytes.js +1 -2
  21. package/dist/esm/utils/bytes.js.map +1 -1
  22. package/dist/esm/utils/crypto.js +83 -2
  23. package/dist/esm/utils/crypto.js.map +1 -1
  24. package/dist/esm/utils/numbers.js.map +1 -1
  25. package/dist/esm/utils/react.js +7 -3
  26. package/dist/esm/utils/react.js.map +1 -1
  27. package/dist/helpers/password.d.mts +4 -4
  28. package/dist/helpers/password.d.ts +4 -4
  29. package/dist/helpers/vault/client-side.d.mts +14 -0
  30. package/dist/helpers/vault/client-side.d.ts +14 -0
  31. package/dist/helpers/vault/client-side.js +73 -0
  32. package/dist/helpers/vault/client-side.js.map +1 -0
  33. package/dist/helpers/vault/server-side.d.mts +7 -0
  34. package/dist/helpers/vault/server-side.d.ts +7 -0
  35. package/dist/helpers/vault/server-side.js +111 -0
  36. package/dist/helpers/vault/server-side.js.map +1 -0
  37. package/dist/hooks/use-hover.d.mts +6 -0
  38. package/dist/hooks/use-hover.d.ts +6 -0
  39. package/dist/hooks/use-hover.js +96 -0
  40. package/dist/hooks/use-hover.js.map +1 -0
  41. package/dist/index.d.mts +3 -3
  42. package/dist/index.d.ts +3 -3
  43. package/dist/interface/admin-interface.d.mts +11 -5
  44. package/dist/interface/admin-interface.d.ts +11 -5
  45. package/dist/interface/admin-interface.js +21 -3
  46. package/dist/interface/admin-interface.js.map +1 -1
  47. package/dist/interface/crud/current-user.d.mts +1 -1
  48. package/dist/interface/crud/current-user.d.ts +1 -1
  49. package/dist/interface/crud/project-api-keys.d.mts +4 -4
  50. package/dist/interface/crud/project-api-keys.d.ts +4 -4
  51. package/dist/interface/crud/team-member-profiles.d.mts +2 -2
  52. package/dist/interface/crud/team-member-profiles.d.ts +2 -2
  53. package/dist/interface/crud/users.d.mts +2 -2
  54. package/dist/interface/crud/users.d.ts +2 -2
  55. package/dist/interface/server-interface.d.mts +2 -0
  56. package/dist/interface/server-interface.d.ts +2 -0
  57. package/dist/interface/server-interface.js +38 -0
  58. package/dist/interface/server-interface.js.map +1 -1
  59. package/dist/known-errors.d.mts +9 -0
  60. package/dist/known-errors.d.ts +9 -0
  61. package/dist/known-errors.js +38 -1
  62. package/dist/known-errors.js.map +1 -1
  63. package/dist/schema-fields.d.mts +1 -1
  64. package/dist/schema-fields.d.ts +1 -1
  65. package/dist/utils/bytes.js +1 -2
  66. package/dist/utils/bytes.js.map +1 -1
  67. package/dist/utils/crypto.d.mts +31 -1
  68. package/dist/utils/crypto.d.ts +31 -1
  69. package/dist/utils/crypto.js +87 -2
  70. package/dist/utils/crypto.js.map +1 -1
  71. package/dist/utils/numbers.js.map +1 -1
  72. package/dist/utils/react.js +7 -3
  73. package/dist/utils/react.js.map +1 -1
  74. package/package.json +2 -1
@@ -20,13 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/utils/crypto.tsx
21
21
  var crypto_exports = {};
22
22
  __export(crypto_exports, {
23
+ decrypt: () => decrypt,
24
+ encrypt: () => encrypt,
23
25
  generateRandomValues: () => generateRandomValues,
24
- generateSecureRandomString: () => generateSecureRandomString
26
+ generateSecureRandomString: () => generateSecureRandomString,
27
+ hash: () => hash,
28
+ iteratedHash: () => iteratedHash
25
29
  });
26
30
  module.exports = __toCommonJS(crypto_exports);
27
31
  var import_bytes = require("./bytes.js");
28
32
  var import_errors = require("./errors.js");
29
33
  var import_globals = require("./globals.js");
34
+ var import_results = require("./results.js");
30
35
  function generateRandomValues(array) {
31
36
  if (!import_globals.globalVar.crypto) {
32
37
  throw new import_errors.StackAssertionError("Crypto API is not available in this environment. Are you using an old browser?");
@@ -43,9 +48,89 @@ function generateSecureRandomString(minBitsOfEntropy = 224) {
43
48
  const str = (0, import_bytes.encodeBase32)(randomBytes);
44
49
  return str.slice(str.length - base32CharactersCount).toLowerCase();
45
50
  }
51
+ async function getDerivedSymmetricKey(purpose, secret, salt) {
52
+ const originalSecretKey = await crypto.subtle.importKey("raw", typeof secret === "string" ? new TextEncoder().encode(secret) : secret, "HKDF", false, ["deriveKey"]);
53
+ return await crypto.subtle.deriveKey(
54
+ {
55
+ name: "HKDF",
56
+ salt,
57
+ hash: "SHA-256",
58
+ info: new TextEncoder().encode(JSON.stringify([
59
+ "stack-crypto-helper-derived-symmetric-key",
60
+ purpose,
61
+ typeof secret === "string" ? "string-key" : "binary-key",
62
+ (0, import_bytes.encodeBase64)(salt)
63
+ ]))
64
+ },
65
+ originalSecretKey,
66
+ { name: "AES-GCM", length: 256 },
67
+ false,
68
+ ["encrypt", "decrypt"]
69
+ );
70
+ }
71
+ async function encrypt({ purpose, secret, value }) {
72
+ const iv = crypto.getRandomValues(new Uint8Array(12));
73
+ const salt = crypto.getRandomValues(new Uint8Array(16));
74
+ const derivedSecretKey = await getDerivedSymmetricKey(purpose, secret, salt);
75
+ const cipher = await crypto.subtle.encrypt({
76
+ name: "AES-GCM",
77
+ iv
78
+ }, derivedSecretKey, value);
79
+ const version = [1, 0];
80
+ return new Uint8Array([...version, ...salt, ...iv, ...new Uint8Array(cipher)]);
81
+ }
82
+ async function decrypt({ purpose, secret, cipher }) {
83
+ const version = cipher.slice(0, 2);
84
+ if (version[0] !== 1 || version[1] !== 0) throw new import_errors.StackAssertionError("Invalid ciphertext version in decrypt(...); expected 0x0100", { purpose });
85
+ const salt = cipher.slice(2, 18);
86
+ const iv = cipher.slice(18, 30);
87
+ const cipherBytes = cipher.slice(30);
88
+ const derivedSecretKey = await getDerivedSymmetricKey(purpose, secret, salt);
89
+ try {
90
+ const plaintext = await crypto.subtle.decrypt({
91
+ name: "AES-GCM",
92
+ iv
93
+ }, derivedSecretKey, cipherBytes);
94
+ return import_results.Result.ok(new Uint8Array(plaintext));
95
+ } catch (e) {
96
+ if (e instanceof DOMException && e.name === "OperationError") {
97
+ return import_results.Result.error(new Error("Invalid ciphertext or secret when decrypting encrypted value", { cause: e }));
98
+ }
99
+ throw e;
100
+ }
101
+ }
102
+ async function hash(options) {
103
+ return await iteratedHash({ ...options, iterations: 1 });
104
+ }
105
+ async function iteratedHash(options) {
106
+ const stringOrUint8ArrayToUint8Array = (value) => typeof value === "string" ? new TextEncoder().encode(value) : value;
107
+ const stringOrUint8ArrayToBase64 = (value) => (0, import_bytes.encodeBase64)(stringOrUint8ArrayToUint8Array(value));
108
+ const input = await crypto.subtle.importKey(
109
+ "raw",
110
+ stringOrUint8ArrayToUint8Array(options.value),
111
+ "PBKDF2",
112
+ false,
113
+ ["deriveBits"]
114
+ );
115
+ return new Uint8Array(await crypto.subtle.deriveBits({
116
+ name: "PBKDF2",
117
+ salt: new TextEncoder().encode(JSON.stringify([
118
+ "stack-crypto-helper-iterated-hash",
119
+ options.purpose,
120
+ stringOrUint8ArrayToBase64(options.salt ?? ""),
121
+ stringOrUint8ArrayToBase64(options.extra ?? "")
122
+ ])),
123
+ iterations: options.iterations,
124
+ hash: "SHA-256"
125
+ }, input, 256));
126
+ }
46
127
  // Annotate the CommonJS export names for ESM import in node:
47
128
  0 && (module.exports = {
129
+ decrypt,
130
+ encrypt,
48
131
  generateRandomValues,
49
- generateSecureRandomString
132
+ generateSecureRandomString,
133
+ hash,
134
+ iteratedHash
50
135
  });
51
136
  //# sourceMappingURL=crypto.js.map
@@ -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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6B;AAC7B,oBAAoC;AACpC,qBAA0B;AAEnB,SAAS,qBAAqB,OAAiC;AACpE,MAAI,CAAC,yBAAU,QAAQ;AACrB,UAAM,IAAI,kCAAoB,gFAAgF;AAAA,EAChH;AACA,MAAI,CAAC,yBAAU,OAAO,iBAAiB;AACrC,UAAM,IAAI,kCAAoB,4FAA4F;AAAA,EAC5H;AACA,SAAO,yBAAU,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,UAAM,2BAAa,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 +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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA0BO,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":[]}
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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":[]}
@@ -95,13 +95,17 @@ function useRefState(initialValue) {
95
95
  const ref = import_react.default.useRef(initialValue);
96
96
  const setValue = import_react.default.useCallback((updater) => {
97
97
  const value = typeof updater === "function" ? updater(ref.current) : updater;
98
- setState(value);
98
+ console.log("setValue", ref.current);
99
99
  ref.current = value;
100
+ console.log("setValue", ref.current);
101
+ setState(value);
100
102
  }, []);
101
103
  const res = import_react.default.useMemo(() => ({
102
- current: ref.current,
104
+ get current() {
105
+ return ref.current;
106
+ },
103
107
  set: setValue
104
- }), [ref.current, setValue]);
108
+ }), [setValue]);
105
109
  return res;
106
110
  }
107
111
  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":["React"]}
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":"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","names":["React"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.8.35",
3
+ "version": "2.8.36",
4
4
  "files": [
5
5
  "README.md",
6
6
  "dist",
@@ -45,6 +45,7 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
+ "@aws-sdk/client-kms": "^3.876.0",
48
49
  "@opentelemetry/api": "^1.9.0",
49
50
  "@simplewebauthn/browser": "^11.0.0",
50
51
  "async-mutex": "^0.5.0",