@event-driven-io/emmett-fastify 0.5.0

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.
Files changed (37) hide show
  1. package/dist/e2e/decider/api.d.mts +27 -0
  2. package/dist/e2e/decider/api.d.ts +27 -0
  3. package/dist/e2e/decider/api.js +100 -0
  4. package/dist/e2e/decider/api.js.map +1 -0
  5. package/dist/e2e/decider/api.mjs +100 -0
  6. package/dist/e2e/decider/api.mjs.map +1 -0
  7. package/dist/e2e/decider/applicationLogicWithOC.int.spec.d.mts +2 -0
  8. package/dist/e2e/decider/applicationLogicWithOC.int.spec.d.ts +2 -0
  9. package/dist/e2e/decider/applicationLogicWithOC.int.spec.js +115 -0
  10. package/dist/e2e/decider/applicationLogicWithOC.int.spec.js.map +1 -0
  11. package/dist/e2e/decider/applicationLogicWithOC.int.spec.mjs +115 -0
  12. package/dist/e2e/decider/applicationLogicWithOC.int.spec.mjs.map +1 -0
  13. package/dist/e2e/decider/businessLogic.d.mts +53 -0
  14. package/dist/e2e/decider/businessLogic.d.ts +53 -0
  15. package/dist/e2e/decider/businessLogic.js +111 -0
  16. package/dist/e2e/decider/businessLogic.js.map +1 -0
  17. package/dist/e2e/decider/businessLogic.mjs +111 -0
  18. package/dist/e2e/decider/businessLogic.mjs.map +1 -0
  19. package/dist/e2e/decider/shoppingCart.d.mts +82 -0
  20. package/dist/e2e/decider/shoppingCart.d.ts +82 -0
  21. package/dist/e2e/decider/shoppingCart.js +97 -0
  22. package/dist/e2e/decider/shoppingCart.js.map +1 -0
  23. package/dist/e2e/decider/shoppingCart.mjs +97 -0
  24. package/dist/e2e/decider/shoppingCart.mjs.map +1 -0
  25. package/dist/e2e/testing.d.mts +20 -0
  26. package/dist/e2e/testing.d.ts +20 -0
  27. package/dist/e2e/testing.js +27 -0
  28. package/dist/e2e/testing.js.map +1 -0
  29. package/dist/e2e/testing.mjs +27 -0
  30. package/dist/e2e/testing.mjs.map +1 -0
  31. package/dist/index.d.mts +22 -0
  32. package/dist/index.d.ts +22 -0
  33. package/dist/index.js +56 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/index.mjs +56 -0
  36. package/dist/index.mjs.map +1 -0
  37. package/package.json +55 -0
@@ -0,0 +1,27 @@
1
+ import { ShoppingCart } from './shoppingCart.mjs';
2
+ import { ShoppingCartCommand } from './businessLogic.mjs';
3
+ import { EventStore } from '@event-driven-io/emmett';
4
+ import { FastifyInstance } from 'fastify';
5
+
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
+ declare const handle: (eventStore: EventStore<bigint>, id: string, command: ShoppingCartCommand, options?: {
23
+ expectedStreamVersion?: ExpectedStreamVersion<bigint> | undefined;
24
+ } | undefined) => Promise<CommandHandlerResult<ShoppingCart, bigint>>;
25
+ declare const RegisterRoutes: (eventStore: EventStore) => (app: FastifyInstance) => void;
26
+
27
+ export { RegisterRoutes, handle };
@@ -0,0 +1,27 @@
1
+ import { ShoppingCart } from './shoppingCart.js';
2
+ import { ShoppingCartCommand } from './businessLogic.js';
3
+ import { EventStore } from '@event-driven-io/emmett';
4
+ import { FastifyInstance } from 'fastify';
5
+
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
+ declare const handle: (eventStore: EventStore<bigint>, id: string, command: ShoppingCartCommand, options?: {
23
+ expectedStreamVersion?: ExpectedStreamVersion<bigint> | undefined;
24
+ } | undefined) => Promise<CommandHandlerResult<ShoppingCart, bigint>>;
25
+ declare const RegisterRoutes: (eventStore: EventStore) => (app: FastifyInstance) => void;
26
+
27
+ export { RegisterRoutes, handle };
@@ -0,0 +1,100 @@
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;
100
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/e2e/decider/api.ts"],"names":[],"mappings":"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","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"]}
@@ -0,0 +1,100 @@
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
+ };
100
+ //# sourceMappingURL=api.mjs.map
@@ -0,0 +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":"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","names":[]}
@@ -0,0 +1,115 @@
1
+ "use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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; }
2
+
3
+
4
+ var _emmett = require('@event-driven-io/emmett');
5
+ require('fastify');
6
+ var _strict = require('node:assert/strict'); var _strict2 = _interopRequireDefault(_strict);
7
+ var _nodetest = require('node:test');
8
+ var _uuid = require('uuid');
9
+ var _ = require('../..');
10
+ var _api = require('./api');
11
+ var _businessLogic = require('./businessLogic');
12
+ _nodetest.describe.call(void 0, "Application logic with optimistic concurrency using Fastify", () => {
13
+ let app;
14
+ let eventStore;
15
+ _nodetest.beforeEach.call(void 0, async () => {
16
+ eventStore = _emmett.getInMemoryEventStore.call(void 0, );
17
+ const registerRoutes = _api.RegisterRoutes.call(void 0, eventStore);
18
+ app = await _.getApplication.call(void 0, { registerRoutes });
19
+ });
20
+ _nodetest.it.call(void 0, "Should handle requests correctly", async () => {
21
+ const clientId = _uuid.v4.call(void 0, );
22
+ const createResponse = await app.inject({
23
+ method: "POST",
24
+ url: `/clients/${clientId}/shopping-carts`
25
+ });
26
+ const current = createResponse.json();
27
+ if (!_optionalChain([current, 'optionalAccess', _3 => _3.id])) {
28
+ _strict2.default.fail();
29
+ }
30
+ _strict2.default.ok(current.id);
31
+ const shoppingCartId = current.id;
32
+ const twoPairsOfShoes = {
33
+ quantity: 2,
34
+ productId: "123"
35
+ };
36
+ let response = await app.inject({
37
+ method: "POST",
38
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,
39
+ body: twoPairsOfShoes
40
+ });
41
+ _strict2.default.equal(response.statusCode, 204);
42
+ const tShirt = {
43
+ productId: "456",
44
+ quantity: 1
45
+ };
46
+ response = await app.inject({
47
+ method: "POST",
48
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,
49
+ body: tShirt
50
+ });
51
+ _strict2.default.equal(response.statusCode, 204);
52
+ const pairOfShoes = {
53
+ productId: "123",
54
+ quantity: 1,
55
+ unitPrice: 100
56
+ };
57
+ response = await app.inject({
58
+ method: "DELETE",
59
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items?productId=${pairOfShoes.productId}&quantity=${pairOfShoes.quantity}&unitPrice=${pairOfShoes.unitPrice}`
60
+ });
61
+ _strict2.default.equal(response.statusCode, 204);
62
+ response = await app.inject({
63
+ method: "POST",
64
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/confirm`
65
+ });
66
+ _strict2.default.equal(response.statusCode, 204);
67
+ response = await app.inject({
68
+ method: "DELETE",
69
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}`
70
+ });
71
+ _strict2.default.equal(response.statusCode, 403);
72
+ _emmett.assertMatches.call(void 0, response.json(), {
73
+ detail: _businessLogic.ShoppingCartErrors.CART_IS_ALREADY_CLOSED
74
+ });
75
+ const result = await eventStore.readStream(shoppingCartId);
76
+ _strict2.default.ok(result);
77
+ _strict2.default.equal(result.events.length, Number(5));
78
+ _emmett.assertMatches.call(void 0, _optionalChain([result, 'optionalAccess', _4 => _4.events]), [
79
+ {
80
+ type: "ShoppingCartOpened",
81
+ data: {
82
+ shoppingCartId,
83
+ clientId
84
+ //openedAt,
85
+ }
86
+ },
87
+ {
88
+ type: "ProductItemAddedToShoppingCart",
89
+ data: {
90
+ shoppingCartId,
91
+ productItem: twoPairsOfShoes
92
+ }
93
+ },
94
+ {
95
+ type: "ProductItemAddedToShoppingCart",
96
+ data: {
97
+ shoppingCartId,
98
+ productItem: tShirt
99
+ }
100
+ },
101
+ {
102
+ type: "ProductItemRemovedFromShoppingCart",
103
+ data: { shoppingCartId, productItem: pairOfShoes }
104
+ },
105
+ {
106
+ type: "ShoppingCartConfirmed",
107
+ data: {
108
+ shoppingCartId
109
+ //confirmedAt,
110
+ }
111
+ }
112
+ ]);
113
+ });
114
+ });
115
+ //# sourceMappingURL=applicationLogicWithOC.int.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/e2e/decider/applicationLogicWithOC.int.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-floating-promises */\nimport {\n assertMatches,\n getInMemoryEventStore,\n type EventStore,\n} from '@event-driven-io/emmett';\nimport { type FastifyInstance } from 'fastify';\nimport assert from 'node:assert/strict';\nimport { beforeEach, describe, it } from 'node:test';\nimport { v4 as uuid } from 'uuid';\nimport { getApplication } from '../..';\nimport { RegisterRoutes } from './api';\nimport { ShoppingCartErrors } from './businessLogic';\nimport type { ShoppingCartEvent } from './shoppingCart';\n\ndescribe('Application logic with optimistic concurrency using Fastify', () => {\n let app: FastifyInstance;\n let eventStore: EventStore;\n beforeEach(async () => {\n eventStore = getInMemoryEventStore();\n const registerRoutes = RegisterRoutes(eventStore);\n app = await getApplication({ registerRoutes });\n });\n\n it('Should handle requests correctly', async () => {\n const clientId = uuid();\n ///////////////////////////////////////////////////\n // 1. Open Shopping Cart\n ///////////////////////////////////////////////////\n\n const createResponse = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts`,\n });\n\n const current = createResponse.json<{ id: string }>();\n if (!current?.id) {\n assert.fail();\n }\n assert.ok(current.id);\n\n const shoppingCartId = current.id;\n ///////////////////////////////////////////////////\n // 2. Add Two Pair of Shoes\n ///////////////////////////////////////////////////\n const twoPairsOfShoes = {\n quantity: 2,\n productId: '123',\n };\n let response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,\n body: twoPairsOfShoes,\n });\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 3. Add T-Shirt\n ///////////////////////////////////////////////////\n const tShirt = {\n productId: '456',\n quantity: 1,\n };\n\n response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,\n body: tShirt,\n });\n\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 4. Remove pair of shoes\n ///////////////////////////////////////////////////\n const pairOfShoes = {\n productId: '123',\n quantity: 1,\n unitPrice: 100,\n };\n\n response = await app.inject({\n method: 'DELETE',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items?productId=${pairOfShoes.productId}&quantity=${pairOfShoes.quantity}&unitPrice=${pairOfShoes.unitPrice}`,\n });\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 5. Confirm cart\n ///////////////////////////////////////////////////\n\n response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/confirm`,\n });\n\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 6. Try Cancel Cart\n ///////////////////////////////////////////////////\n response = await app.inject({\n method: 'DELETE',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}`,\n });\n\n assert.equal(response.statusCode, 403);\n assertMatches(response.json(), {\n detail: ShoppingCartErrors.CART_IS_ALREADY_CLOSED,\n });\n const result =\n await eventStore.readStream<ShoppingCartEvent>(shoppingCartId);\n\n assert.ok(result);\n assert.equal(result.events.length, Number(5));\n\n assertMatches(result?.events, [\n {\n type: 'ShoppingCartOpened',\n data: {\n shoppingCartId,\n clientId,\n //openedAt,\n },\n },\n {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId,\n productItem: twoPairsOfShoes,\n },\n },\n {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId,\n productItem: tShirt,\n },\n },\n {\n type: 'ProductItemRemovedFromShoppingCart',\n data: { shoppingCartId, productItem: pairOfShoes },\n },\n {\n type: 'ShoppingCartConfirmed',\n data: {\n shoppingCartId,\n //confirmedAt,\n },\n },\n ]);\n });\n});\n"]}
@@ -0,0 +1,115 @@
1
+ import {
2
+ assertMatches,
3
+ getInMemoryEventStore
4
+ } from "@event-driven-io/emmett";
5
+ import {} from "fastify";
6
+ import assert from "node:assert/strict";
7
+ import { beforeEach, describe, it } from "node:test";
8
+ import { v4 as uuid } from "uuid";
9
+ import { getApplication } from "../..";
10
+ import { RegisterRoutes } from "./api";
11
+ import { ShoppingCartErrors } from "./businessLogic";
12
+ describe("Application logic with optimistic concurrency using Fastify", () => {
13
+ let app;
14
+ let eventStore;
15
+ beforeEach(async () => {
16
+ eventStore = getInMemoryEventStore();
17
+ const registerRoutes = RegisterRoutes(eventStore);
18
+ app = await getApplication({ registerRoutes });
19
+ });
20
+ it("Should handle requests correctly", async () => {
21
+ const clientId = uuid();
22
+ const createResponse = await app.inject({
23
+ method: "POST",
24
+ url: `/clients/${clientId}/shopping-carts`
25
+ });
26
+ const current = createResponse.json();
27
+ if (!current?.id) {
28
+ assert.fail();
29
+ }
30
+ assert.ok(current.id);
31
+ const shoppingCartId = current.id;
32
+ const twoPairsOfShoes = {
33
+ quantity: 2,
34
+ productId: "123"
35
+ };
36
+ let response = await app.inject({
37
+ method: "POST",
38
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,
39
+ body: twoPairsOfShoes
40
+ });
41
+ assert.equal(response.statusCode, 204);
42
+ const tShirt = {
43
+ productId: "456",
44
+ quantity: 1
45
+ };
46
+ response = await app.inject({
47
+ method: "POST",
48
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,
49
+ body: tShirt
50
+ });
51
+ assert.equal(response.statusCode, 204);
52
+ const pairOfShoes = {
53
+ productId: "123",
54
+ quantity: 1,
55
+ unitPrice: 100
56
+ };
57
+ response = await app.inject({
58
+ method: "DELETE",
59
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items?productId=${pairOfShoes.productId}&quantity=${pairOfShoes.quantity}&unitPrice=${pairOfShoes.unitPrice}`
60
+ });
61
+ assert.equal(response.statusCode, 204);
62
+ response = await app.inject({
63
+ method: "POST",
64
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/confirm`
65
+ });
66
+ assert.equal(response.statusCode, 204);
67
+ response = await app.inject({
68
+ method: "DELETE",
69
+ url: `/clients/${clientId}/shopping-carts/${shoppingCartId}`
70
+ });
71
+ assert.equal(response.statusCode, 403);
72
+ assertMatches(response.json(), {
73
+ detail: ShoppingCartErrors.CART_IS_ALREADY_CLOSED
74
+ });
75
+ const result = await eventStore.readStream(shoppingCartId);
76
+ assert.ok(result);
77
+ assert.equal(result.events.length, Number(5));
78
+ assertMatches(result?.events, [
79
+ {
80
+ type: "ShoppingCartOpened",
81
+ data: {
82
+ shoppingCartId,
83
+ clientId
84
+ //openedAt,
85
+ }
86
+ },
87
+ {
88
+ type: "ProductItemAddedToShoppingCart",
89
+ data: {
90
+ shoppingCartId,
91
+ productItem: twoPairsOfShoes
92
+ }
93
+ },
94
+ {
95
+ type: "ProductItemAddedToShoppingCart",
96
+ data: {
97
+ shoppingCartId,
98
+ productItem: tShirt
99
+ }
100
+ },
101
+ {
102
+ type: "ProductItemRemovedFromShoppingCart",
103
+ data: { shoppingCartId, productItem: pairOfShoes }
104
+ },
105
+ {
106
+ type: "ShoppingCartConfirmed",
107
+ data: {
108
+ shoppingCartId
109
+ //confirmedAt,
110
+ }
111
+ }
112
+ ]);
113
+ });
114
+ });
115
+ //# sourceMappingURL=applicationLogicWithOC.int.spec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/e2e/decider/applicationLogicWithOC.int.spec.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-floating-promises */\nimport {\n assertMatches,\n getInMemoryEventStore,\n type EventStore,\n} from '@event-driven-io/emmett';\nimport { type FastifyInstance } from 'fastify';\nimport assert from 'node:assert/strict';\nimport { beforeEach, describe, it } from 'node:test';\nimport { v4 as uuid } from 'uuid';\nimport { getApplication } from '../..';\nimport { RegisterRoutes } from './api';\nimport { ShoppingCartErrors } from './businessLogic';\nimport type { ShoppingCartEvent } from './shoppingCart';\n\ndescribe('Application logic with optimistic concurrency using Fastify', () => {\n let app: FastifyInstance;\n let eventStore: EventStore;\n beforeEach(async () => {\n eventStore = getInMemoryEventStore();\n const registerRoutes = RegisterRoutes(eventStore);\n app = await getApplication({ registerRoutes });\n });\n\n it('Should handle requests correctly', async () => {\n const clientId = uuid();\n ///////////////////////////////////////////////////\n // 1. Open Shopping Cart\n ///////////////////////////////////////////////////\n\n const createResponse = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts`,\n });\n\n const current = createResponse.json<{ id: string }>();\n if (!current?.id) {\n assert.fail();\n }\n assert.ok(current.id);\n\n const shoppingCartId = current.id;\n ///////////////////////////////////////////////////\n // 2. Add Two Pair of Shoes\n ///////////////////////////////////////////////////\n const twoPairsOfShoes = {\n quantity: 2,\n productId: '123',\n };\n let response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,\n body: twoPairsOfShoes,\n });\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 3. Add T-Shirt\n ///////////////////////////////////////////////////\n const tShirt = {\n productId: '456',\n quantity: 1,\n };\n\n response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items`,\n body: tShirt,\n });\n\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 4. Remove pair of shoes\n ///////////////////////////////////////////////////\n const pairOfShoes = {\n productId: '123',\n quantity: 1,\n unitPrice: 100,\n };\n\n response = await app.inject({\n method: 'DELETE',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/product-items?productId=${pairOfShoes.productId}&quantity=${pairOfShoes.quantity}&unitPrice=${pairOfShoes.unitPrice}`,\n });\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 5. Confirm cart\n ///////////////////////////////////////////////////\n\n response = await app.inject({\n method: 'POST',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}/confirm`,\n });\n\n assert.equal(response.statusCode, 204);\n\n ///////////////////////////////////////////////////\n // 6. Try Cancel Cart\n ///////////////////////////////////////////////////\n response = await app.inject({\n method: 'DELETE',\n url: `/clients/${clientId}/shopping-carts/${shoppingCartId}`,\n });\n\n assert.equal(response.statusCode, 403);\n assertMatches(response.json(), {\n detail: ShoppingCartErrors.CART_IS_ALREADY_CLOSED,\n });\n const result =\n await eventStore.readStream<ShoppingCartEvent>(shoppingCartId);\n\n assert.ok(result);\n assert.equal(result.events.length, Number(5));\n\n assertMatches(result?.events, [\n {\n type: 'ShoppingCartOpened',\n data: {\n shoppingCartId,\n clientId,\n //openedAt,\n },\n },\n {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId,\n productItem: twoPairsOfShoes,\n },\n },\n {\n type: 'ProductItemAddedToShoppingCart',\n data: {\n shoppingCartId,\n productItem: tShirt,\n },\n },\n {\n type: 'ProductItemRemovedFromShoppingCart',\n data: { shoppingCartId, productItem: pairOfShoes },\n },\n {\n type: 'ShoppingCartConfirmed',\n data: {\n shoppingCartId,\n //confirmedAt,\n },\n },\n ]);\n });\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,53 @@
1
+ import { Decider } from '@event-driven-io/emmett';
2
+ import { PricedProductItem, ShoppingCart, ShoppingCartEvent } from './shoppingCart.mjs';
3
+
4
+ type OpenShoppingCart = {
5
+ type: 'OpenShoppingCart';
6
+ data: {
7
+ shoppingCartId: string;
8
+ clientId: string;
9
+ now: Date;
10
+ };
11
+ };
12
+ type AddProductItemToShoppingCart = {
13
+ type: 'AddProductItemToShoppingCart';
14
+ data: {
15
+ shoppingCartId: string;
16
+ productItem: PricedProductItem;
17
+ };
18
+ };
19
+ type RemoveProductItemFromShoppingCart = {
20
+ type: 'RemoveProductItemFromShoppingCart';
21
+ data: {
22
+ shoppingCartId: string;
23
+ productItem: PricedProductItem;
24
+ };
25
+ };
26
+ type ConfirmShoppingCart = {
27
+ type: 'ConfirmShoppingCart';
28
+ data: {
29
+ shoppingCartId: string;
30
+ now: Date;
31
+ };
32
+ };
33
+ type CancelShoppingCart = {
34
+ type: 'CancelShoppingCart';
35
+ data: {
36
+ shoppingCartId: string;
37
+ now: Date;
38
+ };
39
+ };
40
+ type ShoppingCartCommand = OpenShoppingCart | AddProductItemToShoppingCart | RemoveProductItemFromShoppingCart | ConfirmShoppingCart | CancelShoppingCart;
41
+ declare const enum ShoppingCartErrors {
42
+ CART_ALREADY_EXISTS = "CART_ALREADY_EXISTS",
43
+ CART_IS_ALREADY_CLOSED = "CART_IS_ALREADY_CLOSED",
44
+ PRODUCT_ITEM_NOT_FOUND = "PRODUCT_ITEM_NOT_FOUND",
45
+ CART_IS_EMPTY = "CART_IS_EMPTY",
46
+ UNKNOWN_EVENT_TYPE = "UNKNOWN_EVENT_TYPE",
47
+ UNKNOWN_COMMAND_TYPE = "UNKNOWN_COMMAND_TYPE"
48
+ }
49
+ declare const assertProductItemExists: (productItems: PricedProductItem[], { productId, quantity, unitPrice }: PricedProductItem) => void;
50
+ declare const decide: ({ type, data: command }: ShoppingCartCommand, shoppingCart: ShoppingCart) => ShoppingCartEvent;
51
+ declare const decider: Decider<ShoppingCart, ShoppingCartCommand, ShoppingCartEvent>;
52
+
53
+ export { type AddProductItemToShoppingCart, type CancelShoppingCart, type ConfirmShoppingCart, type OpenShoppingCart, type RemoveProductItemFromShoppingCart, type ShoppingCartCommand, ShoppingCartErrors, assertProductItemExists, decide, decider };