@ai-matrx/auth 0.1.3 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog — @ai-matrx/auth
2
2
 
3
+ ## 0.1.4
4
+
5
+ **Fix: `getPerson` no longer calls the auth server on every access check (Wave 0).** The verified
6
+ person is now kept per client until the session changes: concurrent callers share one read, and
7
+ sign-in, sign-out, `updatePerson`, an organization switch (`switchTo`), an active-organization
8
+ reload, and the `SIGNED_IN` / `SIGNED_OUT` / `USER_UPDATED` events drop it. A token refresh keeps
9
+ it; signed-out answers and failures are never cached. New export `invalidatePerson(client)` for a
10
+ change made outside this package. Tests: `session.test.ts` "getPerson asks the auth server once per
11
+ session", "asks again after sign-in, sign-out ...", "a signed-out answer or a failure is never cached".
12
+
3
13
  ## 0.1.3
4
14
 
5
15
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(src_exports, {
28
28
  extensionStorage: () => extensionStorage,
29
29
  getPerson: () => getPerson,
30
30
  getSession: () => getSession,
31
+ invalidatePerson: () => invalidatePerson,
31
32
  keyValueStorage: () => keyValueStorage,
32
33
  memoryStorage: () => memoryStorage,
33
34
  onSessionChange: () => onSessionChange,
@@ -62,10 +63,43 @@ async function getSession(client) {
62
63
  const { data, error } = await client.auth.getSession();
63
64
  return check("getSession", data, error).session;
64
65
  }
66
+ var personCache = /* @__PURE__ */ new WeakMap();
67
+ function personEntry(client) {
68
+ let entry = personCache.get(client);
69
+ if (!entry) {
70
+ const fresh = { read: null, epoch: 0 };
71
+ entry = fresh;
72
+ personCache.set(client, fresh);
73
+ const subscribe = client.auth?.onAuthStateChange;
74
+ if (typeof subscribe === "function") {
75
+ subscribe.call(client.auth, async (event) => {
76
+ if (event === "SIGNED_IN" || event === "SIGNED_OUT" || event === "USER_UPDATED") invalidatePerson(client);
77
+ });
78
+ }
79
+ }
80
+ return entry;
81
+ }
82
+ function invalidatePerson(client) {
83
+ const entry = personCache.get(client);
84
+ if (!entry) return;
85
+ entry.epoch += 1;
86
+ entry.read = null;
87
+ }
65
88
  async function getPerson(client) {
66
- const { data, error } = await client.auth.getUser();
67
- if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
68
- return check("getPerson", data, error).user;
89
+ const entry = personEntry(client);
90
+ if (entry.read) return entry.read;
91
+ const epoch = entry.epoch;
92
+ const read = (async () => {
93
+ const { data, error } = await client.auth.getUser();
94
+ if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
95
+ return check("getPerson", data, error).user;
96
+ })();
97
+ entry.read = read;
98
+ const settle = (keep) => {
99
+ if (entry.epoch === epoch && entry.read === read && !keep) entry.read = null;
100
+ };
101
+ read.then((user) => settle(user !== null), () => settle(false));
102
+ return read;
69
103
  }
70
104
  function onSessionChange(client, cb) {
71
105
  const { data } = client.auth.onAuthStateChange(cb);
@@ -73,6 +107,7 @@ function onSessionChange(client, cb) {
73
107
  }
74
108
  async function signInWithPassword(client, credentials) {
75
109
  const { data, error } = await client.auth.signInWithPassword(credentials);
110
+ invalidatePerson(client);
76
111
  const ok = check("signInWithPassword", data, error);
77
112
  return { session: ok.session, person: ok.user };
78
113
  }
@@ -84,10 +119,12 @@ async function signInWithOAuth(client, options) {
84
119
  }
85
120
  async function signOut(client, scope = "global") {
86
121
  const { error } = await client.auth.signOut({ scope });
122
+ invalidatePerson(client);
87
123
  check("signOut", null, error);
88
124
  }
89
125
  async function updatePerson(client, attributes) {
90
126
  const { data, error } = await client.auth.updateUser(attributes);
127
+ invalidatePerson(client);
91
128
  return check("updatePerson", data, error).user;
92
129
  }
93
130
  function sessionCredentials(client) {
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthClient, S as SessionSourceLike, a as SignInError, U as UserCredentialsPort, g as getPerson, b as getSession, o as onSessionChange, r as refreshSession, s as sessionCredentials, c as signInWithOAuth, d as signInWithPassword, e as signOut, u as updatePerson, f as userCredentials } from './session-DhRO4euL.cjs';
1
+ export { A as AuthClient, S as SessionSourceLike, a as SignInError, U as UserCredentialsPort, g as getPerson, b as getSession, i as invalidatePerson, o as onSessionChange, r as refreshSession, s as sessionCredentials, c as signInWithOAuth, d as signInWithPassword, e as signOut, u as updatePerson, f as userCredentials } from './session-DdQiStp6.cjs';
2
2
  import { SupabaseClient } from '@supabase/supabase-js';
3
3
  export { AuthChangeEvent, Session, User, UserAttributes } from '@supabase/supabase-js';
4
4
  export { CredentialsPort, MatrxCredential, MatrxCredentialKind } from './credentials.cjs';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthClient, S as SessionSourceLike, a as SignInError, U as UserCredentialsPort, g as getPerson, b as getSession, o as onSessionChange, r as refreshSession, s as sessionCredentials, c as signInWithOAuth, d as signInWithPassword, e as signOut, u as updatePerson, f as userCredentials } from './session-DhRO4euL.js';
1
+ export { A as AuthClient, S as SessionSourceLike, a as SignInError, U as UserCredentialsPort, g as getPerson, b as getSession, i as invalidatePerson, o as onSessionChange, r as refreshSession, s as sessionCredentials, c as signInWithOAuth, d as signInWithPassword, e as signOut, u as updatePerson, f as userCredentials } from './session-DdQiStp6.js';
2
2
  import { SupabaseClient } from '@supabase/supabase-js';
3
3
  export { AuthChangeEvent, Session, User, UserAttributes } from '@supabase/supabase-js';
4
4
  export { CredentialsPort, MatrxCredential, MatrxCredentialKind } from './credentials.js';
package/dist/index.js CHANGED
@@ -19,10 +19,43 @@ async function getSession(client) {
19
19
  const { data, error } = await client.auth.getSession();
20
20
  return check("getSession", data, error).session;
21
21
  }
22
+ var personCache = /* @__PURE__ */ new WeakMap();
23
+ function personEntry(client) {
24
+ let entry = personCache.get(client);
25
+ if (!entry) {
26
+ const fresh = { read: null, epoch: 0 };
27
+ entry = fresh;
28
+ personCache.set(client, fresh);
29
+ const subscribe = client.auth?.onAuthStateChange;
30
+ if (typeof subscribe === "function") {
31
+ subscribe.call(client.auth, async (event) => {
32
+ if (event === "SIGNED_IN" || event === "SIGNED_OUT" || event === "USER_UPDATED") invalidatePerson(client);
33
+ });
34
+ }
35
+ }
36
+ return entry;
37
+ }
38
+ function invalidatePerson(client) {
39
+ const entry = personCache.get(client);
40
+ if (!entry) return;
41
+ entry.epoch += 1;
42
+ entry.read = null;
43
+ }
22
44
  async function getPerson(client) {
23
- const { data, error } = await client.auth.getUser();
24
- if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
25
- return check("getPerson", data, error).user;
45
+ const entry = personEntry(client);
46
+ if (entry.read) return entry.read;
47
+ const epoch = entry.epoch;
48
+ const read = (async () => {
49
+ const { data, error } = await client.auth.getUser();
50
+ if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
51
+ return check("getPerson", data, error).user;
52
+ })();
53
+ entry.read = read;
54
+ const settle = (keep) => {
55
+ if (entry.epoch === epoch && entry.read === read && !keep) entry.read = null;
56
+ };
57
+ read.then((user) => settle(user !== null), () => settle(false));
58
+ return read;
26
59
  }
27
60
  function onSessionChange(client, cb) {
28
61
  const { data } = client.auth.onAuthStateChange(cb);
@@ -30,6 +63,7 @@ function onSessionChange(client, cb) {
30
63
  }
31
64
  async function signInWithPassword(client, credentials) {
32
65
  const { data, error } = await client.auth.signInWithPassword(credentials);
66
+ invalidatePerson(client);
33
67
  const ok = check("signInWithPassword", data, error);
34
68
  return { session: ok.session, person: ok.user };
35
69
  }
@@ -41,10 +75,12 @@ async function signInWithOAuth(client, options) {
41
75
  }
42
76
  async function signOut(client, scope = "global") {
43
77
  const { error } = await client.auth.signOut({ scope });
78
+ invalidatePerson(client);
44
79
  check("signOut", null, error);
45
80
  }
46
81
  async function updatePerson(client, attributes) {
47
82
  const { data, error } = await client.auth.updateUser(attributes);
83
+ invalidatePerson(client);
48
84
  return check("updatePerson", data, error).user;
49
85
  }
50
86
  function sessionCredentials(client) {
@@ -153,6 +189,7 @@ export {
153
189
  extensionStorage,
154
190
  getPerson,
155
191
  getSession,
192
+ invalidatePerson,
156
193
  keyValueStorage,
157
194
  memoryStorage,
158
195
  onSessionChange,
@@ -182,10 +182,43 @@ function check(door, data, error) {
182
182
  if (error) throw new SignInError(door, error.message, error.status, error.code);
183
183
  return data;
184
184
  }
185
+ var personCache = /* @__PURE__ */ new WeakMap();
186
+ function personEntry(client) {
187
+ let entry = personCache.get(client);
188
+ if (!entry) {
189
+ const fresh = { read: null, epoch: 0 };
190
+ entry = fresh;
191
+ personCache.set(client, fresh);
192
+ const subscribe = client.auth?.onAuthStateChange;
193
+ if (typeof subscribe === "function") {
194
+ subscribe.call(client.auth, async (event) => {
195
+ if (event === "SIGNED_IN" || event === "SIGNED_OUT" || event === "USER_UPDATED") invalidatePerson(client);
196
+ });
197
+ }
198
+ }
199
+ return entry;
200
+ }
201
+ function invalidatePerson(client) {
202
+ const entry = personCache.get(client);
203
+ if (!entry) return;
204
+ entry.epoch += 1;
205
+ entry.read = null;
206
+ }
185
207
  async function getPerson(client) {
186
- const { data, error } = await client.auth.getUser();
187
- if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
188
- return check("getPerson", data, error).user;
208
+ const entry = personEntry(client);
209
+ if (entry.read) return entry.read;
210
+ const epoch = entry.epoch;
211
+ const read = (async () => {
212
+ const { data, error } = await client.auth.getUser();
213
+ if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
214
+ return check("getPerson", data, error).user;
215
+ })();
216
+ entry.read = read;
217
+ const settle = (keep) => {
218
+ if (entry.epoch === epoch && entry.read === read && !keep) entry.read = null;
219
+ };
220
+ read.then((user) => settle(user !== null), () => settle(false));
221
+ return read;
189
222
  }
190
223
  function onSessionChange(client, cb) {
191
224
  const { data } = client.auth.onAuthStateChange(cb);
@@ -318,6 +351,7 @@ function createActiveOrganization(options) {
318
351
  const load = () => {
319
352
  if (disposed) return Promise.resolve(current);
320
353
  generation += 1;
354
+ invalidatePerson(client);
321
355
  inflight = run(generation).finally(() => {
322
356
  inflight = null;
323
357
  });
@@ -362,6 +396,7 @@ function createActiveOrganization(options) {
362
396
  const organization = snap.memberships.find((m) => m.id === organizationId);
363
397
  if (!organization || !snap.userId) throw new NotAMemberError(organizationId);
364
398
  generation += 1;
399
+ invalidatePerson(client);
365
400
  await held.write(snap.userId, organizationId);
366
401
  publish({ ...snap, status: "ready", organizationId, organization, rung: "switched", error: null });
367
402
  await writeLastActiveOrganization(client, organizationId);
@@ -1,5 +1,5 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { A as AuthClient } from './session-DhRO4euL.cjs';
2
+ import { A as AuthClient } from './session-DdQiStp6.cjs';
3
3
 
4
4
  /**
5
5
  * @ai-matrx/auth/organizations (moved from @ai-matrx/data/organizations, 2026-10-09) — THE active-organization ladder and the account's organization
@@ -1,5 +1,5 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { A as AuthClient } from './session-DhRO4euL.js';
2
+ import { A as AuthClient } from './session-DdQiStp6.js';
3
3
 
4
4
  /**
5
5
  * @ai-matrx/auth/organizations (moved from @ai-matrx/data/organizations, 2026-10-09) — THE active-organization ladder and the account's organization
@@ -138,10 +138,43 @@ function check(door, data, error) {
138
138
  if (error) throw new SignInError(door, error.message, error.status, error.code);
139
139
  return data;
140
140
  }
141
+ var personCache = /* @__PURE__ */ new WeakMap();
142
+ function personEntry(client) {
143
+ let entry = personCache.get(client);
144
+ if (!entry) {
145
+ const fresh = { read: null, epoch: 0 };
146
+ entry = fresh;
147
+ personCache.set(client, fresh);
148
+ const subscribe = client.auth?.onAuthStateChange;
149
+ if (typeof subscribe === "function") {
150
+ subscribe.call(client.auth, async (event) => {
151
+ if (event === "SIGNED_IN" || event === "SIGNED_OUT" || event === "USER_UPDATED") invalidatePerson(client);
152
+ });
153
+ }
154
+ }
155
+ return entry;
156
+ }
157
+ function invalidatePerson(client) {
158
+ const entry = personCache.get(client);
159
+ if (!entry) return;
160
+ entry.epoch += 1;
161
+ entry.read = null;
162
+ }
141
163
  async function getPerson(client) {
142
- const { data, error } = await client.auth.getUser();
143
- if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
144
- return check("getPerson", data, error).user;
164
+ const entry = personEntry(client);
165
+ if (entry.read) return entry.read;
166
+ const epoch = entry.epoch;
167
+ const read = (async () => {
168
+ const { data, error } = await client.auth.getUser();
169
+ if (error && (error.name === "AuthSessionMissingError" || error.status === 401)) return null;
170
+ return check("getPerson", data, error).user;
171
+ })();
172
+ entry.read = read;
173
+ const settle = (keep) => {
174
+ if (entry.epoch === epoch && entry.read === read && !keep) entry.read = null;
175
+ };
176
+ read.then((user) => settle(user !== null), () => settle(false));
177
+ return read;
145
178
  }
146
179
  function onSessionChange(client, cb) {
147
180
  const { data } = client.auth.onAuthStateChange(cb);
@@ -274,6 +307,7 @@ function createActiveOrganization(options) {
274
307
  const load = () => {
275
308
  if (disposed) return Promise.resolve(current);
276
309
  generation += 1;
310
+ invalidatePerson(client);
277
311
  inflight = run(generation).finally(() => {
278
312
  inflight = null;
279
313
  });
@@ -318,6 +352,7 @@ function createActiveOrganization(options) {
318
352
  const organization = snap.memberships.find((m) => m.id === organizationId);
319
353
  if (!organization || !snap.userId) throw new NotAMemberError(organizationId);
320
354
  generation += 1;
355
+ invalidatePerson(client);
321
356
  await held.write(snap.userId, organizationId);
322
357
  publish({ ...snap, status: "ready", organizationId, organization, rung: "switched", error: null });
323
358
  await writeLastActiveOrganization(client, organizationId);
@@ -31,7 +31,17 @@ declare class SignInError extends Error {
31
31
  }
32
32
  /** The stored session (no network unless a refresh is due), or null when signed out. */
33
33
  declare function getSession(client: AuthClient): Promise<Session | null>;
34
- /** The signed-in person, verified by the auth server, or null when signed out. */
34
+ /**
35
+ * Forget the cached person for this client, so the next {@link getPerson} asks the auth server.
36
+ * Sign-in, sign-out, a profile edit and an organization switch already call this; call it yourself
37
+ * only after changing the person outside this package.
38
+ */
39
+ declare function invalidatePerson(client: AuthClient): void;
40
+ /**
41
+ * The signed-in person, verified by the auth server, or null when signed out. The verified read is
42
+ * kept per client until the session changes (sign-in, sign-out, profile edit, organization switch),
43
+ * so repeated access checks cost zero extra auth-server calls; concurrent callers share one read.
44
+ */
35
45
  declare function getPerson(client: AuthClient): Promise<User | null>;
36
46
  /** Hear every session change (sign-in, refresh, sign-out). Returns the unsubscribe. */
37
47
  declare function onSessionChange(client: AuthClient, cb: (event: AuthChangeEvent, session: Session | null) => void): () => void;
@@ -99,4 +109,4 @@ declare const userCredentials: typeof sessionCredentials;
99
109
  */
100
110
  declare function refreshSession(client: AuthClient): Promise<Session>;
101
111
 
102
- export { type AuthClient as A, type SessionSourceLike as S, type UserCredentialsPort as U, SignInError as a, getSession as b, signInWithOAuth as c, signInWithPassword as d, signOut as e, userCredentials as f, getPerson as g, onSessionChange as o, refreshSession as r, sessionCredentials as s, updatePerson as u };
112
+ export { type AuthClient as A, type SessionSourceLike as S, type UserCredentialsPort as U, SignInError as a, getSession as b, signInWithOAuth as c, signInWithPassword as d, signOut as e, userCredentials as f, getPerson as g, invalidatePerson as i, onSessionChange as o, refreshSession as r, sessionCredentials as s, updatePerson as u };
@@ -31,7 +31,17 @@ declare class SignInError extends Error {
31
31
  }
32
32
  /** The stored session (no network unless a refresh is due), or null when signed out. */
33
33
  declare function getSession(client: AuthClient): Promise<Session | null>;
34
- /** The signed-in person, verified by the auth server, or null when signed out. */
34
+ /**
35
+ * Forget the cached person for this client, so the next {@link getPerson} asks the auth server.
36
+ * Sign-in, sign-out, a profile edit and an organization switch already call this; call it yourself
37
+ * only after changing the person outside this package.
38
+ */
39
+ declare function invalidatePerson(client: AuthClient): void;
40
+ /**
41
+ * The signed-in person, verified by the auth server, or null when signed out. The verified read is
42
+ * kept per client until the session changes (sign-in, sign-out, profile edit, organization switch),
43
+ * so repeated access checks cost zero extra auth-server calls; concurrent callers share one read.
44
+ */
35
45
  declare function getPerson(client: AuthClient): Promise<User | null>;
36
46
  /** Hear every session change (sign-in, refresh, sign-out). Returns the unsubscribe. */
37
47
  declare function onSessionChange(client: AuthClient, cb: (event: AuthChangeEvent, session: Session | null) => void): () => void;
@@ -99,4 +109,4 @@ declare const userCredentials: typeof sessionCredentials;
99
109
  */
100
110
  declare function refreshSession(client: AuthClient): Promise<Session>;
101
111
 
102
- export { type AuthClient as A, type SessionSourceLike as S, type UserCredentialsPort as U, SignInError as a, getSession as b, signInWithOAuth as c, signInWithPassword as d, signOut as e, userCredentials as f, getPerson as g, onSessionChange as o, refreshSession as r, sessionCredentials as s, updatePerson as u };
112
+ export { type AuthClient as A, type SessionSourceLike as S, type UserCredentialsPort as U, SignInError as a, getSession as b, signInWithOAuth as c, signInWithPassword as d, signOut as e, userCredentials as f, getPerson as g, invalidatePerson as i, onSessionChange as o, refreshSession as r, sessionCredentials as s, updatePerson as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/auth",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI Matrx sign-in, session and organizations for every app: sign in/out, session storage and refresh per platform (browser, Next.js, extension, desktop, Node), the current person, their organizations and favourites, the one active organization (held requests, never a list filter), and the credentials @ai-matrx/data and @ai-matrx/api read.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -191,5 +191,5 @@
191
191
  "test": "vitest run",
192
192
  "verify:tarball": "node ./scripts/verify-tarball.mjs"
193
193
  },
194
- "gitHead": "7ee2d68e156416d62a043d8f5053ff7f0b5792fc"
194
+ "gitHead": "9c8f9e35b1fe67f38dcc59ed4a2becb9cc7e79cf"
195
195
  }