@onjunctiondev/auth 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Junction
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,58 @@
1
- # Temporary Holding Version
1
+ # @onjunctiondev/auth
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Headless auth SDK for [Junction](https://onjunction.dev). Email/password
4
+ sign-up, sign-in, sessions, and token refresh — talks to a per-project
5
+ Junction auth service.
6
+
7
+ Zero UI. Bring your own form.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @onjunctiondev/auth
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { createJunctionClient } from "@onjunctiondev/auth";
19
+
20
+ const junction = createJunctionClient({
21
+ url: process.env.JUNCTION_URL!, // e.g. https://abc123.onjunction.dev
22
+ });
23
+
24
+ // All methods return { data, error } — they never throw across the API
25
+ // boundary.
26
+ const { data, error } = await junction.auth.signUp({
27
+ email: "jane@example.com",
28
+ password: "correct-horse-battery-staple",
29
+ });
30
+
31
+ if (error) {
32
+ console.error(error.message);
33
+ } else {
34
+ console.log("signed up:", data.session.user.id);
35
+ }
36
+ ```
37
+
38
+ ## Methods
39
+
40
+ ```ts
41
+ junction.auth.signUp({ email, password });
42
+ junction.auth.signIn({ email, password });
43
+ junction.auth.signOut();
44
+ junction.auth.getSession();
45
+ junction.auth.getUser();
46
+ junction.auth.refreshSession();
47
+ junction.auth.onAuthStateChange((event, session) => { /* ... */ });
48
+ ```
49
+
50
+ ## Status
51
+
52
+ Slice 1 — core client only. React hooks (`/react`) and Next.js server
53
+ helpers (`/server`) throw a clear "coming soon" error on import; they
54
+ ship with slices 2 and 3.
55
+
56
+ ## License
57
+
58
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ AuthClient: () => AuthClient,
24
+ createJunctionClient: () => createJunctionClient
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+
28
+ // src/storage.ts
29
+ function memoryStorage() {
30
+ let session = null;
31
+ return {
32
+ get: () => session,
33
+ set: (s) => {
34
+ session = s;
35
+ }
36
+ };
37
+ }
38
+ function localStorageStorage(key) {
39
+ const canUse = typeof globalThis !== "undefined" && "localStorage" in globalThis;
40
+ return {
41
+ get() {
42
+ if (!canUse) return null;
43
+ try {
44
+ const raw = globalThis.localStorage.getItem(key);
45
+ if (!raw) return null;
46
+ return JSON.parse(raw);
47
+ } catch {
48
+ return null;
49
+ }
50
+ },
51
+ set(session) {
52
+ if (!canUse) return;
53
+ try {
54
+ if (session) {
55
+ globalThis.localStorage.setItem(key, JSON.stringify(session));
56
+ } else {
57
+ globalThis.localStorage.removeItem(key);
58
+ }
59
+ } catch {
60
+ }
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/client.ts
66
+ var DEFAULT_STORAGE_KEY = "junction.auth.session";
67
+ function pickStorage(opts) {
68
+ const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
69
+ if (opts.storage === "memory") return memoryStorage();
70
+ if (typeof globalThis !== "undefined" && "localStorage" in globalThis) {
71
+ return localStorageStorage(key);
72
+ }
73
+ return memoryStorage();
74
+ }
75
+ var AuthClient = class {
76
+ #deps;
77
+ #listeners = /* @__PURE__ */ new Set();
78
+ // Refresh in-flight — concurrent 401s share one refresh so we don't
79
+ // race-rotate the single-use token on the server.
80
+ #inflightRefresh = null;
81
+ constructor(opts) {
82
+ this.#deps = {
83
+ url: opts.url.replace(/\/+$/, ""),
84
+ fetchFn: opts.fetch ?? ((...args) => fetch(...args)),
85
+ storage: pickStorage(opts)
86
+ };
87
+ }
88
+ // --- public API --------------------------------------------------------
89
+ async signUp(credentials) {
90
+ return this.#authCall("/auth/v1/signup", credentials, "SIGNED_IN");
91
+ }
92
+ async signIn(credentials) {
93
+ return this.#authCall(
94
+ "/auth/v1/token?grant_type=password",
95
+ credentials,
96
+ "SIGNED_IN"
97
+ );
98
+ }
99
+ async signOut() {
100
+ const session = this.#deps.storage.get();
101
+ this.#deps.storage.set(null);
102
+ this.#emit("SIGNED_OUT", null);
103
+ if (!session) return { data: null, error: null };
104
+ const res = await this.#rawFetch("/auth/v1/logout", {
105
+ method: "POST",
106
+ headers: { authorization: `Bearer ${session.access_token}` }
107
+ }).catch(() => null);
108
+ if (!res) {
109
+ return { data: null, error: null };
110
+ }
111
+ if (!res.ok && res.status !== 401) {
112
+ return { data: null, error: await parseError(res) };
113
+ }
114
+ return { data: null, error: null };
115
+ }
116
+ async refreshSession() {
117
+ if (this.#inflightRefresh) return this.#inflightRefresh;
118
+ const session = this.#deps.storage.get();
119
+ if (!session) {
120
+ return { data: null, error: errorFrom("no_session", "no session to refresh", 400) };
121
+ }
122
+ this.#inflightRefresh = this.#authCall(
123
+ "/auth/v1/token?grant_type=refresh_token",
124
+ { refresh_token: session.refresh_token },
125
+ "TOKEN_REFRESHED"
126
+ ).finally(() => {
127
+ this.#inflightRefresh = null;
128
+ });
129
+ return this.#inflightRefresh;
130
+ }
131
+ getSession() {
132
+ return { data: { session: this.#deps.storage.get() }, error: null };
133
+ }
134
+ async getUser() {
135
+ const session = this.#deps.storage.get();
136
+ if (!session) return { data: { user: null }, error: null };
137
+ const res = await this.#authedFetch("/auth/v1/user", { method: "GET" });
138
+ if (res.error) return { data: null, error: res.error };
139
+ return { data: { user: res.data }, error: null };
140
+ }
141
+ onAuthStateChange(handler) {
142
+ this.#listeners.add(handler);
143
+ try {
144
+ const session = this.#deps.storage.get();
145
+ handler(session ? "SIGNED_IN" : "SIGNED_OUT", session);
146
+ } catch {
147
+ }
148
+ return {
149
+ unsubscribe: () => {
150
+ this.#listeners.delete(handler);
151
+ }
152
+ };
153
+ }
154
+ // --- internals ---------------------------------------------------------
155
+ async #authCall(path, body, event) {
156
+ const res = await this.#rawFetch(path, {
157
+ method: "POST",
158
+ headers: { "content-type": "application/json" },
159
+ body: JSON.stringify(body)
160
+ }).catch(() => null);
161
+ if (!res) {
162
+ return { data: null, error: errorFrom("network_error", "network error", 0) };
163
+ }
164
+ if (!res.ok) return { data: null, error: await parseError(res) };
165
+ const raw = await res.json();
166
+ const session = {
167
+ access_token: raw.access_token,
168
+ refresh_token: raw.refresh_token,
169
+ token_type: raw.token_type,
170
+ expires_in: raw.expires_in,
171
+ expires_at: raw.expires_at,
172
+ user: raw.user
173
+ };
174
+ this.#deps.storage.set(session);
175
+ this.#emit(event, session);
176
+ return { data: session, error: null };
177
+ }
178
+ // Fetches a protected endpoint, auto-refreshing once on 401. We don't
179
+ // loop — a second 401 after a successful refresh means the user is
180
+ // genuinely signed out, propagate as such.
181
+ async #authedFetch(path, init) {
182
+ const session = this.#deps.storage.get();
183
+ if (!session) return { data: null, error: errorFrom("no_session", "no session", 401) };
184
+ const headers = new Headers(init.headers);
185
+ headers.set("authorization", `Bearer ${session.access_token}`);
186
+ let res = await this.#rawFetch(path, { ...init, headers });
187
+ if (res.status === 401) {
188
+ const refreshed = await this.refreshSession();
189
+ if (refreshed.error) {
190
+ this.#deps.storage.set(null);
191
+ this.#emit("SIGNED_OUT", null);
192
+ return { data: null, error: refreshed.error };
193
+ }
194
+ headers.set("authorization", `Bearer ${refreshed.data.access_token}`);
195
+ res = await this.#rawFetch(path, { ...init, headers });
196
+ }
197
+ if (!res.ok) return { data: null, error: await parseError(res) };
198
+ const body = res.status === 204 ? null : await res.json().catch(() => null);
199
+ return { data: body, error: null };
200
+ }
201
+ async #rawFetch(path, init) {
202
+ return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);
203
+ }
204
+ #emit(event, session) {
205
+ for (const l of this.#listeners) {
206
+ try {
207
+ l(event, session);
208
+ } catch {
209
+ }
210
+ }
211
+ }
212
+ };
213
+ function errorFrom(code, message, status) {
214
+ return { code, message, status };
215
+ }
216
+ async function parseError(res) {
217
+ try {
218
+ const body = await res.json();
219
+ const message = body?.error?.message ?? res.statusText ?? "request failed";
220
+ const code = body?.error?.code ?? "error";
221
+ return { code, message, status: res.status };
222
+ } catch {
223
+ return { code: "error", message: res.statusText || "request failed", status: res.status };
224
+ }
225
+ }
226
+
227
+ // src/index.ts
228
+ function createJunctionClient(options) {
229
+ return { auth: new AuthClient(options) };
230
+ }
231
+ // Annotate the CommonJS export names for ESM import in node:
232
+ 0 && (module.exports = {
233
+ AuthClient,
234
+ createJunctionClient
235
+ });
236
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["// Entry point for the core SDK — browser, Node, and Edge-compatible.\nimport { AuthClient } from \"./client.js\";\nimport type { ClientOptions } from \"./types.js\";\n\nexport type JunctionClient = {\n auth: AuthClient;\n};\n\nexport function createJunctionClient(options: ClientOptions): JunctionClient {\n return { auth: new AuthClient(options) };\n}\n\nexport type {\n AuthError,\n AuthEvent,\n AuthSession,\n AuthStateChangeHandler,\n AuthUser,\n ClientOptions,\n Result,\n} from \"./types.js\";\nexport { AuthClient } from \"./client.js\";\n","// Pluggable session storage. We pick an adapter at client creation and\n// never touch `window.localStorage` directly from the client itself —\n// that lets the same client run in Node (tests), the browser, and\n// non-DOM runtimes without conditional branches littered through the\n// request path.\nimport type { AuthSession } from \"./types.js\";\n\nexport interface SessionStorage {\n get(): AuthSession | null;\n set(session: AuthSession | null): void;\n}\n\nexport function memoryStorage(): SessionStorage {\n let session: AuthSession | null = null;\n return {\n get: () => session,\n set: (s) => {\n session = s;\n },\n };\n}\n\nexport function localStorageStorage(key: string): SessionStorage {\n const canUse = typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis;\n return {\n get() {\n if (!canUse) return null;\n try {\n const raw = globalThis.localStorage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as AuthSession;\n } catch {\n return null;\n }\n },\n set(session) {\n if (!canUse) return;\n try {\n if (session) {\n globalThis.localStorage.setItem(key, JSON.stringify(session));\n } else {\n globalThis.localStorage.removeItem(key);\n }\n } catch {\n // storage can throw (quota, private mode); ignore silently\n }\n },\n };\n}\n","// Core Junction auth client. Zero UI; typed functions over `fetch` with\n// session caching, auth state subscribers, and an automatic refresh-on-401\n// interceptor. Mirrors the GoTrue surface so consumers can port familiar\n// patterns without translation.\n//\n// Shape of every op: `{ data, error }`. We never throw across the API\n// boundary — a network failure, a bad credential, or a server error all\n// surface as `error.code` strings callers can switch on.\nimport type {\n AuthError,\n AuthEvent,\n AuthSession,\n AuthStateChangeHandler,\n AuthUser,\n ClientOptions,\n Result,\n} from \"./types.js\";\nimport { localStorageStorage, memoryStorage, type SessionStorage } from \"./storage.js\";\n\nconst DEFAULT_STORAGE_KEY = \"junction.auth.session\";\n\ntype RawAuthResponse = {\n access_token: string;\n refresh_token: string;\n token_type: \"bearer\";\n expires_in: number;\n expires_at: number;\n user: AuthUser;\n};\n\nfunction pickStorage(opts: ClientOptions): SessionStorage {\n const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;\n if (opts.storage === \"memory\") return memoryStorage();\n // Default: localStorage in the browser, memory elsewhere.\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n return localStorageStorage(key);\n }\n return memoryStorage();\n}\n\ntype Deps = {\n url: string;\n fetchFn: typeof fetch;\n storage: SessionStorage;\n};\n\nexport class AuthClient {\n readonly #deps: Deps;\n readonly #listeners = new Set<AuthStateChangeHandler>();\n // Refresh in-flight — concurrent 401s share one refresh so we don't\n // race-rotate the single-use token on the server.\n #inflightRefresh: Promise<Result<AuthSession>> | null = null;\n\n constructor(opts: ClientOptions) {\n this.#deps = {\n url: opts.url.replace(/\\/+$/, \"\"),\n fetchFn: opts.fetch ?? ((...args) => fetch(...args)),\n storage: pickStorage(opts),\n };\n }\n\n // --- public API --------------------------------------------------------\n\n async signUp(credentials: {\n email: string;\n password: string;\n }): Promise<Result<AuthSession>> {\n return this.#authCall(\"/auth/v1/signup\", credentials, \"SIGNED_IN\");\n }\n\n async signIn(credentials: {\n email: string;\n password: string;\n }): Promise<Result<AuthSession>> {\n return this.#authCall(\n \"/auth/v1/token?grant_type=password\",\n credentials,\n \"SIGNED_IN\",\n );\n }\n\n async signOut(): Promise<Result<null>> {\n const session = this.#deps.storage.get();\n this.#deps.storage.set(null);\n this.#emit(\"SIGNED_OUT\", null);\n if (!session) return { data: null, error: null };\n const res = await this.#rawFetch(\"/auth/v1/logout\", {\n method: \"POST\",\n headers: { authorization: `Bearer ${session.access_token}` },\n }).catch(() => null);\n if (!res) {\n // Signed out locally regardless of upstream reachability — the UX\n // is a sign-out, not \"sign out iff the network cooperates\".\n return { data: null, error: null };\n }\n if (!res.ok && res.status !== 401) {\n return { data: null, error: await parseError(res) };\n }\n return { data: null, error: null };\n }\n\n async refreshSession(): Promise<Result<AuthSession>> {\n if (this.#inflightRefresh) return this.#inflightRefresh;\n const session = this.#deps.storage.get();\n if (!session) {\n return { data: null, error: errorFrom(\"no_session\", \"no session to refresh\", 400) };\n }\n this.#inflightRefresh = this.#authCall(\n \"/auth/v1/token?grant_type=refresh_token\",\n { refresh_token: session.refresh_token },\n \"TOKEN_REFRESHED\",\n ).finally(() => {\n this.#inflightRefresh = null;\n });\n return this.#inflightRefresh;\n }\n\n getSession(): Result<{ session: AuthSession | null }> {\n return { data: { session: this.#deps.storage.get() }, error: null };\n }\n\n async getUser(): Promise<Result<{ user: AuthUser | null }>> {\n const session = this.#deps.storage.get();\n if (!session) return { data: { user: null }, error: null };\n const res = await this.#authedFetch(\"/auth/v1/user\", { method: \"GET\" });\n if (res.error) return { data: null, error: res.error };\n return { data: { user: res.data as AuthUser }, error: null };\n }\n\n onAuthStateChange(handler: AuthStateChangeHandler): { unsubscribe: () => void } {\n this.#listeners.add(handler);\n // Fire initial state synchronously so subscribers get the current\n // session without an extra tick.\n try {\n const session = this.#deps.storage.get();\n handler(session ? \"SIGNED_IN\" : \"SIGNED_OUT\", session);\n } catch {\n // ignore subscriber errors\n }\n return {\n unsubscribe: () => {\n this.#listeners.delete(handler);\n },\n };\n }\n\n // --- internals ---------------------------------------------------------\n\n async #authCall(\n path: string,\n body: unknown,\n event: AuthEvent,\n ): Promise<Result<AuthSession>> {\n const res = await this.#rawFetch(path, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n }).catch(() => null);\n if (!res) {\n return { data: null, error: errorFrom(\"network_error\", \"network error\", 0) };\n }\n if (!res.ok) return { data: null, error: await parseError(res) };\n const raw = (await res.json()) as RawAuthResponse;\n const session: AuthSession = {\n access_token: raw.access_token,\n refresh_token: raw.refresh_token,\n token_type: raw.token_type,\n expires_in: raw.expires_in,\n expires_at: raw.expires_at,\n user: raw.user,\n };\n this.#deps.storage.set(session);\n this.#emit(event, session);\n return { data: session, error: null };\n }\n\n // Fetches a protected endpoint, auto-refreshing once on 401. We don't\n // loop — a second 401 after a successful refresh means the user is\n // genuinely signed out, propagate as such.\n async #authedFetch(path: string, init: RequestInit): Promise<Result<unknown>> {\n const session = this.#deps.storage.get();\n if (!session) return { data: null, error: errorFrom(\"no_session\", \"no session\", 401) };\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `Bearer ${session.access_token}`);\n let res = await this.#rawFetch(path, { ...init, headers });\n if (res.status === 401) {\n const refreshed = await this.refreshSession();\n if (refreshed.error) {\n this.#deps.storage.set(null);\n this.#emit(\"SIGNED_OUT\", null);\n return { data: null, error: refreshed.error };\n }\n headers.set(\"authorization\", `Bearer ${refreshed.data.access_token}`);\n res = await this.#rawFetch(path, { ...init, headers });\n }\n if (!res.ok) return { data: null, error: await parseError(res) };\n const body = res.status === 204 ? null : await res.json().catch(() => null);\n return { data: body, error: null };\n }\n\n async #rawFetch(path: string, init: RequestInit): Promise<Response> {\n return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);\n }\n\n #emit(event: AuthEvent, session: AuthSession | null): void {\n for (const l of this.#listeners) {\n try {\n l(event, session);\n } catch {\n // isolate subscribers from each other\n }\n }\n }\n}\n\nfunction errorFrom(code: string, message: string, status: number): AuthError {\n return { code, message, status };\n}\n\nasync function parseError(res: Response): Promise<AuthError> {\n try {\n const body = (await res.json()) as { error?: { message?: string; code?: string } };\n const message = body?.error?.message ?? res.statusText ?? \"request failed\";\n const code = body?.error?.code ?? \"error\";\n return { code, message, status: res.status };\n } catch {\n return { code: \"error\", message: res.statusText || \"request failed\", status: res.status };\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,69 @@
1
+ type AuthUser = {
2
+ id: string;
3
+ email: string;
4
+ email_confirmed_at: string | null;
5
+ created_at: string;
6
+ updated_at: string;
7
+ last_sign_in_at: string | null;
8
+ app_metadata: Record<string, unknown>;
9
+ user_metadata: Record<string, unknown>;
10
+ };
11
+ type AuthSession = {
12
+ access_token: string;
13
+ refresh_token: string;
14
+ token_type: "bearer";
15
+ expires_in: number;
16
+ expires_at: number;
17
+ user: AuthUser;
18
+ };
19
+ type AuthError = {
20
+ message: string;
21
+ code: string;
22
+ status: number;
23
+ };
24
+ type Result<T> = {
25
+ data: T;
26
+ error: null;
27
+ } | {
28
+ data: null;
29
+ error: AuthError;
30
+ };
31
+ type AuthEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED";
32
+ type AuthStateChangeHandler = (event: AuthEvent, session: AuthSession | null) => void;
33
+ type ClientOptions = {
34
+ url: string;
35
+ storage?: "localStorage" | "memory";
36
+ storageKey?: string;
37
+ fetch?: typeof fetch;
38
+ };
39
+
40
+ declare class AuthClient {
41
+ #private;
42
+ constructor(opts: ClientOptions);
43
+ signUp(credentials: {
44
+ email: string;
45
+ password: string;
46
+ }): Promise<Result<AuthSession>>;
47
+ signIn(credentials: {
48
+ email: string;
49
+ password: string;
50
+ }): Promise<Result<AuthSession>>;
51
+ signOut(): Promise<Result<null>>;
52
+ refreshSession(): Promise<Result<AuthSession>>;
53
+ getSession(): Result<{
54
+ session: AuthSession | null;
55
+ }>;
56
+ getUser(): Promise<Result<{
57
+ user: AuthUser | null;
58
+ }>>;
59
+ onAuthStateChange(handler: AuthStateChangeHandler): {
60
+ unsubscribe: () => void;
61
+ };
62
+ }
63
+
64
+ type JunctionClient = {
65
+ auth: AuthClient;
66
+ };
67
+ declare function createJunctionClient(options: ClientOptions): JunctionClient;
68
+
69
+ export { AuthClient, type AuthError, type AuthEvent, type AuthSession, type AuthStateChangeHandler, type AuthUser, type ClientOptions, type JunctionClient, type Result, createJunctionClient };
@@ -0,0 +1,69 @@
1
+ type AuthUser = {
2
+ id: string;
3
+ email: string;
4
+ email_confirmed_at: string | null;
5
+ created_at: string;
6
+ updated_at: string;
7
+ last_sign_in_at: string | null;
8
+ app_metadata: Record<string, unknown>;
9
+ user_metadata: Record<string, unknown>;
10
+ };
11
+ type AuthSession = {
12
+ access_token: string;
13
+ refresh_token: string;
14
+ token_type: "bearer";
15
+ expires_in: number;
16
+ expires_at: number;
17
+ user: AuthUser;
18
+ };
19
+ type AuthError = {
20
+ message: string;
21
+ code: string;
22
+ status: number;
23
+ };
24
+ type Result<T> = {
25
+ data: T;
26
+ error: null;
27
+ } | {
28
+ data: null;
29
+ error: AuthError;
30
+ };
31
+ type AuthEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED";
32
+ type AuthStateChangeHandler = (event: AuthEvent, session: AuthSession | null) => void;
33
+ type ClientOptions = {
34
+ url: string;
35
+ storage?: "localStorage" | "memory";
36
+ storageKey?: string;
37
+ fetch?: typeof fetch;
38
+ };
39
+
40
+ declare class AuthClient {
41
+ #private;
42
+ constructor(opts: ClientOptions);
43
+ signUp(credentials: {
44
+ email: string;
45
+ password: string;
46
+ }): Promise<Result<AuthSession>>;
47
+ signIn(credentials: {
48
+ email: string;
49
+ password: string;
50
+ }): Promise<Result<AuthSession>>;
51
+ signOut(): Promise<Result<null>>;
52
+ refreshSession(): Promise<Result<AuthSession>>;
53
+ getSession(): Result<{
54
+ session: AuthSession | null;
55
+ }>;
56
+ getUser(): Promise<Result<{
57
+ user: AuthUser | null;
58
+ }>>;
59
+ onAuthStateChange(handler: AuthStateChangeHandler): {
60
+ unsubscribe: () => void;
61
+ };
62
+ }
63
+
64
+ type JunctionClient = {
65
+ auth: AuthClient;
66
+ };
67
+ declare function createJunctionClient(options: ClientOptions): JunctionClient;
68
+
69
+ export { AuthClient, type AuthError, type AuthEvent, type AuthSession, type AuthStateChangeHandler, type AuthUser, type ClientOptions, type JunctionClient, type Result, createJunctionClient };
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ // src/storage.ts
2
+ function memoryStorage() {
3
+ let session = null;
4
+ return {
5
+ get: () => session,
6
+ set: (s) => {
7
+ session = s;
8
+ }
9
+ };
10
+ }
11
+ function localStorageStorage(key) {
12
+ const canUse = typeof globalThis !== "undefined" && "localStorage" in globalThis;
13
+ return {
14
+ get() {
15
+ if (!canUse) return null;
16
+ try {
17
+ const raw = globalThis.localStorage.getItem(key);
18
+ if (!raw) return null;
19
+ return JSON.parse(raw);
20
+ } catch {
21
+ return null;
22
+ }
23
+ },
24
+ set(session) {
25
+ if (!canUse) return;
26
+ try {
27
+ if (session) {
28
+ globalThis.localStorage.setItem(key, JSON.stringify(session));
29
+ } else {
30
+ globalThis.localStorage.removeItem(key);
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+ };
36
+ }
37
+
38
+ // src/client.ts
39
+ var DEFAULT_STORAGE_KEY = "junction.auth.session";
40
+ function pickStorage(opts) {
41
+ const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
42
+ if (opts.storage === "memory") return memoryStorage();
43
+ if (typeof globalThis !== "undefined" && "localStorage" in globalThis) {
44
+ return localStorageStorage(key);
45
+ }
46
+ return memoryStorage();
47
+ }
48
+ var AuthClient = class {
49
+ #deps;
50
+ #listeners = /* @__PURE__ */ new Set();
51
+ // Refresh in-flight — concurrent 401s share one refresh so we don't
52
+ // race-rotate the single-use token on the server.
53
+ #inflightRefresh = null;
54
+ constructor(opts) {
55
+ this.#deps = {
56
+ url: opts.url.replace(/\/+$/, ""),
57
+ fetchFn: opts.fetch ?? ((...args) => fetch(...args)),
58
+ storage: pickStorage(opts)
59
+ };
60
+ }
61
+ // --- public API --------------------------------------------------------
62
+ async signUp(credentials) {
63
+ return this.#authCall("/auth/v1/signup", credentials, "SIGNED_IN");
64
+ }
65
+ async signIn(credentials) {
66
+ return this.#authCall(
67
+ "/auth/v1/token?grant_type=password",
68
+ credentials,
69
+ "SIGNED_IN"
70
+ );
71
+ }
72
+ async signOut() {
73
+ const session = this.#deps.storage.get();
74
+ this.#deps.storage.set(null);
75
+ this.#emit("SIGNED_OUT", null);
76
+ if (!session) return { data: null, error: null };
77
+ const res = await this.#rawFetch("/auth/v1/logout", {
78
+ method: "POST",
79
+ headers: { authorization: `Bearer ${session.access_token}` }
80
+ }).catch(() => null);
81
+ if (!res) {
82
+ return { data: null, error: null };
83
+ }
84
+ if (!res.ok && res.status !== 401) {
85
+ return { data: null, error: await parseError(res) };
86
+ }
87
+ return { data: null, error: null };
88
+ }
89
+ async refreshSession() {
90
+ if (this.#inflightRefresh) return this.#inflightRefresh;
91
+ const session = this.#deps.storage.get();
92
+ if (!session) {
93
+ return { data: null, error: errorFrom("no_session", "no session to refresh", 400) };
94
+ }
95
+ this.#inflightRefresh = this.#authCall(
96
+ "/auth/v1/token?grant_type=refresh_token",
97
+ { refresh_token: session.refresh_token },
98
+ "TOKEN_REFRESHED"
99
+ ).finally(() => {
100
+ this.#inflightRefresh = null;
101
+ });
102
+ return this.#inflightRefresh;
103
+ }
104
+ getSession() {
105
+ return { data: { session: this.#deps.storage.get() }, error: null };
106
+ }
107
+ async getUser() {
108
+ const session = this.#deps.storage.get();
109
+ if (!session) return { data: { user: null }, error: null };
110
+ const res = await this.#authedFetch("/auth/v1/user", { method: "GET" });
111
+ if (res.error) return { data: null, error: res.error };
112
+ return { data: { user: res.data }, error: null };
113
+ }
114
+ onAuthStateChange(handler) {
115
+ this.#listeners.add(handler);
116
+ try {
117
+ const session = this.#deps.storage.get();
118
+ handler(session ? "SIGNED_IN" : "SIGNED_OUT", session);
119
+ } catch {
120
+ }
121
+ return {
122
+ unsubscribe: () => {
123
+ this.#listeners.delete(handler);
124
+ }
125
+ };
126
+ }
127
+ // --- internals ---------------------------------------------------------
128
+ async #authCall(path, body, event) {
129
+ const res = await this.#rawFetch(path, {
130
+ method: "POST",
131
+ headers: { "content-type": "application/json" },
132
+ body: JSON.stringify(body)
133
+ }).catch(() => null);
134
+ if (!res) {
135
+ return { data: null, error: errorFrom("network_error", "network error", 0) };
136
+ }
137
+ if (!res.ok) return { data: null, error: await parseError(res) };
138
+ const raw = await res.json();
139
+ const session = {
140
+ access_token: raw.access_token,
141
+ refresh_token: raw.refresh_token,
142
+ token_type: raw.token_type,
143
+ expires_in: raw.expires_in,
144
+ expires_at: raw.expires_at,
145
+ user: raw.user
146
+ };
147
+ this.#deps.storage.set(session);
148
+ this.#emit(event, session);
149
+ return { data: session, error: null };
150
+ }
151
+ // Fetches a protected endpoint, auto-refreshing once on 401. We don't
152
+ // loop — a second 401 after a successful refresh means the user is
153
+ // genuinely signed out, propagate as such.
154
+ async #authedFetch(path, init) {
155
+ const session = this.#deps.storage.get();
156
+ if (!session) return { data: null, error: errorFrom("no_session", "no session", 401) };
157
+ const headers = new Headers(init.headers);
158
+ headers.set("authorization", `Bearer ${session.access_token}`);
159
+ let res = await this.#rawFetch(path, { ...init, headers });
160
+ if (res.status === 401) {
161
+ const refreshed = await this.refreshSession();
162
+ if (refreshed.error) {
163
+ this.#deps.storage.set(null);
164
+ this.#emit("SIGNED_OUT", null);
165
+ return { data: null, error: refreshed.error };
166
+ }
167
+ headers.set("authorization", `Bearer ${refreshed.data.access_token}`);
168
+ res = await this.#rawFetch(path, { ...init, headers });
169
+ }
170
+ if (!res.ok) return { data: null, error: await parseError(res) };
171
+ const body = res.status === 204 ? null : await res.json().catch(() => null);
172
+ return { data: body, error: null };
173
+ }
174
+ async #rawFetch(path, init) {
175
+ return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);
176
+ }
177
+ #emit(event, session) {
178
+ for (const l of this.#listeners) {
179
+ try {
180
+ l(event, session);
181
+ } catch {
182
+ }
183
+ }
184
+ }
185
+ };
186
+ function errorFrom(code, message, status) {
187
+ return { code, message, status };
188
+ }
189
+ async function parseError(res) {
190
+ try {
191
+ const body = await res.json();
192
+ const message = body?.error?.message ?? res.statusText ?? "request failed";
193
+ const code = body?.error?.code ?? "error";
194
+ return { code, message, status: res.status };
195
+ } catch {
196
+ return { code: "error", message: res.statusText || "request failed", status: res.status };
197
+ }
198
+ }
199
+
200
+ // src/index.ts
201
+ function createJunctionClient(options) {
202
+ return { auth: new AuthClient(options) };
203
+ }
204
+ export {
205
+ AuthClient,
206
+ createJunctionClient
207
+ };
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["// Pluggable session storage. We pick an adapter at client creation and\n// never touch `window.localStorage` directly from the client itself —\n// that lets the same client run in Node (tests), the browser, and\n// non-DOM runtimes without conditional branches littered through the\n// request path.\nimport type { AuthSession } from \"./types.js\";\n\nexport interface SessionStorage {\n get(): AuthSession | null;\n set(session: AuthSession | null): void;\n}\n\nexport function memoryStorage(): SessionStorage {\n let session: AuthSession | null = null;\n return {\n get: () => session,\n set: (s) => {\n session = s;\n },\n };\n}\n\nexport function localStorageStorage(key: string): SessionStorage {\n const canUse = typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis;\n return {\n get() {\n if (!canUse) return null;\n try {\n const raw = globalThis.localStorage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as AuthSession;\n } catch {\n return null;\n }\n },\n set(session) {\n if (!canUse) return;\n try {\n if (session) {\n globalThis.localStorage.setItem(key, JSON.stringify(session));\n } else {\n globalThis.localStorage.removeItem(key);\n }\n } catch {\n // storage can throw (quota, private mode); ignore silently\n }\n },\n };\n}\n","// Core Junction auth client. Zero UI; typed functions over `fetch` with\n// session caching, auth state subscribers, and an automatic refresh-on-401\n// interceptor. Mirrors the GoTrue surface so consumers can port familiar\n// patterns without translation.\n//\n// Shape of every op: `{ data, error }`. We never throw across the API\n// boundary — a network failure, a bad credential, or a server error all\n// surface as `error.code` strings callers can switch on.\nimport type {\n AuthError,\n AuthEvent,\n AuthSession,\n AuthStateChangeHandler,\n AuthUser,\n ClientOptions,\n Result,\n} from \"./types.js\";\nimport { localStorageStorage, memoryStorage, type SessionStorage } from \"./storage.js\";\n\nconst DEFAULT_STORAGE_KEY = \"junction.auth.session\";\n\ntype RawAuthResponse = {\n access_token: string;\n refresh_token: string;\n token_type: \"bearer\";\n expires_in: number;\n expires_at: number;\n user: AuthUser;\n};\n\nfunction pickStorage(opts: ClientOptions): SessionStorage {\n const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;\n if (opts.storage === \"memory\") return memoryStorage();\n // Default: localStorage in the browser, memory elsewhere.\n if (typeof globalThis !== \"undefined\" && \"localStorage\" in globalThis) {\n return localStorageStorage(key);\n }\n return memoryStorage();\n}\n\ntype Deps = {\n url: string;\n fetchFn: typeof fetch;\n storage: SessionStorage;\n};\n\nexport class AuthClient {\n readonly #deps: Deps;\n readonly #listeners = new Set<AuthStateChangeHandler>();\n // Refresh in-flight — concurrent 401s share one refresh so we don't\n // race-rotate the single-use token on the server.\n #inflightRefresh: Promise<Result<AuthSession>> | null = null;\n\n constructor(opts: ClientOptions) {\n this.#deps = {\n url: opts.url.replace(/\\/+$/, \"\"),\n fetchFn: opts.fetch ?? ((...args) => fetch(...args)),\n storage: pickStorage(opts),\n };\n }\n\n // --- public API --------------------------------------------------------\n\n async signUp(credentials: {\n email: string;\n password: string;\n }): Promise<Result<AuthSession>> {\n return this.#authCall(\"/auth/v1/signup\", credentials, \"SIGNED_IN\");\n }\n\n async signIn(credentials: {\n email: string;\n password: string;\n }): Promise<Result<AuthSession>> {\n return this.#authCall(\n \"/auth/v1/token?grant_type=password\",\n credentials,\n \"SIGNED_IN\",\n );\n }\n\n async signOut(): Promise<Result<null>> {\n const session = this.#deps.storage.get();\n this.#deps.storage.set(null);\n this.#emit(\"SIGNED_OUT\", null);\n if (!session) return { data: null, error: null };\n const res = await this.#rawFetch(\"/auth/v1/logout\", {\n method: \"POST\",\n headers: { authorization: `Bearer ${session.access_token}` },\n }).catch(() => null);\n if (!res) {\n // Signed out locally regardless of upstream reachability — the UX\n // is a sign-out, not \"sign out iff the network cooperates\".\n return { data: null, error: null };\n }\n if (!res.ok && res.status !== 401) {\n return { data: null, error: await parseError(res) };\n }\n return { data: null, error: null };\n }\n\n async refreshSession(): Promise<Result<AuthSession>> {\n if (this.#inflightRefresh) return this.#inflightRefresh;\n const session = this.#deps.storage.get();\n if (!session) {\n return { data: null, error: errorFrom(\"no_session\", \"no session to refresh\", 400) };\n }\n this.#inflightRefresh = this.#authCall(\n \"/auth/v1/token?grant_type=refresh_token\",\n { refresh_token: session.refresh_token },\n \"TOKEN_REFRESHED\",\n ).finally(() => {\n this.#inflightRefresh = null;\n });\n return this.#inflightRefresh;\n }\n\n getSession(): Result<{ session: AuthSession | null }> {\n return { data: { session: this.#deps.storage.get() }, error: null };\n }\n\n async getUser(): Promise<Result<{ user: AuthUser | null }>> {\n const session = this.#deps.storage.get();\n if (!session) return { data: { user: null }, error: null };\n const res = await this.#authedFetch(\"/auth/v1/user\", { method: \"GET\" });\n if (res.error) return { data: null, error: res.error };\n return { data: { user: res.data as AuthUser }, error: null };\n }\n\n onAuthStateChange(handler: AuthStateChangeHandler): { unsubscribe: () => void } {\n this.#listeners.add(handler);\n // Fire initial state synchronously so subscribers get the current\n // session without an extra tick.\n try {\n const session = this.#deps.storage.get();\n handler(session ? \"SIGNED_IN\" : \"SIGNED_OUT\", session);\n } catch {\n // ignore subscriber errors\n }\n return {\n unsubscribe: () => {\n this.#listeners.delete(handler);\n },\n };\n }\n\n // --- internals ---------------------------------------------------------\n\n async #authCall(\n path: string,\n body: unknown,\n event: AuthEvent,\n ): Promise<Result<AuthSession>> {\n const res = await this.#rawFetch(path, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n }).catch(() => null);\n if (!res) {\n return { data: null, error: errorFrom(\"network_error\", \"network error\", 0) };\n }\n if (!res.ok) return { data: null, error: await parseError(res) };\n const raw = (await res.json()) as RawAuthResponse;\n const session: AuthSession = {\n access_token: raw.access_token,\n refresh_token: raw.refresh_token,\n token_type: raw.token_type,\n expires_in: raw.expires_in,\n expires_at: raw.expires_at,\n user: raw.user,\n };\n this.#deps.storage.set(session);\n this.#emit(event, session);\n return { data: session, error: null };\n }\n\n // Fetches a protected endpoint, auto-refreshing once on 401. We don't\n // loop — a second 401 after a successful refresh means the user is\n // genuinely signed out, propagate as such.\n async #authedFetch(path: string, init: RequestInit): Promise<Result<unknown>> {\n const session = this.#deps.storage.get();\n if (!session) return { data: null, error: errorFrom(\"no_session\", \"no session\", 401) };\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `Bearer ${session.access_token}`);\n let res = await this.#rawFetch(path, { ...init, headers });\n if (res.status === 401) {\n const refreshed = await this.refreshSession();\n if (refreshed.error) {\n this.#deps.storage.set(null);\n this.#emit(\"SIGNED_OUT\", null);\n return { data: null, error: refreshed.error };\n }\n headers.set(\"authorization\", `Bearer ${refreshed.data.access_token}`);\n res = await this.#rawFetch(path, { ...init, headers });\n }\n if (!res.ok) return { data: null, error: await parseError(res) };\n const body = res.status === 204 ? null : await res.json().catch(() => null);\n return { data: body, error: null };\n }\n\n async #rawFetch(path: string, init: RequestInit): Promise<Response> {\n return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);\n }\n\n #emit(event: AuthEvent, session: AuthSession | null): void {\n for (const l of this.#listeners) {\n try {\n l(event, session);\n } catch {\n // isolate subscribers from each other\n }\n }\n }\n}\n\nfunction errorFrom(code: string, message: string, status: number): AuthError {\n return { code, message, status };\n}\n\nasync function parseError(res: Response): Promise<AuthError> {\n try {\n const body = (await res.json()) as { error?: { message?: string; code?: string } };\n const message = body?.error?.message ?? res.statusText ?? \"request failed\";\n const code = body?.error?.code ?? \"error\";\n return { code, message, status: res.status };\n } catch {\n return { code: \"error\", message: res.statusText || \"request failed\", status: res.status };\n }\n}\n","// Entry point for the core SDK — browser, Node, and Edge-compatible.\nimport { AuthClient } from \"./client.js\";\nimport type { ClientOptions } from \"./types.js\";\n\nexport type JunctionClient = {\n auth: AuthClient;\n};\n\nexport function createJunctionClient(options: ClientOptions): JunctionClient {\n return { auth: new AuthClient(options) };\n}\n\nexport type {\n AuthError,\n AuthEvent,\n AuthSession,\n AuthStateChangeHandler,\n AuthUser,\n ClientOptions,\n Result,\n} from \"./types.js\";\nexport { AuthClient } from \"./client.js\";\n"],"mappings":"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","names":[]}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/react/index.ts
17
+ var react_exports = {};
18
+ module.exports = __toCommonJS(react_exports);
19
+ throw new Error(
20
+ "@onjunctiondev/auth/react is not available in slice 1. React hooks ship with slice 2. Use the core client via '@onjunctiondev/auth' for now."
21
+ );
22
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts"],"sourcesContent":["// React hooks placeholder — scheduled for slice 2. Importing this\n// subpath today throws a clear, actionable error instead of a\n// cryptic missing-export.\nthrow new Error(\n \"@onjunctiondev/auth/react is not available in slice 1. React hooks ship with slice 2. Use the core client via '@onjunctiondev/auth' for now.\",\n);\nexport {};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;AAGA,MAAM,IAAI;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,5 @@
1
+ // src/react/index.ts
2
+ throw new Error(
3
+ "@onjunctiondev/auth/react is not available in slice 1. React hooks ship with slice 2. Use the core client via '@onjunctiondev/auth' for now."
4
+ );
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts"],"sourcesContent":["// React hooks placeholder — scheduled for slice 2. Importing this\n// subpath today throws a clear, actionable error instead of a\n// cryptic missing-export.\nthrow new Error(\n \"@onjunctiondev/auth/react is not available in slice 1. React hooks ship with slice 2. Use the core client via '@onjunctiondev/auth' for now.\",\n);\nexport {};\n"],"mappings":";AAGA,MAAM,IAAI;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/server/index.ts
17
+ var server_exports = {};
18
+ module.exports = __toCommonJS(server_exports);
19
+ throw new Error(
20
+ "@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3."
21
+ );
22
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["// Next.js server helpers placeholder — scheduled for slice 3. Importing\n// this subpath today throws a clear, actionable error.\nthrow new Error(\n \"@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3.\",\n);\nexport {};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;AAEA,MAAM,IAAI;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,5 @@
1
+ // src/server/index.ts
2
+ throw new Error(
3
+ "@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3."
4
+ );
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["// Next.js server helpers placeholder — scheduled for slice 3. Importing\n// this subpath today throws a clear, actionable error.\nthrow new Error(\n \"@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3.\",\n);\nexport {};\n"],"mappings":";AAEA,MAAM,IAAI;AAAA,EACR;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@onjunctiondev/auth",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Headless auth SDK for Junction — email/password sessions with per-project JWTs and RLS claims.",
5
+ "license": "MIT",
6
+ "homepage": "https://onjunction.dev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/gsvprharsha/junction.git",
10
+ "directory": "packages/auth"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
19
+ "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
20
+ },
21
+ "./react": {
22
+ "import": { "types": "./dist/react/index.d.ts", "default": "./dist/react/index.js" },
23
+ "require": { "types": "./dist/react/index.d.cts", "default": "./dist/react/index.cjs" }
24
+ },
25
+ "./server": {
26
+ "import": { "types": "./dist/server/index.d.ts", "default": "./dist/server/index.js" },
27
+ "require": { "types": "./dist/server/index.d.cts", "default": "./dist/server/index.cjs" }
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "check-types": "tsc --noEmit",
41
+ "prepublishOnly": "pnpm build"
42
+ },
43
+ "devDependencies": {
44
+ "@junction/typescript-config": "workspace:*",
45
+ "@types/node": "26.4.1",
46
+ "tsup": "^8.3.5",
47
+ "typescript": "~5.7.0"
48
+ }
49
+ }