@getstrata/bootstrap 0.2.16 → 0.2.17
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.
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/discoverModules.ts
|
|
3
|
+
var appModules = [];
|
|
4
|
+
function discoverModules() {
|
|
5
|
+
return appModules;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
9
|
+
function cacheTagsForModelWrite(tableName, action) {
|
|
10
|
+
const module = discoverModules().find((entry) => entry.tableName === tableName);
|
|
11
|
+
const baseTags = module?.cacheTags ?? [`${tableName}s`];
|
|
12
|
+
const isDelete = action === "deleted" || action === "force-deleted";
|
|
13
|
+
const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
|
|
14
|
+
return [...new Set([...baseTags, ...extraTags])];
|
|
15
|
+
}
|
|
16
|
+
function discoverModelTableNames() {
|
|
17
|
+
return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
discoverModelTableNames,
|
|
21
|
+
cacheTagsForModelWrite
|
|
22
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/core/http/csrfProtection.ts
|
|
3
|
+
var DEFAULT_CSRF_TTL_MS = 60 * 60 * 1000;
|
|
4
|
+
function createCsrfProtection(secret, options = {}) {
|
|
5
|
+
const expiresIn = options.expiresIn ?? DEFAULT_CSRF_TTL_MS;
|
|
6
|
+
const maxAge = options.maxAge ?? expiresIn;
|
|
7
|
+
return {
|
|
8
|
+
generate(_sessionKey) {
|
|
9
|
+
return Bun.CSRF.generate(secret, { expiresIn });
|
|
10
|
+
},
|
|
11
|
+
verify(token, _sessionKey) {
|
|
12
|
+
if (!token) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return Bun.CSRF.verify(token, { secret, maxAge });
|
|
16
|
+
},
|
|
17
|
+
secret
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ../../src/bootstrap/web/forms.ts
|
|
22
|
+
async function parseFormBody(request) {
|
|
23
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
24
|
+
const fields = {};
|
|
25
|
+
const files = {};
|
|
26
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
27
|
+
const text = await request.text();
|
|
28
|
+
for (const pair of text.split("&")) {
|
|
29
|
+
const idx = pair.indexOf("=");
|
|
30
|
+
if (idx === -1)
|
|
31
|
+
continue;
|
|
32
|
+
const key = decodeURIComponent(pair.slice(0, idx).replace(/\+/g, " "));
|
|
33
|
+
const value = decodeURIComponent(pair.slice(idx + 1).replace(/\+/g, " "));
|
|
34
|
+
fields[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return { fields, files };
|
|
37
|
+
}
|
|
38
|
+
if (contentType.includes("multipart/form-data")) {
|
|
39
|
+
const form = await request.formData();
|
|
40
|
+
for (const [key, value] of form.entries()) {
|
|
41
|
+
if (value instanceof File) {
|
|
42
|
+
files[key] = value;
|
|
43
|
+
} else {
|
|
44
|
+
fields[key] = String(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { fields, files };
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
parseFormBody,
|
|
52
|
+
createCsrfProtection
|
|
53
|
+
};
|
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/web/routing.ts
|
|
3
|
+
import { withErrorHandling } from "@getstrata/core";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
6
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
7
|
+
function createAsyncContextStore(key) {
|
|
8
|
+
const symbol = Symbol.for(key);
|
|
9
|
+
const globalRecord = globalThis;
|
|
10
|
+
const existing = globalRecord[symbol];
|
|
11
|
+
if (existing) {
|
|
12
|
+
return existing;
|
|
13
|
+
}
|
|
14
|
+
const store = new AsyncLocalStorage;
|
|
15
|
+
globalRecord[symbol] = store;
|
|
16
|
+
return store;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ../../src/core/auth/authContext.ts
|
|
20
|
+
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
21
|
+
function currentAuthUser() {
|
|
22
|
+
return authContext.getStore() ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ../../src/core/errors/http.ts
|
|
26
|
+
class HttpError extends Error {
|
|
27
|
+
status;
|
|
28
|
+
details;
|
|
29
|
+
constructor(status, message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = new.target.name;
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.details = details;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class BadRequestError extends HttpError {
|
|
38
|
+
constructor(message = "Bad Request", details) {
|
|
39
|
+
super(400, message, details);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class ConflictError extends HttpError {
|
|
43
|
+
constructor(message = "Conflict", details) {
|
|
44
|
+
super(409, message, details);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class UnprocessableEntityError extends HttpError {
|
|
49
|
+
constructor(message = "Unprocessable Entity", details) {
|
|
50
|
+
super(422, message, details);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class ForbiddenError extends HttpError {
|
|
54
|
+
constructor(message = "Forbidden", details) {
|
|
55
|
+
super(403, message, details);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class UnauthorizedError extends HttpError {
|
|
60
|
+
constructor(message = "Unauthorized", details) {
|
|
61
|
+
super(401, message, details);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class PreconditionFailedError extends HttpError {
|
|
65
|
+
constructor(message = "Precondition Failed", details) {
|
|
66
|
+
super(412, message, details);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../../src/core/contracts/applicationContext.ts
|
|
71
|
+
function getRequiredDependency(dependencies, key) {
|
|
72
|
+
const dependency = dependencies[key];
|
|
73
|
+
if (dependency === undefined) {
|
|
74
|
+
throw new Error(`Required dependency "${String(key)}" is not registered.`);
|
|
75
|
+
}
|
|
76
|
+
return dependency;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ../../src/core/contracts/serviceTokens.ts
|
|
80
|
+
var CORE_CONFIG_TOKEN = "core.config";
|
|
81
|
+
var CORE_CACHE_TOKEN = "core.cache";
|
|
82
|
+
var CORE_QUEUE_TOKEN = "core.queue";
|
|
83
|
+
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
84
|
+
var CORE_AUTH_TOKEN = "core.auth";
|
|
85
|
+
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
86
|
+
|
|
87
|
+
// ../../src/core/logging/logger.ts
|
|
88
|
+
class Logger {
|
|
89
|
+
channel;
|
|
90
|
+
constructor(channel = "app") {
|
|
91
|
+
this.channel = channel;
|
|
92
|
+
}
|
|
93
|
+
write(level, message, context = {}) {
|
|
94
|
+
const entry = {
|
|
95
|
+
level,
|
|
96
|
+
channel: this.channel,
|
|
97
|
+
message,
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
...context
|
|
100
|
+
};
|
|
101
|
+
const line = JSON.stringify(entry);
|
|
102
|
+
if (level === "error") {
|
|
103
|
+
console.error(line);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.log(line);
|
|
107
|
+
}
|
|
108
|
+
debug(message, context) {
|
|
109
|
+
this.write("debug", message, context);
|
|
110
|
+
}
|
|
111
|
+
info(message, context) {
|
|
112
|
+
this.write("info", message, context);
|
|
113
|
+
}
|
|
114
|
+
warn(message, context) {
|
|
115
|
+
this.write("warn", message, context);
|
|
116
|
+
}
|
|
117
|
+
error(message, context) {
|
|
118
|
+
this.write("error", message, context);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var appLogger = new Logger("app");
|
|
122
|
+
|
|
123
|
+
// ../../src/core/runtime/applicationRegistry.ts
|
|
124
|
+
var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
|
|
125
|
+
var activeContext;
|
|
126
|
+
function readStoredApplicationContext() {
|
|
127
|
+
if (activeContext) {
|
|
128
|
+
return activeContext;
|
|
129
|
+
}
|
|
130
|
+
const globalContext = globalThis[APPLICATION_CONTEXT_KEY];
|
|
131
|
+
if (globalContext) {
|
|
132
|
+
activeContext = globalContext;
|
|
133
|
+
}
|
|
134
|
+
return activeContext;
|
|
135
|
+
}
|
|
136
|
+
function setActiveApplicationContext(context) {
|
|
137
|
+
activeContext = context;
|
|
138
|
+
globalThis[APPLICATION_CONTEXT_KEY] = context;
|
|
139
|
+
}
|
|
140
|
+
function requireActiveApplicationContext() {
|
|
141
|
+
const context = readStoredApplicationContext();
|
|
142
|
+
if (!context) {
|
|
143
|
+
throw new Error("The application context has not been bootstrapped.");
|
|
144
|
+
}
|
|
145
|
+
return context;
|
|
146
|
+
}
|
|
147
|
+
function resolveApplicationCache() {
|
|
148
|
+
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
149
|
+
}
|
|
150
|
+
function resolveApplicationQueue() {
|
|
151
|
+
return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
|
|
152
|
+
}
|
|
153
|
+
function resolveApplicationAuth() {
|
|
154
|
+
return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
|
|
155
|
+
}
|
|
156
|
+
function resolveApplicationPolicyGate() {
|
|
157
|
+
return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
|
|
158
|
+
}
|
|
159
|
+
function resolveApplicationConfig() {
|
|
160
|
+
return requireActiveApplicationContext().config;
|
|
161
|
+
}
|
|
162
|
+
function resolveApplicationLogger() {
|
|
163
|
+
return appLogger;
|
|
164
|
+
}
|
|
165
|
+
function resolveApplicationDependencies() {
|
|
166
|
+
return requireActiveApplicationContext().dependencies;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ../../src/core/crypto/nonCryptographicHash.ts
|
|
170
|
+
function nonCryptographicDigest(input) {
|
|
171
|
+
return Bun.hash(input).toString(16);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ../../src/core/http/etag.ts
|
|
175
|
+
function isEtagEnabled() {
|
|
176
|
+
return (process.env.FEATURE_ETAG ?? "true") !== "false";
|
|
177
|
+
}
|
|
178
|
+
function formatWeakEtag(digest) {
|
|
179
|
+
return `W/"${digest}"`;
|
|
180
|
+
}
|
|
181
|
+
function etagFromResource(resource) {
|
|
182
|
+
const version = resource.updated_at ?? resource.created_at ?? "";
|
|
183
|
+
const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
|
|
184
|
+
const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
|
|
185
|
+
return formatWeakEtag(digest);
|
|
186
|
+
}
|
|
187
|
+
function normalizeEtag(value) {
|
|
188
|
+
return value.trim();
|
|
189
|
+
}
|
|
190
|
+
function etagValuesMatch(left, right) {
|
|
191
|
+
return normalizeEtag(left) === normalizeEtag(right);
|
|
192
|
+
}
|
|
193
|
+
function parseEtagList(header) {
|
|
194
|
+
if (!header) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
function ifNoneMatchSatisfied(request, etag) {
|
|
200
|
+
const header = request.headers.get("if-none-match");
|
|
201
|
+
if (!header) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (header.trim() === "*") {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
208
|
+
}
|
|
209
|
+
function ifMatchSatisfied(request, etag) {
|
|
210
|
+
const header = request.headers.get("if-match");
|
|
211
|
+
if (!header) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (header.trim() === "*") {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
|
|
218
|
+
}
|
|
219
|
+
function assertIfMatch(request, etag, options = {}) {
|
|
220
|
+
const header = request.headers.get("if-match");
|
|
221
|
+
if (!header) {
|
|
222
|
+
if (options.required) {
|
|
223
|
+
throw new PreconditionFailedError("If-Match header is required.");
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!ifMatchSatisfied(request, etag)) {
|
|
228
|
+
throw new PreconditionFailedError("Resource ETag does not match If-Match.");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function applyEtagHeaders(headers, etag) {
|
|
232
|
+
const next = new Headers(headers);
|
|
233
|
+
next.set("ETag", etag);
|
|
234
|
+
next.set("Cache-Control", "private, must-revalidate");
|
|
235
|
+
next.append("Vary", "Authorization");
|
|
236
|
+
next.append("Vary", "X-Tenant-Id");
|
|
237
|
+
return next;
|
|
238
|
+
}
|
|
239
|
+
function notModifiedResponse(etag) {
|
|
240
|
+
return new Response(null, {
|
|
241
|
+
status: 304,
|
|
242
|
+
headers: applyEtagHeaders(new Headers, etag)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function applyConditionalGet(request, response, etag) {
|
|
246
|
+
if (!isEtagEnabled()) {
|
|
247
|
+
return response;
|
|
248
|
+
}
|
|
249
|
+
if (ifNoneMatchSatisfied(request, etag)) {
|
|
250
|
+
return notModifiedResponse(etag);
|
|
251
|
+
}
|
|
252
|
+
const headers = applyEtagHeaders(new Headers(response.headers), etag);
|
|
253
|
+
return new Response(response.body, {
|
|
254
|
+
status: response.status,
|
|
255
|
+
statusText: response.statusText,
|
|
256
|
+
headers
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ../../src/core/tenant/tenantContext.ts
|
|
261
|
+
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
262
|
+
|
|
263
|
+
// ../../src/core/http/validation.ts
|
|
264
|
+
function parsePositiveIntParam(value, name = "id") {
|
|
265
|
+
const parsed = Number.parseInt(value, 10);
|
|
266
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
267
|
+
throw new BadRequestError(`Invalid ${name}. Expected a positive integer.`);
|
|
268
|
+
}
|
|
269
|
+
return parsed;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ../../src/core/http/securedRouteModelBinding.ts
|
|
273
|
+
function isMutatingPolicyAction(action) {
|
|
274
|
+
return action === "update" || action === "delete";
|
|
275
|
+
}
|
|
276
|
+
function securedBindRouteModel(param, resolver, authorization, handler) {
|
|
277
|
+
return async (request) => {
|
|
278
|
+
const id = parsePositiveIntParam(String(request.params[param]), String(param));
|
|
279
|
+
const model = await resolver(id, 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
|
+
}
|
|
296
|
+
function securedBindRouteModelByKey(param, resolver, authorization, handler) {
|
|
297
|
+
return async (request) => {
|
|
298
|
+
const key = String(request.params[param] ?? "").trim();
|
|
299
|
+
if (!key) {
|
|
300
|
+
throw new BadRequestError(`Missing route parameter "${String(param)}".`);
|
|
301
|
+
}
|
|
302
|
+
const model = await resolver(key, request);
|
|
303
|
+
const gate = resolveApplicationPolicyGate();
|
|
304
|
+
const auth = resolveApplicationAuth();
|
|
305
|
+
const user = currentAuthUser() ?? await auth.resolve(request);
|
|
306
|
+
gate.authorize(authorization.resource, authorization.action, user, model);
|
|
307
|
+
if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
|
|
308
|
+
assertIfMatch(request, etagFromResource(model), {
|
|
309
|
+
required: authorization.requireIfMatch ?? true
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const response = await handler(request, model);
|
|
313
|
+
if (isEtagEnabled() && authorization.action === "view") {
|
|
314
|
+
return applyConditionalGet(request, response, etagFromResource(model));
|
|
315
|
+
}
|
|
316
|
+
return response;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// ../../src/bootstrap/httpKernel.ts
|
|
320
|
+
import {
|
|
321
|
+
createAuthMiddleware,
|
|
322
|
+
createAuthorizeMiddleware,
|
|
323
|
+
createBodySizeLimitMiddleware,
|
|
324
|
+
createCorsMiddleware,
|
|
325
|
+
createCsrfMiddleware,
|
|
326
|
+
createFlashMiddleware,
|
|
327
|
+
createLoginThrottleMiddleware,
|
|
328
|
+
createMembershipMiddleware,
|
|
329
|
+
createMemoryThrottleMiddleware,
|
|
330
|
+
createMetricsMiddleware,
|
|
331
|
+
createRequestLoggingMiddleware,
|
|
332
|
+
createRequireAbilityMiddleware,
|
|
333
|
+
createRequireAuthMiddleware,
|
|
334
|
+
createRequireGlobalAdminMiddleware,
|
|
335
|
+
createRequireWebAuthMiddleware,
|
|
336
|
+
createSecurityHeadersMiddleware,
|
|
337
|
+
createTenantMiddleware,
|
|
338
|
+
createThrottleMiddleware,
|
|
339
|
+
createTracingMiddleware,
|
|
340
|
+
isPublicReadsEnabled,
|
|
341
|
+
requestIdMiddleware,
|
|
342
|
+
withMiddleware
|
|
343
|
+
} from "@getstrata/core";
|
|
344
|
+
|
|
345
|
+
// ../../src/config/frontend.ts
|
|
346
|
+
function readFrontendMode() {
|
|
347
|
+
const mode = (process.env.FRONTEND_MODE ?? "api").trim();
|
|
348
|
+
if (mode === "server-htmx") {
|
|
349
|
+
return "server-htmx";
|
|
350
|
+
}
|
|
351
|
+
if (mode === "spa-react") {
|
|
352
|
+
return "spa-react";
|
|
353
|
+
}
|
|
354
|
+
return "api";
|
|
355
|
+
}
|
|
356
|
+
function isViewsEnabled() {
|
|
357
|
+
return readFrontendMode() === "server-htmx";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ../../src/config/rateLimit.ts
|
|
361
|
+
var LOCAL_LOGIN_RATE_LIMIT = {
|
|
362
|
+
maxAttempts: 100,
|
|
363
|
+
decaySeconds: 60
|
|
364
|
+
};
|
|
365
|
+
var PRODUCTION_LOGIN_RATE_LIMIT = {
|
|
366
|
+
maxAttempts: 5,
|
|
367
|
+
decaySeconds: 900
|
|
368
|
+
};
|
|
369
|
+
function isLocalAppEnv() {
|
|
370
|
+
return (process.env.APP_ENV ?? "local") === "local";
|
|
371
|
+
}
|
|
372
|
+
function parsePositiveInt(value, fallback) {
|
|
373
|
+
const parsed = Number(value);
|
|
374
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
375
|
+
return fallback;
|
|
376
|
+
}
|
|
377
|
+
return Math.trunc(parsed);
|
|
378
|
+
}
|
|
379
|
+
function resolveLoginRateLimit() {
|
|
380
|
+
const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
|
|
381
|
+
return {
|
|
382
|
+
maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
383
|
+
decaySeconds: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, defaults.decaySeconds)
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function resolveRegisterRateLimit() {
|
|
387
|
+
const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
|
|
388
|
+
return {
|
|
389
|
+
maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
390
|
+
decaySeconds: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS ?? process.env.REGISTER_RATE_LIMIT_WINDOW_MS ? String(Number(process.env.REGISTER_RATE_LIMIT_WINDOW_MS) / 1000) : undefined, defaults.decaySeconds)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ../../src/bootstrap/config.ts
|
|
395
|
+
var APP_PORT_CONFIG_KEY = "app.port";
|
|
396
|
+
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
397
|
+
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
398
|
+
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
399
|
+
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
400
|
+
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
401
|
+
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
402
|
+
var DEFAULT_APP_PORT = 3000;
|
|
403
|
+
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
404
|
+
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
405
|
+
var DEFAULT_CACHE_DRIVER = "array";
|
|
406
|
+
var DEFAULT_API_TOKEN = "";
|
|
407
|
+
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
408
|
+
|
|
409
|
+
// ../../src/bootstrap/httpKernel.ts
|
|
410
|
+
class HttpKernel {
|
|
411
|
+
dependencies;
|
|
412
|
+
constructor(dependencies) {
|
|
413
|
+
this.dependencies = dependencies;
|
|
414
|
+
}
|
|
415
|
+
globalMiddleware() {
|
|
416
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
417
|
+
return [
|
|
418
|
+
createCorsMiddleware(),
|
|
419
|
+
createSecurityHeadersMiddleware(),
|
|
420
|
+
createBodySizeLimitMiddleware(),
|
|
421
|
+
createTracingMiddleware(),
|
|
422
|
+
createMetricsMiddleware(),
|
|
423
|
+
createRequestLoggingMiddleware(),
|
|
424
|
+
requestIdMiddleware,
|
|
425
|
+
createAuthMiddleware(auth),
|
|
426
|
+
createMembershipMiddleware(),
|
|
427
|
+
createTenantMiddleware()
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
group(name) {
|
|
431
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
432
|
+
switch (name) {
|
|
433
|
+
case "authenticated":
|
|
434
|
+
return [createRequireAuthMiddleware(auth)];
|
|
435
|
+
case "web":
|
|
436
|
+
return isViewsEnabled() ? [createFlashMiddleware(), createCsrfMiddleware()] : [];
|
|
437
|
+
case "api": {
|
|
438
|
+
if (!this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
442
|
+
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
443
|
+
if (!redisUrl) {
|
|
444
|
+
const maxAttempts2 = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
445
|
+
return [
|
|
446
|
+
createMemoryThrottleMiddleware({
|
|
447
|
+
maxAttempts: Number.isFinite(maxAttempts2) ? maxAttempts2 : 120,
|
|
448
|
+
decaySeconds: 60
|
|
449
|
+
})
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
const maxAttempts = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
453
|
+
return [
|
|
454
|
+
createThrottleMiddleware({
|
|
455
|
+
redisUrl,
|
|
456
|
+
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
457
|
+
decaySeconds: 60
|
|
458
|
+
})
|
|
459
|
+
];
|
|
460
|
+
}
|
|
461
|
+
default:
|
|
462
|
+
throw new Error(`Unknown middleware group "${name}".`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
wrap(groups, handler) {
|
|
466
|
+
const names = Array.isArray(groups) ? groups : [groups];
|
|
467
|
+
const middleware = names.flatMap((name) => this.group(name));
|
|
468
|
+
if (middleware.length === 0) {
|
|
469
|
+
return handler;
|
|
470
|
+
}
|
|
471
|
+
return withMiddleware(...middleware)(handler);
|
|
472
|
+
}
|
|
473
|
+
wrapApi(handler) {
|
|
474
|
+
return this.wrap(["api", "authenticated"], handler);
|
|
475
|
+
}
|
|
476
|
+
wrapWeb(handler) {
|
|
477
|
+
return handler;
|
|
478
|
+
}
|
|
479
|
+
wrapWebPublicRead(handler) {
|
|
480
|
+
if (isPublicReadsEnabled()) {
|
|
481
|
+
return this.wrapWeb(handler);
|
|
482
|
+
}
|
|
483
|
+
return this.wrapWebAuthenticated(handler);
|
|
484
|
+
}
|
|
485
|
+
wrapWebAuthenticated(handler) {
|
|
486
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
487
|
+
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
488
|
+
}
|
|
489
|
+
wrapWebAbility(ability, handler) {
|
|
490
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
491
|
+
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
492
|
+
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
493
|
+
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
494
|
+
return withMiddleware(...middleware)(handler);
|
|
495
|
+
}
|
|
496
|
+
wrapWebGlobalAdmin(handler) {
|
|
497
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
498
|
+
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
499
|
+
return withMiddleware(...middleware)(handler);
|
|
500
|
+
}
|
|
501
|
+
wrapAuthenticated(handler) {
|
|
502
|
+
return this.wrap("authenticated", handler);
|
|
503
|
+
}
|
|
504
|
+
wrapPublicRead(handler) {
|
|
505
|
+
if (isPublicReadsEnabled()) {
|
|
506
|
+
return handler;
|
|
507
|
+
}
|
|
508
|
+
return this.wrapAuthenticated(handler);
|
|
509
|
+
}
|
|
510
|
+
wrapGlobalAdmin(handler) {
|
|
511
|
+
const middleware = [...this.group("authenticated"), createRequireGlobalAdminMiddleware()];
|
|
512
|
+
return withMiddleware(...middleware)(handler);
|
|
513
|
+
}
|
|
514
|
+
wrapAbility(ability, handler) {
|
|
515
|
+
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
516
|
+
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
517
|
+
const middleware = [...this.group("authenticated"), requireAbility(ability)];
|
|
518
|
+
return withMiddleware(...middleware)(handler);
|
|
519
|
+
}
|
|
520
|
+
wrapPolicy(resource, action, handler) {
|
|
521
|
+
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
522
|
+
const gate = this.dependencies.container.resolve(CORE_POLICY_GATE_TOKEN);
|
|
523
|
+
return withMiddleware(createAuthorizeMiddleware(gate, auth, resource, action))(handler);
|
|
524
|
+
}
|
|
525
|
+
wrapLogin(handler) {
|
|
526
|
+
return this.wrapThrottle("login", resolveLoginRateLimit(), handler);
|
|
527
|
+
}
|
|
528
|
+
wrapRegister(handler) {
|
|
529
|
+
return this.wrapThrottle("register", resolveRegisterRateLimit(), handler);
|
|
530
|
+
}
|
|
531
|
+
wrapThrottle(scope, rateLimit, handler) {
|
|
532
|
+
const middleware = [];
|
|
533
|
+
const memoryKeyPrefix = scope === "login" ? "login-throttle:" : "register-throttle:";
|
|
534
|
+
if (this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
535
|
+
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
536
|
+
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
537
|
+
if (redisUrl) {
|
|
538
|
+
const throttle = scope === "login" ? createLoginThrottleMiddleware({
|
|
539
|
+
redisUrl,
|
|
540
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
541
|
+
decaySeconds: rateLimit.decaySeconds
|
|
542
|
+
}) : createThrottleMiddleware({
|
|
543
|
+
redisUrl,
|
|
544
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
545
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
546
|
+
keyPrefix: memoryKeyPrefix
|
|
547
|
+
});
|
|
548
|
+
middleware.push(throttle);
|
|
549
|
+
} else {
|
|
550
|
+
middleware.push(createMemoryThrottleMiddleware({
|
|
551
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
552
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
553
|
+
keyPrefix: memoryKeyPrefix
|
|
554
|
+
}));
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
middleware.push(createMemoryThrottleMiddleware({
|
|
558
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
559
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
560
|
+
keyPrefix: memoryKeyPrefix
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
if (middleware.length === 0) {
|
|
564
|
+
return handler;
|
|
565
|
+
}
|
|
566
|
+
return withMiddleware(...middleware)(handler);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function createHttpKernel(dependencies) {
|
|
570
|
+
return new HttpKernel(dependencies);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ../../src/bootstrap/web/routing.ts
|
|
574
|
+
function routeParams(request) {
|
|
575
|
+
const normalized = {};
|
|
576
|
+
const raw = request.params;
|
|
577
|
+
if (raw && typeof raw === "object") {
|
|
578
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
579
|
+
normalized[key] = decodeURIComponent(String(value));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return normalized;
|
|
583
|
+
}
|
|
584
|
+
function toRouteRequest(request) {
|
|
585
|
+
const params = routeParams(request);
|
|
586
|
+
Object.defineProperty(request, "params", {
|
|
587
|
+
value: params,
|
|
588
|
+
enumerable: true,
|
|
589
|
+
configurable: true,
|
|
590
|
+
writable: true
|
|
591
|
+
});
|
|
592
|
+
return request;
|
|
593
|
+
}
|
|
594
|
+
function wrapSecuredRouteModelByKey(param, resolver, authorization, handler) {
|
|
595
|
+
const bound = withErrorHandling(securedBindRouteModelByKey(param, resolver, authorization, handler));
|
|
596
|
+
return async (request) => bound(toRouteRequest(request));
|
|
597
|
+
}
|
|
598
|
+
function wrapWebLogin(kernel, handler, onThrottled) {
|
|
599
|
+
return wrapWebThrottle(kernel, "login", handler, onThrottled);
|
|
600
|
+
}
|
|
601
|
+
function wrapWebRegister(kernel, handler, onThrottled) {
|
|
602
|
+
return wrapWebThrottle(kernel, "register", handler, onThrottled);
|
|
603
|
+
}
|
|
604
|
+
function wrapWebThrottle(kernel, scope, handler, onThrottled) {
|
|
605
|
+
const throttled = scope === "login" ? kernel.wrapLogin(handler) : kernel.wrapRegister(handler);
|
|
606
|
+
return async (request) => {
|
|
607
|
+
const response = await throttled(request);
|
|
608
|
+
if (response.status === 429) {
|
|
609
|
+
return onThrottled(request);
|
|
610
|
+
}
|
|
611
|
+
return response;
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function createRouteKernel(dependencies) {
|
|
615
|
+
return createHttpKernel(dependencies);
|
|
616
|
+
}
|
|
617
|
+
export {
|
|
618
|
+
wrapWebRegister,
|
|
619
|
+
wrapWebLogin,
|
|
620
|
+
wrapSecuredRouteModelByKey,
|
|
621
|
+
toRouteRequest,
|
|
622
|
+
routeParams,
|
|
623
|
+
createRouteKernel
|
|
624
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/web/server.ts
|
|
3
|
+
function wrapRouteHandler(handler) {
|
|
4
|
+
return async (request) => {
|
|
5
|
+
const response = await handler(request);
|
|
6
|
+
return response ?? new Response("Not Found", { status: 404 });
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function convertAppRoutesToBunRoutes(routes) {
|
|
10
|
+
const bunRoutes = {};
|
|
11
|
+
for (const [path, handler] of Object.entries(routes)) {
|
|
12
|
+
if (typeof handler === "function") {
|
|
13
|
+
bunRoutes[path] = { GET: wrapRouteHandler(handler) };
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (handler && typeof handler === "object" && !Array.isArray(handler)) {
|
|
17
|
+
const methods = {};
|
|
18
|
+
for (const [method, methodHandler] of Object.entries(handler)) {
|
|
19
|
+
if (typeof methodHandler !== "function") {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
methods[method.toUpperCase()] = wrapRouteHandler(methodHandler);
|
|
23
|
+
}
|
|
24
|
+
if (Object.keys(methods).length > 0) {
|
|
25
|
+
bunRoutes[path] = methods;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return bunRoutes;
|
|
30
|
+
}
|
|
31
|
+
function createWebServer(options) {
|
|
32
|
+
const publicDir = options.publicDir ?? "./public";
|
|
33
|
+
const bunRoutes = options.routes ? convertAppRoutesToBunRoutes(options.routes) : undefined;
|
|
34
|
+
return Bun.serve({
|
|
35
|
+
port: options.port,
|
|
36
|
+
...bunRoutes ? { routes: bunRoutes } : {},
|
|
37
|
+
async fetch(request) {
|
|
38
|
+
await options.onRequest?.(request);
|
|
39
|
+
const url = new URL(request.url);
|
|
40
|
+
if (url.pathname.startsWith("/assets/")) {
|
|
41
|
+
const file = Bun.file(`${publicDir}${url.pathname}`);
|
|
42
|
+
if (await file.exists()) {
|
|
43
|
+
return new Response(file);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (options.handle) {
|
|
47
|
+
const response = await options.handle(request);
|
|
48
|
+
return response ?? new Response("Not Found", { status: 404 });
|
|
49
|
+
}
|
|
50
|
+
return new Response("Not Found", { status: 404 });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
createWebServer,
|
|
56
|
+
convertAppRoutesToBunRoutes
|
|
57
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/web/session.ts
|
|
3
|
+
import { createHash, randomBytes } from "crypto";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/http/cookies.ts
|
|
6
|
+
function readRequestCookie(request, name) {
|
|
7
|
+
const cookies = request.cookies;
|
|
8
|
+
if (cookies && typeof cookies.get === "function") {
|
|
9
|
+
const value = cookies.get(name);
|
|
10
|
+
if (value) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const header = request.headers.get("cookie");
|
|
15
|
+
if (!header) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
for (const part of header.split(";")) {
|
|
19
|
+
const idx = part.indexOf("=");
|
|
20
|
+
if (idx === -1)
|
|
21
|
+
continue;
|
|
22
|
+
const cookieName = part.slice(0, idx).trim();
|
|
23
|
+
if (cookieName !== name)
|
|
24
|
+
continue;
|
|
25
|
+
return decodeURIComponent(part.slice(idx + 1).trim());
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ../../src/bootstrap/web/session.ts
|
|
31
|
+
class CookieSessionStore {
|
|
32
|
+
sql;
|
|
33
|
+
secret;
|
|
34
|
+
cookieName;
|
|
35
|
+
maxAgeSeconds;
|
|
36
|
+
constructor(sql, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14) {
|
|
37
|
+
this.sql = sql;
|
|
38
|
+
this.secret = secret;
|
|
39
|
+
this.cookieName = cookieName;
|
|
40
|
+
this.maxAgeSeconds = maxAgeSeconds;
|
|
41
|
+
}
|
|
42
|
+
cookieHeader(_user, sessionId) {
|
|
43
|
+
const payload = `${sessionId}.${this.sign(sessionId)}`;
|
|
44
|
+
return this.withSecureFlag(`${this.cookieName}=${payload}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${this.maxAgeSeconds}`);
|
|
45
|
+
}
|
|
46
|
+
clearCookieHeader() {
|
|
47
|
+
return this.withSecureFlag(`${this.cookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
|
|
48
|
+
}
|
|
49
|
+
withSecureFlag(header) {
|
|
50
|
+
if (true) {
|
|
51
|
+
return header;
|
|
52
|
+
}
|
|
53
|
+
return header.includes("Secure") ? header : `${header}; Secure`;
|
|
54
|
+
}
|
|
55
|
+
async create(user) {
|
|
56
|
+
const id = randomBytes(32).toString("hex");
|
|
57
|
+
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
58
|
+
await this.sql.unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
59
|
+
id,
|
|
60
|
+
user.id,
|
|
61
|
+
expires
|
|
62
|
+
]);
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
async destroy(sessionId) {
|
|
66
|
+
await this.sql.unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
67
|
+
}
|
|
68
|
+
async read(request) {
|
|
69
|
+
const cookie = readRequestCookie(request, this.cookieName);
|
|
70
|
+
const raw = cookie ?? null;
|
|
71
|
+
if (!raw)
|
|
72
|
+
return null;
|
|
73
|
+
const [sessionId, signature] = raw.split(".");
|
|
74
|
+
if (!sessionId || !signature || signature !== this.sign(sessionId)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const rows = await this.sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
|
|
78
|
+
COALESCE(u.is_admin, false) AS is_admin
|
|
79
|
+
FROM sessions s
|
|
80
|
+
INNER JOIN users u ON u.id = s.user_id
|
|
81
|
+
WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
|
|
82
|
+
const row = rows[0];
|
|
83
|
+
if (!row)
|
|
84
|
+
return null;
|
|
85
|
+
return {
|
|
86
|
+
id: row.user_id,
|
|
87
|
+
name: row.name,
|
|
88
|
+
email: row.email,
|
|
89
|
+
learn_subscriber: row.learn_subscriber,
|
|
90
|
+
is_admin: row.is_admin
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
sign(value) {
|
|
94
|
+
return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
CookieSessionStore
|
|
99
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"import": "./dist/entries/applicationRegistry.js",
|
|
21
21
|
"default": "./dist/entries/applicationRegistry.js"
|
|
22
22
|
},
|
|
23
|
+
"./cache/modelCacheTags": {
|
|
24
|
+
"types": "./dist/bootstrap/cache/modelCacheTags.d.ts",
|
|
25
|
+
"import": "./dist/entries/cache/modelCacheTags.js",
|
|
26
|
+
"default": "./dist/entries/cache/modelCacheTags.js"
|
|
27
|
+
},
|
|
23
28
|
"./config": {
|
|
24
29
|
"types": "./dist/bootstrap/config.d.ts",
|
|
25
30
|
"import": "./dist/entries/config.js",
|
|
@@ -69,6 +74,31 @@
|
|
|
69
74
|
"types": "./dist/bootstrap/providers/view/index.d.ts",
|
|
70
75
|
"import": "./dist/entries/providers/view.js",
|
|
71
76
|
"default": "./dist/entries/providers/view.js"
|
|
77
|
+
},
|
|
78
|
+
"./web/forms": {
|
|
79
|
+
"types": "./dist/bootstrap/web/forms.d.ts",
|
|
80
|
+
"import": "./dist/entries/web/forms.js",
|
|
81
|
+
"default": "./dist/entries/web/forms.js"
|
|
82
|
+
},
|
|
83
|
+
"./web/routing": {
|
|
84
|
+
"types": "./dist/bootstrap/web/routing.d.ts",
|
|
85
|
+
"import": "./dist/entries/web/routing.js",
|
|
86
|
+
"default": "./dist/entries/web/routing.js"
|
|
87
|
+
},
|
|
88
|
+
"./web/server": {
|
|
89
|
+
"types": "./dist/bootstrap/web/server.d.ts",
|
|
90
|
+
"import": "./dist/entries/web/server.js",
|
|
91
|
+
"default": "./dist/entries/web/server.js"
|
|
92
|
+
},
|
|
93
|
+
"./web/session": {
|
|
94
|
+
"types": "./dist/bootstrap/web/session.d.ts",
|
|
95
|
+
"import": "./dist/entries/web/session.js",
|
|
96
|
+
"default": "./dist/entries/web/session.js"
|
|
97
|
+
},
|
|
98
|
+
"./web/slug": {
|
|
99
|
+
"types": "./dist/bootstrap/web/slug.d.ts",
|
|
100
|
+
"import": "./dist/entries/web/slug.js",
|
|
101
|
+
"default": "./dist/entries/web/slug.js"
|
|
72
102
|
}
|
|
73
103
|
},
|
|
74
104
|
"main": "./dist/index.js",
|
|
@@ -82,14 +112,14 @@
|
|
|
82
112
|
"build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external @getstrata/core",
|
|
83
113
|
"build:types": "tsc -p tsconfig.types.json",
|
|
84
114
|
"prepublishOnly": "bun run build",
|
|
85
|
-
"build:subpaths": "bun build entries/applicationRegistry.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/membershipService.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core",
|
|
115
|
+
"build:subpaths": "bun build entries/applicationRegistry.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/membershipService.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.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",
|
|
86
116
|
"build:shims": "true"
|
|
87
117
|
},
|
|
88
118
|
"publishConfig": {
|
|
89
119
|
"access": "public"
|
|
90
120
|
},
|
|
91
121
|
"peerDependencies": {
|
|
92
|
-
"@getstrata/core": "^0.5.
|
|
122
|
+
"@getstrata/core": "^0.5.25",
|
|
93
123
|
"typescript": "^5.9.0"
|
|
94
124
|
}
|
|
95
125
|
}
|