feat: register the TechLog Studio contract contribution
Registers the TECH_LOG_STUDIO_SESSION SAME_ORIGIN_COOKIE auth profile and declares the tech-log-studio-http-v1 contribution covering all 18 JSON Studio operations (everything except the multipart uploadStudioAsset), built from two shared builders (safeOperation/keyedOperation) so every KEYED command gets IDEMPOTENCY_REPLAY recovery and a zero retry budget, and every SAFE read gets a plain retry budget, without repeating the declaration shape 18 times. Rescopes the canonical package identity from "tech-log-studio-contract" to "@tech-log/studio-contract" (generator, canonical-source.json, contract-generation.test.ts) because the platform's contribution composer requires an npm-scoped packageId; the unscoped form failed composition. Updates the release-manifest test fixture, which hardcoded an empty expected contract set, to derive its expected packages from the real installed set now that TechLog is always installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a6fc536d8a
commit
66c047cec8
@@ -70,6 +70,19 @@ export const REST_AUTH_PROFILES = Object.freeze({
|
||||
allowedCredentialHeaders: Object.freeze([]),
|
||||
requiredCredentialHeaders: Object.freeze([]),
|
||||
}),
|
||||
/**
|
||||
* TechLog Studio session profile. Canonical mandates a session cookie plus
|
||||
* `X-CSRF-TOKEN` on every mutating Studio operation; the platform already
|
||||
* has this combination first-class as `SAME_ORIGIN_COOKIE` credentials with
|
||||
* an `x-csrf-token` credential header.
|
||||
*/
|
||||
TECH_LOG_STUDIO_SESSION: Object.freeze({
|
||||
authProfileId: "TECH_LOG_STUDIO_SESSION",
|
||||
transport: "SAME_ORIGIN_COOKIE",
|
||||
credentials: "include",
|
||||
allowedCredentialHeaders: Object.freeze(["x-csrf-token"] as const),
|
||||
requiredCredentialHeaders: Object.freeze(["x-csrf-token"] as const),
|
||||
}),
|
||||
} satisfies Readonly<Record<string, RestAuthProfile>>);
|
||||
|
||||
function isCredentialHeaderName(value: unknown): value is CredentialHeaderName {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "./reference-feature/contracts/reference-feature-contract-contribution.ts";
|
||||
import { REFERENCE_FEATURE_ID } from "./reference-feature/contracts/reference-feature-contract.ts";
|
||||
import { INSTALLED_PRODUCT_FEATURE_IDS } from "./installed-product-manifest.ts";
|
||||
import { TECH_LOG_STUDIO_CONTRIBUTION } from "./tech-log/contracts/tech-log-studio-contract-contribution.ts";
|
||||
|
||||
/**
|
||||
* §4.8. Static contract selection SSOT.
|
||||
@@ -17,8 +18,8 @@ import { INSTALLED_PRODUCT_FEATURE_IDS } from "./installed-product-manifest.ts";
|
||||
export const INSTALLED_CONTRACT_CONTRIBUTIONS: readonly InstalledContractContribution[] =
|
||||
Object.freeze(
|
||||
INSTALLED_PRODUCT_FEATURE_IDS.includes(REFERENCE_FEATURE_ID)
|
||||
? [REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION]
|
||||
: [],
|
||||
? [REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION, TECH_LOG_STUDIO_CONTRIBUTION]
|
||||
: [TECH_LOG_STUDIO_CONTRIBUTION],
|
||||
);
|
||||
|
||||
export const COMPOSED_CONTRACT_CONTRIBUTIONS = composeContractContributions(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"packageId": "tech-log-studio-contract",
|
||||
"packageId": "@tech-log/studio-contract",
|
||||
"version": "2.0.0",
|
||||
"digest": "sha256:99f54f56ea0c582eafdbdf9be5653e3384bef0a1b08bff67f3147ee0292019ea",
|
||||
"sourceRevision": "ce2e748",
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type {
|
||||
CommandEffectDescriptor,
|
||||
InstalledContractContribution,
|
||||
InstalledHttpContract,
|
||||
RuntimeValidator,
|
||||
} from "../../../contracts/external-contract-runtime.ts";
|
||||
import { TECH_LOG_FEATURE_ID } from "../application/tech-log-feature-input.ts";
|
||||
import canonicalSource from "./studio/canonical-source.json" with { type: "json" };
|
||||
import { STUDIO_ERROR_CODES } from "../adapters/http/studio-error-mapping.ts";
|
||||
|
||||
function zodValidator<T>(schemaId: string, schema: z.ZodType<T>): RuntimeValidator<T> {
|
||||
return Object.freeze({
|
||||
schemaId,
|
||||
safeParse(value: unknown) {
|
||||
const result = schema.safeParse(value);
|
||||
if (result.success) {
|
||||
return Object.freeze({ success: true as const, data: structuredClone(result.data) });
|
||||
}
|
||||
return Object.freeze({
|
||||
success: false as const,
|
||||
issues: Object.freeze(
|
||||
result.error.issues.map((issue) =>
|
||||
Object.freeze({
|
||||
path: Object.freeze(
|
||||
issue.path.map((segment): string | number =>
|
||||
typeof segment === "number" ? segment : String(segment),
|
||||
),
|
||||
),
|
||||
code: String(issue.code),
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 payload는 canonical 계약이 소유한다. 전송 계층은 문제 문서만 엄격히
|
||||
* 검증하고 성공 payload는 통과시킨다 — generated 타입이 컴파일 시점 계약이고,
|
||||
* 런타임 재검증은 계약 갱신 때마다 두 곳을 고치게 만든다.
|
||||
*/
|
||||
const passthrough = <T>(schemaId: string) =>
|
||||
zodValidator<T>(schemaId, z.unknown() as unknown as z.ZodType<T>);
|
||||
|
||||
const problemSchema = z
|
||||
.object({
|
||||
type: z.string().min(1).max(512),
|
||||
title: z.string().min(1).max(240),
|
||||
status: z.int().min(400).max(599),
|
||||
detail: z.string().min(1).max(5000),
|
||||
code: z.enum(STUDIO_ERROR_CODES as unknown as [string, ...string[]]),
|
||||
})
|
||||
.loose();
|
||||
|
||||
const PROBLEM = zodValidator("StudioProblemDetails", problemSchema);
|
||||
|
||||
/** 4xx 도메인 거절은 적용되지 않았음이 확정이다. 5xx/네트워크는 불확정이다. */
|
||||
const COMMAND_EFFECT: CommandEffectDescriptor<z.output<typeof problemSchema>> =
|
||||
Object.freeze({
|
||||
successEffect: "APPLIED_CONFIRMED" as const,
|
||||
classifyProblem({ status }: Readonly<{ status: number; problem: unknown }>) {
|
||||
return status >= 400 && status < 500 ? "NOT_APPLIED" : "MAYBE_APPLIED";
|
||||
},
|
||||
});
|
||||
|
||||
type PathValues = Readonly<Record<string, string>>;
|
||||
type QueryEntries = readonly (readonly [string, string])[];
|
||||
|
||||
function safeOperation(
|
||||
operationId: string,
|
||||
pathTemplate: string,
|
||||
responseByteLimit: number,
|
||||
project: (input: never) => Readonly<{ pathValues: PathValues; queryEntries: QueryEntries }>,
|
||||
): InstalledHttpContract<unknown, unknown, unknown> {
|
||||
return Object.freeze({
|
||||
contract: Object.freeze({
|
||||
operationId,
|
||||
method: "GET" as const,
|
||||
pathTemplate,
|
||||
inputValidator: passthrough(`${operationId}Input`),
|
||||
outputValidator: passthrough(`${operationId}Output`),
|
||||
problemValidator: PROBLEM,
|
||||
acceptedStatuses: Object.freeze([200]),
|
||||
emptyBodyStatuses: Object.freeze([]),
|
||||
retrySemantics: "SAFE" as const,
|
||||
requestBody: "NONE" as const,
|
||||
responseBody: "REQUIRED_JSON" as const,
|
||||
commandRecovery: null,
|
||||
commandEffect: null,
|
||||
projectRequest(input: never) {
|
||||
const projected = project(input);
|
||||
return Object.freeze({ ...projected, body: null });
|
||||
},
|
||||
}),
|
||||
frontend: Object.freeze({
|
||||
policyId: `${operationId}_V1`,
|
||||
requestByteLimit: 0,
|
||||
responseByteLimit,
|
||||
totalDeadlineMs: 10_000,
|
||||
retryBudget: 2 as const,
|
||||
authProfileId: "TECH_LOG_STUDIO_SESSION",
|
||||
diagnosticsOperation: `techLog.studio.${operationId}`,
|
||||
}),
|
||||
}) as InstalledHttpContract<unknown, unknown, unknown>;
|
||||
}
|
||||
|
||||
function keyedOperation(
|
||||
operationId: string,
|
||||
method: "POST" | "PUT" | "DELETE",
|
||||
pathTemplate: string,
|
||||
options: Readonly<{
|
||||
acceptedStatus: number;
|
||||
responseByteLimit: number;
|
||||
requestByteLimit: number;
|
||||
hasBody: boolean;
|
||||
}>,
|
||||
project: (input: never) => Readonly<{
|
||||
pathValues: PathValues;
|
||||
queryEntries: QueryEntries;
|
||||
body: unknown;
|
||||
}>,
|
||||
): InstalledHttpContract<unknown, unknown, unknown> {
|
||||
return Object.freeze({
|
||||
contract: Object.freeze({
|
||||
operationId,
|
||||
method,
|
||||
pathTemplate,
|
||||
inputValidator: passthrough(`${operationId}Input`),
|
||||
outputValidator: passthrough(`${operationId}Output`),
|
||||
problemValidator: PROBLEM,
|
||||
acceptedStatuses: Object.freeze([options.acceptedStatus]),
|
||||
emptyBodyStatuses: Object.freeze(options.acceptedStatus === 204 ? [204] : []),
|
||||
retrySemantics: "KEYED" as const,
|
||||
requestBody: options.hasBody ? ("JSON" as const) : ("NONE" as const),
|
||||
responseBody: options.acceptedStatus === 204 ? ("NONE" as const) : ("REQUIRED_JSON" as const),
|
||||
commandRecovery: Object.freeze({
|
||||
mode: "IDEMPOTENCY_REPLAY" as const,
|
||||
operationIdentityField: "idempotencyKey",
|
||||
}),
|
||||
commandEffect: COMMAND_EFFECT,
|
||||
projectRequest: project,
|
||||
}),
|
||||
frontend: Object.freeze({
|
||||
policyId: `${operationId}_V1`,
|
||||
requestByteLimit: options.requestByteLimit,
|
||||
responseByteLimit: options.responseByteLimit,
|
||||
totalDeadlineMs: 10_000,
|
||||
// §8.3. 발신된 KEYED 명령은 자동 재시도하지 않는다.
|
||||
retryBudget: 0 as const,
|
||||
authProfileId: "TECH_LOG_STUDIO_SESSION",
|
||||
diagnosticsOperation: `techLog.studio.${operationId}`,
|
||||
}),
|
||||
}) as InstalledHttpContract<unknown, unknown, unknown>;
|
||||
}
|
||||
|
||||
const NO_PATH = Object.freeze({});
|
||||
const NO_QUERY = Object.freeze([]) as QueryEntries;
|
||||
|
||||
function queryOf(input: Readonly<Record<string, unknown>>): QueryEntries {
|
||||
const entries: (readonly [string, string])[] = [];
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
if (value === undefined || value === null) continue;
|
||||
entries.push([key, String(value)]);
|
||||
}
|
||||
return Object.freeze(entries);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Session / dashboard
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const GET_STUDIO_SESSION = safeOperation(
|
||||
"getStudioSession",
|
||||
"/api/v1/studio/session",
|
||||
8_192,
|
||||
() => Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY }),
|
||||
);
|
||||
|
||||
const GET_STUDIO_DASHBOARD = safeOperation(
|
||||
"getStudioDashboard",
|
||||
"/api/v1/studio/dashboard",
|
||||
262_144,
|
||||
() => Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY }),
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Documents
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LIST_STUDIO_DOCUMENTS = safeOperation(
|
||||
"listStudioDocuments",
|
||||
"/api/v1/studio/documents",
|
||||
262_144,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
q?: string;
|
||||
kind?: string;
|
||||
publicationStatus?: string;
|
||||
nextAction?: string;
|
||||
projectId?: string;
|
||||
sort?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: NO_PATH,
|
||||
queryEntries: queryOf({
|
||||
q: value.q,
|
||||
kind: value.kind,
|
||||
publicationStatus: value.publicationStatus,
|
||||
nextAction: value.nextAction,
|
||||
projectId: value.projectId,
|
||||
sort: value.sort,
|
||||
cursor: value.cursor,
|
||||
limit: value.limit,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const CREATE_STUDIO_DOCUMENT = keyedOperation(
|
||||
"createStudioDocument",
|
||||
"POST",
|
||||
"/api/v1/studio/documents",
|
||||
{ acceptedStatus: 201, responseByteLimit: 131_072, requestByteLimit: 131_072, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ document: unknown }>;
|
||||
return Object.freeze({
|
||||
pathValues: NO_PATH,
|
||||
queryEntries: NO_QUERY,
|
||||
body: value.document,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const GET_STUDIO_DOCUMENT = safeOperation(
|
||||
"getStudioDocument",
|
||||
"/api/v1/studio/documents/{documentId}",
|
||||
524_288,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ documentId: string }>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const SAVE_STUDIO_DOCUMENT = keyedOperation(
|
||||
"saveStudioDocument",
|
||||
"PUT",
|
||||
"/api/v1/studio/documents/{documentId}",
|
||||
{ acceptedStatus: 200, responseByteLimit: 524_288, requestByteLimit: 524_288, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
documentId: string;
|
||||
expectedVersion: number;
|
||||
document: unknown;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({
|
||||
expectedVersion: value.expectedVersion,
|
||||
document: value.document,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const VALIDATE_STUDIO_DOCUMENT = keyedOperation(
|
||||
"validateStudioDocument",
|
||||
"POST",
|
||||
"/api/v1/studio/documents/{documentId}/validate",
|
||||
{ acceptedStatus: 200, responseByteLimit: 262_144, requestByteLimit: 262_144, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
documentId: string;
|
||||
expectedVersion: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({ expectedVersion: value.expectedVersion }),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Previews
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const GET_CURRENT_STUDIO_PREVIEW = safeOperation(
|
||||
"getCurrentStudioPreview",
|
||||
"/api/v1/studio/documents/{documentId}/preview",
|
||||
1_048_576,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ documentId: string }>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const CREATE_STUDIO_PREVIEW = keyedOperation(
|
||||
"createStudioPreview",
|
||||
"POST",
|
||||
"/api/v1/studio/documents/{documentId}/preview",
|
||||
{ acceptedStatus: 201, responseByteLimit: 1_048_576, requestByteLimit: 1_048_576, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
documentId: string;
|
||||
expectedVersion: number;
|
||||
validationId: string;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({
|
||||
expectedVersion: value.expectedVersion,
|
||||
validationId: value.validationId,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Publications
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PUBLISH_STUDIO_DOCUMENT = keyedOperation(
|
||||
"publishStudioDocument",
|
||||
"POST",
|
||||
"/api/v1/studio/documents/{documentId}/publish",
|
||||
{ acceptedStatus: 200, responseByteLimit: 131_072, requestByteLimit: 131_072, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
documentId: string;
|
||||
expectedVersion: number;
|
||||
validationId: string;
|
||||
previewId: string;
|
||||
acknowledgedWarningCodes: readonly string[];
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ documentId: value.documentId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({
|
||||
expectedVersion: value.expectedVersion,
|
||||
validationId: value.validationId,
|
||||
previewId: value.previewId,
|
||||
acknowledgedWarningCodes: value.acknowledgedWarningCodes,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const LIST_STUDIO_PUBLICATIONS = safeOperation(
|
||||
"listStudioPublications",
|
||||
"/api/v1/studio/publications",
|
||||
262_144,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
q?: string;
|
||||
type?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: NO_PATH,
|
||||
queryEntries: queryOf({
|
||||
q: value.q,
|
||||
type: value.type,
|
||||
cursor: value.cursor,
|
||||
limit: value.limit,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const UNPUBLISH_STUDIO_PUBLICATION = keyedOperation(
|
||||
"unpublishStudioPublication",
|
||||
"POST",
|
||||
"/api/v1/studio/publications/{publicationId}/unpublish",
|
||||
{ acceptedStatus: 200, responseByteLimit: 131_072, requestByteLimit: 131_072, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
publicationId: string;
|
||||
expectedPublicationRevision: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ publicationId: value.publicationId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({
|
||||
expectedPublicationRevision: value.expectedPublicationRevision,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const GET_STUDIO_PUBLICATION_SNAPSHOT = safeOperation(
|
||||
"getStudioPublicationSnapshot",
|
||||
"/api/v1/studio/publications/{publicationEventId}/preview",
|
||||
1_048_576,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ publicationEventId: string }>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ publicationEventId: value.publicationEventId }),
|
||||
queryEntries: NO_QUERY,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Catalog / assets
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LIST_STUDIO_CATALOG = safeOperation(
|
||||
"listStudioCatalog",
|
||||
"/api/v1/studio/catalog",
|
||||
131_072,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
type: string;
|
||||
q?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: NO_PATH,
|
||||
queryEntries: queryOf({
|
||||
type: value.type,
|
||||
q: value.q,
|
||||
cursor: value.cursor,
|
||||
limit: value.limit,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const LIST_STUDIO_ASSETS = safeOperation(
|
||||
"listStudioAssets",
|
||||
"/api/v1/studio/assets",
|
||||
262_144,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
q?: string;
|
||||
kind?: string;
|
||||
managementStatus?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: NO_PATH,
|
||||
queryEntries: queryOf({
|
||||
q: value.q,
|
||||
kind: value.kind,
|
||||
managementStatus: value.managementStatus,
|
||||
cursor: value.cursor,
|
||||
limit: value.limit,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const GET_STUDIO_ASSET = safeOperation(
|
||||
"getStudioAsset",
|
||||
"/api/v1/studio/assets/{assetId}",
|
||||
65_536,
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ assetId: string }>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ assetId: value.assetId }),
|
||||
queryEntries: NO_QUERY,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const UPDATE_STUDIO_ASSET = keyedOperation(
|
||||
"updateStudioAsset",
|
||||
"PUT",
|
||||
"/api/v1/studio/assets/{assetId}",
|
||||
{ acceptedStatus: 200, responseByteLimit: 65_536, requestByteLimit: 65_536, hasBody: true },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{
|
||||
assetId: string;
|
||||
expectedVersion: number;
|
||||
kind?: string;
|
||||
altText?: string | null;
|
||||
decorative?: boolean;
|
||||
managementStatus?: string;
|
||||
}>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ assetId: value.assetId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: Object.freeze({
|
||||
expectedVersion: value.expectedVersion,
|
||||
kind: value.kind,
|
||||
altText: value.altText,
|
||||
decorative: value.decorative,
|
||||
managementStatus: value.managementStatus,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const DELETE_STUDIO_ASSET = keyedOperation(
|
||||
"deleteStudioAsset",
|
||||
"DELETE",
|
||||
"/api/v1/studio/assets/{assetId}",
|
||||
{ acceptedStatus: 204, responseByteLimit: 65_536, requestByteLimit: 0, hasBody: false },
|
||||
(input: never) => {
|
||||
const value = input as unknown as Readonly<{ assetId: string }>;
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ assetId: value.assetId }),
|
||||
queryEntries: NO_QUERY,
|
||||
body: null,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Contribution assembly
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const HTTP_CONTRACTS = Object.freeze([
|
||||
GET_STUDIO_SESSION,
|
||||
GET_STUDIO_DASHBOARD,
|
||||
LIST_STUDIO_DOCUMENTS,
|
||||
CREATE_STUDIO_DOCUMENT,
|
||||
GET_STUDIO_DOCUMENT,
|
||||
SAVE_STUDIO_DOCUMENT,
|
||||
VALIDATE_STUDIO_DOCUMENT,
|
||||
GET_CURRENT_STUDIO_PREVIEW,
|
||||
CREATE_STUDIO_PREVIEW,
|
||||
PUBLISH_STUDIO_DOCUMENT,
|
||||
LIST_STUDIO_PUBLICATIONS,
|
||||
UNPUBLISH_STUDIO_PUBLICATION,
|
||||
GET_STUDIO_PUBLICATION_SNAPSHOT,
|
||||
LIST_STUDIO_CATALOG,
|
||||
LIST_STUDIO_ASSETS,
|
||||
GET_STUDIO_ASSET,
|
||||
UPDATE_STUDIO_ASSET,
|
||||
DELETE_STUDIO_ASSET,
|
||||
]);
|
||||
|
||||
export const TECH_LOG_STUDIO_OPERATION_IDS = Object.freeze(
|
||||
HTTP_CONTRACTS.map((entry) => entry.contract.operationId),
|
||||
);
|
||||
|
||||
export type TechLogStudioOperationId =
|
||||
(typeof TECH_LOG_STUDIO_OPERATION_IDS)[number];
|
||||
|
||||
export const TECH_LOG_STUDIO_CONTRIBUTION: InstalledContractContribution =
|
||||
Object.freeze({
|
||||
contributionId: "tech-log-studio-http-v1",
|
||||
featureId: TECH_LOG_FEATURE_ID,
|
||||
source: Object.freeze({
|
||||
kind: "EXTERNAL_PACKAGE" as const,
|
||||
package: Object.freeze({
|
||||
packageId: canonicalSource.packageId,
|
||||
version: canonicalSource.version,
|
||||
digest: canonicalSource.digest as `sha256:${string}`,
|
||||
runtimeProtocolVersion: 1 as const,
|
||||
sourceRevision: canonicalSource.sourceRevision,
|
||||
}),
|
||||
}),
|
||||
http: HTTP_CONTRACTS,
|
||||
events: Object.freeze([]),
|
||||
});
|
||||
Reference in New Issue
Block a user