feat: libs 0005 — standalone sends the project header on lib requests (project-scoped server-side resolution); bump pcbjam-shared

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-13 17:16:40 +02:00
commit f04915ee1d
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
4 changed files with 20 additions and 14 deletions

@ -1 +1 @@
Subproject commit a1d18b53a7b976c219a824800027c289b9e9fe2c Subproject commit df297291b0bdfd0228c900e9c1a0c7279ffd1533

View file

@ -442,7 +442,7 @@ export function WasmTool({
log: append, log: append,
onStatus: setStatus, onStatus: setStatus,
onAbort: oom.onAbort, onAbort: oom.onAbort,
libsSource: libsSourceConfig(), libsSource: libsSourceConfig(projectId),
}); });
// Register the save sink before the file opens: from here on, every // Register the save sink before the file opens: from here on, every
// editor File→Save (MEMFS write) is routed onward through saveBytes. // editor File→Save (MEMFS write) is routed onward through saveBytes.

View file

@ -69,14 +69,14 @@ export function libsOwner(): string {
return import.meta.env.VITE_LIBS_OWNER ?? "local-user"; return import.meta.env.VITE_LIBS_OWNER ?? "local-user";
} }
export function libsSourceConfig(): LibsSource | null { export function libsSourceConfig(projectId?: string): LibsSource | null {
const kind = import.meta.env.VITE_LIBS_SOURCE ?? "remote"; const kind = import.meta.env.VITE_LIBS_SOURCE ?? "remote";
const base = const base =
kind === "off" kind === "off"
? null ? null
: kind === "static" : kind === "static"
? staticLibsSource() ? staticLibsSource()
: remoteLibsSource(API_BASE_URL, libsOwner()); : remoteLibsSource(API_BASE_URL, libsOwner(), projectId);
// 0004-A spike: `?libwrite=1` adds one in-memory writable user lib so the // 0004-A spike: `?libwrite=1` adds one in-memory writable user lib so the
// editor save path works with no backend (a dev/test aid). The real remote // editor save path works with no backend (a dev/test aid). The real remote

View file

@ -1,4 +1,4 @@
import { contract, OWNER_HEADER } from "@pcbjam/shared"; import { contract, OWNER_HEADER, PROJECT_HEADER } from "@pcbjam/shared";
import { initClient } from "@ts-rest/core"; import { initClient } from "@ts-rest/core";
import type { LibInfo, LibItemInfo, LibsSource } from "./source"; import type { LibInfo, LibItemInfo, LibsSource } from "./source";
@ -7,16 +7,22 @@ import type { LibInfo, LibItemInfo, LibsSource } from "./source";
* server, or the GPL example backend). Read list ops go through the ts-rest * server, or the GPL example backend). Read list ops go through the ts-rest
* client; item bodies stream from the raw text route * client; item bodies stream from the raw text route
* `GET /api/libs/:lib/items/:kind/:name`, and writes hit the symmetric * `GET /api/libs/:lib/items/:kind/:name`, and writes hit the symmetric
* `PUT` route (binary/text doesn't round-trip ts-rest). The `owner` is carried * `PUT` route (binary/text doesn't round-trip ts-rest). Every request carries
* on every request via `OWNER_HEADER` (thin per-user; absent ⇒ backend default). * the `owner` (thin per-user) and the `project` (project-scoped server-side
* resolution) via `OWNER_HEADER`/`PROJECT_HEADER`; absent ⇒ backend default.
*/ */
export function remoteLibsSource(apiBase: string, owner?: string): LibsSource { export function remoteLibsSource(
const ownerHeaders: Record<string, string> = owner apiBase: string,
? { [OWNER_HEADER]: owner } owner?: string,
: {}; project?: string,
): LibsSource {
const reqHeaders: Record<string, string> = {
...(owner ? { [OWNER_HEADER]: owner } : {}),
...(project ? { [PROJECT_HEADER]: project } : {}),
};
const client = initClient(contract, { const client = initClient(contract, {
baseUrl: apiBase, baseUrl: apiBase,
baseHeaders: ownerHeaders, baseHeaders: reqHeaders,
}); });
const itemUrl = (libId: string, kind: string, name: string) => const itemUrl = (libId: string, kind: string, name: string) =>
@ -47,7 +53,7 @@ export function remoteLibsSource(apiBase: string, owner?: string): LibsSource {
name: string, name: string,
): Promise<string | null> { ): Promise<string | null> {
const res = await fetch(itemUrl(libId, kind, name), { const res = await fetch(itemUrl(libId, kind, name), {
headers: ownerHeaders, headers: reqHeaders,
}); });
if (!res.ok) return null; if (!res.ok) return null;
return await res.text(); return await res.text();
@ -61,7 +67,7 @@ export function remoteLibsSource(apiBase: string, owner?: string): LibsSource {
): Promise<boolean> { ): Promise<boolean> {
const res = await fetch(itemUrl(libId, kind, name), { const res = await fetch(itemUrl(libId, kind, name), {
method: "PUT", method: "PUT",
headers: { ...ownerHeaders, "Content-Type": "text/plain; charset=utf-8" }, headers: { ...reqHeaders, "Content-Type": "text/plain; charset=utf-8" },
body, body,
}); });
return res.ok; return res.ok;