chore(ts-migration): migrate base44 client to TypeScript

Phase 4b-ii, and the last file in the transport layer. Type-only: the plain
emitted JavaScript is byte-identical at 9,302 bytes.

This file owns the session, so the annotations stay at the edges and nothing
about the auth model moves. `SESSION_KEY`, the localStorage mirror, the three
module-level mutables, the shared hydration promise, the `{ ...user }` spreads,
`window.location.href` on logout and `window.location.reload()` in
`resetDemoData` are all exactly as they were.

Nine sites, eight of them parameter types. The ninth is `hydration`, which needs
`Promise<any> | null` because the runtime already assigns both: the shared first
`GET /me`, then `null` once it has been consumed or superseded by a login.
Inference would have fixed it at `Promise<any>` and rejected the assignments
that make the sharing work.

`entities` is deliberately NOT annotated, and neither is `createEntity`'s
return. A probe run during the inspection showed what annotating it costs:
`Record<EntityName, EntityClient<any>>` makes `list()` return `Promise<any[]>`
where inference gives `Promise<any>`, and `agentStore.js` does
`qc.getQueryData(KEY) || (await ...list(...))`. React Query types
`getQueryData` as `unknown`; `unknown || any` collapses to `any` while
`unknown || any[]` stays `unknown`, and `.find` on the next line stops
compiling. Two new errors in a file this phase does not migrate. The generated
record types and `EntityClientFor<K>` are ready for the phase that migrates the
forty-five call sites in `krowHooks.js`.

The auth returns are likewise left inferred rather than typed `User`. That was
checked, not assumed: `admin/Profile.jsx` reads `user.avatar_url`, which is
neither a column on `users` nor part of the `/me` projection in `me.go` - it is
always undefined at run time. Typing the return would have turned a latent dead
branch into two compile errors in a file this phase does not touch. Worth
knowing about separately; not this commit's business.

One stale comment is kept verbatim - `@param {any} request` above
`owliver.suggestions`, which names a parameter that does not exist. Removing it
was the only remaining difference in the emitted output, and byte-identity is
worth more here than tidying a comment. Flagged for a later docs pass.

Verified: tsc 42 -> 40 with a set-difference showing two removed and none added;
npm test 1684/1691, the same seven failures as the parent commit (six documented
as deliberate in scripts/__baseline__/README.md, one the stale backend fixture);
lint 0 errors; build succeeds with the API origin inlined. This is also the
first run of the suite with a source-text-inspected file migrated - the harness
fix in 02a2ab0 is what makes it possible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-17 22:59:15 +05:30
parent 02a2ab05ef
commit 1133eca16a

View File

@@ -143,7 +143,11 @@ let authenticated = false;
* A 401 here is the ordinary state of a signed-out visitor, not a failure: the * A 401 here is the ordinary state of a signed-out visitor, not a failure: the
* app opens on the login page and this request is how it finds that out. * app opens on the login page and this request is how it finds that out.
*/ */
let hydration = request('GET', '/me') /* `Promise<any> | null`, because the runtime already assigns both: the shared
first request, then `null` once it has been consumed or superseded by a
login. Inference alone would fix the type at `Promise<any>` and reject the
two `hydration = null` assignments that make the sharing work. */
let hydration: Promise<any> | null = request('GET', '/me')
.then((user) => { .then((user) => {
currentUser = user; currentUser = user;
authenticated = true; authenticated = true;
@@ -204,7 +208,11 @@ const auth = {
* by design: telling the two apart would say whether an address is * by design: telling the two apart would say whether an address is
* registered. The caller shows that message as-is. * registered. The caller shows that message as-is.
*/ */
async login({ email, password, rememberMe = false }) { async login({ email, password, rememberMe = false }: {
email: string;
password: string;
rememberMe?: boolean;
}) {
const user = await request('POST', '/auth/login', { const user = await request('POST', '/auth/login', {
body: { email, password, remember_me: Boolean(rememberMe) }, body: { email, password, remember_me: Boolean(rememberMe) },
}); });
@@ -215,7 +223,7 @@ const auth = {
return { ...user }; return { ...user };
}, },
async updateMe(patch) { async updateMe(patch: any) {
const user = await request('PATCH', '/me', { body: patch }); const user = await request('PATCH', '/me', { body: patch });
currentUser = user; currentUser = user;
cacheUser(); cacheUser();
@@ -247,7 +255,7 @@ const auth = {
* question the shape exists to answer is now answered by whether this * question the shape exists to answer is now answered by whether this
* function resolved at all. * function resolved at all.
*/ */
async updatePreferences(patch) { async updatePreferences(patch: any) {
const preferences = await request('PATCH', '/me/preferences', { body: patch }); const preferences = await request('PATCH', '/me/preferences', { body: patch });
currentUser = { ...currentUser, preferences }; currentUser = { ...currentUser, preferences };
cacheUser(); cacheUser();
@@ -274,7 +282,7 @@ const auth = {
* reach the API must still leave the browser signed out locally, and the * reach the API must still leave the browser signed out locally, and the
* cookie it keeps will be refused by every request it is sent on. * cookie it keeps will be refused by every request it is sent on.
*/ */
async logout(redirectTo = '/admin/login') { async logout(redirectTo: string = '/admin/login') {
try { try {
await request('POST', '/auth/logout'); await request('POST', '/auth/logout');
} catch { } catch {
@@ -320,7 +328,7 @@ const workflows = {
* across from the application by the server, because it is already the truth * across from the application by the server, because it is already the truth
* about this person and retyping it here is how the two records drift apart. * about this person and retyping it here is how the two records drift apart.
*/ */
async hire(applicationId, body = {}) { async hire(applicationId: string, body: any = {}) {
return request('POST', `/job-applications/${encodeURIComponent(applicationId)}/hire`, { return request('POST', `/job-applications/${encodeURIComponent(applicationId)}/hire`, {
body, body,
}); });
@@ -336,7 +344,7 @@ const workflows = {
* inside the same transaction; a worker taken straight from the talent pool * inside the same transaction; a worker taken straight from the talent pool
* with neither is placed without one rather than given an invented one. * with neither is placed without one rather than given an invented one.
*/ */
async assign(jobPostingId, workers = []) { async assign(jobPostingId: string, workers: any[] = []) {
return request('POST', `/job-postings/${encodeURIComponent(jobPostingId)}/assignments`, { return request('POST', `/job-postings/${encodeURIComponent(jobPostingId)}/assignments`, {
body: { workers }, body: { workers },
}); });
@@ -368,7 +376,7 @@ const workflows = {
*/ */
const owliver = { const owliver = {
/** @param {any} request */ /** @param {any} request */
async suggestions({ page, query = '' } = {}) { async suggestions({ page, query = '' }: { page?: string; query?: string } = {}) {
if (!page) return []; if (!page) return [];
const typed = String(query || '').trim(); const typed = String(query || '').trim();
const data = await request('GET', '/owliver/suggestions', { const data = await request('GET', '/owliver/suggestions', {
@@ -388,7 +396,7 @@ const integrations = {
}; };
const analytics = { const analytics = {
track({ eventName, properties } = {}) { track({ eventName, properties }: { eventName?: string; properties?: any } = {}) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.debug('[krow-demo] analytics', eventName, properties || {}); console.debug('[krow-demo] analytics', eventName, properties || {});
} }