148 lines
4.7 KiB
TypeScript
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 });
|
|
}
|