Cold loads on versioned CDN deploys now show a consent card (editor MB + symbol/footprint lib figures, downloaded-now vs on-demand) and wait for OK before any big fetch; warm loads skip it and show truthful stages (loading from cache / Compiling / Starting KiCad) instead of the first-download line. - wasm-assets: resolveWasmMeta (bundle/ver/sizes), download-completion marker keyed by content-addressed bundle/ver, update wording, auto-download opt-out, HEAD size fallback - boot: manifest raw size as the progress total (fixes the br/gzip Content-Length mismatch), marker written after download+instantiate succeed - cdn-source: syncState() — IDB warmth peek + sizes.json cold sums - synced-source: syncState() from the backend envelope's sync refs (private platform); remote-source passes libSchema.sync through - publish-wasm: manifest schema 2 with per-bundle sizes (registry-persisted, reuse + snapshot modes); publish-libs: sizes.json sibling key + top-up mode - fixed 4 stale unit tests (bundle mapping, session-identity email) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CC8aAnUUHcnHy3QCJtUwb
103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
import {
|
|
contract,
|
|
PROJECT_HEADER,
|
|
SCOPE_HEADER,
|
|
USER_HEADER,
|
|
} from "@pcbjam/shared";
|
|
import { initClient } from "@ts-rest/core";
|
|
import type { LibInfo, LibItemInfo, LibsSource } from "./source";
|
|
|
|
/**
|
|
* A `LibsSource` backed by a contract-conforming backend (the closed registry
|
|
* server, or the GPL example backend). Read list ops go through the ts-rest
|
|
* client; item bodies stream from the raw text route
|
|
* `GET /api/scopes/:scope/libs/:lib/items/:kind/:name`, and writes hit the
|
|
* symmetric `PUT` route. The `scope` is a path param; `user` (thin per-user) and
|
|
* `project` (project-scoped resolution) ride USER_HEADER/PROJECT_HEADER.
|
|
*/
|
|
export function remoteLibsSource(
|
|
apiBase: string,
|
|
scope: string,
|
|
user?: string,
|
|
project?: string,
|
|
): LibsSource {
|
|
const reqHeaders: Record<string, string> = {
|
|
[SCOPE_HEADER]: scope,
|
|
...(user ? { [USER_HEADER]: user } : {}),
|
|
...(project ? { [PROJECT_HEADER]: project } : {}),
|
|
};
|
|
const client = initClient(contract, {
|
|
baseUrl: apiBase,
|
|
baseHeaders: reqHeaders,
|
|
// Session-cookie auth (backends with real auth ignore the thin headers).
|
|
credentials: "include",
|
|
});
|
|
const enc = encodeURIComponent;
|
|
|
|
const itemUrl = (libId: string, kind: string, name: string) =>
|
|
`${apiBase}/api/scopes/${enc(scope)}/libs/${enc(libId)}/items/` +
|
|
`${enc(kind)}/${enc(name)}`;
|
|
|
|
return {
|
|
async listLibs(kind?: string): Promise<LibInfo[]> {
|
|
const res = await client.listLibs({ params: { scope }, query: { kind } });
|
|
if (res.status !== 200) return [];
|
|
return res.body.map((l) => ({
|
|
id: l.id,
|
|
name: l.name,
|
|
description: l.description ?? null,
|
|
type: l.type,
|
|
itemCount: l.itemCount,
|
|
// Cache identity + cold-download bytes (standalone-load-ux 0002) —
|
|
// absent on backends predating the field.
|
|
sync: l.sync ?? null,
|
|
}));
|
|
},
|
|
|
|
async listItems(libId: string): Promise<LibItemInfo[]> {
|
|
const res = await client.listLibItems({ params: { scope, lib: libId } });
|
|
if (res.status !== 200) return [];
|
|
return res.body.map((i) => ({ kind: i.kind, name: i.name }));
|
|
},
|
|
|
|
async getItemBody(
|
|
libId: string,
|
|
kind: string,
|
|
name: string,
|
|
): Promise<string | null> {
|
|
const res = await fetch(itemUrl(libId, kind, name), {
|
|
headers: reqHeaders,
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) return null;
|
|
return await res.text();
|
|
},
|
|
|
|
async saveItemBody(
|
|
libId: string,
|
|
kind: string,
|
|
name: string,
|
|
body: string,
|
|
): Promise<boolean> {
|
|
const res = await fetch(itemUrl(libId, kind, name), {
|
|
method: "PUT",
|
|
headers: { ...reqHeaders, "Content-Type": "text/plain; charset=utf-8" },
|
|
body,
|
|
credentials: "include",
|
|
});
|
|
return res.ok;
|
|
},
|
|
|
|
async createLib(name: string): Promise<LibInfo | null> {
|
|
const res = await client.createLib({ params: { scope }, body: { name } });
|
|
if (res.status !== 201) return null;
|
|
return {
|
|
id: res.body.id,
|
|
name: res.body.name,
|
|
description: res.body.description ?? null,
|
|
type: res.body.type,
|
|
sync: res.body.sync ?? null,
|
|
};
|
|
},
|
|
};
|
|
}
|