@getstrata/bootstrap 0.2.23 → 0.2.25
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/bootstrap/buildWebModuleRoutes.d.ts +9 -0
- package/dist/bootstrap/env.d.ts +1 -1
- package/dist/bootstrap/membershipService.d.ts +1 -1
- package/dist/bootstrap/public-api.d.ts +4 -2
- package/dist/bootstrap/routeRegistry.d.ts +1 -1
- package/dist/entries/buildModuleRoutes.js +3 -218
- package/dist/entries/buildWebModuleRoutes.js +435 -0
- package/dist/entries/context.js +1 -17
- package/dist/entries/createWebRoutes.js +100 -682
- package/dist/entries/dependencies.js +1 -17
- package/dist/entries/http/securedRouteModelBinding.js +0 -7
- package/dist/entries/membershipService.js +2 -392
- package/dist/entries/providers/view.js +0 -11
- package/dist/entries/providers.js +1 -17
- package/dist/entries/queue/defaultJobs.js +0 -3
- package/dist/entries/web/routing.js +0 -7
- package/dist/index.js +137 -371
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/bootstrap/public-api.ts
|
|
3
|
+
import { appSchedule as appSchedule2, runDueScheduledTasks as runDueScheduledTasks2, Schedule as Schedule2 } from "@getstrata/core/scheduler/schedule";
|
|
4
|
+
|
|
2
5
|
// ../../src/core/scheduler/schedule.ts
|
|
3
6
|
class Schedule {
|
|
4
7
|
tasks = [];
|
|
@@ -515,179 +518,9 @@ import {
|
|
|
515
518
|
resolveApplicationQueue,
|
|
516
519
|
setActiveApplicationContext
|
|
517
520
|
} from "@getstrata/core/runtime/applicationRegistry.ts";
|
|
518
|
-
// ../../src/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// ../../src/core/http/etag.ts
|
|
524
|
-
function isEtagEnabled() {
|
|
525
|
-
return (process.env.FEATURE_ETAG ?? "true") !== "false";
|
|
526
|
-
}
|
|
527
|
-
function formatWeakEtag(digest) {
|
|
528
|
-
return `W/"${digest}"`;
|
|
529
|
-
}
|
|
530
|
-
function computeEtagFromJson(data) {
|
|
531
|
-
const digest = nonCryptographicDigest(JSON.stringify(data)).slice(0, 32);
|
|
532
|
-
return formatWeakEtag(digest);
|
|
533
|
-
}
|
|
534
|
-
function etagFromResource(resource) {
|
|
535
|
-
const version = resource.updated_at ?? resource.created_at ?? "";
|
|
536
|
-
const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
|
|
537
|
-
const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
|
|
538
|
-
return formatWeakEtag(digest);
|
|
539
|
-
}
|
|
540
|
-
function normalizeEtag(value) {
|
|
541
|
-
return value.trim();
|
|
542
|
-
}
|
|
543
|
-
function etagValuesMatch(left, right) {
|
|
544
|
-
return normalizeEtag(left) === normalizeEtag(right);
|
|
545
|
-
}
|
|
546
|
-
function parseEtagList(header) {
|
|
547
|
-
if (!header) {
|
|
548
|
-
return [];
|
|
549
|
-
}
|
|
550
|
-
return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
|
|
551
|
-
}
|
|
552
|
-
function ifNoneMatchSatisfied(request, etag) {
|
|
553
|
-
const header = request.headers.get("if-none-match");
|
|
554
|
-
if (!header) {
|
|
555
|
-
return false;
|
|
556
|
-
}
|
|
557
|
-
if (header.trim() === "*") {
|
|
558
|
-
return true;
|
|
559
|
-
}
|
|
560
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
561
|
-
}
|
|
562
|
-
function ifMatchSatisfied(request, etag) {
|
|
563
|
-
const header = request.headers.get("if-match");
|
|
564
|
-
if (!header) {
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
if (header.trim() === "*") {
|
|
568
|
-
return true;
|
|
569
|
-
}
|
|
570
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
571
|
-
}
|
|
572
|
-
function assertIfMatch(request, etag, options = {}) {
|
|
573
|
-
const header = request.headers.get("if-match");
|
|
574
|
-
if (!header) {
|
|
575
|
-
if (options.required) {
|
|
576
|
-
throw new PreconditionFailedError("If-Match header is required.");
|
|
577
|
-
}
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (!ifMatchSatisfied(request, etag)) {
|
|
581
|
-
throw new PreconditionFailedError("Resource ETag does not match If-Match.");
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function applyEtagHeaders(headers, etag) {
|
|
585
|
-
const next = new Headers(headers);
|
|
586
|
-
next.set("ETag", etag);
|
|
587
|
-
next.set("Cache-Control", "private, must-revalidate");
|
|
588
|
-
next.append("Vary", "Authorization");
|
|
589
|
-
next.append("Vary", "X-Tenant-Id");
|
|
590
|
-
return next;
|
|
591
|
-
}
|
|
592
|
-
function notModifiedResponse(etag) {
|
|
593
|
-
return new Response(null, {
|
|
594
|
-
status: 304,
|
|
595
|
-
headers: applyEtagHeaders(new Headers, etag)
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
function applyConditionalGet(request, response, etag) {
|
|
599
|
-
if (!isEtagEnabled()) {
|
|
600
|
-
return response;
|
|
601
|
-
}
|
|
602
|
-
if (ifNoneMatchSatisfied(request, etag)) {
|
|
603
|
-
return notModifiedResponse(etag);
|
|
604
|
-
}
|
|
605
|
-
const headers = applyEtagHeaders(new Headers(response.headers), etag);
|
|
606
|
-
return new Response(response.body, {
|
|
607
|
-
status: response.status,
|
|
608
|
-
statusText: response.statusText,
|
|
609
|
-
headers
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// ../../src/core/http/conditionalResponse.ts
|
|
614
|
-
function jsonResponse(data, init = {}) {
|
|
615
|
-
return Response.json(data, {
|
|
616
|
-
status: init.status ?? 200,
|
|
617
|
-
headers: init.headers
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
function conditionalJsonResponse(request, data, init = {}) {
|
|
621
|
-
if (!request || !isEtagEnabled()) {
|
|
622
|
-
return jsonResponse(data, init);
|
|
623
|
-
}
|
|
624
|
-
const etag = computeEtagFromJson(data);
|
|
625
|
-
if (ifNoneMatchSatisfied(request, etag)) {
|
|
626
|
-
return notModifiedResponse(etag);
|
|
627
|
-
}
|
|
628
|
-
const response = jsonResponse(data, init);
|
|
629
|
-
const headers = new Headers(response.headers);
|
|
630
|
-
headers.set("ETag", etag);
|
|
631
|
-
headers.set("Cache-Control", "private, must-revalidate");
|
|
632
|
-
headers.append("Vary", "Authorization");
|
|
633
|
-
headers.append("Vary", "X-Tenant-Id");
|
|
634
|
-
return new Response(response.body, {
|
|
635
|
-
status: response.status,
|
|
636
|
-
statusText: response.statusText,
|
|
637
|
-
headers
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ../../src/core/http/middleware.ts
|
|
642
|
-
function isRouteHandler(value) {
|
|
643
|
-
return typeof value === "function";
|
|
644
|
-
}
|
|
645
|
-
function isMethodRouteMap(value) {
|
|
646
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
647
|
-
return false;
|
|
648
|
-
}
|
|
649
|
-
const entries = Object.entries(value);
|
|
650
|
-
return entries.length > 0 && entries.every(([, handler]) => isRouteHandler(handler));
|
|
651
|
-
}
|
|
652
|
-
function composeMiddleware(...middleware) {
|
|
653
|
-
return (handler) => {
|
|
654
|
-
return async (request) => {
|
|
655
|
-
let index = 0;
|
|
656
|
-
const dispatch = async () => {
|
|
657
|
-
if (index >= middleware.length) {
|
|
658
|
-
return await handler(request);
|
|
659
|
-
}
|
|
660
|
-
const current = middleware[index];
|
|
661
|
-
index += 1;
|
|
662
|
-
if (!current) {
|
|
663
|
-
return await handler(request);
|
|
664
|
-
}
|
|
665
|
-
return await current(request, dispatch);
|
|
666
|
-
};
|
|
667
|
-
return await dispatch();
|
|
668
|
-
};
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
function wrapRouteHandler(handler, middleware) {
|
|
672
|
-
if (isMethodRouteMap(handler)) {
|
|
673
|
-
const wrapped = {};
|
|
674
|
-
for (const [method, routeHandler] of Object.entries(handler)) {
|
|
675
|
-
wrapped[method] = composeMiddleware(...middleware)(routeHandler);
|
|
676
|
-
}
|
|
677
|
-
return wrapped;
|
|
678
|
-
}
|
|
679
|
-
if (isRouteHandler(handler)) {
|
|
680
|
-
return composeMiddleware(...middleware)(handler);
|
|
681
|
-
}
|
|
682
|
-
return handler;
|
|
683
|
-
}
|
|
684
|
-
function applyMiddlewareToRoutes(routes, middleware) {
|
|
685
|
-
const wrapped = {};
|
|
686
|
-
for (const [path, routeHandler] of Object.entries(routes)) {
|
|
687
|
-
wrapped[path] = wrapRouteHandler(routeHandler, middleware);
|
|
688
|
-
}
|
|
689
|
-
return wrapped;
|
|
690
|
-
}
|
|
521
|
+
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
522
|
+
import { conditionalJsonResponse } from "@getstrata/core/http";
|
|
523
|
+
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
691
524
|
|
|
692
525
|
// ../../src/bootstrap/httpKernel.ts
|
|
693
526
|
import {
|
|
@@ -1090,6 +923,28 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
1090
923
|
const prefixedModuleRoutes = prefixRouteMap(apiPrefix, moduleRoutes);
|
|
1091
924
|
return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
|
|
1092
925
|
}
|
|
926
|
+
// ../../src/bootstrap/buildWebModuleRoutes.ts
|
|
927
|
+
import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/core/http/middleware";
|
|
928
|
+
function buildWebModuleRoutes(dependencies, options = {}) {
|
|
929
|
+
const { modules = discoverModules(), clearRegistry = true, seedRoutes = {} } = options;
|
|
930
|
+
if (clearRegistry) {
|
|
931
|
+
routeRegistry.clear();
|
|
932
|
+
}
|
|
933
|
+
const kernel = createHttpKernel(dependencies);
|
|
934
|
+
const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
|
|
935
|
+
const moduleRoutes = { ...seedRoutes };
|
|
936
|
+
for (const module of modules) {
|
|
937
|
+
if (!module.webRoutes) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
Object.assign(moduleRoutes, module.webRoutes({
|
|
941
|
+
dependencies,
|
|
942
|
+
cachedJson: async () => new Response(""),
|
|
943
|
+
kernel
|
|
944
|
+
}));
|
|
945
|
+
}
|
|
946
|
+
return applyMiddlewareToRoutes2(registerOpenApiRouteMap(moduleRoutes, ["global", "web"]), middleware);
|
|
947
|
+
}
|
|
1093
948
|
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
1094
949
|
function cacheTagsForModelWrite(tableName, action) {
|
|
1095
950
|
const module = discoverModules().find((entry) => entry.tableName === tableName);
|
|
@@ -1217,7 +1072,6 @@ var db = new Proxy(function database() {}, {
|
|
|
1217
1072
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
1218
1073
|
}
|
|
1219
1074
|
});
|
|
1220
|
-
var connection_default = db;
|
|
1221
1075
|
|
|
1222
1076
|
// ../../src/modules/user/apiTokenTable.ts
|
|
1223
1077
|
import { defineTable } from "@getstrata/core/database";
|
|
@@ -2049,9 +1903,6 @@ var queueConfig = {
|
|
|
2049
1903
|
};
|
|
2050
1904
|
|
|
2051
1905
|
// ../../src/core/config/envSchema.ts
|
|
2052
|
-
function defineEnvSchema(schema) {
|
|
2053
|
-
return schema;
|
|
2054
|
-
}
|
|
2055
1906
|
function validateEnv(schema, env = process.env) {
|
|
2056
1907
|
const resolved = {};
|
|
2057
1908
|
for (const [name, rule] of Object.entries(schema)) {
|
|
@@ -2080,6 +1931,7 @@ function validateEnv(schema, env = process.env) {
|
|
|
2080
1931
|
}
|
|
2081
1932
|
|
|
2082
1933
|
// ../../src/bootstrap/env.ts
|
|
1934
|
+
import { defineEnvSchema } from "@getstrata/core/config/envSchema";
|
|
2083
1935
|
var appEnvSchema = defineEnvSchema({
|
|
2084
1936
|
DATABASE_URL: { required: true, pattern: /^postgres(ql)?:\/\// },
|
|
2085
1937
|
PORT: {
|
|
@@ -4371,9 +4223,6 @@ function resolveApplicationAuth2() {
|
|
|
4371
4223
|
function resolveApplicationPolicyGate2() {
|
|
4372
4224
|
return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN2);
|
|
4373
4225
|
}
|
|
4374
|
-
function resolveApplicationDependencies2() {
|
|
4375
|
-
return requireActiveApplicationContext().dependencies;
|
|
4376
|
-
}
|
|
4377
4226
|
|
|
4378
4227
|
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
4379
4228
|
function registerDefaultJobs() {
|
|
@@ -4560,16 +4409,6 @@ class EtaViewEngine {
|
|
|
4560
4409
|
});
|
|
4561
4410
|
}
|
|
4562
4411
|
}
|
|
4563
|
-
// ../../src/core/view/htmlResponse.ts
|
|
4564
|
-
function htmlResponse(html, init = {}) {
|
|
4565
|
-
return new Response(html, {
|
|
4566
|
-
status: init.status ?? 200,
|
|
4567
|
-
statusText: init.statusText,
|
|
4568
|
-
headers: {
|
|
4569
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
4570
|
-
}
|
|
4571
|
-
});
|
|
4572
|
-
}
|
|
4573
4412
|
// ../../src/core/http/cookies.ts
|
|
4574
4413
|
function readRequestCookie(request, name) {
|
|
4575
4414
|
const cookies = request.cookies;
|
|
@@ -4858,50 +4697,25 @@ function createAppContext() {
|
|
|
4858
4697
|
}
|
|
4859
4698
|
// ../../src/bootstrap/createWebRoutes.ts
|
|
4860
4699
|
import { join as join5 } from "path";
|
|
4700
|
+
import { htmlResponse as htmlResponse2 } from "@getstrata/core/view";
|
|
4861
4701
|
function registerRoute(method, path, middleware) {
|
|
4862
4702
|
routeRegistry.register({ method, path, middleware });
|
|
4863
4703
|
}
|
|
4864
|
-
function registerRouteMap(routes, middleware) {
|
|
4865
|
-
const registered = {};
|
|
4866
|
-
for (const [path, handler] of Object.entries(routes)) {
|
|
4867
|
-
if (handler && typeof handler === "object" && !Array.isArray(handler)) {
|
|
4868
|
-
const methodMap = handler;
|
|
4869
|
-
registered[path] = methodMap;
|
|
4870
|
-
for (const method of Object.keys(methodMap)) {
|
|
4871
|
-
registerRoute(method.toUpperCase(), path, middleware);
|
|
4872
|
-
}
|
|
4873
|
-
continue;
|
|
4874
|
-
}
|
|
4875
|
-
registered[path] = handler;
|
|
4876
|
-
registerRoute("GET", path, middleware);
|
|
4877
|
-
}
|
|
4878
|
-
return registered;
|
|
4879
|
-
}
|
|
4880
4704
|
function createWebRoutes(dependencies) {
|
|
4881
|
-
const
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
};
|
|
4886
|
-
registerRoute("GET", "/", ["global", "web"]);
|
|
4887
|
-
for (const module of discoverModules()) {
|
|
4888
|
-
if (!module.webRoutes) {
|
|
4889
|
-
continue;
|
|
4705
|
+
const wrappedRoutes = buildWebModuleRoutes(dependencies, {
|
|
4706
|
+
clearRegistry: false,
|
|
4707
|
+
seedRoutes: {
|
|
4708
|
+
"/": () => Response.redirect("/organizations", 302)
|
|
4890
4709
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
cachedJson: async () => htmlResponse(""),
|
|
4894
|
-
kernel
|
|
4895
|
-
}));
|
|
4896
|
-
}
|
|
4897
|
-
const wrappedRoutes = applyMiddlewareToRoutes(registerRouteMap(moduleRoutes, ["global", "web"]), middleware);
|
|
4710
|
+
});
|
|
4711
|
+
registerRoute("GET", "/", ["global", "web"]);
|
|
4898
4712
|
wrappedRoutes["/assets/*"] = async (request) => {
|
|
4899
4713
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
4900
4714
|
const pathname = new URL(request.url).pathname;
|
|
4901
4715
|
const relativePath = pathname.replace(/^\//, "");
|
|
4902
4716
|
const file = Bun.file(join5(process.cwd(), "public", relativePath));
|
|
4903
4717
|
if (!await file.exists()) {
|
|
4904
|
-
return
|
|
4718
|
+
return htmlResponse2("Not Found", { status: 404 });
|
|
4905
4719
|
}
|
|
4906
4720
|
return new Response(file);
|
|
4907
4721
|
};
|
|
@@ -4921,6 +4735,97 @@ function mergeWebRoutes(dependencies, routes) {
|
|
|
4921
4735
|
function createAppDependencies() {
|
|
4922
4736
|
return createAppContext().dependencies;
|
|
4923
4737
|
}
|
|
4738
|
+
// ../../src/core/crypto/nonCryptographicHash.ts
|
|
4739
|
+
function nonCryptographicDigest(input) {
|
|
4740
|
+
return Bun.hash(input).toString(16);
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
// ../../src/core/http/etag.ts
|
|
4744
|
+
function isEtagEnabled() {
|
|
4745
|
+
return (process.env.FEATURE_ETAG ?? "true") !== "false";
|
|
4746
|
+
}
|
|
4747
|
+
function formatWeakEtag(digest) {
|
|
4748
|
+
return `W/"${digest}"`;
|
|
4749
|
+
}
|
|
4750
|
+
function etagFromResource(resource) {
|
|
4751
|
+
const version = resource.updated_at ?? resource.created_at ?? "";
|
|
4752
|
+
const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
|
|
4753
|
+
const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
|
|
4754
|
+
return formatWeakEtag(digest);
|
|
4755
|
+
}
|
|
4756
|
+
function normalizeEtag(value) {
|
|
4757
|
+
return value.trim();
|
|
4758
|
+
}
|
|
4759
|
+
function etagValuesMatch(left, right) {
|
|
4760
|
+
return normalizeEtag(left) === normalizeEtag(right);
|
|
4761
|
+
}
|
|
4762
|
+
function parseEtagList(header) {
|
|
4763
|
+
if (!header) {
|
|
4764
|
+
return [];
|
|
4765
|
+
}
|
|
4766
|
+
return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
|
|
4767
|
+
}
|
|
4768
|
+
function ifNoneMatchSatisfied(request, etag) {
|
|
4769
|
+
const header = request.headers.get("if-none-match");
|
|
4770
|
+
if (!header) {
|
|
4771
|
+
return false;
|
|
4772
|
+
}
|
|
4773
|
+
if (header.trim() === "*") {
|
|
4774
|
+
return true;
|
|
4775
|
+
}
|
|
4776
|
+
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
4777
|
+
}
|
|
4778
|
+
function ifMatchSatisfied(request, etag) {
|
|
4779
|
+
const header = request.headers.get("if-match");
|
|
4780
|
+
if (!header) {
|
|
4781
|
+
return false;
|
|
4782
|
+
}
|
|
4783
|
+
if (header.trim() === "*") {
|
|
4784
|
+
return true;
|
|
4785
|
+
}
|
|
4786
|
+
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
4787
|
+
}
|
|
4788
|
+
function assertIfMatch(request, etag, options = {}) {
|
|
4789
|
+
const header = request.headers.get("if-match");
|
|
4790
|
+
if (!header) {
|
|
4791
|
+
if (options.required) {
|
|
4792
|
+
throw new PreconditionFailedError("If-Match header is required.");
|
|
4793
|
+
}
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
if (!ifMatchSatisfied(request, etag)) {
|
|
4797
|
+
throw new PreconditionFailedError("Resource ETag does not match If-Match.");
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
function applyEtagHeaders(headers, etag) {
|
|
4801
|
+
const next = new Headers(headers);
|
|
4802
|
+
next.set("ETag", etag);
|
|
4803
|
+
next.set("Cache-Control", "private, must-revalidate");
|
|
4804
|
+
next.append("Vary", "Authorization");
|
|
4805
|
+
next.append("Vary", "X-Tenant-Id");
|
|
4806
|
+
return next;
|
|
4807
|
+
}
|
|
4808
|
+
function notModifiedResponse(etag) {
|
|
4809
|
+
return new Response(null, {
|
|
4810
|
+
status: 304,
|
|
4811
|
+
headers: applyEtagHeaders(new Headers, etag)
|
|
4812
|
+
});
|
|
4813
|
+
}
|
|
4814
|
+
function applyConditionalGet(request, response, etag) {
|
|
4815
|
+
if (!isEtagEnabled()) {
|
|
4816
|
+
return response;
|
|
4817
|
+
}
|
|
4818
|
+
if (ifNoneMatchSatisfied(request, etag)) {
|
|
4819
|
+
return notModifiedResponse(etag);
|
|
4820
|
+
}
|
|
4821
|
+
const headers = applyEtagHeaders(new Headers(response.headers), etag);
|
|
4822
|
+
return new Response(response.body, {
|
|
4823
|
+
status: response.status,
|
|
4824
|
+
statusText: response.statusText,
|
|
4825
|
+
headers
|
|
4826
|
+
});
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4924
4829
|
// ../../src/core/tenant/tenantContext.ts
|
|
4925
4830
|
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
4926
4831
|
|
|
@@ -4980,148 +4885,8 @@ function securedBindRouteModelByKey(param, resolver, authorization, handler) {
|
|
|
4980
4885
|
return response;
|
|
4981
4886
|
};
|
|
4982
4887
|
}
|
|
4983
|
-
// ../../src/
|
|
4984
|
-
|
|
4985
|
-
member: 1,
|
|
4986
|
-
admin: 2,
|
|
4987
|
-
owner: 3
|
|
4988
|
-
};
|
|
4989
|
-
function isGlobalAdmin(user) {
|
|
4990
|
-
return user?.role === "admin";
|
|
4991
|
-
}
|
|
4992
|
-
function hasMinimumOrgRole(role, minimum) {
|
|
4993
|
-
if (!role) {
|
|
4994
|
-
return false;
|
|
4995
|
-
}
|
|
4996
|
-
return ROLE_RANK[role] >= ROLE_RANK[minimum];
|
|
4997
|
-
}
|
|
4998
|
-
function resolveUserId(user) {
|
|
4999
|
-
const userId = typeof user.id === "number" ? user.id : Number(user.id);
|
|
5000
|
-
if (!Number.isInteger(userId) || userId <= 0) {
|
|
5001
|
-
throw new ForbiddenError("Invalid authenticated user.");
|
|
5002
|
-
}
|
|
5003
|
-
return userId;
|
|
5004
|
-
}
|
|
5005
|
-
|
|
5006
|
-
// ../../src/modules/organization/memberRepository.ts
|
|
5007
|
-
class OrganizationMemberRepository {
|
|
5008
|
-
constructor() {}
|
|
5009
|
-
async findMembership(userId, organizationId) {
|
|
5010
|
-
const rows = await connection_default`
|
|
5011
|
-
SELECT id, organization_id, user_id, role, created_at
|
|
5012
|
-
FROM organization_member
|
|
5013
|
-
WHERE user_id = ${userId} AND organization_id = ${organizationId}
|
|
5014
|
-
LIMIT 1
|
|
5015
|
-
`;
|
|
5016
|
-
return rows[0] ?? null;
|
|
5017
|
-
}
|
|
5018
|
-
async listForUser(userId) {
|
|
5019
|
-
return await connection_default`
|
|
5020
|
-
SELECT id, organization_id, user_id, role, created_at
|
|
5021
|
-
FROM organization_member
|
|
5022
|
-
WHERE user_id = ${userId}
|
|
5023
|
-
ORDER BY organization_id
|
|
5024
|
-
`;
|
|
5025
|
-
}
|
|
5026
|
-
async listForOrganization(organizationId) {
|
|
5027
|
-
return await connection_default`
|
|
5028
|
-
SELECT id, organization_id, user_id, role, created_at
|
|
5029
|
-
FROM organization_member
|
|
5030
|
-
WHERE organization_id = ${organizationId}
|
|
5031
|
-
ORDER BY id
|
|
5032
|
-
`;
|
|
5033
|
-
}
|
|
5034
|
-
async addMember(input) {
|
|
5035
|
-
const rows = await connection_default`
|
|
5036
|
-
INSERT INTO organization_member (organization_id, user_id, role)
|
|
5037
|
-
VALUES (${input.organizationId}, ${input.userId}, ${input.role ?? "member"})
|
|
5038
|
-
RETURNING id, organization_id, user_id, role, created_at
|
|
5039
|
-
`;
|
|
5040
|
-
const row = rows[0];
|
|
5041
|
-
if (!row) {
|
|
5042
|
-
throw new Error("Organization member insert did not return a row.");
|
|
5043
|
-
}
|
|
5044
|
-
return row;
|
|
5045
|
-
}
|
|
5046
|
-
async removeMember(organizationId, userId) {
|
|
5047
|
-
const rows = await connection_default`
|
|
5048
|
-
DELETE FROM organization_member
|
|
5049
|
-
WHERE organization_id = ${organizationId} AND user_id = ${userId}
|
|
5050
|
-
RETURNING id
|
|
5051
|
-
`;
|
|
5052
|
-
return rows.length > 0;
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
var memberRepository_default = OrganizationMemberRepository;
|
|
5056
|
-
|
|
5057
|
-
// ../../src/core/auth/membershipContext.ts
|
|
5058
|
-
var membershipContext = createAsyncContextStore("@getstrata/membershipContext");
|
|
5059
|
-
var membershipRepository = new memberRepository_default;
|
|
5060
|
-
|
|
5061
|
-
// ../../src/core/auth/membershipService.ts
|
|
5062
|
-
class MembershipService {
|
|
5063
|
-
members;
|
|
5064
|
-
constructor(members = membershipRepository) {
|
|
5065
|
-
this.members = members;
|
|
5066
|
-
}
|
|
5067
|
-
async listOrganizationIdsForUser(userId) {
|
|
5068
|
-
const memberships = await this.members.listForUser(userId);
|
|
5069
|
-
return memberships.map((membership) => membership.organization_id);
|
|
5070
|
-
}
|
|
5071
|
-
async getOrgRole(userId, organizationId) {
|
|
5072
|
-
const membership = await this.members.findMembership(userId, organizationId);
|
|
5073
|
-
return membership?.role ?? null;
|
|
5074
|
-
}
|
|
5075
|
-
async requireOrgAccess(organizationId, minimumRole = "member", user = currentAuthUser()) {
|
|
5076
|
-
if (!user) {
|
|
5077
|
-
throw new ForbiddenError("Authentication required.");
|
|
5078
|
-
}
|
|
5079
|
-
if (isGlobalAdmin(user)) {
|
|
5080
|
-
return "owner";
|
|
5081
|
-
}
|
|
5082
|
-
const role = await this.getOrgRole(resolveUserId(user), organizationId);
|
|
5083
|
-
if (!role || !hasMinimumOrgRole(role, minimumRole)) {
|
|
5084
|
-
throw new ForbiddenError("Organization membership required.");
|
|
5085
|
-
}
|
|
5086
|
-
return role;
|
|
5087
|
-
}
|
|
5088
|
-
async filterAccessibleOrganizationIds(organizationIds, user = currentAuthUser()) {
|
|
5089
|
-
if (!user) {
|
|
5090
|
-
return [];
|
|
5091
|
-
}
|
|
5092
|
-
if (isGlobalAdmin(user)) {
|
|
5093
|
-
return organizationIds;
|
|
5094
|
-
}
|
|
5095
|
-
const allowed = new Set(await this.listOrganizationIdsForUser(resolveUserId(user)));
|
|
5096
|
-
return organizationIds.filter((organizationId) => allowed.has(organizationId));
|
|
5097
|
-
}
|
|
5098
|
-
async addOwnerOnOrganizationCreate(organizationId, userId) {
|
|
5099
|
-
await this.members.addMember({
|
|
5100
|
-
organizationId,
|
|
5101
|
-
userId,
|
|
5102
|
-
role: "owner"
|
|
5103
|
-
});
|
|
5104
|
-
}
|
|
5105
|
-
listMembersForOrganization(organizationId) {
|
|
5106
|
-
return this.members.listForOrganization(organizationId);
|
|
5107
|
-
}
|
|
5108
|
-
addMember(input) {
|
|
5109
|
-
return this.members.addMember(input);
|
|
5110
|
-
}
|
|
5111
|
-
removeMember(organizationId, userId) {
|
|
5112
|
-
return this.members.removeMember(organizationId, userId);
|
|
5113
|
-
}
|
|
5114
|
-
}
|
|
5115
|
-
var membershipService_default = MembershipService;
|
|
5116
|
-
|
|
5117
|
-
// ../../src/core/auth/resolveMembershipService.ts
|
|
5118
|
-
function resolveMembershipService() {
|
|
5119
|
-
const dependencies = resolveApplicationDependencies2();
|
|
5120
|
-
if (dependencies.container.has("core.membership")) {
|
|
5121
|
-
return dependencies.container.resolve("core.membership");
|
|
5122
|
-
}
|
|
5123
|
-
return new membershipService_default;
|
|
5124
|
-
}
|
|
4888
|
+
// ../../src/bootstrap/membershipService.ts
|
|
4889
|
+
import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
|
|
5125
4890
|
// ../../src/core/http/csrfProtection.ts
|
|
5126
4891
|
var DEFAULT_CSRF_TTL_MS = 60 * 60 * 1000;
|
|
5127
4892
|
function createCsrfProtection(secret, options = {}) {
|
|
@@ -5216,7 +4981,7 @@ function createRouteKernel(dependencies) {
|
|
|
5216
4981
|
return createHttpKernel(dependencies);
|
|
5217
4982
|
}
|
|
5218
4983
|
// ../../src/bootstrap/web/server.ts
|
|
5219
|
-
function
|
|
4984
|
+
function wrapRouteHandler(handler) {
|
|
5220
4985
|
return async (request) => {
|
|
5221
4986
|
const response = await handler(request);
|
|
5222
4987
|
return response ?? new Response("Not Found", { status: 404 });
|
|
@@ -5226,7 +4991,7 @@ function convertAppRoutesToBunRoutes(routes) {
|
|
|
5226
4991
|
const bunRoutes = {};
|
|
5227
4992
|
for (const [path, handler] of Object.entries(routes)) {
|
|
5228
4993
|
if (typeof handler === "function") {
|
|
5229
|
-
bunRoutes[path] = { GET:
|
|
4994
|
+
bunRoutes[path] = { GET: wrapRouteHandler(handler) };
|
|
5230
4995
|
continue;
|
|
5231
4996
|
}
|
|
5232
4997
|
if (handler && typeof handler === "object" && !Array.isArray(handler)) {
|
|
@@ -5235,7 +5000,7 @@ function convertAppRoutesToBunRoutes(routes) {
|
|
|
5235
5000
|
if (typeof methodHandler !== "function") {
|
|
5236
5001
|
continue;
|
|
5237
5002
|
}
|
|
5238
|
-
methods[method.toUpperCase()] =
|
|
5003
|
+
methods[method.toUpperCase()] = wrapRouteHandler(methodHandler);
|
|
5239
5004
|
}
|
|
5240
5005
|
if (Object.keys(methods).length > 0) {
|
|
5241
5006
|
bunRoutes[path] = methods;
|
|
@@ -5350,7 +5115,7 @@ export {
|
|
|
5350
5115
|
securedBindRouteModel,
|
|
5351
5116
|
scheduleRunCommand,
|
|
5352
5117
|
runProviderPhase,
|
|
5353
|
-
runDueScheduledTasks,
|
|
5118
|
+
runDueScheduledTasks2 as runDueScheduledTasks,
|
|
5354
5119
|
routeRegistry,
|
|
5355
5120
|
routeParams,
|
|
5356
5121
|
resolveService,
|
|
@@ -5382,12 +5147,13 @@ export {
|
|
|
5382
5147
|
configureModulesDirectory,
|
|
5383
5148
|
collectProviders,
|
|
5384
5149
|
cacheTagsForModelWrite,
|
|
5150
|
+
buildWebModuleRoutes,
|
|
5385
5151
|
buildModuleRoutes,
|
|
5386
5152
|
assertProductionSecrets,
|
|
5387
5153
|
assertAppDependenciesComplete,
|
|
5388
|
-
appSchedule,
|
|
5154
|
+
appSchedule2 as appSchedule,
|
|
5389
5155
|
ServiceContainer,
|
|
5390
|
-
Schedule,
|
|
5156
|
+
Schedule2 as Schedule,
|
|
5391
5157
|
RouteRegistry,
|
|
5392
5158
|
REDIS_URL_CONFIG_KEY,
|
|
5393
5159
|
DEFAULT_APP_PORT,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"import": "./dist/entries/buildModuleRoutes.js",
|
|
26
26
|
"default": "./dist/entries/buildModuleRoutes.js"
|
|
27
27
|
},
|
|
28
|
+
"./buildWebModuleRoutes": {
|
|
29
|
+
"types": "./dist/bootstrap/buildWebModuleRoutes.d.ts",
|
|
30
|
+
"import": "./dist/entries/buildWebModuleRoutes.js",
|
|
31
|
+
"default": "./dist/entries/buildWebModuleRoutes.js"
|
|
32
|
+
},
|
|
28
33
|
"./cache/modelCacheTags": {
|
|
29
34
|
"types": "./dist/bootstrap/cache/modelCacheTags.d.ts",
|
|
30
35
|
"import": "./dist/entries/cache/modelCacheTags.js",
|
|
@@ -137,14 +142,14 @@
|
|
|
137
142
|
"build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external @getstrata/core",
|
|
138
143
|
"build:types": "tsc -p tsconfig.types.json && bun ../../scripts/prune-bootstrap-dist-types.ts",
|
|
139
144
|
"prepublishOnly": "bun run build",
|
|
140
|
-
"build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/membershipService.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core",
|
|
145
|
+
"build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/buildWebModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/membershipService.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core",
|
|
141
146
|
"build:shims": "true"
|
|
142
147
|
},
|
|
143
148
|
"publishConfig": {
|
|
144
149
|
"access": "public"
|
|
145
150
|
},
|
|
146
151
|
"peerDependencies": {
|
|
147
|
-
"@getstrata/core": "^0.5.
|
|
152
|
+
"@getstrata/core": "^0.5.33",
|
|
148
153
|
"typescript": "^5.9.0"
|
|
149
154
|
}
|
|
150
155
|
}
|