@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,581 @@
1
+ import { HexclaveAssertionError } from "./errors";
2
+ import { identity } from "./functions";
3
+ import { stringCompare } from "./strings";
4
+ import { typeAssertIs } from "./types";
5
+
6
+ export function isNotNull<T>(value: T): value is NonNullable<T> {
7
+ return value !== null && value !== undefined;
8
+ }
9
+ import.meta.vitest?.test("isNotNull", ({ expect }) => {
10
+ expect(isNotNull(null)).toBe(false);
11
+ expect(isNotNull(undefined)).toBe(false);
12
+ expect(isNotNull(0)).toBe(true);
13
+ expect(isNotNull("")).toBe(true);
14
+ expect(isNotNull(false)).toBe(true);
15
+ expect(isNotNull({})).toBe(true);
16
+ expect(isNotNull([])).toBe(true);
17
+ });
18
+
19
+ export type DeepPartial<T> = T extends object ? (T extends any[] ? { [P in keyof T]: DeepPartial<T[P]> } : { [P in keyof T]?: DeepPartial<T[P]> }) : T;
20
+ export type DeepRequired<T> = T extends object ? { [P in keyof T]-?: DeepRequired<T[P]> } : T;
21
+ export type DeepRequiredOrUndefined<T> = T extends object ? { [P in keyof { [K in keyof T]-?: K}]: DeepRequiredOrUndefined<T[P]> } : T;
22
+
23
+
24
+ /**
25
+ * Assumes both objects are primitives, arrays, or non-function plain objects, and compares them deeply.
26
+ *
27
+ * Note that since they are assumed to be plain objects, this function does not compare prototypes.
28
+ */
29
+ export function deepPlainEquals<T>(obj1: T, obj2: unknown, options: { ignoreUndefinedValues?: boolean } = {}): obj2 is T {
30
+ if (typeof obj1 !== typeof obj2) return false;
31
+ if (obj1 === obj2) return true;
32
+
33
+ switch (typeof obj1) {
34
+ case 'object': {
35
+ if (!obj1 || !obj2) return false;
36
+
37
+ if (Array.isArray(obj1) || Array.isArray(obj2)) {
38
+ if (!Array.isArray(obj1) || !Array.isArray(obj2)) return false;
39
+ if (obj1.length !== obj2.length) return false;
40
+ return obj1.every((v, i) => deepPlainEquals(v, obj2[i], options));
41
+ }
42
+
43
+ const entries1 = Object.entries(obj1).filter(([k, v]) => !options.ignoreUndefinedValues || v !== undefined);
44
+ const entries2 = Object.entries(obj2).filter(([k, v]) => !options.ignoreUndefinedValues || v !== undefined);
45
+ if (entries1.length !== entries2.length) return false;
46
+ return entries1.every(([k, v1]) => {
47
+ const e2 = entries2.find(([k2]) => k === k2);
48
+ if (!e2) return false;
49
+ return deepPlainEquals(v1, e2[1], options);
50
+ });
51
+ }
52
+ case 'undefined':
53
+ case 'string':
54
+ case 'number':
55
+ case 'boolean':
56
+ case 'bigint':
57
+ case 'symbol':
58
+ case 'function':{
59
+ return false;
60
+ }
61
+ default: {
62
+ throw new Error("Unexpected typeof " + typeof obj1);
63
+ }
64
+ }
65
+ }
66
+ import.meta.vitest?.test("deepPlainEquals", ({ expect }) => {
67
+ // Simple values
68
+ expect(deepPlainEquals(1, 1)).toBe(true);
69
+ expect(deepPlainEquals("test", "test")).toBe(true);
70
+ expect(deepPlainEquals(1, 2)).toBe(false);
71
+ expect(deepPlainEquals("test", "other")).toBe(false);
72
+
73
+ // Arrays
74
+ expect(deepPlainEquals([1, 2, 3], [1, 2, 3])).toBe(true);
75
+ expect(deepPlainEquals([1, 2, 3], [1, 2, 4])).toBe(false);
76
+ expect(deepPlainEquals([1, 2, 3], [1, 2])).toBe(false);
77
+
78
+ // Objects
79
+ expect(deepPlainEquals({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
80
+ expect(deepPlainEquals({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false);
81
+ expect(deepPlainEquals({ a: 1, b: 2 }, { a: 1 })).toBe(false);
82
+
83
+ // Nested structures
84
+ expect(deepPlainEquals({ a: 1, b: [1, 2, { c: 3 }] }, { a: 1, b: [1, 2, { c: 3 }] })).toBe(true);
85
+ expect(deepPlainEquals({ a: 1, b: [1, 2, { c: 3 }] }, { a: 1, b: [1, 2, { c: 4 }] })).toBe(false);
86
+
87
+ // With options
88
+ expect(deepPlainEquals({ a: 1, b: undefined }, { a: 1 }, { ignoreUndefinedValues: true })).toBe(true);
89
+ expect(deepPlainEquals({ a: 1, b: undefined }, { a: 1 })).toBe(false);
90
+ });
91
+
92
+ export function isCloneable<T>(obj: T): obj is Exclude<T, symbol | Function> {
93
+ return typeof obj !== 'symbol' && typeof obj !== 'function';
94
+ }
95
+
96
+ export function shallowClone<T extends object>(obj: T): T {
97
+ if (!isCloneable(obj)) throw new HexclaveAssertionError("shallowClone does not support symbols or functions", { obj });
98
+
99
+ if (Array.isArray(obj)) return obj.map(identity) as T;
100
+ return { ...obj };
101
+ }
102
+ import.meta.vitest?.test("shallowClone", ({ expect }) => {
103
+ expect(shallowClone({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
104
+ expect(shallowClone([1, 2, 3])).toEqual([1, 2, 3]);
105
+ expect(() => shallowClone(() => {})).toThrow();
106
+ });
107
+
108
+ export function deepPlainClone<T>(obj: T): T {
109
+ if (typeof obj === 'function') throw new HexclaveAssertionError("deepPlainClone does not support functions");
110
+ if (typeof obj === 'symbol') throw new HexclaveAssertionError("deepPlainClone does not support symbols");
111
+ if (typeof obj !== 'object' || !obj) return obj;
112
+ if (Array.isArray(obj)) return obj.map(deepPlainClone) as any;
113
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, deepPlainClone(v)])) as any;
114
+ }
115
+ import.meta.vitest?.test("deepPlainClone", ({ expect }) => {
116
+ // Primitive values
117
+ expect(deepPlainClone(1)).toBe(1);
118
+ expect(deepPlainClone("test")).toBe("test");
119
+ expect(deepPlainClone(null)).toBe(null);
120
+ expect(deepPlainClone(undefined)).toBe(undefined);
121
+
122
+ // Arrays
123
+ const arr = [1, 2, 3];
124
+ const clonedArr = deepPlainClone(arr);
125
+ expect(clonedArr).toEqual(arr);
126
+ expect(clonedArr).not.toBe(arr); // Different reference
127
+
128
+ // Objects
129
+ const obj = { a: 1, b: 2 };
130
+ const clonedObj = deepPlainClone(obj);
131
+ expect(clonedObj).toEqual(obj);
132
+ expect(clonedObj).not.toBe(obj); // Different reference
133
+
134
+ // Nested structures
135
+ const nested = { a: 1, b: [1, 2, { c: 3 }] };
136
+ const clonedNested = deepPlainClone(nested);
137
+ expect(clonedNested).toEqual(nested);
138
+ expect(clonedNested).not.toBe(nested); // Different reference
139
+ expect(clonedNested.b).not.toBe(nested.b); // Different reference for nested array
140
+ expect(clonedNested.b[2]).not.toBe(nested.b[2]); // Different reference for nested object
141
+
142
+ // Error cases
143
+ expect(() => deepPlainClone(() => {})).toThrow();
144
+ expect(() => deepPlainClone(Symbol())).toThrow();
145
+ });
146
+
147
+ export type DeepMerge<T, U> = U extends any ? DeepMergeNonDistributive<T, U> : never; // distributive conditional type https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
148
+ type DeepMergeNonDistributive<T, U> = Omit<T, keyof U> & Omit<U, keyof T> & DeepMergeInner<Pick<T, keyof U & keyof T>, Pick<U, keyof U & keyof T>>;
149
+ type DeepMergeInner<T, U> = {
150
+ [K in { [Ki in keyof U]-?: Ki }[keyof U]]: // we use this weird construct instead of just `keyof U` because TypeScript automatically removes the `undefined` key when using `-?` as a modifier; this is a workaround to make TypeScript not recognize the -? and for us to get the `undefined` key back
151
+ undefined extends U[K]
152
+ ? K extends keyof T
153
+ ? T[K] extends object
154
+ ? Exclude<U[K], undefined> extends object
155
+ ? DeepMerge<T[K], Exclude<U[K], undefined>>
156
+ : T[K] | Exclude<U[K], undefined>
157
+ : T[K] | Exclude<U[K], undefined>
158
+ : Exclude<U[K], undefined>
159
+ : K extends keyof T
160
+ ? T[K] extends object
161
+ ? U[K] extends object
162
+ ? DeepMerge<T[K], U[K]>
163
+ : U[K]
164
+ : U[K]
165
+ : U[K];
166
+ };
167
+ export function deepMerge<T extends {}, U extends {}>(baseObj: T, mergeObj: U): DeepMerge<T, U> {
168
+ if ([baseObj, mergeObj, ...Object.values(baseObj), ...Object.values(mergeObj)].some(o => !isCloneable(o))) throw new HexclaveAssertionError("deepMerge does not support functions or symbols", { baseObj, mergeObj });
169
+
170
+ const res: any = shallowClone(baseObj);
171
+ for (const [key, mergeValue] of Object.entries(mergeObj)) {
172
+ if (has(res, key as any)) {
173
+ const baseValue = get(res, key as any);
174
+ if (isObjectLike(baseValue) && isObjectLike(mergeValue)) {
175
+ set(res, key, deepMerge(baseValue, mergeValue));
176
+ continue;
177
+ }
178
+ }
179
+ set(res, key, mergeValue);
180
+ }
181
+ return res as any;
182
+ }
183
+ import.meta.vitest?.test("deepMerge", ({ expect }) => {
184
+ // Test merging flat objects
185
+ expect(deepMerge({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
186
+ expect(deepMerge({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
187
+ expect(deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 })).toEqual({ a: 1, b: 3, c: 4 });
188
+
189
+ // Test with nested objects
190
+ expect(deepMerge(
191
+ { a: { x: 1, y: 2 }, b: 3 },
192
+ { a: { y: 3, z: 4 }, c: 5 }
193
+ )).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 3, c: 5 });
194
+
195
+ // Test with arrays
196
+ expect(deepMerge(
197
+ { a: [1, 2], b: 3 },
198
+ { a: [3, 4], c: 5 }
199
+ )).toEqual({ a: [3, 4], b: 3, c: 5 });
200
+
201
+ // Test with null values
202
+ expect(deepMerge(
203
+ { a: { x: 1 }, b: null },
204
+ { a: { y: 2 }, b: { z: 3 } }
205
+ )).toEqual({ a: { x: 1, y: 2 }, b: { z: 3 } });
206
+
207
+ // Test with undefined values
208
+ expect(deepMerge(
209
+ { a: 1, b: undefined },
210
+ { b: 2, c: 3 }
211
+ )).toEqual({ a: 1, b: 2, c: 3 });
212
+
213
+ // Test deeply nested structures
214
+ expect(deepMerge(
215
+ {
216
+ a: {
217
+ x: { deep: 1 },
218
+ y: [1, 2]
219
+ },
220
+ b: 2
221
+ },
222
+ {
223
+ a: {
224
+ x: { deeper: 3 },
225
+ y: [3, 4]
226
+ },
227
+ c: 3
228
+ }
229
+ )).toEqual({
230
+ a: {
231
+ x: { deep: 1, deeper: 3 },
232
+ y: [3, 4]
233
+ },
234
+ b: 2,
235
+ c: 3
236
+ });
237
+
238
+ // Test with empty objects
239
+ expect(deepMerge({}, { a: 1 })).toEqual({ a: 1 });
240
+ expect(deepMerge({ a: 1 }, {})).toEqual({ a: 1 });
241
+ expect(deepMerge({}, {})).toEqual({});
242
+
243
+ // Test that original objects are not modified
244
+ const base = { a: { x: 1 }, b: 2 };
245
+ const merge = { a: { y: 2 }, c: 3 };
246
+ const baseClone = deepPlainClone(base);
247
+ const mergeClone = deepPlainClone(merge);
248
+
249
+ const result = deepMerge(base, merge);
250
+ expect(base).toEqual(baseClone);
251
+ expect(merge).toEqual(mergeClone);
252
+ expect(result).toEqual({ a: { x: 1, y: 2 }, b: 2, c: 3 });
253
+
254
+ // Test error cases
255
+ expect(() => deepMerge({ a: () => {} }, { b: 2 })).toThrow();
256
+ expect(() => deepMerge({ a: 1 }, { b: () => {} })).toThrow();
257
+ expect(() => deepMerge({ a: Symbol() }, { b: 2 })).toThrow();
258
+ expect(() => deepMerge({ a: 1 }, { b: Symbol() })).toThrow();
259
+ });
260
+
261
+ export type DeepOmit<T, U> = T extends object ? { [K in keyof T]: K extends keyof U ? (T[K] extends U[K] ? undefined : T[K]) : T[K] } : (T extends U ? undefined : T);
262
+
263
+ export function typedEntries<T extends {}>(obj: T): [Exclude<keyof T, number>, T[keyof T]][] {
264
+ return Object.entries(obj) as any;
265
+ }
266
+ import.meta.vitest?.test("typedEntries", ({ expect }) => {
267
+ expect(typedEntries({})).toEqual([]);
268
+ expect(typedEntries({ a: 1, b: 2 })).toEqual([["a", 1], ["b", 2]]);
269
+ expect(typedEntries({ a: "hello", b: true, c: null })).toEqual([["a", "hello"], ["b", true], ["c", null]]);
270
+
271
+ // Test with object containing methods
272
+ const objWithMethod = { a: 1, b: () => "test" };
273
+ const entries = typedEntries(objWithMethod);
274
+ expect(entries.length).toBe(2);
275
+ expect(entries[0][0]).toBe("a");
276
+ expect(entries[0][1]).toBe(1);
277
+ expect(entries[1][0]).toBe("b");
278
+ expect(typeof entries[1][1]).toBe("function");
279
+ });
280
+
281
+ export function typedFromEntries<K extends PropertyKey, V>(entries: (readonly [K, V])[]): Record<K, V> {
282
+ return Object.fromEntries(entries) as any;
283
+ }
284
+ import.meta.vitest?.test("typedFromEntries", ({ expect }) => {
285
+ expect(typedFromEntries([])).toEqual({});
286
+ expect(typedFromEntries([["a", 1], ["b", 2]])).toEqual({ a: 1, b: 2 });
287
+
288
+ // Test with mixed types (using type assertion)
289
+ const mixedEntries = [["a", "hello"], ["b", true], ["c", null]] as [string, string | boolean | null][];
290
+ const mixedObj = typedFromEntries(mixedEntries);
291
+ expect(mixedObj).toEqual({ a: "hello", b: true, c: null });
292
+
293
+ // Test with function values
294
+ const fn = () => "test";
295
+ type MixedValue = number | (() => string);
296
+ const fnEntries: [string, MixedValue][] = [["a", 1], ["b", fn]];
297
+ const obj = typedFromEntries(fnEntries);
298
+ expect(obj.a).toBe(1);
299
+ expect(typeof obj.b).toBe("function");
300
+ // Type assertion needed for the function call
301
+ expect((obj.b as () => string)()).toBe("test");
302
+ });
303
+
304
+ export function typedKeys<T extends {}>(obj: T): (Exclude<keyof T, number>)[] {
305
+ return Object.keys(obj) as any;
306
+ }
307
+ import.meta.vitest?.test("typedKeys", ({ expect }) => {
308
+ expect(typedKeys({})).toEqual([]);
309
+ expect(typedKeys({ a: 1, b: 2 })).toEqual(["a", "b"]);
310
+ expect(typedKeys({ a: "hello", b: true, c: null })).toEqual(["a", "b", "c"]);
311
+
312
+ // Test with object containing methods
313
+ const objWithMethod = { a: 1, b: () => "test" };
314
+ expect(typedKeys(objWithMethod)).toEqual(["a", "b"]);
315
+ });
316
+
317
+ export function typedValues<T extends {}>(obj: T): T[keyof T][] {
318
+ return Object.values(obj) as any;
319
+ }
320
+ import.meta.vitest?.test("typedValues", ({ expect }) => {
321
+ expect(typedValues({})).toEqual([]);
322
+ expect(typedValues({ a: 1, b: 2 })).toEqual([1, 2]);
323
+
324
+ // Test with mixed types
325
+ type MixedObj = { a: string, b: boolean, c: null };
326
+ const mixedObj: MixedObj = { a: "hello", b: true, c: null };
327
+ expect(typedValues(mixedObj)).toEqual(["hello", true, null]);
328
+
329
+ // Test with object containing methods
330
+ type ObjWithFn = { a: number, b: () => string };
331
+ const fn = () => "test";
332
+ const objWithMethod: ObjWithFn = { a: 1, b: fn };
333
+ const values = typedValues(objWithMethod);
334
+ expect(values.length).toBe(2);
335
+ expect(values[0]).toBe(1);
336
+ expect(typeof values[1]).toBe("function");
337
+ // Need to cast to the correct type
338
+ const fnValue = values[1] as () => string;
339
+ expect(fnValue()).toBe("test");
340
+ });
341
+
342
+ export function typedAssign<T extends {}, U extends {}>(target: T, source: U): T & U {
343
+ return Object.assign(target, source);
344
+ }
345
+ import.meta.vitest?.test("typedAssign", ({ expect }) => {
346
+ // Test with empty objects
347
+ const emptyTarget = {};
348
+ const emptyResult = typedAssign(emptyTarget, { a: 1 });
349
+ expect(emptyResult).toEqual({ a: 1 });
350
+ expect(emptyResult).toBe(emptyTarget); // Same reference
351
+
352
+ // Test with non-empty target
353
+ const target = { a: 1, b: 2 };
354
+ const result = typedAssign(target, { c: 3, d: 4 });
355
+ expect(result).toEqual({ a: 1, b: 2, c: 3, d: 4 });
356
+ expect(result).toBe(target); // Same reference
357
+
358
+ // Test with overlapping properties
359
+ const targetWithOverlap = { a: 1, b: 2 };
360
+ const resultWithOverlap = typedAssign(targetWithOverlap, { b: 3, c: 4 });
361
+ expect(resultWithOverlap).toEqual({ a: 1, b: 3, c: 4 });
362
+ expect(resultWithOverlap).toBe(targetWithOverlap); // Same reference
363
+ });
364
+
365
+ export type FilterUndefined<T> =
366
+ & { [k in keyof T as (undefined extends T[k] ? (T[k] extends undefined | void ? never : k) : never)]+?: T[k] & ({} | null) }
367
+ & { [k in keyof T as (undefined extends T[k] ? never : k)]: T[k] & ({} | null) }
368
+
369
+ /**
370
+ * Returns a new object with all undefined values removed. Useful when spreading optional parameters on an object, as
371
+ * TypeScript's `Partial<XYZ>` type allows `undefined` values.
372
+ */
373
+ export function filterUndefined<T extends object>(obj: T): FilterUndefined<T> {
374
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as any;
375
+ }
376
+ import.meta.vitest?.test("filterUndefined", ({ expect }) => {
377
+ expect(filterUndefined({})).toEqual({});
378
+ expect(filterUndefined({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
379
+ expect(filterUndefined({ a: 1, b: undefined })).toEqual({ a: 1 });
380
+ expect(filterUndefined({ a: undefined, b: undefined })).toEqual({});
381
+ expect(filterUndefined({ a: null, b: undefined })).toEqual({ a: null });
382
+ expect(filterUndefined({ a: 0, b: "", c: false, d: undefined })).toEqual({ a: 0, b: "", c: false });
383
+ });
384
+
385
+ export type FilterUndefinedOrNull<T> = FilterUndefined<{ [k in keyof T]: null extends T[k] ? NonNullable<T[k]> | undefined : T[k] }>;
386
+
387
+ /**
388
+ * Returns a new object with all undefined and null values removed. Useful when spreading optional parameters on an object, as
389
+ * TypeScript's `Partial<XYZ>` type allows `undefined` values.
390
+ */
391
+ export function filterUndefinedOrNull<T extends object>(obj: T): FilterUndefinedOrNull<T> {
392
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null)) as any;
393
+ }
394
+ import.meta.vitest?.test("filterUndefinedOrNull", ({ expect }) => {
395
+ expect(filterUndefinedOrNull({})).toEqual({});
396
+ expect(filterUndefinedOrNull({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
397
+ });
398
+
399
+ export type DeepFilterUndefined<T> = T extends object ? FilterUndefined<{ [K in keyof T]: DeepFilterUndefined<T[K]> }> : T;
400
+ typeAssertIs<DeepFilterUndefined<{ a: { b: { c?: undefined, d?: 123 } } }>, { a: { b: { d?: 123 } } }>()();
401
+
402
+ export function deepFilterUndefined<T extends object>(obj: T): DeepFilterUndefined<T> {
403
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined).map(([k, v]) => [k, isObjectLike(v) ? deepFilterUndefined(v) : v])) as any;
404
+ }
405
+ import.meta.vitest?.test("deepFilterUndefined", ({ expect }) => {
406
+ expect(deepFilterUndefined({ a: 1, b: undefined })).toEqual({ a: 1 });
407
+ });
408
+
409
+ export function pick<T extends {}, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
410
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k as K))) as any;
411
+ }
412
+ import.meta.vitest?.test("pick", ({ expect }) => {
413
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
414
+ expect(pick(obj, ["a", "c"])).toEqual({ a: 1, c: 3 });
415
+ expect(pick(obj, [])).toEqual({});
416
+ expect(pick(obj, ["a", "e" as keyof typeof obj])).toEqual({ a: 1 });
417
+ // Use type assertion for empty object to avoid TypeScript error
418
+ expect(pick({} as Record<string, unknown>, ["a"])).toEqual({});
419
+ });
420
+
421
+ export function omit<T extends {}, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
422
+ if (!Array.isArray(keys)) throw new HexclaveAssertionError("omit: keys must be an array", { obj, keys });
423
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k as K))) as any;
424
+ }
425
+ import.meta.vitest?.test("omit", ({ expect }) => {
426
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
427
+ expect(omit(obj, ["a", "c"])).toEqual({ b: 2, d: 4 });
428
+ expect(omit(obj, [])).toEqual(obj);
429
+ expect(omit(obj, ["a", "e" as keyof typeof obj])).toEqual({ b: 2, c: 3, d: 4 });
430
+ // Use type assertion for empty object to avoid TypeScript error
431
+ expect(omit({} as Record<string, unknown>, ["a"])).toEqual({});
432
+ });
433
+
434
+ export function split<T extends {}, K extends keyof T>(obj: T, keys: K[]): [Pick<T, K>, Omit<T, K>] {
435
+ return [pick(obj, keys), omit(obj, keys)];
436
+ }
437
+ import.meta.vitest?.test("split", ({ expect }) => {
438
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
439
+ expect(split(obj, ["a", "c"])).toEqual([{ a: 1, c: 3 }, { b: 2, d: 4 }]);
440
+ expect(split(obj, [])).toEqual([{}, obj]);
441
+ expect(split(obj, ["a", "e" as keyof typeof obj])).toEqual([{ a: 1 }, { b: 2, c: 3, d: 4 }]);
442
+ // Use type assertion for empty object to avoid TypeScript error
443
+ expect(split({} as Record<string, unknown>, ["a"])).toEqual([{}, {}]);
444
+ });
445
+
446
+ export function mapValues<T extends object, U>(obj: T, fn: (value: T extends (infer E)[] ? E : T[keyof T], key: keyof T) => U): Record<keyof T, U> {
447
+ if (Array.isArray(obj)) {
448
+ return obj.map((v, i) => fn(v, i as keyof T)) as any;
449
+ }
450
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v, k as keyof T)])) as any;
451
+ }
452
+ import.meta.vitest?.test("mapValues", ({ expect }) => {
453
+ expect(mapValues({ a: 1, b: 2 }, v => v * 2)).toEqual({ a: 2, b: 4 });
454
+ expect(mapValues([1, 2, 3], v => v * 2)).toEqual([2, 4, 6]);
455
+ });
456
+
457
+ export function sortKeys<T extends object>(obj: T): T {
458
+ if (Array.isArray(obj)) {
459
+ return [...obj] as any;
460
+ }
461
+ return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => stringCompare(a, b))) as any;
462
+ }
463
+ import.meta.vitest?.test("sortKeys", ({ expect }) => {
464
+ const obj = {
465
+ "1": 0,
466
+ "10": 1,
467
+ b: 2,
468
+ "2": 3,
469
+ a: 4,
470
+ "-3.33": 5,
471
+ "-4": 6,
472
+ "-3": 7,
473
+ abc: 8,
474
+ "a-b": 9,
475
+ ab: 10,
476
+ ac: 11,
477
+ aa: 12,
478
+ aab: 13,
479
+ };
480
+ expect(Object.entries(sortKeys(obj))).toEqual([
481
+ ["1", 0],
482
+ ["2", 3],
483
+ ["10", 1],
484
+ ["-3", 7],
485
+ ["-3.33", 5],
486
+ ["-4", 6],
487
+ ["a", 4],
488
+ ["a-b", 9],
489
+ ["aa", 12],
490
+ ["aab", 13],
491
+ ["ab", 10],
492
+ ["abc", 8],
493
+ ["ac", 11],
494
+ ["b", 2],
495
+ ]);
496
+ });
497
+
498
+ export function deepSortKeys<T extends object>(obj: T): T {
499
+ return sortKeys(mapValues(obj, v => isObjectLike(v) ? deepSortKeys(v) : v)) as any;
500
+ }
501
+ import.meta.vitest?.test("deepSortKeys", ({ expect }) => {
502
+ const obj = {
503
+ h: { i: { k: 9, j: 8 }, l: 10 },
504
+ b: { d: 3, c: 2 },
505
+ a: 1,
506
+ e: [4, 5, { g: 7, f: 6 }],
507
+ };
508
+ const sorted = deepSortKeys(obj);
509
+ expect(Object.entries(sorted)).toEqual([
510
+ ["a", 1],
511
+ ["b", { c: 2, d: 3 }],
512
+ ["e", [4, 5, { f: 6, g: 7 }]],
513
+ ["h", { i: { j: 8, k: 9 }, l: 10 }],
514
+ ]);
515
+ expect(Object.entries(sorted.b)).toEqual([
516
+ ["c", 2],
517
+ ["d", 3],
518
+ ]);
519
+ expect(Object.entries(sorted.e[2])).toEqual([
520
+ ["f", 6],
521
+ ["g", 7],
522
+ ]);
523
+ expect(Object.entries(sorted.h)).toEqual([
524
+ ["i", { j: 8, k: 9 }],
525
+ ["l", 10],
526
+ ]);
527
+ expect(Object.entries(sorted.h.i)).toEqual([
528
+ ["j", 8],
529
+ ["k", 9],
530
+ ]);
531
+ });
532
+
533
+ export function set<T extends object, K extends PropertyKey = keyof T>(obj: T, key: K, value: T[K & keyof T]) {
534
+ if (!isObjectLike(obj)) throw new HexclaveAssertionError(`set: obj is not an object (found: ${(obj as any) === null ? "null" : typeof obj})`, { obj, key, value });
535
+ Object.defineProperty(obj, key, { value, writable: true, configurable: true, enumerable: true });
536
+ }
537
+
538
+ export function get<T extends object, K extends PropertyKey = keyof T>(obj: T, key: K): T[K & keyof T] {
539
+ if ((obj as any) == null) throw new HexclaveAssertionError("get: obj is null or undefined", { obj, key });
540
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key);
541
+ if (!descriptor) throw new HexclaveAssertionError(`get: key ${String(key)} does not exist`, { obj, key });
542
+ return descriptor.value;
543
+ }
544
+
545
+ export function getOrUndefined<T extends object, K extends PropertyKey = keyof T>(obj: T, key: K): T[K & keyof T] | undefined {
546
+ if ((obj as any) == null) throw new HexclaveAssertionError("getOrUndefined: obj is null or undefined", { obj, key });
547
+ return has(obj, key) ? get(obj, key) : undefined;
548
+ }
549
+
550
+ export function has<T extends object, K extends PropertyKey = keyof T>(obj: T, key: K): obj is T & { [k in K & keyof T]: unknown } {
551
+ if ((obj as any) == null) throw new HexclaveAssertionError("has: obj is null or undefined", { obj, key });
552
+ return Object.prototype.hasOwnProperty.call(obj, key);
553
+ }
554
+
555
+ import.meta.vitest?.test("has", ({ expect }) => {
556
+ const obj = { a: 1, b: undefined, c: null };
557
+ expect(has(obj, "a")).toBe(true);
558
+ expect(has(obj, "b")).toBe(true);
559
+ expect(has(obj, "c")).toBe(true);
560
+ expect(has(obj, "d" as keyof typeof obj)).toBe(false);
561
+ });
562
+
563
+
564
+ export function hasAndNotUndefined<T extends object, K extends keyof T>(obj: T, key: K): obj is T & { [k in K]: Exclude<T[K], undefined> } {
565
+ return has(obj, key) && get(obj, key) !== undefined;
566
+ }
567
+
568
+ export function deleteKey<T extends object, K extends keyof T>(obj: T, key: K) {
569
+ if (has(obj, key)) {
570
+ Reflect.deleteProperty(obj, key);
571
+ } else {
572
+ throw new HexclaveAssertionError(`deleteKey: key ${String(key)} does not exist`, { obj, key });
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Returns true iff the value is an object or a function, but not null.
578
+ */
579
+ export function isObjectLike(value: unknown): value is object | Function {
580
+ return (typeof value === 'object' || typeof value === 'function') && value !== null;
581
+ }