@congruent-stack/congruent-api 0.7.0 → 0.10.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.
package/dist/index.cjs CHANGED
@@ -19,6 +19,11 @@ class HttpMethodEndpoint {
19
19
  }
20
20
  return this._cachedGenericPath;
21
21
  }
22
+ createPath(pathParams) {
23
+ return `/${this.pathSegments.map(
24
+ (segment) => segment.startsWith(":") ? pathParams[segment.slice(1)] ?? "?" : segment
25
+ ).join("/")}`;
26
+ }
22
27
  _method = null;
23
28
  get method() {
24
29
  return this._method;
@@ -112,6 +117,7 @@ var HttpStatusCode = /* @__PURE__ */ ((HttpStatusCode2) => {
112
117
  HttpStatusCode2[HttpStatusCode2["Forbidden_403"] = 403] = "Forbidden_403";
113
118
  HttpStatusCode2[HttpStatusCode2["NotFound_404"] = 404] = "NotFound_404";
114
119
  HttpStatusCode2[HttpStatusCode2["Conflict_409"] = 409] = "Conflict_409";
120
+ HttpStatusCode2[HttpStatusCode2["UnprocessableEntity_422"] = 422] = "UnprocessableEntity_422";
115
121
  HttpStatusCode2[HttpStatusCode2["InternalServerError_500"] = 500] = "InternalServerError_500";
116
122
  HttpStatusCode2[HttpStatusCode2["NotImplemented_501"] = 501] = "NotImplemented_501";
117
123
  HttpStatusCode2[HttpStatusCode2["BadGateway_502"] = 502] = "BadGateway_502";
@@ -140,6 +146,9 @@ class MethodEndpointHandlerRegistryEntry {
140
146
  this._methodEndpoint = methodEndpoint;
141
147
  this._dicontainer = dicontainer;
142
148
  }
149
+ get genericPath() {
150
+ return this._methodEndpoint.genericPath;
151
+ }
143
152
  _handler = null;
144
153
  get handler() {
145
154
  return this._handler;
@@ -149,6 +158,7 @@ class MethodEndpointHandlerRegistryEntry {
149
158
  if (this._onHandlerRegisteredCallback) {
150
159
  this._onHandlerRegisteredCallback(this);
151
160
  }
161
+ return this;
152
162
  }
153
163
  _onHandlerRegisteredCallback = null;
154
164
  _onHandlerRegistered(callback) {
@@ -158,7 +168,7 @@ class MethodEndpointHandlerRegistryEntry {
158
168
  callback(this);
159
169
  return this;
160
170
  }
161
- _injection = (_dicontainer) => ({});
171
+ _injection = (_diScope) => ({});
162
172
  get injection() {
163
173
  return this._injection;
164
174
  }
@@ -166,61 +176,152 @@ class MethodEndpointHandlerRegistryEntry {
166
176
  this._injection = injection;
167
177
  return this;
168
178
  }
169
- async trigger(data) {
179
+ _decoratorFactories = [];
180
+ get decoratorFactories() {
181
+ return this._decoratorFactories;
182
+ }
183
+ /**
184
+ *
185
+ * @param decoratorFactory must be a function that takes exactly the DI scope type and returns an instance of a class that implements IEndpointHandlerDecorator
186
+ * @returns this
187
+ *
188
+ * Initially, the method was defined as:
189
+ * ```ts
190
+ * decorate<
191
+ * TDecoratorSchemas extends IDecoratorHandlerSchemas,
192
+ * TDecorator extends IEndpointHandlerDecorator<TDecoratorSchemas>
193
+ * > (
194
+ * decoratorFactory: IEndpointHandlerDecoratorFactory<TDecoratorSchemas, TDIContainer, TDecorator>
195
+ * ): this {
196
+ * this._decoratorFactories.push(decoratorFactory);
197
+ * return this;
198
+ * }
199
+ * ```
200
+ *
201
+ * and the type IEndpointHandlerDecoratorFactory was defined as:
202
+ * ```ts
203
+ * type IEndpointHandlerDecoratorFactory<
204
+ * TDecoratorSchemas extends IDecoratorHandlerSchemas,
205
+ * TDIContainer extends DIContainer,
206
+ * TDecorator extends IEndpointHandlerDecorator<TDecoratorSchemas>
207
+ * > = (diScope: ReturnType<TDIContainer['createScope']>) => TDecorator;
208
+ * ```
209
+ *
210
+ * However, TypeScript was incorrectly inferring the types when using the 'decorate' method.
211
+ * The end developer would have had to explicitly provide the generic types, which is not ideal.
212
+ *
213
+ * With the current definition, TypeScript can infer the types from the decoratorFactory parameter,
214
+ * making it easier to use.
215
+ */
216
+ decorate(decoratorFactory, decoratingArgs) {
217
+ this._decoratorFactories.push((scope) => decoratorFactory(scope, decoratingArgs));
218
+ return this;
219
+ }
220
+ decorateWith(decoratorStaticMethodFactory, decoratingArgs) {
221
+ this._decoratorFactories.push((scope) => decoratorStaticMethodFactory.create(scope, decoratingArgs));
222
+ return this;
223
+ }
224
+ async trigger(diScope, requestObject) {
225
+ return this.triggerNoStaticTypeCheck(diScope, requestObject, { originalRequest: requestObject });
226
+ }
227
+ async exec(injected, requestObject) {
170
228
  if (!this._handler) {
171
229
  throw new Error("Handler not set for this endpoint");
172
230
  }
173
231
  let badRequestResponse = null;
174
- const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers", data);
232
+ const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers", requestObject);
175
233
  if (isHttpResponseObject(headers)) {
176
234
  badRequestResponse = headers;
177
235
  return badRequestResponse;
178
236
  }
179
- const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query", data);
237
+ const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query", requestObject);
180
238
  if (isHttpResponseObject(query)) {
181
239
  badRequestResponse = query;
182
240
  return badRequestResponse;
183
241
  }
184
- const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body", data);
242
+ const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body", requestObject);
185
243
  if (isHttpResponseObject(body)) {
186
244
  badRequestResponse = body;
187
245
  return badRequestResponse;
188
246
  }
189
- const path = `/${this._methodEndpoint.pathSegments.map(
190
- (segment) => segment.startsWith(":") ? data.pathParams[segment.slice(1)] ?? "?" : segment
191
- ).join("/")}`;
247
+ const path = this._methodEndpoint.createPath(requestObject.pathParams);
192
248
  return await this._handler({
193
249
  method: this._methodEndpoint.method,
194
250
  path,
195
251
  genericPath: this._methodEndpoint.genericPath,
196
252
  pathSegments: this._methodEndpoint.pathSegments,
197
253
  headers,
198
- pathParams: data.pathParams,
254
+ pathParams: requestObject.pathParams,
199
255
  query,
200
- body,
201
- injected: this._injection(this._dicontainer.createScope())
202
- });
256
+ body
257
+ }, injected);
258
+ }
259
+ async triggerNoStaticTypeCheck(diScope, requestObject, context) {
260
+ if (!this._handler) {
261
+ throw new Error("Handler not set for this endpoint");
262
+ }
263
+ let badRequestResponse = null;
264
+ const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers", requestObject);
265
+ if (isHttpResponseObject(headers)) {
266
+ badRequestResponse = headers;
267
+ return badRequestResponse;
268
+ }
269
+ const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query", requestObject);
270
+ if (isHttpResponseObject(query)) {
271
+ badRequestResponse = query;
272
+ return badRequestResponse;
273
+ }
274
+ const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body", requestObject);
275
+ if (isHttpResponseObject(body)) {
276
+ badRequestResponse = body;
277
+ return badRequestResponse;
278
+ }
279
+ const path = this._methodEndpoint.createPath(requestObject.pathParams);
280
+ Object.assign(context, this._injection(diScope));
281
+ return await this._handler({
282
+ method: this._methodEndpoint.method,
283
+ path,
284
+ genericPath: this._methodEndpoint.genericPath,
285
+ pathSegments: this._methodEndpoint.pathSegments,
286
+ headers,
287
+ pathParams: requestObject.pathParams,
288
+ query,
289
+ body
290
+ }, context);
203
291
  }
204
292
  }
205
- function parseRequestDefinitionField(definition, key, data) {
293
+ function parseRequestDefinitionField(definition, key, requestObject) {
206
294
  if (definition[key]) {
207
- if (!(key in data) || data[key] === null || data[key] === void 0) {
208
- if (!definition[key].safeParse(data[key]).success) {
295
+ if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
296
+ const result2 = definition[key].safeParse(requestObject[key]);
297
+ if (!result2.success) {
298
+ switch (definition[key].type) {
299
+ case "optional":
300
+ if (requestObject[key] === null) {
301
+ return void 0;
302
+ }
303
+ break;
304
+ case "nullable":
305
+ if (requestObject[key] === void 0) {
306
+ return null;
307
+ }
308
+ break;
309
+ }
209
310
  return {
210
311
  code: HttpStatusCode.BadRequest_400,
211
312
  body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
212
313
  };
213
314
  }
214
- return null;
315
+ return result2.data;
215
316
  }
216
- const result = definition[key].safeParse(data[key]);
317
+ const result = definition[key].safeParse(requestObject[key]);
217
318
  if (!result.success) {
218
319
  return {
219
320
  code: HttpStatusCode.BadRequest_400,
220
321
  body: result.error.issues
221
322
  };
222
323
  }
223
- return result.data ?? null;
324
+ return result.data;
224
325
  }
225
326
  return null;
226
327
  }
@@ -235,11 +336,33 @@ function middleware(apiReg, path) {
235
336
  }
236
337
  class MiddlewareHandlersRegistryEntryInternal {
237
338
  _dicontainer;
339
+ get dicontainer() {
340
+ return this._dicontainer;
341
+ }
238
342
  _middlewareGenericPath;
239
343
  get genericPath() {
240
344
  return this._middlewareGenericPath;
241
345
  }
242
- _splitMiddlewarePath() {
346
+ _middlewarePathSegments;
347
+ get pathSegments() {
348
+ return this._middlewarePathSegments;
349
+ }
350
+ _middlewareMethod;
351
+ get method() {
352
+ return this._middlewareMethod;
353
+ }
354
+ _middlewareSchemas;
355
+ get middlewareSchemas() {
356
+ return this._middlewareSchemas;
357
+ }
358
+ _handler;
359
+ constructor(diContainer, middlewarePath, middlewareSchemas, decoratorFactories, injection, handler) {
360
+ this._dicontainer = diContainer;
361
+ this._middlewareGenericPath = middlewarePath;
362
+ this._middlewareSchemas = middlewareSchemas;
363
+ this._decoratorFactories.push(...decoratorFactories);
364
+ this._injection = injection;
365
+ this._handler = handler;
243
366
  const splitResult = this._middlewareGenericPath.split(" ");
244
367
  let method = "";
245
368
  let pathSegments = [];
@@ -250,54 +373,56 @@ class MiddlewareHandlersRegistryEntryInternal {
250
373
  }
251
374
  pathSegments = splitResult[1].split("/").map((segment) => segment.trim()).filter((segment) => segment !== "");
252
375
  }
253
- return {
254
- method,
255
- pathSegments
256
- };
376
+ this._middlewareMethod = method;
377
+ this._middlewarePathSegments = pathSegments;
257
378
  }
258
- _inputSchemas;
259
- _handler;
260
- constructor(diContainer, middlewarePath, inputSchemas, injection, handler) {
261
- this._dicontainer = diContainer;
262
- this._middlewareGenericPath = middlewarePath;
263
- this._inputSchemas = inputSchemas;
264
- this._injection = injection;
265
- this._handler = handler;
379
+ _decoratorFactories = [];
380
+ get decoratorFactories() {
381
+ return this._decoratorFactories;
266
382
  }
267
383
  _injection = (_dicontainer) => ({});
268
- async trigger(data, next) {
384
+ async trigger(diScope, requestObject, context) {
385
+ return this.triggerNoStaticTypeCheck(diScope, requestObject, context);
386
+ }
387
+ async triggerNoStaticTypeCheck(diScope, requestObject, context) {
269
388
  let badRequestResponse = null;
270
- const headers = middlewareParseRequestDefinitionField(this._inputSchemas, "headers", data);
389
+ const headers = middlewareParseRequestDefinitionField(this._middlewareSchemas, "headers", requestObject);
271
390
  if (isHttpResponseObject(headers)) {
272
391
  badRequestResponse = headers;
273
392
  return badRequestResponse;
274
393
  }
275
- const query = middlewareParseRequestDefinitionField(this._inputSchemas, "query", data);
394
+ const query = middlewareParseRequestDefinitionField(this._middlewareSchemas, "query", requestObject);
276
395
  if (isHttpResponseObject(query)) {
277
396
  badRequestResponse = query;
278
397
  return badRequestResponse;
279
398
  }
280
- const body = middlewareParseRequestDefinitionField(this._inputSchemas, "body", data);
399
+ const body = middlewareParseRequestDefinitionField(this._middlewareSchemas, "body", requestObject);
281
400
  if (isHttpResponseObject(body)) {
282
401
  badRequestResponse = body;
283
402
  return badRequestResponse;
284
403
  }
285
- const { method, pathSegments } = this._splitMiddlewarePath();
286
- const path = `/${pathSegments.map(
287
- (segment) => segment.startsWith(":") ? data.pathParams[segment.slice(1)] ?? "?" : segment
404
+ const path = this.createPath(requestObject.pathParams);
405
+ Object.assign(context, this._injection(diScope));
406
+ return await this._handler(
407
+ {
408
+ method: this.method,
409
+ // TODO: might be empty, as middleware can be registered with path only, without method
410
+ path,
411
+ genericPath: this.genericPath,
412
+ pathSegments: this.pathSegments,
413
+ headers,
414
+ pathParams: requestObject.pathParams,
415
+ query,
416
+ body
417
+ },
418
+ context
419
+ );
420
+ }
421
+ createPath(pathParams) {
422
+ const path = `/${this.pathSegments.map(
423
+ (segment) => segment.startsWith(":") ? pathParams[segment.slice(1)] ?? "?" : segment
288
424
  ).join("/")}`;
289
- return await this._handler({
290
- method,
291
- // TODO: might be empty, as middleware can be registered with path only, without method, possible fix: take it from express.request.method
292
- path,
293
- genericPath: this.genericPath,
294
- pathSegments,
295
- headers,
296
- pathParams: data.pathParams,
297
- query,
298
- body,
299
- injected: this._injection(this._dicontainer.createScope())
300
- }, next);
425
+ return path;
301
426
  }
302
427
  }
303
428
  class MiddlewareHandlersRegistryEntry {
@@ -307,7 +432,7 @@ class MiddlewareHandlersRegistryEntry {
307
432
  this._registry = registry;
308
433
  this._path = path;
309
434
  }
310
- _injection = (_dicontainer) => ({});
435
+ _injection = (_diScope) => ({});
311
436
  get injection() {
312
437
  return this._injection;
313
438
  }
@@ -315,16 +440,62 @@ class MiddlewareHandlersRegistryEntry {
315
440
  this._injection = injection;
316
441
  return this;
317
442
  }
318
- register(inputSchemas, handler) {
443
+ register(middlewareSchemas, handler) {
319
444
  const internalEntry = new MiddlewareHandlersRegistryEntryInternal(
320
445
  this._registry.dicontainer,
321
446
  this._path,
322
- inputSchemas,
447
+ middlewareSchemas,
448
+ this._decoratorFactories,
323
449
  this._injection,
324
450
  handler
325
451
  );
326
452
  this._registry.register(internalEntry);
327
453
  }
454
+ _decoratorFactories = [];
455
+ get decoratorFactories() {
456
+ return this._decoratorFactories;
457
+ }
458
+ /**
459
+ *
460
+ * @param decoratorFactory must be a function that takes exactly the DI scope type and returns an instance of a class that implements IEndpointHandlerDecorator
461
+ * @returns this
462
+ *
463
+ * Initially, the method was defined as:
464
+ * ```ts
465
+ * decorate<
466
+ * TDecoratorSchemas extends IDecoratorHandlerSchemas,
467
+ * TDecorator extends IEndpointHandlerDecorator<TDecoratorSchemas>
468
+ * > (
469
+ * decoratorFactory: IEndpointHandlerDecoratorFactory<TDecoratorSchemas, TDIContainer, TDecorator>
470
+ * ): this {
471
+ * this._decoratorFactories.push(decoratorFactory);
472
+ * return this;
473
+ * }
474
+ * ```
475
+ *
476
+ * and the type IEndpointHandlerDecoratorFactory was defined as:
477
+ * ```ts
478
+ * type IEndpointHandlerDecoratorFactory<
479
+ * TDecoratorSchemas extends IDecoratorHandlerSchemas,
480
+ * TDIContainer extends DIContainer,
481
+ * TDecorator extends IEndpointHandlerDecorator<TDecoratorSchemas>
482
+ * > = (diScope: ReturnType<TDIContainer['createScope']>) => TDecorator;
483
+ * ```
484
+ *
485
+ * However, TypeScript was incorrectly inferring the types when using the 'decorate' method.
486
+ * The end developer would have had to explicitly provide the generic types, which is not ideal.
487
+ *
488
+ * With the current definition, TypeScript can infer the types from the decoratorFactory parameter,
489
+ * making it easier to use.
490
+ */
491
+ decorate(decoratorFactory) {
492
+ this._decoratorFactories.push(decoratorFactory);
493
+ return this;
494
+ }
495
+ decorateWith(decoratorStaticMethodFactory) {
496
+ this._decoratorFactories.push(decoratorStaticMethodFactory.create);
497
+ return this;
498
+ }
328
499
  }
329
500
  class MiddlewareHandlersRegistry {
330
501
  dicontainer;
@@ -332,8 +503,13 @@ class MiddlewareHandlersRegistry {
332
503
  this.dicontainer = dicontainer;
333
504
  this._onHandlerRegisteredCallback = callback;
334
505
  }
506
+ _list = [];
507
+ get list() {
508
+ return this._list;
509
+ }
335
510
  register(entry) {
336
511
  if (this._onHandlerRegisteredCallback) {
512
+ this._list.push(entry);
337
513
  this._onHandlerRegisteredCallback(entry);
338
514
  }
339
515
  }
@@ -342,25 +518,38 @@ class MiddlewareHandlersRegistry {
342
518
  this._onHandlerRegisteredCallback = callback;
343
519
  }
344
520
  }
345
- function middlewareParseRequestDefinitionField(inputSchemas, key, data) {
346
- if (inputSchemas[key]) {
347
- if (!(key in data) || data[key] === null || data[key] === void 0) {
348
- if (!inputSchemas[key].safeParse(data[key]).success) {
521
+ function middlewareParseRequestDefinitionField(middlewareSchemas, key, requestObject) {
522
+ if (middlewareSchemas[key]) {
523
+ if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
524
+ const result2 = middlewareSchemas[key].safeParse(requestObject[key]);
525
+ if (!result2.success) {
526
+ switch (middlewareSchemas[key].type) {
527
+ case "optional":
528
+ if (requestObject[key] === null) {
529
+ return void 0;
530
+ }
531
+ break;
532
+ case "nullable":
533
+ if (requestObject[key] === void 0) {
534
+ return null;
535
+ }
536
+ break;
537
+ }
349
538
  return {
350
539
  code: HttpStatusCode.BadRequest_400,
351
540
  body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
352
541
  };
353
542
  }
354
- return null;
543
+ return result2.data;
355
544
  }
356
- const result = inputSchemas[key].safeParse(data[key]);
545
+ const result = middlewareSchemas[key].safeParse(requestObject[key]);
357
546
  if (!result.success) {
358
547
  return {
359
548
  code: HttpStatusCode.BadRequest_400,
360
549
  body: result.error.issues
361
550
  };
362
551
  }
363
- return result.data ?? null;
552
+ return result.data;
364
553
  }
365
554
  return null;
366
555
  }
@@ -484,6 +673,41 @@ function registerEntryHandler(endpointEntry, handler) {
484
673
  endpointEntry.register(handler);
485
674
  }
486
675
 
676
+ async function execHandlerChain(diScope, allHandlerEntries, input) {
677
+ const queue = [...allHandlerEntries];
678
+ let response = void 0;
679
+ const next = async () => {
680
+ if (response) {
681
+ return;
682
+ }
683
+ const current = queue.shift();
684
+ if (!current) {
685
+ return;
686
+ }
687
+ if (!input.genericPath.startsWith(current.genericPath)) {
688
+ await next();
689
+ return;
690
+ }
691
+ const currResponse = await current.triggerNoStaticTypeCheck(
692
+ diScope,
693
+ input,
694
+ {
695
+ next
696
+ }
697
+ );
698
+ if (response) {
699
+ return;
700
+ }
701
+ if (currResponse) {
702
+ response = currResponse;
703
+ return;
704
+ }
705
+ await next();
706
+ };
707
+ await next();
708
+ return response;
709
+ }
710
+
487
711
  function partialPathString(_apiReg, path) {
488
712
  return path;
489
713
  }
@@ -528,12 +752,12 @@ class InnerApiClient {
528
752
  delete currObj[key];
529
753
  InnerApiClient._initialize(client, val, clientGenericHandler);
530
754
  } else if (val instanceof HttpMethodEndpoint) {
531
- currObj[key] = (req) => {
755
+ currObj[key] = (requestObject) => {
532
756
  const pathParams = { ...client.__CONTEXT__.pathParameters };
533
757
  client.__CONTEXT__ = InnerApiClient._initNewContext();
534
- const headers = clientParseRequestDefinitionField(val.definition, "headers", req);
535
- const query = clientParseRequestDefinitionField(val.definition, "query", req);
536
- const body = clientParseRequestDefinitionField(val.definition, "body", req);
758
+ const headers = clientParseRequestDefinitionField(val.definition, "headers", requestObject);
759
+ const query = clientParseRequestDefinitionField(val.definition, "query", requestObject);
760
+ const body = clientParseRequestDefinitionField(val.definition, "body", requestObject);
537
761
  const path = `/${val.pathSegments.map(
538
762
  (segment) => segment.startsWith(":") ? pathParams[segment.slice(1)] ?? "?" : segment
539
763
  ).join("/")}`;
@@ -555,138 +779,304 @@ class InnerApiClient {
555
779
  }
556
780
  }
557
781
  const ApiClient = InnerApiClient;
558
- function clientParseRequestDefinitionField(definition, key, data) {
782
+ function clientParseRequestDefinitionField(definition, key, requestObject) {
559
783
  if (definition[key]) {
560
- if (!(key in data) || data[key] === null || data[key] === void 0) {
561
- if (!definition[key].safeParse(data[key]).success) {
562
- throw new Error(`${key} are required for this endpoint`);
784
+ if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
785
+ const result2 = definition[key].safeParse(requestObject[key]);
786
+ if (!result2.success) {
787
+ switch (definition[key].type) {
788
+ case "optional":
789
+ if (requestObject[key] === null) {
790
+ return void 0;
791
+ }
792
+ break;
793
+ case "nullable":
794
+ if (requestObject[key] === void 0) {
795
+ return null;
796
+ }
797
+ break;
798
+ }
799
+ throw new Error(`'${key}' is required for this endpoint`);
563
800
  }
564
- return null;
801
+ return result2.data;
565
802
  }
566
- const result = definition[key].safeParse(data[key]);
803
+ const result = definition[key].safeParse(requestObject[key]);
567
804
  if (!result.success) {
568
805
  throw new Error(`Validation for '${key}' failed`, { cause: result.error });
569
806
  }
570
- return result.data ?? null;
807
+ return result.data;
571
808
  }
572
809
  return null;
573
810
  }
574
811
 
575
- class DIContainer {
576
- registry = /* @__PURE__ */ new Map();
577
- singletons = /* @__PURE__ */ new Map();
578
- proxy;
579
- /**
580
- * The constructor returns a Proxy. This is the runtime magic that intercepts
581
- * calls to methods like `getLoggerService()`. It parses the method name,
582
- * finds the corresponding service class in the registry, and resolves it.
583
- */
584
- constructor() {
585
- this.proxy = new Proxy(this, {
586
- get: (target, prop, receiver) => {
587
- if (typeof prop === "string" && prop.startsWith("get")) {
588
- const serviceName = prop.substring(3);
589
- if (target.registry.has(serviceName)) {
590
- return () => target.resolveByName(serviceName);
812
+ class DIContainerBase {
813
+ _map = /* @__PURE__ */ new Map();
814
+ _singletonInstances = /* @__PURE__ */ new Map();
815
+ createScope() {
816
+ const proxy = new Proxy({
817
+ _map: this._map,
818
+ _singletonInstances: this._singletonInstances,
819
+ _scopedInstances: /* @__PURE__ */ new Map(),
820
+ _isBuildingSingleton: false
821
+ }, {
822
+ get: (target, prop) => {
823
+ if (prop.startsWith("get")) {
824
+ const serviceName = prop.slice(3);
825
+ if (target._map.has(serviceName)) {
826
+ const entry = target._map.get(serviceName);
827
+ switch (entry.lifetime) {
828
+ case "transient":
829
+ return () => {
830
+ if (target._isBuildingSingleton) {
831
+ throw new Error(`Cannot resolve transient service '${serviceName}' while building a singleton`);
832
+ }
833
+ return entry.factory(proxy);
834
+ };
835
+ case "scoped":
836
+ return () => {
837
+ if (target._isBuildingSingleton) {
838
+ throw new Error(`Cannot resolve scoped service '${serviceName}' while building a singleton`);
839
+ }
840
+ if (!target._scopedInstances.has(serviceName)) {
841
+ const instance = entry.factory(proxy);
842
+ target._scopedInstances.set(serviceName, instance);
843
+ }
844
+ return target._scopedInstances.get(serviceName);
845
+ };
846
+ case "singleton":
847
+ return () => {
848
+ if (!target._singletonInstances.has(serviceName)) {
849
+ target._isBuildingSingleton = true;
850
+ try {
851
+ const instance = entry.factory(proxy);
852
+ target._singletonInstances.set(serviceName, instance);
853
+ } finally {
854
+ target._isBuildingSingleton = false;
855
+ }
856
+ }
857
+ return target._singletonInstances.get(serviceName);
858
+ };
859
+ default:
860
+ throw new Error(`Unsupported lifetime: ${entry.lifetime}`);
861
+ }
591
862
  } else {
592
863
  throw new Error(`Service not registered: ${serviceName}`);
593
864
  }
594
865
  }
595
- return Reflect.get(target, prop, receiver);
866
+ throw new Error(`Property access denied by Proxy: ${String(prop)}`);
596
867
  }
597
868
  });
598
- return this.proxy;
869
+ return proxy;
599
870
  }
600
- /**
601
- * Registers a service with explicit service name (fully type-safe).
602
- */
603
- register(serviceNameLiteral, factory, lifetime = "transient") {
604
- this.registry.set(serviceNameLiteral, { factory, lifetime });
871
+ }
872
+ class DIContainer extends DIContainerBase {
873
+ register(serviceNameCapitalizedLiteral, factory, lifetime) {
874
+ const entry = { factory, lifetime };
875
+ this._map.set(serviceNameCapitalizedLiteral, entry);
605
876
  return this;
606
877
  }
607
- /**
608
- * Resolves a service by service name.
609
- */
610
- resolveByName(serviceName) {
611
- const registration = this.registry.get(serviceName);
878
+ createTestClone() {
879
+ return new DIContainerTestClone(this);
880
+ }
881
+ }
882
+ class DIContainerTestClone extends DIContainerBase {
883
+ constructor(original) {
884
+ super();
885
+ original["_map"].forEach((value, key) => {
886
+ this._map.set(key, {
887
+ factory: (_scope) => {
888
+ throw new Error(`Service registration not overridden: ${key}`);
889
+ },
890
+ lifetime: value.lifetime
891
+ });
892
+ });
893
+ }
894
+ override(serviceNameLiteral, factory) {
895
+ const registration = this._map.get(serviceNameLiteral);
612
896
  if (!registration) {
613
- throw new Error(`Service not registered: ${serviceName}`);
897
+ throw new Error(`Service not registered: ${serviceNameLiteral}`);
614
898
  }
615
- if (registration.lifetime === "singleton") {
616
- if (!this.singletons.has(serviceName)) {
617
- const instance = registration.factory(this.proxy);
618
- this.singletons.set(serviceName, instance);
619
- }
620
- return this.singletons.get(serviceName);
621
- }
622
- return registration.factory(this.proxy);
899
+ this._map.set(serviceNameLiteral, { factory, lifetime: registration.lifetime });
900
+ return this;
623
901
  }
624
- /**
625
- * Creates a scoped container with typed method access to services.
626
- */
627
- createScope() {
628
- const scope = {};
629
- return new Proxy(scope, {
630
- get: (target, prop) => {
631
- if (typeof prop === "string" && prop.startsWith("get")) {
632
- const serviceName = prop.substring(3);
633
- if (this.registry.has(serviceName)) {
634
- const registration = this.registry.get(serviceName);
635
- return () => {
636
- if (registration?.lifetime === "transient") {
637
- return this.resolveByName(serviceName);
638
- }
639
- const cacheKey = `_cached_${serviceName}`;
640
- if (!target[cacheKey]) {
641
- target[cacheKey] = this.resolveByName(serviceName);
642
- }
643
- return target[cacheKey];
644
- };
645
- } else {
646
- throw new Error(`Service not registered: ${serviceName}`);
647
- }
902
+ }
903
+
904
+ async function triggerEndpointDecoratorNoStaticTypeCheck(endpoint, decorator, requestObject, context) {
905
+ let badRequestResponse = null;
906
+ const headers = decoratorParseRequestDefinitionField(endpoint.definition, "headers", requestObject);
907
+ if (isHttpResponseObject(headers)) {
908
+ badRequestResponse = headers;
909
+ return badRequestResponse;
910
+ }
911
+ const query = decoratorParseRequestDefinitionField(endpoint.definition, "query", requestObject);
912
+ if (isHttpResponseObject(query)) {
913
+ badRequestResponse = query;
914
+ return badRequestResponse;
915
+ }
916
+ const body = decoratorParseRequestDefinitionField(endpoint.definition, "body", requestObject);
917
+ if (isHttpResponseObject(body)) {
918
+ badRequestResponse = body;
919
+ return badRequestResponse;
920
+ }
921
+ const path = endpoint.createPath(requestObject.pathParams);
922
+ return decorator.handle({
923
+ ...requestObject,
924
+ method: endpoint.method,
925
+ genericPath: endpoint.genericPath,
926
+ path,
927
+ pathSegments: endpoint.pathSegments
928
+ }, context);
929
+ }
930
+ async function triggerMiddlewareDecoratorNoStaticTypeCheck(middlewareEntry, decorator, requestObject, context) {
931
+ let badRequestResponse = null;
932
+ const headers = decoratorParseRequestDefinitionField(middlewareEntry.middlewareSchemas, "headers", requestObject);
933
+ if (isHttpResponseObject(headers)) {
934
+ badRequestResponse = headers;
935
+ return badRequestResponse;
936
+ }
937
+ const query = decoratorParseRequestDefinitionField(middlewareEntry.middlewareSchemas, "query", requestObject);
938
+ if (isHttpResponseObject(query)) {
939
+ badRequestResponse = query;
940
+ return badRequestResponse;
941
+ }
942
+ const body = decoratorParseRequestDefinitionField(middlewareEntry.middlewareSchemas, "body", requestObject);
943
+ if (isHttpResponseObject(body)) {
944
+ badRequestResponse = body;
945
+ return badRequestResponse;
946
+ }
947
+ const path = middlewareEntry.createPath(requestObject.pathParams);
948
+ return decorator.handle({
949
+ ...requestObject,
950
+ method: middlewareEntry.method,
951
+ // TODO: might be empty, as middleware can be registered with path only, without method
952
+ genericPath: middlewareEntry.genericPath,
953
+ path,
954
+ pathSegments: middlewareEntry.pathSegments
955
+ }, context);
956
+ }
957
+ function decoratorParseRequestDefinitionField(decoratorSchemas, key, requestObject) {
958
+ if (decoratorSchemas[key]) {
959
+ if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
960
+ const result2 = decoratorSchemas[key].safeParse(requestObject[key]);
961
+ if (!result2.success) {
962
+ switch (decoratorSchemas[key].type) {
963
+ case "optional":
964
+ if (requestObject[key] === null) {
965
+ return void 0;
966
+ }
967
+ break;
968
+ case "nullable":
969
+ if (requestObject[key] === void 0) {
970
+ return null;
971
+ }
972
+ break;
648
973
  }
649
- return Reflect.get(target, prop);
974
+ return {
975
+ code: HttpStatusCode.BadRequest_400,
976
+ body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
977
+ };
650
978
  }
651
- });
652
- }
653
- createTestClone() {
654
- const clone = new DIContainer();
655
- return clone;
979
+ return result2.data;
980
+ }
981
+ const result = decoratorSchemas[key].safeParse(requestObject[key]);
982
+ if (!result.success) {
983
+ return {
984
+ code: HttpStatusCode.BadRequest_400,
985
+ body: result.error.issues
986
+ };
987
+ }
988
+ return result.data;
656
989
  }
990
+ return null;
657
991
  }
658
992
 
659
- function createInProcApiClient(contract, testContainer, registry) {
660
- const testApiReg = createRegistry(testContainer, contract, {
661
- handlerRegisteredCallback: (_entry) => {
662
- },
663
- middlewareHandlerRegisteredCallback: (_entry) => {
993
+ function createInProcApiClient(contract, testContainer, registry, options) {
994
+ const mwHandlers = [];
995
+ const mwReg = registry._middlewareRegistry;
996
+ const mwNdx = 0;
997
+ for (const mwEntry of mwReg.list) {
998
+ if (!options?.filterMiddleware) {
999
+ addMiddlewareDecorators(mwHandlers, mwEntry);
1000
+ mwHandlers.push(mwEntry);
1001
+ continue;
664
1002
  }
665
- });
666
- flatListAllRegistryEntries(registry).forEach((entry) => {
667
- if (!entry.handler) {
668
- return;
1003
+ const isIncluded = options.filterMiddleware(mwEntry.genericPath, mwNdx);
1004
+ if (!isIncluded) {
1005
+ continue;
669
1006
  }
670
- const rt = route(testApiReg, `${entry.methodEndpoint.method} ${entry.methodEndpoint.genericPath}`);
671
- rt.inject(entry.injection).register(entry.handler);
672
- });
1007
+ addMiddlewareDecorators(mwHandlers, mwEntry);
1008
+ mwHandlers.push(mwEntry);
1009
+ }
673
1010
  const client = createClient(contract, async (input) => {
674
- const rt = route(testApiReg, `${input.method} ${input.genericPath}`);
675
- const result = rt.trigger({
676
- headers: input.headers,
677
- pathParams: input.pathParams,
678
- body: input.body ?? {},
679
- query: input.query ?? {}
1011
+ if (options?.enhanceRequest) {
1012
+ input = options.enhanceRequest(input);
1013
+ }
1014
+ const diScope = testContainer.createScope();
1015
+ const allHandlerEntries = [...mwHandlers];
1016
+ const endpointHandlerEntry = route(registry, `${input.method} ${input.genericPath}`);
1017
+ if (!endpointHandlerEntry.handler) {
1018
+ throw new Error(`No handler registered for ${input.method} ${input.genericPath}`);
1019
+ }
1020
+ endpointHandlerEntry.decoratorFactories.forEach((decoratorFactory) => {
1021
+ allHandlerEntries.push({
1022
+ genericPath: endpointHandlerEntry.genericPath,
1023
+ triggerNoStaticTypeCheck: async (diScope2, requestObject, context) => {
1024
+ const decorator = decoratorFactory(diScope2);
1025
+ return await triggerEndpointDecoratorNoStaticTypeCheck(
1026
+ endpointHandlerEntry.methodEndpoint,
1027
+ decorator,
1028
+ requestObject,
1029
+ context
1030
+ );
1031
+ }
1032
+ });
680
1033
  });
681
- return result;
1034
+ if (options?.mockEndpointResponse) {
1035
+ const mockResponse = options.mockEndpointResponse(input.genericPath, input.method, diScope);
1036
+ if (mockResponse) {
1037
+ allHandlerEntries.push({
1038
+ genericPath: endpointHandlerEntry.genericPath,
1039
+ triggerNoStaticTypeCheck: async (_diScope, _requestObject, _next) => {
1040
+ return mockResponse;
1041
+ }
1042
+ });
1043
+ } else {
1044
+ allHandlerEntries.push(endpointHandlerEntry);
1045
+ }
1046
+ } else {
1047
+ allHandlerEntries.push(endpointHandlerEntry);
1048
+ }
1049
+ const response = await execHandlerChain(diScope, allHandlerEntries, input);
1050
+ if (!response) {
1051
+ throw new Error(`No response from ${input.method} ${input.genericPath}`);
1052
+ }
1053
+ return response;
682
1054
  });
683
1055
  return client;
684
1056
  }
1057
+ function addMiddlewareDecorators(mwHandlers, mwEntry) {
1058
+ mwEntry.decoratorFactories.forEach((decoratorFactory) => {
1059
+ mwHandlers.push({
1060
+ genericPath: mwEntry.genericPath,
1061
+ triggerNoStaticTypeCheck: async (diScope, requestObject, context) => {
1062
+ const decorator = decoratorFactory(diScope);
1063
+ return await triggerMiddlewareDecoratorNoStaticTypeCheck(
1064
+ mwEntry,
1065
+ decorator,
1066
+ requestObject,
1067
+ context
1068
+ );
1069
+ }
1070
+ });
1071
+ });
1072
+ }
685
1073
 
686
1074
  exports.ApiClient = ApiClient;
687
1075
  exports.ApiContract = ApiContract;
688
1076
  exports.ApiHandlersRegistry = ApiHandlersRegistry;
689
1077
  exports.DIContainer = DIContainer;
1078
+ exports.DIContainerBase = DIContainerBase;
1079
+ exports.DIContainerTestClone = DIContainerTestClone;
690
1080
  exports.HttpMethodEndpoint = HttpMethodEndpoint;
691
1081
  exports.HttpMethodEndpointResponse = HttpMethodEndpointResponse;
692
1082
  exports.HttpStatusCode = HttpStatusCode;
@@ -699,6 +1089,7 @@ exports.createClient = createClient;
699
1089
  exports.createInProcApiClient = createInProcApiClient;
700
1090
  exports.createRegistry = createRegistry;
701
1091
  exports.endpoint = endpoint;
1092
+ exports.execHandlerChain = execHandlerChain;
702
1093
  exports.flatListAllRegistryEntries = flatListAllRegistryEntries;
703
1094
  exports.isHttpResponseObject = isHttpResponseObject;
704
1095
  exports.isHttpStatusCode = isHttpStatusCode;
@@ -708,3 +1099,5 @@ exports.partialPathString = partialPathString;
708
1099
  exports.register = register;
709
1100
  exports.response = response;
710
1101
  exports.route = route;
1102
+ exports.triggerEndpointDecoratorNoStaticTypeCheck = triggerEndpointDecoratorNoStaticTypeCheck;
1103
+ exports.triggerMiddlewareDecoratorNoStaticTypeCheck = triggerMiddlewareDecoratorNoStaticTypeCheck;