Skip to content

Commit

Permalink
Adjusted Network Object and Data Provider Types (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjcouch-sil committed May 1, 2023
2 parents 0a2e4f5 + 4f63c07 commit c70c99b
Show file tree
Hide file tree
Showing 16 changed files with 2,399 additions and 172 deletions.
4 changes: 2 additions & 2 deletions extensions/lib/hello-someone/hello-someone.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DataProvider } from 'shared/models/data-provider.model';
import type IDataProvider from 'shared/models/data-provider.interface';

export interface GreetingsDataProvider extends DataProvider<string, string, string> {
export interface GreetingsDataProvider extends IDataProvider<string, string, string> {
testRandomMethod(things: string): Promise<string>;
}
2 changes: 1 addition & 1 deletion extensions/lib/hello-someone/hello-someone.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import papi from 'papi';
import type { WebViewContentType } from 'shared/data/web-view.model';
import type IDataProvider from 'shared/models/data-provider.interface';
import { UnsubscriberAsync } from 'shared/utils/papi-util';
import type IDataProvider from 'shared/models/data-provider.interface';
// @ts-expect-error ts(1192) this file has no default export; the text is exported by rollup
import helloSomeoneHtmlWebView from './hello-someone.web-view.ejs';

Expand Down
5 changes: 4 additions & 1 deletion extensions/lib/hello-someone/hello-someone.web-view.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@
);
const helloSomeoneOutput = document.getElementById('greetings-button-output');
helloSomeoneOutput.textContent = papi.util.htmlEncode(
`${success ? 'Successfully' : 'Unsuccessfully'} set ${name}'s greeting!`,
`${success ? 'Successfully' : 'Unsuccessfully'} set ${name}'s greeting!`.replace(
/'/g,
'`',
),
);
});
Expand Down
4 changes: 2 additions & 2 deletions extensions/lib/quick-verse/quick-verse.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { DataProvider } from 'shared/models/data-provider.model';
import type IDataProvider from 'shared/models/data-provider.interface';

// TODO: Move these types to quick-verse.ts and generate this file from quick-verse.ts in the future?

export type QuickVerseSetData = string | { text: string; isHeresy: boolean };

export interface QuickVerseDataProvider
extends DataProvider<string, string | undefined, QuickVerseSetData> {
extends IDataProvider<string, string | undefined, QuickVerseSetData> {
setHeresy(verseRef: string, verseText: string): Promise<boolean>;
}
1 change: 0 additions & 1 deletion lib/papi-dts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
papi.d.ts
papi.tsbuildinfo
2,160 changes: 2,160 additions & 0 deletions lib/papi-dts/papi.d.ts

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/renderer/hooks/papi-hooks/use-data-provider.hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dataProviderService from '@shared/services/data-provider.service';
import { DataProvider } from '@shared/models/data-provider.model';
import IDataProvider from '@shared/models/data-provider.interface';
import { useCallback, useMemo, useState } from 'react';
import useEvent from '@renderer/hooks/papi-hooks/use-event.hook';
import usePromise from '@renderer/hooks/papi-hooks/use-promise.hook';
Expand All @@ -18,7 +18,7 @@ import { isString } from '@shared/utils/util';
// User of this hook must provide types. Cannot use `unknown` here unfortunately because
// TypeScript would think we want the implementing IDataProvider types to be unknown as well
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function useDataProvider<T extends DataProvider<any, any, any>>(
function useDataProvider<T extends IDataProvider<any, any, any>>(
providerName: string | undefined,
): T | undefined;
/**
Expand All @@ -32,7 +32,7 @@ function useDataProvider<T extends DataProvider<any, any, any>>(
* specifying your own types, or provide a custom data provider type
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function useDataProvider<T extends DataProvider<any, any, any>>(
function useDataProvider<T extends IDataProvider<any, any, any>>(
dataProvider: T | undefined,
): T | undefined;
/**
Expand All @@ -47,11 +47,11 @@ function useDataProvider<T extends DataProvider<any, any, any>>(
* specifying your own types, or provide a custom data provider type
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function useDataProvider<T extends DataProvider<any, any, any>>(
function useDataProvider<T extends IDataProvider<any, any, any>>(
dataProviderSource: string | T | undefined,
): T | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function useDataProvider<T extends DataProvider<any, any, any>>(
function useDataProvider<T extends IDataProvider<any, any, any>>(
dataProviderSource: string | T | undefined,
): T | undefined {
// Check to see if they passed in the results of a useDataProvider hook or undefined
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/hooks/papi-hooks/use-data.hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataProviderSubscriberOptions } from '@shared/models/data-provider.interface';
import { DataProvider } from '@shared/models/data-provider.model';
import { DataProviderSubscriberOptions } from '@shared/models/data-provider.model';
import IDataProvider from '@shared/models/data-provider.interface';
import useEventAsync from '@renderer/hooks/papi-hooks/use-event-async.hook';
import { useMemo, useState } from 'react';
import { PapiEventAsync, PapiEventHandler } from '@shared/models/papi-event.model';
Expand Down Expand Up @@ -44,7 +44,7 @@ function useData<TSelector, TGetData, TSetData>(
* - `isLoading`: whether the data with the selector is awaiting retrieval from the data provider
*/
function useData<TSelector, TGetData, TSetData>(
dataProvider: DataProvider<TSelector, TGetData, TSetData> | undefined,
dataProvider: IDataProvider<TSelector, TGetData, TSetData> | undefined,
selector: TSelector,
defaultValue: TGetData,
subscriberOptions?: DataProviderSubscriberOptions,
Expand All @@ -54,7 +54,7 @@ function useData<TSelector, TGetData, TSetData>(
* want to consolidate and only get the data provider once)
*/
function useData<TSelector, TGetData, TSetData>(
dataProviderSource: string | DataProvider<TSelector, TGetData, TSetData> | undefined,
dataProviderSource: string | IDataProvider<TSelector, TGetData, TSetData> | undefined,
selector: TSelector,
defaultValue: TGetData,
subscriberOptions?: DataProviderSubscriberOptions,
Expand All @@ -64,7 +64,7 @@ function useData<TSelector, TGetData, TSetData>(

// Get the data provider for this data provider name
const dataProvider =
useDataProvider<DataProvider<TSelector, TGetData, TSetData>>(dataProviderSource);
useDataProvider<IDataProvider<TSelector, TGetData, TSetData>>(dataProviderSource);

// Indicates if the data with the selector is awaiting retrieval from the data provider
const [isLoading, setIsLoading] = useState<boolean>(true);
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/testing/test-buttons-panel.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import useEvent from '@renderer/hooks/papi-hooks/use-event.hook';
import { AddWebViewEvent } from '@shared/services/web-view.service';
import useData from '@renderer/hooks/papi-hooks/use-data.hook';
import useDataProvider from '@renderer/hooks/papi-hooks/use-data-provider.hook';
import { DataProvider } from '@shared/models/data-provider.model';
import IDataProvider from '@shared/models/data-provider.interface';

const testBase: (message: string) => Promise<string> =
networkService.createRequestFunction('electronAPI.env.test');
Expand Down Expand Up @@ -165,7 +165,7 @@ function TestButtonsPanel() {
// Test a method on a data provider engine that isn't on the interface to see if you can actually do this
const [hasTestedRandomMethod, setHasTestedRandomMethod] = useState(false);
const greetingsDataProvider =
useDataProvider<DataProvider<string, string, string>>('hello-someone.greetings');
useDataProvider<IDataProvider<string, string, string>>('hello-someone.greetings');
if (!hasTestedRandomMethod && greetingsDataProvider) {
setHasTestedRandomMethod(true);
(async () => {
Expand Down
6 changes: 4 additions & 2 deletions src/shared/models/data-provider-engine.model.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { NetworkableObject } from './network-object.model';

/**
* The object to register with the DataProviderService to create a data provider.
* The DataProviderService creates a IDataProvider on the papi that layers over this engine, providing special functionality
* The DataProviderService creates an IDataProvider on the papi that layers over this engine, providing special functionality
*
* Note: methods on objects that implement this interface must be unbound functions, not arrow functions.
* @type `TSelector` - the type of selector used to get some data from this provider.
* A selector is an object a caller provides to the data provider to tell the provider what subset of data it wants.
* @type `TGetData` - the type of data provided by this data provider when you run `get` based on a provided selector
* @type `TSetData` - the type of data ingested by this data provider when you run `set` based on a provided selector
*/
interface IDataProviderEngine<TSelector, TGetData, TSetData> {
interface IDataProviderEngine<TSelector, TGetData, TSetData> extends NetworkableObject {
/**
* Method to run to send clients updates outside of the `set` method.
* papi overwrites this function on the DataProviderEngine itself to emit an update after running the defined `notifyUpdate` method in the DataProviderEngine.
Expand Down
99 changes: 21 additions & 78 deletions src/shared/models/data-provider.interface.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,33 @@
import { UnsubscriberAsync } from '@shared/utils/papi-util';
import { PapiEventHandler } from '@shared/models/papi-event.model';

/** Various options to adjust how the data provider subscriber emits updates */
export type DataProviderSubscriberOptions = {
/**
* Whether to immediately retrieve the data for this subscriber and run the callback as soon as possible.
*
* This allows a subscriber to simply subscribe and provide a callback instead of subscribing, running `get`,
* and managing the race condition of an event coming in to update the data and the initial `get` coming back in.
* @default true
*/
retrieveDataImmediately?: boolean;
/**
* Under which conditions to run the callback when we receive updates to the data.
* - `'deeply-equal'` - only run the update callback when the data at this selector has changed.
*
* For example, suppose your selector is targeting John 3:5, and the data provider updates its data for Luke 5:3. Your data
* at John 3:5 does not change, and your callback will not run.
* - `'all'` - run the update callback every time the data has been updated whether or not the data
* at this selector has changed.
*
* For example, suppose your selector is targeting John 3:5, and the data provider updates its data for Luke 5:3. Your data
* at John 3:5 does not change, but your callback will run again with the same data anyway.
*
* @default 'deeply-equal'
*/
whichUpdates?: 'deeply-equal' | 'all';
};

/**
* Subscribe to receive updates from this data provider that are relevant to the provided selector.
*
* Note: By default, this `subscribe` function automatically retrieves the current state of the data
* and runs the provided callback as soon as possible. That way, if you want to keep your data up-to-date,
* you do not also have to run `get`. You can turn this functionality off in the `options` parameter.
* @param selector tells the provider what data this listener is listening for
* @param callback function to run with the updated data for this selector
* @param options various options to adjust how the subscriber emits updates
* @returns unsubscriber to stop listening for updates
*/
export type DataProviderSubscriber<TSelector, TGetData> = (
selector: TSelector,
callback: PapiEventHandler<TGetData>,
options?: DataProviderSubscriberOptions,
) => Promise<UnsubscriberAsync>;
import DataProviderInternal from '@shared/models/data-provider.model';
import { DisposableNetworkObject, NetworkObject, NetworkableObject } from './network-object.model';
import { CanHaveOnDidDispose } from './disposal.model';

/**
* An object on the papi that manages data and has methods for interacting with that data.
* Created by the papi and layers over an IDataProviderEngine provided by an extension.
* Returned from getting a data provider with dataProviderService.get.
* @type `TSelector` - the type of selector used to get some data from this provider.
* A selector is an object a caller provides to the data provider to tell the provider what subset of data it wants.
* Note: A selector must be stringifiable.
* @type `TGetData` - the type of data provided by this data provider when you run `get` based on a provided selector
* @type `TSetData` - the type of data ingested by this data provider when you run `set` based on a provided selector
*/
interface IDataProvider<TSelector, TGetData, TSetData> {
/**
* Set a subset of data according to the selector.
*
* Note: if a data provider engine does not provide `set` (possibly indicating it is read-only), this will throw an exception.
* @param selector tells the provider what subset of data is being set
* @param data the data that determines what to set at the selector
* @returns true if successfully set (will send updates), false otherwise (will not send updates)
*/
set: (selector: TSelector, data: TSetData) => Promise<boolean>;
/**
* Get a subset of data from the provider according to the selector.
*
* Note: This is good for retrieving data from a provider once. If you want to keep the data up-to-date,
* use `subscribe` instead, which can immediately give you the data and keep it up-to-date.
* @param selector tells the provider what subset of data to get
* @returns the subset of data represented by the selector
*/
get: (selector: TSelector) => Promise<TGetData>;
/**
* Subscribe to receive updates from this data provider that are relevant to the provided selector.
*
* Note: By default, this `subscribe` function automatically retrieves the current state of the data
* and runs the provided callback as soon as possible. That way, if you want to keep your data up-to-date,
* you do not also have to run `get`. You can turn this functionality off in the `options` parameter.
* @param selector tells the provider what data this listener is listening for
* @param callback function to run with the updated data for this selector
* @param options various options to adjust how the subscriber emits updates
* @returns unsubscriber to stop listening for updates
*/
subscribe: DataProviderSubscriber<TSelector, TGetData>;
}
// Basically a layer over NetworkObject
interface IDataProvider<TSelector, TGetData, TSetData>
extends NetworkObject<NetworkableObject>,
CanHaveOnDidDispose<DataProviderInternal<TSelector, TGetData, TSetData>> {}

export default IDataProvider;

/**
* A data provider that has control over disposing of it with dispose.
* Returned from registering a data provider (only the service that set it up should dispose of it)
* with dataProviderService.registerEngine
*
* @see IDataProvider
*/
// Basically a layer over DisposableNetworkObject
export interface IDisposableDataProvider<TSelector, TGetData, TSetData>
extends DisposableNetworkObject<NetworkableObject>,
// Need to omit dispose here because it is optional on IDataProvider but is required on DisposableNetworkObject
Omit<IDataProvider<TSelector, TGetData, TSetData>, 'dispose'> {}
Loading

0 comments on commit c70c99b

Please sign in to comment.