@shivaedev/react 0.23.2 → 0.25.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.25.0 - 2026-10-30
4
+
5
+ ### Added
6
+
7
+ - `authCall` turns a Better Auth client callback into an input-taking Effect for form `onSubmit` handlers.
8
+
9
+ ### Breaking
10
+
11
+ - `authAction` now fails with `AuthFailure`. Map its closed `code` union to application text instead of reading raw Better Auth error fields or HTTP statuses. Core Better Auth codes pass through; code-less rate limiting becomes `RATE_LIMITED`, and unsupported codes and rejected promises become `UNKNOWN`. Read the original error from `cause`. Replace action error annotations that include `AuthActionUnavailable` with `AuthFailure`; `authQuery` retains its existing failures.
12
+
13
+ ## 0.24.0 - 2026-10-30
14
+
15
+ ### Added
16
+
17
+ - `useLoaderQuery` accepts the shared query options, including `coalesceWindow`, so loader-backed screens can configure live refetches directly.
18
+ - `NoticeHost` accepts an optional `runtime` and runs notice expiry as an Effect sleep on that atom runtime's `Clock`, so a story's `TestClock` drives expiry. Without it, expiry uses Effect's default clock as before.
19
+
3
20
  ## 0.23.2 - 2026-10-30
4
21
 
5
22
  ### Changed
package/README.md CHANGED
@@ -128,7 +128,7 @@ const connect = () => bind(Notes, Client, { resume });
128
128
 
129
129
  #### Mount the notice host
130
130
 
131
- Mount the optional `NoticeHost` from `@shivaedev/react/notices.ts` inside `SessionBoundary`, above the screens. The host owns a native atom collection, dismissal, expiry and each notice action's native `AsyncResult`. Supply `render(notice)` to choose markup, wording for action outcomes and labels. Render `notice.action.run` as a button handler and disable that control with `notice.action.pending`; `notice.dismiss` removes the entry. No command requires a host.
131
+ Mount the optional `NoticeHost` from `@shivaedev/react/notices.ts` inside `SessionBoundary`, above the screens. The host owns a native atom collection, dismissal, expiry and each notice action's native `AsyncResult`. Supply `render(notice)` to choose markup, wording for action outcomes and labels. Render `notice.action.run` as a button handler and disable that control with `notice.action.pending`; `notice.dismiss` removes the entry. No command requires a host. Expiry sleeps on the `Clock` of the optional `runtime` prop, an atom runtime such as `client.runtime`; without it, the host uses Effect's default clock. A story can pass a runtime built with `Layer.succeed(Clock.Clock, clock)` and drive expiry with `TestClock.adjust`.
132
132
 
133
133
  ```tsx
134
134
  import { Cause } from "effect";
@@ -491,7 +491,7 @@ Here `api`, `NotesClient` and `TitleFields` belong to the app. Render `notes` th
491
491
 
492
492
  Replacing the session during a command or Undo disposes the outgoing registry: late completion callbacks and outcomes do not publish, and retained notice action and dismiss handlers cannot change the new host.
493
493
 
494
- A descriptor has `message`, optional `expiresIn` as a native Duration, and optional `action: { label, run }`. Without an expiry it remains until dismissed or the host ends. Dismissal, expiry, host unmount and session replacement remove entries, clear expiry timers and interrupt pending actions. Native uninterruptible Effects may finish, but their old notice cannot affect the new host. Notices survive screen unmount while their host remains mounted.
494
+ A descriptor has `message`, optional `expiresIn` as a native Duration, and optional `action: { label, run }`. Without an expiry it remains until dismissed or the host ends. Dismissal, expiry, host unmount and session replacement remove entries, interrupt their expiry sleeps and interrupt pending actions. Native uninterruptible Effects may finish, but their old notice cannot affect the new host. Notices survive screen unmount while their host remains mounted.
495
495
 
496
496
  #### Completion inside each command invocation
497
497
 
@@ -746,7 +746,16 @@ function SignInButton({ email, password }: { email: string; password: string })
746
746
  }
747
747
  ```
748
748
 
749
- The example uses `authAction`, `useAction`, `effect/reactivity/AsyncResult` and `effect/Option`, with an application-owned `authClient`. Returned auth errors keep their fields in the typed failure channel. Rejected promises become `AuthActionUnavailable` with the original rejection in `cause`. The application chooses error text and what happens after success.
749
+ The example uses `authAction`, `useAction`, `effect/reactivity/AsyncResult` and `effect/Option`, with an application-owned `authClient`. Actions fail with `AuthFailure` from the same module. Its `code` uses the closed `AuthFailureCode` union: supported Better Auth core codes, `RATE_LIMITED` for a code-less 429, and `UNKNOWN` for unsupported or missing codes and rejected promises. The original error is available in `cause`. Applications own the sentences, and can make their mapping exhaustive with `Record<AuthFailureCode, string>`.
750
+
751
+ For a form's Effect-based `onSubmit`, use `authCall` from the same module:
752
+
753
+ ```ts
754
+ const signIn = authCall((credentials: { email: string; password: string }, signal) => authClient.signIn.email(credentials, { signal }));
755
+ const onSubmit = (credentials: { email: string; password: string }) => signIn(credentials);
756
+ ```
757
+
758
+ `authCall` returns a function from input to `Effect<A, AuthFailure>`. It uses the same failure codes and successful data as `authAction`.
750
759
 
751
760
  `authQuery` from `@shivaedev/react/client/authQuery.ts` creates an input-keyed query atom family for Better Auth reads. Render it with `useQuery`, just like a contract query atom:
752
761
 
@@ -768,7 +777,7 @@ function Account() {
768
777
  }
769
778
  ```
770
779
 
771
- The example uses the application's Better Auth client. Reads preserve returned auth errors and use the same `AuthActionUnavailable` failure as actions for rejected promises. Releasing a pending read interrupts its Effect and aborts the supplied signal.
780
+ The example uses the application's Better Auth client. Reads preserve returned auth errors and use `AuthActionUnavailable` for rejected promises. Releasing a pending read interrupts its Effect and aborts the supplied signal.
772
781
 
773
782
  For an explicit sign-out in a TanStack route, use `useSignOut(signOut, { signIn: "/sign-in" })` from `@shivaedev/react/client/useSignOut.ts` in place of `useAction`. It exposes the same state and a `dispatch()` handler. The hook records sign-out intent before the request and navigates to sign-in after success. Repeated clicks while pending share one request and follow-up. Unmounting the component or disposing its registry stops the follow-up navigation. If a session refresh replaces the component before the response, the replacement session’s route policy owns navigation to sign-in. `permissionGuard` and `signInSearch(location)` omit `returnTo` for that intent, including when the auth owner's session refresh reruns guards first. Use `signInSearch` for module sign-in links too; ordinary visits retain the full requested location. Applications do not need history markers or Better Auth completion timing hooks.
774
783
 
@@ -1,3 +1,4 @@
1
+ import { Effect, Schema } from "effect";
1
2
  import * as Atom from "effect/reactivity/Atom";
2
3
  declare const AuthActionUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
3
4
  readonly _tag: "AuthActionUnavailable";
@@ -10,6 +11,15 @@ export interface AuthActionResult<A, E> {
10
11
  readonly data: A;
11
12
  readonly error: E | null;
12
13
  }
13
- export declare function authAction<TInput, A, E extends object = never>(run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>): Atom.AtomResultFn<TInput, A, E | AuthActionUnavailable>;
14
+ export declare const AuthFailureCode: Schema.Literals<readonly ["USER_NOT_FOUND", "FAILED_TO_CREATE_USER", "FAILED_TO_CREATE_SESSION", "FAILED_TO_UPDATE_USER", "FAILED_TO_GET_SESSION", "INVALID_PASSWORD", "INVALID_EMAIL", "INVALID_EMAIL_OR_PASSWORD", "INVALID_USER", "SOCIAL_ACCOUNT_ALREADY_LINKED", "PROVIDER_NOT_FOUND", "INVALID_TOKEN", "TOKEN_EXPIRED", "ID_TOKEN_NOT_SUPPORTED", "FAILED_TO_GET_USER_INFO", "USER_EMAIL_NOT_FOUND", "EMAIL_NOT_VERIFIED", "PASSWORD_TOO_SHORT", "PASSWORD_TOO_LONG", "USER_ALREADY_EXISTS", "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", "EMAIL_CAN_NOT_BE_UPDATED", "CHANGE_EMAIL_DISABLED", "CREDENTIAL_ACCOUNT_NOT_FOUND", "SESSION_EXPIRED", "FAILED_TO_UNLINK_LAST_ACCOUNT", "ACCOUNT_NOT_FOUND", "USER_ALREADY_HAS_PASSWORD", "CROSS_SITE_NAVIGATION_LOGIN_BLOCKED", "VERIFICATION_EMAIL_NOT_ENABLED", "EMAIL_ALREADY_VERIFIED", "EMAIL_MISMATCH", "SESSION_NOT_FRESH", "LINKED_ACCOUNT_ALREADY_EXISTS", "INVALID_ORIGIN", "INVALID_CALLBACK_URL", "INVALID_REDIRECT_URL", "INVALID_ERROR_CALLBACK_URL", "INVALID_NEW_USER_CALLBACK_URL", "MISSING_OR_NULL_ORIGIN", "CALLBACK_URL_REQUIRED", "FAILED_TO_CREATE_VERIFICATION", "FIELD_NOT_ALLOWED", "ASYNC_VALIDATION_NOT_SUPPORTED", "VALIDATION_ERROR", "MISSING_FIELD", "METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED", "BODY_MUST_BE_AN_OBJECT", "PASSWORD_ALREADY_SET", "RATE_LIMITED", "UNKNOWN"]>;
15
+ export type AuthFailureCode = typeof AuthFailureCode.Type;
16
+ declare const AuthFailure_base: Schema.Class<AuthFailure, Schema.TaggedStruct<"AuthFailure", {
17
+ readonly cause: Schema.Unknown;
18
+ readonly code: Schema.Literals<readonly ["USER_NOT_FOUND", "FAILED_TO_CREATE_USER", "FAILED_TO_CREATE_SESSION", "FAILED_TO_UPDATE_USER", "FAILED_TO_GET_SESSION", "INVALID_PASSWORD", "INVALID_EMAIL", "INVALID_EMAIL_OR_PASSWORD", "INVALID_USER", "SOCIAL_ACCOUNT_ALREADY_LINKED", "PROVIDER_NOT_FOUND", "INVALID_TOKEN", "TOKEN_EXPIRED", "ID_TOKEN_NOT_SUPPORTED", "FAILED_TO_GET_USER_INFO", "USER_EMAIL_NOT_FOUND", "EMAIL_NOT_VERIFIED", "PASSWORD_TOO_SHORT", "PASSWORD_TOO_LONG", "USER_ALREADY_EXISTS", "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", "EMAIL_CAN_NOT_BE_UPDATED", "CHANGE_EMAIL_DISABLED", "CREDENTIAL_ACCOUNT_NOT_FOUND", "SESSION_EXPIRED", "FAILED_TO_UNLINK_LAST_ACCOUNT", "ACCOUNT_NOT_FOUND", "USER_ALREADY_HAS_PASSWORD", "CROSS_SITE_NAVIGATION_LOGIN_BLOCKED", "VERIFICATION_EMAIL_NOT_ENABLED", "EMAIL_ALREADY_VERIFIED", "EMAIL_MISMATCH", "SESSION_NOT_FRESH", "LINKED_ACCOUNT_ALREADY_EXISTS", "INVALID_ORIGIN", "INVALID_CALLBACK_URL", "INVALID_REDIRECT_URL", "INVALID_ERROR_CALLBACK_URL", "INVALID_NEW_USER_CALLBACK_URL", "MISSING_OR_NULL_ORIGIN", "CALLBACK_URL_REQUIRED", "FAILED_TO_CREATE_VERIFICATION", "FIELD_NOT_ALLOWED", "ASYNC_VALIDATION_NOT_SUPPORTED", "VALIDATION_ERROR", "MISSING_FIELD", "METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED", "BODY_MUST_BE_AN_OBJECT", "PASSWORD_ALREADY_SET", "RATE_LIMITED", "UNKNOWN"]>;
19
+ }>, import("effect/Cause").YieldableError>;
20
+ export declare class AuthFailure extends AuthFailure_base {
21
+ }
22
+ export declare function authCall<TInput, A, E extends object = never>(run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>): (input: TInput) => Effect.Effect<A, AuthFailure>;
23
+ export declare function authAction<TInput, A, E extends object = never>(run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>): Atom.AtomResultFn<TInput, A, AuthFailure>;
14
24
  export {};
15
25
  //# sourceMappingURL=authAction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authAction.d.ts","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;;;;AAE/C,qBAAa,qBAAsB,SAAQ,2BAA0C;IACpF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB,CAAC;CAAG;AAEL,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC7D,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1E,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAOzD"}
1
+ {"version":3,"file":"authAction.d.ts","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;;;;AAE/C,qBAAa,qBAAsB,SAAQ,2BAA0C;IACpF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB,CAAC;CAAG;AAEL,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,eAAe,+xCAoD1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;;;;;AAE1D,qBAAa,WAAY,SAAQ,gBAG/B;CAAG;AAIL,wBAAgB,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC3D,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1E,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAiBlD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC7D,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1E,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,CAE3C"}
@@ -1,11 +1,83 @@
1
- import { Data, Effect } from "effect";
1
+ import { Data, Effect, Schema } from "effect";
2
+ import * as HttpStatus from "effect/http/HttpStatus";
2
3
  import * as Atom from "effect/reactivity/Atom";
3
4
  export class AuthActionUnavailable extends Data.TaggedError("AuthActionUnavailable") {
4
5
  }
5
- export function authAction(run) {
6
- return Atom.fn()((input) => Effect.tryPromise({
7
- catch: (cause) => new AuthActionUnavailable({ cause }),
6
+ export const AuthFailureCode = Schema.Literals([
7
+ "USER_NOT_FOUND",
8
+ "FAILED_TO_CREATE_USER",
9
+ "FAILED_TO_CREATE_SESSION",
10
+ "FAILED_TO_UPDATE_USER",
11
+ "FAILED_TO_GET_SESSION",
12
+ "INVALID_PASSWORD",
13
+ "INVALID_EMAIL",
14
+ "INVALID_EMAIL_OR_PASSWORD",
15
+ "INVALID_USER",
16
+ "SOCIAL_ACCOUNT_ALREADY_LINKED",
17
+ "PROVIDER_NOT_FOUND",
18
+ "INVALID_TOKEN",
19
+ "TOKEN_EXPIRED",
20
+ "ID_TOKEN_NOT_SUPPORTED",
21
+ "FAILED_TO_GET_USER_INFO",
22
+ "USER_EMAIL_NOT_FOUND",
23
+ "EMAIL_NOT_VERIFIED",
24
+ "PASSWORD_TOO_SHORT",
25
+ "PASSWORD_TOO_LONG",
26
+ "USER_ALREADY_EXISTS",
27
+ "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL",
28
+ "EMAIL_CAN_NOT_BE_UPDATED",
29
+ "CHANGE_EMAIL_DISABLED",
30
+ "CREDENTIAL_ACCOUNT_NOT_FOUND",
31
+ "SESSION_EXPIRED",
32
+ "FAILED_TO_UNLINK_LAST_ACCOUNT",
33
+ "ACCOUNT_NOT_FOUND",
34
+ "USER_ALREADY_HAS_PASSWORD",
35
+ "CROSS_SITE_NAVIGATION_LOGIN_BLOCKED",
36
+ "VERIFICATION_EMAIL_NOT_ENABLED",
37
+ "EMAIL_ALREADY_VERIFIED",
38
+ "EMAIL_MISMATCH",
39
+ "SESSION_NOT_FRESH",
40
+ "LINKED_ACCOUNT_ALREADY_EXISTS",
41
+ "INVALID_ORIGIN",
42
+ "INVALID_CALLBACK_URL",
43
+ "INVALID_REDIRECT_URL",
44
+ "INVALID_ERROR_CALLBACK_URL",
45
+ "INVALID_NEW_USER_CALLBACK_URL",
46
+ "MISSING_OR_NULL_ORIGIN",
47
+ "CALLBACK_URL_REQUIRED",
48
+ "FAILED_TO_CREATE_VERIFICATION",
49
+ "FIELD_NOT_ALLOWED",
50
+ "ASYNC_VALIDATION_NOT_SUPPORTED",
51
+ "VALIDATION_ERROR",
52
+ "MISSING_FIELD",
53
+ "METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED",
54
+ "BODY_MUST_BE_AN_OBJECT",
55
+ "PASSWORD_ALREADY_SET",
56
+ "RATE_LIMITED",
57
+ "UNKNOWN",
58
+ ]);
59
+ export class AuthFailure extends Schema.TaggedError()("AuthFailure", {
60
+ cause: Schema.Unknown,
61
+ code: AuthFailureCode,
62
+ }) {
63
+ }
64
+ const supportedCode = Schema.is(AuthFailureCode);
65
+ export function authCall(run) {
66
+ return (input) => Effect.tryPromise({
67
+ catch: (cause) => new AuthFailure({ cause, code: "UNKNOWN" }),
8
68
  try: (signal) => run(input, signal),
9
- }).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error)))));
69
+ }).pipe(Effect.flatMap(({ data, error }) => {
70
+ if (error === null) {
71
+ return Effect.succeed(data);
72
+ }
73
+ if ("code" in error && supportedCode(error.code)) {
74
+ return Effect.fail(new AuthFailure({ cause: error, code: error.code }));
75
+ }
76
+ const code = "status" in error && error.status === HttpStatus.fromLiteral("TooManyRequests") ? "RATE_LIMITED" : "UNKNOWN";
77
+ return Effect.fail(new AuthFailure({ cause: error, code }));
78
+ }));
79
+ }
80
+ export function authAction(run) {
81
+ return Atom.fn()(authCall(run));
10
82
  }
11
83
  //# sourceMappingURL=authAction.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authAction.js","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,OAAO,qBAAsB,SAAQ,IAAI,CAAC,WAAW,CAAC,uBAAuB,CAEjF;CAAG;AAOL,MAAM,UAAU,UAAU,CACzB,GAA4E;IAE5E,OAAO,IAAI,CAAC,EAAE,EAAU,CAAC,CAAC,KAAK,EAAE,EAAE,CAClC,MAAM,CAAC,UAAU,CAAC;QACjB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,qBAAqB,CAAC,EAAE,KAAK,EAAE,CAAC;QACtD,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC;KACnC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACtH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"authAction.js","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,OAAO,qBAAsB,SAAQ,IAAI,CAAC,WAAW,CAAC,uBAAuB,CAEjF;CAAG;AAOL,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC9C,gBAAgB;IAChB,uBAAuB;IACvB,0BAA0B;IAC1B,uBAAuB;IACvB,uBAAuB;IACvB,kBAAkB;IAClB,eAAe;IACf,2BAA2B;IAC3B,cAAc;IACd,+BAA+B;IAC/B,oBAAoB;IACpB,eAAe;IACf,eAAe;IACf,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;IACtB,oBAAoB;IACpB,oBAAoB;IACpB,mBAAmB;IACnB,qBAAqB;IACrB,uCAAuC;IACvC,0BAA0B;IAC1B,uBAAuB;IACvB,8BAA8B;IAC9B,iBAAiB;IACjB,+BAA+B;IAC/B,mBAAmB;IACnB,2BAA2B;IAC3B,qCAAqC;IACrC,gCAAgC;IAChC,wBAAwB;IACxB,gBAAgB;IAChB,mBAAmB;IACnB,+BAA+B;IAC/B,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;IACtB,4BAA4B;IAC5B,+BAA+B;IAC/B,wBAAwB;IACxB,uBAAuB;IACvB,+BAA+B;IAC/B,mBAAmB;IACnB,gCAAgC;IAChC,kBAAkB;IAClB,eAAe;IACf,2CAA2C;IAC3C,wBAAwB;IACxB,sBAAsB;IACtB,cAAc;IACd,SAAS;CACT,CAAC,CAAC;AAGH,MAAM,OAAO,WAAY,SAAQ,MAAM,CAAC,WAAW,EAAe,CAAC,aAAa,EAAE;IACjF,KAAK,EAAE,MAAM,CAAC,OAAO;IACrB,IAAI,EAAE,eAAe;CACrB,CAAC;CAAG;AAEL,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC;AAEjD,MAAM,UAAU,QAAQ,CACvB,GAA4E;IAE5E,OAAO,CAAC,KAAK,EAAE,EAAE,CAChB,MAAM,CAAC,UAAU,CAAC;QACjB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7D,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC;KACnC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;QAClC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,MAAM,IAAI,KAAK,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1H,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CACzB,GAA4E;IAE5E,OAAO,IAAI,CAAC,EAAE,EAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -6,6 +6,7 @@ export interface NoticeAction {
6
6
  readonly atom: Atom.AtomResultFn<void, unknown, unknown>;
7
7
  readonly label: NoticeDescriptor["message"];
8
8
  }
9
+ export type NoticeClock = Pick<Atom.AtomRuntime<never, unknown>, "atom">;
9
10
  export interface NoticeEntry {
10
11
  readonly action?: {
11
12
  readonly label: NoticeDescriptor["message"];
@@ -17,5 +18,5 @@ export interface NoticeEntry {
17
18
  readonly release: () => void;
18
19
  readonly status: Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>;
19
20
  }
20
- export declare function noticeEntry(registry: AtomRegistry.AtomRegistry, id: number, descriptor: Pick<NoticeDescriptor, "message" | "expiresIn">, remove: (id: number) => void, action: NoticeAction | undefined): NoticeEntry;
21
+ export declare function noticeEntry(registry: AtomRegistry.AtomRegistry, clock: NoticeClock, id: number, descriptor: Pick<NoticeDescriptor, "message" | "expiresIn">, remove: (id: number) => void, action: NoticeAction | undefined): NoticeEntry;
21
22
  //# sourceMappingURL=noticeEntry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"noticeEntry.d.ts","sourceRoot":"","sources":["../../src/internal/noticeEntry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CAC5C;AACD,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IACxG,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;CACtE;AAED,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,YAAY,CAAC,YAAY,EACnC,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC,EAC3D,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EAC5B,MAAM,EAAE,YAAY,GAAG,SAAS,GAC9B,WAAW,CAuCb"}
1
+ {"version":3,"file":"noticeEntry.d.ts","sourceRoot":"","sources":["../../src/internal/noticeEntry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CAC5C;AACD,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AACzE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IACxG,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;CACtE;AAED,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,YAAY,CAAC,YAAY,EACnC,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC,EAC3D,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EAC5B,MAAM,EAAE,YAAY,GAAG,SAAS,GAC9B,WAAW,CA0Cb"}
@@ -1,8 +1,8 @@
1
- import { Duration } from "effect";
1
+ import { Effect } from "effect";
2
2
  import * as AsyncResult from "effect/reactivity/AsyncResult";
3
3
  import * as Atom from "effect/reactivity/Atom";
4
4
  const idle = Atom.make(AsyncResult.initial());
5
- export function noticeEntry(registry, id, descriptor, remove, action) {
5
+ export function noticeEntry(registry, clock, id, descriptor, remove, action) {
6
6
  let active = true;
7
7
  function dismiss() {
8
8
  if (active) {
@@ -15,13 +15,15 @@ export function noticeEntry(registry, id, descriptor, remove, action) {
15
15
  registry.set(action.atom, undefined);
16
16
  }
17
17
  }
18
+ const expiry = descriptor.expiresIn === undefined
19
+ ? undefined
20
+ : clock.atom(Effect.andThen(Effect.sleep(descriptor.expiresIn), Effect.sync(dismiss))).pipe(Atom.setIdleTTL(0));
18
21
  const resource = Atom.make((get) => {
19
22
  if (action) {
20
23
  get.mount(action.atom);
21
24
  }
22
- if (descriptor.expiresIn !== undefined) {
23
- const timer = setTimeout(dismiss, Duration.toMillis(descriptor.expiresIn));
24
- get.addFinalizer(() => clearTimeout(timer));
25
+ if (expiry) {
26
+ get.mount(expiry);
25
27
  }
26
28
  return true;
27
29
  }).pipe(Atom.setIdleTTL(0));
@@ -1 +1 @@
1
- {"version":3,"file":"noticeEntry.js","sourceRoot":"","sources":["../../src/internal/noticeEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAgB/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAoB,CAAC,CAAC;AAChE,MAAM,UAAU,WAAW,CAC1B,QAAmC,EACnC,EAAU,EACV,UAA2D,EAC3D,MAA4B,EAC5B,MAAgC;IAEhC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,OAAO;QACf,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,EAAE,CAAC,CAAC;YACX,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,SAAS,GAAG;QACX,IAAI,MAAM,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5D,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;IACF,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAClC,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QACD,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;YAC3E,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzC,SAAS,OAAO;QACf,MAAM,GAAG,KAAK,CAAC;QACf,IAAI,MAAM,EAAE,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO;QACN,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE;QACvE,OAAO;QACP,EAAE;QACF,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,OAAO;QACP,MAAM,EAAE,MAAM,EAAE,IAAI,IAAI,IAAI;KAC5B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"noticeEntry.js","sourceRoot":"","sources":["../../src/internal/noticeEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAiB/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAoB,CAAC,CAAC;AAChE,MAAM,UAAU,WAAW,CAC1B,QAAmC,EACnC,KAAkB,EAClB,EAAU,EACV,UAA2D,EAC3D,MAA4B,EAC5B,MAAgC;IAEhC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,OAAO;QACf,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,EAAE,CAAC,CAAC;YACX,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,SAAS,GAAG;QACX,IAAI,MAAM,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5D,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;IACF,CAAC;IACD,MAAM,MAAM,GACX,UAAU,CAAC,SAAS,KAAK,SAAS;QACjC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAClH,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAClC,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QACD,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzC,SAAS,OAAO;QACf,MAAM,GAAG,KAAK,CAAC;QACf,IAAI,MAAM,EAAE,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO;QACN,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE;QACvE,OAAO;QACP,EAAE;QACF,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,OAAO;QACP,MAAM,EAAE,MAAM,EAAE,IAAI,IAAI,IAAI;KAC5B,CAAC;AACH,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import * as Atom from "effect/reactivity/Atom";
2
2
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import type { NoticeDescriptor } from "#notices.ts";
4
- import { type NoticeAction, type NoticeEntry } from "./noticeEntry.ts";
5
- export declare function noticeStore(registry: AtomRegistry.AtomRegistry): {
4
+ import { type NoticeAction, type NoticeClock, type NoticeEntry } from "./noticeEntry.ts";
5
+ export declare function noticeStore(registry: AtomRegistry.AtomRegistry, clock: NoticeClock): {
6
6
  active: boolean;
7
7
  close: () => void;
8
8
  entries: Atom.Writable<readonly NoticeEntry[], readonly NoticeEntry[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"noticeStore.d.ts","sourceRoot":"","sources":["../../src/internal/noticeStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAEpF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY;;;;0BAmBvC,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC,WAAW,YAAY;EAU7F;AACD,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,SAAS,EACjD,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,QAO3C"}
1
+ {"version":3,"file":"noticeStore.d.ts","sourceRoot":"","sources":["../../src/internal/noticeStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAEtG,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,WAAW;;;;0BAmB3D,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC,WAAW,YAAY;EAU7F;AACD,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,SAAS,EACjD,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,QAO3C"}
@@ -1,6 +1,6 @@
1
1
  import * as Atom from "effect/reactivity/Atom";
2
2
  import { noticeEntry } from "./noticeEntry.js";
3
- export function noticeStore(registry) {
3
+ export function noticeStore(registry, clock) {
4
4
  const entries = Atom.make([]);
5
5
  let nextId = 0;
6
6
  function remove(id) {
@@ -20,7 +20,7 @@ export function noticeStore(registry) {
20
20
  if (!store.active) {
21
21
  return;
22
22
  }
23
- const entry = noticeEntry(registry, nextId, descriptor, remove, action);
23
+ const entry = noticeEntry(registry, clock, nextId, descriptor, remove, action);
24
24
  nextId += 1;
25
25
  registry.set(entries, [...registry.get(entries), entry]);
26
26
  },
@@ -1 +1 @@
1
- {"version":3,"file":"noticeStore.js","sourceRoot":"","sources":["../../src/internal/noticeStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAG/C,OAAO,EAAuC,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpF,MAAM,UAAU,WAAW,CAAC,QAAmC;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAyB,EAAE,CAAC,CAAC;IACtD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,SAAS,MAAM,CAAC,EAAU;QACzB,QAAQ,CAAC,GAAG,CACX,OAAO,EACP,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CACxD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG;QACb,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,GAAG,EAAE;YACX,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;YACrB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3C,KAAK,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO;QACP,OAAO,EAAE,CAAC,UAA2D,EAAE,MAAqB,EAAE,EAAE;YAC/F,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YACD,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YACxE,MAAM,IAAI,CAAC,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1D,CAAC;KACD,CAAC;IACF,OAAO,KAAK,CAAC;AACd,CAAC;AACD,MAAM,UAAU,aAAa,CAC5B,KAAiD,EACjD,OAA+B,EAC/B,UAA2C;IAE3C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC;QACpC,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AACpJ,CAAC"}
1
+ {"version":3,"file":"noticeStore.js","sourceRoot":"","sources":["../../src/internal/noticeStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAG/C,OAAO,EAAyD,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEtG,MAAM,UAAU,WAAW,CAAC,QAAmC,EAAE,KAAkB;IAClF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAyB,EAAE,CAAC,CAAC;IACtD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,SAAS,MAAM,CAAC,EAAU;QACzB,QAAQ,CAAC,GAAG,CACX,OAAO,EACP,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CACxD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG;QACb,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,GAAG,EAAE;YACX,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;YACrB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3C,KAAK,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO;QACP,OAAO,EAAE,CAAC,UAA2D,EAAE,MAAqB,EAAE,EAAE;YAC/F,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YACD,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YAC/E,MAAM,IAAI,CAAC,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1D,CAAC;KACD,CAAC;IACF,OAAO,KAAK,CAAC;AACd,CAAC;AACD,MAAM,UAAU,aAAa,CAC5B,KAAiD,EACjD,OAA+B,EAC/B,UAA2C;IAE3C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC;QACpC,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AACpJ,CAAC"}
package/dist/notices.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import type { Duration, Effect } from "effect";
1
+ import { type Duration, type Effect } from "effect";
2
2
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
3
3
  import { type ReactNode } from "react";
4
4
  import type { AtomServices } from "#fieldRejection.ts";
5
+ import type { NoticeClock } from "#internal/noticeEntry.ts";
5
6
  export interface NoticeDescriptor<R = never> {
6
7
  readonly action?: {
7
8
  readonly label: ReactNode;
@@ -25,8 +26,9 @@ export interface NoticeFeedback<TInput, A, E, R = never> {
25
26
  readonly onFailure?: (error: E, input: TInput) => NoticeDescriptor<R> | undefined;
26
27
  readonly onSuccess?: (value: A, input: TInput) => NoticeDescriptor<R> | undefined;
27
28
  }
28
- export declare function NoticeHost({ children, render }: {
29
+ export declare function NoticeHost({ children, render, runtime, }: {
29
30
  readonly children: ReactNode;
30
31
  readonly render: (notice: Notice) => ReactNode;
32
+ readonly runtime?: NoticeClock | undefined;
31
33
  }): ReactNode;
32
34
  //# sourceMappingURL=notices.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notices.d.ts","sourceRoot":"","sources":["../src/notices.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAA2B,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAC;AACtG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,KAAK;IAC1C,QAAQ,CAAC,MAAM,CAAC,EACb;QACA,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;KAC9D,GACD,SAAS,CAAC;IACb,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC5B;AACD,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,MAAM,CAAC,EACb;QACA,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;KACxB,GACD,SAAS,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC5B;AACD,MAAM,WAAW,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;CAClF;AAcD,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAA;CAAE,GAAG,SAAS,CAe5I"}
1
+ {"version":3,"file":"notices.d.ts","sourceRoot":"","sources":["../src/notices.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAS,MAAM,QAAQ,CAAC;AAC3D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAElE,OAAO,EAA2B,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAC;AACtG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,0BAA0B,CAAC;AAGzE,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,KAAK;IAC1C,QAAQ,CAAC,MAAM,CAAC,EACb;QACA,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;KAC9D,GACD,SAAS,CAAC;IACb,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC5B;AACD,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,MAAM,CAAC,EACb;QACA,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;KACxB,GACD,SAAS,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC5B;AACD,MAAM,WAAW,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;CAClF;AAeD,wBAAgB,UAAU,CAAC,EAC1B,QAAQ,EACR,MAAM,EACN,OAAsB,GACtB,EAAE;IACF,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C,GAAG,SAAS,CAeZ"}
package/dist/notices.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { RegistryContext, useAtomValue } from "@effect/atom-react";
2
+ import { Layer } from "effect";
3
+ import * as Atom from "effect/reactivity/Atom";
2
4
  import { createElement, Fragment, useContext, useLayoutEffect, useMemo } from "react";
3
5
  import { NoticeContext } from "#internal/NoticeContext.ts";
4
6
  import { noticeStore } from "#internal/noticeStore.ts";
@@ -15,9 +17,10 @@ function Entries({ store, render }) {
15
17
  const entries = useAtomValue(store.entries);
16
18
  return entries.map((entry) => createElement(Entry, { entry, key: entry.id, render }));
17
19
  }
18
- export function NoticeHost({ children, render }) {
20
+ const defaultClock = Atom.runtime(Layer.empty);
21
+ export function NoticeHost({ children, render, runtime = defaultClock, }) {
19
22
  const registry = useContext(RegistryContext);
20
- const store = useMemo(() => noticeStore(registry), [registry]);
23
+ const store = useMemo(() => noticeStore(registry, runtime), [registry, runtime]);
21
24
  useLayoutEffect(() => {
22
25
  store.active = true;
23
26
  return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"notices.js","sourceRoot":"","sources":["../src/notices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAkB,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEtG,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA6BvD,SAAS,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAmF;IAChH,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;QACb,MAAM,EAAE,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE;QACrG,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,OAAO,EAAE,KAAK,CAAC,OAAO;KACtB,CAAC,CAAC;AACJ,CAAC;AACD,SAAS,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAsG;IACrI,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAoF;IAChI,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/D,eAAe,CAAC,GAAG,EAAE;QACpB,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,OAAO,GAAG,EAAE;YACX,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;YACrB,cAAc,CAAC,GAAG,EAAE;gBACnB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;oBACnB,KAAK,CAAC,KAAK,EAAE,CAAC;gBACf,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,OAAO,aAAa,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpJ,CAAC"}
1
+ {"version":3,"file":"notices.js","sourceRoot":"","sources":["../src/notices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAA8B,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE3D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAkB,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEtG,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA6BvD,SAAS,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAmF;IAChH,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;QACb,MAAM,EAAE,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE;QACrG,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,OAAO,EAAE,KAAK,CAAC,OAAO;KACtB,CAAC,CAAC;AACJ,CAAC;AACD,SAAS,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAsG;IACrI,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC;AACD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC/C,MAAM,UAAU,UAAU,CAAC,EAC1B,QAAQ,EACR,MAAM,EACN,OAAO,GAAG,YAAY,GAKtB;IACA,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACjF,eAAe,CAAC,GAAG,EAAE;QACpB,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,OAAO,GAAG,EAAE;YACX,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;YACrB,cAAc,CAAC,GAAG,EAAE;gBACnB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;oBACnB,KAAK,CAAC,KAAK,EAAE,CAAC;gBACf,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,OAAO,aAAa,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/react",
3
- "version": "0.23.2",
3
+ "version": "0.25.0",
4
4
  "description": "React query, action, editor and session hooks for native Effect atoms",
5
5
  "keywords": [
6
6
  "effect"
@@ -45,26 +45,26 @@
45
45
  "README.md"
46
46
  ],
47
47
  "dependencies": {
48
- "@shivaedev/access": "0.2.0",
49
- "@shivaedev/contract": "0.24.0",
48
+ "@shivaedev/access": "0.3.0",
49
+ "@shivaedev/contract": "0.25.0",
50
50
  "@shivaedev/errors": "0.1.2",
51
- "@shivaedev/form": "0.8.0"
51
+ "@shivaedev/form": "0.9.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@effect/atom-react": "4.0.6",
55
55
  "@effect/platform-node": "4.0.6",
56
56
  "@effect/sql-pg": "4.0.6",
57
57
  "@effect/sql-sqlite-node": "4.0.6",
58
- "@shivaedev/changes": "0.5.1",
59
- "@shivaedev/identity": "0.8.0",
60
- "@shivaedev/jobs": "0.23.1",
61
- "@shivaedev/quality": "0.47.0",
62
- "@shivaedev/server": "0.9.0",
58
+ "@shivaedev/changes": "0.6.0",
59
+ "@shivaedev/identity": "0.9.1",
60
+ "@shivaedev/jobs": "0.23.2",
61
+ "@shivaedev/quality": "0.48.1",
62
+ "@shivaedev/server": "0.9.2",
63
63
  "@shivaedev/service": "0.6.0",
64
- "@shivaedev/sql": "0.26.1",
64
+ "@shivaedev/sql": "0.27.0",
65
65
  "@shivaedev/test": "0.12.0",
66
- "@shivaedev/test-sql": "0.14.0",
67
- "@shivaedev/test-story": "0.26.1",
66
+ "@shivaedev/test-sql": "0.15.0",
67
+ "@shivaedev/test-story": "0.26.3",
68
68
  "@tanstack/react-router": "1.170.46",
69
69
  "@types/node": "24.19.2",
70
70
  "@types/react": "19.3.0",
@@ -1,4 +1,5 @@
1
- import { Data, Effect } from "effect";
1
+ import { Data, Effect, Schema } from "effect";
2
+ import * as HttpStatus from "effect/http/HttpStatus";
2
3
  import * as Atom from "effect/reactivity/Atom";
3
4
 
4
5
  export class AuthActionUnavailable extends Data.TaggedError("AuthActionUnavailable")<{
@@ -10,13 +11,91 @@ export interface AuthActionResult<A, E> {
10
11
  readonly error: E | null;
11
12
  }
12
13
 
13
- export function authAction<TInput, A, E extends object = never>(
14
+ export const AuthFailureCode = Schema.Literals([
15
+ "USER_NOT_FOUND",
16
+ "FAILED_TO_CREATE_USER",
17
+ "FAILED_TO_CREATE_SESSION",
18
+ "FAILED_TO_UPDATE_USER",
19
+ "FAILED_TO_GET_SESSION",
20
+ "INVALID_PASSWORD",
21
+ "INVALID_EMAIL",
22
+ "INVALID_EMAIL_OR_PASSWORD",
23
+ "INVALID_USER",
24
+ "SOCIAL_ACCOUNT_ALREADY_LINKED",
25
+ "PROVIDER_NOT_FOUND",
26
+ "INVALID_TOKEN",
27
+ "TOKEN_EXPIRED",
28
+ "ID_TOKEN_NOT_SUPPORTED",
29
+ "FAILED_TO_GET_USER_INFO",
30
+ "USER_EMAIL_NOT_FOUND",
31
+ "EMAIL_NOT_VERIFIED",
32
+ "PASSWORD_TOO_SHORT",
33
+ "PASSWORD_TOO_LONG",
34
+ "USER_ALREADY_EXISTS",
35
+ "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL",
36
+ "EMAIL_CAN_NOT_BE_UPDATED",
37
+ "CHANGE_EMAIL_DISABLED",
38
+ "CREDENTIAL_ACCOUNT_NOT_FOUND",
39
+ "SESSION_EXPIRED",
40
+ "FAILED_TO_UNLINK_LAST_ACCOUNT",
41
+ "ACCOUNT_NOT_FOUND",
42
+ "USER_ALREADY_HAS_PASSWORD",
43
+ "CROSS_SITE_NAVIGATION_LOGIN_BLOCKED",
44
+ "VERIFICATION_EMAIL_NOT_ENABLED",
45
+ "EMAIL_ALREADY_VERIFIED",
46
+ "EMAIL_MISMATCH",
47
+ "SESSION_NOT_FRESH",
48
+ "LINKED_ACCOUNT_ALREADY_EXISTS",
49
+ "INVALID_ORIGIN",
50
+ "INVALID_CALLBACK_URL",
51
+ "INVALID_REDIRECT_URL",
52
+ "INVALID_ERROR_CALLBACK_URL",
53
+ "INVALID_NEW_USER_CALLBACK_URL",
54
+ "MISSING_OR_NULL_ORIGIN",
55
+ "CALLBACK_URL_REQUIRED",
56
+ "FAILED_TO_CREATE_VERIFICATION",
57
+ "FIELD_NOT_ALLOWED",
58
+ "ASYNC_VALIDATION_NOT_SUPPORTED",
59
+ "VALIDATION_ERROR",
60
+ "MISSING_FIELD",
61
+ "METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED",
62
+ "BODY_MUST_BE_AN_OBJECT",
63
+ "PASSWORD_ALREADY_SET",
64
+ "RATE_LIMITED",
65
+ "UNKNOWN",
66
+ ]);
67
+ export type AuthFailureCode = typeof AuthFailureCode.Type;
68
+
69
+ export class AuthFailure extends Schema.TaggedError<AuthFailure>()("AuthFailure", {
70
+ cause: Schema.Unknown,
71
+ code: AuthFailureCode,
72
+ }) {}
73
+
74
+ const supportedCode = Schema.is(AuthFailureCode);
75
+
76
+ export function authCall<TInput, A, E extends object = never>(
14
77
  run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>,
15
- ): Atom.AtomResultFn<TInput, A, E | AuthActionUnavailable> {
16
- return Atom.fn<TInput>()((input) =>
78
+ ): (input: TInput) => Effect.Effect<A, AuthFailure> {
79
+ return (input) =>
17
80
  Effect.tryPromise({
18
- catch: (cause) => new AuthActionUnavailable({ cause }),
81
+ catch: (cause) => new AuthFailure({ cause, code: "UNKNOWN" }),
19
82
  try: (signal) => run(input, signal),
20
- }).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error)))),
21
- );
83
+ }).pipe(
84
+ Effect.flatMap(({ data, error }) => {
85
+ if (error === null) {
86
+ return Effect.succeed(data);
87
+ }
88
+ if ("code" in error && supportedCode(error.code)) {
89
+ return Effect.fail(new AuthFailure({ cause: error, code: error.code }));
90
+ }
91
+ const code = "status" in error && error.status === HttpStatus.fromLiteral("TooManyRequests") ? "RATE_LIMITED" : "UNKNOWN";
92
+ return Effect.fail(new AuthFailure({ cause: error, code }));
93
+ }),
94
+ );
95
+ }
96
+
97
+ export function authAction<TInput, A, E extends object = never>(
98
+ run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>,
99
+ ): Atom.AtomResultFn<TInput, A, AuthFailure> {
100
+ return Atom.fn<TInput>()(authCall(run));
22
101
  }
@@ -1,4 +1,4 @@
1
- import { Duration } from "effect";
1
+ import { Effect } from "effect";
2
2
  import * as AsyncResult from "effect/reactivity/AsyncResult";
3
3
  import * as Atom from "effect/reactivity/Atom";
4
4
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
@@ -8,6 +8,7 @@ export interface NoticeAction {
8
8
  readonly atom: Atom.AtomResultFn<void, unknown, unknown>;
9
9
  readonly label: NoticeDescriptor["message"];
10
10
  }
11
+ export type NoticeClock = Pick<Atom.AtomRuntime<never, unknown>, "atom">;
11
12
  export interface NoticeEntry {
12
13
  readonly action?: { readonly label: NoticeDescriptor["message"]; readonly run: () => void } | undefined;
13
14
  readonly dismiss: () => void;
@@ -19,6 +20,7 @@ export interface NoticeEntry {
19
20
  const idle = Atom.make(AsyncResult.initial<unknown, unknown>());
20
21
  export function noticeEntry(
21
22
  registry: AtomRegistry.AtomRegistry,
23
+ clock: NoticeClock,
22
24
  id: number,
23
25
  descriptor: Pick<NoticeDescriptor, "message" | "expiresIn">,
24
26
  remove: (id: number) => void,
@@ -36,13 +38,16 @@ export function noticeEntry(
36
38
  registry.set(action.atom, undefined);
37
39
  }
38
40
  }
41
+ const expiry =
42
+ descriptor.expiresIn === undefined
43
+ ? undefined
44
+ : clock.atom(Effect.andThen(Effect.sleep(descriptor.expiresIn), Effect.sync(dismiss))).pipe(Atom.setIdleTTL(0));
39
45
  const resource = Atom.make((get) => {
40
46
  if (action) {
41
47
  get.mount(action.atom);
42
48
  }
43
- if (descriptor.expiresIn !== undefined) {
44
- const timer = setTimeout(dismiss, Duration.toMillis(descriptor.expiresIn));
45
- get.addFinalizer(() => clearTimeout(timer));
49
+ if (expiry) {
50
+ get.mount(expiry);
46
51
  }
47
52
  return true;
48
53
  }).pipe(Atom.setIdleTTL(0));
@@ -1,9 +1,9 @@
1
1
  import * as Atom from "effect/reactivity/Atom";
2
2
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import type { NoticeDescriptor } from "#notices.ts";
4
- import { type NoticeAction, type NoticeEntry, noticeEntry } from "./noticeEntry.ts";
4
+ import { type NoticeAction, type NoticeClock, type NoticeEntry, noticeEntry } from "./noticeEntry.ts";
5
5
 
6
- export function noticeStore(registry: AtomRegistry.AtomRegistry) {
6
+ export function noticeStore(registry: AtomRegistry.AtomRegistry, clock: NoticeClock) {
7
7
  const entries = Atom.make<readonly NoticeEntry[]>([]);
8
8
  let nextId = 0;
9
9
  function remove(id: number) {
@@ -26,7 +26,7 @@ export function noticeStore(registry: AtomRegistry.AtomRegistry) {
26
26
  if (!store.active) {
27
27
  return;
28
28
  }
29
- const entry = noticeEntry(registry, nextId, descriptor, remove, action);
29
+ const entry = noticeEntry(registry, clock, nextId, descriptor, remove, action);
30
30
  nextId += 1;
31
31
  registry.set(entries, [...registry.get(entries), entry]);
32
32
  },
package/src/notices.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { RegistryContext, useAtomValue } from "@effect/atom-react";
2
- import type { Duration, Effect } from "effect";
2
+ import { type Duration, type Effect, Layer } from "effect";
3
3
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
4
+ import * as Atom from "effect/reactivity/Atom";
4
5
  import { createElement, Fragment, type ReactNode, useContext, useLayoutEffect, useMemo } from "react";
5
6
  import type { AtomServices } from "#fieldRejection.ts";
6
7
  import { NoticeContext } from "#internal/NoticeContext.ts";
7
- import type { NoticeEntry } from "#internal/noticeEntry.ts";
8
+ import type { NoticeClock, NoticeEntry } from "#internal/noticeEntry.ts";
8
9
  import { noticeStore } from "#internal/noticeStore.ts";
9
10
 
10
11
  export interface NoticeDescriptor<R = never> {
@@ -47,9 +48,18 @@ function Entries({ store, render }: { readonly store: ReturnType<typeof noticeSt
47
48
  const entries = useAtomValue(store.entries);
48
49
  return entries.map((entry) => createElement(Entry, { entry, key: entry.id, render }));
49
50
  }
50
- export function NoticeHost({ children, render }: { readonly children: ReactNode; readonly render: (notice: Notice) => ReactNode }): ReactNode {
51
+ const defaultClock = Atom.runtime(Layer.empty);
52
+ export function NoticeHost({
53
+ children,
54
+ render,
55
+ runtime = defaultClock,
56
+ }: {
57
+ readonly children: ReactNode;
58
+ readonly render: (notice: Notice) => ReactNode;
59
+ readonly runtime?: NoticeClock | undefined;
60
+ }): ReactNode {
51
61
  const registry = useContext(RegistryContext);
52
- const store = useMemo(() => noticeStore(registry), [registry]);
62
+ const store = useMemo(() => noticeStore(registry, runtime), [registry, runtime]);
53
63
  useLayoutEffect(() => {
54
64
  store.active = true;
55
65
  return () => {