1 | 'use strict';
|
2 |
|
3 | const uuid = require('uuid/v4');
|
4 |
|
5 | const formats = require('./formats');
|
6 |
|
7 | const MESSAGE_TYPE = 'event';
|
8 |
|
9 | const EVENT_TYPES = {
|
10 | CRUD: 'crud',
|
11 | DOMAIN: 'domain',
|
12 | DENORMALIZED_DOMAIN: 'denormalized',
|
13 | DENORMALIZER: 'readmodel',
|
14 | SYSTEM: 'system',
|
15 | };
|
16 |
|
17 | const reversedEventType = Object.entries(EVENT_TYPES).reduce((rev, [, value]) => {
|
18 | rev[value] = true;
|
19 | return rev;
|
20 | }, {});
|
21 |
|
22 | const fullNameGenerator = {
|
23 | [EVENT_TYPES.DOMAIN]: e => `${EVENT_TYPES.DOMAIN}.${e.context}.${e.aggregate.name}.${e.name}`,
|
24 | [EVENT_TYPES.DENORMALIZED_DOMAIN]: e => `${EVENT_TYPES.DENORMALIZED_DOMAIN}.${e.readmodel.type}.${e.context}.${e.aggregate.name}.${e.name}`,
|
25 | [EVENT_TYPES.DENORMALIZER]: e => `${EVENT_TYPES.DENORMALIZER}.${e.readmodel.type}.${e.readmodel.collection}.${e.name}`,
|
26 | [EVENT_TYPES.SYSTEM]: e => `${EVENT_TYPES.SYSTEM}.${e.name}`,
|
27 | [EVENT_TYPES.CRUD]: e => `${EVENT_TYPES.CRUD}.${e.context}.${e.readmodel.collection}.${e.name}`,
|
28 | };
|
29 |
|
30 | class Event {
|
31 | constructor({
|
32 | type,
|
33 | id,
|
34 | context,
|
35 | aggregate,
|
36 | event,
|
37 | command,
|
38 | name,
|
39 | readmodel = {},
|
40 | payload = {},
|
41 | metadata = {},
|
42 | }) {
|
43 | if (!type || !reversedEventType[type])
|
44 | throw new Error('Invalid event type');
|
45 |
|
46 | if (!formats.isAlphanumeric(name, { minLength: 1 }))
|
47 | throw new Error('Event name is missing.');
|
48 |
|
49 | if (!formats.isObject(payload))
|
50 | throw new Error('Payload must be an object.');
|
51 |
|
52 | if (!formats.isObject(metadata))
|
53 | throw new Error('Metadata must be an object.');
|
54 |
|
55 | this.type = type;
|
56 | this.id = id || uuid();
|
57 | this.name = name;
|
58 | this.payload = payload;
|
59 | this.metadata = metadata;
|
60 | this.command = command;
|
61 |
|
62 | if (type === EVENT_TYPES.CRUD) {
|
63 | if (!formats.isAlphanumeric(context, { minLength: 1 }))
|
64 | throw new Error('Invalid context.');
|
65 |
|
66 | if (!formats.isObject(readmodel))
|
67 | throw new Error('Invalid readmodel.');
|
68 |
|
69 | if (!formats.isName(readmodel.collection, { minLength: 1 }))
|
70 | throw new Error('Readmodel collection is missing.');
|
71 |
|
72 | this.context = context;
|
73 | this.readmodel = readmodel;
|
74 | }
|
75 |
|
76 | if (type === EVENT_TYPES.DENORMALIZER || type === EVENT_TYPES.DOMAIN || type === EVENT_TYPES.DENORMALIZED_DOMAIN) {
|
77 | if (!formats.isAlphanumeric(context, { minLength: 1 }))
|
78 | throw new Error('Invalid context.');
|
79 |
|
80 | if (!formats.isObject(aggregate))
|
81 | throw new Error('Invalid aggregate.');
|
82 |
|
83 | if (!formats.isAlphanumeric(aggregate.name, { minLength: 1 }))
|
84 | throw new Error('Aggregate name is missing.');
|
85 |
|
86 | this.context = context;
|
87 | this.aggregate = { name: aggregate.name, id: aggregate.id, revision: aggregate.revision };
|
88 |
|
89 | if (type === EVENT_TYPES.DENORMALIZED_DOMAIN) {
|
90 | if (!formats.isObject(readmodel))
|
91 | throw new Error('Invalid readmodel.');
|
92 | this.readmodel = readmodel;
|
93 | }
|
94 |
|
95 |
|
96 | if (type === EVENT_TYPES.DENORMALIZER) {
|
97 | if (!formats.isObject(event))
|
98 | throw new Error('Invalid event.');
|
99 |
|
100 | if (!formats.isAlphanumeric(event.name, { minLength: 1 }))
|
101 | throw new Error('Event name is missing.');
|
102 |
|
103 | if (!formats.isString(event.id, { minLength: 1 }))
|
104 | throw new Error('Event id is missing.');
|
105 |
|
106 | if (!formats.isObject(readmodel))
|
107 | throw new Error('Invalid readmodel.');
|
108 |
|
109 | if (!formats.isName(readmodel.collection, { minLength: 1 }))
|
110 | throw new Error('Readmodel collection is missing.');
|
111 |
|
112 |
|
113 | this.event = { name: event.name, id: event.id };
|
114 | this.readmodel = readmodel;
|
115 | this.metadata.timestamp = Date.now();
|
116 | }
|
117 | }
|
118 | this.fullname = fullNameGenerator[type](this);
|
119 | this.routingKey = `event.${this.fullname}`;
|
120 | }
|
121 |
|
122 | get messageType() {
|
123 | return MESSAGE_TYPE;
|
124 | }
|
125 |
|
126 | static get fullNameGenerator() {
|
127 | return fullNameGenerator;
|
128 | }
|
129 |
|
130 | serialize() {
|
131 | return JSON.parse(JSON.stringify(this));
|
132 | }
|
133 |
|
134 | setCausationId(v) {
|
135 | this.metadata.CausationId = v;
|
136 | return this;
|
137 | }
|
138 |
|
139 | setCorrelationId(v) {
|
140 | this.metadata.correlationId = v;
|
141 | return this;
|
142 | }
|
143 |
|
144 | toNew(removeId = true, overwrites = {}) {
|
145 | const { id, ...current } = this;
|
146 | const newEvent = (removeId) ? { ...current, ...overwrites } : { id, ...current, ...overwrites };
|
147 | return new Event(newEvent);
|
148 | }
|
149 |
|
150 | static deserialize({
|
151 | type,
|
152 | id,
|
153 | context,
|
154 | aggregate,
|
155 | event,
|
156 | name,
|
157 | readmodel,
|
158 | payload,
|
159 | metadata,
|
160 | command,
|
161 | }) {
|
162 | return new Event({
|
163 | type,
|
164 | id,
|
165 | context,
|
166 | aggregate,
|
167 | event,
|
168 | name,
|
169 | readmodel,
|
170 | payload,
|
171 | metadata,
|
172 | command,
|
173 | });
|
174 | }
|
175 |
|
176 | static definition() {
|
177 | return {
|
178 | id: 'id',
|
179 | context: 'context',
|
180 |
|
181 |
|
182 | aggregateId: 'aggregate.id',
|
183 | aggregate: 'aggregate.name',
|
184 | revision: 'aggregate.revision',
|
185 |
|
186 |
|
187 | event: 'event.name',
|
188 | eventId: 'event.id',
|
189 |
|
190 |
|
191 | collection: 'readmodel.collection',
|
192 |
|
193 |
|
194 | name: 'name',
|
195 |
|
196 | payload: 'payload',
|
197 |
|
198 |
|
199 | meta: 'metadata',
|
200 | position: 'metadata.position',
|
201 | commitStamp: 'metadata.timestamp',
|
202 | correlationId: 'metadata.correlationId',
|
203 | causationId: 'metadata.causationId',
|
204 | };
|
205 | }
|
206 |
|
207 | static get EVENT_TYPES() {
|
208 | return EVENT_TYPES;
|
209 | }
|
210 | }
|
211 |
|
212 | module.exports = Event;
|