feat(wails): move the frontend onto v3's generated bindings
frontend/wailsjs/ is deleted and frontend/bindings/ takes its place — a real TypeScript module tree nested by Go import path, generated by wails3's static analyser rather than by building the app and running it. The @go alias absorbs the constant prefix, so a call site imports '@go/library/library.js' and the codemod over all 93 sites was a specifier rewrite plus splitting @go/models' namespaces into one import per package. The 12 SetContext bindings and the fake `context` model are gone, as Phase 2's ServiceStartup port promised: 272 methods across 12 services, none of them plumbing. @runtime/runtime is now a local shim (src/wails/runtime.ts) over @wailsio/runtime, so the 22 EventsOn imports are untouched. It unwraps v3's WailsEvent into v2's callback shape, which is exact here: nothing in backend/events passes more than one data argument, and v3 only packs arguments into a slice when there is more than one. v3 tells the truth about two things v2 lied about, and that is most of the diff. A Go nil slice really does arrive as JSON null, and a Go named string type really is an enum; v2 typed them as T[] and string. utils/binding.ts states the app's actual contract — an absent list is an empty list — once, at the boundary where it is true, and also drops the CancellablePromise the app never cancels. Four test fixtures widen an enum field back to its value union. Not done, and Phase 5's to fix: frontend/test/support/wails-fake.ts still fakes window.go, which v3 does not have, so `make ui-test` is broken and harness.test.ts fails to compile on EventsEmit. That test also asserts v2 ordering that no longer holds — v3's Events.Emit calls the backend and does not notify in-page listeners at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDCbcCZQepnpSQYJ6SxxZm
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* The boundary between a Go return value and the app's own types.
|
||||
*
|
||||
* v2's binding generator typed a `[]T` return as `T[]`, which was a
|
||||
* lie: a nil slice marshals to JSON `null`, and every list in this app
|
||||
* has always been able to arrive that way. v3 types it honestly as
|
||||
* `T[] | null`, which surfaced ~50 sites that were relying on the lie.
|
||||
*
|
||||
* The app's contract is the one it has always behaved as if it had —
|
||||
* *an absent list is an empty list* — so it is stated once here rather
|
||||
* than as `?? []` at every call site, and stated at the only place it
|
||||
* is true: the moment a value crosses from Go.
|
||||
*
|
||||
* These helpers also return a plain `Promise`. v3 bindings return a
|
||||
* `CancellablePromise`, and nothing in this app cancels one; letting
|
||||
* that type leak inward would put a Wails type in the signature of
|
||||
* every store method for a capability none of them use.
|
||||
*/
|
||||
|
||||
/**
|
||||
* list awaits a binding returning a Go slice and yields `[]` for nil.
|
||||
*/
|
||||
export async function list<T>(
|
||||
request: PromiseLike<T[] | null>,
|
||||
): Promise<T[]> {
|
||||
return (await request) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* dict awaits a binding returning a Go map and yields `{}` for nil.
|
||||
*
|
||||
* Two shapes of the same lie are undone here. The generator types a
|
||||
* `map[int64]T` with a template-literal key (`` `${number}` ``), which
|
||||
* cannot be indexed by a `number` even though every such key is one;
|
||||
* and it types each value as nullable, because a map of slices can
|
||||
* hold a nil one. A null-valued key is dropped rather than kept,
|
||||
* which loses nothing: `noUncheckedIndexedAccess` already makes every
|
||||
* read `V | undefined`, so an absent key and a nil value are
|
||||
* indistinguishable to every consumer.
|
||||
*/
|
||||
export async function dict<V>(
|
||||
request: PromiseLike<Record<string, V | null | undefined> | null>,
|
||||
): Promise<Record<number, V>> {
|
||||
const raw = (await request) ?? {};
|
||||
const out: Record<number, V> = {};
|
||||
|
||||
for (const [key, val] of Object.entries(raw)) {
|
||||
if (val != null) out[Number(key)] = val;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* dictByName is dict for a Go map keyed by something that is already a
|
||||
* string — an MBID, a file path, a genre name.
|
||||
*/
|
||||
export async function dictByName<V>(
|
||||
request: PromiseLike<Record<string, V | null | undefined> | null>,
|
||||
): Promise<Record<string, V>> {
|
||||
return compact(await request);
|
||||
}
|
||||
|
||||
/**
|
||||
* compact is dictByName for a map that arrived as a *field* rather
|
||||
* than as a return value — a nested `map[string]string`, which the
|
||||
* generator types with optional values because a JSON object need not
|
||||
* carry every key.
|
||||
*/
|
||||
export function compact<V>(
|
||||
map: Record<string, V | null | undefined> | null | undefined,
|
||||
): Record<string, V> {
|
||||
const out: Record<string, V> = {};
|
||||
|
||||
for (const [key, val] of Object.entries(map ?? {})) {
|
||||
if (val != null) out[key] = val;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* value awaits a binding whose result is used as-is, dropping only the
|
||||
* cancellation the app never asks for.
|
||||
*/
|
||||
export async function value<T>(request: PromiseLike<T>): Promise<T> {
|
||||
return await request;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { downloadStore } from '@store/download-store';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import type { download } from '@go/models';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import type { LibraryStatus } from '../components/library-status-indicator/library-status-indicator';
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* given array and never again.
|
||||
*/
|
||||
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
|
||||
const byArray = new WeakMap<
|
||||
readonly library.Track[],
|
||||
|
||||
Reference in New Issue
Block a user