@event-driven-io/emmett-fastify 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2YZWWRCS.js +2 -0
- package/dist/chunk-2YZWWRCS.js.map +1 -0
- package/dist/chunk-7VVTVM6G.js +2 -0
- package/dist/chunk-7VVTVM6G.js.map +1 -0
- package/dist/chunk-F2UPJ4YQ.mjs +2 -0
- package/dist/chunk-F2UPJ4YQ.mjs.map +1 -0
- package/dist/chunk-M4ABRLXM.mjs +2 -0
- package/dist/chunk-M4ABRLXM.mjs.map +1 -0
- package/dist/e2e/decider/api.d.mts +4 -18
- package/dist/e2e/decider/api.d.ts +4 -18
- package/dist/e2e/decider/api.js +1 -99
- package/dist/e2e/decider/api.js.map +1 -1
- package/dist/e2e/decider/api.mjs +1 -99
- package/dist/e2e/decider/api.mjs.map +1 -1
- package/dist/e2e/decider/businessLogic.js +1 -110
- package/dist/e2e/decider/businessLogic.js.map +1 -1
- package/dist/e2e/decider/businessLogic.mjs +1 -110
- package/dist/e2e/decider/businessLogic.mjs.map +1 -1
- package/dist/e2e/decider/shoppingCart.js +1 -96
- package/dist/e2e/decider/shoppingCart.js.map +1 -1
- package/dist/e2e/decider/shoppingCart.mjs +1 -96
- package/dist/e2e/decider/shoppingCart.mjs.map +1 -1
- package/dist/e2e/testing.js +1 -26
- package/dist/e2e/testing.js.map +1 -1
- package/dist/e2e/testing.mjs +1 -26
- package/dist/e2e/testing.mjs.map +1 -1
- package/dist/index.js +1 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var d=(t,e,o,s,a=()=>{})=>{let r=!1,i=t.map(n=>o(n)?(r=!0,s(n)):n).filter(n=>n!==void 0).map(n=>{if(!n)throw Error("That should not happen");return n});return!r&&a()!==void 0?[...t,e]:i};var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert);var c=class E extends Error{constructor(e){let o=e&&typeof e=="object"&&"errorCode"in e?e.errorCode:g(e)?e:500,s=e&&typeof e=="object"&&"message"in e?e.message:S(e)?e:`Error with status code '${o}' ocurred during Emmett processing`;super(s),this.errorCode=o,Object.setPrototypeOf(this,E.prototype)}};var u=class y extends c{constructor(e){super({errorCode:400,message:_nullishCoalesce(e, () => ("Validation Error ocurred during Emmett processing"))}),Object.setPrototypeOf(this,y.prototype)}},T= exports.b =class N extends c{constructor(e){super({errorCode:403,message:_nullishCoalesce(e, () => ("Illegal State ocurred during Emmett processing"))}),Object.setPrototypeOf(this,N.prototype)}};var P=(t=>(t.NOT_A_NONEMPTY_STRING="NOT_A_NONEMPTY_STRING",t.NOT_A_POSITIVE_NUMBER="NOT_A_POSITIVE_NUMBER",t.NOT_AN_UNSIGNED_BIGINT="NOT_AN_UNSIGNED_BIGINT",t))(P||{}),g=t=>typeof t=="number"&&t===t,S=t=>typeof t=="string",x= exports.c =t=>{if(!S(t)||t.length===0)throw new u("NOT_A_NONEMPTY_STRING");return t},A= exports.d =t=>{if(!g(t)||t<=0)throw new u("NOT_A_POSITIVE_NUMBER");return t};var l="STREAM_DOES_NOT_EXIST",h="NO_CONCURRENCY_CHECK";var C=(t,e,o=s=>s)=>async(s,a,r,i)=>{let n=o(a),p=await s.aggregateStream(n,{evolve:t,getInitialState:e,read:{expectedStreamVersion:_nullishCoalesce(_optionalChain([i, 'optionalAccess', _2 => _2.expectedStreamVersion]), () => (h))}}),I=_nullishCoalesce(_optionalChain([p, 'optionalAccess', _3 => _3.state]), () => (e())),O=_optionalChain([p, 'optionalAccess', _4 => _4.currentStreamVersion]),m=r(I),f=Array.isArray(m)?m:[m],_=_nullishCoalesce(_nullishCoalesce(_optionalChain([i, 'optionalAccess', _5 => _5.expectedStreamVersion]), () => (O)), () => (l));return{...await s.appendToStream(n,f,{expectedStreamVersion:_}),newState:f.reduce(t,I)}};var b=({decide:t,evolve:e,getInitialState:o},s=a=>a)=>async(a,r,i,n)=>C(e,o,s)(a,r,p=>t(i,p),n);var _crypto = require('crypto');var D=(a=>(a.Empty="Empty",a.Pending="Pending",a.Confirmed="Confirmed",a.Canceled="Canceled",a))(D||{}),R= exports.g ={status:"Empty"},M= exports.h =(t,{type:e,data:o})=>{switch(e){case"ShoppingCartOpened":return{id:o.shoppingCartId,clientId:o.clientId,openedAt:o.openedAt,productItems:[],status:"Pending"};case"ProductItemAddedToShoppingCart":{if(t.status!=="Pending")return t;let{productItems:s}=t,{productItem:a}=o;return{...t,productItems:d(s,a,r=>r.productId===a.productId&&r.unitPrice===a.unitPrice,r=>({...r,quantity:r.quantity+a.quantity}),()=>a)}}case"ProductItemRemovedFromShoppingCart":{if(t.status!=="Pending")return t;let{productItems:s}=t,{productItem:a}=o;return{...t,productItems:d(s,a,r=>r.productId===a.productId&&r.unitPrice===a.unitPrice,r=>({...r,quantity:r.quantity-a.quantity}))}}case"ShoppingCartConfirmed":return t.status!=="Pending"?t:{...t,status:"Confirmed",confirmedAt:o.confirmedAt};case"ShoppingCartCanceled":return t.status!=="Pending"?t:{...t,status:"Canceled",canceledAt:o.canceledAt}}},Ut= exports.i =t=>t.reduce(M,R),V= exports.j =(r=>(r.CART_IS_ALREADY_CLOSED="CART_IS_ALREADY_CLOSED",r.PRODUCT_ITEM_NOT_FOUND="PRODUCT_ITEM_NOT_FOUND",r.CART_IS_EMPTY="CART_IS_EMPTY",r.UNKNOWN_EVENT_TYPE="UNKNOWN_EVENT_TYPE",r.UNKNOWN_COMMAND_TYPE="UNKNOWN_COMMAND_TYPE",r))(V||{});exports.a = c; exports.b = T; exports.c = x; exports.d = A; exports.e = b; exports.f = D; exports.g = R; exports.h = M; exports.i = Ut; exports.j = V;
|
|
2
|
+
//# sourceMappingURL=chunk-2YZWWRCS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../emmett/src/utils/merge.ts","../../emmett/src/testing/deciderSpecification.ts","../../emmett/src/errors/index.ts","../../emmett/src/validation/index.ts","../../emmett/src/eventStore/expectedVersion.ts","../../emmett/src/commandHandling/handleCommand.ts","../../emmett/src/commandHandling/handleCommandWithDecider.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../src/e2e/decider/shoppingCart.ts"],"names":["merge","array","item","where","onExisting","onNotFound","wasFound","result","p","assert","EmmettError","_EmmettError","options","errorCode","isNumber","message","isString","ValidationError","_ValidationError","IllegalStateError","_IllegalStateError","ValidationErrors","val","assertNotEmptyString","value","assertPositiveNumber","STREAM_DOES_NOT_EXIST","NO_CONCURRENCY_CHECK","CommandHandler","evolve","getInitialState","mapToStreamId","id","eventStore","handle","streamName","aggregationResult","state","currentStreamVersion","newEvents","expectedStreamVersion","DeciderCommandHandler","decide","command","randomUUID","ShoppingCartStatus","emptyShoppingCart","type","event","productItems","productItem","i","getShoppingCart","events","ShoppingCartErrors"],"mappings":"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","sourcesContent":["export const merge = <T>(\n array: T[],\n item: T,\n where: (current: T) => boolean,\n onExisting: (current: T) => T,\n onNotFound: () => T | undefined = () => undefined,\n) => {\n let wasFound = false;\n\n const result = array\n // merge the existing item if matches condition\n .map((p: T) => {\n if (!where(p)) return p;\n\n wasFound = true;\n return onExisting(p);\n })\n // filter out item if undefined was returned\n // for cases of removal\n .filter((p) => p !== undefined)\n // make TypeScript happy\n .map((p) => {\n if (!p) throw Error('That should not happen');\n\n return p;\n });\n\n // if item was not found and onNotFound action is defined\n // try to generate new item\n if (!wasFound) {\n const result = onNotFound();\n\n if (result !== undefined) return [...array, item];\n }\n\n return result;\n};\n","import assert from 'assert';\n\nexport type DeciderSpecfication<Command, Event> = (\n givenEvents: Event | Event[],\n) => {\n when: (command: Command) => {\n then: (expectedEvents: Event | Event[]) => void;\n thenThrows: <Error>(assert: (error: Error) => boolean) => void;\n };\n};\n\nexport const DeciderSpecification = {\n for: <Command, Event, State>(decider: {\n decide: (command: Command, state: State) => Event | Event[];\n evolve: (state: State, event: Event) => State;\n initialState: () => State;\n }): DeciderSpecfication<Command, Event> => {\n {\n return (givenEvents: Event | Event[]) => {\n return {\n when: (command: Command) => {\n const handle = () => {\n const existingEvents = Array.isArray(givenEvents)\n ? givenEvents\n : [givenEvents];\n\n const currentState = existingEvents.reduce<State>(\n decider.evolve,\n decider.initialState(),\n );\n\n return decider.decide(command, currentState);\n };\n\n return {\n then: (expectedEvents: Event | Event[]): void => {\n const resultEvents = handle();\n\n const resultEventsArray = Array.isArray(resultEvents)\n ? resultEvents\n : [resultEvents];\n\n const expectedEventsArray = Array.isArray(expectedEvents)\n ? expectedEvents\n : [expectedEvents];\n\n assert.deepEqual(resultEventsArray, expectedEventsArray);\n },\n thenThrows: <Error>(check?: (error: Error) => boolean): void => {\n try {\n handle();\n assert.fail('Handler did not fail as expected');\n } catch (error) {\n if (check) assert.ok(check(error as Error));\n }\n },\n };\n },\n };\n };\n }\n },\n};\n","import { isNumber, isString } from '../validation';\n\nexport class EmmettError extends Error {\n public errorCode: number;\n\n constructor(\n options?: { errorCode: number; message?: string } | string | number,\n ) {\n const errorCode =\n options && typeof options === 'object' && 'errorCode' in options\n ? options.errorCode\n : isNumber(options)\n ? options\n : 500;\n const message =\n options && typeof options === 'object' && 'message' in options\n ? options.message\n : isString(options)\n ? options\n : `Error with status code '${errorCode}' ocurred during Emmett processing`;\n\n super(message);\n this.errorCode = errorCode;\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, EmmettError.prototype);\n }\n}\n\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: 412,\n message:\n message ??\n `Expected version ${expected.toString()} does not match current ${current?.toString()}`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyError.prototype);\n }\n}\n\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 400,\n message: message ?? `Validation Error ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class IllegalStateError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 403,\n message: message ?? `Illegal State ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, IllegalStateError.prototype);\n }\n}\n\nexport class NotFoundError extends EmmettError {\n constructor(options?: { id: string; type: string; message?: string }) {\n super({\n errorCode: 404,\n message:\n options?.message ??\n (options?.id\n ? options.type\n ? `${options.type} with ${options.id} was not found during Emmett processing`\n : `State with ${options.id} was not found during Emmett processing`\n : options?.type\n ? `${options.type} was not found during Emmett processing`\n : 'State was not found during Emmett processing'),\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n}\n","import { ValidationError } from '../errors';\n\nexport const enum ValidationErrors {\n NOT_A_NONEMPTY_STRING = 'NOT_A_NONEMPTY_STRING',\n NOT_A_POSITIVE_NUMBER = 'NOT_A_POSITIVE_NUMBER',\n NOT_AN_UNSIGNED_BIGINT = 'NOT_AN_UNSIGNED_BIGINT',\n}\n\nexport const isNumber = (val: unknown): val is number =>\n typeof val === 'number' && val === val;\n\nexport const isString = (val: unknown): val is string =>\n typeof val === 'string';\n\nexport const assertNotEmptyString = (value: unknown): string => {\n if (!isString(value) || value.length === 0) {\n throw new ValidationError(ValidationErrors.NOT_A_NONEMPTY_STRING);\n }\n return value;\n};\n\nexport const assertPositiveNumber = (value: unknown): number => {\n if (!isNumber(value) || value <= 0) {\n throw new ValidationError(ValidationErrors.NOT_A_POSITIVE_NUMBER);\n }\n return value;\n};\n\nexport const assertUnsignedBigInt = (value: string): bigint => {\n const number = BigInt(value);\n if (number < 0) {\n throw new ValidationError(ValidationErrors.NOT_AN_UNSIGNED_BIGINT);\n }\n return number;\n};\n","import { ConcurrencyError } from '../errors';\nimport type { Flavour } from '../typing';\nimport type { DefaultStreamVersionType } from './eventStore';\n\nexport type ExpectedStreamVersion<VersionType = DefaultStreamVersionType> =\n | ExpectedStreamVersionWithValue<VersionType>\n | ExpectedStreamVersionGeneral;\n\nexport type ExpectedStreamVersionWithValue<\n VersionType = DefaultStreamVersionType,\n> = Flavour<VersionType, 'StreamVersion'>;\n\nexport type ExpectedStreamVersionGeneral = Flavour<\n 'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK',\n 'StreamVersion'\n>;\n\nexport const STREAM_EXISTS = 'STREAM_EXISTS' as ExpectedStreamVersionGeneral;\nexport const STREAM_DOES_NOT_EXIST =\n 'STREAM_DOES_NOT_EXIST' as ExpectedStreamVersionGeneral;\nexport const NO_CONCURRENCY_CHECK =\n 'NO_CONCURRENCY_CHECK' as ExpectedStreamVersionGeneral;\n\nexport const matchesExpectedVersion = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion>,\n): boolean => {\n if (expected === NO_CONCURRENCY_CHECK) return true;\n\n if (expected == STREAM_DOES_NOT_EXIST) return current === undefined;\n\n if (expected == STREAM_EXISTS) return current !== undefined;\n\n return current === expected;\n};\n\nexport const assertExpectedVersionMatchesCurrent = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion> | undefined,\n): void => {\n expected ??= NO_CONCURRENCY_CHECK;\n\n if (!matchesExpectedVersion(current, expected))\n throw new ExpectedVersionConflictError(current, expected);\n};\n\nexport class ExpectedVersionConflictError<\n VersionType = DefaultStreamVersionType,\n> extends ConcurrencyError {\n constructor(\n current: VersionType | undefined,\n expected: ExpectedStreamVersion<VersionType>,\n ) {\n super(current?.toString(), expected?.toString());\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import {\n NO_CONCURRENCY_CHECK,\n STREAM_DOES_NOT_EXIST,\n type AppendToStreamResult,\n type DefaultStreamVersionType,\n type EventStore,\n type ExpectedStreamVersion,\n} from '../eventStore';\nimport type { Event } from '../typing';\n\n// #region command-handler\nexport type CommandHandlerResult<\n State,\n StreamVersion = DefaultStreamVersionType,\n> = AppendToStreamResult<StreamVersion> & { newState: State };\n\nexport const CommandHandler =\n <State, StreamEvent extends Event, StreamVersion = DefaultStreamVersionType>(\n evolve: (state: State, event: StreamEvent) => State,\n getInitialState: () => State,\n mapToStreamId: (id: string) => string = (id) => id,\n ) =>\n async (\n eventStore: EventStore<StreamVersion>,\n id: string,\n handle: (state: State) => StreamEvent | StreamEvent[],\n options?: {\n expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;\n },\n ): Promise<CommandHandlerResult<State, StreamVersion>> => {\n const streamName = mapToStreamId(id);\n\n // 1. Aggregate the stream\n const aggregationResult = await eventStore.aggregateStream<\n State,\n StreamEvent\n >(streamName, {\n evolve,\n getInitialState,\n read: {\n // expected stream version is passed to fail fast\n // if stream is in the wrong state\n expectedStreamVersion:\n options?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n });\n\n // 2. Use the aggregate state or the initial one (when e.g. stream does not exist)\n const state = aggregationResult?.state ?? getInitialState();\n const currentStreamVersion = aggregationResult?.currentStreamVersion;\n\n // 3. Run business logic\n const result = handle(state);\n\n const newEvents = Array.isArray(result) ? result : [result];\n\n // Either use:\n // - provided expected stream version,\n // - current stream version got from stream aggregation,\n // - or expect stream not to exists otherwise.\n const expectedStreamVersion: ExpectedStreamVersion<StreamVersion> =\n options?.expectedStreamVersion ??\n currentStreamVersion ??\n STREAM_DOES_NOT_EXIST;\n\n // 4. Append result to the stream\n const appendResult = await eventStore.appendToStream(\n streamName,\n newEvents,\n {\n expectedStreamVersion,\n },\n );\n\n // 5. Return result with updated state\n return { ...appendResult, newState: newEvents.reduce(evolve, state) };\n };\n// #endregion command-handler\n","import type {\n DefaultStreamVersionType,\n EventStore,\n ExpectedStreamVersion,\n} from '../eventStore';\nimport type { Command, Event } from '../typing';\nimport type { Decider } from '../typing/decider';\nimport { CommandHandler } from './handleCommand';\n\n// #region command-handler\nexport const DeciderCommandHandler =\n <\n State,\n CommandType extends Command,\n StreamEvent extends Event,\n StreamVersion = DefaultStreamVersionType,\n >(\n {\n decide,\n evolve,\n getInitialState,\n }: Decider<State, CommandType, StreamEvent>,\n mapToStreamId: (id: string) => string = (id) => id,\n ) =>\n async (\n eventStore: EventStore<StreamVersion>,\n id: string,\n command: CommandType,\n options?: {\n expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;\n },\n ) =>\n CommandHandler<State, StreamEvent, StreamVersion>(\n evolve,\n getInitialState,\n mapToStreamId,\n )(eventStore, id, (state) => decide(command, state), options);\n// #endregion command-handler\n","import { randomUUID } from 'crypto';\nimport type { Event } from '../typing';\nimport {\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type EventStore,\n type ReadStreamOptions,\n type ReadStreamResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\n\nexport type EventMetadata = Readonly<{\n eventId: string;\n streamPosition: number;\n logPosition: bigint;\n}>;\n\nexport type EventEnvelope<E extends Event = Event> = {\n event: E;\n metadata: EventMetadata;\n};\n\nexport type EventHandler<E extends Event = Event> = (\n eventEnvelope: EventEnvelope<E>,\n) => void;\n\nexport const getInMemoryEventStore = (): EventStore => {\n const streams = new Map<string, EventEnvelope[]>();\n\n const getAllEventsCount = () => {\n return Array.from<EventEnvelope[]>(streams.values())\n .map((s) => s.length)\n .reduce((p, c) => p + c, 0);\n };\n\n return {\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType>,\n ): Promise<AggregateStreamResult<State> | null> {\n const { evolve, getInitialState, read } = options;\n\n const result = await this.readStream<EventType>(streamName, read);\n\n if (!result) return null;\n\n const events = result?.events ?? [];\n\n return {\n currentStreamVersion: BigInt(events.length),\n state: events.reduce(evolve, getInitialState()),\n };\n },\n\n readStream: <EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<ReadStreamResult<EventType>> => {\n const events = streams.get(streamName);\n const currentStreamVersion = events ? BigInt(events.length) : undefined;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n );\n\n const from = Number(options && 'from' in options ? options.from : 0);\n const to = Number(\n options && 'to' in options\n ? options.to\n : options && 'maxCount' in options && options.maxCount\n ? options.from + options.maxCount\n : events?.length ?? 1,\n );\n\n const resultEvents =\n events && events.length > 0\n ? events.map((e) => e.event as EventType).slice(from, to)\n : [];\n\n const result: ReadStreamResult<EventType> =\n events && events.length > 0\n ? {\n currentStreamVersion: currentStreamVersion!,\n events: resultEvents,\n }\n : null;\n\n return Promise.resolve(result);\n },\n\n appendToStream: <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult> => {\n const currentEvents = streams.get(streamName) ?? [];\n const currentStreamVersion =\n currentEvents.length > 0 ? BigInt(currentEvents.length) : undefined;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n );\n\n const eventEnvelopes: EventEnvelope[] = events.map((event, index) => {\n return {\n event,\n metadata: {\n eventId: randomUUID(),\n streamPosition: currentEvents.length + index + 1,\n logPosition: BigInt(getAllEventsCount() + index + 1),\n },\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n eventEnvelopes.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [...currentEvents, ...eventEnvelopes]);\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n };\n\n return Promise.resolve(result);\n },\n };\n};\n","import { merge } from '@event-driven-io/emmett';\n\nexport interface ProductItem {\n productId: string;\n quantity: number;\n}\n\nexport type PricedProductItem = ProductItem & {\n unitPrice: number;\n};\n\nexport type ShoppingCartEvent =\n | {\n type: 'ShoppingCartOpened';\n data: {\n shoppingCartId: string;\n clientId: string;\n openedAt: Date;\n };\n }\n | {\n type: 'ProductItemAddedToShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n }\n | {\n type: 'ProductItemRemovedFromShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n }\n | {\n type: 'ShoppingCartConfirmed';\n data: {\n shoppingCartId: string;\n confirmedAt: Date;\n };\n }\n | {\n type: 'ShoppingCartCanceled';\n data: {\n shoppingCartId: string;\n canceledAt: Date;\n };\n };\n\nexport enum ShoppingCartStatus {\n Empty = 'Empty',\n Pending = 'Pending',\n Confirmed = 'Confirmed',\n Canceled = 'Canceled',\n}\n\nexport type Empty = { status: ShoppingCartStatus.Empty };\n\nexport type Pending = {\n status: ShoppingCartStatus.Pending;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n openedAt: Date;\n};\n\nexport type Confirmed = {\n status: ShoppingCartStatus.Confirmed;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n confirmedAt: Date;\n};\n\nexport type Canceled = {\n status: ShoppingCartStatus.Canceled;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n canceledAt: Date;\n};\n\nexport type ShoppingCart = Empty | Pending | Confirmed | Canceled;\n\nexport const emptyShoppingCart: Empty = { status: ShoppingCartStatus.Empty };\n\nexport const evolve = (\n state: ShoppingCart,\n { type, data: event }: ShoppingCartEvent,\n): ShoppingCart => {\n switch (type) {\n case 'ShoppingCartOpened':\n return {\n id: event.shoppingCartId,\n clientId: event.clientId,\n openedAt: event.openedAt,\n productItems: [],\n status: ShoppingCartStatus.Pending,\n };\n case 'ProductItemAddedToShoppingCart': {\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n const { productItems } = state;\n const { productItem } = event;\n\n return {\n ...state,\n productItems: merge(\n productItems,\n productItem,\n (p) =>\n p.productId === productItem.productId &&\n p.unitPrice === productItem.unitPrice,\n (p) => {\n return {\n ...p,\n quantity: p.quantity + productItem.quantity,\n };\n },\n () => productItem,\n ),\n };\n }\n case 'ProductItemRemovedFromShoppingCart': {\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n const { productItems } = state;\n const { productItem } = event;\n return {\n ...state,\n productItems: merge(\n productItems,\n productItem,\n (p) =>\n p.productId === productItem.productId &&\n p.unitPrice === productItem.unitPrice,\n (p) => {\n return {\n ...p,\n quantity: p.quantity - productItem.quantity,\n };\n },\n ),\n };\n }\n case 'ShoppingCartConfirmed':\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n return {\n ...state,\n status: ShoppingCartStatus.Confirmed,\n confirmedAt: event.confirmedAt,\n };\n case 'ShoppingCartCanceled':\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n return {\n ...state,\n status: ShoppingCartStatus.Canceled,\n canceledAt: event.canceledAt,\n };\n }\n};\n\nexport const getShoppingCart = (events: ShoppingCartEvent[]): ShoppingCart => {\n return events.reduce<ShoppingCart>(evolve, emptyShoppingCart);\n};\n\nexport const enum ShoppingCartErrors {\n CART_IS_ALREADY_CLOSED = 'CART_IS_ALREADY_CLOSED',\n PRODUCT_ITEM_NOT_FOUND = 'PRODUCT_ITEM_NOT_FOUND',\n CART_IS_EMPTY = 'CART_IS_EMPTY',\n UNKNOWN_EVENT_TYPE = 'UNKNOWN_EVENT_TYPE',\n UNKNOWN_COMMAND_TYPE = 'UNKNOWN_COMMAND_TYPE',\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunk2YZWWRCSjs = require('./chunk-2YZWWRCS.js');var S=(p=>(p.CART_ALREADY_EXISTS="CART_ALREADY_EXISTS",p.CART_IS_ALREADY_CLOSED="CART_IS_ALREADY_CLOSED",p.PRODUCT_ITEM_NOT_FOUND="PRODUCT_ITEM_NOT_FOUND",p.CART_IS_EMPTY="CART_IS_EMPTY",p.UNKNOWN_EVENT_TYPE="UNKNOWN_EVENT_TYPE",p.UNKNOWN_COMMAND_TYPE="UNKNOWN_COMMAND_TYPE",p))(S||{}),I= exports.b =(o,{productId:t,quantity:r,unitPrice:n})=>{if((_nullishCoalesce(_optionalChain([o, 'access', _ => _.find, 'call', _2 => _2(i=>i.productId===t&&i.unitPrice==n), 'optionalAccess', _3 => _3.quantity]), () => (0)))<r)throw new (0, _chunk2YZWWRCSjs.b)("PRODUCT_ITEM_NOT_FOUND")},g= exports.c =({type:o,data:t},r)=>{switch(o){case"OpenShoppingCart":{if(r.status!=="Empty")throw new (0, _chunk2YZWWRCSjs.b)("CART_ALREADY_EXISTS");return{type:"ShoppingCartOpened",data:{shoppingCartId:t.shoppingCartId,clientId:t.clientId,openedAt:t.now}}}case"AddProductItemToShoppingCart":{if(r.status!=="Pending")throw new (0, _chunk2YZWWRCSjs.b)("CART_IS_ALREADY_CLOSED");return{type:"ProductItemAddedToShoppingCart",data:{shoppingCartId:t.shoppingCartId,productItem:t.productItem}}}case"RemoveProductItemFromShoppingCart":{if(r.status!=="Pending")throw new (0, _chunk2YZWWRCSjs.b)("CART_IS_ALREADY_CLOSED");return I(r.productItems,t.productItem),{type:"ProductItemRemovedFromShoppingCart",data:{shoppingCartId:t.shoppingCartId,productItem:t.productItem}}}case"ConfirmShoppingCart":{if(r.status!=="Pending")throw new (0, _chunk2YZWWRCSjs.b)("CART_IS_ALREADY_CLOSED");if(r.productItems.length===0)throw new (0, _chunk2YZWWRCSjs.b)("CART_IS_EMPTY");return{type:"ShoppingCartConfirmed",data:{shoppingCartId:t.shoppingCartId,confirmedAt:t.now}}}case"CancelShoppingCart":{if(r.status!=="Pending")throw new (0, _chunk2YZWWRCSjs.b)("CART_IS_ALREADY_CLOSED");return{type:"ShoppingCartCanceled",data:{shoppingCartId:t.shoppingCartId,canceledAt:t.now}}}default:{let n=t;throw new (0, _chunk2YZWWRCSjs.a)("UNKNOWN_COMMAND_TYPE")}}},u= exports.d ={decide:g,evolve:_chunk2YZWWRCSjs.h,getInitialState:()=>_chunk2YZWWRCSjs.g};exports.a = S; exports.b = I; exports.c = g; exports.d = u;
|
|
2
|
+
//# sourceMappingURL=chunk-7VVTVM6G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/e2e/decider/businessLogic.ts"],"names":["ShoppingCartErrors","assertProductItemExists","productItems","productId","quantity","unitPrice","p","u","decide","type","command","shoppingCart","_","s","decider","evolve","emptyShoppingCart"],"mappings":"wDAuEO,IAAWA,OAChBA,EAAA,oBAAsB,sBACtBA,EAAA,uBAAyB,yBACzBA,EAAA,uBAAyB,yBACzBA,EAAA,cAAgB,gBAChBA,EAAA,mBAAqB,qBACrBA,EAAA,qBAAuB,uBANPA,OAAA,IASLC,EAA0B,CACrCC,EACA,CAAE,UAAAC,EAAW,SAAAC,EAAU,UAAAC,CAAU,IACxB,CAMT,IAJEH,EAAa,KACVI,GAAMA,EAAE,YAAcH,GAAaG,EAAE,WAAaD,CACrD,GAAG,UAAY,GAEKD,EACpB,MAAM,IAAIG,EAAkB,wBAAyC,CAEzE,EAEaC,EAAS,CACpB,CAAE,KAAAC,EAAM,KAAMC,CAAQ,EACtBC,IACsB,CACtB,OAAQF,EAAM,CACZ,IAAK,mBAAoB,CACvB,GAAIE,EAAa,iBACf,MAAM,IAAIJ,EAAkB,qBAAsC,EAEpE,MAAO,CACL,KAAM,qBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,SAAUA,EAAQ,SAClB,SAAUA,EAAQ,GACpB,CACF,CACF,CAEA,IAAK,+BAAgC,CACnC,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAEvE,MAAO,CACL,KAAM,iCACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,YAAaA,EAAQ,WACvB,CACF,CACF,CAEA,IAAK,oCAAqC,CACxC,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,OAAAN,EAAwBU,EAAa,aAAcD,EAAQ,WAAW,EAE/D,CACL,KAAM,qCACN,KAAM,CACJ,eAAgBA,EAAQ,eACxB,YAAaA,EAAQ,WACvB,CACF,CACF,CAEA,IAAK,sBAAuB,CAC1B,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,GAAII,EAAa,aAAa,SAAW,EACvC,MAAM,IAAIJ,EAAkB,eAAgC,EAG9D,MAAO,CACL,KAAM,wBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,YAAaA,EAAQ,GACvB,CACF,CACF,CAEA,IAAK,qBAAsB,CACzB,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,MAAO,CACL,KAAM,uBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,WAAYA,EAAQ,GACtB,CACF,CACF,CACA,QAAS,CACP,IAAME,EAAWF,EACjB,MAAM,IAAIG,EAAY,sBAAuC,CAC/D,CACF,CACF,EAEaC,EAIT,CACF,OAAAN,EACA,OAAAO,EACA,gBAAiB,IAAMC,CACzB","sourcesContent":["import {\n EmmettError,\n IllegalStateError,\n type Decider,\n} from '@event-driven-io/emmett';\n\nimport {\n ShoppingCartStatus,\n emptyShoppingCart,\n evolve,\n type PricedProductItem,\n type ShoppingCart,\n type ShoppingCartEvent,\n} from './shoppingCart';\n\n//////////////////////////////////////\n/// Commands\n//////////////////////////////////////\n\nexport type OpenShoppingCart = {\n type: 'OpenShoppingCart';\n data: {\n shoppingCartId: string;\n clientId: string;\n now: Date;\n };\n};\n\nexport type AddProductItemToShoppingCart = {\n type: 'AddProductItemToShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n};\n\nexport type RemoveProductItemFromShoppingCart = {\n type: 'RemoveProductItemFromShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n};\n\nexport type ConfirmShoppingCart = {\n type: 'ConfirmShoppingCart';\n data: {\n shoppingCartId: string;\n now: Date;\n };\n};\n\nexport type CancelShoppingCart = {\n type: 'CancelShoppingCart';\n data: {\n shoppingCartId: string;\n now: Date;\n };\n};\n\nexport type ShoppingCartCommand =\n | OpenShoppingCart\n | AddProductItemToShoppingCart\n | RemoveProductItemFromShoppingCart\n | ConfirmShoppingCart\n | CancelShoppingCart;\n\n//////////////////////////////////////\n/// Decide\n//////////////////////////////////////\n\nexport const enum ShoppingCartErrors {\n CART_ALREADY_EXISTS = 'CART_ALREADY_EXISTS',\n CART_IS_ALREADY_CLOSED = 'CART_IS_ALREADY_CLOSED',\n PRODUCT_ITEM_NOT_FOUND = 'PRODUCT_ITEM_NOT_FOUND',\n CART_IS_EMPTY = 'CART_IS_EMPTY',\n UNKNOWN_EVENT_TYPE = 'UNKNOWN_EVENT_TYPE',\n UNKNOWN_COMMAND_TYPE = 'UNKNOWN_COMMAND_TYPE',\n}\n\nexport const assertProductItemExists = (\n productItems: PricedProductItem[],\n { productId, quantity, unitPrice }: PricedProductItem,\n): void => {\n const currentQuantity =\n productItems.find(\n (p) => p.productId === productId && p.unitPrice == unitPrice,\n )?.quantity ?? 0;\n\n if (currentQuantity < quantity) {\n throw new IllegalStateError(ShoppingCartErrors.PRODUCT_ITEM_NOT_FOUND);\n }\n};\n\nexport const decide = (\n { type, data: command }: ShoppingCartCommand,\n shoppingCart: ShoppingCart,\n): ShoppingCartEvent => {\n switch (type) {\n case 'OpenShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Empty) {\n throw new IllegalStateError(ShoppingCartErrors.CART_ALREADY_EXISTS);\n }\n return {\n type: 'ShoppingCartOpened',\n data: {\n shoppingCartId: command.shoppingCartId,\n clientId: command.clientId,\n openedAt: command.now,\n },\n };\n }\n\n case 'AddProductItemToShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n return {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId: command.shoppingCartId,\n productItem: command.productItem,\n },\n };\n }\n\n case 'RemoveProductItemFromShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n assertProductItemExists(shoppingCart.productItems, command.productItem);\n\n return {\n type: 'ProductItemRemovedFromShoppingCart',\n data: {\n shoppingCartId: command.shoppingCartId,\n productItem: command.productItem,\n },\n };\n }\n\n case 'ConfirmShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n if (shoppingCart.productItems.length === 0) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_EMPTY);\n }\n\n return {\n type: 'ShoppingCartConfirmed',\n data: {\n shoppingCartId: command.shoppingCartId,\n confirmedAt: command.now,\n },\n };\n }\n\n case 'CancelShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n return {\n type: 'ShoppingCartCanceled',\n data: {\n shoppingCartId: command.shoppingCartId,\n canceledAt: command.now,\n },\n };\n }\n default: {\n const _: never = command;\n throw new EmmettError(ShoppingCartErrors.UNKNOWN_COMMAND_TYPE);\n }\n }\n};\n\nexport const decider: Decider<\n ShoppingCart,\n ShoppingCartCommand,\n ShoppingCartEvent\n> = {\n decide,\n evolve,\n getInitialState: () => emptyShoppingCart,\n};\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b as e,g as C,h as d}from"./chunk-M4ABRLXM.mjs";var S=(p=>(p.CART_ALREADY_EXISTS="CART_ALREADY_EXISTS",p.CART_IS_ALREADY_CLOSED="CART_IS_ALREADY_CLOSED",p.PRODUCT_ITEM_NOT_FOUND="PRODUCT_ITEM_NOT_FOUND",p.CART_IS_EMPTY="CART_IS_EMPTY",p.UNKNOWN_EVENT_TYPE="UNKNOWN_EVENT_TYPE",p.UNKNOWN_COMMAND_TYPE="UNKNOWN_COMMAND_TYPE",p))(S||{}),I=(o,{productId:t,quantity:r,unitPrice:n})=>{if((o.find(i=>i.productId===t&&i.unitPrice==n)?.quantity??0)<r)throw new e("PRODUCT_ITEM_NOT_FOUND")},g=({type:o,data:t},r)=>{switch(o){case"OpenShoppingCart":{if(r.status!=="Empty")throw new e("CART_ALREADY_EXISTS");return{type:"ShoppingCartOpened",data:{shoppingCartId:t.shoppingCartId,clientId:t.clientId,openedAt:t.now}}}case"AddProductItemToShoppingCart":{if(r.status!=="Pending")throw new e("CART_IS_ALREADY_CLOSED");return{type:"ProductItemAddedToShoppingCart",data:{shoppingCartId:t.shoppingCartId,productItem:t.productItem}}}case"RemoveProductItemFromShoppingCart":{if(r.status!=="Pending")throw new e("CART_IS_ALREADY_CLOSED");return I(r.productItems,t.productItem),{type:"ProductItemRemovedFromShoppingCart",data:{shoppingCartId:t.shoppingCartId,productItem:t.productItem}}}case"ConfirmShoppingCart":{if(r.status!=="Pending")throw new e("CART_IS_ALREADY_CLOSED");if(r.productItems.length===0)throw new e("CART_IS_EMPTY");return{type:"ShoppingCartConfirmed",data:{shoppingCartId:t.shoppingCartId,confirmedAt:t.now}}}case"CancelShoppingCart":{if(r.status!=="Pending")throw new e("CART_IS_ALREADY_CLOSED");return{type:"ShoppingCartCanceled",data:{shoppingCartId:t.shoppingCartId,canceledAt:t.now}}}default:{let n=t;throw new a("UNKNOWN_COMMAND_TYPE")}}},u={decide:g,evolve:d,getInitialState:()=>C};export{S as a,I as b,g as c,u as d};
|
|
2
|
+
//# sourceMappingURL=chunk-F2UPJ4YQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/e2e/decider/businessLogic.ts"],"sourcesContent":["import {\n EmmettError,\n IllegalStateError,\n type Decider,\n} from '@event-driven-io/emmett';\n\nimport {\n ShoppingCartStatus,\n emptyShoppingCart,\n evolve,\n type PricedProductItem,\n type ShoppingCart,\n type ShoppingCartEvent,\n} from './shoppingCart';\n\n//////////////////////////////////////\n/// Commands\n//////////////////////////////////////\n\nexport type OpenShoppingCart = {\n type: 'OpenShoppingCart';\n data: {\n shoppingCartId: string;\n clientId: string;\n now: Date;\n };\n};\n\nexport type AddProductItemToShoppingCart = {\n type: 'AddProductItemToShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n};\n\nexport type RemoveProductItemFromShoppingCart = {\n type: 'RemoveProductItemFromShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n};\n\nexport type ConfirmShoppingCart = {\n type: 'ConfirmShoppingCart';\n data: {\n shoppingCartId: string;\n now: Date;\n };\n};\n\nexport type CancelShoppingCart = {\n type: 'CancelShoppingCart';\n data: {\n shoppingCartId: string;\n now: Date;\n };\n};\n\nexport type ShoppingCartCommand =\n | OpenShoppingCart\n | AddProductItemToShoppingCart\n | RemoveProductItemFromShoppingCart\n | ConfirmShoppingCart\n | CancelShoppingCart;\n\n//////////////////////////////////////\n/// Decide\n//////////////////////////////////////\n\nexport const enum ShoppingCartErrors {\n CART_ALREADY_EXISTS = 'CART_ALREADY_EXISTS',\n CART_IS_ALREADY_CLOSED = 'CART_IS_ALREADY_CLOSED',\n PRODUCT_ITEM_NOT_FOUND = 'PRODUCT_ITEM_NOT_FOUND',\n CART_IS_EMPTY = 'CART_IS_EMPTY',\n UNKNOWN_EVENT_TYPE = 'UNKNOWN_EVENT_TYPE',\n UNKNOWN_COMMAND_TYPE = 'UNKNOWN_COMMAND_TYPE',\n}\n\nexport const assertProductItemExists = (\n productItems: PricedProductItem[],\n { productId, quantity, unitPrice }: PricedProductItem,\n): void => {\n const currentQuantity =\n productItems.find(\n (p) => p.productId === productId && p.unitPrice == unitPrice,\n )?.quantity ?? 0;\n\n if (currentQuantity < quantity) {\n throw new IllegalStateError(ShoppingCartErrors.PRODUCT_ITEM_NOT_FOUND);\n }\n};\n\nexport const decide = (\n { type, data: command }: ShoppingCartCommand,\n shoppingCart: ShoppingCart,\n): ShoppingCartEvent => {\n switch (type) {\n case 'OpenShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Empty) {\n throw new IllegalStateError(ShoppingCartErrors.CART_ALREADY_EXISTS);\n }\n return {\n type: 'ShoppingCartOpened',\n data: {\n shoppingCartId: command.shoppingCartId,\n clientId: command.clientId,\n openedAt: command.now,\n },\n };\n }\n\n case 'AddProductItemToShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n return {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId: command.shoppingCartId,\n productItem: command.productItem,\n },\n };\n }\n\n case 'RemoveProductItemFromShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n assertProductItemExists(shoppingCart.productItems, command.productItem);\n\n return {\n type: 'ProductItemRemovedFromShoppingCart',\n data: {\n shoppingCartId: command.shoppingCartId,\n productItem: command.productItem,\n },\n };\n }\n\n case 'ConfirmShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n if (shoppingCart.productItems.length === 0) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_EMPTY);\n }\n\n return {\n type: 'ShoppingCartConfirmed',\n data: {\n shoppingCartId: command.shoppingCartId,\n confirmedAt: command.now,\n },\n };\n }\n\n case 'CancelShoppingCart': {\n if (shoppingCart.status !== ShoppingCartStatus.Pending) {\n throw new IllegalStateError(ShoppingCartErrors.CART_IS_ALREADY_CLOSED);\n }\n\n return {\n type: 'ShoppingCartCanceled',\n data: {\n shoppingCartId: command.shoppingCartId,\n canceledAt: command.now,\n },\n };\n }\n default: {\n const _: never = command;\n throw new EmmettError(ShoppingCartErrors.UNKNOWN_COMMAND_TYPE);\n }\n }\n};\n\nexport const decider: Decider<\n ShoppingCart,\n ShoppingCartCommand,\n ShoppingCartEvent\n> = {\n decide,\n evolve,\n getInitialState: () => emptyShoppingCart,\n};\n"],"mappings":"yDAuEO,IAAWA,OAChBA,EAAA,oBAAsB,sBACtBA,EAAA,uBAAyB,yBACzBA,EAAA,uBAAyB,yBACzBA,EAAA,cAAgB,gBAChBA,EAAA,mBAAqB,qBACrBA,EAAA,qBAAuB,uBANPA,OAAA,IASLC,EAA0B,CACrCC,EACA,CAAE,UAAAC,EAAW,SAAAC,EAAU,UAAAC,CAAU,IACxB,CAMT,IAJEH,EAAa,KACVI,GAAMA,EAAE,YAAcH,GAAaG,EAAE,WAAaD,CACrD,GAAG,UAAY,GAEKD,EACpB,MAAM,IAAIG,EAAkB,wBAAyC,CAEzE,EAEaC,EAAS,CACpB,CAAE,KAAAC,EAAM,KAAMC,CAAQ,EACtBC,IACsB,CACtB,OAAQF,EAAM,CACZ,IAAK,mBAAoB,CACvB,GAAIE,EAAa,iBACf,MAAM,IAAIJ,EAAkB,qBAAsC,EAEpE,MAAO,CACL,KAAM,qBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,SAAUA,EAAQ,SAClB,SAAUA,EAAQ,GACpB,CACF,CACF,CAEA,IAAK,+BAAgC,CACnC,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAEvE,MAAO,CACL,KAAM,iCACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,YAAaA,EAAQ,WACvB,CACF,CACF,CAEA,IAAK,oCAAqC,CACxC,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,OAAAN,EAAwBU,EAAa,aAAcD,EAAQ,WAAW,EAE/D,CACL,KAAM,qCACN,KAAM,CACJ,eAAgBA,EAAQ,eACxB,YAAaA,EAAQ,WACvB,CACF,CACF,CAEA,IAAK,sBAAuB,CAC1B,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,GAAII,EAAa,aAAa,SAAW,EACvC,MAAM,IAAIJ,EAAkB,eAAgC,EAG9D,MAAO,CACL,KAAM,wBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,YAAaA,EAAQ,GACvB,CACF,CACF,CAEA,IAAK,qBAAsB,CACzB,GAAIC,EAAa,mBACf,MAAM,IAAIJ,EAAkB,wBAAyC,EAGvE,MAAO,CACL,KAAM,uBACN,KAAM,CACJ,eAAgBG,EAAQ,eACxB,WAAYA,EAAQ,GACtB,CACF,CACF,CACA,QAAS,CACP,IAAME,EAAWF,EACjB,MAAM,IAAIG,EAAY,sBAAuC,CAC/D,CACF,CACF,EAEaC,EAIT,CACF,OAAAN,EACA,OAAAO,EACA,gBAAiB,IAAMC,CACzB","names":["ShoppingCartErrors","assertProductItemExists","productItems","productId","quantity","unitPrice","p","u","decide","type","command","shoppingCart","_","s","decider","evolve","emptyShoppingCart"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var d=(t,e,o,s,a=()=>{})=>{let r=!1,i=t.map(n=>o(n)?(r=!0,s(n)):n).filter(n=>n!==void 0).map(n=>{if(!n)throw Error("That should not happen");return n});return!r&&a()!==void 0?[...t,e]:i};import q from"assert";var c=class E extends Error{errorCode;constructor(e){let o=e&&typeof e=="object"&&"errorCode"in e?e.errorCode:g(e)?e:500,s=e&&typeof e=="object"&&"message"in e?e.message:S(e)?e:`Error with status code '${o}' ocurred during Emmett processing`;super(s),this.errorCode=o,Object.setPrototypeOf(this,E.prototype)}};var u=class y extends c{constructor(e){super({errorCode:400,message:e??"Validation Error ocurred during Emmett processing"}),Object.setPrototypeOf(this,y.prototype)}},T=class N extends c{constructor(e){super({errorCode:403,message:e??"Illegal State ocurred during Emmett processing"}),Object.setPrototypeOf(this,N.prototype)}};var P=(t=>(t.NOT_A_NONEMPTY_STRING="NOT_A_NONEMPTY_STRING",t.NOT_A_POSITIVE_NUMBER="NOT_A_POSITIVE_NUMBER",t.NOT_AN_UNSIGNED_BIGINT="NOT_AN_UNSIGNED_BIGINT",t))(P||{}),g=t=>typeof t=="number"&&t===t,S=t=>typeof t=="string",x=t=>{if(!S(t)||t.length===0)throw new u("NOT_A_NONEMPTY_STRING");return t},A=t=>{if(!g(t)||t<=0)throw new u("NOT_A_POSITIVE_NUMBER");return t};var l="STREAM_DOES_NOT_EXIST",h="NO_CONCURRENCY_CHECK";var C=(t,e,o=s=>s)=>async(s,a,r,i)=>{let n=o(a),p=await s.aggregateStream(n,{evolve:t,getInitialState:e,read:{expectedStreamVersion:i?.expectedStreamVersion??h}}),I=p?.state??e(),O=p?.currentStreamVersion,m=r(I),f=Array.isArray(m)?m:[m],_=i?.expectedStreamVersion??O??l;return{...await s.appendToStream(n,f,{expectedStreamVersion:_}),newState:f.reduce(t,I)}};var b=({decide:t,evolve:e,getInitialState:o},s=a=>a)=>async(a,r,i,n)=>C(e,o,s)(a,r,p=>t(i,p),n);import{randomUUID as tt}from"crypto";var D=(a=>(a.Empty="Empty",a.Pending="Pending",a.Confirmed="Confirmed",a.Canceled="Canceled",a))(D||{}),R={status:"Empty"},M=(t,{type:e,data:o})=>{switch(e){case"ShoppingCartOpened":return{id:o.shoppingCartId,clientId:o.clientId,openedAt:o.openedAt,productItems:[],status:"Pending"};case"ProductItemAddedToShoppingCart":{if(t.status!=="Pending")return t;let{productItems:s}=t,{productItem:a}=o;return{...t,productItems:d(s,a,r=>r.productId===a.productId&&r.unitPrice===a.unitPrice,r=>({...r,quantity:r.quantity+a.quantity}),()=>a)}}case"ProductItemRemovedFromShoppingCart":{if(t.status!=="Pending")return t;let{productItems:s}=t,{productItem:a}=o;return{...t,productItems:d(s,a,r=>r.productId===a.productId&&r.unitPrice===a.unitPrice,r=>({...r,quantity:r.quantity-a.quantity}))}}case"ShoppingCartConfirmed":return t.status!=="Pending"?t:{...t,status:"Confirmed",confirmedAt:o.confirmedAt};case"ShoppingCartCanceled":return t.status!=="Pending"?t:{...t,status:"Canceled",canceledAt:o.canceledAt}}},Ut=t=>t.reduce(M,R),V=(r=>(r.CART_IS_ALREADY_CLOSED="CART_IS_ALREADY_CLOSED",r.PRODUCT_ITEM_NOT_FOUND="PRODUCT_ITEM_NOT_FOUND",r.CART_IS_EMPTY="CART_IS_EMPTY",r.UNKNOWN_EVENT_TYPE="UNKNOWN_EVENT_TYPE",r.UNKNOWN_COMMAND_TYPE="UNKNOWN_COMMAND_TYPE",r))(V||{});export{c as a,T as b,x as c,A as d,b as e,D as f,R as g,M as h,Ut as i,V as j};
|
|
2
|
+
//# sourceMappingURL=chunk-M4ABRLXM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../emmett/src/utils/merge.ts","../../emmett/src/testing/deciderSpecification.ts","../../emmett/src/errors/index.ts","../../emmett/src/validation/index.ts","../../emmett/src/eventStore/expectedVersion.ts","../../emmett/src/commandHandling/handleCommand.ts","../../emmett/src/commandHandling/handleCommandWithDecider.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../src/e2e/decider/shoppingCart.ts"],"sourcesContent":["export const merge = <T>(\n array: T[],\n item: T,\n where: (current: T) => boolean,\n onExisting: (current: T) => T,\n onNotFound: () => T | undefined = () => undefined,\n) => {\n let wasFound = false;\n\n const result = array\n // merge the existing item if matches condition\n .map((p: T) => {\n if (!where(p)) return p;\n\n wasFound = true;\n return onExisting(p);\n })\n // filter out item if undefined was returned\n // for cases of removal\n .filter((p) => p !== undefined)\n // make TypeScript happy\n .map((p) => {\n if (!p) throw Error('That should not happen');\n\n return p;\n });\n\n // if item was not found and onNotFound action is defined\n // try to generate new item\n if (!wasFound) {\n const result = onNotFound();\n\n if (result !== undefined) return [...array, item];\n }\n\n return result;\n};\n","import assert from 'assert';\n\nexport type DeciderSpecfication<Command, Event> = (\n givenEvents: Event | Event[],\n) => {\n when: (command: Command) => {\n then: (expectedEvents: Event | Event[]) => void;\n thenThrows: <Error>(assert: (error: Error) => boolean) => void;\n };\n};\n\nexport const DeciderSpecification = {\n for: <Command, Event, State>(decider: {\n decide: (command: Command, state: State) => Event | Event[];\n evolve: (state: State, event: Event) => State;\n initialState: () => State;\n }): DeciderSpecfication<Command, Event> => {\n {\n return (givenEvents: Event | Event[]) => {\n return {\n when: (command: Command) => {\n const handle = () => {\n const existingEvents = Array.isArray(givenEvents)\n ? givenEvents\n : [givenEvents];\n\n const currentState = existingEvents.reduce<State>(\n decider.evolve,\n decider.initialState(),\n );\n\n return decider.decide(command, currentState);\n };\n\n return {\n then: (expectedEvents: Event | Event[]): void => {\n const resultEvents = handle();\n\n const resultEventsArray = Array.isArray(resultEvents)\n ? resultEvents\n : [resultEvents];\n\n const expectedEventsArray = Array.isArray(expectedEvents)\n ? expectedEvents\n : [expectedEvents];\n\n assert.deepEqual(resultEventsArray, expectedEventsArray);\n },\n thenThrows: <Error>(check?: (error: Error) => boolean): void => {\n try {\n handle();\n assert.fail('Handler did not fail as expected');\n } catch (error) {\n if (check) assert.ok(check(error as Error));\n }\n },\n };\n },\n };\n };\n }\n },\n};\n","import { isNumber, isString } from '../validation';\n\nexport class EmmettError extends Error {\n public errorCode: number;\n\n constructor(\n options?: { errorCode: number; message?: string } | string | number,\n ) {\n const errorCode =\n options && typeof options === 'object' && 'errorCode' in options\n ? options.errorCode\n : isNumber(options)\n ? options\n : 500;\n const message =\n options && typeof options === 'object' && 'message' in options\n ? options.message\n : isString(options)\n ? options\n : `Error with status code '${errorCode}' ocurred during Emmett processing`;\n\n super(message);\n this.errorCode = errorCode;\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, EmmettError.prototype);\n }\n}\n\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: 412,\n message:\n message ??\n `Expected version ${expected.toString()} does not match current ${current?.toString()}`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyError.prototype);\n }\n}\n\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 400,\n message: message ?? `Validation Error ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class IllegalStateError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 403,\n message: message ?? `Illegal State ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, IllegalStateError.prototype);\n }\n}\n\nexport class NotFoundError extends EmmettError {\n constructor(options?: { id: string; type: string; message?: string }) {\n super({\n errorCode: 404,\n message:\n options?.message ??\n (options?.id\n ? options.type\n ? `${options.type} with ${options.id} was not found during Emmett processing`\n : `State with ${options.id} was not found during Emmett processing`\n : options?.type\n ? `${options.type} was not found during Emmett processing`\n : 'State was not found during Emmett processing'),\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n}\n","import { ValidationError } from '../errors';\n\nexport const enum ValidationErrors {\n NOT_A_NONEMPTY_STRING = 'NOT_A_NONEMPTY_STRING',\n NOT_A_POSITIVE_NUMBER = 'NOT_A_POSITIVE_NUMBER',\n NOT_AN_UNSIGNED_BIGINT = 'NOT_AN_UNSIGNED_BIGINT',\n}\n\nexport const isNumber = (val: unknown): val is number =>\n typeof val === 'number' && val === val;\n\nexport const isString = (val: unknown): val is string =>\n typeof val === 'string';\n\nexport const assertNotEmptyString = (value: unknown): string => {\n if (!isString(value) || value.length === 0) {\n throw new ValidationError(ValidationErrors.NOT_A_NONEMPTY_STRING);\n }\n return value;\n};\n\nexport const assertPositiveNumber = (value: unknown): number => {\n if (!isNumber(value) || value <= 0) {\n throw new ValidationError(ValidationErrors.NOT_A_POSITIVE_NUMBER);\n }\n return value;\n};\n\nexport const assertUnsignedBigInt = (value: string): bigint => {\n const number = BigInt(value);\n if (number < 0) {\n throw new ValidationError(ValidationErrors.NOT_AN_UNSIGNED_BIGINT);\n }\n return number;\n};\n","import { ConcurrencyError } from '../errors';\nimport type { Flavour } from '../typing';\nimport type { DefaultStreamVersionType } from './eventStore';\n\nexport type ExpectedStreamVersion<VersionType = DefaultStreamVersionType> =\n | ExpectedStreamVersionWithValue<VersionType>\n | ExpectedStreamVersionGeneral;\n\nexport type ExpectedStreamVersionWithValue<\n VersionType = DefaultStreamVersionType,\n> = Flavour<VersionType, 'StreamVersion'>;\n\nexport type ExpectedStreamVersionGeneral = Flavour<\n 'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK',\n 'StreamVersion'\n>;\n\nexport const STREAM_EXISTS = 'STREAM_EXISTS' as ExpectedStreamVersionGeneral;\nexport const STREAM_DOES_NOT_EXIST =\n 'STREAM_DOES_NOT_EXIST' as ExpectedStreamVersionGeneral;\nexport const NO_CONCURRENCY_CHECK =\n 'NO_CONCURRENCY_CHECK' as ExpectedStreamVersionGeneral;\n\nexport const matchesExpectedVersion = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion>,\n): boolean => {\n if (expected === NO_CONCURRENCY_CHECK) return true;\n\n if (expected == STREAM_DOES_NOT_EXIST) return current === undefined;\n\n if (expected == STREAM_EXISTS) return current !== undefined;\n\n return current === expected;\n};\n\nexport const assertExpectedVersionMatchesCurrent = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion> | undefined,\n): void => {\n expected ??= NO_CONCURRENCY_CHECK;\n\n if (!matchesExpectedVersion(current, expected))\n throw new ExpectedVersionConflictError(current, expected);\n};\n\nexport class ExpectedVersionConflictError<\n VersionType = DefaultStreamVersionType,\n> extends ConcurrencyError {\n constructor(\n current: VersionType | undefined,\n expected: ExpectedStreamVersion<VersionType>,\n ) {\n super(current?.toString(), expected?.toString());\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import {\n NO_CONCURRENCY_CHECK,\n STREAM_DOES_NOT_EXIST,\n type AppendToStreamResult,\n type DefaultStreamVersionType,\n type EventStore,\n type ExpectedStreamVersion,\n} from '../eventStore';\nimport type { Event } from '../typing';\n\n// #region command-handler\nexport type CommandHandlerResult<\n State,\n StreamVersion = DefaultStreamVersionType,\n> = AppendToStreamResult<StreamVersion> & { newState: State };\n\nexport const CommandHandler =\n <State, StreamEvent extends Event, StreamVersion = DefaultStreamVersionType>(\n evolve: (state: State, event: StreamEvent) => State,\n getInitialState: () => State,\n mapToStreamId: (id: string) => string = (id) => id,\n ) =>\n async (\n eventStore: EventStore<StreamVersion>,\n id: string,\n handle: (state: State) => StreamEvent | StreamEvent[],\n options?: {\n expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;\n },\n ): Promise<CommandHandlerResult<State, StreamVersion>> => {\n const streamName = mapToStreamId(id);\n\n // 1. Aggregate the stream\n const aggregationResult = await eventStore.aggregateStream<\n State,\n StreamEvent\n >(streamName, {\n evolve,\n getInitialState,\n read: {\n // expected stream version is passed to fail fast\n // if stream is in the wrong state\n expectedStreamVersion:\n options?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n });\n\n // 2. Use the aggregate state or the initial one (when e.g. stream does not exist)\n const state = aggregationResult?.state ?? getInitialState();\n const currentStreamVersion = aggregationResult?.currentStreamVersion;\n\n // 3. Run business logic\n const result = handle(state);\n\n const newEvents = Array.isArray(result) ? result : [result];\n\n // Either use:\n // - provided expected stream version,\n // - current stream version got from stream aggregation,\n // - or expect stream not to exists otherwise.\n const expectedStreamVersion: ExpectedStreamVersion<StreamVersion> =\n options?.expectedStreamVersion ??\n currentStreamVersion ??\n STREAM_DOES_NOT_EXIST;\n\n // 4. Append result to the stream\n const appendResult = await eventStore.appendToStream(\n streamName,\n newEvents,\n {\n expectedStreamVersion,\n },\n );\n\n // 5. Return result with updated state\n return { ...appendResult, newState: newEvents.reduce(evolve, state) };\n };\n// #endregion command-handler\n","import type {\n DefaultStreamVersionType,\n EventStore,\n ExpectedStreamVersion,\n} from '../eventStore';\nimport type { Command, Event } from '../typing';\nimport type { Decider } from '../typing/decider';\nimport { CommandHandler } from './handleCommand';\n\n// #region command-handler\nexport const DeciderCommandHandler =\n <\n State,\n CommandType extends Command,\n StreamEvent extends Event,\n StreamVersion = DefaultStreamVersionType,\n >(\n {\n decide,\n evolve,\n getInitialState,\n }: Decider<State, CommandType, StreamEvent>,\n mapToStreamId: (id: string) => string = (id) => id,\n ) =>\n async (\n eventStore: EventStore<StreamVersion>,\n id: string,\n command: CommandType,\n options?: {\n expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;\n },\n ) =>\n CommandHandler<State, StreamEvent, StreamVersion>(\n evolve,\n getInitialState,\n mapToStreamId,\n )(eventStore, id, (state) => decide(command, state), options);\n// #endregion command-handler\n","import { randomUUID } from 'crypto';\nimport type { Event } from '../typing';\nimport {\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type EventStore,\n type ReadStreamOptions,\n type ReadStreamResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\n\nexport type EventMetadata = Readonly<{\n eventId: string;\n streamPosition: number;\n logPosition: bigint;\n}>;\n\nexport type EventEnvelope<E extends Event = Event> = {\n event: E;\n metadata: EventMetadata;\n};\n\nexport type EventHandler<E extends Event = Event> = (\n eventEnvelope: EventEnvelope<E>,\n) => void;\n\nexport const getInMemoryEventStore = (): EventStore => {\n const streams = new Map<string, EventEnvelope[]>();\n\n const getAllEventsCount = () => {\n return Array.from<EventEnvelope[]>(streams.values())\n .map((s) => s.length)\n .reduce((p, c) => p + c, 0);\n };\n\n return {\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType>,\n ): Promise<AggregateStreamResult<State> | null> {\n const { evolve, getInitialState, read } = options;\n\n const result = await this.readStream<EventType>(streamName, read);\n\n if (!result) return null;\n\n const events = result?.events ?? [];\n\n return {\n currentStreamVersion: BigInt(events.length),\n state: events.reduce(evolve, getInitialState()),\n };\n },\n\n readStream: <EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<ReadStreamResult<EventType>> => {\n const events = streams.get(streamName);\n const currentStreamVersion = events ? BigInt(events.length) : undefined;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n );\n\n const from = Number(options && 'from' in options ? options.from : 0);\n const to = Number(\n options && 'to' in options\n ? options.to\n : options && 'maxCount' in options && options.maxCount\n ? options.from + options.maxCount\n : events?.length ?? 1,\n );\n\n const resultEvents =\n events && events.length > 0\n ? events.map((e) => e.event as EventType).slice(from, to)\n : [];\n\n const result: ReadStreamResult<EventType> =\n events && events.length > 0\n ? {\n currentStreamVersion: currentStreamVersion!,\n events: resultEvents,\n }\n : null;\n\n return Promise.resolve(result);\n },\n\n appendToStream: <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult> => {\n const currentEvents = streams.get(streamName) ?? [];\n const currentStreamVersion =\n currentEvents.length > 0 ? BigInt(currentEvents.length) : undefined;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n );\n\n const eventEnvelopes: EventEnvelope[] = events.map((event, index) => {\n return {\n event,\n metadata: {\n eventId: randomUUID(),\n streamPosition: currentEvents.length + index + 1,\n logPosition: BigInt(getAllEventsCount() + index + 1),\n },\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n eventEnvelopes.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [...currentEvents, ...eventEnvelopes]);\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n };\n\n return Promise.resolve(result);\n },\n };\n};\n","import { merge } from '@event-driven-io/emmett';\n\nexport interface ProductItem {\n productId: string;\n quantity: number;\n}\n\nexport type PricedProductItem = ProductItem & {\n unitPrice: number;\n};\n\nexport type ShoppingCartEvent =\n | {\n type: 'ShoppingCartOpened';\n data: {\n shoppingCartId: string;\n clientId: string;\n openedAt: Date;\n };\n }\n | {\n type: 'ProductItemAddedToShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n }\n | {\n type: 'ProductItemRemovedFromShoppingCart';\n data: {\n shoppingCartId: string;\n productItem: PricedProductItem;\n };\n }\n | {\n type: 'ShoppingCartConfirmed';\n data: {\n shoppingCartId: string;\n confirmedAt: Date;\n };\n }\n | {\n type: 'ShoppingCartCanceled';\n data: {\n shoppingCartId: string;\n canceledAt: Date;\n };\n };\n\nexport enum ShoppingCartStatus {\n Empty = 'Empty',\n Pending = 'Pending',\n Confirmed = 'Confirmed',\n Canceled = 'Canceled',\n}\n\nexport type Empty = { status: ShoppingCartStatus.Empty };\n\nexport type Pending = {\n status: ShoppingCartStatus.Pending;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n openedAt: Date;\n};\n\nexport type Confirmed = {\n status: ShoppingCartStatus.Confirmed;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n confirmedAt: Date;\n};\n\nexport type Canceled = {\n status: ShoppingCartStatus.Canceled;\n id: string;\n clientId: string;\n productItems: PricedProductItem[];\n canceledAt: Date;\n};\n\nexport type ShoppingCart = Empty | Pending | Confirmed | Canceled;\n\nexport const emptyShoppingCart: Empty = { status: ShoppingCartStatus.Empty };\n\nexport const evolve = (\n state: ShoppingCart,\n { type, data: event }: ShoppingCartEvent,\n): ShoppingCart => {\n switch (type) {\n case 'ShoppingCartOpened':\n return {\n id: event.shoppingCartId,\n clientId: event.clientId,\n openedAt: event.openedAt,\n productItems: [],\n status: ShoppingCartStatus.Pending,\n };\n case 'ProductItemAddedToShoppingCart': {\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n const { productItems } = state;\n const { productItem } = event;\n\n return {\n ...state,\n productItems: merge(\n productItems,\n productItem,\n (p) =>\n p.productId === productItem.productId &&\n p.unitPrice === productItem.unitPrice,\n (p) => {\n return {\n ...p,\n quantity: p.quantity + productItem.quantity,\n };\n },\n () => productItem,\n ),\n };\n }\n case 'ProductItemRemovedFromShoppingCart': {\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n const { productItems } = state;\n const { productItem } = event;\n return {\n ...state,\n productItems: merge(\n productItems,\n productItem,\n (p) =>\n p.productId === productItem.productId &&\n p.unitPrice === productItem.unitPrice,\n (p) => {\n return {\n ...p,\n quantity: p.quantity - productItem.quantity,\n };\n },\n ),\n };\n }\n case 'ShoppingCartConfirmed':\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n return {\n ...state,\n status: ShoppingCartStatus.Confirmed,\n confirmedAt: event.confirmedAt,\n };\n case 'ShoppingCartCanceled':\n if (state.status !== ShoppingCartStatus.Pending) return state;\n\n return {\n ...state,\n status: ShoppingCartStatus.Canceled,\n canceledAt: event.canceledAt,\n };\n }\n};\n\nexport const getShoppingCart = (events: ShoppingCartEvent[]): ShoppingCart => {\n return events.reduce<ShoppingCart>(evolve, emptyShoppingCart);\n};\n\nexport const enum ShoppingCartErrors {\n CART_IS_ALREADY_CLOSED = 'CART_IS_ALREADY_CLOSED',\n PRODUCT_ITEM_NOT_FOUND = 'PRODUCT_ITEM_NOT_FOUND',\n CART_IS_EMPTY = 'CART_IS_EMPTY',\n UNKNOWN_EVENT_TYPE = 'UNKNOWN_EVENT_TYPE',\n UNKNOWN_COMMAND_TYPE = 'UNKNOWN_COMMAND_TYPE',\n}\n"],"mappings":"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","names":["merge","array","item","where","onExisting","onNotFound","wasFound","result","p","assert","EmmettError","_EmmettError","options","errorCode","isNumber","message","isString","ValidationError","_ValidationError","EmmettError","message","IllegalStateError","_IllegalStateError","ValidationErrors","isNumber","val","isString","assertNotEmptyString","value","ValidationError","assertPositiveNumber","STREAM_DOES_NOT_EXIST","NO_CONCURRENCY_CHECK","CommandHandler","evolve","getInitialState","mapToStreamId","id","eventStore","handle","options","streamName","aggregationResult","NO_CONCURRENCY_CHECK","state","currentStreamVersion","result","newEvents","expectedStreamVersion","STREAM_DOES_NOT_EXIST","DeciderCommandHandler","decide","evolve","getInitialState","mapToStreamId","id","eventStore","command","options","CommandHandler","state","randomUUID","ShoppingCartStatus","emptyShoppingCart","evolve","state","type","event","productItems","productItem","i","p","getShoppingCart","events","ShoppingCartErrors"]}
|
|
@@ -1,27 +1,13 @@
|
|
|
1
|
+
import * as _event_driven_io_emmett_dist_commandHandling_handleCommand from '@event-driven-io/emmett/dist/commandHandling/handleCommand';
|
|
1
2
|
import { ShoppingCart } from './shoppingCart.mjs';
|
|
3
|
+
import * as _event_driven_io_emmett_dist_eventStore_N_YMFCDT from '@event-driven-io/emmett/dist/eventStore-N_YMFCDT';
|
|
2
4
|
import { ShoppingCartCommand } from './businessLogic.mjs';
|
|
3
5
|
import { EventStore } from '@event-driven-io/emmett';
|
|
4
6
|
import { FastifyInstance } from 'fastify';
|
|
5
7
|
|
|
6
|
-
type Flavour<K, T> = K & {
|
|
7
|
-
readonly __brand?: T;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type ExpectedStreamVersion<VersionType = DefaultStreamVersionType> = ExpectedStreamVersionWithValue<VersionType> | ExpectedStreamVersionGeneral;
|
|
11
|
-
type ExpectedStreamVersionWithValue<VersionType = DefaultStreamVersionType> = Flavour<VersionType, 'StreamVersion'>;
|
|
12
|
-
type ExpectedStreamVersionGeneral = Flavour<'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK', 'StreamVersion'>;
|
|
13
|
-
type DefaultStreamVersionType = bigint;
|
|
14
|
-
type AppendToStreamResult<StreamVersion = DefaultStreamVersionType> = {
|
|
15
|
-
nextExpectedStreamVersion: StreamVersion;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type CommandHandlerResult<State, StreamVersion = DefaultStreamVersionType> = AppendToStreamResult<StreamVersion> & {
|
|
19
|
-
newState: State;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
8
|
declare const handle: (eventStore: EventStore<bigint>, id: string, command: ShoppingCartCommand, options?: {
|
|
23
|
-
expectedStreamVersion?:
|
|
24
|
-
} | undefined) => Promise<CommandHandlerResult<ShoppingCart, bigint>>;
|
|
9
|
+
expectedStreamVersion?: _event_driven_io_emmett_dist_eventStore_N_YMFCDT.e<bigint> | undefined;
|
|
10
|
+
} | undefined) => Promise<_event_driven_io_emmett_dist_commandHandling_handleCommand.CommandHandlerResult<ShoppingCart, bigint>>;
|
|
25
11
|
declare const RegisterRoutes: (eventStore: EventStore) => (app: FastifyInstance) => void;
|
|
26
12
|
|
|
27
13
|
export { RegisterRoutes, handle };
|
|
@@ -1,27 +1,13 @@
|
|
|
1
|
+
import * as _event_driven_io_emmett_dist_commandHandling_handleCommand from '@event-driven-io/emmett/dist/commandHandling/handleCommand';
|
|
1
2
|
import { ShoppingCart } from './shoppingCart.js';
|
|
3
|
+
import * as _event_driven_io_emmett_dist_eventStore_N_YMFCDT from '@event-driven-io/emmett/dist/eventStore-N_YMFCDT';
|
|
2
4
|
import { ShoppingCartCommand } from './businessLogic.js';
|
|
3
5
|
import { EventStore } from '@event-driven-io/emmett';
|
|
4
6
|
import { FastifyInstance } from 'fastify';
|
|
5
7
|
|
|
6
|
-
type Flavour<K, T> = K & {
|
|
7
|
-
readonly __brand?: T;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type ExpectedStreamVersion<VersionType = DefaultStreamVersionType> = ExpectedStreamVersionWithValue<VersionType> | ExpectedStreamVersionGeneral;
|
|
11
|
-
type ExpectedStreamVersionWithValue<VersionType = DefaultStreamVersionType> = Flavour<VersionType, 'StreamVersion'>;
|
|
12
|
-
type ExpectedStreamVersionGeneral = Flavour<'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK', 'StreamVersion'>;
|
|
13
|
-
type DefaultStreamVersionType = bigint;
|
|
14
|
-
type AppendToStreamResult<StreamVersion = DefaultStreamVersionType> = {
|
|
15
|
-
nextExpectedStreamVersion: StreamVersion;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type CommandHandlerResult<State, StreamVersion = DefaultStreamVersionType> = AppendToStreamResult<StreamVersion> & {
|
|
19
|
-
newState: State;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
8
|
declare const handle: (eventStore: EventStore<bigint>, id: string, command: ShoppingCartCommand, options?: {
|
|
23
|
-
expectedStreamVersion?:
|
|
24
|
-
} | undefined) => Promise<CommandHandlerResult<ShoppingCart, bigint>>;
|
|
9
|
+
expectedStreamVersion?: _event_driven_io_emmett_dist_eventStore_N_YMFCDT.e<bigint> | undefined;
|
|
10
|
+
} | undefined) => Promise<_event_driven_io_emmett_dist_commandHandling_handleCommand.CommandHandlerResult<ShoppingCart, bigint>>;
|
|
25
11
|
declare const RegisterRoutes: (eventStore: EventStore) => (app: FastifyInstance) => void;
|
|
26
12
|
|
|
27
13
|
export { RegisterRoutes, handle };
|
package/dist/e2e/decider/api.js
CHANGED
|
@@ -1,100 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var _emmett = require('@event-driven-io/emmett');
|
|
6
|
-
|
|
7
|
-
require('fastify');
|
|
8
|
-
var _businessLogic = require('./businessLogic');
|
|
9
|
-
require('./shoppingCart');
|
|
10
|
-
const handle = _emmett.DeciderCommandHandler.call(void 0, _businessLogic.decider);
|
|
11
|
-
const dummyPriceProvider = (_productId) => {
|
|
12
|
-
return 100;
|
|
13
|
-
};
|
|
14
|
-
const RegisterRoutes = (eventStore) => (app) => {
|
|
15
|
-
app.post(
|
|
16
|
-
"/clients/:clientId/shopping-carts",
|
|
17
|
-
async (request, reply) => {
|
|
18
|
-
const { clientId } = request.params;
|
|
19
|
-
_emmett.assertNotEmptyString.call(void 0, clientId);
|
|
20
|
-
const shoppingCartId = clientId;
|
|
21
|
-
await handle(eventStore, shoppingCartId, {
|
|
22
|
-
type: "OpenShoppingCart",
|
|
23
|
-
data: { clientId: shoppingCartId, shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
24
|
-
});
|
|
25
|
-
return reply.code(201).send({ id: clientId });
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
app.post(
|
|
29
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/product-items",
|
|
30
|
-
async (request, reply) => {
|
|
31
|
-
const { shoppingCartId } = request.params;
|
|
32
|
-
_emmett.assertNotEmptyString.call(void 0, shoppingCartId);
|
|
33
|
-
const { productId, quantity } = request.body;
|
|
34
|
-
const productItem = {
|
|
35
|
-
productId: _emmett.assertNotEmptyString.call(void 0, productId),
|
|
36
|
-
quantity: _emmett.assertPositiveNumber.call(void 0, quantity)
|
|
37
|
-
};
|
|
38
|
-
const unitPrice = dummyPriceProvider(productItem.productId);
|
|
39
|
-
await handle(eventStore, shoppingCartId, {
|
|
40
|
-
type: "AddProductItemToShoppingCart",
|
|
41
|
-
data: {
|
|
42
|
-
shoppingCartId,
|
|
43
|
-
productItem: { ...productItem, unitPrice }
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return reply.code(204).send();
|
|
47
|
-
}
|
|
48
|
-
);
|
|
49
|
-
app.delete(
|
|
50
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/product-items",
|
|
51
|
-
async (request, reply) => {
|
|
52
|
-
const { shoppingCartId } = request.params;
|
|
53
|
-
_emmett.assertNotEmptyString.call(void 0, shoppingCartId);
|
|
54
|
-
const { productId, quantity, unitPrice } = request.query;
|
|
55
|
-
const productItem = {
|
|
56
|
-
productId: _emmett.assertNotEmptyString.call(void 0, productId),
|
|
57
|
-
quantity: _emmett.assertPositiveNumber.call(void 0, Number(quantity)),
|
|
58
|
-
unitPrice: _emmett.assertPositiveNumber.call(void 0, Number(unitPrice))
|
|
59
|
-
};
|
|
60
|
-
await handle(eventStore, shoppingCartId, {
|
|
61
|
-
type: "RemoveProductItemFromShoppingCart",
|
|
62
|
-
data: { shoppingCartId, productItem }
|
|
63
|
-
});
|
|
64
|
-
return reply.code(204).send();
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
app.post(
|
|
68
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/confirm",
|
|
69
|
-
async (request, reply) => {
|
|
70
|
-
const { shoppingCartId } = request.params;
|
|
71
|
-
_emmett.assertNotEmptyString.call(void 0, shoppingCartId);
|
|
72
|
-
await handle(eventStore, shoppingCartId, {
|
|
73
|
-
type: "ConfirmShoppingCart",
|
|
74
|
-
data: { shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
75
|
-
});
|
|
76
|
-
return reply.code(204).send();
|
|
77
|
-
}
|
|
78
|
-
);
|
|
79
|
-
app.delete(
|
|
80
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId",
|
|
81
|
-
async (request, reply) => {
|
|
82
|
-
const { shoppingCartId } = request.params;
|
|
83
|
-
_emmett.assertNotEmptyString.call(void 0, shoppingCartId);
|
|
84
|
-
try {
|
|
85
|
-
await handle(eventStore, shoppingCartId, {
|
|
86
|
-
type: "CancelShoppingCart",
|
|
87
|
-
data: { shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
return error instanceof Error && "message" in error ? reply.code(403).send({ detail: error.message }) : reply.code(403);
|
|
91
|
-
}
|
|
92
|
-
return reply.code(204).send();
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
exports.RegisterRoutes = RegisterRoutes; exports.handle = handle;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk7VVTVM6Gjs = require('../../chunk-7VVTVM6G.js');var _chunk2YZWWRCSjs = require('../../chunk-2YZWWRCS.js');require('fastify');var a=_chunk2YZWWRCSjs.e.call(void 0, _chunk7VVTVM6Gjs.d),I=o=>100,P= exports.RegisterRoutes =o=>n=>{n.post("/clients/:clientId/shopping-carts",async(e,r)=>{let{clientId:t}=e.params;_chunk2YZWWRCSjs.c.call(void 0, t);let s=t;return await a(o,s,{type:"OpenShoppingCart",data:{clientId:s,shoppingCartId:s,now:new Date}}),r.code(201).send({id:t})}),n.post("/clients/:clientId/shopping-carts/:shoppingCartId/product-items",async(e,r)=>{let{shoppingCartId:t}=e.params;_chunk2YZWWRCSjs.c.call(void 0, t);let{productId:s,quantity:c}=e.body,d={productId:_chunk2YZWWRCSjs.c.call(void 0, s),quantity:_chunk2YZWWRCSjs.d.call(void 0, c)},m=I(d.productId);return await a(o,t,{type:"AddProductItemToShoppingCart",data:{shoppingCartId:t,productItem:{...d,unitPrice:m}}}),r.code(204).send()}),n.delete("/clients/:clientId/shopping-carts/:shoppingCartId/product-items",async(e,r)=>{let{shoppingCartId:t}=e.params;_chunk2YZWWRCSjs.c.call(void 0, t);let{productId:s,quantity:c,unitPrice:d}=e.query,m={productId:_chunk2YZWWRCSjs.c.call(void 0, s),quantity:_chunk2YZWWRCSjs.d.call(void 0, Number(c)),unitPrice:_chunk2YZWWRCSjs.d.call(void 0, Number(d))};return await a(o,t,{type:"RemoveProductItemFromShoppingCart",data:{shoppingCartId:t,productItem:m}}),r.code(204).send()}),n.post("/clients/:clientId/shopping-carts/:shoppingCartId/confirm",async(e,r)=>{let{shoppingCartId:t}=e.params;return _chunk2YZWWRCSjs.c.call(void 0, t),await a(o,t,{type:"ConfirmShoppingCart",data:{shoppingCartId:t,now:new Date}}),r.code(204).send()}),n.delete("/clients/:clientId/shopping-carts/:shoppingCartId",async(e,r)=>{let{shoppingCartId:t}=e.params;_chunk2YZWWRCSjs.c.call(void 0, t);try{await a(o,t,{type:"CancelShoppingCart",data:{shoppingCartId:t,now:new Date}})}catch(s){return s instanceof Error&&"message"in s?r.code(403).send({detail:s.message}):r.code(403)}return r.code(204).send()})};exports.RegisterRoutes = P; exports.handle = a;
|
|
100
2
|
//# sourceMappingURL=api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/e2e/decider/api.ts"],"names":[
|
|
1
|
+
{"version":3,"sources":["../../../src/e2e/decider/api.ts"],"names":["handle","s","decider","dummyPriceProvider","_productId","RegisterRoutes","eventStore","app","request","reply","clientId","_","shoppingCartId","productId","quantity","productItem","b","unitPrice","error"],"mappings":"sGAMA,MAIO,UAIA,IAAMA,EAASC,EAAsBC,CAAO,EAC7CC,EAAsBC,GACnB,IAOIC,EACVC,GAA4BC,GAAyB,CACpDA,EAAI,KACF,oCACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,SAAAC,CAAS,EAAIF,EAAQ,OAC7BG,EAAqBD,CAAQ,EAC7B,IAAME,EAAiBF,EACvB,aAAMV,EAAOM,EAAYM,EAAgB,CACvC,KAAM,mBACN,KAAM,CAAE,SAAUA,EAAgB,eAAAA,EAAgB,IAAK,IAAI,IAAO,CACpE,CAAC,EACMH,EAAM,KAAK,GAAG,EAAE,KAAK,CAAE,GAAIC,CAAS,CAAC,CAC9C,CACF,EACAH,EAAI,KACF,kEACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAM,CAAE,UAAAC,EAAW,SAAAC,CAAS,EAAIN,EAAQ,KAClCO,EAA2B,CAC/B,UAAWJ,EAAqBE,CAAS,EACzC,SAAUG,EAAqBF,CAAQ,CACzC,EAEMG,EAAYd,EAAmBY,EAAY,SAAS,EAC1D,aAAMf,EAAOM,EAAYM,EAAgB,CACvC,KAAM,+BACN,KAAM,CACJ,eAAAA,EACA,YAAa,CAAE,GAAGG,EAAa,UAAAE,CAAU,CAC3C,CACF,CAAC,EACMR,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,OACF,kEACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAM,CAAE,UAAAC,EAAW,SAAAC,EAAU,UAAAG,CAAU,EACrCT,EAAQ,MACJO,EAAiC,CACrC,UAAWJ,EAAqBE,CAAS,EACzC,SAAUG,EAAqB,OAAOF,CAAQ,CAAC,EAC/C,UAAWE,EAAqB,OAAOC,CAAS,CAAC,CACnD,EAEA,aAAMjB,EAAOM,EAAYM,EAAgB,CACvC,KAAM,oCACN,KAAM,CAAE,eAAAA,EAAgB,YAAAG,CAAY,CACtC,CAAC,EACMN,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,KACF,4DACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnC,OAAAG,EAAqBC,CAAc,EAEnC,MAAMZ,EAAOM,EAAYM,EAAgB,CACvC,KAAM,sBACN,KAAM,CAAE,eAAAA,EAAgB,IAAK,IAAI,IAAO,CAC1C,CAAC,EACMH,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,OACF,oDACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAI,CACF,MAAMZ,EAAOM,EAAYM,EAAgB,CACvC,KAAM,qBACN,KAAM,CAAE,eAAAA,EAAgB,IAAK,IAAI,IAAO,CAC1C,CAAC,CACH,OAASM,EAAO,CACd,OAAOA,aAAiB,OAAS,YAAaA,EAC1CT,EAAM,KAAK,GAAG,EAAE,KAAK,CAAE,OAAQS,EAAM,OAAQ,CAAC,EAC9CT,EAAM,KAAK,GAAG,CACpB,CACA,OAAOA,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,CACF","sourcesContent":["import {\n DeciderCommandHandler,\n assertNotEmptyString,\n assertPositiveNumber,\n type EventStore,\n} from '@event-driven-io/emmett';\nimport {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify';\nimport { decider } from './businessLogic';\nimport { type PricedProductItem, type ProductItem } from './shoppingCart';\n\nexport const handle = DeciderCommandHandler(decider);\nconst dummyPriceProvider = (_productId: string) => {\n return 100;\n};\n\ninterface ShoppingCartItem {\n shoppingCartId: string;\n}\n\nexport const RegisterRoutes =\n (eventStore: EventStore) => (app: FastifyInstance) => {\n app.post(\n '/clients/:clientId/shopping-carts',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { clientId } = request.params as { clientId: string };\n assertNotEmptyString(clientId);\n const shoppingCartId = clientId;\n await handle(eventStore, shoppingCartId, {\n type: 'OpenShoppingCart',\n data: { clientId: shoppingCartId, shoppingCartId, now: new Date() },\n });\n return reply.code(201).send({ id: clientId });\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity } = request.body as PricedProductItem;\n const productItem: ProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(quantity),\n };\n\n const unitPrice = dummyPriceProvider(productItem.productId);\n await handle(eventStore, shoppingCartId, {\n type: 'AddProductItemToShoppingCart',\n data: {\n shoppingCartId,\n productItem: { ...productItem, unitPrice },\n },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity, unitPrice } =\n request.query as PricedProductItem;\n const productItem: PricedProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(Number(quantity)),\n unitPrice: assertPositiveNumber(Number(unitPrice)),\n };\n\n await handle(eventStore, shoppingCartId, {\n type: 'RemoveProductItemFromShoppingCart',\n data: { shoppingCartId, productItem },\n });\n return reply.code(204).send();\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/confirm',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n\n await handle(eventStore, shoppingCartId, {\n type: 'ConfirmShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n try {\n await handle(eventStore, shoppingCartId, {\n type: 'CancelShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n } catch (error) {\n return error instanceof Error && 'message' in error\n ? reply.code(403).send({ detail: error.message })\n : reply.code(403);\n }\n return reply.code(204).send();\n },\n );\n };\n"]}
|
package/dist/e2e/decider/api.mjs
CHANGED
|
@@ -1,100 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeciderCommandHandler,
|
|
3
|
-
assertNotEmptyString,
|
|
4
|
-
assertPositiveNumber
|
|
5
|
-
} from "@event-driven-io/emmett";
|
|
6
|
-
import {
|
|
7
|
-
} from "fastify";
|
|
8
|
-
import { decider } from "./businessLogic";
|
|
9
|
-
import {} from "./shoppingCart";
|
|
10
|
-
const handle = DeciderCommandHandler(decider);
|
|
11
|
-
const dummyPriceProvider = (_productId) => {
|
|
12
|
-
return 100;
|
|
13
|
-
};
|
|
14
|
-
const RegisterRoutes = (eventStore) => (app) => {
|
|
15
|
-
app.post(
|
|
16
|
-
"/clients/:clientId/shopping-carts",
|
|
17
|
-
async (request, reply) => {
|
|
18
|
-
const { clientId } = request.params;
|
|
19
|
-
assertNotEmptyString(clientId);
|
|
20
|
-
const shoppingCartId = clientId;
|
|
21
|
-
await handle(eventStore, shoppingCartId, {
|
|
22
|
-
type: "OpenShoppingCart",
|
|
23
|
-
data: { clientId: shoppingCartId, shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
24
|
-
});
|
|
25
|
-
return reply.code(201).send({ id: clientId });
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
app.post(
|
|
29
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/product-items",
|
|
30
|
-
async (request, reply) => {
|
|
31
|
-
const { shoppingCartId } = request.params;
|
|
32
|
-
assertNotEmptyString(shoppingCartId);
|
|
33
|
-
const { productId, quantity } = request.body;
|
|
34
|
-
const productItem = {
|
|
35
|
-
productId: assertNotEmptyString(productId),
|
|
36
|
-
quantity: assertPositiveNumber(quantity)
|
|
37
|
-
};
|
|
38
|
-
const unitPrice = dummyPriceProvider(productItem.productId);
|
|
39
|
-
await handle(eventStore, shoppingCartId, {
|
|
40
|
-
type: "AddProductItemToShoppingCart",
|
|
41
|
-
data: {
|
|
42
|
-
shoppingCartId,
|
|
43
|
-
productItem: { ...productItem, unitPrice }
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return reply.code(204).send();
|
|
47
|
-
}
|
|
48
|
-
);
|
|
49
|
-
app.delete(
|
|
50
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/product-items",
|
|
51
|
-
async (request, reply) => {
|
|
52
|
-
const { shoppingCartId } = request.params;
|
|
53
|
-
assertNotEmptyString(shoppingCartId);
|
|
54
|
-
const { productId, quantity, unitPrice } = request.query;
|
|
55
|
-
const productItem = {
|
|
56
|
-
productId: assertNotEmptyString(productId),
|
|
57
|
-
quantity: assertPositiveNumber(Number(quantity)),
|
|
58
|
-
unitPrice: assertPositiveNumber(Number(unitPrice))
|
|
59
|
-
};
|
|
60
|
-
await handle(eventStore, shoppingCartId, {
|
|
61
|
-
type: "RemoveProductItemFromShoppingCart",
|
|
62
|
-
data: { shoppingCartId, productItem }
|
|
63
|
-
});
|
|
64
|
-
return reply.code(204).send();
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
app.post(
|
|
68
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId/confirm",
|
|
69
|
-
async (request, reply) => {
|
|
70
|
-
const { shoppingCartId } = request.params;
|
|
71
|
-
assertNotEmptyString(shoppingCartId);
|
|
72
|
-
await handle(eventStore, shoppingCartId, {
|
|
73
|
-
type: "ConfirmShoppingCart",
|
|
74
|
-
data: { shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
75
|
-
});
|
|
76
|
-
return reply.code(204).send();
|
|
77
|
-
}
|
|
78
|
-
);
|
|
79
|
-
app.delete(
|
|
80
|
-
"/clients/:clientId/shopping-carts/:shoppingCartId",
|
|
81
|
-
async (request, reply) => {
|
|
82
|
-
const { shoppingCartId } = request.params;
|
|
83
|
-
assertNotEmptyString(shoppingCartId);
|
|
84
|
-
try {
|
|
85
|
-
await handle(eventStore, shoppingCartId, {
|
|
86
|
-
type: "CancelShoppingCart",
|
|
87
|
-
data: { shoppingCartId, now: /* @__PURE__ */ new Date() }
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
return error instanceof Error && "message" in error ? reply.code(403).send({ detail: error.message }) : reply.code(403);
|
|
91
|
-
}
|
|
92
|
-
return reply.code(204).send();
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
};
|
|
96
|
-
export {
|
|
97
|
-
RegisterRoutes,
|
|
98
|
-
handle
|
|
99
|
-
};
|
|
1
|
+
import{d as y}from"../../chunk-F2UPJ4YQ.mjs";import{c as i,d as p,e as u}from"../../chunk-M4ABRLXM.mjs";import"fastify";var a=u(y),I=o=>100,P=o=>n=>{n.post("/clients/:clientId/shopping-carts",async(e,r)=>{let{clientId:t}=e.params;i(t);let s=t;return await a(o,s,{type:"OpenShoppingCart",data:{clientId:s,shoppingCartId:s,now:new Date}}),r.code(201).send({id:t})}),n.post("/clients/:clientId/shopping-carts/:shoppingCartId/product-items",async(e,r)=>{let{shoppingCartId:t}=e.params;i(t);let{productId:s,quantity:c}=e.body,d={productId:i(s),quantity:p(c)},m=I(d.productId);return await a(o,t,{type:"AddProductItemToShoppingCart",data:{shoppingCartId:t,productItem:{...d,unitPrice:m}}}),r.code(204).send()}),n.delete("/clients/:clientId/shopping-carts/:shoppingCartId/product-items",async(e,r)=>{let{shoppingCartId:t}=e.params;i(t);let{productId:s,quantity:c,unitPrice:d}=e.query,m={productId:i(s),quantity:p(Number(c)),unitPrice:p(Number(d))};return await a(o,t,{type:"RemoveProductItemFromShoppingCart",data:{shoppingCartId:t,productItem:m}}),r.code(204).send()}),n.post("/clients/:clientId/shopping-carts/:shoppingCartId/confirm",async(e,r)=>{let{shoppingCartId:t}=e.params;return i(t),await a(o,t,{type:"ConfirmShoppingCart",data:{shoppingCartId:t,now:new Date}}),r.code(204).send()}),n.delete("/clients/:clientId/shopping-carts/:shoppingCartId",async(e,r)=>{let{shoppingCartId:t}=e.params;i(t);try{await a(o,t,{type:"CancelShoppingCart",data:{shoppingCartId:t,now:new Date}})}catch(s){return s instanceof Error&&"message"in s?r.code(403).send({detail:s.message}):r.code(403)}return r.code(204).send()})};export{P as RegisterRoutes,a as handle};
|
|
100
2
|
//# sourceMappingURL=api.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/e2e/decider/api.ts"],"sourcesContent":["import {\n DeciderCommandHandler,\n assertNotEmptyString,\n assertPositiveNumber,\n type EventStore,\n} from '@event-driven-io/emmett';\nimport {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify';\nimport { decider } from './businessLogic';\nimport { type PricedProductItem, type ProductItem } from './shoppingCart';\n\nexport const handle = DeciderCommandHandler(decider);\nconst dummyPriceProvider = (_productId: string) => {\n return 100;\n};\n\ninterface ShoppingCartItem {\n shoppingCartId: string;\n}\n\nexport const RegisterRoutes =\n (eventStore: EventStore) => (app: FastifyInstance) => {\n app.post(\n '/clients/:clientId/shopping-carts',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { clientId } = request.params as { clientId: string };\n assertNotEmptyString(clientId);\n const shoppingCartId = clientId;\n await handle(eventStore, shoppingCartId, {\n type: 'OpenShoppingCart',\n data: { clientId: shoppingCartId, shoppingCartId, now: new Date() },\n });\n return reply.code(201).send({ id: clientId });\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity } = request.body as PricedProductItem;\n const productItem: ProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(quantity),\n };\n\n const unitPrice = dummyPriceProvider(productItem.productId);\n await handle(eventStore, shoppingCartId, {\n type: 'AddProductItemToShoppingCart',\n data: {\n shoppingCartId,\n productItem: { ...productItem, unitPrice },\n },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity, unitPrice } =\n request.query as PricedProductItem;\n const productItem: PricedProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(Number(quantity)),\n unitPrice: assertPositiveNumber(Number(unitPrice)),\n };\n\n await handle(eventStore, shoppingCartId, {\n type: 'RemoveProductItemFromShoppingCart',\n data: { shoppingCartId, productItem },\n });\n return reply.code(204).send();\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/confirm',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n\n await handle(eventStore, shoppingCartId, {\n type: 'ConfirmShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n try {\n await handle(eventStore, shoppingCartId, {\n type: 'CancelShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n } catch (error) {\n return error instanceof Error && 'message' in error\n ? reply.code(403).send({ detail: error.message })\n : reply.code(403);\n }\n return reply.code(204).send();\n },\n );\n };\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/e2e/decider/api.ts"],"sourcesContent":["import {\n DeciderCommandHandler,\n assertNotEmptyString,\n assertPositiveNumber,\n type EventStore,\n} from '@event-driven-io/emmett';\nimport {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify';\nimport { decider } from './businessLogic';\nimport { type PricedProductItem, type ProductItem } from './shoppingCart';\n\nexport const handle = DeciderCommandHandler(decider);\nconst dummyPriceProvider = (_productId: string) => {\n return 100;\n};\n\ninterface ShoppingCartItem {\n shoppingCartId: string;\n}\n\nexport const RegisterRoutes =\n (eventStore: EventStore) => (app: FastifyInstance) => {\n app.post(\n '/clients/:clientId/shopping-carts',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { clientId } = request.params as { clientId: string };\n assertNotEmptyString(clientId);\n const shoppingCartId = clientId;\n await handle(eventStore, shoppingCartId, {\n type: 'OpenShoppingCart',\n data: { clientId: shoppingCartId, shoppingCartId, now: new Date() },\n });\n return reply.code(201).send({ id: clientId });\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity } = request.body as PricedProductItem;\n const productItem: ProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(quantity),\n };\n\n const unitPrice = dummyPriceProvider(productItem.productId);\n await handle(eventStore, shoppingCartId, {\n type: 'AddProductItemToShoppingCart',\n data: {\n shoppingCartId,\n productItem: { ...productItem, unitPrice },\n },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId/product-items',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n const { productId, quantity, unitPrice } =\n request.query as PricedProductItem;\n const productItem: PricedProductItem = {\n productId: assertNotEmptyString(productId),\n quantity: assertPositiveNumber(Number(quantity)),\n unitPrice: assertPositiveNumber(Number(unitPrice)),\n };\n\n await handle(eventStore, shoppingCartId, {\n type: 'RemoveProductItemFromShoppingCart',\n data: { shoppingCartId, productItem },\n });\n return reply.code(204).send();\n },\n );\n app.post(\n '/clients/:clientId/shopping-carts/:shoppingCartId/confirm',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n\n await handle(eventStore, shoppingCartId, {\n type: 'ConfirmShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n return reply.code(204).send();\n },\n );\n app.delete(\n '/clients/:clientId/shopping-carts/:shoppingCartId',\n async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const { shoppingCartId } = request.params as ShoppingCartItem;\n assertNotEmptyString(shoppingCartId);\n try {\n await handle(eventStore, shoppingCartId, {\n type: 'CancelShoppingCart',\n data: { shoppingCartId, now: new Date() },\n });\n } catch (error) {\n return error instanceof Error && 'message' in error\n ? reply.code(403).send({ detail: error.message })\n : reply.code(403);\n }\n return reply.code(204).send();\n },\n );\n };\n"],"mappings":"wGAMA,MAIO,UAIA,IAAMA,EAASC,EAAsBC,CAAO,EAC7CC,EAAsBC,GACnB,IAOIC,EACVC,GAA4BC,GAAyB,CACpDA,EAAI,KACF,oCACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,SAAAC,CAAS,EAAIF,EAAQ,OAC7BG,EAAqBD,CAAQ,EAC7B,IAAME,EAAiBF,EACvB,aAAMV,EAAOM,EAAYM,EAAgB,CACvC,KAAM,mBACN,KAAM,CAAE,SAAUA,EAAgB,eAAAA,EAAgB,IAAK,IAAI,IAAO,CACpE,CAAC,EACMH,EAAM,KAAK,GAAG,EAAE,KAAK,CAAE,GAAIC,CAAS,CAAC,CAC9C,CACF,EACAH,EAAI,KACF,kEACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAM,CAAE,UAAAC,EAAW,SAAAC,CAAS,EAAIN,EAAQ,KAClCO,EAA2B,CAC/B,UAAWJ,EAAqBE,CAAS,EACzC,SAAUG,EAAqBF,CAAQ,CACzC,EAEMG,EAAYd,EAAmBY,EAAY,SAAS,EAC1D,aAAMf,EAAOM,EAAYM,EAAgB,CACvC,KAAM,+BACN,KAAM,CACJ,eAAAA,EACA,YAAa,CAAE,GAAGG,EAAa,UAAAE,CAAU,CAC3C,CACF,CAAC,EACMR,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,OACF,kEACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAM,CAAE,UAAAC,EAAW,SAAAC,EAAU,UAAAG,CAAU,EACrCT,EAAQ,MACJO,EAAiC,CACrC,UAAWJ,EAAqBE,CAAS,EACzC,SAAUG,EAAqB,OAAOF,CAAQ,CAAC,EAC/C,UAAWE,EAAqB,OAAOC,CAAS,CAAC,CACnD,EAEA,aAAMjB,EAAOM,EAAYM,EAAgB,CACvC,KAAM,oCACN,KAAM,CAAE,eAAAA,EAAgB,YAAAG,CAAY,CACtC,CAAC,EACMN,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,KACF,4DACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnC,OAAAG,EAAqBC,CAAc,EAEnC,MAAMZ,EAAOM,EAAYM,EAAgB,CACvC,KAAM,sBACN,KAAM,CAAE,eAAAA,EAAgB,IAAK,IAAI,IAAO,CAC1C,CAAC,EACMH,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,EACAF,EAAI,OACF,oDACA,MAAOC,EAAyBC,IAAuC,CACrE,GAAM,CAAE,eAAAG,CAAe,EAAIJ,EAAQ,OACnCG,EAAqBC,CAAc,EACnC,GAAI,CACF,MAAMZ,EAAOM,EAAYM,EAAgB,CACvC,KAAM,qBACN,KAAM,CAAE,eAAAA,EAAgB,IAAK,IAAI,IAAO,CAC1C,CAAC,CACH,OAASM,EAAO,CACd,OAAOA,aAAiB,OAAS,YAAaA,EAC1CT,EAAM,KAAK,GAAG,EAAE,KAAK,CAAE,OAAQS,EAAM,OAAQ,CAAC,EAC9CT,EAAM,KAAK,GAAG,CACpB,CACA,OAAOA,EAAM,KAAK,GAAG,EAAE,KAAK,CAC9B,CACF,CACF","names":["handle","s","decider","dummyPriceProvider","_productId","RegisterRoutes","eventStore","app","request","reply","clientId","_","shoppingCartId","productId","quantity","productItem","b","unitPrice","error"]}
|