@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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AppDependencies, AppModule, AppRouteMap } from "./contracts";
|
|
2
|
+
interface BuildWebModuleRoutesOptions {
|
|
3
|
+
modules?: AppModule[];
|
|
4
|
+
clearRegistry?: boolean;
|
|
5
|
+
seedRoutes?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
declare function buildWebModuleRoutes(dependencies: AppDependencies, options?: BuildWebModuleRoutesOptions): AppRouteMap;
|
|
8
|
+
export type { BuildWebModuleRoutesOptions };
|
|
9
|
+
export { buildWebModuleRoutes };
|
package/dist/bootstrap/env.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { resolveMembershipService } from "
|
|
1
|
+
export { resolveMembershipService } from "@getstrata/core/auth/membershipService";
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @getstrata/bootstrap — application shell for Strata sibling apps.
|
|
3
3
|
*/
|
|
4
|
+
export type { ScheduledTask } from "@getstrata/core/scheduler/schedule";
|
|
5
|
+
export { appSchedule, runDueScheduledTasks, Schedule } from "@getstrata/core/scheduler/schedule";
|
|
4
6
|
export { scheduleRunCommand } from "../cli/commands/scheduleRun.ts";
|
|
5
|
-
export type { ScheduledTask } from "../core/scheduler/schedule.ts";
|
|
6
|
-
export { appSchedule, runDueScheduledTasks, Schedule } from "../core/scheduler/schedule.ts";
|
|
7
7
|
export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "./applicationRegistry.ts";
|
|
8
8
|
export type { BuildModuleRoutesOptions } from "./buildModuleRoutes.ts";
|
|
9
9
|
export { buildModuleRoutes } from "./buildModuleRoutes.ts";
|
|
10
|
+
export type { BuildWebModuleRoutesOptions } from "./buildWebModuleRoutes.ts";
|
|
11
|
+
export { buildWebModuleRoutes } from "./buildWebModuleRoutes.ts";
|
|
10
12
|
export { cacheTagsForModelWrite, discoverModelTableNames } from "./cache/modelCacheTags.ts";
|
|
11
13
|
export { APP_PORT_CONFIG_KEY, CORE_AUTH_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, DATABASE_URL_CONFIG_KEY, DEFAULT_APP_PORT, REDIS_URL_CONFIG_KEY, } from "./config.ts";
|
|
12
14
|
export { collectProviders, createAppContext, runProviderPhase } from "./context.ts";
|
|
@@ -1,222 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// ../../src/core/errors/http.ts
|
|
8
|
-
class HttpError extends Error {
|
|
9
|
-
status;
|
|
10
|
-
details;
|
|
11
|
-
constructor(status, message, details) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = new.target.name;
|
|
14
|
-
this.status = status;
|
|
15
|
-
this.details = details;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class BadRequestError extends HttpError {
|
|
20
|
-
constructor(message = "Bad Request", details) {
|
|
21
|
-
super(400, message, details);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
class ConflictError extends HttpError {
|
|
25
|
-
constructor(message = "Conflict", details) {
|
|
26
|
-
super(409, message, details);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
class UnprocessableEntityError extends HttpError {
|
|
31
|
-
constructor(message = "Unprocessable Entity", details) {
|
|
32
|
-
super(422, message, details);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
class ForbiddenError extends HttpError {
|
|
36
|
-
constructor(message = "Forbidden", details) {
|
|
37
|
-
super(403, message, details);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class UnauthorizedError extends HttpError {
|
|
42
|
-
constructor(message = "Unauthorized", details) {
|
|
43
|
-
super(401, message, details);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
class PreconditionFailedError extends HttpError {
|
|
47
|
-
constructor(message = "Precondition Failed", details) {
|
|
48
|
-
super(412, message, details);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ../../src/core/http/etag.ts
|
|
53
|
-
function isEtagEnabled() {
|
|
54
|
-
return (process.env.FEATURE_ETAG ?? "true") !== "false";
|
|
55
|
-
}
|
|
56
|
-
function formatWeakEtag(digest) {
|
|
57
|
-
return `W/"${digest}"`;
|
|
58
|
-
}
|
|
59
|
-
function computeEtagFromJson(data) {
|
|
60
|
-
const digest = nonCryptographicDigest(JSON.stringify(data)).slice(0, 32);
|
|
61
|
-
return formatWeakEtag(digest);
|
|
62
|
-
}
|
|
63
|
-
function etagFromResource(resource) {
|
|
64
|
-
const version = resource.updated_at ?? resource.created_at ?? "";
|
|
65
|
-
const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
|
|
66
|
-
const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
|
|
67
|
-
return formatWeakEtag(digest);
|
|
68
|
-
}
|
|
69
|
-
function normalizeEtag(value) {
|
|
70
|
-
return value.trim();
|
|
71
|
-
}
|
|
72
|
-
function etagValuesMatch(left, right) {
|
|
73
|
-
return normalizeEtag(left) === normalizeEtag(right);
|
|
74
|
-
}
|
|
75
|
-
function parseEtagList(header) {
|
|
76
|
-
if (!header) {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
|
|
80
|
-
}
|
|
81
|
-
function ifNoneMatchSatisfied(request, etag) {
|
|
82
|
-
const header = request.headers.get("if-none-match");
|
|
83
|
-
if (!header) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
if (header.trim() === "*") {
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
90
|
-
}
|
|
91
|
-
function ifMatchSatisfied(request, etag) {
|
|
92
|
-
const header = request.headers.get("if-match");
|
|
93
|
-
if (!header) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
if (header.trim() === "*") {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
100
|
-
}
|
|
101
|
-
function assertIfMatch(request, etag, options = {}) {
|
|
102
|
-
const header = request.headers.get("if-match");
|
|
103
|
-
if (!header) {
|
|
104
|
-
if (options.required) {
|
|
105
|
-
throw new PreconditionFailedError("If-Match header is required.");
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (!ifMatchSatisfied(request, etag)) {
|
|
110
|
-
throw new PreconditionFailedError("Resource ETag does not match If-Match.");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function applyEtagHeaders(headers, etag) {
|
|
114
|
-
const next = new Headers(headers);
|
|
115
|
-
next.set("ETag", etag);
|
|
116
|
-
next.set("Cache-Control", "private, must-revalidate");
|
|
117
|
-
next.append("Vary", "Authorization");
|
|
118
|
-
next.append("Vary", "X-Tenant-Id");
|
|
119
|
-
return next;
|
|
120
|
-
}
|
|
121
|
-
function notModifiedResponse(etag) {
|
|
122
|
-
return new Response(null, {
|
|
123
|
-
status: 304,
|
|
124
|
-
headers: applyEtagHeaders(new Headers, etag)
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
function applyConditionalGet(request, response, etag) {
|
|
128
|
-
if (!isEtagEnabled()) {
|
|
129
|
-
return response;
|
|
130
|
-
}
|
|
131
|
-
if (ifNoneMatchSatisfied(request, etag)) {
|
|
132
|
-
return notModifiedResponse(etag);
|
|
133
|
-
}
|
|
134
|
-
const headers = applyEtagHeaders(new Headers(response.headers), etag);
|
|
135
|
-
return new Response(response.body, {
|
|
136
|
-
status: response.status,
|
|
137
|
-
statusText: response.statusText,
|
|
138
|
-
headers
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ../../src/core/http/conditionalResponse.ts
|
|
143
|
-
function jsonResponse(data, init = {}) {
|
|
144
|
-
return Response.json(data, {
|
|
145
|
-
status: init.status ?? 200,
|
|
146
|
-
headers: init.headers
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
function conditionalJsonResponse(request, data, init = {}) {
|
|
150
|
-
if (!request || !isEtagEnabled()) {
|
|
151
|
-
return jsonResponse(data, init);
|
|
152
|
-
}
|
|
153
|
-
const etag = computeEtagFromJson(data);
|
|
154
|
-
if (ifNoneMatchSatisfied(request, etag)) {
|
|
155
|
-
return notModifiedResponse(etag);
|
|
156
|
-
}
|
|
157
|
-
const response = jsonResponse(data, init);
|
|
158
|
-
const headers = new Headers(response.headers);
|
|
159
|
-
headers.set("ETag", etag);
|
|
160
|
-
headers.set("Cache-Control", "private, must-revalidate");
|
|
161
|
-
headers.append("Vary", "Authorization");
|
|
162
|
-
headers.append("Vary", "X-Tenant-Id");
|
|
163
|
-
return new Response(response.body, {
|
|
164
|
-
status: response.status,
|
|
165
|
-
statusText: response.statusText,
|
|
166
|
-
headers
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ../../src/core/http/middleware.ts
|
|
171
|
-
function isRouteHandler(value) {
|
|
172
|
-
return typeof value === "function";
|
|
173
|
-
}
|
|
174
|
-
function isMethodRouteMap(value) {
|
|
175
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const entries = Object.entries(value);
|
|
179
|
-
return entries.length > 0 && entries.every(([, handler]) => isRouteHandler(handler));
|
|
180
|
-
}
|
|
181
|
-
function composeMiddleware(...middleware) {
|
|
182
|
-
return (handler) => {
|
|
183
|
-
return async (request) => {
|
|
184
|
-
let index = 0;
|
|
185
|
-
const dispatch = async () => {
|
|
186
|
-
if (index >= middleware.length) {
|
|
187
|
-
return await handler(request);
|
|
188
|
-
}
|
|
189
|
-
const current = middleware[index];
|
|
190
|
-
index += 1;
|
|
191
|
-
if (!current) {
|
|
192
|
-
return await handler(request);
|
|
193
|
-
}
|
|
194
|
-
return await current(request, dispatch);
|
|
195
|
-
};
|
|
196
|
-
return await dispatch();
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
function wrapRouteHandler(handler, middleware) {
|
|
201
|
-
if (isMethodRouteMap(handler)) {
|
|
202
|
-
const wrapped = {};
|
|
203
|
-
for (const [method, routeHandler] of Object.entries(handler)) {
|
|
204
|
-
wrapped[method] = composeMiddleware(...middleware)(routeHandler);
|
|
205
|
-
}
|
|
206
|
-
return wrapped;
|
|
207
|
-
}
|
|
208
|
-
if (isRouteHandler(handler)) {
|
|
209
|
-
return composeMiddleware(...middleware)(handler);
|
|
210
|
-
}
|
|
211
|
-
return handler;
|
|
212
|
-
}
|
|
213
|
-
function applyMiddlewareToRoutes(routes, middleware) {
|
|
214
|
-
const wrapped = {};
|
|
215
|
-
for (const [path, routeHandler] of Object.entries(routes)) {
|
|
216
|
-
wrapped[path] = wrapRouteHandler(routeHandler, middleware);
|
|
217
|
-
}
|
|
218
|
-
return wrapped;
|
|
219
|
-
}
|
|
2
|
+
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
3
|
+
import { conditionalJsonResponse } from "@getstrata/core/http";
|
|
4
|
+
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
220
5
|
|
|
221
6
|
// ../../src/bootstrap/httpKernel.ts
|
|
222
7
|
import {
|