@getstrata/bootstrap 0.2.28 → 0.2.29
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/http/securedRouteModelBinding.d.ts +2 -2
- package/dist/bootstrap/schedule.d.ts +2 -1
- package/dist/bootstrap/web/forms.d.ts +1 -1
- package/dist/entries/applicationRegistry.js +2 -0
- package/dist/entries/buildModuleRoutes.js +2 -0
- package/dist/entries/buildWebModuleRoutes.js +2 -0
- package/dist/entries/cache/modelCacheTags.js +2 -0
- package/dist/entries/config.js +2 -0
- package/dist/entries/context.js +76 -3157
- package/dist/entries/contracts.js +2 -0
- package/dist/entries/createRoutes.js +1468 -0
- package/dist/entries/createSpaRoutes.js +2 -0
- package/dist/entries/createWebRoutes.js +2 -0
- package/dist/entries/dependencies.js +76 -3157
- package/dist/entries/discoverModules.js +2 -0
- package/dist/entries/health.js +3 -0
- package/dist/entries/http/securedRouteModelBinding.js +6 -293
- package/dist/entries/httpKernel.js +2 -0
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +2 -0
- package/dist/entries/membershipService.js +2 -0
- package/dist/entries/metricsRoutes.js +2 -0
- package/dist/entries/providers/view.js +6 -623
- package/dist/entries/providers.js +69 -3157
- package/dist/entries/queue/defaultJobs.js +7 -465
- package/dist/entries/routeRegistry.js +2 -0
- package/dist/entries/schedule.js +49 -0
- package/dist/entries/secretsGuard.js +9 -0
- package/dist/entries/web/forms.js +4 -18
- package/dist/entries/web/routing.js +18 -304
- package/dist/entries/web/server.js +2 -0
- package/dist/entries/web/session.js +3 -26
- package/dist/entries/web/slug.js +2 -0
- package/dist/index-sfreg6q3.js +0 -0
- package/dist/index.js +69 -3322
- package/package.json +12 -2
package/dist/entries/health.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __jsonParse = (a) => JSON.parse(a);
|
|
3
|
+
|
|
2
4
|
// ../../src/bootstrap/health.ts
|
|
3
5
|
import { jsonResponse } from "@getstrata/core/http";
|
|
4
6
|
var {RedisClient } = globalThis.Bun;
|
|
@@ -137,6 +139,7 @@ var db = new Proxy(function database() {}, {
|
|
|
137
139
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
138
140
|
}
|
|
139
141
|
});
|
|
142
|
+
var connection_default = db;
|
|
140
143
|
|
|
141
144
|
// ../../src/bootstrap/config.ts
|
|
142
145
|
import {
|
|
@@ -1,298 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
4
|
-
function createAsyncContextStore(key) {
|
|
5
|
-
const symbol = Symbol.for(key);
|
|
6
|
-
const globalRecord = globalThis;
|
|
7
|
-
const existing = globalRecord[symbol];
|
|
8
|
-
if (existing) {
|
|
9
|
-
return existing;
|
|
10
|
-
}
|
|
11
|
-
const store = new AsyncLocalStorage;
|
|
12
|
-
globalRecord[symbol] = store;
|
|
13
|
-
return store;
|
|
14
|
-
}
|
|
2
|
+
var __jsonParse = (a) => JSON.parse(a);
|
|
15
3
|
|
|
16
|
-
// ../../src/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ../../src/core/errors/http.ts
|
|
23
|
-
class HttpError extends Error {
|
|
24
|
-
status;
|
|
25
|
-
details;
|
|
26
|
-
constructor(status, message, details) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.name = new.target.name;
|
|
29
|
-
this.status = status;
|
|
30
|
-
this.details = details;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
class BadRequestError extends HttpError {
|
|
35
|
-
constructor(message = "Bad Request", details) {
|
|
36
|
-
super(400, message, details);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
class ConflictError extends HttpError {
|
|
40
|
-
constructor(message = "Conflict", details) {
|
|
41
|
-
super(409, message, details);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
class UnprocessableEntityError extends HttpError {
|
|
46
|
-
constructor(message = "Unprocessable Entity", details) {
|
|
47
|
-
super(422, message, details);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
class ForbiddenError extends HttpError {
|
|
51
|
-
constructor(message = "Forbidden", details) {
|
|
52
|
-
super(403, message, details);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
class UnauthorizedError extends HttpError {
|
|
57
|
-
constructor(message = "Unauthorized", details) {
|
|
58
|
-
super(401, message, details);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
class PreconditionFailedError extends HttpError {
|
|
62
|
-
constructor(message = "Precondition Failed", details) {
|
|
63
|
-
super(412, message, details);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ../../src/core/contracts/di.ts
|
|
68
|
-
function getRequiredDependency(dependencies, key) {
|
|
69
|
-
const dependency = dependencies[key];
|
|
70
|
-
if (dependency === undefined) {
|
|
71
|
-
throw new Error(`Required dependency "${key}" is not registered.`);
|
|
72
|
-
}
|
|
73
|
-
return dependency;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ../../src/core/contracts/serviceTokens.ts
|
|
77
|
-
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
78
|
-
var CORE_AUTH_TOKEN = "core.auth";
|
|
79
|
-
|
|
80
|
-
// ../../src/core/logging/logger.ts
|
|
81
|
-
class Logger {
|
|
82
|
-
channel;
|
|
83
|
-
constructor(channel = "app") {
|
|
84
|
-
this.channel = channel;
|
|
85
|
-
}
|
|
86
|
-
write(level, message, context = {}) {
|
|
87
|
-
const entry = {
|
|
88
|
-
level,
|
|
89
|
-
channel: this.channel,
|
|
90
|
-
message,
|
|
91
|
-
timestamp: new Date().toISOString(),
|
|
92
|
-
...context
|
|
93
|
-
};
|
|
94
|
-
const line = JSON.stringify(entry);
|
|
95
|
-
if (level === "error") {
|
|
96
|
-
console.error(line);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
console.log(line);
|
|
100
|
-
}
|
|
101
|
-
debug(message, context) {
|
|
102
|
-
this.write("debug", message, context);
|
|
103
|
-
}
|
|
104
|
-
info(message, context) {
|
|
105
|
-
this.write("info", message, context);
|
|
106
|
-
}
|
|
107
|
-
warn(message, context) {
|
|
108
|
-
this.write("warn", message, context);
|
|
109
|
-
}
|
|
110
|
-
error(message, context) {
|
|
111
|
-
this.write("error", message, context);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
var appLogger = new Logger("app");
|
|
115
|
-
|
|
116
|
-
// ../../src/core/runtime/applicationRegistry.ts
|
|
117
|
-
var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
|
|
118
|
-
var activeContext;
|
|
119
|
-
function readStoredApplicationContext() {
|
|
120
|
-
if (activeContext) {
|
|
121
|
-
return activeContext;
|
|
122
|
-
}
|
|
123
|
-
const globalContext = globalThis[APPLICATION_CONTEXT_KEY];
|
|
124
|
-
if (globalContext) {
|
|
125
|
-
activeContext = globalContext;
|
|
126
|
-
}
|
|
127
|
-
return activeContext;
|
|
128
|
-
}
|
|
129
|
-
function requireActiveApplicationContext() {
|
|
130
|
-
const context = readStoredApplicationContext();
|
|
131
|
-
if (!context) {
|
|
132
|
-
throw new Error("The application context has not been bootstrapped.");
|
|
133
|
-
}
|
|
134
|
-
return context;
|
|
135
|
-
}
|
|
136
|
-
function resolveApplicationCache() {
|
|
137
|
-
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
138
|
-
}
|
|
139
|
-
function resolveApplicationAuth() {
|
|
140
|
-
return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
|
|
141
|
-
}
|
|
142
|
-
function resolveApplicationPolicyGate() {
|
|
143
|
-
return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ../../src/core/crypto/nonCryptographicHash.ts
|
|
147
|
-
function nonCryptographicDigest(input) {
|
|
148
|
-
return Bun.hash(input).toString(16);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ../../src/core/http/etag.ts
|
|
152
|
-
function isEtagEnabled() {
|
|
153
|
-
return (process.env.FEATURE_ETAG ?? "true") !== "false";
|
|
154
|
-
}
|
|
155
|
-
function formatWeakEtag(digest) {
|
|
156
|
-
return `W/"${digest}"`;
|
|
157
|
-
}
|
|
158
|
-
function etagFromResource(resource) {
|
|
159
|
-
const version = resource.updated_at ?? resource.created_at ?? "";
|
|
160
|
-
const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
|
|
161
|
-
const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
|
|
162
|
-
return formatWeakEtag(digest);
|
|
163
|
-
}
|
|
164
|
-
function normalizeEtag(value) {
|
|
165
|
-
return value.trim();
|
|
166
|
-
}
|
|
167
|
-
function etagValuesMatch(left, right) {
|
|
168
|
-
return normalizeEtag(left) === normalizeEtag(right);
|
|
169
|
-
}
|
|
170
|
-
function parseEtagList(header) {
|
|
171
|
-
if (!header) {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
|
|
175
|
-
}
|
|
176
|
-
function ifNoneMatchSatisfied(request, etag) {
|
|
177
|
-
const header = request.headers.get("if-none-match");
|
|
178
|
-
if (!header) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
if (header.trim() === "*") {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
185
|
-
}
|
|
186
|
-
function ifMatchSatisfied(request, etag) {
|
|
187
|
-
const header = request.headers.get("if-match");
|
|
188
|
-
if (!header) {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
if (header.trim() === "*") {
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
195
|
-
}
|
|
196
|
-
function assertIfMatch(request, etag, options = {}) {
|
|
197
|
-
const header = request.headers.get("if-match");
|
|
198
|
-
if (!header) {
|
|
199
|
-
if (options.required) {
|
|
200
|
-
throw new PreconditionFailedError("If-Match header is required.");
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!ifMatchSatisfied(request, etag)) {
|
|
205
|
-
throw new PreconditionFailedError("Resource ETag does not match If-Match.");
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function applyEtagHeaders(headers, etag) {
|
|
209
|
-
const next = new Headers(headers);
|
|
210
|
-
next.set("ETag", etag);
|
|
211
|
-
next.set("Cache-Control", "private, must-revalidate");
|
|
212
|
-
next.append("Vary", "Authorization");
|
|
213
|
-
next.append("Vary", "X-Tenant-Id");
|
|
214
|
-
return next;
|
|
215
|
-
}
|
|
216
|
-
function notModifiedResponse(etag) {
|
|
217
|
-
return new Response(null, {
|
|
218
|
-
status: 304,
|
|
219
|
-
headers: applyEtagHeaders(new Headers, etag)
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
function applyConditionalGet(request, response, etag) {
|
|
223
|
-
if (!isEtagEnabled()) {
|
|
224
|
-
return response;
|
|
225
|
-
}
|
|
226
|
-
if (ifNoneMatchSatisfied(request, etag)) {
|
|
227
|
-
return notModifiedResponse(etag);
|
|
228
|
-
}
|
|
229
|
-
const headers = applyEtagHeaders(new Headers(response.headers), etag);
|
|
230
|
-
return new Response(response.body, {
|
|
231
|
-
status: response.status,
|
|
232
|
-
statusText: response.statusText,
|
|
233
|
-
headers
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ../../src/core/tenant/tenantContext.ts
|
|
238
|
-
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
239
|
-
|
|
240
|
-
// ../../src/core/http/validation.ts
|
|
241
|
-
function parsePositiveIntParam(value, name = "id") {
|
|
242
|
-
const parsed = Number.parseInt(value, 10);
|
|
243
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
244
|
-
throw new BadRequestError(`Invalid ${name}. Expected a positive integer.`);
|
|
245
|
-
}
|
|
246
|
-
return parsed;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ../../src/core/http/securedRouteModelBinding.ts
|
|
250
|
-
function isMutatingPolicyAction(action) {
|
|
251
|
-
return action === "update" || action === "delete";
|
|
252
|
-
}
|
|
253
|
-
function securedBindRouteModel(param, resolver, authorization, handler) {
|
|
254
|
-
return async (request) => {
|
|
255
|
-
const id = parsePositiveIntParam(String(request.params[param]), String(param));
|
|
256
|
-
const model = await resolver(id, request);
|
|
257
|
-
const gate = resolveApplicationPolicyGate();
|
|
258
|
-
const auth = resolveApplicationAuth();
|
|
259
|
-
const user = currentAuthUser() ?? await auth.resolve(request);
|
|
260
|
-
gate.authorize(authorization.resource, authorization.action, user, model);
|
|
261
|
-
if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
|
|
262
|
-
assertIfMatch(request, etagFromResource(model), {
|
|
263
|
-
required: authorization.requireIfMatch ?? true
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
const response = await handler(request, model);
|
|
267
|
-
if (isEtagEnabled() && authorization.action === "view") {
|
|
268
|
-
return applyConditionalGet(request, response, etagFromResource(model));
|
|
269
|
-
}
|
|
270
|
-
return response;
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
function securedBindRouteModelByKey(param, resolver, authorization, handler) {
|
|
274
|
-
return async (request) => {
|
|
275
|
-
const key = String(request.params[param] ?? "").trim();
|
|
276
|
-
if (!key) {
|
|
277
|
-
throw new BadRequestError(`Missing route parameter "${String(param)}".`);
|
|
278
|
-
}
|
|
279
|
-
const model = await resolver(key, request);
|
|
280
|
-
const gate = resolveApplicationPolicyGate();
|
|
281
|
-
const auth = resolveApplicationAuth();
|
|
282
|
-
const user = currentAuthUser() ?? await auth.resolve(request);
|
|
283
|
-
gate.authorize(authorization.resource, authorization.action, user, model);
|
|
284
|
-
if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
|
|
285
|
-
assertIfMatch(request, etagFromResource(model), {
|
|
286
|
-
required: authorization.requireIfMatch ?? true
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
const response = await handler(request, model);
|
|
290
|
-
if (isEtagEnabled() && authorization.action === "view") {
|
|
291
|
-
return applyConditionalGet(request, response, etagFromResource(model));
|
|
292
|
-
}
|
|
293
|
-
return response;
|
|
294
|
-
};
|
|
295
|
-
}
|
|
4
|
+
// ../../src/bootstrap/http/securedRouteModelBinding.ts
|
|
5
|
+
import {
|
|
6
|
+
securedBindRouteModel,
|
|
7
|
+
securedBindRouteModelByKey
|
|
8
|
+
} from "@getstrata/core/http/securedRouteModelBinding";
|
|
296
9
|
export {
|
|
297
10
|
securedBindRouteModelByKey,
|
|
298
11
|
securedBindRouteModel
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __jsonParse = (a) => JSON.parse(a);
|
|
3
|
+
|
|
2
4
|
// ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
|
|
3
5
|
import { eventBus, modelEventName } from "@getstrata/core/events";
|
|
4
6
|
import InvalidateCacheTagsJob from "@getstrata/core/jobs/invalidateCacheTagsJob";
|