import type { InfraAsCodeIntent } from "./intents.js"; import type { ViewSchema } from "./views.js"; export const APPS_PROVISIONING_SCHEMA = "notion:apps-provisioning:v1"; /** Provisioning intents recorded while evaluating the build-only metadata bundle. */ export type RecordedProvisioning = { intents: InfraAsCodeIntent[]; }; /** Teamspaces are always created in the Apps workspace, so Apps supplies their parent. */ export type ProvisioningIntent = | Omit, "parent"> | Exclude; export type ProvisioningArtifact = { $schema: typeof APPS_PROVISIONING_SCHEMA; version: 1; intents: readonly ProvisioningIntent[]; }; /** Convert recorded declarations into the server-facing provisioning artifact. */ export function createProvisioningArtifact( intents: readonly InfraAsCodeIntent[], ): ProvisioningArtifact { const provisioningIntents: ProvisioningIntent[] = []; for (const intent of intents) { if (intent.type === "view") { validateCustomView(intent.view); } else if (intent.type === "database") { for (const view of intent.views ?? []) { validateCustomView(view); } } if (intent.type === "teamspace") { const { parent: _parent, ...teamspace } = intent; provisioningIntents.push(teamspace); } else { provisioningIntents.push(intent); } } return { $schema: APPS_PROVISIONING_SCHEMA, version: 1, intents: provisioningIntents, }; } /** Check that a metadata bundle exported the recorder output. */ export function isRecordedProvisioning(value: unknown): value is RecordedProvisioning { return ( typeof value === "object" && value !== null && "intents" in value && Array.isArray(value.intents) ); } function validateCustomView(view: ViewSchema): void { if (view.type === "custom") { if (typeof view.customBlockKey !== "string" || view.customBlockKey.trim() === "") { throw new Error("Custom views require a non-empty customBlockKey"); } for (const field of [ "sorts", "filters", "properties", "showPageIcon", "defaultTemplate", ] as const) { if (field in view) { throw new Error(`Custom views do not support ${field}`); } } } }