@getstrata/bootstrap 0.2.65 → 0.2.68
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/CHANGELOG.md +17 -0
- package/README.md +5 -5
- package/dist/bootstrap/createWebRoutes.d.ts +7 -3
- package/dist/bootstrap/dogfoodApp.d.ts +7 -0
- package/dist/bootstrap/public-api.d.ts +1 -0
- package/dist/bootstrap/rateLimit.d.ts +10 -0
- package/dist/bootstrap/web/session.d.ts +18 -3
- package/dist/entries/buildModuleRoutes.js +35 -30
- package/dist/entries/buildWebModuleRoutes.js +35 -30
- package/dist/entries/cache/modelCacheTags.js +7 -2
- package/dist/entries/context.js +13 -163
- package/dist/entries/createRoutes.js +60 -880
- package/dist/entries/createWebRoutes.js +39 -36
- package/dist/entries/dependencies.js +13 -163
- package/dist/entries/discoverModules.js +7 -2
- package/dist/entries/httpKernel.js +28 -28
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +7 -2
- package/dist/entries/providers/view.js +5 -160
- package/dist/entries/providers.js +13 -163
- package/dist/entries/secretsGuard.js +25 -40
- package/dist/entries/web/routing.js +28 -28
- package/dist/entries/web/session.js +24 -10
- package/dist/index.js +93 -246
- package/package.json +2 -2
- package/dist/bootstrap/scimRoutes.d.ts +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @getstrata/bootstrap changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.68
|
|
4
|
+
|
|
5
|
+
- `configureModulesDirectory()` clears the discovered-module cache when the directory changes, so OpenAPI/`createApp` can load HiroApp after core tests pointed at empty fixtures.
|
|
6
|
+
- `createWebRoutes()` / `mergeWebRoutes()` accept optional `{ modules }` so leftover bootstrap route assembly does not pick up the dogfood app's modules. `CreateWebRoutesOptions` is exported from the public API.
|
|
7
|
+
|
|
8
|
+
## 0.2.67
|
|
9
|
+
|
|
10
|
+
- `CookieSessionStore.create()` records optional browser metadata (`userAgent`, `ipAddress`, `last_active_at`).
|
|
11
|
+
- `CookieSessionStore` can list live sessions, touch `last_active_at`, and destroy every session except the current cookie (`listForUser`, `touch`, `destroyOtherSessions`).
|
|
12
|
+
- `CookieSessionAuthManager.signIn()` / `signInRedirect()` accept the same metadata. Apps that use cookie sessions (HiroApp) must have `user_agent`, `ip_address`, and `last_active_at` on `sessions`.
|
|
13
|
+
|
|
14
|
+
## 0.2.66
|
|
15
|
+
|
|
16
|
+
- `assertProductionSecrets()` is feature-gated for every app. API tokens no longer imply WorkHub’s encryption/CORS/OAuth/public-read checklist. Published test token strings are still denied. WorkHub’s extra production profile lives in the app (`src/config/productionSecrets.ts`).
|
|
17
|
+
- **Breaking:** `HttpKernel.wrapWebGuest()` defaults `home` to `/` instead of `/organizations`. Apps with an org home should pass that path (WorkHub already does).
|
|
18
|
+
- `createWebRoutes()` no longer seeds a `/` → `/organizations` redirect. The in-repo app owns `/` via its organization module.
|
|
19
|
+
|
|
3
20
|
## 0.2.65
|
|
4
21
|
|
|
5
22
|
- App listener discovery reads `src/listeners` from `process.cwd()` so a built `@getstrata/bootstrap` bundle still finds WorkHub registrars (`import.meta.dir` after `build:bootstrap` is the package dist). Listener boot calls each registrar every time (registrars are idempotent per `eventBus`).
|
package/README.md
CHANGED
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
`createAppContext()` does not call `assertProductionSecrets`. WorkHub
|
|
31
|
+
`createAppContext()` does not call `assertProductionSecrets`. That helper is feature-gated for every app (tokens, CORS, OAuth, encryption only when those features are on). WorkHub also runs its own production extras from `App.serve()` / `queue:work`. Sibling HTMX apps can call the published helper without API tokens when feature flags are off — see [SIBLING-HTMX.md](../../docs/SIBLING-HTMX.md).
|
|
32
32
|
|
|
33
|
-
Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. Use `signIn` / `signOut` (or the redirect helpers) instead of calling `CookieSessionStore` from controllers. Pass `mapUser` to map roles in the app. Pass `loadSessionUser` when the default `learn_subscriber` / `is_admin` SELECT does not match your schema.
|
|
33
|
+
Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. Use `signIn` / `signOut` (or the redirect helpers) instead of calling `CookieSessionStore` from controllers. Pass `mapUser` to map roles in the app. Pass `loadSessionUser` when the default `learn_subscriber` / `is_admin` SELECT does not match your schema.
|
|
34
34
|
|
|
35
|
-
`wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebGuest` is Laravel `guest` / `RedirectIfAuthenticated` (signed-in users go to
|
|
35
|
+
`wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebGuest` is Laravel `guest` / `RedirectIfAuthenticated` (signed-in users go to `/` by default; pass a string or `(user) => path` to override). `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again. The throttle callback should return an HTML form at 429.
|
|
36
36
|
|
|
37
|
-
`registerDefaultJobs()` registers `cache.invalidate-tags` and `audit.export` only.
|
|
37
|
+
`registerDefaultJobs()` registers `cache.invalidate-tags` and `audit.export` only. Apps that dispatch model webhooks should call `registerWebhookJobs()` themselves.
|
|
38
38
|
|
|
39
|
-
These subpaths
|
|
39
|
+
These subpaths assemble the in-repo dogfood app and are not a generic starter API: `@getstrata/bootstrap/createRoutes` (includes SCIM), `@getstrata/bootstrap/schedule`, and `@getstrata/bootstrap/createWebRoutes` (discovers app `webRoutes`; `/` is owned by the app). Sibling apps should use `buildWebModuleRoutes` / `buildModuleRoutes`.
|
|
40
40
|
|
|
41
41
|
See the [strata](https://github.com/EyK-26/strata) monorepo reference app for full module patterns.
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { AppDependencies, AppRouteMap } from "./contracts";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { AppDependencies, AppModule, AppRouteMap } from "./contracts";
|
|
2
|
+
interface CreateWebRoutesOptions {
|
|
3
|
+
modules?: AppModule[];
|
|
4
|
+
}
|
|
5
|
+
declare function createWebRoutes(dependencies: AppDependencies, options?: CreateWebRoutesOptions): AppRouteMap;
|
|
6
|
+
declare function mergeWebRoutes(dependencies: AppDependencies, routes: AppRouteMap, options?: CreateWebRoutesOptions): AppRouteMap;
|
|
7
|
+
export type { CreateWebRoutesOptions };
|
|
4
8
|
export { createWebRoutes, mergeWebRoutes };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type DogfoodApp = "workhub" | "hiroapp";
|
|
2
|
+
declare function readDogfoodApp(): DogfoodApp;
|
|
3
|
+
declare function isHiroappDogfood(): boolean;
|
|
4
|
+
declare function hiroappSourcePath(...segments: string[]): string;
|
|
5
|
+
declare function importHiroappModule<T = unknown>(relativeFromApp: string): Promise<T>;
|
|
6
|
+
export type { DogfoodApp };
|
|
7
|
+
export { hiroappSourcePath, importHiroappModule, isHiroappDogfood, readDogfoodApp };
|
|
@@ -13,6 +13,7 @@ export { APP_PORT_CONFIG_KEY, CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_
|
|
|
13
13
|
export { collectProviders, createAppContext, runProviderPhase } from "./context.ts";
|
|
14
14
|
export type { AppContext, AppDependencies, AppModule, AppRouteMap, CachedJson, ConfigStore, ModuleRouteContext, MutableAppDependencies, ProviderContext, ServiceFactory, ServiceProvider, } from "./contracts.ts";
|
|
15
15
|
export { assertAppDependenciesComplete, getRequiredDependency, resolveService, ServiceContainer, } from "./contracts.ts";
|
|
16
|
+
export type { CreateWebRoutesOptions } from "./createWebRoutes.ts";
|
|
16
17
|
export { createWebRoutes, mergeWebRoutes } from "./createWebRoutes.ts";
|
|
17
18
|
export { createAppDependencies } from "./dependencies.ts";
|
|
18
19
|
export type { DiscoverModulesOptions } from "./discoverModules.ts";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface LoginRateLimitConfig {
|
|
2
|
+
maxAttempts: number;
|
|
3
|
+
decaySeconds: number;
|
|
4
|
+
}
|
|
5
|
+
declare const LOCAL_LOGIN_RATE_LIMIT: LoginRateLimitConfig;
|
|
6
|
+
declare const PRODUCTION_LOGIN_RATE_LIMIT: LoginRateLimitConfig;
|
|
7
|
+
declare function resolveLoginRateLimit(): LoginRateLimitConfig;
|
|
8
|
+
declare function resolveRegisterRateLimit(): LoginRateLimitConfig;
|
|
9
|
+
export type { LoginRateLimitConfig };
|
|
10
|
+
export { LOCAL_LOGIN_RATE_LIMIT, PRODUCTION_LOGIN_RATE_LIMIT, resolveLoginRateLimit, resolveRegisterRateLimit, };
|
|
@@ -12,6 +12,18 @@ type SqlClient = {
|
|
|
12
12
|
};
|
|
13
13
|
type SqlSource = SqlClient | (() => SqlClient);
|
|
14
14
|
export type LoadSessionUser = (sql: SqlClient, sessionId: string) => Promise<SessionUser | null>;
|
|
15
|
+
export interface SessionCreateMeta {
|
|
16
|
+
userAgent?: string | null;
|
|
17
|
+
ipAddress?: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface BrowserSessionRecord {
|
|
20
|
+
id: string;
|
|
21
|
+
user_id: number;
|
|
22
|
+
user_agent: string | null;
|
|
23
|
+
ip_address: string | null;
|
|
24
|
+
last_active_at: Date | string | null;
|
|
25
|
+
expires_at: Date | string;
|
|
26
|
+
}
|
|
15
27
|
declare function defaultSessionSql(): SqlClient;
|
|
16
28
|
export type MapSessionUser = (user: SessionUser) => AuthUser;
|
|
17
29
|
declare function defaultMapSessionUser(user: SessionUser): AuthUser;
|
|
@@ -27,8 +39,11 @@ export declare class CookieSessionStore {
|
|
|
27
39
|
sessionIdFromRequest(request: Request): string | null;
|
|
28
40
|
private withSecureFlag;
|
|
29
41
|
private sql;
|
|
30
|
-
create(user: SessionUser): Promise<string>;
|
|
42
|
+
create(user: SessionUser, meta?: SessionCreateMeta): Promise<string>;
|
|
31
43
|
destroy(sessionId: string): Promise<void>;
|
|
44
|
+
destroyOtherSessions(userId: number, keepSessionId: string): Promise<void>;
|
|
45
|
+
listForUser(userId: number): Promise<BrowserSessionRecord[]>;
|
|
46
|
+
touch(sessionId: string): Promise<void>;
|
|
32
47
|
read(request: Request): Promise<SessionUser | null>;
|
|
33
48
|
private sign;
|
|
34
49
|
}
|
|
@@ -41,14 +56,14 @@ export declare class CookieSessionGuard implements AuthGuard {
|
|
|
41
56
|
export declare class CookieSessionAuthManager extends AuthManager {
|
|
42
57
|
readonly store: CookieSessionStore;
|
|
43
58
|
constructor(store: CookieSessionStore, mapUser?: MapSessionUser);
|
|
44
|
-
signIn(user: SessionUser): Promise<{
|
|
59
|
+
signIn(user: SessionUser, meta?: SessionCreateMeta): Promise<{
|
|
45
60
|
sessionId: string;
|
|
46
61
|
setCookie: string;
|
|
47
62
|
}>;
|
|
48
63
|
signOut(request: Request): Promise<{
|
|
49
64
|
setCookie: string;
|
|
50
65
|
}>;
|
|
51
|
-
signInRedirect(user: SessionUser, location: string, status?: number): Promise<Response>;
|
|
66
|
+
signInRedirect(user: SessionUser, location: string, status?: number, meta?: SessionCreateMeta): Promise<Response>;
|
|
52
67
|
signOutRedirect(request: Request, location: string, status?: number): Promise<Response>;
|
|
53
68
|
}
|
|
54
69
|
export interface CreateCookieSessionAuthManagerOptions {
|
|
@@ -39,7 +39,33 @@ import { isPublicReadsEnabled } from "@getstrata/core/security/publicReads";
|
|
|
39
39
|
import { createTenantMiddleware } from "@getstrata/core/tenant/tenantMiddleware";
|
|
40
40
|
import { createTracingMiddleware } from "@getstrata/core/tracing/tracingMiddleware";
|
|
41
41
|
|
|
42
|
-
// ../../src/config
|
|
42
|
+
// ../../src/bootstrap/config.ts
|
|
43
|
+
import {
|
|
44
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
45
|
+
CORE_AUTH_TOKEN,
|
|
46
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
47
|
+
CORE_CACHE_TOKEN,
|
|
48
|
+
CORE_CONFIG_TOKEN,
|
|
49
|
+
CORE_EVENT_BUS_TOKEN,
|
|
50
|
+
CORE_POLICY_GATE_TOKEN,
|
|
51
|
+
CORE_QUEUE_TOKEN,
|
|
52
|
+
CORE_TOKEN_SERVICE_TOKEN
|
|
53
|
+
} from "@getstrata/core/contracts/serviceTokens";
|
|
54
|
+
var APP_PORT_CONFIG_KEY = "app.port";
|
|
55
|
+
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
56
|
+
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
57
|
+
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
58
|
+
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
59
|
+
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
60
|
+
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
61
|
+
var DEFAULT_APP_PORT = 3000;
|
|
62
|
+
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
63
|
+
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
64
|
+
var DEFAULT_CACHE_DRIVER = "array";
|
|
65
|
+
var DEFAULT_API_TOKEN = "";
|
|
66
|
+
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
67
|
+
|
|
68
|
+
// ../../src/bootstrap/rateLimit.ts
|
|
43
69
|
var LOCAL_LOGIN_RATE_LIMIT = {
|
|
44
70
|
maxAttempts: 100,
|
|
45
71
|
decaySeconds: 60
|
|
@@ -85,32 +111,6 @@ function resolveRegisterRateLimit() {
|
|
|
85
111
|
};
|
|
86
112
|
}
|
|
87
113
|
|
|
88
|
-
// ../../src/bootstrap/config.ts
|
|
89
|
-
import {
|
|
90
|
-
CORE_ABILITY_CHECKER_TOKEN,
|
|
91
|
-
CORE_AUTH_TOKEN,
|
|
92
|
-
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
93
|
-
CORE_CACHE_TOKEN,
|
|
94
|
-
CORE_CONFIG_TOKEN,
|
|
95
|
-
CORE_EVENT_BUS_TOKEN,
|
|
96
|
-
CORE_POLICY_GATE_TOKEN,
|
|
97
|
-
CORE_QUEUE_TOKEN,
|
|
98
|
-
CORE_TOKEN_SERVICE_TOKEN
|
|
99
|
-
} from "@getstrata/core/contracts/serviceTokens";
|
|
100
|
-
var APP_PORT_CONFIG_KEY = "app.port";
|
|
101
|
-
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
102
|
-
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
103
|
-
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
104
|
-
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
105
|
-
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
106
|
-
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
107
|
-
var DEFAULT_APP_PORT = 3000;
|
|
108
|
-
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
109
|
-
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
110
|
-
var DEFAULT_CACHE_DRIVER = "array";
|
|
111
|
-
var DEFAULT_API_TOKEN = "";
|
|
112
|
-
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
113
|
-
|
|
114
114
|
// ../../src/bootstrap/httpKernel.ts
|
|
115
115
|
class HttpKernel {
|
|
116
116
|
dependencies;
|
|
@@ -181,7 +181,7 @@ class HttpKernel {
|
|
|
181
181
|
wrapWeb(handler) {
|
|
182
182
|
return withErrorHandling(this.wrap("web", handler));
|
|
183
183
|
}
|
|
184
|
-
wrapWebGuest(handler, home = "/
|
|
184
|
+
wrapWebGuest(handler, home = "/") {
|
|
185
185
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
186
186
|
return this.wrapWeb(async (request) => {
|
|
187
187
|
const user = await auth.resolve(request);
|
|
@@ -341,7 +341,12 @@ function readDiscoverModulesState() {
|
|
|
341
341
|
return state;
|
|
342
342
|
}
|
|
343
343
|
function configureModulesDirectory(modulesDir) {
|
|
344
|
-
readDiscoverModulesState()
|
|
344
|
+
const state = readDiscoverModulesState();
|
|
345
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
346
|
+
state.appModules.length = 0;
|
|
347
|
+
state.modulesReady = undefined;
|
|
348
|
+
}
|
|
349
|
+
state.configuredModulesDir = modulesDir;
|
|
345
350
|
}
|
|
346
351
|
function resolveModulesDirectory(options) {
|
|
347
352
|
const state = readDiscoverModulesState();
|
|
@@ -351,7 +356,7 @@ function resolveModulesDirectory(options) {
|
|
|
351
356
|
if (state.configuredModulesDir) {
|
|
352
357
|
return state.configuredModulesDir;
|
|
353
358
|
}
|
|
354
|
-
throw new Error("configureModulesDirectory() must be called before discovering modules.
|
|
359
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. The app preload (or starter) should call it.");
|
|
355
360
|
}
|
|
356
361
|
async function loadDiscoveredModules(options) {
|
|
357
362
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -42,7 +42,33 @@ import { isPublicReadsEnabled } from "@getstrata/core/security/publicReads";
|
|
|
42
42
|
import { createTenantMiddleware } from "@getstrata/core/tenant/tenantMiddleware";
|
|
43
43
|
import { createTracingMiddleware } from "@getstrata/core/tracing/tracingMiddleware";
|
|
44
44
|
|
|
45
|
-
// ../../src/config
|
|
45
|
+
// ../../src/bootstrap/config.ts
|
|
46
|
+
import {
|
|
47
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
48
|
+
CORE_AUTH_TOKEN,
|
|
49
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
50
|
+
CORE_CACHE_TOKEN,
|
|
51
|
+
CORE_CONFIG_TOKEN,
|
|
52
|
+
CORE_EVENT_BUS_TOKEN,
|
|
53
|
+
CORE_POLICY_GATE_TOKEN,
|
|
54
|
+
CORE_QUEUE_TOKEN,
|
|
55
|
+
CORE_TOKEN_SERVICE_TOKEN
|
|
56
|
+
} from "@getstrata/core/contracts/serviceTokens";
|
|
57
|
+
var APP_PORT_CONFIG_KEY = "app.port";
|
|
58
|
+
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
59
|
+
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
60
|
+
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
61
|
+
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
62
|
+
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
63
|
+
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
64
|
+
var DEFAULT_APP_PORT = 3000;
|
|
65
|
+
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
66
|
+
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
67
|
+
var DEFAULT_CACHE_DRIVER = "array";
|
|
68
|
+
var DEFAULT_API_TOKEN = "";
|
|
69
|
+
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
70
|
+
|
|
71
|
+
// ../../src/bootstrap/rateLimit.ts
|
|
46
72
|
var LOCAL_LOGIN_RATE_LIMIT = {
|
|
47
73
|
maxAttempts: 100,
|
|
48
74
|
decaySeconds: 60
|
|
@@ -88,32 +114,6 @@ function resolveRegisterRateLimit() {
|
|
|
88
114
|
};
|
|
89
115
|
}
|
|
90
116
|
|
|
91
|
-
// ../../src/bootstrap/config.ts
|
|
92
|
-
import {
|
|
93
|
-
CORE_ABILITY_CHECKER_TOKEN,
|
|
94
|
-
CORE_AUTH_TOKEN,
|
|
95
|
-
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
96
|
-
CORE_CACHE_TOKEN,
|
|
97
|
-
CORE_CONFIG_TOKEN,
|
|
98
|
-
CORE_EVENT_BUS_TOKEN,
|
|
99
|
-
CORE_POLICY_GATE_TOKEN,
|
|
100
|
-
CORE_QUEUE_TOKEN,
|
|
101
|
-
CORE_TOKEN_SERVICE_TOKEN
|
|
102
|
-
} from "@getstrata/core/contracts/serviceTokens";
|
|
103
|
-
var APP_PORT_CONFIG_KEY = "app.port";
|
|
104
|
-
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
105
|
-
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
106
|
-
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
107
|
-
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
108
|
-
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
109
|
-
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
110
|
-
var DEFAULT_APP_PORT = 3000;
|
|
111
|
-
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
112
|
-
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
113
|
-
var DEFAULT_CACHE_DRIVER = "array";
|
|
114
|
-
var DEFAULT_API_TOKEN = "";
|
|
115
|
-
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
116
|
-
|
|
117
117
|
// ../../src/bootstrap/httpKernel.ts
|
|
118
118
|
class HttpKernel {
|
|
119
119
|
dependencies;
|
|
@@ -184,7 +184,7 @@ class HttpKernel {
|
|
|
184
184
|
wrapWeb(handler) {
|
|
185
185
|
return withErrorHandling(this.wrap("web", handler));
|
|
186
186
|
}
|
|
187
|
-
wrapWebGuest(handler, home = "/
|
|
187
|
+
wrapWebGuest(handler, home = "/") {
|
|
188
188
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
189
189
|
return this.wrapWeb(async (request) => {
|
|
190
190
|
const user = await auth.resolve(request);
|
|
@@ -344,7 +344,12 @@ function readDiscoverModulesState() {
|
|
|
344
344
|
return state;
|
|
345
345
|
}
|
|
346
346
|
function configureModulesDirectory(modulesDir) {
|
|
347
|
-
readDiscoverModulesState()
|
|
347
|
+
const state = readDiscoverModulesState();
|
|
348
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
349
|
+
state.appModules.length = 0;
|
|
350
|
+
state.modulesReady = undefined;
|
|
351
|
+
}
|
|
352
|
+
state.configuredModulesDir = modulesDir;
|
|
348
353
|
}
|
|
349
354
|
function resolveModulesDirectory(options) {
|
|
350
355
|
const state = readDiscoverModulesState();
|
|
@@ -354,7 +359,7 @@ function resolveModulesDirectory(options) {
|
|
|
354
359
|
if (state.configuredModulesDir) {
|
|
355
360
|
return state.configuredModulesDir;
|
|
356
361
|
}
|
|
357
|
-
throw new Error("configureModulesDirectory() must be called before discovering modules.
|
|
362
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. The app preload (or starter) should call it.");
|
|
358
363
|
}
|
|
359
364
|
async function loadDiscoveredModules(options) {
|
|
360
365
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -16,7 +16,12 @@ function readDiscoverModulesState() {
|
|
|
16
16
|
return state;
|
|
17
17
|
}
|
|
18
18
|
function configureModulesDirectory(modulesDir) {
|
|
19
|
-
readDiscoverModulesState()
|
|
19
|
+
const state = readDiscoverModulesState();
|
|
20
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
21
|
+
state.appModules.length = 0;
|
|
22
|
+
state.modulesReady = undefined;
|
|
23
|
+
}
|
|
24
|
+
state.configuredModulesDir = modulesDir;
|
|
20
25
|
}
|
|
21
26
|
function resolveModulesDirectory(options) {
|
|
22
27
|
const state = readDiscoverModulesState();
|
|
@@ -26,7 +31,7 @@ function resolveModulesDirectory(options) {
|
|
|
26
31
|
if (state.configuredModulesDir) {
|
|
27
32
|
return state.configuredModulesDir;
|
|
28
33
|
}
|
|
29
|
-
throw new Error("configureModulesDirectory() must be called before discovering modules.
|
|
34
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. The app preload (or starter) should call it.");
|
|
30
35
|
}
|
|
31
36
|
async function loadDiscoveredModules(options) {
|
|
32
37
|
const modulesDirectory = resolveModulesDirectory(options);
|
package/dist/entries/context.js
CHANGED
|
@@ -40,7 +40,12 @@ function readDiscoverModulesState() {
|
|
|
40
40
|
return state;
|
|
41
41
|
}
|
|
42
42
|
function configureModulesDirectory(modulesDir) {
|
|
43
|
-
readDiscoverModulesState()
|
|
43
|
+
const state = readDiscoverModulesState();
|
|
44
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
45
|
+
state.appModules.length = 0;
|
|
46
|
+
state.modulesReady = undefined;
|
|
47
|
+
}
|
|
48
|
+
state.configuredModulesDir = modulesDir;
|
|
44
49
|
}
|
|
45
50
|
function resolveModulesDirectory(options) {
|
|
46
51
|
const state = readDiscoverModulesState();
|
|
@@ -50,7 +55,7 @@ function resolveModulesDirectory(options) {
|
|
|
50
55
|
if (state.configuredModulesDir) {
|
|
51
56
|
return state.configuredModulesDir;
|
|
52
57
|
}
|
|
53
|
-
throw new Error("configureModulesDirectory() must be called before discovering modules.
|
|
58
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. The app preload (or starter) should call it.");
|
|
54
59
|
}
|
|
55
60
|
async function loadDiscoveredModules(options) {
|
|
56
61
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -170,7 +175,7 @@ import { validateEnv } from "@getstrata/core/config/envSchema";
|
|
|
170
175
|
|
|
171
176
|
// ../../src/config/app.ts
|
|
172
177
|
var appConfig = {
|
|
173
|
-
name: process.env.APP_NAME?.trim() || "
|
|
178
|
+
name: process.env.APP_NAME?.trim() || "Strata",
|
|
174
179
|
env: process.env.APP_ENV ?? "local",
|
|
175
180
|
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
176
181
|
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
@@ -480,7 +485,6 @@ var storageProvider = {
|
|
|
480
485
|
var storage_default = storageProvider;
|
|
481
486
|
|
|
482
487
|
// ../../src/bootstrap/providers/view.ts
|
|
483
|
-
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
484
488
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
485
489
|
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
486
490
|
import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
@@ -492,148 +496,6 @@ import {
|
|
|
492
496
|
errorTemplateName,
|
|
493
497
|
resolveWebLayoutData
|
|
494
498
|
} from "@getstrata/core/view";
|
|
495
|
-
|
|
496
|
-
// ../../src/modules/organization/repository.ts
|
|
497
|
-
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
498
|
-
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
499
|
-
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
500
|
-
|
|
501
|
-
// ../../src/modules/organization/table.ts
|
|
502
|
-
import { defineTable } from "@getstrata/core/database/table";
|
|
503
|
-
|
|
504
|
-
// ../../src/domain/workhub.ts
|
|
505
|
-
var ORGANIZATION_TABLE = "organization";
|
|
506
|
-
|
|
507
|
-
// ../../src/modules/organization/table.ts
|
|
508
|
-
var organizationTable = defineTable({
|
|
509
|
-
name: ORGANIZATION_TABLE,
|
|
510
|
-
primaryKey: "id",
|
|
511
|
-
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
512
|
-
softDeletes: true,
|
|
513
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// ../../src/modules/organization/repository.ts
|
|
517
|
-
class OrganizationRepository extends BaseRepository {
|
|
518
|
-
constructor() {
|
|
519
|
-
super(organizationTable);
|
|
520
|
-
}
|
|
521
|
-
async findBySlug(slug) {
|
|
522
|
-
return await this.firstOrNull({ slug });
|
|
523
|
-
}
|
|
524
|
-
async listForTenant(options) {
|
|
525
|
-
return await this.findAll({
|
|
526
|
-
limit: options.limit,
|
|
527
|
-
offset: options.offset,
|
|
528
|
-
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
async countForTenant(tenantId = currentTenantId()) {
|
|
532
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
533
|
-
}
|
|
534
|
-
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
535
|
-
const organization = await this.findById(id);
|
|
536
|
-
if (!organization || organization.tenant_id !== tenantId) {
|
|
537
|
-
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
538
|
-
}
|
|
539
|
-
return organization;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
var repository_default = OrganizationRepository;
|
|
543
|
-
|
|
544
|
-
// ../../src/modules/user/repository.ts
|
|
545
|
-
import {
|
|
546
|
-
emailLookupForQuery,
|
|
547
|
-
protectEmail,
|
|
548
|
-
revealEmail
|
|
549
|
-
} from "@getstrata/core/crypto/fieldEncryption";
|
|
550
|
-
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
551
|
-
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
552
|
-
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
553
|
-
|
|
554
|
-
// ../../src/modules/user/table.ts
|
|
555
|
-
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
556
|
-
var userTable = defineTable2({
|
|
557
|
-
name: "users",
|
|
558
|
-
primaryKey: "id",
|
|
559
|
-
columns: [
|
|
560
|
-
"id",
|
|
561
|
-
"name",
|
|
562
|
-
"email",
|
|
563
|
-
"email_lookup",
|
|
564
|
-
"role",
|
|
565
|
-
"tenant_id",
|
|
566
|
-
"password_hash",
|
|
567
|
-
"email_verified_at",
|
|
568
|
-
"mfa_secret",
|
|
569
|
-
"mfa_enabled",
|
|
570
|
-
"mfa_recovery_codes",
|
|
571
|
-
"profile_photo_path",
|
|
572
|
-
"session_valid_after",
|
|
573
|
-
"current_organization_id",
|
|
574
|
-
"created_at",
|
|
575
|
-
"updated_at"
|
|
576
|
-
],
|
|
577
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
// ../../src/modules/user/repository.ts
|
|
581
|
-
class UserRepository extends BaseRepository2 {
|
|
582
|
-
constructor() {
|
|
583
|
-
super(userTable);
|
|
584
|
-
}
|
|
585
|
-
decode(record) {
|
|
586
|
-
return {
|
|
587
|
-
...record,
|
|
588
|
-
email: revealEmail(record.email),
|
|
589
|
-
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
async findById(id) {
|
|
593
|
-
const record = await super.findById(id);
|
|
594
|
-
return record ? this.decode(record) : null;
|
|
595
|
-
}
|
|
596
|
-
async findAll(options = {}) {
|
|
597
|
-
const records = await super.findAll(options);
|
|
598
|
-
return records.map((record) => this.decode(record));
|
|
599
|
-
}
|
|
600
|
-
async create(values) {
|
|
601
|
-
const email = values.email;
|
|
602
|
-
if (!email) {
|
|
603
|
-
throw new Error("Email is required.");
|
|
604
|
-
}
|
|
605
|
-
const protectedEmail = protectEmail(email);
|
|
606
|
-
const record = await super.create({
|
|
607
|
-
...values,
|
|
608
|
-
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
609
|
-
email: protectedEmail.storedEmail,
|
|
610
|
-
email_lookup: protectedEmail.emailLookup,
|
|
611
|
-
password_hash: values.password_hash ?? ""
|
|
612
|
-
});
|
|
613
|
-
return this.decode(record);
|
|
614
|
-
}
|
|
615
|
-
async updateByIdOrThrow(id, values, errorFactory) {
|
|
616
|
-
const changes = { ...values };
|
|
617
|
-
if (values.email !== undefined) {
|
|
618
|
-
const protectedEmail = protectEmail(values.email);
|
|
619
|
-
changes.email = protectedEmail.storedEmail;
|
|
620
|
-
changes.email_lookup = protectedEmail.emailLookup;
|
|
621
|
-
}
|
|
622
|
-
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
623
|
-
return this.decode(record);
|
|
624
|
-
}
|
|
625
|
-
async findByEmail(email) {
|
|
626
|
-
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
627
|
-
const record = records[0];
|
|
628
|
-
return record ? this.decode(record) : null;
|
|
629
|
-
}
|
|
630
|
-
async countForTenant(tenantId = currentTenantId2()) {
|
|
631
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
var repository_default2 = UserRepository;
|
|
635
|
-
|
|
636
|
-
// ../../src/bootstrap/providers/view.ts
|
|
637
499
|
var CORE_VIEW_TOKEN = "core.view";
|
|
638
500
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
639
501
|
var viewProvider = {
|
|
@@ -647,23 +509,11 @@ var viewProvider = {
|
|
|
647
509
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
648
510
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
649
511
|
configureWebLayoutData({
|
|
650
|
-
extra: async (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
657
|
-
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
658
|
-
const organizationsRepo = new repository_default;
|
|
659
|
-
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
660
|
-
const currentId = record.current_organization_id ?? null;
|
|
661
|
-
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
662
|
-
return { appName, currentOrganization, organizations };
|
|
663
|
-
} catch {
|
|
664
|
-
return { appName, currentOrganization: null, organizations: [] };
|
|
665
|
-
}
|
|
666
|
-
}
|
|
512
|
+
extra: async () => ({
|
|
513
|
+
appName: appDisplayName(),
|
|
514
|
+
currentOrganization: null,
|
|
515
|
+
organizations: []
|
|
516
|
+
})
|
|
667
517
|
});
|
|
668
518
|
configureWebErrorView({
|
|
669
519
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|