// common/proto/table_definition.proto syntax = "proto3"; package komp_ac.table_definition; import "common.proto"; // The TableDefinition service manages the entire lifecycle of user-defined // tables (stored as both metadata and physical PostgreSQL tables) inside // logical "profiles" (schemas). Each table has stored structure, links, and // validation rules. service TableDefinition { // Creates a new table (and schema if missing) with system columns, // linked-table foreign keys, user-defined columns, and optional indexes. // Also inserts metadata and default validation rules. Entirely transactional. rpc PostTableDefinition(PostTableDefinitionRequest) returns (TableDefinitionResponse); // Creates a regular user-table bundle from the restricted komp_ac_fields // contract embedded in a Typst invoice template. This is invoked explicitly // by the user; templates do not provision tables merely by existing. rpc CreateInvoiceTemplateTable(CreateInvoiceTemplateTableRequest) returns (CreateInvoiceTemplateTableResponse); // Appends new user-defined columns to an existing table. // Existing columns, links, and table logic are never changed by this call. rpc AddTableColumns(AddTableColumnsRequest) returns (TableDefinitionResponse); // Lists all profiles (schemas) and their tables with declared dependencies. // This provides a tree-like overview of table relationships. rpc GetProfileTree(komp_ac.common.Empty) returns (ProfileTreeResponse); // Fetches all tables with their columns and scripts for a specific profile. // Pure data retrieval - no business logic. rpc GetProfileDetails(GetProfileDetailsRequest) returns (GetProfileDetailsResponse); // Copies one complete profile into a new profile without copying table data. rpc CopyProfile(CopyProfileRequest) returns (CopyProfileResponse); // Returns the stored rename history for column aliases in one profile. rpc GetColumnAliasRenameHistory(GetColumnAliasRenameHistoryRequest) returns (GetColumnAliasRenameHistoryResponse); // Renames a user-visible column alias while keeping the physical column unchanged. rpc RenameColumnAlias(RenameColumnAliasRequest) returns (RenameColumnAliasResponse); // Drops a table and its metadata, then deletes the profile if it becomes empty. rpc DeleteTable(DeleteTableRequest) returns (DeleteTableResponse); } // A single link to another table within the same profile (schema). message TableLink { // Name of an existing table within the same profile to link to. // For each link, a "_id" column is created on the new table. // That column references ""(id) and adds an index automatically. string linked_table_name = 1; // If true, the generated foreign key column is NOT NULL. // Otherwise the column allows NULL. // Duplicate links to the same target table in one request are rejected. bool required = 2; // User-defined column read from this linked row as the journal account. // Exactly one required link must set this when the table contains ACCOUNTING. string account_value_column = 3; } // Defines the input for creating a new table definition. message PostTableDefinitionRequest { // Table name to create inside the target profile. // Must be lowercase, alphanumeric with underscores, // start with a letter, and be <= 63 chars. // Forbidden names: "id", "deleted", "created_at", "row_revision", or ending in "_id". string table_name = 1; // List of links (foreign keys) to existing tables in the same profile. // Each will automatically get a "_id" column and an index. repeated TableLink links = 2; // List of user-defined columns (adds to system/id/fk columns). repeated ColumnDefinition columns = 3; // List of column names to be indexed (must match existing user-defined columns). // Indexes can target only user-defined columns; system columns ("id", "deleted", // "created_at", "row_revision") and automatically generated foreign key ("*_id") columns already // have indexes. Requests trying to index those columns are rejected. repeated string indexes = 4; // Name of profile (Postgres schema) where the table will be created. // Same naming rules as table_name; cannot collide with reserved schemas // like "public", "information_schema", or ones starting with "pg_". string profile_name = 5; // ISO-4217 base currency used by every MONEY column in this table. string base_currency = 6; // Columns whose values identify a row to users in pickers, in the order // they are shown. Each must name one of the user-defined columns above. // Empty means the row is identified by its id alone. repeated string row_display_columns = 7; // ISO-4217 currency the profile keeps its accounting in. A profile is one // accounting entity and keeps one set of books, so this is required only when // the request creates the profile, and is ignored afterwards. It is unrelated // to base_currency: tables may hold money in any currency, and amounts convert // to this one when they reach the ledger. string accounting_currency = 8; } // Defines the input for explicitly creating a table backed by one invoice // template. typst_source must contain exactly one declaration of the form: // // #let komp_ac_fields = ( // "invoice.number": (source: "local", field_type: "TEXT", write: "user"), // "items[].product": (source: "reference", table: "stock", required: true), // "items[].quantity": (source: "local", field_type: "DECIMAL(12,3)", write: "user"), // "items[].total": (source: "local", field_type: "MONEY", write: "script"), // ) // // Root fields create columns and references on table_name. Every [] collection // creates a child table with a required FK to its immediate parent; fields in // that collection create columns and references on the child table. Multiple // template paths in one scope may reuse the same reference. message CreateInvoiceTemplateTableRequest { string profile_name = 1; string table_name = 2; string typst_source = 3; string base_currency = 4; repeated string row_display_columns = 5; } // One physical dynamic table created for an invoice template scope. The root // has an empty collection_path and parent_table_name. Each [] scope names its // collection and the generated table that owns those repeated rows. message GeneratedInvoiceTemplateTable { string table_name = 1; string collection_path = 2; string parent_table_name = 3; string sql = 4; } // Reports the complete table bundle created from one invoice template. message CreateInvoiceTemplateTableResponse { bool success = 1; repeated GeneratedInvoiceTemplateTable tables = 2; } // Defines append-only column additions for an existing table. message AddTableColumnsRequest { // Existing profile/schema name. string profile_name = 1; // Existing table name in the profile. string table_name = 2; // New user-defined columns only. Existing columns cannot be changed here. repeated ColumnDefinition columns = 3; // Optional indexes for the new columns only. repeated string indexes = 4; // Required when adding the first MONEY column to a table. string base_currency = 5; } enum MoneyRounding { MONEY_ROUNDING_NONE = 0; MONEY_ROUNDING_HALF_UP = 1; } // Describes one user-defined column for a table. message ColumnDefinition { // Column name that follows the same validation rules as table_name. // Must be lowercase, start with a letter, no uppercase characters, // and cannot be "id", "deleted", "created_at", "row_revision", or end with "_id". string name = 1; // Logical column type. Supported values (case-insensitive): // TEXT // BOOLEAN // INSTANT (local input resolved in the authenticated user's timezone // and displayed in the viewer timezone) // USER_DATETIME (local input resolved in the user's timezone) // RAW_DATETIME (timezone-free civil datetime) // PHONE (international or national phone number; generates extension/type/country/calling-code companions) // TIME (timezone-free time of day) // MONEY (= unconstrained NUMERIC; currency comes from the table) // ACCOUNTING (creates schema-managed name, debit, and credit columns; // the table creator must separately provide a column named account // with any supported type or a required link to a table named account; // linked accounts post the linked row's configured display value; // name is limited to 10 characters and one stored row contributes // one line to the profile journal) // INT // BIGINT // DATE // DURATION // PERIOD // DECIMAL(p,s) → NUMERIC(p,s) // DECIMAL args must be integers (no sign, no dot, no leading zeros); // s ≤ p and p ≥ 1. string field_type = 2; reserved 3; // MONEY rounding applied before a value is stored. MoneyRounding rounding = 4; // When true, the submitted script result is validated normally and the // stored value is also refreshed when one of its dependencies later changes. bool recompute_on_dependency_change = 5; } // Response after table creation (success + DDL preview). message TableDefinitionResponse { // True if all DB changes and metadata inserts succeeded. bool success = 1; // The actual SQL executed: CREATE TABLE + CREATE INDEX statements. string sql = 2; } // Describes the tree of all profiles and their tables. message ProfileTreeResponse { // Table entry in a profile. message Table { // Internal ID from table_definitions.id (metadata record). int64 id = 1; // Table name within the profile (schema). string name = 2; // Other tables this one references (based on link definitions only). repeated string depends_on = 3; // Columns whose values make up the human-readable row label, in order. repeated string row_display_columns = 4; // "dynamic" for user-defined tables, "system" for backend-managed tables. string table_kind = 5; } // Profile (schema) entry. message Profile { // Name of the schema/profile (as stored in `schemas.name`). string name = 1; // All tables in that schema and their dependencies. repeated Table tables = 2; } // All profiles in the system. repeated Profile profiles = 1; } // Request to fetch all tables, columns and scripts for a profile. message GetProfileDetailsRequest { // Profile (schema) name to fetch details for. string profile_name = 1; } // Response with all tables, columns and scripts for a profile. message GetProfileDetailsResponse { string profile_name = 1; repeated TableDetail tables = 2; } // Request to copy one full profile into a new profile. message CopyProfileRequest { string source_profile_name = 1; string target_profile_name = 2; repeated string table_names = 3; } // Response after copying a profile. message CopyProfileResponse { bool success = 1; string message = 2; int32 tables_copied = 3; int32 scripts_copied = 4; } // Request to fetch recorded column alias rename history for one profile. message GetColumnAliasRenameHistoryRequest { string profile_name = 1; // Filter. When omitted, returns all tables in the profile. optional int64 table_definition_id = 2; } // One recorded column alias rename. message ColumnAliasRenameHistoryEntry { int64 id = 1; string profile_name = 2; int64 table_definition_id = 3; string table_name = 4; string old_column_name = 5; string new_column_name = 6; string created_at = 7; } // Response with stored column alias rename history rows. message GetColumnAliasRenameHistoryResponse { string profile_name = 1; repeated ColumnAliasRenameHistoryEntry entries = 2; } // Describes a table with its columns and associated scripts. message TableDetail { string name = 1; int64 id = 2; repeated ColumnDefinition columns = 3; repeated ScriptInfo scripts = 4; string base_currency = 5; repeated string row_display_columns = 6; map column_behaviors = 7; string table_kind = 8; } // Server-owned behavior for one logical column returned in table details. message ColumnBehavior { // True when the server created the column as a companion of another column. bool generated = 1; // True when clients must display but never submit edits for the column. bool read_only = 2; // Logical source column name, empty for ordinary user-defined columns. string generated_from = 3; } // A script that targets a specific column in a table. message ScriptInfo { int64 script_id = 1; string target_column = 2; string target_column_type = 3; string script = 4; string description = 5; } // Request to rename one user-visible column alias in a table. message RenameColumnAliasRequest { string profile_name = 1; string table_name = 2; string old_column_name = 3; string new_column_name = 4; } // Response after renaming one column alias. message RenameColumnAliasResponse { bool success = 1; string message = 2; } // Request to delete one table definition entirely. message DeleteTableRequest { // Profile (schema) name owning the table (must exist). string profile_name = 1; // Table to drop (must exist in the profile). // Executes DROP TABLE "profile"."table" CASCADE and then removes metadata. string table_name = 2; } // Response after table deletion. message DeleteTableResponse { // True if table and metadata were successfully deleted in one transaction. bool success = 1; // Human-readable summary of what was removed. string message = 2; }