@hexclave/shared 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +71 -71
- 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 +71 -71
- 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 +4 -4
- 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 +13 -13
- package/dist/esm/interface/crud/current-user.d.ts +9 -9
- package/dist/esm/interface/crud/email-outbox.d.ts +112 -112
- package/dist/esm/interface/crud/products.d.ts +1 -1
- package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
- 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 +28 -28
- package/dist/esm/interface/crud/team-member-profiles.d.ts +22 -22
- 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 +14 -14
- 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 +4 -4
- package/dist/esm/known-errors.js +4 -4
- 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/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 +4 -4
- 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 +13 -13
- package/dist/interface/crud/current-user.d.ts +9 -9
- package/dist/interface/crud/email-outbox.d.ts +112 -112
- package/dist/interface/crud/products.d.ts +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +2 -2
- 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 +28 -28
- package/dist/interface/crud/team-member-profiles.d.ts +22 -22
- 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 +14 -14
- 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 +4 -4
- package/dist/known-errors.js +4 -4
- 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 +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,411 @@
|
|
|
1
|
+
import { wait } from "./promises";
|
|
2
|
+
import { deindent, nicify } from "./strings";
|
|
3
|
+
|
|
4
|
+
export type Result<T, E = unknown> =
|
|
5
|
+
| {
|
|
6
|
+
status: "ok",
|
|
7
|
+
data: T,
|
|
8
|
+
}
|
|
9
|
+
| {
|
|
10
|
+
status: "error",
|
|
11
|
+
error: E,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type AsyncResult<T, E = unknown, P = void> =
|
|
15
|
+
| Result<T, E>
|
|
16
|
+
| (
|
|
17
|
+
& {
|
|
18
|
+
status: "pending",
|
|
19
|
+
}
|
|
20
|
+
& {
|
|
21
|
+
progress: P,
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export const Result = {
|
|
27
|
+
fromThrowing,
|
|
28
|
+
fromThrowingAsync,
|
|
29
|
+
fromPromise: promiseToResult,
|
|
30
|
+
ok<T>(data: T): Result<T, never> & { status: "ok" } {
|
|
31
|
+
return {
|
|
32
|
+
status: "ok",
|
|
33
|
+
data,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
error<E>(error: E): Result<never, E> & { status: "error" } {
|
|
37
|
+
return {
|
|
38
|
+
status: "error",
|
|
39
|
+
error,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
map: mapResult,
|
|
43
|
+
or: <T, E, U>(result: Result<T, E>, fallback: U): T | U => {
|
|
44
|
+
return result.status === "ok" ? result.data : fallback;
|
|
45
|
+
},
|
|
46
|
+
orThrow: <T, E>(result: Result<T, E>): T => {
|
|
47
|
+
if (result.status === "error") {
|
|
48
|
+
throw result.error;
|
|
49
|
+
}
|
|
50
|
+
return result.data;
|
|
51
|
+
},
|
|
52
|
+
orThrowAsync: async <T, E>(result: Promise<Result<T, E>>): Promise<T> => {
|
|
53
|
+
return Result.orThrow(await result);
|
|
54
|
+
},
|
|
55
|
+
retry,
|
|
56
|
+
};
|
|
57
|
+
import.meta.vitest?.test("Result.ok and Result.error", ({ expect }) => {
|
|
58
|
+
// Test Result.ok
|
|
59
|
+
const okResult = Result.ok(42);
|
|
60
|
+
expect(okResult.status).toBe("ok");
|
|
61
|
+
expect(okResult.data).toBe(42);
|
|
62
|
+
|
|
63
|
+
// Test Result.error
|
|
64
|
+
const error = new Error("Test error");
|
|
65
|
+
const errorResult = Result.error(error);
|
|
66
|
+
expect(errorResult.status).toBe("error");
|
|
67
|
+
expect(errorResult.error).toBe(error);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
import.meta.vitest?.test("Result.or", ({ expect }) => {
|
|
71
|
+
// Test with ok result
|
|
72
|
+
const okResult: Result<number, string> = { status: "ok", data: 42 };
|
|
73
|
+
expect(Result.or(okResult, 0)).toBe(42);
|
|
74
|
+
|
|
75
|
+
// Test with error result
|
|
76
|
+
const errorResult: Result<number, string> = { status: "error", error: "error message" };
|
|
77
|
+
expect(Result.or(errorResult, 0)).toBe(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
import.meta.vitest?.test("Result.orThrow", ({ expect }) => {
|
|
81
|
+
// Test with ok result
|
|
82
|
+
const okResult: Result<number, Error> = { status: "ok", data: 42 };
|
|
83
|
+
expect(Result.orThrow(okResult)).toBe(42);
|
|
84
|
+
|
|
85
|
+
// Test with error result
|
|
86
|
+
const error = new Error("Test error");
|
|
87
|
+
const errorResult: Result<number, Error> = { status: "error", error };
|
|
88
|
+
expect(() => Result.orThrow(errorResult)).toThrow(error);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
import.meta.vitest?.test("Result.orThrowAsync", async ({ expect }) => {
|
|
92
|
+
// Test with ok result
|
|
93
|
+
const okPromise = Promise.resolve({ status: "ok", data: 42 } as Result<number, Error>);
|
|
94
|
+
expect(await Result.orThrowAsync(okPromise)).toBe(42);
|
|
95
|
+
|
|
96
|
+
// Test with error result
|
|
97
|
+
const error = new Error("Test error");
|
|
98
|
+
const errorPromise = Promise.resolve({ status: "error", error } as Result<number, Error>);
|
|
99
|
+
await expect(Result.orThrowAsync(errorPromise)).rejects.toThrow(error);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const AsyncResult = {
|
|
103
|
+
fromThrowing,
|
|
104
|
+
fromPromise: promiseToResult,
|
|
105
|
+
ok: Result.ok,
|
|
106
|
+
error: Result.error,
|
|
107
|
+
pending,
|
|
108
|
+
map: mapResult,
|
|
109
|
+
or: <T, E, P, U>(result: AsyncResult<T, E, P>, fallback: U): T | U => {
|
|
110
|
+
if (result.status === "pending") {
|
|
111
|
+
return fallback;
|
|
112
|
+
}
|
|
113
|
+
return Result.or(result, fallback);
|
|
114
|
+
},
|
|
115
|
+
orThrow: <T, E, P>(result: AsyncResult<T, E, P>): T => {
|
|
116
|
+
if (result.status === "pending") {
|
|
117
|
+
throw new Error("Result still pending");
|
|
118
|
+
}
|
|
119
|
+
return Result.orThrow(result);
|
|
120
|
+
},
|
|
121
|
+
retry,
|
|
122
|
+
};
|
|
123
|
+
import.meta.vitest?.test("AsyncResult.or", ({ expect }) => {
|
|
124
|
+
// Test with ok result
|
|
125
|
+
const okResult: AsyncResult<number, string> = { status: "ok", data: 42 };
|
|
126
|
+
expect(AsyncResult.or(okResult, 0)).toBe(42);
|
|
127
|
+
|
|
128
|
+
// Test with error result
|
|
129
|
+
const errorResult: AsyncResult<number, string> = { status: "error", error: "error message" };
|
|
130
|
+
expect(AsyncResult.or(errorResult, 0)).toBe(0);
|
|
131
|
+
|
|
132
|
+
// Test with pending result
|
|
133
|
+
const pendingResult: AsyncResult<number, string> = { status: "pending", progress: undefined };
|
|
134
|
+
expect(AsyncResult.or(pendingResult, 0)).toBe(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
import.meta.vitest?.test("AsyncResult.orThrow", ({ expect }) => {
|
|
138
|
+
// Test with ok result
|
|
139
|
+
const okResult: AsyncResult<number, Error> = { status: "ok", data: 42 };
|
|
140
|
+
expect(AsyncResult.orThrow(okResult)).toBe(42);
|
|
141
|
+
|
|
142
|
+
// Test with error result
|
|
143
|
+
const error = new Error("Test error");
|
|
144
|
+
const errorResult: AsyncResult<number, Error> = { status: "error", error };
|
|
145
|
+
expect(() => AsyncResult.orThrow(errorResult)).toThrow(error);
|
|
146
|
+
|
|
147
|
+
// Test with pending result
|
|
148
|
+
const pendingResult: AsyncResult<number, Error> = { status: "pending", progress: undefined };
|
|
149
|
+
expect(() => AsyncResult.orThrow(pendingResult)).toThrow("Result still pending");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
function pending(): AsyncResult<never, never, void> & { status: "pending" };
|
|
153
|
+
function pending<P>(progress: P): AsyncResult<never, never, P> & { status: "pending" };
|
|
154
|
+
function pending<P>(progress?: P): AsyncResult<never, never, P> & { status: "pending" } {
|
|
155
|
+
return {
|
|
156
|
+
status: "pending",
|
|
157
|
+
progress: progress!,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
import.meta.vitest?.test("pending", ({ expect }) => {
|
|
161
|
+
// Test without progress
|
|
162
|
+
const pendingResult = pending();
|
|
163
|
+
expect(pendingResult.status).toBe("pending");
|
|
164
|
+
expect(pendingResult.progress).toBe(undefined);
|
|
165
|
+
|
|
166
|
+
// Test with progress
|
|
167
|
+
const progressValue = { loaded: 50, total: 100 };
|
|
168
|
+
const pendingWithProgress = pending(progressValue);
|
|
169
|
+
expect(pendingWithProgress.status).toBe("pending");
|
|
170
|
+
expect(pendingWithProgress.progress).toBe(progressValue);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
async function promiseToResult<T>(promise: Promise<T>): Promise<Result<T>> {
|
|
174
|
+
try {
|
|
175
|
+
const value = await promise;
|
|
176
|
+
return Result.ok(value);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return Result.error(error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
import.meta.vitest?.test("promiseToResult", async ({ expect }) => {
|
|
182
|
+
// Test with resolved promise
|
|
183
|
+
const resolvedPromise = Promise.resolve(42);
|
|
184
|
+
const resolvedResult = await promiseToResult(resolvedPromise);
|
|
185
|
+
expect(resolvedResult.status).toBe("ok");
|
|
186
|
+
if (resolvedResult.status === "ok") {
|
|
187
|
+
expect(resolvedResult.data).toBe(42);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Test with rejected promise
|
|
191
|
+
const error = new Error("Test error");
|
|
192
|
+
const rejectedPromise = Promise.reject(error);
|
|
193
|
+
const rejectedResult = await promiseToResult(rejectedPromise);
|
|
194
|
+
expect(rejectedResult.status).toBe("error");
|
|
195
|
+
if (rejectedResult.status === "error") {
|
|
196
|
+
expect(rejectedResult.error).toBe(error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
function fromThrowing<T>(fn: () => T): Result<T, unknown> {
|
|
201
|
+
try {
|
|
202
|
+
return Result.ok(fn());
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return Result.error(error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
import.meta.vitest?.test("fromThrowing", ({ expect }) => {
|
|
208
|
+
// Test with function that succeeds
|
|
209
|
+
const successFn = () => 42;
|
|
210
|
+
const successResult = fromThrowing(successFn);
|
|
211
|
+
expect(successResult.status).toBe("ok");
|
|
212
|
+
if (successResult.status === "ok") {
|
|
213
|
+
expect(successResult.data).toBe(42);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Test with function that throws
|
|
217
|
+
const error = new Error("Test error");
|
|
218
|
+
const errorFn = () => {
|
|
219
|
+
throw error;
|
|
220
|
+
};
|
|
221
|
+
const errorResult = fromThrowing(errorFn);
|
|
222
|
+
expect(errorResult.status).toBe("error");
|
|
223
|
+
if (errorResult.status === "error") {
|
|
224
|
+
expect(errorResult.error).toBe(error);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
async function fromThrowingAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>> {
|
|
229
|
+
try {
|
|
230
|
+
return Result.ok(await fn());
|
|
231
|
+
} catch (error) {
|
|
232
|
+
return Result.error(error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
import.meta.vitest?.test("fromThrowingAsync", async ({ expect }) => {
|
|
236
|
+
// Test with async function that succeeds
|
|
237
|
+
const successFn = async () => 42;
|
|
238
|
+
const successResult = await fromThrowingAsync(successFn);
|
|
239
|
+
expect(successResult.status).toBe("ok");
|
|
240
|
+
if (successResult.status === "ok") {
|
|
241
|
+
expect(successResult.data).toBe(42);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Test with async function that throws
|
|
245
|
+
const error = new Error("Test error");
|
|
246
|
+
const errorFn = async () => {
|
|
247
|
+
throw error;
|
|
248
|
+
};
|
|
249
|
+
const errorResult = await fromThrowingAsync(errorFn);
|
|
250
|
+
expect(errorResult.status).toBe("error");
|
|
251
|
+
if (errorResult.status === "error") {
|
|
252
|
+
expect(errorResult.error).toBe(error);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
function mapResult<T, U, E = unknown, P = unknown>(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
|
|
257
|
+
function mapResult<T, U, E = unknown, P = unknown>(result: AsyncResult<T, E, P>, fn: (data: T) => U): AsyncResult<U, E, P>;
|
|
258
|
+
function mapResult<T, U, E = unknown, P = unknown>(result: AsyncResult<T, E, P>, fn: (data: T) => U): AsyncResult<U, E, P> {
|
|
259
|
+
if (result.status === "error") return {
|
|
260
|
+
status: "error",
|
|
261
|
+
error: result.error,
|
|
262
|
+
};
|
|
263
|
+
if (result.status === "pending") return {
|
|
264
|
+
status: "pending",
|
|
265
|
+
..."progress" in result ? { progress: result.progress } : {},
|
|
266
|
+
} as any;
|
|
267
|
+
|
|
268
|
+
return Result.ok(fn(result.data));
|
|
269
|
+
}
|
|
270
|
+
import.meta.vitest?.test("mapResult", ({ expect }) => {
|
|
271
|
+
// Test with ok result
|
|
272
|
+
const okResult: Result<number, string> = { status: "ok", data: 42 };
|
|
273
|
+
const mappedOk = mapResult(okResult, (n: number) => n * 2);
|
|
274
|
+
expect(mappedOk.status).toBe("ok");
|
|
275
|
+
if (mappedOk.status === "ok") {
|
|
276
|
+
expect(mappedOk.data).toBe(84);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Test with error result
|
|
280
|
+
const errorResult: Result<number, string> = { status: "error", error: "error message" };
|
|
281
|
+
const mappedError = mapResult(errorResult, (n: number) => n * 2);
|
|
282
|
+
expect(mappedError.status).toBe("error");
|
|
283
|
+
if (mappedError.status === "error") {
|
|
284
|
+
expect(mappedError.error).toBe("error message");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Test with pending result (no progress)
|
|
288
|
+
const pendingResult: AsyncResult<number, string, void> = { status: "pending", progress: undefined };
|
|
289
|
+
const mappedPending = mapResult(pendingResult, (n: number) => n * 2);
|
|
290
|
+
expect(mappedPending.status).toBe("pending");
|
|
291
|
+
|
|
292
|
+
// Test with pending result (with progress)
|
|
293
|
+
const progressValue = { loaded: 50, total: 100 };
|
|
294
|
+
const pendingWithProgress: AsyncResult<number, string, typeof progressValue> = {
|
|
295
|
+
status: "pending",
|
|
296
|
+
progress: progressValue
|
|
297
|
+
};
|
|
298
|
+
const mappedPendingWithProgress = mapResult(pendingWithProgress, (n: number) => n * 2);
|
|
299
|
+
expect(mappedPendingWithProgress.status).toBe("pending");
|
|
300
|
+
if (mappedPendingWithProgress.status === "pending") {
|
|
301
|
+
expect(mappedPendingWithProgress.progress).toBe(progressValue);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RetryError<E = unknown> extends AggregateError {
|
|
307
|
+
constructor(public readonly errors: E[]) {
|
|
308
|
+
const strings = errors.map(e => nicify(e));
|
|
309
|
+
const isAllSame = strings.length > 1 && strings.every(s => s === strings[0]);
|
|
310
|
+
super(
|
|
311
|
+
errors,
|
|
312
|
+
deindent`
|
|
313
|
+
Error after ${errors.length} attempts.
|
|
314
|
+
|
|
315
|
+
${isAllSame ? deindent`
|
|
316
|
+
Attempts 1-${errors.length}:
|
|
317
|
+
${strings[0]}
|
|
318
|
+
` : strings.map((s, i) => deindent`
|
|
319
|
+
Attempt ${i + 1}:
|
|
320
|
+
${s}
|
|
321
|
+
`).join("\n\n")}
|
|
322
|
+
`,
|
|
323
|
+
{ cause: errors[errors.length - 1] }
|
|
324
|
+
);
|
|
325
|
+
this.name = "RetryError";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
get attempts() {
|
|
329
|
+
return this.errors.length;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
RetryError.prototype.name = "RetryError";
|
|
333
|
+
|
|
334
|
+
import.meta.vitest?.test("RetryError", ({ expect }) => {
|
|
335
|
+
// Test with single error
|
|
336
|
+
const singleError = new Error("Single error");
|
|
337
|
+
const retryErrorSingle = new RetryError([singleError]);
|
|
338
|
+
expect(retryErrorSingle.name).toBe("RetryError");
|
|
339
|
+
expect(retryErrorSingle.errors).toEqual([singleError]);
|
|
340
|
+
expect(retryErrorSingle.attempts).toBe(1);
|
|
341
|
+
expect(retryErrorSingle.cause).toBe(singleError);
|
|
342
|
+
expect(retryErrorSingle.message).toContain("Error after 1 attempts");
|
|
343
|
+
|
|
344
|
+
// Test with multiple different errors
|
|
345
|
+
const error1 = new Error("Error 1");
|
|
346
|
+
const error2 = new Error("Error 2");
|
|
347
|
+
const retryErrorMultiple = new RetryError([error1, error2]);
|
|
348
|
+
expect(retryErrorMultiple.name).toBe("RetryError");
|
|
349
|
+
expect(retryErrorMultiple.errors).toEqual([error1, error2]);
|
|
350
|
+
expect(retryErrorMultiple.attempts).toBe(2);
|
|
351
|
+
expect(retryErrorMultiple.cause).toBe(error2);
|
|
352
|
+
expect(retryErrorMultiple.message).toContain("Error after 2 attempts");
|
|
353
|
+
expect(retryErrorMultiple.message).toContain("Attempt 1");
|
|
354
|
+
expect(retryErrorMultiple.message).toContain("Attempt 2");
|
|
355
|
+
|
|
356
|
+
// Test with multiple identical errors
|
|
357
|
+
const sameError = new Error("Same error");
|
|
358
|
+
const retryErrorSame = new RetryError([sameError, sameError]);
|
|
359
|
+
expect(retryErrorSame.name).toBe("RetryError");
|
|
360
|
+
expect(retryErrorSame.errors).toEqual([sameError, sameError]);
|
|
361
|
+
expect(retryErrorSame.attempts).toBe(2);
|
|
362
|
+
expect(retryErrorSame.cause).toBe(sameError);
|
|
363
|
+
expect(retryErrorSame.message).toContain("Error after 2 attempts");
|
|
364
|
+
expect(retryErrorSame.message).toContain("Attempts 1-2");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
async function retry<T, E>(
|
|
368
|
+
fn: (attemptIndex: number) => Result<T, E> | Promise<Result<T, E>>,
|
|
369
|
+
totalAttempts: number,
|
|
370
|
+
{ exponentialDelayBase = 1000 } = {},
|
|
371
|
+
): Promise<Result<T, RetryError<E>> & { attempts: number }> {
|
|
372
|
+
const errors: E[] = [];
|
|
373
|
+
for (let i = 0; i < totalAttempts; i++) {
|
|
374
|
+
const res = await fn(i);
|
|
375
|
+
if (res.status === "ok") {
|
|
376
|
+
return Object.assign(Result.ok(res.data), { attempts: i + 1 });
|
|
377
|
+
} else {
|
|
378
|
+
errors.push(res.error);
|
|
379
|
+
if (i < totalAttempts - 1) {
|
|
380
|
+
await wait((Math.random() + 0.5) * exponentialDelayBase * (2 ** i));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return Object.assign(Result.error(new RetryError(errors)), { attempts: totalAttempts });
|
|
385
|
+
}
|
|
386
|
+
import.meta.vitest?.test("retry", async ({ expect }) => {
|
|
387
|
+
// Test successful on first attempt
|
|
388
|
+
const successFn = async () => Result.ok("success");
|
|
389
|
+
const successResult = await retry(successFn, 3, { exponentialDelayBase: 0 });
|
|
390
|
+
expect(successResult).toEqual({ status: "ok", data: "success", attempts: 1 });
|
|
391
|
+
|
|
392
|
+
// Test successful after failures
|
|
393
|
+
let attemptCount = 0;
|
|
394
|
+
const eventualSuccessFn = async () => {
|
|
395
|
+
return ++attemptCount < 2 ? Result.error(new Error(`Attempt ${attemptCount} failed`))
|
|
396
|
+
: Result.ok("eventual success");
|
|
397
|
+
};
|
|
398
|
+
const eventualSuccessResult = await retry(eventualSuccessFn, 3, { exponentialDelayBase: 0 });
|
|
399
|
+
expect(eventualSuccessResult).toEqual({ status: "ok", data: "eventual success", attempts: 2 });
|
|
400
|
+
|
|
401
|
+
// Test all attempts fail
|
|
402
|
+
const errors = [new Error("Error 1"), new Error("Error 2"), new Error("Error 3")];
|
|
403
|
+
const allFailFn = async (attempt: number) => {
|
|
404
|
+
return Result.error(errors[attempt]);
|
|
405
|
+
};
|
|
406
|
+
const allFailResult = await retry(allFailFn, 3, { exponentialDelayBase: 0 });
|
|
407
|
+
expect(allFailResult).toEqual({ status: "error", error: expect.any(RetryError), attempts: 3 });
|
|
408
|
+
const retryError = (allFailResult as any).error as RetryError;
|
|
409
|
+
expect(retryError.errors).toEqual(errors);
|
|
410
|
+
expect(retryError.attempts).toBe(3);
|
|
411
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const sentryBaseConfig = {
|
|
2
|
+
ignoreErrors: [
|
|
3
|
+
// React throws these errors when used with some browser extensions (eg. Google Translate)
|
|
4
|
+
"NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
|
|
5
|
+
"NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.",
|
|
6
|
+
],
|
|
7
|
+
|
|
8
|
+
normalizeDepth: 5,
|
|
9
|
+
maxValueLength: 5000,
|
|
10
|
+
|
|
11
|
+
// Adjust this value in production, or use tracesSampler for greater control
|
|
12
|
+
tracesSampleRate: 1.0,
|
|
13
|
+
|
|
14
|
+
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
|
15
|
+
debug: false,
|
|
16
|
+
|
|
17
|
+
replaysOnErrorSampleRate: 1.0,
|
|
18
|
+
|
|
19
|
+
replaysSessionSampleRate: 1.0,
|
|
20
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { ReadWriteLock } from "./locks";
|
|
2
|
+
import { ReactPromise, pending, rejected, resolved } from "./promises";
|
|
3
|
+
import { AsyncResult, Result } from "./results";
|
|
4
|
+
import { generateUuid } from "./uuids";
|
|
5
|
+
|
|
6
|
+
export type ReadonlyStore<T> = {
|
|
7
|
+
get(): T,
|
|
8
|
+
onChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void },
|
|
9
|
+
onceChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AsyncStoreStateChangeCallback<T> = (args: { state: AsyncResult<T>, oldState: AsyncResult<T>, lastOkValue: T | undefined }) => void;
|
|
13
|
+
|
|
14
|
+
export type ReadonlyAsyncStore<T> = {
|
|
15
|
+
isAvailable(): boolean,
|
|
16
|
+
get(): AsyncResult<T, unknown, void>,
|
|
17
|
+
getOrWait(): ReactPromise<T>,
|
|
18
|
+
onChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void },
|
|
19
|
+
onceChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void },
|
|
20
|
+
onStateChange(callback: AsyncStoreStateChangeCallback<T>): { unsubscribe: () => void },
|
|
21
|
+
onceStateChange(callback: AsyncStoreStateChangeCallback<T>): { unsubscribe: () => void },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class Store<T> implements ReadonlyStore<T> {
|
|
25
|
+
private readonly _callbacks: Map<string, ((value: T, oldValue: T | undefined) => void)> = new Map();
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private _value: T
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
get(): T {
|
|
32
|
+
return this._value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set(value: T): void {
|
|
36
|
+
const oldValue = this._value;
|
|
37
|
+
this._value = value;
|
|
38
|
+
this._callbacks.forEach((callback) => callback(value, oldValue));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
update(updater: (value: T) => T): T {
|
|
42
|
+
const value = updater(this._value);
|
|
43
|
+
this.set(value);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void } {
|
|
48
|
+
const uuid = generateUuid();
|
|
49
|
+
this._callbacks.set(uuid, callback);
|
|
50
|
+
return {
|
|
51
|
+
unsubscribe: () => {
|
|
52
|
+
this._callbacks.delete(uuid);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onceChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void } {
|
|
58
|
+
const { unsubscribe } = this.onChange((...args) => {
|
|
59
|
+
unsubscribe();
|
|
60
|
+
callback(...args);
|
|
61
|
+
});
|
|
62
|
+
return { unsubscribe };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const storeLock = new ReadWriteLock();
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export class AsyncStore<T> implements ReadonlyAsyncStore<T> {
|
|
70
|
+
private _isAvailable: boolean;
|
|
71
|
+
private _mostRecentOkValue: T | undefined = undefined;
|
|
72
|
+
|
|
73
|
+
private _isRejected = false;
|
|
74
|
+
private _rejectionError: unknown;
|
|
75
|
+
private readonly _waitingRejectFunctions = new Map<string, ((error: unknown) => void)>();
|
|
76
|
+
|
|
77
|
+
private readonly _callbacks: Map<string, AsyncStoreStateChangeCallback<T>> = new Map();
|
|
78
|
+
|
|
79
|
+
private _updateCounter = 0;
|
|
80
|
+
private _lastSuccessfulUpdate = -1;
|
|
81
|
+
|
|
82
|
+
constructor(...args: [] | [T]) {
|
|
83
|
+
if (args.length === 0) {
|
|
84
|
+
this._isAvailable = false;
|
|
85
|
+
} else {
|
|
86
|
+
this._isAvailable = true;
|
|
87
|
+
this._mostRecentOkValue = args[0];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isAvailable(): boolean {
|
|
92
|
+
return this._isAvailable;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isRejected(): boolean {
|
|
96
|
+
return this._isRejected;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get() {
|
|
100
|
+
if (this.isRejected()) {
|
|
101
|
+
return AsyncResult.error(this._rejectionError);
|
|
102
|
+
} else if (this.isAvailable()) {
|
|
103
|
+
return AsyncResult.ok(this._mostRecentOkValue as T);
|
|
104
|
+
} else {
|
|
105
|
+
return AsyncResult.pending();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getOrWait(): ReactPromise<T> {
|
|
110
|
+
const uuid = generateUuid();
|
|
111
|
+
if (this.isRejected()) {
|
|
112
|
+
return rejected(this._rejectionError);
|
|
113
|
+
} else if (this.isAvailable()) {
|
|
114
|
+
return resolved(this._mostRecentOkValue as T);
|
|
115
|
+
}
|
|
116
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
117
|
+
this.onceChange((value) => {
|
|
118
|
+
resolve(value);
|
|
119
|
+
});
|
|
120
|
+
this._waitingRejectFunctions.set(uuid, reject);
|
|
121
|
+
});
|
|
122
|
+
const withFinally = promise.finally(() => {
|
|
123
|
+
this._waitingRejectFunctions.delete(uuid);
|
|
124
|
+
});
|
|
125
|
+
return pending(withFinally);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_setIfLatest(result: Result<T>, curCounter: number) {
|
|
129
|
+
const oldState = this.get();
|
|
130
|
+
const oldValue = this._mostRecentOkValue;
|
|
131
|
+
if (curCounter > this._lastSuccessfulUpdate) {
|
|
132
|
+
switch (result.status) {
|
|
133
|
+
case "ok": {
|
|
134
|
+
if (!this._isAvailable || this._isRejected || this._mostRecentOkValue !== result.data) {
|
|
135
|
+
this._lastSuccessfulUpdate = curCounter;
|
|
136
|
+
this._isAvailable = true;
|
|
137
|
+
this._isRejected = false;
|
|
138
|
+
this._mostRecentOkValue = result.data;
|
|
139
|
+
this._rejectionError = undefined;
|
|
140
|
+
this._callbacks.forEach((callback) => callback({
|
|
141
|
+
state: this.get(),
|
|
142
|
+
oldState,
|
|
143
|
+
lastOkValue: oldValue,
|
|
144
|
+
}));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
case "error": {
|
|
150
|
+
this._lastSuccessfulUpdate = curCounter;
|
|
151
|
+
this._isAvailable = false;
|
|
152
|
+
this._isRejected = true;
|
|
153
|
+
this._rejectionError = result.error;
|
|
154
|
+
this._waitingRejectFunctions.forEach((reject) => reject(result.error));
|
|
155
|
+
this._callbacks.forEach((callback) => callback({
|
|
156
|
+
state: this.get(),
|
|
157
|
+
oldState,
|
|
158
|
+
lastOkValue: oldValue,
|
|
159
|
+
}));
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
set(value: T): void {
|
|
168
|
+
this._setIfLatest(Result.ok(value), ++this._updateCounter);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
update(updater: (value: T | undefined) => T): T {
|
|
172
|
+
const value = updater(this._mostRecentOkValue);
|
|
173
|
+
this.set(value);
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async setAsync(promise: Promise<T>): Promise<boolean> {
|
|
178
|
+
return await storeLock.withReadLock(async () => {
|
|
179
|
+
const curCounter = ++this._updateCounter;
|
|
180
|
+
const result = await Result.fromPromise(promise);
|
|
181
|
+
return this._setIfLatest(result, curCounter);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setUnavailable(): void {
|
|
186
|
+
this._lastSuccessfulUpdate = ++this._updateCounter;
|
|
187
|
+
this._mostRecentOkValue = undefined;
|
|
188
|
+
this._isAvailable = false;
|
|
189
|
+
this._isRejected = false;
|
|
190
|
+
this._rejectionError = undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
setRejected(error: unknown): void {
|
|
194
|
+
this._setIfLatest(Result.error(error), ++this._updateCounter);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
map<U>(mapper: (value: T) => U): AsyncStore<U> {
|
|
198
|
+
const store = new AsyncStore<U>();
|
|
199
|
+
this.onChange((value) => {
|
|
200
|
+
store.set(mapper(value));
|
|
201
|
+
});
|
|
202
|
+
return store;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void } {
|
|
206
|
+
return this.onStateChange(({ state, lastOkValue }) => {
|
|
207
|
+
if (state.status === "ok") {
|
|
208
|
+
callback(state.data, lastOkValue);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
onStateChange(callback: AsyncStoreStateChangeCallback<T>): { unsubscribe: () => void } {
|
|
214
|
+
const uuid = generateUuid();
|
|
215
|
+
this._callbacks.set(uuid, callback);
|
|
216
|
+
return {
|
|
217
|
+
unsubscribe: () => {
|
|
218
|
+
this._callbacks.delete(uuid);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
onceChange(callback: (value: T, oldValue: T | undefined) => void): { unsubscribe: () => void } {
|
|
224
|
+
const { unsubscribe } = this.onChange((...args) => {
|
|
225
|
+
unsubscribe();
|
|
226
|
+
callback(...args);
|
|
227
|
+
});
|
|
228
|
+
return { unsubscribe };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
onceStateChange(callback: AsyncStoreStateChangeCallback<T>): { unsubscribe: () => void } {
|
|
232
|
+
const { unsubscribe } = this.onStateChange((...args) => {
|
|
233
|
+
unsubscribe();
|
|
234
|
+
callback(...args);
|
|
235
|
+
});
|
|
236
|
+
return { unsubscribe };
|
|
237
|
+
}
|
|
238
|
+
}
|