CI / verify (pull_request) Successful in 1m0s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import { z } from "zod";
|
|
import { moduleIdSchema } from "./identifiers.js";
|
|
import { physicalModuleTableNameSchema } from "./module-schema-names.js";
|
|
|
|
export const moduleSchemaForeignKeyMetadataSchema = z
|
|
.object({ table: physicalModuleTableNameSchema })
|
|
.strict();
|
|
|
|
export const moduleSchemaTableMetadataSchema = z
|
|
.object({
|
|
name: physicalModuleTableNameSchema,
|
|
foreignKeys: z.array(moduleSchemaForeignKeyMetadataSchema).default(() => []),
|
|
})
|
|
.strict();
|
|
|
|
/** The ABI-v1 schema entrypoint: inert Ekklesia-owned data, with no ORM object or executable
|
|
* callback anywhere in its shape. Ownership and foreign-key targets are validated by Core against
|
|
* the manifest module id and the complete table list. */
|
|
export const moduleSchemaMetadataDefinitionSchema = z
|
|
.object({
|
|
moduleId: moduleIdSchema,
|
|
tables: z.array(moduleSchemaTableMetadataSchema).min(1),
|
|
})
|
|
.strict();
|
|
|
|
export interface ModuleSchemaForeignKeyMetadata {
|
|
readonly table: string;
|
|
}
|
|
export interface ModuleSchemaTableMetadata {
|
|
readonly name: string;
|
|
readonly foreignKeys: readonly ModuleSchemaForeignKeyMetadata[];
|
|
}
|
|
export interface ModuleSchemaMetadataDefinition {
|
|
readonly moduleId: z.output<typeof moduleIdSchema>;
|
|
readonly tables: readonly ModuleSchemaTableMetadata[];
|
|
}
|
|
|
|
function isPlainData(value: unknown): boolean {
|
|
if (value === null || typeof value === "string") {
|
|
return true;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.every(isPlainData);
|
|
}
|
|
if (typeof value !== "object") {
|
|
return false;
|
|
}
|
|
const prototype = Object.getPrototypeOf(value);
|
|
return (
|
|
(prototype === Object.prototype || prototype === null) &&
|
|
Reflect.ownKeys(value).every(
|
|
(key) => typeof key === "string" && isPlainData((value as Record<string, unknown>)[key]),
|
|
)
|
|
);
|
|
}
|
|
|
|
export function parseModuleSchemaMetadataDefinition(
|
|
value: unknown,
|
|
): ModuleSchemaMetadataDefinition {
|
|
if (!isPlainData(value)) {
|
|
throw new TypeError("Invalid Ekklesia module schema metadata definition.");
|
|
}
|
|
const parsed = moduleSchemaMetadataDefinitionSchema.safeParse(value);
|
|
if (!parsed.success) {
|
|
throw new TypeError("Invalid Ekklesia module schema metadata definition.");
|
|
}
|
|
return Object.freeze({
|
|
moduleId: parsed.data.moduleId,
|
|
tables: Object.freeze(
|
|
parsed.data.tables.map((table) =>
|
|
Object.freeze({
|
|
foreignKeys: Object.freeze(
|
|
table.foreignKeys.map((foreignKey) => Object.freeze(foreignKey)),
|
|
),
|
|
name: table.name,
|
|
}),
|
|
),
|
|
),
|
|
});
|
|
}
|