@objectstack/runtime 6.5.0 → 6.6.0
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/index.cjs +276 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +279 -18
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -2141,6 +2141,7 @@ __export(index_exports, {
|
|
|
2141
2141
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
2142
2142
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
2143
2143
|
resolveMetrics: () => resolveMetrics,
|
|
2144
|
+
resolveObjectStackHome: () => resolveObjectStackHome,
|
|
2144
2145
|
resolveRequestId: () => resolveRequestId,
|
|
2145
2146
|
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
2146
2147
|
});
|
|
@@ -2196,8 +2197,17 @@ var Runtime = class {
|
|
|
2196
2197
|
// src/standalone-stack.ts
|
|
2197
2198
|
var import_node_path2 = require("path");
|
|
2198
2199
|
var import_node_fs = require("fs");
|
|
2200
|
+
var import_node_os = require("os");
|
|
2199
2201
|
var import_zod = require("zod");
|
|
2200
2202
|
init_load_artifact_bundle();
|
|
2203
|
+
function resolveObjectStackHome() {
|
|
2204
|
+
const raw = process.env.OS_HOME?.trim();
|
|
2205
|
+
if (raw && raw.length > 0) {
|
|
2206
|
+
if (raw.startsWith("~")) return (0, import_node_path2.resolve)((0, import_node_os.homedir)(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
2207
|
+
return (0, import_node_path2.resolve)(raw);
|
|
2208
|
+
}
|
|
2209
|
+
return (0, import_node_path2.resolve)((0, import_node_os.homedir)(), ".objectstack");
|
|
2210
|
+
}
|
|
2201
2211
|
var StandaloneStackConfigSchema = import_zod.z.object({
|
|
2202
2212
|
databaseUrl: import_zod.z.string().optional(),
|
|
2203
2213
|
databaseAuthToken: import_zod.z.string().optional(),
|
|
@@ -2228,7 +2238,7 @@ async function createStandaloneStack(config) {
|
|
|
2228
2238
|
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2229
2239
|
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path2.resolve)(cwd, "dist/objectstack.json");
|
|
2230
2240
|
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : (0, import_node_path2.resolve)(cwd, artifactPathInput);
|
|
2231
|
-
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? `file:${(0, import_node_path2.resolve)(
|
|
2241
|
+
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? `file:${(0, import_node_path2.resolve)(resolveObjectStackHome(), "data/standalone.db")}`;
|
|
2232
2242
|
const dbAuthToken = cfg.databaseAuthToken ?? process.env.OS_DATABASE_AUTH_TOKEN?.trim() ?? process.env.TURSO_AUTH_TOKEN?.trim();
|
|
2233
2243
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2234
2244
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
@@ -2348,12 +2358,42 @@ function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
|
2348
2358
|
}
|
|
2349
2359
|
async function createDefaultHostConfig(options = {}) {
|
|
2350
2360
|
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
2351
|
-
|
|
2361
|
+
let resolvedArtifact = resolveDefaultArtifactPath(standaloneOpts.artifactPath);
|
|
2352
2362
|
if (!resolvedArtifact && requireArtifact) {
|
|
2353
2363
|
throw new Error(
|
|
2354
2364
|
"[createDefaultHostConfig] No artifact source available. Set OS_ARTIFACT_PATH (file path or http(s):// URL), place the artifact at <cwd>/dist/objectstack.json, or pass `{ artifactPath: ... }` explicitly. To boot an empty kernel anyway, pass `{ requireArtifact: false }`."
|
|
2355
2365
|
);
|
|
2356
2366
|
}
|
|
2367
|
+
if (!resolvedArtifact && !requireArtifact) {
|
|
2368
|
+
const home = resolveObjectStackHome();
|
|
2369
|
+
const stubPath = (0, import_node_path3.resolve)(home, "dist/objectstack.json");
|
|
2370
|
+
if (!(0, import_node_fs2.existsSync)(stubPath)) {
|
|
2371
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(stubPath, ".."), { recursive: true });
|
|
2372
|
+
(0, import_node_fs2.writeFileSync)(
|
|
2373
|
+
stubPath,
|
|
2374
|
+
JSON.stringify(
|
|
2375
|
+
{
|
|
2376
|
+
manifest: {
|
|
2377
|
+
id: "com.objectstack.empty",
|
|
2378
|
+
name: "empty",
|
|
2379
|
+
version: "0.0.0",
|
|
2380
|
+
type: "app",
|
|
2381
|
+
description: "Empty starter kernel \u2014 install apps via the Studio marketplace."
|
|
2382
|
+
},
|
|
2383
|
+
objects: [],
|
|
2384
|
+
views: [],
|
|
2385
|
+
apps: [],
|
|
2386
|
+
flows: [],
|
|
2387
|
+
requires: []
|
|
2388
|
+
},
|
|
2389
|
+
null,
|
|
2390
|
+
2
|
|
2391
|
+
),
|
|
2392
|
+
"utf8"
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
resolvedArtifact = stubPath;
|
|
2396
|
+
}
|
|
2357
2397
|
return createStandaloneStack({
|
|
2358
2398
|
...standaloneOpts,
|
|
2359
2399
|
artifactPath: resolvedArtifact
|
|
@@ -6901,6 +6941,8 @@ var KernelManager = class {
|
|
|
6901
6941
|
this.maxSize = config.maxSize ?? 32;
|
|
6902
6942
|
this.ttlMs = config.ttlMs ?? 15 * 60 * 1e3;
|
|
6903
6943
|
this.logger = config.logger ?? console;
|
|
6944
|
+
this.freshnessProbe = config.freshnessProbe;
|
|
6945
|
+
this.staleCheckIntervalMs = config.staleCheckIntervalMs ?? 1e4;
|
|
6904
6946
|
}
|
|
6905
6947
|
/** Returns the currently cached environmentIds (ordered by insertion). */
|
|
6906
6948
|
keys() {
|
|
@@ -6923,8 +6965,31 @@ var KernelManager = class {
|
|
|
6923
6965
|
if (this.ttlMs > 0 && Date.now() - existing.lastAccess > this.ttlMs) {
|
|
6924
6966
|
await this.evict(environmentId);
|
|
6925
6967
|
} else {
|
|
6926
|
-
|
|
6927
|
-
|
|
6968
|
+
if (this.freshnessProbe) {
|
|
6969
|
+
const now = Date.now();
|
|
6970
|
+
if (now - existing.lastStaleCheckAt >= this.staleCheckIntervalMs) {
|
|
6971
|
+
existing.lastStaleCheckAt = now;
|
|
6972
|
+
let stale = false;
|
|
6973
|
+
try {
|
|
6974
|
+
stale = await this.freshnessProbe(environmentId, existing.createdAt);
|
|
6975
|
+
} catch (err) {
|
|
6976
|
+
this.logger.warn?.("[KernelManager] freshness probe failed", { environmentId, err });
|
|
6977
|
+
}
|
|
6978
|
+
if (stale) {
|
|
6979
|
+
this.logger.info?.("[KernelManager] kernel evicted by freshness probe", { environmentId });
|
|
6980
|
+
await this.evict(environmentId);
|
|
6981
|
+
} else {
|
|
6982
|
+
existing.lastAccess = Date.now();
|
|
6983
|
+
return existing.kernel;
|
|
6984
|
+
}
|
|
6985
|
+
} else {
|
|
6986
|
+
existing.lastAccess = Date.now();
|
|
6987
|
+
return existing.kernel;
|
|
6988
|
+
}
|
|
6989
|
+
} else {
|
|
6990
|
+
existing.lastAccess = Date.now();
|
|
6991
|
+
return existing.kernel;
|
|
6992
|
+
}
|
|
6928
6993
|
}
|
|
6929
6994
|
}
|
|
6930
6995
|
const inflight = this.pending.get(environmentId);
|
|
@@ -6932,7 +6997,7 @@ var KernelManager = class {
|
|
|
6932
6997
|
const promise = (async () => {
|
|
6933
6998
|
const kernel = await this.factory.create(environmentId);
|
|
6934
6999
|
const now = Date.now();
|
|
6935
|
-
this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now });
|
|
7000
|
+
this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now, lastStaleCheckAt: now });
|
|
6936
7001
|
await this.enforceMaxSize();
|
|
6937
7002
|
return kernel;
|
|
6938
7003
|
})();
|
|
@@ -7100,6 +7165,30 @@ var ArtifactApiClient = class {
|
|
|
7100
7165
|
if (!found?.headCommitId) return null;
|
|
7101
7166
|
return { commitId: String(found.headCommitId), publishedAt: found.headPublishedAt ?? null };
|
|
7102
7167
|
}
|
|
7168
|
+
/**
|
|
7169
|
+
* Cheap freshness probe — returns the env's `last_published_at`
|
|
7170
|
+
* (and best-effort current commit) without rebuilding the artifact.
|
|
7171
|
+
* Used by `KernelManager` on cache hits to detect when a per-env
|
|
7172
|
+
* kernel has been invalidated by an upstream change (marketplace
|
|
7173
|
+
* install/uninstall, artifact publish) so it can be rebuilt
|
|
7174
|
+
* without waiting for the 15-minute LRU TTL to expire.
|
|
7175
|
+
*
|
|
7176
|
+
* Returns `null` on definitive 404 / unknown env. Errors propagate
|
|
7177
|
+
* (caller decides whether to treat unreachable cloud as fresh or
|
|
7178
|
+
* stale — typically fresh, so a brief outage doesn't churn every
|
|
7179
|
+
* cached kernel).
|
|
7180
|
+
*/
|
|
7181
|
+
async getFreshness(environmentId) {
|
|
7182
|
+
const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/freshness`;
|
|
7183
|
+
const res = await this.request(url);
|
|
7184
|
+
if (res === null) return null;
|
|
7185
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
7186
|
+
if (!body || typeof body !== "object") return null;
|
|
7187
|
+
const envId = typeof body.environmentId === "string" ? body.environmentId : environmentId;
|
|
7188
|
+
const lastPublishedAt = typeof body.lastPublishedAt === "string" ? body.lastPublishedAt : null;
|
|
7189
|
+
const commitId = typeof body.commitId === "string" ? body.commitId : null;
|
|
7190
|
+
return { environmentId: envId, lastPublishedAt, commitId };
|
|
7191
|
+
}
|
|
7103
7192
|
/** Drop cached entries for a project (and any matching hostname). */
|
|
7104
7193
|
invalidate(environmentId) {
|
|
7105
7194
|
this.artifactCache.delete(environmentId);
|
|
@@ -7851,7 +7940,30 @@ var ArtifactKernelFactory = class {
|
|
|
7851
7940
|
};
|
|
7852
7941
|
|
|
7853
7942
|
// src/cloud/auth-proxy-plugin.ts
|
|
7943
|
+
var import_node_crypto3 = require("crypto");
|
|
7854
7944
|
var AUTH_PREFIX = "/api/v1/auth";
|
|
7945
|
+
function signSessionCookieValue(rawToken, secret) {
|
|
7946
|
+
const signature = (0, import_node_crypto3.createHmac)("sha256", secret).update(rawToken).digest("base64");
|
|
7947
|
+
return encodeURIComponent(`${rawToken}.${signature}`);
|
|
7948
|
+
}
|
|
7949
|
+
function buildSetCookieHeader(name, encodedValue, attrs, maxAgeSec) {
|
|
7950
|
+
const parts = [`${name}=${encodedValue}`];
|
|
7951
|
+
const a = attrs ?? {};
|
|
7952
|
+
if (a.path) parts.push(`Path=${a.path}`);
|
|
7953
|
+
else parts.push("Path=/");
|
|
7954
|
+
if (Number.isFinite(maxAgeSec) && maxAgeSec > 0) parts.push(`Max-Age=${Math.floor(maxAgeSec)}`);
|
|
7955
|
+
if (a.domain) parts.push(`Domain=${a.domain}`);
|
|
7956
|
+
if (a.sameSite) {
|
|
7957
|
+
const ss = String(a.sameSite);
|
|
7958
|
+
parts.push(`SameSite=${ss.charAt(0).toUpperCase() + ss.slice(1)}`);
|
|
7959
|
+
} else {
|
|
7960
|
+
parts.push("SameSite=Lax");
|
|
7961
|
+
}
|
|
7962
|
+
if (a.secure) parts.push("Secure");
|
|
7963
|
+
if (a.httpOnly !== false) parts.push("HttpOnly");
|
|
7964
|
+
if (a.partitioned) parts.push("Partitioned");
|
|
7965
|
+
return parts.join("; ");
|
|
7966
|
+
}
|
|
7855
7967
|
function pickHandler(svc) {
|
|
7856
7968
|
if (!svc) return void 0;
|
|
7857
7969
|
if (typeof svc.handleRequest === "function") return svc.handleRequest.bind(svc);
|
|
@@ -7945,6 +8057,115 @@ var AuthProxyPlugin = class {
|
|
|
7945
8057
|
return c.json({ hasOwner: true });
|
|
7946
8058
|
}
|
|
7947
8059
|
}
|
|
8060
|
+
if (c.req.method === "POST" && subPath === "sso-handoff-issue") {
|
|
8061
|
+
try {
|
|
8062
|
+
const expected = (process.env.OS_CLOUD_API_KEY ?? "").trim();
|
|
8063
|
+
if (!expected) {
|
|
8064
|
+
return c.json({ error: "sso_handoff_disabled", reason: "OS_CLOUD_API_KEY unset on env runtime" }, 503);
|
|
8065
|
+
}
|
|
8066
|
+
const authz = c.req.header("authorization") ?? "";
|
|
8067
|
+
const provided = authz.toLowerCase().startsWith("bearer ") ? authz.slice(7).trim() : "";
|
|
8068
|
+
if (!provided || provided !== expected) {
|
|
8069
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
8070
|
+
}
|
|
8071
|
+
if (typeof authSvc?.getAuthContext !== "function") {
|
|
8072
|
+
return c.json({ error: "auth_service_unavailable" }, 503);
|
|
8073
|
+
}
|
|
8074
|
+
const handoffAuthCtx = await authSvc.getAuthContext();
|
|
8075
|
+
const internal = handoffAuthCtx?.internalAdapter;
|
|
8076
|
+
if (!internal?.createVerificationValue) {
|
|
8077
|
+
return c.json({ error: "verification_api_unavailable" }, 503);
|
|
8078
|
+
}
|
|
8079
|
+
let body = {};
|
|
8080
|
+
try {
|
|
8081
|
+
body = await c.req.json();
|
|
8082
|
+
} catch {
|
|
8083
|
+
body = {};
|
|
8084
|
+
}
|
|
8085
|
+
const email = String(body?.email ?? "").toLowerCase().trim();
|
|
8086
|
+
if (!email) return c.json({ error: "email_required" }, 400);
|
|
8087
|
+
const name = body?.name == null ? null : String(body.name);
|
|
8088
|
+
const by = body?.by == null ? "service" : String(body.by);
|
|
8089
|
+
const envIdInBody = body?.envId == null ? null : String(body.envId);
|
|
8090
|
+
const handoff = (0, import_node_crypto3.randomUUID)().replace(/-/g, "") + (0, import_node_crypto3.randomUUID)().replace(/-/g, "");
|
|
8091
|
+
const ttlSec = 60;
|
|
8092
|
+
const expiresAt = new Date(Date.now() + ttlSec * 1e3);
|
|
8093
|
+
await internal.createVerificationValue({
|
|
8094
|
+
identifier: `sso-handoff:${handoff}`,
|
|
8095
|
+
value: JSON.stringify({ email, name, by, envId: envIdInBody ?? environmentId }),
|
|
8096
|
+
expiresAt
|
|
8097
|
+
});
|
|
8098
|
+
return c.json({
|
|
8099
|
+
token: handoff,
|
|
8100
|
+
expiresAt: expiresAt.toISOString(),
|
|
8101
|
+
ttlSec
|
|
8102
|
+
});
|
|
8103
|
+
} catch (err) {
|
|
8104
|
+
ctx.logger?.error?.("[AuthProxyPlugin] sso-handoff-issue failed", err instanceof Error ? err : new Error(String(err)));
|
|
8105
|
+
return c.json({ error: "sso_handoff_issue_failed", message: String(err?.message ?? err) }, 500);
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
8108
|
+
if (c.req.method === "GET" && subPath === "sso-exchange") {
|
|
8109
|
+
try {
|
|
8110
|
+
const token = (url.searchParams.get("token") ?? "").trim();
|
|
8111
|
+
const nextRaw = url.searchParams.get("next") ?? "/";
|
|
8112
|
+
const next = nextRaw.startsWith("/") ? nextRaw : "/";
|
|
8113
|
+
if (!token) return c.text("missing token", 400);
|
|
8114
|
+
if (typeof authSvc?.getAuthContext !== "function") {
|
|
8115
|
+
return c.text("auth service unavailable", 503);
|
|
8116
|
+
}
|
|
8117
|
+
const authCtx = await authSvc.getAuthContext();
|
|
8118
|
+
const internal = authCtx?.internalAdapter;
|
|
8119
|
+
if (!internal?.consumeVerificationValue) {
|
|
8120
|
+
return c.text("verification API unavailable", 503);
|
|
8121
|
+
}
|
|
8122
|
+
const consumed = await internal.consumeVerificationValue(`sso-handoff:${token}`);
|
|
8123
|
+
if (!consumed) return c.text("invalid or expired token", 401);
|
|
8124
|
+
const expiresAt = consumed?.expiresAt ? new Date(consumed.expiresAt).getTime() : 0;
|
|
8125
|
+
if (!expiresAt || expiresAt < Date.now()) return c.text("expired token", 401);
|
|
8126
|
+
let payload = {};
|
|
8127
|
+
try {
|
|
8128
|
+
payload = JSON.parse(String(consumed.value));
|
|
8129
|
+
} catch {
|
|
8130
|
+
payload = { email: String(consumed.value) };
|
|
8131
|
+
}
|
|
8132
|
+
const email = String(payload.email ?? "").toLowerCase().trim();
|
|
8133
|
+
if (!email) return c.text("handoff missing email", 400);
|
|
8134
|
+
const found = await internal.findUserByEmail(email, { includeAccounts: true });
|
|
8135
|
+
let userId = found?.user?.id;
|
|
8136
|
+
let hasCredentialAccount = (found?.accounts ?? []).some((a) => a.providerId === "credential" && a.password);
|
|
8137
|
+
if (!userId) {
|
|
8138
|
+
const created = await internal.createUser({
|
|
8139
|
+
email,
|
|
8140
|
+
name: payload.name ?? email,
|
|
8141
|
+
emailVerified: true
|
|
8142
|
+
});
|
|
8143
|
+
userId = created?.id;
|
|
8144
|
+
hasCredentialAccount = false;
|
|
8145
|
+
}
|
|
8146
|
+
if (!userId) return c.text("failed to provision user", 500);
|
|
8147
|
+
const session = await internal.createSession(userId, false);
|
|
8148
|
+
const rawToken = session?.token;
|
|
8149
|
+
const sessionExpiresAt = session?.expiresAt ? new Date(session.expiresAt) : new Date(Date.now() + 7 * 24 * 3600 * 1e3);
|
|
8150
|
+
if (!rawToken) return c.text("failed to mint session", 500);
|
|
8151
|
+
const secret = authCtx?.secret ?? "";
|
|
8152
|
+
if (!secret) return c.text("auth secret unavailable", 503);
|
|
8153
|
+
const cookieName = authCtx?.authCookies?.sessionToken?.name ?? "better-auth.session_token";
|
|
8154
|
+
const cookieAttrs = authCtx?.authCookies?.sessionToken?.attributes ?? {};
|
|
8155
|
+
const encoded = signSessionCookieValue(rawToken, secret);
|
|
8156
|
+
const maxAgeSec = Math.max(60, Math.floor((sessionExpiresAt.getTime() - Date.now()) / 1e3));
|
|
8157
|
+
const setCookie = buildSetCookieHeader(cookieName, encoded, cookieAttrs, maxAgeSec);
|
|
8158
|
+
const finalNext = hasCredentialAccount ? next : `/_console/system/profile?recovery_needed=true&next=${encodeURIComponent(next)}`;
|
|
8159
|
+
const headers = new Headers();
|
|
8160
|
+
headers.set("Set-Cookie", setCookie);
|
|
8161
|
+
headers.set("Location", finalNext);
|
|
8162
|
+
headers.set("Cache-Control", "no-store");
|
|
8163
|
+
return new Response(null, { status: 302, headers });
|
|
8164
|
+
} catch (err) {
|
|
8165
|
+
ctx.logger?.error?.("[AuthProxyPlugin] sso-exchange failed", err instanceof Error ? err : new Error(String(err)));
|
|
8166
|
+
return c.text(`sso-exchange failed: ${err?.message ?? String(err)}`, 500);
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
7948
8169
|
const fn = await resolveAuthHandler(authSvc);
|
|
7949
8170
|
if (!fn) {
|
|
7950
8171
|
return c.json({ error: "auth_service_unavailable", environmentId }, 503);
|
|
@@ -8127,20 +8348,46 @@ var RuntimeConfigPlugin = class {
|
|
|
8127
8348
|
return;
|
|
8128
8349
|
}
|
|
8129
8350
|
const rawApp = httpServer.getRawApp();
|
|
8130
|
-
const
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8351
|
+
const features = {
|
|
8352
|
+
installLocal: this.installLocal,
|
|
8353
|
+
marketplace: true
|
|
8354
|
+
};
|
|
8355
|
+
let envRegistry = null;
|
|
8356
|
+
try {
|
|
8357
|
+
envRegistry = ctx.getService("env-registry");
|
|
8358
|
+
} catch {
|
|
8359
|
+
}
|
|
8360
|
+
const handler = async (c) => {
|
|
8361
|
+
const rawHost = c.req.header("host") ?? "";
|
|
8362
|
+
const host = rawHost.split(":")[0].toLowerCase().trim();
|
|
8363
|
+
let defaultEnvironmentId;
|
|
8364
|
+
let defaultOrgId;
|
|
8365
|
+
let resolvedSingleEnv = this.singleEnvironment;
|
|
8366
|
+
if (envRegistry && host && typeof envRegistry.resolveHostname === "function") {
|
|
8367
|
+
try {
|
|
8368
|
+
const resolved = await envRegistry.resolveHostname(host);
|
|
8369
|
+
if (resolved?.environmentId) {
|
|
8370
|
+
defaultEnvironmentId = resolved.environmentId;
|
|
8371
|
+
if (resolved.organizationId) defaultOrgId = String(resolved.organizationId);
|
|
8372
|
+
resolvedSingleEnv = true;
|
|
8373
|
+
}
|
|
8374
|
+
} catch {
|
|
8375
|
+
}
|
|
8136
8376
|
}
|
|
8377
|
+
return c.json({
|
|
8378
|
+
cloudUrl: this.cloudUrl,
|
|
8379
|
+
singleEnvironment: resolvedSingleEnv,
|
|
8380
|
+
defaultOrgId,
|
|
8381
|
+
defaultEnvironmentId,
|
|
8382
|
+
features
|
|
8383
|
+
});
|
|
8137
8384
|
};
|
|
8138
|
-
const handler = (c) => c.json(payload);
|
|
8139
8385
|
rawApp.get("/api/v1/runtime/config", handler);
|
|
8140
8386
|
rawApp.get("/api/v1/studio/runtime-config", handler);
|
|
8141
8387
|
ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
|
|
8142
8388
|
cloudUrl: this.cloudUrl || "(empty)",
|
|
8143
|
-
installLocal: this.installLocal
|
|
8389
|
+
installLocal: this.installLocal,
|
|
8390
|
+
perHostEnvResolution: !!envRegistry
|
|
8144
8391
|
});
|
|
8145
8392
|
});
|
|
8146
8393
|
};
|
|
@@ -8341,7 +8588,21 @@ var ObjectOSEnvironmentPlugin = class {
|
|
|
8341
8588
|
factory,
|
|
8342
8589
|
maxSize: this.config.kernelCacheSize,
|
|
8343
8590
|
ttlMs: this.config.kernelTtlMs,
|
|
8344
|
-
logger: ctx.logger
|
|
8591
|
+
logger: ctx.logger,
|
|
8592
|
+
// Only the HTTP client exposes /freshness; file-mode (CLI dev)
|
|
8593
|
+
// has no upstream to probe.
|
|
8594
|
+
freshnessProbe: this.config.controlPlaneUrl === "file" ? void 0 : async (envId, builtAtMs) => {
|
|
8595
|
+
const fresh = await client.getFreshness(envId);
|
|
8596
|
+
if (!fresh) return false;
|
|
8597
|
+
const t = fresh.lastPublishedAt ? Date.parse(fresh.lastPublishedAt) : NaN;
|
|
8598
|
+
if (!Number.isFinite(t)) return false;
|
|
8599
|
+
if (t <= builtAtMs) return false;
|
|
8600
|
+
try {
|
|
8601
|
+
client.invalidate(envId);
|
|
8602
|
+
} catch {
|
|
8603
|
+
}
|
|
8604
|
+
return true;
|
|
8605
|
+
}
|
|
8345
8606
|
});
|
|
8346
8607
|
this.kernelManager = kernelManager;
|
|
8347
8608
|
ctx.registerService("env-registry", envRegistry);
|
|
@@ -8942,6 +9203,7 @@ __reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
|
8942
9203
|
resolveDefaultArtifactPath,
|
|
8943
9204
|
resolveErrorReporter,
|
|
8944
9205
|
resolveMetrics,
|
|
9206
|
+
resolveObjectStackHome,
|
|
8945
9207
|
resolveRequestId,
|
|
8946
9208
|
seedPlatformSsoClient,
|
|
8947
9209
|
...require("@objectstack/core")
|