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, "scope" | "execute" >; function bindReferenceCreateMutation( binding: ReferenceCreateMutationBinding, ): BoundMutation { 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 }); }