@congruent-stack/congruent-api 0.7.0 → 0.9.0-rc.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 +215 -121
- package/dist/index.d.cts +28 -49
- package/dist/index.d.mts +28 -49
- package/dist/index.mjs +214 -122
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -158,7 +158,7 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
158
158
|
callback(this);
|
|
159
159
|
return this;
|
|
160
160
|
}
|
|
161
|
-
_injection = (
|
|
161
|
+
_injection = (_diScope) => ({});
|
|
162
162
|
get injection() {
|
|
163
163
|
return this._injection;
|
|
164
164
|
}
|
|
@@ -166,28 +166,28 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
166
166
|
this._injection = injection;
|
|
167
167
|
return this;
|
|
168
168
|
}
|
|
169
|
-
async trigger(
|
|
169
|
+
async trigger(diScope, requestObject) {
|
|
170
170
|
if (!this._handler) {
|
|
171
171
|
throw new Error("Handler not set for this endpoint");
|
|
172
172
|
}
|
|
173
173
|
let badRequestResponse = null;
|
|
174
|
-
const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers",
|
|
174
|
+
const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers", requestObject);
|
|
175
175
|
if (isHttpResponseObject(headers)) {
|
|
176
176
|
badRequestResponse = headers;
|
|
177
177
|
return badRequestResponse;
|
|
178
178
|
}
|
|
179
|
-
const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query",
|
|
179
|
+
const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query", requestObject);
|
|
180
180
|
if (isHttpResponseObject(query)) {
|
|
181
181
|
badRequestResponse = query;
|
|
182
182
|
return badRequestResponse;
|
|
183
183
|
}
|
|
184
|
-
const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body",
|
|
184
|
+
const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body", requestObject);
|
|
185
185
|
if (isHttpResponseObject(body)) {
|
|
186
186
|
badRequestResponse = body;
|
|
187
187
|
return badRequestResponse;
|
|
188
188
|
}
|
|
189
189
|
const path = `/${this._methodEndpoint.pathSegments.map(
|
|
190
|
-
(segment) => segment.startsWith(":") ?
|
|
190
|
+
(segment) => segment.startsWith(":") ? requestObject.pathParams[segment.slice(1)] ?? "?" : segment
|
|
191
191
|
).join("/")}`;
|
|
192
192
|
return await this._handler({
|
|
193
193
|
method: this._methodEndpoint.method,
|
|
@@ -195,32 +195,45 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
195
195
|
genericPath: this._methodEndpoint.genericPath,
|
|
196
196
|
pathSegments: this._methodEndpoint.pathSegments,
|
|
197
197
|
headers,
|
|
198
|
-
pathParams:
|
|
198
|
+
pathParams: requestObject.pathParams,
|
|
199
199
|
query,
|
|
200
200
|
body,
|
|
201
|
-
injected: this._injection(
|
|
201
|
+
injected: this._injection(diScope)
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
-
function parseRequestDefinitionField(definition, key,
|
|
205
|
+
function parseRequestDefinitionField(definition, key, requestObject) {
|
|
206
206
|
if (definition[key]) {
|
|
207
|
-
if (!(key in
|
|
208
|
-
|
|
207
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
208
|
+
const result2 = definition[key].safeParse(requestObject[key]);
|
|
209
|
+
if (!result2.success) {
|
|
210
|
+
switch (definition[key].type) {
|
|
211
|
+
case "optional":
|
|
212
|
+
if (requestObject[key] === null) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
case "nullable":
|
|
217
|
+
if (requestObject[key] === void 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
209
222
|
return {
|
|
210
223
|
code: HttpStatusCode.BadRequest_400,
|
|
211
224
|
body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
|
|
212
225
|
};
|
|
213
226
|
}
|
|
214
|
-
return
|
|
227
|
+
return result2.data;
|
|
215
228
|
}
|
|
216
|
-
const result = definition[key].safeParse(
|
|
229
|
+
const result = definition[key].safeParse(requestObject[key]);
|
|
217
230
|
if (!result.success) {
|
|
218
231
|
return {
|
|
219
232
|
code: HttpStatusCode.BadRequest_400,
|
|
220
233
|
body: result.error.issues
|
|
221
234
|
};
|
|
222
235
|
}
|
|
223
|
-
return result.data
|
|
236
|
+
return result.data;
|
|
224
237
|
}
|
|
225
238
|
return null;
|
|
226
239
|
}
|
|
@@ -235,6 +248,9 @@ function middleware(apiReg, path) {
|
|
|
235
248
|
}
|
|
236
249
|
class MiddlewareHandlersRegistryEntryInternal {
|
|
237
250
|
_dicontainer;
|
|
251
|
+
get dicontainer() {
|
|
252
|
+
return this._dicontainer;
|
|
253
|
+
}
|
|
238
254
|
_middlewareGenericPath;
|
|
239
255
|
get genericPath() {
|
|
240
256
|
return this._middlewareGenericPath;
|
|
@@ -265,39 +281,42 @@ class MiddlewareHandlersRegistryEntryInternal {
|
|
|
265
281
|
this._handler = handler;
|
|
266
282
|
}
|
|
267
283
|
_injection = (_dicontainer) => ({});
|
|
268
|
-
async trigger(
|
|
284
|
+
async trigger(diScope, requestObject, next) {
|
|
269
285
|
let badRequestResponse = null;
|
|
270
|
-
const headers = middlewareParseRequestDefinitionField(this._inputSchemas, "headers",
|
|
286
|
+
const headers = middlewareParseRequestDefinitionField(this._inputSchemas, "headers", requestObject);
|
|
271
287
|
if (isHttpResponseObject(headers)) {
|
|
272
288
|
badRequestResponse = headers;
|
|
273
289
|
return badRequestResponse;
|
|
274
290
|
}
|
|
275
|
-
const query = middlewareParseRequestDefinitionField(this._inputSchemas, "query",
|
|
291
|
+
const query = middlewareParseRequestDefinitionField(this._inputSchemas, "query", requestObject);
|
|
276
292
|
if (isHttpResponseObject(query)) {
|
|
277
293
|
badRequestResponse = query;
|
|
278
294
|
return badRequestResponse;
|
|
279
295
|
}
|
|
280
|
-
const body = middlewareParseRequestDefinitionField(this._inputSchemas, "body",
|
|
296
|
+
const body = middlewareParseRequestDefinitionField(this._inputSchemas, "body", requestObject);
|
|
281
297
|
if (isHttpResponseObject(body)) {
|
|
282
298
|
badRequestResponse = body;
|
|
283
299
|
return badRequestResponse;
|
|
284
300
|
}
|
|
285
301
|
const { method, pathSegments } = this._splitMiddlewarePath();
|
|
286
302
|
const path = `/${pathSegments.map(
|
|
287
|
-
(segment) => segment.startsWith(":") ?
|
|
303
|
+
(segment) => segment.startsWith(":") ? requestObject.pathParams[segment.slice(1)] ?? "?" : segment
|
|
288
304
|
).join("/")}`;
|
|
289
|
-
return await this._handler(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
305
|
+
return await this._handler(
|
|
306
|
+
{
|
|
307
|
+
method,
|
|
308
|
+
// TODO: might be empty, as middleware can be registered with path only, without method, possible fix: take it from express.request.method
|
|
309
|
+
path,
|
|
310
|
+
genericPath: this.genericPath,
|
|
311
|
+
pathSegments,
|
|
312
|
+
headers,
|
|
313
|
+
pathParams: requestObject.pathParams,
|
|
314
|
+
query,
|
|
315
|
+
body,
|
|
316
|
+
injected: this._injection(diScope)
|
|
317
|
+
},
|
|
318
|
+
next
|
|
319
|
+
);
|
|
301
320
|
}
|
|
302
321
|
}
|
|
303
322
|
class MiddlewareHandlersRegistryEntry {
|
|
@@ -307,7 +326,7 @@ class MiddlewareHandlersRegistryEntry {
|
|
|
307
326
|
this._registry = registry;
|
|
308
327
|
this._path = path;
|
|
309
328
|
}
|
|
310
|
-
_injection = (
|
|
329
|
+
_injection = (_diScope) => ({});
|
|
311
330
|
get injection() {
|
|
312
331
|
return this._injection;
|
|
313
332
|
}
|
|
@@ -332,8 +351,13 @@ class MiddlewareHandlersRegistry {
|
|
|
332
351
|
this.dicontainer = dicontainer;
|
|
333
352
|
this._onHandlerRegisteredCallback = callback;
|
|
334
353
|
}
|
|
354
|
+
_list = [];
|
|
355
|
+
get list() {
|
|
356
|
+
return this._list;
|
|
357
|
+
}
|
|
335
358
|
register(entry) {
|
|
336
359
|
if (this._onHandlerRegisteredCallback) {
|
|
360
|
+
this._list.push(entry);
|
|
337
361
|
this._onHandlerRegisteredCallback(entry);
|
|
338
362
|
}
|
|
339
363
|
}
|
|
@@ -342,25 +366,38 @@ class MiddlewareHandlersRegistry {
|
|
|
342
366
|
this._onHandlerRegisteredCallback = callback;
|
|
343
367
|
}
|
|
344
368
|
}
|
|
345
|
-
function middlewareParseRequestDefinitionField(inputSchemas, key,
|
|
369
|
+
function middlewareParseRequestDefinitionField(inputSchemas, key, requestObject) {
|
|
346
370
|
if (inputSchemas[key]) {
|
|
347
|
-
if (!(key in
|
|
348
|
-
|
|
371
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
372
|
+
const result2 = inputSchemas[key].safeParse(requestObject[key]);
|
|
373
|
+
if (!result2.success) {
|
|
374
|
+
switch (inputSchemas[key].type) {
|
|
375
|
+
case "optional":
|
|
376
|
+
if (requestObject[key] === null) {
|
|
377
|
+
return void 0;
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
case "nullable":
|
|
381
|
+
if (requestObject[key] === void 0) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
349
386
|
return {
|
|
350
387
|
code: HttpStatusCode.BadRequest_400,
|
|
351
388
|
body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
|
|
352
389
|
};
|
|
353
390
|
}
|
|
354
|
-
return
|
|
391
|
+
return result2.data;
|
|
355
392
|
}
|
|
356
|
-
const result = inputSchemas[key].safeParse(
|
|
393
|
+
const result = inputSchemas[key].safeParse(requestObject[key]);
|
|
357
394
|
if (!result.success) {
|
|
358
395
|
return {
|
|
359
396
|
code: HttpStatusCode.BadRequest_400,
|
|
360
397
|
body: result.error.issues
|
|
361
398
|
};
|
|
362
399
|
}
|
|
363
|
-
return result.data
|
|
400
|
+
return result.data;
|
|
364
401
|
}
|
|
365
402
|
return null;
|
|
366
403
|
}
|
|
@@ -528,12 +565,12 @@ class InnerApiClient {
|
|
|
528
565
|
delete currObj[key];
|
|
529
566
|
InnerApiClient._initialize(client, val, clientGenericHandler);
|
|
530
567
|
} else if (val instanceof HttpMethodEndpoint) {
|
|
531
|
-
currObj[key] = (
|
|
568
|
+
currObj[key] = (requestObject) => {
|
|
532
569
|
const pathParams = { ...client.__CONTEXT__.pathParameters };
|
|
533
570
|
client.__CONTEXT__ = InnerApiClient._initNewContext();
|
|
534
|
-
const headers = clientParseRequestDefinitionField(val.definition, "headers",
|
|
535
|
-
const query = clientParseRequestDefinitionField(val.definition, "query",
|
|
536
|
-
const body = clientParseRequestDefinitionField(val.definition, "body",
|
|
571
|
+
const headers = clientParseRequestDefinitionField(val.definition, "headers", requestObject);
|
|
572
|
+
const query = clientParseRequestDefinitionField(val.definition, "query", requestObject);
|
|
573
|
+
const body = clientParseRequestDefinitionField(val.definition, "body", requestObject);
|
|
537
574
|
const path = `/${val.pathSegments.map(
|
|
538
575
|
(segment) => segment.startsWith(":") ? pathParams[segment.slice(1)] ?? "?" : segment
|
|
539
576
|
).join("/")}`;
|
|
@@ -555,107 +592,153 @@ class InnerApiClient {
|
|
|
555
592
|
}
|
|
556
593
|
}
|
|
557
594
|
const ApiClient = InnerApiClient;
|
|
558
|
-
function clientParseRequestDefinitionField(definition, key,
|
|
595
|
+
function clientParseRequestDefinitionField(definition, key, requestObject) {
|
|
559
596
|
if (definition[key]) {
|
|
560
|
-
if (!(key in
|
|
561
|
-
|
|
562
|
-
|
|
597
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
598
|
+
const result2 = definition[key].safeParse(requestObject[key]);
|
|
599
|
+
if (!result2.success) {
|
|
600
|
+
switch (definition[key].type) {
|
|
601
|
+
case "optional":
|
|
602
|
+
if (requestObject[key] === null) {
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
case "nullable":
|
|
607
|
+
if (requestObject[key] === void 0) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
throw new Error(`'${key}' is required for this endpoint`);
|
|
563
613
|
}
|
|
564
|
-
return
|
|
614
|
+
return result2.data;
|
|
565
615
|
}
|
|
566
|
-
const result = definition[key].safeParse(
|
|
616
|
+
const result = definition[key].safeParse(requestObject[key]);
|
|
567
617
|
if (!result.success) {
|
|
568
618
|
throw new Error(`Validation for '${key}' failed`, { cause: result.error });
|
|
569
619
|
}
|
|
570
|
-
return result.data
|
|
620
|
+
return result.data;
|
|
571
621
|
}
|
|
572
622
|
return null;
|
|
573
623
|
}
|
|
574
624
|
|
|
575
|
-
class
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
625
|
+
class DIContainerBase {
|
|
626
|
+
_map = /* @__PURE__ */ new Map();
|
|
627
|
+
_singletonInstances = /* @__PURE__ */ new Map();
|
|
628
|
+
createScope() {
|
|
629
|
+
const proxy = new Proxy({
|
|
630
|
+
_map: this._map,
|
|
631
|
+
_singletonInstances: this._singletonInstances,
|
|
632
|
+
_scopedInstances: /* @__PURE__ */ new Map(),
|
|
633
|
+
_isBuildingSingleton: false
|
|
634
|
+
}, {
|
|
635
|
+
get: (target, prop) => {
|
|
636
|
+
if (prop.startsWith("get")) {
|
|
637
|
+
const serviceName = prop.slice(3);
|
|
638
|
+
if (target._map.has(serviceName)) {
|
|
639
|
+
const entry = target._map.get(serviceName);
|
|
640
|
+
switch (entry.lifetime) {
|
|
641
|
+
case "transient":
|
|
642
|
+
return () => {
|
|
643
|
+
if (target._isBuildingSingleton) {
|
|
644
|
+
throw new Error(`Cannot resolve transient service '${serviceName}' while building a singleton`);
|
|
645
|
+
}
|
|
646
|
+
return entry.factory(proxy);
|
|
647
|
+
};
|
|
648
|
+
case "scoped":
|
|
649
|
+
return () => {
|
|
650
|
+
if (target._isBuildingSingleton) {
|
|
651
|
+
throw new Error(`Cannot resolve scoped service '${serviceName}' while building a singleton`);
|
|
652
|
+
}
|
|
653
|
+
if (!target._scopedInstances.has(serviceName)) {
|
|
654
|
+
const instance = entry.factory(proxy);
|
|
655
|
+
target._scopedInstances.set(serviceName, instance);
|
|
656
|
+
}
|
|
657
|
+
return target._scopedInstances.get(serviceName);
|
|
658
|
+
};
|
|
659
|
+
case "singleton":
|
|
660
|
+
return () => {
|
|
661
|
+
if (!target._singletonInstances.has(serviceName)) {
|
|
662
|
+
target._isBuildingSingleton = true;
|
|
663
|
+
try {
|
|
664
|
+
const instance = entry.factory(proxy);
|
|
665
|
+
target._singletonInstances.set(serviceName, instance);
|
|
666
|
+
} finally {
|
|
667
|
+
target._isBuildingSingleton = false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return target._singletonInstances.get(serviceName);
|
|
671
|
+
};
|
|
672
|
+
default:
|
|
673
|
+
throw new Error(`Unsupported lifetime: ${entry.lifetime}`);
|
|
674
|
+
}
|
|
591
675
|
} else {
|
|
592
676
|
throw new Error(`Service not registered: ${serviceName}`);
|
|
593
677
|
}
|
|
594
678
|
}
|
|
595
|
-
|
|
679
|
+
throw new Error(`Property access denied by Proxy: ${String(prop)}`);
|
|
596
680
|
}
|
|
597
681
|
});
|
|
598
|
-
return
|
|
682
|
+
return proxy;
|
|
599
683
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.
|
|
684
|
+
}
|
|
685
|
+
class DIContainer extends DIContainerBase {
|
|
686
|
+
register(serviceNameCapitalizedLiteral, factory, lifetime) {
|
|
687
|
+
const entry = { factory, lifetime };
|
|
688
|
+
this._map.set(serviceNameCapitalizedLiteral, entry);
|
|
605
689
|
return this;
|
|
606
690
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
*/
|
|
610
|
-
resolveByName(serviceName) {
|
|
611
|
-
const registration = this.registry.get(serviceName);
|
|
612
|
-
if (!registration) {
|
|
613
|
-
throw new Error(`Service not registered: ${serviceName}`);
|
|
614
|
-
}
|
|
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);
|
|
691
|
+
createTestClone() {
|
|
692
|
+
return new DIContainerTestClone(this);
|
|
623
693
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return Reflect.get(target, prop);
|
|
650
|
-
}
|
|
694
|
+
}
|
|
695
|
+
class DIContainerTestClone extends DIContainerBase {
|
|
696
|
+
constructor(original) {
|
|
697
|
+
super();
|
|
698
|
+
original["_map"].forEach((value, key) => {
|
|
699
|
+
this._map.set(key, {
|
|
700
|
+
factory: (_scope) => {
|
|
701
|
+
throw new Error(`Service registration not overridden: ${key}`);
|
|
702
|
+
},
|
|
703
|
+
lifetime: value.lifetime
|
|
704
|
+
});
|
|
651
705
|
});
|
|
652
706
|
}
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
707
|
+
override(serviceNameLiteral, factory) {
|
|
708
|
+
const registration = this._map.get(serviceNameLiteral);
|
|
709
|
+
if (!registration) {
|
|
710
|
+
throw new Error(`Service not registered: ${serviceNameLiteral}`);
|
|
711
|
+
}
|
|
712
|
+
this._map.set(serviceNameLiteral, { factory, lifetime: registration.lifetime });
|
|
713
|
+
return this;
|
|
656
714
|
}
|
|
657
715
|
}
|
|
658
716
|
|
|
717
|
+
function execMiddleware(diScope, list, input) {
|
|
718
|
+
const queue = [...list];
|
|
719
|
+
const next = async () => {
|
|
720
|
+
const current = queue.shift();
|
|
721
|
+
if (!current) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const result = await current.trigger(
|
|
725
|
+
diScope,
|
|
726
|
+
{
|
|
727
|
+
headers: input.headers,
|
|
728
|
+
pathParams: input.pathParams,
|
|
729
|
+
body: input.body,
|
|
730
|
+
query: input.query
|
|
731
|
+
},
|
|
732
|
+
next
|
|
733
|
+
);
|
|
734
|
+
if (result) {
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
return next();
|
|
738
|
+
};
|
|
739
|
+
return next();
|
|
740
|
+
}
|
|
741
|
+
|
|
659
742
|
function createInProcApiClient(contract, testContainer, registry) {
|
|
660
743
|
const testApiReg = createRegistry(testContainer, contract, {
|
|
661
744
|
handlerRegisteredCallback: (_entry) => {
|
|
@@ -663,6 +746,10 @@ function createInProcApiClient(contract, testContainer, registry) {
|
|
|
663
746
|
middlewareHandlerRegisteredCallback: (_entry) => {
|
|
664
747
|
}
|
|
665
748
|
});
|
|
749
|
+
registry._middlewareRegistry.list.forEach((mwEntry) => {
|
|
750
|
+
testApiReg._middlewareRegistry.register(mwEntry);
|
|
751
|
+
});
|
|
752
|
+
const mwReg = testApiReg._middlewareRegistry;
|
|
666
753
|
flatListAllRegistryEntries(registry).forEach((entry) => {
|
|
667
754
|
if (!entry.handler) {
|
|
668
755
|
return;
|
|
@@ -671,12 +758,17 @@ function createInProcApiClient(contract, testContainer, registry) {
|
|
|
671
758
|
rt.inject(entry.injection).register(entry.handler);
|
|
672
759
|
});
|
|
673
760
|
const client = createClient(contract, async (input) => {
|
|
761
|
+
const diScope = testContainer.createScope();
|
|
762
|
+
const haltExecResponse = await execMiddleware(diScope, mwReg.list, input);
|
|
763
|
+
if (haltExecResponse) {
|
|
764
|
+
return haltExecResponse;
|
|
765
|
+
}
|
|
674
766
|
const rt = route(testApiReg, `${input.method} ${input.genericPath}`);
|
|
675
|
-
const result = rt.trigger({
|
|
767
|
+
const result = await rt.trigger(diScope, {
|
|
676
768
|
headers: input.headers,
|
|
677
769
|
pathParams: input.pathParams,
|
|
678
|
-
body: input.body
|
|
679
|
-
query: input.query
|
|
770
|
+
body: input.body,
|
|
771
|
+
query: input.query
|
|
680
772
|
});
|
|
681
773
|
return result;
|
|
682
774
|
});
|
|
@@ -687,6 +779,8 @@ exports.ApiClient = ApiClient;
|
|
|
687
779
|
exports.ApiContract = ApiContract;
|
|
688
780
|
exports.ApiHandlersRegistry = ApiHandlersRegistry;
|
|
689
781
|
exports.DIContainer = DIContainer;
|
|
782
|
+
exports.DIContainerBase = DIContainerBase;
|
|
783
|
+
exports.DIContainerTestClone = DIContainerTestClone;
|
|
690
784
|
exports.HttpMethodEndpoint = HttpMethodEndpoint;
|
|
691
785
|
exports.HttpMethodEndpointResponse = HttpMethodEndpointResponse;
|
|
692
786
|
exports.HttpStatusCode = HttpStatusCode;
|
package/dist/index.d.cts
CHANGED
|
@@ -171,53 +171,29 @@ declare function isHttpResponseObject(obj: any): obj is HttpResponseObject;
|
|
|
171
171
|
type HttpMethodEndpointHandler<TDef extends IHttpMethodEndpointDefinition, TPathParams extends string, TInjected> = (input: HttpMethodEndpointHandlerInput<TDef, TPathParams, TInjected>) => Promise<HttpMethodEndpointHandlerOutput<TDef>>;
|
|
172
172
|
type ClientHttpMethodEndpointHandler = (input: ClientHttpMethodEndpointHandlerInput) => Promise<ClientHttpMethodEndpointHandlerOutput>;
|
|
173
173
|
|
|
174
|
-
type StringLiteral<
|
|
175
|
-
type
|
|
174
|
+
type StringLiteral<T extends string> = string extends T ? never : T;
|
|
175
|
+
type CapitalizedStringLiteral<T extends string> = string extends T ? never : T extends `${Uppercase<infer F>}${infer _}` ? F extends Lowercase<F> ? never : T : `❌ ERROR: Must start with uppercase letter`;
|
|
176
176
|
type DILifetime = 'singleton' | 'transient' | 'scoped';
|
|
177
|
-
type
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*/
|
|
181
|
-
type DIScope<R extends DIServiceRegistry> = {
|
|
182
|
-
[K in keyof R as `get${string & K}`]: () => R[K];
|
|
177
|
+
type DIRegistryEntry<T> = {
|
|
178
|
+
factory: (scope: any) => T;
|
|
179
|
+
lifetime: DILifetime;
|
|
183
180
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* it adds a method named `getK` that returns an instance of the corresponding service type.
|
|
188
|
-
*
|
|
189
|
-
* For example, if R is `{ LoggerService: LoggerService }`, this type will be:
|
|
190
|
-
* { getLoggerService: () => LoggerService }
|
|
191
|
-
*/
|
|
192
|
-
type DITypedContainer<R extends DIServiceRegistry> = DIContainer<R> & {
|
|
193
|
-
[K in keyof R as `get${string & K}`]: () => R[K];
|
|
181
|
+
type DIRegistry = Record<string, DIRegistryEntry<any>>;
|
|
182
|
+
type DIScope<R extends DIRegistry> = {
|
|
183
|
+
[K in keyof R as `get${string & K}`]: () => R[K] extends DIRegistryEntry<infer T> ? T : never;
|
|
194
184
|
};
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
declare class DIContainer<R extends DIServiceRegistry = {}> {
|
|
199
|
-
private registry;
|
|
200
|
-
private singletons;
|
|
201
|
-
private proxy;
|
|
202
|
-
/**
|
|
203
|
-
* The constructor returns a Proxy. This is the runtime magic that intercepts
|
|
204
|
-
* calls to methods like `getLoggerService()`. It parses the method name,
|
|
205
|
-
* finds the corresponding service class in the registry, and resolves it.
|
|
206
|
-
*/
|
|
207
|
-
constructor();
|
|
208
|
-
/**
|
|
209
|
-
* Registers a service with explicit service name (fully type-safe).
|
|
210
|
-
*/
|
|
211
|
-
register<T, N extends string>(serviceNameLiteral: StringLiteral<N>, factory: (container: DITypedContainer<R>) => T, lifetime?: DILifetime): DITypedContainer<R & Record<N, T>>;
|
|
212
|
-
/**
|
|
213
|
-
* Resolves a service by service name.
|
|
214
|
-
*/
|
|
215
|
-
private resolveByName;
|
|
216
|
-
/**
|
|
217
|
-
* Creates a scoped container with typed method access to services.
|
|
218
|
-
*/
|
|
185
|
+
declare class DIContainerBase<R extends DIRegistry> {
|
|
186
|
+
protected _map: Map<string, DIRegistryEntry<any>>;
|
|
187
|
+
protected _singletonInstances: Map<string, any>;
|
|
219
188
|
createScope(): DIScope<R>;
|
|
220
|
-
|
|
189
|
+
}
|
|
190
|
+
declare class DIContainer<R extends DIRegistry = {}> extends DIContainerBase<R> {
|
|
191
|
+
register<K extends string, T>(serviceNameCapitalizedLiteral: CapitalizedStringLiteral<K>, factory: (scope: DIScope<R>) => T, lifetime: DILifetime): DIContainer<R & Record<K, DIRegistryEntry<T>>>;
|
|
192
|
+
createTestClone(): DIContainerTestClone<R, this>;
|
|
193
|
+
}
|
|
194
|
+
declare class DIContainerTestClone<R extends DIRegistry, TDIContainer extends DIContainer<R>> extends DIContainerBase<R> {
|
|
195
|
+
constructor(original: TDIContainer);
|
|
196
|
+
override<K extends keyof R & string>(serviceNameLiteral: K, factory: (scope: DIScope<R>) => R[K] extends DIRegistryEntry<infer T> ? T : never): this;
|
|
221
197
|
}
|
|
222
198
|
|
|
223
199
|
type PrepareRegistryEntryCallback<TDef extends IHttpMethodEndpointDefinition & ValidateHttpMethodEndpointDefinition<TDef>, TDIContainer extends DIContainer, TPathParams extends string> = (entry: MethodEndpointHandlerRegistryEntry<TDef, TDIContainer, TPathParams, any>) => void;
|
|
@@ -236,8 +212,8 @@ declare class MethodEndpointHandlerRegistryEntry<TDef extends IHttpMethodEndpoin
|
|
|
236
212
|
prepare(callback: PrepareRegistryEntryCallback<TDef, TDIContainer, TPathParams>): this;
|
|
237
213
|
private _injection;
|
|
238
214
|
get injection(): any;
|
|
239
|
-
inject<TNewInjected>(injection: (
|
|
240
|
-
trigger(
|
|
215
|
+
inject<TNewInjected>(injection: (diScope: ReturnType<TDIContainer['createScope']>) => TNewInjected): MethodEndpointHandlerRegistryEntry<TDef, TDIContainer, TPathParams, TNewInjected>;
|
|
216
|
+
trigger(diScope: DIScope<any>, requestObject: {
|
|
241
217
|
headers: Record<string, string>;
|
|
242
218
|
pathParams: Record<string, string>;
|
|
243
219
|
query: object;
|
|
@@ -280,6 +256,7 @@ type MiddlewarePath<TDef, BasePath extends string = ""> = (BasePath extends "" ?
|
|
|
280
256
|
}[keyof TDef & string];
|
|
281
257
|
declare class MiddlewareHandlersRegistryEntryInternal<TDIContainer extends DIContainer, TInjected> {
|
|
282
258
|
private readonly _dicontainer;
|
|
259
|
+
get dicontainer(): TDIContainer;
|
|
283
260
|
private readonly _middlewareGenericPath;
|
|
284
261
|
get genericPath(): string;
|
|
285
262
|
private _splitMiddlewarePath;
|
|
@@ -287,7 +264,7 @@ declare class MiddlewareHandlersRegistryEntryInternal<TDIContainer extends DICon
|
|
|
287
264
|
private readonly _handler;
|
|
288
265
|
constructor(diContainer: TDIContainer, middlewarePath: string, inputSchemas: MiddlewareHandlerInputSchemas, injection: (dicontainer: TDIContainer) => TInjected, handler: MiddlewareHandlerInternal<TInjected>);
|
|
289
266
|
private _injection;
|
|
290
|
-
trigger(
|
|
267
|
+
trigger(diScope: DIScope<any>, requestObject: {
|
|
291
268
|
headers: Record<string, string>;
|
|
292
269
|
pathParams: Record<string, string>;
|
|
293
270
|
query: object;
|
|
@@ -300,13 +277,15 @@ declare class MiddlewareHandlersRegistryEntry<TApiDef extends IApiContractDefini
|
|
|
300
277
|
constructor(registry: MiddlewareHandlersRegistry<TDIContainer>, path: TPath);
|
|
301
278
|
private _injection;
|
|
302
279
|
get injection(): any;
|
|
303
|
-
inject<TNewInjected>(injection: (
|
|
280
|
+
inject<TNewInjected>(injection: (diScope: ReturnType<TDIContainer['createScope']>) => TNewInjected): MiddlewareHandlersRegistryEntry<TApiDef, TDIContainer, TPathParams, TPath, TNewInjected>;
|
|
304
281
|
register<const InputSchemas extends MiddlewareHandlerInputSchemas>(inputSchemas: InputSchemas, handler: MiddlewareHandler<`${TPathParams}${ExtractConcatenatedParamNamesFromPath<TPath>}`, InputSchemas, TInjected>): void;
|
|
305
282
|
}
|
|
306
283
|
type OnMiddlewareHandlerRegisteredCallback<TDIContainer extends DIContainer, TInjected> = (entry: MiddlewareHandlersRegistryEntryInternal<TDIContainer, TInjected>) => void;
|
|
307
284
|
declare class MiddlewareHandlersRegistry<TDIContainer extends DIContainer> {
|
|
308
285
|
readonly dicontainer: TDIContainer;
|
|
309
286
|
constructor(dicontainer: TDIContainer, callback: OnMiddlewareHandlerRegisteredCallback<TDIContainer, unknown>);
|
|
287
|
+
private readonly _list;
|
|
288
|
+
get list(): Readonly<MiddlewareHandlersRegistryEntryInternal<TDIContainer, unknown>[]>;
|
|
310
289
|
register<TInjected>(entry: MiddlewareHandlersRegistryEntryInternal<TDIContainer, TInjected>): void;
|
|
311
290
|
private _onHandlerRegisteredCallback;
|
|
312
291
|
_onHandlerRegistered(callback: OnMiddlewareHandlerRegisteredCallback<TDIContainer, unknown>): void;
|
|
@@ -379,6 +358,6 @@ type ApiClientDef<ObjType extends object> = {
|
|
|
379
358
|
type ApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>> = Omit<ApiClientDef<InnerApiClient<TDef> & TDef>, "__CONTEXT__">;
|
|
380
359
|
declare const ApiClient: new <TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>>(contract: ApiContract<TDef>, clientGenericHandler: ClientHttpMethodEndpointHandler) => ApiClient<TDef>;
|
|
381
360
|
|
|
382
|
-
declare function createInProcApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>, TDIContainer extends DIContainer
|
|
361
|
+
declare function createInProcApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>, TDIContainer extends DIContainer, TDIContainerTestClone extends DIContainerTestClone<any, TDIContainer>>(contract: ApiContract<TDef>, testContainer: TDIContainerTestClone, registry: ApiHandlersRegistry<TDef, TDIContainer>): ApiClient<TDef>;
|
|
383
362
|
|
|
384
|
-
export { ApiClient, type ApiClientDef, ApiContract, ApiHandlersRegistry, type ApiHandlersRegistryDef, type ClientHttpMethodEndpointHandler, type ClientHttpMethodEndpointHandlerInput, type ClientHttpMethodEndpointHandlerOutput,
|
|
363
|
+
export { ApiClient, type ApiClientDef, ApiContract, ApiHandlersRegistry, type ApiHandlersRegistryDef, type CapitalizedStringLiteral, type ClientHttpMethodEndpointHandler, type ClientHttpMethodEndpointHandlerInput, type ClientHttpMethodEndpointHandlerOutput, DIContainer, DIContainerBase, DIContainerTestClone, type DILifetime, type DIRegistry, type DIRegistryEntry, type DIScope, type ExtractConcatenatedParamNamesFromMethodFirstPath, type ExtractConcatenatedParamNamesFromPath, type ExtractConcatenatedParamNamesFromPathSegments, type ExtractEndpointFromPath, type GenericOnHandlerRegisteredCallback, type HttpMethod, type HttpMethodCallFunc, type HttpMethodCallInput, HttpMethodEndpoint, type HttpMethodEndpointHandler, type HttpMethodEndpointHandlerInput, type HttpMethodEndpointHandlerOutput, HttpMethodEndpointResponse, type HttpMethodEndpointResponses, type HttpResponseObject, HttpStatusCode, type IApiContractDefinition, type IClientContext, type IHttpMethodEndpointDefinition, type IHttpMethodEndpointResponseDefinition, type IRegistrySettings, type LowerCasedHttpMethod, MethodEndpointHandlerRegistryEntry, type MethodFirstPath, type MiddlewareHandler, type MiddlewareHandlerInput, type MiddlewareHandlerInputInternal, type MiddlewareHandlerInputSchemas, type MiddlewareHandlerInternal, MiddlewareHandlersRegistry, MiddlewareHandlersRegistryEntry, MiddlewareHandlersRegistryEntryInternal, type MiddlewarePath, type OnHandlerRegisteredCallback, type OnMiddlewareHandlerRegisteredCallback, type PartialPath, type PartialPathResult, type PathParamFunc, type PrepareRegistryEntryCallback, type StringLiteral, type TypedPathParams, type ValidateApiContractDefinition, type ValidateHttpMethodEndpointDefinition, apiContract, createClient, createInProcApiClient, createRegistry, endpoint, flatListAllRegistryEntries, isHttpResponseObject, isHttpStatusCode, middleware, partial, partialPathString, register, response, route };
|
package/dist/index.d.mts
CHANGED
|
@@ -171,53 +171,29 @@ declare function isHttpResponseObject(obj: any): obj is HttpResponseObject;
|
|
|
171
171
|
type HttpMethodEndpointHandler<TDef extends IHttpMethodEndpointDefinition, TPathParams extends string, TInjected> = (input: HttpMethodEndpointHandlerInput<TDef, TPathParams, TInjected>) => Promise<HttpMethodEndpointHandlerOutput<TDef>>;
|
|
172
172
|
type ClientHttpMethodEndpointHandler = (input: ClientHttpMethodEndpointHandlerInput) => Promise<ClientHttpMethodEndpointHandlerOutput>;
|
|
173
173
|
|
|
174
|
-
type StringLiteral<
|
|
175
|
-
type
|
|
174
|
+
type StringLiteral<T extends string> = string extends T ? never : T;
|
|
175
|
+
type CapitalizedStringLiteral<T extends string> = string extends T ? never : T extends `${Uppercase<infer F>}${infer _}` ? F extends Lowercase<F> ? never : T : `❌ ERROR: Must start with uppercase letter`;
|
|
176
176
|
type DILifetime = 'singleton' | 'transient' | 'scoped';
|
|
177
|
-
type
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*/
|
|
181
|
-
type DIScope<R extends DIServiceRegistry> = {
|
|
182
|
-
[K in keyof R as `get${string & K}`]: () => R[K];
|
|
177
|
+
type DIRegistryEntry<T> = {
|
|
178
|
+
factory: (scope: any) => T;
|
|
179
|
+
lifetime: DILifetime;
|
|
183
180
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* it adds a method named `getK` that returns an instance of the corresponding service type.
|
|
188
|
-
*
|
|
189
|
-
* For example, if R is `{ LoggerService: LoggerService }`, this type will be:
|
|
190
|
-
* { getLoggerService: () => LoggerService }
|
|
191
|
-
*/
|
|
192
|
-
type DITypedContainer<R extends DIServiceRegistry> = DIContainer<R> & {
|
|
193
|
-
[K in keyof R as `get${string & K}`]: () => R[K];
|
|
181
|
+
type DIRegistry = Record<string, DIRegistryEntry<any>>;
|
|
182
|
+
type DIScope<R extends DIRegistry> = {
|
|
183
|
+
[K in keyof R as `get${string & K}`]: () => R[K] extends DIRegistryEntry<infer T> ? T : never;
|
|
194
184
|
};
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
declare class DIContainer<R extends DIServiceRegistry = {}> {
|
|
199
|
-
private registry;
|
|
200
|
-
private singletons;
|
|
201
|
-
private proxy;
|
|
202
|
-
/**
|
|
203
|
-
* The constructor returns a Proxy. This is the runtime magic that intercepts
|
|
204
|
-
* calls to methods like `getLoggerService()`. It parses the method name,
|
|
205
|
-
* finds the corresponding service class in the registry, and resolves it.
|
|
206
|
-
*/
|
|
207
|
-
constructor();
|
|
208
|
-
/**
|
|
209
|
-
* Registers a service with explicit service name (fully type-safe).
|
|
210
|
-
*/
|
|
211
|
-
register<T, N extends string>(serviceNameLiteral: StringLiteral<N>, factory: (container: DITypedContainer<R>) => T, lifetime?: DILifetime): DITypedContainer<R & Record<N, T>>;
|
|
212
|
-
/**
|
|
213
|
-
* Resolves a service by service name.
|
|
214
|
-
*/
|
|
215
|
-
private resolveByName;
|
|
216
|
-
/**
|
|
217
|
-
* Creates a scoped container with typed method access to services.
|
|
218
|
-
*/
|
|
185
|
+
declare class DIContainerBase<R extends DIRegistry> {
|
|
186
|
+
protected _map: Map<string, DIRegistryEntry<any>>;
|
|
187
|
+
protected _singletonInstances: Map<string, any>;
|
|
219
188
|
createScope(): DIScope<R>;
|
|
220
|
-
|
|
189
|
+
}
|
|
190
|
+
declare class DIContainer<R extends DIRegistry = {}> extends DIContainerBase<R> {
|
|
191
|
+
register<K extends string, T>(serviceNameCapitalizedLiteral: CapitalizedStringLiteral<K>, factory: (scope: DIScope<R>) => T, lifetime: DILifetime): DIContainer<R & Record<K, DIRegistryEntry<T>>>;
|
|
192
|
+
createTestClone(): DIContainerTestClone<R, this>;
|
|
193
|
+
}
|
|
194
|
+
declare class DIContainerTestClone<R extends DIRegistry, TDIContainer extends DIContainer<R>> extends DIContainerBase<R> {
|
|
195
|
+
constructor(original: TDIContainer);
|
|
196
|
+
override<K extends keyof R & string>(serviceNameLiteral: K, factory: (scope: DIScope<R>) => R[K] extends DIRegistryEntry<infer T> ? T : never): this;
|
|
221
197
|
}
|
|
222
198
|
|
|
223
199
|
type PrepareRegistryEntryCallback<TDef extends IHttpMethodEndpointDefinition & ValidateHttpMethodEndpointDefinition<TDef>, TDIContainer extends DIContainer, TPathParams extends string> = (entry: MethodEndpointHandlerRegistryEntry<TDef, TDIContainer, TPathParams, any>) => void;
|
|
@@ -236,8 +212,8 @@ declare class MethodEndpointHandlerRegistryEntry<TDef extends IHttpMethodEndpoin
|
|
|
236
212
|
prepare(callback: PrepareRegistryEntryCallback<TDef, TDIContainer, TPathParams>): this;
|
|
237
213
|
private _injection;
|
|
238
214
|
get injection(): any;
|
|
239
|
-
inject<TNewInjected>(injection: (
|
|
240
|
-
trigger(
|
|
215
|
+
inject<TNewInjected>(injection: (diScope: ReturnType<TDIContainer['createScope']>) => TNewInjected): MethodEndpointHandlerRegistryEntry<TDef, TDIContainer, TPathParams, TNewInjected>;
|
|
216
|
+
trigger(diScope: DIScope<any>, requestObject: {
|
|
241
217
|
headers: Record<string, string>;
|
|
242
218
|
pathParams: Record<string, string>;
|
|
243
219
|
query: object;
|
|
@@ -280,6 +256,7 @@ type MiddlewarePath<TDef, BasePath extends string = ""> = (BasePath extends "" ?
|
|
|
280
256
|
}[keyof TDef & string];
|
|
281
257
|
declare class MiddlewareHandlersRegistryEntryInternal<TDIContainer extends DIContainer, TInjected> {
|
|
282
258
|
private readonly _dicontainer;
|
|
259
|
+
get dicontainer(): TDIContainer;
|
|
283
260
|
private readonly _middlewareGenericPath;
|
|
284
261
|
get genericPath(): string;
|
|
285
262
|
private _splitMiddlewarePath;
|
|
@@ -287,7 +264,7 @@ declare class MiddlewareHandlersRegistryEntryInternal<TDIContainer extends DICon
|
|
|
287
264
|
private readonly _handler;
|
|
288
265
|
constructor(diContainer: TDIContainer, middlewarePath: string, inputSchemas: MiddlewareHandlerInputSchemas, injection: (dicontainer: TDIContainer) => TInjected, handler: MiddlewareHandlerInternal<TInjected>);
|
|
289
266
|
private _injection;
|
|
290
|
-
trigger(
|
|
267
|
+
trigger(diScope: DIScope<any>, requestObject: {
|
|
291
268
|
headers: Record<string, string>;
|
|
292
269
|
pathParams: Record<string, string>;
|
|
293
270
|
query: object;
|
|
@@ -300,13 +277,15 @@ declare class MiddlewareHandlersRegistryEntry<TApiDef extends IApiContractDefini
|
|
|
300
277
|
constructor(registry: MiddlewareHandlersRegistry<TDIContainer>, path: TPath);
|
|
301
278
|
private _injection;
|
|
302
279
|
get injection(): any;
|
|
303
|
-
inject<TNewInjected>(injection: (
|
|
280
|
+
inject<TNewInjected>(injection: (diScope: ReturnType<TDIContainer['createScope']>) => TNewInjected): MiddlewareHandlersRegistryEntry<TApiDef, TDIContainer, TPathParams, TPath, TNewInjected>;
|
|
304
281
|
register<const InputSchemas extends MiddlewareHandlerInputSchemas>(inputSchemas: InputSchemas, handler: MiddlewareHandler<`${TPathParams}${ExtractConcatenatedParamNamesFromPath<TPath>}`, InputSchemas, TInjected>): void;
|
|
305
282
|
}
|
|
306
283
|
type OnMiddlewareHandlerRegisteredCallback<TDIContainer extends DIContainer, TInjected> = (entry: MiddlewareHandlersRegistryEntryInternal<TDIContainer, TInjected>) => void;
|
|
307
284
|
declare class MiddlewareHandlersRegistry<TDIContainer extends DIContainer> {
|
|
308
285
|
readonly dicontainer: TDIContainer;
|
|
309
286
|
constructor(dicontainer: TDIContainer, callback: OnMiddlewareHandlerRegisteredCallback<TDIContainer, unknown>);
|
|
287
|
+
private readonly _list;
|
|
288
|
+
get list(): Readonly<MiddlewareHandlersRegistryEntryInternal<TDIContainer, unknown>[]>;
|
|
310
289
|
register<TInjected>(entry: MiddlewareHandlersRegistryEntryInternal<TDIContainer, TInjected>): void;
|
|
311
290
|
private _onHandlerRegisteredCallback;
|
|
312
291
|
_onHandlerRegistered(callback: OnMiddlewareHandlerRegisteredCallback<TDIContainer, unknown>): void;
|
|
@@ -379,6 +358,6 @@ type ApiClientDef<ObjType extends object> = {
|
|
|
379
358
|
type ApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>> = Omit<ApiClientDef<InnerApiClient<TDef> & TDef>, "__CONTEXT__">;
|
|
380
359
|
declare const ApiClient: new <TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>>(contract: ApiContract<TDef>, clientGenericHandler: ClientHttpMethodEndpointHandler) => ApiClient<TDef>;
|
|
381
360
|
|
|
382
|
-
declare function createInProcApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>, TDIContainer extends DIContainer
|
|
361
|
+
declare function createInProcApiClient<TDef extends IApiContractDefinition & ValidateApiContractDefinition<TDef>, TDIContainer extends DIContainer, TDIContainerTestClone extends DIContainerTestClone<any, TDIContainer>>(contract: ApiContract<TDef>, testContainer: TDIContainerTestClone, registry: ApiHandlersRegistry<TDef, TDIContainer>): ApiClient<TDef>;
|
|
383
362
|
|
|
384
|
-
export { ApiClient, type ApiClientDef, ApiContract, ApiHandlersRegistry, type ApiHandlersRegistryDef, type ClientHttpMethodEndpointHandler, type ClientHttpMethodEndpointHandlerInput, type ClientHttpMethodEndpointHandlerOutput,
|
|
363
|
+
export { ApiClient, type ApiClientDef, ApiContract, ApiHandlersRegistry, type ApiHandlersRegistryDef, type CapitalizedStringLiteral, type ClientHttpMethodEndpointHandler, type ClientHttpMethodEndpointHandlerInput, type ClientHttpMethodEndpointHandlerOutput, DIContainer, DIContainerBase, DIContainerTestClone, type DILifetime, type DIRegistry, type DIRegistryEntry, type DIScope, type ExtractConcatenatedParamNamesFromMethodFirstPath, type ExtractConcatenatedParamNamesFromPath, type ExtractConcatenatedParamNamesFromPathSegments, type ExtractEndpointFromPath, type GenericOnHandlerRegisteredCallback, type HttpMethod, type HttpMethodCallFunc, type HttpMethodCallInput, HttpMethodEndpoint, type HttpMethodEndpointHandler, type HttpMethodEndpointHandlerInput, type HttpMethodEndpointHandlerOutput, HttpMethodEndpointResponse, type HttpMethodEndpointResponses, type HttpResponseObject, HttpStatusCode, type IApiContractDefinition, type IClientContext, type IHttpMethodEndpointDefinition, type IHttpMethodEndpointResponseDefinition, type IRegistrySettings, type LowerCasedHttpMethod, MethodEndpointHandlerRegistryEntry, type MethodFirstPath, type MiddlewareHandler, type MiddlewareHandlerInput, type MiddlewareHandlerInputInternal, type MiddlewareHandlerInputSchemas, type MiddlewareHandlerInternal, MiddlewareHandlersRegistry, MiddlewareHandlersRegistryEntry, MiddlewareHandlersRegistryEntryInternal, type MiddlewarePath, type OnHandlerRegisteredCallback, type OnMiddlewareHandlerRegisteredCallback, type PartialPath, type PartialPathResult, type PathParamFunc, type PrepareRegistryEntryCallback, type StringLiteral, type TypedPathParams, type ValidateApiContractDefinition, type ValidateHttpMethodEndpointDefinition, apiContract, createClient, createInProcApiClient, createRegistry, endpoint, flatListAllRegistryEntries, isHttpResponseObject, isHttpStatusCode, middleware, partial, partialPathString, register, response, route };
|
package/dist/index.mjs
CHANGED
|
@@ -156,7 +156,7 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
156
156
|
callback(this);
|
|
157
157
|
return this;
|
|
158
158
|
}
|
|
159
|
-
_injection = (
|
|
159
|
+
_injection = (_diScope) => ({});
|
|
160
160
|
get injection() {
|
|
161
161
|
return this._injection;
|
|
162
162
|
}
|
|
@@ -164,28 +164,28 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
164
164
|
this._injection = injection;
|
|
165
165
|
return this;
|
|
166
166
|
}
|
|
167
|
-
async trigger(
|
|
167
|
+
async trigger(diScope, requestObject) {
|
|
168
168
|
if (!this._handler) {
|
|
169
169
|
throw new Error("Handler not set for this endpoint");
|
|
170
170
|
}
|
|
171
171
|
let badRequestResponse = null;
|
|
172
|
-
const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers",
|
|
172
|
+
const headers = parseRequestDefinitionField(this._methodEndpoint.definition, "headers", requestObject);
|
|
173
173
|
if (isHttpResponseObject(headers)) {
|
|
174
174
|
badRequestResponse = headers;
|
|
175
175
|
return badRequestResponse;
|
|
176
176
|
}
|
|
177
|
-
const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query",
|
|
177
|
+
const query = parseRequestDefinitionField(this._methodEndpoint.definition, "query", requestObject);
|
|
178
178
|
if (isHttpResponseObject(query)) {
|
|
179
179
|
badRequestResponse = query;
|
|
180
180
|
return badRequestResponse;
|
|
181
181
|
}
|
|
182
|
-
const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body",
|
|
182
|
+
const body = parseRequestDefinitionField(this._methodEndpoint.definition, "body", requestObject);
|
|
183
183
|
if (isHttpResponseObject(body)) {
|
|
184
184
|
badRequestResponse = body;
|
|
185
185
|
return badRequestResponse;
|
|
186
186
|
}
|
|
187
187
|
const path = `/${this._methodEndpoint.pathSegments.map(
|
|
188
|
-
(segment) => segment.startsWith(":") ?
|
|
188
|
+
(segment) => segment.startsWith(":") ? requestObject.pathParams[segment.slice(1)] ?? "?" : segment
|
|
189
189
|
).join("/")}`;
|
|
190
190
|
return await this._handler({
|
|
191
191
|
method: this._methodEndpoint.method,
|
|
@@ -193,32 +193,45 @@ class MethodEndpointHandlerRegistryEntry {
|
|
|
193
193
|
genericPath: this._methodEndpoint.genericPath,
|
|
194
194
|
pathSegments: this._methodEndpoint.pathSegments,
|
|
195
195
|
headers,
|
|
196
|
-
pathParams:
|
|
196
|
+
pathParams: requestObject.pathParams,
|
|
197
197
|
query,
|
|
198
198
|
body,
|
|
199
|
-
injected: this._injection(
|
|
199
|
+
injected: this._injection(diScope)
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
function parseRequestDefinitionField(definition, key,
|
|
203
|
+
function parseRequestDefinitionField(definition, key, requestObject) {
|
|
204
204
|
if (definition[key]) {
|
|
205
|
-
if (!(key in
|
|
206
|
-
|
|
205
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
206
|
+
const result2 = definition[key].safeParse(requestObject[key]);
|
|
207
|
+
if (!result2.success) {
|
|
208
|
+
switch (definition[key].type) {
|
|
209
|
+
case "optional":
|
|
210
|
+
if (requestObject[key] === null) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
case "nullable":
|
|
215
|
+
if (requestObject[key] === void 0) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
207
220
|
return {
|
|
208
221
|
code: HttpStatusCode.BadRequest_400,
|
|
209
222
|
body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
|
|
210
223
|
};
|
|
211
224
|
}
|
|
212
|
-
return
|
|
225
|
+
return result2.data;
|
|
213
226
|
}
|
|
214
|
-
const result = definition[key].safeParse(
|
|
227
|
+
const result = definition[key].safeParse(requestObject[key]);
|
|
215
228
|
if (!result.success) {
|
|
216
229
|
return {
|
|
217
230
|
code: HttpStatusCode.BadRequest_400,
|
|
218
231
|
body: result.error.issues
|
|
219
232
|
};
|
|
220
233
|
}
|
|
221
|
-
return result.data
|
|
234
|
+
return result.data;
|
|
222
235
|
}
|
|
223
236
|
return null;
|
|
224
237
|
}
|
|
@@ -233,6 +246,9 @@ function middleware(apiReg, path) {
|
|
|
233
246
|
}
|
|
234
247
|
class MiddlewareHandlersRegistryEntryInternal {
|
|
235
248
|
_dicontainer;
|
|
249
|
+
get dicontainer() {
|
|
250
|
+
return this._dicontainer;
|
|
251
|
+
}
|
|
236
252
|
_middlewareGenericPath;
|
|
237
253
|
get genericPath() {
|
|
238
254
|
return this._middlewareGenericPath;
|
|
@@ -263,39 +279,42 @@ class MiddlewareHandlersRegistryEntryInternal {
|
|
|
263
279
|
this._handler = handler;
|
|
264
280
|
}
|
|
265
281
|
_injection = (_dicontainer) => ({});
|
|
266
|
-
async trigger(
|
|
282
|
+
async trigger(diScope, requestObject, next) {
|
|
267
283
|
let badRequestResponse = null;
|
|
268
|
-
const headers = middlewareParseRequestDefinitionField(this._inputSchemas, "headers",
|
|
284
|
+
const headers = middlewareParseRequestDefinitionField(this._inputSchemas, "headers", requestObject);
|
|
269
285
|
if (isHttpResponseObject(headers)) {
|
|
270
286
|
badRequestResponse = headers;
|
|
271
287
|
return badRequestResponse;
|
|
272
288
|
}
|
|
273
|
-
const query = middlewareParseRequestDefinitionField(this._inputSchemas, "query",
|
|
289
|
+
const query = middlewareParseRequestDefinitionField(this._inputSchemas, "query", requestObject);
|
|
274
290
|
if (isHttpResponseObject(query)) {
|
|
275
291
|
badRequestResponse = query;
|
|
276
292
|
return badRequestResponse;
|
|
277
293
|
}
|
|
278
|
-
const body = middlewareParseRequestDefinitionField(this._inputSchemas, "body",
|
|
294
|
+
const body = middlewareParseRequestDefinitionField(this._inputSchemas, "body", requestObject);
|
|
279
295
|
if (isHttpResponseObject(body)) {
|
|
280
296
|
badRequestResponse = body;
|
|
281
297
|
return badRequestResponse;
|
|
282
298
|
}
|
|
283
299
|
const { method, pathSegments } = this._splitMiddlewarePath();
|
|
284
300
|
const path = `/${pathSegments.map(
|
|
285
|
-
(segment) => segment.startsWith(":") ?
|
|
301
|
+
(segment) => segment.startsWith(":") ? requestObject.pathParams[segment.slice(1)] ?? "?" : segment
|
|
286
302
|
).join("/")}`;
|
|
287
|
-
return await this._handler(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
303
|
+
return await this._handler(
|
|
304
|
+
{
|
|
305
|
+
method,
|
|
306
|
+
// TODO: might be empty, as middleware can be registered with path only, without method, possible fix: take it from express.request.method
|
|
307
|
+
path,
|
|
308
|
+
genericPath: this.genericPath,
|
|
309
|
+
pathSegments,
|
|
310
|
+
headers,
|
|
311
|
+
pathParams: requestObject.pathParams,
|
|
312
|
+
query,
|
|
313
|
+
body,
|
|
314
|
+
injected: this._injection(diScope)
|
|
315
|
+
},
|
|
316
|
+
next
|
|
317
|
+
);
|
|
299
318
|
}
|
|
300
319
|
}
|
|
301
320
|
class MiddlewareHandlersRegistryEntry {
|
|
@@ -305,7 +324,7 @@ class MiddlewareHandlersRegistryEntry {
|
|
|
305
324
|
this._registry = registry;
|
|
306
325
|
this._path = path;
|
|
307
326
|
}
|
|
308
|
-
_injection = (
|
|
327
|
+
_injection = (_diScope) => ({});
|
|
309
328
|
get injection() {
|
|
310
329
|
return this._injection;
|
|
311
330
|
}
|
|
@@ -330,8 +349,13 @@ class MiddlewareHandlersRegistry {
|
|
|
330
349
|
this.dicontainer = dicontainer;
|
|
331
350
|
this._onHandlerRegisteredCallback = callback;
|
|
332
351
|
}
|
|
352
|
+
_list = [];
|
|
353
|
+
get list() {
|
|
354
|
+
return this._list;
|
|
355
|
+
}
|
|
333
356
|
register(entry) {
|
|
334
357
|
if (this._onHandlerRegisteredCallback) {
|
|
358
|
+
this._list.push(entry);
|
|
335
359
|
this._onHandlerRegisteredCallback(entry);
|
|
336
360
|
}
|
|
337
361
|
}
|
|
@@ -340,25 +364,38 @@ class MiddlewareHandlersRegistry {
|
|
|
340
364
|
this._onHandlerRegisteredCallback = callback;
|
|
341
365
|
}
|
|
342
366
|
}
|
|
343
|
-
function middlewareParseRequestDefinitionField(inputSchemas, key,
|
|
367
|
+
function middlewareParseRequestDefinitionField(inputSchemas, key, requestObject) {
|
|
344
368
|
if (inputSchemas[key]) {
|
|
345
|
-
if (!(key in
|
|
346
|
-
|
|
369
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
370
|
+
const result2 = inputSchemas[key].safeParse(requestObject[key]);
|
|
371
|
+
if (!result2.success) {
|
|
372
|
+
switch (inputSchemas[key].type) {
|
|
373
|
+
case "optional":
|
|
374
|
+
if (requestObject[key] === null) {
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
case "nullable":
|
|
379
|
+
if (requestObject[key] === void 0) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
347
384
|
return {
|
|
348
385
|
code: HttpStatusCode.BadRequest_400,
|
|
349
386
|
body: `'${key}' is required for this endpoint` + (key === "body" ? ", { 'Content-Type': 'application/json' } header might be missing" : "")
|
|
350
387
|
};
|
|
351
388
|
}
|
|
352
|
-
return
|
|
389
|
+
return result2.data;
|
|
353
390
|
}
|
|
354
|
-
const result = inputSchemas[key].safeParse(
|
|
391
|
+
const result = inputSchemas[key].safeParse(requestObject[key]);
|
|
355
392
|
if (!result.success) {
|
|
356
393
|
return {
|
|
357
394
|
code: HttpStatusCode.BadRequest_400,
|
|
358
395
|
body: result.error.issues
|
|
359
396
|
};
|
|
360
397
|
}
|
|
361
|
-
return result.data
|
|
398
|
+
return result.data;
|
|
362
399
|
}
|
|
363
400
|
return null;
|
|
364
401
|
}
|
|
@@ -526,12 +563,12 @@ class InnerApiClient {
|
|
|
526
563
|
delete currObj[key];
|
|
527
564
|
InnerApiClient._initialize(client, val, clientGenericHandler);
|
|
528
565
|
} else if (val instanceof HttpMethodEndpoint) {
|
|
529
|
-
currObj[key] = (
|
|
566
|
+
currObj[key] = (requestObject) => {
|
|
530
567
|
const pathParams = { ...client.__CONTEXT__.pathParameters };
|
|
531
568
|
client.__CONTEXT__ = InnerApiClient._initNewContext();
|
|
532
|
-
const headers = clientParseRequestDefinitionField(val.definition, "headers",
|
|
533
|
-
const query = clientParseRequestDefinitionField(val.definition, "query",
|
|
534
|
-
const body = clientParseRequestDefinitionField(val.definition, "body",
|
|
569
|
+
const headers = clientParseRequestDefinitionField(val.definition, "headers", requestObject);
|
|
570
|
+
const query = clientParseRequestDefinitionField(val.definition, "query", requestObject);
|
|
571
|
+
const body = clientParseRequestDefinitionField(val.definition, "body", requestObject);
|
|
535
572
|
const path = `/${val.pathSegments.map(
|
|
536
573
|
(segment) => segment.startsWith(":") ? pathParams[segment.slice(1)] ?? "?" : segment
|
|
537
574
|
).join("/")}`;
|
|
@@ -553,107 +590,153 @@ class InnerApiClient {
|
|
|
553
590
|
}
|
|
554
591
|
}
|
|
555
592
|
const ApiClient = InnerApiClient;
|
|
556
|
-
function clientParseRequestDefinitionField(definition, key,
|
|
593
|
+
function clientParseRequestDefinitionField(definition, key, requestObject) {
|
|
557
594
|
if (definition[key]) {
|
|
558
|
-
if (!(key in
|
|
559
|
-
|
|
560
|
-
|
|
595
|
+
if (!(key in requestObject) || requestObject[key] === null || requestObject[key] === void 0) {
|
|
596
|
+
const result2 = definition[key].safeParse(requestObject[key]);
|
|
597
|
+
if (!result2.success) {
|
|
598
|
+
switch (definition[key].type) {
|
|
599
|
+
case "optional":
|
|
600
|
+
if (requestObject[key] === null) {
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
case "nullable":
|
|
605
|
+
if (requestObject[key] === void 0) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
throw new Error(`'${key}' is required for this endpoint`);
|
|
561
611
|
}
|
|
562
|
-
return
|
|
612
|
+
return result2.data;
|
|
563
613
|
}
|
|
564
|
-
const result = definition[key].safeParse(
|
|
614
|
+
const result = definition[key].safeParse(requestObject[key]);
|
|
565
615
|
if (!result.success) {
|
|
566
616
|
throw new Error(`Validation for '${key}' failed`, { cause: result.error });
|
|
567
617
|
}
|
|
568
|
-
return result.data
|
|
618
|
+
return result.data;
|
|
569
619
|
}
|
|
570
620
|
return null;
|
|
571
621
|
}
|
|
572
622
|
|
|
573
|
-
class
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
623
|
+
class DIContainerBase {
|
|
624
|
+
_map = /* @__PURE__ */ new Map();
|
|
625
|
+
_singletonInstances = /* @__PURE__ */ new Map();
|
|
626
|
+
createScope() {
|
|
627
|
+
const proxy = new Proxy({
|
|
628
|
+
_map: this._map,
|
|
629
|
+
_singletonInstances: this._singletonInstances,
|
|
630
|
+
_scopedInstances: /* @__PURE__ */ new Map(),
|
|
631
|
+
_isBuildingSingleton: false
|
|
632
|
+
}, {
|
|
633
|
+
get: (target, prop) => {
|
|
634
|
+
if (prop.startsWith("get")) {
|
|
635
|
+
const serviceName = prop.slice(3);
|
|
636
|
+
if (target._map.has(serviceName)) {
|
|
637
|
+
const entry = target._map.get(serviceName);
|
|
638
|
+
switch (entry.lifetime) {
|
|
639
|
+
case "transient":
|
|
640
|
+
return () => {
|
|
641
|
+
if (target._isBuildingSingleton) {
|
|
642
|
+
throw new Error(`Cannot resolve transient service '${serviceName}' while building a singleton`);
|
|
643
|
+
}
|
|
644
|
+
return entry.factory(proxy);
|
|
645
|
+
};
|
|
646
|
+
case "scoped":
|
|
647
|
+
return () => {
|
|
648
|
+
if (target._isBuildingSingleton) {
|
|
649
|
+
throw new Error(`Cannot resolve scoped service '${serviceName}' while building a singleton`);
|
|
650
|
+
}
|
|
651
|
+
if (!target._scopedInstances.has(serviceName)) {
|
|
652
|
+
const instance = entry.factory(proxy);
|
|
653
|
+
target._scopedInstances.set(serviceName, instance);
|
|
654
|
+
}
|
|
655
|
+
return target._scopedInstances.get(serviceName);
|
|
656
|
+
};
|
|
657
|
+
case "singleton":
|
|
658
|
+
return () => {
|
|
659
|
+
if (!target._singletonInstances.has(serviceName)) {
|
|
660
|
+
target._isBuildingSingleton = true;
|
|
661
|
+
try {
|
|
662
|
+
const instance = entry.factory(proxy);
|
|
663
|
+
target._singletonInstances.set(serviceName, instance);
|
|
664
|
+
} finally {
|
|
665
|
+
target._isBuildingSingleton = false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return target._singletonInstances.get(serviceName);
|
|
669
|
+
};
|
|
670
|
+
default:
|
|
671
|
+
throw new Error(`Unsupported lifetime: ${entry.lifetime}`);
|
|
672
|
+
}
|
|
589
673
|
} else {
|
|
590
674
|
throw new Error(`Service not registered: ${serviceName}`);
|
|
591
675
|
}
|
|
592
676
|
}
|
|
593
|
-
|
|
677
|
+
throw new Error(`Property access denied by Proxy: ${String(prop)}`);
|
|
594
678
|
}
|
|
595
679
|
});
|
|
596
|
-
return
|
|
680
|
+
return proxy;
|
|
597
681
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
this.
|
|
682
|
+
}
|
|
683
|
+
class DIContainer extends DIContainerBase {
|
|
684
|
+
register(serviceNameCapitalizedLiteral, factory, lifetime) {
|
|
685
|
+
const entry = { factory, lifetime };
|
|
686
|
+
this._map.set(serviceNameCapitalizedLiteral, entry);
|
|
603
687
|
return this;
|
|
604
688
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
*/
|
|
608
|
-
resolveByName(serviceName) {
|
|
609
|
-
const registration = this.registry.get(serviceName);
|
|
610
|
-
if (!registration) {
|
|
611
|
-
throw new Error(`Service not registered: ${serviceName}`);
|
|
612
|
-
}
|
|
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);
|
|
689
|
+
createTestClone() {
|
|
690
|
+
return new DIContainerTestClone(this);
|
|
621
691
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return Reflect.get(target, prop);
|
|
648
|
-
}
|
|
692
|
+
}
|
|
693
|
+
class DIContainerTestClone extends DIContainerBase {
|
|
694
|
+
constructor(original) {
|
|
695
|
+
super();
|
|
696
|
+
original["_map"].forEach((value, key) => {
|
|
697
|
+
this._map.set(key, {
|
|
698
|
+
factory: (_scope) => {
|
|
699
|
+
throw new Error(`Service registration not overridden: ${key}`);
|
|
700
|
+
},
|
|
701
|
+
lifetime: value.lifetime
|
|
702
|
+
});
|
|
649
703
|
});
|
|
650
704
|
}
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
|
|
705
|
+
override(serviceNameLiteral, factory) {
|
|
706
|
+
const registration = this._map.get(serviceNameLiteral);
|
|
707
|
+
if (!registration) {
|
|
708
|
+
throw new Error(`Service not registered: ${serviceNameLiteral}`);
|
|
709
|
+
}
|
|
710
|
+
this._map.set(serviceNameLiteral, { factory, lifetime: registration.lifetime });
|
|
711
|
+
return this;
|
|
654
712
|
}
|
|
655
713
|
}
|
|
656
714
|
|
|
715
|
+
function execMiddleware(diScope, list, input) {
|
|
716
|
+
const queue = [...list];
|
|
717
|
+
const next = async () => {
|
|
718
|
+
const current = queue.shift();
|
|
719
|
+
if (!current) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const result = await current.trigger(
|
|
723
|
+
diScope,
|
|
724
|
+
{
|
|
725
|
+
headers: input.headers,
|
|
726
|
+
pathParams: input.pathParams,
|
|
727
|
+
body: input.body,
|
|
728
|
+
query: input.query
|
|
729
|
+
},
|
|
730
|
+
next
|
|
731
|
+
);
|
|
732
|
+
if (result) {
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
return next();
|
|
736
|
+
};
|
|
737
|
+
return next();
|
|
738
|
+
}
|
|
739
|
+
|
|
657
740
|
function createInProcApiClient(contract, testContainer, registry) {
|
|
658
741
|
const testApiReg = createRegistry(testContainer, contract, {
|
|
659
742
|
handlerRegisteredCallback: (_entry) => {
|
|
@@ -661,6 +744,10 @@ function createInProcApiClient(contract, testContainer, registry) {
|
|
|
661
744
|
middlewareHandlerRegisteredCallback: (_entry) => {
|
|
662
745
|
}
|
|
663
746
|
});
|
|
747
|
+
registry._middlewareRegistry.list.forEach((mwEntry) => {
|
|
748
|
+
testApiReg._middlewareRegistry.register(mwEntry);
|
|
749
|
+
});
|
|
750
|
+
const mwReg = testApiReg._middlewareRegistry;
|
|
664
751
|
flatListAllRegistryEntries(registry).forEach((entry) => {
|
|
665
752
|
if (!entry.handler) {
|
|
666
753
|
return;
|
|
@@ -669,16 +756,21 @@ function createInProcApiClient(contract, testContainer, registry) {
|
|
|
669
756
|
rt.inject(entry.injection).register(entry.handler);
|
|
670
757
|
});
|
|
671
758
|
const client = createClient(contract, async (input) => {
|
|
759
|
+
const diScope = testContainer.createScope();
|
|
760
|
+
const haltExecResponse = await execMiddleware(diScope, mwReg.list, input);
|
|
761
|
+
if (haltExecResponse) {
|
|
762
|
+
return haltExecResponse;
|
|
763
|
+
}
|
|
672
764
|
const rt = route(testApiReg, `${input.method} ${input.genericPath}`);
|
|
673
|
-
const result = rt.trigger({
|
|
765
|
+
const result = await rt.trigger(diScope, {
|
|
674
766
|
headers: input.headers,
|
|
675
767
|
pathParams: input.pathParams,
|
|
676
|
-
body: input.body
|
|
677
|
-
query: input.query
|
|
768
|
+
body: input.body,
|
|
769
|
+
query: input.query
|
|
678
770
|
});
|
|
679
771
|
return result;
|
|
680
772
|
});
|
|
681
773
|
return client;
|
|
682
774
|
}
|
|
683
775
|
|
|
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 };
|
|
776
|
+
export { ApiClient, ApiContract, ApiHandlersRegistry, DIContainer, DIContainerBase, DIContainerTestClone, HttpMethodEndpoint, HttpMethodEndpointResponse, HttpStatusCode, MethodEndpointHandlerRegistryEntry, MiddlewareHandlersRegistry, MiddlewareHandlersRegistryEntry, MiddlewareHandlersRegistryEntryInternal, apiContract, createClient, createInProcApiClient, createRegistry, endpoint, flatListAllRegistryEntries, isHttpResponseObject, isHttpStatusCode, middleware, partial, partialPathString, register, response, route };
|
package/package.json
CHANGED