limen-auth 0.0.0 → 0.0.1

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.
Files changed (118) hide show
  1. package/README.md +16 -41
  2. package/dist/broadcast-channel.mjs +51 -0
  3. package/dist/build-tree.mjs +73 -0
  4. package/dist/client.d.mts +7 -0
  5. package/dist/client.mjs +71 -0
  6. package/dist/{constants-CsR2pQ_9.mjs → constants.mjs} +1 -3
  7. package/dist/context.d.mts +32 -0
  8. package/dist/define-plugin.d.mts +40 -0
  9. package/dist/{define-plugin-C7WOGU4b.mjs → define-plugin.mjs} +1 -3
  10. package/dist/envelope.mjs +39 -0
  11. package/dist/errors.d.mts +19 -0
  12. package/dist/{errors-4YJYt6f0.mjs → errors.mjs} +1 -3
  13. package/dist/fetcher.d.mts +17 -0
  14. package/dist/fetcher.mjs +112 -0
  15. package/dist/{helpers-CvmKjWi2.d.mts → helpers.d.mts} +1 -2
  16. package/dist/{helpers-Cs3VtXdv.mjs → helpers.mjs} +1 -3
  17. package/dist/hooks.d.mts +1 -0
  18. package/dist/hooks.mjs +34 -0
  19. package/dist/index.d.mts +14 -30
  20. package/dist/index.mjs +9 -11
  21. package/dist/infer.d.mts +42 -0
  22. package/dist/json-deep-equal.mjs +27 -0
  23. package/dist/normalize.d.mts +12 -0
  24. package/dist/normalize.mjs +19 -0
  25. package/dist/package.mjs +4 -0
  26. package/dist/path.mjs +41 -0
  27. package/dist/pipeline.mjs +65 -0
  28. package/dist/plugin.d.mts +71 -0
  29. package/dist/plugins/bearer/index.d.mts +13 -1
  30. package/dist/plugins/bearer/index.mjs +43 -1
  31. package/dist/plugins/bearer/storage.d.mts +8 -0
  32. package/dist/plugins/bearer/storage.mjs +41 -0
  33. package/dist/{index-B8SpHkSd.d.mts → plugins/bearer/types.d.mts} +1 -18
  34. package/dist/plugins/credential/index.d.mts +54 -1
  35. package/dist/plugins/credential/index.mjs +2 -4
  36. package/dist/plugins/credential/types.d.mts +35 -0
  37. package/dist/plugins/index.d.mts +13 -6
  38. package/dist/plugins/index.mjs +3 -2
  39. package/dist/plugins/magic-link/index.d.mts +17 -1
  40. package/dist/plugins/magic-link/index.mjs +2 -4
  41. package/dist/plugins/magic-link/types.d.mts +13 -0
  42. package/dist/plugins/oauth/index.d.mts +47 -1
  43. package/dist/plugins/oauth/index.mjs +3 -5
  44. package/dist/plugins/oauth/types.d.mts +35 -0
  45. package/dist/plugins/session-jwt/index.d.mts +24 -1
  46. package/dist/plugins/session-jwt/index.mjs +95 -1
  47. package/dist/plugins/session-jwt/jwt.mjs +34 -0
  48. package/dist/plugins/session-jwt/types.d.mts +11 -0
  49. package/dist/plugins/two-factor/index.d.mts +41 -1
  50. package/dist/plugins/two-factor/index.mjs +2 -4
  51. package/dist/plugins/two-factor/types.d.mts +26 -0
  52. package/dist/react/index.d.mts +6 -7
  53. package/dist/react/index.mjs +2 -20
  54. package/dist/react/react-store.d.mts +6 -0
  55. package/dist/react/react-store.mjs +18 -0
  56. package/dist/route.d.mts +80 -0
  57. package/dist/{route-DGxvFqWl.mjs → route.mjs} +1 -3
  58. package/dist/routes.d.mts +53 -0
  59. package/dist/routes.mjs +58 -0
  60. package/dist/serialize.d.mts +11 -0
  61. package/dist/serialize.mjs +23 -0
  62. package/dist/session-store.d.mts +29 -0
  63. package/dist/session-store.mjs +79 -0
  64. package/dist/session-sync.mjs +47 -0
  65. package/dist/solid/index.d.mts +6 -7
  66. package/dist/solid/index.mjs +2 -17
  67. package/dist/solid/solid-store.d.mts +7 -0
  68. package/dist/solid/solid-store.mjs +15 -0
  69. package/dist/svelte/index.d.mts +5 -3
  70. package/dist/svelte/index.mjs +1 -3
  71. package/dist/type-utils.d.mts +15 -0
  72. package/dist/types.d.mts +97 -0
  73. package/dist/version.d.mts +4 -0
  74. package/dist/version.mjs +5 -0
  75. package/dist/vue/index.d.mts +6 -19
  76. package/dist/vue/index.mjs +2 -25
  77. package/dist/vue/vue-store.d.mts +18 -0
  78. package/dist/vue/vue-store.mjs +23 -0
  79. package/package.json +4 -3
  80. package/dist/bearer-Cqmrmjjf.mjs +0 -84
  81. package/dist/bearer-Cqmrmjjf.mjs.map +0 -1
  82. package/dist/client-Er91De-z.mjs +0 -705
  83. package/dist/client-Er91De-z.mjs.map +0 -1
  84. package/dist/constants-CsR2pQ_9.mjs.map +0 -1
  85. package/dist/define-plugin-C7WOGU4b.mjs.map +0 -1
  86. package/dist/define-plugin-Dv0xXIaH.d.mts +0 -450
  87. package/dist/define-plugin-Dv0xXIaH.d.mts.map +0 -1
  88. package/dist/errors-4YJYt6f0.mjs.map +0 -1
  89. package/dist/helpers-Cs3VtXdv.mjs.map +0 -1
  90. package/dist/helpers-CvmKjWi2.d.mts.map +0 -1
  91. package/dist/index-B8SpHkSd.d.mts.map +0 -1
  92. package/dist/index-C6atwjEq.d.mts +0 -65
  93. package/dist/index-C6atwjEq.d.mts.map +0 -1
  94. package/dist/index-C9EuA9UZ.d.mts +0 -32
  95. package/dist/index-C9EuA9UZ.d.mts.map +0 -1
  96. package/dist/index-Cgr2wHmM.d.mts +0 -87
  97. package/dist/index-Cgr2wHmM.d.mts.map +0 -1
  98. package/dist/index-DV6YcNSu.d.mts +0 -81
  99. package/dist/index-DV6YcNSu.d.mts.map +0 -1
  100. package/dist/index-dEksIImj.d.mts +0 -28
  101. package/dist/index-dEksIImj.d.mts.map +0 -1
  102. package/dist/index.d.mts.map +0 -1
  103. package/dist/index.mjs.map +0 -1
  104. package/dist/plugins/credential/index.mjs.map +0 -1
  105. package/dist/plugins/magic-link/index.mjs.map +0 -1
  106. package/dist/plugins/oauth/index.mjs.map +0 -1
  107. package/dist/plugins/two-factor/index.mjs.map +0 -1
  108. package/dist/react/index.d.mts.map +0 -1
  109. package/dist/react/index.mjs.map +0 -1
  110. package/dist/route-DGxvFqWl.mjs.map +0 -1
  111. package/dist/session-jwt-DgLdMQxP.mjs +0 -129
  112. package/dist/session-jwt-DgLdMQxP.mjs.map +0 -1
  113. package/dist/solid/index.d.mts.map +0 -1
  114. package/dist/solid/index.mjs.map +0 -1
  115. package/dist/svelte/index.d.mts.map +0 -1
  116. package/dist/svelte/index.mjs.map +0 -1
  117. package/dist/vue/index.d.mts.map +0 -1
  118. package/dist/vue/index.mjs.map +0 -1
@@ -1,705 +0,0 @@
1
- import { a as joinURL, c as stripTrailingSlash, i as ensureLeadingSlash, l as toCamelCaseKey, n as camelizeEach, o as kebabToCamel, s as normalizeBasePath, t as camelToSnake } from "./helpers-Cs3VtXdv.mjs";
2
- import { n as DEFAULT_ENVELOPE_FIELDS, t as DEFAULT_ENVELOPE_CONFIG } from "./constants-CsR2pQ_9.mjs";
3
- import { n as deriveErrorCode, t as LimenError } from "./errors-4YJYt6f0.mjs";
4
- import { n as defineRoutes, t as defineClientPlugin } from "./define-plugin-C7WOGU4b.mjs";
5
- import { t as route } from "./route-DGxvFqWl.mjs";
6
- import { atom, onMount, onNotify } from "nanostores";
7
- //#region src/path.ts
8
- /**
9
- * Derive the public client chain from a route path. Mirrors the type-level
10
- * `PathSegments` in `infer.ts` exactly, so runtime materialization and compile
11
- * time inference can never drift.
12
- *
13
- * "/otp/send" -> ["otp", "send"]
14
- * "/revoke-sessions" -> ["revokeSessions"]
15
- * "/:provider/authorize" -> ["authorize"] (param segments are dropped)
16
- */
17
- function pathToChain(path) {
18
- return path.split("/").filter((seg) => seg.length > 0 && !seg.startsWith(":")).map(kebabToCamel);
19
- }
20
- function chainFromDotted(chain) {
21
- return chain.split(".").filter((seg) => seg.length > 0);
22
- }
23
- /**
24
- * Substitute declared path params from `input` into `path` and strip them from
25
- * the payload.
26
- */
27
- function resolvePath(path, params, input) {
28
- if (!params || params.length === 0) return {
29
- path,
30
- rest: input
31
- };
32
- const rest = { ...input ?? {} };
33
- let resolved = path;
34
- for (const param of params) {
35
- const value = rest[param];
36
- if (value === void 0) throw new Error(`Missing required path param "${param}" for route "${path}"`);
37
- resolved = resolved.replace(`:${param}`, encodeURIComponent(value));
38
- delete rest[param];
39
- }
40
- return {
41
- path: resolved,
42
- rest
43
- };
44
- }
45
- //#endregion
46
- //#region src/serialize.ts
47
- /**
48
- * Default request serializer: shallow camelCase → snake_case, drops `undefined`,
49
- * leaves non-objects unchanged.
50
- *
51
- * `additionalFields` entries are merged into the top-level body verbatim.
52
- * Known route fields win on key collisions.
53
- */
54
- function defaultSerialize(input) {
55
- if (input === null || input === void 0) return input;
56
- if (typeof input !== "object" || Array.isArray(input)) return input;
57
- const { additionalFields, ...rest } = input;
58
- const out = {};
59
- if (additionalFields && typeof additionalFields === "object" && !Array.isArray(additionalFields)) Object.assign(out, additionalFields);
60
- for (const [key, value] of Object.entries(rest)) {
61
- if (value === void 0) continue;
62
- out[camelToSnake(key)] = value;
63
- }
64
- return out;
65
- }
66
- //#endregion
67
- //#region src/pipeline.ts
68
- /**
69
- * Run the default HTTP steps for a route — merge defaults, resolve path params,
70
- * serialize, dispatch, parse — without applying session effects.
71
- */
72
- async function runHttp(ctx, def, input) {
73
- let merged = input;
74
- if (def.defaults !== void 0) merged = {
75
- ...def.defaults,
76
- ...input ?? {}
77
- };
78
- const { path, rest } = resolvePath(def.path, def.params, merged);
79
- const payload = def.serialize !== void 0 ? def.serialize(rest) : defaultSerialize(rest);
80
- const init = {
81
- method: def.method,
82
- absolute: def.absolute ?? false
83
- };
84
- if (def.method === "GET" && payload !== void 0) init.query = payload;
85
- else init.body = payload;
86
- const raw = await ctx.fetch(path, init);
87
- if (def.parseSession === true) return ctx.parseSession(raw);
88
- if (def.parse !== void 0) return def.parse(raw);
89
- return Array.isArray(raw) ? camelizeEach(raw) : raw;
90
- }
91
- async function applyEffects(ctx, def, result) {
92
- if (def.clearSession === true) ctx.store.setData(null);
93
- if (def.parseSession === true && def.skipStore !== true) {
94
- if (result !== null && typeof result === "object" && "user" in result) ctx.store.setData(result);
95
- }
96
- if (def.refetchSession === true) await ctx.store.refetch();
97
- }
98
- function makeHttpRunner(ctx, def, boundInput) {
99
- const run = (override) => runHttp(ctx, def, override === void 0 ? boundInput : override);
100
- return run;
101
- }
102
- /**
103
- * Execute a route's behaviour: delegate to its `handler` when present (handler
104
- * owns all behaviour, including any effects), otherwise run the default
105
- * pipeline and apply declarative effects once at the top level.
106
- */
107
- async function dispatchRoute(ctx, def, input) {
108
- if (def.handler !== void 0) return def.handler(ctx, input, makeHttpRunner(ctx, def, input));
109
- const result = await runHttp(ctx, def, input);
110
- await applyEffects(ctx, def, result);
111
- return result;
112
- }
113
- /**
114
- * Run a route as a public client call, firing the per-call `onSuccess` /
115
- * `onError` hooks around the resolved value or thrown error.
116
- */
117
- async function runRoute(ctx, def, input, opts) {
118
- try {
119
- const result = await dispatchRoute(ctx, def, input);
120
- opts?.onSuccess?.(result);
121
- return result;
122
- } catch (error) {
123
- opts?.onError?.(error);
124
- throw error;
125
- }
126
- }
127
- //#endregion
128
- //#region src/build-tree.ts
129
- /** Scope a context's `fetch` to one plugin's base path (after client `overrides`). */
130
- function scopeContext(ctx, fetcher, plugin, overrides) {
131
- const defaultBase = normalizeBasePath(plugin.basePath ?? "");
132
- const overrideBase = overrides?.[kebabToCamel(plugin.id)]?.basePath;
133
- const resolvedBase = normalizeBasePath(overrideBase ?? plugin.basePath ?? "");
134
- return {
135
- ...ctx,
136
- fetch: (path, init) => {
137
- const absolute = init?.absolute === true;
138
- const requestPath = (absolute ? "" : resolvedBase) + ensureLeadingSlash(path);
139
- const routePath = (absolute ? "" : defaultBase) + ensureLeadingSlash(path);
140
- return fetcher.fetch(requestPath, init, routePath);
141
- }
142
- };
143
- }
144
- function chainFor(plugin, def) {
145
- if (typeof def.as === "string") return chainFromDotted(def.as);
146
- return [...pathToChain(plugin.basePath ?? ""), ...pathToChain(def.path)];
147
- }
148
- function mountAtChain(target, pathSegments, callable) {
149
- let current = target;
150
- for (let i = 0; i < pathSegments.length - 1; i += 1) {
151
- const segment = pathSegments[i];
152
- const child = current[segment];
153
- if (child === void 0) {
154
- const namespace = {};
155
- current[segment] = namespace;
156
- current = namespace;
157
- } else current = child;
158
- }
159
- const finalSegment = pathSegments[pathSegments.length - 1];
160
- current[finalSegment] = callable;
161
- }
162
- function isNamespace(value) {
163
- return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value !== "function";
164
- }
165
- function mergeInto(target, source) {
166
- for (const [key, value] of Object.entries(source)) {
167
- const existing = target[key];
168
- if (isNamespace(existing) && isNamespace(value)) {
169
- mergeInto(existing, value);
170
- continue;
171
- }
172
- target[key] = value;
173
- }
174
- }
175
- /**
176
- * Build the public API object from plugin routes and actions.
177
- */
178
- function buildClientTree({ plugins, ctx, fetcher, overrides }) {
179
- const api = {};
180
- for (const plugin of plugins) {
181
- const scopedCtx = scopeContext(ctx, fetcher, plugin, overrides);
182
- const contribution = {};
183
- for (const def of plugin.routes) {
184
- if (def.expose === false) continue;
185
- const call = (input, opts) => runRoute(scopedCtx, def, input, opts);
186
- mountAtChain(contribution, chainFor(plugin, def), call);
187
- }
188
- if (plugin.actions !== void 0) {
189
- const run = (route, input) => runRoute(scopedCtx, route, input);
190
- mergeInto(contribution, plugin.actions(scopedCtx, run));
191
- }
192
- mergeInto(api, contribution);
193
- }
194
- return api;
195
- }
196
- //#endregion
197
- //#region src/envelope.ts
198
- /**
199
- * Unwrap a parsed JSON success body according to envelope config.
200
- *
201
- * - `mode: "off"`: return as-is.
202
- * - `mode: "wrap-success" | "always"`: extract `body[fields.data]` if present;
203
- * if the key is missing (server didn't wrap this particular response), fall
204
- * back to the raw body so we don't lose data.
205
- *
206
- * Returning `unknown` is intentional: the caller knows the expected shape and
207
- * narrows / validates it. Trying to be clever here would invite false typing.
208
- */
209
- function unwrapPayload(body, envelope) {
210
- if (envelope.mode === "off") return body;
211
- if (body === null || typeof body !== "object") return body;
212
- const fields = envelope.fields ?? DEFAULT_ENVELOPE_FIELDS;
213
- const record = body;
214
- if (fields.data in record) return record[fields.data];
215
- return body;
216
- }
217
- /**
218
- * Pull the human-readable error message out of a non-2xx body.
219
- *
220
- * - `mode: "off" | "wrap-success"`: error bodies look like `{ message }`
221
- * - `mode: "always"`: error bodies use the configured `fields.message` key.
222
- *
223
- * Returns `undefined` when no message can be located; the caller substitutes
224
- * a status-based fallback (e.g. HTTP status text).
225
- */
226
- function unwrapErrorMessage(body, envelope) {
227
- if (body === null || typeof body !== "object") return;
228
- const record = body;
229
- const fields = envelope.fields ?? DEFAULT_ENVELOPE_FIELDS;
230
- const value = record[envelope.mode === "always" ? fields.message : "message"];
231
- return typeof value === "string" ? value : void 0;
232
- }
233
- //#endregion
234
- //#region src/fetcher.ts
235
- var Fetcher = class {
236
- opts;
237
- fetchImpl;
238
- credentials;
239
- constructor(opts) {
240
- this.opts = opts;
241
- this.fetchImpl = opts.fetchOptions.impl ?? globalThis.fetch.bind(globalThis);
242
- this.credentials = opts.fetchOptions.credentials ?? "include";
243
- }
244
- async fetch(path, init, routePath = path) {
245
- const method = init?.method ?? (init?.body !== void 0 ? "POST" : "GET");
246
- return this.run({
247
- method,
248
- path,
249
- routePath,
250
- body: init?.body,
251
- headers: init?.headers,
252
- query: init?.query
253
- });
254
- }
255
- /**
256
- * Builds the request context, runs hooks, does
257
- * the fetch, parses the response, runs hooks again, throws on non-2xx.
258
- */
259
- async run(args) {
260
- const fullPath = this.normalizeRelativePath(this.opts.basePath, args.path, args.query);
261
- const url = joinURL(this.opts.baseURL, fullPath);
262
- const headers = new Headers({
263
- ...args.headers,
264
- ...this.opts.fetchOptions.headers ?? {}
265
- });
266
- if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
267
- if (!headers.has("Accept")) headers.set("Accept", "application/json");
268
- let reqCtx = {
269
- method: args.method,
270
- fullPath,
271
- path: args.path,
272
- routePath: args.routePath,
273
- url,
274
- headers,
275
- body: args.body
276
- };
277
- reqCtx = await this.opts.hooks.runBeforeRequest(reqCtx);
278
- const payload = reqCtx.body !== void 0 && reqCtx.body !== null ? JSON.stringify(reqCtx.body) : void 0;
279
- const requestInit = {
280
- method: reqCtx.method,
281
- headers: reqCtx.headers,
282
- credentials: this.credentials
283
- };
284
- if (payload !== void 0) requestInit.body = payload;
285
- let response;
286
- try {
287
- response = await this.fetchImpl(reqCtx.url, requestInit);
288
- } catch (err) {
289
- this.opts.fetchOptions.onError?.({
290
- ...reqCtx,
291
- status: 0,
292
- ok: false,
293
- error: err
294
- });
295
- throw new LimenError(err instanceof Error ? err.message : "Network request failed", 0, "unknown");
296
- }
297
- const parsedBody = await this.parseResponseBody(response);
298
- const unwrapped = response.ok && parsedBody !== void 0 ? unwrapPayload(parsedBody, this.opts.envelope) : parsedBody;
299
- let resCtx = {
300
- method: args.method,
301
- fullPath,
302
- path: args.path,
303
- routePath: args.routePath,
304
- status: response.status,
305
- ok: response.ok,
306
- headers: response.headers,
307
- body: unwrapped
308
- };
309
- resCtx = await this.opts.hooks.runAfterResponse(resCtx);
310
- if (resCtx.ok) {
311
- this.opts.fetchOptions.onSuccess?.({
312
- ...resCtx,
313
- response
314
- });
315
- return resCtx.body;
316
- }
317
- const error = new LimenError(unwrapErrorMessage(resCtx.body, this.opts.envelope) ?? response.statusText ?? `Request failed with status ${response.status}`, response.status, deriveErrorCode(response.status));
318
- this.opts.fetchOptions.onError?.({
319
- ...reqCtx,
320
- status: response.status,
321
- ok: false,
322
- error
323
- });
324
- throw error;
325
- }
326
- async parseResponseBody(response) {
327
- if (response.status === 204) return;
328
- const text = await response.text();
329
- if (text.length === 0) return;
330
- return JSON.parse(text);
331
- }
332
- normalizeRelativePath(basePath, relativePath, query) {
333
- let path = (basePath === "" || basePath === "/" ? "" : ensureLeadingSlash(stripTrailingSlash(basePath))) + ensureLeadingSlash(relativePath);
334
- if (query !== void 0) {
335
- const qs = new URLSearchParams(query).toString();
336
- if (qs.length > 0) path = `${path}?${qs}`;
337
- }
338
- return path;
339
- }
340
- };
341
- //#endregion
342
- //#region src/hooks.ts
343
- function matchesRoute(matcher, routePath) {
344
- if (matcher === void 0) return true;
345
- if (typeof matcher === "string") return routePath === matcher;
346
- if (typeof matcher === "function") return matcher({ path: routePath });
347
- return routePath !== void 0 && matcher.includes(routePath);
348
- }
349
- var HookRunner = class {
350
- before;
351
- after;
352
- constructor(plugins) {
353
- this.before = plugins.flatMap((p) => p.hooks?.beforeRequest ?? []);
354
- this.after = plugins.flatMap((p) => p.hooks?.afterResponse ?? []);
355
- }
356
- async runBeforeRequest(initial) {
357
- let ctx = initial;
358
- for (const hook of this.before) {
359
- if (!matchesRoute(hook.match, ctx.routePath)) continue;
360
- ctx = await hook.run(ctx);
361
- }
362
- return ctx;
363
- }
364
- async runAfterResponse(initial) {
365
- let ctx = initial;
366
- for (const hook of this.after) {
367
- if (!matchesRoute(hook.match, ctx.routePath)) continue;
368
- if (!hook.allowOnFailure && ctx.status >= 400) continue;
369
- ctx = await hook.run(ctx);
370
- }
371
- return ctx;
372
- }
373
- };
374
- //#endregion
375
- //#region src/normalize.ts
376
- /**
377
- * Convert snake_case User keys from server payloads to camelCase. Unknown
378
- * extension fields are converted with the same rule so consumers consistently
379
- * read camelCase in SDK responses.
380
- */
381
- function normalizeUser(raw) {
382
- const out = {};
383
- for (const [key, value] of Object.entries(raw)) out[toCamelCaseKey(key)] = value;
384
- return out;
385
- }
386
- function defaultSessionParse(raw) {
387
- if (!raw || typeof raw !== "object") throw new TypeError(`Expected session response to be an object, got ${raw === null ? "null" : typeof raw}`);
388
- const obj = raw;
389
- return { user: normalizeUser(obj["user"] ?? obj) };
390
- }
391
- //#endregion
392
- //#region src/routes.ts
393
- /**
394
- * Core routes available on every client.
395
- */
396
- function coreClientPlugin() {
397
- return defineClientPlugin({
398
- id: "core",
399
- basePath: "/",
400
- routes: defineRoutes(route()({
401
- method: "GET",
402
- path: "/sessions"
403
- }), route()({
404
- method: "POST",
405
- path: "/signout",
406
- clearSession: true
407
- }), route()({
408
- method: "POST",
409
- path: "/revoke-sessions",
410
- clearSession: true
411
- }), route()({
412
- method: "POST",
413
- path: "/verify-email",
414
- refetchSession: true
415
- }), route()({
416
- method: "POST",
417
- path: "/email-verifications",
418
- as: "requestEmailVerification"
419
- })),
420
- actions: (ctx) => ({
421
- /**
422
- * Revalidate session state with `GET /me`, update `$session`, and return the
423
- * resolved value (`null` when signed out).
424
- *
425
- * Prefer subscribing to `$session` for reactive UI state (`data`, `isPending`,
426
- * `error`). Use `getSession()` when you need an awaited server re-check, such
427
- * as route guards or SSR revalidation after `initialSession`.
428
- */
429
- getSession: async () => {
430
- await ctx.refetchSession();
431
- const state = ctx.store.$session.get();
432
- if (state.error) throw state.error;
433
- return state.data;
434
- } })
435
- });
436
- }
437
- /**
438
- * Fetch and parse the current session for the reactive store.
439
- */
440
- function createSessionHydrator(ctx) {
441
- return async () => {
442
- const raw = await ctx.fetch("/me", { method: "GET" });
443
- return ctx.parseSession(raw);
444
- };
445
- }
446
- //#endregion
447
- //#region src/broadcast-channel.ts
448
- const NOOP_PORT = {
449
- post() {},
450
- subscribe: () => () => {},
451
- close() {}
452
- };
453
- function createBroadcastChannel(name) {
454
- if (typeof window === "undefined") return NOOP_PORT;
455
- const storageKey = `${name}-sync`;
456
- const listeners = /* @__PURE__ */ new Set();
457
- const emit = (message) => {
458
- for (const listener of listeners) listener(message);
459
- };
460
- const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(name) : null;
461
- if (channel) channel.onmessage = (event) => {
462
- if (event.data != null) emit(event.data);
463
- };
464
- const onStorage = (event) => {
465
- if (event.key !== storageKey || !event.newValue) return;
466
- emit(JSON.parse(event.newValue));
467
- };
468
- if (!channel) window.addEventListener("storage", onStorage);
469
- return {
470
- post(message) {
471
- if (channel) {
472
- channel.postMessage(message);
473
- return;
474
- }
475
- if (typeof globalThis.localStorage !== "undefined") {
476
- globalThis.localStorage.setItem(storageKey, JSON.stringify(message));
477
- globalThis.localStorage.removeItem(storageKey);
478
- }
479
- },
480
- subscribe(listener) {
481
- listeners.add(listener);
482
- return () => {
483
- listeners.delete(listener);
484
- };
485
- },
486
- close() {
487
- listeners.clear();
488
- if (channel) {
489
- channel.close();
490
- return;
491
- }
492
- window.removeEventListener("storage", onStorage);
493
- }
494
- };
495
- }
496
- //#endregion
497
- //#region src/json-deep-equal.ts
498
- function isPlainObject(value) {
499
- if (typeof value !== "object" || value === null) return false;
500
- const proto = Object.getPrototypeOf(value);
501
- return proto === Object.prototype || proto === null;
502
- }
503
- function equalArrays(a, b) {
504
- if (a.length !== b.length) return false;
505
- for (let i = 0; i < a.length; i += 1) if (!deepJsonEqual(a[i], b[i])) return false;
506
- return true;
507
- }
508
- function equalObjects(a, b) {
509
- const aKeys = Object.keys(a);
510
- const bKeys = Object.keys(b);
511
- if (aKeys.length !== bKeys.length) return false;
512
- for (const key of aKeys) if (!Object.hasOwn(b, key) || !deepJsonEqual(a[key], b[key])) return false;
513
- return true;
514
- }
515
- /** Structural equality for JSON-shaped values. */
516
- function deepJsonEqual(a, b) {
517
- if (Object.is(a, b)) return true;
518
- if (Array.isArray(a) && Array.isArray(b)) return equalArrays(a, b);
519
- if (!isPlainObject(a) || !isPlainObject(b)) return false;
520
- return equalObjects(a, b);
521
- }
522
- //#endregion
523
- //#region src/session-sync.ts
524
- const CHANNEL_NAME = "limen.session";
525
- const FOCUS_REFETCH_THROTTLE_MS = 5e3;
526
- function createSessionSync(store, options) {
527
- if (options.fetchOnMount) store.refetch();
528
- const teardowns = [];
529
- if (options.crossTabSync) teardowns.push(syncAcrossTabs(store));
530
- if (options.refetchOnWindowFocus) teardowns.push(refetchOnFocus(store));
531
- return () => {
532
- for (const teardown of teardowns) teardown();
533
- };
534
- }
535
- function syncAcrossTabs(store) {
536
- const port = createBroadcastChannel(CHANNEL_NAME);
537
- let lastData = store.$session.get().data;
538
- const unsubscribe = port.subscribe((message) => {
539
- lastData = message.data;
540
- store.setData(message.data);
541
- });
542
- const unbindNotify = onNotify(store.$session, () => {
543
- const data = store.$session.get().data;
544
- if (deepJsonEqual(data, lastData)) return;
545
- lastData = data;
546
- port.post({ data });
547
- });
548
- return () => {
549
- unbindNotify();
550
- unsubscribe();
551
- port.close();
552
- };
553
- }
554
- function refetchOnFocus(store) {
555
- if (typeof document === "undefined") return () => {};
556
- const onVisibilityChange = () => {
557
- if (document.visibilityState === "visible") store.refetch({
558
- maxAgeMs: FOCUS_REFETCH_THROTTLE_MS,
559
- skipSignedOut: true
560
- });
561
- };
562
- document.addEventListener("visibilitychange", onVisibilityChange);
563
- return () => document.removeEventListener("visibilitychange", onVisibilityChange);
564
- }
565
- //#endregion
566
- //#region src/session-store.ts
567
- function createSessionStore(options) {
568
- const $session = atom({
569
- data: options.initialSession ?? null,
570
- isPending: false,
571
- error: null
572
- });
573
- let inFlightHydration = null;
574
- let writeVersion = 0;
575
- let lastRefreshedAt = 0;
576
- const isStale = (requestVersion) => requestVersion !== writeVersion;
577
- const fetchSessionFromServer = async () => {
578
- const requestVersion = ++writeVersion;
579
- $session.set({
580
- data: $session.get().data,
581
- isPending: true,
582
- error: null
583
- });
584
- try {
585
- const session = await options.hydrator();
586
- if (isStale(requestVersion)) return;
587
- $session.set({
588
- data: session,
589
- isPending: false,
590
- error: null
591
- });
592
- } catch (err) {
593
- if (isStale(requestVersion)) return;
594
- if (err instanceof LimenError && err.isUnauthorized) {
595
- $session.set({
596
- data: null,
597
- isPending: false,
598
- error: null
599
- });
600
- return;
601
- }
602
- const error = err instanceof LimenError ? err : new LimenError(err instanceof Error ? err.message : "Failed to load session", 0, "unknown");
603
- $session.set({
604
- data: $session.get().data,
605
- isPending: false,
606
- error
607
- });
608
- }
609
- };
610
- const refetch = (options) => {
611
- const { skipSignedOut, maxAgeMs } = options ?? {};
612
- if (skipSignedOut && $session.get().data === null) return Promise.resolve();
613
- if (maxAgeMs !== void 0 && Date.now() - lastRefreshedAt < maxAgeMs) return Promise.resolve();
614
- if (!inFlightHydration) inFlightHydration = fetchSessionFromServer().finally(() => {
615
- inFlightHydration = null;
616
- lastRefreshedAt = Date.now();
617
- });
618
- return inFlightHydration;
619
- };
620
- const setData = (session) => {
621
- writeVersion++;
622
- $session.set({
623
- data: session,
624
- isPending: false,
625
- error: null
626
- });
627
- };
628
- const store = {
629
- $session,
630
- setData,
631
- refetch
632
- };
633
- onMount($session, () => createSessionSync(store, {
634
- fetchOnMount: options.initialSession === void 0,
635
- crossTabSync: options.crossTabSync ?? false,
636
- refetchOnWindowFocus: options.refetchOnWindowFocus ?? false
637
- }));
638
- return store;
639
- }
640
- //#endregion
641
- //#region src/client.ts
642
- function createAuthClient(opts) {
643
- const baseURL = stripTrailingSlash(opts.baseURL);
644
- const basePath = normalizeBasePath(opts.basePath ?? "/auth");
645
- const userPlugins = opts.plugins ?? [];
646
- const plugins = [coreClientPlugin(), ...userPlugins];
647
- const hooks = new HookRunner(plugins);
648
- const fetcher = buildFetcher(baseURL, basePath, {
649
- ...DEFAULT_ENVELOPE_CONFIG,
650
- ...opts.envelope
651
- }, hooks, opts.fetchOptions ?? {});
652
- const parseSession = opts.parseSession ?? defaultSessionParse;
653
- const redirect = resolveRedirect(opts.redirectFn);
654
- const baseFetch = (path, init) => fetcher.fetch(path, init);
655
- const store = createSessionStore({
656
- hydrator: createSessionHydrator({
657
- fetch: baseFetch,
658
- parseSession
659
- }),
660
- crossTabSync: opts.crossTabSync !== false,
661
- refetchOnWindowFocus: opts.refetchOnWindowFocus !== false,
662
- ...opts.initialSession !== void 0 ? { initialSession: opts.initialSession } : {}
663
- });
664
- return {
665
- baseURL,
666
- basePath,
667
- ...buildClientTree({
668
- plugins,
669
- ctx: {
670
- fetch: baseFetch,
671
- redirect,
672
- parseSession,
673
- setSession: (session) => store.setData(session),
674
- refetchSession: () => store.refetch(),
675
- store
676
- },
677
- fetcher,
678
- overrides: opts.overrides
679
- }),
680
- $session: store.$session
681
- };
682
- }
683
- function buildFetcher(baseURL, basePath, envelope, hooks, fetchOptions) {
684
- return new Fetcher({
685
- baseURL,
686
- basePath,
687
- envelope,
688
- hooks,
689
- fetchOptions
690
- });
691
- }
692
- function resolveRedirect(redirect) {
693
- return (url) => {
694
- if (redirect !== void 0) return redirect(url);
695
- if (typeof window !== "undefined" && typeof window.location !== "undefined") {
696
- window.location.href = url;
697
- return true;
698
- }
699
- return false;
700
- };
701
- }
702
- //#endregion
703
- export { defaultSerialize as a, normalizeUser as i, coreClientPlugin as n, defaultSessionParse as r, createAuthClient as t };
704
-
705
- //# sourceMappingURL=client-Er91De-z.mjs.map