@hexclave/shared 1.0.2 → 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.
- package/dist/ai/unified-prompts/reminders.js +4 -3
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +217 -30
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/config/schema.d.ts +63 -63
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config-authoring.d.ts +4 -4
- package/dist/config-authoring.d.ts.map +1 -1
- package/dist/config-authoring.js +2 -2
- package/dist/config-authoring.js.map +1 -1
- package/dist/config-rendering.d.ts +4 -4
- package/dist/config-rendering.d.ts.map +1 -1
- package/dist/config-rendering.js +19 -18
- package/dist/config-rendering.js.map +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +4 -3
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +216 -31
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/esm/config/schema.d.ts +63 -63
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +5 -5
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/config-authoring.d.ts +4 -4
- package/dist/esm/config-authoring.d.ts.map +1 -1
- package/dist/esm/config-authoring.js +2 -2
- package/dist/esm/config-authoring.js.map +1 -1
- package/dist/esm/config-rendering.d.ts +4 -4
- package/dist/esm/config-rendering.d.ts.map +1 -1
- package/dist/esm/config-rendering.js +15 -14
- package/dist/esm/config-rendering.js.map +1 -1
- package/dist/esm/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/client-side.js.map +1 -1
- package/dist/esm/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/server-side.js.map +1 -1
- package/dist/esm/hexclave-config-file.d.ts +15 -0
- package/dist/esm/hexclave-config-file.d.ts.map +1 -0
- package/dist/esm/{stack-config-file.js → hexclave-config-file.js} +7 -7
- package/dist/esm/hexclave-config-file.js.map +1 -0
- package/dist/esm/hooks/use-async-callback.js +1 -1
- package/dist/esm/hooks/use-async-external-store.js +1 -1
- package/dist/esm/hooks/use-strict-memo.js +1 -1
- package/dist/esm/interface/admin-metrics.d.ts +8 -8
- package/dist/esm/interface/admin-metrics.js.map +1 -1
- package/dist/esm/interface/client-interface.js +2 -2
- package/dist/esm/interface/client-interface.js.map +1 -1
- package/dist/esm/interface/conversations.d.ts +32 -32
- package/dist/esm/interface/crud/current-user.d.ts +9 -9
- package/dist/esm/interface/crud/email-outbox.d.ts +200 -200
- package/dist/esm/interface/crud/products.d.ts +13 -13
- package/dist/esm/interface/crud/products.d.ts.map +1 -1
- package/dist/esm/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/esm/interface/crud/project-permissions.js +5 -5
- package/dist/esm/interface/crud/project-permissions.js.map +1 -1
- package/dist/esm/interface/crud/projects.d.ts +36 -36
- package/dist/esm/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/esm/interface/crud/team-permissions.js +5 -5
- package/dist/esm/interface/crud/team-permissions.js.map +1 -1
- package/dist/esm/interface/crud/transactions.d.ts +6 -6
- package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +12 -12
- package/dist/esm/interface/page-component-versions.js +107 -107
- package/dist/esm/interface/page-component-versions.js.map +1 -1
- package/dist/esm/interface/webhooks.d.ts +2 -2
- package/dist/esm/known-errors.d.ts +2 -2
- package/dist/esm/known-errors.js +4 -4
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.d.ts +17 -7
- package/dist/esm/schema-fields.d.ts.map +1 -1
- package/dist/esm/schema-fields.js +26 -16
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/sessions.d.ts +7 -7
- package/dist/esm/utils/crypto.d.ts.map +1 -1
- package/dist/esm/utils/crypto.js.map +1 -1
- package/dist/esm/utils/currency-constants.d.ts +6 -0
- package/dist/esm/utils/currency-constants.d.ts.map +1 -1
- package/dist/esm/utils/currency-constants.js.map +1 -1
- package/dist/esm/utils/dev-env-state-path.d.ts +3 -3
- package/dist/esm/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/esm/utils/dev-env-state-path.js +4 -4
- package/dist/esm/utils/dev-env-state-path.js.map +1 -1
- package/dist/esm/utils/errors.js +2 -2
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/esbuild.d.ts.map +1 -1
- package/dist/esm/utils/esbuild.js +0 -1
- package/dist/esm/utils/esbuild.js.map +1 -1
- package/dist/esm/utils/featurebase.d.ts +3 -3
- package/dist/esm/utils/featurebase.d.ts.map +1 -1
- package/dist/esm/utils/featurebase.js +15 -15
- package/dist/esm/utils/featurebase.js.map +1 -1
- package/dist/esm/utils/fs.js +6 -6
- package/dist/esm/utils/fs.js.map +1 -1
- package/dist/esm/utils/globals.d.ts.map +1 -1
- package/dist/esm/utils/globals.js +10 -10
- package/dist/esm/utils/globals.js.map +1 -1
- package/dist/esm/utils/jwt.d.ts.map +1 -1
- package/dist/esm/utils/jwt.js +3 -3
- package/dist/esm/utils/jwt.js.map +1 -1
- package/dist/esm/utils/redirect-urls.d.ts +3 -3
- package/dist/esm/utils/redirect-urls.d.ts.map +1 -1
- package/dist/esm/utils/redirect-urls.js +5 -5
- package/dist/esm/utils/redirect-urls.js.map +1 -1
- package/dist/esm/utils/urls.js.map +1 -1
- package/dist/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/helpers/vault/client-side.js.map +1 -1
- package/dist/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/helpers/vault/server-side.js.map +1 -1
- package/dist/hexclave-config-file.d.ts +15 -0
- package/dist/hexclave-config-file.d.ts.map +1 -0
- package/dist/{stack-config-file.js → hexclave-config-file.js} +8 -8
- package/dist/hexclave-config-file.js.map +1 -0
- package/dist/hooks/use-async-callback.js +1 -1
- package/dist/hooks/use-async-external-store.js +1 -1
- package/dist/hooks/use-strict-memo.js +1 -1
- package/dist/interface/admin-metrics.d.ts +8 -8
- package/dist/interface/admin-metrics.js.map +1 -1
- package/dist/interface/client-interface.js +2 -2
- package/dist/interface/client-interface.js.map +1 -1
- package/dist/interface/conversations.d.ts +32 -32
- package/dist/interface/crud/current-user.d.ts +9 -9
- package/dist/interface/crud/email-outbox.d.ts +200 -200
- package/dist/interface/crud/products.d.ts +13 -13
- package/dist/interface/crud/products.d.ts.map +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/interface/crud/project-permissions.js +5 -5
- package/dist/interface/crud/project-permissions.js.map +1 -1
- package/dist/interface/crud/projects.d.ts +36 -36
- package/dist/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/interface/crud/team-permissions.js +5 -5
- package/dist/interface/crud/team-permissions.js.map +1 -1
- package/dist/interface/crud/transactions.d.ts +6 -6
- package/dist/interface/crud/transactions.d.ts.map +1 -1
- package/dist/interface/crud/users.d.ts +12 -12
- package/dist/interface/page-component-versions.js +107 -107
- package/dist/interface/page-component-versions.js.map +1 -1
- package/dist/interface/webhooks.d.ts +2 -2
- package/dist/known-errors.d.ts +2 -2
- package/dist/known-errors.js +4 -4
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.ts +17 -7
- package/dist/schema-fields.d.ts.map +1 -1
- package/dist/schema-fields.js +26 -16
- package/dist/schema-fields.js.map +1 -1
- package/dist/sessions.d.ts +7 -7
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/currency-constants.d.ts +6 -0
- package/dist/utils/currency-constants.d.ts.map +1 -1
- package/dist/utils/currency-constants.js.map +1 -1
- package/dist/utils/dev-env-state-path.d.ts +3 -3
- package/dist/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/utils/dev-env-state-path.js +5 -5
- package/dist/utils/dev-env-state-path.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/esbuild.d.ts.map +1 -1
- package/dist/utils/esbuild.js +0 -1
- package/dist/utils/esbuild.js.map +1 -1
- package/dist/utils/featurebase.d.ts +3 -3
- package/dist/utils/featurebase.d.ts.map +1 -1
- package/dist/utils/featurebase.js +15 -15
- package/dist/utils/featurebase.js.map +1 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/globals.d.ts.map +1 -1
- package/dist/utils/globals.js +10 -10
- package/dist/utils/globals.js.map +1 -1
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/jwt.js.map +1 -1
- package/dist/utils/redirect-urls.d.ts +3 -3
- package/dist/utils/redirect-urls.d.ts.map +1 -1
- package/dist/utils/redirect-urls.js +5 -5
- package/dist/utils/redirect-urls.js.map +1 -1
- package/dist/utils/urls.js.map +1 -1
- package/package.json +2 -1
- package/src/ai/image-limits.ts +62 -0
- package/src/ai/llms/llms.ts +36 -0
- package/src/ai/unified-prompts/reminders.ts +27 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +1047 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/brain.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/cli-help.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/config-docs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/custom-components-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/dashboard-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-index.ts +80 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +322 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/sdk-interface-source.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt.ts +72 -0
- package/src/apps/apps-config.ts +177 -0
- package/src/apps/apps-ui.tsx +112 -0
- package/src/config/README.md +154 -0
- package/src/config/db-sync-mappings.ts +2293 -0
- package/src/config/format.ts +388 -0
- package/src/config/migrate-catalogs-to-product-lines.ts +475 -0
- package/src/config/schema-fuzzer.test.ts +377 -0
- package/src/config/schema.ts +1507 -0
- package/src/config-authoring.test.ts +52 -0
- package/src/config-authoring.ts +37 -0
- package/src/config-rendering.ts +142 -0
- package/src/crud.tsx +275 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/emails.ts +250 -0
- package/src/helpers/init-prompt.ts +39 -0
- package/src/helpers/password.ts +16 -0
- package/src/helpers/production-mode.ts +58 -0
- package/src/helpers/support-sla.test.ts +124 -0
- package/src/helpers/support-sla.ts +93 -0
- package/src/helpers/vault/client-side.ts +92 -0
- package/src/helpers/vault/server-side.ts +153 -0
- package/src/hexclave-config-file.ts +120 -0
- package/src/hooks/use-async-callback.tsx +43 -0
- package/src/hooks/use-async-external-store.tsx +24 -0
- package/src/hooks/use-hash.tsx +14 -0
- package/src/hooks/use-hover.tsx +88 -0
- package/src/hooks/use-strict-memo.tsx +157 -0
- package/src/index.ts +17 -0
- package/src/interface/admin-interface.ts +1043 -0
- package/src/interface/admin-metrics.ts +214 -0
- package/src/interface/client-interface.test.ts +765 -0
- package/src/interface/client-interface.ts +2586 -0
- package/src/interface/conversations.ts +109 -0
- package/src/interface/crud/analytics.ts +11 -0
- package/src/interface/crud/connected-accounts.ts +52 -0
- package/src/interface/crud/contact-channels.ts +74 -0
- package/src/interface/crud/current-user.ts +72 -0
- package/src/interface/crud/email-outbox.ts +263 -0
- package/src/interface/crud/email-templates.ts +50 -0
- package/src/interface/crud/emails.ts +22 -0
- package/src/interface/crud/internal-api-keys.ts +69 -0
- package/src/interface/crud/invoices.ts +36 -0
- package/src/interface/crud/items.ts +24 -0
- package/src/interface/crud/notification-preferences.ts +33 -0
- package/src/interface/crud/oauth-providers.ts +91 -0
- package/src/interface/crud/products.ts +42 -0
- package/src/interface/crud/project-api-keys.ts +100 -0
- package/src/interface/crud/project-permissions.ts +118 -0
- package/src/interface/crud/projects.ts +229 -0
- package/src/interface/crud/session-replays.ts +85 -0
- package/src/interface/crud/sessions.ts +66 -0
- package/src/interface/crud/sign-up-rules.ts +12 -0
- package/src/interface/crud/svix-token.ts +20 -0
- package/src/interface/crud/team-invitation-details.ts +22 -0
- package/src/interface/crud/team-invitation.ts +35 -0
- package/src/interface/crud/team-member-profiles.ts +61 -0
- package/src/interface/crud/team-memberships.ts +56 -0
- package/src/interface/crud/team-permissions.ts +119 -0
- package/src/interface/crud/teams.ts +142 -0
- package/src/interface/crud/transactions.ts +131 -0
- package/src/interface/crud/users.ts +185 -0
- package/src/interface/handler-urls.ts +74 -0
- package/src/interface/page-component-versions.ts +1702 -0
- package/src/interface/server-interface.ts +1146 -0
- package/src/interface/webhooks.ts +28 -0
- package/src/known-errors.tsx +2031 -0
- package/src/local-emulator.ts +5 -0
- package/src/payments/payment-methods.ts +167 -0
- package/src/payments/stripe-limits.ts +18 -0
- package/src/plans.ts +107 -0
- package/src/schema-fields.ts +944 -0
- package/src/sessions.ts +310 -0
- package/src/utils/api-keys.tsx +122 -0
- package/src/utils/arrays.tsx +248 -0
- package/src/utils/auth-methods.ts +8 -0
- package/src/utils/base64.tsx +24 -0
- package/src/utils/booleans.tsx +34 -0
- package/src/utils/browser-compat.tsx +17 -0
- package/src/utils/bytes.tsx +280 -0
- package/src/utils/caches.tsx +280 -0
- package/src/utils/cel-fields.ts +92 -0
- package/src/utils/cloud-hosts.tsx +92 -0
- package/src/utils/compile-time.tsx +11 -0
- package/src/utils/country-codes.ts +39 -0
- package/src/utils/crypto.tsx +174 -0
- package/src/utils/currencies.tsx +53 -0
- package/src/utils/currency-constants.tsx +51 -0
- package/src/utils/dates.tsx +246 -0
- package/src/utils/dev-env-state-path.ts +14 -0
- package/src/utils/dom.tsx +7 -0
- package/src/utils/env.tsx +117 -0
- package/src/utils/errors.tsx +284 -0
- package/src/utils/esbuild.tsx +235 -0
- package/src/utils/featurebase.tsx +247 -0
- package/src/utils/fs.tsx +33 -0
- package/src/utils/functions.tsx +22 -0
- package/src/utils/geo.tsx +16 -0
- package/src/utils/globals.tsx +48 -0
- package/src/utils/hashes.tsx +52 -0
- package/src/utils/html.tsx +60 -0
- package/src/utils/http.tsx +91 -0
- package/src/utils/ips.tsx +47 -0
- package/src/utils/json.tsx +200 -0
- package/src/utils/jsx-editable-transpiler.test.ts +156 -0
- package/src/utils/jsx-editable-transpiler.tsx +401 -0
- package/src/utils/jwt.tsx +170 -0
- package/src/utils/locks.tsx +71 -0
- package/src/utils/maps.tsx +407 -0
- package/src/utils/math.tsx +18 -0
- package/src/utils/monkey-patch.tsx +29 -0
- package/src/utils/node-http.tsx +47 -0
- package/src/utils/numbers.tsx +82 -0
- package/src/utils/oauth.tsx +9 -0
- package/src/utils/objects.tsx +581 -0
- package/src/utils/paginated-lists.test.ts +1039 -0
- package/src/utils/paginated-lists.tsx +479 -0
- package/src/utils/passkey.tsx +1 -0
- package/src/utils/promises.tsx +540 -0
- package/src/utils/proxies.tsx +212 -0
- package/src/utils/react.tsx +308 -0
- package/src/utils/redirect-urls.tsx +299 -0
- package/src/utils/regex.tsx +10 -0
- package/src/utils/results.tsx +411 -0
- package/src/utils/sentry.tsx +20 -0
- package/src/utils/stores.tsx +238 -0
- package/src/utils/strings.nicify.test.ts +253 -0
- package/src/utils/strings.tsx +723 -0
- package/src/utils/telemetry.tsx +37 -0
- package/src/utils/turnstile-browser.ts +106 -0
- package/src/utils/turnstile-flow.test.ts +101 -0
- package/src/utils/turnstile-flow.ts +266 -0
- package/src/utils/turnstile.ts +34 -0
- package/src/utils/typed-arrays.tsx +17 -0
- package/src/utils/types.tsx +208 -0
- package/src/utils/unicode.tsx +24 -0
- package/src/utils/urls.tsx +411 -0
- package/src/utils/uuids.tsx +47 -0
- package/dist/esm/stack-config-file.d.ts +0 -15
- package/dist/esm/stack-config-file.d.ts.map +0 -1
- package/dist/esm/stack-config-file.js.map +0 -1
- package/dist/stack-config-file.d.ts +0 -15
- package/dist/stack-config-file.d.ts.map +0 -1
- package/dist/stack-config-file.js.map +0 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { DeepPartial } from "./objects";
|
|
2
|
+
import { Join } from "./strings";
|
|
3
|
+
|
|
4
|
+
export type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
5
|
+
export type IsNever<T> = [T] extends [never] ? true : false;
|
|
6
|
+
export type IsNullish<T> = [T] extends [null | undefined] ? true : false;
|
|
7
|
+
export type IsUnion<T, U = T> =
|
|
8
|
+
IsNever<T> extends true ? false
|
|
9
|
+
: IsAny<T> extends true ? false
|
|
10
|
+
: T extends U // distributive conditional https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
|
|
11
|
+
? /* if the *whole* original type (`U`) still fits inside the current variant, then `T` wasn’t a union */ ([U] extends [T] ? false : true)
|
|
12
|
+
: never;
|
|
13
|
+
|
|
14
|
+
export type NullishCoalesce<T, U> = T extends null | undefined ? U : T;
|
|
15
|
+
|
|
16
|
+
export type LastUnionElement<U> = UnionToIntersection<U extends any ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L & U : never;
|
|
17
|
+
|
|
18
|
+
type primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Makes a type prettier by recursively expanding all object types. For example, `Omit<{ a: 1 }, "a">` becomes just `{}`.
|
|
22
|
+
*/
|
|
23
|
+
export type Expand<T> = T extends (...args: infer A) => infer R
|
|
24
|
+
? ((...args: A) => R) extends T
|
|
25
|
+
? (...args: Expand<A>) => Expand<R>
|
|
26
|
+
: ((...args: Expand<A>) => Expand<R>) & { [K in keyof T]: Expand<T[K]> }
|
|
27
|
+
: T extends object
|
|
28
|
+
? T extends primitive
|
|
29
|
+
? T
|
|
30
|
+
: T extends infer O
|
|
31
|
+
? { [K in keyof O]: Expand<O[K]> }
|
|
32
|
+
: never
|
|
33
|
+
: T;
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Removes all optional undefined/never keys from an object.
|
|
38
|
+
*/
|
|
39
|
+
export type DeepRemoveOptionalUndefined<T> = T extends object ? { [K in keyof T]: DeepRemoveOptionalUndefined<T[K]> } : T;
|
|
40
|
+
|
|
41
|
+
// why this works: https://stackoverflow.com/a/50375286
|
|
42
|
+
export type UnionToIntersection<U> =
|
|
43
|
+
(U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never
|
|
44
|
+
|
|
45
|
+
type _UnionToTupleInner<U, R extends any[], Last> = UnionToTuple<Exclude<U, Last>, [...R, Last]>
|
|
46
|
+
export type UnionToTuple<U, R extends any[] = []> = [U] extends [never] ? R : _UnionToTupleInner<U, R, LastUnionElement<U>>;
|
|
47
|
+
|
|
48
|
+
export type CollapseObjectUnion<T extends object> = {
|
|
49
|
+
[K in AllUnionKeys<T>]?: T extends Record<K, infer V> ? V : never;
|
|
50
|
+
};
|
|
51
|
+
typeAssertIs<CollapseObjectUnion<{ a: string } | { b: number }>, { a?: string, b?: number }>()();
|
|
52
|
+
typeAssertIs<CollapseObjectUnion<{ a: string } | { a: number }>, { a?: string | number }>()();
|
|
53
|
+
|
|
54
|
+
export type IntersectAll<T extends any[]> = UnionToIntersection<T[number]>;
|
|
55
|
+
|
|
56
|
+
export type OptionalKeys<T> = {
|
|
57
|
+
[K in keyof T]: {} extends Pick<T, K> ? K : never;
|
|
58
|
+
}[keyof T];
|
|
59
|
+
export type RequiredKeys<T> = {
|
|
60
|
+
[K in keyof T]: {} extends Pick<T, K> ? never : K;
|
|
61
|
+
}[keyof T];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns a type whose keys are the intersection of the keys of T and U, deeply.
|
|
65
|
+
*/
|
|
66
|
+
export type KeyIntersect<T, U> =
|
|
67
|
+
& { [K in keyof T & keyof U]?: T[K] & U[K] }
|
|
68
|
+
& { [K in RequiredKeys<T> & keyof U]: T[K] & U[K] }
|
|
69
|
+
& { [K in RequiredKeys<U> & keyof T]: U[K] & T[K] }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns ALL keys of all union elements.
|
|
73
|
+
*/
|
|
74
|
+
export type AllUnionKeys<T extends object> = T extends T ? keyof T : never;
|
|
75
|
+
typeAssertIs<AllUnionKeys<{ a: string } | { b: number }>, "a" | "b">()();
|
|
76
|
+
|
|
77
|
+
export type SubtractType<T, U> = T extends object ? { [K in keyof T]: K extends keyof U ? SubtractType<T[K], U[K]> : T[K] } : (T extends U ? never : T); // note: this only works due to the distributive property of conditional types https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
|
|
78
|
+
|
|
79
|
+
export type XOR<T extends readonly any[]> = T extends readonly [infer A, infer B, ...infer Rest]
|
|
80
|
+
? Rest extends []
|
|
81
|
+
? (A & { [K in keyof B]?: never }) | (B & { [K in keyof A]?: never })
|
|
82
|
+
: XOR<[(A & { [K in keyof B]?: never }) | (B & { [K in keyof A]?: never }), ...Rest]>
|
|
83
|
+
: T[0];
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
type _AntiIntersectInner<T, U> = T extends object ? (
|
|
87
|
+
& Omit<U, keyof T>
|
|
88
|
+
& { [K in keyof Pick<U, { [K in keyof T & keyof U]: U[K] extends T[K] ? (T[K] extends U[K] ? never : K) : never }[keyof T & keyof U]>]: PseudoAntiIntersect<T[K], U[K]> }
|
|
89
|
+
& { [K in keyof Pick<U, keyof T & keyof U>]?: PseudoAntiIntersect<T[K], U[K]> }
|
|
90
|
+
) : U;
|
|
91
|
+
/**
|
|
92
|
+
* Returns a type R such that T & R = U.
|
|
93
|
+
*/
|
|
94
|
+
export type AntiIntersect<T, U> = U extends T ? _AntiIntersectInner<T, U> : "Cannot anti-intersect a type with a type that is not a subtype of it"; // NOTE: This type is mostly untested — not sure how well it works on the edge cases
|
|
95
|
+
export type PseudoAntiIntersect<T, U> = _AntiIntersectInner<T, T & U>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A variation of TypeScript's conditionals with slightly different semantics. It is the perfect type for cases where:
|
|
99
|
+
*
|
|
100
|
+
* - If all possible values are contained in `Extends`, then it will be mapped to `Then`.
|
|
101
|
+
* - If all possible values are not contained in `Extends`, then it will be mapped to `Otherwise`.
|
|
102
|
+
* - If some possible values are contained in `Extends` and some are not, then it will be mapped to `Then | Otherwise`.
|
|
103
|
+
*
|
|
104
|
+
* This is different from TypeScript's built-in conditional types (`Value extends Extends ? Then : Otherwise`), which
|
|
105
|
+
* returns `Otherwise` for the third case (causing unsoundness in many real-world cases).
|
|
106
|
+
*/
|
|
107
|
+
export type IfAndOnlyIf<Value, Extends, Then, Otherwise> =
|
|
108
|
+
| (Value extends Extends ? never : Otherwise)
|
|
109
|
+
| (Value & Extends extends never ? never : Then);
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Can be used to prettify a type in the IDE; for example, some complicated intersected types can be flattened into a single type.
|
|
114
|
+
*/
|
|
115
|
+
export type PrettifyType<T> = T extends object ? { [K in keyof T]: T[K] } & {} : T;
|
|
116
|
+
|
|
117
|
+
type _ToStringAndJoin<T extends any[], Separator extends string> =
|
|
118
|
+
T extends [infer U, ...infer Rest extends any[]]
|
|
119
|
+
? `${TypeToString<U>}${Rest extends [any, ...any[]] ? `${Separator}${_ToStringAndJoin<Rest, Separator>}` : ""}`
|
|
120
|
+
: "<error-joining-tuple-elements>";
|
|
121
|
+
type _TypeToStringInner<T> =
|
|
122
|
+
IsAny<T> extends true ? "any"
|
|
123
|
+
: IsNever<T> extends true ? "never"
|
|
124
|
+
: IsUnion<T> extends true ? _ToStringAndJoin<UnionToTuple<T>, " | ">
|
|
125
|
+
: [T] extends [number] ? (number extends T ? "number" : `${T}`)
|
|
126
|
+
: [T] extends [boolean] ? `${T}`
|
|
127
|
+
: [T] extends [undefined] ? "undefined"
|
|
128
|
+
: [T] extends [null] ? "null"
|
|
129
|
+
: [T] extends [string] ? (string extends T ? "string" : `'${T}'`)
|
|
130
|
+
: [T] extends [[]] ? "[]"
|
|
131
|
+
: [T] extends [[any, ...any[]]] ? `[${_ToStringAndJoin<T, ", ">}]`
|
|
132
|
+
: [T] extends [(infer E)[]] ? `${TypeToString<E>}[]`
|
|
133
|
+
: [T] extends [Function] ? "function"
|
|
134
|
+
: [T] extends [symbol] ? `symbol(${T['description']})`
|
|
135
|
+
: [T] extends [object] ? `{ ${Join<UnionToTuple<{ [K in keyof T]: `${TypeToString<K>}: ${TypeToString<T[K]>}` }[keyof T]>, ", ">} }`
|
|
136
|
+
: "<unknown-type>"
|
|
137
|
+
export type TypeToString<T> = _TypeToStringInner<T> extends `${infer S}` ? S : never;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Can be used to create assertions on types. For example, if passed any T other than `true`, the following will
|
|
141
|
+
* show a type error:
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* typeAssert<T>()(); // the second pair of braces is important!
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export function typeAssert<T>(): (
|
|
148
|
+
IsAny<T> extends true ? TypeAssertionError<`Type assertion failed. Expected true, but got any.`>
|
|
149
|
+
: IsNever<T> extends true ? TypeAssertionError<`Type assertion failed. Expected true, but got never.`>
|
|
150
|
+
: T extends true ? (() => undefined)
|
|
151
|
+
: TypeAssertionError<`Type assertion failed. Expected true, but got: ${TypeToString<T>}`>
|
|
152
|
+
) {
|
|
153
|
+
return (() => undefined) as any;
|
|
154
|
+
}
|
|
155
|
+
type TypeAssertionError<T> =
|
|
156
|
+
& [T]
|
|
157
|
+
& /* this promise makes sure that if we accidentally forget the second pair of braces, eslint will complain (if we have no-floating-promises enabled) */ Promise<any>;
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
typeAssertExtends<ReturnType<typeof typeAssert<true>>, () => undefined>()();
|
|
161
|
+
typeAssertExtends<ReturnType<typeof typeAssert<false>>, TypeAssertionError<`Type assertion failed. Expected true, but got: false`>>()();
|
|
162
|
+
typeAssertExtends<ReturnType<typeof typeAssert<never>>, TypeAssertionError<`Type assertion failed. Expected true, but got never.`>>()();
|
|
163
|
+
typeAssertExtends<ReturnType<typeof typeAssert<any>>, TypeAssertionError<`Type assertion failed. Expected true, but got any.`>>()();
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Functionally equivalent to `typeAssert<T extends S ? true : false>()()`, but with better error messages.
|
|
167
|
+
*/
|
|
168
|
+
export function typeAssertExtends<T, S>(): (
|
|
169
|
+
[T] extends [S] ? (() => undefined) : TypeAssertionError<`Type assertion failed. Expected ${TypeToString<T>} to extend ${TypeToString<S>}`>
|
|
170
|
+
) {
|
|
171
|
+
return (() => undefined) as any;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<never, true>>, () => undefined>()();
|
|
175
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<any, true>>, () => undefined>()();
|
|
176
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<false, false>>, () => undefined>()();
|
|
177
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<"abc", string>>, () => undefined>()();
|
|
178
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<{a: 1, b: 123}, {a: number}>>, () => undefined>()();
|
|
179
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<never, never>>, () => undefined>()();
|
|
180
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<true, any>>, () => undefined>()();
|
|
181
|
+
|
|
182
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<{a: number}, {a: 1}>>, ["Type assertion failed. Expected { 'a': number } to extend { 'a': 1 }"]>()();
|
|
183
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<any, never>>, ["Type assertion failed. Expected any to extend never"]>()();
|
|
184
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<false, true>>, ["Type assertion failed. Expected false to extend true"]>()();
|
|
185
|
+
typeAssertExtends<ReturnType<typeof typeAssertExtends<false, never>>, ["Type assertion failed. Expected false to extend never"]>()();
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
export function typeAssertIs<T, U>(): (
|
|
189
|
+
IsAny<T> extends true ? (IsAny<U> extends true ? (() => undefined) : TypeAssertionError<`Type assertion failed. Expected ${TypeToString<T>} to be ${TypeToString<U>}`>)
|
|
190
|
+
: IsAny<U> extends true ? TypeAssertionError<`Type assertion failed. Expected ${TypeToString<T>} to be ${TypeToString<U>}`>
|
|
191
|
+
: [T] extends [U] ? ([U] extends [T] ? (() => undefined) : TypeAssertionError<`Type assertion failed. Expected ${TypeToString<T>} to be ${TypeToString<U>}`>)
|
|
192
|
+
: TypeAssertionError<`Type assertion failed. Expected ${TypeToString<T>} to be ${TypeToString<U>}`>
|
|
193
|
+
) {
|
|
194
|
+
return (() => undefined) as any;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<"123", "123">>, () => undefined>()();
|
|
198
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<{a: 1}, {a: 1}>>, () => undefined>()();
|
|
199
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<DeepPartial<{a: 1}>, {a?: 1}>>, () => undefined>()();
|
|
200
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<any, any>>, () => undefined>()();
|
|
201
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<never, never>>, () => undefined>()();
|
|
202
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<1, any>>, ["Type assertion failed. Expected 1 to be any"]>()();
|
|
203
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<any, 1>>, ["Type assertion failed. Expected any to be 1"]>()();
|
|
204
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<false, true>>, ["Type assertion failed. Expected false to be true"]>()();
|
|
205
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<{a: number}, {a: 1}>>, ["Type assertion failed. Expected { 'a': number } to be { 'a': 1 }"]>()();
|
|
206
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<any, never>>, ["Type assertion failed. Expected any to be never"]>()();
|
|
207
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<false, true>>, ["Type assertion failed. Expected false to be true"]>()();
|
|
208
|
+
typeAssertExtends<ReturnType<typeof typeAssertIs<false, never>>, ["Type assertion failed. Expected false to be never"]>()();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isValidCountryCode, normalizeCountryCode } from "./country-codes";
|
|
2
|
+
import { HexclaveAssertionError } from "./errors";
|
|
3
|
+
|
|
4
|
+
export function getFlagEmoji(twoLetterCountryCode: string) {
|
|
5
|
+
if (!isValidCountryCode(twoLetterCountryCode)) throw new HexclaveAssertionError("Country code must be two alphabetical letters");
|
|
6
|
+
const codePoints = normalizeCountryCode(twoLetterCountryCode)
|
|
7
|
+
.split('')
|
|
8
|
+
.map(char => 127397 + char.charCodeAt(0));
|
|
9
|
+
return String.fromCodePoint(...codePoints);
|
|
10
|
+
}
|
|
11
|
+
import.meta.vitest?.test("getFlagEmoji", ({ expect }) => {
|
|
12
|
+
// Test with valid country codes
|
|
13
|
+
expect(getFlagEmoji("US")).toBe("🇺🇸");
|
|
14
|
+
expect(getFlagEmoji("us")).toBe("🇺🇸");
|
|
15
|
+
expect(getFlagEmoji("GB")).toBe("🇬🇧");
|
|
16
|
+
expect(getFlagEmoji("JP")).toBe("🇯🇵");
|
|
17
|
+
|
|
18
|
+
// Test with invalid country codes
|
|
19
|
+
expect(() => getFlagEmoji("")).toThrow("Country code must be two alphabetical letters");
|
|
20
|
+
expect(() => getFlagEmoji("A")).toThrow("Country code must be two alphabetical letters");
|
|
21
|
+
expect(() => getFlagEmoji("ABC")).toThrow("Country code must be two alphabetical letters");
|
|
22
|
+
expect(() => getFlagEmoji("12")).toThrow("Country code must be two alphabetical letters");
|
|
23
|
+
expect(() => getFlagEmoji("A1")).toThrow("Country code must be two alphabetical letters");
|
|
24
|
+
});
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { generateSecureRandomString } from "./crypto";
|
|
2
|
+
import { templateIdentity } from "./strings";
|
|
3
|
+
|
|
4
|
+
export function createUrlIfValid(...args: ConstructorParameters<typeof URL>) {
|
|
5
|
+
try {
|
|
6
|
+
return new URL(...args);
|
|
7
|
+
} catch (e) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
import.meta.vitest?.test("createUrlIfValid", ({ expect }) => {
|
|
12
|
+
// Test with valid URLs
|
|
13
|
+
expect(createUrlIfValid("https://example.com")).toBeInstanceOf(URL);
|
|
14
|
+
expect(createUrlIfValid("https://example.com/path?query=value#hash")).toBeInstanceOf(URL);
|
|
15
|
+
expect(createUrlIfValid("/path", "https://example.com")).toBeInstanceOf(URL);
|
|
16
|
+
|
|
17
|
+
// Test with invalid URLs
|
|
18
|
+
expect(createUrlIfValid("")).toBeNull();
|
|
19
|
+
expect(createUrlIfValid("not a url")).toBeNull();
|
|
20
|
+
expect(createUrlIfValid("http://")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export function isValidUrl(url: string) {
|
|
24
|
+
return !!createUrlIfValid(url);
|
|
25
|
+
}
|
|
26
|
+
import.meta.vitest?.test("isValidUrl", ({ expect }) => {
|
|
27
|
+
// Test with valid URLs
|
|
28
|
+
expect(isValidUrl("https://example.com")).toBe(true);
|
|
29
|
+
expect(isValidUrl("http://localhost:3000")).toBe(true);
|
|
30
|
+
expect(isValidUrl("ftp://example.com")).toBe(true);
|
|
31
|
+
|
|
32
|
+
// Test with invalid URLs
|
|
33
|
+
expect(isValidUrl("")).toBe(false);
|
|
34
|
+
expect(isValidUrl("not a url")).toBe(false);
|
|
35
|
+
expect(isValidUrl("http://")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export function isValidHostname(hostname: string) {
|
|
39
|
+
// Basic validation
|
|
40
|
+
if (!hostname || hostname.startsWith('.') || hostname.endsWith('.') || hostname.includes('..')) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const url = createUrlIfValid(`https://${hostname}`);
|
|
45
|
+
if (!url) return false;
|
|
46
|
+
return url.hostname === hostname;
|
|
47
|
+
}
|
|
48
|
+
import.meta.vitest?.test("isValidHostname", ({ expect }) => {
|
|
49
|
+
// Test with valid hostnames
|
|
50
|
+
expect(isValidHostname("example.com")).toBe(true);
|
|
51
|
+
expect(isValidHostname("localhost")).toBe(true);
|
|
52
|
+
expect(isValidHostname("sub.domain.example.com")).toBe(true);
|
|
53
|
+
expect(isValidHostname("127.0.0.1")).toBe(true);
|
|
54
|
+
|
|
55
|
+
// Test with invalid hostnames
|
|
56
|
+
expect(isValidHostname("")).toBe(false);
|
|
57
|
+
expect(isValidHostname("example.com/path")).toBe(false);
|
|
58
|
+
expect(isValidHostname("https://example.com")).toBe(false);
|
|
59
|
+
expect(isValidHostname("example com")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export function isValidHostnameWithWildcards(hostname: string) {
|
|
63
|
+
// Empty hostnames are invalid
|
|
64
|
+
if (!hostname) return false;
|
|
65
|
+
|
|
66
|
+
// Check if it contains wildcards
|
|
67
|
+
const hasWildcard = hostname.includes('*');
|
|
68
|
+
|
|
69
|
+
if (!hasWildcard) {
|
|
70
|
+
// If no wildcards, validate as a normal hostname
|
|
71
|
+
return isValidHostname(hostname);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Basic validation checks that apply even with wildcards
|
|
75
|
+
// - Hostname cannot start or end with a dot
|
|
76
|
+
if (hostname.startsWith('.') || hostname.endsWith('.')) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// - No consecutive dots
|
|
81
|
+
if (hostname.includes('..')) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For wildcard validation, check that non-wildcard parts contain valid characters
|
|
86
|
+
// Replace wildcards with a valid placeholder to check the rest
|
|
87
|
+
const testHostname = hostname.replace(/\*+/g, 'wildcard');
|
|
88
|
+
|
|
89
|
+
// Check if the resulting string would be a valid hostname
|
|
90
|
+
if (!/^[a-zA-Z0-9.-]+$/.test(testHostname)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Additional check: ensure the pattern makes sense
|
|
95
|
+
// Check each segment between wildcards
|
|
96
|
+
const segments = hostname.split(/\*+/);
|
|
97
|
+
for (let i = 0; i < segments.length; i++) {
|
|
98
|
+
const segment = segments[i];
|
|
99
|
+
if (segment === '') continue; // Empty segments are OK (consecutive wildcards)
|
|
100
|
+
|
|
101
|
+
// First segment can't start with dot
|
|
102
|
+
if (i === 0 && segment.startsWith('.')) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Last segment can't end with dot
|
|
107
|
+
if (i === segments.length - 1 && segment.endsWith('.')) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// No segment should have consecutive dots
|
|
112
|
+
if (segment.includes('..')) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
import.meta.vitest?.test("isValidHostnameWithWildcards", ({ expect }) => {
|
|
120
|
+
// Test with valid regular hostnames
|
|
121
|
+
expect(isValidHostnameWithWildcards("example.com")).toBe(true);
|
|
122
|
+
expect(isValidHostnameWithWildcards("localhost")).toBe(true);
|
|
123
|
+
expect(isValidHostnameWithWildcards("sub.domain.example.com")).toBe(true);
|
|
124
|
+
|
|
125
|
+
// Test with valid wildcard hostnames
|
|
126
|
+
expect(isValidHostnameWithWildcards("*.example.com")).toBe(true);
|
|
127
|
+
expect(isValidHostnameWithWildcards("a-*.example.com")).toBe(true);
|
|
128
|
+
expect(isValidHostnameWithWildcards("*.*.org")).toBe(true);
|
|
129
|
+
expect(isValidHostnameWithWildcards("**.example.com")).toBe(true);
|
|
130
|
+
expect(isValidHostnameWithWildcards("sub.**.com")).toBe(true);
|
|
131
|
+
expect(isValidHostnameWithWildcards("*-api.*.com")).toBe(true);
|
|
132
|
+
|
|
133
|
+
// Test with invalid hostnames
|
|
134
|
+
expect(isValidHostnameWithWildcards("")).toBe(false);
|
|
135
|
+
expect(isValidHostnameWithWildcards("example.com/path")).toBe(false);
|
|
136
|
+
expect(isValidHostnameWithWildcards("https://example.com")).toBe(false);
|
|
137
|
+
expect(isValidHostnameWithWildcards("example com")).toBe(false);
|
|
138
|
+
expect(isValidHostnameWithWildcards(".example.com")).toBe(false);
|
|
139
|
+
expect(isValidHostnameWithWildcards("example.com.")).toBe(false);
|
|
140
|
+
expect(isValidHostnameWithWildcards("example..com")).toBe(false);
|
|
141
|
+
expect(isValidHostnameWithWildcards("*.example..com")).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export function matchHostnamePattern(pattern: string, hostname: string): boolean {
|
|
145
|
+
// If no wildcards, it's an exact match
|
|
146
|
+
if (!pattern.includes('*')) {
|
|
147
|
+
return pattern === hostname;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Convert the pattern to a regex
|
|
151
|
+
// First, escape all regex special characters except *
|
|
152
|
+
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
153
|
+
|
|
154
|
+
// Use a placeholder for ** to handle it separately from single *
|
|
155
|
+
const doubleWildcardPlaceholder = '\x00DOUBLE_WILDCARD\x00';
|
|
156
|
+
regexPattern = regexPattern.replace(/\*\*/g, doubleWildcardPlaceholder);
|
|
157
|
+
|
|
158
|
+
// Replace single * with a pattern that matches anything except dots
|
|
159
|
+
regexPattern = regexPattern.replace(/\*/g, '[^.]*');
|
|
160
|
+
|
|
161
|
+
// Replace the double wildcard placeholder with a pattern that matches anything including dots
|
|
162
|
+
regexPattern = regexPattern.replace(new RegExp(doubleWildcardPlaceholder, 'g'), '.*');
|
|
163
|
+
|
|
164
|
+
// Anchor the pattern to match the entire hostname
|
|
165
|
+
regexPattern = '^' + regexPattern + '$';
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const regex = new RegExp(regexPattern);
|
|
169
|
+
return regex.test(hostname);
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
import.meta.vitest?.test("matchHostnamePattern", ({ expect }) => {
|
|
175
|
+
// Test exact matches
|
|
176
|
+
expect(matchHostnamePattern("example.com", "example.com")).toBe(true);
|
|
177
|
+
expect(matchHostnamePattern("example.com", "other.com")).toBe(false);
|
|
178
|
+
|
|
179
|
+
// Test single wildcard matches
|
|
180
|
+
expect(matchHostnamePattern("*.example.com", "api.example.com")).toBe(true);
|
|
181
|
+
expect(matchHostnamePattern("*.example.com", "www.example.com")).toBe(true);
|
|
182
|
+
expect(matchHostnamePattern("*.example.com", "example.com")).toBe(false);
|
|
183
|
+
expect(matchHostnamePattern("*.example.com", "api.v2.example.com")).toBe(false);
|
|
184
|
+
|
|
185
|
+
// Test double wildcard matches
|
|
186
|
+
expect(matchHostnamePattern("**.example.com", "api.example.com")).toBe(true);
|
|
187
|
+
expect(matchHostnamePattern("**.example.com", "api.v2.example.com")).toBe(true);
|
|
188
|
+
expect(matchHostnamePattern("**.example.com", "a.b.c.example.com")).toBe(true);
|
|
189
|
+
expect(matchHostnamePattern("**.example.com", "example.com")).toBe(false);
|
|
190
|
+
|
|
191
|
+
// Test complex patterns
|
|
192
|
+
expect(matchHostnamePattern("api-*.example.com", "api-v1.example.com")).toBe(true);
|
|
193
|
+
expect(matchHostnamePattern("api-*.example.com", "api-v2.example.com")).toBe(true);
|
|
194
|
+
expect(matchHostnamePattern("api-*.example.com", "api.example.com")).toBe(false);
|
|
195
|
+
expect(matchHostnamePattern("*.*.org", "mail.example.org")).toBe(true);
|
|
196
|
+
expect(matchHostnamePattern("*.*.org", "example.org")).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export function getHardcodedFallbackUrls(primaryBaseUrl: string): string[] {
|
|
200
|
+
// `defaultBaseUrl` in `@stackframe/*` SDK builds is `https://api.stack-auth.com`
|
|
201
|
+
// (see `packages/template/src/lib/hexclave-app/apps/implementations/common.ts`).
|
|
202
|
+
// The `@hexclave/*` mirror packages will get this rewritten to the hexclave
|
|
203
|
+
// hostname by `scripts/rewrite-packages-to-hexclave.ts` in a follow-up PR; both
|
|
204
|
+
// branches below stay listed so either build resolves its own fallback set.
|
|
205
|
+
if (primaryBaseUrl === "https://api.stack-auth.com") {
|
|
206
|
+
return ["https://api1.stack-auth.com", "https://api2.stack-auth.com"];
|
|
207
|
+
}
|
|
208
|
+
if (primaryBaseUrl === "https://api.dev.stack-auth.com") {
|
|
209
|
+
return ["https://api1.dev.stack-auth.com", "https://api2.dev.stack-auth.com"];
|
|
210
|
+
}
|
|
211
|
+
if (primaryBaseUrl === "https://api.hexclave.com") {
|
|
212
|
+
return ["https://api1.hexclave.com", "https://api2.hexclave.com"];
|
|
213
|
+
}
|
|
214
|
+
if (primaryBaseUrl === "https://api.dev.hexclave.com") {
|
|
215
|
+
return ["https://api1.dev.hexclave.com", "https://api2.dev.hexclave.com"];
|
|
216
|
+
}
|
|
217
|
+
const localhostMatch = primaryBaseUrl.match(/^http:\/\/localhost:(\d+)02$/);
|
|
218
|
+
if (localhostMatch) {
|
|
219
|
+
return [`http://localhost:${localhostMatch[1]}10`];
|
|
220
|
+
}
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getDefaultApiUrls(primaryBaseUrl: string): string[] {
|
|
225
|
+
return [primaryBaseUrl, ...getHardcodedFallbackUrls(primaryBaseUrl)];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function isLocalhost(urlOrString: string | URL) {
|
|
229
|
+
const url = createUrlIfValid(urlOrString);
|
|
230
|
+
if (!url) return false;
|
|
231
|
+
if (url.hostname === "localhost" || url.hostname.endsWith(".localhost")) return true;
|
|
232
|
+
if (url.hostname.match(/^127\.\d+\.\d+\.\d+$/)) return true;
|
|
233
|
+
if (url.hostname === "[::1]" || url.hostname === "::1") return true;
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
import.meta.vitest?.test("isLocalhost", ({ expect }) => {
|
|
237
|
+
// Test with localhost URLs
|
|
238
|
+
expect(isLocalhost("http://localhost")).toBe(true);
|
|
239
|
+
expect(isLocalhost("https://localhost:8080")).toBe(true);
|
|
240
|
+
expect(isLocalhost("http://sub.localhost")).toBe(true);
|
|
241
|
+
expect(isLocalhost("http://127.0.0.1")).toBe(true);
|
|
242
|
+
expect(isLocalhost("http://127.1.2.3")).toBe(true);
|
|
243
|
+
expect(isLocalhost("http://[::1]")).toBe(true);
|
|
244
|
+
|
|
245
|
+
// Test with non-localhost URLs
|
|
246
|
+
expect(isLocalhost("https://example.com")).toBe(false);
|
|
247
|
+
expect(isLocalhost("http://192.168.1.1")).toBe(false);
|
|
248
|
+
expect(isLocalhost("http://10.0.0.1")).toBe(false);
|
|
249
|
+
|
|
250
|
+
// Test with URL objects
|
|
251
|
+
expect(isLocalhost(new URL("http://localhost"))).toBe(true);
|
|
252
|
+
expect(isLocalhost(new URL("https://example.com"))).toBe(false);
|
|
253
|
+
|
|
254
|
+
// Test with invalid URLs
|
|
255
|
+
expect(isLocalhost("not a url")).toBe(false);
|
|
256
|
+
expect(isLocalhost("")).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export function isRelative(url: string) {
|
|
260
|
+
const randomDomain = `${generateSecureRandomString()}.stack-auth.example.com`;
|
|
261
|
+
const u = createUrlIfValid(url, `https://${randomDomain}`);
|
|
262
|
+
if (!u) return false;
|
|
263
|
+
if (u.host !== randomDomain) return false;
|
|
264
|
+
if (u.protocol !== "https:") return false;
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
import.meta.vitest?.test("isRelative", ({ expect }) => {
|
|
268
|
+
// We can't easily mock generateSecureRandomString in this context
|
|
269
|
+
// but we can still test the function's behavior
|
|
270
|
+
|
|
271
|
+
// Test with relative URLs
|
|
272
|
+
expect(isRelative("/")).toBe(true);
|
|
273
|
+
expect(isRelative("/path")).toBe(true);
|
|
274
|
+
expect(isRelative("/path?query=value#hash")).toBe(true);
|
|
275
|
+
|
|
276
|
+
// Test with absolute URLs
|
|
277
|
+
expect(isRelative("https://example.com")).toBe(false);
|
|
278
|
+
expect(isRelative("http://example.com")).toBe(false);
|
|
279
|
+
expect(isRelative("//example.com")).toBe(false);
|
|
280
|
+
|
|
281
|
+
// Note: The implementation treats empty strings and invalid URLs as relative
|
|
282
|
+
// This is because they can be resolved against a base URL
|
|
283
|
+
expect(isRelative("")).toBe(true);
|
|
284
|
+
expect(isRelative("not a url")).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export function getRelativePart(url: URL) {
|
|
288
|
+
return url.pathname + url.search + url.hash;
|
|
289
|
+
}
|
|
290
|
+
import.meta.vitest?.test("getRelativePart", ({ expect }) => {
|
|
291
|
+
// Test with various URLs
|
|
292
|
+
expect(getRelativePart(new URL("https://example.com"))).toBe("/");
|
|
293
|
+
expect(getRelativePart(new URL("https://example.com/path"))).toBe("/path");
|
|
294
|
+
expect(getRelativePart(new URL("https://example.com/path?query=value"))).toBe("/path?query=value");
|
|
295
|
+
expect(getRelativePart(new URL("https://example.com/path#hash"))).toBe("/path#hash");
|
|
296
|
+
expect(getRelativePart(new URL("https://example.com/path?query=value#hash"))).toBe("/path?query=value#hash");
|
|
297
|
+
|
|
298
|
+
// Test with different domains but same paths
|
|
299
|
+
const url1 = new URL("https://example.com/path?query=value#hash");
|
|
300
|
+
const url2 = new URL("https://different.com/path?query=value#hash");
|
|
301
|
+
expect(getRelativePart(url1)).toBe(getRelativePart(url2));
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* A template literal tag that returns a URL.
|
|
306
|
+
*
|
|
307
|
+
* Any values passed are encoded.
|
|
308
|
+
*/
|
|
309
|
+
export function url(strings: TemplateStringsArray | readonly string[], ...values: (string | number | boolean)[]): URL {
|
|
310
|
+
return new URL(urlString(strings, ...values));
|
|
311
|
+
}
|
|
312
|
+
import.meta.vitest?.test("url", ({ expect }) => {
|
|
313
|
+
// Test with no interpolation
|
|
314
|
+
expect(url`https://example.com`).toBeInstanceOf(URL);
|
|
315
|
+
expect(url`https://example.com`.href).toBe("https://example.com/");
|
|
316
|
+
|
|
317
|
+
// Test with string interpolation
|
|
318
|
+
expect(url`https://example.com/${"path"}`).toBeInstanceOf(URL);
|
|
319
|
+
expect(url`https://example.com/${"path"}`.pathname).toBe("/path");
|
|
320
|
+
|
|
321
|
+
// Test with number interpolation
|
|
322
|
+
expect(url`https://example.com/${42}`).toBeInstanceOf(URL);
|
|
323
|
+
expect(url`https://example.com/${42}`.pathname).toBe("/42");
|
|
324
|
+
|
|
325
|
+
// Test with boolean interpolation
|
|
326
|
+
expect(url`https://example.com/${true}`).toBeInstanceOf(URL);
|
|
327
|
+
expect(url`https://example.com/${true}`.pathname).toBe("/true");
|
|
328
|
+
|
|
329
|
+
// Test with special characters in interpolation
|
|
330
|
+
expect(url`https://example.com/${"path with spaces"}`).toBeInstanceOf(URL);
|
|
331
|
+
expect(url`https://example.com/${"path with spaces"}`.pathname).toBe("/path%20with%20spaces");
|
|
332
|
+
|
|
333
|
+
// Test with multiple interpolations
|
|
334
|
+
expect(url`https://example.com/${"path"}?query=${"value"}`).toBeInstanceOf(URL);
|
|
335
|
+
expect(url`https://example.com/${"path"}?query=${"value"}`.pathname).toBe("/path");
|
|
336
|
+
expect(url`https://example.com/${"path"}?query=${"value"}`.search).toBe("?query=value");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* A template literal tag that returns a URL string.
|
|
342
|
+
*
|
|
343
|
+
* Any values passed are encoded.
|
|
344
|
+
*/
|
|
345
|
+
export function urlString(strings: TemplateStringsArray | readonly string[], ...values: (string | number | boolean)[]): string {
|
|
346
|
+
return templateIdentity(strings, ...values.map(encodeURIComponent));
|
|
347
|
+
}
|
|
348
|
+
import.meta.vitest?.test("urlString", ({ expect }) => {
|
|
349
|
+
// Test with no interpolation
|
|
350
|
+
expect(urlString`https://example.com`).toBe("https://example.com");
|
|
351
|
+
|
|
352
|
+
// Test with string interpolation
|
|
353
|
+
expect(urlString`https://example.com/${"path"}`).toBe("https://example.com/path");
|
|
354
|
+
|
|
355
|
+
// Test with number interpolation
|
|
356
|
+
expect(urlString`https://example.com/${42}`).toBe("https://example.com/42");
|
|
357
|
+
|
|
358
|
+
// Test with boolean interpolation
|
|
359
|
+
expect(urlString`https://example.com/${true}`).toBe("https://example.com/true");
|
|
360
|
+
|
|
361
|
+
// Test with special characters in interpolation
|
|
362
|
+
expect(urlString`https://example.com/${"path with spaces"}`).toBe("https://example.com/path%20with%20spaces");
|
|
363
|
+
expect(urlString`https://example.com/${"?&="}`).toBe("https://example.com/%3F%26%3D");
|
|
364
|
+
|
|
365
|
+
// Test with multiple interpolations
|
|
366
|
+
expect(urlString`https://example.com/${"path"}?query=${"value"}`).toBe("https://example.com/path?query=value");
|
|
367
|
+
expect(urlString`https://example.com/${"path"}?query=${"value with spaces"}`).toBe("https://example.com/path?query=value%20with%20spaces");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
export function isChildUrl(parentUrl: URL, maybeChildUrl: URL) {
|
|
371
|
+
return parentUrl.origin === maybeChildUrl.origin
|
|
372
|
+
&& isChildPath(parentUrl.pathname, maybeChildUrl.pathname)
|
|
373
|
+
&& [...parentUrl.searchParams].every(([key, value]) => maybeChildUrl.searchParams.get(key) === value)
|
|
374
|
+
&& (!parentUrl.hash || parentUrl.hash === maybeChildUrl.hash);
|
|
375
|
+
}
|
|
376
|
+
import.meta.vitest?.test("isChildUrl", ({ expect }) => {
|
|
377
|
+
// true
|
|
378
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://abc.com/"))).toBe(true);
|
|
379
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://abc.com/path"))).toBe(true);
|
|
380
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://abc.com/path?query=value"))).toBe(true);
|
|
381
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://abc.com/path?query=value#hash"))).toBe(true);
|
|
382
|
+
|
|
383
|
+
// false
|
|
384
|
+
expect(isChildUrl(new URL("https://abc.com"), new URL("https://example.com"))).toBe(false);
|
|
385
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://example.com/path"))).toBe(false);
|
|
386
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://example.com/path?query=value"))).toBe(false);
|
|
387
|
+
expect(isChildUrl(new URL("https://abc.com/"), new URL("https://example.com/path?query=value#hash"))).toBe(false);
|
|
388
|
+
expect(isChildUrl(new URL("https://example.com"), new URL("https://abc.com/path?query=value#hash"))).toBe(false);
|
|
389
|
+
expect(isChildUrl(new URL("https://example.com?query=value123"), new URL("https://example.com/path?query=value#hash"))).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
export function isChildPath(parentPath: string, maybeChildPath: string) {
|
|
393
|
+
parentPath = parentPath.endsWith("/") ? parentPath : parentPath + "/";
|
|
394
|
+
maybeChildPath = maybeChildPath.endsWith("/") ? maybeChildPath : maybeChildPath + "/";
|
|
395
|
+
return maybeChildPath.startsWith(parentPath);
|
|
396
|
+
}
|
|
397
|
+
import.meta.vitest?.test("isSubPath", ({ expect }) => {
|
|
398
|
+
expect(isChildPath("/", "/")).toBe(true);
|
|
399
|
+
expect(isChildPath("/", "/path")).toBe(true);
|
|
400
|
+
expect(isChildPath("/path", "/")).toBe(false);
|
|
401
|
+
expect(isChildPath("/path", "/path")).toBe(true);
|
|
402
|
+
expect(isChildPath("/path/", "/path")).toBe(true);
|
|
403
|
+
expect(isChildPath("/path", "/path/")).toBe(true);
|
|
404
|
+
expect(isChildPath("/path/", "/path/")).toBe(true);
|
|
405
|
+
expect(isChildPath("/path", "/path/abc")).toBe(true);
|
|
406
|
+
expect(isChildPath("/path/", "/path/abc")).toBe(true);
|
|
407
|
+
expect(isChildPath("/path", "/path-abc")).toBe(false);
|
|
408
|
+
expect(isChildPath("/path", "/path-abc/")).toBe(false);
|
|
409
|
+
expect(isChildPath("/path/", "/path-abc")).toBe(false);
|
|
410
|
+
expect(isChildPath("/path/", "/path-abc/")).toBe(false);
|
|
411
|
+
});
|