Skip to content
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
4 changes: 2 additions & 2 deletions src/app/handleCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Future } from '@/lib/Future';
import { Result, Failure } from '@/lib/Result';
import { Repositories, Projections, allProjections } from '@/app/projections';
import { Services } from '@/app/integrations';
import { WithProjectionStore } from '@/app/projectionStore';
import { WithProjectionStore, Mode } from '@/app/projectionStore';
import { WithEventStore } from '@/app/eventStore';

type CommandHandler<Command> = (v: {
Expand Down Expand Up @@ -39,7 +39,7 @@ function handleCommand<Command>(
): express.Handler {
return router.route((req) =>
decodeCommand(decoder, req).chain((command) =>
withProjectionStore(onStoreError, (projectionStore) =>
withProjectionStore(onStoreError, Mode.ReadOnly, (projectionStore) =>
withEventStore(onStoreError, (store) =>
handler({
command,
Expand Down
3 changes: 2 additions & 1 deletion src/app/handleProjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Projections, Repositories, allProjections } from '@/app/projections';
import {
MongoProjectionStore,
WithProjectionStore,
Mode,
} from '@/app/projectionStore';

type ProjectionHandler<E> = (v: {
Expand All @@ -44,7 +45,7 @@ function handleProjection<E extends Event<any>>(
return router.route((req) =>
decodeEvent(decoder, req)
.chain(({ event, info }) =>
withProjectionStore(onProjectionStoreError, (store) =>
withProjectionStore(onProjectionStoreError, Mode.ReadWrite, (store) =>
handler({
event,
info,
Expand Down
4 changes: 2 additions & 2 deletions src/app/handleQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as router from '@/lib/router';
import { Future } from '@/lib/Future';
import { Result, Failure } from '@/lib/Result';
import { Projections, Repositories, allProjections } from '@/app/projections';
import { WithProjectionStore } from '@/app/projectionStore';
import { WithProjectionStore, Mode } from '@/app/projectionStore';
import { internalServerError } from '@/app/responses';

type QueryHandler<Query> = (v: {
Expand All @@ -29,7 +29,7 @@ function handleQuery<Query>(
): express.Handler {
return router.route((req) =>
decodeQuery(decoder, req).chain((query) =>
withProjectionStore(onProjectionStoreError, (store) =>
withProjectionStore(onProjectionStoreError, Mode.ReadOnly, (store) =>
handler({
query,
projections: allProjections(repositories, store),
Expand Down
41 changes: 22 additions & 19 deletions src/app/handleReaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { AmbarResponse, ErrorMustRetry } from '@/lib/ambar';
import { Future } from '@/lib/Future';
import { Maybe } from '@/lib/Maybe';
import { decodeEvent } from '@/app/handleProjection';

type Projections = {};
type Services = {};
import { Services } from '@/app/integrations';
import { Repositories, Projections, allProjections } from '@/app/projections';
import { WithProjectionStore, Mode } from '@/app/projectionStore';

type ReactionController<E extends Event<any>> = {
decoder: Decoder<Maybe<E>>;
Expand All @@ -32,7 +32,7 @@ type ReactionHandler<E> = (v: {
store: EventStore;
}) => Future<AmbarResponse, void>;

const onEventStoreError = (err: Error) => new Ambar.ErrorMustRetry(err.message);
const onStoreError = (err: Error) => new Ambar.ErrorMustRetry(err.message);

type WithStoreGeneric = <E, T>(
onError: (e: Error) => E,
Expand All @@ -47,31 +47,34 @@ const wrapWithEventStore = (
withEventStore: WithStoreGeneric,
): WithStoreConcrete =>
function (f) {
return withEventStore(onEventStoreError, (store) => f(store));
return withEventStore(onStoreError, (store) => f(store));
};

function handleReaction<E extends Event<any>>(
withEventStore: WithStoreConcrete,
projections: Projections,
withProjectionStore: WithProjectionStore,
services: Services,
repositories: Repositories,
{ decoder, handler }: ReactionController<E>,
): express.Handler {
return router.route((req) =>
decodeEvent(decoder, req)
.chain(({ event, info }) =>
withEventStore((store) =>
handler({
event,
info,
projections,
services,
store,
}).chainRej((r) =>
r instanceof Ambar.Success
? Future.resolve(undefined)
: r instanceof Ambar.ErrorMustRetry
? Future.reject(r)
: (r satisfies never),
withProjectionStore(onStoreError, Mode.ReadOnly, (projectionStore) =>
withEventStore((store) =>
handler({
event,
info,
projections: allProjections(repositories, projectionStore),
services,
store,
}).chainRej((r) =>
r instanceof Ambar.Success
? Future.resolve(undefined)
: r instanceof Ambar.ErrorMustRetry
? Future.reject(r)
: (r satisfies never),
),
),
),
)
Expand Down
7 changes: 4 additions & 3 deletions src/app/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Mongo } from '@/lib/mongo';
import {
MongoProjectionStore,
WithProjectionStore,
Mode,
} from '@/app/projectionStore';
import { EmailService } from '@/app/services/email';
import { FileStorageService } from '@/app/services/file-storage';
Expand Down Expand Up @@ -77,11 +78,11 @@ async function configureDependencies(): Promise<Dependencies> {
f(new PostgresEventStore(t, schemas, table)),
);

const withProjectionStore: WithProjectionStore = (onError, f) =>
mongo.withTransaction(onError, (t) => f(new MongoProjectionStore(t)));
const withProjectionStore: WithProjectionStore = (onError, mode, f) =>
mongo.withTransaction(onError, (t) => f(new MongoProjectionStore(t, mode)));

const repositories = await mongo.withTransactionP(async (t) =>
initializeRepositories(new MongoProjectionStore(t)),
initializeRepositories(new MongoProjectionStore(t, Mode.ReadOnly)),
);

const services = initializeServices();
Expand Down
18 changes: 17 additions & 1 deletion src/app/projectionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
type JsonDoc,
type RepositoryArgs,
type WithProjectionStore,
Mode,
};

import { Collection } from 'mongodb';
Expand Down Expand Up @@ -53,11 +54,20 @@ const schemaIdAndValue = <T>(schema: Schema<T>): Schema<IdAndDoc<T>> => {

type WithProjectionStore = <E, T>(
onError: (e: Error) => E,
mode: Mode,
f: (s: MongoProjectionStore) => Future<E, T>,
) => Future<E, T>;

enum Mode {
ReadOnly = 'ReadOnly',
ReadWrite = 'ReadWrite',
}

class MongoProjectionStore {
constructor(private transaction: MongoTransaction) {}
constructor(
private transaction: MongoTransaction,
private readonly readOnly: Mode,
) {}

// Initialize a repository, creating the collection and indexes if needed.
async createRepository<T>(args: RepositoryArgs<T>): Promise<Repository<T>> {
Expand Down Expand Up @@ -122,6 +132,9 @@ class MongoProjectionStore {
document: T,
options?: InsertOneOptions,
): Promise<void> {
if (this.readOnly == Mode.ReadOnly) {
throw new Error('Trying to write to read-only projection store');
}
const schema = schemaIdAndValue(repository.values.schema);
const _id = repository.values.toId(document);
await this.transaction.insertOne(
Expand All @@ -137,6 +150,9 @@ class MongoProjectionStore {
document: T,
options: InsertOneOptions = {},
): Promise<void> {
if (this.readOnly == Mode.ReadOnly) {
throw new Error('Trying to write to read-only projection store');
}
const schema = schemaIdAndValue(repository.values.schema);
const _id = repository.values.toId(document);
await this.transaction.replaceOne(
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async function main() {
endpoint,
handleReaction(
wrapWithEventStore(withEventStore),
withProjectionStore,
services,
repositories,
controller,
Expand Down