@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 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
@@ -19,4 +19,3 @@ module.exports = __toCommonJS(react_exports);
19
19
  throw new Error(
20
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
21
  );
22
- //# sourceMappingURL=index.cjs.map
@@ -2,4 +2,3 @@
2
2
  throw new Error(
3
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
4
  );
5
- //# sourceMappingURL=index.js.map
@@ -19,4 +19,3 @@ module.exports = __toCommonJS(server_exports);
19
19
  throw new Error(
20
20
  "@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3."
21
21
  );
22
- //# sourceMappingURL=index.cjs.map
@@ -2,4 +2,3 @@
2
2
  throw new Error(
3
3
  "@onjunctiondev/auth/server is not available in slice 1. Server helpers ship with slice 3."
4
4
  );
5
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onjunctiondev/auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Headless auth SDK for Junction — email/password sessions with per-project JWTs and RLS claims.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://onjunction.dev",
@@ -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":[]}
@@ -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":[]}
@@ -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":[]}
@@ -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":[]}