@getstrata/bootstrap 0.2.2 → 0.2.3
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/web/forms.d.ts +1 -5
- package/dist/bootstrap/web/index.d.ts +1 -1
- package/dist/bootstrap/web/server.d.ts +7 -1
- package/dist/bootstrap/web/session.d.ts +0 -1
- package/dist/core/crypto/nonCryptographicHash.d.ts +2 -0
- package/dist/core/http/cookies.d.ts +4 -0
- package/dist/core/http/csrfProtection.d.ts +12 -0
- package/dist/framework/public-api.d.ts +4 -0
- package/dist/index.js +108 -103
- package/package.json +2 -2
|
@@ -1,10 +1,6 @@
|
|
|
1
|
+
export { type CsrfProtectionOptions, createCsrfProtection, } from "../../core/http/csrfProtection.ts";
|
|
1
2
|
export interface ParsedForm {
|
|
2
3
|
fields: Record<string, string>;
|
|
3
4
|
files: Record<string, File>;
|
|
4
5
|
}
|
|
5
6
|
export declare function parseFormBody(request: Request): Promise<ParsedForm>;
|
|
6
|
-
export declare function createCsrfProtection(secret: string): {
|
|
7
|
-
generate: (sessionKey: string) => string;
|
|
8
|
-
verify: (token: string | undefined, maxAgeMs?: number) => boolean;
|
|
9
|
-
secret: string;
|
|
10
|
-
};
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Web-focused bootstrap utilities for sibling apps (getstrata, marketing sites).
|
|
3
3
|
*/
|
|
4
4
|
export { createCsrfProtection, type ParsedForm, parseFormBody } from "./forms.ts";
|
|
5
|
-
export { createWebServer, type WebServerOptions } from "./server.ts";
|
|
5
|
+
export { convertAppRoutesToBunRoutes, createWebServer, type WebServerOptions } from "./server.ts";
|
|
6
6
|
export { CookieSessionStore, type SessionUser } from "./session.ts";
|
|
7
7
|
export { slugify } from "./slug.ts";
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { BunRequest } from "bun";
|
|
2
|
+
import type { AppRouteMap } from "../contracts.ts";
|
|
1
3
|
export interface WebServerOptions {
|
|
2
4
|
port: number;
|
|
3
|
-
handle
|
|
5
|
+
handle?: (request: Request) => Promise<Response | null> | Response | null;
|
|
6
|
+
routes?: AppRouteMap;
|
|
4
7
|
publicDir?: string;
|
|
5
8
|
onRequest?: (request: Request) => Promise<void> | void;
|
|
6
9
|
}
|
|
10
|
+
type BunRouteHandler = (request: BunRequest) => Response | Promise<Response>;
|
|
11
|
+
declare function convertAppRoutesToBunRoutes(routes: AppRouteMap): Record<string, Record<string, BunRouteHandler>>;
|
|
7
12
|
export declare function createWebServer(options: WebServerOptions): Bun.Server<undefined>;
|
|
13
|
+
export { convertAppRoutesToBunRoutes };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const DEFAULT_CSRF_TTL_MS: number;
|
|
2
|
+
interface CsrfProtectionOptions {
|
|
3
|
+
expiresIn?: number;
|
|
4
|
+
maxAge?: number;
|
|
5
|
+
}
|
|
6
|
+
declare function createCsrfProtection(secret: string, options?: CsrfProtectionOptions): {
|
|
7
|
+
generate(_sessionKey?: string): string;
|
|
8
|
+
verify(token: string | undefined, _sessionKey?: string): boolean;
|
|
9
|
+
secret: string;
|
|
10
|
+
};
|
|
11
|
+
export type { CsrfProtectionOptions };
|
|
12
|
+
export { createCsrfProtection, DEFAULT_CSRF_TTL_MS };
|
|
@@ -14,6 +14,7 @@ export { CACHE_TAGS } from "../core/cache/tags.ts";
|
|
|
14
14
|
export type { DatabaseConnection } from "../core/database/baseRepository.ts";
|
|
15
15
|
export { default as BaseRepository } from "../core/database/baseRepository.ts";
|
|
16
16
|
export { bindDatabaseConnection } from "../core/database/bindConnection.ts";
|
|
17
|
+
export { createDatabaseConnection } from "../core/database/connection.ts";
|
|
17
18
|
export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
|
|
18
19
|
export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
|
|
19
20
|
export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner.ts";
|
|
@@ -36,7 +37,10 @@ export { BadRequestError, ConflictError, ForbiddenError, NotFoundError, Precondi
|
|
|
36
37
|
export { EventBus } from "../core/events/eventBus.ts";
|
|
37
38
|
export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
|
|
38
39
|
export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
|
|
40
|
+
export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
|
|
39
41
|
export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
|
|
42
|
+
export { createCsrfProtection } from "../core/http/csrfProtection.ts";
|
|
43
|
+
export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
|
|
40
44
|
export { assertIfMatch, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
|
|
41
45
|
export { FormRequest } from "../core/http/formRequest.ts";
|
|
42
46
|
export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
|
package/dist/index.js
CHANGED
|
@@ -3748,76 +3748,61 @@ function htmlResponse(html, init = {}) {
|
|
|
3748
3748
|
});
|
|
3749
3749
|
}
|
|
3750
3750
|
// ../../src/core/http/csrfToken.ts
|
|
3751
|
-
import {
|
|
3751
|
+
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3752
|
+
|
|
3753
|
+
// ../../src/core/http/cookies.ts
|
|
3754
|
+
function readRequestCookie(request, name) {
|
|
3755
|
+
const cookies = request.cookies;
|
|
3756
|
+
if (cookies && typeof cookies.get === "function") {
|
|
3757
|
+
const value = cookies.get(name);
|
|
3758
|
+
if (value) {
|
|
3759
|
+
return value;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
const header = request.headers.get("cookie");
|
|
3763
|
+
if (!header) {
|
|
3764
|
+
return null;
|
|
3765
|
+
}
|
|
3766
|
+
for (const part of header.split(";")) {
|
|
3767
|
+
const idx = part.indexOf("=");
|
|
3768
|
+
if (idx === -1)
|
|
3769
|
+
continue;
|
|
3770
|
+
const cookieName = part.slice(0, idx).trim();
|
|
3771
|
+
if (cookieName !== name)
|
|
3772
|
+
continue;
|
|
3773
|
+
return decodeURIComponent(part.slice(idx + 1).trim());
|
|
3774
|
+
}
|
|
3775
|
+
return null;
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
// ../../src/core/http/csrfToken.ts
|
|
3752
3779
|
var CSRF_COOKIE = "workhub_csrf";
|
|
3753
3780
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
3754
3781
|
function resolveCsrfSecret() {
|
|
3755
3782
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
3756
3783
|
}
|
|
3757
|
-
function
|
|
3758
|
-
|
|
3759
|
-
const signature = createHmac3("sha256", resolveCsrfSecret()).update(payload).digest("hex");
|
|
3760
|
-
return `${payload}.${signature}`;
|
|
3761
|
-
}
|
|
3762
|
-
function readCsrfCookie(request) {
|
|
3763
|
-
const cookieHeader = request.headers.get("cookie");
|
|
3764
|
-
if (!cookieHeader) {
|
|
3765
|
-
return null;
|
|
3766
|
-
}
|
|
3767
|
-
for (const part of cookieHeader.split(";")) {
|
|
3768
|
-
const [name, ...rest] = part.trim().split("=");
|
|
3769
|
-
if (name === CSRF_COOKIE) {
|
|
3770
|
-
return decodeURIComponent(rest.join("="));
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
return null;
|
|
3784
|
+
function csrfVerifyOptions() {
|
|
3785
|
+
return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
|
|
3774
3786
|
}
|
|
3775
|
-
function
|
|
3776
|
-
const
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
const [token, issuedAtRaw, cookieSignature] = parts;
|
|
3781
|
-
if (!token || !issuedAtRaw || !cookieSignature) {
|
|
3782
|
-
return null;
|
|
3783
|
-
}
|
|
3784
|
-
const issuedAt = Number.parseInt(issuedAtRaw, 10);
|
|
3785
|
-
if (!Number.isFinite(issuedAt)) {
|
|
3786
|
-
return null;
|
|
3787
|
-
}
|
|
3788
|
-
if (Date.now() - issuedAt > CSRF_TTL_MS) {
|
|
3789
|
-
return null;
|
|
3790
|
-
}
|
|
3791
|
-
const expectedSignature = signCsrfToken(token, issuedAt).split(".").pop();
|
|
3792
|
-
if (!expectedSignature) {
|
|
3793
|
-
return null;
|
|
3794
|
-
}
|
|
3795
|
-
const expectedBuffer = Buffer.from(expectedSignature);
|
|
3796
|
-
const actualBuffer = Buffer.from(cookieSignature);
|
|
3797
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
3798
|
-
return null;
|
|
3799
|
-
}
|
|
3800
|
-
if (!timingSafeEqual2(expectedBuffer, actualBuffer)) {
|
|
3801
|
-
return null;
|
|
3787
|
+
function tokensMatch(left, right) {
|
|
3788
|
+
const leftBuffer = Buffer.from(left);
|
|
3789
|
+
const rightBuffer = Buffer.from(right);
|
|
3790
|
+
if (leftBuffer.length !== rightBuffer.length) {
|
|
3791
|
+
return false;
|
|
3802
3792
|
}
|
|
3803
|
-
return
|
|
3793
|
+
return timingSafeEqual2(leftBuffer, rightBuffer);
|
|
3804
3794
|
}
|
|
3805
3795
|
function createCsrfTokenCookie() {
|
|
3806
|
-
const token =
|
|
3807
|
-
const issuedAt = Date.now();
|
|
3808
|
-
const value = signCsrfToken(token, issuedAt);
|
|
3796
|
+
const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
|
|
3809
3797
|
return {
|
|
3810
3798
|
token,
|
|
3811
|
-
cookie: `${CSRF_COOKIE}=${encodeURIComponent(
|
|
3799
|
+
cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}`
|
|
3812
3800
|
};
|
|
3813
3801
|
}
|
|
3814
3802
|
function resolveCsrfToken(request) {
|
|
3815
|
-
const cookieValue =
|
|
3816
|
-
if (cookieValue) {
|
|
3817
|
-
|
|
3818
|
-
if (parsed) {
|
|
3819
|
-
return { token: parsed.token };
|
|
3820
|
-
}
|
|
3803
|
+
const cookieValue = readRequestCookie(request, CSRF_COOKIE);
|
|
3804
|
+
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
3805
|
+
return { token: cookieValue };
|
|
3821
3806
|
}
|
|
3822
3807
|
return createCsrfTokenCookie();
|
|
3823
3808
|
}
|
|
@@ -3840,6 +3825,10 @@ async function readSubmittedCsrfTokenFromBody(request) {
|
|
|
3840
3825
|
if (typeof field === "string" && field.trim().length > 0) {
|
|
3841
3826
|
return field.trim();
|
|
3842
3827
|
}
|
|
3828
|
+
const legacyField = formData.get("_csrf");
|
|
3829
|
+
if (typeof legacyField === "string" && legacyField.trim().length > 0) {
|
|
3830
|
+
return legacyField.trim();
|
|
3831
|
+
}
|
|
3843
3832
|
}
|
|
3844
3833
|
return null;
|
|
3845
3834
|
}
|
|
@@ -3847,20 +3836,14 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
3847
3836
|
if (!submittedToken) {
|
|
3848
3837
|
return false;
|
|
3849
3838
|
}
|
|
3850
|
-
const cookieValue =
|
|
3839
|
+
const cookieValue = readRequestCookie(request, CSRF_COOKIE);
|
|
3851
3840
|
if (!cookieValue) {
|
|
3852
3841
|
return false;
|
|
3853
3842
|
}
|
|
3854
|
-
|
|
3855
|
-
if (!parsed) {
|
|
3843
|
+
if (!tokensMatch(submittedToken, cookieValue)) {
|
|
3856
3844
|
return false;
|
|
3857
3845
|
}
|
|
3858
|
-
|
|
3859
|
-
const expectedBuffer = Buffer.from(parsed.token);
|
|
3860
|
-
if (submittedBuffer.length !== expectedBuffer.length) {
|
|
3861
|
-
return false;
|
|
3862
|
-
}
|
|
3863
|
-
return timingSafeEqual2(submittedBuffer, expectedBuffer);
|
|
3846
|
+
return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
|
|
3864
3847
|
}
|
|
3865
3848
|
function resolveCsrfTokenForRequest(request) {
|
|
3866
3849
|
const metaToken = currentRequestMeta().csrfToken;
|
|
@@ -3871,14 +3854,14 @@ function resolveCsrfTokenForRequest(request) {
|
|
|
3871
3854
|
}
|
|
3872
3855
|
|
|
3873
3856
|
// ../../src/core/http/flashSession.ts
|
|
3874
|
-
import { createHmac as
|
|
3857
|
+
import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
3875
3858
|
var FLASH_COOKIE = "workhub_flash";
|
|
3876
3859
|
var FLASH_TTL_MS = 60 * 1000;
|
|
3877
3860
|
function resolveFlashSecret() {
|
|
3878
3861
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
|
|
3879
3862
|
}
|
|
3880
3863
|
function signFlashPayload(payload, issuedAt) {
|
|
3881
|
-
const signature =
|
|
3864
|
+
const signature = createHmac3("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
|
|
3882
3865
|
return `${payload}.${issuedAt}.${signature}`;
|
|
3883
3866
|
}
|
|
3884
3867
|
function readFlashCookie(request) {
|
|
@@ -4967,9 +4950,9 @@ function createTenantMiddleware() {
|
|
|
4967
4950
|
}
|
|
4968
4951
|
|
|
4969
4952
|
// ../../src/core/tracing/otel.ts
|
|
4970
|
-
import { randomBytes
|
|
4953
|
+
import { randomBytes } from "crypto";
|
|
4971
4954
|
function randomHex(bytes) {
|
|
4972
|
-
return
|
|
4955
|
+
return randomBytes(bytes).toString("hex");
|
|
4973
4956
|
}
|
|
4974
4957
|
function createSpan(input) {
|
|
4975
4958
|
const spanId = randomHex(8);
|
|
@@ -5302,6 +5285,25 @@ function prefixRouteMap(prefix, routes) {
|
|
|
5302
5285
|
}
|
|
5303
5286
|
return prefixed;
|
|
5304
5287
|
}
|
|
5288
|
+
// ../../src/core/http/csrfProtection.ts
|
|
5289
|
+
var DEFAULT_CSRF_TTL_MS = 60 * 60 * 1000;
|
|
5290
|
+
function createCsrfProtection(secret, options = {}) {
|
|
5291
|
+
const expiresIn = options.expiresIn ?? DEFAULT_CSRF_TTL_MS;
|
|
5292
|
+
const maxAge = options.maxAge ?? expiresIn;
|
|
5293
|
+
return {
|
|
5294
|
+
generate(_sessionKey) {
|
|
5295
|
+
return Bun.CSRF.generate(secret, { expiresIn });
|
|
5296
|
+
},
|
|
5297
|
+
verify(token, _sessionKey) {
|
|
5298
|
+
if (!token) {
|
|
5299
|
+
return false;
|
|
5300
|
+
}
|
|
5301
|
+
return Bun.CSRF.verify(token, { secret, maxAge });
|
|
5302
|
+
},
|
|
5303
|
+
secret
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5305
5307
|
// ../../src/bootstrap/web/forms.ts
|
|
5306
5308
|
async function parseFormBody(request) {
|
|
5307
5309
|
const contentType = request.headers.get("content-type") ?? "";
|
|
@@ -5331,30 +5333,41 @@ async function parseFormBody(request) {
|
|
|
5331
5333
|
}
|
|
5332
5334
|
return { fields, files };
|
|
5333
5335
|
}
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
const
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5336
|
+
// ../../src/bootstrap/web/server.ts
|
|
5337
|
+
function wrapRouteHandler2(handler) {
|
|
5338
|
+
return async (request) => {
|
|
5339
|
+
const response = await handler(request);
|
|
5340
|
+
return response ?? new Response("Not Found", { status: 404 });
|
|
5341
|
+
};
|
|
5342
|
+
}
|
|
5343
|
+
function convertAppRoutesToBunRoutes(routes) {
|
|
5344
|
+
const bunRoutes = {};
|
|
5345
|
+
for (const [path, handler] of Object.entries(routes)) {
|
|
5346
|
+
if (typeof handler === "function") {
|
|
5347
|
+
bunRoutes[path] = { GET: wrapRouteHandler2(handler) };
|
|
5348
|
+
continue;
|
|
5349
|
+
}
|
|
5350
|
+
if (handler && typeof handler === "object" && !Array.isArray(handler)) {
|
|
5351
|
+
const methods = {};
|
|
5352
|
+
for (const [method, methodHandler] of Object.entries(handler)) {
|
|
5353
|
+
if (typeof methodHandler !== "function") {
|
|
5354
|
+
continue;
|
|
5355
|
+
}
|
|
5356
|
+
methods[method.toUpperCase()] = wrapRouteHandler2(methodHandler);
|
|
5357
|
+
}
|
|
5358
|
+
if (Object.keys(methods).length > 0) {
|
|
5359
|
+
bunRoutes[path] = methods;
|
|
5360
|
+
}
|
|
5348
5361
|
}
|
|
5349
|
-
return true;
|
|
5350
5362
|
}
|
|
5351
|
-
return
|
|
5363
|
+
return bunRoutes;
|
|
5352
5364
|
}
|
|
5353
|
-
// ../../src/bootstrap/web/server.ts
|
|
5354
5365
|
function createWebServer(options) {
|
|
5355
5366
|
const publicDir = options.publicDir ?? "./public";
|
|
5367
|
+
const bunRoutes = options.routes ? convertAppRoutesToBunRoutes(options.routes) : undefined;
|
|
5356
5368
|
return Bun.serve({
|
|
5357
5369
|
port: options.port,
|
|
5370
|
+
...bunRoutes ? { routes: bunRoutes } : {},
|
|
5358
5371
|
async fetch(request) {
|
|
5359
5372
|
await options.onRequest?.(request);
|
|
5360
5373
|
const url = new URL(request.url);
|
|
@@ -5364,14 +5377,16 @@ function createWebServer(options) {
|
|
|
5364
5377
|
return new Response(file);
|
|
5365
5378
|
}
|
|
5366
5379
|
}
|
|
5367
|
-
|
|
5368
|
-
|
|
5380
|
+
if (options.handle) {
|
|
5381
|
+
const response = await options.handle(request);
|
|
5382
|
+
return response ?? new Response("Not Found", { status: 404 });
|
|
5383
|
+
}
|
|
5384
|
+
return new Response("Not Found", { status: 404 });
|
|
5369
5385
|
}
|
|
5370
5386
|
});
|
|
5371
5387
|
}
|
|
5372
5388
|
// ../../src/bootstrap/web/session.ts
|
|
5373
|
-
import { createHash, randomBytes as
|
|
5374
|
-
|
|
5389
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
5375
5390
|
class CookieSessionStore {
|
|
5376
5391
|
sql;
|
|
5377
5392
|
secret;
|
|
@@ -5397,7 +5412,7 @@ class CookieSessionStore {
|
|
|
5397
5412
|
return header.includes("Secure") ? header : `${header}; Secure`;
|
|
5398
5413
|
}
|
|
5399
5414
|
async create(user) {
|
|
5400
|
-
const id =
|
|
5415
|
+
const id = randomBytes2(32).toString("hex");
|
|
5401
5416
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
5402
5417
|
await this.sql.unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
5403
5418
|
id,
|
|
@@ -5410,8 +5425,8 @@ class CookieSessionStore {
|
|
|
5410
5425
|
await this.sql.unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
5411
5426
|
}
|
|
5412
5427
|
async read(request) {
|
|
5413
|
-
const cookie =
|
|
5414
|
-
const raw = cookie
|
|
5428
|
+
const cookie = readRequestCookie(request, this.cookieName);
|
|
5429
|
+
const raw = cookie ?? null;
|
|
5415
5430
|
if (!raw)
|
|
5416
5431
|
return null;
|
|
5417
5432
|
const [sessionId, signature] = raw.split(".");
|
|
@@ -5435,16 +5450,6 @@ class CookieSessionStore {
|
|
|
5435
5450
|
sign(value) {
|
|
5436
5451
|
return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
|
|
5437
5452
|
}
|
|
5438
|
-
parseCookie(header) {
|
|
5439
|
-
const out = {};
|
|
5440
|
-
for (const part of header.split(";")) {
|
|
5441
|
-
const idx = part.indexOf("=");
|
|
5442
|
-
if (idx === -1)
|
|
5443
|
-
continue;
|
|
5444
|
-
out[part.slice(0, idx).trim()] = decodeURIComponent(part.slice(idx + 1).trim());
|
|
5445
|
-
}
|
|
5446
|
-
return out;
|
|
5447
|
-
}
|
|
5448
5453
|
}
|
|
5449
5454
|
// ../../src/bootstrap/web/slug.ts
|
|
5450
5455
|
function slugify(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"access": "public"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@getstrata/core": "^0.5.
|
|
76
|
+
"@getstrata/core": "^0.5.6",
|
|
77
77
|
"typescript": "^5.9.0"
|
|
78
78
|
}
|
|
79
79
|
}
|