limen-auth 0.0.0 → 0.0.2-beta.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 +18 -41
- package/dist/broadcast-channel.mjs +51 -0
- package/dist/build-tree.mjs +73 -0
- package/dist/client.d.mts +7 -0
- package/dist/client.mjs +71 -0
- package/dist/{constants-CsR2pQ_9.mjs → constants.mjs} +1 -3
- package/dist/context.d.mts +32 -0
- package/dist/define-plugin.d.mts +40 -0
- package/dist/{define-plugin-C7WOGU4b.mjs → define-plugin.mjs} +1 -3
- package/dist/envelope.mjs +39 -0
- package/dist/errors.d.mts +18 -0
- package/dist/{errors-4YJYt6f0.mjs → errors.mjs} +2 -5
- package/dist/fetcher.d.mts +17 -0
- package/dist/fetcher.mjs +112 -0
- package/dist/{helpers-CvmKjWi2.d.mts → helpers.d.mts} +1 -2
- package/dist/{helpers-Cs3VtXdv.mjs → helpers.mjs} +1 -3
- package/dist/hooks.d.mts +1 -0
- package/dist/hooks.mjs +34 -0
- package/dist/index.d.mts +14 -30
- package/dist/index.mjs +9 -11
- package/dist/infer.d.mts +42 -0
- package/dist/json-deep-equal.mjs +27 -0
- package/dist/normalize.d.mts +12 -0
- package/dist/normalize.mjs +19 -0
- package/dist/package.mjs +4 -0
- package/dist/path.mjs +41 -0
- package/dist/pipeline.mjs +65 -0
- package/dist/plugin.d.mts +71 -0
- package/dist/plugins/bearer/index.d.mts +13 -1
- package/dist/plugins/bearer/index.mjs +43 -1
- package/dist/plugins/bearer/storage.d.mts +8 -0
- package/dist/plugins/bearer/storage.mjs +41 -0
- package/dist/{index-B8SpHkSd.d.mts → plugins/bearer/types.d.mts} +1 -18
- package/dist/plugins/credential/index.d.mts +54 -1
- package/dist/plugins/credential/index.mjs +2 -4
- package/dist/plugins/credential/types.d.mts +35 -0
- package/dist/plugins/index.d.mts +13 -6
- package/dist/plugins/index.mjs +3 -2
- package/dist/plugins/magic-link/index.d.mts +17 -1
- package/dist/plugins/magic-link/index.mjs +2 -4
- package/dist/plugins/magic-link/types.d.mts +13 -0
- package/dist/plugins/oauth/index.d.mts +47 -1
- package/dist/plugins/oauth/index.mjs +3 -5
- package/dist/plugins/oauth/types.d.mts +35 -0
- package/dist/plugins/session-jwt/index.d.mts +24 -1
- package/dist/plugins/session-jwt/index.mjs +95 -1
- package/dist/plugins/session-jwt/jwt.mjs +34 -0
- package/dist/plugins/session-jwt/types.d.mts +11 -0
- package/dist/plugins/two-factor/index.d.mts +41 -1
- package/dist/plugins/two-factor/index.mjs +2 -4
- package/dist/plugins/two-factor/types.d.mts +26 -0
- package/dist/react/index.d.mts +6 -7
- package/dist/react/index.mjs +2 -20
- package/dist/react/react-store.d.mts +6 -0
- package/dist/react/react-store.mjs +18 -0
- package/dist/route.d.mts +80 -0
- package/dist/{route-DGxvFqWl.mjs → route.mjs} +1 -3
- package/dist/routes.d.mts +53 -0
- package/dist/routes.mjs +58 -0
- package/dist/serialize.d.mts +11 -0
- package/dist/serialize.mjs +23 -0
- package/dist/session-store.d.mts +29 -0
- package/dist/session-store.mjs +79 -0
- package/dist/session-sync.mjs +47 -0
- package/dist/solid/index.d.mts +6 -7
- package/dist/solid/index.mjs +2 -17
- package/dist/solid/solid-store.d.mts +7 -0
- package/dist/solid/solid-store.mjs +15 -0
- package/dist/svelte/index.d.mts +5 -3
- package/dist/svelte/index.mjs +1 -3
- package/dist/type-utils.d.mts +15 -0
- package/dist/types.d.mts +97 -0
- package/dist/version.d.mts +4 -0
- package/dist/version.mjs +5 -0
- package/dist/vue/index.d.mts +6 -19
- package/dist/vue/index.mjs +2 -25
- package/dist/vue/vue-store.d.mts +18 -0
- package/dist/vue/vue-store.mjs +23 -0
- package/package.json +4 -3
- package/dist/bearer-Cqmrmjjf.mjs +0 -84
- package/dist/bearer-Cqmrmjjf.mjs.map +0 -1
- package/dist/client-Er91De-z.mjs +0 -705
- package/dist/client-Er91De-z.mjs.map +0 -1
- package/dist/constants-CsR2pQ_9.mjs.map +0 -1
- package/dist/define-plugin-C7WOGU4b.mjs.map +0 -1
- package/dist/define-plugin-Dv0xXIaH.d.mts +0 -450
- package/dist/define-plugin-Dv0xXIaH.d.mts.map +0 -1
- package/dist/errors-4YJYt6f0.mjs.map +0 -1
- package/dist/helpers-Cs3VtXdv.mjs.map +0 -1
- package/dist/helpers-CvmKjWi2.d.mts.map +0 -1
- package/dist/index-B8SpHkSd.d.mts.map +0 -1
- package/dist/index-C6atwjEq.d.mts +0 -65
- package/dist/index-C6atwjEq.d.mts.map +0 -1
- package/dist/index-C9EuA9UZ.d.mts +0 -32
- package/dist/index-C9EuA9UZ.d.mts.map +0 -1
- package/dist/index-Cgr2wHmM.d.mts +0 -87
- package/dist/index-Cgr2wHmM.d.mts.map +0 -1
- package/dist/index-DV6YcNSu.d.mts +0 -81
- package/dist/index-DV6YcNSu.d.mts.map +0 -1
- package/dist/index-dEksIImj.d.mts +0 -28
- package/dist/index-dEksIImj.d.mts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/plugins/credential/index.mjs.map +0 -1
- package/dist/plugins/magic-link/index.mjs.map +0 -1
- package/dist/plugins/oauth/index.mjs.map +0 -1
- package/dist/plugins/two-factor/index.mjs.map +0 -1
- package/dist/react/index.d.mts.map +0 -1
- package/dist/react/index.mjs.map +0 -1
- package/dist/route-DGxvFqWl.mjs.map +0 -1
- package/dist/session-jwt-DgLdMQxP.mjs +0 -129
- package/dist/session-jwt-DgLdMQxP.mjs.map +0 -1
- package/dist/solid/index.d.mts.map +0 -1
- package/dist/solid/index.mjs.map +0 -1
- package/dist/svelte/index.d.mts.map +0 -1
- package/dist/svelte/index.mjs.map +0 -1
- package/dist/vue/index.d.mts.map +0 -1
- package/dist/vue/index.mjs.map +0 -1
package/dist/routes.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { defineClientPlugin, defineRoutes } from "./define-plugin.mjs";
|
|
2
|
+
import { route } from "./route.mjs";
|
|
3
|
+
//#region src/routes.ts
|
|
4
|
+
/**
|
|
5
|
+
* Core routes available on every client.
|
|
6
|
+
*/
|
|
7
|
+
function coreClientPlugin() {
|
|
8
|
+
return defineClientPlugin({
|
|
9
|
+
id: "core",
|
|
10
|
+
basePath: "/",
|
|
11
|
+
routes: defineRoutes(route()({
|
|
12
|
+
method: "GET",
|
|
13
|
+
path: "/sessions"
|
|
14
|
+
}), route()({
|
|
15
|
+
method: "POST",
|
|
16
|
+
path: "/signout",
|
|
17
|
+
clearSession: true
|
|
18
|
+
}), route()({
|
|
19
|
+
method: "POST",
|
|
20
|
+
path: "/revoke-sessions",
|
|
21
|
+
clearSession: true
|
|
22
|
+
}), route()({
|
|
23
|
+
method: "POST",
|
|
24
|
+
path: "/verify-email",
|
|
25
|
+
refetchSession: true
|
|
26
|
+
}), route()({
|
|
27
|
+
method: "POST",
|
|
28
|
+
path: "/email-verifications",
|
|
29
|
+
as: "requestEmailVerification"
|
|
30
|
+
})),
|
|
31
|
+
actions: (ctx) => ({
|
|
32
|
+
/**
|
|
33
|
+
* Revalidate session state with `GET /me`, update `$session`, and return the
|
|
34
|
+
* resolved value (`null` when signed out).
|
|
35
|
+
*
|
|
36
|
+
* Prefer subscribing to `$session` for reactive UI state (`data`, `isPending`,
|
|
37
|
+
* `error`). Use `getSession()` when you need an awaited server re-check, such
|
|
38
|
+
* as route guards or SSR revalidation after `initialSession`.
|
|
39
|
+
*/
|
|
40
|
+
getSession: async () => {
|
|
41
|
+
await ctx.refetchSession();
|
|
42
|
+
const state = ctx.store.$session.get();
|
|
43
|
+
if (state.error) throw state.error;
|
|
44
|
+
return state.data;
|
|
45
|
+
} })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch and parse the current session for the reactive store.
|
|
50
|
+
*/
|
|
51
|
+
function createSessionHydrator(ctx) {
|
|
52
|
+
return async () => {
|
|
53
|
+
const raw = await ctx.fetch("/me", { method: "GET" });
|
|
54
|
+
return ctx.parseSession(raw);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { coreClientPlugin, createSessionHydrator };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/serialize.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default request serializer: shallow camelCase → snake_case, drops `undefined`,
|
|
4
|
+
* leaves non-objects unchanged.
|
|
5
|
+
*
|
|
6
|
+
* `additionalFields` entries are merged into the top-level body verbatim.
|
|
7
|
+
* Known route fields win on key collisions.
|
|
8
|
+
*/
|
|
9
|
+
declare function defaultSerialize(input: unknown): unknown;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { defaultSerialize };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { camelToSnake } from "./helpers.mjs";
|
|
2
|
+
//#region src/serialize.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default request serializer: shallow camelCase → snake_case, drops `undefined`,
|
|
5
|
+
* leaves non-objects unchanged.
|
|
6
|
+
*
|
|
7
|
+
* `additionalFields` entries are merged into the top-level body verbatim.
|
|
8
|
+
* Known route fields win on key collisions.
|
|
9
|
+
*/
|
|
10
|
+
function defaultSerialize(input) {
|
|
11
|
+
if (input === null || input === void 0) return input;
|
|
12
|
+
if (typeof input !== "object" || Array.isArray(input)) return input;
|
|
13
|
+
const { additionalFields, ...rest } = input;
|
|
14
|
+
const out = {};
|
|
15
|
+
if (additionalFields && typeof additionalFields === "object" && !Array.isArray(additionalFields)) Object.assign(out, additionalFields);
|
|
16
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
17
|
+
if (value === void 0) continue;
|
|
18
|
+
out[camelToSnake(key)] = value;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { defaultSerialize };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LimenError } from "./errors.mjs";
|
|
2
|
+
import { Session } from "./types.mjs";
|
|
3
|
+
import { ReadableAtom } from "nanostores";
|
|
4
|
+
|
|
5
|
+
//#region src/session-store.d.ts
|
|
6
|
+
type SessionState<TFields = unknown> = {
|
|
7
|
+
/** The current session, or `null` when signed out. */data: Session<TFields> | null; /** True while a `/me` fetch is in flight. */
|
|
8
|
+
isPending: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The last non-401 failure (network error, 5xx, etc.). A 401 is not an error
|
|
11
|
+
* — it resolves to `data: null`. `error` is cleared on the next successful or
|
|
12
|
+
* 401 outcome.
|
|
13
|
+
*/
|
|
14
|
+
error: LimenError | null;
|
|
15
|
+
};
|
|
16
|
+
type RefetchOptions = {
|
|
17
|
+
/** Skip the fetch when the last hydration ran within this many milliseconds. */maxAgeMs?: number; /** Skip the fetch when the session is signed out. */
|
|
18
|
+
skipSignedOut?: boolean;
|
|
19
|
+
};
|
|
20
|
+
type SessionStore<TFields = unknown> = {
|
|
21
|
+
readonly $session: ReadableAtom<SessionState<TFields>>;
|
|
22
|
+
setData(session: Session<TFields> | null): void;
|
|
23
|
+
/**
|
|
24
|
+
* Re-validate the session from the server.
|
|
25
|
+
*/
|
|
26
|
+
refetch(options?: RefetchOptions): Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { SessionState, SessionStore };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LimenError } from "./errors.mjs";
|
|
2
|
+
import { createSessionSync } from "./session-sync.mjs";
|
|
3
|
+
import { atom, onMount } from "nanostores";
|
|
4
|
+
//#region src/session-store.ts
|
|
5
|
+
function createSessionStore(options) {
|
|
6
|
+
const $session = atom({
|
|
7
|
+
data: options.initialSession ?? null,
|
|
8
|
+
isPending: false,
|
|
9
|
+
error: null
|
|
10
|
+
});
|
|
11
|
+
let inFlightHydration = null;
|
|
12
|
+
let writeVersion = 0;
|
|
13
|
+
let lastRefreshedAt = 0;
|
|
14
|
+
const isStale = (requestVersion) => requestVersion !== writeVersion;
|
|
15
|
+
const fetchSessionFromServer = async () => {
|
|
16
|
+
const requestVersion = ++writeVersion;
|
|
17
|
+
$session.set({
|
|
18
|
+
data: $session.get().data,
|
|
19
|
+
isPending: true,
|
|
20
|
+
error: null
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const session = await options.hydrator();
|
|
24
|
+
if (isStale(requestVersion)) return;
|
|
25
|
+
$session.set({
|
|
26
|
+
data: session,
|
|
27
|
+
isPending: false,
|
|
28
|
+
error: null
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (isStale(requestVersion)) return;
|
|
32
|
+
if (err instanceof LimenError && err.isUnauthorized) {
|
|
33
|
+
$session.set({
|
|
34
|
+
data: null,
|
|
35
|
+
isPending: false,
|
|
36
|
+
error: null
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const error = err instanceof LimenError ? err : new LimenError(err instanceof Error ? err.message : "Failed to load session", 0, "unknown");
|
|
41
|
+
$session.set({
|
|
42
|
+
data: $session.get().data,
|
|
43
|
+
isPending: false,
|
|
44
|
+
error
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const refetch = (options) => {
|
|
49
|
+
const { skipSignedOut, maxAgeMs } = options ?? {};
|
|
50
|
+
if (skipSignedOut && $session.get().data === null) return Promise.resolve();
|
|
51
|
+
if (maxAgeMs !== void 0 && Date.now() - lastRefreshedAt < maxAgeMs) return Promise.resolve();
|
|
52
|
+
if (!inFlightHydration) inFlightHydration = fetchSessionFromServer().finally(() => {
|
|
53
|
+
inFlightHydration = null;
|
|
54
|
+
lastRefreshedAt = Date.now();
|
|
55
|
+
});
|
|
56
|
+
return inFlightHydration;
|
|
57
|
+
};
|
|
58
|
+
const setData = (session) => {
|
|
59
|
+
writeVersion++;
|
|
60
|
+
$session.set({
|
|
61
|
+
data: session,
|
|
62
|
+
isPending: false,
|
|
63
|
+
error: null
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
const store = {
|
|
67
|
+
$session,
|
|
68
|
+
setData,
|
|
69
|
+
refetch
|
|
70
|
+
};
|
|
71
|
+
onMount($session, () => createSessionSync(store, {
|
|
72
|
+
fetchOnMount: options.initialSession === void 0,
|
|
73
|
+
crossTabSync: options.crossTabSync ?? false,
|
|
74
|
+
refetchOnWindowFocus: options.refetchOnWindowFocus ?? false
|
|
75
|
+
}));
|
|
76
|
+
return store;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { createSessionStore };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createBroadcastChannel } from "./broadcast-channel.mjs";
|
|
2
|
+
import { deepJsonEqual } from "./json-deep-equal.mjs";
|
|
3
|
+
import { onNotify } from "nanostores";
|
|
4
|
+
//#region src/session-sync.ts
|
|
5
|
+
const CHANNEL_NAME = "limen.session";
|
|
6
|
+
const FOCUS_REFETCH_THROTTLE_MS = 5e3;
|
|
7
|
+
function createSessionSync(store, options) {
|
|
8
|
+
if (options.fetchOnMount) store.refetch();
|
|
9
|
+
const teardowns = [];
|
|
10
|
+
if (options.crossTabSync) teardowns.push(syncAcrossTabs(store));
|
|
11
|
+
if (options.refetchOnWindowFocus) teardowns.push(refetchOnFocus(store));
|
|
12
|
+
return () => {
|
|
13
|
+
for (const teardown of teardowns) teardown();
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function syncAcrossTabs(store) {
|
|
17
|
+
const port = createBroadcastChannel(CHANNEL_NAME);
|
|
18
|
+
let lastData = store.$session.get().data;
|
|
19
|
+
const unsubscribe = port.subscribe((message) => {
|
|
20
|
+
lastData = message.data;
|
|
21
|
+
store.setData(message.data);
|
|
22
|
+
});
|
|
23
|
+
const unbindNotify = onNotify(store.$session, () => {
|
|
24
|
+
const data = store.$session.get().data;
|
|
25
|
+
if (deepJsonEqual(data, lastData)) return;
|
|
26
|
+
lastData = data;
|
|
27
|
+
port.post({ data });
|
|
28
|
+
});
|
|
29
|
+
return () => {
|
|
30
|
+
unbindNotify();
|
|
31
|
+
unsubscribe();
|
|
32
|
+
port.close();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function refetchOnFocus(store) {
|
|
36
|
+
if (typeof document === "undefined") return () => {};
|
|
37
|
+
const onVisibilityChange = () => {
|
|
38
|
+
if (document.visibilityState === "visible") store.refetch({
|
|
39
|
+
maxAgeMs: FOCUS_REFETCH_THROTTLE_MS,
|
|
40
|
+
skipSignedOut: true
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
44
|
+
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createSessionSync };
|
package/dist/solid/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Prettify } from "../type-utils.mjs";
|
|
2
|
+
import { SessionState, SessionStore } from "../session-store.mjs";
|
|
3
|
+
import { AuthClient, CreateAuthClientOptions, Session, User } from "../types.mjs";
|
|
4
|
+
import { AnyClientPlugin } from "../define-plugin.mjs";
|
|
5
|
+
import { useStore } from "./solid-store.mjs";
|
|
3
6
|
import { Accessor } from "solid-js";
|
|
4
7
|
|
|
5
|
-
//#region src/solid/solid-store.d.ts
|
|
6
|
-
declare function useStore<SomeStore extends Store, Value extends StoreValue<SomeStore>>(store: SomeStore): Accessor<Value>;
|
|
7
|
-
//#endregion
|
|
8
8
|
//#region src/solid/index.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* An {@link AuthClient} augmented with Solid primitives.
|
|
@@ -21,5 +21,4 @@ type SolidAuthClient<Plugins extends readonly AnyClientPlugin[], TFields = unkno
|
|
|
21
21
|
*/
|
|
22
22
|
declare function createAuthClient<const Plugins extends readonly AnyClientPlugin[] = readonly [], TFields = unknown>(opts: CreateAuthClientOptions<Plugins, TFields>): SolidAuthClient<Plugins, TFields>;
|
|
23
23
|
//#endregion
|
|
24
|
-
export { type AuthClient, type CreateAuthClientOptions, type Session, type SessionState, type SessionStore, SolidAuthClient, type User, createAuthClient, useStore };
|
|
25
|
-
//# sourceMappingURL=index.d.mts.map
|
|
24
|
+
export { type AuthClient, type CreateAuthClientOptions, type Session, type SessionState, type SessionStore, SolidAuthClient, type User, createAuthClient, useStore };
|
package/dist/solid/index.mjs
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { createStore, reconcile } from "solid-js/store";
|
|
4
|
-
//#region src/solid/solid-store.ts
|
|
5
|
-
function useStore(store) {
|
|
6
|
-
const unbindActivation = store.listen(() => {});
|
|
7
|
-
const [state, setState] = createStore({ value: store.get() });
|
|
8
|
-
const unsubscribe = store.subscribe((value) => {
|
|
9
|
-
setState("value", reconcile(value));
|
|
10
|
-
});
|
|
11
|
-
onCleanup(() => unsubscribe());
|
|
12
|
-
unbindActivation();
|
|
13
|
-
return () => state.value;
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
1
|
+
import { createAuthClient as createAuthClient$1 } from "../client.mjs";
|
|
2
|
+
import { useStore } from "./solid-store.mjs";
|
|
16
3
|
//#region src/solid/index.ts
|
|
17
4
|
/**
|
|
18
5
|
* Create a Limen auth client with Solid primitives attached.
|
|
@@ -24,5 +11,3 @@ function createAuthClient(opts) {
|
|
|
24
11
|
}
|
|
25
12
|
//#endregion
|
|
26
13
|
export { createAuthClient, useStore };
|
|
27
|
-
|
|
28
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Store, StoreValue } from "nanostores";
|
|
2
|
+
import { Accessor } from "solid-js";
|
|
3
|
+
|
|
4
|
+
//#region src/solid/solid-store.d.ts
|
|
5
|
+
declare function useStore<SomeStore extends Store, Value extends StoreValue<SomeStore>>(store: SomeStore): Accessor<Value>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { useStore };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { onCleanup } from "solid-js";
|
|
2
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
3
|
+
//#region src/solid/solid-store.ts
|
|
4
|
+
function useStore(store) {
|
|
5
|
+
const unbindActivation = store.listen(() => {});
|
|
6
|
+
const [state, setState] = createStore({ value: store.get() });
|
|
7
|
+
const unsubscribe = store.subscribe((value) => {
|
|
8
|
+
setState("value", reconcile(value));
|
|
9
|
+
});
|
|
10
|
+
onCleanup(() => unsubscribe());
|
|
11
|
+
unbindActivation();
|
|
12
|
+
return () => state.value;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { useStore };
|
package/dist/svelte/index.d.mts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Prettify } from "../type-utils.mjs";
|
|
2
|
+
import { SessionState, SessionStore } from "../session-store.mjs";
|
|
3
|
+
import { AuthClient, CreateAuthClientOptions, Session, User } from "../types.mjs";
|
|
4
|
+
import { AnyClientPlugin } from "../define-plugin.mjs";
|
|
2
5
|
import { Readable } from "svelte/store";
|
|
3
6
|
|
|
4
7
|
//#region src/svelte/index.d.ts
|
|
@@ -21,5 +24,4 @@ type SvelteAuthClient<Plugins extends readonly AnyClientPlugin[], TFields = unkn
|
|
|
21
24
|
*/
|
|
22
25
|
declare function createAuthClient<const Plugins extends readonly AnyClientPlugin[] = readonly [], TFields = unknown>(opts: CreateAuthClientOptions<Plugins, TFields>): SvelteAuthClient<Plugins, TFields>;
|
|
23
26
|
//#endregion
|
|
24
|
-
export { type AuthClient, type CreateAuthClientOptions, type Session, type SessionState, type SessionStore, SvelteAuthClient, type User, createAuthClient };
|
|
25
|
-
//# sourceMappingURL=index.d.mts.map
|
|
27
|
+
export { type AuthClient, type CreateAuthClientOptions, type Session, type SessionState, type SessionStore, SvelteAuthClient, type User, createAuthClient };
|
package/dist/svelte/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createAuthClient as createAuthClient$1 } from "../client.mjs";
|
|
2
2
|
//#region src/svelte/index.ts
|
|
3
3
|
/**
|
|
4
4
|
* Create a Limen auth client with Svelte stores attached.
|
|
@@ -14,5 +14,3 @@ function createAuthClient(opts) {
|
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
16
16
|
export { createAuthClient };
|
|
17
|
-
|
|
18
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/type-utils.d.ts
|
|
2
|
+
/** Collapse an intersection into a single readable object type on hover. */
|
|
3
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
4
|
+
/** Turn a union `A | B | C` into the intersection `A & B & C`. */
|
|
5
|
+
type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
|
|
6
|
+
/**
|
|
7
|
+
* Convert a kebab-case literal to camelCase: `"magic-link"` -> `"magicLink"`.
|
|
8
|
+
*/
|
|
9
|
+
type KebabToCamel<S extends string> = S extends `${infer Head}-${infer Tail}` ? `${Head}${Capitalize<KebabToCamel<Tail>>}` : S;
|
|
10
|
+
/** Split a string literal into a tuple on a delimiter, dropping empty segments. */
|
|
11
|
+
type Split<S extends string, D extends string> = S extends `${infer Head}${D}${infer Tail}` ? Head extends "" ? Split<Tail, D> : [Head, ...Split<Tail, D>] : S extends "" ? [] : [S];
|
|
12
|
+
/** Check if a type is `any`. */
|
|
13
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { IsAny, KebabToCamel, Prettify, Split, UnionToIntersection };
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Prettify } from "./type-utils.mjs";
|
|
2
|
+
import { FetcherFetchOptions } from "./fetcher.mjs";
|
|
3
|
+
import { CoreContribution } from "./routes.mjs";
|
|
4
|
+
import { SessionState } from "./session-store.mjs";
|
|
5
|
+
import { PluginOverrides } from "./plugin.mjs";
|
|
6
|
+
import { AnyClientPlugin, CombinedClientContributions } from "./define-plugin.mjs";
|
|
7
|
+
import { ReadableAtom } from "nanostores";
|
|
8
|
+
|
|
9
|
+
//#region src/types.d.ts
|
|
10
|
+
type ClientFetchOptions = Partial<FetcherFetchOptions>;
|
|
11
|
+
type CreateAuthClientOptions<Plugins extends readonly AnyClientPlugin[], TFields = unknown> = {
|
|
12
|
+
/** Server origin, e.g. `"http://localhost:8080"`. Trailing slash is stripped. */baseURL: string; /** Path where the Limen handler is mounted. Defaults to `"/auth"`. */
|
|
13
|
+
basePath?: string; /** Options that modify how the SDK performs HTTP requests. */
|
|
14
|
+
fetchOptions?: ClientFetchOptions;
|
|
15
|
+
/**
|
|
16
|
+
* Optional transformer for non-default session payloads.
|
|
17
|
+
*
|
|
18
|
+
* Provide this when your server returns custom user/session fields. It must
|
|
19
|
+
* map the raw response into `Session<TFields>`.
|
|
20
|
+
*/
|
|
21
|
+
parseSession?: ParseSession<TFields>;
|
|
22
|
+
/**
|
|
23
|
+
* How the SDK navigates the browser when a flow hands control to an external
|
|
24
|
+
* page (e.g. an OAuth provider's authorization URL). Defaults to
|
|
25
|
+
* `window.location.href = url` when a `window` is available; a no-op in
|
|
26
|
+
* non-browser environments. Provide a custom function to integrate with a
|
|
27
|
+
* client-side router.
|
|
28
|
+
*/
|
|
29
|
+
redirectFn?: RedirectFn; /** Plugins to register. */
|
|
30
|
+
plugins?: Plugins;
|
|
31
|
+
/**
|
|
32
|
+
* Response-envelope config. Set this when the server wraps successful or all
|
|
33
|
+
* responses.
|
|
34
|
+
*/
|
|
35
|
+
envelope?: EnvelopeConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Per-plugin route overrides. Keys are camelCased plugin ids, e.g.
|
|
38
|
+
* `{ magicLink: { basePath: "/passwordless" } }`.
|
|
39
|
+
*/
|
|
40
|
+
overrides?: PluginOverrides<Plugins>;
|
|
41
|
+
/**
|
|
42
|
+
* SSR seed for the session store. Provide the session resolved server-side to
|
|
43
|
+
* avoid a hydration flash. When provided, lazy hydration is skipped until you
|
|
44
|
+
* call `getSession()` or the store revalidates.
|
|
45
|
+
*/
|
|
46
|
+
initialSession?: Session<TFields> | null;
|
|
47
|
+
/**
|
|
48
|
+
* Keep session state in sync across browser tabs.
|
|
49
|
+
* Enabled by default in browsers. Set `false` to disable.
|
|
50
|
+
*/
|
|
51
|
+
crossTabSync?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Refresh session state when the tab becomes active again.
|
|
54
|
+
* Enabled by default in browsers. Set `false` to disable.
|
|
55
|
+
*/
|
|
56
|
+
refetchOnWindowFocus?: boolean;
|
|
57
|
+
};
|
|
58
|
+
type AuthClient<Plugins extends readonly AnyClientPlugin[], TFields = unknown> = Prettify<{
|
|
59
|
+
readonly baseURL: string;
|
|
60
|
+
readonly basePath: string;
|
|
61
|
+
/**
|
|
62
|
+
* Reactive session store holding `{ data, isPending, error }`. Read it with
|
|
63
|
+
* `.get()` / `.listen()` or a framework `useStore`.
|
|
64
|
+
*/
|
|
65
|
+
readonly $session: ReadableAtom<SessionState<TFields>>;
|
|
66
|
+
} & CoreContribution<TFields> & CombinedClientContributions<Plugins>>;
|
|
67
|
+
/**
|
|
68
|
+
* The user object returned from `/me` and any session-bearing response.
|
|
69
|
+
*
|
|
70
|
+
* `TFields` lets consumers extend the shape with custom user fields. The
|
|
71
|
+
* fields listed here are always present.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* type AppUser = User<{ firstName: string; orgId: string }>;
|
|
75
|
+
*/
|
|
76
|
+
type User<TFields = unknown> = {
|
|
77
|
+
id: string;
|
|
78
|
+
email: string;
|
|
79
|
+
emailVerifiedAt: string | null;
|
|
80
|
+
} & TFields;
|
|
81
|
+
type Session<TFields = unknown> = {
|
|
82
|
+
user: User<TFields>;
|
|
83
|
+
};
|
|
84
|
+
type EnvelopeMode = "off" | "wrap-success" | "always";
|
|
85
|
+
type EnvelopeFields = {
|
|
86
|
+
data: string;
|
|
87
|
+
message: string;
|
|
88
|
+
};
|
|
89
|
+
type EnvelopeConfig = {
|
|
90
|
+
mode: EnvelopeMode;
|
|
91
|
+
fields?: EnvelopeFields;
|
|
92
|
+
};
|
|
93
|
+
type ParseSession<TFields = unknown> = (raw: unknown) => Session<TFields>;
|
|
94
|
+
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
|
95
|
+
type RedirectFn = (url: string) => boolean;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { AuthClient, ClientFetchOptions, CreateAuthClientOptions, EnvelopeConfig, EnvelopeFields, EnvelopeMode, HTTPMethod, ParseSession, RedirectFn, Session, User };
|
package/dist/version.mjs
ADDED
package/dist/vue/index.d.mts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Prettify } from "../type-utils.mjs";
|
|
2
|
+
import { SessionState, SessionStore } from "../session-store.mjs";
|
|
3
|
+
import { AuthClient, CreateAuthClientOptions, Session, User } from "../types.mjs";
|
|
4
|
+
import { AnyClientPlugin } from "../define-plugin.mjs";
|
|
5
|
+
import { ReactiveValue, useStore } from "./vue-store.mjs";
|
|
4
6
|
|
|
5
|
-
//#region src/vue/vue-store.d.ts
|
|
6
|
-
/** What {@link useStore} returns: a readonly reactive ref of the store value. */
|
|
7
|
-
type ReactiveValue<Value> = DeepReadonly<UnwrapNestedRefs<ShallowRef<Value>>>;
|
|
8
|
-
/**
|
|
9
|
-
* Subscribe a Vue component to a nanostores store.
|
|
10
|
-
*
|
|
11
|
-
* Modeled on `@nanostores/vue`'s `useStore`: a `shallowRef` mirrors the store
|
|
12
|
-
* value, `subscribe` keeps it in sync (firing once immediately), and the
|
|
13
|
-
* listener is torn down on scope dispose. On the server (no `window`) it reads
|
|
14
|
-
* once without subscribing, so a store seeded with `initialSession` renders
|
|
15
|
-
* without leaving a subscription behind.
|
|
16
|
-
*/
|
|
17
|
-
declare function useStore<SomeStore extends Store>(store: SomeStore): ReactiveValue<StoreValue<SomeStore>>;
|
|
18
|
-
//#endregion
|
|
19
7
|
//#region src/vue/index.d.ts
|
|
20
8
|
/**
|
|
21
9
|
* An {@link AuthClient} augmented with Vue composables.
|
|
@@ -32,5 +20,4 @@ type VueAuthClient<Plugins extends readonly AnyClientPlugin[], TFields = unknown
|
|
|
32
20
|
*/
|
|
33
21
|
declare function createAuthClient<const Plugins extends readonly AnyClientPlugin[] = readonly [], TFields = unknown>(opts: CreateAuthClientOptions<Plugins, TFields>): VueAuthClient<Plugins, TFields>;
|
|
34
22
|
//#endregion
|
|
35
|
-
export { type AuthClient, type CreateAuthClientOptions, type ReactiveValue, type Session, type SessionState, type SessionStore, type User, VueAuthClient, createAuthClient, useStore };
|
|
36
|
-
//# sourceMappingURL=index.d.mts.map
|
|
23
|
+
export { type AuthClient, type CreateAuthClientOptions, type ReactiveValue, type Session, type SessionState, type SessionStore, type User, VueAuthClient, createAuthClient, useStore };
|
package/dist/vue/index.mjs
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
//#region src/vue/vue-store.ts
|
|
4
|
-
/**
|
|
5
|
-
* Subscribe a Vue component to a nanostores store.
|
|
6
|
-
*
|
|
7
|
-
* Modeled on `@nanostores/vue`'s `useStore`: a `shallowRef` mirrors the store
|
|
8
|
-
* value, `subscribe` keeps it in sync (firing once immediately), and the
|
|
9
|
-
* listener is torn down on scope dispose. On the server (no `window`) it reads
|
|
10
|
-
* once without subscribing, so a store seeded with `initialSession` renders
|
|
11
|
-
* without leaving a subscription behind.
|
|
12
|
-
*/
|
|
13
|
-
function useStore(store) {
|
|
14
|
-
const state = shallowRef(store.get());
|
|
15
|
-
if (typeof window !== "undefined") {
|
|
16
|
-
const unsubscribe = store.subscribe((value) => {
|
|
17
|
-
state.value = value;
|
|
18
|
-
});
|
|
19
|
-
if (getCurrentScope()) onScopeDispose(unsubscribe);
|
|
20
|
-
}
|
|
21
|
-
return readonly(state);
|
|
22
|
-
}
|
|
23
|
-
//#endregion
|
|
1
|
+
import { createAuthClient as createAuthClient$1 } from "../client.mjs";
|
|
2
|
+
import { useStore } from "./vue-store.mjs";
|
|
24
3
|
//#region src/vue/index.ts
|
|
25
4
|
/**
|
|
26
5
|
* Create a Limen auth client with Vue composables attached.
|
|
@@ -32,5 +11,3 @@ function createAuthClient(opts) {
|
|
|
32
11
|
}
|
|
33
12
|
//#endregion
|
|
34
13
|
export { createAuthClient, useStore };
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Store, StoreValue } from "nanostores";
|
|
2
|
+
import { DeepReadonly, ShallowRef, UnwrapNestedRefs } from "vue";
|
|
3
|
+
|
|
4
|
+
//#region src/vue/vue-store.d.ts
|
|
5
|
+
/** What {@link useStore} returns: a readonly reactive ref of the store value. */
|
|
6
|
+
type ReactiveValue<Value> = DeepReadonly<UnwrapNestedRefs<ShallowRef<Value>>>;
|
|
7
|
+
/**
|
|
8
|
+
* Subscribe a Vue component to a nanostores store.
|
|
9
|
+
*
|
|
10
|
+
* Modeled on `@nanostores/vue`'s `useStore`: a `shallowRef` mirrors the store
|
|
11
|
+
* value, `subscribe` keeps it in sync (firing once immediately), and the
|
|
12
|
+
* listener is torn down on scope dispose. On the server (no `window`) it reads
|
|
13
|
+
* once without subscribing, so a store seeded with `initialSession` renders
|
|
14
|
+
* without leaving a subscription behind.
|
|
15
|
+
*/
|
|
16
|
+
declare function useStore<SomeStore extends Store>(store: SomeStore): ReactiveValue<StoreValue<SomeStore>>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ReactiveValue, useStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getCurrentScope, onScopeDispose, readonly, shallowRef } from "vue";
|
|
2
|
+
//#region src/vue/vue-store.ts
|
|
3
|
+
/**
|
|
4
|
+
* Subscribe a Vue component to a nanostores store.
|
|
5
|
+
*
|
|
6
|
+
* Modeled on `@nanostores/vue`'s `useStore`: a `shallowRef` mirrors the store
|
|
7
|
+
* value, `subscribe` keeps it in sync (firing once immediately), and the
|
|
8
|
+
* listener is torn down on scope dispose. On the server (no `window`) it reads
|
|
9
|
+
* once without subscribing, so a store seeded with `initialSession` renders
|
|
10
|
+
* without leaving a subscription behind.
|
|
11
|
+
*/
|
|
12
|
+
function useStore(store) {
|
|
13
|
+
const state = shallowRef(store.get());
|
|
14
|
+
if (typeof window !== "undefined") {
|
|
15
|
+
const unsubscribe = store.subscribe((value) => {
|
|
16
|
+
state.value = value;
|
|
17
|
+
});
|
|
18
|
+
if (getCurrentScope()) onScopeDispose(unsubscribe);
|
|
19
|
+
}
|
|
20
|
+
return readonly(state);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { useStore };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "limen-auth",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"description": "TypeScript
|
|
3
|
+
"version": "0.0.2-beta.0",
|
|
4
|
+
"description": "Official TypeScript SDK for Limen, a composable Go auth library. Framework-agnostic core with React, Vue, Svelte, and Solid adapters.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"limen",
|
|
7
7
|
"auth",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"typescript",
|
|
19
19
|
"sdk"
|
|
20
20
|
],
|
|
21
|
-
"homepage": "https://
|
|
21
|
+
"homepage": "https://limenauth.dev",
|
|
22
22
|
"bugs": "https://github.com/thecodearcher/limen/issues",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"author": "Brian Iyoha <brian.iyoha@gmail.com>",
|
|
30
30
|
"type": "module",
|
|
31
31
|
"module": "./dist/index.mjs",
|
|
32
|
+
"types": "./dist/index.d.mts",
|
|
32
33
|
"publishConfig": {
|
|
33
34
|
"access": "public"
|
|
34
35
|
},
|