@objectstack/runtime 7.8.0 → 8.0.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 +530 -2170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -641
- package/dist/index.d.ts +123 -641
- package/dist/index.js +545 -2164
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -188,7 +188,7 @@ var init_seed_loader = __esm({
|
|
|
188
188
|
const config = request.config;
|
|
189
189
|
const allErrors = [];
|
|
190
190
|
const allResults = [];
|
|
191
|
-
const datasets = this.filterByEnv(request.
|
|
191
|
+
const datasets = this.filterByEnv(request.seeds, config.env);
|
|
192
192
|
if (datasets.length === 0) {
|
|
193
193
|
return this.buildEmptyResult(config, Date.now() - startTime);
|
|
194
194
|
}
|
|
@@ -258,10 +258,10 @@ var init_seed_loader = __esm({
|
|
|
258
258
|
}
|
|
259
259
|
async validate(datasets, config) {
|
|
260
260
|
const parsedConfig = import_data.SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
|
|
261
|
-
return this.load({ datasets, config: parsedConfig });
|
|
261
|
+
return this.load({ seeds: datasets, config: parsedConfig });
|
|
262
262
|
}
|
|
263
263
|
// ==========================================================================
|
|
264
|
-
// Internal:
|
|
264
|
+
// Internal: Seed Loading
|
|
265
265
|
// ==========================================================================
|
|
266
266
|
async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
|
|
267
267
|
const objectName = dataset.object;
|
|
@@ -1680,7 +1680,7 @@ var init_app_plugin = __esm({
|
|
|
1680
1680
|
const seedLoader = new SeedLoaderService(ql, md, loggerRef);
|
|
1681
1681
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1682
1682
|
const request = SeedLoaderRequestSchema.parse({
|
|
1683
|
-
|
|
1683
|
+
seeds: datasetsNow,
|
|
1684
1684
|
config: {
|
|
1685
1685
|
defaultMode: "upsert",
|
|
1686
1686
|
multiPass: true,
|
|
@@ -1700,7 +1700,7 @@ var init_app_plugin = __esm({
|
|
|
1700
1700
|
};
|
|
1701
1701
|
};
|
|
1702
1702
|
registerSvc("seed-replayer", replayer);
|
|
1703
|
-
ctx.logger.info(`[Seeder] Registered ${normalizedDatasets.length} datasets + replayer on kernel (total
|
|
1703
|
+
ctx.logger.info(`[Seeder] Registered ${normalizedDatasets.length} datasets + replayer on kernel (total seeds: ${merged.length})`);
|
|
1704
1704
|
} catch (e) {
|
|
1705
1705
|
ctx.logger.warn("[Seeder] Failed to register seed-datasets/seed-replayer service", { error: e?.message });
|
|
1706
1706
|
}
|
|
@@ -1716,7 +1716,7 @@ var init_app_plugin = __esm({
|
|
|
1716
1716
|
const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
|
|
1717
1717
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1718
1718
|
const request = SeedLoaderRequestSchema.parse({
|
|
1719
|
-
|
|
1719
|
+
seeds: normalizedDatasets,
|
|
1720
1720
|
config: { defaultMode: "upsert", multiPass: true, identity: seedIdentity }
|
|
1721
1721
|
});
|
|
1722
1722
|
const result = await seedLoader.load(request);
|
|
@@ -2190,170 +2190,18 @@ var init_standalone_stack = __esm({
|
|
|
2190
2190
|
}
|
|
2191
2191
|
});
|
|
2192
2192
|
|
|
2193
|
-
// src/cloud/environment-org-seed.ts
|
|
2194
|
-
var environment_org_seed_exports = {};
|
|
2195
|
-
__export(environment_org_seed_exports, {
|
|
2196
|
-
seedProjectMember: () => seedProjectMember,
|
|
2197
|
-
seedProjectOrganization: () => seedProjectOrganization
|
|
2198
|
-
});
|
|
2199
|
-
async function seedProjectOrganization(kernel, seed, logger) {
|
|
2200
|
-
if (!seed?.id || !seed?.name) return "skipped";
|
|
2201
|
-
try {
|
|
2202
|
-
const ql = kernel.getService("objectql");
|
|
2203
|
-
if (!ql?.insert || !ql?.find) {
|
|
2204
|
-
logger?.warn?.("[seedProjectOrganization] objectql service unavailable", { orgId: seed.id });
|
|
2205
|
-
return "skipped";
|
|
2206
|
-
}
|
|
2207
|
-
try {
|
|
2208
|
-
const existing = await ql.find(SYS_ORG, { where: { id: seed.id } });
|
|
2209
|
-
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2210
|
-
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2211
|
-
} catch {
|
|
2212
|
-
}
|
|
2213
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2214
|
-
await ql.insert(SYS_ORG, {
|
|
2215
|
-
id: seed.id,
|
|
2216
|
-
name: seed.name,
|
|
2217
|
-
slug: seed.slug ?? null,
|
|
2218
|
-
logo: seed.logo ?? null,
|
|
2219
|
-
metadata: null,
|
|
2220
|
-
created_at: nowIso
|
|
2221
|
-
});
|
|
2222
|
-
logger?.info?.("[seedProjectOrganization] org seeded", {
|
|
2223
|
-
orgId: seed.id,
|
|
2224
|
-
name: seed.name
|
|
2225
|
-
});
|
|
2226
|
-
return "inserted";
|
|
2227
|
-
} catch (err) {
|
|
2228
|
-
logger?.warn?.("[seedProjectOrganization] failed (non-fatal)", {
|
|
2229
|
-
orgId: seed.id,
|
|
2230
|
-
error: err?.message
|
|
2231
|
-
});
|
|
2232
|
-
return "error";
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
async function seedProjectMember(kernel, args, logger) {
|
|
2236
|
-
const { userId, organizationId } = args;
|
|
2237
|
-
const role = args.role ?? "member";
|
|
2238
|
-
if (!userId || !organizationId) return "skipped";
|
|
2239
|
-
try {
|
|
2240
|
-
const ql = kernel.getService("objectql");
|
|
2241
|
-
if (!ql?.insert || !ql?.find) {
|
|
2242
|
-
logger?.warn?.("[seedProjectMember] objectql service unavailable", { userId, organizationId });
|
|
2243
|
-
return "skipped";
|
|
2244
|
-
}
|
|
2245
|
-
try {
|
|
2246
|
-
const existing = await ql.find("sys_member", {
|
|
2247
|
-
where: { user_id: userId, organization_id: organizationId }
|
|
2248
|
-
});
|
|
2249
|
-
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2250
|
-
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2251
|
-
} catch {
|
|
2252
|
-
}
|
|
2253
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2254
|
-
const memId = `mem_${Math.random().toString(36).slice(2, 14)}`;
|
|
2255
|
-
await ql.insert("sys_member", {
|
|
2256
|
-
id: memId,
|
|
2257
|
-
organization_id: organizationId,
|
|
2258
|
-
user_id: userId,
|
|
2259
|
-
role,
|
|
2260
|
-
created_at: nowIso
|
|
2261
|
-
});
|
|
2262
|
-
logger?.info?.("[seedProjectMember] member seeded", {
|
|
2263
|
-
userId,
|
|
2264
|
-
organizationId,
|
|
2265
|
-
role
|
|
2266
|
-
});
|
|
2267
|
-
return "inserted";
|
|
2268
|
-
} catch (err) {
|
|
2269
|
-
logger?.warn?.("[seedProjectMember] failed (non-fatal)", {
|
|
2270
|
-
userId,
|
|
2271
|
-
organizationId,
|
|
2272
|
-
error: err?.message
|
|
2273
|
-
});
|
|
2274
|
-
return "error";
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
var SYS_ORG;
|
|
2278
|
-
var init_environment_org_seed = __esm({
|
|
2279
|
-
"src/cloud/environment-org-seed.ts"() {
|
|
2280
|
-
"use strict";
|
|
2281
|
-
SYS_ORG = "sys_organization";
|
|
2282
|
-
}
|
|
2283
|
-
});
|
|
2284
|
-
|
|
2285
|
-
// src/cloud/environment-owner-seed.ts
|
|
2286
|
-
var environment_owner_seed_exports = {};
|
|
2287
|
-
__export(environment_owner_seed_exports, {
|
|
2288
|
-
seedProjectOwner: () => seedProjectOwner
|
|
2289
|
-
});
|
|
2290
|
-
async function seedProjectOwner(kernel, seed, logger) {
|
|
2291
|
-
if (!seed?.userId || !seed?.email) return "skipped";
|
|
2292
|
-
try {
|
|
2293
|
-
const ql = kernel.getService("objectql");
|
|
2294
|
-
if (!ql?.insert || !ql?.find) {
|
|
2295
|
-
logger?.warn?.("[seedProjectOwner] objectql service unavailable", { userId: seed.userId });
|
|
2296
|
-
return "skipped";
|
|
2297
|
-
}
|
|
2298
|
-
try {
|
|
2299
|
-
const existing = await ql.find(SYS_USER, { where: { id: seed.userId } });
|
|
2300
|
-
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2301
|
-
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2302
|
-
} catch {
|
|
2303
|
-
}
|
|
2304
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2305
|
-
await ql.insert(SYS_USER, {
|
|
2306
|
-
id: seed.userId,
|
|
2307
|
-
email: seed.email,
|
|
2308
|
-
name: seed.name ?? seed.email.split("@")[0] ?? "Owner",
|
|
2309
|
-
image: seed.image ?? null,
|
|
2310
|
-
// Cloud already verified the upstream email. Marking it verified
|
|
2311
|
-
// here is what unblocks better-auth's accountLinking check on
|
|
2312
|
-
// the first SSO callback (alongside the trustedProviders config
|
|
2313
|
-
// in plugin-auth/auth-manager.ts).
|
|
2314
|
-
email_verified: true,
|
|
2315
|
-
created_at: nowIso,
|
|
2316
|
-
updated_at: nowIso
|
|
2317
|
-
});
|
|
2318
|
-
logger?.info?.("[seedProjectOwner] owner seeded", {
|
|
2319
|
-
userId: seed.userId,
|
|
2320
|
-
email: seed.email
|
|
2321
|
-
});
|
|
2322
|
-
return "inserted";
|
|
2323
|
-
} catch (err) {
|
|
2324
|
-
logger?.warn?.("[seedProjectOwner] failed (non-fatal)", {
|
|
2325
|
-
userId: seed.userId,
|
|
2326
|
-
error: err?.message
|
|
2327
|
-
});
|
|
2328
|
-
return "error";
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
var SYS_USER;
|
|
2332
|
-
var init_environment_owner_seed = __esm({
|
|
2333
|
-
"src/cloud/environment-owner-seed.ts"() {
|
|
2334
|
-
"use strict";
|
|
2335
|
-
SYS_USER = "sys_user";
|
|
2336
|
-
}
|
|
2337
|
-
});
|
|
2338
|
-
|
|
2339
2193
|
// src/index.ts
|
|
2340
2194
|
var index_exports = {};
|
|
2341
2195
|
__export(index_exports, {
|
|
2342
2196
|
AppPlugin: () => AppPlugin,
|
|
2343
|
-
ArtifactApiClient: () => ArtifactApiClient,
|
|
2344
|
-
ArtifactEnvironmentRegistry: () => ArtifactEnvironmentRegistry,
|
|
2345
|
-
ArtifactKernelFactory: () => ArtifactKernelFactory,
|
|
2346
|
-
AuthProxyPlugin: () => AuthProxyPlugin,
|
|
2347
2197
|
DEFAULT_CLOUD_URL: () => DEFAULT_CLOUD_URL,
|
|
2348
2198
|
DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
|
|
2349
2199
|
DriverPlugin: () => DriverPlugin,
|
|
2350
2200
|
ExternalValidationPlugin: () => ExternalValidationPlugin,
|
|
2351
|
-
FileArtifactApiClient: () => FileArtifactApiClient,
|
|
2352
2201
|
HttpDispatcher: () => HttpDispatcher,
|
|
2353
2202
|
HttpServer: () => HttpServer,
|
|
2354
2203
|
InMemoryErrorReporter: () => import_observability2.InMemoryErrorReporter,
|
|
2355
2204
|
InMemoryMetricsRegistry: () => import_observability.InMemoryMetricsRegistry,
|
|
2356
|
-
KernelManager: () => KernelManager,
|
|
2357
2205
|
MarketplaceInstallLocalPlugin: () => MarketplaceInstallLocalPlugin,
|
|
2358
2206
|
MarketplaceProxyPlugin: () => MarketplaceProxyPlugin,
|
|
2359
2207
|
MiddlewareManager: () => MiddlewareManager,
|
|
@@ -2363,7 +2211,6 @@ __export(index_exports, {
|
|
|
2363
2211
|
OBSERVABILITY_METRICS_SERVICE: () => import_observability3.OBSERVABILITY_METRICS_SERVICE,
|
|
2364
2212
|
ObjectKernel: () => import_core4.ObjectKernel,
|
|
2365
2213
|
ObservabilityServicePlugin: () => ObservabilityServicePlugin,
|
|
2366
|
-
PLATFORM_SSO_PROVIDER_ID: () => PLATFORM_SSO_PROVIDER_ID,
|
|
2367
2214
|
QuickJSScriptRunner: () => QuickJSScriptRunner,
|
|
2368
2215
|
RUNTIME_METRICS: () => import_observability.RUNTIME_METRICS,
|
|
2369
2216
|
RateLimiter: () => RateLimiter,
|
|
@@ -2376,10 +2223,8 @@ __export(index_exports, {
|
|
|
2376
2223
|
SandboxError: () => SandboxError,
|
|
2377
2224
|
SeedLoaderService: () => SeedLoaderService,
|
|
2378
2225
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
2379
|
-
_resetEnvDeprecationWarnings: () =>
|
|
2226
|
+
_resetEnvDeprecationWarnings: () => import_types4._resetEnvDeprecationWarnings,
|
|
2380
2227
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
2381
|
-
backfillPlatformSsoClients: () => backfillPlatformSsoClients,
|
|
2382
|
-
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
2383
2228
|
buildSecurityHeaders: () => buildSecurityHeaders,
|
|
2384
2229
|
collectBundleActions: () => collectBundleActions,
|
|
2385
2230
|
collectBundleFunctions: () => collectBundleFunctions,
|
|
@@ -2387,12 +2232,9 @@ __export(index_exports, {
|
|
|
2387
2232
|
createDefaultHostConfig: () => createDefaultHostConfig,
|
|
2388
2233
|
createDispatcherPlugin: () => createDispatcherPlugin,
|
|
2389
2234
|
createExternalValidationPlugin: () => createExternalValidationPlugin,
|
|
2390
|
-
createObjectOSStack: () => createObjectOSStack,
|
|
2391
2235
|
createRestApiPlugin: () => import_rest.createRestApiPlugin,
|
|
2392
2236
|
createStandaloneStack: () => createStandaloneStack,
|
|
2393
2237
|
createSystemEnvironmentPlugin: () => createSystemEnvironmentPlugin,
|
|
2394
|
-
derivePlatformSsoClientId: () => derivePlatformSsoClientId,
|
|
2395
|
-
derivePlatformSsoClientSecret: () => derivePlatformSsoClientSecret,
|
|
2396
2238
|
extractRequestId: () => extractRequestId,
|
|
2397
2239
|
formatTraceparent: () => formatTraceparent,
|
|
2398
2240
|
generateRequestId: () => generateRequestId,
|
|
@@ -2402,14 +2244,13 @@ __export(index_exports, {
|
|
|
2402
2244
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
2403
2245
|
parseTraceparent: () => parseTraceparent,
|
|
2404
2246
|
readArtifactSource: () => readArtifactSource,
|
|
2405
|
-
readEnvWithDeprecation: () =>
|
|
2247
|
+
readEnvWithDeprecation: () => import_types4.readEnvWithDeprecation,
|
|
2406
2248
|
resolveCloudUrl: () => resolveCloudUrl,
|
|
2407
2249
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
2408
2250
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
2409
2251
|
resolveMetrics: () => resolveMetrics,
|
|
2410
2252
|
resolveObjectStackHome: () => resolveObjectStackHome,
|
|
2411
|
-
resolveRequestId: () => resolveRequestId
|
|
2412
|
-
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
2253
|
+
resolveRequestId: () => resolveRequestId
|
|
2413
2254
|
});
|
|
2414
2255
|
module.exports = __toCommonJS(index_exports);
|
|
2415
2256
|
var import_core4 = require("@objectstack/core");
|
|
@@ -2684,35 +2525,15 @@ function safeGet(ctx, name) {
|
|
|
2684
2525
|
}
|
|
2685
2526
|
|
|
2686
2527
|
// src/http-dispatcher.ts
|
|
2687
|
-
var
|
|
2528
|
+
var import_core3 = require("@objectstack/core");
|
|
2688
2529
|
var import_system2 = require("@objectstack/spec/system");
|
|
2689
2530
|
var import_shared2 = require("@objectstack/spec/shared");
|
|
2690
2531
|
init_package_state_store();
|
|
2691
2532
|
|
|
2533
|
+
// src/security/api-key.ts
|
|
2534
|
+
var import_core2 = require("@objectstack/core");
|
|
2535
|
+
|
|
2692
2536
|
// src/security/resolve-execution-context.ts
|
|
2693
|
-
function readHeader(headers, name) {
|
|
2694
|
-
if (!headers) return void 0;
|
|
2695
|
-
const lower = name.toLowerCase();
|
|
2696
|
-
if (typeof headers.get === "function") {
|
|
2697
|
-
const v = headers.get(name) ?? headers.get(lower);
|
|
2698
|
-
return v == null ? void 0 : String(v);
|
|
2699
|
-
}
|
|
2700
|
-
for (const key of Object.keys(headers)) {
|
|
2701
|
-
if (key.toLowerCase() === lower) {
|
|
2702
|
-
const v = headers[key];
|
|
2703
|
-
return Array.isArray(v) ? v[0] : v == null ? void 0 : String(v);
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
return void 0;
|
|
2707
|
-
}
|
|
2708
|
-
function extractApiKey(headers) {
|
|
2709
|
-
const x = readHeader(headers, "x-api-key");
|
|
2710
|
-
if (x) return x.trim();
|
|
2711
|
-
const auth = readHeader(headers, "authorization");
|
|
2712
|
-
if (!auth) return void 0;
|
|
2713
|
-
const m = auth.match(/^ApiKey\s+(.+)$/i);
|
|
2714
|
-
return m ? m[1].trim() : void 0;
|
|
2715
|
-
}
|
|
2716
2537
|
function toHeaders(input) {
|
|
2717
2538
|
if (!input) return new Headers();
|
|
2718
2539
|
if (typeof Headers !== "undefined" && input instanceof Headers) return input;
|
|
@@ -2755,34 +2576,12 @@ async function resolveExecutionContext(opts) {
|
|
|
2755
2576
|
};
|
|
2756
2577
|
let userId;
|
|
2757
2578
|
let tenantId;
|
|
2758
|
-
const
|
|
2759
|
-
if (
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
if (
|
|
2764
|
-
const res = await verify({ body: { key: apiKey } });
|
|
2765
|
-
const payload = res?.key ?? res;
|
|
2766
|
-
if (payload?.userId) userId = payload.userId;
|
|
2767
|
-
if (payload?.organizationId) tenantId = payload.organizationId;
|
|
2768
|
-
if (Array.isArray(payload?.permissions)) {
|
|
2769
|
-
ctx.permissions.push(...payload.permissions);
|
|
2770
|
-
}
|
|
2771
|
-
if (Array.isArray(payload?.scopes)) {
|
|
2772
|
-
ctx.permissions.push(...payload.scopes);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
} catch {
|
|
2776
|
-
}
|
|
2777
|
-
if (!userId) {
|
|
2778
|
-
const ql2 = await opts.getQl();
|
|
2779
|
-
const rows = await tryFind(ql2, "sys_api_key", { key: apiKey, active: true }, 1);
|
|
2780
|
-
const row = rows[0];
|
|
2781
|
-
if (row) {
|
|
2782
|
-
userId = row.user_id ?? row.userId;
|
|
2783
|
-
tenantId = row.organization_id ?? row.organizationId;
|
|
2784
|
-
if (Array.isArray(row.scopes)) ctx.permissions.push(...row.scopes);
|
|
2785
|
-
}
|
|
2579
|
+
const keyPrincipal = await (0, import_core2.resolveApiKeyPrincipal)(await opts.getQl(), headers);
|
|
2580
|
+
if (keyPrincipal) {
|
|
2581
|
+
userId = keyPrincipal.userId;
|
|
2582
|
+
tenantId = keyPrincipal.tenantId;
|
|
2583
|
+
for (const scope of keyPrincipal.scopes) {
|
|
2584
|
+
if (!ctx.permissions.includes(scope)) ctx.permissions.push(scope);
|
|
2786
2585
|
}
|
|
2787
2586
|
}
|
|
2788
2587
|
if (!userId) {
|
|
@@ -3058,6 +2857,253 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3058
2857
|
}
|
|
3059
2858
|
throw { statusCode: 400, message: `Unknown data action: ${action}` };
|
|
3060
2859
|
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Handle an MCP request over the Streamable HTTP transport (`/mcp`).
|
|
2862
|
+
*
|
|
2863
|
+
* Gating + auth (fail-closed):
|
|
2864
|
+
* - **opt-in**: only served when `OS_MCP_SERVER_ENABLED=true` (single-env
|
|
2865
|
+
* runtime). Multi-tenant cloud overrides this gate per env. When off we
|
|
2866
|
+
* return 404 so the surface isn't advertised.
|
|
2867
|
+
* - **auth**: requires a principal already resolved by
|
|
2868
|
+
* `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
|
|
2869
|
+
* session). Anonymous → 401.
|
|
2870
|
+
*
|
|
2871
|
+
* Execution: the MCP runtime builds a stateless per-request server whose
|
|
2872
|
+
* object-CRUD tools run through {@link callData} bound to THIS request's
|
|
2873
|
+
* ExecutionContext — i.e. the exact permission + RLS path the REST API
|
|
2874
|
+
* uses. An external agent can never exceed the key's authority.
|
|
2875
|
+
*/
|
|
2876
|
+
async handleMcp(body, context) {
|
|
2877
|
+
if (!_HttpDispatcher.isMcpEnabled()) {
|
|
2878
|
+
return { handled: true, response: this.error("MCP server is not enabled for this environment", 404) };
|
|
2879
|
+
}
|
|
2880
|
+
const mcp = await this.resolveService("mcp", context.environmentId);
|
|
2881
|
+
if (!mcp || typeof mcp.handleHttpRequest !== "function") {
|
|
2882
|
+
return { handled: true, response: this.error("MCP server is not available", 501) };
|
|
2883
|
+
}
|
|
2884
|
+
const ec = context.executionContext;
|
|
2885
|
+
if (!ec || !ec.userId && !ec.isSystem) {
|
|
2886
|
+
return { handled: true, response: this.error("Unauthorized: a valid API key is required", 401) };
|
|
2887
|
+
}
|
|
2888
|
+
const webRequest = this.toMcpWebRequest(context.request, body);
|
|
2889
|
+
if (!webRequest) {
|
|
2890
|
+
return { handled: true, response: this.error("MCP transport requires a standard HTTP request", 400) };
|
|
2891
|
+
}
|
|
2892
|
+
const bridge = this.buildMcpBridge(context);
|
|
2893
|
+
let webRes;
|
|
2894
|
+
try {
|
|
2895
|
+
webRes = await mcp.handleHttpRequest(webRequest, { bridge, parsedBody: body });
|
|
2896
|
+
} catch (err) {
|
|
2897
|
+
return { handled: true, response: this.error(err?.message ?? "MCP request failed", 500) };
|
|
2898
|
+
}
|
|
2899
|
+
const headers = {};
|
|
2900
|
+
try {
|
|
2901
|
+
webRes.headers.forEach((v, k) => {
|
|
2902
|
+
headers[k] = v;
|
|
2903
|
+
});
|
|
2904
|
+
} catch {
|
|
2905
|
+
}
|
|
2906
|
+
const text = await webRes.text().catch(() => "");
|
|
2907
|
+
let responseBody = null;
|
|
2908
|
+
if (text) {
|
|
2909
|
+
const ct = headers["content-type"] ?? "";
|
|
2910
|
+
if (ct.includes("application/json")) {
|
|
2911
|
+
try {
|
|
2912
|
+
responseBody = JSON.parse(text);
|
|
2913
|
+
} catch {
|
|
2914
|
+
responseBody = text;
|
|
2915
|
+
}
|
|
2916
|
+
} else {
|
|
2917
|
+
responseBody = text;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
return { handled: true, response: { status: webRes.status, headers, body: responseBody } };
|
|
2921
|
+
}
|
|
2922
|
+
/** Whether the MCP HTTP surface is opted in for this single-env runtime. */
|
|
2923
|
+
static isMcpEnabled() {
|
|
2924
|
+
return typeof process !== "undefined" && process.env?.OS_MCP_SERVER_ENABLED === "true";
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
2928
|
+
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
|
2929
|
+
* (plain `headers` object, path-only `url`). Returns undefined only if the
|
|
2930
|
+
* shape is unusable. The body is carried separately via `parsedBody`, so a
|
|
2931
|
+
* GET/DELETE (no body) and a POST (JSON-RPC) both normalise cleanly.
|
|
2932
|
+
*/
|
|
2933
|
+
toMcpWebRequest(raw, parsedBody) {
|
|
2934
|
+
if (!raw) return void 0;
|
|
2935
|
+
if (typeof raw.headers?.get === "function" && typeof raw.url === "string" && typeof raw.method === "string") {
|
|
2936
|
+
return raw;
|
|
2937
|
+
}
|
|
2938
|
+
try {
|
|
2939
|
+
const method = String(raw.method ?? "POST").toUpperCase();
|
|
2940
|
+
const headers = new Headers();
|
|
2941
|
+
const h = raw.headers;
|
|
2942
|
+
if (h) {
|
|
2943
|
+
if (typeof h.forEach === "function") {
|
|
2944
|
+
h.forEach((v, k) => {
|
|
2945
|
+
if (v != null) headers.set(String(k), String(v));
|
|
2946
|
+
});
|
|
2947
|
+
} else {
|
|
2948
|
+
for (const k of Object.keys(h)) {
|
|
2949
|
+
const v = h[k];
|
|
2950
|
+
if (v != null) headers.set(k, Array.isArray(v) ? v.join(",") : String(v));
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
let url;
|
|
2955
|
+
try {
|
|
2956
|
+
url = new URL(String(raw.url)).toString();
|
|
2957
|
+
} catch {
|
|
2958
|
+
const host = headers.get("host") || "mcp.local";
|
|
2959
|
+
const path = typeof raw.url === "string" && raw.url ? raw.url : "/api/v1/mcp";
|
|
2960
|
+
url = `https://${host}${path.startsWith("/") ? path : `/${path}`}`;
|
|
2961
|
+
}
|
|
2962
|
+
const init = { method, headers };
|
|
2963
|
+
if (method !== "GET" && method !== "HEAD" && method !== "DELETE") {
|
|
2964
|
+
init.body = typeof parsedBody === "string" ? parsedBody : JSON.stringify(parsedBody ?? {});
|
|
2965
|
+
}
|
|
2966
|
+
return new Request(url, init);
|
|
2967
|
+
} catch {
|
|
2968
|
+
return void 0;
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Build a principal-bound {@link McpDataBridge}: every method runs AS the
|
|
2973
|
+
* request's ExecutionContext through {@link callData} (RLS/permissions) and
|
|
2974
|
+
* the per-env metadata service. Keeps the MCP tool layer free of any direct
|
|
2975
|
+
* engine access.
|
|
2976
|
+
*/
|
|
2977
|
+
buildMcpBridge(context) {
|
|
2978
|
+
const ec = context.executionContext;
|
|
2979
|
+
const envId = context.environmentId;
|
|
2980
|
+
const driver = context.dataDriver;
|
|
2981
|
+
const callData = this.callData.bind(this);
|
|
2982
|
+
const getMeta = () => this.resolveService("metadata", envId);
|
|
2983
|
+
return {
|
|
2984
|
+
listObjects: async () => {
|
|
2985
|
+
const meta = await getMeta();
|
|
2986
|
+
const objs = await meta?.listObjects?.() ?? [];
|
|
2987
|
+
return objs.map((o) => ({
|
|
2988
|
+
name: o.name,
|
|
2989
|
+
label: o.label ?? o.name,
|
|
2990
|
+
fieldCount: o.fields ? Object.keys(o.fields).length : void 0
|
|
2991
|
+
}));
|
|
2992
|
+
},
|
|
2993
|
+
describeObject: async (name) => {
|
|
2994
|
+
const meta = await getMeta();
|
|
2995
|
+
const def = await meta?.getObject?.(name);
|
|
2996
|
+
if (!def) return null;
|
|
2997
|
+
const fields = def.fields ?? {};
|
|
2998
|
+
return {
|
|
2999
|
+
name: def.name,
|
|
3000
|
+
label: def.label ?? def.name,
|
|
3001
|
+
fields: Object.entries(fields).map(([k, f]) => ({
|
|
3002
|
+
name: k,
|
|
3003
|
+
type: f?.type,
|
|
3004
|
+
label: f?.label ?? k,
|
|
3005
|
+
required: f?.required ?? false
|
|
3006
|
+
})),
|
|
3007
|
+
enableFeatures: def.enable ?? {}
|
|
3008
|
+
};
|
|
3009
|
+
},
|
|
3010
|
+
query: async (object, o) => {
|
|
3011
|
+
const query = {};
|
|
3012
|
+
if (o?.where) query.where = o.where;
|
|
3013
|
+
if (o?.fields) query.fields = o.fields;
|
|
3014
|
+
if (typeof o?.limit === "number") query.limit = o.limit;
|
|
3015
|
+
if (typeof o?.offset === "number") query.offset = o.offset;
|
|
3016
|
+
if (o?.orderBy) query.orderBy = o.orderBy;
|
|
3017
|
+
return await callData("query", { object, query }, driver, envId, ec);
|
|
3018
|
+
},
|
|
3019
|
+
get: async (object, id) => {
|
|
3020
|
+
const res = await callData("get", { object, id }, driver, envId, ec);
|
|
3021
|
+
return res?.record ?? res ?? null;
|
|
3022
|
+
},
|
|
3023
|
+
create: async (object, data) => await callData("create", { object, data }, driver, envId, ec),
|
|
3024
|
+
update: async (object, id, data) => await callData("update", { object, id, data }, driver, envId, ec),
|
|
3025
|
+
remove: async (object, id) => await callData("delete", { object, id }, driver, envId, ec)
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Generate a `sys_api_key` and return the raw secret EXACTLY ONCE
|
|
3030
|
+
* (`POST /keys`). This is the only mint path — the raw key is never stored
|
|
3031
|
+
* (only its sha256 hash) and never re-displayable.
|
|
3032
|
+
*
|
|
3033
|
+
* Security (zero-tolerance):
|
|
3034
|
+
* - Requires an authenticated principal; `user_id` is PINNED to that
|
|
3035
|
+
* caller and is NEVER read from the request body (no impersonation).
|
|
3036
|
+
* - Body is whitelisted to `name` (+ optional `expires_at`); any
|
|
3037
|
+
* `key` / `id` / `user_id` / `revoked` in the body is ignored, so a
|
|
3038
|
+
* caller cannot forge a known-secret or escalate.
|
|
3039
|
+
* - `scopes` are intentionally NOT accepted from the body in v1: the
|
|
3040
|
+
* verify path ADDS scopes to the principal's permissions, so honouring
|
|
3041
|
+
* arbitrary body scopes would be an escalation vector. A generated key
|
|
3042
|
+
* therefore acts exactly AS the caller (via `user_id` resolution).
|
|
3043
|
+
* Narrowing/scoped keys need subset-enforcement — deferred.
|
|
3044
|
+
* - The raw key and its hash never enter logs or error messages.
|
|
3045
|
+
* - The row is written with an elevated `{ isSystem: true }` context
|
|
3046
|
+
* because `sys_api_key` is protection-locked; safe because the row's
|
|
3047
|
+
* contents are fully server-controlled (user_id pinned to caller).
|
|
3048
|
+
*/
|
|
3049
|
+
async handleKeys(method, body, context) {
|
|
3050
|
+
if (method !== "POST") {
|
|
3051
|
+
return { handled: true, response: this.error("Method not allowed", 405) };
|
|
3052
|
+
}
|
|
3053
|
+
const ec = context.executionContext;
|
|
3054
|
+
if (!ec || !ec.userId) {
|
|
3055
|
+
return { handled: true, response: this.error("Unauthorized: sign in to generate an API key", 401) };
|
|
3056
|
+
}
|
|
3057
|
+
const rawName = typeof body?.name === "string" ? body.name.trim() : "";
|
|
3058
|
+
const name = rawName || "API Key";
|
|
3059
|
+
let expiresAt;
|
|
3060
|
+
if (body?.expires_at != null && body.expires_at !== "") {
|
|
3061
|
+
const ms = typeof body.expires_at === "number" ? body.expires_at < 1e12 ? body.expires_at * 1e3 : body.expires_at : Date.parse(String(body.expires_at));
|
|
3062
|
+
if (Number.isNaN(ms)) {
|
|
3063
|
+
return { handled: true, response: this.error("Invalid expires_at: must be a parseable date", 400) };
|
|
3064
|
+
}
|
|
3065
|
+
if (ms <= Date.now()) {
|
|
3066
|
+
return { handled: true, response: this.error("Invalid expires_at: must be in the future", 400) };
|
|
3067
|
+
}
|
|
3068
|
+
expiresAt = new Date(ms).toISOString();
|
|
3069
|
+
}
|
|
3070
|
+
const ql = await this.getObjectQLService(context.environmentId) ?? await this.resolveService("objectql", context.environmentId);
|
|
3071
|
+
if (!ql || typeof ql.insert !== "function") {
|
|
3072
|
+
return { handled: true, response: this.error("Data service not available", 503) };
|
|
3073
|
+
}
|
|
3074
|
+
const generated = (0, import_core2.generateApiKey)();
|
|
3075
|
+
const row = {
|
|
3076
|
+
name,
|
|
3077
|
+
key: generated.hash,
|
|
3078
|
+
prefix: generated.prefix,
|
|
3079
|
+
user_id: ec.userId,
|
|
3080
|
+
revoked: false
|
|
3081
|
+
};
|
|
3082
|
+
if (expiresAt) row.expires_at = expiresAt;
|
|
3083
|
+
let inserted;
|
|
3084
|
+
try {
|
|
3085
|
+
inserted = await ql.insert("sys_api_key", row, { context: { isSystem: true } });
|
|
3086
|
+
} catch {
|
|
3087
|
+
return { handled: true, response: this.error("Failed to create API key", 500) };
|
|
3088
|
+
}
|
|
3089
|
+
const id = inserted?.id ?? (Array.isArray(inserted) ? inserted[0]?.id : void 0);
|
|
3090
|
+
return {
|
|
3091
|
+
handled: true,
|
|
3092
|
+
response: {
|
|
3093
|
+
status: 201,
|
|
3094
|
+
body: {
|
|
3095
|
+
success: true,
|
|
3096
|
+
data: {
|
|
3097
|
+
id,
|
|
3098
|
+
name,
|
|
3099
|
+
prefix: generated.prefix,
|
|
3100
|
+
key: generated.raw,
|
|
3101
|
+
...expiresAt ? { expires_at: expiresAt } : {}
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3061
3107
|
/**
|
|
3062
3108
|
* Parse a project UUID out of a scoped URL path such as
|
|
3063
3109
|
* `/api/v1/environments/abc-123/data/task` or `/projects/abc-123/meta`.
|
|
@@ -3344,7 +3390,11 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3344
3390
|
realtime: hasWebSockets ? `${prefix}/realtime` : void 0,
|
|
3345
3391
|
notifications: hasNotification ? `${prefix}/notifications` : void 0,
|
|
3346
3392
|
ai: hasAi ? `${prefix}/ai` : void 0,
|
|
3347
|
-
i18n: hasI18n ? `${prefix}/i18n` : void 0
|
|
3393
|
+
i18n: hasI18n ? `${prefix}/i18n` : void 0,
|
|
3394
|
+
// MCP (Streamable HTTP) is opt-in per env — only advertised
|
|
3395
|
+
// when OS_MCP_SERVER_ENABLED=true so the surface isn't exposed
|
|
3396
|
+
// by default. The objectui Integrations page reads this.
|
|
3397
|
+
mcp: _HttpDispatcher.isMcpEnabled() ? `${prefix}/mcp` : void 0
|
|
3348
3398
|
};
|
|
3349
3399
|
const svcAvailable = (route, provider) => ({
|
|
3350
3400
|
enabled: true,
|
|
@@ -3372,7 +3422,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3372
3422
|
return {
|
|
3373
3423
|
name: "ObjectOS",
|
|
3374
3424
|
version: "1.0.0",
|
|
3375
|
-
environment: (0,
|
|
3425
|
+
environment: (0, import_core3.getEnv)("NODE_ENV", "development"),
|
|
3376
3426
|
routes,
|
|
3377
3427
|
endpoints: routes,
|
|
3378
3428
|
// Alias for backward compatibility with some clients
|
|
@@ -3607,7 +3657,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3607
3657
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
3608
3658
|
try {
|
|
3609
3659
|
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
3610
|
-
const
|
|
3660
|
+
const previewDrafts = query?.preview === "draft";
|
|
3661
|
+
const data = await protocol.getMetaItem({ type: singularType, name, packageId, organizationId, previewDrafts });
|
|
3611
3662
|
return { handled: true, response: this.success(data) };
|
|
3612
3663
|
} catch (e) {
|
|
3613
3664
|
}
|
|
@@ -3649,7 +3700,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3649
3700
|
if (protocol && typeof protocol.getMetaItems === "function") {
|
|
3650
3701
|
try {
|
|
3651
3702
|
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
3652
|
-
const
|
|
3703
|
+
const previewDrafts = query?.preview === "draft";
|
|
3704
|
+
const data = await protocol.getMetaItems({ type: typeOrName, packageId, organizationId, previewDrafts });
|
|
3653
3705
|
if (data && (data.items !== void 0 || Array.isArray(data))) {
|
|
3654
3706
|
return { handled: true, response: this.success(data) };
|
|
3655
3707
|
}
|
|
@@ -3875,7 +3927,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3875
3927
|
let translations = i18nService.getTranslations(locale);
|
|
3876
3928
|
if (Object.keys(translations).length === 0) {
|
|
3877
3929
|
const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
|
|
3878
|
-
const resolved = (0,
|
|
3930
|
+
const resolved = (0, import_core3.resolveLocale)(locale, availableLocales);
|
|
3879
3931
|
if (resolved && resolved !== locale) {
|
|
3880
3932
|
translations = i18nService.getTranslations(resolved);
|
|
3881
3933
|
return { handled: true, response: this.success({ locale: resolved, requestedLocale: locale, translations }) };
|
|
@@ -3888,7 +3940,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3888
3940
|
let locale = parts[2] ? decodeURIComponent(parts[2]) : query?.locale;
|
|
3889
3941
|
if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
|
|
3890
3942
|
const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
|
|
3891
|
-
const resolved = (0,
|
|
3943
|
+
const resolved = (0, import_core3.resolveLocale)(locale, availableLocales);
|
|
3892
3944
|
if (resolved) locale = resolved;
|
|
3893
3945
|
if (typeof i18nService.getFieldLabels === "function") {
|
|
3894
3946
|
const labels2 = i18nService.getFieldLabels(objectName, locale);
|
|
@@ -3996,6 +4048,18 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3996
4048
|
...organizationId ? { organizationId } : {},
|
|
3997
4049
|
...body?.actor ? { actor: body.actor } : {}
|
|
3998
4050
|
});
|
|
4051
|
+
try {
|
|
4052
|
+
const seedNames = (result?.published ?? []).filter((p) => p?.type === "seed").map((p) => p.name);
|
|
4053
|
+
if (seedNames.length > 0) {
|
|
4054
|
+
result.seedApplied = await this.applyPublishedSeeds(
|
|
4055
|
+
seedNames,
|
|
4056
|
+
organizationId,
|
|
4057
|
+
_context
|
|
4058
|
+
);
|
|
4059
|
+
}
|
|
4060
|
+
} catch (e) {
|
|
4061
|
+
result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
|
|
4062
|
+
}
|
|
3999
4063
|
return { handled: true, response: this.success(result) };
|
|
4000
4064
|
} catch (e) {
|
|
4001
4065
|
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
@@ -4003,6 +4067,24 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4003
4067
|
}
|
|
4004
4068
|
return { handled: true, response: this.error("Draft publishing not supported", 501) };
|
|
4005
4069
|
}
|
|
4070
|
+
if (parts.length === 2 && parts[1] === "discard-drafts" && m === "POST") {
|
|
4071
|
+
const id = decodeURIComponent(parts[0]);
|
|
4072
|
+
const protocol = await this.resolveService("protocol");
|
|
4073
|
+
if (protocol && typeof protocol.discardPackageDrafts === "function") {
|
|
4074
|
+
try {
|
|
4075
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
4076
|
+
const result = await protocol.discardPackageDrafts({
|
|
4077
|
+
packageId: id,
|
|
4078
|
+
...organizationId ? { organizationId } : {},
|
|
4079
|
+
...body?.actor ? { actor: body.actor } : {}
|
|
4080
|
+
});
|
|
4081
|
+
return { handled: true, response: this.success(result) };
|
|
4082
|
+
} catch (e) {
|
|
4083
|
+
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
return { handled: true, response: this.error("Draft discarding not supported", 501) };
|
|
4087
|
+
}
|
|
4006
4088
|
if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
|
|
4007
4089
|
const id = decodeURIComponent(parts[0]);
|
|
4008
4090
|
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
@@ -4028,9 +4110,27 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4028
4110
|
}
|
|
4029
4111
|
if (parts.length === 1 && m === "DELETE") {
|
|
4030
4112
|
const id = decodeURIComponent(parts[0]);
|
|
4031
|
-
const
|
|
4032
|
-
|
|
4033
|
-
|
|
4113
|
+
const registryRemoved = registry.uninstallPackage(id);
|
|
4114
|
+
let persisted = void 0;
|
|
4115
|
+
const protocol = await this.resolveService("protocol");
|
|
4116
|
+
if (protocol && typeof protocol.deletePackage === "function") {
|
|
4117
|
+
try {
|
|
4118
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
4119
|
+
const keepData = query?.keepData === "true" || query?.keepData === "1";
|
|
4120
|
+
persisted = await protocol.deletePackage({
|
|
4121
|
+
packageId: id,
|
|
4122
|
+
...organizationId ? { organizationId } : {},
|
|
4123
|
+
...keepData ? { keepData: true } : {}
|
|
4124
|
+
});
|
|
4125
|
+
} catch (e) {
|
|
4126
|
+
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
const deletedCount = persisted?.deletedCount ?? 0;
|
|
4130
|
+
if (!registryRemoved && deletedCount === 0) {
|
|
4131
|
+
return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
4132
|
+
}
|
|
4133
|
+
return { handled: true, response: this.success({ success: true, registryRemoved, persisted }) };
|
|
4034
4134
|
}
|
|
4035
4135
|
} catch (e) {
|
|
4036
4136
|
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
@@ -4152,6 +4252,66 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4152
4252
|
* Physical database addressing (database_url, database_driver, etc.)
|
|
4153
4253
|
* is stored directly on the sys_environment row.
|
|
4154
4254
|
*/
|
|
4255
|
+
/**
|
|
4256
|
+
* Apply just-published `seed` metadata: load each seed's rows into its
|
|
4257
|
+
* target object so publishing a seed draft makes the data live (the runtime
|
|
4258
|
+
* counterpart to staging it). Reads each seed body via the protocol, then
|
|
4259
|
+
* runs the {@link SeedLoaderService} for the active org. Best-effort and
|
|
4260
|
+
* idempotent (upsert) — callers must never let this fail the publish.
|
|
4261
|
+
*
|
|
4262
|
+
* Lives at the runtime layer (not in the objectql publish primitive)
|
|
4263
|
+
* because the seed loader needs the data engine + metadata service, which
|
|
4264
|
+
* objectql cannot depend on without a layering cycle.
|
|
4265
|
+
*/
|
|
4266
|
+
async applyPublishedSeeds(names, organizationId, _context) {
|
|
4267
|
+
const protocol = await this.resolveService("protocol");
|
|
4268
|
+
const metadata = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
4269
|
+
const ql = await this.resolveService("objectql");
|
|
4270
|
+
if (!protocol || typeof protocol.getMetaItem !== "function" || !ql || !metadata) {
|
|
4271
|
+
return { success: false, error: "seed apply: required services unavailable" };
|
|
4272
|
+
}
|
|
4273
|
+
const datasets = [];
|
|
4274
|
+
const readErrors = [];
|
|
4275
|
+
for (const name of names) {
|
|
4276
|
+
const attempts = organizationId ? [{ type: "seed", name, organizationId }, { type: "seed", name }] : [{ type: "seed", name }];
|
|
4277
|
+
let item;
|
|
4278
|
+
for (const args of attempts) {
|
|
4279
|
+
try {
|
|
4280
|
+
item = await protocol.getMetaItem(args);
|
|
4281
|
+
if (item) break;
|
|
4282
|
+
} catch (e) {
|
|
4283
|
+
readErrors.push(`read ${name}: ${e?.message ?? String(e)}`);
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
const seed = item?.object && Array.isArray(item?.records) ? item : item?.item ?? item?.metadata ?? item?.body;
|
|
4287
|
+
if (seed?.object && Array.isArray(seed?.records)) {
|
|
4288
|
+
datasets.push(seed);
|
|
4289
|
+
} else {
|
|
4290
|
+
readErrors.push(`seed "${name}" body unreadable (keys: ${item ? Object.keys(item).join(",") : "none"})`);
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (datasets.length === 0) {
|
|
4294
|
+
return { success: false, inserted: 0, updated: 0, error: "seed apply: no readable seed bodies", errors: readErrors };
|
|
4295
|
+
}
|
|
4296
|
+
const { SeedLoaderService: SeedLoaderService2 } = await Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports));
|
|
4297
|
+
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
4298
|
+
const loader = new SeedLoaderService2(ql, metadata, this.logger ?? console);
|
|
4299
|
+
const request = SeedLoaderRequestSchema.parse({
|
|
4300
|
+
seeds: datasets,
|
|
4301
|
+
config: {
|
|
4302
|
+
defaultMode: "upsert",
|
|
4303
|
+
multiPass: true,
|
|
4304
|
+
...organizationId ? { organizationId } : {}
|
|
4305
|
+
}
|
|
4306
|
+
});
|
|
4307
|
+
const r = await loader.load(request);
|
|
4308
|
+
return {
|
|
4309
|
+
success: r.success,
|
|
4310
|
+
inserted: r.summary.totalInserted,
|
|
4311
|
+
updated: r.summary.totalUpdated,
|
|
4312
|
+
errors: [...readErrors, ...r.errors ?? []]
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4155
4315
|
/**
|
|
4156
4316
|
* Resolve the calling user id from the request session, if any.
|
|
4157
4317
|
* Returns `undefined` for anonymous calls or when auth is not wired up.
|
|
@@ -4736,6 +4896,12 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4736
4896
|
if (cleanPath.startsWith("/data")) {
|
|
4737
4897
|
return this.handleData(cleanPath.substring(5), method, body, query, context);
|
|
4738
4898
|
}
|
|
4899
|
+
if (cleanPath === "/mcp" || cleanPath.startsWith("/mcp/") || cleanPath.startsWith("/mcp?")) {
|
|
4900
|
+
return this.handleMcp(body, context);
|
|
4901
|
+
}
|
|
4902
|
+
if (cleanPath === "/keys" || cleanPath.startsWith("/keys/") || cleanPath.startsWith("/keys?")) {
|
|
4903
|
+
return this.handleKeys(method, body, context);
|
|
4904
|
+
}
|
|
4739
4905
|
if (cleanPath.startsWith("/graphql")) {
|
|
4740
4906
|
if (method === "POST") return this.handleGraphQL(body, context);
|
|
4741
4907
|
}
|
|
@@ -5438,6 +5604,28 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5438
5604
|
errorResponse(err, res);
|
|
5439
5605
|
}
|
|
5440
5606
|
});
|
|
5607
|
+
const mountMcp = (method) => {
|
|
5608
|
+
const register = method === "GET" ? server.get : method === "DELETE" ? server.delete : server.post;
|
|
5609
|
+
register.call(server, `${prefix}/mcp`, async (req, res) => {
|
|
5610
|
+
try {
|
|
5611
|
+
const result = await dispatcher.dispatch(method, "/mcp", req.body, req.query, { request: req });
|
|
5612
|
+
sendResult(result, res);
|
|
5613
|
+
} catch (err) {
|
|
5614
|
+
errorResponse(err, res);
|
|
5615
|
+
}
|
|
5616
|
+
});
|
|
5617
|
+
};
|
|
5618
|
+
mountMcp("POST");
|
|
5619
|
+
mountMcp("GET");
|
|
5620
|
+
mountMcp("DELETE");
|
|
5621
|
+
server.post(`${prefix}/keys`, async (req, res) => {
|
|
5622
|
+
try {
|
|
5623
|
+
const result = await dispatcher.dispatch("POST", "/keys", req.body, req.query, { request: req });
|
|
5624
|
+
sendResult(result, res);
|
|
5625
|
+
} catch (err) {
|
|
5626
|
+
errorResponse(err, res);
|
|
5627
|
+
}
|
|
5628
|
+
});
|
|
5441
5629
|
server.get(`${prefix}/packages`, async (req, res) => {
|
|
5442
5630
|
try {
|
|
5443
5631
|
const result = await dispatcher.handlePackages("", "GET", {}, req.query, { request: req });
|
|
@@ -6083,1571 +6271,24 @@ var MiddlewareManager = class {
|
|
|
6083
6271
|
// src/index.ts
|
|
6084
6272
|
init_load_artifact_bundle();
|
|
6085
6273
|
|
|
6086
|
-
// src/cloud/
|
|
6087
|
-
var
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
this.ttlMs = config.ttlMs ?? 15 * 60 * 1e3;
|
|
6094
|
-
this.logger = config.logger ?? console;
|
|
6095
|
-
this.freshnessProbe = config.freshnessProbe;
|
|
6096
|
-
this.staleCheckIntervalMs = config.staleCheckIntervalMs ?? 1e4;
|
|
6097
|
-
}
|
|
6098
|
-
/** Returns the currently cached environmentIds (ordered by insertion). */
|
|
6099
|
-
keys() {
|
|
6100
|
-
return Array.from(this.cache.keys());
|
|
6101
|
-
}
|
|
6102
|
-
/** Cache size for diagnostics. */
|
|
6103
|
-
get size() {
|
|
6104
|
-
return this.cache.size;
|
|
6105
|
-
}
|
|
6106
|
-
/**
|
|
6107
|
-
* Resolve or construct the kernel for `environmentId`.
|
|
6108
|
-
*
|
|
6109
|
-
* - Cache hit (fresh): bumps `lastAccess` and returns immediately.
|
|
6110
|
-
* - Cache hit (TTL expired): evicts then falls through to factory.
|
|
6111
|
-
* - Cache miss: dedupes concurrent callers through `pending`.
|
|
6112
|
-
*/
|
|
6113
|
-
async getOrCreate(environmentId) {
|
|
6114
|
-
const existing = this.cache.get(environmentId);
|
|
6115
|
-
if (existing) {
|
|
6116
|
-
if (this.ttlMs > 0 && Date.now() - existing.lastAccess > this.ttlMs) {
|
|
6117
|
-
await this.evict(environmentId);
|
|
6118
|
-
} else {
|
|
6119
|
-
if (this.freshnessProbe) {
|
|
6120
|
-
const now = Date.now();
|
|
6121
|
-
if (now - existing.lastStaleCheckAt >= this.staleCheckIntervalMs) {
|
|
6122
|
-
existing.lastStaleCheckAt = now;
|
|
6123
|
-
let stale = false;
|
|
6124
|
-
try {
|
|
6125
|
-
stale = await this.freshnessProbe(environmentId, existing.createdAt);
|
|
6126
|
-
} catch (err) {
|
|
6127
|
-
this.logger.warn?.("[KernelManager] freshness probe failed", { environmentId, err });
|
|
6128
|
-
}
|
|
6129
|
-
if (stale) {
|
|
6130
|
-
this.logger.info?.("[KernelManager] kernel evicted by freshness probe", { environmentId });
|
|
6131
|
-
await this.evict(environmentId);
|
|
6132
|
-
} else {
|
|
6133
|
-
existing.lastAccess = Date.now();
|
|
6134
|
-
return existing.kernel;
|
|
6135
|
-
}
|
|
6136
|
-
} else {
|
|
6137
|
-
existing.lastAccess = Date.now();
|
|
6138
|
-
return existing.kernel;
|
|
6139
|
-
}
|
|
6140
|
-
} else {
|
|
6141
|
-
existing.lastAccess = Date.now();
|
|
6142
|
-
return existing.kernel;
|
|
6143
|
-
}
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
const inflight = this.pending.get(environmentId);
|
|
6147
|
-
if (inflight) return inflight;
|
|
6148
|
-
const promise = (async () => {
|
|
6149
|
-
const kernel = await this.factory.create(environmentId);
|
|
6150
|
-
const now = Date.now();
|
|
6151
|
-
this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now, lastStaleCheckAt: now });
|
|
6152
|
-
await this.enforceMaxSize();
|
|
6153
|
-
return kernel;
|
|
6154
|
-
})();
|
|
6155
|
-
this.pending.set(environmentId, promise);
|
|
6156
|
-
try {
|
|
6157
|
-
return await promise;
|
|
6158
|
-
} finally {
|
|
6159
|
-
this.pending.delete(environmentId);
|
|
6160
|
-
}
|
|
6161
|
-
}
|
|
6162
|
-
/**
|
|
6163
|
-
* Evict the kernel for `environmentId` and invoke `kernel.shutdown()`.
|
|
6164
|
-
* No-op when the entry is absent.
|
|
6165
|
-
*/
|
|
6166
|
-
async evict(environmentId) {
|
|
6167
|
-
const entry = this.cache.get(environmentId);
|
|
6168
|
-
if (!entry) return;
|
|
6169
|
-
this.cache.delete(environmentId);
|
|
6170
|
-
try {
|
|
6171
|
-
await entry.kernel.shutdown();
|
|
6172
|
-
} catch (err) {
|
|
6173
|
-
this.logger.error?.("[KernelManager] shutdown failed", { environmentId, err });
|
|
6174
|
-
}
|
|
6175
|
-
}
|
|
6176
|
-
/** Evict all resident kernels. Used on runtime shutdown. */
|
|
6177
|
-
async evictAll() {
|
|
6178
|
-
const ids = Array.from(this.cache.keys());
|
|
6179
|
-
await Promise.all(ids.map((id) => this.evict(id)));
|
|
6180
|
-
}
|
|
6181
|
-
async enforceMaxSize() {
|
|
6182
|
-
while (this.cache.size > this.maxSize) {
|
|
6183
|
-
let oldestKey;
|
|
6184
|
-
let oldestAccess = Infinity;
|
|
6185
|
-
for (const [key, entry] of this.cache) {
|
|
6186
|
-
if (entry.lastAccess < oldestAccess) {
|
|
6187
|
-
oldestAccess = entry.lastAccess;
|
|
6188
|
-
oldestKey = key;
|
|
6189
|
-
}
|
|
6190
|
-
}
|
|
6191
|
-
if (!oldestKey) return;
|
|
6192
|
-
await this.evict(oldestKey);
|
|
6193
|
-
}
|
|
6274
|
+
// src/cloud/cloud-url.ts
|
|
6275
|
+
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
6276
|
+
function resolveCloudUrl(explicit) {
|
|
6277
|
+
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
6278
|
+
const lower = raw.toLowerCase();
|
|
6279
|
+
if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
|
|
6280
|
+
return "";
|
|
6194
6281
|
}
|
|
6195
|
-
|
|
6282
|
+
const picked = raw || DEFAULT_CLOUD_URL;
|
|
6283
|
+
return picked.replace(/\/+$/, "");
|
|
6284
|
+
}
|
|
6196
6285
|
|
|
6197
|
-
// src/cloud/
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
this.pendingArtifact = /* @__PURE__ */ new Map();
|
|
6204
|
-
if (!config.controlPlaneUrl) {
|
|
6205
|
-
throw new Error("[ArtifactApiClient] controlPlaneUrl is required");
|
|
6206
|
-
}
|
|
6207
|
-
this.base = config.controlPlaneUrl.replace(/\/+$/, "");
|
|
6208
|
-
this.apiKey = config.apiKey;
|
|
6209
|
-
this.cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1e3;
|
|
6210
|
-
this.requestTimeoutMs = config.requestTimeoutMs ?? 1e4;
|
|
6211
|
-
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
6212
|
-
this.logger = config.logger ?? console;
|
|
6213
|
-
if (typeof this.fetchImpl !== "function") {
|
|
6214
|
-
throw new Error("[ArtifactApiClient] global fetch is not available \u2014 provide config.fetch");
|
|
6215
|
-
}
|
|
6216
|
-
}
|
|
6217
|
-
/**
|
|
6218
|
-
* Resolve a hostname to its project. Returns `null` on 404 or
|
|
6219
|
-
* malformed responses. Errors (network / 5xx) are thrown so
|
|
6220
|
-
* upstream callers can retry.
|
|
6221
|
-
*/
|
|
6222
|
-
async resolveHostname(host) {
|
|
6223
|
-
const cached = this.hostnameCache.get(host);
|
|
6224
|
-
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
6225
|
-
const inflight = this.pendingHostname.get(host);
|
|
6226
|
-
if (inflight) return inflight;
|
|
6227
|
-
const promise = (async () => {
|
|
6228
|
-
try {
|
|
6229
|
-
const url = `${this.base}/api/v1/cloud/resolve-hostname?host=${encodeURIComponent(host)}`;
|
|
6230
|
-
const res = await this.request(url);
|
|
6231
|
-
if (res === null) return null;
|
|
6232
|
-
const body = res.success === false ? null : res.data ?? res;
|
|
6233
|
-
if (!body || typeof body.environmentId !== "string" || !body.environmentId) return null;
|
|
6234
|
-
const value = {
|
|
6235
|
-
environmentId: body.environmentId,
|
|
6236
|
-
organizationId: body.organizationId,
|
|
6237
|
-
runtime: body.runtime
|
|
6238
|
-
};
|
|
6239
|
-
this.hostnameCache.set(host, { value, expiresAt: Date.now() + this.cacheTtlMs });
|
|
6240
|
-
return value;
|
|
6241
|
-
} finally {
|
|
6242
|
-
this.pendingHostname.delete(host);
|
|
6243
|
-
}
|
|
6244
|
-
})();
|
|
6245
|
-
this.pendingHostname.set(host, promise);
|
|
6246
|
-
return promise;
|
|
6247
|
-
}
|
|
6248
|
-
/**
|
|
6249
|
-
* Fetch the compiled artifact for a project.
|
|
6250
|
-
*
|
|
6251
|
-
* When `opts.commit` is set, requests that specific revision via the
|
|
6252
|
-
* existing `?commit=` query param. Different commits are cached
|
|
6253
|
-
* independently (the cache key includes the commit id) so the preview
|
|
6254
|
-
* runtime can hold multiple versions in memory simultaneously.
|
|
6255
|
-
*/
|
|
6256
|
-
async fetchArtifact(environmentId, opts) {
|
|
6257
|
-
const commit = opts?.commit?.trim() || "";
|
|
6258
|
-
const cacheKey = commit ? `${environmentId}@${commit}` : environmentId;
|
|
6259
|
-
const cached = this.artifactCache.get(cacheKey);
|
|
6260
|
-
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
6261
|
-
const inflight = this.pendingArtifact.get(cacheKey);
|
|
6262
|
-
if (inflight) return inflight;
|
|
6263
|
-
const promise = (async () => {
|
|
6264
|
-
try {
|
|
6265
|
-
const qs = commit ? `?commit=${encodeURIComponent(commit)}` : "";
|
|
6266
|
-
const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/artifact${qs}`;
|
|
6267
|
-
const res = await this.request(url);
|
|
6268
|
-
if (res === null) return null;
|
|
6269
|
-
const body = res.success === false ? null : res.data ?? res;
|
|
6270
|
-
if (!body || typeof body !== "object") return null;
|
|
6271
|
-
if (!body.metadata) {
|
|
6272
|
-
this.logger.warn?.("[ArtifactApiClient] artifact response missing `metadata`", { environmentId, commit });
|
|
6273
|
-
return null;
|
|
6274
|
-
}
|
|
6275
|
-
const value = body;
|
|
6276
|
-
this.artifactCache.set(cacheKey, { value, expiresAt: Date.now() + this.cacheTtlMs });
|
|
6277
|
-
return value;
|
|
6278
|
-
} finally {
|
|
6279
|
-
this.pendingArtifact.delete(cacheKey);
|
|
6280
|
-
}
|
|
6281
|
-
})();
|
|
6282
|
-
this.pendingArtifact.set(cacheKey, promise);
|
|
6283
|
-
return promise;
|
|
6284
|
-
}
|
|
6285
|
-
/**
|
|
6286
|
-
* Resolve an 8-hex project short id (first 8 hex chars of the UUID,
|
|
6287
|
-
* dashes stripped) to the full environmentId. Used by the preview
|
|
6288
|
-
* runtime, which encodes project ids in subdomains.
|
|
6289
|
-
*
|
|
6290
|
-
* Returns `null` on 404 or ambiguity (the control plane returns 409
|
|
6291
|
-
* if the prefix matches more than one project).
|
|
6292
|
-
*/
|
|
6293
|
-
async lookupProjectByShortId(shortId) {
|
|
6294
|
-
const short = String(shortId ?? "").trim().toLowerCase();
|
|
6295
|
-
if (!/^[0-9a-f]{8,}$/.test(short)) return null;
|
|
6296
|
-
const url = `${this.base}/api/v1/cloud/environments-by-short-id/${encodeURIComponent(short)}`;
|
|
6297
|
-
const res = await this.request(url);
|
|
6298
|
-
if (res === null) return null;
|
|
6299
|
-
const body = res.success === false ? null : res.data ?? res;
|
|
6300
|
-
if (!body || typeof body.environmentId !== "string" || !body.environmentId) return null;
|
|
6301
|
-
return { environmentId: body.environmentId, organizationId: body.organizationId };
|
|
6302
|
-
}
|
|
6303
|
-
/**
|
|
6304
|
-
* Fetch the head commit of a branch. Returns the commit id (and the
|
|
6305
|
-
* matching revision row's `published_at` for cache-validity checks).
|
|
6306
|
-
* Reuses the existing `GET /cloud/environments/:id/branches` endpoint.
|
|
6307
|
-
*/
|
|
6308
|
-
async fetchBranchHead(environmentId, branchName) {
|
|
6309
|
-
const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/branches`;
|
|
6310
|
-
const res = await this.request(url);
|
|
6311
|
-
if (res === null) return null;
|
|
6312
|
-
const body = res.success === false ? null : res.data ?? res;
|
|
6313
|
-
const branches = Array.isArray(body?.branches) ? body.branches : [];
|
|
6314
|
-
const target = String(branchName ?? "").trim().toLowerCase();
|
|
6315
|
-
const found = branches.find((b) => String(b?.branch ?? "").toLowerCase() === target);
|
|
6316
|
-
if (!found?.headCommitId) return null;
|
|
6317
|
-
return { commitId: String(found.headCommitId), publishedAt: found.headPublishedAt ?? null };
|
|
6318
|
-
}
|
|
6319
|
-
/**
|
|
6320
|
-
* Cheap freshness probe — returns the env's `last_published_at`
|
|
6321
|
-
* (and best-effort current commit) without rebuilding the artifact.
|
|
6322
|
-
* Used by `KernelManager` on cache hits to detect when a per-env
|
|
6323
|
-
* kernel has been invalidated by an upstream change (marketplace
|
|
6324
|
-
* install/uninstall, artifact publish) so it can be rebuilt
|
|
6325
|
-
* without waiting for the 15-minute LRU TTL to expire.
|
|
6326
|
-
*
|
|
6327
|
-
* Returns `null` on definitive 404 / unknown env. Errors propagate
|
|
6328
|
-
* (caller decides whether to treat unreachable cloud as fresh or
|
|
6329
|
-
* stale — typically fresh, so a brief outage doesn't churn every
|
|
6330
|
-
* cached kernel).
|
|
6331
|
-
*/
|
|
6332
|
-
async getFreshness(environmentId) {
|
|
6333
|
-
const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/freshness`;
|
|
6334
|
-
const res = await this.request(url);
|
|
6335
|
-
if (res === null) return null;
|
|
6336
|
-
const body = res.success === false ? null : res.data ?? res;
|
|
6337
|
-
if (!body || typeof body !== "object") return null;
|
|
6338
|
-
const envId = typeof body.environmentId === "string" ? body.environmentId : environmentId;
|
|
6339
|
-
const lastPublishedAt = typeof body.lastPublishedAt === "string" ? body.lastPublishedAt : null;
|
|
6340
|
-
const commitId = typeof body.commitId === "string" ? body.commitId : null;
|
|
6341
|
-
return { environmentId: envId, lastPublishedAt, commitId };
|
|
6342
|
-
}
|
|
6343
|
-
/** Drop cached entries for a project (and any matching hostname). */
|
|
6344
|
-
invalidate(environmentId) {
|
|
6345
|
-
this.artifactCache.delete(environmentId);
|
|
6346
|
-
const prefix = `${environmentId}@`;
|
|
6347
|
-
for (const key of Array.from(this.artifactCache.keys())) {
|
|
6348
|
-
if (key.startsWith(prefix)) this.artifactCache.delete(key);
|
|
6349
|
-
}
|
|
6350
|
-
for (const [host, entry] of this.hostnameCache) {
|
|
6351
|
-
if (entry.value.environmentId === environmentId) this.hostnameCache.delete(host);
|
|
6352
|
-
}
|
|
6353
|
-
}
|
|
6354
|
-
/** Drop everything. Used on shutdown / hot-reload. */
|
|
6355
|
-
clear() {
|
|
6356
|
-
this.hostnameCache.clear();
|
|
6357
|
-
this.artifactCache.clear();
|
|
6358
|
-
}
|
|
6359
|
-
async request(url) {
|
|
6360
|
-
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
6361
|
-
const timer = controller ? setTimeout(() => controller.abort(), this.requestTimeoutMs) : null;
|
|
6362
|
-
try {
|
|
6363
|
-
const res = await this.fetchImpl(url, {
|
|
6364
|
-
method: "GET",
|
|
6365
|
-
headers: this.buildHeaders(),
|
|
6366
|
-
signal: controller?.signal
|
|
6367
|
-
});
|
|
6368
|
-
if (res.status === 404) return null;
|
|
6369
|
-
if (!res.ok) {
|
|
6370
|
-
throw new Error(`[ArtifactApiClient] ${url} \u2192 HTTP ${res.status}`);
|
|
6371
|
-
}
|
|
6372
|
-
return await res.json();
|
|
6373
|
-
} finally {
|
|
6374
|
-
if (timer) clearTimeout(timer);
|
|
6375
|
-
}
|
|
6376
|
-
}
|
|
6377
|
-
buildHeaders() {
|
|
6378
|
-
const headers = {
|
|
6379
|
-
"accept": "application/json",
|
|
6380
|
-
"user-agent": "objectos-runtime"
|
|
6381
|
-
};
|
|
6382
|
-
if (this.apiKey) headers["authorization"] = `Bearer ${this.apiKey}`;
|
|
6383
|
-
return headers;
|
|
6384
|
-
}
|
|
6385
|
-
};
|
|
6386
|
-
|
|
6387
|
-
// src/cloud/artifact-environment-registry.ts
|
|
6388
|
-
var import_node_path5 = require("path");
|
|
6389
|
-
var ArtifactEnvironmentRegistry = class {
|
|
6390
|
-
constructor(config) {
|
|
6391
|
-
this.hostnameCache = /* @__PURE__ */ new Map();
|
|
6392
|
-
this.idCache = /* @__PURE__ */ new Map();
|
|
6393
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
6394
|
-
this.client = config.client;
|
|
6395
|
-
this.cacheTTL = config.cacheTtlMs ?? 5 * 60 * 1e3;
|
|
6396
|
-
this.logger = config.logger ?? console;
|
|
6397
|
-
}
|
|
6398
|
-
async resolveByHostname(host) {
|
|
6399
|
-
const cached = this.hostnameCache.get(host);
|
|
6400
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
6401
|
-
return { environmentId: cached.environmentId, driver: cached.driver };
|
|
6402
|
-
}
|
|
6403
|
-
const key = `host:${host}`;
|
|
6404
|
-
const inflight = this.pending.get(key);
|
|
6405
|
-
if (inflight) {
|
|
6406
|
-
const result = await inflight;
|
|
6407
|
-
return result ? { environmentId: result.environmentId, driver: result.driver } : null;
|
|
6408
|
-
}
|
|
6409
|
-
const promise = (async () => {
|
|
6410
|
-
try {
|
|
6411
|
-
const resolved = await this.client.resolveHostname(host);
|
|
6412
|
-
if (!resolved) return null;
|
|
6413
|
-
const entry2 = await this.buildCacheEntry(resolved.environmentId, resolved.runtime, resolved.organizationId, host);
|
|
6414
|
-
if (!entry2) return null;
|
|
6415
|
-
this.hostnameCache.set(host, entry2);
|
|
6416
|
-
this.idCache.set(entry2.environmentId, entry2);
|
|
6417
|
-
return entry2;
|
|
6418
|
-
} catch (err) {
|
|
6419
|
-
this.logger.error?.("[ArtifactEnvironmentRegistry] resolveByHostname failed", {
|
|
6420
|
-
host,
|
|
6421
|
-
error: err?.message ?? err
|
|
6422
|
-
});
|
|
6423
|
-
return null;
|
|
6424
|
-
} finally {
|
|
6425
|
-
this.pending.delete(key);
|
|
6426
|
-
}
|
|
6427
|
-
})();
|
|
6428
|
-
this.pending.set(key, promise);
|
|
6429
|
-
const entry = await promise;
|
|
6430
|
-
return entry ? { environmentId: entry.environmentId, driver: entry.driver } : null;
|
|
6431
|
-
}
|
|
6432
|
-
async resolveById(environmentId) {
|
|
6433
|
-
const cached = this.idCache.get(environmentId);
|
|
6434
|
-
if (cached && cached.expiresAt > Date.now()) return cached.driver;
|
|
6435
|
-
const key = `id:${environmentId}`;
|
|
6436
|
-
const inflight = this.pending.get(key);
|
|
6437
|
-
if (inflight) {
|
|
6438
|
-
const result = await inflight;
|
|
6439
|
-
return result?.driver ?? null;
|
|
6440
|
-
}
|
|
6441
|
-
const promise = (async () => {
|
|
6442
|
-
try {
|
|
6443
|
-
const entry2 = await this.buildCacheEntry(environmentId, void 0, void 0, void 0);
|
|
6444
|
-
if (!entry2) return null;
|
|
6445
|
-
this.idCache.set(environmentId, entry2);
|
|
6446
|
-
if (entry2.project?.hostname) this.hostnameCache.set(entry2.project.hostname, entry2);
|
|
6447
|
-
return entry2;
|
|
6448
|
-
} catch (err) {
|
|
6449
|
-
this.logger.error?.("[ArtifactEnvironmentRegistry] resolveById failed", {
|
|
6450
|
-
environmentId,
|
|
6451
|
-
error: err?.message ?? err
|
|
6452
|
-
});
|
|
6453
|
-
return null;
|
|
6454
|
-
} finally {
|
|
6455
|
-
this.pending.delete(key);
|
|
6456
|
-
}
|
|
6457
|
-
})();
|
|
6458
|
-
this.pending.set(key, promise);
|
|
6459
|
-
const entry = await promise;
|
|
6460
|
-
return entry?.driver ?? null;
|
|
6461
|
-
}
|
|
6462
|
-
peekById(environmentId) {
|
|
6463
|
-
const cached = this.idCache.get(environmentId);
|
|
6464
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
6465
|
-
return { environmentId: cached.environmentId, driver: cached.driver, project: cached.project };
|
|
6466
|
-
}
|
|
6467
|
-
return null;
|
|
6468
|
-
}
|
|
6469
|
-
invalidate(environmentId) {
|
|
6470
|
-
this.idCache.delete(environmentId);
|
|
6471
|
-
for (const [host, entry] of this.hostnameCache) {
|
|
6472
|
-
if (entry.environmentId === environmentId) this.hostnameCache.delete(host);
|
|
6473
|
-
}
|
|
6474
|
-
this.client.invalidate(environmentId);
|
|
6475
|
-
}
|
|
6476
|
-
async buildCacheEntry(environmentId, runtimeFromHostname, orgIdFromHostname, hostname) {
|
|
6477
|
-
let runtime = runtimeFromHostname;
|
|
6478
|
-
let organizationId = orgIdFromHostname;
|
|
6479
|
-
let host = hostname;
|
|
6480
|
-
let artifactProjectId = environmentId;
|
|
6481
|
-
if (!runtime || !organizationId) {
|
|
6482
|
-
const artifact = await this.client.fetchArtifact(environmentId);
|
|
6483
|
-
if (!artifact) {
|
|
6484
|
-
this.logger.warn?.("[ArtifactEnvironmentRegistry] artifact not found", { environmentId });
|
|
6485
|
-
return null;
|
|
6486
|
-
}
|
|
6487
|
-
artifactProjectId = artifact.environmentId ?? environmentId;
|
|
6488
|
-
if (!runtime) runtime = artifact.runtime ?? extractRuntimeFromMetadata(artifact.metadata);
|
|
6489
|
-
if (!organizationId) organizationId = artifact.runtime?.organizationId;
|
|
6490
|
-
if (!host) host = artifact.runtime?.hostname;
|
|
6491
|
-
}
|
|
6492
|
-
if (!runtime || !runtime.databaseUrl || !runtime.databaseDriver) {
|
|
6493
|
-
this.logger.warn?.("[ArtifactEnvironmentRegistry] no runtime config for project", { environmentId });
|
|
6494
|
-
return null;
|
|
6495
|
-
}
|
|
6496
|
-
const driver = await createDriver(runtime.databaseDriver, runtime.databaseUrl, runtime.databaseAuthToken ?? "");
|
|
6497
|
-
const projectRow = {
|
|
6498
|
-
id: artifactProjectId,
|
|
6499
|
-
organization_id: organizationId,
|
|
6500
|
-
hostname: host,
|
|
6501
|
-
database_url: runtime.databaseUrl,
|
|
6502
|
-
database_driver: runtime.databaseDriver,
|
|
6503
|
-
metadata: runtime.metadata
|
|
6504
|
-
};
|
|
6505
|
-
return {
|
|
6506
|
-
environmentId: artifactProjectId,
|
|
6507
|
-
driver,
|
|
6508
|
-
project: projectRow,
|
|
6509
|
-
expiresAt: Date.now() + this.cacheTTL
|
|
6510
|
-
};
|
|
6511
|
-
}
|
|
6512
|
-
};
|
|
6513
|
-
function extractRuntimeFromMetadata(metadata) {
|
|
6514
|
-
const datasources = metadata?.datasources;
|
|
6515
|
-
if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
|
|
6516
|
-
const mapping = metadata?.datasourceMapping;
|
|
6517
|
-
let preferredName;
|
|
6518
|
-
if (mapping) {
|
|
6519
|
-
const def = mapping.find((m) => m?.default === true);
|
|
6520
|
-
if (def?.datasource) preferredName = def.datasource;
|
|
6521
|
-
}
|
|
6522
|
-
const ds = preferredName ? datasources.find((d) => d?.name === preferredName) : datasources[0];
|
|
6523
|
-
if (!ds || typeof ds !== "object") return void 0;
|
|
6524
|
-
const config = ds.config ?? {};
|
|
6525
|
-
const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
|
|
6526
|
-
const driver = ds.driver;
|
|
6527
|
-
if (typeof driver !== "string" || typeof url !== "string") return void 0;
|
|
6528
|
-
return {
|
|
6529
|
-
databaseDriver: driver,
|
|
6530
|
-
databaseUrl: url,
|
|
6531
|
-
databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
|
|
6532
|
-
};
|
|
6533
|
-
}
|
|
6534
|
-
async function createDriver(driverType, databaseUrl, authToken) {
|
|
6535
|
-
switch (driverType) {
|
|
6536
|
-
case "libsql":
|
|
6537
|
-
case "turso": {
|
|
6538
|
-
let TursoDriver;
|
|
6539
|
-
try {
|
|
6540
|
-
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
6541
|
-
} catch (primaryErr) {
|
|
6542
|
-
try {
|
|
6543
|
-
const { createRequire } = await import("module");
|
|
6544
|
-
const path = await import("path");
|
|
6545
|
-
const url = await import("url");
|
|
6546
|
-
const hostRequire = createRequire(path.join(process.cwd(), "noop.js"));
|
|
6547
|
-
const resolved = hostRequire.resolve("@objectstack/driver-turso");
|
|
6548
|
-
({ TursoDriver } = await import(url.pathToFileURL(resolved).href));
|
|
6549
|
-
} catch (fallbackErr) {
|
|
6550
|
-
throw new Error(
|
|
6551
|
-
`[ArtifactEnvironmentRegistry] libsql/turso driver requested but @objectstack/driver-turso is not resolvable. Install it from the cloud monorepo (cloud/packages/driver-turso) or via npm. (primary: ${primaryErr?.message ?? primaryErr}; fallback: ${fallbackErr?.message ?? fallbackErr})`
|
|
6552
|
-
);
|
|
6553
|
-
}
|
|
6554
|
-
}
|
|
6555
|
-
return new TursoDriver({ url: databaseUrl, authToken });
|
|
6556
|
-
}
|
|
6557
|
-
case "memory": {
|
|
6558
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
6559
|
-
const dbName = databaseUrl.replace(/^memory:\/\//, "").trim();
|
|
6560
|
-
const filePath = dbName ? (0, import_node_path5.resolve)(process.cwd(), ".objectstack/data/projects", `${dbName}.json`) : void 0;
|
|
6561
|
-
return new InMemoryDriver({
|
|
6562
|
-
persistence: filePath ? { type: "file", path: filePath } : "file"
|
|
6563
|
-
});
|
|
6564
|
-
}
|
|
6565
|
-
case "sqlite":
|
|
6566
|
-
case "sql": {
|
|
6567
|
-
const filePath = databaseUrl.replace(/^file:/, "").replace(/^sql:\/\//, "");
|
|
6568
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
6569
|
-
return new SqlDriver({
|
|
6570
|
-
client: "better-sqlite3",
|
|
6571
|
-
connection: { filename: filePath },
|
|
6572
|
-
useNullAsDefault: true
|
|
6573
|
-
});
|
|
6574
|
-
}
|
|
6575
|
-
case "postgres":
|
|
6576
|
-
case "postgresql":
|
|
6577
|
-
case "pg": {
|
|
6578
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
6579
|
-
return new SqlDriver({
|
|
6580
|
-
client: "pg",
|
|
6581
|
-
connection: databaseUrl,
|
|
6582
|
-
pool: { min: 0, max: 5 }
|
|
6583
|
-
});
|
|
6584
|
-
}
|
|
6585
|
-
case "mongodb":
|
|
6586
|
-
case "mongo": {
|
|
6587
|
-
const { MongoDBDriver } = await import("@objectstack/driver-mongodb");
|
|
6588
|
-
return new MongoDBDriver({ url: databaseUrl });
|
|
6589
|
-
}
|
|
6590
|
-
default:
|
|
6591
|
-
throw new Error(`[ArtifactEnvironmentRegistry] Unsupported driver type: ${driverType}`);
|
|
6592
|
-
}
|
|
6593
|
-
}
|
|
6594
|
-
|
|
6595
|
-
// src/cloud/artifact-kernel-factory.ts
|
|
6596
|
-
var import_node_crypto2 = require("crypto");
|
|
6597
|
-
var import_core3 = require("@objectstack/core");
|
|
6598
|
-
var import_types3 = require("@objectstack/types");
|
|
6599
|
-
init_driver_plugin();
|
|
6600
|
-
init_app_plugin();
|
|
6601
|
-
|
|
6602
|
-
// src/cloud/capability-loader.ts
|
|
6603
|
-
var CAPABILITY_PROVIDERS = {
|
|
6604
|
-
automation: {
|
|
6605
|
-
// Self-contained: AutomationServicePlugin seeds all built-in node
|
|
6606
|
-
// executors itself (ADR-0018), so no companion node-pack plugins.
|
|
6607
|
-
pkg: "@objectstack/service-automation",
|
|
6608
|
-
export: "AutomationServicePlugin"
|
|
6609
|
-
},
|
|
6610
|
-
ai: {
|
|
6611
|
-
pkg: "@objectstack/service-ai",
|
|
6612
|
-
export: "AIServicePlugin"
|
|
6613
|
-
},
|
|
6614
|
-
analytics: {
|
|
6615
|
-
pkg: "@objectstack/service-analytics",
|
|
6616
|
-
export: "AnalyticsServicePlugin",
|
|
6617
|
-
configKey: "analyticsCubes"
|
|
6618
|
-
},
|
|
6619
|
-
audit: {
|
|
6620
|
-
pkg: "@objectstack/plugin-audit",
|
|
6621
|
-
export: "AuditPlugin"
|
|
6622
|
-
},
|
|
6623
|
-
cache: {
|
|
6624
|
-
pkg: "@objectstack/service-cache",
|
|
6625
|
-
export: "CacheServicePlugin"
|
|
6626
|
-
},
|
|
6627
|
-
storage: {
|
|
6628
|
-
pkg: "@objectstack/service-storage",
|
|
6629
|
-
export: "StorageServicePlugin"
|
|
6630
|
-
},
|
|
6631
|
-
queue: {
|
|
6632
|
-
pkg: "@objectstack/service-queue",
|
|
6633
|
-
export: "QueueServicePlugin"
|
|
6634
|
-
},
|
|
6635
|
-
job: {
|
|
6636
|
-
pkg: "@objectstack/service-job",
|
|
6637
|
-
export: "JobServicePlugin"
|
|
6638
|
-
},
|
|
6639
|
-
messaging: {
|
|
6640
|
-
// Backs the `notify` flow node (ADR-0012): delivers to a user's
|
|
6641
|
-
// channels (inbox by default → `sys_inbox_message` rows).
|
|
6642
|
-
pkg: "@objectstack/service-messaging",
|
|
6643
|
-
export: "MessagingServicePlugin"
|
|
6644
|
-
},
|
|
6645
|
-
triggers: {
|
|
6646
|
-
// Concrete flow triggers — record-change (ObjectQL hooks) + schedule
|
|
6647
|
-
// (cron/interval via the job service; pair `triggers` with `job`).
|
|
6648
|
-
pkg: "@objectstack/plugin-trigger-record-change",
|
|
6649
|
-
export: "RecordChangeTriggerPlugin",
|
|
6650
|
-
extras: [{ pkg: "@objectstack/plugin-trigger-schedule", export: "ScheduleTriggerPlugin" }]
|
|
6651
|
-
},
|
|
6652
|
-
realtime: {
|
|
6653
|
-
pkg: "@objectstack/service-realtime",
|
|
6654
|
-
export: "RealtimeServicePlugin"
|
|
6655
|
-
},
|
|
6656
|
-
feed: {
|
|
6657
|
-
pkg: "@objectstack/service-feed",
|
|
6658
|
-
export: "FeedServicePlugin"
|
|
6659
|
-
},
|
|
6660
|
-
settings: {
|
|
6661
|
-
pkg: "@objectstack/service-settings",
|
|
6662
|
-
export: "SettingsServicePlugin"
|
|
6663
|
-
}
|
|
6664
|
-
};
|
|
6665
|
-
async function loadCapabilities(opts) {
|
|
6666
|
-
const { kernel, requires, bundle, environmentId } = opts;
|
|
6667
|
-
const logger = opts.logger ?? console;
|
|
6668
|
-
const installed = [];
|
|
6669
|
-
const resolved = [...new Set(requires)];
|
|
6670
|
-
if (resolved.includes("audit") && !resolved.includes("messaging")) {
|
|
6671
|
-
resolved.push("messaging");
|
|
6672
|
-
}
|
|
6673
|
-
for (const cap of resolved) {
|
|
6674
|
-
const spec = CAPABILITY_PROVIDERS[cap];
|
|
6675
|
-
if (!spec) {
|
|
6676
|
-
continue;
|
|
6677
|
-
}
|
|
6678
|
-
try {
|
|
6679
|
-
const mod = await import(
|
|
6680
|
-
/* webpackIgnore: true */
|
|
6681
|
-
spec.pkg
|
|
6682
|
-
);
|
|
6683
|
-
const Ctor = mod[spec.export];
|
|
6684
|
-
if (!Ctor) {
|
|
6685
|
-
logger.warn?.(
|
|
6686
|
-
`[CapabilityLoader] '${cap}': package '${spec.pkg}' did not export '${spec.export}'`,
|
|
6687
|
-
{ environmentId }
|
|
6688
|
-
);
|
|
6689
|
-
continue;
|
|
6690
|
-
}
|
|
6691
|
-
let arg;
|
|
6692
|
-
if (spec.configKey) {
|
|
6693
|
-
const v = bundle[spec.configKey];
|
|
6694
|
-
if (spec.configKey === "analyticsCubes") {
|
|
6695
|
-
arg = { cubes: Array.isArray(v) ? v : [] };
|
|
6696
|
-
} else if (v !== void 0) {
|
|
6697
|
-
arg = v;
|
|
6698
|
-
}
|
|
6699
|
-
}
|
|
6700
|
-
await kernel.use(arg !== void 0 ? new Ctor(arg) : new Ctor());
|
|
6701
|
-
installed.push(spec.export);
|
|
6702
|
-
if (spec.extras) {
|
|
6703
|
-
for (const ex of spec.extras) {
|
|
6704
|
-
try {
|
|
6705
|
-
const exMod = await import(
|
|
6706
|
-
/* webpackIgnore: true */
|
|
6707
|
-
ex.pkg
|
|
6708
|
-
);
|
|
6709
|
-
const ExCtor = exMod[ex.export];
|
|
6710
|
-
if (ExCtor) {
|
|
6711
|
-
await kernel.use(new ExCtor());
|
|
6712
|
-
installed.push(ex.export);
|
|
6713
|
-
}
|
|
6714
|
-
} catch {
|
|
6715
|
-
}
|
|
6716
|
-
}
|
|
6717
|
-
}
|
|
6718
|
-
logger.info?.(
|
|
6719
|
-
`[CapabilityLoader] '${cap}' installed (${spec.export}${spec.extras ? " + " + spec.extras.length + " extras" : ""})`,
|
|
6720
|
-
{ environmentId }
|
|
6721
|
-
);
|
|
6722
|
-
} catch (err) {
|
|
6723
|
-
const msg = err?.message ?? String(err);
|
|
6724
|
-
if (msg.includes("Cannot find module") || msg.includes("ERR_MODULE_NOT_FOUND")) {
|
|
6725
|
-
logger.warn?.(
|
|
6726
|
-
`[CapabilityLoader] '${cap}' requested but '${spec.pkg}' not installed in host \u2014 skipped`,
|
|
6727
|
-
{ environmentId }
|
|
6728
|
-
);
|
|
6729
|
-
} else {
|
|
6730
|
-
logger.error?.(
|
|
6731
|
-
`[CapabilityLoader] '${cap}' load failed: ${msg}`,
|
|
6732
|
-
{ environmentId }
|
|
6733
|
-
);
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
6736
|
-
}
|
|
6737
|
-
return installed;
|
|
6738
|
-
}
|
|
6739
|
-
|
|
6740
|
-
// src/cloud/platform-sso.ts
|
|
6741
|
-
var import_node_crypto = require("crypto");
|
|
6742
|
-
var PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
6743
|
-
function derivePlatformSsoClientId(environmentId) {
|
|
6744
|
-
return `project_${environmentId}`;
|
|
6745
|
-
}
|
|
6746
|
-
function derivePlatformSsoClientSecret(baseSecret, environmentId) {
|
|
6747
|
-
return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
|
|
6748
|
-
}
|
|
6749
|
-
function hashPlatformSsoClientSecret(plaintext) {
|
|
6750
|
-
return (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
6751
|
-
}
|
|
6752
|
-
function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
|
|
6753
|
-
let host;
|
|
6754
|
-
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
|
|
6755
|
-
host = hostname;
|
|
6756
|
-
} else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
|
|
6757
|
-
const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
6758
|
-
const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
|
|
6759
|
-
host = `http://${hostWithPort}`;
|
|
6760
|
-
} else {
|
|
6761
|
-
host = `https://${hostname}`;
|
|
6762
|
-
}
|
|
6763
|
-
const trimmed = host.replace(/\/+$/, "");
|
|
6764
|
-
const path = basePath.replace(/\/+$/, "");
|
|
6765
|
-
return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
|
|
6766
|
-
}
|
|
6767
|
-
async function seedPlatformSsoClient(opts) {
|
|
6768
|
-
const { ql, environmentId, hostname, baseSecret, logger, throwOnError } = opts;
|
|
6769
|
-
if (!baseSecret) {
|
|
6770
|
-
logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { environmentId });
|
|
6771
|
-
return;
|
|
6772
|
-
}
|
|
6773
|
-
const clientId = derivePlatformSsoClientId(environmentId);
|
|
6774
|
-
const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, environmentId);
|
|
6775
|
-
const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
|
|
6776
|
-
const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
|
|
6777
|
-
let existing = null;
|
|
6778
|
-
try {
|
|
6779
|
-
const rows = await ql.find("sys_oauth_application", {
|
|
6780
|
-
where: { client_id: clientId },
|
|
6781
|
-
limit: 1
|
|
6782
|
-
}, { context: { isSystem: true } });
|
|
6783
|
-
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6784
|
-
existing = list[0] ?? null;
|
|
6785
|
-
} catch (err) {
|
|
6786
|
-
logger?.warn?.("[platform-sso] sys_oauth_application read failed \u2014 skipping seed", {
|
|
6787
|
-
environmentId,
|
|
6788
|
-
error: err?.message
|
|
6789
|
-
});
|
|
6790
|
-
return;
|
|
6791
|
-
}
|
|
6792
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6793
|
-
if (!existing) {
|
|
6794
|
-
const redirects = desiredRedirect ? [desiredRedirect] : [];
|
|
6795
|
-
try {
|
|
6796
|
-
await ql.insert("sys_oauth_application", {
|
|
6797
|
-
id: `oauthc_${environmentId}`,
|
|
6798
|
-
name: `Project ${environmentId}`,
|
|
6799
|
-
client_id: clientId,
|
|
6800
|
-
client_secret: clientSecretStored,
|
|
6801
|
-
type: "web",
|
|
6802
|
-
redirect_uris: JSON.stringify(redirects),
|
|
6803
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6804
|
-
response_types: JSON.stringify(["code"]),
|
|
6805
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6806
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
6807
|
-
require_pkce: false,
|
|
6808
|
-
skip_consent: true,
|
|
6809
|
-
disabled: false,
|
|
6810
|
-
subject_type: "public",
|
|
6811
|
-
created_at: nowIso,
|
|
6812
|
-
updated_at: nowIso
|
|
6813
|
-
}, { context: { isSystem: true } });
|
|
6814
|
-
logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
|
|
6815
|
-
} catch (err) {
|
|
6816
|
-
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
6817
|
-
environmentId,
|
|
6818
|
-
error: err?.message
|
|
6819
|
-
});
|
|
6820
|
-
if (throwOnError) throw err;
|
|
6821
|
-
}
|
|
6822
|
-
return;
|
|
6823
|
-
}
|
|
6824
|
-
let currentRedirects = [];
|
|
6825
|
-
try {
|
|
6826
|
-
const raw = existing.redirect_uris;
|
|
6827
|
-
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
6828
|
-
if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
|
|
6829
|
-
} catch {
|
|
6830
|
-
}
|
|
6831
|
-
const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
|
|
6832
|
-
const repairPatch = {
|
|
6833
|
-
name: existing.name || `Project ${environmentId}`,
|
|
6834
|
-
client_secret: clientSecretStored,
|
|
6835
|
-
type: existing.type || "web",
|
|
6836
|
-
redirect_uris: JSON.stringify(mergedRedirects),
|
|
6837
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6838
|
-
response_types: JSON.stringify(["code"]),
|
|
6839
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6840
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
6841
|
-
require_pkce: false,
|
|
6842
|
-
skip_consent: true,
|
|
6843
|
-
disabled: false,
|
|
6844
|
-
subject_type: "public",
|
|
6845
|
-
updated_at: nowIso
|
|
6846
|
-
};
|
|
6847
|
-
try {
|
|
6848
|
-
await ql.update(
|
|
6849
|
-
"sys_oauth_application",
|
|
6850
|
-
repairPatch,
|
|
6851
|
-
{ where: { id: existing.id } },
|
|
6852
|
-
{ context: { isSystem: true } }
|
|
6853
|
-
);
|
|
6854
|
-
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
6855
|
-
environmentId,
|
|
6856
|
-
clientId,
|
|
6857
|
-
redirect_uris: mergedRedirects
|
|
6858
|
-
});
|
|
6859
|
-
} catch (err) {
|
|
6860
|
-
logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
|
|
6861
|
-
environmentId,
|
|
6862
|
-
error: err?.message
|
|
6863
|
-
});
|
|
6864
|
-
if (throwOnError) throw err;
|
|
6865
|
-
}
|
|
6866
|
-
}
|
|
6867
|
-
async function backfillPlatformSsoClients(opts) {
|
|
6868
|
-
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
6869
|
-
if (!baseSecret) {
|
|
6870
|
-
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
6871
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
6872
|
-
}
|
|
6873
|
-
let projects = [];
|
|
6874
|
-
try {
|
|
6875
|
-
const rows = await ql.find("sys_environment", {
|
|
6876
|
-
limit,
|
|
6877
|
-
fields: ["id", "hostname", "status"]
|
|
6878
|
-
}, { context: { isSystem: true } });
|
|
6879
|
-
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6880
|
-
} catch (err) {
|
|
6881
|
-
logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
|
|
6882
|
-
error: err?.message
|
|
6883
|
-
});
|
|
6884
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
|
|
6885
|
-
}
|
|
6886
|
-
let seeded = 0;
|
|
6887
|
-
let alreadyExisted = 0;
|
|
6888
|
-
const failures = [];
|
|
6889
|
-
for (const p of projects) {
|
|
6890
|
-
if (!p?.id) continue;
|
|
6891
|
-
const before = await (async () => {
|
|
6892
|
-
try {
|
|
6893
|
-
const r = await ql.find("sys_oauth_application", {
|
|
6894
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6895
|
-
limit: 1
|
|
6896
|
-
}, { context: { isSystem: true } });
|
|
6897
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6898
|
-
return list[0] ?? null;
|
|
6899
|
-
} catch {
|
|
6900
|
-
return null;
|
|
6901
|
-
}
|
|
6902
|
-
})();
|
|
6903
|
-
try {
|
|
6904
|
-
await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
6905
|
-
if (before) alreadyExisted++;
|
|
6906
|
-
else {
|
|
6907
|
-
const after = await (async () => {
|
|
6908
|
-
try {
|
|
6909
|
-
const r = await ql.find("sys_oauth_application", {
|
|
6910
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6911
|
-
limit: 1
|
|
6912
|
-
}, { context: { isSystem: true } });
|
|
6913
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6914
|
-
return list[0] ?? null;
|
|
6915
|
-
} catch (err) {
|
|
6916
|
-
return { _readErr: err?.message };
|
|
6917
|
-
}
|
|
6918
|
-
})();
|
|
6919
|
-
if (after && !after._readErr) seeded++;
|
|
6920
|
-
else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
6921
|
-
}
|
|
6922
|
-
} catch (err) {
|
|
6923
|
-
failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
|
|
6924
|
-
}
|
|
6925
|
-
}
|
|
6926
|
-
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
6927
|
-
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
6928
|
-
}
|
|
6929
|
-
|
|
6930
|
-
// src/cloud/artifact-kernel-factory.ts
|
|
6931
|
-
function deriveProjectAuthSecret(baseSecret, environmentId) {
|
|
6932
|
-
return (0, import_node_crypto2.createHmac)("sha256", baseSecret).update(`project:${environmentId}`).digest("hex");
|
|
6933
|
-
}
|
|
6934
|
-
var ArtifactKernelFactory = class {
|
|
6935
|
-
constructor(config) {
|
|
6936
|
-
this.client = config.client;
|
|
6937
|
-
this.envRegistry = config.envRegistry;
|
|
6938
|
-
this.logger = config.logger ?? console;
|
|
6939
|
-
this.kernelConfig = config.kernelConfig;
|
|
6940
|
-
this.authBaseSecret = (config.authBaseSecret ?? (0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
6941
|
-
}
|
|
6942
|
-
async create(environmentId) {
|
|
6943
|
-
let cached = this.envRegistry.peekById(environmentId);
|
|
6944
|
-
if (!cached) {
|
|
6945
|
-
const driver2 = await this.envRegistry.resolveById(environmentId);
|
|
6946
|
-
if (!driver2) {
|
|
6947
|
-
throw new Error(`[ArtifactKernelFactory] Could not resolve driver for project '${environmentId}'`);
|
|
6948
|
-
}
|
|
6949
|
-
cached = this.envRegistry.peekById(environmentId);
|
|
6950
|
-
if (!cached) {
|
|
6951
|
-
throw new Error(`[ArtifactKernelFactory] envRegistry returned a driver but no cached entry for '${environmentId}'`);
|
|
6952
|
-
}
|
|
6953
|
-
}
|
|
6954
|
-
const driver = cached.driver;
|
|
6955
|
-
const project = cached.project;
|
|
6956
|
-
const artifact = await this.client.fetchArtifact(environmentId);
|
|
6957
|
-
if (!artifact) {
|
|
6958
|
-
throw new Error(`[ArtifactKernelFactory] Artifact not available for project '${environmentId}'`);
|
|
6959
|
-
}
|
|
6960
|
-
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
6961
|
-
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
6962
|
-
const kernel = new import_core3.ObjectKernel(this.kernelConfig);
|
|
6963
|
-
await kernel.use(new DriverPlugin(driver, { datasourceName: "cloud" }));
|
|
6964
|
-
await kernel.use(new ObjectQLPlugin({ environmentId, skipSchemaSync: false }));
|
|
6965
|
-
await kernel.use(new MetadataPlugin({
|
|
6966
|
-
watch: false,
|
|
6967
|
-
environmentId,
|
|
6968
|
-
organizationId: project.organization_id,
|
|
6969
|
-
// ADR-0005: customization overlays (user-created views, dashboards,
|
|
6970
|
-
// edited objects, ...) are persisted by
|
|
6971
|
-
// ObjectStackProtocolImplementation.saveMetaItem on whichever
|
|
6972
|
-
// engine the protocol is attached to. For per-project kernels that
|
|
6973
|
-
// means the project's own DB, so the sys_metadata + history tables
|
|
6974
|
-
// MUST be provisioned here. The previous `false` setting caused
|
|
6975
|
-
// "no such table: sys_metadata" errors on any PUT /api/v1/meta/*
|
|
6976
|
-
// call (e.g. Studio "Create View") against a project deployment.
|
|
6977
|
-
registerSystemObjects: true
|
|
6978
|
-
}));
|
|
6979
|
-
if (this.authBaseSecret) {
|
|
6980
|
-
try {
|
|
6981
|
-
const { AuthPlugin } = await import("@objectstack/plugin-auth");
|
|
6982
|
-
const projectSecret = deriveProjectAuthSecret(this.authBaseSecret, environmentId);
|
|
6983
|
-
const baseUrl = project.hostname ? project.hostname.startsWith("http") ? project.hostname : /(\.|^)localhost(:\d+)?$/i.test(project.hostname) ? (() => {
|
|
6984
|
-
const runtimePort = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
6985
|
-
const hasPort = /:\d+$/.test(project.hostname);
|
|
6986
|
-
const hostWithPort = hasPort || !runtimePort ? project.hostname : `${project.hostname}:${runtimePort}`;
|
|
6987
|
-
return `http://${hostWithPort}`;
|
|
6988
|
-
})() : `https://${project.hostname}` : void 0;
|
|
6989
|
-
const trustedOriginsList = [];
|
|
6990
|
-
if (baseUrl) trustedOriginsList.push(baseUrl);
|
|
6991
|
-
const platformOrigins = (process.env.OS_TRUSTED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6992
|
-
for (const o of platformOrigins) {
|
|
6993
|
-
if (!trustedOriginsList.includes(o)) trustedOriginsList.push(o);
|
|
6994
|
-
}
|
|
6995
|
-
const rootDomain = (process.env.OS_ROOT_DOMAIN ?? "").trim().replace(/^https?:\/\//, "");
|
|
6996
|
-
if (rootDomain) {
|
|
6997
|
-
const wildcard = `https://*.${rootDomain}`;
|
|
6998
|
-
if (!trustedOriginsList.includes(wildcard)) trustedOriginsList.push(wildcard);
|
|
6999
|
-
}
|
|
7000
|
-
if (project.hostname) {
|
|
7001
|
-
const bareHost = project.hostname.replace(/^https?:\/\//, "");
|
|
7002
|
-
if (bareHost.endsWith(".localhost") || bareHost === "localhost") {
|
|
7003
|
-
trustedOriginsList.push(`http://${bareHost}`);
|
|
7004
|
-
trustedOriginsList.push(`http://${bareHost}:*`);
|
|
7005
|
-
trustedOriginsList.push(`https://${bareHost}:*`);
|
|
7006
|
-
}
|
|
7007
|
-
}
|
|
7008
|
-
const platformSsoEnabled = String(
|
|
7009
|
-
process.env.OS_PLATFORM_SSO ?? "true"
|
|
7010
|
-
).toLowerCase() !== "false";
|
|
7011
|
-
const cloudBaseUrl = (process.env.OS_CLOUD_URL ?? "").trim().replace(/\/+$/, "");
|
|
7012
|
-
const oidcProviders = platformSsoEnabled && cloudBaseUrl && /^https?:\/\//.test(cloudBaseUrl) ? [{
|
|
7013
|
-
providerId: PLATFORM_SSO_PROVIDER_ID,
|
|
7014
|
-
name: "ObjectStack",
|
|
7015
|
-
discoveryUrl: `${cloudBaseUrl}/.well-known/openid-configuration`,
|
|
7016
|
-
clientId: derivePlatformSsoClientId(environmentId),
|
|
7017
|
-
clientSecret: derivePlatformSsoClientSecret(this.authBaseSecret, environmentId),
|
|
7018
|
-
scopes: ["openid", "email", "profile"]
|
|
7019
|
-
}] : void 0;
|
|
7020
|
-
await kernel.use(new AuthPlugin({
|
|
7021
|
-
secret: projectSecret,
|
|
7022
|
-
baseUrl,
|
|
7023
|
-
// Project kernel has no http-server (host owns it). The
|
|
7024
|
-
// dispatcher's handleAuth path resolves `auth` via
|
|
7025
|
-
// getService and invokes the handler directly — route
|
|
7026
|
-
// registration is unnecessary and would warn.
|
|
7027
|
-
registerRoutes: false,
|
|
7028
|
-
// Identity tables live in the project's own DB — keep
|
|
7029
|
-
// sys_user/sys_session local to this kernel.
|
|
7030
|
-
manifestDatasource: "default",
|
|
7031
|
-
// Cookie scope: default to the project's own host. We
|
|
7032
|
-
// intentionally do NOT pass crossSubDomainCookies here
|
|
7033
|
-
// so cookies stay isolated per project subdomain.
|
|
7034
|
-
trustedOrigins: trustedOriginsList.length ? trustedOriginsList : void 0,
|
|
7035
|
-
...oidcProviders ? { oidcProviders } : {}
|
|
7036
|
-
}));
|
|
7037
|
-
if (oidcProviders) {
|
|
7038
|
-
this.logger.info?.("[ArtifactKernelFactory] platform SSO wired", {
|
|
7039
|
-
environmentId,
|
|
7040
|
-
cloudBaseUrl
|
|
7041
|
-
});
|
|
7042
|
-
}
|
|
7043
|
-
} catch (err) {
|
|
7044
|
-
this.logger.warn?.("[ArtifactKernelFactory] AuthPlugin not registered", {
|
|
7045
|
-
environmentId,
|
|
7046
|
-
error: err?.message
|
|
7047
|
-
});
|
|
7048
|
-
}
|
|
7049
|
-
} else {
|
|
7050
|
-
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
|
|
7051
|
-
}
|
|
7052
|
-
try {
|
|
7053
|
-
const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
7054
|
-
if (multiTenant) {
|
|
7055
|
-
try {
|
|
7056
|
-
const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
|
|
7057
|
-
await kernel.use(new OrgScopingPlugin());
|
|
7058
|
-
} catch (err) {
|
|
7059
|
-
this.logger.warn?.("[ArtifactKernelFactory] OrgScopingPlugin not registered (multi-tenant disabled)", {
|
|
7060
|
-
environmentId,
|
|
7061
|
-
error: err?.message
|
|
7062
|
-
});
|
|
7063
|
-
}
|
|
7064
|
-
}
|
|
7065
|
-
const { SecurityPlugin } = await import("@objectstack/plugin-security");
|
|
7066
|
-
await kernel.use(new SecurityPlugin());
|
|
7067
|
-
} catch (err) {
|
|
7068
|
-
this.logger.warn?.("[ArtifactKernelFactory] SecurityPlugin not registered", {
|
|
7069
|
-
environmentId,
|
|
7070
|
-
error: err?.message
|
|
7071
|
-
});
|
|
7072
|
-
}
|
|
7073
|
-
const projectName = project.hostname ?? environmentId;
|
|
7074
|
-
const artifactAny = artifact;
|
|
7075
|
-
const topLevelManifest = artifactAny?.manifest && typeof artifactAny.manifest === "object" ? artifactAny.manifest : null;
|
|
7076
|
-
const topLevelFunctions = Array.isArray(artifactAny?.functions) ? artifactAny.functions : [];
|
|
7077
|
-
const bundle = {
|
|
7078
|
-
...artifact.metadata ?? {},
|
|
7079
|
-
...topLevelManifest ? { manifest: topLevelManifest } : {},
|
|
7080
|
-
functions: topLevelFunctions
|
|
7081
|
-
};
|
|
7082
|
-
const sys = bundle.manifest ?? bundle;
|
|
7083
|
-
const packageId = sys?.packageId ?? sys?.package_id ?? bundle?.packageId;
|
|
7084
|
-
const i18nCfg = bundle?.i18n ?? sys?.i18n ?? {};
|
|
7085
|
-
const trArr = Array.isArray(bundle?.translations) ? bundle.translations : Array.isArray(sys?.translations) ? sys.translations : [];
|
|
7086
|
-
try {
|
|
7087
|
-
const { I18nServicePlugin } = await import("@objectstack/service-i18n");
|
|
7088
|
-
await kernel.use(new I18nServicePlugin({
|
|
7089
|
-
defaultLocale: i18nCfg.defaultLocale,
|
|
7090
|
-
fallbackLocale: i18nCfg.fallbackLocale ?? i18nCfg.defaultLocale ?? "en",
|
|
7091
|
-
// Routes are dispatched by HttpDispatcher.handleI18n via
|
|
7092
|
-
// kernel.getService('i18n'); the host worker owns the
|
|
7093
|
-
// HTTP server. Skip self-registration to avoid warnings.
|
|
7094
|
-
registerRoutes: false
|
|
7095
|
-
}));
|
|
7096
|
-
console.warn(
|
|
7097
|
-
`[ArtifactKernelFactory] I18nServicePlugin registered (project=${environmentId}, translations=${trArr.length}, defaultLocale=${i18nCfg.defaultLocale ?? "en"})`
|
|
7098
|
-
);
|
|
7099
|
-
} catch (err) {
|
|
7100
|
-
this.logger.warn?.("[ArtifactKernelFactory] I18nServicePlugin not registered", {
|
|
7101
|
-
environmentId,
|
|
7102
|
-
error: err?.message
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7105
|
-
const requiresRaw = (Array.isArray(bundle?.requires) ? bundle.requires : null) ?? (Array.isArray(sys?.requires) ? sys.requires : null) ?? [];
|
|
7106
|
-
const requires = requiresRaw.filter((x) => typeof x === "string" && x.length > 0);
|
|
7107
|
-
if (requires.length > 0) {
|
|
7108
|
-
const installed = await loadCapabilities({
|
|
7109
|
-
kernel,
|
|
7110
|
-
requires,
|
|
7111
|
-
bundle: { ...bundle ?? {}, ...sys ?? {} },
|
|
7112
|
-
logger: this.logger,
|
|
7113
|
-
environmentId
|
|
7114
|
-
});
|
|
7115
|
-
this.logger.info?.("[ArtifactKernelFactory] capabilities loaded", {
|
|
7116
|
-
environmentId,
|
|
7117
|
-
requires,
|
|
7118
|
-
installed
|
|
7119
|
-
});
|
|
7120
|
-
}
|
|
7121
|
-
await kernel.use(new AppPlugin(bundle, {
|
|
7122
|
-
environmentId,
|
|
7123
|
-
organizationId: project.organization_id ?? "",
|
|
7124
|
-
projectName,
|
|
7125
|
-
packageId,
|
|
7126
|
-
source: packageId ? "package" : "user"
|
|
7127
|
-
}));
|
|
7128
|
-
await kernel.bootstrap();
|
|
7129
|
-
try {
|
|
7130
|
-
const projMeta = typeof project?.metadata === "string" ? JSON.parse(project.metadata) : project?.metadata ?? {};
|
|
7131
|
-
const ownerSeed = projMeta?.ownerSeed;
|
|
7132
|
-
const orgSeed = projMeta?.orgSeed;
|
|
7133
|
-
if (orgSeed?.id && orgSeed?.name) {
|
|
7134
|
-
try {
|
|
7135
|
-
const { seedProjectOrganization: seedProjectOrganization2 } = await Promise.resolve().then(() => (init_environment_org_seed(), environment_org_seed_exports));
|
|
7136
|
-
await seedProjectOrganization2(kernel, orgSeed, this.logger);
|
|
7137
|
-
} catch (e) {
|
|
7138
|
-
this.logger.warn?.("[ArtifactKernelFactory] orgSeed threw", {
|
|
7139
|
-
environmentId,
|
|
7140
|
-
error: e?.message
|
|
7141
|
-
});
|
|
7142
|
-
}
|
|
7143
|
-
}
|
|
7144
|
-
if (ownerSeed?.userId && ownerSeed?.email) {
|
|
7145
|
-
try {
|
|
7146
|
-
const { seedProjectOwner: seedProjectOwner2 } = await Promise.resolve().then(() => (init_environment_owner_seed(), environment_owner_seed_exports));
|
|
7147
|
-
await seedProjectOwner2(kernel, ownerSeed, this.logger);
|
|
7148
|
-
} catch (e) {
|
|
7149
|
-
this.logger.warn?.("[ArtifactKernelFactory] ownerSeed threw", {
|
|
7150
|
-
environmentId,
|
|
7151
|
-
error: e?.message
|
|
7152
|
-
});
|
|
7153
|
-
}
|
|
7154
|
-
if (orgSeed?.id) {
|
|
7155
|
-
try {
|
|
7156
|
-
const { seedProjectMember: seedProjectMember2 } = await Promise.resolve().then(() => (init_environment_org_seed(), environment_org_seed_exports));
|
|
7157
|
-
await seedProjectMember2(
|
|
7158
|
-
kernel,
|
|
7159
|
-
{ userId: ownerSeed.userId, organizationId: orgSeed.id, role: "owner" },
|
|
7160
|
-
this.logger
|
|
7161
|
-
);
|
|
7162
|
-
} catch (e) {
|
|
7163
|
-
this.logger.warn?.("[ArtifactKernelFactory] memberSeed threw", {
|
|
7164
|
-
environmentId,
|
|
7165
|
-
error: e?.message
|
|
7166
|
-
});
|
|
7167
|
-
}
|
|
7168
|
-
}
|
|
7169
|
-
}
|
|
7170
|
-
} catch (err) {
|
|
7171
|
-
this.logger.warn?.("[ArtifactKernelFactory] owner/org seed skipped", {
|
|
7172
|
-
environmentId,
|
|
7173
|
-
error: err?.message
|
|
7174
|
-
});
|
|
7175
|
-
}
|
|
7176
|
-
try {
|
|
7177
|
-
const datasetsNow = (() => {
|
|
7178
|
-
try {
|
|
7179
|
-
return kernel.getService?.("seed-datasets");
|
|
7180
|
-
} catch {
|
|
7181
|
-
return void 0;
|
|
7182
|
-
}
|
|
7183
|
-
})();
|
|
7184
|
-
const replayer = (() => {
|
|
7185
|
-
try {
|
|
7186
|
-
return kernel.getService?.("seed-replayer");
|
|
7187
|
-
} catch {
|
|
7188
|
-
return void 0;
|
|
7189
|
-
}
|
|
7190
|
-
})();
|
|
7191
|
-
if (Array.isArray(datasetsNow) && datasetsNow.length > 0 && typeof replayer === "function") {
|
|
7192
|
-
const projMetaRaw = project?.metadata;
|
|
7193
|
-
const projMeta = typeof projMetaRaw === "string" ? (() => {
|
|
7194
|
-
try {
|
|
7195
|
-
return JSON.parse(projMetaRaw);
|
|
7196
|
-
} catch {
|
|
7197
|
-
return {};
|
|
7198
|
-
}
|
|
7199
|
-
})() : projMetaRaw ?? {};
|
|
7200
|
-
let primaryOrgId = projMeta?.orgSeed?.id;
|
|
7201
|
-
if (!primaryOrgId) {
|
|
7202
|
-
try {
|
|
7203
|
-
const ql = kernel.getService?.("objectql");
|
|
7204
|
-
if (ql?.find) {
|
|
7205
|
-
const rows = await ql.find("sys_organization", { limit: 5, orderBy: [{ field: "created_at", direction: "asc" }] });
|
|
7206
|
-
const list = Array.isArray(rows) ? rows : rows?.value ?? rows?.records ?? [];
|
|
7207
|
-
if (Array.isArray(list) && list.length > 0 && list[0]?.id) {
|
|
7208
|
-
primaryOrgId = String(list[0].id);
|
|
7209
|
-
}
|
|
7210
|
-
}
|
|
7211
|
-
} catch {
|
|
7212
|
-
}
|
|
7213
|
-
}
|
|
7214
|
-
if (primaryOrgId) {
|
|
7215
|
-
try {
|
|
7216
|
-
const summary = await replayer(primaryOrgId);
|
|
7217
|
-
const inserted = summary?.inserted ?? 0;
|
|
7218
|
-
const updated = summary?.updated ?? 0;
|
|
7219
|
-
const errs = summary?.errors?.length ?? 0;
|
|
7220
|
-
if (inserted > 0 || updated > 0 || errs > 0) {
|
|
7221
|
-
this.logger.info?.("[ArtifactKernelFactory] post-bootstrap seed replay", {
|
|
7222
|
-
environmentId,
|
|
7223
|
-
organizationId: primaryOrgId,
|
|
7224
|
-
datasets: datasetsNow.length,
|
|
7225
|
-
inserted,
|
|
7226
|
-
updated,
|
|
7227
|
-
errors: errs
|
|
7228
|
-
});
|
|
7229
|
-
}
|
|
7230
|
-
} catch (e) {
|
|
7231
|
-
this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed replay failed", {
|
|
7232
|
-
environmentId,
|
|
7233
|
-
organizationId: primaryOrgId,
|
|
7234
|
-
error: e?.message
|
|
7235
|
-
});
|
|
7236
|
-
}
|
|
7237
|
-
}
|
|
7238
|
-
}
|
|
7239
|
-
} catch (err) {
|
|
7240
|
-
this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed step threw", {
|
|
7241
|
-
environmentId,
|
|
7242
|
-
error: err?.message
|
|
7243
|
-
});
|
|
7244
|
-
}
|
|
7245
|
-
let i18nSvc = null;
|
|
7246
|
-
try {
|
|
7247
|
-
i18nSvc = kernel.getService?.("i18n");
|
|
7248
|
-
} catch {
|
|
7249
|
-
i18nSvc = null;
|
|
7250
|
-
}
|
|
7251
|
-
try {
|
|
7252
|
-
if (i18nSvc && typeof i18nSvc.loadTranslations === "function") {
|
|
7253
|
-
if (i18nCfg.defaultLocale && typeof i18nSvc.setDefaultLocale === "function") {
|
|
7254
|
-
i18nSvc.setDefaultLocale(i18nCfg.defaultLocale);
|
|
7255
|
-
}
|
|
7256
|
-
let loaded = 0;
|
|
7257
|
-
for (const tbundle of trArr) {
|
|
7258
|
-
if (!tbundle || typeof tbundle !== "object") continue;
|
|
7259
|
-
for (const [locale, data] of Object.entries(tbundle)) {
|
|
7260
|
-
if (data && typeof data === "object") {
|
|
7261
|
-
try {
|
|
7262
|
-
i18nSvc.loadTranslations(locale, data);
|
|
7263
|
-
loaded++;
|
|
7264
|
-
} catch (err) {
|
|
7265
|
-
this.logger.warn?.("[ArtifactKernelFactory] i18n loadTranslations failed", {
|
|
7266
|
-
environmentId,
|
|
7267
|
-
locale,
|
|
7268
|
-
error: err?.message
|
|
7269
|
-
});
|
|
7270
|
-
}
|
|
7271
|
-
}
|
|
7272
|
-
}
|
|
7273
|
-
}
|
|
7274
|
-
if (loaded > 0) {
|
|
7275
|
-
this.logger.info?.("[ArtifactKernelFactory] i18n direct-load complete", {
|
|
7276
|
-
environmentId,
|
|
7277
|
-
locales: loaded,
|
|
7278
|
-
bundles: trArr.length
|
|
7279
|
-
});
|
|
7280
|
-
}
|
|
7281
|
-
}
|
|
7282
|
-
} catch (err) {
|
|
7283
|
-
this.logger.warn?.("[ArtifactKernelFactory] i18n direct-load failed", {
|
|
7284
|
-
environmentId,
|
|
7285
|
-
error: err?.message
|
|
7286
|
-
});
|
|
7287
|
-
}
|
|
7288
|
-
this.logger.info?.("[ArtifactKernelFactory] kernel ready", {
|
|
7289
|
-
environmentId,
|
|
7290
|
-
commitId: artifact.commitId,
|
|
7291
|
-
checksum: artifact.checksum,
|
|
7292
|
-
authEnabled: Boolean(this.authBaseSecret)
|
|
7293
|
-
});
|
|
7294
|
-
return kernel;
|
|
7295
|
-
}
|
|
7296
|
-
};
|
|
7297
|
-
|
|
7298
|
-
// src/cloud/auth-proxy-plugin.ts
|
|
7299
|
-
var import_node_crypto3 = require("crypto");
|
|
7300
|
-
var AUTH_PREFIX = "/api/v1/auth";
|
|
7301
|
-
function signSessionCookieValue(rawToken, secret) {
|
|
7302
|
-
const signature = (0, import_node_crypto3.createHmac)("sha256", secret).update(rawToken).digest("base64");
|
|
7303
|
-
return encodeURIComponent(`${rawToken}.${signature}`);
|
|
7304
|
-
}
|
|
7305
|
-
function buildSetCookieHeader(name, encodedValue, attrs, maxAgeSec) {
|
|
7306
|
-
const parts = [`${name}=${encodedValue}`];
|
|
7307
|
-
const a = attrs ?? {};
|
|
7308
|
-
if (a.path) parts.push(`Path=${a.path}`);
|
|
7309
|
-
else parts.push("Path=/");
|
|
7310
|
-
if (Number.isFinite(maxAgeSec) && maxAgeSec > 0) parts.push(`Max-Age=${Math.floor(maxAgeSec)}`);
|
|
7311
|
-
if (a.domain) parts.push(`Domain=${a.domain}`);
|
|
7312
|
-
if (a.sameSite) {
|
|
7313
|
-
const ss = String(a.sameSite);
|
|
7314
|
-
parts.push(`SameSite=${ss.charAt(0).toUpperCase() + ss.slice(1)}`);
|
|
7315
|
-
} else {
|
|
7316
|
-
parts.push("SameSite=Lax");
|
|
7317
|
-
}
|
|
7318
|
-
if (a.secure) parts.push("Secure");
|
|
7319
|
-
if (a.httpOnly !== false) parts.push("HttpOnly");
|
|
7320
|
-
if (a.partitioned) parts.push("Partitioned");
|
|
7321
|
-
return parts.join("; ");
|
|
7322
|
-
}
|
|
7323
|
-
function pickHandler(svc) {
|
|
7324
|
-
if (!svc) return void 0;
|
|
7325
|
-
if (typeof svc.handleRequest === "function") return svc.handleRequest.bind(svc);
|
|
7326
|
-
if (typeof svc.handler === "function") return svc.handler.bind(svc);
|
|
7327
|
-
if (svc.api && typeof svc.api.handler === "function") return svc.api.handler.bind(svc.api);
|
|
7328
|
-
if (svc.auth && typeof svc.auth.handler === "function") return svc.auth.handler.bind(svc.auth);
|
|
7329
|
-
return void 0;
|
|
7330
|
-
}
|
|
7331
|
-
async function resolveAuthHandler(svc) {
|
|
7332
|
-
const direct = pickHandler(svc);
|
|
7333
|
-
if (direct) return direct;
|
|
7334
|
-
if (typeof svc?.getApi === "function") {
|
|
7335
|
-
try {
|
|
7336
|
-
const api = await svc.getApi();
|
|
7337
|
-
return pickHandler(api) ?? pickHandler({ api });
|
|
7338
|
-
} catch {
|
|
7339
|
-
return void 0;
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
return void 0;
|
|
7343
|
-
}
|
|
7344
|
-
var AuthProxyPlugin = class {
|
|
7345
|
-
constructor() {
|
|
7346
|
-
this.name = "com.objectstack.runtime.auth-proxy";
|
|
7347
|
-
this.version = "1.0.0";
|
|
7348
|
-
this.init = async (_ctx) => {
|
|
7349
|
-
};
|
|
7350
|
-
this.start = async (ctx) => {
|
|
7351
|
-
ctx.hook("kernel:ready", async () => {
|
|
7352
|
-
let httpServer;
|
|
7353
|
-
try {
|
|
7354
|
-
httpServer = ctx.getService("http-server");
|
|
7355
|
-
} catch {
|
|
7356
|
-
ctx.logger?.warn?.("[AuthProxyPlugin] http-server not available \u2014 auth routes not mounted");
|
|
7357
|
-
return;
|
|
7358
|
-
}
|
|
7359
|
-
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
7360
|
-
ctx.logger?.warn?.("[AuthProxyPlugin] http-server missing getRawApp() \u2014 auth routes not mounted");
|
|
7361
|
-
return;
|
|
7362
|
-
}
|
|
7363
|
-
const rawApp = httpServer.getRawApp();
|
|
7364
|
-
const kernelManager = ctx.getService("kernel-manager");
|
|
7365
|
-
const envRegistry = ctx.getService("env-registry");
|
|
7366
|
-
const handler = async (c) => {
|
|
7367
|
-
try {
|
|
7368
|
-
const url = new URL(c.req.url);
|
|
7369
|
-
const host = url.hostname;
|
|
7370
|
-
let environmentId;
|
|
7371
|
-
try {
|
|
7372
|
-
const env = await envRegistry.resolveByHostname(host);
|
|
7373
|
-
environmentId = env?.environmentId;
|
|
7374
|
-
} catch {
|
|
7375
|
-
}
|
|
7376
|
-
if (!environmentId) {
|
|
7377
|
-
return c.json({ error: "project_not_found", host }, 404);
|
|
7378
|
-
}
|
|
7379
|
-
const projectKernel = await kernelManager.getOrCreate(environmentId);
|
|
7380
|
-
let authSvc;
|
|
7381
|
-
try {
|
|
7382
|
-
authSvc = await projectKernel.getServiceAsync?.("auth");
|
|
7383
|
-
} catch {
|
|
7384
|
-
authSvc = void 0;
|
|
7385
|
-
}
|
|
7386
|
-
if (!authSvc) {
|
|
7387
|
-
try {
|
|
7388
|
-
authSvc = projectKernel.getService?.("auth");
|
|
7389
|
-
} catch {
|
|
7390
|
-
}
|
|
7391
|
-
}
|
|
7392
|
-
const subPath = url.pathname.startsWith(AUTH_PREFIX + "/") ? url.pathname.substring(AUTH_PREFIX.length + 1) : "";
|
|
7393
|
-
if (c.req.method === "GET" && (subPath === "config" || subPath === "bootstrap-status")) {
|
|
7394
|
-
if (subPath === "config") {
|
|
7395
|
-
try {
|
|
7396
|
-
const config = typeof authSvc?.getPublicConfig === "function" ? authSvc.getPublicConfig() : null;
|
|
7397
|
-
if (config) {
|
|
7398
|
-
return c.json({ success: true, data: config });
|
|
7399
|
-
}
|
|
7400
|
-
return c.json({ success: false, error: { code: "auth_config_unavailable", message: "AuthManager has no getPublicConfig()" } }, 503);
|
|
7401
|
-
} catch (e) {
|
|
7402
|
-
return c.json({ success: false, error: { code: "auth_config_error", message: String(e?.message ?? e) } }, 500);
|
|
7403
|
-
}
|
|
7404
|
-
}
|
|
7405
|
-
try {
|
|
7406
|
-
const dataEngine = typeof authSvc?.getDataEngine === "function" ? authSvc.getDataEngine() : null;
|
|
7407
|
-
if (!dataEngine || typeof dataEngine.count !== "function") {
|
|
7408
|
-
return c.json({ hasOwner: true });
|
|
7409
|
-
}
|
|
7410
|
-
const count = await dataEngine.count("sys_user", {});
|
|
7411
|
-
return c.json({ hasOwner: (count ?? 0) > 0 });
|
|
7412
|
-
} catch {
|
|
7413
|
-
return c.json({ hasOwner: true });
|
|
7414
|
-
}
|
|
7415
|
-
}
|
|
7416
|
-
if (c.req.method === "POST" && subPath === "sso-handoff-issue") {
|
|
7417
|
-
try {
|
|
7418
|
-
const expected = (process.env.OS_CLOUD_API_KEY ?? "").trim();
|
|
7419
|
-
if (!expected) {
|
|
7420
|
-
return c.json({ error: "sso_handoff_disabled", reason: "OS_CLOUD_API_KEY unset on env runtime" }, 503);
|
|
7421
|
-
}
|
|
7422
|
-
const authz = c.req.header("authorization") ?? "";
|
|
7423
|
-
const provided = authz.toLowerCase().startsWith("bearer ") ? authz.slice(7).trim() : "";
|
|
7424
|
-
if (!provided || provided !== expected) {
|
|
7425
|
-
return c.json({ error: "unauthorized" }, 401);
|
|
7426
|
-
}
|
|
7427
|
-
if (typeof authSvc?.getAuthContext !== "function") {
|
|
7428
|
-
return c.json({ error: "auth_service_unavailable" }, 503);
|
|
7429
|
-
}
|
|
7430
|
-
const handoffAuthCtx = await authSvc.getAuthContext();
|
|
7431
|
-
const internal = handoffAuthCtx?.internalAdapter;
|
|
7432
|
-
if (!internal?.createVerificationValue) {
|
|
7433
|
-
return c.json({ error: "verification_api_unavailable" }, 503);
|
|
7434
|
-
}
|
|
7435
|
-
let body = {};
|
|
7436
|
-
try {
|
|
7437
|
-
body = await c.req.json();
|
|
7438
|
-
} catch {
|
|
7439
|
-
body = {};
|
|
7440
|
-
}
|
|
7441
|
-
const email = String(body?.email ?? "").toLowerCase().trim();
|
|
7442
|
-
if (!email) return c.json({ error: "email_required" }, 400);
|
|
7443
|
-
const name = body?.name == null ? null : String(body.name);
|
|
7444
|
-
const by = body?.by == null ? "service" : String(body.by);
|
|
7445
|
-
const envIdInBody = body?.envId == null ? null : String(body.envId);
|
|
7446
|
-
const handoff = (0, import_node_crypto3.randomUUID)().replace(/-/g, "") + (0, import_node_crypto3.randomUUID)().replace(/-/g, "");
|
|
7447
|
-
const ttlSec = 60;
|
|
7448
|
-
const expiresAt = new Date(Date.now() + ttlSec * 1e3);
|
|
7449
|
-
await internal.createVerificationValue({
|
|
7450
|
-
identifier: `sso-handoff:${handoff}`,
|
|
7451
|
-
value: JSON.stringify({ email, name, by, envId: envIdInBody ?? environmentId }),
|
|
7452
|
-
expiresAt
|
|
7453
|
-
});
|
|
7454
|
-
return c.json({
|
|
7455
|
-
token: handoff,
|
|
7456
|
-
expiresAt: expiresAt.toISOString(),
|
|
7457
|
-
ttlSec
|
|
7458
|
-
});
|
|
7459
|
-
} catch (err) {
|
|
7460
|
-
ctx.logger?.error?.("[AuthProxyPlugin] sso-handoff-issue failed", err instanceof Error ? err : new Error(String(err)));
|
|
7461
|
-
return c.json({ error: "sso_handoff_issue_failed", message: String(err?.message ?? err) }, 500);
|
|
7462
|
-
}
|
|
7463
|
-
}
|
|
7464
|
-
if (c.req.method === "GET" && subPath === "sso-exchange") {
|
|
7465
|
-
try {
|
|
7466
|
-
const token = (url.searchParams.get("token") ?? "").trim();
|
|
7467
|
-
const nextRaw = url.searchParams.get("next") ?? "/";
|
|
7468
|
-
const next = nextRaw.startsWith("/") ? nextRaw : "/";
|
|
7469
|
-
if (!token) return c.text("missing token", 400);
|
|
7470
|
-
if (typeof authSvc?.getAuthContext !== "function") {
|
|
7471
|
-
return c.text("auth service unavailable", 503);
|
|
7472
|
-
}
|
|
7473
|
-
const authCtx = await authSvc.getAuthContext();
|
|
7474
|
-
const internal = authCtx?.internalAdapter;
|
|
7475
|
-
if (!internal?.consumeVerificationValue) {
|
|
7476
|
-
return c.text("verification API unavailable", 503);
|
|
7477
|
-
}
|
|
7478
|
-
const consumed = await internal.consumeVerificationValue(`sso-handoff:${token}`);
|
|
7479
|
-
if (!consumed) return c.text("invalid or expired token", 401);
|
|
7480
|
-
const expiresAt = consumed?.expiresAt ? new Date(consumed.expiresAt).getTime() : 0;
|
|
7481
|
-
if (!expiresAt || expiresAt < Date.now()) return c.text("expired token", 401);
|
|
7482
|
-
let payload = {};
|
|
7483
|
-
try {
|
|
7484
|
-
payload = JSON.parse(String(consumed.value));
|
|
7485
|
-
} catch {
|
|
7486
|
-
payload = { email: String(consumed.value) };
|
|
7487
|
-
}
|
|
7488
|
-
const email = String(payload.email ?? "").toLowerCase().trim();
|
|
7489
|
-
if (!email) return c.text("handoff missing email", 400);
|
|
7490
|
-
const found = await internal.findUserByEmail(email, { includeAccounts: true });
|
|
7491
|
-
let userId = found?.user?.id;
|
|
7492
|
-
let hasCredentialAccount = (found?.accounts ?? []).some((a) => a.providerId === "credential" && a.password);
|
|
7493
|
-
if (!userId) {
|
|
7494
|
-
const created = await internal.createUser({
|
|
7495
|
-
email,
|
|
7496
|
-
name: payload.name ?? email,
|
|
7497
|
-
emailVerified: true
|
|
7498
|
-
});
|
|
7499
|
-
userId = created?.id;
|
|
7500
|
-
hasCredentialAccount = false;
|
|
7501
|
-
}
|
|
7502
|
-
if (!userId) return c.text("failed to provision user", 500);
|
|
7503
|
-
const session = await internal.createSession(userId, false);
|
|
7504
|
-
const rawToken = session?.token;
|
|
7505
|
-
const sessionExpiresAt = session?.expiresAt ? new Date(session.expiresAt) : new Date(Date.now() + 7 * 24 * 3600 * 1e3);
|
|
7506
|
-
if (!rawToken) return c.text("failed to mint session", 500);
|
|
7507
|
-
const secret = authCtx?.secret ?? "";
|
|
7508
|
-
if (!secret) return c.text("auth secret unavailable", 503);
|
|
7509
|
-
const cookieName = authCtx?.authCookies?.sessionToken?.name ?? "better-auth.session_token";
|
|
7510
|
-
const cookieAttrs = authCtx?.authCookies?.sessionToken?.attributes ?? {};
|
|
7511
|
-
const encoded = signSessionCookieValue(rawToken, secret);
|
|
7512
|
-
const maxAgeSec = Math.max(60, Math.floor((sessionExpiresAt.getTime() - Date.now()) / 1e3));
|
|
7513
|
-
const setCookie = buildSetCookieHeader(cookieName, encoded, cookieAttrs, maxAgeSec);
|
|
7514
|
-
const finalNext = hasCredentialAccount ? next : `/_console/system/profile?recovery_needed=true&next=${encodeURIComponent(next)}`;
|
|
7515
|
-
const headers = new Headers();
|
|
7516
|
-
headers.set("Set-Cookie", setCookie);
|
|
7517
|
-
headers.set("Location", finalNext);
|
|
7518
|
-
headers.set("Cache-Control", "no-store");
|
|
7519
|
-
return new Response(null, { status: 302, headers });
|
|
7520
|
-
} catch (err) {
|
|
7521
|
-
ctx.logger?.error?.("[AuthProxyPlugin] sso-exchange failed", err instanceof Error ? err : new Error(String(err)));
|
|
7522
|
-
return c.text(`sso-exchange failed: ${err?.message ?? String(err)}`, 500);
|
|
7523
|
-
}
|
|
7524
|
-
}
|
|
7525
|
-
if (c.req.method === "POST" && subPath === "set-initial-password") {
|
|
7526
|
-
try {
|
|
7527
|
-
let body = {};
|
|
7528
|
-
try {
|
|
7529
|
-
body = await c.req.json();
|
|
7530
|
-
} catch {
|
|
7531
|
-
body = {};
|
|
7532
|
-
}
|
|
7533
|
-
const newPassword = body?.newPassword;
|
|
7534
|
-
if (typeof newPassword !== "string" || newPassword.length === 0) {
|
|
7535
|
-
return c.json({ success: false, error: { code: "invalid_request", message: "newPassword is required" } }, 400);
|
|
7536
|
-
}
|
|
7537
|
-
if (typeof authSvc?.getAuthContext !== "function") {
|
|
7538
|
-
return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
|
|
7539
|
-
}
|
|
7540
|
-
let userId;
|
|
7541
|
-
try {
|
|
7542
|
-
const api = typeof authSvc.getApi === "function" ? await authSvc.getApi() : null;
|
|
7543
|
-
const session = await api?.getSession?.({ headers: c.req.raw.headers });
|
|
7544
|
-
userId = session?.user?.id ? String(session.user.id) : void 0;
|
|
7545
|
-
} catch {
|
|
7546
|
-
}
|
|
7547
|
-
if (!userId) {
|
|
7548
|
-
return c.json({ success: false, error: { code: "unauthorized", message: "Sign in first" } }, 401);
|
|
7549
|
-
}
|
|
7550
|
-
const setPwCtx = await authSvc.getAuthContext();
|
|
7551
|
-
if (!setPwCtx?.internalAdapter || !setPwCtx?.password) {
|
|
7552
|
-
return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
|
|
7553
|
-
}
|
|
7554
|
-
const minLen = setPwCtx.password?.config?.minPasswordLength ?? 8;
|
|
7555
|
-
const maxLen = setPwCtx.password?.config?.maxPasswordLength ?? 128;
|
|
7556
|
-
if (newPassword.length < minLen) {
|
|
7557
|
-
return c.json({ success: false, error: { code: "password_too_short", message: `Password must be at least ${minLen} characters` } }, 400);
|
|
7558
|
-
}
|
|
7559
|
-
if (newPassword.length > maxLen) {
|
|
7560
|
-
return c.json({ success: false, error: { code: "password_too_long", message: `Password must be at most ${maxLen} characters` } }, 400);
|
|
7561
|
-
}
|
|
7562
|
-
const accounts = await setPwCtx.internalAdapter.findAccounts(userId);
|
|
7563
|
-
const existingCredential = accounts?.find?.((a) => a.providerId === "credential" && a.password);
|
|
7564
|
-
if (existingCredential) {
|
|
7565
|
-
return c.json({ success: false, error: { code: "credential_account_exists", message: "A local password is already set for this account. Use change-password instead." } }, 409);
|
|
7566
|
-
}
|
|
7567
|
-
const passwordHash = await setPwCtx.password.hash(newPassword);
|
|
7568
|
-
await setPwCtx.internalAdapter.createAccount({
|
|
7569
|
-
userId,
|
|
7570
|
-
providerId: "credential",
|
|
7571
|
-
accountId: userId,
|
|
7572
|
-
password: passwordHash
|
|
7573
|
-
});
|
|
7574
|
-
return c.json({ success: true });
|
|
7575
|
-
} catch (err) {
|
|
7576
|
-
ctx.logger?.error?.("[AuthProxyPlugin] set-initial-password failed", err instanceof Error ? err : new Error(String(err)));
|
|
7577
|
-
return c.json({ success: false, error: { code: "set_password_failed", message: String(err?.message ?? err) } }, 500);
|
|
7578
|
-
}
|
|
7579
|
-
}
|
|
7580
|
-
const fn = await resolveAuthHandler(authSvc);
|
|
7581
|
-
if (!fn) {
|
|
7582
|
-
return c.json({ error: "auth_service_unavailable", environmentId }, 503);
|
|
7583
|
-
}
|
|
7584
|
-
const resp = await fn(c.req.raw);
|
|
7585
|
-
const rootDomain = process.env.OS_ROOT_DOMAIN || "";
|
|
7586
|
-
if (rootDomain) {
|
|
7587
|
-
const leakyDomain = rootDomain.startsWith(".") ? rootDomain : `.${rootDomain}`;
|
|
7588
|
-
const leakyNames = [
|
|
7589
|
-
"__Secure-better-auth.session_token",
|
|
7590
|
-
"better-auth.session_token",
|
|
7591
|
-
"__Secure-better-auth.state",
|
|
7592
|
-
"better-auth.state",
|
|
7593
|
-
"__Secure-better-auth.csrf_token",
|
|
7594
|
-
"better-auth.csrf_token"
|
|
7595
|
-
];
|
|
7596
|
-
try {
|
|
7597
|
-
for (const n of leakyNames) {
|
|
7598
|
-
const isSecure = n.startsWith("__Secure-");
|
|
7599
|
-
const attrs = `Max-Age=0; Path=/; Domain=${leakyDomain}; SameSite=Lax${isSecure ? "; Secure" : ""}`;
|
|
7600
|
-
resp.headers?.append?.("Set-Cookie", `${n}=; ${attrs}`);
|
|
7601
|
-
}
|
|
7602
|
-
} catch {
|
|
7603
|
-
}
|
|
7604
|
-
}
|
|
7605
|
-
return resp;
|
|
7606
|
-
} catch (err) {
|
|
7607
|
-
ctx.logger?.error?.("[AuthProxyPlugin] auth dispatch failed", {
|
|
7608
|
-
error: err?.message,
|
|
7609
|
-
stack: err?.stack
|
|
7610
|
-
});
|
|
7611
|
-
return c.json({
|
|
7612
|
-
error: "auth_dispatch_failed",
|
|
7613
|
-
message: err?.message ?? String(err)
|
|
7614
|
-
}, 500);
|
|
7615
|
-
}
|
|
7616
|
-
};
|
|
7617
|
-
if (typeof rawApp.all === "function") {
|
|
7618
|
-
rawApp.all(`${AUTH_PREFIX}/*`, handler);
|
|
7619
|
-
} else {
|
|
7620
|
-
for (const m of ["get", "post", "put", "delete", "patch", "options"]) {
|
|
7621
|
-
try {
|
|
7622
|
-
rawApp[m]?.(`${AUTH_PREFIX}/*`, handler);
|
|
7623
|
-
} catch {
|
|
7624
|
-
}
|
|
7625
|
-
}
|
|
7626
|
-
}
|
|
7627
|
-
ctx.logger?.info?.(`[AuthProxyPlugin] auth proxy mounted at ${AUTH_PREFIX}/*`);
|
|
7628
|
-
});
|
|
7629
|
-
};
|
|
7630
|
-
}
|
|
7631
|
-
};
|
|
7632
|
-
|
|
7633
|
-
// src/cloud/cloud-url.ts
|
|
7634
|
-
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
7635
|
-
function resolveCloudUrl(explicit) {
|
|
7636
|
-
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
7637
|
-
const lower = raw.toLowerCase();
|
|
7638
|
-
if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
|
|
7639
|
-
return "";
|
|
7640
|
-
}
|
|
7641
|
-
const picked = raw || DEFAULT_CLOUD_URL;
|
|
7642
|
-
return picked.replace(/\/+$/, "");
|
|
7643
|
-
}
|
|
7644
|
-
|
|
7645
|
-
// src/cloud/marketplace-public-url.ts
|
|
7646
|
-
function resolveMarketplacePublicBaseUrl(explicit) {
|
|
7647
|
-
const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
|
|
7648
|
-
const lower = raw.toLowerCase();
|
|
7649
|
-
if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
|
|
7650
|
-
return "";
|
|
6286
|
+
// src/cloud/marketplace-public-url.ts
|
|
6287
|
+
function resolveMarketplacePublicBaseUrl(explicit) {
|
|
6288
|
+
const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
|
|
6289
|
+
const lower = raw.toLowerCase();
|
|
6290
|
+
if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
|
|
6291
|
+
return "";
|
|
7651
6292
|
}
|
|
7652
6293
|
return raw.replace(/\/+$/, "");
|
|
7653
6294
|
}
|
|
@@ -7762,13 +6403,7 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
|
7762
6403
|
}
|
|
7763
6404
|
const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
|
|
7764
6405
|
if (method !== "GET" && method !== "HEAD") {
|
|
7765
|
-
return
|
|
7766
|
-
success: false,
|
|
7767
|
-
error: {
|
|
7768
|
-
code: "marketplace_method_not_allowed",
|
|
7769
|
-
message: `Marketplace proxy only forwards GET/HEAD; install via cloud.`
|
|
7770
|
-
}
|
|
7771
|
-
}, 405);
|
|
6406
|
+
return next();
|
|
7772
6407
|
}
|
|
7773
6408
|
const accept = c.req.header("accept") ?? "application/json";
|
|
7774
6409
|
const acceptLang = c.req.header("accept-language") ?? "";
|
|
@@ -7969,356 +6604,10 @@ async function consumeAndMaybeCache(resp, key, pathname, method, cache) {
|
|
|
7969
6604
|
return new Response(outBody, { status: resp.status, headers: respHeaders });
|
|
7970
6605
|
}
|
|
7971
6606
|
|
|
7972
|
-
// src/cloud/runtime-config-plugin.ts
|
|
7973
|
-
var RuntimeConfigPlugin = class {
|
|
7974
|
-
constructor(config = {}) {
|
|
7975
|
-
this.name = "com.objectstack.runtime.runtime-config";
|
|
7976
|
-
this.version = "1.0.0";
|
|
7977
|
-
this.init = async (_ctx) => {
|
|
7978
|
-
};
|
|
7979
|
-
this.start = async (ctx) => {
|
|
7980
|
-
ctx.hook("kernel:ready", async () => {
|
|
7981
|
-
let httpServer;
|
|
7982
|
-
try {
|
|
7983
|
-
httpServer = ctx.getService("http-server");
|
|
7984
|
-
} catch {
|
|
7985
|
-
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server not available \u2014 runtime/config not mounted");
|
|
7986
|
-
return;
|
|
7987
|
-
}
|
|
7988
|
-
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
7989
|
-
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server missing getRawApp() \u2014 runtime/config not mounted");
|
|
7990
|
-
return;
|
|
7991
|
-
}
|
|
7992
|
-
const rawApp = httpServer.getRawApp();
|
|
7993
|
-
const features = {
|
|
7994
|
-
installLocal: this.installLocal,
|
|
7995
|
-
marketplace: true
|
|
7996
|
-
};
|
|
7997
|
-
let envRegistry = null;
|
|
7998
|
-
try {
|
|
7999
|
-
envRegistry = ctx.getService("env-registry");
|
|
8000
|
-
} catch {
|
|
8001
|
-
}
|
|
8002
|
-
const handler = async (c) => {
|
|
8003
|
-
const rawHost = c.req.header("host") ?? "";
|
|
8004
|
-
const host = rawHost.split(":")[0].toLowerCase().trim();
|
|
8005
|
-
let defaultEnvironmentId;
|
|
8006
|
-
let defaultOrgId;
|
|
8007
|
-
let resolvedSingleEnv = this.singleEnvironment;
|
|
8008
|
-
const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
|
|
8009
|
-
if (resolveFn && host) {
|
|
8010
|
-
try {
|
|
8011
|
-
const resolved = await resolveFn(host);
|
|
8012
|
-
if (resolved?.environmentId) {
|
|
8013
|
-
defaultEnvironmentId = String(resolved.environmentId);
|
|
8014
|
-
const orgId = resolved.organizationId ?? resolved.organization_id;
|
|
8015
|
-
if (orgId) defaultOrgId = String(orgId);
|
|
8016
|
-
resolvedSingleEnv = true;
|
|
8017
|
-
}
|
|
8018
|
-
} catch {
|
|
8019
|
-
}
|
|
8020
|
-
}
|
|
8021
|
-
return c.json({
|
|
8022
|
-
cloudUrl: this.cloudUrl,
|
|
8023
|
-
singleEnvironment: resolvedSingleEnv,
|
|
8024
|
-
defaultOrgId,
|
|
8025
|
-
defaultEnvironmentId,
|
|
8026
|
-
features,
|
|
8027
|
-
branding: {
|
|
8028
|
-
productName: this.productName,
|
|
8029
|
-
productShortName: this.productShortName
|
|
8030
|
-
}
|
|
8031
|
-
});
|
|
8032
|
-
};
|
|
8033
|
-
rawApp.get("/api/v1/runtime/config", handler);
|
|
8034
|
-
rawApp.get("/api/v1/studio/runtime-config", handler);
|
|
8035
|
-
ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
|
|
8036
|
-
cloudUrl: this.cloudUrl || "(empty)",
|
|
8037
|
-
installLocal: this.installLocal,
|
|
8038
|
-
perHostEnvResolution: !!envRegistry
|
|
8039
|
-
});
|
|
8040
|
-
});
|
|
8041
|
-
};
|
|
8042
|
-
this.destroy = async () => {
|
|
8043
|
-
};
|
|
8044
|
-
this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
|
|
8045
|
-
this.installLocal = !!config.installLocal;
|
|
8046
|
-
this.singleEnvironment = !!config.singleEnvironment;
|
|
8047
|
-
const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
|
|
8048
|
-
const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
|
|
8049
|
-
this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
|
|
8050
|
-
this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
|
|
8051
|
-
}
|
|
8052
|
-
};
|
|
8053
|
-
|
|
8054
|
-
// src/cloud/file-artifact-api-client.ts
|
|
8055
|
-
var import_promises2 = require("fs/promises");
|
|
8056
|
-
var import_node_path6 = require("path");
|
|
8057
|
-
var FileArtifactApiClient = class {
|
|
8058
|
-
constructor(config = {}) {
|
|
8059
|
-
const cwd = process.cwd();
|
|
8060
|
-
this.artifactPath = (0, import_node_path6.resolve)(
|
|
8061
|
-
cwd,
|
|
8062
|
-
config.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? "dist/objectstack.json"
|
|
8063
|
-
);
|
|
8064
|
-
this.environmentId = config.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
8065
|
-
this.organizationId = config.organizationId ?? process.env.OS_ORGANIZATION_ID ?? "org_local";
|
|
8066
|
-
this.overrideRuntime = config.runtime;
|
|
8067
|
-
this.watch = config.watch ?? true;
|
|
8068
|
-
this.logger = config.logger ?? console;
|
|
8069
|
-
}
|
|
8070
|
-
async resolveHostname(_host) {
|
|
8071
|
-
const runtime = this.overrideRuntime ?? await this.readRuntimeFromArtifact();
|
|
8072
|
-
return {
|
|
8073
|
-
environmentId: this.environmentId,
|
|
8074
|
-
organizationId: this.organizationId,
|
|
8075
|
-
...runtime ? { runtime } : {}
|
|
8076
|
-
};
|
|
8077
|
-
}
|
|
8078
|
-
async fetchArtifact(_environmentId, _opts) {
|
|
8079
|
-
return this.loadArtifact();
|
|
8080
|
-
}
|
|
8081
|
-
async lookupProjectByShortId(_shortId) {
|
|
8082
|
-
return { environmentId: this.environmentId, organizationId: this.organizationId };
|
|
8083
|
-
}
|
|
8084
|
-
async fetchBranchHead(_environmentId, _branchName) {
|
|
8085
|
-
const artifact = await this.loadArtifact();
|
|
8086
|
-
return artifact ? { commitId: artifact.commitId ?? "local", publishedAt: null } : null;
|
|
8087
|
-
}
|
|
8088
|
-
invalidate(_environmentId) {
|
|
8089
|
-
this.cached = void 0;
|
|
8090
|
-
}
|
|
8091
|
-
clear() {
|
|
8092
|
-
this.cached = void 0;
|
|
8093
|
-
}
|
|
8094
|
-
async loadArtifact() {
|
|
8095
|
-
try {
|
|
8096
|
-
const stats = await (0, import_promises2.stat)(this.artifactPath);
|
|
8097
|
-
const mtimeMs = stats.mtimeMs;
|
|
8098
|
-
if (!this.watch && this.cached) return this.cached.response;
|
|
8099
|
-
if (this.cached && this.cached.mtimeMs === mtimeMs) return this.cached.response;
|
|
8100
|
-
const raw = await (0, import_promises2.readFile)(this.artifactPath, "utf8");
|
|
8101
|
-
const parsed = JSON.parse(raw);
|
|
8102
|
-
const isEnvelope = parsed && typeof parsed === "object" && typeof parsed.metadata === "object" && parsed.metadata !== null;
|
|
8103
|
-
const metadata = isEnvelope ? parsed.metadata : parsed;
|
|
8104
|
-
const runtime = this.overrideRuntime ?? (isEnvelope ? parsed.runtime : void 0) ?? this.deriveRuntimeFromMetadata(metadata) ?? this.defaultLocalSqliteRuntime();
|
|
8105
|
-
const response = {
|
|
8106
|
-
schemaVersion: parsed.schemaVersion ?? "1",
|
|
8107
|
-
environmentId: parsed.environmentId ?? this.environmentId,
|
|
8108
|
-
commitId: parsed.commitId ?? "local",
|
|
8109
|
-
checksum: parsed.checksum ?? "",
|
|
8110
|
-
publishedAt: parsed.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8111
|
-
metadata,
|
|
8112
|
-
functions: parsed.functions,
|
|
8113
|
-
manifest: parsed.manifest,
|
|
8114
|
-
runtime: {
|
|
8115
|
-
organizationId: this.organizationId,
|
|
8116
|
-
...runtime
|
|
8117
|
-
}
|
|
8118
|
-
};
|
|
8119
|
-
this.cached = { mtimeMs, response };
|
|
8120
|
-
return response;
|
|
8121
|
-
} catch (err) {
|
|
8122
|
-
this.logger.error?.("[FileArtifactApiClient] failed to load artifact", {
|
|
8123
|
-
artifactPath: this.artifactPath,
|
|
8124
|
-
error: err?.message ?? err
|
|
8125
|
-
});
|
|
8126
|
-
return null;
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
8129
|
-
async readRuntimeFromArtifact() {
|
|
8130
|
-
const artifact = await this.loadArtifact();
|
|
8131
|
-
return artifact?.runtime;
|
|
8132
|
-
}
|
|
8133
|
-
deriveRuntimeFromMetadata(metadata) {
|
|
8134
|
-
const datasources = metadata?.datasources;
|
|
8135
|
-
if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
|
|
8136
|
-
const mapping = metadata?.datasourceMapping;
|
|
8137
|
-
let preferredName;
|
|
8138
|
-
if (mapping) {
|
|
8139
|
-
const def = mapping.find((m) => m?.default === true);
|
|
8140
|
-
if (def?.datasource) preferredName = def.datasource;
|
|
8141
|
-
}
|
|
8142
|
-
const ds = preferredName ? datasources.find((d) => d?.name === preferredName) ?? datasources[0] : datasources[0];
|
|
8143
|
-
if (!ds || typeof ds !== "object") return void 0;
|
|
8144
|
-
const config = ds.config ?? {};
|
|
8145
|
-
const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
|
|
8146
|
-
const driver = ds.driver;
|
|
8147
|
-
if (typeof driver !== "string" || typeof url !== "string") return void 0;
|
|
8148
|
-
return {
|
|
8149
|
-
databaseDriver: driver,
|
|
8150
|
-
databaseUrl: url,
|
|
8151
|
-
databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
|
|
8152
|
-
};
|
|
8153
|
-
}
|
|
8154
|
-
defaultLocalSqliteRuntime() {
|
|
8155
|
-
const cwd = process.cwd();
|
|
8156
|
-
const dbPath = (0, import_node_path6.resolve)(cwd, ".objectstack/data", `${this.environmentId}.db`);
|
|
8157
|
-
return {
|
|
8158
|
-
databaseDriver: "sqlite",
|
|
8159
|
-
databaseUrl: `file:${dbPath}`
|
|
8160
|
-
};
|
|
8161
|
-
}
|
|
8162
|
-
};
|
|
8163
|
-
|
|
8164
|
-
// src/cloud/objectos-stack.ts
|
|
8165
|
-
async function createHostEnginePlugins() {
|
|
8166
|
-
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
8167
|
-
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
8168
|
-
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
8169
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
8170
|
-
const driver = new InMemoryDriver();
|
|
8171
|
-
const driverName = "memory";
|
|
8172
|
-
const oqlRef = { ql: null };
|
|
8173
|
-
const objectql = {
|
|
8174
|
-
name: "com.objectstack.engine.objectql",
|
|
8175
|
-
version: "0.0.0",
|
|
8176
|
-
async init(ctx) {
|
|
8177
|
-
const plugin = new ObjectQLPlugin();
|
|
8178
|
-
this._inner = plugin;
|
|
8179
|
-
if (plugin.init) await plugin.init(ctx);
|
|
8180
|
-
oqlRef.ql = plugin.ql ?? plugin;
|
|
8181
|
-
},
|
|
8182
|
-
async start(ctx) {
|
|
8183
|
-
const plugin = this._inner;
|
|
8184
|
-
if (plugin?.start) await plugin.start(ctx);
|
|
8185
|
-
},
|
|
8186
|
-
async destroy() {
|
|
8187
|
-
const plugin = this._inner;
|
|
8188
|
-
if (plugin?.destroy) await plugin.destroy();
|
|
8189
|
-
else if (plugin?.stop) await plugin.stop();
|
|
8190
|
-
}
|
|
8191
|
-
};
|
|
8192
|
-
const datasourceMapping = {
|
|
8193
|
-
name: "objectos-host-datasource-mapping",
|
|
8194
|
-
version: "0.0.0",
|
|
8195
|
-
dependencies: ["com.objectstack.engine.objectql"],
|
|
8196
|
-
async init() {
|
|
8197
|
-
const ql = oqlRef.ql;
|
|
8198
|
-
if (ql?.setDatasourceMapping) {
|
|
8199
|
-
ql.setDatasourceMapping([
|
|
8200
|
-
{ default: true, datasource: `com.objectstack.driver.${driverName}` }
|
|
8201
|
-
]);
|
|
8202
|
-
}
|
|
8203
|
-
}
|
|
8204
|
-
};
|
|
8205
|
-
const driverPlugin = new DriverPlugin2(driver, driverName);
|
|
8206
|
-
const metadata = new MetadataPlugin({
|
|
8207
|
-
watch: false,
|
|
8208
|
-
// The host kernel is a routing shell. It doesn't own metadata —
|
|
8209
|
-
// every per-project kernel registers its own.
|
|
8210
|
-
registerSystemObjects: false
|
|
8211
|
-
});
|
|
8212
|
-
return [objectql, datasourceMapping, driverPlugin, metadata];
|
|
8213
|
-
}
|
|
8214
|
-
var ObjectOSEnvironmentPlugin = class {
|
|
8215
|
-
constructor(config) {
|
|
8216
|
-
this.name = "com.objectstack.runtime.objectos-environment";
|
|
8217
|
-
this.version = "1.0.0";
|
|
8218
|
-
this.init = async (ctx) => {
|
|
8219
|
-
const client = this.config.client ?? (this.config.controlPlaneUrl === "file" ? new FileArtifactApiClient({
|
|
8220
|
-
...this.config.fileConfig ?? {},
|
|
8221
|
-
logger: ctx.logger
|
|
8222
|
-
}) : new ArtifactApiClient({
|
|
8223
|
-
controlPlaneUrl: this.config.controlPlaneUrl,
|
|
8224
|
-
apiKey: this.config.controlPlaneApiKey,
|
|
8225
|
-
cacheTtlMs: this.config.artifactCacheTtlMs,
|
|
8226
|
-
logger: ctx.logger
|
|
8227
|
-
}));
|
|
8228
|
-
this.client = client;
|
|
8229
|
-
const envRegistry = new ArtifactEnvironmentRegistry({
|
|
8230
|
-
client,
|
|
8231
|
-
cacheTtlMs: this.config.envCacheTtlMs,
|
|
8232
|
-
logger: ctx.logger
|
|
8233
|
-
});
|
|
8234
|
-
const factory = new ArtifactKernelFactory({
|
|
8235
|
-
client,
|
|
8236
|
-
envRegistry,
|
|
8237
|
-
logger: ctx.logger
|
|
8238
|
-
});
|
|
8239
|
-
const kernelManager = new KernelManager({
|
|
8240
|
-
factory,
|
|
8241
|
-
maxSize: this.config.kernelCacheSize,
|
|
8242
|
-
ttlMs: this.config.kernelTtlMs,
|
|
8243
|
-
logger: ctx.logger,
|
|
8244
|
-
// Only the HTTP client exposes /freshness; file-mode (CLI dev)
|
|
8245
|
-
// has no upstream to probe.
|
|
8246
|
-
freshnessProbe: this.config.controlPlaneUrl === "file" ? void 0 : async (envId, builtAtMs) => {
|
|
8247
|
-
const fresh = await client.getFreshness(envId);
|
|
8248
|
-
if (!fresh) return false;
|
|
8249
|
-
const t = fresh.lastPublishedAt ? Date.parse(fresh.lastPublishedAt) : NaN;
|
|
8250
|
-
if (!Number.isFinite(t)) return false;
|
|
8251
|
-
if (t <= builtAtMs) return false;
|
|
8252
|
-
try {
|
|
8253
|
-
client.invalidate(envId);
|
|
8254
|
-
} catch {
|
|
8255
|
-
}
|
|
8256
|
-
return true;
|
|
8257
|
-
}
|
|
8258
|
-
});
|
|
8259
|
-
this.kernelManager = kernelManager;
|
|
8260
|
-
ctx.registerService("env-registry", envRegistry);
|
|
8261
|
-
ctx.registerService("kernel-manager", kernelManager);
|
|
8262
|
-
ctx.registerService("artifact-api-client", client);
|
|
8263
|
-
ctx.logger.info?.("ObjectOSEnvironmentPlugin: registered env-registry + kernel-manager", {
|
|
8264
|
-
mode: this.config.controlPlaneUrl === "file" ? "file" : "http",
|
|
8265
|
-
controlPlaneUrl: this.config.controlPlaneUrl
|
|
8266
|
-
});
|
|
8267
|
-
};
|
|
8268
|
-
this.destroy = async () => {
|
|
8269
|
-
try {
|
|
8270
|
-
await this.kernelManager?.evictAll();
|
|
8271
|
-
} catch {
|
|
8272
|
-
}
|
|
8273
|
-
try {
|
|
8274
|
-
this.client?.clear();
|
|
8275
|
-
} catch {
|
|
8276
|
-
}
|
|
8277
|
-
};
|
|
8278
|
-
this.config = config;
|
|
8279
|
-
}
|
|
8280
|
-
};
|
|
8281
|
-
async function createObjectOSStack(config) {
|
|
8282
|
-
if (!config.controlPlaneUrl && !config.client) {
|
|
8283
|
-
throw new Error("[createObjectOSStack] either controlPlaneUrl or client is required");
|
|
8284
|
-
}
|
|
8285
|
-
const merged = {
|
|
8286
|
-
...config,
|
|
8287
|
-
kernelCacheSize: Number(process.env.OS_KERNEL_CACHE_SIZE ?? config.kernelCacheSize ?? 32),
|
|
8288
|
-
kernelTtlMs: Number(process.env.OS_KERNEL_TTL_MS ?? config.kernelTtlMs ?? 15 * 60 * 1e3),
|
|
8289
|
-
envCacheTtlMs: Number(process.env.OS_ENV_CACHE_TTL_MS ?? config.envCacheTtlMs ?? 5 * 60 * 1e3),
|
|
8290
|
-
artifactCacheTtlMs: Number(process.env.OS_ARTIFACT_CACHE_TTL_MS ?? config.artifactCacheTtlMs ?? 5 * 60 * 1e3)
|
|
8291
|
-
};
|
|
8292
|
-
const enginePlugins = await createHostEnginePlugins();
|
|
8293
|
-
return {
|
|
8294
|
-
plugins: [
|
|
8295
|
-
...enginePlugins,
|
|
8296
|
-
new ObjectOSEnvironmentPlugin(merged),
|
|
8297
|
-
new AuthProxyPlugin(),
|
|
8298
|
-
new MarketplaceProxyPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl }),
|
|
8299
|
-
new RuntimeConfigPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl, installLocal: false }),
|
|
8300
|
-
// Host-supplied product/policy plugins (the official seam — see
|
|
8301
|
-
// ObjectOSStackConfig.extraPlugins). Appended last so they mount
|
|
8302
|
-
// after the framework defaults.
|
|
8303
|
-
...config.extraPlugins ?? []
|
|
8304
|
-
],
|
|
8305
|
-
api: {
|
|
8306
|
-
enableProjectScoping: true,
|
|
8307
|
-
projectResolution: "auto",
|
|
8308
|
-
// ObjectOS is multi-tenant: anonymous /api/v1/data/* must never
|
|
8309
|
-
// leak per-project data across organisations. AuthProxyPlugin
|
|
8310
|
-
// verifies upstream tokens and populates ctx.userId; requireAuth
|
|
8311
|
-
// turns missing userId into 401 at the REST layer before the
|
|
8312
|
-
// request reaches the per-project kernel.
|
|
8313
|
-
requireAuth: true
|
|
8314
|
-
}
|
|
8315
|
-
};
|
|
8316
|
-
}
|
|
8317
|
-
|
|
8318
6607
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
8319
6608
|
var import_node_fs4 = require("fs");
|
|
8320
|
-
var
|
|
8321
|
-
var
|
|
6609
|
+
var import_node_path5 = require("path");
|
|
6610
|
+
var import_types3 = require("@objectstack/types");
|
|
8322
6611
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
8323
6612
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
8324
6613
|
function safeFilename(manifestId) {
|
|
@@ -8518,7 +6807,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8518
6807
|
};
|
|
8519
6808
|
try {
|
|
8520
6809
|
(0, import_node_fs4.mkdirSync)(this.storageDir, { recursive: true });
|
|
8521
|
-
(0, import_node_fs4.writeFileSync)((0,
|
|
6810
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
8522
6811
|
} catch (err) {
|
|
8523
6812
|
return c.json({
|
|
8524
6813
|
success: false,
|
|
@@ -8538,7 +6827,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8538
6827
|
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
8539
6828
|
entry.withSampleData = true;
|
|
8540
6829
|
try {
|
|
8541
|
-
(0, import_node_fs4.writeFileSync)((0,
|
|
6830
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
8542
6831
|
} catch {
|
|
8543
6832
|
}
|
|
8544
6833
|
}
|
|
@@ -8585,7 +6874,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8585
6874
|
if (!manifestId) {
|
|
8586
6875
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
8587
6876
|
}
|
|
8588
|
-
const file = (0,
|
|
6877
|
+
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
8589
6878
|
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
8590
6879
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
8591
6880
|
}
|
|
@@ -8613,7 +6902,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8613
6902
|
* (refuse to avoid silently overwriting authored code)
|
|
8614
6903
|
*/
|
|
8615
6904
|
this.findConflict = (ctx, manifestId) => {
|
|
8616
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
6905
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)))) {
|
|
8617
6906
|
return "marketplace";
|
|
8618
6907
|
}
|
|
8619
6908
|
try {
|
|
@@ -8655,7 +6944,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8655
6944
|
if (!manifestId) {
|
|
8656
6945
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
8657
6946
|
}
|
|
8658
|
-
const file = (0,
|
|
6947
|
+
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
8659
6948
|
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
8660
6949
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
8661
6950
|
}
|
|
@@ -8709,7 +6998,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8709
6998
|
if (!manifestId) {
|
|
8710
6999
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
8711
7000
|
}
|
|
8712
|
-
const file = (0,
|
|
7001
|
+
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
8713
7002
|
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
8714
7003
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
8715
7004
|
}
|
|
@@ -8862,7 +7151,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8862
7151
|
}
|
|
8863
7152
|
}
|
|
8864
7153
|
if (opts.seedNow && datasets.length > 0) {
|
|
8865
|
-
const multiTenant = String((0,
|
|
7154
|
+
const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
8866
7155
|
try {
|
|
8867
7156
|
const ql = ctx.getService("objectql");
|
|
8868
7157
|
let metadata;
|
|
@@ -8968,7 +7257,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8968
7257
|
for (const name of (0, import_node_fs4.readdirSync)(this.storageDir)) {
|
|
8969
7258
|
if (!name.endsWith(".json")) continue;
|
|
8970
7259
|
try {
|
|
8971
|
-
const raw = (0, import_node_fs4.readFileSync)((0,
|
|
7260
|
+
const raw = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(this.storageDir, name), "utf8");
|
|
8972
7261
|
out.push(JSON.parse(raw));
|
|
8973
7262
|
} catch {
|
|
8974
7263
|
}
|
|
@@ -8976,7 +7265,91 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8976
7265
|
return out;
|
|
8977
7266
|
};
|
|
8978
7267
|
this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
|
|
8979
|
-
this.storageDir = config.storageDir ? (0,
|
|
7268
|
+
this.storageDir = config.storageDir ? (0, import_node_path5.resolve)(config.storageDir) : (0, import_node_path5.resolve)(process.cwd(), DEFAULT_DIR);
|
|
7269
|
+
}
|
|
7270
|
+
};
|
|
7271
|
+
|
|
7272
|
+
// src/cloud/runtime-config-plugin.ts
|
|
7273
|
+
var RuntimeConfigPlugin = class {
|
|
7274
|
+
constructor(config = {}) {
|
|
7275
|
+
this.name = "com.objectstack.runtime.runtime-config";
|
|
7276
|
+
this.version = "1.0.0";
|
|
7277
|
+
this.init = async (_ctx) => {
|
|
7278
|
+
};
|
|
7279
|
+
this.start = async (ctx) => {
|
|
7280
|
+
ctx.hook("kernel:ready", async () => {
|
|
7281
|
+
let httpServer;
|
|
7282
|
+
try {
|
|
7283
|
+
httpServer = ctx.getService("http-server");
|
|
7284
|
+
} catch {
|
|
7285
|
+
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server not available \u2014 runtime/config not mounted");
|
|
7286
|
+
return;
|
|
7287
|
+
}
|
|
7288
|
+
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
7289
|
+
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server missing getRawApp() \u2014 runtime/config not mounted");
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
const rawApp = httpServer.getRawApp();
|
|
7293
|
+
const features = {
|
|
7294
|
+
installLocal: this.installLocal,
|
|
7295
|
+
marketplace: true,
|
|
7296
|
+
aiStudio: this.aiStudio
|
|
7297
|
+
};
|
|
7298
|
+
let envRegistry = null;
|
|
7299
|
+
try {
|
|
7300
|
+
envRegistry = ctx.getService("env-registry");
|
|
7301
|
+
} catch {
|
|
7302
|
+
}
|
|
7303
|
+
const handler = async (c) => {
|
|
7304
|
+
const rawHost = c.req.header("host") ?? "";
|
|
7305
|
+
const host = rawHost.split(":")[0].toLowerCase().trim();
|
|
7306
|
+
let defaultEnvironmentId;
|
|
7307
|
+
let defaultOrgId;
|
|
7308
|
+
let resolvedSingleEnv = this.singleEnvironment;
|
|
7309
|
+
const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
|
|
7310
|
+
if (resolveFn && host) {
|
|
7311
|
+
try {
|
|
7312
|
+
const resolved = await resolveFn(host);
|
|
7313
|
+
if (resolved?.environmentId) {
|
|
7314
|
+
defaultEnvironmentId = String(resolved.environmentId);
|
|
7315
|
+
const orgId = resolved.organizationId ?? resolved.organization_id;
|
|
7316
|
+
if (orgId) defaultOrgId = String(orgId);
|
|
7317
|
+
resolvedSingleEnv = true;
|
|
7318
|
+
}
|
|
7319
|
+
} catch {
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
return c.json({
|
|
7323
|
+
cloudUrl: this.cloudUrl,
|
|
7324
|
+
singleEnvironment: resolvedSingleEnv,
|
|
7325
|
+
defaultOrgId,
|
|
7326
|
+
defaultEnvironmentId,
|
|
7327
|
+
features,
|
|
7328
|
+
branding: {
|
|
7329
|
+
productName: this.productName,
|
|
7330
|
+
productShortName: this.productShortName
|
|
7331
|
+
}
|
|
7332
|
+
});
|
|
7333
|
+
};
|
|
7334
|
+
rawApp.get("/api/v1/runtime/config", handler);
|
|
7335
|
+
rawApp.get("/api/v1/studio/runtime-config", handler);
|
|
7336
|
+
ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
|
|
7337
|
+
cloudUrl: this.cloudUrl || "(empty)",
|
|
7338
|
+
installLocal: this.installLocal,
|
|
7339
|
+
perHostEnvResolution: !!envRegistry
|
|
7340
|
+
});
|
|
7341
|
+
});
|
|
7342
|
+
};
|
|
7343
|
+
this.destroy = async () => {
|
|
7344
|
+
};
|
|
7345
|
+
this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
|
|
7346
|
+
this.installLocal = !!config.installLocal;
|
|
7347
|
+
this.aiStudio = config.aiStudio !== false;
|
|
7348
|
+
this.singleEnvironment = !!config.singleEnvironment;
|
|
7349
|
+
const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
|
|
7350
|
+
const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
|
|
7351
|
+
this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
|
|
7352
|
+
this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
|
|
8980
7353
|
}
|
|
8981
7354
|
};
|
|
8982
7355
|
|
|
@@ -9005,24 +7378,18 @@ init_body_runner();
|
|
|
9005
7378
|
// src/index.ts
|
|
9006
7379
|
var import_rest = require("@objectstack/rest");
|
|
9007
7380
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
9008
|
-
var
|
|
7381
|
+
var import_types4 = require("@objectstack/types");
|
|
9009
7382
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9010
7383
|
0 && (module.exports = {
|
|
9011
7384
|
AppPlugin,
|
|
9012
|
-
ArtifactApiClient,
|
|
9013
|
-
ArtifactEnvironmentRegistry,
|
|
9014
|
-
ArtifactKernelFactory,
|
|
9015
|
-
AuthProxyPlugin,
|
|
9016
7385
|
DEFAULT_CLOUD_URL,
|
|
9017
7386
|
DEFAULT_RATE_LIMITS,
|
|
9018
7387
|
DriverPlugin,
|
|
9019
7388
|
ExternalValidationPlugin,
|
|
9020
|
-
FileArtifactApiClient,
|
|
9021
7389
|
HttpDispatcher,
|
|
9022
7390
|
HttpServer,
|
|
9023
7391
|
InMemoryErrorReporter,
|
|
9024
7392
|
InMemoryMetricsRegistry,
|
|
9025
|
-
KernelManager,
|
|
9026
7393
|
MarketplaceInstallLocalPlugin,
|
|
9027
7394
|
MarketplaceProxyPlugin,
|
|
9028
7395
|
MiddlewareManager,
|
|
@@ -9032,7 +7399,6 @@ var import_types5 = require("@objectstack/types");
|
|
|
9032
7399
|
OBSERVABILITY_METRICS_SERVICE,
|
|
9033
7400
|
ObjectKernel,
|
|
9034
7401
|
ObservabilityServicePlugin,
|
|
9035
|
-
PLATFORM_SSO_PROVIDER_ID,
|
|
9036
7402
|
QuickJSScriptRunner,
|
|
9037
7403
|
RUNTIME_METRICS,
|
|
9038
7404
|
RateLimiter,
|
|
@@ -9047,8 +7413,6 @@ var import_types5 = require("@objectstack/types");
|
|
|
9047
7413
|
UnimplementedScriptRunner,
|
|
9048
7414
|
_resetEnvDeprecationWarnings,
|
|
9049
7415
|
actionBodyRunnerFactory,
|
|
9050
|
-
backfillPlatformSsoClients,
|
|
9051
|
-
buildPlatformSsoRedirectUri,
|
|
9052
7416
|
buildSecurityHeaders,
|
|
9053
7417
|
collectBundleActions,
|
|
9054
7418
|
collectBundleFunctions,
|
|
@@ -9056,12 +7420,9 @@ var import_types5 = require("@objectstack/types");
|
|
|
9056
7420
|
createDefaultHostConfig,
|
|
9057
7421
|
createDispatcherPlugin,
|
|
9058
7422
|
createExternalValidationPlugin,
|
|
9059
|
-
createObjectOSStack,
|
|
9060
7423
|
createRestApiPlugin,
|
|
9061
7424
|
createStandaloneStack,
|
|
9062
7425
|
createSystemEnvironmentPlugin,
|
|
9063
|
-
derivePlatformSsoClientId,
|
|
9064
|
-
derivePlatformSsoClientSecret,
|
|
9065
7426
|
extractRequestId,
|
|
9066
7427
|
formatTraceparent,
|
|
9067
7428
|
generateRequestId,
|
|
@@ -9078,7 +7439,6 @@ var import_types5 = require("@objectstack/types");
|
|
|
9078
7439
|
resolveMetrics,
|
|
9079
7440
|
resolveObjectStackHome,
|
|
9080
7441
|
resolveRequestId,
|
|
9081
|
-
seedPlatformSsoClient,
|
|
9082
7442
|
...require("@objectstack/core")
|
|
9083
7443
|
});
|
|
9084
7444
|
//# sourceMappingURL=index.cjs.map
|