lakebed 0.0.17 → 0.0.18

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/README.md CHANGED
@@ -61,7 +61,7 @@ export default capsule({
61
61
 
62
62
  ## Auth
63
63
 
64
- Every app starts with local guest auth. To let users sign in with Google, render the built-in button. Lakebed always asks Shoo for profile fields so the app can show a useful identifier like `auth.email` or `auth.displayName`.
64
+ Every app starts with local guest auth. To let users sign in with Google, render the built-in button. Lakebed asks Shoo for profile fields and exposes the user's name and avatar through `auth.displayName` and `auth.picture`.
65
65
  Check `auth.isLoading` before showing signed-out UI, because Lakebed may still be confirming a stored session.
66
66
 
67
67
  ```tsx
@@ -69,7 +69,7 @@ import { SignInWithGoogle, signOut, useAuth } from "lakebed/client";
69
69
 
70
70
  export function App() {
71
71
  const auth = useAuth();
72
- const authLabel = auth.email ?? auth.displayName;
72
+ const authLabel = auth.displayName;
73
73
 
74
74
  if (auth.isLoading) {
75
75
  return <p>Checking session</p>;
@@ -78,7 +78,8 @@ export function App() {
78
78
  return auth.isGuest ? (
79
79
  <SignInWithGoogle />
80
80
  ) : (
81
- <button type="button" onClick={() => signOut()}>
81
+ <button className="inline-flex items-center gap-2" type="button" onClick={() => signOut()}>
82
+ {auth.picture ? <img alt="" className="h-6 w-6 rounded-full" referrerPolicy="no-referrer" src={auth.picture} /> : null}
82
83
  Sign out {authLabel}
83
84
  </button>
84
85
  );
@@ -90,7 +91,7 @@ After sign-in, server handlers receive the verified Google identity through `ctx
90
91
  ```ts
91
92
  mutations: {
92
93
  save: mutation((ctx) => {
93
- ctx.log.info("signed in user", { userId: ctx.auth.userId, email: ctx.auth.email });
94
+ ctx.log.info("signed in user", { userId: ctx.auth.userId, displayName: ctx.auth.displayName });
94
95
  });
95
96
  }
96
97
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lakebed",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Agent-native CLI and runtime for building and deploying Lakebed capsules.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -52,9 +52,6 @@
52
52
  "publishConfig": {
53
53
  "access": "public"
54
54
  },
55
- "scripts": {
56
- "check": "node --check src/cli.js && node --check src/runtime.js && node --check src/server.js && node --check src/client.js && node --check src/source-store.js && node --check src/source-runtime.js && node --check src/source-runtime-worker.js && node --check src/source-runtime-loader.mjs && node --check src/anonymous.js && node --check src/anonymous-server.js && node --check src/auth.js && node --check src/version.js"
57
- },
58
55
  "dependencies": {
59
56
  "esbuild": "^0.27.1",
60
57
  "pg": "^8.16.3",
@@ -63,5 +60,8 @@
63
60
  },
64
61
  "devDependencies": {
65
62
  "@types/ws": "^8.18.1"
63
+ },
64
+ "scripts": {
65
+ "check": "node --check src/cli.js && node --check src/runtime.js && node --check src/server.js && node --check src/client.js && node --check src/source-store.js && node --check src/source-runtime.js && node --check src/source-runtime-worker.js && node --check src/source-runtime-loader.mjs && node --check src/anonymous.js && node --check src/anonymous-server.js && node --check src/auth.js && node --check src/version.js"
66
66
  }
67
- }
67
+ }
package/src/anonymous.js CHANGED
@@ -29,7 +29,7 @@ export const DEFAULT_ANONYMOUS_LIMITS = {
29
29
  };
30
30
 
31
31
  const expressionOps = new Set(["arg", "auth", "call", "row"]);
32
- const authFields = new Set(["displayName", "email", "emailVerified", "isAuthenticated", "isGuest", "name", "picture", "provider", "userId"]);
32
+ const authFields = new Set(["displayName", "email", "emailVerified", "isAuthenticated", "isGuest", "picture", "provider", "userId"]);
33
33
  const sourceModuleCache = new Map();
34
34
 
35
35
  export class AnonymousCompilerError extends Error {
@@ -228,7 +228,6 @@ function createSymbolicAuth() {
228
228
  emailVerified: new SymbolicValue(["auth", "emailVerified"], true),
229
229
  isAuthenticated: new SymbolicValue(["auth", "isAuthenticated"], true),
230
230
  isGuest: new SymbolicValue(["auth", "isGuest"], false),
231
- name: new SymbolicValue(["auth", "name"], "Trace Guest"),
232
231
  picture: new SymbolicValue(["auth", "picture"], "https://example.test/avatar.png"),
233
232
  provider: new SymbolicValue(["auth", "provider"], "google"),
234
233
  userId: new SymbolicValue(["auth", "userId"], "guest:trace")
@@ -248,6 +247,8 @@ function createSymbolicRow({ auth, idExpr, scanId, schema, tableName }) {
248
247
  row[fieldName] = auth.userId;
249
248
  } else if (fieldName === "authorName") {
250
249
  row[fieldName] = auth.displayName;
250
+ } else if (fieldName === "authorPicture") {
251
+ row[fieldName] = auth.picture;
251
252
  } else if (field.kind === "boolean") {
252
253
  row[fieldName] = true;
253
254
  } else {
package/src/auth.js CHANGED
@@ -81,15 +81,14 @@ function authFromClaims(claims) {
81
81
  return null;
82
82
  }
83
83
 
84
- const name = stringClaim(claims, "name");
84
+ const name = stringClaim(claims, "name")?.trim();
85
85
  const email = stringClaim(claims, "email");
86
86
  return {
87
- displayName: name ?? email ?? "Google User",
87
+ displayName: name || "Google User",
88
88
  email,
89
89
  emailVerified: booleanClaim(claims, "email_verified"),
90
90
  isAuthenticated: true,
91
91
  isGuest: false,
92
- name,
93
92
  picture: stringClaim(claims, "picture"),
94
93
  provider: "google",
95
94
  userId: `google:${pairwiseSub}`
package/src/cli.js CHANGED
@@ -1609,11 +1609,35 @@ export default capsule({
1609
1609
  "client/index.tsx": `import { SignInWithGoogle, signOut, useAuth, useMutation, useQuery } from "lakebed/client";
1610
1610
  import { cleanTodoText, type Todo } from "../shared/todo";
1611
1611
 
1612
+ function AuthAvatar({ label, picture }: { label: string; picture?: string }) {
1613
+ const initial = label.trim().slice(0, 1).toUpperCase() || "?";
1614
+
1615
+ if (picture) {
1616
+ return (
1617
+ <img
1618
+ alt=""
1619
+ className="h-7 w-7 shrink-0 rounded-full border border-neutral-800 bg-neutral-900 object-cover"
1620
+ referrerPolicy="no-referrer"
1621
+ src={picture}
1622
+ />
1623
+ );
1624
+ }
1625
+
1626
+ return (
1627
+ <span
1628
+ aria-hidden="true"
1629
+ className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-neutral-800 bg-neutral-900 text-xs font-medium text-neutral-300"
1630
+ >
1631
+ {initial}
1632
+ </span>
1633
+ );
1634
+ }
1635
+
1612
1636
  export function App() {
1613
1637
  const auth = useAuth();
1614
1638
  const todos = useQuery<Todo[]>("todos");
1615
1639
  const addTodo = useMutation<[text: string], void>("addTodo");
1616
- const authLabel = auth.email ?? auth.displayName;
1640
+ const authLabel = auth.displayName;
1617
1641
  const authStatus = auth.isLoading && auth.isGuest ? "checking session" : "signed in as " + authLabel;
1618
1642
 
1619
1643
  async function onSubmit(event: SubmitEvent) {
@@ -1633,11 +1657,14 @@ export function App() {
1633
1657
  <main className="min-h-screen bg-black px-6 py-10 text-white">
1634
1658
  <section className="mx-auto max-w-2xl">
1635
1659
  <div className="mb-3 flex items-center justify-between gap-3">
1636
- <p className="font-mono text-sm text-neutral-500">{authStatus}</p>
1660
+ <div className="flex min-w-0 items-center gap-2">
1661
+ {!auth.isLoading ? <AuthAvatar label={authLabel} picture={auth.picture} /> : null}
1662
+ <p className="min-w-0 truncate font-mono text-sm text-neutral-500">{authStatus}</p>
1663
+ </div>
1637
1664
  {!auth.isLoading && auth.isGuest ? (
1638
- <SignInWithGoogle className="border border-neutral-700 px-3 py-1.5 text-sm font-medium text-neutral-200 hover:border-white hover:text-white" />
1665
+ <SignInWithGoogle className="shrink-0 border border-neutral-700 px-3 py-1.5 text-sm font-medium text-neutral-200 hover:border-white hover:text-white" />
1639
1666
  ) : !auth.isLoading ? (
1640
- <button className="text-sm text-neutral-400 hover:text-white" type="button" onClick={() => signOut()}>
1667
+ <button className="shrink-0 text-sm text-neutral-400 hover:text-white" type="button" onClick={() => signOut()}>
1641
1668
  Sign out
1642
1669
  </button>
1643
1670
  ) : null}
package/src/client.d.ts CHANGED
@@ -9,7 +9,6 @@ export type Auth = {
9
9
  isLoading?: boolean;
10
10
  email?: string;
11
11
  emailVerified?: boolean;
12
- name?: string;
13
12
  picture?: string;
14
13
  };
15
14
 
package/src/client.js CHANGED
@@ -349,13 +349,13 @@ function createGoogleAuthFromToken(token) {
349
349
  return null;
350
350
  }
351
351
 
352
+ const displayName = typeof claims.name === "string" && claims.name.trim() ? claims.name.trim() : "Google User";
352
353
  return {
353
- displayName: claims.name ?? claims.email ?? "Google User",
354
+ displayName,
354
355
  email: claims.email,
355
356
  emailVerified: claims.email_verified,
356
357
  isAuthenticated: true,
357
358
  isGuest: false,
358
- name: claims.name,
359
359
  picture: claims.picture,
360
360
  provider: "google",
361
361
  userId: `google:${pairwiseSub}`
package/src/server.d.ts CHANGED
@@ -17,7 +17,6 @@ export type AuthContext = {
17
17
  isAuthenticated: boolean;
18
18
  email?: string;
19
19
  emailVerified?: boolean;
20
- name?: string;
21
20
  picture?: string;
22
21
  };
23
22
 
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export const LAKEBED_VERSION = "0.0.17";
1
+ export const LAKEBED_VERSION = "0.0.18";