feat: 기능 추가 과정중
This commit is contained in:
+83
-10
@@ -1,55 +1,81 @@
|
||||
import { canonicalize } from "../../../contracts/query-keys.js";
|
||||
import { canonicalize } from "../../../contracts/query-keys.ts";
|
||||
import { defineQueryInvalidationTopic } from "../../../contracts/query-invalidation.ts";
|
||||
import { defineRestOperation } from "../../../contracts/api-operations.ts";
|
||||
import { REFERENCE_RUNTIME_SCHEMA_CODECS } from "./reference-schemas.ts";
|
||||
import { REFERENCE_BOUNDARY_MAPPERS } from "./reference-mapper.ts";
|
||||
|
||||
export const REFERENCE_FEATURE_ID = "reference-feature";
|
||||
const REFERENCE_NAMESPACE = Object.freeze(["reference-resource", 1]);
|
||||
const REFERENCE_NAMESPACE = Object.freeze(["reference-resource", 1] as const);
|
||||
export const REFERENCE_RESOURCE_INVALIDATION_TOPIC =
|
||||
defineQueryInvalidationTopic("qinv.01k10f7m3w9p6r2c8v5n4x");
|
||||
|
||||
export const referenceQueryKeys = Object.freeze({
|
||||
all: () => REFERENCE_NAMESPACE,
|
||||
list: (filters = {}) =>
|
||||
list: (filters: Readonly<object> = {}) =>
|
||||
Object.freeze([...REFERENCE_NAMESPACE, "list", canonicalize(filters)]),
|
||||
/** @param {string} resourceId */
|
||||
detail: (resourceId) =>
|
||||
|
||||
detail: (resourceId: string) =>
|
||||
Object.freeze([...REFERENCE_NAMESPACE, "detail", String(resourceId)]),
|
||||
});
|
||||
|
||||
export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
featureId: REFERENCE_FEATURE_ID,
|
||||
runtimeSchemas: REFERENCE_RUNTIME_SCHEMA_CODECS,
|
||||
mappers: REFERENCE_BOUNDARY_MAPPERS,
|
||||
schemas: Object.freeze({
|
||||
ReferenceResourceParams: Object.freeze({
|
||||
schemaId: "ReferenceResourceParams",
|
||||
boundary: "route-params",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "REQUEST",
|
||||
unknownFieldPolicy: "REJECT_UNKNOWN",
|
||||
}),
|
||||
ReferenceResourceListQuery: Object.freeze({
|
||||
schemaId: "ReferenceResourceListQuery",
|
||||
boundary: "route-search-api-request",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "REQUEST",
|
||||
unknownFieldPolicy: "REJECT_UNKNOWN",
|
||||
}),
|
||||
CreateReferenceResourceCommand: Object.freeze({
|
||||
schemaId: "CreateReferenceResourceCommand",
|
||||
boundary: "api-request",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "REQUEST",
|
||||
unknownFieldPolicy: "REJECT_UNKNOWN",
|
||||
}),
|
||||
NoRequest: Object.freeze({
|
||||
schemaId: "NoRequest",
|
||||
boundary: "api-request",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "REQUEST",
|
||||
unknownFieldPolicy: "REJECT_UNKNOWN",
|
||||
}),
|
||||
ReferenceResourceListPayload: Object.freeze({
|
||||
schemaId: "ReferenceResourceListPayload",
|
||||
boundary: "api-response",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "RESPONSE",
|
||||
unknownFieldPolicy: "STRIP_UNKNOWN",
|
||||
}),
|
||||
ReferenceResourcePayload: Object.freeze({
|
||||
schemaId: "ReferenceResourcePayload",
|
||||
boundary: "api-response",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
runtime: "zod",
|
||||
schemaVersion: 1,
|
||||
direction: "RESPONSE",
|
||||
unknownFieldPolicy: "STRIP_UNKNOWN",
|
||||
}),
|
||||
}),
|
||||
routes: Object.freeze({
|
||||
@@ -133,7 +159,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
}),
|
||||
}),
|
||||
apiOperations: Object.freeze({
|
||||
LIST_REFERENCE_RESOURCES: Object.freeze({
|
||||
LIST_REFERENCE_RESOURCES: defineRestOperation({
|
||||
method: "GET",
|
||||
path: "/api/reference-resources",
|
||||
operationId: "LIST_REFERENCE_RESOURCES",
|
||||
@@ -145,8 +171,23 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
requestSchema: "ReferenceResourceListQuery",
|
||||
responseSchema: "ReferenceResourceListPayload",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
contractVersion: 2,
|
||||
protocol: "REST",
|
||||
semantics: "QUERY",
|
||||
replayPolicy: "SAFE",
|
||||
idempotencyKeyPolicy: "NONE",
|
||||
mapperId: "ReferenceResourceListMapper",
|
||||
successStatuses: [200],
|
||||
responseMediaTypes: ["application/json"],
|
||||
maxResponseBytes: 262_144,
|
||||
providerId: "PRIMARY_API",
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
csrfProfileId: "NO_CSRF_BEARER",
|
||||
pathSchema: "NoRequest",
|
||||
pathParameterNames: [],
|
||||
maxEncodedSearchBytes: 4_096,
|
||||
}),
|
||||
CREATE_REFERENCE_RESOURCE: Object.freeze({
|
||||
CREATE_REFERENCE_RESOURCE: defineRestOperation({
|
||||
method: "POST",
|
||||
path: "/api/reference-resources",
|
||||
operationId: "CREATE_REFERENCE_RESOURCE",
|
||||
@@ -158,8 +199,23 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
requestSchema: "CreateReferenceResourceCommand",
|
||||
responseSchema: "ReferenceResourcePayload",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
contractVersion: 2,
|
||||
protocol: "REST",
|
||||
semantics: "COMMAND",
|
||||
replayPolicy: "KEYED_COMMAND",
|
||||
idempotencyKeyPolicy: "REQUIRED",
|
||||
mapperId: "ReferenceResourceMapper",
|
||||
successStatuses: [200, 201],
|
||||
responseMediaTypes: ["application/json"],
|
||||
maxResponseBytes: 32_768,
|
||||
providerId: "PRIMARY_API",
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
csrfProfileId: "NO_CSRF_BEARER",
|
||||
pathSchema: "NoRequest",
|
||||
pathParameterNames: [],
|
||||
maxEncodedSearchBytes: 0,
|
||||
}),
|
||||
GET_REFERENCE_RESOURCE: Object.freeze({
|
||||
GET_REFERENCE_RESOURCE: defineRestOperation({
|
||||
method: "GET",
|
||||
path: "/api/reference-resources/{resourceId}",
|
||||
operationId: "GET_REFERENCE_RESOURCE",
|
||||
@@ -170,7 +226,22 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
requestSource: "none",
|
||||
requestSchema: "NoRequest",
|
||||
responseSchema: "ReferenceResourcePayload",
|
||||
owner: "feature-frontend-form-page-platform",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
contractVersion: 2,
|
||||
protocol: "REST",
|
||||
semantics: "QUERY",
|
||||
replayPolicy: "SAFE",
|
||||
idempotencyKeyPolicy: "NONE",
|
||||
mapperId: "ReferenceResourceMapper",
|
||||
successStatuses: [200],
|
||||
responseMediaTypes: ["application/json"],
|
||||
maxResponseBytes: 32_768,
|
||||
providerId: "PRIMARY_API",
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
csrfProfileId: "NO_CSRF_BEARER",
|
||||
pathSchema: "ReferenceResourceParams",
|
||||
pathParameterNames: ["resourceId"],
|
||||
maxEncodedSearchBytes: 0,
|
||||
}),
|
||||
}),
|
||||
queryRegistry: Object.freeze({
|
||||
@@ -179,8 +250,10 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
|
||||
serialization: "canonical-object-order",
|
||||
identity: "no-pii-token-or-raw-url",
|
||||
invalidation: "reference resource namespace after successful mutation",
|
||||
invalidationTopic: REFERENCE_RESOURCE_INVALIDATION_TOPIC,
|
||||
crossContext: "invalidate-only",
|
||||
version: 1,
|
||||
persistence: "disabled",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
} as const);
|
||||
@@ -1,7 +1,13 @@
|
||||
import {
|
||||
createReferenceResource,
|
||||
type ReferenceResource,
|
||||
} from "../domain/reference-resource.js";
|
||||
} from "../domain/reference-resource.ts";
|
||||
import {
|
||||
mappingFailure,
|
||||
mappingSuccess,
|
||||
type MappingResult,
|
||||
type InstalledBoundaryMapper,
|
||||
} from "../../../contracts/boundary-mapper.ts";
|
||||
|
||||
export type ReferenceResourceView = Readonly<{
|
||||
resourceId: string;
|
||||
@@ -10,28 +16,43 @@ export type ReferenceResourceView = Readonly<{
|
||||
optimistic?: boolean;
|
||||
}>;
|
||||
|
||||
function mapReferenceDto(value: unknown): ReferenceResource {
|
||||
function mapReferenceDto(value: unknown): MappingResult<ReferenceResource> {
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new TypeError("Validated reference DTO is required");
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
const dto = value as Record<string, unknown>;
|
||||
if (typeof dto.id !== "string" || typeof dto.name !== "string") {
|
||||
throw new TypeError("Validated reference DTO invariants were breached");
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
try {
|
||||
return mappingSuccess(
|
||||
createReferenceResource({
|
||||
id: dto.id,
|
||||
displayName: dto.name,
|
||||
createdAt: typeof dto.createdAt === "string" ? dto.createdAt : null,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
return createReferenceResource({
|
||||
id: dto.id,
|
||||
displayName: dto.name,
|
||||
createdAt: typeof dto.createdAt === "string" ? dto.createdAt : null,
|
||||
});
|
||||
}
|
||||
|
||||
export function mapReferenceOperation(
|
||||
operationId: string,
|
||||
payload: unknown,
|
||||
): ReferenceResource | readonly ReferenceResource[] {
|
||||
): MappingResult<ReferenceResource | readonly ReferenceResource[]> {
|
||||
if (operationId === "LIST_REFERENCE_RESOURCES") {
|
||||
if (!Array.isArray(payload)) throw new TypeError("Expected a reference list");
|
||||
return payload.map(mapReferenceDto);
|
||||
if (!Array.isArray(payload)) {
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
if (payload.length > 100) return mappingFailure("OUTPUT_LIMIT_EXCEEDED");
|
||||
const output: ReferenceResource[] = [];
|
||||
for (const item of payload) {
|
||||
const mapped = mapReferenceDto(item);
|
||||
if (!mapped.ok) return mapped;
|
||||
output.push(mapped.value);
|
||||
}
|
||||
return mappingSuccess(Object.freeze(output));
|
||||
}
|
||||
if (
|
||||
operationId === "CREATE_REFERENCE_RESOURCE" ||
|
||||
@@ -39,9 +60,32 @@ export function mapReferenceOperation(
|
||||
) {
|
||||
return mapReferenceDto(payload);
|
||||
}
|
||||
throw new TypeError(`No reference mapper registered for ${operationId}`);
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
|
||||
export const REFERENCE_BOUNDARY_MAPPERS = Object.freeze({
|
||||
ReferenceResourceListMapper: Object.freeze({
|
||||
mapperId: "ReferenceResourceListMapper",
|
||||
mapperVersion: 1,
|
||||
inputSchemaId: "ReferenceResourceListPayload",
|
||||
outputContractId: "ReferenceResourceList",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
maxOutputItems: 100,
|
||||
map: (input: unknown) =>
|
||||
mapReferenceOperation("LIST_REFERENCE_RESOURCES", input),
|
||||
}),
|
||||
ReferenceResourceMapper: Object.freeze({
|
||||
mapperId: "ReferenceResourceMapper",
|
||||
mapperVersion: 1,
|
||||
inputSchemaId: "ReferenceResourcePayload",
|
||||
outputContractId: "ReferenceResource",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
maxOutputItems: 1,
|
||||
map: (input: unknown) =>
|
||||
mapReferenceOperation("GET_REFERENCE_RESOURCE", input),
|
||||
}),
|
||||
} satisfies Readonly<Record<string, InstalledBoundaryMapper>>);
|
||||
|
||||
export function toReferenceView(
|
||||
resource: ReferenceResource,
|
||||
): ReferenceResourceView {
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@ export const REFERENCE_MESSAGE_CATALOGS = Object.freeze({
|
||||
"route.REFERENCE_RESOURCE_STATUS.navigation": "Reference status",
|
||||
"route.REFERENCE_RESOURCE_STATUS.title": "Reference resource status",
|
||||
}),
|
||||
});
|
||||
} as const);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import type { RuntimeSchemaCodec } from "../../../contracts/schema-registry.ts";
|
||||
|
||||
export const referenceResourceListQuerySchema = z
|
||||
.object({
|
||||
@@ -26,18 +27,19 @@ export const referenceResourceParamsSchema = z
|
||||
|
||||
const referenceResourceDtoSchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
id: z.string().min(1).max(120),
|
||||
name: z.string().min(1).max(240),
|
||||
createdAt: z.string().datetime().optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const payloadSchemas = {
|
||||
ReferenceResourceListPayload: z.array(referenceResourceDtoSchema),
|
||||
ReferenceResourceListPayload: z.array(referenceResourceDtoSchema).max(100),
|
||||
ReferenceResourcePayload: referenceResourceDtoSchema,
|
||||
} satisfies Record<string, z.ZodType>;
|
||||
|
||||
const requestSchemas = {
|
||||
ReferenceResourceParams: referenceResourceParamsSchema,
|
||||
ReferenceResourceListQuery: referenceResourceListQuerySchema,
|
||||
NoRequest: z.object({}).strict(),
|
||||
CreateReferenceResourceCommand: z
|
||||
@@ -60,6 +62,40 @@ function project(result: z.ZodSafeParseResult<unknown>) {
|
||||
});
|
||||
}
|
||||
|
||||
function codec(
|
||||
schemaId: string,
|
||||
schema: z.ZodType,
|
||||
): RuntimeSchemaCodec {
|
||||
return Object.freeze({
|
||||
schemaId,
|
||||
parse: (value: unknown) => project(schema.safeParse(value)),
|
||||
});
|
||||
}
|
||||
|
||||
export const REFERENCE_RUNTIME_SCHEMA_CODECS = Object.freeze({
|
||||
ReferenceResourceParams: codec(
|
||||
"ReferenceResourceParams",
|
||||
referenceResourceParamsSchema,
|
||||
),
|
||||
ReferenceResourceListQuery: codec(
|
||||
"ReferenceResourceListQuery",
|
||||
referenceResourceListQuerySchema,
|
||||
),
|
||||
NoRequest: codec("NoRequest", requestSchemas.NoRequest),
|
||||
CreateReferenceResourceCommand: codec(
|
||||
"CreateReferenceResourceCommand",
|
||||
requestSchemas.CreateReferenceResourceCommand,
|
||||
),
|
||||
ReferenceResourceListPayload: codec(
|
||||
"ReferenceResourceListPayload",
|
||||
payloadSchemas.ReferenceResourceListPayload,
|
||||
),
|
||||
ReferenceResourcePayload: codec(
|
||||
"ReferenceResourcePayload",
|
||||
payloadSchemas.ReferenceResourcePayload,
|
||||
),
|
||||
});
|
||||
|
||||
export function validateReferencePayload(schemaId: string, value: unknown) {
|
||||
const schema = payloadSchemas[schemaId as keyof typeof payloadSchemas];
|
||||
return schema
|
||||
|
||||
Reference in New Issue
Block a user