Skip to content

Commit

Permalink
feat(transaction): added transaction triggers and mutator helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Sep 8, 2021
1 parent 98a4787 commit 88cab67
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 28 deletions.
6 changes: 3 additions & 3 deletions extensions/transaction/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
"build": "tsup",
"prepublish": "yarn build"
},
"dependencies": {
"@harlem/extension-snapshot": "^2.0.0-beta.9"
},
"peerDependencies": {
"@harlem/core": "^2.0.0-alpha.0"
},
"dependencies": {
"@harlem/utilities": "^2.0.0-beta.9"
},
"devDependencies": {
"@harlem/core": "^2.0.0-beta.9"
}
Expand Down
1 change: 1 addition & 0 deletions extensions/transaction/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const EVENTS = {
transaction: {
before: 'transaction:before',
after: 'transaction:after',
success: 'transaction:success',
error: 'transaction:error',
},
};
69 changes: 49 additions & 20 deletions extensions/transaction/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,88 @@
import snapshotExtension from '@harlem/extension-snapshot';

import {
SENDER,
EVENTS,
} from './constants';

import {
clone,
overwrite,
} from '@harlem/utilities';

import {
BaseState,
EventPayload,
InternalStore,
ReadState,
Mutator,
} from '@harlem/core';

import type {
Transactor,
Transaction,
TransactionEventData,
TransactionHookHandler,
} from './types';

export * from './types';

export default function transactionExtension<TState extends BaseState>() {
return (store: InternalStore<TState>) => {
function rollback(snapshot: ReadState<TState>) {
store.write('$transaction-rollback', SENDER, state => overwrite(state, snapshot));
}
const {
snapshot,
} = snapshotExtension({
mutationName: '$transaction-rollback',
})(store);


function transaction<TPayload>(name: string, transactor: Transactor<TPayload>): Transaction<TPayload> {
return payload => {
const snapshot = clone(store.state) as ReadState<TState>;
function transaction<TPayload>(name: string, transactor: Transactor<TState, TPayload>): Transaction<TPayload> {
const mutate = (mutator: Mutator<TState, undefined, void>) => store.write(name, SENDER, mutator);

const eventData = {
payload,
return ((payload: TPayload) => {
const snap = snapshot();
const providedPayload = store.providers.payload(payload) ?? payload;

const emit = (event: string) => store.emit(event, SENDER, {
transaction: name,
} as TransactionEventData;
payload: providedPayload,
} as TransactionEventData);

store.emit(EVENTS.transaction.before, SENDER, eventData);
emit(EVENTS.transaction.before);

try {
transactor(payload!);
const providedPayload = store.providers.payload(payload) ?? payload;

transactor(providedPayload, mutate);
emit(EVENTS.transaction.success);
} catch (error) {
rollback(snapshot);
store.emit(EVENTS.transaction.error, SENDER, eventData);
snap.apply();
emit(EVENTS.transaction.error);

throw error;
} finally {
emit(EVENTS.transaction.after);
}
}) as Transaction<TPayload>;
}

store.emit(EVENTS.transaction.after, SENDER, eventData);
function getTransactionTrigger(eventName: string) {
return <TPayload = any>(actionName: string | string[], handler: TransactionHookHandler<TPayload>) => {
const transactions = ([] as string[]).concat(actionName);

return store.on(eventName, (event?: EventPayload<TransactionEventData>) => {
if (event && transactions.includes(event.data.transaction)) {
handler(event.data);
}
});
};
}

const onBeforeTransaction = getTransactionTrigger(EVENTS.transaction.before);
const onAfterTransaction = getTransactionTrigger(EVENTS.transaction.after);
const onTransactionSuccess = getTransactionTrigger(EVENTS.transaction.success);
const onTransactionError = getTransactionTrigger(EVENTS.transaction.error);

return {
transaction,
onBeforeTransaction,
onAfterTransaction,
onTransactionSuccess,
onTransactionError,
};
};
}
15 changes: 10 additions & 5 deletions extensions/transaction/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
export type Transactor<TPayload> = undefined extends TPayload ? (payload?: TPayload) => void : (payload: TPayload) => void;
export type Transaction<TPayload> = (payload?: TPayload) => void;
export type TransactionRollback = () => void;
import type {
BaseState,
Mutator,
} from '@harlem/core';

export interface TransactionEventData {
export type Transactor<TState extends BaseState, TPayload = undefined> = (payload: TPayload, mutator: (mutate: Mutator<TState, undefined, void>) => void) => void;
export type Transaction<TPayload> = undefined extends TPayload ? (payload?: TPayload) => void : (payload: TPayload) => void;
export type TransactionHookHandler<TPayload> = (data: TransactionEventData<TPayload>) => void;

export interface TransactionEventData<TPayload = any> {
transaction: string;
payload: any;
payload: TPayload;
}

0 comments on commit 88cab67

Please sign in to comment.