@onjunctiondev/auth 0.1.1 → 0.1.3
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/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +0 -1
- package/dist/react/index.js +0 -1
- package/dist/server/index.cjs +0 -1
- package/dist/server/index.js +0 -1
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.js.map +0 -1
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -164,6 +164,7 @@ var AuthClient = class {
|
|
|
164
164
|
if (!res.ok) return { data: null, error: await parseError(res) };
|
|
165
165
|
const raw = await res.json();
|
|
166
166
|
const session = {
|
|
167
|
+
status: raw.status ?? "complete",
|
|
167
168
|
access_token: raw.access_token,
|
|
168
169
|
refresh_token: raw.refresh_token,
|
|
169
170
|
token_type: raw.token_type,
|
|
@@ -233,4 +234,3 @@ function createJunctionClient(options) {
|
|
|
233
234
|
AuthClient,
|
|
234
235
|
createJunctionClient
|
|
235
236
|
});
|
|
236
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -8,7 +8,9 @@ type AuthUser = {
|
|
|
8
8
|
app_metadata: Record<string, unknown>;
|
|
9
9
|
user_metadata: Record<string, unknown>;
|
|
10
10
|
};
|
|
11
|
+
type AuthSessionStatus = "complete";
|
|
11
12
|
type AuthSession = {
|
|
13
|
+
status: AuthSessionStatus;
|
|
12
14
|
access_token: string;
|
|
13
15
|
refresh_token: string;
|
|
14
16
|
token_type: "bearer";
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,9 @@ type AuthUser = {
|
|
|
8
8
|
app_metadata: Record<string, unknown>;
|
|
9
9
|
user_metadata: Record<string, unknown>;
|
|
10
10
|
};
|
|
11
|
+
type AuthSessionStatus = "complete";
|
|
11
12
|
type AuthSession = {
|
|
13
|
+
status: AuthSessionStatus;
|
|
12
14
|
access_token: string;
|
|
13
15
|
refresh_token: string;
|
|
14
16
|
token_type: "bearer";
|
package/dist/index.js
CHANGED
|
@@ -137,6 +137,7 @@ var AuthClient = class {
|
|
|
137
137
|
if (!res.ok) return { data: null, error: await parseError(res) };
|
|
138
138
|
const raw = await res.json();
|
|
139
139
|
const session = {
|
|
140
|
+
status: raw.status ?? "complete",
|
|
140
141
|
access_token: raw.access_token,
|
|
141
142
|
refresh_token: raw.refresh_token,
|
|
142
143
|
token_type: raw.token_type,
|
|
@@ -205,4 +206,3 @@ export {
|
|
|
205
206
|
AuthClient,
|
|
206
207
|
createJunctionClient
|
|
207
208
|
};
|
|
208
|
-
//# sourceMappingURL=index.js.map
|
package/dist/react/index.cjs
CHANGED
package/dist/react/index.js
CHANGED
package/dist/server/index.cjs
CHANGED
package/dist/server/index.js
CHANGED
package/package.json
CHANGED
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
package/dist/react/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
package/dist/react/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
package/dist/server/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|