/
DocActions.ts
225 lines (190 loc) · 8.52 KB
/
DocActions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* This mirrors action definitions from sandbox/grist/actions.py
*/
// Some definitions have moved to be part of plugin API.
import { BulkColValues, CellValue, RowRecord } from 'app/plugin/GristData';
export type { BulkColValues, CellValue, RowRecord };
// Part of a special CellValue used for comparisons, embedding several versions of a CellValue.
export interface AllCellVersions {
parent: CellValue;
remote: CellValue;
local: CellValue;
}
export type CellVersions = Partial<AllCellVersions>;
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, Partial<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']);
const DATA_ACTIONS = new Set(['AddRecord', 'RemoveRecord', 'UpdateRecord', 'BulkAddRecord',
'BulkRemoveRecord', 'BulkUpdateRecord', 'ReplaceTableData', 'TableData']);
/**
* Determines whether a given action is a schema action or not.
*/
export function isSchemaAction(action: DocAction):
action is AddTable | RemoveTable | RenameTable | AddColumn | RemoveColumn | RenameColumn | ModifyColumn {
return SCHEMA_ACTIONS.has(action[0]);
}
export function isDataAction(action: DocAction|UserAction):
action is AddRecord | RemoveRecord | UpdateRecord |
BulkAddRecord | BulkRemoveRecord | BulkUpdateRecord |
ReplaceTableData | TableDataAction {
return DATA_ACTIONS.has(String(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.
}
export interface TableDataActionSet {
[tableId: string]: TableDataAction;
}
// Helper types used in the definitions above.
export interface ColValues { [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;
}
// 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[];
}
// Multiple records in record-oriented format
export interface TableRecordValues {
records: TableRecordValue[];
}
export interface TableRecordValue {
id: number | string;
fields: {
[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<string|number|object|boolean|null|undefined>;
// Actions that trigger formula calculations in the data engine
export const CALCULATING_USER_ACTIONS = new Set(['Calculate', 'UpdateCurrentTime', 'RespondToRequests']);
export function getNumRows(action: DocAction): number {
return !isDataAction(action) ? 0
: Array.isArray(action[2]) ? action[2].length
: 1;
}
// 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 as BulkColValues).id;
return ['TableData', tableId, rowIds, colData];
}
// Convert from TableDataAction (used mainly by the sandbox) to TableColValues (used by DocStorage
// and external APIs).
// Also accepts a TableDataAction nested as a tableData member of a larger structure,
// for convenience in dealing with the result of fetches.
export function fromTableDataAction(tableData: TableDataAction|{tableData: TableDataAction}): TableColValues {
const data = ('tableData' in tableData) ? tableData.tableData : tableData;
const rowIds: number[] = data[2];
const colValues: BulkColValues = data[3];
return {id: rowIds, ...colValues};
}
/**
* Convert a list of rows into an object with columns of values, used for
* BulkAddRecord/BulkUpdateRecord actions.
*/
export function getColValues(records: Partial<RowRecord>[]): BulkColValues {
const colIdSet = new Set<string>();
for (const r of records) {
for (const c of Object.keys(r)) {
if (c !== 'id') {
colIdSet.add(c);
}
}
}
const result: BulkColValues = {};
for (const colId of colIdSet) {
result[colId] = records.map(r => r[colId]!);
}
return result;
}
/**
* Extract the col ids mentioned in a record-related DocAction as a list
* (even if the action is not a bulk action). Returns undefined if no col ids
* mentioned.
*/
export function getColIdsFromDocAction(docActions: RemoveRecord | BulkRemoveRecord | AddRecord |
BulkAddRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData |
TableDataAction): string[] | undefined {
if (docActions[3]) { return Object.keys(docActions[3]); }
return undefined;
}
/**
* Extract column values for a particular column as CellValue[] from a
* record-related DocAction. Undefined if absent.
*/
export function getColValuesFromDocAction(docAction: RemoveRecord | BulkRemoveRecord | AddRecord |
BulkAddRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData |
TableDataAction, colId: string): CellValue[]|undefined {
const colValues = docAction[3];
if (!colValues) { return undefined; }
const cellValues = colValues[colId];
if (!cellValues) { return undefined; }
if (Array.isArray(docAction[2])) {
return cellValues as CellValue[];
} else {
return [cellValues as CellValue];
}
}