ras-stack 0.14.0 → 0.15.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/README.md +13 -0
- package/dist/auth/client.d.ts +8 -0
- package/dist/auth/client.js +17 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/react.d.ts +18 -0
- package/dist/auth/react.js +34 -0
- package/dist/auth/react.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -100,6 +100,19 @@ Applications still own their auth clients, authorization, file-route declaration
|
|
|
100
100
|
|
|
101
101
|
Only enable `trustForwardedHeaders` behind a proxy that replaces incoming forwarded headers. Otherwise a client could choose the origin used by the check.
|
|
102
102
|
|
|
103
|
+
Browser auth flows can share failure classification and pending/error state without sharing forms or navigation:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { classifySignInFailure } from 'ras-stack/auth/client'
|
|
107
|
+
import { useAuthAction } from 'ras-stack/auth/react'
|
|
108
|
+
|
|
109
|
+
const signIn = useAuthAction({ failureMessage: (failure) => messageFor(classifySignInFailure(failure)) })
|
|
110
|
+
const result = await signIn.run(() => authClient.signIn.email({ email, password }))
|
|
111
|
+
if (!result.error) await navigateAfterSignIn()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Applications retain field models, validation, password-reset disclosure policy, two-factor transitions, telemetry, copy, and success navigation.
|
|
115
|
+
|
|
103
116
|
## Database lifecycle
|
|
104
117
|
|
|
105
118
|
The SQLite entrypoint owns the native client lifecycle, standard safety PRAGMAs, and optional Drizzle migrations while returning the upstream typed database:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type AuthFailure = {
|
|
2
|
+
status?: number;
|
|
3
|
+
code?: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
} | null | undefined;
|
|
6
|
+
export type SignInFailureReason = 'invalid_credentials' | 'rate_limited' | 'error';
|
|
7
|
+
export declare function classifySignInFailure(failure: unknown): SignInFailureReason;
|
|
8
|
+
export declare function authFailureMessage(failure: unknown, fallback: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function classifySignInFailure(failure) {
|
|
2
|
+
if (!failure || typeof failure !== 'object')
|
|
3
|
+
return 'error';
|
|
4
|
+
const status = 'status' in failure ? failure.status : undefined;
|
|
5
|
+
const code = 'code' in failure ? failure.code : undefined;
|
|
6
|
+
if (status === 429)
|
|
7
|
+
return 'rate_limited';
|
|
8
|
+
if (status === 401 || code === 'INVALID_EMAIL_OR_PASSWORD')
|
|
9
|
+
return 'invalid_credentials';
|
|
10
|
+
return 'error';
|
|
11
|
+
}
|
|
12
|
+
export function authFailureMessage(failure, fallback) {
|
|
13
|
+
if (!failure || typeof failure !== 'object' || !('message' in failure))
|
|
14
|
+
return fallback;
|
|
15
|
+
return typeof failure.message === 'string' && failure.message.trim() ? failure.message : fallback;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAA;IAC3D,MAAM,MAAM,GAAG,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/D,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;IACzD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,cAAc,CAAA;IACzC,IAAI,MAAM,KAAK,GAAG,IAAI,IAAI,KAAK,2BAA2B;QAAE,OAAO,qBAAqB,CAAA;IACxF,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,QAAgB;IACnE,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;QAAE,OAAO,QAAQ,CAAA;IACvF,OAAO,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;AACnG,CAAC","sourcesContent":["export type AuthFailure = { status?: number; code?: string; message?: string } | null | undefined\nexport type SignInFailureReason = 'invalid_credentials' | 'rate_limited' | 'error'\n\nexport function classifySignInFailure(failure: unknown): SignInFailureReason {\n if (!failure || typeof failure !== 'object') return 'error'\n const status = 'status' in failure ? failure.status : undefined\n const code = 'code' in failure ? failure.code : undefined\n if (status === 429) return 'rate_limited'\n if (status === 401 || code === 'INVALID_EMAIL_OR_PASSWORD') return 'invalid_credentials'\n return 'error'\n}\n\nexport function authFailureMessage(failure: unknown, fallback: string) {\n if (!failure || typeof failure !== 'object' || !('message' in failure)) return fallback\n return typeof failure.message === 'string' && failure.message.trim() ? failure.message : fallback\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AuthActionResult<T, TFailure = unknown> = {
|
|
2
|
+
data?: T;
|
|
3
|
+
error?: TFailure | null;
|
|
4
|
+
};
|
|
5
|
+
export type AuthActionState = {
|
|
6
|
+
busy: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function useAuthAction(options?: {
|
|
10
|
+
failureMessage?: (failure: unknown) => string;
|
|
11
|
+
}): {
|
|
12
|
+
busy: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
clearError: () => void;
|
|
15
|
+
run: <T, TFailure>(work: () => Promise<AuthActionResult<T, TFailure>>) => Promise<AuthActionResult<T, TFailure> | {
|
|
16
|
+
error: unknown;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { authFailureMessage } from './client.js';
|
|
3
|
+
const defaultFailureMessage = (failure) => authFailureMessage(failure, 'That did not work. Try again.');
|
|
4
|
+
export function useAuthAction(options = {}) {
|
|
5
|
+
const failureMessage = options.failureMessage ?? defaultFailureMessage;
|
|
6
|
+
const [state, setState] = useState({ busy: false });
|
|
7
|
+
const active = useRef(true);
|
|
8
|
+
const invocation = useRef(0);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
active.current = true;
|
|
11
|
+
return () => {
|
|
12
|
+
active.current = false;
|
|
13
|
+
};
|
|
14
|
+
}, []);
|
|
15
|
+
const clearError = useCallback(() => setState((current) => ({ busy: current.busy })), []);
|
|
16
|
+
const run = useCallback(async (work) => {
|
|
17
|
+
const current = ++invocation.current;
|
|
18
|
+
setState({ busy: true });
|
|
19
|
+
try {
|
|
20
|
+
const result = await work();
|
|
21
|
+
if (active.current && current === invocation.current) {
|
|
22
|
+
setState(result.error ? { busy: false, error: failureMessage(result.error) } : { busy: false });
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (active.current && current === invocation.current)
|
|
28
|
+
setState({ busy: false, error: failureMessage(error) });
|
|
29
|
+
return { error };
|
|
30
|
+
}
|
|
31
|
+
}, [failureMessage]);
|
|
32
|
+
return { ...state, clearError, run };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/auth/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAKhD,MAAM,qBAAqB,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,+BAA+B,CAAC,CAAA;AAEhH,MAAM,UAAU,aAAa,CAAC,OAAO,GAAsD,EAAE;IAC3F,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,qBAAqB,CAAA;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC5B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;QACrB,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,OAAO,GAAG,KAAK,CAAA;QACxB,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACzF,MAAM,GAAG,GAAG,WAAW,CACrB,KAAK,EAAe,IAAkD,EAAE,EAAE;QACxE,MAAM,OAAO,GAAG,EAAE,UAAU,CAAC,OAAO,CAAA;QACpC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAA;YAC3B,IAAI,MAAM,CAAC,OAAO,IAAI,OAAO,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;gBACrD,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YACjG,CAAC;YACD,OAAO,MAAM,CAAA;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,MAAM,CAAC,OAAO,IAAI,OAAO,KAAK,UAAU,CAAC,OAAO;gBAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAC7G,OAAO,EAAE,KAAK,EAAoC,CAAA;QACpD,CAAC;IACH,CAAC,EACD,CAAC,cAAc,CAAC,CACjB,CAAA;IACD,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,CAAA;AACtC,CAAC","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { authFailureMessage } from './client.js'\n\nexport type AuthActionResult<T, TFailure = unknown> = { data?: T; error?: TFailure | null }\nexport type AuthActionState = { busy: boolean; error?: string }\n\nconst defaultFailureMessage = (failure: unknown) => authFailureMessage(failure, 'That did not work. Try again.')\n\nexport function useAuthAction(options: { failureMessage?: (failure: unknown) => string } = {}) {\n const failureMessage = options.failureMessage ?? defaultFailureMessage\n const [state, setState] = useState<AuthActionState>({ busy: false })\n const active = useRef(true)\n const invocation = useRef(0)\n useEffect(() => {\n active.current = true\n return () => {\n active.current = false\n }\n }, [])\n const clearError = useCallback(() => setState((current) => ({ busy: current.busy })), [])\n const run = useCallback(\n async <T, TFailure>(work: () => Promise<AuthActionResult<T, TFailure>>) => {\n const current = ++invocation.current\n setState({ busy: true })\n try {\n const result = await work()\n if (active.current && current === invocation.current) {\n setState(result.error ? { busy: false, error: failureMessage(result.error) } : { busy: false })\n }\n return result\n } catch (error) {\n if (active.current && current === invocation.current) setState({ busy: false, error: failureMessage(error) })\n return { error } satisfies AuthActionResult<never>\n }\n },\n [failureMessage],\n )\n return { ...state, clearError, run }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ras-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Composable full-stack primitives shared across Richard Solomou's applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"types": "./dist/auth/index.d.ts",
|
|
31
31
|
"default": "./dist/auth/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./auth/client": {
|
|
34
|
+
"types": "./dist/auth/client.d.ts",
|
|
35
|
+
"default": "./dist/auth/client.js"
|
|
36
|
+
},
|
|
37
|
+
"./auth/react": {
|
|
38
|
+
"types": "./dist/auth/react.d.ts",
|
|
39
|
+
"default": "./dist/auth/react.js"
|
|
40
|
+
},
|
|
33
41
|
"./email": {
|
|
34
42
|
"types": "./dist/email/index.d.ts",
|
|
35
43
|
"default": "./dist/email/index.js"
|