@fragno-dev/core 0.1.11 → 0.2.2
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/.turbo/turbo-build.log +87 -69
- package/CHANGELOG.md +79 -0
- package/dist/api/api.d.ts +21 -2
- package/dist/api/api.d.ts.map +1 -1
- package/dist/api/api.js +2 -1
- package/dist/api/api.js.map +1 -1
- package/dist/api/bind-services.d.ts +0 -1
- package/dist/api/bind-services.d.ts.map +1 -1
- package/dist/api/bind-services.js.map +1 -1
- package/dist/api/error.d.ts.map +1 -1
- package/dist/api/error.js.map +1 -1
- package/dist/api/fragment-definition-builder.d.ts +32 -40
- package/dist/api/fragment-definition-builder.d.ts.map +1 -1
- package/dist/api/fragment-definition-builder.js +15 -21
- package/dist/api/fragment-definition-builder.js.map +1 -1
- package/dist/api/fragment-instantiator.d.ts +51 -30
- package/dist/api/fragment-instantiator.d.ts.map +1 -1
- package/dist/api/fragment-instantiator.js +201 -52
- package/dist/api/fragment-instantiator.js.map +1 -1
- package/dist/api/request-context-storage.d.ts +4 -0
- package/dist/api/request-context-storage.d.ts.map +1 -1
- package/dist/api/request-context-storage.js +6 -0
- package/dist/api/request-context-storage.js.map +1 -1
- package/dist/api/request-input-context.d.ts +57 -1
- package/dist/api/request-input-context.d.ts.map +1 -1
- package/dist/api/request-input-context.js +67 -0
- package/dist/api/request-input-context.js.map +1 -1
- package/dist/api/request-middleware.d.ts +2 -2
- package/dist/api/request-middleware.d.ts.map +1 -1
- package/dist/api/request-middleware.js.map +1 -1
- package/dist/api/request-output-context.d.ts +1 -1
- package/dist/api/request-output-context.d.ts.map +1 -1
- package/dist/api/request-output-context.js.map +1 -1
- package/dist/api/route-caller.d.ts +30 -0
- package/dist/api/route-caller.d.ts.map +1 -0
- package/dist/api/route-caller.js +63 -0
- package/dist/api/route-caller.js.map +1 -0
- package/dist/api/route-handler-input-options.d.ts.map +1 -1
- package/dist/api/route.d.ts +8 -8
- package/dist/api/route.d.ts.map +1 -1
- package/dist/api/route.js.map +1 -1
- package/dist/api/shared-types.d.ts.map +1 -1
- package/dist/client/client-error.d.ts.map +1 -1
- package/dist/client/client-error.js.map +1 -1
- package/dist/client/client.d.ts +90 -50
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +128 -16
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.svelte.d.ts +6 -5
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +10 -2
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/internal/ndjson-streaming.js.map +1 -1
- package/dist/client/react.d.ts +5 -4
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +104 -12
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +7 -5
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +23 -9
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +16 -4
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +21 -1
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +10 -4
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +24 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/id.d.ts +2 -0
- package/dist/id.js +3 -0
- package/dist/internal/cuid.d.ts +16 -0
- package/dist/internal/cuid.d.ts.map +1 -0
- package/dist/internal/cuid.js +82 -0
- package/dist/internal/cuid.js.map +1 -0
- package/dist/internal/trace-context.d.ts +23 -0
- package/dist/internal/trace-context.d.ts.map +1 -0
- package/dist/internal/trace-context.js +14 -0
- package/dist/internal/trace-context.js.map +1 -0
- package/dist/mod-client.d.ts +7 -20
- package/dist/mod-client.d.ts.map +1 -1
- package/dist/mod-client.js +25 -13
- package/dist/mod-client.js.map +1 -1
- package/dist/mod.d.ts +8 -6
- package/dist/mod.js +3 -1
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +33 -0
- package/dist/runtime.js.map +1 -0
- package/dist/test/test.d.ts +6 -6
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js.map +1 -1
- package/dist/util/ssr.js.map +1 -1
- package/package.json +42 -52
- package/src/api/api.test.ts +3 -1
- package/src/api/api.ts +28 -0
- package/src/api/bind-services.ts +0 -5
- package/src/api/error.ts +1 -0
- package/src/api/fragment-definition-builder.extend.test.ts +2 -1
- package/src/api/fragment-definition-builder.test.ts +2 -1
- package/src/api/fragment-definition-builder.ts +56 -112
- package/src/api/fragment-instantiator.test.ts +311 -166
- package/src/api/fragment-instantiator.ts +470 -131
- package/src/api/fragment-services.test.ts +1 -0
- package/src/api/internal/path-runtime.test.ts +8 -0
- package/src/api/internal/path-type.test.ts +3 -1
- package/src/api/internal/route.test.ts +1 -0
- package/src/api/request-context-storage.ts +7 -0
- package/src/api/request-input-context.test.ts +156 -2
- package/src/api/request-input-context.ts +87 -1
- package/src/api/request-middleware.test.ts +43 -2
- package/src/api/request-middleware.ts +4 -3
- package/src/api/request-output-context.test.ts +3 -1
- package/src/api/request-output-context.ts +2 -1
- package/src/api/route-caller.test.ts +195 -0
- package/src/api/route-caller.ts +167 -0
- package/src/api/route-handler-input-options.ts +2 -1
- package/src/api/route.test.ts +4 -2
- package/src/api/route.ts +9 -3
- package/src/api/shared-types.ts +2 -1
- package/src/client/client-builder.test.ts +4 -2
- package/src/client/client-error.test.ts +2 -1
- package/src/client/client-error.ts +1 -1
- package/src/client/client-types.test.ts +19 -5
- package/src/client/client.ssr.test.ts +6 -4
- package/src/client/client.svelte.test.ts +18 -9
- package/src/client/client.svelte.ts +38 -13
- package/src/client/client.test.ts +244 -10
- package/src/client/client.ts +473 -148
- package/src/client/internal/ndjson-streaming.test.ts +6 -3
- package/src/client/internal/ndjson-streaming.ts +1 -0
- package/src/client/react.test.ts +176 -6
- package/src/client/react.ts +226 -31
- package/src/client/solid.test.ts +29 -5
- package/src/client/solid.ts +60 -22
- package/src/client/vanilla.test.ts +148 -6
- package/src/client/vanilla.ts +63 -9
- package/src/client/vue.test.ts +397 -8
- package/src/client/vue.ts +74 -4
- package/src/id.ts +1 -0
- package/src/internal/cuid.test.ts +164 -0
- package/src/internal/cuid.ts +133 -0
- package/src/internal/trace-context.ts +35 -0
- package/src/mod-client.ts +55 -9
- package/src/mod.ts +9 -3
- package/src/runtime.ts +48 -0
- package/src/test/test.test.ts +4 -2
- package/src/test/test.ts +14 -7
- package/src/util/async.test.ts +1 -0
- package/src/util/content-type.test.ts +1 -0
- package/src/util/nanostores.test.ts +3 -1
- package/src/util/ssr.ts +1 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +2 -0
- package/vitest.config.ts +2 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, expectTypeOf } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import type { RequestThisContext } from "./api";
|
|
2
6
|
import { defineFragment } from "./fragment-definition-builder";
|
|
3
7
|
import {
|
|
4
8
|
instantiate,
|
|
@@ -7,8 +11,6 @@ import {
|
|
|
7
11
|
} from "./fragment-instantiator";
|
|
8
12
|
import { defineRoute, defineRoutes, type AnyFragmentDefinition } from "./route";
|
|
9
13
|
import type { FragnoPublicConfig } from "./shared-types";
|
|
10
|
-
import type { RequestThisContext } from "./api";
|
|
11
|
-
import { z } from "zod";
|
|
12
14
|
|
|
13
15
|
describe("fragment-instantiator", () => {
|
|
14
16
|
describe("basic instantiation", () => {
|
|
@@ -416,6 +418,56 @@ describe("fragment-instantiator", () => {
|
|
|
416
418
|
expect(data).toEqual({ userId: "123" });
|
|
417
419
|
});
|
|
418
420
|
|
|
421
|
+
it("should URL decode path params", async () => {
|
|
422
|
+
const definition = defineFragment("test-fragment").build();
|
|
423
|
+
|
|
424
|
+
const route = defineRoute({
|
|
425
|
+
method: "GET",
|
|
426
|
+
path: "/users/:name",
|
|
427
|
+
handler: async (input, { json }) => {
|
|
428
|
+
return json({ userName: input.pathParams.name });
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const fragment = instantiate(definition)
|
|
433
|
+
.withRoutes([route])
|
|
434
|
+
.withOptions({ mountRoute: "/api" })
|
|
435
|
+
.build();
|
|
436
|
+
|
|
437
|
+
// URL with encoded space: "a%20b" should be decoded to "a b"
|
|
438
|
+
const request = new Request("http://localhost/api/users/a%20b");
|
|
439
|
+
const response = await fragment.handler(request);
|
|
440
|
+
|
|
441
|
+
expect(response.status).toBe(200);
|
|
442
|
+
const data = await response.json();
|
|
443
|
+
expect(data).toEqual({ userName: "a b" });
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("should URL decode path params with special characters", async () => {
|
|
447
|
+
const definition = defineFragment("test-fragment").build();
|
|
448
|
+
|
|
449
|
+
const route = defineRoute({
|
|
450
|
+
method: "GET",
|
|
451
|
+
path: "/files/:path",
|
|
452
|
+
handler: async (input, { json }) => {
|
|
453
|
+
return json({ filePath: input.pathParams.path });
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const fragment = instantiate(definition)
|
|
458
|
+
.withRoutes([route])
|
|
459
|
+
.withOptions({ mountRoute: "/api" })
|
|
460
|
+
.build();
|
|
461
|
+
|
|
462
|
+
// URL with encoded slash: "folder%2Fsubfolder" should be decoded to "folder/subfolder"
|
|
463
|
+
const request = new Request("http://localhost/api/files/folder%2Fsubfolder");
|
|
464
|
+
const response = await fragment.handler(request);
|
|
465
|
+
|
|
466
|
+
expect(response.status).toBe(200);
|
|
467
|
+
const data = await response.json();
|
|
468
|
+
expect(data).toEqual({ filePath: "folder/subfolder" });
|
|
469
|
+
});
|
|
470
|
+
|
|
419
471
|
it("should handle POST requests with body", async () => {
|
|
420
472
|
const definition = defineFragment("test-fragment").build();
|
|
421
473
|
|
|
@@ -450,6 +502,170 @@ describe("fragment-instantiator", () => {
|
|
|
450
502
|
const data = await response.json();
|
|
451
503
|
expect(data).toEqual({ received: { test: "data" } });
|
|
452
504
|
});
|
|
505
|
+
|
|
506
|
+
it("should accept FormData for routes with contentType: multipart/form-data", async () => {
|
|
507
|
+
const definition = defineFragment("test-fragment").build();
|
|
508
|
+
|
|
509
|
+
const route = defineRoute({
|
|
510
|
+
method: "POST",
|
|
511
|
+
path: "/upload",
|
|
512
|
+
contentType: "multipart/form-data",
|
|
513
|
+
handler: async (ctx, { json }) => {
|
|
514
|
+
const formData = ctx.formData();
|
|
515
|
+
const description = formData.get("description") as string;
|
|
516
|
+
return json({ description });
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const fragment = instantiate(definition)
|
|
521
|
+
.withRoutes([route])
|
|
522
|
+
.withOptions({ mountRoute: "/api" })
|
|
523
|
+
.build();
|
|
524
|
+
|
|
525
|
+
const formData = new FormData();
|
|
526
|
+
formData.append("description", "Test file upload");
|
|
527
|
+
|
|
528
|
+
const request = new Request("http://localhost/api/upload", {
|
|
529
|
+
method: "POST",
|
|
530
|
+
body: formData,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const response = await fragment.handler(request);
|
|
534
|
+
|
|
535
|
+
expect(response.status).toBe(200);
|
|
536
|
+
const data = await response.json();
|
|
537
|
+
expect(data).toEqual({ description: "Test file upload" });
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("should reject FormData for JSON routes (default contentType)", async () => {
|
|
541
|
+
const definition = defineFragment("test-fragment").build();
|
|
542
|
+
|
|
543
|
+
const route = defineRoute({
|
|
544
|
+
method: "POST",
|
|
545
|
+
path: "/json-only",
|
|
546
|
+
inputSchema: z.object({ name: z.string() }),
|
|
547
|
+
handler: async ({ input }, { json }) => {
|
|
548
|
+
const body = await input.valid();
|
|
549
|
+
return json(body);
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
const fragment = instantiate(definition)
|
|
554
|
+
.withRoutes([route])
|
|
555
|
+
.withOptions({ mountRoute: "/api" })
|
|
556
|
+
.build();
|
|
557
|
+
|
|
558
|
+
const formData = new FormData();
|
|
559
|
+
formData.append("name", "test");
|
|
560
|
+
|
|
561
|
+
const request = new Request("http://localhost/api/json-only", {
|
|
562
|
+
method: "POST",
|
|
563
|
+
body: formData,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const response = await fragment.handler(request);
|
|
567
|
+
|
|
568
|
+
expect(response.status).toBe(415);
|
|
569
|
+
const data = await response.json();
|
|
570
|
+
expect(data.code).toBe("UNSUPPORTED_MEDIA_TYPE");
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("should reject JSON for FormData routes", async () => {
|
|
574
|
+
const definition = defineFragment("test-fragment").build();
|
|
575
|
+
|
|
576
|
+
const route = defineRoute({
|
|
577
|
+
method: "POST",
|
|
578
|
+
path: "/upload",
|
|
579
|
+
contentType: "multipart/form-data",
|
|
580
|
+
handler: async (ctx, { json }) => {
|
|
581
|
+
// Verify formData() works (would throw if not FormData)
|
|
582
|
+
ctx.formData();
|
|
583
|
+
return json({ success: true });
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const fragment = instantiate(definition)
|
|
588
|
+
.withRoutes([route])
|
|
589
|
+
.withOptions({ mountRoute: "/api" })
|
|
590
|
+
.build();
|
|
591
|
+
|
|
592
|
+
const request = new Request("http://localhost/api/upload", {
|
|
593
|
+
method: "POST",
|
|
594
|
+
headers: { "Content-Type": "application/json" },
|
|
595
|
+
body: JSON.stringify({ name: "test" }),
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const response = await fragment.handler(request);
|
|
599
|
+
|
|
600
|
+
expect(response.status).toBe(415);
|
|
601
|
+
const data = await response.json();
|
|
602
|
+
expect(data.code).toBe("UNSUPPORTED_MEDIA_TYPE");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("should accept octet-stream for routes with contentType: application/octet-stream", async () => {
|
|
606
|
+
const definition = defineFragment("test-fragment").build();
|
|
607
|
+
|
|
608
|
+
const route = defineRoute({
|
|
609
|
+
method: "PUT",
|
|
610
|
+
path: "/stream",
|
|
611
|
+
contentType: "application/octet-stream",
|
|
612
|
+
handler: async (ctx, { json }) => {
|
|
613
|
+
const stream = ctx.bodyStream();
|
|
614
|
+
const text = await new Response(stream).text();
|
|
615
|
+
return json({ received: text });
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const fragment = instantiate(definition)
|
|
620
|
+
.withRoutes([route])
|
|
621
|
+
.withOptions({ mountRoute: "/api" })
|
|
622
|
+
.build();
|
|
623
|
+
|
|
624
|
+
const request = new Request("http://localhost/api/stream", {
|
|
625
|
+
method: "PUT",
|
|
626
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
627
|
+
body: new TextEncoder().encode("hello"),
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const response = await fragment.handler(request);
|
|
631
|
+
|
|
632
|
+
expect(response.status).toBe(200);
|
|
633
|
+
const data = await response.json();
|
|
634
|
+
expect(data).toEqual({ received: "hello" });
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it("should reject JSON for octet-stream routes", async () => {
|
|
638
|
+
const definition = defineFragment("test-fragment").build();
|
|
639
|
+
|
|
640
|
+
const route = defineRoute({
|
|
641
|
+
method: "PUT",
|
|
642
|
+
path: "/stream",
|
|
643
|
+
contentType: "application/octet-stream",
|
|
644
|
+
handler: async (ctx, { json }) => {
|
|
645
|
+
if (!ctx.isBodyStream()) {
|
|
646
|
+
throw new Error("Expected stream body");
|
|
647
|
+
}
|
|
648
|
+
return json({ ok: true });
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const fragment = instantiate(definition)
|
|
653
|
+
.withRoutes([route])
|
|
654
|
+
.withOptions({ mountRoute: "/api" })
|
|
655
|
+
.build();
|
|
656
|
+
|
|
657
|
+
const request = new Request("http://localhost/api/stream", {
|
|
658
|
+
method: "PUT",
|
|
659
|
+
headers: { "Content-Type": "application/json" },
|
|
660
|
+
body: JSON.stringify({ hello: "world" }),
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
const response = await fragment.handler(request);
|
|
664
|
+
|
|
665
|
+
expect(response.status).toBe(415);
|
|
666
|
+
const data = await response.json();
|
|
667
|
+
expect(data.code).toBe("UNSUPPORTED_MEDIA_TYPE");
|
|
668
|
+
});
|
|
453
669
|
});
|
|
454
670
|
|
|
455
671
|
describe("callRoute", () => {
|
|
@@ -803,6 +1019,48 @@ describe("fragment-instantiator", () => {
|
|
|
803
1019
|
|
|
804
1020
|
expect(contextCreationSpy).toHaveBeenCalledTimes(2);
|
|
805
1021
|
});
|
|
1022
|
+
|
|
1023
|
+
it("should store lifecycle waitUntil in request storage", async () => {
|
|
1024
|
+
const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
|
|
1025
|
+
const waitUntil = vi.fn();
|
|
1026
|
+
|
|
1027
|
+
const definition = defineFragment("test-fragment")
|
|
1028
|
+
.withRequestStorage(() => ({}))
|
|
1029
|
+
.withThisContext(({ storage }) => {
|
|
1030
|
+
const ctx = {
|
|
1031
|
+
get waitUntil() {
|
|
1032
|
+
return (storage.getStore() as Record<symbol, unknown>)[requestWaitUntilSymbol];
|
|
1033
|
+
},
|
|
1034
|
+
};
|
|
1035
|
+
return { serviceContext: ctx, handlerContext: ctx };
|
|
1036
|
+
})
|
|
1037
|
+
.build();
|
|
1038
|
+
|
|
1039
|
+
const routes = defineRoutes(definition).create(({ defineRoute }) => [
|
|
1040
|
+
defineRoute({
|
|
1041
|
+
method: "GET",
|
|
1042
|
+
path: "/test",
|
|
1043
|
+
handler: async function (_input, { json }) {
|
|
1044
|
+
return json({
|
|
1045
|
+
hasWaitUntil: typeof this.waitUntil === "function",
|
|
1046
|
+
sameWaitUntil: this.waitUntil === waitUntil,
|
|
1047
|
+
});
|
|
1048
|
+
},
|
|
1049
|
+
}),
|
|
1050
|
+
]);
|
|
1051
|
+
|
|
1052
|
+
const fragment = instantiate(definition)
|
|
1053
|
+
.withRoutes([routes])
|
|
1054
|
+
.withOptions({ mountRoute: "/api" })
|
|
1055
|
+
.build();
|
|
1056
|
+
|
|
1057
|
+
const response = await fragment.handler(new Request("http://localhost/api/test"), {
|
|
1058
|
+
waitUntil,
|
|
1059
|
+
});
|
|
1060
|
+
const data = await response.json();
|
|
1061
|
+
|
|
1062
|
+
expect(data).toEqual({ hasWaitUntil: true, sameWaitUntil: true });
|
|
1063
|
+
});
|
|
806
1064
|
});
|
|
807
1065
|
|
|
808
1066
|
describe("defineService with custom this context", () => {
|
|
@@ -1251,188 +1509,75 @@ describe("fragment-instantiator", () => {
|
|
|
1251
1509
|
});
|
|
1252
1510
|
});
|
|
1253
1511
|
|
|
1254
|
-
describe("
|
|
1255
|
-
it("should
|
|
1256
|
-
interface Config {
|
|
1257
|
-
apiKey: string;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// Create a linked fragment definition
|
|
1261
|
-
const linkedFragmentDef = defineFragment<Config>("linked-fragment")
|
|
1262
|
-
.providesService("linkedService", () => ({
|
|
1263
|
-
getValue: () => "from-linked",
|
|
1264
|
-
}))
|
|
1265
|
-
.build();
|
|
1266
|
-
|
|
1267
|
-
// Create main fragment with linked fragment
|
|
1268
|
-
const definition = defineFragment<Config>("main-fragment")
|
|
1269
|
-
.withLinkedFragment("internal", ({ config, options }) => {
|
|
1270
|
-
return instantiate(linkedFragmentDef).withConfig(config).withOptions(options).build();
|
|
1271
|
-
})
|
|
1272
|
-
.build();
|
|
1273
|
-
|
|
1274
|
-
const fragment = instantiate(definition)
|
|
1275
|
-
.withConfig({ apiKey: "test-key" })
|
|
1276
|
-
.withOptions({ mountRoute: "/api" })
|
|
1277
|
-
.build();
|
|
1278
|
-
|
|
1279
|
-
// Verify linked fragment exists
|
|
1280
|
-
expect(Object.keys(fragment.$internal.linkedFragments).length).toBe(1);
|
|
1281
|
-
expect("internal" in fragment.$internal.linkedFragments).toBe(true);
|
|
1282
|
-
|
|
1283
|
-
const linkedFragment = fragment.$internal.linkedFragments.internal;
|
|
1284
|
-
expect(linkedFragment).toBeDefined();
|
|
1285
|
-
expect(linkedFragment?.name).toBe("linked-fragment");
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
it("should pass config and options to linked fragments", () => {
|
|
1289
|
-
interface Config {
|
|
1290
|
-
value: string;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
interface Options extends FragnoPublicConfig {
|
|
1294
|
-
customOption: string;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
const linkedFragmentDef = defineFragment<Config, Options>("linked-fragment")
|
|
1298
|
-
.withDependencies(({ config, options }) => ({
|
|
1299
|
-
combined: `${config.value}-${options.customOption}`,
|
|
1300
|
-
}))
|
|
1301
|
-
.build();
|
|
1302
|
-
|
|
1303
|
-
const definition = defineFragment<Config, Options>("main-fragment")
|
|
1304
|
-
.withLinkedFragment("internal", ({ config, options }) => {
|
|
1305
|
-
return instantiate(linkedFragmentDef).withConfig(config).withOptions(options).build();
|
|
1306
|
-
})
|
|
1307
|
-
.build();
|
|
1308
|
-
|
|
1309
|
-
const fragment = instantiate(definition)
|
|
1310
|
-
.withConfig({ value: "config" })
|
|
1311
|
-
.withOptions({ customOption: "option", mountRoute: "/api" } as Options)
|
|
1312
|
-
.build();
|
|
1313
|
-
|
|
1314
|
-
const linkedFragment = fragment.$internal.linkedFragments.internal;
|
|
1315
|
-
expect(linkedFragment?.$internal.deps).toEqual({
|
|
1316
|
-
combined: "config-option",
|
|
1317
|
-
});
|
|
1318
|
-
});
|
|
1319
|
-
|
|
1320
|
-
it("should allow linked fragments to provide services", () => {
|
|
1321
|
-
const linkedFragmentDef = defineFragment("linked-fragment")
|
|
1322
|
-
.providesService("settingsService", () => ({
|
|
1323
|
-
get: (key: string) => `value-for-${key}`,
|
|
1324
|
-
set: (key: string, value: string) => {
|
|
1325
|
-
console.log(`Setting ${key} = ${value}`);
|
|
1326
|
-
},
|
|
1327
|
-
}))
|
|
1328
|
-
.build();
|
|
1329
|
-
|
|
1512
|
+
describe("internal routes", () => {
|
|
1513
|
+
it("should mount internal routes under /_internal", async () => {
|
|
1330
1514
|
const definition = defineFragment("main-fragment")
|
|
1331
|
-
.
|
|
1332
|
-
|
|
1333
|
-
|
|
1515
|
+
.withInternalRoutes([
|
|
1516
|
+
defineRoute({
|
|
1517
|
+
method: "GET",
|
|
1518
|
+
path: "/status",
|
|
1519
|
+
handler: async (_input, { json }) => {
|
|
1520
|
+
return json({ ok: true });
|
|
1521
|
+
},
|
|
1522
|
+
}),
|
|
1523
|
+
])
|
|
1334
1524
|
.build();
|
|
1335
1525
|
|
|
1336
1526
|
const fragment = instantiate(definition).withOptions({}).build();
|
|
1337
1527
|
|
|
1338
|
-
const
|
|
1339
|
-
expect(
|
|
1340
|
-
expect(
|
|
1528
|
+
const response = await fragment.callRouteRaw("GET", "/_internal/status" as never);
|
|
1529
|
+
expect(response.status).toBe(200);
|
|
1530
|
+
await expect(response.json()).resolves.toEqual({ ok: true });
|
|
1341
1531
|
});
|
|
1342
1532
|
|
|
1343
|
-
it("should
|
|
1344
|
-
const linkedFragmentDef1 = defineFragment("linked-fragment-1")
|
|
1345
|
-
.providesService("service1", () => ({ method: () => "service1" }))
|
|
1346
|
-
.build();
|
|
1347
|
-
|
|
1348
|
-
const linkedFragmentDef2 = defineFragment("linked-fragment-2")
|
|
1349
|
-
.providesService("service2", () => ({ method: () => "service2" }))
|
|
1350
|
-
.build();
|
|
1351
|
-
|
|
1533
|
+
it("should mount internal routes under the fragment mount route", async () => {
|
|
1352
1534
|
const definition = defineFragment("main-fragment")
|
|
1353
|
-
.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1535
|
+
.withInternalRoutes([
|
|
1536
|
+
defineRoute({
|
|
1537
|
+
method: "GET",
|
|
1538
|
+
path: "/status",
|
|
1539
|
+
handler: async (_input, { json }) => {
|
|
1540
|
+
return json({ ok: true });
|
|
1541
|
+
},
|
|
1542
|
+
}),
|
|
1543
|
+
])
|
|
1359
1544
|
.build();
|
|
1360
1545
|
|
|
1361
|
-
const fragment = instantiate(definition).withOptions({}).build();
|
|
1362
|
-
|
|
1363
|
-
expect(Object.keys(fragment.$internal.linkedFragments).length).toBe(2);
|
|
1364
|
-
expect("internal1" in fragment.$internal.linkedFragments).toBe(true);
|
|
1365
|
-
expect("internal2" in fragment.$internal.linkedFragments).toBe(true);
|
|
1366
|
-
|
|
1367
|
-
const linked1 = fragment.$internal.linkedFragments.internal1;
|
|
1368
|
-
const linked2 = fragment.$internal.linkedFragments.internal2;
|
|
1546
|
+
const fragment = instantiate(definition).withOptions({ mountRoute: "/api" }).build();
|
|
1369
1547
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1548
|
+
const request = new Request("http://localhost/api/_internal/status");
|
|
1549
|
+
const response = await fragment.handler(request);
|
|
1550
|
+
expect(response.status).toBe(200);
|
|
1551
|
+
await expect(response.json()).resolves.toEqual({ ok: true });
|
|
1372
1552
|
});
|
|
1373
1553
|
|
|
1374
|
-
it("should
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
};
|
|
1382
|
-
|
|
1383
|
-
const linkedFragmentDef = defineFragment("linked-fragment")
|
|
1384
|
-
.usesService<"externalService", ExternalService>("externalService")
|
|
1385
|
-
.providesService("linkedService", ({ serviceDeps }) => ({
|
|
1386
|
-
getFromExternal: () => serviceDeps.externalService.getValue(),
|
|
1387
|
-
}))
|
|
1388
|
-
.build();
|
|
1389
|
-
|
|
1390
|
-
const definition = defineFragment("main-fragment")
|
|
1391
|
-
.usesService<"externalService", ExternalService>("externalService")
|
|
1392
|
-
.withLinkedFragment("internal", ({ config, options, serviceDependencies }) => {
|
|
1393
|
-
return instantiate(linkedFragmentDef)
|
|
1394
|
-
.withConfig(config)
|
|
1395
|
-
.withOptions(options)
|
|
1396
|
-
.withServices(serviceDependencies!)
|
|
1397
|
-
.build();
|
|
1398
|
-
})
|
|
1399
|
-
.build();
|
|
1400
|
-
|
|
1401
|
-
const fragment = instantiate(definition)
|
|
1402
|
-
.withOptions({})
|
|
1403
|
-
.withServices({ externalService })
|
|
1404
|
-
.build();
|
|
1405
|
-
|
|
1406
|
-
const linkedFragment = fragment.$internal.linkedFragments.internal;
|
|
1407
|
-
expect(linkedFragment?.services.linkedService.getFromExternal()).toBe("external-value");
|
|
1408
|
-
});
|
|
1554
|
+
it("should resolve internal route factories with services", async () => {
|
|
1555
|
+
const definitionBuilder = defineFragment("main-fragment").providesService(
|
|
1556
|
+
"statusService",
|
|
1557
|
+
() => ({
|
|
1558
|
+
getStatus: () => ({ ok: true }),
|
|
1559
|
+
}),
|
|
1560
|
+
);
|
|
1409
1561
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1562
|
+
const internalRoutes = defineRoutes(definitionBuilder.build()).create(
|
|
1563
|
+
({ services, defineRoute }) => [
|
|
1564
|
+
defineRoute({
|
|
1565
|
+
method: "GET",
|
|
1566
|
+
path: "/status",
|
|
1567
|
+
handler: async (_input, { json }) => {
|
|
1568
|
+
return json(services.statusService.getStatus());
|
|
1569
|
+
},
|
|
1570
|
+
}),
|
|
1571
|
+
],
|
|
1572
|
+
);
|
|
1416
1573
|
|
|
1417
|
-
const definition =
|
|
1418
|
-
.withLinkedFragment("internal", ({ config, options }) => {
|
|
1419
|
-
return instantiate(linkedFragmentDef).withConfig(config).withOptions(options).build();
|
|
1420
|
-
})
|
|
1421
|
-
.providesService("mainService", ({ privateServices }) => ({
|
|
1422
|
-
getLinkedValue: () => {
|
|
1423
|
-
return privateServices.linkedService.getValue();
|
|
1424
|
-
},
|
|
1425
|
-
}))
|
|
1426
|
-
.build();
|
|
1574
|
+
const definition = definitionBuilder.withInternalRoutes([internalRoutes]).build();
|
|
1427
1575
|
|
|
1428
1576
|
const fragment = instantiate(definition).withOptions({}).build();
|
|
1429
1577
|
|
|
1430
|
-
|
|
1431
|
-
expect(
|
|
1432
|
-
|
|
1433
|
-
// Linked fragment services are NOT directly exposed on the main fragment
|
|
1434
|
-
// @ts-expect-error - Linked fragment service should not be accessible
|
|
1435
|
-
expect(fragment.services.linkedService).toBeUndefined();
|
|
1578
|
+
const response = await fragment.callRouteRaw("GET", "/_internal/status" as never);
|
|
1579
|
+
expect(response.status).toBe(200);
|
|
1580
|
+
await expect(response.json()).resolves.toEqual({ ok: true });
|
|
1436
1581
|
});
|
|
1437
1582
|
});
|
|
1438
1583
|
|