From 9b453e5235b26062d898c94907a833c8367bb932 Mon Sep 17 00:00:00 2001 From: George Enciu Date: Sat, 8 Jul 2023 21:55:38 +0200 Subject: [PATCH] training events #1 --- libs/training-events/.env.test | 3 + libs/training-events/.eslintrc.json | 46 +++++ libs/training-events/Dockerfile.firebase | 9 + libs/training-events/README.md | 11 ++ libs/training-events/docker-compose.yml | 14 ++ libs/training-events/package.json | 5 + libs/training-events/project.json | 48 ++++++ .../src/actions/emit-training-event.test.ts | 101 +++++++++++ .../src/actions/emit-training-event.ts | 47 ++++++ .../src/actions/get-training-events.ts | 49 ++++++ libs/training-events/src/config.ts | 3 + libs/training-events/src/events/index.ts | 19 +++ libs/training-events/src/events/reducer.ts | 60 +++++++ .../src/events/training-completed.ts | 20 +++ .../src/events/training-cpt-performed.ts | 22 +++ .../src/events/training-cpt-requested.ts | 23 +++ .../src/events/training-cpt-scheduled.ts | 22 +++ .../training-intent-confirmation-expired.ts | 31 ++++ .../training-intent-confirmation-rejected.ts | 31 ++++ .../training-intent-confirmation-requested.ts | 30 ++++ .../training-intent-confirmation-responded.ts | 31 ++++ .../src/events/training-intent.ts | 22 +++ .../src/events/training-mentor-assigned.ts | 26 +++ .../src/events/training-mentor-reassigned.ts | 29 ++++ .../src/events/training-session-performed.ts | 34 ++++ .../src/events/training-session-scheduled.ts | 33 ++++ .../src/events/training-solo-performed.ts | 22 +++ .../src/events/training-solo-requested.ts | 23 +++ .../src/events/training-solo-scheduled.ts | 22 +++ .../src/events/training-test-assigned.ts | 22 +++ .../src/events/training-test-completed.ts | 23 +++ .../exception/firestore-not-initialized.ts | 5 + .../src/exception/not-recognised-event.ts | 5 + .../src/exception/training-not-found.ts | 5 + libs/training-events/src/helpers/get-date.ts | 1 + libs/training-events/src/index.ts | 2 + libs/training-events/src/types.ts | 158 ++++++++++++++++++ libs/training-events/test/setup-test.ts | 6 + libs/training-events/tsconfig.json | 23 +++ libs/training-events/tsconfig.lib.json | 10 ++ libs/training-events/tsconfig.spec.json | 19 +++ libs/training-events/vite.config.ts | 60 +++++++ 42 files changed, 1175 insertions(+) create mode 100644 libs/training-events/.env.test create mode 100644 libs/training-events/.eslintrc.json create mode 100644 libs/training-events/Dockerfile.firebase create mode 100644 libs/training-events/README.md create mode 100644 libs/training-events/docker-compose.yml create mode 100644 libs/training-events/package.json create mode 100644 libs/training-events/project.json create mode 100644 libs/training-events/src/actions/emit-training-event.test.ts create mode 100644 libs/training-events/src/actions/emit-training-event.ts create mode 100644 libs/training-events/src/actions/get-training-events.ts create mode 100644 libs/training-events/src/config.ts create mode 100644 libs/training-events/src/events/index.ts create mode 100644 libs/training-events/src/events/reducer.ts create mode 100644 libs/training-events/src/events/training-completed.ts create mode 100644 libs/training-events/src/events/training-cpt-performed.ts create mode 100644 libs/training-events/src/events/training-cpt-requested.ts create mode 100644 libs/training-events/src/events/training-cpt-scheduled.ts create mode 100644 libs/training-events/src/events/training-intent-confirmation-expired.ts create mode 100644 libs/training-events/src/events/training-intent-confirmation-rejected.ts create mode 100644 libs/training-events/src/events/training-intent-confirmation-requested.ts create mode 100644 libs/training-events/src/events/training-intent-confirmation-responded.ts create mode 100644 libs/training-events/src/events/training-intent.ts create mode 100644 libs/training-events/src/events/training-mentor-assigned.ts create mode 100644 libs/training-events/src/events/training-mentor-reassigned.ts create mode 100644 libs/training-events/src/events/training-session-performed.ts create mode 100644 libs/training-events/src/events/training-session-scheduled.ts create mode 100644 libs/training-events/src/events/training-solo-performed.ts create mode 100644 libs/training-events/src/events/training-solo-requested.ts create mode 100644 libs/training-events/src/events/training-solo-scheduled.ts create mode 100644 libs/training-events/src/events/training-test-assigned.ts create mode 100644 libs/training-events/src/events/training-test-completed.ts create mode 100644 libs/training-events/src/exception/firestore-not-initialized.ts create mode 100644 libs/training-events/src/exception/not-recognised-event.ts create mode 100644 libs/training-events/src/exception/training-not-found.ts create mode 100644 libs/training-events/src/helpers/get-date.ts create mode 100644 libs/training-events/src/index.ts create mode 100644 libs/training-events/src/types.ts create mode 100644 libs/training-events/test/setup-test.ts create mode 100644 libs/training-events/tsconfig.json create mode 100644 libs/training-events/tsconfig.lib.json create mode 100644 libs/training-events/tsconfig.spec.json create mode 100644 libs/training-events/vite.config.ts diff --git a/libs/training-events/.env.test b/libs/training-events/.env.test new file mode 100644 index 0000000..7dab89a --- /dev/null +++ b/libs/training-events/.env.test @@ -0,0 +1,3 @@ +SYSTEM_ID=rovacc-system-id +FIRESTORE_EMULATOR_HOST="localhost:8080" +GCLOUD_PROJECT="dummy-project" diff --git a/libs/training-events/.eslintrc.json b/libs/training-events/.eslintrc.json new file mode 100644 index 0000000..e489d0b --- /dev/null +++ b/libs/training-events/.eslintrc.json @@ -0,0 +1,46 @@ +{ + "extends": [ + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.json" + ], + "rules": { + "array-element-newline": [ + "error", + { + "minItems": 3 + } + ] + } + }, + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/libs/training-events/Dockerfile.firebase b/libs/training-events/Dockerfile.firebase new file mode 100644 index 0000000..b6ff7dd --- /dev/null +++ b/libs/training-events/Dockerfile.firebase @@ -0,0 +1,9 @@ +FROM node:alpine + +RUN apk add openjdk11 + +RUN npm install -g firebase-tools + +WORKDIR /app + +CMD [ "firebase", "--project=training-events", "emulators:start", "--only", "firestore" ] diff --git a/libs/training-events/README.md b/libs/training-events/README.md new file mode 100644 index 0000000..21dd6b0 --- /dev/null +++ b/libs/training-events/README.md @@ -0,0 +1,11 @@ +# training-events + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build training-events` to build the library. + +## Running unit tests + +Run `nx test training-events` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/training-events/docker-compose.yml b/libs/training-events/docker-compose.yml new file mode 100644 index 0000000..d40edc4 --- /dev/null +++ b/libs/training-events/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.5' +services: + training_events_firebase: + image: matthewkrupnik/docker-firestore-emulator-with-ui + ports: + - "8080:8080" + - "4000:4000" + # trainingeevents_firebase: + # build: + # dockerfile: ./Dockerfile.firebase + # context: . + # ports: + # - "8080:8080" + # - "4000:4000" diff --git a/libs/training-events/package.json b/libs/training-events/package.json new file mode 100644 index 0000000..07c3da7 --- /dev/null +++ b/libs/training-events/package.json @@ -0,0 +1,5 @@ +{ + "name": "@rovacc/training-events", + "version": "0.0.1", + "type": "commonjs" +} diff --git a/libs/training-events/project.json b/libs/training-events/project.json new file mode 100644 index 0000000..7059629 --- /dev/null +++ b/libs/training-events/project.json @@ -0,0 +1,48 @@ +{ + "name": "training-events", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/training-events/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/vite:build", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/libs/training-events" + } + }, + "test": { + "executor": "@nx/vite:test", + "dependsOn": [ + "pre-test" + ], + "outputs": [ + "coverage/libs/training-events" + ], + "options": { + "passWithNoTests": true, + "reportsDirectory": "../../coverage/libs/training-events" + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "libs/training-events/**/*.ts" + ] + } + }, + "pretest": { + "command": "docker-compose -f libs/training-events/docker-compose.yml up -d" + }, + "posttest": { + "command": "docker-compose -f libs/training-events/docker-compose.yml down" + } + }, + "tags": [] +} \ No newline at end of file diff --git a/libs/training-events/src/actions/emit-training-event.test.ts b/libs/training-events/src/actions/emit-training-event.test.ts new file mode 100644 index 0000000..789d12b --- /dev/null +++ b/libs/training-events/src/actions/emit-training-event.test.ts @@ -0,0 +1,101 @@ +import { afterEach, beforeEach, describe, it, expect } from "vitest"; +import { buildTrainingIntentEvent } from '@rovacc/test-helpers' + +import { emitTrainingEvent } from "./emit-training-event"; +import { deleteCollection } from "@rovacc/test-helpers"; +import { getDatabaseCollection } from "@rovacc/clients"; +import { Timestamp } from "firebase-admin/firestore"; + + +const TRAINING_ID = 'trainingId' +const EVENT_ID = 'eventId' +const DATE = new Date() + +describe('emitTrainingEvent', () => { + + beforeEach(async () => { + await deleteCollection('training') + vi.mock('uuid', () => ({ + v4: () => EVENT_ID + })) + vi.mock('../helpers/get-date', () => ({ + getDate: () => DATE + })) + }) + + afterEach(async () => { + vi.restoreAllMocks() + }) + + it('should emit the event correctly and create the empty training object', async () => { + const eventData = buildTrainingIntentEvent({ + trainingId: TRAINING_ID, + emittedAt: DATE, + eventId: EVENT_ID + }) + + const reducedTraining = await emitTrainingEvent(eventData, null, 'correlationId') + + expect(reducedTraining).toEqual({ + trainingId: TRAINING_ID, + status: 'QUEUED', + purpose: 'acquire_rating', + rating: 2, + student: 123123123, + requestedAt: DATE + }) + const trainingCollection = getDatabaseCollection('training') + const event = await trainingCollection.doc(TRAINING_ID).collection('events').doc(EVENT_ID).get() + + expect(event.data()).toEqual({ + eventId: EVENT_ID, + emittedAt: Timestamp.fromDate(DATE), + system: 'rovacc-system-id', + trainingId: TRAINING_ID, + payload: { student: 123123123, purpose: 'acquire_rating', rating: 2 }, + name: 'training-intent', + correlationId: 'correlationId' + }) + }) + + it('should not emit the same event twice', async () => { + const eventData1 = buildTrainingIntentEvent({ + trainingId: TRAINING_ID, + emittedAt: DATE, + eventId: EVENT_ID + }) + + const reducedTraining1 = await emitTrainingEvent(eventData1, null, 'correlationId') + + expect(reducedTraining1).toEqual({ + trainingId: TRAINING_ID, + status: 'QUEUED', + purpose: 'acquire_rating', + rating: 2, + student: 123123123, + requestedAt: DATE + }) + const trainingCollection = getDatabaseCollection('training') + const event1 = await trainingCollection.doc(TRAINING_ID).collection('events').doc(EVENT_ID).get() + + expect(event1.data()).toEqual({ + eventId: EVENT_ID, + emittedAt: Timestamp.fromDate(DATE), + system: 'rovacc-system-id', + trainingId: TRAINING_ID, + payload: { student: 123123123, purpose: 'acquire_rating', rating: 2 }, + name: 'training-intent', + correlationId: 'correlationId' + }) + + const eventData2 = buildTrainingIntentEvent({ + trainingId: TRAINING_ID, + emittedAt: DATE, + eventId: 'eventId2' + }) + await emitTrainingEvent(eventData2, reducedTraining1, 'correlationId2') + const eventCount = await trainingCollection.doc(TRAINING_ID).collection('events').count().get() + + expect(eventCount.data().count).toEqual(1) + }) +}) diff --git a/libs/training-events/src/actions/emit-training-event.ts b/libs/training-events/src/actions/emit-training-event.ts new file mode 100644 index 0000000..0800fcc --- /dev/null +++ b/libs/training-events/src/actions/emit-training-event.ts @@ -0,0 +1,47 @@ +import { v4 as uuidv4 } from 'uuid'; +import { TrainingEvent, TrainingEventData } from '../types'; +import { + SYSTEM_ID, + TRAINING_COLLECTION, + TRAINING_EVENTS_SUBCOLLECTION, +} from '../config'; +import { getFirestore } from 'firebase-admin/firestore'; +import { FirestoreNotInitialzedException } from '../exception/firestore-not-initialized'; + +export const emitTrainingEvent = async ( + eventData: TrainingEventData, + training: Training | null, + correlationId: string +): Promise => { + const trainingId = eventData.trainingId; + const event: TrainingEvent = { + eventId: uuidv4(), + emittedAt: getDate(), + system: SYSTEM_ID, + correlationId, + ...eventData, + }; + + if (isEmitted[event.name] && isEmitted[event.name](training, event)) { + return training; + } + + const db = getFirestore(); + if (!db) { + throw new FirestoreNotInitialzedException(); + } + const trainingCollection = db.collection(TRAINING_COLLECTION); + + const trainingObj = await trainingCollection.doc(trainingId).get(); + if (!trainingObj.exists) { + await trainingCollection.doc(trainingId).set({ trainingId }); + } + + await trainingCollection + .doc(trainingId) + .collection(TRAINING_EVENTS_SUBCOLLECTION) + .doc(event.eventId) + .set(event); + + return reduceEvent(training, event); +}; diff --git a/libs/training-events/src/actions/get-training-events.ts b/libs/training-events/src/actions/get-training-events.ts new file mode 100644 index 0000000..c3739fd --- /dev/null +++ b/libs/training-events/src/actions/get-training-events.ts @@ -0,0 +1,49 @@ +import { getDatabaseCollection } from "@rovacc/clients"; +import { Training, TrainingEvent } from "@rovacc/training-events-types"; +import { TRAINING_COLLECTION, TRAINING_EVENTS_SUBCOLLECTION } from "../config"; +import { TrainingNotFound } from "../exception/training-not-found"; + +export const getTrainingEvents = async (trainingId: string): Promise => { + const trainingCollection = getDatabaseCollection(TRAINING_COLLECTION) + + const trainingEvents = await trainingCollection + .doc(trainingId) + .collection(TRAINING_EVENTS_SUBCOLLECTION) + .get() + + if (!trainingEvents || !trainingEvents.docs || trainingEvents.docs.length === 0) { + return undefined + } + + return trainingEvents.docs.map(event => event.data() as TrainingEvent) +} + +export const tryGetTrainingEvents = async (trainingId: string): Promise => { + const trainingEvents = await getTrainingEvents(trainingId) + if (!trainingEvents) { + throw new TrainingNotFound(trainingId) + } + return trainingEvents +} + +export const getTraining = async (trainingId: string): Promise => { + const trainingCollection = getDatabaseCollection(TRAINING_COLLECTION) + + const training = await trainingCollection + .doc(trainingId) + .get() + + if (!training.exists) { + return undefined + } + + return training.data() as Training +} + +export const tryGetTraining = async (trainingId: string): Promise => { + const training = await getTraining(trainingId) + if (!training) { + throw new TrainingNotFound(trainingId) + } + return training +} diff --git a/libs/training-events/src/config.ts b/libs/training-events/src/config.ts new file mode 100644 index 0000000..a7a6128 --- /dev/null +++ b/libs/training-events/src/config.ts @@ -0,0 +1,3 @@ +export const SYSTEM_ID = process.env['SYSTEM_ID'] as string +export const TRAINING_COLLECTION = 'training' +export const TRAINING_EVENTS_SUBCOLLECTION = 'events' diff --git a/libs/training-events/src/events/index.ts b/libs/training-events/src/events/index.ts new file mode 100644 index 0000000..6d1d9d1 --- /dev/null +++ b/libs/training-events/src/events/index.ts @@ -0,0 +1,19 @@ +export { trainingCompleted } from "./training-completed" +export { trainingCptPerformed } from "./training-cpt-performed" +export { trainingCptRequested } from "./training-cpt-requested" +export { trainingCptScheduled } from "./training-cpt-scheduled" +export { trainingIntent } from "./training-intent"; +export { trainingIntentConfirmationExpired } from "./training-intent-confirmation-expired"; +export { trainingIntentConfirmationRejected } from "./training-intent-confirmation-rejected"; +export { trainingIntentConfirmationRequested } from "./training-intent-confirmation-requested"; +export { trainingIntentConfirmationResponded } from "./training-intent-confirmation-responded"; +export { trainingMentorAssigned } from "./training-mentor-assigned"; +export { trainingMentorReassigned } from "./training-mentor-reassigned"; +export { trainingSessionPerformed } from "./training-session-performed"; +export { trainingSessionScheduled } from "./training-session-scheduled"; +export { trainingSoloPerformed } from "./training-solo-performed" +export { trainingSoloRequested } from "./training-solo-requested" +export { trainingSoloScheduled } from "./training-solo-scheduled" +export { trainingTestAssigned } from "./training-test-assigned"; +export { trainingTestCompleted } from "./training-test-completed"; +export { reducer, isEmitted, reduceEvent } from "./reducer"; diff --git a/libs/training-events/src/events/reducer.ts b/libs/training-events/src/events/reducer.ts new file mode 100644 index 0000000..0636192 --- /dev/null +++ b/libs/training-events/src/events/reducer.ts @@ -0,0 +1,60 @@ +import { IsEmitted, Reducer, Training, TrainingEvent } from "@rovacc/training-events-types"; +import { trainingCompleted } from "./training-completed"; +import { trainingCptPerformed } from "./training-cpt-performed"; +import { trainingCptRequested } from "./training-cpt-requested"; +import { trainingCptScheduled } from "./training-cpt-scheduled"; +import { trainingIntent } from "./training-intent"; +import { trainingIntentConfirmationExpired } from "./training-intent-confirmation-expired"; +import { trainingIntentConfirmationRejected } from "./training-intent-confirmation-rejected"; +import { trainingIntentConfirmationRequested } from "./training-intent-confirmation-requested"; +import { trainingIntentConfirmationResponded } from "./training-intent-confirmation-responded"; +import { trainingMentorAssigned } from "./training-mentor-assigned"; +import { trainingMentorReassigned } from "./training-mentor-reassigned"; +import { trainingSessionPerformed } from "./training-session-performed"; +import { trainingSessionScheduled } from "./training-session-scheduled"; +import { trainingSoloPerformed } from "./training-solo-performed"; +import { trainingSoloRequested } from "./training-solo-requested"; +import { trainingSoloScheduled } from "./training-solo-scheduled"; +import { trainingTestAssigned } from "./training-test-assigned"; +import { trainingTestCompleted } from "./training-test-completed"; + +const events = [ + trainingCompleted, + trainingCptPerformed, + trainingCptRequested, + trainingCptScheduled, + trainingIntent, + trainingIntentConfirmationExpired, + trainingIntentConfirmationRejected, + trainingIntentConfirmationRequested, + trainingIntentConfirmationResponded, + trainingMentorAssigned, + trainingMentorReassigned, + trainingSessionPerformed, + trainingSessionScheduled, + trainingSoloPerformed, + trainingSoloRequested, + trainingSoloScheduled, + trainingTestAssigned, + trainingTestCompleted, +] as const + +type Names = typeof events[number]['name'] + +export const { reducer, isEmitted } = events.reduce( + (acc, event) => ({ + reducer: { ...acc.reducer, [event.name]: event.reducer }, + isEmitted: { ...acc.isEmitted, [event.name]: event.isEmitted } + }), { reducer: {} as Record, isEmitted: {} as Record }) + +export const reduceEvent = (training: Training | null, event: TrainingEvent): Training | null => { + if (!reducer[event.name]) { + console.log('Event not found', event) + return training + } else { + return !isEmitted[event.name](training, event) + ? reducer[event.name](training, event) + : training + } +} + diff --git a/libs/training-events/src/events/training-completed.ts b/libs/training-events/src/events/training-completed.ts new file mode 100644 index 0000000..c89fe50 --- /dev/null +++ b/libs/training-events/src/events/training-completed.ts @@ -0,0 +1,20 @@ + +import { TrainingCompletedEvent, Reducer, Training } from "@rovacc/training-events-types"; + +const name = 'training-completed' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingCompletedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: event.payload.reason === 'completed' ? 'COMPLETED' : 'TERMINATED', + ...event.payload, + completedAt: event.emittedAt, +}) + +const isEmitted = (training: Training | null) => !!training?.completedAt + +export const trainingCompleted = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-cpt-performed.ts b/libs/training-events/src/events/training-cpt-performed.ts new file mode 100644 index 0000000..169eea0 --- /dev/null +++ b/libs/training-events/src/events/training-cpt-performed.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingCptPerformedEvent } from "@rovacc/training-events-types"; + +const name = 'training-cpt-performed' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingCptPerformedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: event.payload.passed ? 'COMPLETED' : 'AWAITING_CPT', + cpt: { + ...training?.cpt, + ...event.payload + } +}) + +const isEmitted = (training: Training | null) => !!training?.cpt && !!training?.cpt.assessedBy + +export const trainingCptPerformed = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-cpt-requested.ts b/libs/training-events/src/events/training-cpt-requested.ts new file mode 100644 index 0000000..2a4bac0 --- /dev/null +++ b/libs/training-events/src/events/training-cpt-requested.ts @@ -0,0 +1,23 @@ + +import { Reducer, Training, TrainingCptRequestedEvent } from "@rovacc/training-events-types"; + +const name = 'training-cpt-requested' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingCptRequestedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'AWAITING_CPT', + cpt: { + ...training?.cpt, + ...event.payload, + requestedAt: event.emittedAt, + } +}) + +const isEmitted = (training: Training | null) => !!training?.cpt && !!training?.cpt.requestedAt + +export const trainingCptRequested = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-cpt-scheduled.ts b/libs/training-events/src/events/training-cpt-scheduled.ts new file mode 100644 index 0000000..88a778d --- /dev/null +++ b/libs/training-events/src/events/training-cpt-scheduled.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingCptScheduledEvent } from "@rovacc/training-events-types"; + +const name = 'training-cpt-scheduled' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingCptScheduledEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'AWAITING_CPT', + cpt: { + ...training?.cpt, + scheduledAt: event.payload.scheduledAt + } +}) + +const isEmitted = (training: Training | null) => !!training?.cpt && !!training?.cpt.scheduledAt + +export const trainingCptScheduled = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-intent-confirmation-expired.ts b/libs/training-events/src/events/training-intent-confirmation-expired.ts new file mode 100644 index 0000000..277ecf7 --- /dev/null +++ b/libs/training-events/src/events/training-intent-confirmation-expired.ts @@ -0,0 +1,31 @@ +import { + Reducer, + Training, + TrainingIntentConfirmationExpiredEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-intent-confirmation-expired'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingIntentConfirmationExpiredEvent +): Training => ({ + ...training, + status: 'QUEUED', + trainingId: event.trainingId, + intentConfirmation: { + ...training?.intentConfirmation, + expiredAt: event.emittedAt, + }, +}); + +const isEmitted = (training: Training | null) => + training && + training?.intentConfirmation && + !!training.intentConfirmation.expiredAt; + +export const trainingIntentConfirmationExpired = { + name, + reducer, + isEmitted, +} as const; diff --git a/libs/training-events/src/events/training-intent-confirmation-rejected.ts b/libs/training-events/src/events/training-intent-confirmation-rejected.ts new file mode 100644 index 0000000..d80c665 --- /dev/null +++ b/libs/training-events/src/events/training-intent-confirmation-rejected.ts @@ -0,0 +1,31 @@ +import { + Reducer, + Training, + TrainingIntentConfirmationRejectedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-intent-confirmation-rejected'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingIntentConfirmationRejectedEvent +): Training => ({ + ...training, + status: 'QUEUED', + trainingId: event.trainingId, + intentConfirmation: { + ...training?.intentConfirmation, + rejectedAt: event.emittedAt, + }, +}); + +const isEmitted = (training: Training | null) => + training && + training?.intentConfirmation && + training.intentConfirmation.rejectedAt; + +export const trainingIntentConfirmationRejected = { + name, + reducer, + isEmitted, +} as const; diff --git a/libs/training-events/src/events/training-intent-confirmation-requested.ts b/libs/training-events/src/events/training-intent-confirmation-requested.ts new file mode 100644 index 0000000..284fc6e --- /dev/null +++ b/libs/training-events/src/events/training-intent-confirmation-requested.ts @@ -0,0 +1,30 @@ +import { + Reducer, + Training, + TrainingIntentConfirmationRequestedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-intent-confirmation-requested'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingIntentConfirmationRequestedEvent +): Training => ({ + ...training, + status: 'QUEUED', + trainingId: event.trainingId, + intentConfirmation: { + requestedAt: event.emittedAt, + }, +}); + +const isEmitted = (training: Training | null) => + training && + training?.intentConfirmation && + !!training.intentConfirmation.requestedAt; + +export const trainingIntentConfirmationRequested = { + name, + reducer, + isEmitted, +} as const; diff --git a/libs/training-events/src/events/training-intent-confirmation-responded.ts b/libs/training-events/src/events/training-intent-confirmation-responded.ts new file mode 100644 index 0000000..d884c9b --- /dev/null +++ b/libs/training-events/src/events/training-intent-confirmation-responded.ts @@ -0,0 +1,31 @@ +import { + Reducer, + Training, + TrainingIntentConfirmationRespondedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-intent-confirmation-responded'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingIntentConfirmationRespondedEvent +): Training => ({ + ...training, + status: 'QUEUED', + trainingId: event.trainingId, + intentConfirmation: { + ...training?.intentConfirmation, + respondedAt: event.emittedAt, + }, +}); + +const isEmitted = (training: Training | null) => + training && + training?.intentConfirmation && + training.intentConfirmation.respondedAt; + +export const trainingIntentConfirmationResponded = { + name, + reducer, + isEmitted, +} as const; diff --git a/libs/training-events/src/events/training-intent.ts b/libs/training-events/src/events/training-intent.ts new file mode 100644 index 0000000..920486d --- /dev/null +++ b/libs/training-events/src/events/training-intent.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingIntentEvent } from "@rovacc/training-events-types"; + +const name = 'training-intent'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingIntentEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'QUEUED', + purpose: event.payload.purpose, + rating: event.payload.rating, + student: event.payload.student, + requestedAt: event.emittedAt, +}) + +const isEmitted = (training: Training | null) => !!training?.student + +export const trainingIntent = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-mentor-assigned.ts b/libs/training-events/src/events/training-mentor-assigned.ts new file mode 100644 index 0000000..19c4dd2 --- /dev/null +++ b/libs/training-events/src/events/training-mentor-assigned.ts @@ -0,0 +1,26 @@ +import { + Member, + Reducer, + Training, + TrainingMentorAssignedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-mentor-assigned'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingMentorAssignedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'STARTED', + mentor: { + member: event.payload.mentor, + assignedAt: event.emittedAt, + assignedBy: event.payload.assignedBy, + }, +}); + +const isEmitted = (training: Training | null) => !!training?.mentor; + +export const trainingMentorAssigned = { name, reducer, isEmitted } as const; diff --git a/libs/training-events/src/events/training-mentor-reassigned.ts b/libs/training-events/src/events/training-mentor-reassigned.ts new file mode 100644 index 0000000..a45abde --- /dev/null +++ b/libs/training-events/src/events/training-mentor-reassigned.ts @@ -0,0 +1,29 @@ +import { + Member, + Reducer, + Training, + TrainingMentorReassignedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-mentor-reassigned'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingMentorReassignedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'IN_PROGRESS', + mentor: { + member: event.payload.mentor, + assignedAt: event.emittedAt, + assignedBy: event.payload.assignedBy, + }, +}); + +const isEmitted = ( + training: Training | null, + event: TrainingMentorReassignedEvent +) => training && training?.mentor?.member.id !== event.payload.mentor.id; + +export const trainingMentorReassigned = { name, reducer, isEmitted } as const; diff --git a/libs/training-events/src/events/training-session-performed.ts b/libs/training-events/src/events/training-session-performed.ts new file mode 100644 index 0000000..bd70160 --- /dev/null +++ b/libs/training-events/src/events/training-session-performed.ts @@ -0,0 +1,34 @@ +import { + Reducer, + Training, + TrainingSessionPerformedEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-session-performed'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingSessionPerformedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'IN_PROGRESS', + sessions: { + ...training?.sessions, + [event.payload.sessionId]: { + ...training?.sessions?.[event.payload.sessionId], + mentor: event.payload.mentor, + report: event.payload.report, + }, + }, +}); + +const isEmitted = ( + training: Training | null, + event: TrainingSessionPerformedEvent +) => + !!training?.sessions && + !!training.sessions[event.payload.sessionId] && + !!training.sessions[event.payload.sessionId].report; + +export const trainingSessionPerformed = { name, reducer, isEmitted } as const; diff --git a/libs/training-events/src/events/training-session-scheduled.ts b/libs/training-events/src/events/training-session-scheduled.ts new file mode 100644 index 0000000..1a0e970 --- /dev/null +++ b/libs/training-events/src/events/training-session-scheduled.ts @@ -0,0 +1,33 @@ +import { + Reducer, + Training, + TrainingSessionScheduledEvent, +} from '@rovacc/training-events-types'; + +const name = 'training-session-scheduled'; + +const reducer: Reducer = ( + training: Training | null, + event: TrainingSessionScheduledEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'IN_PROGRESS', + sessions: { + ...training?.sessions, + [event.payload.sessionId]: { + ...training?.sessions?.[event.payload.sessionId], + scheduledAt: event.payload.scheduledAt, + }, + }, +}); + +const isEmitted = ( + training: Training | null, + event: TrainingSessionScheduledEvent +) => + !!training?.sessions && + !!training.sessions[event.payload.sessionId] && + !!training.sessions[event.payload.sessionId].scheduledAt; + +export const trainingSessionScheduled = { name, reducer, isEmitted } as const; diff --git a/libs/training-events/src/events/training-solo-performed.ts b/libs/training-events/src/events/training-solo-performed.ts new file mode 100644 index 0000000..58458d6 --- /dev/null +++ b/libs/training-events/src/events/training-solo-performed.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingSoloPerformedEvent } from "@rovacc/training-events-types"; + +const name = 'training-solo-performed' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingSoloPerformedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: event.payload.passed ? 'SOLO' : 'AWAITING_SOLO', + solo: { + ...training?.solo, + ...event.payload, + } +}) + +const isEmitted = (training: Training | null) => !!training?.solo && !!training?.solo.performedBy + +export const trainingSoloPerformed = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-solo-requested.ts b/libs/training-events/src/events/training-solo-requested.ts new file mode 100644 index 0000000..be101b1 --- /dev/null +++ b/libs/training-events/src/events/training-solo-requested.ts @@ -0,0 +1,23 @@ + +import { Reducer, Training, TrainingSoloRequestedEvent } from "@rovacc/training-events-types"; + +const name = 'training-solo-requested' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingSoloRequestedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'AWAITING_SOLO', + solo: { + ...training?.solo, + requestedAt: event.emittedAt, + requestedBy: event.payload.requestedBy + } +}) + +const isEmitted = (training: Training | null) => !!training?.solo && !!training?.solo.requestedAt + +export const trainingSoloRequested = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-solo-scheduled.ts b/libs/training-events/src/events/training-solo-scheduled.ts new file mode 100644 index 0000000..72b5f27 --- /dev/null +++ b/libs/training-events/src/events/training-solo-scheduled.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingSoloScheduledEvent } from "@rovacc/training-events-types"; + +const name = 'training-solo-scheduled' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingSoloScheduledEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: 'AWAITING_SOLO', + solo: { + ...training?.solo, + scheduledAt: event.emittedAt, + } +}) + +const isEmitted = (training: Training | null) => !!training?.solo && !!training?.solo.scheduledAt + +export const trainingSoloScheduled = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-test-assigned.ts b/libs/training-events/src/events/training-test-assigned.ts new file mode 100644 index 0000000..a5c2a50 --- /dev/null +++ b/libs/training-events/src/events/training-test-assigned.ts @@ -0,0 +1,22 @@ + +import { Reducer, Training, TrainingTestAssignedEvent } from "@rovacc/training-events-types"; + +const name = 'training-test-assigned' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingTestAssignedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: "AWAITING_TEST", + test: { + assignedAt: event.emittedAt, + assignedBy: event.payload.assignedBy, + } +}) + +const isEmitted = (training: Training | null) => !!training?.test + +export const trainingTestAssigned = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/events/training-test-completed.ts b/libs/training-events/src/events/training-test-completed.ts new file mode 100644 index 0000000..cea01e0 --- /dev/null +++ b/libs/training-events/src/events/training-test-completed.ts @@ -0,0 +1,23 @@ +import { Reducer, Training, TrainingTestCompletedEvent } from "@rovacc/training-events-types"; + +const name = 'training-test-completed' + +const reducer: Reducer = ( + training: Training | null, + event: TrainingTestCompletedEvent +): Training => ({ + ...training, + trainingId: event.trainingId, + status: event.payload.passed ? "IN_PROGRESS" : "AWAITING_TEST", + test: { + ...training?.test, + completedAt: event.emittedAt, + result: event.payload.result, + willExpireAt: event.payload.willExpireAt + } +}) + +const isEmitted = (training: Training | null) => !!training?.test && !!training?.test?.completedAt + +export const trainingTestCompleted = { name, reducer, isEmitted } as const + diff --git a/libs/training-events/src/exception/firestore-not-initialized.ts b/libs/training-events/src/exception/firestore-not-initialized.ts new file mode 100644 index 0000000..ab227ee --- /dev/null +++ b/libs/training-events/src/exception/firestore-not-initialized.ts @@ -0,0 +1,5 @@ +export class FirestoreNotInitialzedException extends Error { + constructor() { + super('Firestore is not initialized'); + } +} diff --git a/libs/training-events/src/exception/not-recognised-event.ts b/libs/training-events/src/exception/not-recognised-event.ts new file mode 100644 index 0000000..32bab87 --- /dev/null +++ b/libs/training-events/src/exception/not-recognised-event.ts @@ -0,0 +1,5 @@ +export class NotRecognisedEvent extends Error { + constructor(eventName: string) { + super(`Event "${eventName}" is not recognised, consider redeploying the service with the new events logic.`); + } +} diff --git a/libs/training-events/src/exception/training-not-found.ts b/libs/training-events/src/exception/training-not-found.ts new file mode 100644 index 0000000..ed55f89 --- /dev/null +++ b/libs/training-events/src/exception/training-not-found.ts @@ -0,0 +1,5 @@ +export class TrainingNotFound extends Error { + constructor(trainingId: string) { + super(`Training ${trainingId} not found`); + } +} diff --git a/libs/training-events/src/helpers/get-date.ts b/libs/training-events/src/helpers/get-date.ts new file mode 100644 index 0000000..725e1eb --- /dev/null +++ b/libs/training-events/src/helpers/get-date.ts @@ -0,0 +1 @@ +export const getDate = () => new Date() diff --git a/libs/training-events/src/index.ts b/libs/training-events/src/index.ts new file mode 100644 index 0000000..cbea9ad --- /dev/null +++ b/libs/training-events/src/index.ts @@ -0,0 +1,2 @@ +export * from './events'; +export { reduceEvent } from './events/reducer'; diff --git a/libs/training-events/src/types.ts b/libs/training-events/src/types.ts new file mode 100644 index 0000000..dbeb6c0 --- /dev/null +++ b/libs/training-events/src/types.ts @@ -0,0 +1,158 @@ +import { TrainingCompletedEvent, TrainingCompletedEventData } from "./events/training-completed" +import { TrainingCptPerformedEvent, TrainingCptPerformedEventData } from "./events/training-cpt-performed" +import { TrainingCptRequestedEvent, TrainingCptRequestedEventData } from "./events/training-cpt-requested" +import { TrainingCptScheduledEvent, TrainingCptScheduledEventData } from "./events/training-cpt-scheduled" +import { TrainingIntentEvent, TrainingIntentEventData } from "./events/training-intent" +import { TrainingIntentConfirmationExpiredEvent, TrainingIntentConfirmationExpiredEventData } from "./events/training-intent-confirmation-expired" +import { TrainingIntentConfirmationRejectedEvent, TrainingIntentConfirmationRejectedEventData } from "./events/training-intent-confirmation-rejected" +import { TrainingIntentConfirmationRequestedEvent, TrainingIntentConfirmationRequestedEventData } from "./events/training-intent-confirmation-requested" +import { TrainingIntentConfirmationRespondedEvent, TrainingIntentConfirmationRespondedEventData } from "./events/training-intent-confirmation-responded" +import { TrainingMentorAssignedEvent, TrainingMentorAssignedEventData } from "./events/training-mentor-assigned" +import { TrainingMentorReassignedEvent, TrainingMentorReassignedEventData } from "./events/training-mentor-reassigned" +import { TrainingSessionPerformedEvent, TrainingSessionPerformedEventData } from "./events/training-session-performed" +import { TrainingSessionScheduledEvent, TrainingSessionScheduledEventData } from "./events/training-session-scheduled" +import { TrainingSoloPerformedEvent, TrainingSoloPerformedEventData } from "./events/training-solo-performed" +import { TrainingSoloRequestedEvent, TrainingSoloRequestedEventData } from "./events/training-solo-requested" +import { TrainingSoloScheduledEvent, TrainingSoloScheduledEventData } from "./events/training-solo-scheduled" +import { TrainingTestAssignedEvent, TrainingTestAssignedEventData } from "./events/training-test-assigned" +import { TrainingTestCompletedEvent, TrainingTestCompletedEventData } from "./events/training-test-completed" + +export type TrainingEvent = + | TrainingCompletedEvent + | TrainingCptPerformedEvent + | TrainingCptScheduledEvent + | TrainingIntentConfirmationExpiredEvent + | TrainingIntentConfirmationRejectedEvent + | TrainingIntentConfirmationRequestedEvent + | TrainingIntentConfirmationRespondedEvent + | TrainingIntentEvent + | TrainingMentorAssignedEvent + | TrainingMentorReassignedEvent + | TrainingSessionPerformedEvent + | TrainingSessionScheduledEvent + | TrainingSoloPerformedEvent + | TrainingSoloRequestedEvent + | TrainingSoloScheduledEvent + | TrainingTestAssignedEvent + | TrainingTestCompletedEvent + | TrainingCptRequestedEvent + +export type TrainingEventData = + | TrainingCompletedEventData + | TrainingCptPerformedEventData + | TrainingCptScheduledEventData + | TrainingIntentConfirmationExpiredEventData + | TrainingIntentConfirmationRejectedEventData + | TrainingIntentConfirmationRequestedEventData + | TrainingIntentConfirmationRespondedEventData + | TrainingIntentEventData + | TrainingMentorAssignedEventData + | TrainingMentorReassignedEventData + | TrainingSessionPerformedEventData + | TrainingSessionScheduledEventData + | TrainingSoloPerformedEventData + | TrainingSoloRequestedEventData + | TrainingSoloScheduledEventData + | TrainingTestAssignedEventData + | TrainingTestCompletedEventData + | TrainingCptRequestedEventData + +export type Member = { + id: number + name: string +} + +export type TrainingStatus = + | "QUEUED" + | "STARTED" + | "AWAITING_TEST" + | "IN_PROGRESS" + | "AWAITING_SOLO" + | "SOLO" + | "AWAITING_CPT" + | "COMPLETED" + | "TERMINATED" + +export type TrainingPurpose = "acquire_rating" | "revalidate_rating" | "visiting" +export type TestResult = "passed" | "failed" +export type TrainingReport = { + purpose: "cpt_ots" | "revalidation" | "training" | "sim_session" + workload: "light" | 'moderate' | 'heavy' + complexity: "routine" | "occasionally_difficult" | "mostly_difficult" | "very_difficult" + traffic: "light" | "medium" | "heavy" + comments: string +} + +export type TrainingSessions = { + scheduledAt?: Date + mentor?: Member, + report?: TrainingReport +} + +export type OutcomeReason = + | "completed" + | "terminated" + +export type OutcomeReasonDetailed = + | "rating_upgraded" + | "student_did_not_confirm_intent" + | "student_cancelled_training" + | "atsimtest_expired" + +export type Training = { + trainingId: string + status: TrainingStatus, + requestedAt?: Date + student?: number + rating?: number + purpose?: TrainingPurpose + mentor?: { + member: Member + assignedBy: Member + assignedAt: Date + } + intentConfirmation?: { + requestedAt?: Date + respondedAt?: Date + rejectedAt?: Date + expiredAt?: Date + } + test?: { + assignedAt?: Date + assignedBy?: Member + completedAt?: Date + result?: TestResult + willExpireAt?: Date + passed?: boolean + } + sessions?: Record + solo?: { + requestedAt?: Date + requestedBy?: Member + scheduledAt?: Date + performedBy?: Member + report?: TrainingReport + passed?: boolean + } + cpt?: { + requestedAt?: Date + scheduledAt?: Date + assessedBy?: Member + report?: TrainingReport + passed?: boolean + } + completedAt?: Date + outcome?: OutcomeReason + outcomeDetailed?: OutcomeReasonDetailed +} + +export type Reducer = (training: Training | null, event: T) => Training +export type IsEmitted = (training: Training | null, event: T) => boolean + +export type TrainingEventMetadata = { + eventId: string + emittedAt: Date + system: string + correlationId: string +} + diff --git a/libs/training-events/test/setup-test.ts b/libs/training-events/test/setup-test.ts new file mode 100644 index 0000000..17f0e01 --- /dev/null +++ b/libs/training-events/test/setup-test.ts @@ -0,0 +1,6 @@ +import { initializeApp } from "firebase-admin"; +import { beforeAll } from "vitest"; + +beforeAll(() => { + initializeApp() +}) diff --git a/libs/training-events/tsconfig.json b/libs/training-events/tsconfig.json new file mode 100644 index 0000000..bdf594c --- /dev/null +++ b/libs/training-events/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/training-events/tsconfig.lib.json b/libs/training-events/tsconfig.lib.json new file mode 100644 index 0000000..33eca2c --- /dev/null +++ b/libs/training-events/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/training-events/tsconfig.spec.json b/libs/training-events/tsconfig.spec.json new file mode 100644 index 0000000..6d3be74 --- /dev/null +++ b/libs/training-events/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"] + }, + "include": [ + "vite.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/libs/training-events/vite.config.ts b/libs/training-events/vite.config.ts new file mode 100644 index 0000000..32db02f --- /dev/null +++ b/libs/training-events/vite.config.ts @@ -0,0 +1,60 @@ +/// +import { defineConfig } from 'vite'; + +import viteTsConfigPaths from 'vite-tsconfig-paths'; +import dts from 'vite-plugin-dts'; +import { joinPathFragments } from '@nx/devkit'; + +export default defineConfig({ + cacheDir: '../../node_modules/.vite/training-events', + + plugins: [ + dts({ + entryRoot: 'src', + tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), + skipDiagnostics: true, + }), + + viteTsConfigPaths({ + root: '../../', + }), + ], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ + // viteTsConfigPaths({ + // root: '../../', + // }), + // ], + // }, + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'training-events', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [], + }, + }, + + test: { + globals: true, + testTimeout: 20000, + cache: { + dir: '../../node_modules/.vitest', + }, + setupFiles: ['./test/setup-test.ts'], + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, +});