import AsyncData from './AsyncData';
export default class RefreshableData<T extends AsyncData<any, any>> {
constructor(
/**
* Current-most AsyncData value.
*/
public current: T = AsyncData.NotAsked() as T,
/**
* Most recent settled AsyncData value,
* where "settled" here means "AsyncData.Error or AsyncData.Data".
*/
public lastSettled: T = current
) {}
/**
* Create a new RefreshableData instance with the next value,
* only updating the lastSettled value if the next value
* is a settled case.
* @param next Next AsyncData instance.
*/
public next(next: T): RefreshableData<T> {
return new RefreshableData(
next,
next.cata({
NotAsked: () => this.lastSettled,
Waiting: () => (
this.lastSettled.tag === 'NotAsked'
? next
: this.lastSettled
),
Error: () => next,
Data: () => next,
})
);
}
}
import AsyncData from './AsyncData';
export default class RefreshableData<T extends AsyncData<any, any>> {
constructor(
/**
* Current-most AsyncData value.
*/
public current: T = AsyncData.NotAsked() as T,
/**
* Most recent settled AsyncData value,
* where "settled" here means "AsyncData.Error or AsyncData.Data".
*/
public lastSettled: T = current
) {}
/**
* Create a new RefreshableData instance with the next value,
* only updating the lastSettled value if the next value
* is a settled case.
* @param next Next AsyncData instance.
*/
public next(next: T): RefreshableData<T> {
return new RefreshableData(
next,
next.cata({
NotAsked: () => this.lastSettled,
Waiting: () => (
this.lastSettled.tag === 'NotAsked'
? next
: this.lastSettled
),
Error: () => next,
Data: () => next,
})
);
}
}
import TaggedSum, { TaggedSumTagNames, TaggedSumSpecializedTo } from './TaggedSum';
/**
* A TaggedSum type that represents the possible states of data that is
* obtained through some asynchronous process, most frequently a fetch
* of some sort, but not necessarily.
*
* Does not encode any notion of Progress, as that is usually considered
* a separate concern, not necessarily directly related to the data itself,
* although this could be optionally added to the Waiting state.
*/
export default class AsyncData<D, E> extends TaggedSum<'AsyncData',
| ['NotAsked']
| ['Waiting']
| ['Error', E]
| ['Data', D]
> {
/**
* Create a new AsyncData instance of the NotAsked tag.
*/
public static NotAsked = <D, E>() => new AsyncData<D, E>('NotAsked');
/**
* Create a new AsyncData instance of the Waiting tag.
*/
public static Waiting = <D, E>() => new AsyncData<D, E>('Waiting');
/**
* Create a new AsyncData instance of the Error tag.
*/
public static Error = <D, E>(error: E) => new AsyncData<D, E>('Error', error);
/**
* Create a new AsyncData instance of the Data tag.
*/
public static Data = <D, E>(data: D) => new AsyncData<D, E>('Data', data);
/**
* Check if a value is an instance of a TaggedSum without regard for any Tag.
* Note that since the value may be anything, we cannot derive type parametrization
* from it because that information was lost. We can only determine the Sum and Tag.
* @param inst The value that may or may not be an instance of a given tagged sum.
*/
public static is(inst: unknown): inst is AsyncData<any, any> {
return inst != null && inst instanceof AsyncData;
}
/**
* Checks if a value is an instance of a TaggedSum with a particular Tag.
* Note that since the value may be anything, we cannot derive type parametrization
* from it because that information was lost. We can only determine the Sum and Tag.
* @param tagName The tag name you want to check against.
* @param inst The item that may or may not be an instance of a given tagged sum.
*/
public static isTag<
TTagName extends TaggedSumTagNames<AsyncData<any, any>>
>(tagName: TTagName, inst: unknown): inst is TaggedSumSpecializedTo<AsyncData<any, any>, TTagName> {
return AsyncData.is(inst) && inst.type[0] === tagName;
}
/**
* Coalesces a tuple of AsyncData instances down to a single AsyncData instance
* whose Data type param is a tuple of all the inputs' Data type params, and whose
* Error type param is a Union of all the inputs' Error type params.
*
* NOTE: Unlike with `Promise.all`, you do NOT pass an array to this function.
*
* Example:
*
* let foo: AsyncData<number, Error>;
* let bar: AsyncData<string, Error>;
* let baz: AsyncData<{ baz: string }, { error: Error }>;
*
* // Type: AsyncData<[number, string, { baz: string; }], Error | { error: Error; }>
* const allThemData = AsyncData.all(foo, bar, baz);
*
* Literally just reduces over the tuple using the coalesce method.
* @param allInsts A tuple of AsyncData instances
*/
public static all<TAllInsts extends Array<AsyncData<any, any>>>(...allInsts: TAllInsts): AsyncDataAllReturnType<TAllInsts> {
return allInsts.reduce(
(acc, next) => acc.coalesce(next, (accData: any[], nextData: any) => {
accData.push(nextData);
return accData;
}),
AsyncData.Data([])
);
}
protected sum = 'AsyncData' as 'AsyncData';
public map<DB>(fn: (data: D) => DB): AsyncData<DB, E> {
return this.cata({
// In these cases, we know that "this" is compatible with AsyncData<DB, E>,
// but TS has no way of knowing that in any given handler.
// So, coerce, coerce, coerce. Woo.
NotAsked: () => this as unknown as AsyncData<DB, E>,
Waiting: () => this as unknown as AsyncData<DB, E>,
Error: () => this as unknown as AsyncData<DB, E>,
// TS auto-infers AsyncData<DB, DB> for some reason, so being explicit.
Data: (data: D) => AsyncData.Data<DB, E>(fn(data)),
});
}
public mapError<EB>(fn: (error: E) => EB): AsyncData<D, EB> {
return this.cata({
NotAsked: () => this as unknown as AsyncData<D, EB>,
Waiting: () => this as unknown as AsyncData<D, EB>,
Error: (error: E) => AsyncData.Error<D, EB>(fn(error)),
Data: () => this as unknown as AsyncData<D, EB>,
});
}
public flatten<DI, EI>(this: AsyncData<AsyncData<DI, EI>, E>): AsyncData<DI, E | EI> {
return this.cata({
NotAsked: () => this as unknown as AsyncData<DI, E>,
Waiting: () => this as unknown as AsyncData<DI, E>,
Error: () => this as unknown as AsyncData<DI, E>,
Data: (data: AsyncData<DI, EI>) => data,
});
}
public flatMap<DB, EB>(fn: (a: D) => AsyncData<DB, EB>): AsyncData<DB, E | EB> {
return this.map(fn).flatten();
}
/**
* Performs an opinionated merge of two AsyncData values with a bias towards
* what would make the most sense in most cases for rendering to the UI:
* - That Errors take precedence over everything (like Promise.all()).
* - That Waiting takes precedence next.
* - That NotAsked takes precedence next.
* - And that Result is only the case if both inputs are Result.
* @param other Another AsyncData instance.
* @param merge How to merge the data values of the two AsyncData instances, if they're both AsyncData.Data.
*/
public coalesce<Do, Eo, Dm>(other: AsyncData<Do, Eo>, merge: (thisData: D, otherData: Do) => Dm): AsyncData<Dm, E | Eo> {
if (AsyncData.isTag('Error', this)) {
return this as unknown as AsyncData<Dm, E>;
}
if (AsyncData.isTag('Error', other)) {
return other as unknown as AsyncData<Dm, Eo>;
}
if (AsyncData.isTag('Waiting', this)) {
return this as unknown as AsyncData<Dm, E>;
}
if (AsyncData.isTag('Waiting', other)) {
return other as unknown as AsyncData<Dm, Eo>;
}
if (AsyncData.isTag('NotAsked', this)) {
return this as unknown as AsyncData<Dm, E>;
}
if (AsyncData.isTag('NotAsked', other)) {
return other as unknown as AsyncData<Dm, Eo>;
}
// now both should be 'Data'.
return this.flatMap(thisData => other.map(otherData => merge(thisData, otherData)))
}
}
/**
* Return type of `AsyncData.all()`.
*/
type AsyncDataAllReturnType<TAllInsts extends Array<AsyncData<any, any>>> =
AsyncData<
{ [I in keyof TAllInsts]: TAllInsts[I] extends AsyncData<infer R, any> ? R : never; },
TAllInsts extends Array<AsyncData<any, infer TError>> ? TError : never
>;
// .NET core is built from scratch
// Main method is the entry point to the application
// Startup class is the enhanced way to configure your pipeline as well as dependency injection
// The project file is now "csproj" file (project.json/xproj is replaced with csproj file in .NET core 2, project.json was not compatible with MS build (DevOps platform))
// wwwroot folder for static files
// ConfigureServices method in Startup class configures any dependency injection (like providers in Angular)
// NuGet is used for managing packages.
// NPM can also be used for client side packages ..
// MVC had a dependency on System.Web (which is tied to IIS and Windows)
// Microsoft didn't want to tie Web API with System.Web
// in .NET Core, the combined functinality of MVC and Web API is now called .NET Core MVC which is independent of System.Web
// Tag helpers - angular like directives to add logic in html
// View components - partial views with mini controllers (partials alone don't have controllers)