@nymphjs/server 1.0.0-beta.13 → 1.0.0-beta.130

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.
@@ -1,29 +1,38 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createServer = void 0;
7
- const express_1 = __importDefault(require("express"));
8
- const cookie_parser_1 = __importDefault(require("cookie-parser"));
9
- const nymph_1 = require("@nymphjs/nymph");
10
- const nymph_2 = require("@nymphjs/nymph");
11
- const statusDescriptions_1 = require("./statusDescriptions");
1
+ import express from 'express';
2
+ import cookieParser from 'cookie-parser';
3
+ import { EntityConflictError, InvalidParametersError, TilmeldAccessLevels, classNamesToEntityConstructors, referencesToEntities, } from '@nymphjs/nymph';
4
+ import { EntityInvalidDataError } from '@nymphjs/nymph';
5
+ import { statusDescriptions } from './statusDescriptions.js';
12
6
  const NOT_FOUND_ERROR = 'Entity is not found.';
13
- function createServer(nymph, { jsonOptions = {} } = {}) {
14
- const rest = (0, express_1.default)();
15
- rest.use((0, cookie_parser_1.default)());
16
- rest.use(express_1.default.json(jsonOptions || {}));
7
+ export class ForbiddenClassError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'ForbiddenClassError';
11
+ }
12
+ }
13
+ /**
14
+ * A REST server middleware creator for Nymph.
15
+ *
16
+ * Written by Hunter Perrin for SciActive.
17
+ *
18
+ * @author Hunter Perrin <hperrin@port87.com>
19
+ * @copyright SciActive Inc
20
+ * @see http://nymph.io/
21
+ */
22
+ export function createServer(nymph, { jsonOptions = {} } = {}) {
23
+ const rest = express();
24
+ rest.use(cookieParser());
25
+ rest.use(express.json(jsonOptions || {}));
17
26
  function instantiateNymph(_request, response, next) {
18
27
  response.locals.nymph = nymph.clone();
19
28
  next();
20
29
  }
21
- function authenticateTilmeld(request, response, next) {
30
+ async function authenticateTilmeld(request, response, next) {
22
31
  if (response.locals.nymph.tilmeld) {
23
32
  response.locals.nymph.tilmeld.request = request;
24
33
  response.locals.nymph.tilmeld.response = response;
25
34
  try {
26
- response.locals.nymph.tilmeld.authenticate();
35
+ await response.locals.nymph.tilmeld.authenticate(false, request.header('x-tilmeld-token-renewal') === 'off');
27
36
  }
28
37
  catch (e) {
29
38
  httpError(response, 500, e);
@@ -67,7 +76,9 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
67
76
  };
68
77
  }
69
78
  }
79
+ // Create a new instance of Nymph for the request/response.
70
80
  rest.use(instantiateNymph);
81
+ // Authenticate before the request.
71
82
  rest.use(authenticateTilmeld);
72
83
  rest.get('/', async (request, response) => {
73
84
  try {
@@ -91,18 +102,37 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
91
102
  return;
92
103
  }
93
104
  let [options, ...selectors] = data;
94
- let EntityClass;
105
+ options.source = 'client';
106
+ options.skipAc = false;
107
+ if (options.return === 'object') {
108
+ options.return = 'entity';
109
+ }
95
110
  try {
96
- EntityClass = response.locals.nymph.getEntityClass(data[0].class);
111
+ options = classNamesToEntityConstructors(response.locals.nymph, options, true);
97
112
  }
98
113
  catch (e) {
99
- httpError(response, 400, e);
100
- return;
114
+ if (e?.message === 'Not accessible.') {
115
+ httpError(response, 403);
116
+ return;
117
+ }
118
+ else {
119
+ httpError(response, 500, e);
120
+ return;
121
+ }
122
+ }
123
+ try {
124
+ selectors = classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(selectors, response.locals.nymph, false, true), true);
125
+ }
126
+ catch (e) {
127
+ if (e?.message === 'Not accessible.') {
128
+ httpError(response, 403);
129
+ return;
130
+ }
131
+ else {
132
+ httpError(response, 500, e);
133
+ return;
134
+ }
101
135
  }
102
- options.class = EntityClass;
103
- options.source = 'client';
104
- options.skipAc = false;
105
- selectors = (0, nymph_1.classNamesToEntityConstructors)(response.locals.nymph, selectors);
106
136
  let result;
107
137
  try {
108
138
  if (action === 'entity') {
@@ -132,7 +162,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
132
162
  return;
133
163
  }
134
164
  if (response.locals.nymph.tilmeld) {
135
- if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.READ_ACCESS)) {
165
+ if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.READ_ACCESS))) {
136
166
  httpError(response, 403);
137
167
  return;
138
168
  }
@@ -179,6 +209,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
179
209
  let invalidRequest = false;
180
210
  let conflict = false;
181
211
  let notfound = false;
212
+ let forbidden = false;
182
213
  let lastException = null;
183
214
  for (let entData of data) {
184
215
  if (entData.guid) {
@@ -188,19 +219,28 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
188
219
  }
189
220
  let entity;
190
221
  try {
191
- entity = await loadEntity(entData, response.locals.nymph);
222
+ entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(entData, response.locals.nymph, false, true), true), response.locals.nymph);
192
223
  }
193
224
  catch (e) {
194
- if (e instanceof nymph_1.EntityConflictError) {
225
+ if (e instanceof EntityConflictError) {
195
226
  conflict = true;
227
+ lastException = e;
228
+ }
229
+ else if (e instanceof ForbiddenClassError) {
230
+ forbidden = true;
231
+ lastException = e;
196
232
  }
197
233
  else if (e.message === NOT_FOUND_ERROR) {
198
234
  notfound = true;
199
235
  }
200
- else if (e instanceof nymph_1.InvalidParametersError) {
236
+ else if (e instanceof InvalidParametersError) {
201
237
  invalidRequest = true;
202
238
  lastException = e;
203
239
  }
240
+ else if (e.message === 'Not accessible.') {
241
+ forbidden = true;
242
+ lastException = e;
243
+ }
204
244
  else {
205
245
  lastException = e;
206
246
  }
@@ -213,16 +253,12 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
213
253
  continue;
214
254
  }
215
255
  try {
216
- if (await entity.$save()) {
217
- created.push(entity);
218
- hadSuccess = true;
219
- }
220
- else {
221
- created.push(false);
222
- }
256
+ await entity.$save();
257
+ created.push(entity);
258
+ hadSuccess = true;
223
259
  }
224
260
  catch (e) {
225
- if (e instanceof nymph_2.EntityInvalidDataError) {
261
+ if (e instanceof EntityInvalidDataError) {
226
262
  invalidRequest = true;
227
263
  }
228
264
  else {
@@ -237,7 +273,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
237
273
  return;
238
274
  }
239
275
  else if (conflict) {
240
- httpError(response, 409);
276
+ httpError(response, 409, lastException);
277
+ return;
278
+ }
279
+ else if (forbidden) {
280
+ httpError(response, 403, lastException);
241
281
  return;
242
282
  }
243
283
  else if (notfound) {
@@ -263,93 +303,157 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
263
303
  httpError(response, 400);
264
304
  return;
265
305
  }
266
- const params = referencesToEntities([...data.params], response.locals.nymph);
267
- if (data.static) {
268
- let EntityClass;
269
- try {
270
- EntityClass = response.locals.nymph.getEntityClass(data.class);
271
- }
272
- catch (e) {
273
- httpError(response, 400);
274
- return;
275
- }
276
- if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
277
- httpError(response, 403);
278
- return;
279
- }
280
- if (!(data.method in EntityClass)) {
281
- httpError(response, 400);
282
- return;
283
- }
284
- const method = EntityClass[data.method];
285
- if (typeof method !== 'function') {
286
- httpError(response, 400);
287
- return;
288
- }
289
- try {
290
- const result = method.call(EntityClass, ...params);
291
- let ret = result;
292
- if (result instanceof Promise) {
293
- ret = await result;
306
+ try {
307
+ const params = classNamesToEntityConstructors(response.locals.nymph, referencesToEntities([...data.params], response.locals.nymph, false, true), true);
308
+ if (data.static) {
309
+ let EntityClass;
310
+ try {
311
+ EntityClass = response.locals.nymph.getEntityClass(data.class);
312
+ if (!EntityClass.restEnabled) {
313
+ httpError(response, 403);
314
+ return;
315
+ }
294
316
  }
295
- response.status(200);
296
- response.setHeader('Content-Type', 'application/json');
297
- response.send({ return: ret });
298
- }
299
- catch (e) {
300
- httpError(response, 500, e);
301
- return;
302
- }
303
- }
304
- else {
305
- let entity;
306
- try {
307
- entity = await loadEntity(data.entity, response.locals.nymph);
308
- }
309
- catch (e) {
310
- if (e instanceof nymph_1.EntityConflictError) {
311
- httpError(response, 409);
317
+ catch (e) {
318
+ httpError(response, 400);
319
+ return;
312
320
  }
313
- else if (e.message === NOT_FOUND_ERROR) {
314
- httpError(response, 404, e);
321
+ if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
322
+ httpError(response, 403);
323
+ return;
324
+ }
325
+ if (!(data.method in EntityClass)) {
326
+ httpError(response, 400);
327
+ return;
328
+ }
329
+ // @ts-ignore Dynamic methods make TypeScript sad.
330
+ const method = EntityClass[data.method];
331
+ if (typeof method !== 'function') {
332
+ httpError(response, 400);
333
+ return;
315
334
  }
316
- else if (e instanceof nymph_1.InvalidParametersError) {
317
- httpError(response, 400, e);
335
+ if (data.iterator) {
336
+ // Ping every 15 seconds to keep connection alive.
337
+ const interval = setInterval(() => {
338
+ if (response.headersSent) {
339
+ response.write('event: ping\n');
340
+ response.write(`data: ${new Date().toISOString()}\n\n`);
341
+ }
342
+ }, 15000);
343
+ try {
344
+ response.set({
345
+ 'Cache-Control': 'no-cache',
346
+ 'Content-Type': 'text/event-stream',
347
+ Connection: 'keep-alive',
348
+ });
349
+ response.flushHeaders();
350
+ const result = method.call(EntityClass, ...params);
351
+ let sequence = result;
352
+ if (result instanceof Promise) {
353
+ sequence = await result;
354
+ }
355
+ let { value, done } = await sequence.next(response.destroyed);
356
+ while (!done) {
357
+ response.write('event: next\n');
358
+ response.write(`data: ${JSON.stringify(value)}\n\n`);
359
+ // Wait for an event loop each iteration, to allow data to flush.
360
+ await new Promise((resolve) => setImmediate(resolve));
361
+ ({ value, done } = await sequence.next(response.destroyed));
362
+ }
363
+ clearInterval(interval);
364
+ response.write('event: finished\n');
365
+ response.write('data: \n\n');
366
+ response.end();
367
+ }
368
+ catch (e) {
369
+ clearInterval(interval);
370
+ eventStreamError(response, 500, e);
371
+ return;
372
+ }
318
373
  }
319
374
  else {
320
- httpError(response, 500, e);
375
+ try {
376
+ const result = method.call(EntityClass, ...params);
377
+ let ret = result;
378
+ if (result instanceof Promise) {
379
+ ret = await result;
380
+ }
381
+ response.status(200);
382
+ response.setHeader('Content-Type', 'application/json');
383
+ response.send({ return: ret });
384
+ }
385
+ catch (e) {
386
+ httpError(response, 500, e);
387
+ return;
388
+ }
321
389
  }
322
- return;
323
- }
324
- if (data.entity.guid && !entity.guid) {
325
- httpError(response, 400);
326
- return;
327
390
  }
328
- if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
329
- httpError(response, 403);
330
- return;
331
- }
332
- if (!(data.method in entity) ||
333
- typeof entity[data.method] !== 'function') {
334
- httpError(response, 400);
335
- return;
336
- }
337
- try {
338
- const result = entity[data.method](...params);
339
- let ret = result;
340
- if (result instanceof Promise) {
341
- ret = await result;
391
+ else {
392
+ let entity;
393
+ try {
394
+ entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(data.entity, response.locals.nymph, false, true), true), response.locals.nymph);
342
395
  }
343
- response.status(200);
344
- response.setHeader('Content-Type', 'application/json');
345
- if (data.stateless) {
346
- response.send({ return: ret });
396
+ catch (e) {
397
+ if (e instanceof EntityConflictError) {
398
+ httpError(response, 409, e);
399
+ }
400
+ else if (e instanceof ForbiddenClassError) {
401
+ httpError(response, 403, e);
402
+ }
403
+ else if (e.message === NOT_FOUND_ERROR) {
404
+ httpError(response, 404, e);
405
+ }
406
+ else if (e instanceof InvalidParametersError) {
407
+ httpError(response, 400, e);
408
+ }
409
+ else if (e.message === 'Not accessible.') {
410
+ httpError(response, 403);
411
+ }
412
+ else {
413
+ httpError(response, 500, e);
414
+ }
415
+ return;
347
416
  }
348
- else {
349
- response.send({ entity: entity, return: ret });
417
+ if (data.entity.guid && !entity.guid) {
418
+ httpError(response, 400);
419
+ return;
420
+ }
421
+ if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
422
+ httpError(response, 403);
423
+ return;
424
+ }
425
+ if (!(data.method in entity) ||
426
+ typeof entity[data.method] !== 'function') {
427
+ httpError(response, 400);
428
+ return;
429
+ }
430
+ try {
431
+ const result = entity[data.method](...params);
432
+ let ret = result;
433
+ if (result instanceof Promise) {
434
+ ret = await result;
435
+ }
436
+ response.status(200);
437
+ response.setHeader('Content-Type', 'application/json');
438
+ if (data.stateless) {
439
+ response.send({ return: ret });
440
+ }
441
+ else {
442
+ response.send({ entity: entity, return: ret });
443
+ }
444
+ }
445
+ catch (e) {
446
+ httpError(response, 500, e);
447
+ return;
350
448
  }
351
449
  }
352
- catch (e) {
450
+ }
451
+ catch (e) {
452
+ if (e instanceof ForbiddenClassError) {
453
+ httpError(response, 403, e);
454
+ return;
455
+ }
456
+ else {
353
457
  httpError(response, 500, e);
354
458
  return;
355
459
  }
@@ -361,7 +465,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
361
465
  return;
362
466
  }
363
467
  if (response.locals.nymph.tilmeld) {
364
- if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.WRITE_ACCESS)) {
468
+ if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.WRITE_ACCESS))) {
365
469
  httpError(response, 403);
366
470
  return;
367
471
  }
@@ -423,7 +527,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
423
527
  return;
424
528
  }
425
529
  if (response.locals.nymph.tilmeld) {
426
- if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name, nymph_1.TilmeldAccessLevels.FULL_ACCESS)) {
530
+ if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name, TilmeldAccessLevels.FULL_ACCESS))) {
427
531
  httpError(response, 403);
428
532
  return;
429
533
  }
@@ -452,6 +556,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
452
556
  let hadSuccess = false;
453
557
  let invalidRequest = false;
454
558
  let conflict = false;
559
+ let forbidden = false;
455
560
  let notfound = false;
456
561
  let lastException = null;
457
562
  for (let entData of data) {
@@ -462,19 +567,28 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
462
567
  }
463
568
  let entity;
464
569
  try {
465
- entity = await loadEntity(entData, response.locals.nymph, patch);
570
+ entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(entData, response.locals.nymph, false, true), true), response.locals.nymph, patch);
466
571
  }
467
572
  catch (e) {
468
- if (e instanceof nymph_1.EntityConflictError) {
573
+ if (e instanceof EntityConflictError) {
469
574
  conflict = true;
575
+ lastException = e;
576
+ }
577
+ else if (e instanceof ForbiddenClassError) {
578
+ forbidden = true;
579
+ lastException = e;
470
580
  }
471
581
  else if (e.message === NOT_FOUND_ERROR) {
472
582
  notfound = true;
473
583
  }
474
- else if (e instanceof nymph_1.InvalidParametersError) {
584
+ else if (e instanceof InvalidParametersError) {
475
585
  invalidRequest = true;
476
586
  lastException = e;
477
587
  }
588
+ else if (e.message === 'Not accessible.') {
589
+ forbidden = true;
590
+ lastException = e;
591
+ }
478
592
  else {
479
593
  lastException = e;
480
594
  }
@@ -487,16 +601,12 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
487
601
  continue;
488
602
  }
489
603
  try {
490
- if (await entity.$save()) {
491
- saved.push(entity);
492
- hadSuccess = true;
493
- }
494
- else {
495
- saved.push(false);
496
- }
604
+ await entity.$save();
605
+ saved.push(entity);
606
+ hadSuccess = true;
497
607
  }
498
608
  catch (e) {
499
- if (e instanceof nymph_2.EntityInvalidDataError) {
609
+ if (e instanceof EntityInvalidDataError) {
500
610
  invalidRequest = true;
501
611
  }
502
612
  else {
@@ -509,8 +619,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
509
619
  if (invalidRequest) {
510
620
  httpError(response, 400, lastException);
511
621
  }
622
+ else if (forbidden) {
623
+ httpError(response, 403, lastException);
624
+ }
512
625
  else if (conflict) {
513
- httpError(response, 409);
626
+ httpError(response, 409, lastException);
514
627
  }
515
628
  else if (notfound) {
516
629
  httpError(response, 404);
@@ -555,6 +668,10 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
555
668
  let EntityClass;
556
669
  try {
557
670
  EntityClass = response.locals.nymph.getEntityClass(entData.class);
671
+ if (!EntityClass.restEnabled) {
672
+ httpError(response, 403);
673
+ return;
674
+ }
558
675
  }
559
676
  catch (e) {
560
677
  invalidRequest = true;
@@ -576,10 +693,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
576
693
  continue;
577
694
  }
578
695
  try {
579
- if (await entity.$delete()) {
696
+ try {
697
+ await entity.$delete();
580
698
  deleted.push(entData.guid);
581
699
  }
582
- else {
700
+ catch (e) {
583
701
  failures = true;
584
702
  }
585
703
  }
@@ -615,7 +733,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
615
733
  return;
616
734
  }
617
735
  if (response.locals.nymph.tilmeld) {
618
- if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.FULL_ACCESS)) {
736
+ if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.FULL_ACCESS))) {
619
737
  httpError(response, 403);
620
738
  return;
621
739
  }
@@ -642,12 +760,17 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
642
760
  return;
643
761
  }
644
762
  });
763
+ // Unauthenticate after the request.
645
764
  rest.use(unauthenticateTilmeld);
646
765
  async function loadEntity(entityData, nymph, patch = false, allowConflict = false) {
647
- if (entityData.class === 'Entity') {
648
- throw new nymph_1.InvalidParametersError("Can't use Entity class directly from the front end.");
766
+ let EntityClass = entityData.class;
767
+ if (EntityClass.class === 'Entity') {
768
+ // Don't let clients use the `Entity` class, since it has no validity/AC checks.
769
+ throw new ForbiddenClassError("Can't use Entity class directly from the front end.");
770
+ }
771
+ if (!EntityClass.restEnabled) {
772
+ throw new ForbiddenClassError('Not accessible.');
649
773
  }
650
- let EntityClass = nymph.getEntityClass(entityData.class);
651
774
  let entity;
652
775
  if (entityData.guid) {
653
776
  entity = await nymph.getEntity({ class: EntityClass, source: 'client' }, {
@@ -669,42 +792,60 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
669
792
  }
670
793
  return entity;
671
794
  }
672
- function referencesToEntities(item, nymph) {
673
- if (Array.isArray(item)) {
674
- if (item.length === 3 && item[0] === 'nymph_entity_reference') {
675
- try {
676
- const EntityClass = nymph.getEntityClass(item[1]);
677
- return EntityClass.factoryReference(item);
678
- }
679
- catch (e) {
680
- return item;
681
- }
682
- }
683
- return item.map((entry) => referencesToEntities(entry, nymph));
684
- }
685
- else if (typeof item === 'object' && !(item instanceof nymph_1.Entity)) {
686
- const newItem = {};
687
- for (let curProperty in item) {
688
- newItem[curProperty] = referencesToEntities(item[curProperty], nymph);
689
- }
690
- return newItem;
691
- }
692
- return item;
693
- }
795
+ /**
796
+ * Return the request with an HTTP error response.
797
+ *
798
+ * @param res The server response object.
799
+ * @param defaultStatusCode The HTTP status code to use if none is given in the error object.
800
+ * @param error An optional error object to report.
801
+ */
694
802
  function httpError(res, defaultStatusCode, error) {
695
803
  const status = error?.status || defaultStatusCode;
696
804
  const statusText = error?.statusText ||
697
805
  (error?.status == null
698
- ? statusDescriptions_1.statusDescriptions[defaultStatusCode]
699
- : error.status in statusDescriptions_1.statusDescriptions &&
700
- statusDescriptions_1.statusDescriptions[error.status]) ||
806
+ ? statusDescriptions[defaultStatusCode]
807
+ : error.status in statusDescriptions &&
808
+ statusDescriptions[error.status]) ||
701
809
  'Internal Server Error';
810
+ const errorResponse = error
811
+ ? {
812
+ textStatus: `${status} ${statusText}`,
813
+ statusText,
814
+ message: error.message,
815
+ error,
816
+ ...(process.env.NODE_ENV !== 'production'
817
+ ? { stack: error.stack }
818
+ : {}),
819
+ }
820
+ : {
821
+ textStatus: `${status} ${statusText}`,
822
+ statusText,
823
+ message: statusText,
824
+ };
702
825
  if (!res.headersSent) {
703
826
  res.status(status);
704
827
  res.setHeader('Content-Type', 'application/json');
705
828
  }
706
- if (error) {
707
- res.send({
829
+ res.send(errorResponse);
830
+ }
831
+ /**
832
+ * End the event stream with an HTTP error response.
833
+ *
834
+ * @param res The server response object.
835
+ * @param defaultStatusCode The HTTP status code to use if none is given in the error object.
836
+ * @param error An optional error object to report.
837
+ */
838
+ function eventStreamError(res, defaultStatusCode, error) {
839
+ const status = error?.status || defaultStatusCode;
840
+ const statusText = error?.statusText ||
841
+ (error?.status == null
842
+ ? statusDescriptions[defaultStatusCode]
843
+ : error.status in statusDescriptions &&
844
+ statusDescriptions[error.status]) ||
845
+ 'Internal Server Error';
846
+ const errorResponse = error
847
+ ? {
848
+ status,
708
849
  textStatus: `${status} ${statusText}`,
709
850
  statusText,
710
851
  message: error.message,
@@ -712,17 +853,26 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
712
853
  ...(process.env.NODE_ENV !== 'production'
713
854
  ? { stack: error.stack }
714
855
  : {}),
715
- });
716
- }
717
- else {
718
- res.send({
856
+ }
857
+ : {
858
+ status,
719
859
  textStatus: `${status} ${statusText}`,
720
860
  statusText,
721
861
  message: statusText,
722
- });
862
+ };
863
+ if (!res.headersSent) {
864
+ res.status(status);
865
+ res.setHeader('Content-Type', 'application/json');
866
+ res.send(errorResponse);
867
+ }
868
+ else {
869
+ res.write('event: error\n');
870
+ res.write(`data: ${JSON.stringify(errorResponse)}\n\n`);
871
+ res.write('event: finished\n');
872
+ res.write('data: \n\n');
873
+ res.end();
723
874
  }
724
875
  }
725
876
  return rest;
726
877
  }
727
- exports.createServer = createServer;
728
878
  //# sourceMappingURL=createServer.js.map