You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6f01ef3
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec): UserSchema.image and OrganizationSchema.logo accept null, the shape better-auth serves
Both were `z.string().url().optional()` — a URL string or the key absent,
`null` refused. Both columns are better-auth-owned and nullable
(`sys_user.image` / `sys_organization.logo` are each
`Field.url({ required: false })`, reaching SQLite as `varchar(255)` with
`notnull=0`), and better-auth SELECTs them and serialises them
present-and-null for a user who never set an avatar and an organization
created without a logo.
Measured through a real `AuthManager` (better-auth 1.7.3) over a real
`ObjectQL` on a real `SqliteWasmDriver`, with the platform's own object
definitions — not inferred from the sibling ruling:
/auth/sign-up/email -> user.image = null
/auth/get-session -> user.image = null
/auth/organization/create -> logo = null
/auth/organization/list -> [0].logo = null
/auth/organization/get-full-organization -> logo = null
-> members[].user.image = null
`.nullish()`, not `.nullable()`: the key's absence is a legal shape today,
so `.nullable()` would retire a live shape as the price of admitting null.
`.url()` is kept and does not fight `null` — `.nullish()` wraps the whole
`z.string().url()`, so null and undefined are branches the URL check never
sees while a present string must still be a well-formed URL. Of six inputs
(absent / null / '' / a URL / a non-URL / a number) exactly one row moves.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
`UserSchema.image` and `OrganizationSchema.logo` are declared `z.string().url().nullish()` — a URL string, `null`, or the key absent are all accepted — so the user and organization bodies this platform serves parse against the schemas it publishes (#18509).
6
+
7
+
Both were `z.string().url().optional()`: a URL string or the key's absence, and `null` refused. Both columns are better-auth-owned and nullable — `sys_user.image` and `sys_organization.logo` are each `Field.url({ required: false })`, reaching SQLite as `varchar(255)` with `notnull=0` — and better-auth SELECTs them and serialises them present-and-null for a user who never set an avatar and an organization created without a logo.
8
+
9
+
Measured through a real `AuthManager` (better-auth 1.7.3) over a real `ObjectQL` on a real `SqliteWasmDriver`, with the platform's own `sys_user` / `sys_organization` object definitions:
10
+
11
+
```
12
+
/auth/sign-up/email -> user.image = null
13
+
/auth/get-session -> user.image = null
14
+
/auth/organization/create -> logo = null
15
+
/auth/organization/list -> [0].logo = null
16
+
/auth/organization/get-full-organization
17
+
-> logo = null
18
+
-> members[].user.image = null
19
+
20
+
UserSchema.safeParse(<the served session user>)
21
+
-> [{ path: ["image"], code: "invalid_type",
22
+
message: "Invalid input: expected string, received null" }]
23
+
OrganizationSchema.safeParse(<the served organization>)
24
+
-> [{ path: ["logo"], code: "invalid_type",
25
+
message: "Invalid input: expected string, received null" }, … ]
26
+
```
27
+
28
+
Those two paths now parse.
29
+
30
+
-**Measured, not inferred.**#18509 exists because PR #18501's contract review named these two siblings as *not measured* rather than folding them into the `SessionUserSchema.image` ruling it had. The verdict here comes from the probe above, run the way that ruling's own evidence was taken; the analogy was only ever a reason to look.
31
+
-**The declaration was the thing that was wrong.** Prime Directive #12's default — fix the producer, never widen the consumer — rests on the premise it states out loud, that we own both ends. We do not: the nullable columns belong to a third-party model, so PD #12's own exit clause is the operative sentence.
32
+
-**A pure widening.**`.nullish()`, not `.nullable()`: the key's ABSENCE is a legal shape today, so `.nullable()` would retire a live shape as the price of admitting `null`. Every body legal before this change is still legal.
33
+
-**`.url()` is kept, and it does not fight `null`.** These two declarations carry `.url()`, which `SessionUserSchema.image` did not, so the question had to be answered rather than copied. `.nullish()` wraps the whole `z.string().url()`: `null` and `undefined` are separate branches the URL check never sees, while a present string is still required to be a well-formed URL. Of six inputs — absent, `null`, `''`, a URL, a non-URL, a number — exactly one row moves, and it is the ruled one. `''` and `'not-a-url'` are still refused.
34
+
-**No key is added or removed** — both keys were already authored and already published, so no authorable surface moves and nothing is retired.
35
+
-**`OrganizationSchema` is not made whole by this.** The same probe found `metadata` served present-and-null and `/auth/organization/create` omitting the required `updatedAt`. Those are separate defects with their own reasoning, filed separately rather than folded in; #18509 asked about `logo`.
0 commit comments