/** Same-origin endpoint descriptions for a future HUX-04 backend adapter. */ import { artifactsEnabled } from "./model.ts"; import { isOpaqueId, normalizeIdentity, scopedPath } from "./security.ts"; import type { ArtifactEndpoint, ArtifactEndpointContract, ArtifactScope, HuxIdentity, } from "./types.ts"; interface FoundationClient { apiVersion: string; identity: HuxIdentity; endpoint(path: string): string; } function endpoint(method: ArtifactEndpoint["method"], path: string, responseSchema: string, requiredBody?: readonly string[]): ArtifactEndpoint { return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})}); } function versionNumber(value: number): string { if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("Artifact version is invalid"); return String(value); } /** * The server derives identity from the authenticated session and verifies the * project/conversation path binding. Mutation bodies MUST carry * expected_current_version and return 409 on stale state. Existing versions * are never PATCHed or deleted; edits append a new content_ref-backed version. */ export function createArtifactEndpointContract(client: FoundationClient, scope: ArtifactScope, flags?: Iterable): ArtifactEndpointContract | null { if (!artifactsEnabled(flags)) return null; if (client.apiVersion !== "hux.v1") throw new TypeError("HUX v1 client required"); normalizeIdentity({tenant_ref: client.identity.tenantRef, user_ref: client.identity.userRef, surface: client.identity.surface}); const collection = scopedPath("/projects", scope); function itemPath(artifactId: string): string { if (!isOpaqueId(artifactId, "art")) throw new TypeError("Opaque artifact id required"); return scopedPath("/projects", scope, artifactId); } function itemEndpoint(artifactId: string, suffix = ""): string { return client.endpoint(`${itemPath(artifactId)}${suffix}`); } return Object.freeze({ list: endpoint("GET", client.endpoint(collection), "hux.artifact_workspace.v1"), item(artifactId) { return endpoint("GET", itemEndpoint(artifactId), "hux.artifact_response.v1"); }, preview(artifactId, version) { return endpoint("GET", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/preview`), "hux.artifact_preview.v1"); }, diff(artifactId, fromVersion, toVersion) { if (fromVersion >= toVersion) throw new TypeError("Artifact diff range is invalid"); return endpoint("POST", itemEndpoint(artifactId, "/diffs"), "hux.artifact_diff.v1", Object.freeze(["from_version", "to_version", "expected_current_version"])); }, appendVersion(artifactId) { return endpoint("POST", itemEndpoint(artifactId, "/versions"), "hux.artifact_response.v1", Object.freeze(["content_ref", "expected_current_version"])); }, continueEdit(artifactId) { return endpoint("POST", itemEndpoint(artifactId, "/continue"), "hux.artifact_edit_intent.v1", Object.freeze(["version", "expected_current_version"])); }, authorize(artifactId, action) { if (action !== "download" && action !== "share") throw new TypeError("Artifact action is invalid"); return endpoint("POST", itemEndpoint(artifactId, `/authorizations/${action}`), "hux.artifact_authorization.v1", Object.freeze(["expected_current_version"])); }, download(artifactId, version) { return endpoint("GET", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/download`), "hux.artifact_content.v1"); }, share(artifactId, version) { return endpoint("POST", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/shares`), "hux.artifact_share.v1", Object.freeze(["expected_current_version"])); }, promote(artifactId) { return endpoint("POST", itemEndpoint(artifactId, "/promotions"), "hux.artifact_response.v1", Object.freeze(["target_project_id", "version", "expected_current_version"])); }, sources(artifactId) { return endpoint("GET", itemEndpoint(artifactId, "/sources"), "hux.artifact_sources.v1"); }, }); }