@getstrata/bootstrap 0.2.3 → 0.2.4

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,7 @@
1
+ declare class App {
2
+ private server?;
3
+ serve(): void;
4
+ stop(force?: boolean): void;
5
+ private resolvePort;
6
+ }
7
+ export default App;
@@ -0,0 +1,3 @@
1
+ import type { AppDependencies, AppRouteMap } from "./contracts";
2
+ declare function createRoutes(dependencies: AppDependencies): AppRouteMap;
3
+ export { createRoutes };
@@ -0,0 +1,5 @@
1
+ import type { AppDependencies, AppRouteMap } from "./contracts";
2
+ declare const SPA_DIST_DIRECTORY: string;
3
+ declare function createSpaRoutes(_dependencies: AppDependencies): AppRouteMap;
4
+ declare function mergeSpaRoutes(dependencies: AppDependencies, routes: AppRouteMap): AppRouteMap;
5
+ export { createSpaRoutes, mergeSpaRoutes, SPA_DIST_DIRECTORY };
@@ -0,0 +1,4 @@
1
+ import { createAppContext } from "./context";
2
+ declare function createAppDependencies(): import("./contracts").AppDependencies;
3
+ export type { AppContext, AppDependencies } from "./contracts";
4
+ export { createAppContext, createAppDependencies };
@@ -0,0 +1,8 @@
1
+ import type { AppDependencies } from "./contracts";
2
+ declare function checkDatabase(): Promise<boolean>;
3
+ declare function checkRedis(redisUrl: string): Promise<boolean>;
4
+ declare function createHealthRoutes(dependencies: AppDependencies): {
5
+ "/health": () => Promise<Response>;
6
+ "/ready": () => Promise<Response>;
7
+ };
8
+ export { checkDatabase, checkRedis, createHealthRoutes };
@@ -0,0 +1,4 @@
1
+ declare function createMetricsRoutes(): {
2
+ "/metrics": () => Promise<Response>;
3
+ };
4
+ export { createMetricsRoutes };
@@ -0,0 +1,4 @@
1
+ import { type Middleware } from "../core/http/middleware";
2
+ import type { AppDependencies } from "./contracts";
3
+ declare function createDefaultMiddleware(dependencies: AppDependencies): Middleware[];
4
+ export { createDefaultMiddleware };
@@ -13,4 +13,4 @@ export { createWebRoutes, mergeWebRoutes } from "./createWebRoutes.ts";
13
13
  export { createHttpKernel, type HttpKernel, type MiddlewareGroupName } from "./httpKernel.ts";
14
14
  export { prefixRouteMap } from "./prefixRouteMap.ts";
15
15
  export { coreProviders } from "./providers/index.ts";
16
- export { CookieSessionStore, createCsrfProtection, createWebServer, type ParsedForm, parseFormBody, type SessionUser, slugify, type WebServerOptions, } from "./web/index.ts";
16
+ export { CookieSessionStore, createCsrfProtection, createRouteKernel, createWebServer, type ParsedForm, parseFormBody, routeParams, type SessionUser, slugify, toRouteRequest, type WebServerOptions, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./web/index.ts";
@@ -0,0 +1,2 @@
1
+ declare const routes: import("./contracts").AppRouteMap;
2
+ export { routes };
@@ -0,0 +1,24 @@
1
+ import type { RouteHandler } from "../core/http/middleware";
2
+ import type { AppDependencies } from "./contracts";
3
+ declare function createScimRoutes(dependencies: AppDependencies): {
4
+ "/scim/v2/ServiceProviderConfig": {
5
+ GET: RouteHandler;
6
+ };
7
+ "/scim/v2/Users": {
8
+ GET: RouteHandler;
9
+ POST: RouteHandler;
10
+ };
11
+ "/scim/v2/Users/:id": {
12
+ GET: RouteHandler;
13
+ PATCH: RouteHandler;
14
+ DELETE: RouteHandler;
15
+ };
16
+ "/scim/v2/Groups": {
17
+ GET: RouteHandler;
18
+ };
19
+ "/scim/v2/Groups/:id": {
20
+ GET: RouteHandler;
21
+ PATCH: RouteHandler;
22
+ };
23
+ };
24
+ export { createScimRoutes };
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,7 @@
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 { createRouteKernel, routeParams, toRouteRequest, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./routing.ts";
5
6
  export { convertAppRoutesToBunRoutes, createWebServer, type WebServerOptions } from "./server.ts";
6
7
  export { CookieSessionStore, type SessionUser } from "./session.ts";
7
8
  export { slugify } from "./slug.ts";
@@ -0,0 +1,21 @@
1
+ import type { RouteHandler, RouteRequest } from "@getstrata/core";
2
+ import type { AppDependencies } from "../contracts.ts";
3
+ import type { HttpKernel } from "../httpKernel.ts";
4
+ /** Read Bun native `:param` values from a route handler request. */
5
+ export declare function routeParams(request: Request): Record<string, string>;
6
+ /**
7
+ * Attach decoded route params to Bun's native Request for RouteRequest handlers.
8
+ * Mutates the request in place so instanceof Request and Bun internals stay valid.
9
+ */
10
+ export declare function toRouteRequest<TParams extends Record<string, string>>(request: Request): RouteRequest<TParams>;
11
+ /** Laravel-style secured route-model binding for string keys (slugs, UUIDs). */
12
+ export declare function wrapSecuredRouteModelByKey<TParams extends Record<string, string>, TModel, TParam extends keyof TParams & string>(param: TParam, resolver: (key: string, request: RouteRequest<TParams>) => Promise<TModel>, authorization: {
13
+ resource: string;
14
+ action: "view" | "create" | "update" | "delete";
15
+ }, handler: (request: RouteRequest<TParams>, model: TModel) => Response | Promise<Response>): RouteHandler;
16
+ /** Converts kernel login throttle 429 JSON into an HTML response for web forms. */
17
+ export declare function wrapWebLogin(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
18
+ /** Converts kernel register throttle 429 JSON into an HTML response for web forms. */
19
+ export declare function wrapWebRegister(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
20
+ /** Convenience alias: HttpKernel is the Laravel-style router middleware wrapper. */
21
+ export declare function createRouteKernel(dependencies: AppDependencies): HttpKernel;
@@ -0,0 +1,3 @@
1
+ import type { Middleware } from "../http/middleware";
2
+ declare function createScimAuthMiddleware(): Middleware;
3
+ export { createScimAuthMiddleware };
@@ -0,0 +1,9 @@
1
+ import type { Middleware } from "./middleware";
2
+ interface ScimThrottleOptions {
3
+ redisUrl?: string;
4
+ maxAttempts: number;
5
+ decaySeconds: number;
6
+ }
7
+ declare function createScimThrottleMiddleware(options: ScimThrottleOptions): Middleware;
8
+ export type { ScimThrottleOptions };
9
+ export { createScimThrottleMiddleware };
@@ -0,0 +1,3 @@
1
+ declare function parseScimTenantTokens(raw: string | undefined): Map<number, string>;
2
+ declare function resolveScimTenantFromToken(token: string): number | null;
3
+ export { parseScimTenantTokens, resolveScimTenantFromToken };
@@ -0,0 +1,2 @@
1
+ declare function timingSafeCompareString(left: string, right: string): boolean;
2
+ export { timingSafeCompareString };
package/dist/index.js CHANGED
@@ -5333,6 +5333,51 @@ async function parseFormBody(request) {
5333
5333
  }
5334
5334
  return { fields, files };
5335
5335
  }
5336
+ // ../../src/bootstrap/web/routing.ts
5337
+ import { securedBindRouteModelByKey, withErrorHandling } from "@getstrata/core";
5338
+ function routeParams(request) {
5339
+ const normalized = {};
5340
+ const raw = request.params;
5341
+ if (raw && typeof raw === "object") {
5342
+ for (const [key, value] of Object.entries(raw)) {
5343
+ normalized[key] = decodeURIComponent(String(value));
5344
+ }
5345
+ }
5346
+ return normalized;
5347
+ }
5348
+ function toRouteRequest(request) {
5349
+ const params = routeParams(request);
5350
+ Object.defineProperty(request, "params", {
5351
+ value: params,
5352
+ enumerable: true,
5353
+ configurable: true,
5354
+ writable: true
5355
+ });
5356
+ return request;
5357
+ }
5358
+ function wrapSecuredRouteModelByKey(param, resolver, authorization, handler) {
5359
+ const bound = withErrorHandling(securedBindRouteModelByKey(param, resolver, authorization, handler));
5360
+ return async (request) => bound(toRouteRequest(request));
5361
+ }
5362
+ function wrapWebLogin(kernel, handler, onThrottled) {
5363
+ return wrapWebThrottle(kernel, "login", handler, onThrottled);
5364
+ }
5365
+ function wrapWebRegister(kernel, handler, onThrottled) {
5366
+ return wrapWebThrottle(kernel, "register", handler, onThrottled);
5367
+ }
5368
+ function wrapWebThrottle(kernel, scope, handler, onThrottled) {
5369
+ const throttled = scope === "login" ? kernel.wrapLogin(handler) : kernel.wrapRegister(handler);
5370
+ return async (request) => {
5371
+ const response = await throttled(request);
5372
+ if (response.status === 429) {
5373
+ return onThrottled(request);
5374
+ }
5375
+ return response;
5376
+ };
5377
+ }
5378
+ function createRouteKernel(dependencies) {
5379
+ return createHttpKernel(dependencies);
5380
+ }
5336
5381
  // ../../src/bootstrap/web/server.ts
5337
5382
  function wrapRouteHandler2(handler) {
5338
5383
  return async (request) => {
@@ -5456,11 +5501,16 @@ function slugify(value) {
5456
5501
  return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
5457
5502
  }
5458
5503
  export {
5504
+ wrapWebRegister,
5505
+ wrapWebLogin,
5506
+ wrapSecuredRouteModelByKey,
5507
+ toRouteRequest,
5459
5508
  slugify,
5460
5509
  setActiveApplicationContext2 as setActiveApplicationContext,
5461
5510
  scheduleRunCommand,
5462
5511
  runProviderPhase,
5463
5512
  runDueScheduledTasks,
5513
+ routeParams,
5464
5514
  resolveService,
5465
5515
  resolveApplicationQueue2 as resolveApplicationQueue,
5466
5516
  prefixRouteMap,
@@ -5469,6 +5519,7 @@ export {
5469
5519
  getRequiredDependency,
5470
5520
  createWebServer,
5471
5521
  createWebRoutes,
5522
+ createRouteKernel,
5472
5523
  createHttpKernel,
5473
5524
  createCsrfProtection,
5474
5525
  createAppContext,
@@ -0,0 +1,15 @@
1
+ import type { AppDependencies } from "@getstrata/bootstrap/contracts";
2
+ declare class ScimController {
3
+ private readonly service;
4
+ constructor(dependencies: AppDependencies);
5
+ readonly serviceProviderConfig: () => Promise<Response>;
6
+ readonly listUsers: (request: Request) => Promise<Response>;
7
+ readonly createUser: (request: Request) => Promise<Response>;
8
+ readonly showUser: (request: Request) => Promise<Response>;
9
+ readonly patchUser: (request: Request) => Promise<Response>;
10
+ readonly deleteUser: (request: Request) => Promise<Response>;
11
+ readonly listGroups: (request: Request) => Promise<Response>;
12
+ readonly showGroup: (request: Request) => Promise<Response>;
13
+ readonly patchGroup: (request: Request) => Promise<Response>;
14
+ }
15
+ export default ScimController;
@@ -0,0 +1,10 @@
1
+ import { type EtagVersioned } from "@getstrata/core/http/etag";
2
+ interface ScimResponseOptions {
3
+ status?: number;
4
+ request?: Request;
5
+ etagSource?: EtagVersioned;
6
+ }
7
+ declare function scimResponse(data: unknown, options?: ScimResponseOptions): Response;
8
+ declare function assertScimIfMatch(request: Request, etagSource: EtagVersioned): void;
9
+ export type { ScimResponseOptions };
10
+ export { assertScimIfMatch, scimResponse };
@@ -0,0 +1,217 @@
1
+ import type { AppDependencies } from "@getstrata/bootstrap/contracts";
2
+ import OrganizationMemberRepository from "../organization/memberRepository";
3
+ import type UserRepository from "../user/repository";
4
+ interface ScimUserPayload {
5
+ userName?: string;
6
+ name?: {
7
+ formatted?: string;
8
+ };
9
+ active?: boolean;
10
+ emails?: Array<{
11
+ value: string;
12
+ primary?: boolean;
13
+ }>;
14
+ }
15
+ interface ScimPatchOperation {
16
+ op: string;
17
+ path?: string;
18
+ value?: unknown;
19
+ }
20
+ declare class ScimService {
21
+ private readonly users;
22
+ private readonly members;
23
+ constructor(users: UserRepository, members: OrganizationMemberRepository);
24
+ serviceProviderConfig(): {
25
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"[];
26
+ patch: {
27
+ supported: boolean;
28
+ };
29
+ bulk: {
30
+ supported: boolean;
31
+ };
32
+ filter: {
33
+ supported: boolean;
34
+ };
35
+ changePassword: {
36
+ supported: boolean;
37
+ };
38
+ sort: {
39
+ supported: boolean;
40
+ };
41
+ etag: {
42
+ supported: boolean;
43
+ };
44
+ authenticationSchemes: {
45
+ type: string;
46
+ name: string;
47
+ description: string;
48
+ }[];
49
+ };
50
+ listUsers(startIndex?: number, count?: number): Promise<{
51
+ schemas: "urn:ietf:params:scim:api:messages:2.0:ListResponse"[];
52
+ totalResults: number;
53
+ startIndex: number;
54
+ itemsPerPage: number;
55
+ Resources: {
56
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:User"[];
57
+ id: string;
58
+ userName: string;
59
+ name: {
60
+ formatted: string;
61
+ };
62
+ displayName: string;
63
+ active: boolean;
64
+ emails: {
65
+ value: string;
66
+ primary: boolean;
67
+ type: string;
68
+ }[];
69
+ roles: {
70
+ value: string;
71
+ primary: boolean;
72
+ }[];
73
+ meta: {
74
+ resourceType: string;
75
+ created: string | undefined;
76
+ lastModified: string | undefined;
77
+ };
78
+ }[];
79
+ }>;
80
+ getUser(id: number): Promise<{
81
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:User"[];
82
+ id: string;
83
+ userName: string;
84
+ name: {
85
+ formatted: string;
86
+ };
87
+ displayName: string;
88
+ active: boolean;
89
+ emails: {
90
+ value: string;
91
+ primary: boolean;
92
+ type: string;
93
+ }[];
94
+ roles: {
95
+ value: string;
96
+ primary: boolean;
97
+ }[];
98
+ meta: {
99
+ resourceType: string;
100
+ created: string | undefined;
101
+ lastModified: string | undefined;
102
+ };
103
+ }>;
104
+ findUserRecord(id: number): Promise<import("../user/types").UserRecord>;
105
+ createUser(payload: ScimUserPayload): Promise<{
106
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:User"[];
107
+ id: string;
108
+ userName: string;
109
+ name: {
110
+ formatted: string;
111
+ };
112
+ displayName: string;
113
+ active: boolean;
114
+ emails: {
115
+ value: string;
116
+ primary: boolean;
117
+ type: string;
118
+ }[];
119
+ roles: {
120
+ value: string;
121
+ primary: boolean;
122
+ }[];
123
+ meta: {
124
+ resourceType: string;
125
+ created: string | undefined;
126
+ lastModified: string | undefined;
127
+ };
128
+ }>;
129
+ patchUser(id: number, operations: ScimPatchOperation[]): Promise<{
130
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:User"[];
131
+ id: string;
132
+ userName: string;
133
+ name: {
134
+ formatted: string;
135
+ };
136
+ displayName: string;
137
+ active: boolean;
138
+ emails: {
139
+ value: string;
140
+ primary: boolean;
141
+ type: string;
142
+ }[];
143
+ roles: {
144
+ value: string;
145
+ primary: boolean;
146
+ }[];
147
+ meta: {
148
+ resourceType: string;
149
+ created: string | undefined;
150
+ lastModified: string | undefined;
151
+ };
152
+ }>;
153
+ deleteUser(id: number): Promise<{
154
+ id: number;
155
+ updated_at: Date;
156
+ }>;
157
+ listGroups(startIndex?: number, count?: number): Promise<{
158
+ schemas: "urn:ietf:params:scim:api:messages:2.0:ListResponse"[];
159
+ totalResults: number;
160
+ startIndex: number;
161
+ itemsPerPage: number;
162
+ Resources: {
163
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:Group"[];
164
+ id: string;
165
+ displayName: string;
166
+ externalId: string;
167
+ members: {
168
+ value: string;
169
+ display: string;
170
+ }[];
171
+ meta: {
172
+ resourceType: string;
173
+ lastModified: string;
174
+ };
175
+ }[];
176
+ }>;
177
+ getGroup(id: number): Promise<{
178
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:Group"[];
179
+ id: string;
180
+ displayName: string;
181
+ externalId: string;
182
+ members: {
183
+ value: string;
184
+ display: string;
185
+ }[];
186
+ meta: {
187
+ resourceType: string;
188
+ lastModified: string;
189
+ };
190
+ }>;
191
+ findOrganizationRecord(id: number): Promise<{
192
+ id: number;
193
+ name: string;
194
+ slug: string;
195
+ updated_at: Date;
196
+ }>;
197
+ patchGroup(id: number, operations: ScimPatchOperation[]): Promise<{
198
+ schemas: "urn:ietf:params:scim:schemas:core:2.0:Group"[];
199
+ id: string;
200
+ displayName: string;
201
+ externalId: string;
202
+ members: {
203
+ value: string;
204
+ display: string;
205
+ }[];
206
+ meta: {
207
+ resourceType: string;
208
+ lastModified: string;
209
+ };
210
+ }>;
211
+ private toScimGroup;
212
+ private toScimUser;
213
+ }
214
+ declare function createScimService(dependencies: AppDependencies): ScimService;
215
+ export default ScimService;
216
+ export type { ScimPatchOperation, ScimUserPayload };
217
+ export { createScimService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,42 +16,42 @@
16
16
  "default": "./dist/index.js"
17
17
  },
18
18
  "./applicationRegistry": {
19
- "types": "./dist/entries/applicationRegistry.d.ts",
19
+ "types": "./dist/bootstrap/applicationRegistry.d.ts",
20
20
  "import": "./dist/entries/applicationRegistry.js",
21
21
  "default": "./dist/entries/applicationRegistry.js"
22
22
  },
23
23
  "./config": {
24
- "types": "./dist/entries/config.d.ts",
24
+ "types": "./dist/bootstrap/config.d.ts",
25
25
  "import": "./dist/entries/config.js",
26
26
  "default": "./dist/entries/config.js"
27
27
  },
28
28
  "./context": {
29
- "types": "./dist/entries/context.d.ts",
29
+ "types": "./dist/bootstrap/context.d.ts",
30
30
  "import": "./dist/entries/context.js",
31
31
  "default": "./dist/entries/context.js"
32
32
  },
33
33
  "./contracts": {
34
- "types": "./dist/entries/contracts.d.ts",
34
+ "types": "./dist/bootstrap/contracts.d.ts",
35
35
  "import": "./dist/entries/contracts.js",
36
36
  "default": "./dist/entries/contracts.js"
37
37
  },
38
38
  "./createWebRoutes": {
39
- "types": "./dist/entries/createWebRoutes.d.ts",
39
+ "types": "./dist/bootstrap/createWebRoutes.d.ts",
40
40
  "import": "./dist/entries/createWebRoutes.js",
41
41
  "default": "./dist/entries/createWebRoutes.js"
42
42
  },
43
43
  "./httpKernel": {
44
- "types": "./dist/entries/httpKernel.d.ts",
44
+ "types": "./dist/bootstrap/httpKernel.d.ts",
45
45
  "import": "./dist/entries/httpKernel.js",
46
46
  "default": "./dist/entries/httpKernel.js"
47
47
  },
48
48
  "./providers": {
49
- "types": "./dist/entries/providers.d.ts",
49
+ "types": "./dist/bootstrap/providers/index.d.ts",
50
50
  "import": "./dist/entries/providers.js",
51
51
  "default": "./dist/entries/providers.js"
52
52
  },
53
53
  "./providers/view": {
54
- "types": "./dist/entries/providers/view.d.ts",
54
+ "types": "./dist/bootstrap/providers/view/index.d.ts",
55
55
  "import": "./dist/entries/providers/view.js",
56
56
  "default": "./dist/entries/providers/view.js"
57
57
  }
@@ -73,7 +73,7 @@
73
73
  "access": "public"
74
74
  },
75
75
  "peerDependencies": {
76
- "@getstrata/core": "^0.5.6",
76
+ "@getstrata/core": "^0.5.9",
77
77
  "typescript": "^5.9.0"
78
78
  }
79
79
  }