@happyvertical/smrt-app-runtime 0.53.3 → 0.53.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.
- package/AGENTS.md +40 -0
- package/README.md +101 -0
- package/dist/application-identity.d.ts +43 -0
- package/dist/application-identity.d.ts.map +1 -0
- package/dist/chunks/src-DZGOVGsU.js +2258 -0
- package/dist/chunks/src-DZGOVGsU.js.map +1 -0
- package/dist/deployed-runtime.d.ts +11 -0
- package/dist/deployed-runtime.d.ts.map +1 -1
- package/dist/error-code.d.ts +3 -0
- package/dist/error-code.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1831
- package/dist/manifest.json +1 -1
- package/dist/operation-lock.d.ts +28 -0
- package/dist/operation-lock.d.ts.map +1 -0
- package/dist/provider-readiness.d.ts +48 -0
- package/dist/provider-readiness.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +9 -6
- package/dist/stale-reclaim.d.ts +33 -0
- package/dist/stale-reclaim.d.ts.map +1 -0
- package/dist/state-root.d.ts +42 -0
- package/dist/state-root.d.ts.map +1 -0
- package/dist/sveltekit/handlers.d.ts +83 -0
- package/dist/sveltekit/handlers.d.ts.map +1 -0
- package/dist/sveltekit/index.d.ts +20 -0
- package/dist/sveltekit/index.d.ts.map +1 -0
- package/dist/sveltekit/owner-setup.d.ts +102 -0
- package/dist/sveltekit/owner-setup.d.ts.map +1 -0
- package/dist/sveltekit/runtime.d.ts +277 -0
- package/dist/sveltekit/runtime.d.ts.map +1 -0
- package/dist/sveltekit/tenancy.d.ts +59 -0
- package/dist/sveltekit/tenancy.d.ts.map +1 -0
- package/dist/sveltekit.d.ts +2 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +704 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/writer-lease.d.ts +36 -0
- package/dist/writer-lease.d.ts.map +1 -0
- package/package.json +22 -8
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { A as initializeDeployedApplicationRuntime, M as resolveApplicationId, N as runtimeConfigurationFingerprint, _ as projectRuntimeDiagnostics, i as initializeLocalApplicationRuntime, j as isSessionTenantAuthorized, o as resolveLocalRuntimePaths, s as validateApplicationId } from "./chunks/src-DZGOVGsU.js";
|
|
2
|
+
import { loadConfig, resolveApplicationRuntime, resolveConfiguredApplicationRuntime } from "@happyvertical/smrt-config";
|
|
3
|
+
import { enableTenancy, getCurrentTenant, isTenancyEnabled } from "@happyvertical/smrt-tenancy";
|
|
4
|
+
import { PermissionResolver, RoleCollection, SessionService, TenantCollection, TenantStatus, getCurrentSessionPermissionContext, getRequestScopedDatabase, withPrincipalPermissionContext, withSessionPermissionContext } from "@happyvertical/smrt-users";
|
|
5
|
+
import { getDatabase } from "@happyvertical/sql";
|
|
6
|
+
import { fail, json, redirect } from "@sveltejs/kit";
|
|
7
|
+
//#region src/sveltekit/handlers.ts
|
|
8
|
+
var RUNTIME_DIAGNOSTICS_READ_PERMISSION = "runtime_diagnostics.read";
|
|
9
|
+
function createRuntimeHealthHandler(runtime) {
|
|
10
|
+
return async () => json(await runtime.health());
|
|
11
|
+
}
|
|
12
|
+
function resolveInputValue(value) {
|
|
13
|
+
return typeof value === "function" ? value() : value;
|
|
14
|
+
}
|
|
15
|
+
function createRuntimeDiagnosticsHandler(options) {
|
|
16
|
+
const runtime = options.runtime;
|
|
17
|
+
const readDiagnostics = options.readDiagnostics ?? (runtime ? () => runtime.readDiagnostics({
|
|
18
|
+
toolNames: resolveInputValue(options.toolNames) ?? [],
|
|
19
|
+
observedAt: options.now?.() ?? /* @__PURE__ */ new Date(),
|
|
20
|
+
schemaStatus: resolveInputValue(options.schemaStatus) ?? "unknown",
|
|
21
|
+
migrationStatus: resolveInputValue(options.migrationStatus) ?? "unknown",
|
|
22
|
+
workerHeartbeatAt: options.workerHeartbeatAt?.() ?? null,
|
|
23
|
+
recentErrors: options.recentErrors?.() ?? []
|
|
24
|
+
}) : void 0);
|
|
25
|
+
const resolveRoleSlug = options.resolveRoleSlug ?? (runtime ? async (roleId) => {
|
|
26
|
+
const config = runtime.classOptions("Role");
|
|
27
|
+
const requestDb = getRequestScopedDatabase();
|
|
28
|
+
return (await (await RoleCollection.create(requestDb ? {
|
|
29
|
+
...config,
|
|
30
|
+
db: requestDb
|
|
31
|
+
} : config)).findById(roleId))?.slug ?? null;
|
|
32
|
+
} : void 0);
|
|
33
|
+
if (!readDiagnostics || !resolveRoleSlug) throw new TypeError("createRuntimeDiagnosticsHandler requires `runtime`, or both `readDiagnostics` and `resolveRoleSlug`.");
|
|
34
|
+
const resolveLocals = options.resolveLocals ?? ((event) => event.locals);
|
|
35
|
+
return async (event) => {
|
|
36
|
+
const locals = resolveLocals(event);
|
|
37
|
+
const principal = authenticatedPrincipal(locals);
|
|
38
|
+
if (!principal) return stableError(401, "authentication_required");
|
|
39
|
+
let authorized = locals.permissions?.includes(RUNTIME_DIAGNOSTICS_READ_PERMISSION) === true;
|
|
40
|
+
if (!authorized) try {
|
|
41
|
+
authorized = await resolveRoleSlug(principal.roleId) === "owner";
|
|
42
|
+
} catch {
|
|
43
|
+
authorized = false;
|
|
44
|
+
}
|
|
45
|
+
if (!authorized) return stableError(403, "authorization_denied");
|
|
46
|
+
try {
|
|
47
|
+
return json(await readDiagnostics());
|
|
48
|
+
} catch {
|
|
49
|
+
return stableError(503, "diagnostics_unavailable");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function authenticatedPrincipal(locals) {
|
|
54
|
+
const userId = locals?.user?.id;
|
|
55
|
+
const tenantId = locals?.tenantId;
|
|
56
|
+
const sessionId = locals?.sessionId;
|
|
57
|
+
const membership = locals?.membership;
|
|
58
|
+
if (typeof userId !== "string" || !userId || typeof tenantId !== "string" || !tenantId || typeof sessionId !== "string" || !sessionId || !membership || membership.userId !== userId || membership.tenantId !== tenantId || typeof membership.roleId !== "string" || !membership.roleId) return null;
|
|
59
|
+
try {
|
|
60
|
+
if (membership.isActive?.() !== true) return null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { roleId: membership.roleId };
|
|
65
|
+
}
|
|
66
|
+
function stableError(status, code) {
|
|
67
|
+
return json({
|
|
68
|
+
schemaVersion: 1,
|
|
69
|
+
error: { code }
|
|
70
|
+
}, { status });
|
|
71
|
+
}
|
|
72
|
+
function createSessionLayoutLoad() {
|
|
73
|
+
return ({ locals }) => {
|
|
74
|
+
const value = locals;
|
|
75
|
+
return { session: {
|
|
76
|
+
authenticated: Boolean(value.user),
|
|
77
|
+
activeTenantId: value.tenantId ?? null,
|
|
78
|
+
selectedTenantSlug: value.selectedTenantSlug ?? null
|
|
79
|
+
} };
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/sveltekit/owner-setup.ts
|
|
84
|
+
var OWNER_SETUP_FIELDS = Object.freeze({
|
|
85
|
+
token: "token",
|
|
86
|
+
name: "name",
|
|
87
|
+
email: "email",
|
|
88
|
+
tenantName: "tenantName"
|
|
89
|
+
});
|
|
90
|
+
var DEFAULT_OWNER_SETUP_MESSAGES = Object.freeze({
|
|
91
|
+
setup_disabled: "Local owner setup is disabled.",
|
|
92
|
+
setup_unavailable: "Local owner setup is available only from this device.",
|
|
93
|
+
setup_invalid_input: "Name, email, and a valid setup token are required.",
|
|
94
|
+
setup_invalid: "The setup invitation is invalid, expired, or already used. Run pnpm app:stop, pnpm app:recover, pnpm app:start, then pnpm app:open."
|
|
95
|
+
});
|
|
96
|
+
function createOwnerSetupPage(runtime, options = {}) {
|
|
97
|
+
const redirectTo = options.redirectTo ?? "/";
|
|
98
|
+
const messages = {
|
|
99
|
+
...DEFAULT_OWNER_SETUP_MESSAGES,
|
|
100
|
+
...options.messages
|
|
101
|
+
};
|
|
102
|
+
const failure = (status, code) => fail(status, {
|
|
103
|
+
code,
|
|
104
|
+
message: messages[code]
|
|
105
|
+
});
|
|
106
|
+
const resolveUser = options.resolveUser ?? ((event) => event.locals.user);
|
|
107
|
+
const load = async (event) => {
|
|
108
|
+
if (resolveUser(event)) redirect(303, redirectTo);
|
|
109
|
+
if ((await runtime.resolvedRuntime()).profile !== "local" || !isLoopbackRequest(event)) return {
|
|
110
|
+
available: false,
|
|
111
|
+
token: ""
|
|
112
|
+
};
|
|
113
|
+
const available = (await (await runtime.localRuntime()).diagnostics()).bootstrap.status !== "claimed";
|
|
114
|
+
return {
|
|
115
|
+
available,
|
|
116
|
+
token: available ? event.url.searchParams.get(OWNER_SETUP_FIELDS.token) || "" : ""
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const claim = async (event) => {
|
|
120
|
+
if ((await runtime.resolvedRuntime()).profile !== "local") return failure(404, "setup_disabled");
|
|
121
|
+
if (!isLoopbackRequest(event)) return failure(403, "setup_unavailable");
|
|
122
|
+
let form;
|
|
123
|
+
try {
|
|
124
|
+
form = await event.request.formData();
|
|
125
|
+
} catch {
|
|
126
|
+
return failure(400, "setup_invalid_input");
|
|
127
|
+
}
|
|
128
|
+
const field = (name2) => {
|
|
129
|
+
const value = form.get(name2);
|
|
130
|
+
return typeof value === "string" ? value : "";
|
|
131
|
+
};
|
|
132
|
+
const token = field(OWNER_SETUP_FIELDS.token);
|
|
133
|
+
const name = field(OWNER_SETUP_FIELDS.name);
|
|
134
|
+
const email = field(OWNER_SETUP_FIELDS.email);
|
|
135
|
+
const tenantName = field(OWNER_SETUP_FIELDS.tenantName).trim();
|
|
136
|
+
if (!token || !name.trim() || !email.includes("@")) return failure(400, "setup_invalid_input");
|
|
137
|
+
const local = await runtime.localRuntime();
|
|
138
|
+
let result;
|
|
139
|
+
try {
|
|
140
|
+
result = await local.claimOwner({
|
|
141
|
+
token,
|
|
142
|
+
name,
|
|
143
|
+
email,
|
|
144
|
+
...tenantName ? { tenantName } : {},
|
|
145
|
+
userAgent: event.request.headers.get("user-agent") || void 0,
|
|
146
|
+
ipAddress: event.getClientAddress()
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
return failure(400, "setup_invalid");
|
|
150
|
+
}
|
|
151
|
+
const cookie = runtime.sessionCookie;
|
|
152
|
+
event.cookies.set(cookie.name, result.sessionId, {
|
|
153
|
+
path: cookie.path,
|
|
154
|
+
...cookie.domain ? { domain: cookie.domain } : {},
|
|
155
|
+
httpOnly: true,
|
|
156
|
+
secure: cookie.secure ?? event.url.protocol === "https:",
|
|
157
|
+
sameSite: cookie.sameSite,
|
|
158
|
+
maxAge: cookie.maxAgeSeconds
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
await options.onOwnerClaimed?.(result, event);
|
|
162
|
+
} catch {}
|
|
163
|
+
redirect(303, redirectTo);
|
|
164
|
+
};
|
|
165
|
+
return Object.freeze({
|
|
166
|
+
load,
|
|
167
|
+
actions: Object.freeze({ default: claim })
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function isLoopbackRequest(event) {
|
|
171
|
+
let address;
|
|
172
|
+
try {
|
|
173
|
+
address = event.getClientAddress();
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return isLoopbackAddress(address) && isLoopbackHostname(event.url.hostname);
|
|
178
|
+
}
|
|
179
|
+
function isLoopbackAddress(value) {
|
|
180
|
+
if (typeof value !== "string") return false;
|
|
181
|
+
let host = value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
182
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
183
|
+
if (host.startsWith("::ffff:")) host = host.slice(7);
|
|
184
|
+
const octets = host.split(".");
|
|
185
|
+
return octets.length === 4 && octets.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255) && Number(octets[0]) === 127;
|
|
186
|
+
}
|
|
187
|
+
function isLoopbackHostname(hostname) {
|
|
188
|
+
const host = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
189
|
+
return host === "localhost" || isLoopbackAddress(host);
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/sveltekit/tenancy.ts
|
|
193
|
+
var ROOT_LIKE_HOSTS = /* @__PURE__ */ new Set([
|
|
194
|
+
"localhost",
|
|
195
|
+
"127.0.0.1",
|
|
196
|
+
"::1",
|
|
197
|
+
"[::1]"
|
|
198
|
+
]);
|
|
199
|
+
var RESERVED_SUBDOMAINS = /* @__PURE__ */ new Set([
|
|
200
|
+
"www",
|
|
201
|
+
"api",
|
|
202
|
+
"app",
|
|
203
|
+
"admin"
|
|
204
|
+
]);
|
|
205
|
+
var NO_SELECTION = Object.freeze({
|
|
206
|
+
tenantId: null,
|
|
207
|
+
tenantSlug: null
|
|
208
|
+
});
|
|
209
|
+
function selectTenantSlug(url, baseDomain = process.env.TENANT_BASE_DOMAIN) {
|
|
210
|
+
const hostname = url.hostname.toLowerCase();
|
|
211
|
+
if (ROOT_LIKE_HOSTS.has(hostname) || hostname.startsWith("[") || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return null;
|
|
212
|
+
const normalizedBase = baseDomain?.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
|
|
213
|
+
let candidate;
|
|
214
|
+
if (normalizedBase) {
|
|
215
|
+
if (hostname === normalizedBase) return null;
|
|
216
|
+
const suffix = `.${normalizedBase}`;
|
|
217
|
+
if (!hostname.endsWith(suffix)) return null;
|
|
218
|
+
candidate = hostname.slice(0, -suffix.length).split(".")[0];
|
|
219
|
+
} else {
|
|
220
|
+
const labels = hostname.split(".");
|
|
221
|
+
if (labels.length < 3) return null;
|
|
222
|
+
candidate = labels[0];
|
|
223
|
+
}
|
|
224
|
+
return candidate && !RESERVED_SUBDOMAINS.has(candidate) ? candidate : null;
|
|
225
|
+
}
|
|
226
|
+
function isMissingTenantTable(error) {
|
|
227
|
+
if (!(error instanceof Error)) return false;
|
|
228
|
+
return error.code === "42P01" || error.message.includes("relation \"tenants\" does not exist") || error.message.includes("no such table: tenants");
|
|
229
|
+
}
|
|
230
|
+
function createSubdomainTenantSelector(options) {
|
|
231
|
+
return async (event) => {
|
|
232
|
+
const tenantSlug = selectTenantSlug(event.url, options.baseDomain);
|
|
233
|
+
if (!tenantSlug) return NO_SELECTION;
|
|
234
|
+
let tenantId;
|
|
235
|
+
try {
|
|
236
|
+
const tenant = await (await TenantCollection.create(options.classOptions())).findBySlug(tenantSlug);
|
|
237
|
+
tenantId = tenant?.status === TenantStatus.ACTIVE ? tenant.id ?? null : null;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (!isMissingTenantTable(error)) throw error;
|
|
240
|
+
tenantId = null;
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
tenantId,
|
|
244
|
+
tenantSlug
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function normalizeTenantSelection(selection) {
|
|
249
|
+
return {
|
|
250
|
+
tenantId: typeof selection?.tenantId === "string" && selection.tenantId ? selection.tenantId : null,
|
|
251
|
+
tenantSlug: typeof selection?.tenantSlug === "string" && selection.tenantSlug ? selection.tenantSlug : null
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/sveltekit/runtime.ts
|
|
256
|
+
var DEFAULT_SESSION_TTL_SECONDS = 10080 * 60;
|
|
257
|
+
function createSmrtSvelteKitRuntime(options = {}) {
|
|
258
|
+
return composeSmrtSvelteKitRuntime(options, {});
|
|
259
|
+
}
|
|
260
|
+
function composeSmrtSvelteKitRuntime(options, seams) {
|
|
261
|
+
const env = options.env ?? process.env;
|
|
262
|
+
const sourceRoot = options.sourceRoot ?? process.cwd();
|
|
263
|
+
const dataDirectory = options.dataDirectory ?? env.SMRT_DATA_DIR;
|
|
264
|
+
const initializeLocal = seams.initializeLocal ?? initializeLocalApplicationRuntime;
|
|
265
|
+
const initializeDeployed = seams.initializeDeployed ?? initializeDeployedApplicationRuntime;
|
|
266
|
+
if (options.enableTenancy !== false && !isTenancyEnabled()) enableTenancy();
|
|
267
|
+
let appId;
|
|
268
|
+
const applicationId = () => {
|
|
269
|
+
appId ??= options.appId ? validateApplicationId(options.appId) : resolveApplicationId({
|
|
270
|
+
sourceRoot,
|
|
271
|
+
explicitId: env.SMRT_APP_ID
|
|
272
|
+
});
|
|
273
|
+
return appId;
|
|
274
|
+
};
|
|
275
|
+
let resolved;
|
|
276
|
+
let resolvedPromise;
|
|
277
|
+
const resolvedRuntime = () => {
|
|
278
|
+
resolvedPromise ??= (async () => {
|
|
279
|
+
const value = typeof options.runtime === "function" ? await options.runtime() : options.runtime ?? await (seams.loadRuntime ?? loadConfiguredRuntime)();
|
|
280
|
+
resolved = value;
|
|
281
|
+
return value;
|
|
282
|
+
})().catch((error) => {
|
|
283
|
+
resolvedPromise = void 0;
|
|
284
|
+
throw error;
|
|
285
|
+
});
|
|
286
|
+
return resolvedPromise;
|
|
287
|
+
};
|
|
288
|
+
const requireResolved = () => {
|
|
289
|
+
if (!resolved) throw new Error("The SMRT application runtime is not resolved yet; await init() first.");
|
|
290
|
+
return resolved;
|
|
291
|
+
};
|
|
292
|
+
const baseDatabaseConfig = () => {
|
|
293
|
+
const runtime = requireResolved();
|
|
294
|
+
if (runtime.profile === "local") return {
|
|
295
|
+
type: "sqlite",
|
|
296
|
+
url: resolveLocalRuntimePaths({
|
|
297
|
+
appId: applicationId(),
|
|
298
|
+
dataDirectory,
|
|
299
|
+
sourceRoot,
|
|
300
|
+
env
|
|
301
|
+
}).database
|
|
302
|
+
};
|
|
303
|
+
const databaseUrl = env.DATABASE_URL;
|
|
304
|
+
if (!databaseUrl) throw new Error(`${runtime.profile} requires DATABASE_URL.`);
|
|
305
|
+
return {
|
|
306
|
+
type: "postgres",
|
|
307
|
+
url: databaseUrl
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
const baseClassOptions = (className) => {
|
|
311
|
+
const defaults = { db: baseDatabaseConfig() };
|
|
312
|
+
const override = options.classOverrides?.[className];
|
|
313
|
+
return override ? {
|
|
314
|
+
...defaults,
|
|
315
|
+
...override
|
|
316
|
+
} : defaults;
|
|
317
|
+
};
|
|
318
|
+
const rlsRequestDatabase = () => {
|
|
319
|
+
if (getCurrentSessionPermissionContext()?.postgresRls !== true) return void 0;
|
|
320
|
+
return getRequestScopedDatabase() ?? void 0;
|
|
321
|
+
};
|
|
322
|
+
const databaseConfig = () => rlsRequestDatabase() ?? baseDatabaseConfig();
|
|
323
|
+
const classOptions = (className) => {
|
|
324
|
+
const base = baseClassOptions(className);
|
|
325
|
+
if (options.classOverrides?.[className]?.db !== void 0) return base;
|
|
326
|
+
const requestDb = rlsRequestDatabase();
|
|
327
|
+
return requestDb ? {
|
|
328
|
+
...base,
|
|
329
|
+
db: requestDb
|
|
330
|
+
} : base;
|
|
331
|
+
};
|
|
332
|
+
let localPromise;
|
|
333
|
+
let localLease;
|
|
334
|
+
const localRuntime = async () => {
|
|
335
|
+
const runtime = await resolvedRuntime();
|
|
336
|
+
if (runtime.profile !== "local") throw new Error("Owner bootstrap is available only in the local profile.");
|
|
337
|
+
if (localPromise) return localPromise;
|
|
338
|
+
const bindHost = options.bindHost || env.HOST || (env.NODE_ENV === "development" ? "127.0.0.1" : null);
|
|
339
|
+
if (!bindHost) throw new Error("Local production startup requires an explicit loopback HOST; use pnpm app:start.");
|
|
340
|
+
const id = applicationId();
|
|
341
|
+
localLease ??= options.acquireWriterLease?.({
|
|
342
|
+
appId: id,
|
|
343
|
+
dataDirectory,
|
|
344
|
+
sourceRoot
|
|
345
|
+
});
|
|
346
|
+
localPromise = initializeLocal({
|
|
347
|
+
appId: id,
|
|
348
|
+
dataDirectory,
|
|
349
|
+
sourceRoot,
|
|
350
|
+
env,
|
|
351
|
+
bindHost,
|
|
352
|
+
providers: {
|
|
353
|
+
database: runtime.providers.database,
|
|
354
|
+
authentication: runtime.providers.authentication,
|
|
355
|
+
tenancy: runtime.providers.tenancy,
|
|
356
|
+
assets: runtime.providers.assets,
|
|
357
|
+
secrets: runtime.providers.secrets,
|
|
358
|
+
jobs: runtime.providers.jobs,
|
|
359
|
+
network: runtime.providers.network
|
|
360
|
+
},
|
|
361
|
+
backgroundJobs: options.backgroundJobs ?? env.SMRT_BACKGROUND_JOBS === "true",
|
|
362
|
+
paidCapabilities: options.paidCapabilities,
|
|
363
|
+
bootstrapTtlSeconds: options.bootstrapTtlSeconds,
|
|
364
|
+
sessionTtlSeconds: options.sessionTtlSeconds,
|
|
365
|
+
prepareDatabase: options.prepareDatabase,
|
|
366
|
+
now: options.now
|
|
367
|
+
}).then(({ runtime: initialized, bootstrap }) => {
|
|
368
|
+
if (bootstrap) try {
|
|
369
|
+
options.onBootstrapInvitation?.(bootstrap, { bindHost });
|
|
370
|
+
} catch {}
|
|
371
|
+
return initialized;
|
|
372
|
+
}).catch((error) => {
|
|
373
|
+
localLease?.release();
|
|
374
|
+
localLease = void 0;
|
|
375
|
+
localPromise = void 0;
|
|
376
|
+
throw error;
|
|
377
|
+
});
|
|
378
|
+
return localPromise;
|
|
379
|
+
};
|
|
380
|
+
let deployedPromise;
|
|
381
|
+
const deployedRuntime = async () => {
|
|
382
|
+
const runtime = await resolvedRuntime();
|
|
383
|
+
if (runtime.profile === "local") throw new Error("deployed_runtime_unavailable");
|
|
384
|
+
const profile = runtime.profile;
|
|
385
|
+
const databaseUrl = env.DATABASE_URL;
|
|
386
|
+
if (!databaseUrl) throw new Error(`${profile} requires DATABASE_URL.`);
|
|
387
|
+
const authenticationProvider = runtime.providers.authentication.provider;
|
|
388
|
+
if (authenticationProvider === "owner-bootstrap") throw new Error("Deployed profiles require public authentication.");
|
|
389
|
+
const readiness = options.providerReadiness;
|
|
390
|
+
if (!readiness) throw new Error(`${profile} requires providerReadiness probes for authentication, assets, and secrets.`);
|
|
391
|
+
deployedPromise ??= initializeDeployed({
|
|
392
|
+
profile,
|
|
393
|
+
providers: {
|
|
394
|
+
database: runtime.providers.database,
|
|
395
|
+
authentication: runtime.providers.authentication,
|
|
396
|
+
tenancy: runtime.providers.tenancy,
|
|
397
|
+
assets: runtime.providers.assets,
|
|
398
|
+
secrets: runtime.providers.secrets,
|
|
399
|
+
jobs: runtime.providers.jobs,
|
|
400
|
+
network: runtime.providers.network
|
|
401
|
+
},
|
|
402
|
+
database: {
|
|
403
|
+
engine: "postgres",
|
|
404
|
+
connect: () => getDatabase({
|
|
405
|
+
type: "postgres",
|
|
406
|
+
url: databaseUrl
|
|
407
|
+
}),
|
|
408
|
+
close: async (db) => {
|
|
409
|
+
await db.close?.();
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
authentication: {
|
|
413
|
+
provider: authenticationProvider,
|
|
414
|
+
readiness: readiness("authentication", {
|
|
415
|
+
profile,
|
|
416
|
+
provider: authenticationProvider
|
|
417
|
+
})
|
|
418
|
+
},
|
|
419
|
+
assets: {
|
|
420
|
+
provider: runtime.providers.assets.provider,
|
|
421
|
+
readiness: readiness("assets", {
|
|
422
|
+
profile,
|
|
423
|
+
provider: runtime.providers.assets.provider
|
|
424
|
+
})
|
|
425
|
+
},
|
|
426
|
+
secrets: {
|
|
427
|
+
provider: runtime.providers.secrets.provider,
|
|
428
|
+
readiness: readiness("secrets", {
|
|
429
|
+
profile,
|
|
430
|
+
provider: runtime.providers.secrets.provider
|
|
431
|
+
})
|
|
432
|
+
},
|
|
433
|
+
prepareDatabase: options.prepareDatabase
|
|
434
|
+
});
|
|
435
|
+
return deployedPromise;
|
|
436
|
+
};
|
|
437
|
+
const ready = async () => {
|
|
438
|
+
if ((await resolvedRuntime()).profile === "local") {
|
|
439
|
+
await localRuntime();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
await deployedRuntime();
|
|
443
|
+
};
|
|
444
|
+
const configurationFingerprint = () => runtimeConfigurationFingerprint(requireResolved(), env);
|
|
445
|
+
const health = async () => {
|
|
446
|
+
await ready();
|
|
447
|
+
const runtime = requireResolved();
|
|
448
|
+
const publicHealth = {
|
|
449
|
+
schemaVersion: 1,
|
|
450
|
+
status: "ready",
|
|
451
|
+
profile: runtime.profile
|
|
452
|
+
};
|
|
453
|
+
return runtime.profile === "local" ? {
|
|
454
|
+
...publicHealth,
|
|
455
|
+
application: applicationId(),
|
|
456
|
+
instance: env.SMRT_PROCESS_INSTANCE || null,
|
|
457
|
+
configuration: configurationFingerprint()
|
|
458
|
+
} : publicHealth;
|
|
459
|
+
};
|
|
460
|
+
const readDiagnostics = async (diagnosticsOptions) => {
|
|
461
|
+
await ready();
|
|
462
|
+
const runtime = requireResolved();
|
|
463
|
+
const schema = {
|
|
464
|
+
status: diagnosticsOptions.schemaStatus ?? "unknown",
|
|
465
|
+
migrations: diagnosticsOptions.migrationStatus ?? "unknown"
|
|
466
|
+
};
|
|
467
|
+
if (runtime.profile === "local") {
|
|
468
|
+
const diagnostics = await (await localRuntime()).diagnostics();
|
|
469
|
+
return projectRuntimeDiagnostics({
|
|
470
|
+
profile: "local",
|
|
471
|
+
health: "healthy",
|
|
472
|
+
schema,
|
|
473
|
+
capabilities: {
|
|
474
|
+
"asset-storage": "available",
|
|
475
|
+
authentication: "available",
|
|
476
|
+
"background-jobs": diagnostics.jobs.backgroundEnabled ? "available" : "disabled",
|
|
477
|
+
database: "available",
|
|
478
|
+
"paid-capabilities": diagnostics.paidCapabilitiesEnabled ? "available" : "disabled",
|
|
479
|
+
"secret-storage": "available"
|
|
480
|
+
},
|
|
481
|
+
toolNames: diagnosticsOptions.toolNames,
|
|
482
|
+
worker: {
|
|
483
|
+
topology: diagnostics.jobs.topology,
|
|
484
|
+
required: diagnostics.jobs.backgroundEnabled,
|
|
485
|
+
heartbeatAt: diagnosticsOptions.workerHeartbeatAt
|
|
486
|
+
},
|
|
487
|
+
recentErrors: diagnosticsOptions.recentErrors,
|
|
488
|
+
observedAt: diagnosticsOptions.observedAt
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const deployed = await deployedPromise;
|
|
492
|
+
if (!deployed) throw new Error("deployed_runtime_unavailable");
|
|
493
|
+
const readiness = await deployed.readiness();
|
|
494
|
+
const componentStatus = (component) => readiness.components[component].status === "ready" ? "available" : "unavailable";
|
|
495
|
+
return projectRuntimeDiagnostics({
|
|
496
|
+
profile: runtime.profile,
|
|
497
|
+
health: deployed.health().status === "healthy" ? readiness.status === "ready" ? "healthy" : "degraded" : "stopped",
|
|
498
|
+
schema,
|
|
499
|
+
capabilities: {
|
|
500
|
+
"asset-storage": componentStatus("assets"),
|
|
501
|
+
authentication: componentStatus("authentication"),
|
|
502
|
+
"background-jobs": "available",
|
|
503
|
+
database: componentStatus("database"),
|
|
504
|
+
"paid-capabilities": "unknown",
|
|
505
|
+
"secret-storage": componentStatus("secrets")
|
|
506
|
+
},
|
|
507
|
+
toolNames: diagnosticsOptions.toolNames,
|
|
508
|
+
worker: {
|
|
509
|
+
topology: deployed.resolvedRuntime.providers.jobs.topology,
|
|
510
|
+
required: true,
|
|
511
|
+
heartbeatAt: diagnosticsOptions.workerHeartbeatAt
|
|
512
|
+
},
|
|
513
|
+
recentErrors: diagnosticsOptions.recentErrors,
|
|
514
|
+
observedAt: diagnosticsOptions.observedAt
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
const selectTenant = options.selectTenant ?? createSubdomainTenantSelector({
|
|
518
|
+
baseDomain: options.tenantBaseDomain ?? env.TENANT_BASE_DOMAIN,
|
|
519
|
+
classOptions: () => baseClassOptions("Tenant")
|
|
520
|
+
});
|
|
521
|
+
const readinessHandle = async ({ event, resolve }) => {
|
|
522
|
+
await ready();
|
|
523
|
+
return resolve(event);
|
|
524
|
+
};
|
|
525
|
+
const tenantSelectionHandle = async ({ event, resolve }) => {
|
|
526
|
+
const locals = runtimeLocals(event.locals);
|
|
527
|
+
const selection = normalizeTenantSelection(await selectTenant({
|
|
528
|
+
url: event.url,
|
|
529
|
+
request: event.request
|
|
530
|
+
}));
|
|
531
|
+
locals.selectedTenantId = selection.tenantId;
|
|
532
|
+
locals.selectedTenantSlug = selection.tenantSlug;
|
|
533
|
+
return resolve(event);
|
|
534
|
+
};
|
|
535
|
+
const sessionCookieName = options.session?.cookieName ?? "sid";
|
|
536
|
+
const sessionSkipPaths = options.session?.skipPaths ?? [];
|
|
537
|
+
const sessionServiceOptions = () => ({
|
|
538
|
+
...baseClassOptions("Session"),
|
|
539
|
+
...options.session,
|
|
540
|
+
defaultTTL: options.session?.ttl ?? DEFAULT_SESSION_TTL_SECONDS,
|
|
541
|
+
autoExtend: options.session?.autoExtend ?? false
|
|
542
|
+
});
|
|
543
|
+
const postgresRls = () => requireResolved().providers.tenancy.isolation === "database-rls" ? true : options.session?.postgresRls;
|
|
544
|
+
let permissionResolverPromise;
|
|
545
|
+
const permissionResolver = () => {
|
|
546
|
+
permissionResolverPromise ??= PermissionResolver.create(baseClassOptions("Permission")).catch((error) => {
|
|
547
|
+
permissionResolverPromise = void 0;
|
|
548
|
+
throw error;
|
|
549
|
+
});
|
|
550
|
+
return permissionResolverPromise;
|
|
551
|
+
};
|
|
552
|
+
const runAsPrincipal = async (principal, fn) => {
|
|
553
|
+
const userId = principal?.id;
|
|
554
|
+
const tenantId = principal?.tenantId;
|
|
555
|
+
if (typeof userId !== "string" || userId.length === 0) throw new Error("A bound principal requires a user id.");
|
|
556
|
+
if (typeof tenantId !== "string" || tenantId.length === 0) throw new Error("A bound principal requires a tenant.");
|
|
557
|
+
const resolved2 = await (await permissionResolver()).resolvePermissions(userId, tenantId);
|
|
558
|
+
if (!resolved2.membershipId) throw new Error("The bound principal has no membership in its tenant.");
|
|
559
|
+
const cap = principal.scopes ? new Set(principal.scopes) : void 0;
|
|
560
|
+
const permissions = [...resolved2.permissions].filter((permission) => !cap || cap.has(permission));
|
|
561
|
+
return withPrincipalPermissionContext({
|
|
562
|
+
...baseClassOptions("Session"),
|
|
563
|
+
userId,
|
|
564
|
+
tenantId,
|
|
565
|
+
permissions,
|
|
566
|
+
enterTenantContext: true,
|
|
567
|
+
postgresRls: postgresRls()
|
|
568
|
+
}, () => fn({
|
|
569
|
+
...principal,
|
|
570
|
+
scopes: [...permissions]
|
|
571
|
+
}));
|
|
572
|
+
};
|
|
573
|
+
let sessionServicePromise;
|
|
574
|
+
const sessionService = () => {
|
|
575
|
+
sessionServicePromise ??= (async () => {
|
|
576
|
+
const service = new TenantAuthorizingSessionService(sessionServiceOptions(), requireResolved().providers.tenancy.context === "required");
|
|
577
|
+
await service.initialize();
|
|
578
|
+
return service;
|
|
579
|
+
})().catch((error) => {
|
|
580
|
+
sessionServicePromise = void 0;
|
|
581
|
+
throw error;
|
|
582
|
+
});
|
|
583
|
+
return sessionServicePromise;
|
|
584
|
+
};
|
|
585
|
+
const sessionStep = async ({ event, resolve }) => {
|
|
586
|
+
const locals = runtimeLocals(event.locals);
|
|
587
|
+
clearSessionLocals(locals);
|
|
588
|
+
const rls = postgresRls();
|
|
589
|
+
const skipped = sessionSkipPaths.some((path) => event.url.pathname.startsWith(path));
|
|
590
|
+
if (skipped && !rls) return resolve(event);
|
|
591
|
+
const sessionId = skipped ? void 0 : event.cookies.get(sessionCookieName);
|
|
592
|
+
if (!sessionId && !rls) return resolve(event);
|
|
593
|
+
let resolveEntered = false;
|
|
594
|
+
let downstream;
|
|
595
|
+
try {
|
|
596
|
+
const service = await sessionService();
|
|
597
|
+
return await withSessionPermissionContext({
|
|
598
|
+
...sessionServiceOptions(),
|
|
599
|
+
enterTenantContext: true,
|
|
600
|
+
postgresRls: rls,
|
|
601
|
+
sessionId: sessionId ?? null,
|
|
602
|
+
sessionService: service
|
|
603
|
+
}, async (context) => {
|
|
604
|
+
if (context.session) {
|
|
605
|
+
locals.user = context.user;
|
|
606
|
+
locals.membership = context.membership ?? null;
|
|
607
|
+
locals.permissions = context.permissions;
|
|
608
|
+
locals.tenantId = context.tenantId;
|
|
609
|
+
locals.sessionId = context.sessionId;
|
|
610
|
+
if (context.tenantId) verifiedSessionTenants.set(locals, context.tenantId);
|
|
611
|
+
}
|
|
612
|
+
resolveEntered = true;
|
|
613
|
+
try {
|
|
614
|
+
return await resolve(event);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
downstream = { error };
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
} catch (error) {
|
|
621
|
+
if (downstream) throw downstream.error;
|
|
622
|
+
if (resolveEntered) throw error;
|
|
623
|
+
clearSessionLocals(locals);
|
|
624
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const sessionCookie = Object.freeze({
|
|
628
|
+
name: options.session?.cookieName ?? "sid",
|
|
629
|
+
path: options.session?.cookiePath ?? "/",
|
|
630
|
+
...options.session?.cookieDomain ? { domain: options.session.cookieDomain } : {},
|
|
631
|
+
sameSite: options.session?.cookieSameSite ?? "lax",
|
|
632
|
+
...options.session?.cookieSecure !== void 0 ? { secure: options.session.cookieSecure } : {},
|
|
633
|
+
maxAgeSeconds: options.sessionTtlSeconds ?? DEFAULT_SESSION_TTL_SECONDS
|
|
634
|
+
});
|
|
635
|
+
const init = () => ready();
|
|
636
|
+
return Object.freeze({
|
|
637
|
+
handle: composeHandles(readinessHandle, tenantSelectionHandle, sessionStep, authorizedTenantLocalsHandle),
|
|
638
|
+
init,
|
|
639
|
+
sessionCookie,
|
|
640
|
+
resolvedRuntime,
|
|
641
|
+
ready,
|
|
642
|
+
applicationId,
|
|
643
|
+
configurationFingerprint,
|
|
644
|
+
databaseConfig,
|
|
645
|
+
classOptions,
|
|
646
|
+
runAsPrincipal,
|
|
647
|
+
localRuntime,
|
|
648
|
+
deployedRuntime,
|
|
649
|
+
health,
|
|
650
|
+
readDiagnostics
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
var verifiedSessionTenants = /* @__PURE__ */ new WeakMap();
|
|
654
|
+
var TenantAuthorizingSessionService = class extends SessionService {
|
|
655
|
+
tenantRequired;
|
|
656
|
+
constructor(options, tenantRequired) {
|
|
657
|
+
super(options);
|
|
658
|
+
this.tenantRequired = tenantRequired;
|
|
659
|
+
}
|
|
660
|
+
async loadSessionContext(sessionId) {
|
|
661
|
+
const context = await super.loadSessionContext(sessionId);
|
|
662
|
+
if (!context) return null;
|
|
663
|
+
if (typeof context.tenantId === "string" && context.tenantId.length > 0) return isSessionTenantAuthorized(context) ? context : null;
|
|
664
|
+
return this.tenantRequired ? null : context;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
function clearSessionLocals(locals) {
|
|
668
|
+
locals.user = null;
|
|
669
|
+
locals.membership = null;
|
|
670
|
+
locals.permissions = [];
|
|
671
|
+
locals.tenantId = null;
|
|
672
|
+
locals.sessionId = null;
|
|
673
|
+
locals.tenantContext = void 0;
|
|
674
|
+
verifiedSessionTenants.delete(locals);
|
|
675
|
+
}
|
|
676
|
+
var authorizedTenantLocalsHandle = async ({ event, resolve }) => {
|
|
677
|
+
const locals = runtimeLocals(event.locals);
|
|
678
|
+
const activeContext = getCurrentTenant();
|
|
679
|
+
if (locals.user && locals.tenantId && verifiedSessionTenants.get(locals) === locals.tenantId && activeContext?.tenantId === locals.tenantId) locals.tenantContext = activeContext;
|
|
680
|
+
return resolve(event);
|
|
681
|
+
};
|
|
682
|
+
function composeHandles(...handles) {
|
|
683
|
+
return ({ event, resolve }) => {
|
|
684
|
+
const run = (index, current, resolveOptions) => {
|
|
685
|
+
const next = handles[index];
|
|
686
|
+
if (!next) return resolve(current, resolveOptions);
|
|
687
|
+
return next({
|
|
688
|
+
event: current,
|
|
689
|
+
resolve: (nextEvent, nextOptions) => run(index + 1, nextEvent, nextOptions ?? resolveOptions)
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
return run(0, event);
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function runtimeLocals(locals) {
|
|
696
|
+
return locals;
|
|
697
|
+
}
|
|
698
|
+
async function loadConfiguredRuntime() {
|
|
699
|
+
return (await loadConfig()).runtime ? resolveConfiguredApplicationRuntime() : resolveApplicationRuntime({ profile: "local" });
|
|
700
|
+
}
|
|
701
|
+
//#endregion
|
|
702
|
+
export { DEFAULT_OWNER_SETUP_MESSAGES, OWNER_SETUP_FIELDS, RUNTIME_DIAGNOSTICS_READ_PERMISSION, authorizedTenantLocalsHandle, composeHandles, createOwnerSetupPage, createRuntimeDiagnosticsHandler, createRuntimeHealthHandler, createSessionLayoutLoad, createSmrtSvelteKitRuntime, createSubdomainTenantSelector, isLoopbackAddress, isLoopbackHostname, isLoopbackRequest, normalizeTenantSelection, selectTenantSlug };
|
|
703
|
+
|
|
704
|
+
//# sourceMappingURL=sveltekit.js.map
|