@hachej/boring-core 0.1.63 → 0.1.65
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/CoreFront-DB1OfNJU.d.ts +65 -0
- package/dist/{PostgresMeteringStore-qjKGmVFr.d.ts → PostgresModelBudgetStore-CFbEEdQL.d.ts} +44 -2
- package/dist/app/front/index.d.ts +9 -8
- package/dist/app/front/index.js +4 -1
- package/dist/app/server/index.d.ts +3 -3
- package/dist/app/server/index.js +4 -2
- package/dist/{authHook-BbgCBHjP.d.ts → authHook-BUSGTx_o.d.ts} +2 -2
- package/dist/{chunk-NC46VGXS.js → chunk-3B7LU76T.js} +673 -502
- package/dist/{chunk-ZMS6O4CY.js → chunk-CIRY3ZLU.js} +128 -18
- package/dist/{chunk-BVZ2YT3M.js → chunk-EFM5IWTK.js} +412 -17
- package/dist/{connection-B1iC6B-w.d.ts → connection-D9s7Td0I.d.ts} +21 -2
- package/dist/front/index.d.ts +29 -27
- package/dist/front/index.js +7 -1
- package/dist/front/top-bar-slot.d.ts +2 -2
- package/dist/server/db/index.d.ts +31 -6
- package/dist/server/db/index.js +5 -1
- package/dist/server/index.d.ts +11 -8
- package/dist/server/index.js +13 -5
- package/dist/shared/index.d.ts +2 -2
- package/dist/{types-Bb7I-83I.d.ts → types-DVgeIw1d.d.ts} +1 -0
- package/drizzle/0015_model_budget_reservations.sql +22 -0
- package/drizzle/0016_workspace_managed_by.sql +1 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +30 -30
- package/dist/CoreFront-N0QJSYaM.d.ts +0 -23
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
workspaceRuntimes,
|
|
13
13
|
workspaceSettings,
|
|
14
14
|
workspaces
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EFM5IWTK.js";
|
|
16
16
|
import {
|
|
17
17
|
noopTelemetry,
|
|
18
18
|
safeCapture
|
|
@@ -599,6 +599,30 @@ async function closeDbPoolIfPresent(app) {
|
|
|
599
599
|
}
|
|
600
600
|
function installShutdownHandlers(app) {
|
|
601
601
|
let shuttingDown = false;
|
|
602
|
+
const activeRequests = /* @__PURE__ */ new Set();
|
|
603
|
+
const drainWaiters = /* @__PURE__ */ new Set();
|
|
604
|
+
const notifyRequestDrained = () => {
|
|
605
|
+
if (activeRequests.size > 0) return;
|
|
606
|
+
for (const resolve3 of drainWaiters) resolve3();
|
|
607
|
+
drainWaiters.clear();
|
|
608
|
+
};
|
|
609
|
+
app.addHook("onRequest", async (request) => {
|
|
610
|
+
activeRequests.add(request.id);
|
|
611
|
+
});
|
|
612
|
+
app.addHook("onResponse", async (request) => {
|
|
613
|
+
activeRequests.delete(request.id);
|
|
614
|
+
notifyRequestDrained();
|
|
615
|
+
});
|
|
616
|
+
app.addHook("onError", async (request) => {
|
|
617
|
+
activeRequests.delete(request.id);
|
|
618
|
+
notifyRequestDrained();
|
|
619
|
+
});
|
|
620
|
+
const waitForInflightRequests = async () => {
|
|
621
|
+
if (activeRequests.size === 0) return;
|
|
622
|
+
await new Promise((resolve3) => {
|
|
623
|
+
drainWaiters.add(resolve3);
|
|
624
|
+
});
|
|
625
|
+
};
|
|
602
626
|
const onSignal = async (signal) => {
|
|
603
627
|
if (shuttingDown) return;
|
|
604
628
|
shuttingDown = true;
|
|
@@ -606,7 +630,7 @@ function installShutdownHandlers(app) {
|
|
|
606
630
|
try {
|
|
607
631
|
let timeoutHandle;
|
|
608
632
|
const result = await Promise.race([
|
|
609
|
-
app.close(),
|
|
633
|
+
Promise.all([app.close(), waitForInflightRequests()]).then(() => "closed"),
|
|
610
634
|
new Promise((resolve3) => {
|
|
611
635
|
timeoutHandle = setTimeout(() => {
|
|
612
636
|
resolve3("timeout");
|
|
@@ -767,6 +791,24 @@ async function createCoreApp(config, options) {
|
|
|
767
791
|
return app;
|
|
768
792
|
}
|
|
769
793
|
|
|
794
|
+
// src/server/app/userSettingsLocks.ts
|
|
795
|
+
var userSettingsWriteQueues = /* @__PURE__ */ new Map();
|
|
796
|
+
function userSettingsWriteKey(userId, appId) {
|
|
797
|
+
return `${appId}\0${userId}`;
|
|
798
|
+
}
|
|
799
|
+
async function withUserSettingsWriteLock(userId, appId, fn) {
|
|
800
|
+
const key = userSettingsWriteKey(userId, appId);
|
|
801
|
+
const previous = userSettingsWriteQueues.get(key) ?? Promise.resolve();
|
|
802
|
+
const run = previous.catch(() => void 0).then(fn);
|
|
803
|
+
const settled = run.then(() => void 0, () => void 0);
|
|
804
|
+
userSettingsWriteQueues.set(key, settled);
|
|
805
|
+
try {
|
|
806
|
+
return await run;
|
|
807
|
+
} finally {
|
|
808
|
+
if (userSettingsWriteQueues.get(key) === settled) userSettingsWriteQueues.delete(key);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
770
812
|
// src/server/app/routes.ts
|
|
771
813
|
import fp from "fastify-plugin";
|
|
772
814
|
import { z as z2 } from "zod";
|
|
@@ -904,6 +946,24 @@ async function deleteUserCompletely(userId, deps) {
|
|
|
904
946
|
|
|
905
947
|
// src/server/app/routes.ts
|
|
906
948
|
var HEALTH_DB_TIMEOUT_MS = 2e3;
|
|
949
|
+
var SERVER_OWNED_USER_SETTINGS_PREFIX = "__server";
|
|
950
|
+
function stripServerOwnedUserSettings(next) {
|
|
951
|
+
const sanitized = { ...next };
|
|
952
|
+
for (const key of Object.keys(sanitized)) {
|
|
953
|
+
if (key.startsWith(SERVER_OWNED_USER_SETTINGS_PREFIX)) delete sanitized[key];
|
|
954
|
+
}
|
|
955
|
+
return sanitized;
|
|
956
|
+
}
|
|
957
|
+
function sanitizeUserSettingsResult(result) {
|
|
958
|
+
return { ...result, settings: stripServerOwnedUserSettings(result.settings) };
|
|
959
|
+
}
|
|
960
|
+
function preserveServerOwnedUserSettings(next, current) {
|
|
961
|
+
const sanitized = stripServerOwnedUserSettings(next);
|
|
962
|
+
for (const [key, value] of Object.entries(current)) {
|
|
963
|
+
if (key.startsWith(SERVER_OWNED_USER_SETTINGS_PREFIX)) sanitized[key] = value;
|
|
964
|
+
}
|
|
965
|
+
return sanitized;
|
|
966
|
+
}
|
|
907
967
|
async function pingDatabase(sqlClient, timeoutMs) {
|
|
908
968
|
const timeoutMessage = `Database ping timed out after ${timeoutMs}ms`;
|
|
909
969
|
let timeoutId;
|
|
@@ -964,7 +1024,7 @@ var routesPlugin = async (app, opts) => {
|
|
|
964
1024
|
app.get("/api/v1/me", async (request) => {
|
|
965
1025
|
const user = request.user;
|
|
966
1026
|
const settings = await userStore.getUserSettings(user.id, app.config.appId);
|
|
967
|
-
return { user, settings };
|
|
1027
|
+
return { user, settings: sanitizeUserSettingsResult(settings) };
|
|
968
1028
|
});
|
|
969
1029
|
app.put("/api/v1/me/settings", async (request, reply) => {
|
|
970
1030
|
const parsed = updateSettingsBody.safeParse(request.body);
|
|
@@ -976,13 +1036,19 @@ var routesPlugin = async (app, opts) => {
|
|
|
976
1036
|
requestId: request.id
|
|
977
1037
|
});
|
|
978
1038
|
}
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
parsed.data
|
|
983
|
-
|
|
1039
|
+
const userId = request.user.id;
|
|
1040
|
+
const clientSettings = parsed.data.settings ? stripServerOwnedUserSettings(parsed.data.settings) : void 0;
|
|
1041
|
+
const result = userStore.putClientUserSettings ? await userStore.putClientUserSettings(userId, app.config.appId, { ...parsed.data, ...clientSettings ? { settings: clientSettings } : {} }) : await withUserSettingsWriteLock(userId, app.config.appId, async () => {
|
|
1042
|
+
const current = parsed.data.settings ? await userStore.getUserSettings(userId, app.config.appId) : void 0;
|
|
1043
|
+
const updates = parsed.data.settings ? { ...parsed.data, settings: preserveServerOwnedUserSettings(parsed.data.settings, current?.settings ?? {}) } : parsed.data;
|
|
1044
|
+
return await userStore.putUserSettings(
|
|
1045
|
+
userId,
|
|
1046
|
+
app.config.appId,
|
|
1047
|
+
updates
|
|
1048
|
+
);
|
|
1049
|
+
});
|
|
984
1050
|
reply.status(200);
|
|
985
|
-
return result;
|
|
1051
|
+
return sanitizeUserSettingsResult(result);
|
|
986
1052
|
});
|
|
987
1053
|
app.delete("/api/v1/me", async (request, reply) => {
|
|
988
1054
|
const parsed = deleteMeBody.safeParse(request.body);
|
|
@@ -1715,14 +1781,14 @@ function createPostSignupHook(deps) {
|
|
|
1715
1781
|
import { betterAuth, APIError } from "better-auth";
|
|
1716
1782
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
1717
1783
|
import { magicLink } from "better-auth/plugins/magic-link";
|
|
1718
|
-
import {
|
|
1784
|
+
import { ZxcvbnFactory } from "@zxcvbn-ts/core";
|
|
1719
1785
|
import * as zxcvbnCommon from "@zxcvbn-ts/language-common";
|
|
1720
1786
|
import * as zxcvbnEn from "@zxcvbn-ts/language-en";
|
|
1721
1787
|
var MIN_ZXCVBN_SCORE = 2;
|
|
1722
|
-
var
|
|
1723
|
-
function
|
|
1724
|
-
if (
|
|
1725
|
-
|
|
1788
|
+
var zxcvbnInstance = null;
|
|
1789
|
+
function getZxcvbn() {
|
|
1790
|
+
if (zxcvbnInstance) return zxcvbnInstance;
|
|
1791
|
+
zxcvbnInstance = new ZxcvbnFactory({
|
|
1726
1792
|
translations: zxcvbnEn.translations,
|
|
1727
1793
|
graphs: zxcvbnCommon.adjacencyGraphs,
|
|
1728
1794
|
dictionary: {
|
|
@@ -1730,11 +1796,10 @@ function ensureZxcvbn() {
|
|
|
1730
1796
|
...zxcvbnEn.dictionary
|
|
1731
1797
|
}
|
|
1732
1798
|
});
|
|
1733
|
-
|
|
1799
|
+
return zxcvbnInstance;
|
|
1734
1800
|
}
|
|
1735
1801
|
function validatePasswordStrength(password) {
|
|
1736
|
-
|
|
1737
|
-
const result = zxcvbn(password);
|
|
1802
|
+
const result = getZxcvbn().check(password);
|
|
1738
1803
|
if (result.score < MIN_ZXCVBN_SCORE) {
|
|
1739
1804
|
return {
|
|
1740
1805
|
valid: false,
|
|
@@ -1748,6 +1813,29 @@ function buildMailTransport(config) {
|
|
|
1748
1813
|
const env = process.env.NODE_ENV === "production" ? "production" : process.env.NODE_ENV === "test" ? "test" : "development";
|
|
1749
1814
|
return createMailTransport(config.auth.mail.transportUrl, config.auth.mail.from, env);
|
|
1750
1815
|
}
|
|
1816
|
+
async function createReplayableRequest(request) {
|
|
1817
|
+
if (request.bodyUsed || request.method === "GET" || request.method === "HEAD") return request;
|
|
1818
|
+
const body2 = await request.text();
|
|
1819
|
+
const init = {
|
|
1820
|
+
method: request.method,
|
|
1821
|
+
headers: request.headers,
|
|
1822
|
+
body: body2,
|
|
1823
|
+
redirect: request.redirect,
|
|
1824
|
+
referrer: request.referrer,
|
|
1825
|
+
referrerPolicy: request.referrerPolicy,
|
|
1826
|
+
integrity: request.integrity,
|
|
1827
|
+
keepalive: request.keepalive,
|
|
1828
|
+
credentials: request.credentials,
|
|
1829
|
+
cache: request.cache,
|
|
1830
|
+
mode: request.mode
|
|
1831
|
+
};
|
|
1832
|
+
const replayable = new Request(request.url, init);
|
|
1833
|
+
Object.defineProperty(replayable, "clone", {
|
|
1834
|
+
configurable: true,
|
|
1835
|
+
value: () => new Request(request.url, init)
|
|
1836
|
+
});
|
|
1837
|
+
return replayable;
|
|
1838
|
+
}
|
|
1751
1839
|
function createAuth(config, db, opts) {
|
|
1752
1840
|
const transport = buildMailTransport(config);
|
|
1753
1841
|
const telemetry = opts?.telemetry ?? noopTelemetry;
|
|
@@ -1806,7 +1894,7 @@ function createAuth(config, db, opts) {
|
|
|
1806
1894
|
}
|
|
1807
1895
|
} : {}
|
|
1808
1896
|
};
|
|
1809
|
-
|
|
1897
|
+
const auth = betterAuth({
|
|
1810
1898
|
database: drizzleAdapter(db, { provider: "pg", schema: schema_exports }),
|
|
1811
1899
|
secret: config.auth.secret,
|
|
1812
1900
|
baseURL: config.auth.url,
|
|
@@ -1911,6 +1999,9 @@ function createAuth(config, db, opts) {
|
|
|
1911
1999
|
socialProviders,
|
|
1912
2000
|
plugins
|
|
1913
2001
|
});
|
|
2002
|
+
const handler = auth.handler.bind(auth);
|
|
2003
|
+
auth.handler = async (request) => handler(await createReplayableRequest(request));
|
|
2004
|
+
return auth;
|
|
1914
2005
|
}
|
|
1915
2006
|
|
|
1916
2007
|
// src/server/auth/authHook.ts
|
|
@@ -2040,6 +2131,7 @@ var updateWorkspaceBody = z3.object({
|
|
|
2040
2131
|
|
|
2041
2132
|
// src/server/routes/workspaces.ts
|
|
2042
2133
|
var DEFAULT_WORKSPACE_NAME = "Default workspace";
|
|
2134
|
+
var COMPANY_CONTEXT_WORKSPACE_MANAGED_BY = "company-context";
|
|
2043
2135
|
var workspaceRoutesPlugin = async (app) => {
|
|
2044
2136
|
const store = app.workspaceStore;
|
|
2045
2137
|
const provisioner = app.provisioner;
|
|
@@ -2193,6 +2285,23 @@ var workspaceRoutesPlugin = async (app) => {
|
|
|
2193
2285
|
async (request) => {
|
|
2194
2286
|
const { id } = request.params;
|
|
2195
2287
|
request.log.info({ workspaceId: id }, "workspace.delete.start");
|
|
2288
|
+
const workspace = await store.get(id);
|
|
2289
|
+
if (!workspace) {
|
|
2290
|
+
throw new HttpError({
|
|
2291
|
+
status: 404,
|
|
2292
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
2293
|
+
message: "Workspace not found",
|
|
2294
|
+
requestId: request.id
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
if (workspace.managedBy === COMPANY_CONTEXT_WORKSPACE_MANAGED_BY) {
|
|
2298
|
+
throw new HttpError({
|
|
2299
|
+
status: 403,
|
|
2300
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
2301
|
+
message: "Managed workspace cannot be deleted",
|
|
2302
|
+
requestId: request.id
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2196
2305
|
if (provisioner) {
|
|
2197
2306
|
try {
|
|
2198
2307
|
await provisioner.destroy(id);
|
|
@@ -2919,6 +3028,7 @@ export {
|
|
|
2919
3028
|
validateConfig,
|
|
2920
3029
|
buildRuntimeConfigPayload,
|
|
2921
3030
|
createCoreApp,
|
|
3031
|
+
withUserSettingsWriteLock,
|
|
2922
3032
|
registerRoutes,
|
|
2923
3033
|
MailDeliveryError,
|
|
2924
3034
|
createMailTransport,
|