@hexclave/shared 1.0.3 → 1.0.5

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 (343) hide show
  1. package/dist/ai/unified-prompts/reminders.js +4 -3
  2. package/dist/ai/unified-prompts/reminders.js.map +1 -1
  3. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
  4. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
  5. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +216 -29
  6. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  7. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
  8. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
  9. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
  10. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
  11. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
  12. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
  13. package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
  14. package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
  15. package/dist/config/schema.d.ts +71 -71
  16. package/dist/config/schema.d.ts.map +1 -1
  17. package/dist/config/schema.js +5 -5
  18. package/dist/config/schema.js.map +1 -1
  19. package/dist/config-authoring.d.ts +4 -4
  20. package/dist/config-authoring.d.ts.map +1 -1
  21. package/dist/config-authoring.js +2 -2
  22. package/dist/config-authoring.js.map +1 -1
  23. package/dist/config-rendering.d.ts +4 -4
  24. package/dist/config-rendering.d.ts.map +1 -1
  25. package/dist/config-rendering.js +19 -18
  26. package/dist/config-rendering.js.map +1 -1
  27. package/dist/esm/ai/unified-prompts/reminders.js +4 -3
  28. package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
  29. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
  30. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
  31. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +215 -30
  32. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  33. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
  34. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
  35. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
  36. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
  37. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
  38. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
  39. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
  40. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
  41. package/dist/esm/config/schema.d.ts +71 -71
  42. package/dist/esm/config/schema.d.ts.map +1 -1
  43. package/dist/esm/config/schema.js +5 -5
  44. package/dist/esm/config/schema.js.map +1 -1
  45. package/dist/esm/config-authoring.d.ts +4 -4
  46. package/dist/esm/config-authoring.d.ts.map +1 -1
  47. package/dist/esm/config-authoring.js +2 -2
  48. package/dist/esm/config-authoring.js.map +1 -1
  49. package/dist/esm/config-rendering.d.ts +4 -4
  50. package/dist/esm/config-rendering.d.ts.map +1 -1
  51. package/dist/esm/config-rendering.js +15 -14
  52. package/dist/esm/config-rendering.js.map +1 -1
  53. package/dist/esm/helpers/vault/client-side.d.ts.map +1 -1
  54. package/dist/esm/helpers/vault/client-side.js.map +1 -1
  55. package/dist/esm/helpers/vault/server-side.d.ts.map +1 -1
  56. package/dist/esm/helpers/vault/server-side.js.map +1 -1
  57. package/dist/esm/hexclave-config-file.d.ts +15 -0
  58. package/dist/esm/hexclave-config-file.d.ts.map +1 -0
  59. package/dist/esm/{stack-config-file.js → hexclave-config-file.js} +7 -7
  60. package/dist/esm/hexclave-config-file.js.map +1 -0
  61. package/dist/esm/hooks/use-async-callback.js +1 -1
  62. package/dist/esm/hooks/use-async-external-store.js +1 -1
  63. package/dist/esm/hooks/use-strict-memo.js +1 -1
  64. package/dist/esm/interface/admin-metrics.d.ts +4 -4
  65. package/dist/esm/interface/admin-metrics.js.map +1 -1
  66. package/dist/esm/interface/client-interface.js +2 -2
  67. package/dist/esm/interface/client-interface.js.map +1 -1
  68. package/dist/esm/interface/conversations.d.ts +13 -13
  69. package/dist/esm/interface/crud/current-user.d.ts +9 -9
  70. package/dist/esm/interface/crud/email-outbox.d.ts +112 -112
  71. package/dist/esm/interface/crud/products.d.ts +1 -1
  72. package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
  73. package/dist/esm/interface/crud/project-permissions.js +5 -5
  74. package/dist/esm/interface/crud/project-permissions.js.map +1 -1
  75. package/dist/esm/interface/crud/projects.d.ts +28 -28
  76. package/dist/esm/interface/crud/team-member-profiles.d.ts +22 -22
  77. package/dist/esm/interface/crud/team-permissions.js +5 -5
  78. package/dist/esm/interface/crud/team-permissions.js.map +1 -1
  79. package/dist/esm/interface/crud/users.d.ts +14 -14
  80. package/dist/esm/interface/page-component-versions.js +107 -107
  81. package/dist/esm/interface/page-component-versions.js.map +1 -1
  82. package/dist/esm/interface/webhooks.d.ts +4 -4
  83. package/dist/esm/known-errors.js +4 -4
  84. package/dist/esm/known-errors.js.map +1 -1
  85. package/dist/esm/schema-fields.d.ts +15 -5
  86. package/dist/esm/schema-fields.d.ts.map +1 -1
  87. package/dist/esm/schema-fields.js +26 -16
  88. package/dist/esm/schema-fields.js.map +1 -1
  89. package/dist/esm/utils/crypto.d.ts.map +1 -1
  90. package/dist/esm/utils/crypto.js.map +1 -1
  91. package/dist/esm/utils/currency-constants.d.ts +6 -0
  92. package/dist/esm/utils/currency-constants.d.ts.map +1 -1
  93. package/dist/esm/utils/currency-constants.js.map +1 -1
  94. package/dist/esm/utils/dev-env-state-path.d.ts +3 -3
  95. package/dist/esm/utils/dev-env-state-path.d.ts.map +1 -1
  96. package/dist/esm/utils/dev-env-state-path.js +4 -4
  97. package/dist/esm/utils/dev-env-state-path.js.map +1 -1
  98. package/dist/esm/utils/errors.js +2 -2
  99. package/dist/esm/utils/errors.js.map +1 -1
  100. package/dist/esm/utils/esbuild.d.ts.map +1 -1
  101. package/dist/esm/utils/esbuild.js +0 -1
  102. package/dist/esm/utils/esbuild.js.map +1 -1
  103. package/dist/esm/utils/featurebase.d.ts +3 -3
  104. package/dist/esm/utils/featurebase.d.ts.map +1 -1
  105. package/dist/esm/utils/featurebase.js +15 -15
  106. package/dist/esm/utils/featurebase.js.map +1 -1
  107. package/dist/esm/utils/fs.js +6 -6
  108. package/dist/esm/utils/fs.js.map +1 -1
  109. package/dist/esm/utils/globals.d.ts.map +1 -1
  110. package/dist/esm/utils/globals.js +10 -10
  111. package/dist/esm/utils/globals.js.map +1 -1
  112. package/dist/esm/utils/jwt.d.ts.map +1 -1
  113. package/dist/esm/utils/jwt.js +3 -3
  114. package/dist/esm/utils/jwt.js.map +1 -1
  115. package/dist/esm/utils/redirect-urls.d.ts +3 -3
  116. package/dist/esm/utils/redirect-urls.d.ts.map +1 -1
  117. package/dist/esm/utils/redirect-urls.js +5 -5
  118. package/dist/esm/utils/redirect-urls.js.map +1 -1
  119. package/dist/esm/utils/urls.js.map +1 -1
  120. package/dist/helpers/vault/client-side.d.ts.map +1 -1
  121. package/dist/helpers/vault/client-side.js.map +1 -1
  122. package/dist/helpers/vault/server-side.d.ts.map +1 -1
  123. package/dist/helpers/vault/server-side.js.map +1 -1
  124. package/dist/hexclave-config-file.d.ts +15 -0
  125. package/dist/hexclave-config-file.d.ts.map +1 -0
  126. package/dist/{stack-config-file.js → hexclave-config-file.js} +8 -8
  127. package/dist/hexclave-config-file.js.map +1 -0
  128. package/dist/hooks/use-async-callback.js +1 -1
  129. package/dist/hooks/use-async-external-store.js +1 -1
  130. package/dist/hooks/use-strict-memo.js +1 -1
  131. package/dist/interface/admin-metrics.d.ts +4 -4
  132. package/dist/interface/admin-metrics.js.map +1 -1
  133. package/dist/interface/client-interface.js +2 -2
  134. package/dist/interface/client-interface.js.map +1 -1
  135. package/dist/interface/conversations.d.ts +13 -13
  136. package/dist/interface/crud/current-user.d.ts +9 -9
  137. package/dist/interface/crud/email-outbox.d.ts +112 -112
  138. package/dist/interface/crud/products.d.ts +1 -1
  139. package/dist/interface/crud/project-api-keys.d.ts +2 -2
  140. package/dist/interface/crud/project-permissions.js +5 -5
  141. package/dist/interface/crud/project-permissions.js.map +1 -1
  142. package/dist/interface/crud/projects.d.ts +28 -28
  143. package/dist/interface/crud/team-member-profiles.d.ts +22 -22
  144. package/dist/interface/crud/team-permissions.js +5 -5
  145. package/dist/interface/crud/team-permissions.js.map +1 -1
  146. package/dist/interface/crud/users.d.ts +14 -14
  147. package/dist/interface/page-component-versions.js +107 -107
  148. package/dist/interface/page-component-versions.js.map +1 -1
  149. package/dist/interface/webhooks.d.ts +4 -4
  150. package/dist/known-errors.js +4 -4
  151. package/dist/known-errors.js.map +1 -1
  152. package/dist/schema-fields.d.ts +15 -5
  153. package/dist/schema-fields.d.ts.map +1 -1
  154. package/dist/schema-fields.js +26 -16
  155. package/dist/schema-fields.js.map +1 -1
  156. package/dist/utils/crypto.d.ts.map +1 -1
  157. package/dist/utils/crypto.js.map +1 -1
  158. package/dist/utils/currency-constants.d.ts +6 -0
  159. package/dist/utils/currency-constants.d.ts.map +1 -1
  160. package/dist/utils/currency-constants.js.map +1 -1
  161. package/dist/utils/dev-env-state-path.d.ts +3 -3
  162. package/dist/utils/dev-env-state-path.d.ts.map +1 -1
  163. package/dist/utils/dev-env-state-path.js +5 -5
  164. package/dist/utils/dev-env-state-path.js.map +1 -1
  165. package/dist/utils/errors.js +2 -2
  166. package/dist/utils/errors.js.map +1 -1
  167. package/dist/utils/esbuild.d.ts.map +1 -1
  168. package/dist/utils/esbuild.js +0 -1
  169. package/dist/utils/esbuild.js.map +1 -1
  170. package/dist/utils/featurebase.d.ts +3 -3
  171. package/dist/utils/featurebase.d.ts.map +1 -1
  172. package/dist/utils/featurebase.js +15 -15
  173. package/dist/utils/featurebase.js.map +1 -1
  174. package/dist/utils/fs.js.map +1 -1
  175. package/dist/utils/globals.d.ts.map +1 -1
  176. package/dist/utils/globals.js +10 -10
  177. package/dist/utils/globals.js.map +1 -1
  178. package/dist/utils/jwt.d.ts.map +1 -1
  179. package/dist/utils/jwt.js +3 -3
  180. package/dist/utils/jwt.js.map +1 -1
  181. package/dist/utils/redirect-urls.d.ts +3 -3
  182. package/dist/utils/redirect-urls.d.ts.map +1 -1
  183. package/dist/utils/redirect-urls.js +5 -5
  184. package/dist/utils/redirect-urls.js.map +1 -1
  185. package/dist/utils/urls.js.map +1 -1
  186. package/package.json +2 -1
  187. package/src/ai/image-limits.ts +62 -0
  188. package/src/ai/llms/llms.ts +36 -0
  189. package/src/ai/unified-prompts/reminders.ts +27 -0
  190. package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +1047 -0
  191. package/src/ai/unified-prompts/skill-site-prompt-parts/brain.ts +5 -0
  192. package/src/ai/unified-prompts/skill-site-prompt-parts/cli-help.ts +5 -0
  193. package/src/ai/unified-prompts/skill-site-prompt-parts/config-docs.ts +5 -0
  194. package/src/ai/unified-prompts/skill-site-prompt-parts/custom-components-instructions.ts +5 -0
  195. package/src/ai/unified-prompts/skill-site-prompt-parts/dashboard-instructions.ts +5 -0
  196. package/src/ai/unified-prompts/skill-site-prompt-parts/docs-index.ts +80 -0
  197. package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +322 -0
  198. package/src/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.ts +5 -0
  199. package/src/ai/unified-prompts/skill-site-prompt-parts/sdk-interface-source.ts +5 -0
  200. package/src/ai/unified-prompts/skill-site-prompt.ts +72 -0
  201. package/src/apps/apps-config.ts +177 -0
  202. package/src/apps/apps-ui.tsx +112 -0
  203. package/src/config/README.md +154 -0
  204. package/src/config/db-sync-mappings.ts +2293 -0
  205. package/src/config/format.ts +388 -0
  206. package/src/config/migrate-catalogs-to-product-lines.ts +475 -0
  207. package/src/config/schema-fuzzer.test.ts +377 -0
  208. package/src/config/schema.ts +1507 -0
  209. package/src/config-authoring.test.ts +52 -0
  210. package/src/config-authoring.ts +37 -0
  211. package/src/config-rendering.ts +142 -0
  212. package/src/crud.tsx +275 -0
  213. package/src/global.d.ts +1 -0
  214. package/src/helpers/emails.ts +250 -0
  215. package/src/helpers/init-prompt.ts +39 -0
  216. package/src/helpers/password.ts +16 -0
  217. package/src/helpers/production-mode.ts +58 -0
  218. package/src/helpers/support-sla.test.ts +124 -0
  219. package/src/helpers/support-sla.ts +93 -0
  220. package/src/helpers/vault/client-side.ts +92 -0
  221. package/src/helpers/vault/server-side.ts +153 -0
  222. package/src/hexclave-config-file.ts +120 -0
  223. package/src/hooks/use-async-callback.tsx +43 -0
  224. package/src/hooks/use-async-external-store.tsx +24 -0
  225. package/src/hooks/use-hash.tsx +14 -0
  226. package/src/hooks/use-hover.tsx +88 -0
  227. package/src/hooks/use-strict-memo.tsx +157 -0
  228. package/src/index.ts +17 -0
  229. package/src/interface/admin-interface.ts +1043 -0
  230. package/src/interface/admin-metrics.ts +214 -0
  231. package/src/interface/client-interface.test.ts +765 -0
  232. package/src/interface/client-interface.ts +2586 -0
  233. package/src/interface/conversations.ts +109 -0
  234. package/src/interface/crud/analytics.ts +11 -0
  235. package/src/interface/crud/connected-accounts.ts +52 -0
  236. package/src/interface/crud/contact-channels.ts +74 -0
  237. package/src/interface/crud/current-user.ts +72 -0
  238. package/src/interface/crud/email-outbox.ts +263 -0
  239. package/src/interface/crud/email-templates.ts +50 -0
  240. package/src/interface/crud/emails.ts +22 -0
  241. package/src/interface/crud/internal-api-keys.ts +69 -0
  242. package/src/interface/crud/invoices.ts +36 -0
  243. package/src/interface/crud/items.ts +24 -0
  244. package/src/interface/crud/notification-preferences.ts +33 -0
  245. package/src/interface/crud/oauth-providers.ts +91 -0
  246. package/src/interface/crud/products.ts +42 -0
  247. package/src/interface/crud/project-api-keys.ts +100 -0
  248. package/src/interface/crud/project-permissions.ts +118 -0
  249. package/src/interface/crud/projects.ts +229 -0
  250. package/src/interface/crud/session-replays.ts +85 -0
  251. package/src/interface/crud/sessions.ts +66 -0
  252. package/src/interface/crud/sign-up-rules.ts +12 -0
  253. package/src/interface/crud/svix-token.ts +20 -0
  254. package/src/interface/crud/team-invitation-details.ts +22 -0
  255. package/src/interface/crud/team-invitation.ts +35 -0
  256. package/src/interface/crud/team-member-profiles.ts +61 -0
  257. package/src/interface/crud/team-memberships.ts +56 -0
  258. package/src/interface/crud/team-permissions.ts +119 -0
  259. package/src/interface/crud/teams.ts +142 -0
  260. package/src/interface/crud/transactions.ts +131 -0
  261. package/src/interface/crud/users.ts +185 -0
  262. package/src/interface/handler-urls.ts +74 -0
  263. package/src/interface/page-component-versions.ts +1702 -0
  264. package/src/interface/server-interface.ts +1146 -0
  265. package/src/interface/webhooks.ts +28 -0
  266. package/src/known-errors.tsx +2031 -0
  267. package/src/local-emulator.ts +5 -0
  268. package/src/payments/payment-methods.ts +167 -0
  269. package/src/payments/stripe-limits.ts +18 -0
  270. package/src/plans.ts +107 -0
  271. package/src/schema-fields.ts +944 -0
  272. package/src/sessions.ts +310 -0
  273. package/src/utils/api-keys.tsx +122 -0
  274. package/src/utils/arrays.tsx +248 -0
  275. package/src/utils/auth-methods.ts +8 -0
  276. package/src/utils/base64.tsx +24 -0
  277. package/src/utils/booleans.tsx +34 -0
  278. package/src/utils/browser-compat.tsx +17 -0
  279. package/src/utils/bytes.tsx +280 -0
  280. package/src/utils/caches.tsx +280 -0
  281. package/src/utils/cel-fields.ts +92 -0
  282. package/src/utils/cloud-hosts.tsx +92 -0
  283. package/src/utils/compile-time.tsx +11 -0
  284. package/src/utils/country-codes.ts +39 -0
  285. package/src/utils/crypto.tsx +174 -0
  286. package/src/utils/currencies.tsx +53 -0
  287. package/src/utils/currency-constants.tsx +51 -0
  288. package/src/utils/dates.tsx +246 -0
  289. package/src/utils/dev-env-state-path.ts +14 -0
  290. package/src/utils/dom.tsx +7 -0
  291. package/src/utils/env.tsx +117 -0
  292. package/src/utils/errors.tsx +284 -0
  293. package/src/utils/esbuild.tsx +235 -0
  294. package/src/utils/featurebase.tsx +247 -0
  295. package/src/utils/fs.tsx +33 -0
  296. package/src/utils/functions.tsx +22 -0
  297. package/src/utils/geo.tsx +16 -0
  298. package/src/utils/globals.tsx +48 -0
  299. package/src/utils/hashes.tsx +52 -0
  300. package/src/utils/html.tsx +60 -0
  301. package/src/utils/http.tsx +91 -0
  302. package/src/utils/ips.tsx +47 -0
  303. package/src/utils/json.tsx +200 -0
  304. package/src/utils/jsx-editable-transpiler.test.ts +156 -0
  305. package/src/utils/jsx-editable-transpiler.tsx +401 -0
  306. package/src/utils/jwt.tsx +170 -0
  307. package/src/utils/locks.tsx +71 -0
  308. package/src/utils/maps.tsx +407 -0
  309. package/src/utils/math.tsx +18 -0
  310. package/src/utils/monkey-patch.tsx +29 -0
  311. package/src/utils/node-http.tsx +47 -0
  312. package/src/utils/numbers.tsx +82 -0
  313. package/src/utils/oauth.tsx +9 -0
  314. package/src/utils/objects.tsx +581 -0
  315. package/src/utils/paginated-lists.test.ts +1039 -0
  316. package/src/utils/paginated-lists.tsx +479 -0
  317. package/src/utils/passkey.tsx +1 -0
  318. package/src/utils/promises.tsx +540 -0
  319. package/src/utils/proxies.tsx +212 -0
  320. package/src/utils/react.tsx +308 -0
  321. package/src/utils/redirect-urls.tsx +299 -0
  322. package/src/utils/regex.tsx +10 -0
  323. package/src/utils/results.tsx +411 -0
  324. package/src/utils/sentry.tsx +20 -0
  325. package/src/utils/stores.tsx +238 -0
  326. package/src/utils/strings.nicify.test.ts +253 -0
  327. package/src/utils/strings.tsx +723 -0
  328. package/src/utils/telemetry.tsx +37 -0
  329. package/src/utils/turnstile-browser.ts +106 -0
  330. package/src/utils/turnstile-flow.test.ts +101 -0
  331. package/src/utils/turnstile-flow.ts +266 -0
  332. package/src/utils/turnstile.ts +34 -0
  333. package/src/utils/typed-arrays.tsx +17 -0
  334. package/src/utils/types.tsx +208 -0
  335. package/src/utils/unicode.tsx +24 -0
  336. package/src/utils/urls.tsx +411 -0
  337. package/src/utils/uuids.tsx +47 -0
  338. package/dist/esm/stack-config-file.d.ts +0 -15
  339. package/dist/esm/stack-config-file.d.ts.map +0 -1
  340. package/dist/esm/stack-config-file.js.map +0 -1
  341. package/dist/stack-config-file.d.ts +0 -15
  342. package/dist/stack-config-file.d.ts.map +0 -1
  343. package/dist/stack-config-file.js.map +0 -1
@@ -0,0 +1,723 @@
1
+ import { findLastIndex, unique } from "./arrays";
2
+ import { HexclaveAssertionError } from "./errors";
3
+ import { filterUndefined } from "./objects";
4
+
5
+ export type Join<T extends string[], Separator extends string> =
6
+ T extends [] ? ""
7
+ : T extends [infer U extends string, ...infer Rest extends string[]]
8
+ ? `${U}${Rest extends [any, ...any[]] ? `${Separator}${Join<Rest, Separator>}` : ""}`
9
+ : "<error-joining-strings>";
10
+
11
+ export function typedJoin<T extends string[], Separator extends string>(strings: T, separator: Separator): Join<T, Separator> {
12
+ return strings.join(separator) as Join<T, Separator>;
13
+ }
14
+
15
+ export function typedToLowercase<S extends string>(s: S): Lowercase<S> {
16
+ if (typeof s !== "string") throw new HexclaveAssertionError("Expected a string for typedToLowercase", { s });
17
+ return s.toLowerCase() as Lowercase<S>;
18
+ }
19
+ import.meta.vitest?.test("typedToLowercase", ({ expect }) => {
20
+ expect(typedToLowercase("")).toBe("");
21
+ expect(typedToLowercase("HELLO")).toBe("hello");
22
+ expect(typedToLowercase("Hello World")).toBe("hello world");
23
+ expect(typedToLowercase("hello")).toBe("hello");
24
+ expect(typedToLowercase("123")).toBe("123");
25
+ expect(typedToLowercase("MIXED123case")).toBe("mixed123case");
26
+ expect(typedToLowercase("Special@Chars!")).toBe("special@chars!");
27
+ expect(() => typedToLowercase(123 as any)).toThrow("Expected a string for typedToLowercase");
28
+ });
29
+
30
+ export function typedToUppercase<S extends string>(s: S): Uppercase<S> {
31
+ if (typeof s !== "string") throw new HexclaveAssertionError("Expected a string for typedToUppercase", { s });
32
+ return s.toUpperCase() as Uppercase<S>;
33
+ }
34
+ import.meta.vitest?.test("typedToUppercase", ({ expect }) => {
35
+ expect(typedToUppercase("")).toBe("");
36
+ expect(typedToUppercase("hello")).toBe("HELLO");
37
+ expect(typedToUppercase("Hello World")).toBe("HELLO WORLD");
38
+ expect(typedToUppercase("HELLO")).toBe("HELLO");
39
+ expect(typedToUppercase("123")).toBe("123");
40
+ expect(typedToUppercase("mixed123Case")).toBe("MIXED123CASE");
41
+ expect(typedToUppercase("special@chars!")).toBe("SPECIAL@CHARS!");
42
+ expect(() => typedToUppercase(123 as any)).toThrow("Expected a string for typedToUppercase");
43
+ });
44
+
45
+ export function typedCapitalize<S extends string>(s: S): Capitalize<S> {
46
+ return s.charAt(0).toUpperCase() + s.slice(1) as Capitalize<S>;
47
+ }
48
+ import.meta.vitest?.test("typedCapitalize", ({ expect }) => {
49
+ expect(typedCapitalize("")).toBe("");
50
+ expect(typedCapitalize("hello")).toBe("Hello");
51
+ expect(typedCapitalize("hello world")).toBe("Hello world");
52
+ expect(typedCapitalize("HELLO")).toBe("HELLO");
53
+ expect(typedCapitalize("123test")).toBe("123test");
54
+ expect(typedCapitalize("already Capitalized")).toBe("Already Capitalized");
55
+ expect(typedCapitalize("h")).toBe("H");
56
+ });
57
+
58
+ /**
59
+ * Compares two strings in a way that is not dependent on the current locale.
60
+ */
61
+ export function stringCompare(a: string, b: string): number {
62
+ if (typeof a !== "string" || typeof b !== "string") throw new HexclaveAssertionError(`Expected two strings for stringCompare, found ${typeof a} and ${typeof b}`, { a, b });
63
+ const cmp = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
64
+ return cmp(a.toUpperCase(), b.toUpperCase()) || cmp(b, a);
65
+ }
66
+ import.meta.vitest?.test("stringCompare", ({ expect }) => {
67
+ // Equal strings
68
+ expect(stringCompare("a", "a")).toBe(0);
69
+ expect(stringCompare("", "")).toBe(0);
70
+
71
+ // Case comparison - note that this function is NOT case-insensitive
72
+ // It compares uppercase versions first, then original strings
73
+ expect(stringCompare("a", "A")).toBe(-1); // lowercase comes after uppercase
74
+ expect(stringCompare("A", "a")).toBe(1); // uppercase comes before lowercase
75
+ expect(stringCompare("abc", "ABC")).toBe(-1);
76
+ expect(stringCompare("ABC", "abc")).toBe(1);
77
+
78
+ // Different strings
79
+ expect(stringCompare("a", "b")).toBe(-1);
80
+ expect(stringCompare("b", "a")).toBe(1);
81
+
82
+ // Strings with different lengths
83
+ expect(stringCompare("abc", "abcd")).toBe(-1);
84
+ expect(stringCompare("abcd", "abc")).toBe(1);
85
+
86
+ // Strings with numbers
87
+ expect(stringCompare("a1", "a2")).toBe(-1);
88
+ expect(stringCompare("a10", "a2")).toBe(-1);
89
+
90
+ // Strings with special characters
91
+ expect(stringCompare("a", "a!")).toBe(-1);
92
+ expect(stringCompare("a!", "a")).toBe(1);
93
+ });
94
+
95
+ /**
96
+ * Returns all whitespace character at the start of the string.
97
+ *
98
+ * Uses the same definition for whitespace as `String.prototype.trim()`.
99
+ */
100
+ export function getWhitespacePrefix(s: string): string {
101
+ return s.substring(0, s.length - s.trimStart().length);
102
+ }
103
+ import.meta.vitest?.test("getWhitespacePrefix", ({ expect }) => {
104
+ expect(getWhitespacePrefix("")).toBe("");
105
+ expect(getWhitespacePrefix("hello")).toBe("");
106
+ expect(getWhitespacePrefix(" hello")).toBe(" ");
107
+ expect(getWhitespacePrefix(" hello")).toBe(" ");
108
+ expect(getWhitespacePrefix("\thello")).toBe("\t");
109
+ expect(getWhitespacePrefix("\n hello")).toBe("\n ");
110
+ expect(getWhitespacePrefix(" ")).toBe(" ");
111
+ expect(getWhitespacePrefix(" \t\n\r")).toBe(" \t\n\r");
112
+ });
113
+
114
+ /**
115
+ * Returns all whitespace character at the end of the string.
116
+ *
117
+ * Uses the same definition for whitespace as `String.prototype.trim()`.
118
+ */
119
+ export function getWhitespaceSuffix(s: string): string {
120
+ return s.substring(s.trimEnd().length);
121
+ }
122
+ import.meta.vitest?.test("getWhitespaceSuffix", ({ expect }) => {
123
+ expect(getWhitespaceSuffix("")).toBe("");
124
+ expect(getWhitespaceSuffix("hello")).toBe("");
125
+ expect(getWhitespaceSuffix("hello ")).toBe(" ");
126
+ expect(getWhitespaceSuffix("hello ")).toBe(" ");
127
+ expect(getWhitespaceSuffix("hello\t")).toBe("\t");
128
+ expect(getWhitespaceSuffix("hello \n")).toBe(" \n");
129
+ expect(getWhitespaceSuffix(" ")).toBe(" ");
130
+ expect(getWhitespaceSuffix(" \t\n\r")).toBe(" \t\n\r");
131
+ });
132
+
133
+ /**
134
+ * Returns a string with all empty or whitespace-only lines at the start removed.
135
+ *
136
+ * Uses the same definition for whitespace as `String.prototype.trim()`.
137
+ */
138
+ export function trimEmptyLinesStart(s: string): string {
139
+ const lines = s.split("\n");
140
+ const firstNonEmptyLineIndex = lines.findIndex((line) => line.trim() !== "");
141
+ // If all lines are empty or whitespace-only, return an empty string
142
+ if (firstNonEmptyLineIndex === -1) return "";
143
+ return lines.slice(firstNonEmptyLineIndex).join("\n");
144
+ }
145
+ import.meta.vitest?.test("trimEmptyLinesStart", ({ expect }) => {
146
+ expect(trimEmptyLinesStart("")).toBe("");
147
+ expect(trimEmptyLinesStart("hello")).toBe("hello");
148
+ expect(trimEmptyLinesStart("\nhello")).toBe("hello");
149
+ expect(trimEmptyLinesStart("\n\nhello")).toBe("hello");
150
+ expect(trimEmptyLinesStart(" \n\t\nhello")).toBe("hello");
151
+ expect(trimEmptyLinesStart("\n\nhello\nworld")).toBe("hello\nworld");
152
+ expect(trimEmptyLinesStart("hello\n\nworld")).toBe("hello\n\nworld");
153
+ expect(trimEmptyLinesStart("hello\nworld\n")).toBe("hello\nworld\n");
154
+ expect(trimEmptyLinesStart("\n \n\nhello\n \nworld")).toBe("hello\n \nworld");
155
+ // Edge case: all lines are empty
156
+ expect(trimEmptyLinesStart("\n\n \n\t")).toBe("");
157
+ });
158
+
159
+ /**
160
+ * Returns a string with all empty or whitespace-only lines at the end removed.
161
+ *
162
+ * Uses the same definition for whitespace as `String.prototype.trim()`.
163
+ */
164
+ export function trimEmptyLinesEnd(s: string): string {
165
+ const lines = s.split("\n");
166
+ const lastNonEmptyLineIndex = findLastIndex(lines, (line) => line.trim() !== "");
167
+ return lines.slice(0, lastNonEmptyLineIndex + 1).join("\n");
168
+ }
169
+ import.meta.vitest?.test("trimEmptyLinesEnd", ({ expect }) => {
170
+ expect(trimEmptyLinesEnd("")).toBe("");
171
+ expect(trimEmptyLinesEnd("hello")).toBe("hello");
172
+ expect(trimEmptyLinesEnd("hello\n")).toBe("hello");
173
+ expect(trimEmptyLinesEnd("hello\n\n")).toBe("hello");
174
+ expect(trimEmptyLinesEnd("hello\n \n\t")).toBe("hello");
175
+ expect(trimEmptyLinesEnd("hello\nworld\n\n")).toBe("hello\nworld");
176
+ expect(trimEmptyLinesEnd("hello\n\nworld")).toBe("hello\n\nworld");
177
+ expect(trimEmptyLinesEnd("\nhello\nworld")).toBe("\nhello\nworld");
178
+ expect(trimEmptyLinesEnd("hello\n \nworld\n\n ")).toBe("hello\n \nworld");
179
+ // Edge case: all lines are empty
180
+ expect(trimEmptyLinesEnd("\n\n \n\t")).toBe("");
181
+ });
182
+
183
+ /**
184
+ * Returns a string with all empty or whitespace-only lines trimmed at the start and end.
185
+ *
186
+ * Uses the same definition for whitespace as `String.prototype.trim()`.
187
+ */
188
+ export function trimLines(s: string): string {
189
+ return trimEmptyLinesEnd(trimEmptyLinesStart(s));
190
+ }
191
+ import.meta.vitest?.test("trimLines", ({ expect }) => {
192
+ expect(trimLines("")).toBe("");
193
+ expect(trimLines(" ")).toBe("");
194
+ expect(trimLines(" \n ")).toBe("");
195
+ expect(trimLines(" abc ")).toBe(" abc ");
196
+ expect(trimLines("\n \nLine1\nLine2\n \n")).toBe("Line1\nLine2");
197
+ expect(trimLines("Line1\n \nLine2")).toBe("Line1\n \nLine2");
198
+ expect(trimLines(" \n \n\t")).toBe("");
199
+ expect(trimLines(" Hello World")).toBe(" Hello World");
200
+ expect(trimLines("\n")).toBe("");
201
+ expect(trimLines("\t \n\t\tLine1 \n \nLine2\t\t\n\t ")).toBe("\t\tLine1 \n \nLine2\t\t");
202
+ });
203
+
204
+
205
+ /**
206
+ * A template literal tag that returns the same string as the template literal without a tag.
207
+ *
208
+ * Useful for implementing your own template literal tags.
209
+ */
210
+ export function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: string[]): string {
211
+ if (values.length !== strings.length - 1) throw new HexclaveAssertionError("Invalid number of values; must be one less than strings", { strings, values });
212
+
213
+ return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');
214
+ }
215
+ import.meta.vitest?.test("templateIdentity", ({ expect }) => {
216
+ expect(templateIdentity`Hello World`).toBe("Hello World");
217
+ expect(templateIdentity`${"Hello"}`).toBe("Hello");
218
+ const greeting = "Hello";
219
+ const subject = "World";
220
+ expect(templateIdentity`${greeting}, ${subject}!`).toBe("Hello, World!");
221
+ expect(templateIdentity`${"A"}${"B"}${"C"}`).toBe("ABC");
222
+ expect(templateIdentity`Start${""}Middle${""}End`).toBe("StartMiddleEnd");
223
+ expect(templateIdentity``).toBe("");
224
+ expect(templateIdentity`Line1
225
+ Line2`).toBe("Line1\nLine2");
226
+ expect(templateIdentity(["a ", " scientific ", "gun"], "certain", "rail")).toBe("a certain scientific railgun");
227
+ expect(templateIdentity(["only one part"])).toBe("only one part");
228
+ expect(() => templateIdentity(["a ", "b", "c"], "only one")).toThrow("Invalid number of values");
229
+ expect(() => templateIdentity(["a", "b"], "x", "y")).toThrow("Invalid number of values");
230
+ });
231
+
232
+
233
+ export function deindent(code: string): string;
234
+ export function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;
235
+ export function deindent(strings: string | readonly string[], ...values: any[]): string {
236
+ if (typeof strings === "string") return deindent([strings]);
237
+ return templateIdentity(...deindentTemplate(strings, ...values));
238
+ }
239
+
240
+ export function deindentTemplate(strings: TemplateStringsArray | readonly string[], ...values: any[]): [string[], ...string[]] {
241
+ if (values.length !== strings.length - 1) throw new HexclaveAssertionError("Invalid number of values; must be one less than strings", { strings, values });
242
+
243
+ const trimmedStrings = [...strings];
244
+ trimmedStrings[0] = trimEmptyLinesStart(trimmedStrings[0] + "+").slice(0, -1);
245
+ trimmedStrings[trimmedStrings.length - 1] = trimEmptyLinesEnd("+" + trimmedStrings[trimmedStrings.length - 1]).slice(1);
246
+
247
+ const indentation = trimmedStrings
248
+ .join("${SOME_VALUE}")
249
+ .split("\n")
250
+ .filter((line) => line.trim() !== "")
251
+ .map((line) => getWhitespacePrefix(line).length)
252
+ .reduce((min, current) => Math.min(min, current), Infinity);
253
+
254
+ const deindentedStrings = trimmedStrings
255
+ .map((string, stringIndex) => {
256
+ return string
257
+ .split("\n")
258
+ .map((line, lineIndex) => stringIndex !== 0 && lineIndex === 0 ? line : line.substring(indentation))
259
+ .join("\n");
260
+ });
261
+
262
+ const indentedValues = values.map((value, i) => {
263
+ const firstLineIndentation = getWhitespacePrefix(deindentedStrings[i].split("\n").at(-1)!);
264
+ return `${value}`.replaceAll("\n", `\n${firstLineIndentation}`);
265
+ });
266
+
267
+ return [deindentedStrings, ...indentedValues];
268
+ }
269
+ import.meta.vitest?.test("deindent", ({ expect }) => {
270
+ // Test with string input
271
+ expect(deindent(" hello")).toBe("hello");
272
+ expect(deindent(" hello\n world")).toBe("hello\nworld");
273
+ expect(deindent(" hello\n world")).toBe("hello\n world");
274
+ expect(deindent("\n hello\n world\n")).toBe("hello\nworld");
275
+
276
+ // Test with empty input
277
+ expect(deindent("")).toBe("");
278
+
279
+ // Test with template literal
280
+ expect(deindent`
281
+ hello
282
+ world
283
+ `).toBe("hello\nworld");
284
+
285
+ expect(deindent`
286
+ hello
287
+ world
288
+ `).toBe("hello\n world");
289
+
290
+ // Test with values
291
+ const value = "test";
292
+ expect(deindent`
293
+ hello ${value}
294
+ world
295
+ `).toBe(`hello ${value}\nworld`);
296
+
297
+ // Test with multiline values
298
+ expect(deindent`
299
+ hello
300
+ to ${"line1\n line2"}
301
+ world
302
+ `).toBe(`hello\n to line1\n line2\nworld`);
303
+
304
+ // Leading whitespace values
305
+ expect(deindent`
306
+ ${" "}A
307
+ ${" "}B
308
+ ${" "}C
309
+ `).toBe(` A\n B\n C`);
310
+
311
+ // Trailing whitespaces (note: there are two whitespaces each after A and after C)
312
+ expect(deindent`
313
+ A
314
+ B ${" "}
315
+ C
316
+ `).toBe(`A \nB \nC `);
317
+
318
+ // Test with mixed indentation
319
+ expect(deindent`
320
+ hello
321
+ world
322
+ !
323
+ `).toBe("hello\n world\n !");
324
+
325
+ // Test error cases
326
+ expect(() => deindent(["a", "b", "c"], "too", "many", "values")).toThrow("Invalid number of values");
327
+ });
328
+
329
+ export function extractScopes(scope: string, removeDuplicates=true): string[] {
330
+ // TODO what is this for? can we move this into the OAuth code in the backend?
331
+ const trimmedString = scope.trim();
332
+ const scopesArray = trimmedString.split(/\s+/);
333
+ const filtered = scopesArray.filter(scope => scope.length > 0);
334
+ return removeDuplicates ? [...new Set(filtered)] : filtered;
335
+ }
336
+ import.meta.vitest?.test("extractScopes", ({ expect }) => {
337
+ // Test with empty string
338
+ expect(extractScopes("")).toEqual([]);
339
+
340
+ // Test with single scope
341
+ expect(extractScopes("read")).toEqual(["read"]);
342
+
343
+ // Test with multiple scopes
344
+ expect(extractScopes("read write")).toEqual(["read", "write"]);
345
+
346
+ // Test with extra whitespace
347
+ expect(extractScopes(" read write ")).toEqual(["read", "write"]);
348
+
349
+ // Test with newlines and tabs
350
+ expect(extractScopes("read\nwrite\tdelete")).toEqual(["read", "write", "delete"]);
351
+
352
+ // Test with duplicates (default behavior)
353
+ expect(extractScopes("read write read")).toEqual(["read", "write"]);
354
+
355
+ // Test with duplicates (explicitly set to remove)
356
+ expect(extractScopes("read write read", true)).toEqual(["read", "write"]);
357
+
358
+ // Test with duplicates (explicitly set to keep)
359
+ expect(extractScopes("read write read", false)).toEqual(["read", "write", "read"]);
360
+ });
361
+
362
+ export function mergeScopeStrings(...scopes: string[]): string {
363
+ // TODO what is this for? can we move this into the OAuth code in the backend?
364
+ const allScope = scopes.map((s) => extractScopes(s)).flat().join(" ");
365
+ return extractScopes(allScope).join(" ");
366
+ }
367
+ import.meta.vitest?.test("mergeScopeStrings", ({ expect }) => {
368
+ // Test with empty input
369
+ expect(mergeScopeStrings()).toBe("");
370
+
371
+ // Test with single scope string
372
+ expect(mergeScopeStrings("read write")).toBe("read write");
373
+
374
+ // Test with multiple scope strings
375
+ expect(mergeScopeStrings("read", "write")).toBe("read write");
376
+
377
+ // Test with overlapping scopes
378
+ expect(mergeScopeStrings("read write", "write delete")).toBe("read write delete");
379
+
380
+ // Test with extra whitespace
381
+ expect(mergeScopeStrings(" read write ", " delete ")).toBe("read write delete");
382
+
383
+ // Test with duplicates across strings
384
+ expect(mergeScopeStrings("read write", "write delete", "read")).toBe("read write delete");
385
+
386
+ // Test with empty strings
387
+ expect(mergeScopeStrings("read write", "", "delete")).toBe("read write delete");
388
+ });
389
+
390
+ export function escapeTemplateLiteral(s: string): string {
391
+ return s.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
392
+ }
393
+ import.meta.vitest?.test("escapeTemplateLiteral", ({ expect }) => {
394
+ // Test with empty string
395
+ expect(escapeTemplateLiteral("")).toBe("");
396
+
397
+ // Test with normal string (no special characters)
398
+ expect(escapeTemplateLiteral("hello world")).toBe("hello world");
399
+
400
+ // Test with backtick
401
+ const input1 = "hello `world`";
402
+ const output1 = escapeTemplateLiteral(input1);
403
+ // Verify backticks are escaped
404
+ expect(output1).toBe("hello \\`world\\`");
405
+
406
+ // Test with backslash
407
+ const input2 = "hello \\world";
408
+ const output2 = escapeTemplateLiteral(input2);
409
+ // Verify backslashes are escaped
410
+ expect(output2).toBe("hello \\\\world");
411
+
412
+ // Test with dollar sign
413
+ const input3 = "hello $world";
414
+ const output3 = escapeTemplateLiteral(input3);
415
+ // Verify dollar signs are escaped
416
+ expect(output3).toBe("hello $world");
417
+
418
+ // Test with dollar sign in interpolation
419
+ const input4 = "hello ${$world";
420
+ const output4 = escapeTemplateLiteral(input4);
421
+ // Verify dollar signs are escaped
422
+ expect(output4).toBe("hello \\${$world");
423
+
424
+ // Test with multiple special characters
425
+ const input5 = "`hello` ${world\\";
426
+ const output5 = escapeTemplateLiteral(input5);
427
+ // Verify all special characters are escaped
428
+ expect(output5).toBe("\\`hello\\` \\${world\\\\");
429
+
430
+ // Test with already escaped characters
431
+ const input6 = "\\`hello\\`";
432
+ const output6 = escapeTemplateLiteral(input6);
433
+ expect(output6).toBe("\\\\\\`hello\\\\\\`");
434
+ });
435
+
436
+ /**
437
+ * Some classes have different constructor names in different environments (eg. `Headers` is sometimes called `_Headers`,
438
+ * so we create an object of overrides to handle these cases.
439
+ */
440
+ const nicifiableClassNameOverrides = new Map(Object.entries({
441
+ Headers,
442
+ } as Record<string, unknown>).map(([k, v]) => [v, k]));
443
+ export type Nicifiable = {
444
+ getNicifiableKeys?(): PropertyKey[],
445
+ getNicifiedObjectExtraLines?(): string[],
446
+ };
447
+ export type NicifyOptions = {
448
+ maxDepth: number,
449
+ currentIndent: string,
450
+ lineIndent: string,
451
+ multiline: boolean,
452
+ refs: Map<unknown, string>,
453
+ path: string,
454
+ parent: null | {
455
+ options: NicifyOptions,
456
+ value: unknown,
457
+ },
458
+ keyInParent: PropertyKey | null,
459
+ hideFields: PropertyKey[],
460
+ overrides: (...args: Parameters<typeof nicify>) => string | null,
461
+ };
462
+ export function nicify(
463
+ value: unknown,
464
+ options: Partial<NicifyOptions> = {},
465
+ ): string {
466
+ const fullOptions: NicifyOptions = {
467
+ maxDepth: 5,
468
+ currentIndent: "",
469
+ lineIndent: " ",
470
+ multiline: true,
471
+ refs: new Map(),
472
+ path: "value",
473
+ parent: null,
474
+ overrides: () => null,
475
+ keyInParent: null,
476
+ hideFields: [],
477
+ ...filterUndefined(options),
478
+ };
479
+ const {
480
+ maxDepth,
481
+ currentIndent,
482
+ lineIndent,
483
+ multiline,
484
+ refs,
485
+ path,
486
+ overrides,
487
+ hideFields,
488
+ } = fullOptions;
489
+ const nl = `\n${currentIndent}`;
490
+
491
+ const overrideResult = overrides(value, options);
492
+ if (overrideResult !== null) return overrideResult;
493
+
494
+ if (["function", "object", "symbol"].includes(typeof value) && value !== null) {
495
+ if (refs.has(value)) {
496
+ return `Ref<${refs.get(value)}>`;
497
+ }
498
+ refs.set(value, path);
499
+ }
500
+
501
+ const newOptions: NicifyOptions = {
502
+ maxDepth: maxDepth - 1,
503
+ currentIndent,
504
+ lineIndent,
505
+ multiline,
506
+ refs,
507
+ path: path + "->[unknown property]",
508
+ overrides,
509
+ parent: { value, options: fullOptions },
510
+ keyInParent: null,
511
+ hideFields: [],
512
+ };
513
+ const nestedNicify = (newValue: unknown, newPath: string, keyInParent: PropertyKey | null, options: Partial<NicifyOptions> = {}) => {
514
+ return nicify(newValue, {
515
+ ...newOptions,
516
+ path: newPath,
517
+ currentIndent: currentIndent + lineIndent,
518
+ keyInParent,
519
+ ...options,
520
+ });
521
+ };
522
+
523
+ switch (typeof value) {
524
+ case "boolean": case "number": {
525
+ return JSON.stringify(value);
526
+ }
527
+ case "string": {
528
+ const isDeindentable = (v: string) => deindent(v) === v && v.includes("\n");
529
+ const wrapInDeindent = (v: string) => deindent`
530
+ deindent\`
531
+ ${currentIndent + lineIndent}${escapeTemplateLiteral(v).replaceAll("\n", nl + lineIndent)}
532
+ ${currentIndent}\`
533
+ `;
534
+ if (isDeindentable(value)) {
535
+ return wrapInDeindent(value);
536
+ } else if (value.endsWith("\n") && isDeindentable(value.slice(0, -1))) {
537
+ return wrapInDeindent(value.slice(0, -1)) + ' + "\\n"';
538
+ } else {
539
+ return JSON.stringify(value);
540
+ }
541
+ }
542
+ case "undefined": {
543
+ return "undefined";
544
+ }
545
+ case "symbol": {
546
+ return value.toString();
547
+ }
548
+ case "bigint": {
549
+ return `${value}n`;
550
+ }
551
+ case "function": {
552
+ if (value.name) return `function ${value.name}(...) { ... }`;
553
+ return `(...) => { ... }`;
554
+ }
555
+ case "object": {
556
+ if (value === null) return "null";
557
+ if (Array.isArray(value)) {
558
+ const extraLines = getNicifiedObjectExtraLines(value);
559
+ const resValueLength = value.length + extraLines.length;
560
+ if (resValueLength === 0) return "[]"; // early return in case maxDepth <= 0
561
+ if (maxDepth <= 0) return `[...]`;
562
+ const resValues = value.map((v, i) => nestedNicify(v, `${path}[${i}]`, i));
563
+ resValues.push(...extraLines);
564
+ if (resValues.length !== resValueLength) throw new HexclaveAssertionError("nicify of object: resValues.length !== resValueLength", { value, resValues, resValueLength });
565
+ const shouldIndent = resValues.length > 4 || resValues.some(x => (resValues.length > 1 && x.length > 4) || x.includes("\n"));
566
+ if (shouldIndent) {
567
+ return `[${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join("")}]`;
568
+ } else {
569
+ return `[${resValues.join(", ")}]`;
570
+ }
571
+ }
572
+ if (value instanceof Date) {
573
+ return `Date(${nestedNicify(value.toISOString(), `${path}.toISOString()`, null)})`;
574
+ }
575
+ if (value instanceof URL) {
576
+ return `URL(${nestedNicify(value.toString(), `${path}.toString()`, null)})`;
577
+ }
578
+ if (ArrayBuffer.isView(value)) {
579
+ return `${value.constructor.name}([${value.toString()}])`;
580
+ }
581
+ if (value instanceof ArrayBuffer) {
582
+ return `ArrayBuffer [${new Uint8Array(value).toString()}]`;
583
+ }
584
+ if (value instanceof Error) {
585
+ let stack = value.stack ?? "";
586
+ const toString = value.toString();
587
+ if (!stack.startsWith(toString)) stack = `${toString}\n${stack}`; // some browsers don't include the error message in the stack, some do
588
+ stack = stack.trimEnd();
589
+ stack = stack.replace(/\n\s+/g, `\n${lineIndent}${lineIndent}`);
590
+ stack = stack.replace("\n", `\n${lineIndent}Stack:\n`);
591
+ if (Object.keys(value).length > 0) {
592
+ stack += `\n${lineIndent}Extra properties: ${nestedNicify(Object.fromEntries(Object.entries(value)), path, null)}`;
593
+ }
594
+ if (value.cause) {
595
+ stack += `\n${lineIndent}Cause:\n${lineIndent}${lineIndent}${nestedNicify(value.cause, path, null, { currentIndent: currentIndent + lineIndent + lineIndent })}`;
596
+ }
597
+ stack = stack.replaceAll("\n", `\n${currentIndent}`);
598
+ return stack;
599
+ }
600
+
601
+ const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
602
+ const constructorString = constructorName ? `${constructorName} ` : "";
603
+
604
+ const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
605
+ const extraLines = [
606
+ ...getNicifiedObjectExtraLines(value),
607
+ ...hideFields.length > 0 ? [`<some fields may have been hidden>`] : [],
608
+ ];
609
+ const resValueLength = entries.length + extraLines.length;
610
+ if (resValueLength === 0) return `${constructorString}{}`;
611
+ if (maxDepth <= 0) return `${constructorString}{ ... }`;
612
+ const resValues = entries.map(([k, v], keyIndex) => {
613
+ const keyNicified = nestedNicify(k, `Object.keys(${path})[${keyIndex}]`, null);
614
+ const keyInObjectLiteral = typeof k === "string" ? nicifyPropertyString(k) : `[${keyNicified}]`;
615
+ if (typeof v === "function" && v.name === k) {
616
+ return `${keyInObjectLiteral}(...): { ... }`;
617
+ } else {
618
+ return `${keyInObjectLiteral}: ${nestedNicify(v, `${path}[${keyNicified}]`, k)}`;
619
+ }
620
+ });
621
+ resValues.push(...extraLines);
622
+ if (resValues.length !== resValueLength) throw new HexclaveAssertionError("nicify of object: resValues.length !== resValueLength", { value, resValues, resValueLength });
623
+ const shouldIndent = resValues.length > 1 || resValues.some(x => x.includes("\n"));
624
+
625
+ if (resValues.length === 0) return `${constructorString}{}`;
626
+ if (shouldIndent) {
627
+ return `${constructorString}{${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join("")}}`;
628
+ } else {
629
+ return `${constructorString}{ ${resValues.join(", ")} }`;
630
+ }
631
+ }
632
+ default: {
633
+ return `${typeof value}<${value}>`;
634
+ }
635
+ }
636
+ }
637
+
638
+ export function replaceAll(input: string, searchValue: string, replaceValue: string): string {
639
+ if (searchValue === "") throw new HexclaveAssertionError("replaceAll: searchValue is empty");
640
+ return input.split(searchValue).join(replaceValue);
641
+ }
642
+ import.meta.vitest?.test("replaceAll", ({ expect }) => {
643
+ expect(replaceAll("hello world", "o", "x")).toBe("hellx wxrld");
644
+ expect(replaceAll("aaa", "a", "b")).toBe("bbb");
645
+ expect(replaceAll("", "a", "b")).toBe("");
646
+ expect(replaceAll("abc", "b", "")).toBe("ac");
647
+ expect(replaceAll("test.test.test", ".", "_")).toBe("test_test_test");
648
+ expect(replaceAll("a.b*c", ".", "x")).toBe("axb*c");
649
+ expect(replaceAll("a*b*c", "*", "x")).toBe("axbxc");
650
+ expect(replaceAll("hello hello", "hello", "hi")).toBe("hi hi");
651
+ });
652
+
653
+ function nicifyPropertyString(str: string) {
654
+ return JSON.stringify(str);
655
+ }
656
+ import.meta.vitest?.test("nicifyPropertyString", ({ expect }) => {
657
+ // Test valid identifiers
658
+ expect(nicifyPropertyString("validName")).toBe('"validName"');
659
+ expect(nicifyPropertyString("_validName")).toBe('"_validName"');
660
+ expect(nicifyPropertyString("valid123Name")).toBe('"valid123Name"');
661
+
662
+ // Test invalid identifiers
663
+ expect(nicifyPropertyString("123invalid")).toBe('"123invalid"');
664
+ expect(nicifyPropertyString("invalid-name")).toBe('"invalid-name"');
665
+ expect(nicifyPropertyString("invalid space")).toBe('"invalid space"');
666
+ expect(nicifyPropertyString("$invalid")).toBe('"$invalid"');
667
+ expect(nicifyPropertyString("")).toBe('""');
668
+
669
+ // Test with special characters
670
+ expect(nicifyPropertyString("property!")).toBe('"property!"');
671
+ expect(nicifyPropertyString("property.name")).toBe('"property.name"');
672
+
673
+ // Test with escaped characters
674
+ expect(nicifyPropertyString("\\")).toBe('"\\\\"');
675
+ expect(nicifyPropertyString('"')).toBe('"\\""');
676
+ });
677
+
678
+ function getNicifiableKeys(value: Nicifiable | object) {
679
+ const overridden = ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.();
680
+ if (overridden != null) return overridden;
681
+ if (value instanceof Response) {
682
+ return ['status', 'headers'];
683
+ }
684
+ const keys = Object.keys(value).sort();
685
+ return unique(keys);
686
+ }
687
+ import.meta.vitest?.test("getNicifiableKeys", ({ expect }) => {
688
+ // Test regular object
689
+ expect(getNicifiableKeys({ b: 1, a: 2, c: 3 })).toEqual(["a", "b", "c"]);
690
+
691
+ // Test empty object
692
+ expect(getNicifiableKeys({})).toEqual([]);
693
+
694
+
695
+ expect(getNicifiableKeys(new Response())).toEqual(["status", "headers"]);
696
+
697
+ // Test object with custom getNicifiableKeys
698
+ const customObject = {
699
+ a: 1,
700
+ b: 2,
701
+ getNicifiableKeys() {
702
+ return ["customKey1", "customKey2"];
703
+ }
704
+ };
705
+ expect(getNicifiableKeys(customObject)).toEqual(["customKey1", "customKey2"]);
706
+ });
707
+
708
+ function getNicifiableEntries(value: Nicifiable | object): [PropertyKey, unknown][] {
709
+ const recordLikes = [Headers];
710
+ function isRecordLike(value: unknown): value is InstanceType<typeof recordLikes[number]> {
711
+ return recordLikes.some(x => value instanceof x);
712
+ }
713
+
714
+ if (isRecordLike(value)) {
715
+ return [...value.entries()].sort(([a], [b]) => stringCompare(`${a}`, `${b}`));
716
+ }
717
+ const keys = getNicifiableKeys(value);
718
+ return keys.map((k) => [k, value[k as never]] as [PropertyKey, unknown]);
719
+ }
720
+
721
+ function getNicifiedObjectExtraLines(value: Nicifiable | object) {
722
+ return ("getNicifiedObjectExtraLines" in value ? value.getNicifiedObjectExtraLines : null)?.() ?? [];
723
+ }