(core) configure typedoc for generating plugin api documentation

Summary:
This annotates the plugin api sufficiently to generate some documentation
for it. See https://github.com/gristlabs/grist-help/pull/139

Contains some small code tweaks for things that caused typedoc some
trouble.

Test Plan: manual inspection of output

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D3342
pull/178/head
Paul Fitzpatrick 2 years ago
parent d8af25de9d
commit c6d66e15bf

@ -69,40 +69,58 @@ export interface GristAPI {
}
/**
* GristDocAPI interface is implemented by Grist, and allows getting information from and
* interacting with the Grist document to which a plugin is attached.
* Allows getting information from and nteracting with the Grist document to which a plugin or widget is attached.
*/
export interface GristDocAPI {
// Returns the docName that identifies the document.
/**
* Returns an identifier for the document.
*/
getDocName(): Promise<string>;
// Returns a sorted list of table IDs.
/**
* Returns a sorted list of table IDs.
*/
listTables(): Promise<string[]>;
// Returns a complete table of data in the format {colId: [values]}, including the 'id' column.
// Do not modify the returned arrays in-place, especially if used directly (not over RPC).
// TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
// ts-interface-builder does not properly support index-signature.
/**
* Returns a complete table of data in the format {colId: [values]}, including the 'id' column.
* Do not modify the returned arrays in-place, especially if used directly (not over RPC).
* TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
* ts-interface-builder does not properly support index-signature.
*/
fetchTable(tableId: string): Promise<any>;
// Applies an array of user actions.
// todo: return type should be Promise<ApplyUAResult>, but this requires importing modules from
// `app/common` which is not currently supported by the build.
/**
* Applies an array of user actions.
* TODO: return type should be Promise<ApplyUAResult>, but this requires importing modules from
* `app/common` which is not currently supported by the build.
*/
applyUserActions(actions: any[][], options?: any): Promise<any>;
}
/**
* Interface for the data backing a single widget.
*/
export interface GristView {
// Like fetchTable, but gets data for the custom section specifically, if there is any.
// TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
// ts-interface-builder does not properly support index-signature.
/**
* Like [[GristDocAPI.fetchTable]], but gets data for the custom section specifically, if there is any.
* TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
* ts-interface-builder does not properly support index-signature.
*/
fetchSelectedTable(): Promise<any>;
// Similar TODO to fetchSelectedTable for return type.
/**
* Similar TODO to `fetchSelectedTable()` for return type.
*/
fetchSelectedRecord(rowId: number): Promise<any>;
// Allow custom widget to be listed as a possible source for linking with SELECT BY.
/**
* Allow custom widget to be listed as a possible source for linking with SELECT BY.
*/
allowSelectBy(): Promise<void>;
// Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`.
/**
* Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`.
*/
setSelectedRows(rowIds: number[]): Promise<void>;
}

@ -0,0 +1 @@
Methods here are available for use in Grist custom widgets.

@ -1,5 +1,7 @@
/**
* Where to append the content that a plugin renders.
*
* @internal
*/
export type RenderTarget = "fullscreen" | number;

@ -4,22 +4,32 @@ import * as Types from 'app/plugin/DocApiTypes';
* Offer CRUD-style operations on a table.
*/
export interface TableOperations {
// Create a record or records.
/**
* Create a record or records.
*/
create(records: Types.NewRecord, options?: OpOptions): Promise<Types.MinimalRecord>;
create(records: Types.NewRecord[], options?: OpOptions): Promise<Types.MinimalRecord[]>;
// Update a record or records.
/**
* Update a record or records.
*/
update(records: Types.Record|Types.Record[], options?: OpOptions): Promise<void>;
// Delete a record or records.
/**
* Delete a record or records.
*/
destroy(recordId: Types.RecordId): Promise<Types.RecordId>;
destroy(recordIds: Types.RecordId[]): Promise<Types.RecordId[]>;
// Add or update a record or records.
/**
* Add or update a record or records.
*/
upsert(records: Types.AddOrUpdateRecord|Types.AddOrUpdateRecord[],
options?: UpsertOptions): Promise<void>;
// Determine the tableId of the table.
/**
* Determine the tableId of the table.
*/
getTableId(): Promise<string>;
// TODO: offer a way to query the table.
@ -32,7 +42,7 @@ export interface TableOperations {
* This can be disabled.
*/
export interface OpOptions {
parseStrings?: boolean;
parseStrings?: boolean; /** whether to parse strings based on the column type. */
}
/**
@ -40,8 +50,8 @@ export interface OpOptions {
* onMany is first, and allowEmptyRequire is false.
*/
export interface UpsertOptions extends OpOptions {
add?: boolean; // permit inserting a record
update?: boolean; // permit updating a record
onMany?: 'none' | 'first' | 'all'; // whether to update none, one, or all matching records
allowEmptyRequire?: boolean; // allow "wildcard" operation
add?: boolean; /** permit inserting a record */
update?: boolean; /** permit updating a record */
onMany?: 'none' | 'first' | 'all'; /** whether to update none, one, or all matching records */
allowEmptyRequire?: boolean; /** allow "wildcard" operation */
}

@ -46,46 +46,103 @@ export const rpc: Rpc = new Rpc({logger: createRpcLogger()});
export const api = rpc.getStub<GristAPI>(RPC_GRISTAPI_INTERFACE, checkers.GristAPI);
export const coreDocApi = rpc.getStub<GristDocAPI>('GristDocAPI@grist', checkers.GristDocAPI);
/**
* Interface for the records backing a custom widget.
*/
export const viewApi = rpc.getStub<GristView>('GristView', checkers.GristView);
/**
* Interface for the state of a custom widget.
*/
export const widgetApi = rpc.getStub<WidgetAPI>('WidgetAPI', checkers.WidgetAPI);
/**
* Interface for the mapping of a custom widget.
*/
export const sectionApi = rpc.getStub<CustomSectionAPI>('CustomSectionAPI', checkers.CustomSectionAPI);
/**
* Shortcut for [[GristView.allowSelectBy]].
*/
export const allowSelectBy = viewApi.allowSelectBy;
/**
* Shortcut for [[GristView.setSelectedRows]].
*/
export const setSelectedRows = viewApi.setSelectedRows;
/**
* Fetches data backing the widget as for [[GristView.fetchSelectedTable]],
* but decoding data by default, replacing e.g. ['D', timestamp] with
* a moment date. Option `keepEncoded` skips the decoding step.
*/
export async function fetchSelectedTable(options: {keepEncoded?: boolean} = {}) {
const table = await viewApi.fetchSelectedTable();
return options.keepEncoded ? table :
mapValues<any[], any[]>(table, (col) => col.map(decodeObject));
}
/**
* Fetches current selected record as for [[GristView.fetchSelectedRecord]],
* but decoding data by default, replacing e.g. ['D', timestamp] with
* a moment date. Option `keepEncoded` skips the decoding step.
*/
export async function fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) {
const rec = await viewApi.fetchSelectedRecord(rowId);
return options.keepEncoded ? rec :
mapValues(rec, decodeObject);
}
/**
* A collection of methods for fetching document data. The
* fetchSelectedTable and fetchSelectedRecord methods are
* overridden to decode data by default.
*/
export const docApi: GristDocAPI & GristView = {
...coreDocApi,
...viewApi,
// Change fetchSelectedTable() to decode data by default, replacing e.g. ['D', timestamp] with
// a moment date. New option `keepEncoded` skips the decoding step.
async fetchSelectedTable(options: {keepEncoded?: boolean} = {}) {
const table = await viewApi.fetchSelectedTable();
return options.keepEncoded ? table :
mapValues<any[], any[]>(table, (col) => col.map(decodeObject));
},
// Change fetchSelectedRecord() to decode data by default, replacing e.g. ['D', timestamp] with
// a moment date. New option `keepEncoded` skips the decoding step.
async fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) {
const rec = await viewApi.fetchSelectedRecord(rowId);
return options.keepEncoded ? rec :
mapValues(rec, decodeObject);
},
fetchSelectedTable,
fetchSelectedRecord,
};
export const on = rpc.on.bind(rpc);
// Exposing widgetApi methods in a module scope.
/**
* Shortcut for [[WidgetAPI.getOption]]
*/
export const getOption = widgetApi.getOption.bind(widgetApi);
/**
* Shortcut for [[WidgetAPI.setOption]]
*/
export const setOption = widgetApi.setOption.bind(widgetApi);
/**
* Shortcut for [[WidgetAPI.setOptions]]
*/
export const setOptions = widgetApi.setOptions.bind(widgetApi);
/**
* Shortcut for [[WidgetAPI.getOptions]]
*/
export const getOptions = widgetApi.getOptions.bind(widgetApi);
/**
* Shortcut for [[WidgetAPI.clearOptions]]
*/
export const clearOptions = widgetApi.clearOptions.bind(widgetApi);
// Get access to a table in the document. If no tableId specified, this
// will use the current selected table (for custom widgets).
// If a table does not exist, there will be no error until an operation
// on the table is attempted.
/**
* Get access to a table in the document. If no tableId specified, this
* will use the current selected table (for custom widgets).
* If a table does not exist, there will be no error until an operation
* on the table is attempted.
*/
export function getTable(tableId?: string): TableOperations {
return new TableOperationsImpl({
async getTableId() {
@ -100,7 +157,9 @@ export function getTable(tableId?: string): TableOperations {
}, {});
}
// Get the current selected table (for custom widgets).
/**
* Get the current selected table (for custom widgets).
*/
export const selectedTable: TableOperations = getTable();
// Get the ID of the current selected table (for custom widgets).
@ -240,13 +299,15 @@ export function mapColumnNamesBack(data: any, options: {
return mapColumnNames(data, {...options, reverse: true});
}
// For custom widgets, add a handler that will be called whenever the
// row with the cursor changes - either by switching to a different row, or
// by some value within the row potentially changing. Handler may
// in the future be called with null if the cursor moves away from
// any row.
// TODO: currently this will be called even if the content of a different row
// changes.
/**
* For custom widgets, add a handler that will be called whenever the
* row with the cursor changes - either by switching to a different row, or
* by some value within the row potentially changing. Handler may
* in the future be called with null if the cursor moves away from
* any row.
* TODO: currently this will be called even if the content of a different row
* changes.
*/
export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColumnMap | null) => unknown) {
on('message', async function(msg) {
if (!msg.tableId || !msg.rowId) { return; }
@ -254,8 +315,11 @@ export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColu
callback(rec, await getMappingsIfChanged(msg));
});
}
// For custom widgets, add a handler that will be called whenever the
// selected records change. Handler will be called with a list of records.
/**
* For custom widgets, add a handler that will be called whenever the
* selected records change. Handler will be called with a list of records.
*/
export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMap | null) => unknown) {
on('message', async function(msg) {
if (!msg.tableId || !msg.dataChange) { return; }
@ -274,10 +338,13 @@ export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMa
}
// For custom widgets, add a handler that will be called whenever the
// widget options change (and on initial ready message). Handler will be
// called with an object containing save json options, or null if no options were saved.
// Second parameter
/**
* For custom widgets, add a handler that will be called whenever the
* widget options change (and on initial ready message). Handler will be
* called with an object containing saved json options, or null if no options were saved.
* The second parameter has information about the widgets relationship with
* the document that contains it.
*/
export function onOptions(callback: (options: any, settings: InteractionOptions) => unknown) {
on('message', async function(msg) {
if (msg.settings) {
@ -297,6 +364,7 @@ export function onOptions(callback: (options: any, settings: InteractionOptions)
* `name`. Calling `addImporter(...)` from another component than a `safeBrowser` component is not
* currently supported.
*
* @internal
*/
export async function addImporter(name: string, path: string, mode: 'fullscreen' | 'inline', options?: RenderOptions) {
// checker is omitted for implementation because call was already checked by grist.
@ -315,7 +383,10 @@ export async function addImporter(name: string, path: string, mode: 'fullscreen'
});
}
interface ReadyPayload extends Omit<InteractionOptionsRequest, "hasCustomOptions"> {
/**
* Options when initializing connection to Grist.
*/
export interface ReadyPayload extends Omit<InteractionOptionsRequest, "hasCustomOptions"> {
/**
* Handler that will be called by Grist to open additional configuration panel inside the Custom Widget.
*/
@ -354,6 +425,7 @@ export function ready(settings?: ReadyPayload): void {
})();
}
/** @internal */
function getPluginPath(location: Location) {
return location.pathname.replace(/^\/plugins\//, '');
}
@ -393,6 +465,7 @@ if (typeof window !== 'undefined') {
rpc.setSendMessage((data) => { return; });
}
/** @internal */
function createRpcLogger(): IRpcLogger {
let prefix: string;
if (typeof window !== 'undefined') {

@ -90,7 +90,7 @@ export class TestServerMerged implements IMochaServer {
// logging. Server code uses a global logger, so it's hard to separate out (especially so if
// we ever run different servers for different tests).
const serverLog = process.env.VERBOSE ? 'inherit' : nodeLogFd;
const env = {
const env: Record<string, string> = {
TYPEORM_DATABASE: this._getDatabaseFile(),
TEST_CLEAN_DATABASE: reset ? 'true' : '',
GRIST_DATA_DIR: this.testDocDir,

@ -70,7 +70,7 @@ export class GristClient {
if (this._pending.length) {
return this._pending.shift();
}
await new Promise(resolve => this._consumer = resolve);
await new Promise<void>(resolve => this._consumer = resolve);
}
}

@ -6,5 +6,14 @@
{ "path": "./app" },
{ "path": "./stubs/app" },
{ "path": "./test" },
]
],
"typedocOptions": {
"entryPoints": [
"app/plugin/grist-plugin-api.ts",
"app/plugin/TableOperations.ts",
],
"excludeInternal": "true",
"excludeNotDocumented": "true",
"out": "doc"
}
}

Loading…
Cancel
Save