refactor: 리펙토링
This commit is contained in:
@@ -1,65 +1,174 @@
|
||||
import type { ApiOperation } from "../../../contracts/api-operations.ts";
|
||||
import type { Result } from "../../../application/result.ts";
|
||||
import type { ApiFailure, FailureKind } from "../../../contracts/errors.ts";
|
||||
import type { FailureEffectCertainty } from "../../../contracts/errors.ts";
|
||||
import {
|
||||
createFailure,
|
||||
kindForStatus,
|
||||
} from "../../../contracts/errors.ts";
|
||||
import type { HttpExecutionOutcome } from "../../../adapters/http/http-execution-v3.ts";
|
||||
import { createReferenceFeatureInput } from "../application/reference-feature-api.ts";
|
||||
import {
|
||||
REFERENCE_FEATURE_CONTRACT,
|
||||
REFERENCE_FEATURE_ID,
|
||||
} from "../contracts/reference-feature-contract.ts";
|
||||
import {
|
||||
mapWithBoundaryRegistry,
|
||||
type MappingResult,
|
||||
} from "../../../contracts/boundary-mapper.ts";
|
||||
import { validateWithRuntimeSchemaRegistry } from "../../../contracts/schema-registry.ts";
|
||||
import { mapReferenceOperation } from "../contracts/reference-mapper.ts";
|
||||
import {
|
||||
createReferenceHttpGateway,
|
||||
type RawReferenceHttpExecutor,
|
||||
type ReferenceHttpRequest,
|
||||
type ReferenceOperationId,
|
||||
} from "./reference-http-gateway.ts";
|
||||
|
||||
type HttpContract = Readonly<{
|
||||
getOperation(operationId: string): ApiOperation;
|
||||
validatePayload(schemaId: string, value: unknown): ReturnType<
|
||||
typeof validateWithRuntimeSchemaRegistry
|
||||
>;
|
||||
validateRequest(schemaId: string, value: unknown): ReturnType<
|
||||
typeof validateWithRuntimeSchemaRegistry
|
||||
>;
|
||||
validatePath(schemaId: string, value: unknown): ReturnType<
|
||||
typeof validateWithRuntimeSchemaRegistry
|
||||
>;
|
||||
mapPayload(operationId: string, payload: unknown): MappingResult<unknown>;
|
||||
export type InstalledContractOperationExecutor = Readonly<{
|
||||
execute(
|
||||
operationId: string,
|
||||
input: unknown,
|
||||
context?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<HttpExecutionOutcome<unknown, unknown>>;
|
||||
}>;
|
||||
|
||||
type HttpExecutor = RawReferenceHttpExecutor;
|
||||
|
||||
/**
|
||||
* The installed feature consumes the composed external-contract operation
|
||||
* registry through one descriptor-driven executor. Legacy ApiOperation/schema
|
||||
* registries are intentionally absent from this production composition seam.
|
||||
*/
|
||||
export function createReferenceFeatureInstalledInput(context: Readonly<{
|
||||
createHttpClient(contract: HttpContract): HttpExecutor;
|
||||
contractOperations: InstalledContractOperationExecutor;
|
||||
}>) {
|
||||
const operations =
|
||||
REFERENCE_FEATURE_CONTRACT.apiOperations as Readonly<Record<string, ApiOperation>>;
|
||||
const schemas = REFERENCE_FEATURE_CONTRACT.runtimeSchemas;
|
||||
const mappers = REFERENCE_FEATURE_CONTRACT.mappers;
|
||||
const validate = (schemaId: string, value: unknown) =>
|
||||
validateWithRuntimeSchemaRegistry(schemaId, value, schemas);
|
||||
const rawHttp = context.createHttpClient({
|
||||
getOperation(operationId) {
|
||||
const operation = operations[operationId];
|
||||
if (!operation) {
|
||||
throw new Error(`Unknown reference operation: ${operationId}`);
|
||||
}
|
||||
return operation;
|
||||
},
|
||||
validatePayload: validate,
|
||||
validateRequest: validate,
|
||||
validatePath: validate,
|
||||
mapPayload(operationId, payload) {
|
||||
const operation = operations[operationId];
|
||||
if (!operation?.mapperId) {
|
||||
return { ok: false, code: "MAPPING_INVARIANT_REJECTED" };
|
||||
}
|
||||
return mapWithBoundaryRegistry(operation.mapperId, payload, mappers);
|
||||
const rawHttp: RawReferenceHttpExecutor = Object.freeze({
|
||||
async execute(request) {
|
||||
const operationId = request.operationId;
|
||||
const input = inputFor(request);
|
||||
const signal = "signal" in request ? request.signal : undefined;
|
||||
const outcome = await context.contractOperations.execute(
|
||||
operationId,
|
||||
input,
|
||||
signal === undefined ? {} : { signal },
|
||||
);
|
||||
return projectExecutionOutcome(operationId, outcome);
|
||||
},
|
||||
});
|
||||
|
||||
return Object.freeze({
|
||||
featureId: REFERENCE_FEATURE_ID,
|
||||
input: createReferenceFeatureInput(createReferenceHttpGateway(rawHttp)),
|
||||
});
|
||||
}
|
||||
|
||||
function inputFor(
|
||||
request: ReferenceHttpRequest<ReferenceOperationId>,
|
||||
): unknown {
|
||||
switch (request.operationId) {
|
||||
case "LIST_REFERENCE_RESOURCES":
|
||||
return request.searchParams;
|
||||
case "CREATE_REFERENCE_RESOURCE":
|
||||
return request.body;
|
||||
case "GET_REFERENCE_RESOURCE":
|
||||
return request.pathParams;
|
||||
}
|
||||
}
|
||||
|
||||
function projectExecutionOutcome(
|
||||
operationId: ReferenceOperationId,
|
||||
outcome: HttpExecutionOutcome<unknown, unknown>,
|
||||
): Result<unknown, ApiFailure> {
|
||||
switch (outcome.kind) {
|
||||
case "SUCCESS": {
|
||||
const mapped = mapReferenceOperation(operationId, outcome.value);
|
||||
return mapped.ok
|
||||
? Object.freeze({ ok: true as const, value: mapped.value })
|
||||
: failure(
|
||||
"MAPPING_CONTRACT_VIOLATION",
|
||||
operationId,
|
||||
mapped.code,
|
||||
{ effect: outcome.effect },
|
||||
);
|
||||
}
|
||||
case "PROBLEM":
|
||||
return failure(
|
||||
kindForStatus(outcome.metadata.status),
|
||||
operationId,
|
||||
"CONTRACT_PROBLEM",
|
||||
{ httpStatus: outcome.metadata.status, effect: outcome.effect },
|
||||
);
|
||||
case "UNAUTHENTICATED":
|
||||
return failure("AUTH_REQUIRED", operationId, "UNAUTHENTICATED", {
|
||||
effect: outcome.effect,
|
||||
});
|
||||
case "FORBIDDEN":
|
||||
return failure("FORBIDDEN", operationId, "FORBIDDEN", {
|
||||
effect: outcome.effect,
|
||||
});
|
||||
case "RATE_LIMITED":
|
||||
return failure("RATE_LIMITED", operationId, "RATE_LIMITED", {
|
||||
...(outcome.retryAfterMs === undefined
|
||||
? {}
|
||||
: { retryAfterMs: outcome.retryAfterMs }),
|
||||
effect: outcome.effect,
|
||||
});
|
||||
case "CANCELLED":
|
||||
return failure("REQUEST_ABORTED", operationId, "REQUEST_ABORTED", {
|
||||
effect: outcome.effect,
|
||||
});
|
||||
case "TRANSPORT_FAILURE":
|
||||
return failure(
|
||||
outcome.failure.kind === "TIMEOUT"
|
||||
? "REQUEST_TIMEOUT"
|
||||
: outcome.failure.kind === "ABORTED_BY_SCOPE"
|
||||
? "SCOPE_GENERATION_CHANGED"
|
||||
: "NETWORK_UNREACHABLE",
|
||||
operationId,
|
||||
outcome.failure.kind,
|
||||
{ effect: outcome.effect },
|
||||
);
|
||||
case "CONTRACT_VIOLATION":
|
||||
return failure(
|
||||
failureKindForViolation(outcome.violation.kind),
|
||||
operationId,
|
||||
outcome.violation.kind,
|
||||
{ effect: outcome.effect },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function failureKindForViolation(
|
||||
violation: Extract<
|
||||
HttpExecutionOutcome<unknown, unknown>,
|
||||
{ kind: "CONTRACT_VIOLATION" }
|
||||
>["violation"]["kind"],
|
||||
): FailureKind {
|
||||
switch (violation) {
|
||||
case "CONTENT_TYPE_MISMATCH":
|
||||
return "CONTENT_TYPE_MISMATCH";
|
||||
case "RESPONSE_TOO_LARGE":
|
||||
return "RESPONSE_BODY_LIMIT";
|
||||
case "UTF8_INVALID":
|
||||
case "JSON_INVALID":
|
||||
return "MALFORMED_JSON";
|
||||
case "MAPPING_CONTRACT_VIOLATION":
|
||||
return "MAPPING_CONTRACT_VIOLATION";
|
||||
case "SCOPE_FENCED":
|
||||
return "SCOPE_GENERATION_CHANGED";
|
||||
case "SUCCESS_SCHEMA_INVALID":
|
||||
case "PROBLEM_SCHEMA_INVALID":
|
||||
case "VALIDATOR_RUNTIME_FAILURE":
|
||||
return "SCHEMA_MISMATCH";
|
||||
default:
|
||||
return "ENVELOPE_MISMATCH";
|
||||
}
|
||||
}
|
||||
|
||||
function failure(
|
||||
kind: FailureKind,
|
||||
operationId: string,
|
||||
code: string,
|
||||
details: Readonly<{
|
||||
httpStatus?: number;
|
||||
retryAfterMs?: number;
|
||||
effect?: FailureEffectCertainty;
|
||||
}> = {},
|
||||
): Result<never, ApiFailure> {
|
||||
return Object.freeze({
|
||||
ok: false as const,
|
||||
error: createFailure(kind, operationId, 0, { code, ...details }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type ReferenceOperationMap = Readonly<{
|
||||
operationId: "CREATE_REFERENCE_RESOURCE";
|
||||
routeId: "REFERENCE_RESOURCE_LIST";
|
||||
body: ReferenceCreateCommand;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
value: ReferenceResource;
|
||||
}>;
|
||||
@@ -71,11 +72,15 @@ export function createReferenceHttpGateway(
|
||||
});
|
||||
return projectListResult(result);
|
||||
},
|
||||
async create(command: ReferenceCreateCommand) {
|
||||
async create(
|
||||
command: ReferenceCreateCommand,
|
||||
context?: Readonly<{ signal?: AbortSignal }>,
|
||||
) {
|
||||
const result = await http.execute({
|
||||
operationId: "CREATE_REFERENCE_RESOURCE",
|
||||
routeId: "REFERENCE_RESOURCE_LIST",
|
||||
body: command,
|
||||
signal: context?.signal,
|
||||
});
|
||||
return projectResourceResult("CREATE_REFERENCE_RESOURCE", result);
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ export type ReferenceFeatureInput = Readonly<{
|
||||
): Promise<ReferenceResult<readonly ReferenceResourceView[]>>;
|
||||
createResource(
|
||||
command: ReferenceCreateCommand,
|
||||
context?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<ReferenceResult<ReferenceResourceView>>;
|
||||
getResource(
|
||||
resourceId: string,
|
||||
@@ -46,6 +47,7 @@ export type ReferenceGateway = Readonly<{
|
||||
): Promise<ReferenceResult<readonly ReferenceResource[]>>;
|
||||
create(
|
||||
command: ReferenceCreateCommand,
|
||||
context?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<ReferenceResult<ReferenceResource>>;
|
||||
get(
|
||||
resourceId: string,
|
||||
@@ -66,8 +68,8 @@ export function createReferenceFeatureInput(
|
||||
}
|
||||
: result;
|
||||
},
|
||||
async createResource(command) {
|
||||
const result = await gateway.create(command);
|
||||
async createResource(command, context) {
|
||||
const result = await gateway.create(command, context);
|
||||
return result.ok
|
||||
? { ok: true as const, value: toReferenceView(result.value) }
|
||||
: result;
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type {
|
||||
CommandEffectDescriptor,
|
||||
HttpRequestProjection,
|
||||
InstalledContractContribution,
|
||||
InstalledHttpContract,
|
||||
RuntimeValidator,
|
||||
} from "../../../contracts/external-contract-runtime.ts";
|
||||
import { REFERENCE_FEATURE_ID } from "./reference-feature-contract.ts";
|
||||
import {
|
||||
referenceResourceListQuerySchema,
|
||||
referenceResourceParamsSchema,
|
||||
} from "./reference-schemas.ts";
|
||||
|
||||
/**
|
||||
* §4.8. The single `TEMPLATE_FIXTURE` contribution. It keeps the reference
|
||||
* HTTP vertical executable as deterministic template data and is excluded from
|
||||
* `contractSet`. A product feature MUST instead pin an external package and
|
||||
* import it from its own `contracts/<service>-contract-contribution.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),
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const referenceResourceDto = z
|
||||
.object({
|
||||
id: z.string().min(1).max(120),
|
||||
name: z.string().min(1).max(240),
|
||||
createdAt: z.string().min(1).optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
const problemSchema = z
|
||||
.object({
|
||||
type: z.string().min(1).max(512),
|
||||
title: z.string().min(1).max(240),
|
||||
status: z.int().min(100).max(599),
|
||||
code: z.string().min(1).max(120).optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
export type ReferenceProblem = z.output<typeof problemSchema>;
|
||||
|
||||
const PROBLEM_VALIDATOR = zodValidator("ReferenceProblem", problemSchema);
|
||||
|
||||
const createCommandSchema = z
|
||||
.object({
|
||||
name: z.string().trim().min(1).max(120),
|
||||
note: z.string().trim().max(500).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/**
|
||||
* Fixture-side classifier standing in for the package-provided pure bounded
|
||||
* classifier. A status the fixture does not describe stays `MAYBE_APPLIED`.
|
||||
*/
|
||||
const CREATE_EFFECT: CommandEffectDescriptor<ReferenceProblem> = Object.freeze({
|
||||
successEffect: "APPLIED_CONFIRMED" as const,
|
||||
classifyProblem({ status }: Readonly<{ status: number; problem: ReferenceProblem }>) {
|
||||
if (status === 400 || status === 409 || status === 422) return "NOT_APPLIED";
|
||||
return "MAYBE_APPLIED";
|
||||
},
|
||||
});
|
||||
|
||||
const LIST_REFERENCE_RESOURCES: InstalledHttpContract<
|
||||
z.output<typeof referenceResourceListQuerySchema>,
|
||||
readonly z.output<typeof referenceResourceDto>[],
|
||||
ReferenceProblem
|
||||
> = Object.freeze({
|
||||
contract: Object.freeze({
|
||||
operationId: "LIST_REFERENCE_RESOURCES",
|
||||
method: "GET" as const,
|
||||
pathTemplate: "/api/reference-resources",
|
||||
inputValidator: zodValidator(
|
||||
"ReferenceResourceListQuery",
|
||||
referenceResourceListQuerySchema,
|
||||
),
|
||||
outputValidator: zodValidator(
|
||||
"ReferenceResourceListPayload",
|
||||
z.array(referenceResourceDto).max(100),
|
||||
),
|
||||
problemValidator: PROBLEM_VALIDATOR,
|
||||
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: z.output<typeof referenceResourceListQuerySchema>) {
|
||||
const entries: (readonly [string, string])[] = [];
|
||||
if (input.cursor !== undefined) entries.push(["cursor", input.cursor]);
|
||||
entries.push(["limit", String(input.limit)]);
|
||||
for (const tag of input.tags ?? []) entries.push(["tags", tag]);
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({}),
|
||||
queryEntries: Object.freeze(entries),
|
||||
body: null,
|
||||
});
|
||||
},
|
||||
}),
|
||||
frontend: Object.freeze({
|
||||
policyId: "REFERENCE_LIST_V1",
|
||||
requestByteLimit: 0,
|
||||
responseByteLimit: 262_144,
|
||||
totalDeadlineMs: 10_000,
|
||||
retryBudget: 2 as const,
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
diagnosticsOperation: "reference.list",
|
||||
}),
|
||||
});
|
||||
|
||||
const GET_REFERENCE_RESOURCE: InstalledHttpContract<
|
||||
z.output<typeof referenceResourceParamsSchema>,
|
||||
z.output<typeof referenceResourceDto>,
|
||||
ReferenceProblem
|
||||
> = Object.freeze({
|
||||
contract: Object.freeze({
|
||||
operationId: "GET_REFERENCE_RESOURCE",
|
||||
method: "GET" as const,
|
||||
pathTemplate: "/api/reference-resources/{resourceId}",
|
||||
inputValidator: zodValidator(
|
||||
"ReferenceResourceParams",
|
||||
referenceResourceParamsSchema,
|
||||
),
|
||||
outputValidator: zodValidator(
|
||||
"ReferenceResourcePayload",
|
||||
referenceResourceDto,
|
||||
),
|
||||
problemValidator: PROBLEM_VALIDATOR,
|
||||
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: z.output<typeof referenceResourceParamsSchema>) {
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({ resourceId: input.resourceId }),
|
||||
queryEntries: Object.freeze([]),
|
||||
body: null,
|
||||
});
|
||||
},
|
||||
}),
|
||||
frontend: Object.freeze({
|
||||
policyId: "REFERENCE_DETAIL_V1",
|
||||
requestByteLimit: 0,
|
||||
responseByteLimit: 32_768,
|
||||
totalDeadlineMs: 10_000,
|
||||
retryBudget: 2 as const,
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
diagnosticsOperation: "reference.detail",
|
||||
}),
|
||||
});
|
||||
|
||||
const CREATE_REFERENCE_RESOURCE: InstalledHttpContract<
|
||||
z.output<typeof createCommandSchema>,
|
||||
z.output<typeof referenceResourceDto>,
|
||||
ReferenceProblem
|
||||
> = Object.freeze({
|
||||
contract: Object.freeze({
|
||||
operationId: "CREATE_REFERENCE_RESOURCE",
|
||||
method: "POST" as const,
|
||||
pathTemplate: "/api/reference-resources",
|
||||
inputValidator: zodValidator(
|
||||
"CreateReferenceResourceCommand",
|
||||
createCommandSchema,
|
||||
),
|
||||
outputValidator: zodValidator(
|
||||
"ReferenceResourcePayload",
|
||||
referenceResourceDto,
|
||||
),
|
||||
problemValidator: PROBLEM_VALIDATOR,
|
||||
acceptedStatuses: Object.freeze([200, 201]),
|
||||
emptyBodyStatuses: Object.freeze([]),
|
||||
retrySemantics: "KEYED" as const,
|
||||
requestBody: "JSON" as const,
|
||||
responseBody: "REQUIRED_JSON" as const,
|
||||
commandRecovery: Object.freeze({
|
||||
mode: "IDEMPOTENCY_REPLAY" as const,
|
||||
operationIdentityField: "idempotencyKey",
|
||||
}),
|
||||
commandEffect: CREATE_EFFECT,
|
||||
projectRequest(input: z.output<typeof createCommandSchema>) {
|
||||
return Object.freeze({
|
||||
pathValues: Object.freeze({}),
|
||||
queryEntries: Object.freeze([]),
|
||||
body: Object.freeze({ ...input }),
|
||||
});
|
||||
},
|
||||
}),
|
||||
frontend: Object.freeze({
|
||||
policyId: "REFERENCE_CREATE_V1",
|
||||
requestByteLimit: 32_768,
|
||||
responseByteLimit: 32_768,
|
||||
totalDeadlineMs: 10_000,
|
||||
/**
|
||||
* §8.3. A KEYED command has no automatic retry after a dispatched attempt
|
||||
* lost its response; the fixture therefore declares a zero budget rather
|
||||
* than relying on a runtime special case.
|
||||
*/
|
||||
retryBudget: 0 as const,
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
diagnosticsOperation: "reference.create",
|
||||
}),
|
||||
});
|
||||
|
||||
export const REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION: InstalledContractContribution =
|
||||
Object.freeze({
|
||||
contributionId: "reference-feature-http-v1",
|
||||
featureId: REFERENCE_FEATURE_ID,
|
||||
source: Object.freeze({
|
||||
kind: "TEMPLATE_FIXTURE" as const,
|
||||
fixtureId: "REFERENCE_FEATURE_V1" as const,
|
||||
revision: 1 as const,
|
||||
}),
|
||||
http: Object.freeze([
|
||||
LIST_REFERENCE_RESOURCES,
|
||||
GET_REFERENCE_RESOURCE,
|
||||
CREATE_REFERENCE_RESOURCE,
|
||||
]) as readonly InstalledHttpContract<unknown, unknown, unknown>[],
|
||||
events: Object.freeze([]),
|
||||
});
|
||||
@@ -84,7 +84,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
path: "/examples/reference-resources",
|
||||
paramsSchema: null,
|
||||
searchSchema: "ReferenceResourceListQuery",
|
||||
access: "integration-defined",
|
||||
access: "session-required",
|
||||
loadingSurface: "reference-resource-list",
|
||||
errorSurface: "feature-boundary",
|
||||
chunkId: "route-reference-resources",
|
||||
@@ -97,7 +97,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
path: "/examples/reference-resources/:resourceId",
|
||||
paramsSchema: "ReferenceResourceParams",
|
||||
searchSchema: null,
|
||||
access: "integration-defined",
|
||||
access: "session-required",
|
||||
loadingSurface: "reference-resource-detail",
|
||||
errorSurface: "feature-boundary",
|
||||
chunkId: "route-reference-resource-detail",
|
||||
@@ -110,7 +110,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
path: "/examples/reference-resources/new",
|
||||
paramsSchema: null,
|
||||
searchSchema: null,
|
||||
access: "integration-defined",
|
||||
access: "session-required",
|
||||
loadingSurface: "reference-resource-form",
|
||||
errorSurface: "feature-boundary",
|
||||
chunkId: "route-reference-resource-form",
|
||||
@@ -123,7 +123,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
path: "/examples/reference-resources/status",
|
||||
paramsSchema: null,
|
||||
searchSchema: null,
|
||||
access: "integration-defined",
|
||||
access: "session-required",
|
||||
loadingSurface: "reference-resource-status",
|
||||
errorSurface: "feature-boundary",
|
||||
chunkId: "route-reference-resource-status",
|
||||
|
||||
@@ -16,22 +16,38 @@ import type {
|
||||
import type { ReferenceResourceView } from "../contracts/reference-mapper.ts";
|
||||
import {
|
||||
bindQuery,
|
||||
defineServerStateProfile,
|
||||
type BoundMutation,
|
||||
type QueryResultMeasure,
|
||||
} from "../../../contracts/server-state.ts";
|
||||
import { useServerStateScope } from "../../../presentation/adapters/query/server-state-scope-provider.tsx";
|
||||
|
||||
const REFERENCE_READ_PROFILE = defineServerStateProfile({
|
||||
profileId: "reference-resource-read-v1",
|
||||
staleTimeMs: 30_000,
|
||||
gcTimeMs: 300_000,
|
||||
refetchOnMount: true,
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
retryOwner: "TRANSPORT",
|
||||
maxResultItems: 100,
|
||||
maxEstimatedResultBytes: 262_144,
|
||||
});
|
||||
const UTF8 = new TextEncoder();
|
||||
|
||||
/**
|
||||
* §10.4. Feature-owned measurement over the mapped application value. There is
|
||||
* no generic fallback: bounded string bytes plus fixed primitive width plus a
|
||||
* small per-item overhead, never `JSON.stringify` or a recursive walker.
|
||||
*/
|
||||
function measureResourceView(view: ReferenceResourceView): QueryResultMeasure {
|
||||
return {
|
||||
itemCount: 1,
|
||||
estimatedBytes:
|
||||
UTF8.encode(view.resourceId).byteLength +
|
||||
UTF8.encode(view.title).byteLength +
|
||||
UTF8.encode(view.createdAt ?? "").byteLength +
|
||||
32,
|
||||
};
|
||||
}
|
||||
|
||||
function measureResourceList(
|
||||
views: readonly ReferenceResourceView[],
|
||||
): QueryResultMeasure {
|
||||
let estimatedBytes = 16;
|
||||
for (const view of views) {
|
||||
estimatedBytes += measureResourceView(view).estimatedBytes;
|
||||
}
|
||||
return { itemCount: views.length, estimatedBytes };
|
||||
}
|
||||
|
||||
export function useReferenceFeatureInput(): ReferenceFeatureInput {
|
||||
return useApplication().features.get(REFERENCE_FEATURE_ID);
|
||||
@@ -49,7 +65,8 @@ export function useReferenceDetail(resourceId: string) {
|
||||
namespace: "reference-resource",
|
||||
namespaceVersion: 1,
|
||||
operationId: "GET_REFERENCE_RESOURCE",
|
||||
profile: REFERENCE_READ_PROFILE,
|
||||
profileId: "DETAIL_STANDARD",
|
||||
measureResult: measureResourceView,
|
||||
execute: (selectedResourceId: string, { signal }) =>
|
||||
input.getResource(selectedResourceId, { signal }),
|
||||
},
|
||||
@@ -71,7 +88,7 @@ export function useReferenceCreate() {
|
||||
definitionVersion: 1,
|
||||
operationId: "CREATE_REFERENCE_RESOURCE",
|
||||
owner: REFERENCE_FEATURE_ID,
|
||||
duplicatePolicy: "JOIN_IDENTICAL",
|
||||
duplicatePolicy: "REJECT_WHILE_ACTIVE",
|
||||
scope,
|
||||
execute: input.createResource,
|
||||
invalidate: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],
|
||||
@@ -93,7 +110,8 @@ export function useReferenceFeature() {
|
||||
namespace: "reference-resource",
|
||||
namespaceVersion: 1,
|
||||
operationId: "LIST_REFERENCE_RESOURCES",
|
||||
profile: REFERENCE_READ_PROFILE,
|
||||
profileId: "LIST_STANDARD",
|
||||
measureResult: measureResourceList,
|
||||
execute: (selectedFilters: ReferenceListFilters, { signal }) =>
|
||||
input.listResources(selectedFilters, { signal }),
|
||||
},
|
||||
@@ -106,7 +124,7 @@ export function useReferenceFeature() {
|
||||
definitionVersion: 1,
|
||||
operationId: "CREATE_REFERENCE_RESOURCE",
|
||||
owner: REFERENCE_FEATURE_ID,
|
||||
duplicatePolicy: "JOIN_IDENTICAL",
|
||||
duplicatePolicy: "REJECT_WHILE_ACTIVE",
|
||||
scope,
|
||||
execute: input.createResource,
|
||||
invalidate: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],
|
||||
|
||||
Reference in New Issue
Block a user