/** * This mirrors action definitions from sandbox/grist/actions.py */ import map = require('lodash/map'); export type AddRecord = ['AddRecord', string, number, ColValues]; export type BulkAddRecord = ['BulkAddRecord', string, number[], BulkColValues]; export type RemoveRecord = ['RemoveRecord', string, number]; export type BulkRemoveRecord = ['BulkRemoveRecord', string, number[]]; export type UpdateRecord = ['UpdateRecord', string, number, ColValues]; export type BulkUpdateRecord = ['BulkUpdateRecord', string, number[], BulkColValues]; export type ReplaceTableData = ['ReplaceTableData', string, number[], BulkColValues]; // This is the format in which data comes when we fetch a table from the sandbox. export type TableDataAction = ['TableData', string, number[], BulkColValues]; export type AddColumn = ['AddColumn', string, string, ColInfo]; export type RemoveColumn = ['RemoveColumn', string, string]; export type RenameColumn = ['RenameColumn', string, string, string]; export type ModifyColumn = ['ModifyColumn', string, string, ColInfo]; export type AddTable = ['AddTable', string, ColInfoWithId[]]; export type RemoveTable = ['RemoveTable', string]; export type RenameTable = ['RenameTable', string, string]; export type DocAction = ( AddRecord | BulkAddRecord | RemoveRecord | BulkRemoveRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData | TableDataAction | AddColumn | RemoveColumn | RenameColumn | ModifyColumn | AddTable | RemoveTable | RenameTable ); // type guards for convenience - see: // https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards export function isAddRecord(act: DocAction): act is AddRecord { return act[0] === 'AddRecord'; } export function isBulkAddRecord(act: DocAction): act is BulkAddRecord { return act[0] === 'BulkAddRecord'; } export function isRemoveRecord(act: DocAction): act is RemoveRecord { return act[0] === 'RemoveRecord'; } export function isBulkRemoveRecord(act: DocAction): act is BulkRemoveRecord { return act[0] === 'BulkRemoveRecord'; } export function isUpdateRecord(act: DocAction): act is UpdateRecord { return act[0] === 'UpdateRecord'; } export function isBulkUpdateRecord(act: DocAction): act is BulkUpdateRecord { return act[0] === 'BulkUpdateRecord'; } export function isReplaceTableData(act: DocAction): act is ReplaceTableData { return act[0] === 'ReplaceTableData'; } export function isAddColumn(act: DocAction): act is AddColumn { return act[0] === 'AddColumn'; } export function isRemoveColumn(act: DocAction): act is RemoveColumn { return act[0] === 'RemoveColumn'; } export function isRenameColumn(act: DocAction): act is RenameColumn { return act[0] === 'RenameColumn'; } export function isModifyColumn(act: DocAction): act is ModifyColumn { return act[0] === 'ModifyColumn'; } export function isAddTable(act: DocAction): act is AddTable { return act[0] === 'AddTable'; } export function isRemoveTable(act: DocAction): act is RemoveTable { return act[0] === 'RemoveTable'; } export function isRenameTable(act: DocAction): act is RenameTable { return act[0] === 'RenameTable'; } const SCHEMA_ACTIONS = new Set(['AddTable', 'RemoveTable', 'RenameTable', 'AddColumn', 'RemoveColumn', 'RenameColumn', 'ModifyColumn']); /** * Determines whether a given action is a schema action or not. */ export function isSchemaAction(action: DocAction): boolean { return SCHEMA_ACTIONS.has(action[0]); } /** * Returns the tableId from the action. */ export function getTableId(action: DocAction): string { return action[1]; // It happens to always be in the same position in the action tuple. } // Helper types used in the definitions above. export type CellValue = number|string|boolean|null|[string, any?]; export interface ColValues { [colId: string]: CellValue; } export interface BulkColValues { [colId: string]: CellValue[]; } export interface ColInfoMap { [colId: string]: ColInfo; } export interface ColInfo { type: string; isFormula: boolean; formula: string; } export interface ColInfoWithId extends ColInfo { id: string; } export interface RowRecord { id: number; [colId: string]: CellValue; } // Multiple records in column-oriented format, i.e. same as BulkColValues but with a mandatory // 'id' column. This is preferred over TableDataAction in external APIs. export interface TableColValues { id: number[]; [colId: string]: CellValue[]; } // Both UserActions and DocActions are represented as [ActionName, ...actionArgs]. // TODO I think it's better to represent DocAction as a Buffer containing the marshalled action. export type UserAction = Array; /** * Gives a description for an action which involves setting values to a selection. * @param {Array} action - The (Bulk)AddRecord/(Bulk)UpdateRecord action to describe. * @param {Boolean} optExcludeVals - Indicates whether the values should be excluded from * the description. */ export function getSelectionDesc(action: UserAction, optExcludeVals: boolean): string { const table = action[1]; const rows = action[2]; const colValues: number[] = action[3] as any; // TODO: better typing - but code may evaporate const columns = map(colValues, (values, col) => optExcludeVals ? col : `${col}: ${values}`); const s = typeof rows === 'object' ? 's' : ''; return `table ${table}, row${s} ${rows}; ${columns.join(", ")}`; } // Convert from TableColValues (used by DocStorage and external APIs) to TableDataAction (used // mainly by the sandbox). export function toTableDataAction(tableId: string, colValues: TableColValues): TableDataAction { const colData = {...colValues}; // Make a copy to avoid changing passed-in arguments. const rowIds: number[] = colData.id; delete colData.id; return ['TableData', tableId, rowIds, colData]; } // Convert from TableDataAction (used mainly by the sandbox) to TableColValues (used by DocStorage // and external APIs). export function fromTableDataAction(tableData: TableDataAction): TableColValues { const rowIds: number[] = tableData[2]; const colValues: BulkColValues = tableData[3]; return {id: rowIds, ...colValues}; }