Files
clean-architecture-frontend…/src/features/reference-feature/presentation/use-reference-feature.ts
T

148 lines
4.7 KiB
TypeScript

import { useApplicationFeature } from "../../../presentation/providers/application-provider.tsx";
import {
useApplicationMutation,
useApplicationQuery,
} from "../../../presentation/adapters/query/application-query.ts";
import { useRouteInput } from "../../../presentation/routes/route-input.tsx";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
REFERENCE_RESOURCE_QUERY_NAMESPACE,
} from "../contracts/reference-feature-contract.ts";
import type {
ReferenceCreateCommand,
ReferenceFeatureInput,
ReferenceListFilters,
} from "../application/reference-feature-api.ts";
import type { ReferenceResourceView } from "../contracts/reference-mapper.ts";
import {
bindQuery,
type BoundMutation,
type QueryResultMeasure,
} from "../../../contracts/server-state.ts";
import { useServerStateScope } from "../../../presentation/adapters/query/server-state-scope-provider.tsx";
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 };
}
const REFERENCE_CREATE_MUTATION_POLICY = Object.freeze({
definitionId: "reference-resource-create-v1",
definitionVersion: 1,
operationId: "CREATE_REFERENCE_RESOURCE",
requiresIdempotencyKey: true,
owner: REFERENCE_FEATURE_ID,
duplicatePolicy: "REJECT_WHILE_ACTIVE" as const,
invalidate: Object.freeze([REFERENCE_RESOURCE_INVALIDATION_TOPIC]),
});
type ReferenceCreateMutationBinding = Pick<
BoundMutation<ReferenceCreateCommand, ReferenceResourceView>,
"scope" | "execute"
>;
function bindReferenceCreateMutation(
binding: ReferenceCreateMutationBinding,
): BoundMutation<ReferenceCreateCommand, ReferenceResourceView> {
return Object.freeze({
...REFERENCE_CREATE_MUTATION_POLICY,
...binding,
});
}
export function useReferenceFeatureInput(): ReferenceFeatureInput {
return useApplicationFeature(REFERENCE_FEATURE_ID);
}
export function useReferenceDetail(resourceId: string) {
const input = useReferenceFeatureInput();
const scope = useServerStateScope();
const query = useApplicationQuery(
bindQuery(
{
definitionId: "reference-resource-detail-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceId,
namespaceVersion:
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceVersion,
operationId: "GET_REFERENCE_RESOURCE",
profileId: "DETAIL_STANDARD",
measureResult: measureResourceView,
execute: (selectedResourceId: string, { signal }) =>
input.getResource(selectedResourceId, { signal }),
},
resourceId,
scope,
),
);
return Object.freeze({ query });
}
export function useReferenceCreate() {
const input = useReferenceFeatureInput();
const scope = useServerStateScope();
const mutation = bindReferenceCreateMutation({
scope,
execute: input.createResource,
});
return useApplicationMutation(mutation);
}
export function useReferenceFeature() {
const input = useReferenceFeatureInput();
const scope = useServerStateScope();
const routeInput = useRouteInput();
const filters = routeInput.search as ReferenceListFilters;
const query = useApplicationQuery(
bindQuery(
{
definitionId: "reference-resource-list-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceId,
namespaceVersion:
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceVersion,
operationId: "LIST_REFERENCE_RESOURCES",
profileId: "LIST_STANDARD",
measureResult: measureResourceList,
execute: (selectedFilters: ReferenceListFilters, { signal }) =>
input.listResources(selectedFilters, { signal }),
},
filters,
scope,
),
);
const mutation = useApplicationMutation(
bindReferenceCreateMutation({
scope,
execute: input.createResource,
}),
);
return Object.freeze({ filters, query, mutation });
}