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