@hexclave/shared 1.0.3 → 1.0.6
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 +216 -29
- 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 +1 -1
- 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 +215 -30
- 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 +1 -1
- 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/interface/admin-metrics.d.ts +2 -2
- 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 +7 -7
- package/dist/esm/interface/crud/email-outbox.d.ts +42 -42
- 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/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/users.d.ts +10 -10
- 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 +1 -1
- package/dist/esm/known-errors.d.ts.map +1 -1
- package/dist/esm/known-errors.js +9 -5
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.d.ts +15 -5
- 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/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/interface/admin-metrics.d.ts +12 -12
- 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 +7 -7
- package/dist/interface/crud/email-outbox.d.ts +42 -42
- package/dist/interface/crud/project-permissions.js +5 -5
- package/dist/interface/crud/project-permissions.js.map +1 -1
- 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/users.d.ts +10 -10
- 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 +1 -1
- package/dist/known-errors.d.ts.map +1 -1
- package/dist/known-errors.js +9 -5
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.ts +15 -5
- 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/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 +2034 -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,212 @@
|
|
|
1
|
+
import { throwErr } from "./errors";
|
|
2
|
+
import { nicify } from "./strings";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a proxy that throws an error when any property is accessed, function is called,
|
|
6
|
+
* or any other operation is performed on it.
|
|
7
|
+
*
|
|
8
|
+
* Useful for placeholder values that should never actually be used at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export function throwingProxy<T = any>(error: string | Error): T {
|
|
11
|
+
const doThrow = (): never => {
|
|
12
|
+
if (typeof error === "string") {
|
|
13
|
+
throwErr(error);
|
|
14
|
+
} else {
|
|
15
|
+
throwErr(error);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return new Proxy(() => {}, {
|
|
20
|
+
get: doThrow,
|
|
21
|
+
set: doThrow,
|
|
22
|
+
has: doThrow,
|
|
23
|
+
deleteProperty: doThrow,
|
|
24
|
+
ownKeys: doThrow,
|
|
25
|
+
getOwnPropertyDescriptor: doThrow,
|
|
26
|
+
defineProperty: doThrow,
|
|
27
|
+
getPrototypeOf: doThrow,
|
|
28
|
+
setPrototypeOf: doThrow,
|
|
29
|
+
isExtensible: doThrow,
|
|
30
|
+
preventExtensions: doThrow,
|
|
31
|
+
apply: doThrow,
|
|
32
|
+
construct: doThrow,
|
|
33
|
+
}) as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function logged<T extends object>(name: string, toLog: T, options: {} = {}): T {
|
|
37
|
+
const proxy = new Proxy(toLog, {
|
|
38
|
+
get(target, prop, receiver) {
|
|
39
|
+
const orig = Reflect.get(target, prop, receiver);
|
|
40
|
+
if (typeof orig === "function") {
|
|
41
|
+
return function (this: any, ...args: any[]) {
|
|
42
|
+
const success = (v: any, isPromise: boolean) => console.debug(`logged(...): Called ${name}.${String(prop)}(${args.map(a => nicify(a)).join(", ")}) => ${isPromise ? "Promise<" : ""}${nicify(result)}${isPromise ? ">" : ""}`, { this: this, args, promise: isPromise ? result : false, result: v, trace: new Error() });
|
|
43
|
+
const error = (e: any, isPromise: boolean) => console.debug(`logged(...): Error in ${name}.${String(prop)}(${args.map(a => nicify(a)).join(", ")})`, { this: this, args, promise: isPromise ? result : false, error: e, trace: new Error() });
|
|
44
|
+
|
|
45
|
+
let result: unknown;
|
|
46
|
+
try {
|
|
47
|
+
result = orig.apply(this, args);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
error(e, false);
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
if (result instanceof Promise) {
|
|
53
|
+
result.then((v) => success(v, true)).catch((e) => error(e, true));
|
|
54
|
+
} else {
|
|
55
|
+
success(result, false);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return orig;
|
|
61
|
+
},
|
|
62
|
+
set(target, prop, value) {
|
|
63
|
+
console.log(`Setting ${name}.${String(prop)} to ${value}`);
|
|
64
|
+
return Reflect.set(target, prop, value);
|
|
65
|
+
},
|
|
66
|
+
apply(target, thisArg, args) {
|
|
67
|
+
console.log(`Calling ${name}(${JSON.stringify(args).slice(1, -1)})`);
|
|
68
|
+
return Reflect.apply(target as any, thisArg, args);
|
|
69
|
+
},
|
|
70
|
+
construct(target, args, newTarget) {
|
|
71
|
+
console.log(`Constructing ${name}(${JSON.stringify(args).slice(1, -1)})`);
|
|
72
|
+
return Reflect.construct(target as any, args, newTarget);
|
|
73
|
+
},
|
|
74
|
+
defineProperty(target, prop, descriptor) {
|
|
75
|
+
console.log(`Defining ${name}.${String(prop)} as ${JSON.stringify(descriptor)}`);
|
|
76
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
77
|
+
},
|
|
78
|
+
deleteProperty(target, prop) {
|
|
79
|
+
console.log(`Deleting ${name}.${String(prop)}`);
|
|
80
|
+
return Reflect.deleteProperty(target, prop);
|
|
81
|
+
},
|
|
82
|
+
setPrototypeOf(target, prototype) {
|
|
83
|
+
console.log(`Setting prototype of ${name} to ${prototype}`);
|
|
84
|
+
return Reflect.setPrototypeOf(target, prototype);
|
|
85
|
+
},
|
|
86
|
+
preventExtensions(target) {
|
|
87
|
+
console.log(`Preventing extensions of ${name}`);
|
|
88
|
+
return Reflect.preventExtensions(target);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
return proxy;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createLazyProxy<FactoryResult>(factory: () => FactoryResult): FactoryResult {
|
|
95
|
+
let cache: FactoryResult | undefined = undefined;
|
|
96
|
+
let initialized: boolean = false;
|
|
97
|
+
|
|
98
|
+
function initializeIfNeeded() {
|
|
99
|
+
if (!initialized) {
|
|
100
|
+
cache = factory();
|
|
101
|
+
initialized = true;
|
|
102
|
+
}
|
|
103
|
+
return cache!;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Proxy({}, {
|
|
107
|
+
get(target, prop, receiver) {
|
|
108
|
+
const instance = initializeIfNeeded();
|
|
109
|
+
return Reflect.get(instance, prop, receiver);
|
|
110
|
+
},
|
|
111
|
+
set(target, prop, value, receiver) {
|
|
112
|
+
const instance = initializeIfNeeded();
|
|
113
|
+
return Reflect.set(instance, prop, value, receiver);
|
|
114
|
+
},
|
|
115
|
+
has(target, prop) {
|
|
116
|
+
const instance = initializeIfNeeded();
|
|
117
|
+
return Reflect.has(instance, prop);
|
|
118
|
+
},
|
|
119
|
+
deleteProperty(target, prop) {
|
|
120
|
+
const instance = initializeIfNeeded();
|
|
121
|
+
return Reflect.deleteProperty(instance, prop);
|
|
122
|
+
},
|
|
123
|
+
ownKeys(target) {
|
|
124
|
+
const instance = initializeIfNeeded();
|
|
125
|
+
return Reflect.ownKeys(instance);
|
|
126
|
+
},
|
|
127
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
128
|
+
const instance = initializeIfNeeded();
|
|
129
|
+
return Reflect.getOwnPropertyDescriptor(instance, prop);
|
|
130
|
+
},
|
|
131
|
+
defineProperty(target, prop, descriptor) {
|
|
132
|
+
const instance = initializeIfNeeded();
|
|
133
|
+
return Reflect.defineProperty(instance, prop, descriptor);
|
|
134
|
+
},
|
|
135
|
+
getPrototypeOf(target) {
|
|
136
|
+
const instance = initializeIfNeeded();
|
|
137
|
+
return Reflect.getPrototypeOf(instance);
|
|
138
|
+
},
|
|
139
|
+
setPrototypeOf(target, proto) {
|
|
140
|
+
const instance = initializeIfNeeded();
|
|
141
|
+
return Reflect.setPrototypeOf(instance, proto);
|
|
142
|
+
},
|
|
143
|
+
isExtensible(target) {
|
|
144
|
+
const instance = initializeIfNeeded();
|
|
145
|
+
return Reflect.isExtensible(instance);
|
|
146
|
+
},
|
|
147
|
+
preventExtensions(target) {
|
|
148
|
+
const instance = initializeIfNeeded();
|
|
149
|
+
return Reflect.preventExtensions(instance);
|
|
150
|
+
},
|
|
151
|
+
apply(target, thisArg, argumentsList) {
|
|
152
|
+
const instance = initializeIfNeeded();
|
|
153
|
+
return Reflect.apply(instance as any, thisArg, argumentsList);
|
|
154
|
+
},
|
|
155
|
+
construct(target, argumentsList, newTarget) {
|
|
156
|
+
const instance = initializeIfNeeded();
|
|
157
|
+
return Reflect.construct(instance as any, argumentsList, newTarget);
|
|
158
|
+
}
|
|
159
|
+
}) as FactoryResult;
|
|
160
|
+
}
|
|
161
|
+
import.meta.vitest?.test("createLazyProxy", ({ expect }) => {
|
|
162
|
+
// Test with a simple object factory
|
|
163
|
+
let factoryCallCount = 0;
|
|
164
|
+
const createObject = () => {
|
|
165
|
+
factoryCallCount++;
|
|
166
|
+
return { value: 42, method: () => "hello" };
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const proxy = createLazyProxy(createObject);
|
|
170
|
+
|
|
171
|
+
// Factory should not be called until property is accessed
|
|
172
|
+
expect(factoryCallCount).toBe(0);
|
|
173
|
+
|
|
174
|
+
// Accessing a property should initialize the object
|
|
175
|
+
expect(proxy.value).toBe(42);
|
|
176
|
+
expect(factoryCallCount).toBe(1);
|
|
177
|
+
|
|
178
|
+
// Accessing another property should not call factory again
|
|
179
|
+
expect(proxy.method()).toBe("hello");
|
|
180
|
+
expect(factoryCallCount).toBe(1);
|
|
181
|
+
|
|
182
|
+
// Test with property setting
|
|
183
|
+
proxy.value = 100;
|
|
184
|
+
expect(proxy.value).toBe(100);
|
|
185
|
+
expect(factoryCallCount).toBe(1);
|
|
186
|
+
|
|
187
|
+
// Test with a class factory
|
|
188
|
+
let classFactoryCallCount = 0;
|
|
189
|
+
class TestClass {
|
|
190
|
+
constructor() {
|
|
191
|
+
classFactoryCallCount++;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getValue() {
|
|
195
|
+
return "class value";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const classFactory = () => new TestClass();
|
|
200
|
+
const classProxy = createLazyProxy(classFactory);
|
|
201
|
+
|
|
202
|
+
// Factory should not be called until method is accessed
|
|
203
|
+
expect(classFactoryCallCount).toBe(0);
|
|
204
|
+
|
|
205
|
+
// Accessing a method should initialize the object
|
|
206
|
+
expect(classProxy.getValue()).toBe("class value");
|
|
207
|
+
expect(classFactoryCallCount).toBe(1);
|
|
208
|
+
|
|
209
|
+
// Accessing the method again should not call factory again
|
|
210
|
+
expect(classProxy.getValue()).toBe("class value");
|
|
211
|
+
expect(classFactoryCallCount).toBe(1);
|
|
212
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import React, { SetStateAction } from "react";
|
|
2
|
+
import { isBrowserLike } from "./env";
|
|
3
|
+
import { neverResolve, runAsynchronously } from "./promises";
|
|
4
|
+
import { AsyncResult } from "./results";
|
|
5
|
+
import { ensureMonkeyPatch, NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL } from "./monkey-patch";
|
|
6
|
+
import { deindent } from "./strings";
|
|
7
|
+
|
|
8
|
+
export function componentWrapper<
|
|
9
|
+
C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements,
|
|
10
|
+
ExtraProps extends {} = {}
|
|
11
|
+
>(displayName: string, render: React.ForwardRefRenderFunction<RefFromComponent<C>, React.ComponentPropsWithRef<C> & ExtraProps>) {
|
|
12
|
+
const Component = forwardRefIfNeeded(render);
|
|
13
|
+
Component.displayName = displayName;
|
|
14
|
+
return Component;
|
|
15
|
+
}
|
|
16
|
+
type RefFromComponent<C extends React.ComponentType<any> | keyof React.JSX.IntrinsicElements> = NonNullable<RefFromComponentDistCond<React.ComponentPropsWithRef<C>["ref"]>>;
|
|
17
|
+
type RefFromComponentDistCond<A> = A extends React.RefObject<infer T> ? T : never; // distributive conditional type; see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
|
|
18
|
+
|
|
19
|
+
const react18PromiseCache = new WeakMap<Promise<unknown>, AsyncResult<unknown, unknown>>();
|
|
20
|
+
export function use<T>(promise: Promise<T>): T {
|
|
21
|
+
if ("use" in React) {
|
|
22
|
+
return React.use(promise);
|
|
23
|
+
} else {
|
|
24
|
+
if (react18PromiseCache.has(promise)) {
|
|
25
|
+
const result = react18PromiseCache.get(promise)!;
|
|
26
|
+
if (result.status === "pending") {
|
|
27
|
+
throw promise;
|
|
28
|
+
} else if (result.status === "ok") {
|
|
29
|
+
return result.data as T;
|
|
30
|
+
} else {
|
|
31
|
+
throw result.error;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
react18PromiseCache.set(promise, { "status": "pending", progress: undefined });
|
|
35
|
+
runAsynchronously(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const res = await promise;
|
|
38
|
+
react18PromiseCache.set(promise, { "status": "ok", data: res });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
react18PromiseCache.set(promise, { "status": "error", error: e });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
throw promise;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function forwardRefIfNeeded<T, P = {}>(render: React.ForwardRefRenderFunction<T, P>): React.FC<P & { ref?: React.Ref<T> }> {
|
|
49
|
+
// TODO: when we drop support for react 18, remove this
|
|
50
|
+
|
|
51
|
+
const version = React.version;
|
|
52
|
+
const major = parseInt(version.split(".")[0]);
|
|
53
|
+
if (major < 19) {
|
|
54
|
+
return React.forwardRef<T, P>(render as any) as any;
|
|
55
|
+
} else {
|
|
56
|
+
return ((props: P) => render(props, (props as any).ref)) as any;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getNodeText(node: React.ReactNode): string {
|
|
61
|
+
if (["number", "string"].includes(typeof node)) {
|
|
62
|
+
return `${node}`;
|
|
63
|
+
}
|
|
64
|
+
if (!node) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(node)) {
|
|
68
|
+
return node.map(getNodeText).join("");
|
|
69
|
+
}
|
|
70
|
+
if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
|
|
71
|
+
return getNodeText(node.props.children);
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Unknown node type: ${typeof node}`);
|
|
74
|
+
}
|
|
75
|
+
import.meta.vitest?.test("getNodeText", ({ expect }) => {
|
|
76
|
+
// Test with string
|
|
77
|
+
expect(getNodeText("hello")).toBe("hello");
|
|
78
|
+
|
|
79
|
+
// Test with number
|
|
80
|
+
expect(getNodeText(42)).toBe("42");
|
|
81
|
+
|
|
82
|
+
// Test with null/undefined
|
|
83
|
+
expect(getNodeText(null)).toBe("");
|
|
84
|
+
expect(getNodeText(undefined)).toBe("");
|
|
85
|
+
|
|
86
|
+
// Test with array
|
|
87
|
+
expect(getNodeText(["hello", " ", "world"])).toBe("hello world");
|
|
88
|
+
expect(getNodeText([1, 2, 3])).toBe("123");
|
|
89
|
+
|
|
90
|
+
// Test with mixed array
|
|
91
|
+
expect(getNodeText(["hello", 42, null])).toBe("hello42");
|
|
92
|
+
|
|
93
|
+
// Test with React element
|
|
94
|
+
const mockElement = React.createElement("span", null, "child text");
|
|
95
|
+
expect(getNodeText(mockElement)).toBe("child text");
|
|
96
|
+
|
|
97
|
+
// Test with nested React elements
|
|
98
|
+
const nestedElement = React.createElement("div", null, React.createElement("span", null, "nested text"));
|
|
99
|
+
expect(getNodeText(nestedElement)).toBe("nested text");
|
|
100
|
+
|
|
101
|
+
// Test with array of React elements
|
|
102
|
+
const arrayOfElements = [
|
|
103
|
+
React.createElement("span", null, "first"),
|
|
104
|
+
React.createElement("span", null, "second"),
|
|
105
|
+
];
|
|
106
|
+
expect(getNodeText(arrayOfElements)).toBe("firstsecond");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Suspends the currently rendered component indefinitely. Will not unsuspend unless the component rerenders.
|
|
111
|
+
*
|
|
112
|
+
* You can use this to translate older query- or AsyncResult-based code to new the Suspense system, for example: `if (query.isLoading) suspend();`
|
|
113
|
+
*/
|
|
114
|
+
export function suspend(): never {
|
|
115
|
+
use(neverResolve());
|
|
116
|
+
throw new Error("Somehow a Promise that never resolves was resolved?");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function mapRef<T, R>(ref: ReadonlyRef<T>, mapper: (value: T) => R): ReadonlyRef<R> {
|
|
120
|
+
let last: [T, R] | null = null;
|
|
121
|
+
return {
|
|
122
|
+
get current() {
|
|
123
|
+
const input = ref.current;
|
|
124
|
+
if (last === null || input !== last[0]) {
|
|
125
|
+
last = [input, mapper(input)];
|
|
126
|
+
}
|
|
127
|
+
return last[1];
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type ReadonlyRef<T> = {
|
|
133
|
+
readonly current: T,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type RefState<T> = ReadonlyRef<T> & {
|
|
137
|
+
set: (updater: SetStateAction<T>) => void,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Like useState, but its value is immediately available on refState.current after being set.
|
|
142
|
+
*
|
|
143
|
+
* Like useRef, but setting the value will cause a rerender.
|
|
144
|
+
*
|
|
145
|
+
* Note that useRefState returns a new object every time a rerender happens due to a value change, which is intentional
|
|
146
|
+
* as it allows you to specify it in a dependency array like this:
|
|
147
|
+
*
|
|
148
|
+
* ```tsx
|
|
149
|
+
* useEffect(() => {
|
|
150
|
+
* // do something with refState.current
|
|
151
|
+
* }, [refState]); // instead of refState.current
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* If you don't want this, you can wrap the result in a useMemo call.
|
|
155
|
+
*/
|
|
156
|
+
export function useRefState<T>(initialValue: T | (() => T)): RefState<T> {
|
|
157
|
+
// Support lazy initialization like React.useState does: if initialValue is a function,
|
|
158
|
+
// call it once to get the actual initial value (React.useRef does NOT do this automatically).
|
|
159
|
+
const lazyInitRef = React.useRef<{ v: T } | null>(null);
|
|
160
|
+
if (lazyInitRef.current === null) {
|
|
161
|
+
lazyInitRef.current = {
|
|
162
|
+
v: typeof initialValue === "function" ? (initialValue as () => T)() : initialValue,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const resolvedInitialValue = lazyInitRef.current.v;
|
|
166
|
+
const [, setState] = React.useState<T>(() => resolvedInitialValue);
|
|
167
|
+
const ref = React.useRef(resolvedInitialValue);
|
|
168
|
+
const setValue = React.useCallback((updater: SetStateAction<T>) => {
|
|
169
|
+
const value: T = typeof updater === "function" ? (updater as any)(ref.current) : updater;
|
|
170
|
+
ref.current = value;
|
|
171
|
+
setState(value);
|
|
172
|
+
}, []);
|
|
173
|
+
const res = React.useMemo(() => ({
|
|
174
|
+
get current() {
|
|
175
|
+
return ref.current;
|
|
176
|
+
},
|
|
177
|
+
set: setValue,
|
|
178
|
+
}), [setValue]);
|
|
179
|
+
return res;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function mapRefState<T, R>(refState: RefState<T>, mapper: (value: T) => R, reverseMapper: (oldT: T, newR: R) => T): RefState<R> {
|
|
183
|
+
let last: [T, R] | null = null;
|
|
184
|
+
return {
|
|
185
|
+
get current() {
|
|
186
|
+
const input = refState.current;
|
|
187
|
+
if (last === null || input !== last[0]) {
|
|
188
|
+
last = [input, mapper(input)];
|
|
189
|
+
}
|
|
190
|
+
return last[1];
|
|
191
|
+
},
|
|
192
|
+
set(updater: SetStateAction<R>) {
|
|
193
|
+
const value: R = typeof updater === "function" ? (updater as any)(this.current) : updater;
|
|
194
|
+
refState.set(reverseMapper(refState.current, value));
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function useQueryState(key: string, defaultValue?: string) {
|
|
200
|
+
const getValue = () => new URLSearchParams(window.location.search).get(key) ?? defaultValue ?? null;
|
|
201
|
+
|
|
202
|
+
const [value, setValue] = React.useState(getValue);
|
|
203
|
+
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
const onPopState = () => setValue(getValue());
|
|
206
|
+
window.addEventListener("popstate", onPopState);
|
|
207
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
208
|
+
}, []);
|
|
209
|
+
|
|
210
|
+
const update = (next: string | null) => {
|
|
211
|
+
const params = new URLSearchParams(window.location.search);
|
|
212
|
+
if (next !== null) {
|
|
213
|
+
params.set(key, next);
|
|
214
|
+
} else {
|
|
215
|
+
params.delete(key);
|
|
216
|
+
}
|
|
217
|
+
const newUrl = params.toString()
|
|
218
|
+
? `${window.location.pathname}?${params.toString()}`
|
|
219
|
+
: window.location.pathname;
|
|
220
|
+
window.history.pushState(null, "", newUrl);
|
|
221
|
+
setValue(next);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return [value, update] as const;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function shouldRethrowRenderingError(error: unknown): boolean {
|
|
228
|
+
return !!error && typeof error === "object" && "digest" in error && error.digest === "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export class NoSuspenseBoundaryError extends Error {
|
|
232
|
+
digest: string;
|
|
233
|
+
reason: string;
|
|
234
|
+
__noSuspenseBoundarySentinel = NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL;
|
|
235
|
+
|
|
236
|
+
constructor(options: { caller?: string }) {
|
|
237
|
+
ensureMonkeyPatch();
|
|
238
|
+
|
|
239
|
+
super(deindent`
|
|
240
|
+
Suspense boundary not found! Read the error message below carefully (or paste it into your AI agent).
|
|
241
|
+
|
|
242
|
+
${options.caller ?? "This code path"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.
|
|
243
|
+
|
|
244
|
+
There are several potential causes:
|
|
245
|
+
|
|
246
|
+
1. [Next.js] You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.
|
|
247
|
+
|
|
248
|
+
2. [React] You are missing a <Suspense> boundary in your component. Fix it by wrapping your component (or the entire app) in a <Suspense> component.
|
|
249
|
+
|
|
250
|
+
3. [Next.js] The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:
|
|
251
|
+
|
|
252
|
+
- app
|
|
253
|
+
- - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? "this code path"}
|
|
254
|
+
- - loading.tsx // required for suspense
|
|
255
|
+
- - (main)
|
|
256
|
+
- - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? "this code path"}
|
|
257
|
+
- - - route.tsx // your actual main page
|
|
258
|
+
- - - the rest of your app
|
|
259
|
+
|
|
260
|
+
For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups
|
|
261
|
+
|
|
262
|
+
4. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.
|
|
263
|
+
|
|
264
|
+
5. Your version of Hexclave is too old. Upgrade to the latest version to see if that fixes the issue.
|
|
265
|
+
|
|
266
|
+
See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
|
|
267
|
+
|
|
268
|
+
More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content
|
|
269
|
+
`);
|
|
270
|
+
|
|
271
|
+
this.name = "NoSuspenseBoundaryError";
|
|
272
|
+
this.reason = options.caller ?? "suspendIfSsr()";
|
|
273
|
+
|
|
274
|
+
// set the digest so nextjs doesn't log the error
|
|
275
|
+
// https://github.com/vercel/next.js/blob/d01d6d9c35a8c2725b3d74c1402ab76d4779a6cf/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts#L14
|
|
276
|
+
this.digest = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
import.meta.vitest?.test("NoSuspenseBoundaryError", ({ expect }) => {
|
|
280
|
+
// Test with default options
|
|
281
|
+
const defaultError = new NoSuspenseBoundaryError({});
|
|
282
|
+
expect(defaultError.name).toBe("NoSuspenseBoundaryError");
|
|
283
|
+
expect(defaultError.reason).toBe("suspendIfSsr()");
|
|
284
|
+
expect(defaultError.digest).toBe("BAILOUT_TO_CLIENT_SIDE_RENDERING");
|
|
285
|
+
expect(defaultError.message).toContain("This code path attempted to display a loading indicator");
|
|
286
|
+
|
|
287
|
+
// Test with custom caller
|
|
288
|
+
const customError = new NoSuspenseBoundaryError({ caller: "CustomComponent" });
|
|
289
|
+
expect(customError.name).toBe("NoSuspenseBoundaryError");
|
|
290
|
+
expect(customError.reason).toBe("CustomComponent");
|
|
291
|
+
expect(customError.digest).toBe("BAILOUT_TO_CLIENT_SIDE_RENDERING");
|
|
292
|
+
expect(customError.message).toContain("CustomComponent attempted to display a loading indicator");
|
|
293
|
+
|
|
294
|
+
// Verify error message contains all the necessary information
|
|
295
|
+
expect(customError.message).toContain("loading.tsx");
|
|
296
|
+
expect(customError.message).toContain("route groups");
|
|
297
|
+
expect(customError.message).toContain("https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Use this in a component or a hook to disable SSR. Should be wrapped in a Suspense boundary, or it will throw an error.
|
|
303
|
+
*/
|
|
304
|
+
export function suspendIfSsr(caller?: string) {
|
|
305
|
+
if (!isBrowserLike()) {
|
|
306
|
+
throw new NoSuspenseBoundaryError({ caller });
|
|
307
|
+
}
|
|
308
|
+
}
|