From 1133eca16abe06a3f7b4b33fdd4e8d9509bb77dd Mon Sep 17 00:00:00 2001 From: Aravind Date: Thu, 17 Sep 2026 22:59:15 +0530 Subject: [PATCH] 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 | 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` 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>` makes `list()` return `Promise` where inference gives `Promise`, 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` 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) Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 --- src/api/{base44Client.js => base44Client.ts} | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) rename src/api/{base44Client.js => base44Client.ts} (94%) diff --git a/src/api/base44Client.js b/src/api/base44Client.ts similarity index 94% rename from src/api/base44Client.js rename to src/api/base44Client.ts index 05f1992..f8369f2 100644 --- a/src/api/base44Client.js +++ b/src/api/base44Client.ts @@ -143,7 +143,11 @@ let authenticated = false; * 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. */ -let hydration = request('GET', '/me') +/* `Promise | 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` and reject the + two `hydration = null` assignments that make the sharing work. */ +let hydration: Promise | null = request('GET', '/me') .then((user) => { currentUser = user; authenticated = true; @@ -204,7 +208,11 @@ const auth = { * by design: telling the two apart would say whether an address 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', { body: { email, password, remember_me: Boolean(rememberMe) }, }); @@ -215,7 +223,7 @@ const auth = { return { ...user }; }, - async updateMe(patch) { + async updateMe(patch: any) { const user = await request('PATCH', '/me', { body: patch }); currentUser = user; cacheUser(); @@ -247,7 +255,7 @@ const auth = { * question the shape exists to answer is now answered by whether this * function resolved at all. */ - async updatePreferences(patch) { + async updatePreferences(patch: any) { const preferences = await request('PATCH', '/me/preferences', { body: patch }); currentUser = { ...currentUser, preferences }; cacheUser(); @@ -274,7 +282,7 @@ const auth = { * 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. */ - async logout(redirectTo = '/admin/login') { + async logout(redirectTo: string = '/admin/login') { try { await request('POST', '/auth/logout'); } catch { @@ -320,7 +328,7 @@ const workflows = { * 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. */ - async hire(applicationId, body = {}) { + async hire(applicationId: string, body: any = {}) { return request('POST', `/job-applications/${encodeURIComponent(applicationId)}/hire`, { body, }); @@ -336,7 +344,7 @@ const workflows = { * inside the same transaction; a worker taken straight from the talent pool * 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`, { body: { workers }, }); @@ -368,7 +376,7 @@ const workflows = { */ const owliver = { /** @param {any} request */ - async suggestions({ page, query = '' } = {}) { + async suggestions({ page, query = '' }: { page?: string; query?: string } = {}) { if (!page) return []; const typed = String(query || '').trim(); const data = await request('GET', '/owliver/suggestions', { @@ -388,7 +396,7 @@ const integrations = { }; const analytics = { - track({ eventName, properties } = {}) { + track({ eventName, properties }: { eventName?: string; properties?: any } = {}) { if (import.meta.env.DEV) { console.debug('[krow-demo] analytics', eventName, properties || {}); }