@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,235 @@
1
+ import * as esbuild from 'esbuild-wasm/lib/browser.js';
2
+ import { join } from 'path';
3
+ import { getProcessEnv, isBrowserLike } from './env';
4
+ import { captureError, HexclaveAssertionError, throwErr } from "./errors";
5
+ import { createGlobalAsync } from './globals';
6
+ import { ignoreUnhandledRejection, runAsynchronously } from './promises';
7
+ import { Result } from "./results";
8
+ import { traceSpan, withTraceSpan } from './telemetry';
9
+
10
+
11
+ // esbuild requires self property to be set, and it is not set by default in nodejs
12
+ (globalThis.self as any) ??= globalThis as any;
13
+
14
+ let esbuildInitializePromise: Promise<void> | null = null;
15
+
16
+ if (typeof process !== "undefined" && typeof process.exit === "function" && getProcessEnv("NODE_ENV") === 'development') {
17
+ // On development Node.js servers, initialize ESBuild as soon as the module is imported so we have to wait less on the first request
18
+ runAsynchronously(async () => {
19
+ try {
20
+ await initializeEsbuild();
21
+ } catch (e) {
22
+ captureError("initialize-esbuild-in-dev", e);
23
+ }
24
+ });
25
+ }
26
+
27
+ export function initializeEsbuild(): Promise<void> {
28
+ const esbuildWasmUrl = `https://unpkg.com/esbuild-wasm@${esbuild.version}/esbuild.wasm`;
29
+ if (esbuildInitializePromise == null) {
30
+ esbuildInitializePromise = withTraceSpan('initializeEsbuild', async () => {
31
+ try {
32
+ let initOptions;
33
+ if (isBrowserLike()) {
34
+ initOptions = {
35
+ wasmURL: esbuildWasmUrl,
36
+ };
37
+ } else {
38
+ const esbuildWasmModule = await createGlobalAsync('esbuildWasmModule', async () => {
39
+ const esbuildWasmResponse = await fetch(esbuildWasmUrl);
40
+ if (!esbuildWasmResponse.ok) {
41
+ throw new HexclaveAssertionError(`Failed to fetch esbuild.wasm: ${esbuildWasmResponse.status} ${esbuildWasmResponse.statusText}: ${await esbuildWasmResponse.text()}`);
42
+ }
43
+ const esbuildWasm = await esbuildWasmResponse.arrayBuffer();
44
+ const esbuildWasmArray = new Uint8Array(esbuildWasm);
45
+ if (esbuildWasmArray[0] !== 0x00 || esbuildWasmArray[1] !== 0x61 || esbuildWasmArray[2] !== 0x73 || esbuildWasmArray[3] !== 0x6d) {
46
+ throw new HexclaveAssertionError(`Invalid esbuild.wasm file: ${new TextDecoder().decode(esbuildWasmArray)}`);
47
+ }
48
+ return new WebAssembly.Module(esbuildWasm);
49
+ });
50
+ initOptions = {
51
+ wasmModule: esbuildWasmModule,
52
+ worker: false,
53
+ };
54
+ }
55
+ try {
56
+ await esbuild.initialize(initOptions);
57
+ } catch (e) {
58
+ if (e instanceof Error && e.message === 'Cannot call "initialize" more than once') {
59
+ // this happens especially in local development, just ignore
60
+ } else {
61
+ throw e;
62
+ }
63
+ }
64
+ } catch (e) {
65
+ esbuildInitializePromise = null;
66
+ throw new HexclaveAssertionError("Failed to initialize ESBuild", { cause: e });
67
+ }
68
+ })();
69
+ ignoreUnhandledRejection(esbuildInitializePromise);
70
+ }
71
+
72
+ return esbuildInitializePromise;
73
+ }
74
+
75
+ export async function bundleJavaScript(sourceFiles: Record<string, string> & { '/entry.js': string }, options: {
76
+ format?: 'iife' | 'esm' | 'cjs',
77
+ externalPackages?: Record<string, string>,
78
+ keepAsImports?: string[],
79
+ sourcemap?: false | 'inline',
80
+ allowHttpImports?: boolean,
81
+ } = {}): Promise<Result<string, string>> {
82
+ await initializeEsbuild();
83
+
84
+ const sourceFilesMap = new Map(Object.entries(sourceFiles));
85
+ const externalPackagesMap = new Map(Object.entries(options.externalPackages ?? {}));
86
+ const keepAsImports = options.keepAsImports ?? [];
87
+
88
+ const httpImportCache = new Map<string, { contents: string, loader: esbuild.Loader, resolveDir: string }>();
89
+
90
+ const extToLoader: Map<string, esbuild.Loader> = new Map([
91
+ ['tsx', 'tsx'],
92
+ ['ts', 'ts'],
93
+ ['js', 'js'],
94
+ ['jsx', 'jsx'],
95
+ ['json', 'json'],
96
+ ['css', 'css'],
97
+ ]);
98
+ let result;
99
+ try {
100
+ result = await traceSpan('bundleJavaScript', async () => await esbuild.build({
101
+ entryPoints: ['/entry.js'],
102
+ bundle: true,
103
+ write: false,
104
+ format: options.format ?? 'iife',
105
+ platform: 'browser',
106
+ target: 'es2015',
107
+ jsx: 'automatic',
108
+ sourcemap: options.sourcemap ?? 'inline',
109
+ external: keepAsImports,
110
+ plugins: [
111
+ ...options.allowHttpImports ? [{
112
+ name: "esm-sh-only",
113
+ setup(build: esbuild.PluginBuild) {
114
+ // Handle absolute URLs and relative imports from esm.sh modules.
115
+ build.onResolve({ filter: /.*/ }, (args) => {
116
+ // Only touch absolute http(s) specifiers or children of our own namespace
117
+ const isHttp = args.path.startsWith("http://") || args.path.startsWith("https://");
118
+ const fromEsmNs = args.namespace === "esm-sh";
119
+
120
+ if (!isHttp && !fromEsmNs) return null; // Let other plugins handle bare/relative/local
121
+
122
+ // Resolve relative URLs inside esm.sh-fetched modules
123
+ const url = new URL(args.path, fromEsmNs ? args.importer : undefined);
124
+
125
+ if (url.protocol !== "https:" || url.host !== "esm.sh") {
126
+ throw new Error(`Blocked non-esm.sh URL import: ${url.href}`);
127
+ }
128
+
129
+ return { path: url.href, namespace: "esm-sh" };
130
+ });
131
+
132
+ build.onLoad({ filter: /.*/, namespace: "esm-sh" }, async (args) => {
133
+ if (httpImportCache.has(args.path)) return httpImportCache.get(args.path)!;
134
+
135
+ const res = await fetch(args.path, { redirect: "follow" });
136
+ if (!res.ok) throw new Error(`Fetch ${res.status} ${res.statusText} for ${args.path}`);
137
+ const finalUrl = new URL(res.url);
138
+ // Defensive: follow shouldn’t leave esm.sh, but re-check.
139
+ if (finalUrl.host !== "esm.sh") {
140
+ throw new Error(`Redirect escaped esm.sh: ${finalUrl.href}`);
141
+ }
142
+
143
+ const ct = (res.headers.get("content-type") || "").toLowerCase();
144
+ let loader: esbuild.Loader =
145
+ ct.includes("css") ? "css" :
146
+ ct.includes("json") ? "json" :
147
+ ct.includes("typescript") ? "ts" :
148
+ ct.includes("jsx") ? "jsx" :
149
+ ct.includes("tsx") ? "tsx" :
150
+ "js";
151
+
152
+ // Fallback by extension (esm.sh sometimes omits CT)
153
+ const p = finalUrl.pathname;
154
+ if (p.endsWith(".css")) loader = "css";
155
+ else if (p.endsWith(".json")) loader = "json";
156
+ else if (p.endsWith(".ts")) loader = "ts";
157
+ else if (p.endsWith(".tsx")) loader = "tsx";
158
+ else if (p.endsWith(".jsx")) loader = "jsx";
159
+
160
+ const contents = await res.text();
161
+ const result = {
162
+ contents,
163
+ loader,
164
+ // Ensures relative imports inside that module resolve against the file’s URL
165
+ resolveDir: new URL(".", finalUrl.href).toString(),
166
+ watchFiles: [finalUrl.href],
167
+ };
168
+ httpImportCache.set(args.path, result);
169
+ return result;
170
+ });
171
+ },
172
+ } as esbuild.Plugin] : [],
173
+ {
174
+ name: 'replace-packages-with-globals',
175
+ setup(build) {
176
+ build.onResolve({ filter: /.*/ }, args => {
177
+ // Skip packages that should remain external (not be shimmed)
178
+ if (keepAsImports.includes(args.path)) {
179
+ return undefined;
180
+ }
181
+ if (externalPackagesMap.has(args.path)) {
182
+ return { path: args.path, namespace: 'package-shim' };
183
+ }
184
+ return undefined;
185
+ });
186
+
187
+ build.onLoad({ filter: /.*/, namespace: 'package-shim' }, (args) => {
188
+ const contents = externalPackagesMap.get(args.path);
189
+ if (contents == null) throw new HexclaveAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);
190
+
191
+ return { contents, loader: 'ts' };
192
+ });
193
+ },
194
+ },
195
+ {
196
+ name: 'virtual-fs',
197
+ setup(build) {
198
+ build.onResolve({ filter: /.*/ }, args => {
199
+ const absolutePath = join("/", args.path);
200
+ if (sourceFilesMap.has(absolutePath)) {
201
+ return { path: absolutePath, namespace: 'virtual' };
202
+ }
203
+ return undefined;
204
+ });
205
+
206
+ /* 2️⃣ Load the module from the map */
207
+ build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {
208
+ const contents = sourceFilesMap.get(args.path);
209
+ if (contents == null) throw new HexclaveAssertionError(`esbuild requested file ${args.path} that is not in the virtual file system`);
210
+
211
+ const ext = args.path.split('.').pop() ?? '';
212
+ const loader = extToLoader.get(ext) ?? throwErr(`esbuild requested file ${args.path} with unknown extension ${ext}`);
213
+
214
+ return { contents, loader };
215
+ });
216
+ },
217
+ },
218
+ ],
219
+ }));
220
+ } catch (e) {
221
+ if (e instanceof Error && e.message.startsWith("Build failed with ")) {
222
+ return Result.error(e.message);
223
+ }
224
+ throw e;
225
+ }
226
+
227
+ if (result.errors.length > 0) {
228
+ return Result.error(result.errors.map(e => e.text).join('\n'));
229
+ }
230
+
231
+ if (result.outputFiles.length > 0) {
232
+ return Result.ok(result.outputFiles[0].text);
233
+ }
234
+ return throwErr("No output generated??");
235
+ }
@@ -0,0 +1,247 @@
1
+ import { getEnvVariable } from "./env";
2
+ import { HexclaveAssertionError } from "./errors";
3
+
4
+ export type FeaturebaseUser = {
5
+ userId: string,
6
+ email: string,
7
+ name?: string,
8
+ profilePicture?: string,
9
+ };
10
+
11
+ export type HexclaveAuthUser = {
12
+ id: string,
13
+ primaryEmail: string | null,
14
+ displayName?: string | null,
15
+ profileImageUrl?: string | null,
16
+ };
17
+
18
+ /**
19
+ * Find a Featurebase user by their Hexclave user ID
20
+ */
21
+ async function findFeaturebaseUserById(hexclaveAuthUserId: string, apiKey: string): Promise<FeaturebaseUser | null> {
22
+ try {
23
+ const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?id=${hexclaveAuthUserId}`, {
24
+ method: 'GET',
25
+ headers: {
26
+ 'X-API-Key': apiKey,
27
+ },
28
+ });
29
+
30
+ if (response.status === 404) {
31
+ return null;
32
+ }
33
+
34
+ if (!response.ok) {
35
+ throw new HexclaveAssertionError(`Failed to find Featurebase user by ID: ${response.statusText}`);
36
+ }
37
+
38
+ const data = await response.json();
39
+ const user = data.user;
40
+
41
+ if (!user) {
42
+ throw new HexclaveAssertionError(`Featurebase API returned success but no user data for ID: ${hexclaveAuthUserId}`, { data });
43
+ }
44
+
45
+ return {
46
+ userId: user.externalUserId || user.userId || hexclaveAuthUserId,
47
+ email: user.email,
48
+ name: user.name,
49
+ profilePicture: user.profilePicture,
50
+ };
51
+ } catch (error) {
52
+ if (error instanceof HexclaveAssertionError) {
53
+ throw error;
54
+ }
55
+ throw new HexclaveAssertionError("Failed to find Featurebase user by ID", { cause: error });
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Find a Featurebase user by their email address
61
+ */
62
+ async function findFeaturebaseUserByEmail(email: string, apiKey: string): Promise<FeaturebaseUser | null> {
63
+ try {
64
+ const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?email=${encodeURIComponent(email)}`, {
65
+ method: 'GET',
66
+ headers: {
67
+ 'X-API-Key': apiKey,
68
+ },
69
+ });
70
+
71
+ if (response.status === 404) {
72
+ return null;
73
+ }
74
+
75
+ if (!response.ok) {
76
+ throw new HexclaveAssertionError(`Failed to find Featurebase user by email: ${response.statusText}`);
77
+ }
78
+
79
+ const data = await response.json();
80
+ const user = data.user;
81
+
82
+ if (!user) {
83
+ throw new HexclaveAssertionError(`Featurebase API returned success but no user data for email: ${email}`, { data });
84
+ }
85
+
86
+ return {
87
+ userId: user.externalUserId || user.userId,
88
+ email: user.email,
89
+ name: user.name,
90
+ profilePicture: user.profilePicture,
91
+ };
92
+ } catch (error) {
93
+ console.error('Error finding Featurebase user by email:', error);
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a new Featurebase user using the identifyUser endpoint
100
+ */
101
+ async function createFeaturebaseUser(user: FeaturebaseUser, apiKey: string): Promise<FeaturebaseUser> {
102
+ try {
103
+ const response = await fetch('https://do.featurebase.app/v2/organization/identifyUser', {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'X-API-Key': apiKey,
108
+ },
109
+ body: JSON.stringify({
110
+ userId: user.userId,
111
+ email: user.email,
112
+ name: user.name,
113
+ profilePicture: user.profilePicture,
114
+ }),
115
+ });
116
+
117
+ if (!response.ok) {
118
+ const errorData = await response.json().catch(() => ({}));
119
+ throw new HexclaveAssertionError(`Failed to create Featurebase user: ${errorData.error || response.statusText}`, { errorData });
120
+ }
121
+
122
+ // The identifyUser endpoint just returns { "success": true }, so we return the input data
123
+ return user;
124
+ } catch (error) {
125
+ if (error instanceof HexclaveAssertionError) {
126
+ throw error;
127
+ }
128
+ throw new HexclaveAssertionError("Failed to create Featurebase user", { cause: error });
129
+
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Update an existing Featurebase user (excluding email)
135
+ */
136
+ async function updateFeaturebaseUser(userId: string, updates: Partial<Omit<FeaturebaseUser, 'userId' | 'email'>>, apiKey: string): Promise<FeaturebaseUser> {
137
+ try {
138
+ const response = await fetch(`https://do.featurebase.app/v2/users/${userId}`, {
139
+ method: 'PATCH',
140
+ headers: {
141
+ 'Content-Type': 'application/json',
142
+ 'X-API-Key': apiKey,
143
+ },
144
+ body: JSON.stringify(updates),
145
+ });
146
+
147
+ if (!response.ok) {
148
+ const errorData = await response.json().catch(() => ({}));
149
+ throw new HexclaveAssertionError(`Failed to update Featurebase user: ${errorData.error || response.statusText}`, { errorData });
150
+ }
151
+
152
+ const data = await response.json();
153
+ return {
154
+ userId: data.userId || userId,
155
+ email: data.email,
156
+ name: data.name,
157
+ profilePicture: data.profilePicture,
158
+ };
159
+ } catch (error) {
160
+ if (error instanceof HexclaveAssertionError) {
161
+ throw error;
162
+ }
163
+ throw new HexclaveAssertionError("Failed to update Featurebase user", { cause: error });
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Get or create a Featurebase user based on Hexclave user data.
169
+ * This function ensures that:
170
+ * 1. We never change a user's email address on Featurebase
171
+ * 2. We use Hexclave user ID as the primary identifier
172
+ * 3. We handle email conflicts by using fallback emails
173
+ * 4. We update profile information when needed
174
+ */
175
+ export async function getOrCreateFeaturebaseUser(
176
+ hexclaveAuthUser: HexclaveAuthUser,
177
+ options?: { apiKey?: string }
178
+ ): Promise<{ userId: string, email: string }> {
179
+ const apiKey = options?.apiKey || getEnvVariable("STACK_FEATUREBASE_API_KEY");
180
+ const fallbackEmail = `${hexclaveAuthUser.id}@featurebase-user.stack-auth-app.com`;
181
+
182
+ // First, try to find existing user by Hexclave user ID
183
+ const existingById = await findFeaturebaseUserById(hexclaveAuthUser.id, apiKey);
184
+ if (existingById) {
185
+ // Ensure the user has an email on Featurebase.
186
+ let ensuredEmail = existingById.email;
187
+ if (!ensuredEmail) {
188
+ try {
189
+ await createFeaturebaseUser({
190
+ userId: existingById.userId,
191
+ email: fallbackEmail,
192
+ name: hexclaveAuthUser.displayName || undefined,
193
+ profilePicture: hexclaveAuthUser.profileImageUrl || undefined,
194
+ }, apiKey);
195
+ ensuredEmail = fallbackEmail;
196
+ } catch (e) {
197
+ // If setting fallback email failed, keep ensuredEmail as-is (undefined) and let callers handle
198
+ throw new HexclaveAssertionError(`Failed to set fallback email for existing Featurebase user ${existingById.userId}`, { cause: e });
199
+ }
200
+ }
201
+
202
+ // Update profile information if needed (but not email)
203
+ try {
204
+ const updates: Partial<Omit<FeaturebaseUser, 'userId' | 'email'>> = {};
205
+
206
+ if (hexclaveAuthUser.displayName && hexclaveAuthUser.displayName !== existingById.name) {
207
+ updates.name = hexclaveAuthUser.displayName;
208
+ }
209
+
210
+ if (hexclaveAuthUser.profileImageUrl && hexclaveAuthUser.profileImageUrl !== existingById.profilePicture) {
211
+ updates.profilePicture = hexclaveAuthUser.profileImageUrl;
212
+ }
213
+
214
+ if (Object.keys(updates).length > 0) {
215
+ await updateFeaturebaseUser(existingById.userId, updates, apiKey);
216
+ }
217
+ } catch (error) {
218
+ console.error('Failed to update existing Featurebase user profile:', error);
219
+ // Continue with existing user data even if update fails
220
+ }
221
+
222
+ return {
223
+ userId: existingById.userId,
224
+ email: ensuredEmail,
225
+ };
226
+ }
227
+
228
+ // No existing user found by ID, need to create one
229
+ const candidateEmail = hexclaveAuthUser.primaryEmail ?? fallbackEmail;
230
+
231
+ // Check if someone already has this email on Featurebase
232
+ const existingByEmail = await findFeaturebaseUserByEmail(candidateEmail, apiKey);
233
+ const safeEmail = existingByEmail ? fallbackEmail : candidateEmail;
234
+
235
+ // Create new user
236
+ const created = await createFeaturebaseUser({
237
+ userId: hexclaveAuthUser.id,
238
+ email: safeEmail,
239
+ name: hexclaveAuthUser.displayName || hexclaveAuthUser.primaryEmail?.split('@')[0] || 'User',
240
+ profilePicture: hexclaveAuthUser.profileImageUrl || undefined,
241
+ }, apiKey);
242
+
243
+ return {
244
+ userId: created.userId,
245
+ email: created.email,
246
+ };
247
+ }
@@ -0,0 +1,33 @@
1
+ import * as hexclaveFs from "fs";
2
+ import * as path from "path";
3
+
4
+ export async function list(path: string) {
5
+ return await hexclaveFs.promises.readdir(path);
6
+ }
7
+
8
+ export async function listRecursively(p: string, options: { excludeDirectories?: boolean } = {}): Promise<string[]> {
9
+ const files = await list(p);
10
+ return [
11
+ ...(await Promise.all(files.map(async (fileName) => {
12
+ const filePath = path.join(p, fileName);
13
+ if ((await hexclaveFs.promises.stat(filePath)).isDirectory()) {
14
+ return [
15
+ ...(await listRecursively(filePath, options)),
16
+ ...(options.excludeDirectories ? [] : [filePath]),
17
+ ];
18
+ } else {
19
+ return [filePath];
20
+ }
21
+ }))).flat(),
22
+ ];
23
+ }
24
+
25
+ export function writeFileSyncIfChanged(path: string, content: string): void {
26
+ if (hexclaveFs.existsSync(path)) {
27
+ const existingContent = hexclaveFs.readFileSync(path, "utf-8");
28
+ if (existingContent === content) {
29
+ return;
30
+ }
31
+ }
32
+ hexclaveFs.writeFileSync(path, content);
33
+ }
@@ -0,0 +1,22 @@
1
+ export function identity<T>(t: T): T {
2
+ return t;
3
+ }
4
+ import.meta.vitest?.test("identity", ({ expect }) => {
5
+ expect(identity(1)).toBe(1);
6
+ expect(identity("test")).toBe("test");
7
+ expect(identity(null)).toBe(null);
8
+ expect(identity(undefined)).toBe(undefined);
9
+ const obj = { a: 1 };
10
+ expect(identity(obj)).toBe(obj);
11
+ });
12
+
13
+ export function identityArgs<T extends any[]>(...args: T): T {
14
+ return args;
15
+ }
16
+ import.meta.vitest?.test("identityArgs", ({ expect }) => {
17
+ expect(identityArgs()).toEqual([]);
18
+ expect(identityArgs(1)).toEqual([1]);
19
+ expect(identityArgs(1, 2, 3)).toEqual([1, 2, 3]);
20
+ expect(identityArgs("a", "b", "c")).toEqual(["a", "b", "c"]);
21
+ expect(identityArgs(null, undefined)).toEqual([null, undefined]);
22
+ });
@@ -0,0 +1,16 @@
1
+
2
+ import * as yup from "yup";
3
+ import { yupNumber, yupObject, yupString } from "../schema-fields";
4
+
5
+ export const geoInfoSchema = yupObject({
6
+ ip: yupString().defined(),
7
+ countryCode: yupString().nullable(),
8
+ regionCode: yupString().nullable(),
9
+ cityName: yupString().nullable(),
10
+ latitude: yupNumber().nullable(),
11
+ longitude: yupNumber().nullable(),
12
+ tzIdentifier: yupString().nullable(),
13
+ });
14
+
15
+ export type GeoInfo = yup.InferType<typeof geoInfoSchema>;
16
+
@@ -0,0 +1,48 @@
1
+ const globalVar: any =
2
+ typeof globalThis !== 'undefined' ? globalThis :
3
+ typeof global !== 'undefined' ? global :
4
+ typeof window !== 'undefined' ? window :
5
+ typeof self !== 'undefined' ? self :
6
+ {};
7
+ export {
8
+ globalVar
9
+ };
10
+
11
+ if (typeof globalThis === 'undefined') {
12
+ (globalVar as any).globalThis = globalVar;
13
+ }
14
+
15
+ // Hexclave rebrand: file-private symbol key, renamed outright (no cross-version compat needed).
16
+ const hexclaveGlobalsSymbol = Symbol.for('__hexclave-globals');
17
+ globalVar[hexclaveGlobalsSymbol] ??= {};
18
+
19
+ export function createGlobal<T>(key: string, init: () => T) {
20
+ if (!globalVar[hexclaveGlobalsSymbol][key]) {
21
+ globalVar[hexclaveGlobalsSymbol][key] = init();
22
+ }
23
+ return globalVar[hexclaveGlobalsSymbol][key] as T;
24
+ }
25
+
26
+ /**
27
+ * Like createGlobal, but if the asynchronous initialization fails, the global will be reset and recomputed on the next
28
+ * invocation.
29
+ */
30
+ export function createGlobalAsync<T>(key: string, init: () => Promise<T>): Promise<T> {
31
+ let promise: Promise<T> | null = null;
32
+ if (!globalVar[hexclaveGlobalsSymbol][key]) {
33
+ promise = init().catch((e) => {
34
+ delete globalVar[hexclaveGlobalsSymbol][key];
35
+ throw e;
36
+ });
37
+ globalVar[hexclaveGlobalsSymbol][key] = promise;
38
+ }
39
+ return promise ?? globalVar[hexclaveGlobalsSymbol][key] as Promise<T>;
40
+ }
41
+
42
+ export function getGlobal(key: string): any {
43
+ return globalVar[hexclaveGlobalsSymbol][key];
44
+ }
45
+
46
+ export function setGlobal(key: string, value: any) {
47
+ globalVar[hexclaveGlobalsSymbol][key] = value;
48
+ }
@@ -0,0 +1,52 @@
1
+ import bcrypt from 'bcryptjs';
2
+ import { HexclaveAssertionError } from './errors';
3
+ import { toArrayBufferBacked } from './typed-arrays';
4
+
5
+ export async function sha512(input: Uint8Array | string): Promise<Uint8Array> {
6
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : toArrayBufferBacked(input);
7
+ return new Uint8Array(await crypto.subtle.digest("SHA-512", bytes));
8
+ }
9
+
10
+ export async function hashPassword(password: string) {
11
+ const passwordBytes = new TextEncoder().encode(password);
12
+ if (passwordBytes.length >= 72) {
13
+ throw new HexclaveAssertionError(`Password is too long for bcrypt`, { len: passwordBytes.length });
14
+ }
15
+ const salt = await bcrypt.genSalt(10);
16
+ return await bcrypt.hash(password, salt);
17
+ }
18
+
19
+ export async function comparePassword(password: string, hash: string): Promise<boolean> {
20
+ switch (await getPasswordHashAlgorithm(hash)) {
21
+ case "bcrypt": {
22
+ return await bcrypt.compare(password, hash);
23
+ }
24
+ default: {
25
+ return false;
26
+ }
27
+ }
28
+ }
29
+
30
+ export async function isPasswordHashValid(hash: string) {
31
+ return !!(await getPasswordHashAlgorithm(hash));
32
+ }
33
+
34
+ export async function getPasswordHashAlgorithm(hash: string): Promise<"bcrypt" | undefined> {
35
+ if (typeof hash !== "string") {
36
+ throw new HexclaveAssertionError(`Passed non-string value to getPasswordHashAlgorithm`, { hash });
37
+ }
38
+ if (hash.match(/^\$2[ayb]\$.{56}$/)) {
39
+ try {
40
+ if (bcrypt.getRounds(hash) > 16) {
41
+ return undefined;
42
+ }
43
+ await bcrypt.compare("any string", hash);
44
+ return "bcrypt";
45
+ } catch (e) {
46
+ console.warn(`Error while checking bcrypt password hash. Assuming the hash is invalid`, e);
47
+ return undefined;
48
+ }
49
+ } else {
50
+ return undefined;
51
+ }
52
+ }