@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,60 @@
1
+ import { templateIdentity } from "./strings";
2
+
3
+ export function escapeHtml(unsafe: string): string {
4
+ return `${unsafe}`
5
+ .replace(/&/g, '&')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, "&quot;")
9
+ .replace(/'/g, "&#039;");
10
+ }
11
+ import.meta.vitest?.test("escapeHtml", ({ expect }) => {
12
+ // Test with empty string
13
+ expect(escapeHtml("")).toBe("");
14
+
15
+ // Test with string without special characters
16
+ expect(escapeHtml("hello world")).toBe("hello world");
17
+
18
+ // Test with special characters
19
+ expect(escapeHtml("<div>")).toBe("&lt;div&gt;");
20
+ expect(escapeHtml("a & b")).toBe("a &amp; b");
21
+ expect(escapeHtml('a "quoted" string')).toBe("a &quot;quoted&quot; string");
22
+ expect(escapeHtml("it's a test")).toBe("it&#039;s a test");
23
+
24
+ // Test with multiple special characters
25
+ expect(escapeHtml("<a href=\"test\">It's a link</a>")).toBe(
26
+ "&lt;a href=&quot;test&quot;&gt;It&#039;s a link&lt;/a&gt;"
27
+ );
28
+ });
29
+
30
+ export function html(strings: TemplateStringsArray, ...values: any[]): string {
31
+ return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));
32
+ }
33
+ import.meta.vitest?.test("html", ({ expect }) => {
34
+ // Test with no interpolation
35
+ expect(html`simple string`).toBe("simple string");
36
+
37
+ // Test with string interpolation
38
+ expect(html`Hello, ${"world"}!`).toBe("Hello, world!");
39
+
40
+ // Test with number interpolation
41
+ expect(html`Count: ${42}`).toBe("Count: 42");
42
+
43
+ // Test with HTML special characters in interpolated values
44
+ expect(html`<div>${"<script>"}</div>`).toBe("<div>&lt;script&gt;</div>");
45
+
46
+ // Test with multiple interpolations
47
+ expect(html`${1} + ${2} = ${"<3"}`).toBe("1 + 2 = &lt;3");
48
+
49
+ // Test with object interpolation
50
+ const obj = { toString: () => "<object>" };
51
+ expect(html`Object: ${obj}`).toBe("Object: &lt;object&gt;");
52
+ });
53
+
54
+ export function htmlToText(untrustedHtml: string): string {
55
+
56
+ const doc = new DOMParser().parseFromString(untrustedHtml, 'text/html');
57
+
58
+ return doc.body.textContent;
59
+
60
+ }
@@ -0,0 +1,91 @@
1
+ import { decodeBase64, encodeBase64, isBase64 } from "./bytes";
2
+
3
+ export const HTTP_METHODS = {
4
+ "GET": {
5
+ safe: true,
6
+ idempotent: true,
7
+ },
8
+ "POST": {
9
+ safe: false,
10
+ idempotent: false,
11
+ },
12
+ "PUT": {
13
+ safe: false,
14
+ idempotent: true,
15
+ },
16
+ "DELETE": {
17
+ safe: false,
18
+ idempotent: true,
19
+ },
20
+ "PATCH": {
21
+ safe: false,
22
+ idempotent: false,
23
+ },
24
+ "OPTIONS": {
25
+ safe: true,
26
+ idempotent: true,
27
+ },
28
+ "HEAD": {
29
+ safe: true,
30
+ idempotent: true,
31
+ },
32
+ "TRACE": {
33
+ safe: true,
34
+ idempotent: true,
35
+ },
36
+ "CONNECT": {
37
+ safe: false,
38
+ idempotent: false,
39
+ },
40
+ } as const;
41
+ export type HttpMethod = keyof typeof HTTP_METHODS;
42
+
43
+ export function decodeBasicAuthorizationHeader(value: string): [string, string] | null {
44
+ const [type, encoded, ...rest] = value.split(' ');
45
+ if (rest.length > 0) return null;
46
+ if (!encoded) return null;
47
+ if (type !== 'Basic') return null;
48
+ if (!isBase64(encoded)) return null;
49
+ const decoded = new TextDecoder().decode(decodeBase64(encoded));
50
+ const split = decoded.split(':');
51
+ return [split[0], split.slice(1).join(':')];
52
+ }
53
+ import.meta.vitest?.test("decodeBasicAuthorizationHeader", ({ expect }) => {
54
+ // Test with valid Basic Authorization header
55
+ const username = "user";
56
+ const password = "pass";
57
+ const encoded = encodeBasicAuthorizationHeader(username, password);
58
+ expect(decodeBasicAuthorizationHeader(encoded)).toEqual([username, password]);
59
+
60
+ // Test with password containing colons
61
+ const complexPassword = "pass:with:colons";
62
+ const encodedComplex = encodeBasicAuthorizationHeader(username, complexPassword);
63
+ expect(decodeBasicAuthorizationHeader(encodedComplex)).toEqual([username, complexPassword]);
64
+
65
+ // Test with invalid headers
66
+ expect(decodeBasicAuthorizationHeader("NotBasic dXNlcjpwYXNz")).toBe(null); // Wrong type
67
+ expect(decodeBasicAuthorizationHeader("Basic")).toBe(null); // Missing encoded part
68
+ expect(decodeBasicAuthorizationHeader("Basic not-base64")).toBe(null); // Not base64
69
+ expect(decodeBasicAuthorizationHeader("Basic dXNlcjpwYXNz extra")).toBe(null); // Extra parts
70
+ });
71
+
72
+ export function encodeBasicAuthorizationHeader(id: string, password: string): string {
73
+ if (id.includes(':')) throw new Error("Basic authorization header id cannot contain ':'");
74
+ return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
75
+ }
76
+ import.meta.vitest?.test("encodeBasicAuthorizationHeader", ({ expect }) => {
77
+ // Test with simple username and password
78
+ const encoded = encodeBasicAuthorizationHeader("user", "pass");
79
+ expect(encoded).toMatch(/^Basic [A-Za-z0-9+/=]+$/); // Should start with "Basic " followed by base64
80
+
81
+ // Test with empty password
82
+ const encodedEmptyPass = encodeBasicAuthorizationHeader("user", "");
83
+ expect(encodedEmptyPass).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
84
+
85
+ // Test with password containing special characters
86
+ const encodedSpecialChars = encodeBasicAuthorizationHeader("user", "p@ss!w0rd");
87
+ expect(encodedSpecialChars).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
88
+
89
+ // Test with username containing colon should throw
90
+ expect(() => encodeBasicAuthorizationHeader("user:name", "pass")).toThrow();
91
+ });
@@ -0,0 +1,47 @@
1
+ import ipRegex from "ip-regex";
2
+
3
+ export type Ipv4Address = `${number}.${number}.${number}.${number}`;
4
+ export type Ipv6Address = string;
5
+
6
+ export function isIpAddress(ip: string) {
7
+ return ipRegex({ exact: true }).test(ip);
8
+ }
9
+ import.meta.vitest?.test("isIpAddress", ({ expect }) => {
10
+ // Test valid IPv4 addresses
11
+ expect(isIpAddress("192.168.1.1")).toBe(true);
12
+ expect(isIpAddress("127.0.0.1")).toBe(true);
13
+ expect(isIpAddress("0.0.0.0")).toBe(true);
14
+ expect(isIpAddress("255.255.255.255")).toBe(true);
15
+
16
+ // Test valid IPv6 addresses
17
+ expect(isIpAddress("::1")).toBe(true);
18
+ expect(isIpAddress("2001:db8::")).toBe(true);
19
+ expect(isIpAddress("2001:db8:85a3:8d3:1319:8a2e:370:7348")).toBe(true);
20
+
21
+ // Test invalid IP addresses
22
+ expect(isIpAddress("")).toBe(false);
23
+ expect(isIpAddress("not an ip")).toBe(false);
24
+ expect(isIpAddress("256.256.256.256")).toBe(false);
25
+ expect(isIpAddress("192.168.1")).toBe(false);
26
+ expect(isIpAddress("192.168.1.1.1")).toBe(false);
27
+ expect(isIpAddress("2001:db8::xyz")).toBe(false);
28
+ });
29
+
30
+ export function assertIpAddress(ip: string): asserts ip is Ipv4Address | Ipv6Address {
31
+ if (!isIpAddress(ip)) {
32
+ throw new Error(`Invalid IP address: ${ip}`);
33
+ }
34
+ }
35
+ import.meta.vitest?.test("assertIpAddress", ({ expect }) => {
36
+ // Test with valid IPv4 address
37
+ expect(() => assertIpAddress("192.168.1.1")).not.toThrow();
38
+
39
+ // Test with valid IPv6 address
40
+ expect(() => assertIpAddress("::1")).not.toThrow();
41
+
42
+ // Test with invalid IP addresses
43
+ expect(() => assertIpAddress("")).toThrow("Invalid IP address: ");
44
+ expect(() => assertIpAddress("not an ip")).toThrow("Invalid IP address: not an ip");
45
+ expect(() => assertIpAddress("256.256.256.256")).toThrow("Invalid IP address: 256.256.256.256");
46
+ expect(() => assertIpAddress("192.168.1")).toThrow("Invalid IP address: 192.168.1");
47
+ });
@@ -0,0 +1,200 @@
1
+ import { Result } from "./results";
2
+
3
+ export type Json =
4
+ | null
5
+ | boolean
6
+ | number
7
+ | string
8
+ | Json[]
9
+ | { [key: string]: Json };
10
+
11
+ export type ReadonlyJson =
12
+ | null
13
+ | boolean
14
+ | number
15
+ | string
16
+ | readonly ReadonlyJson[]
17
+ | { readonly [key: string]: ReadonlyJson };
18
+
19
+ export function isJsonSerializable(value: unknown): value is Json {
20
+ switch (typeof value) {
21
+ case "object": {
22
+ if (value === null) return true;
23
+ if (Array.isArray(value)) return value.every(isJsonSerializable);
24
+ return Object.keys(value).every(k => typeof k === "string") && Object.values(value).every(isJsonSerializable);
25
+ }
26
+ case "string":
27
+ case "number":
28
+ case "boolean": {
29
+ return true;
30
+ }
31
+ default: {
32
+ return false;
33
+ }
34
+ }
35
+ }
36
+ import.meta.vitest?.test("isJsonSerializable", ({ expect }) => {
37
+ // Test primitive values
38
+ expect(isJsonSerializable(null)).toBe(true);
39
+ expect(isJsonSerializable(true)).toBe(true);
40
+ expect(isJsonSerializable(false)).toBe(true);
41
+ expect(isJsonSerializable(123)).toBe(true);
42
+ expect(isJsonSerializable("string")).toBe(true);
43
+
44
+ // Test arrays
45
+ expect(isJsonSerializable([])).toBe(true);
46
+ expect(isJsonSerializable([1, 2, 3])).toBe(true);
47
+ expect(isJsonSerializable(["a", "b", "c"])).toBe(true);
48
+ expect(isJsonSerializable([1, "a", true, null])).toBe(true);
49
+ expect(isJsonSerializable([1, [2, 3], { a: "b" }])).toBe(true);
50
+
51
+ // Test objects
52
+ expect(isJsonSerializable({})).toBe(true);
53
+ expect(isJsonSerializable({ a: 1, b: 2 })).toBe(true);
54
+ expect(isJsonSerializable({ a: "string", b: true, c: null })).toBe(true);
55
+ expect(isJsonSerializable({ a: [1, 2, 3], b: { c: "d" } })).toBe(true);
56
+
57
+ // Test invalid JSON values
58
+ expect(isJsonSerializable(undefined)).toBe(false);
59
+ expect(isJsonSerializable(() => {})).toBe(false);
60
+ expect(isJsonSerializable(Symbol())).toBe(false);
61
+ expect(isJsonSerializable(BigInt(123))).toBe(false);
62
+
63
+ // Test arrays with invalid JSON values
64
+ expect(isJsonSerializable([1, undefined, 3])).toBe(false);
65
+ expect(isJsonSerializable([1, () => {}, 3])).toBe(false);
66
+
67
+ // Test objects with invalid JSON values
68
+ expect(isJsonSerializable({ a: 1, b: undefined })).toBe(false);
69
+ expect(isJsonSerializable({ a: 1, b: () => {} })).toBe(false);
70
+ });
71
+
72
+ export function parseJson(json: string): Result<Json> {
73
+ return Result.fromThrowing(() => JSON.parse(json));
74
+ }
75
+ import.meta.vitest?.test("parseJson", ({ expect }) => {
76
+ // Test valid JSON strings
77
+ const nullResult = parseJson("null");
78
+ expect(nullResult.status).toBe("ok");
79
+ if (nullResult.status === "ok") {
80
+ expect(nullResult.data).toBe(null);
81
+ }
82
+
83
+ const trueResult = parseJson("true");
84
+ expect(trueResult.status).toBe("ok");
85
+ if (trueResult.status === "ok") {
86
+ expect(trueResult.data).toBe(true);
87
+ }
88
+
89
+ const numberResult = parseJson("123");
90
+ expect(numberResult.status).toBe("ok");
91
+ if (numberResult.status === "ok") {
92
+ expect(numberResult.data).toBe(123);
93
+ }
94
+
95
+ const stringResult = parseJson('"string"');
96
+ expect(stringResult.status).toBe("ok");
97
+ if (stringResult.status === "ok") {
98
+ expect(stringResult.data).toBe("string");
99
+ }
100
+
101
+ const emptyArrayResult = parseJson("[]");
102
+ expect(emptyArrayResult.status).toBe("ok");
103
+ if (emptyArrayResult.status === "ok") {
104
+ expect(emptyArrayResult.data).toEqual([]);
105
+ }
106
+
107
+ const arrayResult = parseJson("[1,2,3]");
108
+ expect(arrayResult.status).toBe("ok");
109
+ if (arrayResult.status === "ok") {
110
+ expect(arrayResult.data).toEqual([1, 2, 3]);
111
+ }
112
+
113
+ const emptyObjectResult = parseJson("{}");
114
+ expect(emptyObjectResult.status).toBe("ok");
115
+ if (emptyObjectResult.status === "ok") {
116
+ expect(emptyObjectResult.data).toEqual({});
117
+ }
118
+
119
+ const objectResult = parseJson('{"a":1,"b":"string"}');
120
+ expect(objectResult.status).toBe("ok");
121
+ if (objectResult.status === "ok") {
122
+ expect(objectResult.data).toEqual({ a: 1, b: "string" });
123
+ }
124
+
125
+ // Test invalid JSON strings
126
+ expect(parseJson("").status).toBe("error");
127
+ expect(parseJson("undefined").status).toBe("error");
128
+ expect(parseJson("{").status).toBe("error");
129
+ expect(parseJson('{"a":1,}').status).toBe("error");
130
+ expect(parseJson("function(){}").status).toBe("error");
131
+ });
132
+
133
+ export function stringifyJson(json: Json): Result<string> {
134
+ return Result.fromThrowing(() => JSON.stringify(json));
135
+ }
136
+ import.meta.vitest?.test("stringifyJson", ({ expect }) => {
137
+ // Test primitive values
138
+ const nullResult = stringifyJson(null);
139
+ expect(nullResult.status).toBe("ok");
140
+ if (nullResult.status === "ok") {
141
+ expect(nullResult.data).toBe("null");
142
+ }
143
+
144
+ const trueResult = stringifyJson(true);
145
+ expect(trueResult.status).toBe("ok");
146
+ if (trueResult.status === "ok") {
147
+ expect(trueResult.data).toBe("true");
148
+ }
149
+
150
+ const numberResult = stringifyJson(123);
151
+ expect(numberResult.status).toBe("ok");
152
+ if (numberResult.status === "ok") {
153
+ expect(numberResult.data).toBe("123");
154
+ }
155
+
156
+ const stringResult = stringifyJson("string");
157
+ expect(stringResult.status).toBe("ok");
158
+ if (stringResult.status === "ok") {
159
+ expect(stringResult.data).toBe('"string"');
160
+ }
161
+
162
+ // Test arrays
163
+ const emptyArrayResult = stringifyJson([]);
164
+ expect(emptyArrayResult.status).toBe("ok");
165
+ if (emptyArrayResult.status === "ok") {
166
+ expect(emptyArrayResult.data).toBe("[]");
167
+ }
168
+
169
+ const arrayResult = stringifyJson([1, 2, 3]);
170
+ expect(arrayResult.status).toBe("ok");
171
+ if (arrayResult.status === "ok") {
172
+ expect(arrayResult.data).toBe("[1,2,3]");
173
+ }
174
+
175
+ // Test objects
176
+ const emptyObjectResult = stringifyJson({});
177
+ expect(emptyObjectResult.status).toBe("ok");
178
+ if (emptyObjectResult.status === "ok") {
179
+ expect(emptyObjectResult.data).toBe("{}");
180
+ }
181
+
182
+ const objectResult = stringifyJson({ a: 1, b: "string" });
183
+ expect(objectResult.status).toBe("ok");
184
+ if (objectResult.status === "ok") {
185
+ expect(objectResult.data).toBe('{"a":1,"b":"string"}');
186
+ }
187
+
188
+ // Test nested structures
189
+ const nested = { a: [1, 2, 3], b: { c: "d" } };
190
+ const nestedResult = stringifyJson(nested);
191
+ expect(nestedResult.status).toBe("ok");
192
+ if (nestedResult.status === "ok") {
193
+ expect(nestedResult.data).toBe('{"a":[1,2,3],"b":{"c":"d"}}');
194
+ }
195
+
196
+ // Test circular references (should error)
197
+ const circular: any = { a: 1 };
198
+ circular.self = circular;
199
+ expect(stringifyJson(circular).status).toBe("error");
200
+ });
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { transpileJsxForEditing, convertSentinelTokensToComments } from './jsx-editable-transpiler';
3
+
4
+ describe('transpileJsxForEditing', () => {
5
+ it('should wrap simple JSX text nodes with __Editable', () => {
6
+ const source = `
7
+ export function EmailTemplate() {
8
+ return <div>Hello World!</div>;
9
+ }
10
+ `;
11
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
12
+
13
+ expect(result.code).toContain('__Editable');
14
+ expect(result.code).toContain('Hello World!');
15
+ expect(Object.keys(result.editableRegions).length).toBeGreaterThan(0);
16
+
17
+ // Check that the editable region has the expected metadata
18
+ const region = Object.values(result.editableRegions)[0];
19
+ expect(region.originalText).toBe('Hello World!');
20
+ expect(region.sourceFile).toBe('template');
21
+ expect(region.parentElement.tagName).toBe('div');
22
+ });
23
+
24
+ it('should handle multiple text nodes in the same element', () => {
25
+ const source = `
26
+ export function EmailTemplate({ name }) {
27
+ return <div>Hello, {name}! Welcome to our platform.</div>;
28
+ }
29
+ `;
30
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
31
+
32
+ // Should have 2 editable regions (text before and after the expression)
33
+ const regions = Object.values(result.editableRegions);
34
+ expect(regions.length).toBe(2);
35
+
36
+ const texts = regions.map(r => r.originalText);
37
+ expect(texts).toContain('Hello, ');
38
+ expect(texts).toContain('! Welcome to our platform.');
39
+ });
40
+
41
+ it('should skip whitespace-only text nodes', () => {
42
+ const source = `
43
+ export function EmailTemplate() {
44
+ return (
45
+ <div>
46
+ <span>Text</span>
47
+ </div>
48
+ );
49
+ }
50
+ `;
51
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
52
+
53
+ // Should only have 1 editable region (the "Text" in span)
54
+ const regions = Object.values(result.editableRegions);
55
+ expect(regions.length).toBe(1);
56
+ expect(regions[0].originalText).toBe('Text');
57
+ });
58
+
59
+ it('should track occurrence count for duplicate text', () => {
60
+ const source = `
61
+ export function EmailTemplate() {
62
+ return (
63
+ <div>
64
+ <p>Click here</p>
65
+ <p>Click here</p>
66
+ </div>
67
+ );
68
+ }
69
+ `;
70
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
71
+
72
+ const regions = Object.values(result.editableRegions);
73
+ expect(regions.length).toBe(2);
74
+
75
+ // Both should have occurrenceCount of 2
76
+ expect(regions[0].occurrenceCount).toBe(2);
77
+ expect(regions[1].occurrenceCount).toBe(2);
78
+
79
+ // They should have different occurrence indices
80
+ expect(regions[0].occurrenceIndex).toBe(1);
81
+ expect(regions[1].occurrenceIndex).toBe(2);
82
+ });
83
+
84
+ it('should capture JSX path correctly', () => {
85
+ const source = `
86
+ export function EmailTemplate() {
87
+ return (
88
+ <Container>
89
+ <Section>
90
+ <Text>Hello</Text>
91
+ </Section>
92
+ </Container>
93
+ );
94
+ }
95
+ `;
96
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
97
+
98
+ const region = Object.values(result.editableRegions)[0];
99
+ expect(region.jsxPath).toContain('EmailTemplate');
100
+ expect(region.jsxPath).toContain('Container');
101
+ expect(region.jsxPath).toContain('Section');
102
+ expect(region.jsxPath).toContain('Text');
103
+ });
104
+
105
+ it('should capture parent element props', () => {
106
+ const source = `
107
+ export function EmailTemplate() {
108
+ return <Text className="heading" style={{ color: 'red' }}>Title</Text>;
109
+ }
110
+ `;
111
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
112
+
113
+ const region = Object.values(result.editableRegions)[0];
114
+ expect(region.parentElement.tagName).toBe('Text');
115
+ expect(region.parentElement.props.className).toBe('heading');
116
+ });
117
+
118
+ it('should inject __Editable component definition', () => {
119
+ const source = `
120
+ export function EmailTemplate() {
121
+ return <div>Test</div>;
122
+ }
123
+ `;
124
+ const result = transpileJsxForEditing(source, { sourceFile: 'template' });
125
+
126
+ expect(result.code).toContain('function __Editable');
127
+ expect(result.code).toContain('STACK_EDITABLE_START');
128
+ expect(result.code).toContain('STACK_EDITABLE_END');
129
+ });
130
+ });
131
+
132
+ describe('convertSentinelTokensToComments', () => {
133
+ it('should convert start tokens to HTML comments', () => {
134
+ const html = '<div>⟦STACK_EDITABLE_START:e0⟧Hello⟦STACK_EDITABLE_END:e0⟧</div>';
135
+ const result = convertSentinelTokensToComments(html);
136
+
137
+ expect(result).toBe('<div><!-- STACK_EDITABLE_START e0 -->Hello<!-- STACK_EDITABLE_END e0 --></div>');
138
+ });
139
+
140
+ it('should handle multiple editable regions', () => {
141
+ const html = '<div>⟦STACK_EDITABLE_START:e0⟧Hello⟦STACK_EDITABLE_END:e0⟧ ⟦STACK_EDITABLE_START:e1⟧World⟦STACK_EDITABLE_END:e1⟧</div>';
142
+ const result = convertSentinelTokensToComments(html);
143
+
144
+ expect(result).toContain('<!-- STACK_EDITABLE_START e0 -->');
145
+ expect(result).toContain('<!-- STACK_EDITABLE_END e0 -->');
146
+ expect(result).toContain('<!-- STACK_EDITABLE_START e1 -->');
147
+ expect(result).toContain('<!-- STACK_EDITABLE_END e1 -->');
148
+ });
149
+
150
+ it('should leave regular HTML unchanged', () => {
151
+ const html = '<div><p>Hello World</p></div>';
152
+ const result = convertSentinelTokensToComments(html);
153
+
154
+ expect(result).toBe(html);
155
+ });
156
+ });