112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
export type SchemaDefinition = Readonly<{
|
|
schemaId: string;
|
|
boundary:
|
|
| "route-params"
|
|
| "route-search"
|
|
| "route-search-api-request"
|
|
| "api-request"
|
|
| "api-response";
|
|
owner: string;
|
|
runtime: "zod";
|
|
schemaVersion?: number;
|
|
direction?: "REQUEST" | "RESPONSE";
|
|
unknownFieldPolicy?: "REJECT_UNKNOWN" | "STRIP_UNKNOWN";
|
|
}>;
|
|
|
|
export type RuntimeSchemaResult =
|
|
| Readonly<{ success: true; data: unknown }>
|
|
| Readonly<{
|
|
success: false;
|
|
issues: readonly Readonly<{ path: string; code: string }>[];
|
|
}>;
|
|
|
|
export type RuntimeSchemaCodec = Readonly<{
|
|
schemaId: string;
|
|
parse(value: unknown): RuntimeSchemaResult;
|
|
}>;
|
|
|
|
export function composeRuntimeSchemaCodecs(
|
|
contributions: readonly Readonly<Record<string, RuntimeSchemaCodec>>[],
|
|
): Readonly<Record<string, RuntimeSchemaCodec>> {
|
|
const result: Record<string, RuntimeSchemaCodec> = Object.create(null);
|
|
for (const contribution of contributions) {
|
|
for (const [registryId, codec] of Object.entries(contribution)) {
|
|
if (
|
|
registryId !== codec.schemaId ||
|
|
Object.hasOwn(result, registryId)
|
|
) {
|
|
throw new TypeError(
|
|
`Invalid or duplicate runtime schema codec: ${registryId}`,
|
|
);
|
|
}
|
|
result[registryId] = codec;
|
|
}
|
|
}
|
|
return Object.freeze(result);
|
|
}
|
|
|
|
export function validateWithRuntimeSchemaRegistry(
|
|
schemaId: string,
|
|
value: unknown,
|
|
registry: Readonly<Record<string, RuntimeSchemaCodec>>,
|
|
): RuntimeSchemaResult {
|
|
const codec = registry[schemaId];
|
|
if (!codec) {
|
|
return Object.freeze({
|
|
success: false,
|
|
issues: Object.freeze([
|
|
Object.freeze({ path: "", code: "SCHEMA_NOT_REGISTERED" }),
|
|
]),
|
|
});
|
|
}
|
|
try {
|
|
return codec.parse(value);
|
|
} catch {
|
|
return Object.freeze({
|
|
success: false,
|
|
issues: Object.freeze([
|
|
Object.freeze({ path: "", code: "SCHEMA_EXECUTION_FAILED" }),
|
|
]),
|
|
});
|
|
}
|
|
}
|
|
|
|
export function composeSchemaRegistry(
|
|
contributions: readonly Readonly<Record<string, SchemaDefinition>>[],
|
|
): Readonly<Record<string, SchemaDefinition>> {
|
|
const result: Record<string, SchemaDefinition> = Object.create(null);
|
|
for (const contribution of contributions) {
|
|
for (const [registryId, definition] of Object.entries(contribution)) {
|
|
if (
|
|
registryId !== definition.schemaId ||
|
|
!definition.owner ||
|
|
(definition.schemaVersion !== undefined &&
|
|
(!Number.isSafeInteger(definition.schemaVersion) ||
|
|
definition.schemaVersion < 1)) ||
|
|
Object.hasOwn(result, registryId)
|
|
) {
|
|
throw new TypeError(`Invalid or duplicate schema definition: ${registryId}`);
|
|
}
|
|
result[registryId] = definition;
|
|
}
|
|
}
|
|
return Object.freeze(result);
|
|
}
|
|
|
|
export const PLATFORM_SCHEMA_REGISTRY: Readonly<
|
|
Record<string, SchemaDefinition>
|
|
> = Object.freeze({
|
|
none: Object.freeze({
|
|
schemaId: "none",
|
|
boundary: "route-params",
|
|
owner: "feature-frontend-routing-release-recovery-runtime",
|
|
runtime: "zod",
|
|
}),
|
|
NotFoundSplat: Object.freeze({
|
|
schemaId: "NotFoundSplat",
|
|
boundary: "route-params",
|
|
owner: "feature-frontend-routing-release-recovery-runtime",
|
|
runtime: "zod",
|
|
}),
|
|
});
|