Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusted Network Object and Data Provider Types #163

Merged
merged 6 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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