Repository: dphilipson/typescript-fsa-reducers
Branch: master
Commit: a8086ec0667c
Files: 13
Total size: 33.5 KB
Directory structure:
gitextract_07iw849q/
├── .gitignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── __tests__/
│ └── test.ts
├── jest.config.js
├── package.json
├── scripts/
│ └── markdown-toc-all.sh
├── src/
│ └── index.ts
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/visualstudiocode
### VisualStudioCode ###
.vscode/
dist/
node_modules/
npm-debug.log
================================================
FILE: .prettierrc
================================================
{
"tabWidth": 4,
"trailingComma": "all"
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "node"
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 David Philipson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# TypeScript FSA Reducers
Fluent syntax for defining typesafe Redux reducers on top of
[typescript-fsa](https://github.com/aikoven/typescript-fsa).
[](https://travis-ci.org/dphilipson/typescript-fsa-reducers)
## Introduction
This library will allow you to write typesafe reducers that look like this:
```ts
const reducer = reducerWithInitialState(INITIAL_STATE)
.case(setName, setNameHandler)
.case(addBalance, addBalanceHandler)
.case(setIsFrozen, setIsFrozenHandler);
```
It removes the boilerplate normally associated with writing reducers, including
if-else chains, the default case, and the need to pull the payload field off of
the action.
## Table of Contents
<!-- toc -->
- [Usage](#usage)
- [Installation](#installation)
- [API](#api)
* [Starting a reducer chain](#starting-a-reducer-chain)
+ [`reducerWithInitialState(initialState)`](#reducerwithinitialstateinitialstate)
+ [`reducerWithoutInitialState()`](#reducerwithoutinitialstate)
+ [`upcastingReducer()`](#upcastingreducer)
* [Reducer chain methods](#reducer-chain-methods)
+ [`.case(actionCreator, handler(state, payload) => newState)`](#caseactioncreator-handlerstate-payload--newstate)
+ [`.caseWithAction(actionCreator, handler(state, action) => newState)`](#casewithactionactioncreator-handlerstate-action--newstate)
+ [`.cases(actionCreators, handler(state, payload) => newState)`](#casesactioncreators-handlerstate-payload--newstate)
+ [`.casesWithAction(actionCreators, handler(state, action) => newState)`](#caseswithactionactioncreators-handlerstate-action--newstate)
+ [`.withHandling(updateBuilder(builder) => builder)`](#withhandlingupdatebuilderbuilder--builder)
+ [`.default(handler(state, action) => newState)`](#defaulthandlerstate-action--newstate)
+ [`.build()`](#build)
<!-- tocstop -->
## Usage
This library allows you to define reducers by chaining a series of handlers for
different action types and optionally providing an initial value. It builds on
top of and assumes familiarity with the excellent
[typescript-fsa](https://github.com/aikoven/typescript-fsa).
Suppose we have used [typescript-fsa](https://github.com/aikoven/typescript-fsa)
to define our state and some actions:
```ts
import actionCreatorFactory from "typescript-fsa";
const actionCreator = actionCreatorFactory();
interface State {
name: string;
balance: number;
isFrozen: boolean;
}
const INITIAL_STATE: State = {
name: "Untitled",
balance: 0,
isFrozen: false,
};
const setName = actionCreator<string>("SET_NAME");
const addBalance = actionCreator<number>("ADD_BALANCE");
const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");
```
Using vanilla `typescript-fsa`, we might define a reducer as follows:
```ts
import { Action } from "redux";
import { isType } from "typescript-fsa";
function reducer(state = INITIAL_STATE, action: Action): State {
if (isType(action, setName)) {
return { ...state, name: action.payload };
} else if (isType(action, addBalance)) {
return {
...state,
balance: state.balance + action.payload,
};
} else if (isType(action, setIsFrozen)) {
return { ...state, isFrozen: action.payload };
} else {
return state;
}
}
```
Using this library, the above is exactly equivalent to the following code:
```ts
import { reducerWithInitialState } from "typescript-fsa-reducers";
const reducer = reducerWithInitialState(INITIAL_STATE)
.case(setName, (state, name) => ({ ...state, name }))
.case(addBalance, (state, amount) => ({
...state,
balance: state.balance + amount,
}))
.case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));
```
Note that unlike the vanilla case, there is no need to pull the payload off of
the action, as it is passed directly to the handler, nor is it necessary to
specify a default case which returns `state` unmodified.
Everything is typesafe. If the types of the action payload and handler don't
line up, then TypeScript will complain. If you find it easier to read, you can
of course pull out the handlers into separate functions, as shown in the
[Introduction](#introduction).
If the full action is needed rather than just the payload, `.caseWithAction()`
may be used in place of `.case()`. This may be useful if you intend to pass the
action unchanged to a different reducer, or if you need to read the `meta` field
of the action. For example:
```ts
import { Action } from "typescript-fsa";
const setText = actionCreator<string>("SET_TEXT");
const reducer = reducerWithInitialState({
text: "",
lastEditBy: "",
}).caseWithAction(incrementCount, (state, { payload, meta }) => ({
text: payload,
lastEditBy: meta.author,
}));
// Returns { text: "hello", lastEditBy: "cbrontë" }.
reducer(undefined, setText("hello", { author: "cbrontë" }));
```
Further, a single handler may be assigned to multiple action types at once using
`.cases()` or `.casesWithAction()`:
```ts
const reducer = reducerWithInitialState(initialState).cases(
[setName, addBalance],
(state, payload) => {
// Payload has type SetNamePayload | AddBalancePayload.
// ...
// Make sure to return the updated state, or TypeScript will give you a
// rather unhelpful error message.
return state;
},
);
```
The reducer builder chains are mutable. Each call to `.case()` modifies the
callee to respond to the specified action type. If this is undesirable, see the
[`.build()`](#build) method below.
## Installation
For this library to be useful, you will also need
[typescript-fsa](https://github.com/aikoven/typescript-fsa) to define your
actions.
With Yarn:
```
yarn add typescript-fsa-reducers typescript-fsa
```
Or with NPM:
```
npm install --save typescript-fsa-reducers typescript-fsa
```
## API
### Starting a reducer chain
#### `reducerWithInitialState(initialState)`
Starts a reducer builder-chain which uses the provided initial state if passed
`undefined` as its state. For example usage, see the [Usage](#usage) section
above.
#### `reducerWithoutInitialState()`
Starts a reducer builder-chain without special logic for an initial state.
`undefined` will be treated like any other value for the state.
Redux seems to really want you to provide an initial state for your reducers.
Its `createStore` API encourages it and `combineReducers` function enforces it.
For the Redux author's reasoning behind this, see [this
thread](https://github.com/reactjs/redux/issues/514). For this reason,
`reducerWithInitialState` will likely be the more common choice, but the option
to not provide an initial state is there in case you have some means of
composing reducers for which initial state is unnecessary.
Note that since the type of the state cannot be inferred from the initial state,
it must be provided as a type parameter:
```ts
const reducer = reducerWithoutInitialState<State>()
.case(setName, setNameHandler)
.case(addBalance, addBalanceHandler)
.case(setIsFrozen, setIsFrozenHandler);
```
#### `upcastingReducer()`
Starts a builder-chain which produces a "reducer" whose return type is a
supertype of the input state. This is most useful for handling a state which may
be in one of several "modes", each of which responds differently to actions and
can transition to the other modes. Many applications will not have a use for
this.
Note that the function produced is technically not a reducer because the initial
and updated states are different types.
Example usage:
```javascript
type State = StoppedState | StartedState;
interface StoppedState {
type: "STOPPED";
}
interface StartedState {
type: "STARTED";
count: number;
}
const INITIAL_STATE: State = { type: "STOPPED" };
const startWithCount = actionCreator<number>("START_WITH_COUNT");
const addToCount = actionCreator<number>("ADD_TO_COUNT");
const stop = actionCreator<void>("STOP");
function startWithCountHandler(state: StoppedState, count: number): State {
return { type: "STARTED", count };
}
function addToCountHandler(state: StartedState, count: number): State {
return { ...state, count: state.count + count };
}
function stopHandler(state: StartedState): State {
return { type: "STOPPED" };
}
const stoppedReducer = upcastingReducer<StoppedState, State>()
.case(startWithCount, startWithCountHandler);
const startedReducer = upcastingReducer<StartedState, State>()
.case(addToCount, addToCountHandler)
.case(stop, stopHandler);
function reducer(state = INITIAL_STATE, action: Redux.Action): State {
if (state.type === "STOPPED") {
return stoppedReducer(state, action);
} else if (state.type === "STARTED") {
return startedReducer(state, action);
} else {
throw new Error("Unknown state");
}
}
```
### Reducer chain methods
#### `.case(actionCreator, handler(state, payload) => newState)`
Mutates the reducer such that it applies `handler` when passed actions matching
the type of `actionCreator`. For examples, see [Usage](#usage).
#### `.caseWithAction(actionCreator, handler(state, action) => newState)`
Like `.case()`, except that `handler` receives the entire action as its second
argument rather than just the payload. This is useful if you want to read other
properties of the action, such as `meta` or `error`, or if you want to pass the
entire action unmodified to some other function. For an example, see
[Usage](#usage).
#### `.cases(actionCreators, handler(state, payload) => newState)`
Like `.case()`, except that multiple action creators may be provided and the
same handler is applied to all of them. That is,
```javascript
reducerWithInitialState(initialState).cases(
[setName, addBalance, setIsFrozen],
handler,
);
```
is equivalent to
```javascript
reducerWithInitialState(initialState)
.case(setName, handler)
.case(addBalance, handler)
.case(setIsFrozen, handler);
```
Note that the payload passed to the handler may be of the type of any of the
listed action types' payloads. In TypeScript terms, this means it has type `P1 | P2 | ...`, where `P1, P2, ...` are the payload types of the listed action
creators.
The payload type is inferred automatically for up to four action types. After
that, it must be supplied as a type annotation, for example:
```javascript
reducerWithInitialState(initialState).cases <
{ documentId: number } >
([
selectDocument,
editDocument,
deleteDocument,
sendDocument,
archiveDocument,
],
handler);
```
#### `.casesWithAction(actionCreators, handler(state, action) => newState)`
Like `.cases()`, except that the handler receives the entire action as its
second argument rather than just the payload.
#### `.withHandling(updateBuilder(builder) => builder)`
Convenience method which applies the provided function to the current builder
and returns the result. Useful if you have a sequence of builder updates (calls
to `.case()`, etc.) which you want to reuse across several reducers.
#### `.default(handler(state, action) => newState)`
Produces a reducer which applies `handler` when no previously added `.case()`,
`.caseWithAction()`, etc. matched. The handler is similar to the one in
`.caseWithAction()`. Note that `.default()` ends the chain and internally does
the same as [`.build()`](#build), because it is not intended that the chain be
mutated after calling `.default()`.
This is useful if you have a "delegate" reducer that should be called on any
action after handling a few specific actions in the parent.
```ts
const NESTED_STATE = {
someProp: "hello",
};
const nestedReducer = reducerWithInitialState(NESTED_STATE)
.case(...);
const INITIAL_STATE = {
someOtherProp: "world"
nested: NESTED_STATE
};
const reducer = reducerWithInitialState(INITIAL_STATE)
.case(...)
.default((state, action) => ({
...state,
nested: nestedReducer(state.nested, action),
}));
```
#### `.build()`
Returns a plain reducer function whose behavior matches the current state of the
reducer chain. Further updates to the chain (through calls to `.case()`) will
have no effect on this function.
There are two reasons you may want to do this:
1. **You want to ensure that the reducer is not modified further**
Calling `.build()` is an example of defensive coding. It prevents someone
from causing confusing behavior by importing your reducer in an unrelated
file and adding cases to it.
2. **You want your package to export a reducer, but not have its types depend
on `typescript-fsa-reducers`**
If the code that defines a reducer and the code that uses it reside in
separate NPM packages, you may run into type errors since the exported
reducer has type `ReducerBuilder`, which the consuming package does not
recognize unless it also depends on `typescript-fsa-reducers`. This is
avoided by calling `.build()`, whose return type is a plain function
instead.
Example usage:
```javascript
const reducer = reducerWithInitialState(INITIAL_STATE)
.case(setName, setNameHandler)
.case(addBalance, addBalanceHandler)
.case(setIsFrozen, setIsFrozenHandler)
.build();
```
Copyright © 2017 David Philipson
================================================
FILE: __tests__/test.ts
================================================
import actionCreatorFactory from "typescript-fsa";
import {
ReducerBuilder,
reducerWithInitialState,
reducerWithoutInitialState,
upcastingReducer,
} from "../src/index";
const actionCreator = actionCreatorFactory();
interface State {
data: string;
}
interface StateWithCount extends State {
count: number;
}
const initialState: State = { data: "hello" };
const defaultHandlerResult: State = { ...initialState, data: "world" };
function defaultHandler(state: State): State {
return {
...state,
data: "world",
};
}
const sliceData = actionCreator<number>("SLICE_DATA");
function sliceDataHandler(state: State, fromIndex: number): State {
return { data: state.data.slice(fromIndex) };
}
const dataToUpperCase = actionCreator<void>("DATA_TO_UPPERCASE");
function dataToUpperCaseHandler(state: State): State {
return { data: state.data.toUpperCase() };
}
const toBasicState = actionCreator<void>("TO_BASIC_STATE");
function toBasicStateHandler(state: StateWithCount): State {
return { data: state.data };
}
describe("reducer builder", () => {
it("should return a no-op reducer if no cases provided", () => {
const reducer = reducerWithoutInitialState<State>();
expect(reducer(initialState, { type: "UNKNOWN" })).toBe(initialState);
});
it("should execute the default handler if no cases provided", () => {
const reducer = reducerWithoutInitialState<State>().default(
defaultHandler,
);
expect(reducer(initialState, { type: "UNKNOWN" })).toEqual(
defaultHandlerResult,
);
});
it("should no-op on unknown actions if cases provided", () => {
const reducer = reducerWithoutInitialState<State>()
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler);
expect(reducer(initialState, { type: "UNKNOWN" })).toBe(initialState);
});
it("should execute the default handler on unknown actions if cases provided", () => {
const reducer = reducerWithoutInitialState<State>()
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler)
.default(defaultHandler);
expect(reducer(initialState, { type: "UNKNOWN" })).toEqual(
defaultHandlerResult,
);
});
it("should return an initial value if state is undefined if no cases provided", () => {
const reducer = reducerWithInitialState(initialState);
expect(reducer(undefined, { type: "UNKNOWN" })).toBe(initialState);
});
it("should return default handler result if state is undefined if only default handler provided", () => {
const reducer = reducerWithInitialState(initialState).default(
defaultHandler,
);
expect(reducer(undefined, { type: "UNKNOWN" })).toEqual(
defaultHandlerResult,
);
});
it("should return an initial value if state is undefined if cases provided", () => {
const reducer = reducerWithInitialState(initialState)
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler);
expect(reducer(undefined, { type: "UNKNOWN" })).toBe(initialState);
});
it("should return default handler result if state is undefined if cases and default handler provided", () => {
const reducer = reducerWithInitialState(initialState)
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler)
.default(defaultHandler);
expect(reducer(undefined, { type: "UNKNOWN" })).toEqual(
defaultHandlerResult,
);
});
it("should call handler on matching action with single handler", () => {
const reducer = reducerWithoutInitialState<State>().case(
sliceData,
sliceDataHandler,
);
expect(reducer(initialState, sliceData(1))).toEqual({ data: "ello" });
});
it("should call handler on matching action with multiple handlers", () => {
const reducer = reducerWithoutInitialState<State>()
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler);
expect(reducer(initialState, dataToUpperCase)).toEqual({
data: "HELLO",
});
});
it("should call handler on matching action with multiple handlers and default handler", () => {
const reducer = reducerWithoutInitialState<State>()
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler)
.default(defaultHandler);
expect(reducer(initialState, dataToUpperCase)).toEqual({
data: "HELLO",
});
});
it("should call full-action handler when using .caseWithAction()", () => {
const reducer = reducerWithInitialState(initialState).caseWithAction(
sliceData,
(state, action) => ({
...state,
data: state.data.slice(action.payload),
meta: { author: action.meta && action.meta.author },
}),
);
expect(reducer(undefined, sliceData(1, { author: "cbrontë" }))).toEqual(
{
data: "ello",
meta: { author: "cbrontë" },
},
);
});
it("should call upcasting handler on matching action", () => {
const reducer = upcastingReducer<StateWithCount, State>().case(
toBasicState,
toBasicStateHandler,
);
expect(reducer({ data: "hello", count: 2 }, toBasicState)).toEqual({
data: "hello",
});
});
it("should be able to call nested reducer when using default handler", () => {
const nestedReducer = reducerWithoutInitialState<State>()
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler);
const reducer = reducerWithoutInitialState<{ nested: State }>().default(
(state, action) => ({
nested: nestedReducer(state.nested, action),
}),
);
expect(reducer({ nested: initialState }, dataToUpperCase)).toEqual({
nested: { data: "HELLO" },
});
});
(() => {
// Scope for shared testing values for .cases() and .casesWithAction().
interface PayloadA {
data: string;
x: number;
}
interface PayloadB {
data: string;
y: number;
}
interface PayloadC {
data: string;
z: number;
}
const actionA = actionCreator<PayloadA>("ACTION_A");
const actionB = actionCreator<PayloadB>("ACTION_B");
const actionC = actionCreator<PayloadC>("ACTION_C");
it("should call handler on any matching action when using .cases()", () => {
const reducer = reducerWithInitialState(initialState).cases(
[actionA, actionB, actionC],
(state, payload) => {
return { ...state, data: payload.data };
},
);
expect(
reducer(initialState, actionA({ data: "from A", x: 0 })),
).toEqual({
data: "from A",
});
expect(
reducer(initialState, actionB({ data: "from B", y: 1 })),
).toEqual({
data: "from B",
});
expect(
reducer(initialState, actionC({ data: "from C", z: 2 })),
).toEqual({
data: "from C",
});
});
it("should call handler on any matching action when using .casesWithAction()", () => {
const reducer = reducerWithInitialState(
initialState,
).casesWithAction([actionA, actionB, actionC], (state, action) => {
return { ...state, data: action.payload.data };
});
expect(
reducer(initialState, actionA({ data: "from A", x: 0 })),
).toEqual({
data: "from A",
});
expect(
reducer(initialState, actionB({ data: "from B", y: 1 })),
).toEqual({
data: "from B",
});
expect(
reducer(initialState, actionC({ data: "from C", z: 2 })),
).toEqual({
data: "from C",
});
});
})();
it("should be mutated by .case()", () => {
const reducer = reducerWithInitialState(initialState);
reducer.case(sliceData, sliceDataHandler);
reducer.case(dataToUpperCase, dataToUpperCaseHandler);
expect(reducer(undefined, sliceData(1))).toEqual({
data: "ello",
});
});
it("should apply handling function to itself in .withHandling()", () => {
const handling = (
builder: ReducerBuilder<State>,
): ReducerBuilder<State> => builder.case(sliceData, sliceDataHandler);
const reducer = reducerWithInitialState(initialState).withHandling(
handling,
);
expect(reducer(undefined, sliceData(1))).toEqual({
data: "ello",
});
});
describe(".build()", () => {
const reducer = reducerWithInitialState(initialState)
.case(sliceData, sliceDataHandler)
.case(dataToUpperCase, dataToUpperCaseHandler)
.build();
it("should return a function with no extra keys", () => {
expect(Object.keys(reducer)).toEqual([]);
});
it("should return a function which behaves like the reducer", () => {
expect(reducer(undefined, sliceData(1))).toEqual({
data: "ello",
});
});
it("should return a function that does not mutate if parent builder mutates", () => {
const builder = reducerWithInitialState(initialState);
const reducer1 = builder.build();
builder.case(sliceData, sliceDataHandler);
const reducer2 = builder.build();
expect(reducer1(undefined, sliceData(1))).toEqual({
data: "hello",
});
expect(reducer2(undefined, sliceData(1))).toEqual({
data: "ello",
});
});
});
});
================================================
FILE: jest.config.js
================================================
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};
================================================
FILE: package.json
================================================
{
"name": "typescript-fsa-reducers",
"version": "1.2.2",
"description": "Fluent syntax for defining typesafe Redux reducers on top of typescript-fsa.",
"main": "dist/index.js",
"types": "dist/index",
"files": [
"dist/"
],
"repository": {
"type": "git",
"url": "git://github.com/dphilipson/typescript-fsa-reducers.git"
},
"keywords": [
"redux",
"typescript",
"action",
"reducer",
"builder"
],
"author": "David Philipson <david.philipson@gmail.com> (http://dphil.me)",
"license": "MIT",
"bugs": {
"url": "https://github.com/dphilipson/typescript-fsa-reducers/issues"
},
"homepage": "https://github.com/dphilipson/typescript-fsa-reducers#readme",
"scripts": {
"build": "yarn run clean && tsc -p tsconfig.build.json",
"clean": "rm -rf dist/*",
"format-file": "prettier --write",
"format": "git ls-files | egrep '\\.(js(on)?|scss|tsx?)?$' | xargs yarn run format-file",
"generate-toc": "git ls-files | egrep '\\.md$' | xargs scripts/markdown-toc-all.sh",
"jest": "jest",
"lint-file": "tslint",
"lint": "tslint --project .",
"prepublishOnly": "yarn run test && yarn run build",
"test": "yarn run lint && tsc && yarn run jest"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*.{js,json}": [
"yarn run format-file",
"git add"
],
"**/*.ts": [
"yarn run format-file",
"yarn run lint-file --fix",
"git add"
],
"*.md": [
"./scripts/markdown-toc-all.sh",
"git add"
]
},
"devDependencies": {
"@types/jest": "^24.0.20",
"husky": "^3.0.9",
"jest": "^24.9.0",
"lint-staged": "^9.4.2",
"markdown-toc": "^1.2.0",
"prettier": "^1.18.2",
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.6.4",
"typescript-fsa": "^3.0.0"
},
"peerDependencies": {
"typescript-fsa": "*"
}
}
================================================
FILE: scripts/markdown-toc-all.sh
================================================
#!/bin/sh
# Needed for lint-staged if multiple .md files are changed since markdown-toc
# only accepts a single file argument.
for var in "$@"
do
yarn markdown-toc -i "$var"
done
================================================
FILE: src/index.ts
================================================
import { Action, ActionCreator, AnyAction } from "typescript-fsa";
export interface ReducerBuilder<InS, OutS = InS, PassedS = InS | undefined> {
case<P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, P>,
): ReducerBuilder<InS, OutS, PassedS>;
caseWithAction<P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, Action<P>>,
): ReducerBuilder<InS, OutS, PassedS>;
// cases variadic overloads
cases<P1, P2>(
actionCreators: [ActionCreator<P1>, ActionCreator<P2>],
handler: Handler<InS, OutS, P1 | P2>,
): ReducerBuilder<InS, OutS, PassedS>;
cases<P1, P2, P3>(
actionCreators: [
ActionCreator<P1>,
ActionCreator<P2>,
ActionCreator<P3>,
],
handler: Handler<InS, OutS, P1 | P2 | P3>,
): ReducerBuilder<InS, OutS, PassedS>;
cases<P1, P2, P3, P4>(
actionCreators: [
ActionCreator<P1>,
ActionCreator<P2>,
ActionCreator<P3>,
ActionCreator<P4>,
],
handler: Handler<InS, OutS, P1 | P2 | P3 | P4>,
): ReducerBuilder<InS, OutS, PassedS>;
cases<P>(
actionCreators: Array<ActionCreator<P>>,
handler: Handler<InS, OutS, P>,
): ReducerBuilder<InS, OutS, PassedS>;
// casesWithAction variadic overloads
casesWithAction<P1, P2>(
actionCreators: [ActionCreator<P1>, ActionCreator<P2>],
handler: Handler<InS, OutS, Action<P1 | P2>>,
): ReducerBuilder<InS, OutS, PassedS>;
casesWithAction<P1, P2, P3>(
actionCreators: [
ActionCreator<P1>,
ActionCreator<P2>,
ActionCreator<P3>,
],
handler: Handler<InS, OutS, Action<P1 | P2 | P3>>,
): ReducerBuilder<InS, OutS, PassedS>;
casesWithAction<P1, P2, P3, P4>(
actionCreators: [
ActionCreator<P1>,
ActionCreator<P2>,
ActionCreator<P3>,
ActionCreator<P4>,
],
handler: Handler<InS, OutS, Action<P1 | P2 | P3 | P4>>,
): ReducerBuilder<InS, OutS, PassedS>;
casesWithAction<P>(
actionCreators: Array<ActionCreator<P>>,
handler: Handler<InS, OutS, Action<P>>,
): ReducerBuilder<InS, OutS, PassedS>;
withHandling(
updateBuilder: (
builder: ReducerBuilder<InS, OutS, PassedS>,
) => ReducerBuilder<InS, OutS, PassedS>,
): ReducerBuilder<InS, OutS, PassedS>;
// Intentionally avoid AnyAction in return type so packages can export
// reducers created using .default() or .build() without consumers requiring
// a dependency on typescript-fsa.
default(
defaultHandler: Handler<InS, OutS, AnyAction>,
): (state: PassedS, action: { type: any }) => OutS;
build(): (state: PassedS, action: { type: any }) => OutS;
(state: PassedS, action: AnyAction): OutS;
}
export type Handler<InS, OutS, P> = (state: InS, payload: P) => OutS;
export function reducerWithInitialState<S>(initialState: S): ReducerBuilder<S> {
return makeReducer<S, S, S | undefined>(initialState);
}
export function reducerWithoutInitialState<S>(): ReducerBuilder<S, S, S> {
return makeReducer<S, S, S>();
}
export function upcastingReducer<InS extends OutS, OutS>(): ReducerBuilder<
InS,
OutS,
InS
> {
return makeReducer<InS, OutS, InS>();
}
function makeReducer<InS, OutS, PassedS>(
initialState?: InS,
): ReducerBuilder<InS, OutS, PassedS> {
const handlersByActionType: {
[actionType: string]: Handler<InS, OutS, any>;
} = {};
const reducer = getReducerFunction(
initialState,
handlersByActionType,
) as ReducerBuilder<InS, OutS, PassedS>;
reducer.caseWithAction = <P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, Action<P>>,
) => {
handlersByActionType[actionCreator.type] = handler;
return reducer;
};
reducer.case = <P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, P>,
) =>
reducer.caseWithAction(actionCreator, (state, action) =>
handler(state, action.payload),
);
reducer.casesWithAction = <P>(
actionCreators: Array<ActionCreator<P>>,
handler: Handler<InS, OutS, Action<P>>,
) => {
for (const actionCreator of actionCreators) {
reducer.caseWithAction(actionCreator, handler);
}
return reducer;
};
reducer.cases = <P>(
actionCreators: Array<ActionCreator<P>>,
handler: Handler<InS, OutS, P>,
) =>
reducer.casesWithAction(actionCreators, (state, action) =>
handler(state, action.payload),
);
reducer.withHandling = (
updateBuilder: (
builder: ReducerBuilder<InS, OutS, PassedS>,
) => ReducerBuilder<InS, OutS, PassedS>,
) => updateBuilder(reducer);
reducer.default = (defaultHandler: Handler<InS, OutS, AnyAction>) =>
getReducerFunction<InS, OutS, PassedS>(
initialState,
{ ...handlersByActionType },
defaultHandler,
);
reducer.build = () =>
getReducerFunction(initialState, { ...handlersByActionType });
return reducer;
}
function getReducerFunction<InS, OutS, PassedS>(
initialState: InS | undefined,
handlersByActionType: { [actionType: string]: Handler<InS, OutS, any> },
defaultHandler?: Handler<InS, OutS, AnyAction>,
) {
return (passedState: PassedS, action: AnyAction) => {
const state = passedState !== undefined ? passedState : initialState;
const handler = handlersByActionType[action.type] || defaultHandler;
return handler
? handler(state as InS, action)
: ((state as unknown) as OutS);
};
}
================================================
FILE: tsconfig.build.json
================================================
{
"compilerOptions": {
"declaration": true,
"inlineSources": true,
"noEmit": false,
"outDir": "dist",
"sourceMap": true
},
"extends": "./tsconfig.json",
"include": ["src/**/*"]
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"strict": true,
"target": "es5"
},
"include": ["src/**/*", "__tests__/**/*"]
}
================================================
FILE: tslint.json
================================================
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"interface-name": [true, "never-prefix"],
"no-bitwise": false,
"no-submodule-imports": false,
"object-literal-sort-keys": false
}
}
gitextract_07iw849q/ ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__/ │ └── test.ts ├── jest.config.js ├── package.json ├── scripts/ │ └── markdown-toc-all.sh ├── src/ │ └── index.ts ├── tsconfig.build.json ├── tsconfig.json └── tslint.json
SYMBOL INDEX (16 symbols across 2 files)
FILE: __tests__/test.ts
type State (line 11) | interface State {
type StateWithCount (line 15) | interface StateWithCount extends State {
function defaultHandler (line 23) | function defaultHandler(state: State): State {
function sliceDataHandler (line 31) | function sliceDataHandler(state: State, fromIndex: number): State {
function dataToUpperCaseHandler (line 36) | function dataToUpperCaseHandler(state: State): State {
function toBasicStateHandler (line 41) | function toBasicStateHandler(state: StateWithCount): State {
type PayloadA (line 180) | interface PayloadA {
type PayloadB (line 184) | interface PayloadB {
type PayloadC (line 188) | interface PayloadC {
FILE: src/index.ts
type ReducerBuilder (line 3) | interface ReducerBuilder<InS, OutS = InS, PassedS = InS | undefined> {
type Handler (line 83) | type Handler<InS, OutS, P> = (state: InS, payload: P) => OutS;
function reducerWithInitialState (line 85) | function reducerWithInitialState<S>(initialState: S): ReducerBuilder<S> {
function reducerWithoutInitialState (line 89) | function reducerWithoutInitialState<S>(): ReducerBuilder<S, S, S> {
function upcastingReducer (line 93) | function upcastingReducer<InS extends OutS, OutS>(): ReducerBuilder<
function makeReducer (line 101) | function makeReducer<InS, OutS, PassedS>(
function getReducerFunction (line 165) | function getReducerFunction<InS, OutS, PassedS>(
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
{
"path": ".gitignore",
"chars": 129,
"preview": "# Created by https://www.gitignore.io/api/visualstudiocode\n\n### VisualStudioCode ###\n.vscode/\n\ndist/\nnode_modules/\nnpm-d"
},
{
"path": ".prettierrc",
"chars": 50,
"preview": "{\n \"tabWidth\": 4,\n \"trailingComma\": \"all\"\n}\n"
},
{
"path": ".travis.yml",
"chars": 38,
"preview": "language: node_js\nnode_js:\n - \"node\"\n"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017 David Philipson\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 13404,
"preview": "# TypeScript FSA Reducers\n\nFluent syntax for defining typesafe Redux reducers on top of\n[typescript-fsa](https://github."
},
{
"path": "__tests__/test.ts",
"chars": 10462,
"preview": "import actionCreatorFactory from \"typescript-fsa\";\nimport {\n ReducerBuilder,\n reducerWithInitialState,\n reducer"
},
{
"path": "jest.config.js",
"chars": 74,
"preview": "module.exports = {\n preset: \"ts-jest\",\n testEnvironment: \"node\",\n};\n"
},
{
"path": "package.json",
"chars": 2242,
"preview": "{\n \"name\": \"typescript-fsa-reducers\",\n \"version\": \"1.2.2\",\n \"description\": \"Fluent syntax for defining typesafe"
},
{
"path": "scripts/markdown-toc-all.sh",
"chars": 184,
"preview": "#!/bin/sh\n\n# Needed for lint-staged if multiple .md files are changed since markdown-toc\n# only accepts a single file ar"
},
{
"path": "src/index.ts",
"chars": 5838,
"preview": "import { Action, ActionCreator, AnyAction } from \"typescript-fsa\";\n\nexport interface ReducerBuilder<InS, OutS = InS, Pas"
},
{
"path": "tsconfig.build.json",
"chars": 235,
"preview": "{\n \"compilerOptions\": {\n \"declaration\": true,\n \"inlineSources\": true,\n \"noEmit\": false,\n "
},
{
"path": "tsconfig.json",
"chars": 361,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"noEmit\": true,\n \"noFallthroughCasesInSwitch\": t"
},
{
"path": "tslint.json",
"chars": 245,
"preview": "{\n \"extends\": [\"tslint:latest\", \"tslint-config-prettier\"],\n \"rules\": {\n \"interface-name\": [true, \"never-pre"
}
]
About this extraction
This page contains the full source code of the dphilipson/typescript-fsa-reducers GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (33.5 KB), approximately 8.5k tokens, and a symbol index with 16 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.