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

Initial implementation #3

Merged
merged 4 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# --------------------------------------------------------------------------------------------------
# EditorConfig configuration. Overrides IDEs defaults.
# See: https://editorconfig.org
# VS Code: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig

root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 120
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store

# Dependencies
node_modules/

# Logs and temp files
npm-debug.log
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "vuex-toolkit",
"version": "0.0.1",
"description": "Vuex Toolkit. An alternative API for Vuex.",
"main": "src/index.ts",
"dependencies": {
"@redux-saga/types": "^1.1.0",
"lodash": "^4.17.15"
},
"peerDependencies": {
"vuex": "3.x.x",
"redux-saga": "1.x.x"
},
"devDependencies": {
"@types/lodash": "^4.14.151",
"vuex": "3.x.x"
},
"repository": "[email protected]:banderror/vuex-toolkit.git",
"author": "Georgii (Egor) Gorbachev <[email protected]>",
"license": "MIT"
}
102 changes: 102 additions & 0 deletions src/base-types/core-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { Store, ActionContext } from 'vuex';
import { Map, IfVoid } from './helper-types';

// ---------------------------------------------------------------------------------------------------------------------
// Message objects: mutations, actions, and "abstract" messages that bypass the store.

// Any message must have a type - its unique identifier within a Vuex store.
// Some messages can carry a payload, which should be an object.

export type Payload = Map;
export type MaybePayload = Payload | void;

export interface BareMessage {
type: string;
}

export interface PayloadMessage<P extends Payload> extends BareMessage {
type: string;
payload: P;
}

export type Message<P = void> = IfVoid<P, BareMessage, PayloadMessage<P>>;

// Messages can be routed in different ways.
// The two routes in original Vuex are mutations and actions:
// - if you commit() a mutation, it goes through the store and is picked by the corresponding handler
// - if you dispatch() an action, it goes through the store as well
// Besides defining mutations and actions, we allow to define abstract messages
// that can bypass the store to be handled by other mechanisms: sagas, RxJS observables, etc.

export const ROUTE_MUTATION = 'mutation';
export const ROUTE_ACTION = 'action';
export const ROUTE_BYPASS = 'bypass';
export const ROUTES = [ROUTE_MUTATION, ROUTE_ACTION, ROUTE_BYPASS] as const;
export type MessageRoute = (typeof ROUTES)[number];

export type MutationObject<P extends MaybePayload = void> = Message<P> & {
route: typeof ROUTE_MUTATION;
};

export type ActionObject<P extends MaybePayload = void> = Message<P> & {
route: typeof ROUTE_ACTION;
};

export type BypassMessageObject<P extends MaybePayload = void> = Message<P> & {
route: typeof ROUTE_BYPASS;
};

export type MessageObject<P extends MaybePayload = void> =
| MutationObject<P>
| ActionObject<P>
| BypassMessageObject<P>;

// ---------------------------------------------------------------------------------------------------------------------
// Message creators

export interface MutationCreator<P extends MaybePayload = void, A = P> {
(arg: A): MutationObject<P>;
}

export interface ActionCreator<P extends MaybePayload = void, A = P> {
(arg: A): ActionObject<P>;
}

export interface BypassMessageCreator<P extends MaybePayload = void, A = P> {
(arg: A): BypassMessageObject<P>;
}

export interface MessageCreator<P extends MaybePayload = void, A = P> {
(arg: A): MessageObject<P>;
}

// ---------------------------------------------------------------------------------------------------------------------
// Message handlers

export type MutationHandler<S, P extends MaybePayload = void> =
(state: S, payload: P) => void;

export type ActionHandler<S, R, P extends MaybePayload = void> =
(this: Store<R>, context: ActionContext<S, R>, payload: P) => any;

// ---------------------------------------------------------------------------------------------------------------------
// Getters (Vuex native API) and selectors (Redux native API)

export type Getter<S, R, V> =
(state: S, getters: any, rootState: R, rootGetters: any) => V;
export type Getter1<S, R, V, A1> =
(state: S, getters: any, rootState: R, rootGetters: any) => (arg: A1) => V;
export type Getter2<S, R, V, A1, A2> =
(state: S, getters: any, rootState: R, rootGetters: any) => (arg1: A1, arg2: A2) => V;
export type Getter3<S, R, V, A1, A2, A3> =
(state: S, getters: any, rootState: R, rootGetters: any) => (arg1: A1, arg2: A2, arg3: A3) => V;
export type GetterN<S, R, V> =
(state: S, getters: any, rootState: R, rootGetters: any) => (...args: any[]) => V;

export type Selector<S, V> = (state: S) => V;
export type Selector1<S, V, A1> = (arg: A1) => Selector<S, V>;
export type Selector2<S, V, A1, A2> = (arg1: A1, arg2: A2) => Selector<S, V>;
export type Selector3<S, V, A1, A2, A3> = (arg1: A1, arg2: A2, arg3: A3) => Selector<S, V>;
export type SelectorN<S, V> = (...args: any[]) => Selector<S, V>;
131 changes: 131 additions & 0 deletions src/base-types/getter-definition-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
Getter,
Getter1,
Getter2,
Getter3,
Selector,
Selector1,
Selector2,
Selector3,
} from './core-types';

// ---------------------------------------------------------------------------------------------------------------------
// Fluent API (builders)

export interface GetterNameDefinition<S, R> extends GetterDefinitionBuilder0<S, R> {
params(): GetterNameDefinition<S, R>;
param<A1>(): GetterDefinitionBuilder1<S, R, A1>;
one<A1>(): GetterDefinitionBuilder1<S, R, A1>;
two<A1, A2>(): GetterDefinitionBuilder2<S, R, A1, A2>;
three<A1, A2, A3>(): GetterDefinitionBuilder3<S, R, A1, A2, A3>;
}

export interface GetterDefinitionBuilder0<S, R> {
<V>(selector: Selector<S, V>): SelectorDefinition0<S, R, V>;
selector<V>(selector: Selector<S, V>): SelectorDefinition0<S, R, V>;
getter<V>(getter: Getter<S, R, V>): GetterDefinition0<S, R, V>;
}

export interface GetterDefinitionBuilder1<S, R, A1> {
selector<V>(selector: Selector1<S, V, A1>): SelectorDefinition1<S, R, V, A1>;
getter<V>(getter: Getter1<S, R, V, A1>): GetterDefinition1<S, R, V, A1>;
}

export interface GetterDefinitionBuilder2<S, R, A1, A2> {
selector<V>(selector: Selector2<S, V, A1, A2>): SelectorDefinition2<S, R, V, A1, A2>;
getter<V>(getter: Getter2<S, R, V, A1, A2>): GetterDefinition2<S, R, V, A1, A2>;
}

export interface GetterDefinitionBuilder3<S, R, A1, A2, A3> {
selector<V>(selector: Selector3<S, V, A1, A2, A3>): SelectorDefinition3<S, R, V, A1, A2, A3>;
getter<V>(getter: Getter3<S, R, V, A1, A2, A3>): GetterDefinition3<S, R, V, A1, A2, A3>;
}

// ---------------------------------------------------------------------------------------------------------------------
// Final definitions (builders' output)

export interface BaseGetterDefinition {
getterFullName: string;
getterShortName: string;
toString(): string;
}

// Selector definitions

export interface SelectorDefinitionAny extends BaseGetterDefinition {
type: 'selector-definition';
}

export type SelectorDefinition0<S, R, V> = SelectorDefinitionAny & Selector<R, V> & {
params: 0;
selector: Selector<S, V>;
getter: Getter<S, R, V>;
};

export type SelectorDefinition1<S, R, V, A1> = SelectorDefinitionAny & Selector1<R, V, A1> & {
params: 1;
selector: Selector1<S, V, A1>;
getter: Getter1<S, R, V, A1>;
};

export type SelectorDefinition2<S, R, V, A1, A2> = SelectorDefinitionAny & Selector2<R, V, A1, A2> & {
params: 2;
selector: Selector2<S, V, A1, A2>;
getter: Getter2<S, R, V, A1, A2>;
};

export type SelectorDefinition3<S, R, V, A1, A2, A3> = SelectorDefinitionAny & Selector3<R, V, A1, A2, A3> & {
params: 3;
selector: Selector3<S, V, A1, A2, A3>;
getter: Getter3<S, R, V, A1, A2, A3>;
};

// Getter definitions

export interface GetterDefinitionAny extends BaseGetterDefinition {
type: 'getter-definition';
}

export type GetterDefinition0<S, R, V> = GetterDefinitionAny & {
params: 0;
getter: Getter<S, R, V>;
};

export type GetterDefinition1<S, R, V, A1> = GetterDefinitionAny & {
params: 1;
getter: Getter1<S, R, V, A1>;
};

export type GetterDefinition2<S, R, V, A1, A2> = GetterDefinitionAny & {
params: 2;
getter: Getter2<S, R, V, A1, A2>;
};

export type GetterDefinition3<S, R, V, A1, A2, A3> = GetterDefinitionAny & {
params: 3;
getter: Getter3<S, R, V, A1, A2, A3>;
};

// Union type

export type GetterDefinition<S, R, V, A1 = void, A2 = void, A3 = void> =
| SelectorDefinition0<S, R, V>
| SelectorDefinition1<S, R, V, A1>
| SelectorDefinition2<S, R, V, A1, A2>
| SelectorDefinition3<S, R, V, A1, A2, A3>
| GetterDefinition0<S, R, V>
| GetterDefinition1<S, R, V, A1>
| GetterDefinition2<S, R, V, A1, A2>
| GetterDefinition3<S, R, V, A1, A2, A3>;

// ---------------------------------------------------------------------------------------------------------------------
// Type guards

export function isGetterDefinition(x): x is GetterDefinition<unknown, unknown, unknown, unknown, unknown, unknown> {
return typeof x.type === 'string'
&& typeof x.params === 'number'
&& typeof x.getter === 'function'
&& typeof x.getterFullName === 'string'
&& typeof x.getterShortName === 'string'
&& typeof x.toString === 'function';
}
5 changes: 5 additions & 0 deletions src/base-types/helper-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

export type Map<T = any> = Record<string, T>;

export type IfVoid<P, True, False> = [void] extends [P] ? True : False;
4 changes: 4 additions & 0 deletions src/base-types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './core-types';
export * from './module-definition-types';
export * from './message-definition-types';
export * from './getter-definition-types';
56 changes: 56 additions & 0 deletions src/base-types/message-definition-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
MaybePayload,
MessageCreator,
ROUTE_MUTATION,
ROUTE_ACTION,
ROUTE_BYPASS,
} from './core-types';

// ---------------------------------------------------------------------------------------------------------------------
// Types

export interface BaseMessageDefinition<P extends MaybePayload = void> extends MessageCreator<P> {
type: string;
messageFullType: string;
messageShortType: string;
toString(): string;
}

export type MutationDefinition<P extends MaybePayload = void> = BaseMessageDefinition<P> & {
messageRoute: typeof ROUTE_MUTATION;
};

export type ActionDefinition<P extends MaybePayload = void> = BaseMessageDefinition<P> & {
messageRoute: typeof ROUTE_ACTION;
};

export type BypassMessageDefinition<P extends MaybePayload = void> = BaseMessageDefinition<P> & {
messageRoute: typeof ROUTE_BYPASS;
};

export type MessageDefinition<P extends MaybePayload = void> =
| MutationDefinition<P>
| ActionDefinition<P>
| BypassMessageDefinition<P>;

// ---------------------------------------------------------------------------------------------------------------------
// Type guards

export function isMessageDefinition<P = unknown>(x): x is MessageDefinition<P> {
return typeof x.type === 'string'
&& typeof x.messageFullType === 'string'
&& typeof x.messageShortType === 'string'
&& typeof x.toString === 'function';
}

export function isMutationDefinition<P = unknown>(x): x is MutationDefinition<P> {
return x.messageRoute === ROUTE_MUTATION && isMessageDefinition<P>(x);
}

export function isActionDefinition<P = unknown>(x): x is ActionDefinition<P> {
return x.messageRoute === ROUTE_ACTION && isMessageDefinition<P>(x);
}

export function isBypassMessageDefinition<P = unknown>(x): x is BypassMessageDefinition<P> {
return x.messageRoute === ROUTE_BYPASS && isMessageDefinition<P>(x);
}
28 changes: 28 additions & 0 deletions src/base-types/module-definition-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Module } from 'vuex';
import { MutationHandler, ActionHandler, MaybePayload } from './core-types';
import { MutationDefinition, ActionDefinition, BypassMessageDefinition } from './message-definition-types';
import { GetterNameDefinition } from './getter-definition-types';

export interface ModuleDefinition<S, R = unknown> {
name: string;
define: MessageDefinitionApi<S, R>;
handle: HandlerDefinitionApi<S, R>;
get: GetterDefinitionApi<S, R>;

toNativeVuexModule(): Module<S, R>;
}

export interface MessageDefinitionApi<S, R> {
mutation<P extends MaybePayload = void>(type: string): MutationDefinition<P>;
action<P extends MaybePayload = void>(type: string): ActionDefinition<P>;
message<P extends MaybePayload = void>(type: string): BypassMessageDefinition<P>;
}

export interface HandlerDefinitionApi<S, R> {
mutation<P>(mutation: MutationDefinition<P>, handler: MutationHandler<S, P>): void;
action<P>(action: ActionDefinition<P>, handler: ActionHandler<S, R, P>): void;
}

export interface GetterDefinitionApi<S, R> {
(getterName: string): GetterNameDefinition<S, R>;
}
Loading