@palbase/web 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +20 -3
  2. package/dist/{analytics-facade-DwOtlXPP.d.cts → analytics-facade-CaluA4bW.d.cts} +26 -1
  3. package/dist/{analytics-facade-Nk6J9Ncm.d.ts → analytics-facade-Cp3d4QI-.d.ts} +26 -1
  4. package/dist/chunk-BNAGRUIQ.js +381 -0
  5. package/dist/chunk-BNAGRUIQ.js.map +1 -0
  6. package/dist/{chunk-NXEPAEF5.js → chunk-CFDU23TB.js} +3 -3
  7. package/dist/chunk-CFDU23TB.js.map +1 -0
  8. package/dist/chunk-OWLTZG2V.js +24 -0
  9. package/dist/chunk-OWLTZG2V.js.map +1 -0
  10. package/dist/{chunk-TGDS6VMT.js → chunk-XTQXFZUP.js} +68 -376
  11. package/dist/chunk-XTQXFZUP.js.map +1 -0
  12. package/dist/gen/cli.cjs +5 -5
  13. package/dist/gen/cli.cjs.map +1 -1
  14. package/dist/gen/cli.js +5 -5
  15. package/dist/gen/cli.js.map +1 -1
  16. package/dist/index.cjs +55 -3
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +6 -4
  21. package/dist/internal.cjs +57 -5
  22. package/dist/internal.cjs.map +1 -1
  23. package/dist/internal.d.cts +3 -3
  24. package/dist/internal.d.ts +3 -3
  25. package/dist/internal.js +3 -2
  26. package/dist/next/client.cjs +57 -5
  27. package/dist/next/client.cjs.map +1 -1
  28. package/dist/next/client.js +3 -2
  29. package/dist/next/client.js.map +1 -1
  30. package/dist/next/index.cjs +61 -93
  31. package/dist/next/index.cjs.map +1 -1
  32. package/dist/next/index.d.cts +4 -64
  33. package/dist/next/index.d.ts +4 -64
  34. package/dist/next/index.js +9 -103
  35. package/dist/next/index.js.map +1 -1
  36. package/dist/next/middleware.cjs +263 -0
  37. package/dist/next/middleware.cjs.map +1 -0
  38. package/dist/next/middleware.d.cts +101 -0
  39. package/dist/next/middleware.d.ts +101 -0
  40. package/dist/next/middleware.js +107 -0
  41. package/dist/next/middleware.js.map +1 -0
  42. package/dist/{pb-FG_nV7NR.d.ts → pb-DUK5qy81.d.ts} +1 -1
  43. package/dist/{pb-C9v5dEO6.d.cts → pb-DvV6ftmf.d.cts} +1 -1
  44. package/dist/react/index.cjs +10 -2
  45. package/dist/react/index.cjs.map +1 -1
  46. package/dist/react/index.d.cts +1 -1
  47. package/dist/react/index.d.ts +1 -1
  48. package/dist/react/index.js +3 -2
  49. package/dist/react/index.js.map +1 -1
  50. package/package.json +11 -1
  51. package/dist/chunk-NXEPAEF5.js.map +0 -1
  52. package/dist/chunk-TGDS6VMT.js.map +0 -1
@@ -6,8 +6,9 @@ import {
6
6
  import {
7
7
  __configure,
8
8
  getRuntime
9
- } from "../chunk-TGDS6VMT.js";
10
- import "../chunk-NXEPAEF5.js";
9
+ } from "../chunk-XTQXFZUP.js";
10
+ import "../chunk-BNAGRUIQ.js";
11
+ import "../chunk-CFDU23TB.js";
11
12
  import "../chunk-PZ5AY32C.js";
12
13
 
13
14
  // src/next/client.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/client.ts"],"sourcesContent":["/**\n * '@palbase/web/next/client' — the browser half of the Next.js adapter. This entry\n * must NEVER import 'next' (it runs in plain client bundles); the cookie jar\n * is document.cookie itself, written with the shared codec so the server\n * side (pbServer / middleware / callback) reads the same bytes.\n */\nimport { __configure, getRuntime } from '../internal.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookies,\n} from './cookie-codec.js';\n\n/** 30 days — the refresh-token TTL (P3 design contract). */\nconst SESSION_MAX_AGE_S = 2_592_000;\n\n// Secure is unconditional, INCLUDING http://localhost: browsers treat\n// localhost as a potentially-trustworthy origin, so document.cookie accepts\n// Secure cookies there — no dev-mode special case needed. KNOWN LIMITATION:\n// plain-http origins OTHER than localhost — e.g. LAN-IP device testing on\n// http://192.168.x.x — are NOT trustworthy, so the browser silently DROPS\n// these Secure cookie writes and the session won't persist; use https (or a\n// localhost tunnel/port-forward) for on-device testing. NOT HttpOnly by\n// design: the browser SDK must read/write the session (Supabase-paradigm\n// tradeoff, documented in the P3 plan).\nconst WRITE_ATTRS = 'Path=/; SameSite=Lax; Secure';\n\n/** Parse document.cookie (\"a=1; b=2\") into a name → raw-value map. */\nfunction cookieJar(): Map<string, string> {\n const jar = new Map<string, string>();\n for (const part of document.cookie.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const name = part.slice(0, eq).trim();\n if (name) jar.set(name, part.slice(eq + 1).trim());\n }\n return jar;\n}\n\nfunction deleteCookie(name: string): void {\n // biome-ignore lint/suspicious/noDocumentCookie: SessionStorageAdapter is a SYNC contract; the async Cookie Store API can't back it (and isn't universal).\n document.cookie = `${name}=; ${WRITE_ATTRS}; Max-Age=0`;\n}\n\n/**\n * document.cookie-backed SessionStorageAdapter. `load` returns the EXTENDED\n * PersistedSession (access token + expiry ride along) so hydration adopts\n * the full session without a refresh round-trip.\n */\nexport function cookieSessionStorage(environmentRef: string): SessionStorageAdapter {\n return {\n load(): PersistedSession | null {\n if (typeof document === 'undefined') return null;\n const jar = cookieJar();\n const stored = decodeSessionCookies((name) => jar.get(name), environmentRef);\n if (!stored) return null;\n // A refresh-only save round-trips as a:'' / e:0 — normalize back to\n // the legacy shape so hydration takes the expired-trick path cleanly.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (typeof document === 'undefined') return;\n const jar = cookieJar();\n // Delete every currently-present session cookie first (stale chunks of\n // a previously-larger session, or a stale base when the new write\n // chunks), then set the new cookie(s).\n for (const name of clearedSessionCookieNames(environmentRef, (n) => jar.has(n))) {\n deleteCookie(name);\n }\n const { set, clear } = encodeSessionCookies(environmentRef, {\n accessToken: session.accessToken ?? '',\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt ?? 0,\n });\n for (const { name, value } of set) {\n // biome-ignore lint/suspicious/noDocumentCookie: SessionStorageAdapter is a SYNC contract; the async Cookie Store API can't back it (and isn't universal).\n document.cookie = `${name}=${value}; ${WRITE_ATTRS}; Max-Age=${SESSION_MAX_AGE_S}`;\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) deleteCookie(name);\n },\n clear(): void {\n if (typeof document === 'undefined') return;\n const jar = cookieJar();\n for (const name of clearedSessionCookieNames(environmentRef, (n) => jar.has(n))) {\n deleteCookie(name);\n }\n },\n };\n}\n\n/**\n * One-liner for a client component/provider: re-configure the already-loaded\n * gen config (palbe.gen.ts must be imported first — throws the guided\n * notConfigured error otherwise) with cookie-backed session storage so the\n * browser and the server share the session. Calling it again simply\n * re-configures (safe, e.g. under fast refresh). When Next evaluates the\n * client component module server-side there is no document — no-op.\n */\nexport function setupPalbeNext(): void {\n if (typeof document === 'undefined') return;\n const config = getRuntime().config;\n __configure({\n ...config,\n storage: cookieSessionStorage(config.environmentRef),\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAM,oBAAoB;AAW1B,IAAM,cAAc;AAGpB,SAAS,YAAiC;AACxC,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,QAAQ,SAAS,OAAO,MAAM,GAAG,GAAG;AAC7C,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,OAAO,GAAI;AACf,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,QAAI,KAAM,KAAI,IAAI,MAAM,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,EACnD;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAoB;AAExC,WAAS,SAAS,GAAG,IAAI,MAAM,WAAW;AAC5C;AAOO,SAAS,qBAAqB,gBAA+C;AAClF,SAAO;AAAA,IACL,OAAgC;AAC9B,UAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,YAAM,MAAM,UAAU;AACtB,YAAM,SAAS,qBAAqB,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,cAAc;AAC3E,UAAI,CAAC,OAAQ,QAAO;AAGpB,aAAO,OAAO,eAAe,OAAO,YAAY,IAC5C;AAAA,QACE,cAAc,OAAO;AAAA,QACrB,aAAa,OAAO;AAAA,QACpB,WAAW,OAAO;AAAA,MACpB,IACA,EAAE,cAAc,OAAO,aAAa;AAAA,IAC1C;AAAA,IACA,KAAK,SAAiC;AACpC,UAAI,OAAO,aAAa,YAAa;AACrC,YAAM,MAAM,UAAU;AAItB,iBAAW,QAAQ,0BAA0B,gBAAgB,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG;AAC/E,qBAAa,IAAI;AAAA,MACnB;AACA,YAAM,EAAE,KAAK,MAAM,IAAI,qBAAqB,gBAAgB;AAAA,QAC1D,aAAa,QAAQ,eAAe;AAAA,QACpC,cAAc,QAAQ;AAAA,QACtB,WAAW,QAAQ,aAAa;AAAA,MAClC,CAAC;AACD,iBAAW,EAAE,MAAM,MAAM,KAAK,KAAK;AAEjC,iBAAS,SAAS,GAAG,IAAI,IAAI,KAAK,KAAK,WAAW,aAAa,iBAAiB;AAAA,MAClF;AAGA,iBAAW,QAAQ,MAAO,cAAa,IAAI;AAAA,IAC7C;AAAA,IACA,QAAc;AACZ,UAAI,OAAO,aAAa,YAAa;AACrC,YAAM,MAAM,UAAU;AACtB,iBAAW,QAAQ,0BAA0B,gBAAgB,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG;AAC/E,qBAAa,IAAI;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAUO,SAAS,iBAAuB;AACrC,MAAI,OAAO,aAAa,YAAa;AACrC,QAAM,SAAS,WAAW,EAAE;AAC5B,cAAY;AAAA,IACV,GAAG;AAAA,IACH,SAAS,qBAAqB,OAAO,cAAc;AAAA,EACrD,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../src/next/client.ts"],"sourcesContent":["/**\n * '@palbase/web/next/client' — the browser half of the Next.js adapter. This entry\n * must NEVER import 'next' (it runs in plain client bundles); the cookie jar\n * is document.cookie itself, written with the shared codec so the server\n * side (pbServer / middleware / callback) reads the same bytes.\n */\nimport { __configure, getRuntime } from '../internal.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookies,\n} from './cookie-codec.js';\n\n/** 30 days — the refresh-token TTL (P3 design contract). */\nconst SESSION_MAX_AGE_S = 2_592_000;\n\n// Secure is unconditional, INCLUDING http://localhost: browsers treat\n// localhost as a potentially-trustworthy origin, so document.cookie accepts\n// Secure cookies there — no dev-mode special case needed. KNOWN LIMITATION:\n// plain-http origins OTHER than localhost — e.g. LAN-IP device testing on\n// http://192.168.x.x — are NOT trustworthy, so the browser silently DROPS\n// these Secure cookie writes and the session won't persist; use https (or a\n// localhost tunnel/port-forward) for on-device testing. NOT HttpOnly by\n// design: the browser SDK must read/write the session (Supabase-paradigm\n// tradeoff, documented in the P3 plan).\nconst WRITE_ATTRS = 'Path=/; SameSite=Lax; Secure';\n\n/** Parse document.cookie (\"a=1; b=2\") into a name → raw-value map. */\nfunction cookieJar(): Map<string, string> {\n const jar = new Map<string, string>();\n for (const part of document.cookie.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const name = part.slice(0, eq).trim();\n if (name) jar.set(name, part.slice(eq + 1).trim());\n }\n return jar;\n}\n\nfunction deleteCookie(name: string): void {\n // biome-ignore lint/suspicious/noDocumentCookie: SessionStorageAdapter is a SYNC contract; the async Cookie Store API can't back it (and isn't universal).\n document.cookie = `${name}=; ${WRITE_ATTRS}; Max-Age=0`;\n}\n\n/**\n * document.cookie-backed SessionStorageAdapter. `load` returns the EXTENDED\n * PersistedSession (access token + expiry ride along) so hydration adopts\n * the full session without a refresh round-trip.\n */\nexport function cookieSessionStorage(environmentRef: string): SessionStorageAdapter {\n return {\n load(): PersistedSession | null {\n if (typeof document === 'undefined') return null;\n const jar = cookieJar();\n const stored = decodeSessionCookies((name) => jar.get(name), environmentRef);\n if (!stored) return null;\n // A refresh-only save round-trips as a:'' / e:0 — normalize back to\n // the legacy shape so hydration takes the expired-trick path cleanly.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (typeof document === 'undefined') return;\n const jar = cookieJar();\n // Delete every currently-present session cookie first (stale chunks of\n // a previously-larger session, or a stale base when the new write\n // chunks), then set the new cookie(s).\n for (const name of clearedSessionCookieNames(environmentRef, (n) => jar.has(n))) {\n deleteCookie(name);\n }\n const { set, clear } = encodeSessionCookies(environmentRef, {\n accessToken: session.accessToken ?? '',\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt ?? 0,\n });\n for (const { name, value } of set) {\n // biome-ignore lint/suspicious/noDocumentCookie: SessionStorageAdapter is a SYNC contract; the async Cookie Store API can't back it (and isn't universal).\n document.cookie = `${name}=${value}; ${WRITE_ATTRS}; Max-Age=${SESSION_MAX_AGE_S}`;\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) deleteCookie(name);\n },\n clear(): void {\n if (typeof document === 'undefined') return;\n const jar = cookieJar();\n for (const name of clearedSessionCookieNames(environmentRef, (n) => jar.has(n))) {\n deleteCookie(name);\n }\n },\n };\n}\n\n/**\n * One-liner for a client component/provider: re-configure the already-loaded\n * gen config (palbe.gen.ts must be imported first — throws the guided\n * notConfigured error otherwise) with cookie-backed session storage so the\n * browser and the server share the session. Calling it again simply\n * re-configures (safe, e.g. under fast refresh). When Next evaluates the\n * client component module server-side there is no document — no-op.\n */\nexport function setupPalbeNext(): void {\n if (typeof document === 'undefined') return;\n const config = getRuntime().config;\n __configure({\n ...config,\n storage: cookieSessionStorage(config.environmentRef),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,IAAM,oBAAoB;AAW1B,IAAM,cAAc;AAGpB,SAAS,YAAiC;AACxC,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,QAAQ,SAAS,OAAO,MAAM,GAAG,GAAG;AAC7C,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,OAAO,GAAI;AACf,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,QAAI,KAAM,KAAI,IAAI,MAAM,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,EACnD;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAoB;AAExC,WAAS,SAAS,GAAG,IAAI,MAAM,WAAW;AAC5C;AAOO,SAAS,qBAAqB,gBAA+C;AAClF,SAAO;AAAA,IACL,OAAgC;AAC9B,UAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,YAAM,MAAM,UAAU;AACtB,YAAM,SAAS,qBAAqB,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,cAAc;AAC3E,UAAI,CAAC,OAAQ,QAAO;AAGpB,aAAO,OAAO,eAAe,OAAO,YAAY,IAC5C;AAAA,QACE,cAAc,OAAO;AAAA,QACrB,aAAa,OAAO;AAAA,QACpB,WAAW,OAAO;AAAA,MACpB,IACA,EAAE,cAAc,OAAO,aAAa;AAAA,IAC1C;AAAA,IACA,KAAK,SAAiC;AACpC,UAAI,OAAO,aAAa,YAAa;AACrC,YAAM,MAAM,UAAU;AAItB,iBAAW,QAAQ,0BAA0B,gBAAgB,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG;AAC/E,qBAAa,IAAI;AAAA,MACnB;AACA,YAAM,EAAE,KAAK,MAAM,IAAI,qBAAqB,gBAAgB;AAAA,QAC1D,aAAa,QAAQ,eAAe;AAAA,QACpC,cAAc,QAAQ;AAAA,QACtB,WAAW,QAAQ,aAAa;AAAA,MAClC,CAAC;AACD,iBAAW,EAAE,MAAM,MAAM,KAAK,KAAK;AAEjC,iBAAS,SAAS,GAAG,IAAI,IAAI,KAAK,KAAK,WAAW,aAAa,iBAAiB;AAAA,MAClF;AAGA,iBAAW,QAAQ,MAAO,cAAa,IAAI;AAAA,IAC7C;AAAA,IACA,QAAc;AACZ,UAAI,OAAO,aAAa,YAAa;AACrC,YAAM,MAAM,UAAU;AACtB,iBAAW,QAAQ,0BAA0B,gBAAgB,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG;AAC/E,qBAAa,IAAI;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAUO,SAAS,iBAAuB;AACrC,MAAI,OAAO,aAAa,YAAa;AACrC,QAAM,SAAS,WAAW,EAAE;AAC5B,cAAY;AAAA,IACV,GAAG;AAAA,IACH,SAAS,qBAAqB,OAAO,cAAc;AAAA,EACrD,CAAC;AACH;","names":[]}
@@ -37,7 +37,6 @@ __export(next_exports, {
37
37
  encodeSessionCookiesDecoded: () => encodeSessionCookiesDecoded,
38
38
  environmentRefFromApiKey: () => environmentRefFromApiKey,
39
39
  handleAuthCallback: () => handleAuthCallback,
40
- palbeMiddleware: () => palbeMiddleware,
41
40
  pbServer: () => pbServer,
42
41
  sessionCookieName: () => sessionCookieName
43
42
  });
@@ -57,8 +56,8 @@ function asWireAuthResult(raw) {
57
56
  }
58
57
 
59
58
  // src/api-key.ts
60
- var API_KEY_RE = /^pb_([a-z0-9]+)_[cs][A-Za-z0-9]{20}$/;
61
- var PUBLISHABLE_API_KEY_RE = /^pb_([a-z0-9]+)_c[A-Za-z0-9]{20}$/;
59
+ var API_KEY_RE = /^pb_([a-z0-9]{4,24})_[cs][A-Za-z0-9]{20}$/;
60
+ var PUBLISHABLE_API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;
62
61
  function environmentRefFromApiKey(apiKey) {
63
62
  const m = API_KEY_RE.exec(apiKey);
64
63
  return m ? m[1] ?? "" : "";
@@ -168,7 +167,7 @@ var PalbaseError = class extends Error {
168
167
  }
169
168
  };
170
169
  var PALBASE_DEFAULT_HOST = "api.palbase.studio";
171
- var API_KEY_RE2 = /^pb_([a-z0-9]+)_c[A-Za-z0-9]{20}$/;
170
+ var API_KEY_RE2 = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;
172
171
  function parseEnvironmentRef(apiKey) {
173
172
  return API_KEY_RE2.exec(apiKey)?.[1] ?? null;
174
173
  }
@@ -545,6 +544,11 @@ var PERF_EXCLUDED_PREFIX = "/v1/analytics/";
545
544
  function isSelfTraced(path) {
546
545
  return path.startsWith(PERF_EXCLUDED_PREFIX);
547
546
  }
547
+ function isSessionKilled(status, code) {
548
+ if (status === 401) return code === "session_revoked";
549
+ if (status === 403) return code === "subject_fenced" || code === "subject_erased";
550
+ return false;
551
+ }
548
552
  function nowMs() {
549
553
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
550
554
  }
@@ -580,7 +584,7 @@ async function palbeRequest(rt, method, path, spec = {}) {
580
584
  let res;
581
585
  try {
582
586
  res = await attempt();
583
- if (res.error?.status === 401 && rt.tokenManager.getRefreshToken() && rt.tokenManager.refreshFunction) {
587
+ if (res.error?.status === 401 && res.error.code !== "session_revoked" && rt.tokenManager.getRefreshToken() && rt.tokenManager.refreshFunction) {
584
588
  try {
585
589
  await rt.tokenManager.refreshSession();
586
590
  } catch (refreshErr) {
@@ -600,6 +604,9 @@ async function palbeRequest(rt, method, path, spec = {}) {
600
604
  if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);
601
605
  throw e;
602
606
  }
607
+ if (res.error && isSessionKilled(res.error.status, res.error.code)) {
608
+ rt.auth.reactiveTeardown();
609
+ }
603
610
  record(res.error?.status ?? 200);
604
611
  return unwrap(res);
605
612
  }
@@ -1478,7 +1485,8 @@ var PalbeAuth = class {
1478
1485
  if (!this.signedInState) return;
1479
1486
  this.signedInState = false;
1480
1487
  this.cachedUser = null;
1481
- const reason = this.signingOut ? "userInitiated" : "sessionExpired";
1488
+ const reason = this.nextSignOutReason ?? (this.signingOut ? "userInitiated" : "sessionExpired");
1489
+ this.nextSignOutReason = null;
1482
1490
  this.emitState({ status: "signedOut" });
1483
1491
  this.emitEvent({ type: "signedOut", reason });
1484
1492
  } else if (event === "TOKEN_REFRESHED") {
@@ -1491,6 +1499,10 @@ var PalbeAuth = class {
1491
1499
  rt;
1492
1500
  cachedUser = null;
1493
1501
  signingOut = false;
1502
+ // Explicit signedOut reason pinned for the NEXT session clear (deleteAccount /
1503
+ // reactiveTeardown set it before clearSession; the SIGNED_OUT handler consumes
1504
+ // it exactly once, then resets to null → normal signOut/expiry split resumes).
1505
+ nextSignOutReason = null;
1494
1506
  signingIn = false;
1495
1507
  // suppresses AuthClient's TOKEN_REFRESHED during re-signIn
1496
1508
  signedInState = false;
@@ -1538,6 +1550,45 @@ var PalbeAuth = class {
1538
1550
  this.cachedUser = null;
1539
1551
  }
1540
1552
  }
1553
+ /**
1554
+ * Self-service account erasure ("right to be forgotten"). Calls palauth
1555
+ * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass
1556
+ * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up
1557
+ * session. On 202 the erasure workflow is durably queued AND every session is
1558
+ * already revoked server-side — so we tear down local state (mirroring
1559
+ * `signOut()`'s clear, minus the pointless `/logout`) and emit
1560
+ * `signedOut{reason:'accountDeleted'}`.
1561
+ *
1562
+ * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)
1563
+ * the request throws BEFORE the teardown line, so the local session is left
1564
+ * fully intact — the deletion did not happen and the user is still signed in.
1565
+ */
1566
+ async deleteAccount(params = {}) {
1567
+ const res = await palbeRequest(this.rt, "DELETE", "/auth/user", {
1568
+ body: params.password ? { password: params.password } : {}
1569
+ });
1570
+ this.nextSignOutReason = "accountDeleted";
1571
+ this.rt.tokenManager.clearSession();
1572
+ this.rt.storage.clear();
1573
+ this.cachedUser = null;
1574
+ return { erasureId: res.erasure_id };
1575
+ }
1576
+ /**
1577
+ * @internal — invoked by the transport choke point (`request.ts`), not app code.
1578
+ * Reactive teardown when the server reports the session is gone: a 403
1579
+ * `subject_fenced`/`subject_erased` (subject being erased) or a 401
1580
+ * `session_revoked`. Clears local state and emits
1581
+ * `signedOut{reason:'sessionInvalid'}` exactly once (deduped by
1582
+ * `signedInState`). An ordinary 403 authz denial does NOT reach here —
1583
+ * `request.ts` matches only the exact session-kill codes.
1584
+ */
1585
+ reactiveTeardown() {
1586
+ if (!this.signedInState) return;
1587
+ this.nextSignOutReason = "sessionInvalid";
1588
+ this.rt.tokenManager.clearSession();
1589
+ this.rt.storage.clear();
1590
+ this.cachedUser = null;
1591
+ }
1541
1592
  async getUser() {
1542
1593
  const raw = await palbeRequest(this.rt, "GET", "/auth/user");
1543
1594
  return mapWireUser(raw);
@@ -8986,7 +9037,7 @@ function defaultSessionStorage(key) {
8986
9037
  }
8987
9038
 
8988
9039
  // src/version.ts
8989
- var VERSION = "4.0.1";
9040
+ var VERSION = "5.0.0";
8990
9041
 
8991
9042
  // src/runtime.ts
8992
9043
  function buildRuntime(config) {
@@ -9366,7 +9417,7 @@ function getRuntime() {
9366
9417
  return rt;
9367
9418
  }
9368
9419
 
9369
- // src/next/shared.ts
9420
+ // src/next/global-config.ts
9370
9421
  function requireGlobalConfig(hint) {
9371
9422
  try {
9372
9423
  return getRuntime().config;
@@ -9377,6 +9428,8 @@ function requireGlobalConfig(hint) {
9377
9428
  });
9378
9429
  }
9379
9430
  }
9431
+
9432
+ // src/next/shared.ts
9380
9433
  var nextServerModule;
9381
9434
  async function importNextServer(caller) {
9382
9435
  nextServerModule ??= import("next/server");
@@ -9482,90 +9535,6 @@ function handleAuthCallback(opts) {
9482
9535
  };
9483
9536
  }
9484
9537
 
9485
- // src/next/middleware.ts
9486
- var DEFAULT_REFRESH_MARGIN_MS = 6e4;
9487
- var inflightRefreshes = /* @__PURE__ */ new Map();
9488
- function refreshSingleFlight(config, refreshToken) {
9489
- const existing = inflightRefreshes.get(refreshToken);
9490
- if (existing) return existing;
9491
- const pending = refresh(config, refreshToken).finally(() => {
9492
- inflightRefreshes.delete(refreshToken);
9493
- });
9494
- inflightRefreshes.set(refreshToken, pending);
9495
- return pending;
9496
- }
9497
- async function refresh(config, refreshToken) {
9498
- try {
9499
- const res = await fetch(`${config.url}/auth/token/refresh`, {
9500
- method: "POST",
9501
- headers: { apikey: config.apiKey, "content-type": "application/json" },
9502
- body: JSON.stringify({ refresh_token: refreshToken })
9503
- });
9504
- if (res.status === 400 || res.status === 401 || res.status === 403) {
9505
- return { kind: "terminal" };
9506
- }
9507
- if (!res.ok) return { kind: "transient" };
9508
- const raw = await res.json();
9509
- if (typeof raw === "object" && raw !== null) {
9510
- const obj = raw;
9511
- if (typeof obj.access_token === "string" && typeof obj.refresh_token === "string" && typeof obj.expires_in === "number") {
9512
- return {
9513
- kind: "rotated",
9514
- session: {
9515
- accessToken: obj.access_token,
9516
- refreshToken: obj.refresh_token,
9517
- expiresAt: Date.now() + obj.expires_in * 1e3
9518
- }
9519
- };
9520
- }
9521
- }
9522
- return { kind: "transient" };
9523
- } catch {
9524
- return { kind: "transient" };
9525
- }
9526
- }
9527
- async function palbeMiddleware(request, opts) {
9528
- const config = requireGlobalConfig(
9529
- "import the generated palbe.gen.ts at the top of middleware.ts \u2014 Next bundles middleware as its own module graph, so the layout.tsx import does not reach it."
9530
- );
9531
- const { NextResponse } = await importNextServer("palbeMiddleware");
9532
- const passThrough = () => opts?.response ?? NextResponse.next({ request });
9533
- const ref = config.environmentRef;
9534
- const session = decodeSessionCookies((name) => request.cookies.get(name)?.value, ref);
9535
- if (!session) return passThrough();
9536
- if (session.expiresAt - Date.now() > (opts?.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS)) {
9537
- return passThrough();
9538
- }
9539
- const outcome = await refreshSingleFlight(config, session.refreshToken);
9540
- if (outcome.kind === "transient") return passThrough();
9541
- const staleNames = clearedSessionCookieNames(ref, (name) => request.cookies.has(name));
9542
- if (outcome.kind === "terminal") {
9543
- for (const name of staleNames) request.cookies.delete(name);
9544
- const response2 = opts?.response ?? NextResponse.next({ request });
9545
- for (const name of staleNames) {
9546
- response2.cookies.set(name, "", { ...SESSION_COOKIE_ATTRS, maxAge: 0 });
9547
- }
9548
- response2.headers.set("cache-control", "private, no-store");
9549
- return response2;
9550
- }
9551
- const write = encodeSessionCookiesDecoded(ref, outcome.session);
9552
- for (const name of staleNames) request.cookies.delete(name);
9553
- for (const { name, value } of write.set) request.cookies.set(name, value);
9554
- for (const name of write.clear) request.cookies.delete(name);
9555
- const response = opts?.response ?? NextResponse.next({ request });
9556
- const written = new Set(write.set.map((c) => c.name));
9557
- for (const { name, value } of write.set) {
9558
- response.cookies.set(name, value, SESSION_COOKIE_ATTRS);
9559
- }
9560
- for (const name of /* @__PURE__ */ new Set([...write.clear, ...staleNames])) {
9561
- if (!written.has(name)) {
9562
- response.cookies.set(name, "", { ...SESSION_COOKIE_ATTRS, maxAge: 0 });
9563
- }
9564
- }
9565
- response.headers.set("cache-control", "private, no-store");
9566
- return response;
9567
- }
9568
-
9569
9538
  // src/next/server.ts
9570
9539
  function nextRequired(cause) {
9571
9540
  const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : "";
@@ -9655,7 +9624,6 @@ async function pbServer(opts) {
9655
9624
  encodeSessionCookiesDecoded,
9656
9625
  environmentRefFromApiKey,
9657
9626
  handleAuthCallback,
9658
- palbeMiddleware,
9659
9627
  pbServer,
9660
9628
  sessionCookieName
9661
9629
  });