@getstrata/bootstrap 0.2.8 → 0.2.10
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/membershipService.d.ts +3 -0
- package/dist/bootstrap/public-api.d.ts +3 -1
- package/dist/bootstrap/queue/defaultJobs.d.ts +2 -0
- package/dist/core/auth/authContext.d.ts +1 -2
- package/dist/core/auth/membershipContext.d.ts +1 -2
- package/dist/core/auth/membershipService.d.ts +1 -2
- package/dist/core/http/requestMetaContext.d.ts +1 -2
- package/dist/core/queue/createAppQueue.d.ts +3 -3
- package/dist/core/runtime/asyncContextStore.d.ts +3 -0
- package/dist/core/tenant/tenantContext.d.ts +1 -2
- package/dist/core/tracing/traceContext.d.ts +1 -2
- package/dist/entries/applicationRegistry.js +15 -2
- package/dist/entries/context.js +272 -321
- package/dist/entries/createWebRoutes.js +18 -6
- package/dist/entries/http/securedRouteModelBinding.js +19 -179
- package/dist/entries/membershipService.js +317 -0
- package/dist/entries/providers/view.js +18 -6
- package/dist/entries/providers.js +279 -413
- package/dist/entries/queue/defaultJobs.js +390 -0
- package/dist/index.js +288 -125
- package/package.json +13 -3
|
@@ -173,9 +173,22 @@ var databaseConfig = {
|
|
|
173
173
|
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
174
174
|
};
|
|
175
175
|
|
|
176
|
-
// ../../src/core/
|
|
176
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
177
177
|
import { AsyncLocalStorage } from "async_hooks";
|
|
178
|
-
|
|
178
|
+
function createAsyncContextStore(key) {
|
|
179
|
+
const symbol = Symbol.for(key);
|
|
180
|
+
const globalRecord = globalThis;
|
|
181
|
+
const existing = globalRecord[symbol];
|
|
182
|
+
if (existing) {
|
|
183
|
+
return existing;
|
|
184
|
+
}
|
|
185
|
+
const store = new AsyncLocalStorage;
|
|
186
|
+
globalRecord[symbol] = store;
|
|
187
|
+
return store;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ../../src/core/database/connectionContext.ts
|
|
191
|
+
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
179
192
|
function getActiveDatabaseConnection(fallback) {
|
|
180
193
|
return activeConnection.getStore() ?? fallback;
|
|
181
194
|
}
|
|
@@ -262,6 +275,7 @@ var db = new Proxy(function database() {}, {
|
|
|
262
275
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
263
276
|
}
|
|
264
277
|
});
|
|
278
|
+
var connection_default = db;
|
|
265
279
|
|
|
266
280
|
// ../../src/modules/user/apiTokenTable.ts
|
|
267
281
|
import { defineTable } from "@getstrata/core/database";
|
|
@@ -485,8 +499,7 @@ import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from
|
|
|
485
499
|
var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
|
|
486
500
|
|
|
487
501
|
// ../../src/core/auth/authContext.ts
|
|
488
|
-
|
|
489
|
-
var authContext = new AsyncLocalStorage2;
|
|
502
|
+
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
490
503
|
function currentAuthUser() {
|
|
491
504
|
return authContext.getStore() ?? null;
|
|
492
505
|
}
|
|
@@ -517,8 +530,7 @@ function readRequestCookie(request, name) {
|
|
|
517
530
|
}
|
|
518
531
|
|
|
519
532
|
// ../../src/core/http/requestMetaContext.ts
|
|
520
|
-
|
|
521
|
-
var requestMetaContext = new AsyncLocalStorage3;
|
|
533
|
+
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
522
534
|
function currentRequestMeta() {
|
|
523
535
|
return requestMetaContext.getStore() ?? {
|
|
524
536
|
ipAddress: null,
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
2
|
+
// ../../src/bootstrap/http/securedRouteModelBinding.ts
|
|
3
|
+
import { resolveApplicationAuth, resolveApplicationPolicyGate } from "@getstrata/core";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
3
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
4
|
-
|
|
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");
|
|
5
21
|
function currentAuthUser() {
|
|
6
22
|
return authContext.getStore() ?? null;
|
|
7
23
|
}
|
|
@@ -143,8 +159,7 @@ function applyConditionalGet(request, response, etag) {
|
|
|
143
159
|
}
|
|
144
160
|
|
|
145
161
|
// ../../src/core/tenant/tenantContext.ts
|
|
146
|
-
|
|
147
|
-
var tenantContext = new AsyncLocalStorage2;
|
|
162
|
+
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
148
163
|
|
|
149
164
|
// ../../src/core/http/validation.ts
|
|
150
165
|
function parsePositiveIntParam(value, name = "id") {
|
|
@@ -155,181 +170,6 @@ function parsePositiveIntParam(value, name = "id") {
|
|
|
155
170
|
return parsed;
|
|
156
171
|
}
|
|
157
172
|
|
|
158
|
-
// ../../src/core/logging/logger.ts
|
|
159
|
-
class Logger {
|
|
160
|
-
channel;
|
|
161
|
-
constructor(channel = "app") {
|
|
162
|
-
this.channel = channel;
|
|
163
|
-
}
|
|
164
|
-
write(level, message, context = {}) {
|
|
165
|
-
const entry = {
|
|
166
|
-
level,
|
|
167
|
-
channel: this.channel,
|
|
168
|
-
message,
|
|
169
|
-
timestamp: new Date().toISOString(),
|
|
170
|
-
...context
|
|
171
|
-
};
|
|
172
|
-
const line = JSON.stringify(entry);
|
|
173
|
-
if (level === "error") {
|
|
174
|
-
console.error(line);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
console.log(line);
|
|
178
|
-
}
|
|
179
|
-
debug(message, context) {
|
|
180
|
-
this.write("debug", message, context);
|
|
181
|
-
}
|
|
182
|
-
info(message, context) {
|
|
183
|
-
this.write("info", message, context);
|
|
184
|
-
}
|
|
185
|
-
warn(message, context) {
|
|
186
|
-
this.write("warn", message, context);
|
|
187
|
-
}
|
|
188
|
-
error(message, context) {
|
|
189
|
-
this.write("error", message, context);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
var appLogger = new Logger("app");
|
|
193
|
-
|
|
194
|
-
// ../../src/bootstrap/config.ts
|
|
195
|
-
var APP_PORT_CONFIG_KEY = "app.port";
|
|
196
|
-
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
197
|
-
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
198
|
-
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
199
|
-
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
200
|
-
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
201
|
-
var CORE_CONFIG_TOKEN = "core.config";
|
|
202
|
-
var CORE_CACHE_TOKEN = "core.cache";
|
|
203
|
-
var CORE_QUEUE_TOKEN = "core.queue";
|
|
204
|
-
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
205
|
-
var CORE_AUTH_TOKEN = "core.auth";
|
|
206
|
-
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
207
|
-
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
208
|
-
var DEFAULT_APP_PORT = 3000;
|
|
209
|
-
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
210
|
-
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
211
|
-
var DEFAULT_CACHE_DRIVER = "array";
|
|
212
|
-
var DEFAULT_API_TOKEN = "";
|
|
213
|
-
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
214
|
-
|
|
215
|
-
// ../../src/bootstrap/contracts.ts
|
|
216
|
-
class ServiceContainer {
|
|
217
|
-
services = new Map;
|
|
218
|
-
singletonFactories = new Map;
|
|
219
|
-
bindings = new Map;
|
|
220
|
-
set(key, value) {
|
|
221
|
-
this.singletonFactories.delete(key);
|
|
222
|
-
this.bindings.delete(key);
|
|
223
|
-
this.services.set(key, value);
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
singleton(key, factory) {
|
|
227
|
-
this.bindings.delete(key);
|
|
228
|
-
this.services.delete(key);
|
|
229
|
-
this.singletonFactories.set(key, factory);
|
|
230
|
-
}
|
|
231
|
-
bind(key, factory) {
|
|
232
|
-
this.singletonFactories.delete(key);
|
|
233
|
-
this.services.delete(key);
|
|
234
|
-
this.bindings.set(key, factory);
|
|
235
|
-
}
|
|
236
|
-
get(key) {
|
|
237
|
-
if (this.services.has(key)) {
|
|
238
|
-
return this.services.get(key);
|
|
239
|
-
}
|
|
240
|
-
const singletonFactory = this.singletonFactories.get(key);
|
|
241
|
-
if (singletonFactory) {
|
|
242
|
-
const value = singletonFactory(this);
|
|
243
|
-
this.services.set(key, value);
|
|
244
|
-
return value;
|
|
245
|
-
}
|
|
246
|
-
const binding = this.bindings.get(key);
|
|
247
|
-
if (binding) {
|
|
248
|
-
return binding(this);
|
|
249
|
-
}
|
|
250
|
-
throw new Error(`Service "${key}" is not registered.`);
|
|
251
|
-
}
|
|
252
|
-
resolve(key) {
|
|
253
|
-
return this.get(key);
|
|
254
|
-
}
|
|
255
|
-
has(key) {
|
|
256
|
-
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
class ConfigStore {
|
|
261
|
-
values = new Map;
|
|
262
|
-
set(key, value) {
|
|
263
|
-
this.values.set(key, value);
|
|
264
|
-
return value;
|
|
265
|
-
}
|
|
266
|
-
get(key) {
|
|
267
|
-
return this.values.get(key);
|
|
268
|
-
}
|
|
269
|
-
require(key) {
|
|
270
|
-
if (!this.values.has(key)) {
|
|
271
|
-
throw new Error(`Config key "${key}" is not defined.`);
|
|
272
|
-
}
|
|
273
|
-
return this.values.get(key);
|
|
274
|
-
}
|
|
275
|
-
has(key) {
|
|
276
|
-
return this.values.has(key);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
var requiredDependencyKeys = [
|
|
280
|
-
"container",
|
|
281
|
-
"cache",
|
|
282
|
-
"storage"
|
|
283
|
-
];
|
|
284
|
-
function getRequiredDependency(dependencies, key) {
|
|
285
|
-
const dependency = dependencies[key];
|
|
286
|
-
if (dependency === undefined) {
|
|
287
|
-
throw new Error(`Required dependency "${key}" is not registered.`);
|
|
288
|
-
}
|
|
289
|
-
return dependency;
|
|
290
|
-
}
|
|
291
|
-
function assertAppDependenciesComplete(dependencies) {
|
|
292
|
-
for (const key of requiredDependencyKeys) {
|
|
293
|
-
getRequiredDependency(dependencies, key);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function resolveService(dependencies, token) {
|
|
297
|
-
return dependencies.container.resolve(token);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// ../../src/bootstrap/applicationRegistry.ts
|
|
301
|
-
var activeContext;
|
|
302
|
-
function setActiveApplicationContext(context) {
|
|
303
|
-
activeContext = context;
|
|
304
|
-
}
|
|
305
|
-
function requireActiveApplicationContext() {
|
|
306
|
-
if (!activeContext) {
|
|
307
|
-
throw new Error("The application context has not been bootstrapped.");
|
|
308
|
-
}
|
|
309
|
-
return activeContext;
|
|
310
|
-
}
|
|
311
|
-
function resolveApplicationCache() {
|
|
312
|
-
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
313
|
-
}
|
|
314
|
-
function resolveApplicationQueue() {
|
|
315
|
-
return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
|
|
316
|
-
}
|
|
317
|
-
function resolveApplicationAuth() {
|
|
318
|
-
return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
|
|
319
|
-
}
|
|
320
|
-
function resolveApplicationPolicyGate() {
|
|
321
|
-
return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
|
|
322
|
-
}
|
|
323
|
-
function resolveApplicationConfig() {
|
|
324
|
-
return requireActiveApplicationContext().config;
|
|
325
|
-
}
|
|
326
|
-
function resolveApplicationLogger() {
|
|
327
|
-
return appLogger;
|
|
328
|
-
}
|
|
329
|
-
function resolveApplicationDependencies() {
|
|
330
|
-
return requireActiveApplicationContext().dependencies;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
173
|
// ../../src/bootstrap/http/securedRouteModelBinding.ts
|
|
334
174
|
function isMutatingPolicyAction(action) {
|
|
335
175
|
return action === "update" || action === "delete";
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/membershipService.ts
|
|
3
|
+
import { resolveApplicationDependencies } from "@getstrata/core";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/errors/http.ts
|
|
6
|
+
class HttpError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
details;
|
|
9
|
+
constructor(status, message, details) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = new.target.name;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.details = details;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class BadRequestError extends HttpError {
|
|
18
|
+
constructor(message = "Bad Request", details) {
|
|
19
|
+
super(400, message, details);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
class ConflictError extends HttpError {
|
|
23
|
+
constructor(message = "Conflict", details) {
|
|
24
|
+
super(409, message, details);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class UnprocessableEntityError extends HttpError {
|
|
29
|
+
constructor(message = "Unprocessable Entity", details) {
|
|
30
|
+
super(422, message, details);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
class ForbiddenError extends HttpError {
|
|
34
|
+
constructor(message = "Forbidden", details) {
|
|
35
|
+
super(403, message, details);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class UnauthorizedError extends HttpError {
|
|
40
|
+
constructor(message = "Unauthorized", details) {
|
|
41
|
+
super(401, message, details);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
class PreconditionFailedError extends HttpError {
|
|
45
|
+
constructor(message = "Precondition Failed", details) {
|
|
46
|
+
super(412, message, details);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
51
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
52
|
+
function createAsyncContextStore(key) {
|
|
53
|
+
const symbol = Symbol.for(key);
|
|
54
|
+
const globalRecord = globalThis;
|
|
55
|
+
const existing = globalRecord[symbol];
|
|
56
|
+
if (existing) {
|
|
57
|
+
return existing;
|
|
58
|
+
}
|
|
59
|
+
const store = new AsyncLocalStorage;
|
|
60
|
+
globalRecord[symbol] = store;
|
|
61
|
+
return store;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ../../src/core/auth/authContext.ts
|
|
65
|
+
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
66
|
+
function currentAuthUser() {
|
|
67
|
+
return authContext.getStore() ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../../src/core/auth/accessControl.ts
|
|
71
|
+
var ROLE_RANK = {
|
|
72
|
+
member: 1,
|
|
73
|
+
admin: 2,
|
|
74
|
+
owner: 3
|
|
75
|
+
};
|
|
76
|
+
function isGlobalAdmin(user) {
|
|
77
|
+
return user?.role === "admin";
|
|
78
|
+
}
|
|
79
|
+
function hasMinimumOrgRole(role, minimum) {
|
|
80
|
+
if (!role) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return ROLE_RANK[role] >= ROLE_RANK[minimum];
|
|
84
|
+
}
|
|
85
|
+
function resolveUserId(user) {
|
|
86
|
+
const userId = typeof user.id === "number" ? user.id : Number(user.id);
|
|
87
|
+
if (!Number.isInteger(userId) || userId <= 0) {
|
|
88
|
+
throw new ForbiddenError("Invalid authenticated user.");
|
|
89
|
+
}
|
|
90
|
+
return userId;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ../../src/config/database.ts
|
|
94
|
+
function readInteger(name, fallback) {
|
|
95
|
+
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
96
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
97
|
+
}
|
|
98
|
+
var databaseConfig = {
|
|
99
|
+
url: process.env.DATABASE_URL ?? "",
|
|
100
|
+
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
101
|
+
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
102
|
+
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
103
|
+
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ../../src/core/database/connectionContext.ts
|
|
107
|
+
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
108
|
+
function getActiveDatabaseConnection(fallback) {
|
|
109
|
+
return activeConnection.getStore() ?? fallback;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ../../src/core/database/queryProxy.ts
|
|
113
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
114
|
+
function createDatabaseQueryProxy(pool) {
|
|
115
|
+
function resolveDatabase() {
|
|
116
|
+
return getActiveDatabaseConnection(pool);
|
|
117
|
+
}
|
|
118
|
+
function resolveDatabaseForProperty(property) {
|
|
119
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
120
|
+
return pool;
|
|
121
|
+
}
|
|
122
|
+
return resolveDatabase();
|
|
123
|
+
}
|
|
124
|
+
return new Proxy(function database() {}, {
|
|
125
|
+
apply(_target, _thisArg, args) {
|
|
126
|
+
return resolveDatabase()(...args);
|
|
127
|
+
},
|
|
128
|
+
get(_target, property) {
|
|
129
|
+
const connection = resolveDatabaseForProperty(property);
|
|
130
|
+
const value = connection[property];
|
|
131
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ../../src/core/database/defaultConnection.ts
|
|
137
|
+
var defaultPool = {
|
|
138
|
+
connection: null
|
|
139
|
+
};
|
|
140
|
+
var defaultQuery = {
|
|
141
|
+
connection: null
|
|
142
|
+
};
|
|
143
|
+
function registerDefaultDatabasePool(connection) {
|
|
144
|
+
defaultPool.connection = connection;
|
|
145
|
+
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
146
|
+
}
|
|
147
|
+
function getDefaultDatabaseQuery() {
|
|
148
|
+
if (!defaultQuery.connection) {
|
|
149
|
+
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
150
|
+
}
|
|
151
|
+
return defaultQuery.connection;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ../../src/db/connection/createConnection.ts
|
|
155
|
+
var {SQL } = globalThis.Bun;
|
|
156
|
+
function createDatabaseConnection(config) {
|
|
157
|
+
if (!config.url) {
|
|
158
|
+
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
159
|
+
}
|
|
160
|
+
return new SQL({
|
|
161
|
+
url: config.url,
|
|
162
|
+
max: config.poolMax,
|
|
163
|
+
idleTimeout: config.idleTimeoutSeconds,
|
|
164
|
+
maxLifetime: config.maxLifetimeSeconds,
|
|
165
|
+
connectionTimeout: config.connectionTimeoutSeconds
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ../../src/db/connection/index.ts
|
|
170
|
+
var connectionHolder = {
|
|
171
|
+
connection: null
|
|
172
|
+
};
|
|
173
|
+
function getDatabase() {
|
|
174
|
+
if (!connectionHolder.connection) {
|
|
175
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
176
|
+
registerDefaultDatabasePool(connectionHolder.connection);
|
|
177
|
+
}
|
|
178
|
+
return connectionHolder.connection;
|
|
179
|
+
}
|
|
180
|
+
function getDb() {
|
|
181
|
+
getDatabase();
|
|
182
|
+
return getDefaultDatabaseQuery();
|
|
183
|
+
}
|
|
184
|
+
var db = new Proxy(function database() {}, {
|
|
185
|
+
apply(_target, _thisArg, args) {
|
|
186
|
+
return getDb()(...args);
|
|
187
|
+
},
|
|
188
|
+
get(_target, property) {
|
|
189
|
+
const connection = getDb();
|
|
190
|
+
const value = connection[property];
|
|
191
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
var connection_default = db;
|
|
195
|
+
|
|
196
|
+
// ../../src/modules/organization/memberRepository.ts
|
|
197
|
+
class OrganizationMemberRepository {
|
|
198
|
+
constructor() {}
|
|
199
|
+
async findMembership(userId, organizationId) {
|
|
200
|
+
const rows = await connection_default`
|
|
201
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
202
|
+
FROM organization_member
|
|
203
|
+
WHERE user_id = ${userId} AND organization_id = ${organizationId}
|
|
204
|
+
LIMIT 1
|
|
205
|
+
`;
|
|
206
|
+
return rows[0] ?? null;
|
|
207
|
+
}
|
|
208
|
+
async listForUser(userId) {
|
|
209
|
+
return await connection_default`
|
|
210
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
211
|
+
FROM organization_member
|
|
212
|
+
WHERE user_id = ${userId}
|
|
213
|
+
ORDER BY organization_id
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
async listForOrganization(organizationId) {
|
|
217
|
+
return await connection_default`
|
|
218
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
219
|
+
FROM organization_member
|
|
220
|
+
WHERE organization_id = ${organizationId}
|
|
221
|
+
ORDER BY id
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
async addMember(input) {
|
|
225
|
+
const rows = await connection_default`
|
|
226
|
+
INSERT INTO organization_member (organization_id, user_id, role)
|
|
227
|
+
VALUES (${input.organizationId}, ${input.userId}, ${input.role ?? "member"})
|
|
228
|
+
RETURNING id, organization_id, user_id, role, created_at
|
|
229
|
+
`;
|
|
230
|
+
const row = rows[0];
|
|
231
|
+
if (!row) {
|
|
232
|
+
throw new Error("Organization member insert did not return a row.");
|
|
233
|
+
}
|
|
234
|
+
return row;
|
|
235
|
+
}
|
|
236
|
+
async removeMember(organizationId, userId) {
|
|
237
|
+
const rows = await connection_default`
|
|
238
|
+
DELETE FROM organization_member
|
|
239
|
+
WHERE organization_id = ${organizationId} AND user_id = ${userId}
|
|
240
|
+
RETURNING id
|
|
241
|
+
`;
|
|
242
|
+
return rows.length > 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
var memberRepository_default = OrganizationMemberRepository;
|
|
246
|
+
|
|
247
|
+
// ../../src/core/auth/membershipContext.ts
|
|
248
|
+
var membershipContext = createAsyncContextStore("@getstrata/membershipContext");
|
|
249
|
+
var membershipRepository = new memberRepository_default;
|
|
250
|
+
|
|
251
|
+
// ../../src/core/auth/membershipService.ts
|
|
252
|
+
class MembershipService {
|
|
253
|
+
members;
|
|
254
|
+
constructor(members = membershipRepository) {
|
|
255
|
+
this.members = members;
|
|
256
|
+
}
|
|
257
|
+
async listOrganizationIdsForUser(userId) {
|
|
258
|
+
const memberships = await this.members.listForUser(userId);
|
|
259
|
+
return memberships.map((membership) => membership.organization_id);
|
|
260
|
+
}
|
|
261
|
+
async getOrgRole(userId, organizationId) {
|
|
262
|
+
const membership = await this.members.findMembership(userId, organizationId);
|
|
263
|
+
return membership?.role ?? null;
|
|
264
|
+
}
|
|
265
|
+
async requireOrgAccess(organizationId, minimumRole = "member", user = currentAuthUser()) {
|
|
266
|
+
if (!user) {
|
|
267
|
+
throw new ForbiddenError("Authentication required.");
|
|
268
|
+
}
|
|
269
|
+
if (isGlobalAdmin(user)) {
|
|
270
|
+
return "owner";
|
|
271
|
+
}
|
|
272
|
+
const role = await this.getOrgRole(resolveUserId(user), organizationId);
|
|
273
|
+
if (!role || !hasMinimumOrgRole(role, minimumRole)) {
|
|
274
|
+
throw new ForbiddenError("Organization membership required.");
|
|
275
|
+
}
|
|
276
|
+
return role;
|
|
277
|
+
}
|
|
278
|
+
async filterAccessibleOrganizationIds(organizationIds, user = currentAuthUser()) {
|
|
279
|
+
if (!user) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
if (isGlobalAdmin(user)) {
|
|
283
|
+
return organizationIds;
|
|
284
|
+
}
|
|
285
|
+
const allowed = new Set(await this.listOrganizationIdsForUser(resolveUserId(user)));
|
|
286
|
+
return organizationIds.filter((organizationId) => allowed.has(organizationId));
|
|
287
|
+
}
|
|
288
|
+
async addOwnerOnOrganizationCreate(organizationId, userId) {
|
|
289
|
+
await this.members.addMember({
|
|
290
|
+
organizationId,
|
|
291
|
+
userId,
|
|
292
|
+
role: "owner"
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
listMembersForOrganization(organizationId) {
|
|
296
|
+
return this.members.listForOrganization(organizationId);
|
|
297
|
+
}
|
|
298
|
+
addMember(input) {
|
|
299
|
+
return this.members.addMember(input);
|
|
300
|
+
}
|
|
301
|
+
removeMember(organizationId, userId) {
|
|
302
|
+
return this.members.removeMember(organizationId, userId);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
var membershipService_default = MembershipService;
|
|
306
|
+
|
|
307
|
+
// ../../src/bootstrap/membershipService.ts
|
|
308
|
+
function resolveMembershipService() {
|
|
309
|
+
const dependencies = resolveApplicationDependencies();
|
|
310
|
+
if (dependencies.container.has("core.membership")) {
|
|
311
|
+
return dependencies.container.resolve("core.membership");
|
|
312
|
+
}
|
|
313
|
+
return new membershipService_default;
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
resolveMembershipService
|
|
317
|
+
};
|
|
@@ -14,9 +14,22 @@ function isViewsEnabled() {
|
|
|
14
14
|
return readFrontendMode() === "server-htmx";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// ../../src/core/
|
|
17
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
18
18
|
import { AsyncLocalStorage } from "async_hooks";
|
|
19
|
-
|
|
19
|
+
function createAsyncContextStore(key) {
|
|
20
|
+
const symbol = Symbol.for(key);
|
|
21
|
+
const globalRecord = globalThis;
|
|
22
|
+
const existing = globalRecord[symbol];
|
|
23
|
+
if (existing) {
|
|
24
|
+
return existing;
|
|
25
|
+
}
|
|
26
|
+
const store = new AsyncLocalStorage;
|
|
27
|
+
globalRecord[symbol] = store;
|
|
28
|
+
return store;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ../../src/core/http/requestMetaContext.ts
|
|
32
|
+
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
20
33
|
function currentRequestMeta() {
|
|
21
34
|
return requestMetaContext.getStore() ?? {
|
|
22
35
|
ipAddress: null,
|
|
@@ -130,8 +143,7 @@ var databaseConfig = {
|
|
|
130
143
|
};
|
|
131
144
|
|
|
132
145
|
// ../../src/core/database/connectionContext.ts
|
|
133
|
-
|
|
134
|
-
var activeConnection = new AsyncLocalStorage2;
|
|
146
|
+
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
135
147
|
function getActiveDatabaseConnection(fallback) {
|
|
136
148
|
return activeConnection.getStore() ?? fallback;
|
|
137
149
|
}
|
|
@@ -218,6 +230,7 @@ var db = new Proxy(function database() {}, {
|
|
|
218
230
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
219
231
|
}
|
|
220
232
|
});
|
|
233
|
+
var connection_default = db;
|
|
221
234
|
|
|
222
235
|
// ../../src/modules/user/apiTokenTable.ts
|
|
223
236
|
import { defineTable } from "@getstrata/core/database";
|
|
@@ -441,8 +454,7 @@ import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from
|
|
|
441
454
|
var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
|
|
442
455
|
|
|
443
456
|
// ../../src/core/auth/authContext.ts
|
|
444
|
-
|
|
445
|
-
var authContext = new AsyncLocalStorage3;
|
|
457
|
+
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
446
458
|
function currentAuthUser() {
|
|
447
459
|
return authContext.getStore() ?? null;
|
|
448
460
|
}
|