@event-driven-io/emmett-expressjs 0.43.0-beta.2 → 0.43.0-beta.20
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/README.md +390 -0
- package/dist/index.cjs +317 -461
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -52
- package/dist/index.d.ts +74 -52
- package/dist/index.js +229 -442
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,464 +1,251 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
1
|
import "express-async-errors";
|
|
3
|
-
|
|
4
|
-
// src/application.ts
|
|
5
2
|
import express, { Router } from "express";
|
|
6
|
-
import "express-async-errors";
|
|
7
3
|
import http from "http";
|
|
8
|
-
|
|
9
|
-
// ../emmett/dist/chunk-AZDDB5SF.js
|
|
10
|
-
var isNumber = (val) => typeof val === "number" && val === val;
|
|
11
|
-
var isString = (val) => typeof val === "string";
|
|
12
|
-
var EmmettError = class _EmmettError extends Error {
|
|
13
|
-
static Codes = {
|
|
14
|
-
ValidationError: 400,
|
|
15
|
-
IllegalStateError: 403,
|
|
16
|
-
NotFoundError: 404,
|
|
17
|
-
ConcurrencyError: 412,
|
|
18
|
-
InternalServerError: 500
|
|
19
|
-
};
|
|
20
|
-
errorCode;
|
|
21
|
-
constructor(options) {
|
|
22
|
-
const errorCode = options && typeof options === "object" && "errorCode" in options ? options.errorCode : isNumber(options) ? options : _EmmettError.Codes.InternalServerError;
|
|
23
|
-
const message = options && typeof options === "object" && "message" in options ? options.message : isString(options) ? options : `Error with status code '${errorCode}' ocurred during Emmett processing`;
|
|
24
|
-
super(message);
|
|
25
|
-
this.errorCode = errorCode;
|
|
26
|
-
Object.setPrototypeOf(this, _EmmettError.prototype);
|
|
27
|
-
}
|
|
28
|
-
static mapFrom(error) {
|
|
29
|
-
if (_EmmettError.isInstanceOf(error)) {
|
|
30
|
-
return error;
|
|
31
|
-
}
|
|
32
|
-
return new _EmmettError({
|
|
33
|
-
errorCode: "errorCode" in error && error.errorCode !== void 0 && error.errorCode !== null ? error.errorCode : _EmmettError.Codes.InternalServerError,
|
|
34
|
-
message: error.message ?? "An unknown error occurred"
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
static isInstanceOf(error, errorCode) {
|
|
38
|
-
return typeof error === "object" && error !== null && "errorCode" in error && isNumber(error.errorCode) && (errorCode === void 0 || error.errorCode === errorCode);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// ../emmett/dist/index.js
|
|
43
|
-
import { v4 as uuid4 } from "uuid";
|
|
44
|
-
import { v7 as uuid } from "uuid";
|
|
45
|
-
import retry from "async-retry";
|
|
46
|
-
import { v7 as uuid2 } from "uuid";
|
|
47
|
-
import { v4 as uuid3 } from "uuid";
|
|
48
|
-
import { v7 as uuid5 } from "uuid";
|
|
49
|
-
var emmettPrefix = "emt";
|
|
50
|
-
var defaultTag = `${emmettPrefix}:default`;
|
|
51
|
-
var unknownTag = `${emmettPrefix}:unknown`;
|
|
52
|
-
var ParseError = class extends Error {
|
|
53
|
-
constructor(text) {
|
|
54
|
-
super(`Cannot parse! ${text}`);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
var JSONParser = {
|
|
58
|
-
stringify: (value, options) => {
|
|
59
|
-
return JSON.stringify(
|
|
60
|
-
options?.map ? options.map(value) : value,
|
|
61
|
-
//TODO: Consider adding support to DateTime and adding specific format to mark that's a bigint
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
63
|
-
(_, v) => typeof v === "bigint" ? v.toString() : v
|
|
64
|
-
);
|
|
65
|
-
},
|
|
66
|
-
parse: (text, options) => {
|
|
67
|
-
const parsed = JSON.parse(text, options?.reviver);
|
|
68
|
-
if (options?.typeCheck && !options?.typeCheck(parsed))
|
|
69
|
-
throw new ParseError(text);
|
|
70
|
-
return options?.map ? options.map(parsed) : parsed;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
var textEncoder = new TextEncoder();
|
|
74
|
-
var AssertionError = class extends Error {
|
|
75
|
-
constructor(message2) {
|
|
76
|
-
super(message2);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
var isSubset = (superObj, subObj) => {
|
|
80
|
-
const sup = superObj;
|
|
81
|
-
const sub = subObj;
|
|
82
|
-
assertOk(sup);
|
|
83
|
-
assertOk(sub);
|
|
84
|
-
return Object.keys(sub).every((ele) => {
|
|
85
|
-
if (sub[ele] !== null && typeof sub[ele] == "object") {
|
|
86
|
-
return isSubset(sup[ele], sub[ele]);
|
|
87
|
-
}
|
|
88
|
-
return sub[ele] === sup[ele];
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
var assertFails = (message2) => {
|
|
92
|
-
throw new AssertionError(message2 ?? "That should not ever happened, right?");
|
|
93
|
-
};
|
|
94
|
-
var assertMatches = (actual, expected, message2) => {
|
|
95
|
-
if (!isSubset(actual, expected))
|
|
96
|
-
throw new AssertionError(
|
|
97
|
-
message2 ?? `subObj:
|
|
98
|
-
${JSONParser.stringify(expected)}
|
|
99
|
-
is not subset of
|
|
100
|
-
${JSONParser.stringify(actual)}`
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
function assertOk(obj, message2) {
|
|
104
|
-
if (!obj) throw new AssertionError(message2 ?? `Condition is not truthy`);
|
|
105
|
-
}
|
|
106
|
-
function assertEqual(expected, actual, message2) {
|
|
107
|
-
if (expected !== actual)
|
|
108
|
-
throw new AssertionError(
|
|
109
|
-
`${message2 ?? "Objects are not equal"}:
|
|
110
|
-
Expected: ${JSONParser.stringify(expected)}
|
|
111
|
-
Actual: ${JSONParser.stringify(actual)}`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
var WrapEventStore = (eventStore) => {
|
|
115
|
-
const appendedEvents = /* @__PURE__ */ new Map();
|
|
116
|
-
const wrapped = {
|
|
117
|
-
...eventStore,
|
|
118
|
-
aggregateStream(streamName, options) {
|
|
119
|
-
return eventStore.aggregateStream(streamName, options);
|
|
120
|
-
},
|
|
121
|
-
async readStream(streamName, options) {
|
|
122
|
-
return await eventStore.readStream(
|
|
123
|
-
streamName,
|
|
124
|
-
options
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
appendToStream: async (streamName, events, options) => {
|
|
128
|
-
const result = await eventStore.appendToStream(
|
|
129
|
-
streamName,
|
|
130
|
-
events,
|
|
131
|
-
options
|
|
132
|
-
);
|
|
133
|
-
const currentStream = appendedEvents.get(streamName) ?? [streamName, []];
|
|
134
|
-
appendedEvents.set(streamName, [
|
|
135
|
-
streamName,
|
|
136
|
-
[...currentStream[1], ...events]
|
|
137
|
-
]);
|
|
138
|
-
return result;
|
|
139
|
-
},
|
|
140
|
-
appendedEvents,
|
|
141
|
-
setup: async (streamName, events) => {
|
|
142
|
-
return eventStore.appendToStream(streamName, events);
|
|
143
|
-
}
|
|
144
|
-
// streamEvents: (): ReadableStream<
|
|
145
|
-
// // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
146
|
-
// ReadEvent<Event, ReadEventMetadataType> | GlobalSubscriptionEvent
|
|
147
|
-
// > => {
|
|
148
|
-
// return eventStore.streamEvents();
|
|
149
|
-
// },
|
|
150
|
-
};
|
|
151
|
-
return wrapped;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// src/middlewares/problemDetailsMiddleware.ts
|
|
4
|
+
import { EmmettError, WrapEventStore, assertEqual, assertFails, assertMatches, getInMemoryEventStore, isNumber } from "@event-driven-io/emmett";
|
|
155
5
|
import { ProblemDocument } from "http-problem-details";
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (mapError) problemDetails = mapError(error, request);
|
|
159
|
-
problemDetails = problemDetails ?? defaultErrorToProblemDetailsMapping(error);
|
|
160
|
-
sendProblem(response, problemDetails.status, { problem: problemDetails });
|
|
161
|
-
};
|
|
162
|
-
var defaultErrorToProblemDetailsMapping = (error) => {
|
|
163
|
-
let statusCode = 500;
|
|
164
|
-
if ("errorCode" in error && isNumber(error.errorCode) && error.errorCode >= 100 && error.errorCode < 600) {
|
|
165
|
-
statusCode = error.errorCode;
|
|
166
|
-
}
|
|
167
|
-
return new ProblemDocument({
|
|
168
|
-
detail: error.message,
|
|
169
|
-
status: statusCode
|
|
170
|
-
});
|
|
171
|
-
};
|
|
6
|
+
import supertest from "supertest";
|
|
7
|
+
import assert from "assert";
|
|
172
8
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (!disableUrlEncodingMiddleware)
|
|
188
|
-
app.use(
|
|
189
|
-
express.urlencoded({
|
|
190
|
-
extended: true
|
|
191
|
-
})
|
|
192
|
-
);
|
|
193
|
-
for (const api of apis) {
|
|
194
|
-
api(router);
|
|
195
|
-
}
|
|
196
|
-
app.use(router);
|
|
197
|
-
if (!disableProblemDetailsMiddleware)
|
|
198
|
-
app.use(problemDetailsMiddleware(mapError));
|
|
199
|
-
return app;
|
|
200
|
-
};
|
|
201
|
-
var startAPI = (app, options = { port: 3e3 }) => {
|
|
202
|
-
const { port } = options;
|
|
203
|
-
const server = http.createServer(app);
|
|
204
|
-
server.on("listening", () => {
|
|
205
|
-
console.info("server up listening");
|
|
206
|
-
});
|
|
207
|
-
return server.listen(port);
|
|
9
|
+
//#region src/middlewares/problemDetailsMiddleware.ts
|
|
10
|
+
const problemDetailsMiddleware = (mapError) => (error, request, response, _next) => {
|
|
11
|
+
let problemDetails;
|
|
12
|
+
if (mapError) problemDetails = mapError(error, request);
|
|
13
|
+
problemDetails = problemDetails ?? defaultErrorToProblemDetailsMapping(error);
|
|
14
|
+
sendProblem(response, problemDetails.status, { problem: problemDetails });
|
|
15
|
+
};
|
|
16
|
+
const defaultErrorToProblemDetailsMapping = (error) => {
|
|
17
|
+
let statusCode = 500;
|
|
18
|
+
if ("errorCode" in error && isNumber(error.errorCode) && error.errorCode >= 100 && error.errorCode < 600) statusCode = error.errorCode;
|
|
19
|
+
return new ProblemDocument({
|
|
20
|
+
detail: error.message,
|
|
21
|
+
status: statusCode
|
|
22
|
+
});
|
|
208
23
|
};
|
|
209
24
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/application.ts
|
|
27
|
+
const getApplication = (options) => {
|
|
28
|
+
const app = express();
|
|
29
|
+
const { apis, mapError, enableDefaultExpressEtag, disableJsonMiddleware, disableUrlEncodingMiddleware, disableProblemDetailsMiddleware } = options;
|
|
30
|
+
const router = Router();
|
|
31
|
+
app.set("etag", enableDefaultExpressEtag ?? false);
|
|
32
|
+
if (!disableJsonMiddleware) app.use(express.json());
|
|
33
|
+
if (!disableUrlEncodingMiddleware) app.use(express.urlencoded({ extended: true }));
|
|
34
|
+
for (const api of apis) api(router);
|
|
35
|
+
app.use(router);
|
|
36
|
+
if (!disableProblemDetailsMiddleware) app.use(problemDetailsMiddleware(mapError));
|
|
37
|
+
return app;
|
|
38
|
+
};
|
|
39
|
+
const startAPI = (app, options = { port: 3e3 }) => {
|
|
40
|
+
const { port } = options;
|
|
41
|
+
const server = http.createServer(app);
|
|
42
|
+
server.on("listening", () => {
|
|
43
|
+
console.info("server up listening");
|
|
44
|
+
});
|
|
45
|
+
return server.listen(port);
|
|
225
46
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/etag.ts
|
|
50
|
+
const HeaderNames = {
|
|
51
|
+
IF_MATCH: "if-match",
|
|
52
|
+
IF_NOT_MATCH: "if-not-match",
|
|
53
|
+
ETag: "etag"
|
|
54
|
+
};
|
|
55
|
+
const WeakETagRegex = /W\/"(-?\d+.*)"/;
|
|
56
|
+
let ETagErrors = /* @__PURE__ */ function(ETagErrors) {
|
|
57
|
+
ETagErrors["WRONG_WEAK_ETAG_FORMAT"] = "WRONG_WEAK_ETAG_FORMAT";
|
|
58
|
+
ETagErrors["MISSING_IF_MATCH_HEADER"] = "MISSING_IF_MATCH_HEADER";
|
|
59
|
+
ETagErrors["MISSING_IF_NOT_MATCH_HEADER"] = "MISSING_IF_NOT_MATCH_HEADER";
|
|
60
|
+
return ETagErrors;
|
|
61
|
+
}({});
|
|
62
|
+
const isWeakETag = (etag) => {
|
|
63
|
+
return WeakETagRegex.test(etag);
|
|
64
|
+
};
|
|
65
|
+
const getWeakETagValue = (etag) => {
|
|
66
|
+
const result = WeakETagRegex.exec(etag);
|
|
67
|
+
if (result === null || result.length === 0) throw new EmmettError({
|
|
68
|
+
errorCode: EmmettError.Codes.ConcurrencyError,
|
|
69
|
+
message: "WRONG_WEAK_ETAG_FORMAT"
|
|
70
|
+
});
|
|
71
|
+
return result[1];
|
|
72
|
+
};
|
|
73
|
+
const toWeakETag = (value) => {
|
|
74
|
+
return `W/"${value}"`;
|
|
75
|
+
};
|
|
76
|
+
const getETagFromIfMatch = (request) => {
|
|
77
|
+
const etag = request.headers[HeaderNames.IF_MATCH];
|
|
78
|
+
if (etag === void 0) throw new EmmettError({
|
|
79
|
+
errorCode: EmmettError.Codes.ConcurrencyError,
|
|
80
|
+
message: "MISSING_IF_MATCH_HEADER"
|
|
81
|
+
});
|
|
82
|
+
return etag;
|
|
83
|
+
};
|
|
84
|
+
const getETagFromIfNotMatch = (request) => {
|
|
85
|
+
const etag = request.headers[HeaderNames.IF_NOT_MATCH];
|
|
86
|
+
if (etag === void 0) throw new EmmettError({
|
|
87
|
+
errorCode: EmmettError.Codes.ConcurrencyError,
|
|
88
|
+
message: "MISSING_IF_NOT_MATCH_HEADER"
|
|
89
|
+
});
|
|
90
|
+
return Array.isArray(etag) ? etag[0] : etag;
|
|
91
|
+
};
|
|
92
|
+
const setETag = (response, etag) => {
|
|
93
|
+
response.setHeader(HeaderNames.ETag, etag);
|
|
94
|
+
};
|
|
95
|
+
const getETagValueFromIfMatch = (request) => {
|
|
96
|
+
const eTagValue = getETagFromIfMatch(request);
|
|
97
|
+
return isWeakETag(eTagValue) ? getWeakETagValue(eTagValue) : eTagValue;
|
|
265
98
|
};
|
|
266
99
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/handler.ts
|
|
102
|
+
const on = (handle) => async (request, response, _next) => {
|
|
103
|
+
return (await Promise.resolve(handle(request)))(response);
|
|
271
104
|
};
|
|
272
|
-
|
|
273
|
-
|
|
105
|
+
const OK = (options) => (response) => {
|
|
106
|
+
send(response, 200, options);
|
|
274
107
|
};
|
|
275
|
-
|
|
276
|
-
|
|
108
|
+
const Created = (options) => (response) => {
|
|
109
|
+
sendCreated(response, options);
|
|
277
110
|
};
|
|
278
|
-
|
|
279
|
-
|
|
111
|
+
const Accepted = (options) => (response) => {
|
|
112
|
+
sendAccepted(response, options);
|
|
280
113
|
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
114
|
+
const NoContent = (options) => HttpResponse(204, options);
|
|
115
|
+
const HttpResponse = (statusCode, options) => (response) => {
|
|
116
|
+
send(response, statusCode, options);
|
|
284
117
|
};
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
118
|
+
const BadRequest = (options) => HttpProblem(400, options);
|
|
119
|
+
const Forbidden = (options) => HttpProblem(403, options);
|
|
120
|
+
const NotFound = (options) => HttpProblem(404, options);
|
|
121
|
+
const Conflict = (options) => HttpProblem(409, options);
|
|
122
|
+
const PreconditionFailed = (options) => HttpProblem(412, options);
|
|
123
|
+
const HttpProblem = (statusCode, options) => (response) => {
|
|
124
|
+
sendProblem(response, statusCode, options);
|
|
292
125
|
};
|
|
293
126
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/responses.ts
|
|
129
|
+
const DefaultHttpResponseOptions = {};
|
|
130
|
+
const DefaultHttpProblemResponseOptions = { problemDetails: "Error occured!" };
|
|
131
|
+
const sendCreated = (response, { eTag, ...options }) => send(response, 201, {
|
|
132
|
+
location: "url" in options ? options.url : `${response.req.url}/${options.createdId}`,
|
|
133
|
+
body: "createdId" in options ? {
|
|
134
|
+
id: options.createdId,
|
|
135
|
+
...options.body ?? {}
|
|
136
|
+
} : options.body,
|
|
137
|
+
eTag
|
|
304
138
|
});
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
response.json(problemDetails);
|
|
139
|
+
const sendAccepted = (response, options) => send(response, 202, options);
|
|
140
|
+
const sendNoContent = (response, options) => send(response, 204, options);
|
|
141
|
+
const send = (response, statusCode, options) => {
|
|
142
|
+
const { location, body, eTag } = options ?? DefaultHttpResponseOptions;
|
|
143
|
+
if (eTag) setETag(response, eTag);
|
|
144
|
+
if (location) response.setHeader("Location", location);
|
|
145
|
+
if (body) {
|
|
146
|
+
response.statusCode = statusCode;
|
|
147
|
+
response.send(body);
|
|
148
|
+
} else response.sendStatus(statusCode);
|
|
149
|
+
};
|
|
150
|
+
const sendProblem = (response, statusCode, options) => {
|
|
151
|
+
options = options ?? DefaultHttpProblemResponseOptions;
|
|
152
|
+
const { location, eTag } = options;
|
|
153
|
+
const problemDetails = "problem" in options ? options.problem : new ProblemDocument({
|
|
154
|
+
detail: options.problemDetails,
|
|
155
|
+
status: statusCode
|
|
156
|
+
});
|
|
157
|
+
if (eTag) setETag(response, eTag);
|
|
158
|
+
if (location) response.setHeader("Location", location);
|
|
159
|
+
response.setHeader("Content-Type", "application/problem+json");
|
|
160
|
+
response.statusCode = statusCode;
|
|
161
|
+
response.json(problemDetails);
|
|
329
162
|
};
|
|
330
163
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
};
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/testing/apiE2ESpecification.ts
|
|
166
|
+
function apiE2ESpecificationFor(optionsOrGetApplication, getApplication) {
|
|
167
|
+
const resolveApplication = () => {
|
|
168
|
+
if (typeof optionsOrGetApplication === "function" && getApplication) return getApplication(optionsOrGetApplication());
|
|
169
|
+
if (typeof optionsOrGetApplication !== "object") throw new EmmettError("Invalid arguments provided to apiE2ESpecificationFor. Expected either an options object or a getEventStore function and getApplication function.");
|
|
170
|
+
const eventStore = optionsOrGetApplication.getEventStore?.() ?? getInMemoryEventStore();
|
|
171
|
+
return optionsOrGetApplication.getApplication(eventStore);
|
|
172
|
+
};
|
|
173
|
+
return (...givenRequests) => {
|
|
174
|
+
const application = resolveApplication();
|
|
175
|
+
return { when: (setupRequest) => {
|
|
176
|
+
const handle = async () => {
|
|
177
|
+
for (const requestFn of givenRequests) await requestFn(supertest(application));
|
|
178
|
+
return setupRequest(supertest(application));
|
|
179
|
+
};
|
|
180
|
+
return { then: async (verify) => {
|
|
181
|
+
const response = await handle();
|
|
182
|
+
verify.forEach((assertion) => {
|
|
183
|
+
if (assertion(response) === false) assert.fail();
|
|
184
|
+
});
|
|
185
|
+
} };
|
|
186
|
+
} };
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const ApiE2ESpecification = { for: apiE2ESpecificationFor };
|
|
363
190
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
};
|
|
425
|
-
export {
|
|
426
|
-
Accepted,
|
|
427
|
-
ApiE2ESpecification,
|
|
428
|
-
ApiSpecification,
|
|
429
|
-
BadRequest,
|
|
430
|
-
Conflict,
|
|
431
|
-
Created,
|
|
432
|
-
DefaultHttpProblemResponseOptions,
|
|
433
|
-
DefaultHttpResponseOptions,
|
|
434
|
-
ETagErrors,
|
|
435
|
-
Forbidden,
|
|
436
|
-
HeaderNames,
|
|
437
|
-
HttpProblem,
|
|
438
|
-
HttpResponse,
|
|
439
|
-
NoContent,
|
|
440
|
-
NotFound,
|
|
441
|
-
OK,
|
|
442
|
-
PreconditionFailed,
|
|
443
|
-
WeakETagRegex,
|
|
444
|
-
existingStream,
|
|
445
|
-
expect,
|
|
446
|
-
expectError,
|
|
447
|
-
expectNewEvents,
|
|
448
|
-
expectResponse,
|
|
449
|
-
getApplication,
|
|
450
|
-
getETagFromIfMatch,
|
|
451
|
-
getETagFromIfNotMatch,
|
|
452
|
-
getETagValueFromIfMatch,
|
|
453
|
-
getWeakETagValue,
|
|
454
|
-
isWeakETag,
|
|
455
|
-
on,
|
|
456
|
-
send,
|
|
457
|
-
sendAccepted,
|
|
458
|
-
sendCreated,
|
|
459
|
-
sendProblem,
|
|
460
|
-
setETag,
|
|
461
|
-
startAPI,
|
|
462
|
-
toWeakETag
|
|
463
|
-
};
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/testing/apiSpecification.ts
|
|
193
|
+
const existingStream = (streamId, events) => {
|
|
194
|
+
return [streamId, events];
|
|
195
|
+
};
|
|
196
|
+
const expect = (streamId, events) => {
|
|
197
|
+
return [streamId, events];
|
|
198
|
+
};
|
|
199
|
+
const expectNewEvents = (streamId, events) => {
|
|
200
|
+
return [streamId, events];
|
|
201
|
+
};
|
|
202
|
+
const expectResponse = (statusCode, options) => (response) => {
|
|
203
|
+
const { body, headers } = options ?? {};
|
|
204
|
+
assertEqual(statusCode, response.statusCode, "Response code doesn't match");
|
|
205
|
+
if (body) assertMatches(response.body, body);
|
|
206
|
+
if (headers) assertMatches(response.headers, headers);
|
|
207
|
+
};
|
|
208
|
+
const expectError = (errorCode, problemDetails) => expectResponse(errorCode, problemDetails ? { body: problemDetails } : void 0);
|
|
209
|
+
function apiSpecificationFor(optionsOrGetEventStore, getApplication) {
|
|
210
|
+
const resolveStoreAndApplication = () => {
|
|
211
|
+
if (typeof optionsOrGetEventStore === "function") {
|
|
212
|
+
const eventStore = WrapEventStore(optionsOrGetEventStore());
|
|
213
|
+
return {
|
|
214
|
+
eventStore,
|
|
215
|
+
application: getApplication(eventStore)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const eventStore = WrapEventStore(optionsOrGetEventStore.getEventStore());
|
|
219
|
+
return {
|
|
220
|
+
eventStore,
|
|
221
|
+
application: optionsOrGetEventStore.getApplication(eventStore)
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
return (...givenStreams) => {
|
|
225
|
+
const { eventStore, application } = resolveStoreAndApplication();
|
|
226
|
+
return { when: (setupRequest) => {
|
|
227
|
+
const handle = async () => {
|
|
228
|
+
for (const [streamName, events] of givenStreams) await eventStore.setup(streamName, events);
|
|
229
|
+
return setupRequest(supertest(application));
|
|
230
|
+
};
|
|
231
|
+
return { then: async (verify) => {
|
|
232
|
+
const response = await handle();
|
|
233
|
+
if (typeof verify === "function") {
|
|
234
|
+
if (verify(response) === false) assertFails();
|
|
235
|
+
} else if (Array.isArray(verify)) {
|
|
236
|
+
const [first, ...rest] = verify;
|
|
237
|
+
if (typeof first === "function") {
|
|
238
|
+
if (first(response) === false) assertFails();
|
|
239
|
+
}
|
|
240
|
+
const events = typeof first === "function" ? rest : verify;
|
|
241
|
+
assertMatches(Array.from(eventStore.appendedEvents.values()), events);
|
|
242
|
+
}
|
|
243
|
+
} };
|
|
244
|
+
} };
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const ApiSpecification = { for: apiSpecificationFor };
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
export { Accepted, ApiE2ESpecification, ApiSpecification, BadRequest, Conflict, Created, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, ETagErrors, Forbidden, HeaderNames, HttpProblem, HttpResponse, NoContent, NotFound, OK, PreconditionFailed, WeakETagRegex, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isWeakETag, on, send, sendAccepted, sendCreated, sendNoContent, sendProblem, setETag, startAPI, toWeakETag };
|
|
464
251
|
//# sourceMappingURL=index.js.map
|