feat: deliver module database ABI by manifest
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
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
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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,
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user