@objectstack/runtime 7.3.0 → 7.4.1
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 +769 -1955
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -14
- package/dist/index.d.ts +88 -14
- package/dist/index.js +782 -1968
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -32,13 +32,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
34
|
// src/load-artifact-bundle.ts
|
|
35
|
-
var load_artifact_bundle_exports = {};
|
|
36
|
-
__export(load_artifact_bundle_exports, {
|
|
37
|
-
isHttpUrl: () => isHttpUrl,
|
|
38
|
-
loadArtifactBundle: () => loadArtifactBundle,
|
|
39
|
-
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
40
|
-
readArtifactSource: () => readArtifactSource
|
|
41
|
-
});
|
|
42
35
|
function isHttpUrl(pathOrUrl) {
|
|
43
36
|
return /^https?:\/\//i.test(pathOrUrl);
|
|
44
37
|
}
|
|
@@ -294,17 +287,37 @@ var init_seed_loader = __esm({
|
|
|
294
287
|
}
|
|
295
288
|
const objectRefs = refMap.get(objectName) || [];
|
|
296
289
|
const seedNow = /* @__PURE__ */ new Date();
|
|
290
|
+
const seedIdentity = config.identity;
|
|
291
|
+
const baseEvalCtx = {
|
|
292
|
+
now: seedNow,
|
|
293
|
+
user: seedIdentity?.user,
|
|
294
|
+
// Fall back to the per-tenant organizationId so `os.org.id` resolves
|
|
295
|
+
// during per-org replay even without an explicit identity.org.
|
|
296
|
+
org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
|
|
297
|
+
env: config.env
|
|
298
|
+
};
|
|
297
299
|
for (let i = 0; i < dataset.records.length; i++) {
|
|
298
300
|
const seedResult = (0, import_formula.resolveSeedRecord)(
|
|
299
301
|
dataset.records[i],
|
|
300
|
-
|
|
302
|
+
baseEvalCtx
|
|
301
303
|
);
|
|
302
|
-
const record = seedResult.ok ? { ...seedResult.value } : { ...dataset.records[i] };
|
|
303
304
|
if (!seedResult.ok) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
errored++;
|
|
306
|
+
const error = {
|
|
307
|
+
sourceObject: objectName,
|
|
308
|
+
field: "(expression)",
|
|
309
|
+
targetObject: objectName,
|
|
310
|
+
targetField: "(expression)",
|
|
311
|
+
attemptedValue: dataset.records[i],
|
|
312
|
+
recordIndex: i,
|
|
313
|
+
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}. Records using cel\`os.user.id\` / cel\`os.org.id\` require a seed identity \u2014 ensure a system/admin user exists before seeding (see SeedLoaderConfig.identity).`
|
|
314
|
+
};
|
|
315
|
+
errors.push(error);
|
|
316
|
+
allErrors.push(error);
|
|
317
|
+
this.logger.warn(`[SeedLoader] ${error.message}`);
|
|
318
|
+
continue;
|
|
307
319
|
}
|
|
320
|
+
const record = { ...seedResult.value };
|
|
308
321
|
if (config.organizationId && record["organization_id"] == null) {
|
|
309
322
|
record["organization_id"] = config.organizationId;
|
|
310
323
|
}
|
|
@@ -383,10 +396,18 @@ var init_seed_loader = __esm({
|
|
|
383
396
|
}
|
|
384
397
|
} catch (err) {
|
|
385
398
|
errored++;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
399
|
+
const error = {
|
|
400
|
+
sourceObject: objectName,
|
|
401
|
+
field: "(write)",
|
|
402
|
+
targetObject: objectName,
|
|
403
|
+
targetField: externalId,
|
|
404
|
+
attemptedValue: record[externalId] ?? null,
|
|
405
|
+
recordIndex: i,
|
|
406
|
+
message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
|
|
407
|
+
};
|
|
408
|
+
errors.push(error);
|
|
409
|
+
allErrors.push(error);
|
|
410
|
+
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
390
411
|
}
|
|
391
412
|
} else {
|
|
392
413
|
const externalIdValue = String(record[externalId] ?? "");
|
|
@@ -1330,13 +1351,14 @@ function collectBundleFunctions(bundle) {
|
|
|
1330
1351
|
merge(bundle?.manifest?.functions);
|
|
1331
1352
|
return out;
|
|
1332
1353
|
}
|
|
1333
|
-
var import_types, AppPlugin;
|
|
1354
|
+
var import_types, import_system, AppPlugin;
|
|
1334
1355
|
var init_app_plugin = __esm({
|
|
1335
1356
|
"src/app-plugin.ts"() {
|
|
1336
1357
|
"use strict";
|
|
1337
1358
|
import_types = require("@objectstack/types");
|
|
1338
1359
|
init_seed_loader();
|
|
1339
1360
|
init_package_state_store();
|
|
1361
|
+
import_system = require("@objectstack/spec/system");
|
|
1340
1362
|
init_quickjs_runner();
|
|
1341
1363
|
init_body_runner();
|
|
1342
1364
|
AppPlugin = class {
|
|
@@ -1411,6 +1433,27 @@ var init_app_plugin = __esm({
|
|
|
1411
1433
|
});
|
|
1412
1434
|
ql.setDatasourceMapping(this.bundle.datasourceMapping);
|
|
1413
1435
|
}
|
|
1436
|
+
try {
|
|
1437
|
+
const dsDefs = this.bundle.datasources;
|
|
1438
|
+
const dsList = Array.isArray(dsDefs) ? dsDefs : dsDefs && typeof dsDefs === "object" ? Object.entries(dsDefs).map(([name, def]) => ({ name, ...def })) : [];
|
|
1439
|
+
if (dsList.length > 0) {
|
|
1440
|
+
const metadata = ctx.getService("metadata");
|
|
1441
|
+
if (typeof metadata?.registerInMemory === "function") {
|
|
1442
|
+
for (const ds of dsList) {
|
|
1443
|
+
if (!ds?.name) continue;
|
|
1444
|
+
metadata.registerInMemory("datasource", ds.name, { ...ds, origin: "code" });
|
|
1445
|
+
}
|
|
1446
|
+
ctx.logger.info("Registered code-defined datasources in metadata registry", {
|
|
1447
|
+
appId,
|
|
1448
|
+
count: dsList.length
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
ctx.logger.warn("[AppPlugin] failed to register code-defined datasources", {
|
|
1454
|
+
error: err?.message ?? String(err)
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1414
1457
|
const stackBundle = this.bundle.default || this.bundle;
|
|
1415
1458
|
const runtime = stackBundle && typeof stackBundle.onEnable === "function" ? stackBundle : this.bundle;
|
|
1416
1459
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
@@ -1505,49 +1548,6 @@ var init_app_plugin = __esm({
|
|
|
1505
1548
|
appId
|
|
1506
1549
|
});
|
|
1507
1550
|
}
|
|
1508
|
-
try {
|
|
1509
|
-
const approvals = Array.isArray(this.bundle.approvals) ? this.bundle.approvals : Array.isArray((this.bundle.manifest || {}).approvals) ? this.bundle.manifest.approvals : [];
|
|
1510
|
-
if (approvals.length > 0) {
|
|
1511
|
-
ctx.hook("kernel:ready", async () => {
|
|
1512
|
-
let svc;
|
|
1513
|
-
try {
|
|
1514
|
-
svc = ctx.getService("approvals");
|
|
1515
|
-
} catch {
|
|
1516
|
-
}
|
|
1517
|
-
if (!svc || typeof svc.defineProcess !== "function") {
|
|
1518
|
-
ctx.logger.warn("[AppPlugin] approvals service not registered \u2014 skipping declarative processes", {
|
|
1519
|
-
appId,
|
|
1520
|
-
processCount: approvals.length
|
|
1521
|
-
});
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
const sysCtx = { isSystem: true, roles: [], permissions: [] };
|
|
1525
|
-
let ok = 0;
|
|
1526
|
-
for (const proc of approvals) {
|
|
1527
|
-
try {
|
|
1528
|
-
await svc.defineProcess({
|
|
1529
|
-
name: proc.name,
|
|
1530
|
-
label: proc.label,
|
|
1531
|
-
object: proc.object,
|
|
1532
|
-
description: proc.description,
|
|
1533
|
-
active: proc.active !== false,
|
|
1534
|
-
definition: proc
|
|
1535
|
-
}, sysCtx);
|
|
1536
|
-
ok++;
|
|
1537
|
-
} catch (err) {
|
|
1538
|
-
ctx.logger.warn("[AppPlugin] Failed to register approval process", {
|
|
1539
|
-
appId,
|
|
1540
|
-
process: proc?.name,
|
|
1541
|
-
error: err?.message ?? String(err)
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
ctx.logger.info("[AppPlugin] Registered approval processes", { appId, count: ok });
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
} catch (err) {
|
|
1549
|
-
ctx.logger.error("[AppPlugin] Failed to schedule approval-process registration", err, { appId });
|
|
1550
|
-
}
|
|
1551
1551
|
try {
|
|
1552
1552
|
const jobs = Array.isArray(this.bundle.jobs) ? this.bundle.jobs : Array.isArray((this.bundle.manifest || {}).jobs) ? this.bundle.manifest.jobs : [];
|
|
1553
1553
|
if (jobs.length > 0) {
|
|
@@ -1624,6 +1624,7 @@ var init_app_plugin = __esm({
|
|
|
1624
1624
|
...d,
|
|
1625
1625
|
object: d.object
|
|
1626
1626
|
}));
|
|
1627
|
+
const seedIdentity = await this.ensureSeedIdentity(ql, ctx.logger);
|
|
1627
1628
|
try {
|
|
1628
1629
|
const kernel = ctx.kernel;
|
|
1629
1630
|
const existing = (() => {
|
|
@@ -1665,7 +1666,12 @@ var init_app_plugin = __esm({
|
|
|
1665
1666
|
config: {
|
|
1666
1667
|
defaultMode: "upsert",
|
|
1667
1668
|
multiPass: true,
|
|
1668
|
-
organizationId
|
|
1669
|
+
organizationId,
|
|
1670
|
+
// Bind os.user (system identity) and os.org (this
|
|
1671
|
+
// tenant) so identity-derived seed values resolve
|
|
1672
|
+
// per-org. org.id falls back to organizationId
|
|
1673
|
+
// inside the loader when identity.org is absent.
|
|
1674
|
+
identity: seedIdentity
|
|
1669
1675
|
}
|
|
1670
1676
|
});
|
|
1671
1677
|
const result = await seedLoader.load(request);
|
|
@@ -1693,14 +1699,34 @@ var init_app_plugin = __esm({
|
|
|
1693
1699
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1694
1700
|
const request = SeedLoaderRequestSchema.parse({
|
|
1695
1701
|
datasets: normalizedDatasets,
|
|
1696
|
-
config: { defaultMode: "upsert", multiPass: true }
|
|
1702
|
+
config: { defaultMode: "upsert", multiPass: true, identity: seedIdentity }
|
|
1697
1703
|
});
|
|
1698
1704
|
const result = await seedLoader.load(request);
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1705
|
+
const { totalInserted, totalUpdated, totalSkipped, totalErrored } = result.summary;
|
|
1706
|
+
if (result.success) {
|
|
1707
|
+
ctx.logger.info("[Seeder] Seed loading complete", {
|
|
1708
|
+
inserted: totalInserted,
|
|
1709
|
+
updated: totalUpdated,
|
|
1710
|
+
skipped: totalSkipped,
|
|
1711
|
+
errored: totalErrored
|
|
1712
|
+
});
|
|
1713
|
+
} else {
|
|
1714
|
+
ctx.logger.warn(
|
|
1715
|
+
`[Seeder] Seed loading completed with ${totalErrored} dropped record(s) and ${result.errors.length} error(s) for ${appId}`,
|
|
1716
|
+
{
|
|
1717
|
+
inserted: totalInserted,
|
|
1718
|
+
updated: totalUpdated,
|
|
1719
|
+
skipped: totalSkipped,
|
|
1720
|
+
errored: totalErrored
|
|
1721
|
+
}
|
|
1722
|
+
);
|
|
1723
|
+
for (const e of result.errors.slice(0, 20)) {
|
|
1724
|
+
ctx.logger.warn(`[Seeder] \u2717 ${e.message}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (result.errors.length > 20) {
|
|
1727
|
+
ctx.logger.warn(`[Seeder] \u2026and ${result.errors.length - 20} more error(s)`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1704
1730
|
} else {
|
|
1705
1731
|
ctx.logger.debug("[Seeder] No metadata service; using basic insert fallback");
|
|
1706
1732
|
for (const dataset of normalizedDatasets) {
|
|
@@ -1802,6 +1828,64 @@ var init_app_plugin = __esm({
|
|
|
1802
1828
|
this.name = `plugin.app.${appId}`;
|
|
1803
1829
|
this.version = sys?.version;
|
|
1804
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Resolve the identity bound to `os.user` / `os.org` for seed CEL values.
|
|
1833
|
+
*
|
|
1834
|
+
* On a fresh boot there are zero users until the first human sign-up
|
|
1835
|
+
* (which the SeedLoader runs *before*), so identity-derived seeds like
|
|
1836
|
+
* `owner_id: cel`os.user.id`` had nothing to resolve against and were
|
|
1837
|
+
* dropped silently. To make seeds deterministic and self-sufficient we
|
|
1838
|
+
* upsert a single non-loginable **system user** (`usr_system`) and bind
|
|
1839
|
+
* it as `os.user`.
|
|
1840
|
+
*
|
|
1841
|
+
* Why a dedicated system user rather than the login admin:
|
|
1842
|
+
* - `sys_user` is better-auth-managed and schema-locked (ADR-0010); the
|
|
1843
|
+
* password lives in `sys_account`, so a *loginable* admin can only be
|
|
1844
|
+
* minted through better-auth (the CLI does this via HTTP sign-up after
|
|
1845
|
+
* boot). A raw insert here would bypass those invariants.
|
|
1846
|
+
* - `usr_system` is an owner identity only (no credential row), analogous
|
|
1847
|
+
* to Salesforce's "Automated Process" user. The human admin is created
|
|
1848
|
+
* independently and need not be the seed owner.
|
|
1849
|
+
*
|
|
1850
|
+
* Idempotent: matches by the stable id, inserts once, reuses thereafter.
|
|
1851
|
+
* Failures are non-fatal (logged) — records that actually need `os.user`
|
|
1852
|
+
* then fail loudly in the loader with an actionable message.
|
|
1853
|
+
*/
|
|
1854
|
+
async ensureSeedIdentity(ql, logger) {
|
|
1855
|
+
const SYSTEM_USER_ID = import_system.SystemUserId.SYSTEM;
|
|
1856
|
+
const SYSTEM_USER_EMAIL = "system@objectstack.local";
|
|
1857
|
+
const identity = { user: { id: SYSTEM_USER_ID, role: "system", email: SYSTEM_USER_EMAIL } };
|
|
1858
|
+
const opts = { context: { isSystem: true } };
|
|
1859
|
+
try {
|
|
1860
|
+
const existing = await ql.find(
|
|
1861
|
+
"sys_user",
|
|
1862
|
+
{ where: { id: SYSTEM_USER_ID }, limit: 1 },
|
|
1863
|
+
opts
|
|
1864
|
+
);
|
|
1865
|
+
if (Array.isArray(existing) && existing.length > 0) {
|
|
1866
|
+
return identity;
|
|
1867
|
+
}
|
|
1868
|
+
await ql.insert(
|
|
1869
|
+
"sys_user",
|
|
1870
|
+
{
|
|
1871
|
+
id: SYSTEM_USER_ID,
|
|
1872
|
+
name: "System",
|
|
1873
|
+
email: SYSTEM_USER_EMAIL,
|
|
1874
|
+
email_verified: true,
|
|
1875
|
+
role: "system"
|
|
1876
|
+
},
|
|
1877
|
+
opts
|
|
1878
|
+
);
|
|
1879
|
+
logger.info(
|
|
1880
|
+
`[Seeder] Provisioned deterministic system user (${SYSTEM_USER_ID}) as seed owner \u2014 binds os.user for identity-derived seed values`
|
|
1881
|
+
);
|
|
1882
|
+
} catch (err) {
|
|
1883
|
+
logger.warn("[Seeder] Failed to ensure system seed user; os.user-dependent seeds may be dropped", {
|
|
1884
|
+
error: err?.message ?? String(err)
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
return identity;
|
|
1888
|
+
}
|
|
1805
1889
|
/**
|
|
1806
1890
|
* Emit a kernel hook so the control-plane `AppCatalogService` can
|
|
1807
1891
|
* upsert / delete the corresponding `sys_app` row. Silently no-ops
|
|
@@ -2088,294 +2172,88 @@ var init_standalone_stack = __esm({
|
|
|
2088
2172
|
}
|
|
2089
2173
|
});
|
|
2090
2174
|
|
|
2091
|
-
// src/cloud/
|
|
2092
|
-
var
|
|
2093
|
-
__export(
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
2097
|
-
derivePlatformSsoClientId: () => derivePlatformSsoClientId,
|
|
2098
|
-
derivePlatformSsoClientSecret: () => derivePlatformSsoClientSecret,
|
|
2099
|
-
hashPlatformSsoClientSecret: () => hashPlatformSsoClientSecret,
|
|
2100
|
-
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
2175
|
+
// src/cloud/environment-org-seed.ts
|
|
2176
|
+
var environment_org_seed_exports = {};
|
|
2177
|
+
__export(environment_org_seed_exports, {
|
|
2178
|
+
seedProjectMember: () => seedProjectMember,
|
|
2179
|
+
seedProjectOrganization: () => seedProjectOrganization
|
|
2101
2180
|
});
|
|
2102
|
-
function
|
|
2103
|
-
return
|
|
2104
|
-
}
|
|
2105
|
-
function derivePlatformSsoClientSecret(baseSecret, environmentId) {
|
|
2106
|
-
return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
|
|
2107
|
-
}
|
|
2108
|
-
function hashPlatformSsoClientSecret(plaintext) {
|
|
2109
|
-
return (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2110
|
-
}
|
|
2111
|
-
function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
|
|
2112
|
-
let host;
|
|
2113
|
-
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
|
|
2114
|
-
host = hostname;
|
|
2115
|
-
} else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
|
|
2116
|
-
const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
2117
|
-
const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
|
|
2118
|
-
host = `http://${hostWithPort}`;
|
|
2119
|
-
} else {
|
|
2120
|
-
host = `https://${hostname}`;
|
|
2121
|
-
}
|
|
2122
|
-
const trimmed = host.replace(/\/+$/, "");
|
|
2123
|
-
const path = basePath.replace(/\/+$/, "");
|
|
2124
|
-
return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
|
|
2125
|
-
}
|
|
2126
|
-
async function seedPlatformSsoClient(opts) {
|
|
2127
|
-
const { ql, environmentId, hostname, baseSecret, logger, throwOnError } = opts;
|
|
2128
|
-
if (!baseSecret) {
|
|
2129
|
-
logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { environmentId });
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
const clientId = derivePlatformSsoClientId(environmentId);
|
|
2133
|
-
const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, environmentId);
|
|
2134
|
-
const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
|
|
2135
|
-
const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
|
|
2136
|
-
let existing = null;
|
|
2181
|
+
async function seedProjectOrganization(kernel, seed, logger) {
|
|
2182
|
+
if (!seed?.id || !seed?.name) return "skipped";
|
|
2137
2183
|
try {
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2184
|
+
const ql = kernel.getService("objectql");
|
|
2185
|
+
if (!ql?.insert || !ql?.find) {
|
|
2186
|
+
logger?.warn?.("[seedProjectOrganization] objectql service unavailable", { orgId: seed.id });
|
|
2187
|
+
return "skipped";
|
|
2188
|
+
}
|
|
2189
|
+
try {
|
|
2190
|
+
const existing = await ql.find(SYS_ORG, { where: { id: seed.id } });
|
|
2191
|
+
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2192
|
+
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2193
|
+
} catch {
|
|
2194
|
+
}
|
|
2195
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2196
|
+
await ql.insert(SYS_ORG, {
|
|
2197
|
+
id: seed.id,
|
|
2198
|
+
name: seed.name,
|
|
2199
|
+
slug: seed.slug ?? null,
|
|
2200
|
+
logo: seed.logo ?? null,
|
|
2201
|
+
metadata: null,
|
|
2202
|
+
created_at: nowIso
|
|
2203
|
+
});
|
|
2204
|
+
logger?.info?.("[seedProjectOrganization] org seeded", {
|
|
2205
|
+
orgId: seed.id,
|
|
2206
|
+
name: seed.name
|
|
2207
|
+
});
|
|
2208
|
+
return "inserted";
|
|
2144
2209
|
} catch (err) {
|
|
2145
|
-
logger?.warn?.("[
|
|
2146
|
-
|
|
2210
|
+
logger?.warn?.("[seedProjectOrganization] failed (non-fatal)", {
|
|
2211
|
+
orgId: seed.id,
|
|
2147
2212
|
error: err?.message
|
|
2148
2213
|
});
|
|
2149
|
-
return;
|
|
2214
|
+
return "error";
|
|
2150
2215
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2216
|
+
}
|
|
2217
|
+
async function seedProjectMember(kernel, args, logger) {
|
|
2218
|
+
const { userId, organizationId } = args;
|
|
2219
|
+
const role = args.role ?? "member";
|
|
2220
|
+
if (!userId || !organizationId) return "skipped";
|
|
2221
|
+
try {
|
|
2222
|
+
const ql = kernel.getService("objectql");
|
|
2223
|
+
if (!ql?.insert || !ql?.find) {
|
|
2224
|
+
logger?.warn?.("[seedProjectMember] objectql service unavailable", { userId, organizationId });
|
|
2225
|
+
return "skipped";
|
|
2226
|
+
}
|
|
2154
2227
|
try {
|
|
2155
|
-
await ql.
|
|
2156
|
-
|
|
2157
|
-
name: `Project ${environmentId}`,
|
|
2158
|
-
client_id: clientId,
|
|
2159
|
-
client_secret: clientSecretStored,
|
|
2160
|
-
type: "web",
|
|
2161
|
-
redirect_uris: JSON.stringify(redirects),
|
|
2162
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
2163
|
-
response_types: JSON.stringify(["code"]),
|
|
2164
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
2165
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
2166
|
-
require_pkce: false,
|
|
2167
|
-
skip_consent: true,
|
|
2168
|
-
disabled: false,
|
|
2169
|
-
subject_type: "public",
|
|
2170
|
-
created_at: nowIso,
|
|
2171
|
-
updated_at: nowIso
|
|
2172
|
-
}, { context: { isSystem: true } });
|
|
2173
|
-
logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
|
|
2174
|
-
} catch (err) {
|
|
2175
|
-
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
2176
|
-
environmentId,
|
|
2177
|
-
error: err?.message
|
|
2228
|
+
const existing = await ql.find("sys_member", {
|
|
2229
|
+
where: { user_id: userId, organization_id: organizationId }
|
|
2178
2230
|
});
|
|
2179
|
-
|
|
2231
|
+
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2232
|
+
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2233
|
+
} catch {
|
|
2180
2234
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
type: existing.type || "web",
|
|
2195
|
-
redirect_uris: JSON.stringify(mergedRedirects),
|
|
2196
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
2197
|
-
response_types: JSON.stringify(["code"]),
|
|
2198
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
2199
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
2200
|
-
require_pkce: false,
|
|
2201
|
-
skip_consent: true,
|
|
2202
|
-
disabled: false,
|
|
2203
|
-
subject_type: "public",
|
|
2204
|
-
updated_at: nowIso
|
|
2205
|
-
};
|
|
2206
|
-
try {
|
|
2207
|
-
await ql.update(
|
|
2208
|
-
"sys_oauth_application",
|
|
2209
|
-
repairPatch,
|
|
2210
|
-
{ where: { id: existing.id } },
|
|
2211
|
-
{ context: { isSystem: true } }
|
|
2212
|
-
);
|
|
2213
|
-
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
2214
|
-
environmentId,
|
|
2215
|
-
clientId,
|
|
2216
|
-
redirect_uris: mergedRedirects
|
|
2235
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2236
|
+
const memId = `mem_${Math.random().toString(36).slice(2, 14)}`;
|
|
2237
|
+
await ql.insert("sys_member", {
|
|
2238
|
+
id: memId,
|
|
2239
|
+
organization_id: organizationId,
|
|
2240
|
+
user_id: userId,
|
|
2241
|
+
role,
|
|
2242
|
+
created_at: nowIso
|
|
2243
|
+
});
|
|
2244
|
+
logger?.info?.("[seedProjectMember] member seeded", {
|
|
2245
|
+
userId,
|
|
2246
|
+
organizationId,
|
|
2247
|
+
role
|
|
2217
2248
|
});
|
|
2249
|
+
return "inserted";
|
|
2218
2250
|
} catch (err) {
|
|
2219
|
-
logger?.warn?.("[
|
|
2220
|
-
|
|
2251
|
+
logger?.warn?.("[seedProjectMember] failed (non-fatal)", {
|
|
2252
|
+
userId,
|
|
2253
|
+
organizationId,
|
|
2221
2254
|
error: err?.message
|
|
2222
2255
|
});
|
|
2223
|
-
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
async function backfillPlatformSsoClients(opts) {
|
|
2227
|
-
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
2228
|
-
if (!baseSecret) {
|
|
2229
|
-
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
2230
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
2231
|
-
}
|
|
2232
|
-
let projects = [];
|
|
2233
|
-
try {
|
|
2234
|
-
const rows = await ql.find("sys_environment", {
|
|
2235
|
-
limit,
|
|
2236
|
-
fields: ["id", "hostname", "status"]
|
|
2237
|
-
}, { context: { isSystem: true } });
|
|
2238
|
-
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
2239
|
-
} catch (err) {
|
|
2240
|
-
logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
|
|
2241
|
-
error: err?.message
|
|
2242
|
-
});
|
|
2243
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
|
|
2244
|
-
}
|
|
2245
|
-
let seeded = 0;
|
|
2246
|
-
let alreadyExisted = 0;
|
|
2247
|
-
const failures = [];
|
|
2248
|
-
for (const p of projects) {
|
|
2249
|
-
if (!p?.id) continue;
|
|
2250
|
-
const before = await (async () => {
|
|
2251
|
-
try {
|
|
2252
|
-
const r = await ql.find("sys_oauth_application", {
|
|
2253
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
2254
|
-
limit: 1
|
|
2255
|
-
}, { context: { isSystem: true } });
|
|
2256
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
2257
|
-
return list[0] ?? null;
|
|
2258
|
-
} catch {
|
|
2259
|
-
return null;
|
|
2260
|
-
}
|
|
2261
|
-
})();
|
|
2262
|
-
try {
|
|
2263
|
-
await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
2264
|
-
if (before) alreadyExisted++;
|
|
2265
|
-
else {
|
|
2266
|
-
const after = await (async () => {
|
|
2267
|
-
try {
|
|
2268
|
-
const r = await ql.find("sys_oauth_application", {
|
|
2269
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
2270
|
-
limit: 1
|
|
2271
|
-
}, { context: { isSystem: true } });
|
|
2272
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
2273
|
-
return list[0] ?? null;
|
|
2274
|
-
} catch (err) {
|
|
2275
|
-
return { _readErr: err?.message };
|
|
2276
|
-
}
|
|
2277
|
-
})();
|
|
2278
|
-
if (after && !after._readErr) seeded++;
|
|
2279
|
-
else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
2280
|
-
}
|
|
2281
|
-
} catch (err) {
|
|
2282
|
-
failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
2286
|
-
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
2287
|
-
}
|
|
2288
|
-
var import_node_crypto, PLATFORM_SSO_PROVIDER_ID;
|
|
2289
|
-
var init_platform_sso = __esm({
|
|
2290
|
-
"src/cloud/platform-sso.ts"() {
|
|
2291
|
-
"use strict";
|
|
2292
|
-
import_node_crypto = require("crypto");
|
|
2293
|
-
PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
2294
|
-
}
|
|
2295
|
-
});
|
|
2296
|
-
|
|
2297
|
-
// src/cloud/environment-org-seed.ts
|
|
2298
|
-
var environment_org_seed_exports = {};
|
|
2299
|
-
__export(environment_org_seed_exports, {
|
|
2300
|
-
seedProjectMember: () => seedProjectMember,
|
|
2301
|
-
seedProjectOrganization: () => seedProjectOrganization
|
|
2302
|
-
});
|
|
2303
|
-
async function seedProjectOrganization(kernel, seed, logger) {
|
|
2304
|
-
if (!seed?.id || !seed?.name) return "skipped";
|
|
2305
|
-
try {
|
|
2306
|
-
const ql = kernel.getService("objectql");
|
|
2307
|
-
if (!ql?.insert || !ql?.find) {
|
|
2308
|
-
logger?.warn?.("[seedProjectOrganization] objectql service unavailable", { orgId: seed.id });
|
|
2309
|
-
return "skipped";
|
|
2310
|
-
}
|
|
2311
|
-
try {
|
|
2312
|
-
const existing = await ql.find(SYS_ORG, { where: { id: seed.id } });
|
|
2313
|
-
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2314
|
-
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2315
|
-
} catch {
|
|
2316
|
-
}
|
|
2317
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2318
|
-
await ql.insert(SYS_ORG, {
|
|
2319
|
-
id: seed.id,
|
|
2320
|
-
name: seed.name,
|
|
2321
|
-
slug: seed.slug ?? null,
|
|
2322
|
-
logo: seed.logo ?? null,
|
|
2323
|
-
metadata: null,
|
|
2324
|
-
created_at: nowIso
|
|
2325
|
-
});
|
|
2326
|
-
logger?.info?.("[seedProjectOrganization] org seeded", {
|
|
2327
|
-
orgId: seed.id,
|
|
2328
|
-
name: seed.name
|
|
2329
|
-
});
|
|
2330
|
-
return "inserted";
|
|
2331
|
-
} catch (err) {
|
|
2332
|
-
logger?.warn?.("[seedProjectOrganization] failed (non-fatal)", {
|
|
2333
|
-
orgId: seed.id,
|
|
2334
|
-
error: err?.message
|
|
2335
|
-
});
|
|
2336
|
-
return "error";
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
async function seedProjectMember(kernel, args, logger) {
|
|
2340
|
-
const { userId, organizationId } = args;
|
|
2341
|
-
const role = args.role ?? "member";
|
|
2342
|
-
if (!userId || !organizationId) return "skipped";
|
|
2343
|
-
try {
|
|
2344
|
-
const ql = kernel.getService("objectql");
|
|
2345
|
-
if (!ql?.insert || !ql?.find) {
|
|
2346
|
-
logger?.warn?.("[seedProjectMember] objectql service unavailable", { userId, organizationId });
|
|
2347
|
-
return "skipped";
|
|
2348
|
-
}
|
|
2349
|
-
try {
|
|
2350
|
-
const existing = await ql.find("sys_member", {
|
|
2351
|
-
where: { user_id: userId, organization_id: organizationId }
|
|
2352
|
-
});
|
|
2353
|
-
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
2354
|
-
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
2355
|
-
} catch {
|
|
2356
|
-
}
|
|
2357
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2358
|
-
const memId = `mem_${Math.random().toString(36).slice(2, 14)}`;
|
|
2359
|
-
await ql.insert("sys_member", {
|
|
2360
|
-
id: memId,
|
|
2361
|
-
organization_id: organizationId,
|
|
2362
|
-
user_id: userId,
|
|
2363
|
-
role,
|
|
2364
|
-
created_at: nowIso
|
|
2365
|
-
});
|
|
2366
|
-
logger?.info?.("[seedProjectMember] member seeded", {
|
|
2367
|
-
userId,
|
|
2368
|
-
organizationId,
|
|
2369
|
-
role
|
|
2370
|
-
});
|
|
2371
|
-
return "inserted";
|
|
2372
|
-
} catch (err) {
|
|
2373
|
-
logger?.warn?.("[seedProjectMember] failed (non-fatal)", {
|
|
2374
|
-
userId,
|
|
2375
|
-
organizationId,
|
|
2376
|
-
error: err?.message
|
|
2377
|
-
});
|
|
2378
|
-
return "error";
|
|
2256
|
+
return "error";
|
|
2379
2257
|
}
|
|
2380
2258
|
}
|
|
2381
2259
|
var SYS_ORG;
|
|
@@ -2451,6 +2329,7 @@ __export(index_exports, {
|
|
|
2451
2329
|
DEFAULT_CLOUD_URL: () => DEFAULT_CLOUD_URL,
|
|
2452
2330
|
DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
|
|
2453
2331
|
DriverPlugin: () => DriverPlugin,
|
|
2332
|
+
ExternalValidationPlugin: () => ExternalValidationPlugin,
|
|
2454
2333
|
FileArtifactApiClient: () => FileArtifactApiClient,
|
|
2455
2334
|
HttpDispatcher: () => HttpDispatcher,
|
|
2456
2335
|
HttpServer: () => HttpServer,
|
|
@@ -2479,7 +2358,7 @@ __export(index_exports, {
|
|
|
2479
2358
|
SandboxError: () => SandboxError,
|
|
2480
2359
|
SeedLoaderService: () => SeedLoaderService,
|
|
2481
2360
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
2482
|
-
_resetEnvDeprecationWarnings: () =>
|
|
2361
|
+
_resetEnvDeprecationWarnings: () => import_types5._resetEnvDeprecationWarnings,
|
|
2483
2362
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
2484
2363
|
backfillPlatformSsoClients: () => backfillPlatformSsoClients,
|
|
2485
2364
|
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
@@ -2489,6 +2368,7 @@ __export(index_exports, {
|
|
|
2489
2368
|
collectBundleHooks: () => collectBundleHooks,
|
|
2490
2369
|
createDefaultHostConfig: () => createDefaultHostConfig,
|
|
2491
2370
|
createDispatcherPlugin: () => createDispatcherPlugin,
|
|
2371
|
+
createExternalValidationPlugin: () => createExternalValidationPlugin,
|
|
2492
2372
|
createObjectOSStack: () => createObjectOSStack,
|
|
2493
2373
|
createRestApiPlugin: () => import_rest.createRestApiPlugin,
|
|
2494
2374
|
createStandaloneStack: () => createStandaloneStack,
|
|
@@ -2504,7 +2384,7 @@ __export(index_exports, {
|
|
|
2504
2384
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
2505
2385
|
parseTraceparent: () => parseTraceparent,
|
|
2506
2386
|
readArtifactSource: () => readArtifactSource,
|
|
2507
|
-
readEnvWithDeprecation: () =>
|
|
2387
|
+
readEnvWithDeprecation: () => import_types5.readEnvWithDeprecation,
|
|
2508
2388
|
resolveCloudUrl: () => resolveCloudUrl,
|
|
2509
2389
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
2510
2390
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
@@ -2625,11 +2505,170 @@ init_driver_plugin();
|
|
|
2625
2505
|
init_app_plugin();
|
|
2626
2506
|
init_seed_loader();
|
|
2627
2507
|
|
|
2508
|
+
// src/external-validation-plugin.ts
|
|
2509
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
2510
|
+
var ExternalValidationPlugin = class {
|
|
2511
|
+
constructor() {
|
|
2512
|
+
this.name = "com.objectstack.external-validation";
|
|
2513
|
+
this.type = "standard";
|
|
2514
|
+
this.version = "1.0.0";
|
|
2515
|
+
/** Active background drift-check timers, keyed by datasource name. */
|
|
2516
|
+
this.driftTimers = /* @__PURE__ */ new Map();
|
|
2517
|
+
this.init = (_ctx) => {
|
|
2518
|
+
};
|
|
2519
|
+
this.start = (ctx) => {
|
|
2520
|
+
ctx.hook("kernel:ready", async () => {
|
|
2521
|
+
await this.runValidation(ctx);
|
|
2522
|
+
await this.scheduleDriftChecks(ctx);
|
|
2523
|
+
});
|
|
2524
|
+
};
|
|
2525
|
+
/** Tear down background drift-check timers (idempotent). */
|
|
2526
|
+
this.stop = () => {
|
|
2527
|
+
for (const timer of this.driftTimers.values()) clearInterval(timer);
|
|
2528
|
+
this.driftTimers.clear();
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
/** Exposed for testing; invoked from the kernel:ready handler. */
|
|
2532
|
+
async runValidation(ctx) {
|
|
2533
|
+
const svc = safeGet(ctx, "external-datasource");
|
|
2534
|
+
if (!svc?.validateAll) {
|
|
2535
|
+
ctx.logger?.debug?.("[external-validation] service not registered; skipping");
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const metadata = safeGet(ctx, "metadata");
|
|
2539
|
+
let report;
|
|
2540
|
+
try {
|
|
2541
|
+
report = await svc.validateAll();
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
ctx.logger?.warn?.("[external-validation] validateAll failed", { err });
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
const failures = report.results.filter((r) => !r.ok);
|
|
2547
|
+
if (failures.length === 0) {
|
|
2548
|
+
ctx.logger?.info?.("[external-validation] all federated objects match their remote schema", {
|
|
2549
|
+
objects: report.results.length
|
|
2550
|
+
});
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
for (const r of failures) {
|
|
2554
|
+
const mode = await resolveOnMismatch(metadata, r.datasource);
|
|
2555
|
+
if (mode === "ignore") continue;
|
|
2556
|
+
if (mode === "warn") {
|
|
2557
|
+
ctx.logger?.warn?.("[external-validation] external schema drift", {
|
|
2558
|
+
datasource: r.datasource,
|
|
2559
|
+
object: r.object,
|
|
2560
|
+
diffs: r.diffs
|
|
2561
|
+
});
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
throw new import_shared.ExternalSchemaMismatchError(r.datasource, r.object, r.diffs);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Arm a background drift checker for every federated datasource that declares
|
|
2569
|
+
* `external.validation.checkIntervalMs`. Each fires on its own interval and
|
|
2570
|
+
* emits `external.schema.drift` events — it never throws or aborts the
|
|
2571
|
+
* process, since drift past boot is observational, not fatal.
|
|
2572
|
+
*
|
|
2573
|
+
* No-op when metadata can't be enumerated or no datasource opts in. Re-arming
|
|
2574
|
+
* (e.g. a second `kernel:ready`) first clears existing timers so intervals
|
|
2575
|
+
* don't accumulate.
|
|
2576
|
+
*/
|
|
2577
|
+
async scheduleDriftChecks(ctx) {
|
|
2578
|
+
this.stop();
|
|
2579
|
+
const metadata = safeGet(ctx, "metadata");
|
|
2580
|
+
if (!metadata?.list) return;
|
|
2581
|
+
let datasources;
|
|
2582
|
+
try {
|
|
2583
|
+
datasources = await metadata.list("datasource");
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
ctx.logger?.warn?.("[external-validation] could not list datasources for drift checks", { err });
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
for (const def of datasources) {
|
|
2589
|
+
const interval = def?.external?.validation?.checkIntervalMs;
|
|
2590
|
+
const name = def?.name;
|
|
2591
|
+
if (!name || typeof interval !== "number" || interval <= 0) continue;
|
|
2592
|
+
const timer = setInterval(() => {
|
|
2593
|
+
void this.runDriftCheck(ctx, name);
|
|
2594
|
+
}, interval);
|
|
2595
|
+
timer.unref?.();
|
|
2596
|
+
this.driftTimers.set(name, timer);
|
|
2597
|
+
ctx.logger?.info?.("[external-validation] armed background drift check", {
|
|
2598
|
+
datasource: name,
|
|
2599
|
+
intervalMs: interval
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Re-validate one datasource's federated objects and emit an
|
|
2605
|
+
* `external.schema.drift` event per mismatch. Exposed for testing; invoked
|
|
2606
|
+
* from the interval armed by {@link scheduleDriftChecks}. Never throws.
|
|
2607
|
+
*
|
|
2608
|
+
* @returns the number of drift events emitted.
|
|
2609
|
+
*/
|
|
2610
|
+
async runDriftCheck(ctx, datasource) {
|
|
2611
|
+
const svc = safeGet(ctx, "external-datasource");
|
|
2612
|
+
if (!svc?.validateAll) return 0;
|
|
2613
|
+
let report;
|
|
2614
|
+
try {
|
|
2615
|
+
report = await svc.validateAll();
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
ctx.logger?.warn?.("[external-validation] drift check validateAll failed", {
|
|
2618
|
+
datasource,
|
|
2619
|
+
err
|
|
2620
|
+
});
|
|
2621
|
+
return 0;
|
|
2622
|
+
}
|
|
2623
|
+
const drifted = report.results.filter((r) => !r.ok && r.datasource === datasource);
|
|
2624
|
+
for (const r of drifted) {
|
|
2625
|
+
const event = {
|
|
2626
|
+
datasource: r.datasource,
|
|
2627
|
+
object: r.object,
|
|
2628
|
+
diffs: r.diffs
|
|
2629
|
+
};
|
|
2630
|
+
try {
|
|
2631
|
+
await ctx.trigger("external.schema.drift", event);
|
|
2632
|
+
} catch (err) {
|
|
2633
|
+
ctx.logger?.warn?.("[external-validation] failed to emit drift event", {
|
|
2634
|
+
datasource,
|
|
2635
|
+
object: r.object,
|
|
2636
|
+
err
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
if (drifted.length > 0) {
|
|
2641
|
+
ctx.logger?.warn?.("[external-validation] background drift detected", {
|
|
2642
|
+
datasource,
|
|
2643
|
+
objects: drifted.map((r) => r.object)
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
return drifted.length;
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
function createExternalValidationPlugin() {
|
|
2650
|
+
return new ExternalValidationPlugin();
|
|
2651
|
+
}
|
|
2652
|
+
async function resolveOnMismatch(metadata, datasource) {
|
|
2653
|
+
try {
|
|
2654
|
+
const ds = await metadata?.get?.("datasource", datasource);
|
|
2655
|
+
return ds?.external?.validation?.onMismatch ?? "fail";
|
|
2656
|
+
} catch {
|
|
2657
|
+
return "fail";
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function safeGet(ctx, name) {
|
|
2661
|
+
try {
|
|
2662
|
+
return ctx.getService(name);
|
|
2663
|
+
} catch {
|
|
2664
|
+
return void 0;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2628
2668
|
// src/http-dispatcher.ts
|
|
2629
2669
|
var import_core2 = require("@objectstack/core");
|
|
2630
|
-
var
|
|
2631
|
-
var
|
|
2632
|
-
var import_shared = require("@objectstack/spec/shared");
|
|
2670
|
+
var import_system2 = require("@objectstack/spec/system");
|
|
2671
|
+
var import_shared2 = require("@objectstack/spec/shared");
|
|
2633
2672
|
init_package_state_store();
|
|
2634
2673
|
|
|
2635
2674
|
// src/security/resolve-execution-context.ts
|
|
@@ -3092,7 +3131,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3092
3131
|
}
|
|
3093
3132
|
}
|
|
3094
3133
|
try {
|
|
3095
|
-
const authService = await this.getService(
|
|
3134
|
+
const authService = await this.getService(import_system2.CoreServiceName.enum.auth);
|
|
3096
3135
|
const sessionData = await authService?.api?.getSession?.({
|
|
3097
3136
|
headers: context.request?.headers
|
|
3098
3137
|
});
|
|
@@ -3173,7 +3212,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3173
3212
|
let userId;
|
|
3174
3213
|
let activeOrganizationId;
|
|
3175
3214
|
try {
|
|
3176
|
-
const authService = await this.resolveService(
|
|
3215
|
+
const authService = await this.resolveService(import_system2.CoreServiceName.enum.auth);
|
|
3177
3216
|
const sessionData = await authService?.api?.getSession?.({
|
|
3178
3217
|
headers: context.request?.headers
|
|
3179
3218
|
});
|
|
@@ -3242,21 +3281,21 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3242
3281
|
queueSvc,
|
|
3243
3282
|
jobSvc
|
|
3244
3283
|
] = await Promise.all([
|
|
3245
|
-
this.resolveService(
|
|
3246
|
-
this.resolveService(
|
|
3247
|
-
this.resolveService(
|
|
3248
|
-
this.resolveService(
|
|
3249
|
-
this.resolveService(
|
|
3250
|
-
this.resolveService(
|
|
3251
|
-
this.resolveService(
|
|
3252
|
-
this.resolveService(
|
|
3253
|
-
this.resolveService(
|
|
3254
|
-
this.resolveService(
|
|
3255
|
-
this.resolveService(
|
|
3256
|
-
this.resolveService(
|
|
3257
|
-
this.resolveService(
|
|
3258
|
-
this.resolveService(
|
|
3259
|
-
this.resolveService(
|
|
3284
|
+
this.resolveService(import_system2.CoreServiceName.enum.auth),
|
|
3285
|
+
this.resolveService(import_system2.CoreServiceName.enum.graphql),
|
|
3286
|
+
this.resolveService(import_system2.CoreServiceName.enum.search),
|
|
3287
|
+
this.resolveService(import_system2.CoreServiceName.enum.realtime),
|
|
3288
|
+
this.resolveService(import_system2.CoreServiceName.enum["file-storage"]),
|
|
3289
|
+
this.resolveService(import_system2.CoreServiceName.enum.analytics),
|
|
3290
|
+
this.resolveService(import_system2.CoreServiceName.enum.workflow),
|
|
3291
|
+
this.resolveService(import_system2.CoreServiceName.enum.ai),
|
|
3292
|
+
this.resolveService(import_system2.CoreServiceName.enum.notification),
|
|
3293
|
+
this.resolveService(import_system2.CoreServiceName.enum.i18n),
|
|
3294
|
+
this.resolveService(import_system2.CoreServiceName.enum.ui),
|
|
3295
|
+
this.resolveService(import_system2.CoreServiceName.enum.automation),
|
|
3296
|
+
this.resolveService(import_system2.CoreServiceName.enum.cache),
|
|
3297
|
+
this.resolveService(import_system2.CoreServiceName.enum.queue),
|
|
3298
|
+
this.resolveService(import_system2.CoreServiceName.enum.job)
|
|
3260
3299
|
]);
|
|
3261
3300
|
const hasAuth = !!authSvc;
|
|
3262
3301
|
const hasGraphQL = !!(graphqlSvc || this.kernel.graphql);
|
|
@@ -3373,7 +3412,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3373
3412
|
* path: sub-path after /auth/
|
|
3374
3413
|
*/
|
|
3375
3414
|
async handleAuth(path, method, body, context) {
|
|
3376
|
-
const authService = await this.getService(
|
|
3415
|
+
const authService = await this.getService(import_system2.CoreServiceName.enum.auth);
|
|
3377
3416
|
if (authService && typeof authService.handler === "function") {
|
|
3378
3417
|
const response = await authService.handler(context.request, context.response);
|
|
3379
3418
|
return { handled: true, result: response };
|
|
@@ -3457,10 +3496,21 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3457
3496
|
}
|
|
3458
3497
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
3459
3498
|
}
|
|
3499
|
+
if (parts.length === 4 && (parts[0] === "objects" || parts[0] === "object") && parts[2] === "state" && (!method || method === "GET")) {
|
|
3500
|
+
const name = parts[1];
|
|
3501
|
+
const field = parts[3];
|
|
3502
|
+
const from = query?.from !== void 0 ? String(query.from) : void 0;
|
|
3503
|
+
const qlService = await this.getObjectQLService();
|
|
3504
|
+
const schema = qlService?.registry?.getObject(name);
|
|
3505
|
+
if (!schema) return { handled: true, response: this.error("Object not found", 404) };
|
|
3506
|
+
const { legalNextStates } = await import("@objectstack/objectql");
|
|
3507
|
+
const next = from === void 0 ? null : legalNextStates(schema, field, from);
|
|
3508
|
+
return { handled: true, response: this.success({ object: name, field, from: from ?? null, next }) };
|
|
3509
|
+
}
|
|
3460
3510
|
if (parts.length >= 3 && parts[parts.length - 1] === "published" && (!method || method === "GET")) {
|
|
3461
3511
|
const type = parts[0];
|
|
3462
3512
|
const name = parts.slice(1, -1).join("/");
|
|
3463
|
-
const metadataService = await this.getService(
|
|
3513
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3464
3514
|
if (metadataService && typeof metadataService.getPublished === "function") {
|
|
3465
3515
|
const data = await metadataService.getPublished(type, name);
|
|
3466
3516
|
if (data === void 0) return { handled: true, response: this.error("Not found", 404) };
|
|
@@ -3534,7 +3584,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3534
3584
|
}
|
|
3535
3585
|
return { handled: true, response: this.error("Not found", 404) };
|
|
3536
3586
|
}
|
|
3537
|
-
const singularType = (0,
|
|
3587
|
+
const singularType = (0, import_shared2.pluralToSingular)(type);
|
|
3538
3588
|
const protocol = await this.resolveService("protocol");
|
|
3539
3589
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
3540
3590
|
try {
|
|
@@ -3571,7 +3621,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3571
3621
|
} catch {
|
|
3572
3622
|
}
|
|
3573
3623
|
}
|
|
3574
|
-
const metadataService = await this.getService(
|
|
3624
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3575
3625
|
if (metadataService && typeof metadataService.list === "function") {
|
|
3576
3626
|
try {
|
|
3577
3627
|
let items = await metadataService.list(typeOrName);
|
|
@@ -3705,7 +3755,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3705
3755
|
* path: sub-path after /analytics/
|
|
3706
3756
|
*/
|
|
3707
3757
|
async handleAnalytics(path, method, body, _context) {
|
|
3708
|
-
const analyticsService = await this.getService(
|
|
3758
|
+
const analyticsService = await this.getService(import_system2.CoreServiceName.enum.analytics);
|
|
3709
3759
|
if (!analyticsService) return { handled: false };
|
|
3710
3760
|
const m = method.toUpperCase();
|
|
3711
3761
|
const subPath = path.replace(/^\/+/, "");
|
|
@@ -3735,7 +3785,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3735
3785
|
* GET /labels/:object?locale=xx → getFieldLabels (locale from query)
|
|
3736
3786
|
*/
|
|
3737
3787
|
async handleI18n(path, method, query, _context) {
|
|
3738
|
-
const i18nService = await this.getService(
|
|
3788
|
+
const i18nService = await this.getService(import_system2.CoreServiceName.enum.i18n);
|
|
3739
3789
|
if (!i18nService) return { handled: true, response: this.error("i18n service not available", 501) };
|
|
3740
3790
|
const m = method.toUpperCase();
|
|
3741
3791
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -3845,7 +3895,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3845
3895
|
}
|
|
3846
3896
|
if (parts.length === 2 && parts[1] === "publish" && m === "POST") {
|
|
3847
3897
|
const id = decodeURIComponent(parts[0]);
|
|
3848
|
-
const metadataService = await this.getService(
|
|
3898
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3849
3899
|
if (metadataService && typeof metadataService.publishPackage === "function") {
|
|
3850
3900
|
const result = await metadataService.publishPackage(id, body || {});
|
|
3851
3901
|
return { handled: true, response: this.success(result) };
|
|
@@ -3854,7 +3904,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3854
3904
|
}
|
|
3855
3905
|
if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
|
|
3856
3906
|
const id = decodeURIComponent(parts[0]);
|
|
3857
|
-
const metadataService = await this.getService(
|
|
3907
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3858
3908
|
if (metadataService && typeof metadataService.revertPackage === "function") {
|
|
3859
3909
|
await metadataService.revertPackage(id);
|
|
3860
3910
|
return { handled: true, response: this.success({ success: true }) };
|
|
@@ -3929,13 +3979,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3929
3979
|
}
|
|
3930
3980
|
return out;
|
|
3931
3981
|
};
|
|
3932
|
-
const exportPluralKeys = Object.keys(
|
|
3982
|
+
const exportPluralKeys = Object.keys(import_shared2.PLURAL_TO_SINGULAR).filter(
|
|
3933
3983
|
(k) => k !== "datasources" && k !== "emailTemplates"
|
|
3934
3984
|
);
|
|
3935
3985
|
const manifest = {};
|
|
3936
3986
|
let total = 0;
|
|
3937
3987
|
for (const plural of exportPluralKeys) {
|
|
3938
|
-
const singular =
|
|
3988
|
+
const singular = import_shared2.PLURAL_TO_SINGULAR[plural];
|
|
3939
3989
|
let items = [];
|
|
3940
3990
|
try {
|
|
3941
3991
|
const res = await protocol.getMetaItems({ type: singular, packageId, organizationId });
|
|
@@ -4007,7 +4057,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4007
4057
|
*/
|
|
4008
4058
|
async resolveActiveOrganizationId(context) {
|
|
4009
4059
|
try {
|
|
4010
|
-
const authService = await this.resolveService(
|
|
4060
|
+
const authService = await this.resolveService(import_system2.CoreServiceName.enum.auth);
|
|
4011
4061
|
const rawHeaders = context.request?.headers;
|
|
4012
4062
|
let headers = rawHeaders;
|
|
4013
4063
|
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
@@ -4030,1305 +4080,14 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4030
4080
|
return void 0;
|
|
4031
4081
|
}
|
|
4032
4082
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
4042
|
-
if (v == null) continue;
|
|
4043
|
-
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
4044
|
-
}
|
|
4045
|
-
headers = h;
|
|
4046
|
-
} catch {
|
|
4047
|
-
headers = rawHeaders;
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
const sessionData = await (authService?.auth?.api?.getSession ?? authService?.api?.getSession)?.call(
|
|
4051
|
-
authService?.auth?.api ?? authService?.api,
|
|
4052
|
-
{ headers }
|
|
4053
|
-
);
|
|
4054
|
-
return sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
4055
|
-
} catch (e) {
|
|
4056
|
-
return void 0;
|
|
4057
|
-
}
|
|
4058
|
-
}
|
|
4059
|
-
async handleCloud(path, method, body, query, _context) {
|
|
4060
|
-
const m = method.toUpperCase();
|
|
4061
|
-
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
4062
|
-
const qlService = await this.getObjectQLService();
|
|
4063
|
-
const ql = qlService ?? await this.resolveService("objectql");
|
|
4064
|
-
if (!ql) {
|
|
4065
|
-
return { handled: true, response: this.error("Project service not available (ObjectQL missing)", 503) };
|
|
4066
|
-
}
|
|
4067
|
-
const ENV = "sys_environment";
|
|
4068
|
-
const CRED = "sys_environment_credential";
|
|
4069
|
-
const MEM = "sys_environment_member";
|
|
4070
|
-
const PKG_INSTALL = "sys_package_installation";
|
|
4071
|
-
const PKG = "sys_package";
|
|
4072
|
-
const PKG_VERSION = "sys_package_version";
|
|
4073
|
-
const ensureSysPackage = async (manifestId, ownerOrgId, createdBy, manifest) => {
|
|
4074
|
-
const existing = await ql.findOne(PKG, { where: { manifest_id: manifestId } });
|
|
4075
|
-
if (existing?.id) return existing.id;
|
|
4076
|
-
const id = randomUUID();
|
|
4077
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4078
|
-
await ql.insert(PKG, {
|
|
4079
|
-
id,
|
|
4080
|
-
manifest_id: manifestId,
|
|
4081
|
-
owner_org_id: ownerOrgId,
|
|
4082
|
-
display_name: manifest?.name ?? manifestId,
|
|
4083
|
-
description: manifest?.description ?? null,
|
|
4084
|
-
visibility: "private",
|
|
4085
|
-
created_by: createdBy,
|
|
4086
|
-
created_at: nowIso,
|
|
4087
|
-
updated_at: nowIso
|
|
4088
|
-
});
|
|
4089
|
-
return id;
|
|
4090
|
-
};
|
|
4091
|
-
const ensureSysPackageVersion = async (packageId, version, createdBy, manifest) => {
|
|
4092
|
-
const existing = await ql.findOne(PKG_VERSION, {
|
|
4093
|
-
where: { package_id: packageId, version }
|
|
4094
|
-
});
|
|
4095
|
-
if (existing?.id) return existing.id;
|
|
4096
|
-
const id = randomUUID();
|
|
4097
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4098
|
-
await ql.insert(PKG_VERSION, {
|
|
4099
|
-
id,
|
|
4100
|
-
package_id: packageId,
|
|
4101
|
-
version,
|
|
4102
|
-
status: "published",
|
|
4103
|
-
manifest_json: manifest ? JSON.stringify(manifest) : null,
|
|
4104
|
-
is_pre_release: false,
|
|
4105
|
-
published_at: nowIso,
|
|
4106
|
-
published_by: createdBy,
|
|
4107
|
-
created_by: createdBy,
|
|
4108
|
-
created_at: nowIso,
|
|
4109
|
-
updated_at: nowIso
|
|
4110
|
-
});
|
|
4111
|
-
return id;
|
|
4112
|
-
};
|
|
4113
|
-
const findInstallByManifestId = async (envId, manifestId) => {
|
|
4114
|
-
const pkgRow = await ql.findOne(PKG, { where: { manifest_id: manifestId } });
|
|
4115
|
-
if (!pkgRow?.id) return null;
|
|
4116
|
-
return await ql.findOne(PKG_INSTALL, {
|
|
4117
|
-
where: { environment_id: envId, package_id: pkgRow.id }
|
|
4118
|
-
});
|
|
4119
|
-
};
|
|
4120
|
-
const toShortName = (driverId) => {
|
|
4121
|
-
const prefix = "com.objectstack.driver.";
|
|
4122
|
-
return driverId.startsWith(prefix) ? driverId.slice(prefix.length) : driverId;
|
|
4123
|
-
};
|
|
4124
|
-
const listRegisteredDrivers = () => {
|
|
4125
|
-
const services = this.getServicesMap();
|
|
4126
|
-
const registry = services["project-provisioning-adapters"];
|
|
4127
|
-
if (registry && typeof registry.list === "function") {
|
|
4128
|
-
try {
|
|
4129
|
-
const adapters = registry.list();
|
|
4130
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4131
|
-
const drivers2 = [];
|
|
4132
|
-
for (const adapter of adapters ?? []) {
|
|
4133
|
-
const name = adapter?.driver;
|
|
4134
|
-
if (!name || seen.has(name)) continue;
|
|
4135
|
-
seen.add(name);
|
|
4136
|
-
drivers2.push({ name, driverId: `com.objectstack.driver.${name}` });
|
|
4137
|
-
}
|
|
4138
|
-
if (drivers2.length > 0) return drivers2;
|
|
4139
|
-
} catch {
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
const drivers = [];
|
|
4143
|
-
for (const [serviceKey, svc] of Object.entries(services)) {
|
|
4144
|
-
if (!serviceKey.startsWith("driver.")) continue;
|
|
4145
|
-
const raw = serviceKey.slice("driver.".length);
|
|
4146
|
-
if (!raw || raw === "unknown") continue;
|
|
4147
|
-
const driverId = svc?.name ?? raw;
|
|
4148
|
-
drivers.push({ name: toShortName(driverId), driverId });
|
|
4149
|
-
}
|
|
4150
|
-
return drivers;
|
|
4151
|
-
};
|
|
4152
|
-
const resolveDriver = (requested) => {
|
|
4153
|
-
const registered = listRegisteredDrivers();
|
|
4154
|
-
if (requested) {
|
|
4155
|
-
const wanted = String(requested).toLowerCase();
|
|
4156
|
-
return registered.find((d) => d.name === wanted || d.driverId === wanted);
|
|
4157
|
-
}
|
|
4158
|
-
return registered.find((d) => d.name === "turso") ?? registered.find((d) => d.name === "memory") ?? registered[0];
|
|
4159
|
-
};
|
|
4160
|
-
const buildDatabaseUrl = (driverName, environmentId) => {
|
|
4161
|
-
const dbName = `env-${environmentId}`;
|
|
4162
|
-
switch (driverName) {
|
|
4163
|
-
case "memory":
|
|
4164
|
-
return `memory://${dbName}`;
|
|
4165
|
-
case "turso":
|
|
4166
|
-
return `libsql://${dbName}.mock-turso.local`;
|
|
4167
|
-
default:
|
|
4168
|
-
return `${driverName}://${dbName}`;
|
|
4169
|
-
}
|
|
4170
|
-
};
|
|
4171
|
-
const getRealAdapter = async (driverName) => {
|
|
4172
|
-
try {
|
|
4173
|
-
const registry = await this.resolveService("project-provisioning-adapters");
|
|
4174
|
-
const aliases = { sql: "sqlite" };
|
|
4175
|
-
const effective = aliases[driverName] ?? driverName;
|
|
4176
|
-
return registry?.get?.(effective) ?? registry?.get?.(driverName);
|
|
4177
|
-
} catch {
|
|
4178
|
-
return void 0;
|
|
4179
|
-
}
|
|
4180
|
-
};
|
|
4181
|
-
const findOne = async (obj, where) => {
|
|
4182
|
-
let rows = await ql.find(obj, { where });
|
|
4183
|
-
if (rows && rows.value) rows = rows.value;
|
|
4184
|
-
if (!Array.isArray(rows)) return void 0;
|
|
4185
|
-
return rows[0];
|
|
4186
|
-
};
|
|
4187
|
-
const cleanProjectRow = (row) => {
|
|
4188
|
-
if (!row) return row;
|
|
4189
|
-
let metadata = row.metadata;
|
|
4190
|
-
if (typeof metadata === "string") {
|
|
4191
|
-
try {
|
|
4192
|
-
metadata = JSON.parse(metadata);
|
|
4193
|
-
} catch {
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
return { ...row, metadata };
|
|
4197
|
-
};
|
|
4198
|
-
try {
|
|
4199
|
-
if (parts.length === 1 && parts[0] === "drivers" && m === "GET") {
|
|
4200
|
-
const drivers = listRegisteredDrivers();
|
|
4201
|
-
return { handled: true, response: this.success({ drivers, total: drivers.length }) };
|
|
4202
|
-
}
|
|
4203
|
-
if (parts.length === 1 && parts[0] === "templates" && m === "GET") {
|
|
4204
|
-
try {
|
|
4205
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4206
|
-
const templates = seeder?.listTemplates?.() ?? [];
|
|
4207
|
-
return { handled: true, response: this.success({ templates, total: templates.length }) };
|
|
4208
|
-
} catch (err) {
|
|
4209
|
-
try {
|
|
4210
|
-
console.error("[HttpDispatcher] /cloud/templates: failed to resolve template-seeder:", err?.message ?? err);
|
|
4211
|
-
} catch {
|
|
4212
|
-
}
|
|
4213
|
-
return { handled: true, response: this.success({ templates: [], total: 0 }) };
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
if (parts.length === 3 && parts[0] === "admin" && parts[1] === "platform-sso" && parts[2] === "backfill" && m === "POST") {
|
|
4217
|
-
const baseSecret = ((0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
4218
|
-
if (!baseSecret) {
|
|
4219
|
-
return { handled: true, response: this.error("OS_AUTH_SECRET not configured on this worker", 503) };
|
|
4220
|
-
}
|
|
4221
|
-
const rawHeaders = _context?.request?.headers;
|
|
4222
|
-
let authHeader;
|
|
4223
|
-
if (rawHeaders && typeof rawHeaders.get === "function") {
|
|
4224
|
-
authHeader = rawHeaders.get("authorization") ?? void 0;
|
|
4225
|
-
} else if (rawHeaders && typeof rawHeaders === "object") {
|
|
4226
|
-
authHeader = rawHeaders["authorization"] ?? rawHeaders["Authorization"];
|
|
4227
|
-
}
|
|
4228
|
-
const presented = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
|
|
4229
|
-
if (!presented || presented !== baseSecret) {
|
|
4230
|
-
return { handled: true, response: this.error("forbidden: Bearer token must match OS_AUTH_SECRET", 403) };
|
|
4231
|
-
}
|
|
4232
|
-
try {
|
|
4233
|
-
const { backfillPlatformSsoClients: backfillPlatformSsoClients2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
4234
|
-
const result = await backfillPlatformSsoClients2({
|
|
4235
|
-
ql,
|
|
4236
|
-
baseSecret,
|
|
4237
|
-
logger: console
|
|
4238
|
-
});
|
|
4239
|
-
let sample = [];
|
|
4240
|
-
let total = 0;
|
|
4241
|
-
try {
|
|
4242
|
-
const rows = await ql.find("sys_oauth_application", { limit: 5 }, { context: { isSystem: true } });
|
|
4243
|
-
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
4244
|
-
sample = list;
|
|
4245
|
-
total = typeof rows?.total === "number" ? rows.total : list.length;
|
|
4246
|
-
} catch (e) {
|
|
4247
|
-
sample = [{ _readErr: e?.message ?? String(e) }];
|
|
4248
|
-
}
|
|
4249
|
-
return { handled: true, response: this.success({ ...result, total, sample }) };
|
|
4250
|
-
} catch (err) {
|
|
4251
|
-
return { handled: true, response: this.error(`backfill failed: ${err?.message ?? String(err)}`, 500) };
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
if (parts.length === 1 && parts[0] === "projects" && m === "GET") {
|
|
4255
|
-
const where = {};
|
|
4256
|
-
if (query?.organizationId) where.organization_id = query.organizationId;
|
|
4257
|
-
if (query?.status) where.status = query.status;
|
|
4258
|
-
let rows = await ql.find(ENV, Object.keys(where).length ? { where } : void 0);
|
|
4259
|
-
if (rows && rows.value) rows = rows.value;
|
|
4260
|
-
const projects = (Array.isArray(rows) ? rows : []).map(cleanProjectRow);
|
|
4261
|
-
return { handled: true, response: this.success({ projects, total: projects.length }) };
|
|
4262
|
-
}
|
|
4263
|
-
if (parts.length === 1 && parts[0] === "projects" && m === "POST") {
|
|
4264
|
-
const req = body || {};
|
|
4265
|
-
if (req.organization_id === "__session__" || req.created_by === "__session__") {
|
|
4266
|
-
try {
|
|
4267
|
-
const userId = await this.resolveCallerUserId(_context);
|
|
4268
|
-
if (req.created_by === "__session__") {
|
|
4269
|
-
req.created_by = userId ?? "system";
|
|
4270
|
-
}
|
|
4271
|
-
if (req.organization_id === "__session__") {
|
|
4272
|
-
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
4273
|
-
const rawHeaders = _context?.request?.headers;
|
|
4274
|
-
let headers = rawHeaders;
|
|
4275
|
-
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
4276
|
-
const h = new Headers();
|
|
4277
|
-
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
4278
|
-
if (v == null) continue;
|
|
4279
|
-
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
4280
|
-
}
|
|
4281
|
-
headers = h;
|
|
4282
|
-
}
|
|
4283
|
-
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
4284
|
-
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
4285
|
-
req.organization_id = sessionData?.session?.activeOrganizationId ?? void 0;
|
|
4286
|
-
}
|
|
4287
|
-
} catch {
|
|
4288
|
-
}
|
|
4289
|
-
}
|
|
4290
|
-
if (!req.organization_id || !req.display_name) {
|
|
4291
|
-
return { handled: true, response: this.error("organization_id and display_name are required", 400) };
|
|
4292
|
-
}
|
|
4293
|
-
const environmentId = randomUUID();
|
|
4294
|
-
const credentialId = randomUUID();
|
|
4295
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4296
|
-
const resolved = resolveDriver(req.driver);
|
|
4297
|
-
if (!resolved) {
|
|
4298
|
-
const available = listRegisteredDrivers().map((d) => d.name);
|
|
4299
|
-
if (req.driver) {
|
|
4300
|
-
return {
|
|
4301
|
-
handled: true,
|
|
4302
|
-
response: this.error(
|
|
4303
|
-
`Unknown driver '${req.driver}'. Available drivers: [${available.join(", ") || "none"}]`,
|
|
4304
|
-
400
|
|
4305
|
-
)
|
|
4306
|
-
};
|
|
4307
|
-
}
|
|
4308
|
-
return {
|
|
4309
|
-
handled: true,
|
|
4310
|
-
response: this.error(
|
|
4311
|
-
"No ObjectQL driver is registered. Register at least one DriverPlugin (e.g. InMemoryDriver or SqlDriver).",
|
|
4312
|
-
503
|
|
4313
|
-
)
|
|
4314
|
-
};
|
|
4315
|
-
}
|
|
4316
|
-
const driver = resolved.name;
|
|
4317
|
-
let plaintextSecret = `mock-token-${environmentId}`;
|
|
4318
|
-
let computedHostname = req.hostname;
|
|
4319
|
-
if (!computedHostname) {
|
|
4320
|
-
const shortId = environmentId.slice(0, 8);
|
|
4321
|
-
try {
|
|
4322
|
-
const orgRow = await findOne("sys_organization", { id: req.organization_id });
|
|
4323
|
-
const orgSlug = orgRow?.slug || req.organization_id;
|
|
4324
|
-
const rootDomain = (0, import_core2.getEnv)("OS_ROOT_DOMAIN") ?? (0, import_core2.getEnv)("ROOT_DOMAIN", "objectstack.app");
|
|
4325
|
-
computedHostname = `${orgSlug}-${shortId}.${rootDomain}`;
|
|
4326
|
-
} catch {
|
|
4327
|
-
computedHostname = `${req.organization_id}-${shortId}.objectstack.app`;
|
|
4328
|
-
}
|
|
4329
|
-
}
|
|
4330
|
-
try {
|
|
4331
|
-
const existing = await findOne("sys_environment", {
|
|
4332
|
-
hostname: computedHostname
|
|
4333
|
-
});
|
|
4334
|
-
if (existing && existing.id !== environmentId) {
|
|
4335
|
-
return {
|
|
4336
|
-
handled: true,
|
|
4337
|
-
response: this.error(
|
|
4338
|
-
`Hostname '${computedHostname}' is already in use by another project.`,
|
|
4339
|
-
409,
|
|
4340
|
-
{ code: "HOSTNAME_TAKEN", hostname: computedHostname }
|
|
4341
|
-
)
|
|
4342
|
-
};
|
|
4343
|
-
}
|
|
4344
|
-
} catch {
|
|
4345
|
-
}
|
|
4346
|
-
const baseMetadata = { ...req.metadata ?? {} };
|
|
4347
|
-
const simulateFailure = Boolean(baseMetadata.__simulateFailure);
|
|
4348
|
-
const simulateDelayMs = Number(baseMetadata.__simulateDelayMs ?? 1500);
|
|
4349
|
-
try {
|
|
4350
|
-
let ownerUserId = req.created_by && req.created_by !== "system" ? String(req.created_by) : void 0;
|
|
4351
|
-
if (!ownerUserId) {
|
|
4352
|
-
ownerUserId = await this.resolveCallerUserId(_context);
|
|
4353
|
-
}
|
|
4354
|
-
if (ownerUserId) {
|
|
4355
|
-
const userRow = await ql.find("sys_user", { where: { id: ownerUserId } });
|
|
4356
|
-
const userRows = Array.isArray(userRow) ? userRow : userRow?.value ?? [];
|
|
4357
|
-
const u = Array.isArray(userRows) && userRows.length > 0 ? userRows[0] : null;
|
|
4358
|
-
if (u?.email) {
|
|
4359
|
-
baseMetadata.ownerSeed = {
|
|
4360
|
-
userId: String(ownerUserId),
|
|
4361
|
-
email: String(u.email),
|
|
4362
|
-
name: u.name ? String(u.name) : null,
|
|
4363
|
-
image: u.image ? String(u.image) : null
|
|
4364
|
-
};
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
} catch {
|
|
4368
|
-
}
|
|
4369
|
-
try {
|
|
4370
|
-
const orgRow = await ql.find("sys_organization", { where: { id: req.organization_id } });
|
|
4371
|
-
const orgRows = Array.isArray(orgRow) ? orgRow : orgRow?.value ?? [];
|
|
4372
|
-
const org = Array.isArray(orgRows) && orgRows.length > 0 ? orgRows[0] : null;
|
|
4373
|
-
if (org?.id && org?.name) {
|
|
4374
|
-
baseMetadata.orgSeed = {
|
|
4375
|
-
id: String(org.id),
|
|
4376
|
-
name: String(org.name),
|
|
4377
|
-
slug: org.slug ? String(org.slug) : null,
|
|
4378
|
-
logo: org.logo ? String(org.logo) : null
|
|
4379
|
-
};
|
|
4380
|
-
}
|
|
4381
|
-
} catch {
|
|
4382
|
-
}
|
|
4383
|
-
await ql.insert(ENV, {
|
|
4384
|
-
id: environmentId,
|
|
4385
|
-
organization_id: req.organization_id,
|
|
4386
|
-
display_name: req.display_name,
|
|
4387
|
-
is_default: req.is_default ?? false,
|
|
4388
|
-
is_system: req.is_system ?? false,
|
|
4389
|
-
plan: req.plan ?? "free",
|
|
4390
|
-
status: "provisioning",
|
|
4391
|
-
created_by: req.created_by ?? "system",
|
|
4392
|
-
metadata: JSON.stringify(baseMetadata),
|
|
4393
|
-
created_at: nowIso,
|
|
4394
|
-
updated_at: nowIso,
|
|
4395
|
-
database_url: null,
|
|
4396
|
-
database_driver: driver,
|
|
4397
|
-
storage_limit_mb: req.storage_limit_mb ?? 1024,
|
|
4398
|
-
provisioned_at: null,
|
|
4399
|
-
hostname: computedHostname,
|
|
4400
|
-
visibility: (() => {
|
|
4401
|
-
const raw = String(req.visibility ?? "private");
|
|
4402
|
-
return raw === "unlisted" ? "private" : raw;
|
|
4403
|
-
})()
|
|
4404
|
-
});
|
|
4405
|
-
try {
|
|
4406
|
-
const { seedPlatformSsoClient: seedPlatformSsoClient2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
4407
|
-
const baseSecret = ((0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
4408
|
-
if (baseSecret) {
|
|
4409
|
-
await seedPlatformSsoClient2({
|
|
4410
|
-
ql,
|
|
4411
|
-
environmentId,
|
|
4412
|
-
hostname: computedHostname,
|
|
4413
|
-
baseSecret,
|
|
4414
|
-
logger: console
|
|
4415
|
-
});
|
|
4416
|
-
}
|
|
4417
|
-
} catch (ssoErr) {
|
|
4418
|
-
console.warn?.("[http-dispatcher] platform SSO seed failed (non-fatal)", {
|
|
4419
|
-
environmentId,
|
|
4420
|
-
error: ssoErr?.message
|
|
4421
|
-
});
|
|
4422
|
-
}
|
|
4423
|
-
const runProvisioning = async () => {
|
|
4424
|
-
try {
|
|
4425
|
-
if (simulateDelayMs > 0) {
|
|
4426
|
-
await new Promise((r) => setTimeout(r, simulateDelayMs));
|
|
4427
|
-
}
|
|
4428
|
-
if (simulateFailure) {
|
|
4429
|
-
throw new Error("Simulated provisioning failure (metadata.__simulateFailure=true)");
|
|
4430
|
-
}
|
|
4431
|
-
let databaseUrl;
|
|
4432
|
-
try {
|
|
4433
|
-
const adapter = await getRealAdapter(driver);
|
|
4434
|
-
if (adapter) {
|
|
4435
|
-
const result = await adapter.createDatabase({
|
|
4436
|
-
environmentId,
|
|
4437
|
-
databaseName: `p-${environmentId.replace(/-/g, "").slice(0, 24)}`,
|
|
4438
|
-
region: "us-east-1",
|
|
4439
|
-
storageLimitMb: req.storage_limit_mb ?? 1024
|
|
4440
|
-
});
|
|
4441
|
-
databaseUrl = result.databaseUrl;
|
|
4442
|
-
if (result.plaintextSecret) plaintextSecret = result.plaintextSecret;
|
|
4443
|
-
} else {
|
|
4444
|
-
databaseUrl = buildDatabaseUrl(driver, environmentId);
|
|
4445
|
-
}
|
|
4446
|
-
} catch (adapterErr) {
|
|
4447
|
-
throw adapterErr instanceof Error ? adapterErr : new Error(String(adapterErr));
|
|
4448
|
-
}
|
|
4449
|
-
const seedStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4450
|
-
await ql.update(
|
|
4451
|
-
ENV,
|
|
4452
|
-
{
|
|
4453
|
-
database_url: databaseUrl,
|
|
4454
|
-
updated_at: seedStartedAt
|
|
4455
|
-
},
|
|
4456
|
-
{ where: { id: environmentId } }
|
|
4457
|
-
);
|
|
4458
|
-
await ql.insert(CRED, {
|
|
4459
|
-
id: credentialId,
|
|
4460
|
-
environment_id: environmentId,
|
|
4461
|
-
secret_ciphertext: plaintextSecret,
|
|
4462
|
-
encryption_key_id: "noop",
|
|
4463
|
-
authorization: "full_access",
|
|
4464
|
-
status: "active",
|
|
4465
|
-
created_at: seedStartedAt,
|
|
4466
|
-
updated_at: seedStartedAt
|
|
4467
|
-
});
|
|
4468
|
-
const templateId = req.template_id ?? "blank";
|
|
4469
|
-
if (templateId !== "blank") {
|
|
4470
|
-
try {
|
|
4471
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4472
|
-
if (seeder) {
|
|
4473
|
-
await seeder.seed({ environmentId, templateId });
|
|
4474
|
-
}
|
|
4475
|
-
} catch (seedErr) {
|
|
4476
|
-
const seedMessage = seedErr instanceof Error ? seedErr.message : String(seedErr);
|
|
4477
|
-
try {
|
|
4478
|
-
const existing = await findOne(ENV, { id: environmentId });
|
|
4479
|
-
const existingMeta = typeof existing?.metadata === "string" ? JSON.parse(existing.metadata) : existing?.metadata ?? {};
|
|
4480
|
-
await ql.update(
|
|
4481
|
-
ENV,
|
|
4482
|
-
{
|
|
4483
|
-
metadata: JSON.stringify({
|
|
4484
|
-
...existingMeta,
|
|
4485
|
-
templateSeedError: { message: seedMessage, templateId }
|
|
4486
|
-
})
|
|
4487
|
-
},
|
|
4488
|
-
{ where: { id: environmentId } }
|
|
4489
|
-
);
|
|
4490
|
-
} catch {
|
|
4491
|
-
}
|
|
4492
|
-
}
|
|
4493
|
-
}
|
|
4494
|
-
const artifactPathRaw = baseMetadata.artifact_path;
|
|
4495
|
-
if (typeof artifactPathRaw === "string" && artifactPathRaw.length > 0) {
|
|
4496
|
-
try {
|
|
4497
|
-
const path2 = await import("path");
|
|
4498
|
-
const { isHttpUrl: isHttpUrl2, loadArtifactBundle: loadArtifactBundle2 } = await Promise.resolve().then(() => (init_load_artifact_bundle(), load_artifact_bundle_exports));
|
|
4499
|
-
const root = process.env.OS_PROJECT_ARTIFACT_ROOT ?? process.cwd();
|
|
4500
|
-
const resolved2 = isHttpUrl2(artifactPathRaw) ? artifactPathRaw : path2.isAbsolute(artifactPathRaw) ? artifactPathRaw : path2.resolve(root, artifactPathRaw);
|
|
4501
|
-
const bundle = await loadArtifactBundle2(resolved2, { tag: "[bind-artifact]" });
|
|
4502
|
-
if (!bundle) {
|
|
4503
|
-
throw new Error(`failed to load artifact bundle at '${resolved2}'`);
|
|
4504
|
-
}
|
|
4505
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4506
|
-
if (seeder?.seedBundle) {
|
|
4507
|
-
await seeder.seedBundle({ environmentId, bundle });
|
|
4508
|
-
} else {
|
|
4509
|
-
throw new Error("template-seeder.seedBundle is unavailable");
|
|
4510
|
-
}
|
|
4511
|
-
} catch (bindErr) {
|
|
4512
|
-
const bindMessage = bindErr instanceof Error ? bindErr.message : String(bindErr);
|
|
4513
|
-
try {
|
|
4514
|
-
const existing = await findOne(ENV, { id: environmentId });
|
|
4515
|
-
const existingMeta = typeof existing?.metadata === "string" ? JSON.parse(existing.metadata) : existing?.metadata ?? {};
|
|
4516
|
-
await ql.update(
|
|
4517
|
-
ENV,
|
|
4518
|
-
{
|
|
4519
|
-
metadata: JSON.stringify({
|
|
4520
|
-
...existingMeta,
|
|
4521
|
-
artifactBindError: { message: bindMessage, artifactPath: artifactPathRaw }
|
|
4522
|
-
})
|
|
4523
|
-
},
|
|
4524
|
-
{ where: { id: environmentId } }
|
|
4525
|
-
);
|
|
4526
|
-
} catch {
|
|
4527
|
-
}
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4531
|
-
await ql.update(
|
|
4532
|
-
ENV,
|
|
4533
|
-
{
|
|
4534
|
-
status: "active",
|
|
4535
|
-
provisioned_at: finishedAt,
|
|
4536
|
-
updated_at: finishedAt
|
|
4537
|
-
},
|
|
4538
|
-
{ where: { id: environmentId } }
|
|
4539
|
-
);
|
|
4540
|
-
} catch (err) {
|
|
4541
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4542
|
-
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4543
|
-
await ql.update(
|
|
4544
|
-
ENV,
|
|
4545
|
-
{
|
|
4546
|
-
status: "failed",
|
|
4547
|
-
metadata: JSON.stringify({
|
|
4548
|
-
...baseMetadata,
|
|
4549
|
-
provisioningError: { message, failedAt }
|
|
4550
|
-
}),
|
|
4551
|
-
updated_at: failedAt
|
|
4552
|
-
},
|
|
4553
|
-
{ where: { id: environmentId } }
|
|
4554
|
-
);
|
|
4555
|
-
}
|
|
4556
|
-
};
|
|
4557
|
-
const provisionSyncEnv = process.env.OS_PROVISION_SYNC;
|
|
4558
|
-
const onServerless = !!(process.env.VERCEL || process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.NETLIFY || process.env.CF_PAGES);
|
|
4559
|
-
const syncProvisioning = provisionSyncEnv === void 0 ? onServerless : provisionSyncEnv !== "0" && provisionSyncEnv !== "false";
|
|
4560
|
-
if (syncProvisioning) {
|
|
4561
|
-
await runProvisioning();
|
|
4562
|
-
} else {
|
|
4563
|
-
void runProvisioning();
|
|
4564
|
-
}
|
|
4565
|
-
const project = cleanProjectRow(await findOne(ENV, { id: environmentId }));
|
|
4566
|
-
const res = this.success({ project });
|
|
4567
|
-
res.status = syncProvisioning ? 201 : 202;
|
|
4568
|
-
return { handled: true, response: res };
|
|
4569
|
-
}
|
|
4570
|
-
if (parts.length === 2 && parts[0] === "projects") {
|
|
4571
|
-
const id = decodeURIComponent(parts[1]);
|
|
4572
|
-
if (m === "GET") {
|
|
4573
|
-
const envRow = await findOne(ENV, { id });
|
|
4574
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4575
|
-
const credRow = await findOne(CRED, { environment_id: id, status: "active" });
|
|
4576
|
-
const callerUserId = await this.resolveCallerUserId(_context);
|
|
4577
|
-
const membership = callerUserId ? await findOne(MEM, { environment_id: id, user_id: callerUserId }) : await findOne(MEM, { environment_id: id });
|
|
4578
|
-
const credMeta = credRow ? {
|
|
4579
|
-
id: credRow.id,
|
|
4580
|
-
status: credRow.status,
|
|
4581
|
-
authorization: credRow.authorization,
|
|
4582
|
-
activatedAt: credRow.created_at,
|
|
4583
|
-
expiresAt: credRow.expires_at
|
|
4584
|
-
} : void 0;
|
|
4585
|
-
const project = cleanProjectRow(envRow);
|
|
4586
|
-
const database = project.database_url ? {
|
|
4587
|
-
driver: project.database_driver,
|
|
4588
|
-
database_name: `env-${project.id}`,
|
|
4589
|
-
database_url: project.database_url,
|
|
4590
|
-
storage_limit_mb: project.storage_limit_mb,
|
|
4591
|
-
provisioned_at: project.provisioned_at
|
|
4592
|
-
} : void 0;
|
|
4593
|
-
return {
|
|
4594
|
-
handled: true,
|
|
4595
|
-
response: this.success({ project, database, credential: credMeta, membership })
|
|
4596
|
-
};
|
|
4597
|
-
}
|
|
4598
|
-
if (m === "PATCH") {
|
|
4599
|
-
const patch = {};
|
|
4600
|
-
if (body?.display_name !== void 0) patch.display_name = body.display_name;
|
|
4601
|
-
if (body?.plan !== void 0) patch.plan = body.plan;
|
|
4602
|
-
if (body?.status !== void 0) patch.status = body.status;
|
|
4603
|
-
if (body?.is_default !== void 0) patch.is_default = body.is_default;
|
|
4604
|
-
if (body?.visibility !== void 0) {
|
|
4605
|
-
let v = String(body.visibility);
|
|
4606
|
-
if (v === "unlisted") v = "private";
|
|
4607
|
-
if (!["private", "public"].includes(v)) {
|
|
4608
|
-
return { handled: true, response: this.error(`Invalid visibility '${v}' (expected private | public)`, 400) };
|
|
4609
|
-
}
|
|
4610
|
-
patch.visibility = v;
|
|
4611
|
-
}
|
|
4612
|
-
if (body?.metadata !== void 0) patch.metadata = JSON.stringify(body.metadata);
|
|
4613
|
-
patch.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4614
|
-
await ql.update(ENV, patch, { where: { id } });
|
|
4615
|
-
const envRow = await findOne(ENV, { id });
|
|
4616
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4617
|
-
return { handled: true, response: this.success({ project: cleanProjectRow(envRow) }) };
|
|
4618
|
-
}
|
|
4619
|
-
if (m === "DELETE") {
|
|
4620
|
-
const force = query?.force === "1" || query?.force === "true" || body?.force === true;
|
|
4621
|
-
const result = await this.deleteProjectCascade(id, { ql, findOne, getRealAdapter, force });
|
|
4622
|
-
if (!result.ok) {
|
|
4623
|
-
return { handled: true, response: this.error(result.error ?? "Delete failed", result.status ?? 500) };
|
|
4624
|
-
}
|
|
4625
|
-
return { handled: true, response: this.success({ deleted: true, environmentId: id, warnings: result.warnings }) };
|
|
4626
|
-
}
|
|
4627
|
-
}
|
|
4628
|
-
if (parts.length === 2 && parts[0] === "organizations" && m === "DELETE") {
|
|
4629
|
-
const orgId = decodeURIComponent(parts[1]);
|
|
4630
|
-
let projectRows = [];
|
|
4631
|
-
try {
|
|
4632
|
-
let rows = await ql.find(ENV, { where: { organization_id: orgId } });
|
|
4633
|
-
if (rows && rows.value) rows = rows.value;
|
|
4634
|
-
projectRows = Array.isArray(rows) ? rows : [];
|
|
4635
|
-
} catch {
|
|
4636
|
-
projectRows = [];
|
|
4637
|
-
}
|
|
4638
|
-
const warnings = [];
|
|
4639
|
-
let deletedProjects = 0;
|
|
4640
|
-
for (const row of projectRows) {
|
|
4641
|
-
const pid = row?.id;
|
|
4642
|
-
if (!pid) continue;
|
|
4643
|
-
try {
|
|
4644
|
-
const r = await this.deleteProjectCascade(pid, { ql, findOne, getRealAdapter, force: true });
|
|
4645
|
-
if (r.ok) deletedProjects++;
|
|
4646
|
-
if (r.warnings?.length) warnings.push(...r.warnings);
|
|
4647
|
-
if (!r.ok && r.error) warnings.push(`Project ${pid}: ${r.error}`);
|
|
4648
|
-
} catch (err) {
|
|
4649
|
-
warnings.push(
|
|
4650
|
-
`Failed to delete project ${pid}: ${err instanceof Error ? err.message : String(err)}`
|
|
4651
|
-
);
|
|
4652
|
-
}
|
|
4653
|
-
}
|
|
4654
|
-
let orgDeleted = false;
|
|
4655
|
-
try {
|
|
4656
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
4657
|
-
const fn = authService?.api?.deleteOrganization;
|
|
4658
|
-
if (typeof fn === "function") {
|
|
4659
|
-
await fn.call(authService.api, {
|
|
4660
|
-
body: { organizationId: orgId },
|
|
4661
|
-
headers: _context?.request?.headers
|
|
4662
|
-
});
|
|
4663
|
-
orgDeleted = true;
|
|
4664
|
-
}
|
|
4665
|
-
} catch (err) {
|
|
4666
|
-
warnings.push(
|
|
4667
|
-
`auth.deleteOrganization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4668
|
-
);
|
|
4669
|
-
}
|
|
4670
|
-
if (!orgDeleted) {
|
|
4671
|
-
try {
|
|
4672
|
-
await ql.delete("sys_organization", { where: { id: orgId } });
|
|
4673
|
-
orgDeleted = true;
|
|
4674
|
-
} catch (err) {
|
|
4675
|
-
warnings.push(
|
|
4676
|
-
`Failed to delete sys_organization row: ${err instanceof Error ? err.message : String(err)}`
|
|
4677
|
-
);
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
return {
|
|
4681
|
-
handled: true,
|
|
4682
|
-
response: this.success({
|
|
4683
|
-
deleted: orgDeleted,
|
|
4684
|
-
organizationId: orgId,
|
|
4685
|
-
deletedProjects,
|
|
4686
|
-
warnings
|
|
4687
|
-
})
|
|
4688
|
-
};
|
|
4689
|
-
}
|
|
4690
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "hostname" && (m === "POST" || m === "PUT")) {
|
|
4691
|
-
const id = decodeURIComponent(parts[1]);
|
|
4692
|
-
const hostname = body?.hostname;
|
|
4693
|
-
if (!hostname || typeof hostname !== "string") {
|
|
4694
|
-
return { handled: true, response: this.error("hostname is required", 400) };
|
|
4695
|
-
}
|
|
4696
|
-
const normalized = hostname.trim().toLowerCase();
|
|
4697
|
-
if (!/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/.test(normalized)) {
|
|
4698
|
-
return { handled: true, response: this.error("Invalid hostname format", 400) };
|
|
4699
|
-
}
|
|
4700
|
-
const envRow = await findOne(ENV, { id });
|
|
4701
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4702
|
-
let existing;
|
|
4703
|
-
try {
|
|
4704
|
-
const rows = await ql.find(ENV, { where: { hostname: normalized } });
|
|
4705
|
-
const arr = Array.isArray(rows) ? rows : rows?.value ?? [];
|
|
4706
|
-
existing = arr.find((r) => r.id !== id);
|
|
4707
|
-
} catch {
|
|
4708
|
-
}
|
|
4709
|
-
if (existing) {
|
|
4710
|
-
return {
|
|
4711
|
-
handled: true,
|
|
4712
|
-
response: this.error(
|
|
4713
|
-
`Hostname '${normalized}' is already in use by another project.`,
|
|
4714
|
-
409,
|
|
4715
|
-
{ code: "HOSTNAME_TAKEN", hostname: normalized }
|
|
4716
|
-
)
|
|
4717
|
-
};
|
|
4718
|
-
}
|
|
4719
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4720
|
-
await ql.update(ENV, { hostname: normalized, updated_at: updatedAt }, { where: { id } });
|
|
4721
|
-
if (this.envRegistry?.invalidate) {
|
|
4722
|
-
try {
|
|
4723
|
-
await this.envRegistry.invalidate(id);
|
|
4724
|
-
} catch {
|
|
4725
|
-
}
|
|
4726
|
-
}
|
|
4727
|
-
const updated = cleanProjectRow(await findOne(ENV, { id }));
|
|
4728
|
-
return { handled: true, response: this.success({ project: updated }) };
|
|
4729
|
-
}
|
|
4730
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "retry" && m === "POST") {
|
|
4731
|
-
const id = decodeURIComponent(parts[1]);
|
|
4732
|
-
const envRow = await findOne(ENV, { id });
|
|
4733
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4734
|
-
if (envRow.status !== "failed" && envRow.status !== "provisioning") {
|
|
4735
|
-
return {
|
|
4736
|
-
handled: true,
|
|
4737
|
-
response: this.error(
|
|
4738
|
-
`Project '${id}' is '${envRow.status}'; only failed or provisioning projects can be retried.`,
|
|
4739
|
-
409
|
|
4740
|
-
)
|
|
4741
|
-
};
|
|
4742
|
-
}
|
|
4743
|
-
const driverName = envRow.database_driver;
|
|
4744
|
-
const resolved = resolveDriver(driverName);
|
|
4745
|
-
if (!resolved) {
|
|
4746
|
-
return {
|
|
4747
|
-
handled: true,
|
|
4748
|
-
response: this.error(
|
|
4749
|
-
`Driver '${driverName}' is no longer registered; retry aborted.`,
|
|
4750
|
-
503
|
|
4751
|
-
)
|
|
4752
|
-
};
|
|
4753
|
-
}
|
|
4754
|
-
let metadata = {};
|
|
4755
|
-
if (envRow.metadata) {
|
|
4756
|
-
if (typeof envRow.metadata === "string") {
|
|
4757
|
-
try {
|
|
4758
|
-
metadata = JSON.parse(envRow.metadata);
|
|
4759
|
-
} catch {
|
|
4760
|
-
metadata = {};
|
|
4761
|
-
}
|
|
4762
|
-
} else if (typeof envRow.metadata === "object") {
|
|
4763
|
-
metadata = { ...envRow.metadata };
|
|
4764
|
-
}
|
|
4765
|
-
}
|
|
4766
|
-
delete metadata.provisioningError;
|
|
4767
|
-
const retryStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4768
|
-
await ql.update(
|
|
4769
|
-
ENV,
|
|
4770
|
-
{
|
|
4771
|
-
status: "provisioning",
|
|
4772
|
-
metadata: JSON.stringify(metadata),
|
|
4773
|
-
updated_at: retryStartedAt
|
|
4774
|
-
},
|
|
4775
|
-
{ where: { id } }
|
|
4776
|
-
);
|
|
4777
|
-
const simulateRetryFailure = Boolean(metadata.__simulateFailure);
|
|
4778
|
-
const simulateRetryDelay = Number(metadata.__simulateDelayMs ?? 1500);
|
|
4779
|
-
const runRetry = async () => {
|
|
4780
|
-
try {
|
|
4781
|
-
if (simulateRetryDelay > 0) {
|
|
4782
|
-
await new Promise((r) => setTimeout(r, simulateRetryDelay));
|
|
4783
|
-
}
|
|
4784
|
-
if (simulateRetryFailure) {
|
|
4785
|
-
throw new Error("Simulated provisioning failure (metadata.__simulateFailure=true)");
|
|
4786
|
-
}
|
|
4787
|
-
let databaseUrl;
|
|
4788
|
-
let retrySecret = `mock-token-${id}`;
|
|
4789
|
-
try {
|
|
4790
|
-
const adapter = await getRealAdapter(resolved.name);
|
|
4791
|
-
if (adapter) {
|
|
4792
|
-
const result = await adapter.createDatabase({
|
|
4793
|
-
environmentId: id,
|
|
4794
|
-
databaseName: `p-${id.replace(/-/g, "").slice(0, 24)}`,
|
|
4795
|
-
region: "us-east-1",
|
|
4796
|
-
storageLimitMb: envRow.storage_limit_mb ?? 1024
|
|
4797
|
-
});
|
|
4798
|
-
databaseUrl = result.databaseUrl;
|
|
4799
|
-
if (result.plaintextSecret) retrySecret = result.plaintextSecret;
|
|
4800
|
-
} else {
|
|
4801
|
-
databaseUrl = buildDatabaseUrl(resolved.name, id);
|
|
4802
|
-
}
|
|
4803
|
-
} catch (adapterErr) {
|
|
4804
|
-
throw adapterErr instanceof Error ? adapterErr : new Error(String(adapterErr));
|
|
4805
|
-
}
|
|
4806
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4807
|
-
await ql.update(
|
|
4808
|
-
ENV,
|
|
4809
|
-
{
|
|
4810
|
-
status: "active",
|
|
4811
|
-
database_url: databaseUrl,
|
|
4812
|
-
database_driver: resolved.name,
|
|
4813
|
-
provisioned_at: nowIso,
|
|
4814
|
-
updated_at: nowIso
|
|
4815
|
-
},
|
|
4816
|
-
{ where: { id } }
|
|
4817
|
-
);
|
|
4818
|
-
const existingCred = await findOne(CRED, { environment_id: id, status: "active" });
|
|
4819
|
-
if (!existingCred) {
|
|
4820
|
-
await ql.insert(CRED, {
|
|
4821
|
-
id: randomUUID(),
|
|
4822
|
-
environment_id: id,
|
|
4823
|
-
secret_ciphertext: retrySecret,
|
|
4824
|
-
encryption_key_id: "noop",
|
|
4825
|
-
authorization: "full_access",
|
|
4826
|
-
status: "active",
|
|
4827
|
-
created_at: nowIso,
|
|
4828
|
-
updated_at: nowIso
|
|
4829
|
-
});
|
|
4830
|
-
}
|
|
4831
|
-
} catch (err) {
|
|
4832
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4833
|
-
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4834
|
-
await ql.update(
|
|
4835
|
-
ENV,
|
|
4836
|
-
{
|
|
4837
|
-
status: "failed",
|
|
4838
|
-
metadata: JSON.stringify({
|
|
4839
|
-
...metadata,
|
|
4840
|
-
provisioningError: { message, failedAt }
|
|
4841
|
-
}),
|
|
4842
|
-
updated_at: failedAt
|
|
4843
|
-
},
|
|
4844
|
-
{ where: { id } }
|
|
4845
|
-
);
|
|
4846
|
-
}
|
|
4847
|
-
};
|
|
4848
|
-
void runRetry();
|
|
4849
|
-
const envAfter = cleanProjectRow(await findOne(ENV, { id }));
|
|
4850
|
-
const retryRes = this.success({ project: envAfter });
|
|
4851
|
-
retryRes.status = 202;
|
|
4852
|
-
return { handled: true, response: retryRes };
|
|
4853
|
-
}
|
|
4854
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "activate" && m === "POST") {
|
|
4855
|
-
const id = decodeURIComponent(parts[1]);
|
|
4856
|
-
const envRow = await findOne(ENV, { id });
|
|
4857
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4858
|
-
return { handled: true, response: this.success({ project: cleanProjectRow(envRow), sessionUpdated: false }) };
|
|
4859
|
-
}
|
|
4860
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "credentials" && parts[3] === "rotate" && m === "POST") {
|
|
4861
|
-
const id = decodeURIComponent(parts[1]);
|
|
4862
|
-
const plaintext = body?.plaintext;
|
|
4863
|
-
if (!plaintext || typeof plaintext !== "string") {
|
|
4864
|
-
return { handled: true, response: this.error("plaintext is required", 400) };
|
|
4865
|
-
}
|
|
4866
|
-
const envRow = await findOne(ENV, { id });
|
|
4867
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4868
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4869
|
-
let existing = await ql.find(CRED, { where: { environment_id: id, status: "active" } });
|
|
4870
|
-
if (existing && existing.value) existing = existing.value;
|
|
4871
|
-
for (const row of Array.isArray(existing) ? existing : []) {
|
|
4872
|
-
await ql.update(CRED, {
|
|
4873
|
-
status: "revoked",
|
|
4874
|
-
revoked_at: nowIso,
|
|
4875
|
-
updated_at: nowIso
|
|
4876
|
-
}, { where: { id: row.id } });
|
|
4877
|
-
}
|
|
4878
|
-
const credentialId = randomUUID();
|
|
4879
|
-
await ql.insert(CRED, {
|
|
4880
|
-
id: credentialId,
|
|
4881
|
-
environment_id: id,
|
|
4882
|
-
secret_ciphertext: plaintext,
|
|
4883
|
-
encryption_key_id: "noop",
|
|
4884
|
-
authorization: "full_access",
|
|
4885
|
-
status: "active",
|
|
4886
|
-
created_at: nowIso,
|
|
4887
|
-
updated_at: nowIso
|
|
4888
|
-
});
|
|
4889
|
-
const credential = await findOne(CRED, { id: credentialId });
|
|
4890
|
-
const credMeta = credential ? {
|
|
4891
|
-
id: credential.id,
|
|
4892
|
-
status: credential.status,
|
|
4893
|
-
authorization: credential.authorization,
|
|
4894
|
-
activatedAt: credential.created_at
|
|
4895
|
-
} : void 0;
|
|
4896
|
-
return { handled: true, response: this.success({ credential: credMeta }) };
|
|
4897
|
-
}
|
|
4898
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "GET") {
|
|
4899
|
-
const id = decodeURIComponent(parts[1]);
|
|
4900
|
-
let rows = await ql.find(MEM, { where: { environment_id: id } });
|
|
4901
|
-
if (rows && rows.value) rows = rows.value;
|
|
4902
|
-
const members = Array.isArray(rows) ? rows : [];
|
|
4903
|
-
const userIds = Array.from(new Set(members.map((mem) => mem.user_id).filter(Boolean)));
|
|
4904
|
-
const userMap = /* @__PURE__ */ new Map();
|
|
4905
|
-
for (const uid of userIds) {
|
|
4906
|
-
let row = null;
|
|
4907
|
-
for (const tableName of ["sys_user", "user"]) {
|
|
4908
|
-
try {
|
|
4909
|
-
const u = await ql.findOne(tableName, { where: { id: uid } });
|
|
4910
|
-
row = u?.value ?? u;
|
|
4911
|
-
if (row) break;
|
|
4912
|
-
} catch {
|
|
4913
|
-
}
|
|
4914
|
-
}
|
|
4915
|
-
if (row) userMap.set(String(uid), {
|
|
4916
|
-
id: row.id,
|
|
4917
|
-
name: row.name ?? row.display_name,
|
|
4918
|
-
email: row.email,
|
|
4919
|
-
image: row.image ?? row.avatar_url
|
|
4920
|
-
});
|
|
4921
|
-
}
|
|
4922
|
-
const enriched = members.map((mem) => ({
|
|
4923
|
-
...mem,
|
|
4924
|
-
user: userMap.get(String(mem.user_id)) ?? void 0
|
|
4925
|
-
}));
|
|
4926
|
-
return { handled: true, response: this.success({ members: enriched }) };
|
|
4927
|
-
}
|
|
4928
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "POST") {
|
|
4929
|
-
const id = decodeURIComponent(parts[1]);
|
|
4930
|
-
const project = await findOne(ENV, { id });
|
|
4931
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4932
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
4933
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
4934
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
4935
|
-
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
4936
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
4937
|
-
}
|
|
4938
|
-
const email = typeof body?.email === "string" ? String(body.email).trim().toLowerCase() : null;
|
|
4939
|
-
let inviteUserId = typeof body?.user_id === "string" ? String(body.user_id).trim() : null;
|
|
4940
|
-
let role = String(body?.role ?? "member").trim().toLowerCase();
|
|
4941
|
-
if (!["owner", "admin", "member", "viewer"].includes(role)) {
|
|
4942
|
-
return { handled: true, response: this.error(`Invalid role '${role}' (expected owner | admin | member | viewer)`, 400) };
|
|
4943
|
-
}
|
|
4944
|
-
if (!email && !inviteUserId) {
|
|
4945
|
-
return { handled: true, response: this.error("email or user_id is required", 400) };
|
|
4946
|
-
}
|
|
4947
|
-
if (!inviteUserId && email) {
|
|
4948
|
-
let row = null;
|
|
4949
|
-
for (const tableName of ["sys_user", "user"]) {
|
|
4950
|
-
try {
|
|
4951
|
-
const u = await ql.findOne(tableName, { where: { email } });
|
|
4952
|
-
row = u?.value ?? u;
|
|
4953
|
-
if (row) break;
|
|
4954
|
-
} catch {
|
|
4955
|
-
}
|
|
4956
|
-
}
|
|
4957
|
-
if (!row?.id) {
|
|
4958
|
-
return { handled: true, response: this.error(`No user found with email '${email}'`, 404) };
|
|
4959
|
-
}
|
|
4960
|
-
inviteUserId = String(row.id);
|
|
4961
|
-
}
|
|
4962
|
-
const existing = await findOne(MEM, { environment_id: id, user_id: inviteUserId });
|
|
4963
|
-
if (existing) {
|
|
4964
|
-
return { handled: true, response: this.success({ member: existing, alreadyMember: true }) };
|
|
4965
|
-
}
|
|
4966
|
-
try {
|
|
4967
|
-
const memberId = randomUUID();
|
|
4968
|
-
await ql.insert(MEM, {
|
|
4969
|
-
id: memberId,
|
|
4970
|
-
environment_id: id,
|
|
4971
|
-
user_id: inviteUserId,
|
|
4972
|
-
role,
|
|
4973
|
-
invited_by: callerId,
|
|
4974
|
-
organization_id: project.organization_id ?? null
|
|
4975
|
-
});
|
|
4976
|
-
const created = await findOne(MEM, { id: memberId });
|
|
4977
|
-
return { handled: true, response: this.success({ member: created, alreadyMember: false }) };
|
|
4978
|
-
} catch (e) {
|
|
4979
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to add member", 500) };
|
|
4980
|
-
}
|
|
4981
|
-
}
|
|
4982
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "PATCH") {
|
|
4983
|
-
const id = decodeURIComponent(parts[1]);
|
|
4984
|
-
const memberId = decodeURIComponent(parts[3]);
|
|
4985
|
-
const project = await findOne(ENV, { id });
|
|
4986
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4987
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
4988
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
4989
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
4990
|
-
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
4991
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
4992
|
-
}
|
|
4993
|
-
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
4994
|
-
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
4995
|
-
const newRole = String(body?.role ?? "").trim().toLowerCase();
|
|
4996
|
-
if (!["owner", "admin", "member", "viewer"].includes(newRole)) {
|
|
4997
|
-
return { handled: true, response: this.error(`Invalid role '${newRole}'`, 400) };
|
|
4998
|
-
}
|
|
4999
|
-
if (target.role === "owner" && newRole !== "owner") {
|
|
5000
|
-
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
5001
|
-
if (owners && owners.value) owners = owners.value;
|
|
5002
|
-
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
5003
|
-
if (ownerCount <= 1) {
|
|
5004
|
-
return { handled: true, response: this.error("Cannot demote the last owner", 409) };
|
|
5005
|
-
}
|
|
5006
|
-
}
|
|
5007
|
-
try {
|
|
5008
|
-
await ql.update(MEM, { role: newRole, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, { where: { id: memberId } });
|
|
5009
|
-
const updated = await findOne(MEM, { id: memberId });
|
|
5010
|
-
return { handled: true, response: this.success({ member: updated }) };
|
|
5011
|
-
} catch (e) {
|
|
5012
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to update role", 500) };
|
|
5013
|
-
}
|
|
5014
|
-
}
|
|
5015
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "DELETE") {
|
|
5016
|
-
const id = decodeURIComponent(parts[1]);
|
|
5017
|
-
const memberId = decodeURIComponent(parts[3]);
|
|
5018
|
-
const project = await findOne(ENV, { id });
|
|
5019
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
5020
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
5021
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
5022
|
-
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
5023
|
-
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
5024
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
5025
|
-
const isSelf = String(target.user_id) === String(callerId);
|
|
5026
|
-
const isPrivileged = callerMem && ["owner", "admin"].includes(String(callerMem.role));
|
|
5027
|
-
if (!isSelf && !isPrivileged) {
|
|
5028
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
5029
|
-
}
|
|
5030
|
-
if (target.role === "owner") {
|
|
5031
|
-
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
5032
|
-
if (owners && owners.value) owners = owners.value;
|
|
5033
|
-
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
5034
|
-
if (ownerCount <= 1) {
|
|
5035
|
-
return { handled: true, response: this.error("Cannot remove the last owner", 409) };
|
|
5036
|
-
}
|
|
5037
|
-
}
|
|
5038
|
-
try {
|
|
5039
|
-
await ql.delete(MEM, { where: { id: memberId } });
|
|
5040
|
-
return { handled: true, response: this.success({ removed: true, memberId }) };
|
|
5041
|
-
} catch (e) {
|
|
5042
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to remove member", 500) };
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
5046
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5047
|
-
let rows = await ql.find(PKG_INSTALL, { where: { environment_id: envId } });
|
|
5048
|
-
if (rows && rows.value) rows = rows.value;
|
|
5049
|
-
const installs = Array.isArray(rows) ? rows : [];
|
|
5050
|
-
const packages = await Promise.all(
|
|
5051
|
-
installs.map(async (r) => {
|
|
5052
|
-
let manifestId = null;
|
|
5053
|
-
let versionStr = null;
|
|
5054
|
-
try {
|
|
5055
|
-
if (r.package_id) {
|
|
5056
|
-
const pkg = await ql.findOne(PKG, { where: { id: r.package_id } });
|
|
5057
|
-
manifestId = pkg?.manifest_id ?? null;
|
|
5058
|
-
}
|
|
5059
|
-
if (r.package_version_id) {
|
|
5060
|
-
const ver = await ql.findOne(PKG_VERSION, { where: { id: r.package_version_id } });
|
|
5061
|
-
versionStr = ver?.version ?? null;
|
|
5062
|
-
}
|
|
5063
|
-
} catch {
|
|
5064
|
-
}
|
|
5065
|
-
return {
|
|
5066
|
-
...r,
|
|
5067
|
-
// Surface user-facing identifiers expected by client SDK
|
|
5068
|
-
packageId: manifestId,
|
|
5069
|
-
package_id: manifestId ?? r.package_id,
|
|
5070
|
-
version: versionStr ?? r.version ?? null
|
|
5071
|
-
};
|
|
5072
|
-
})
|
|
5073
|
-
);
|
|
5074
|
-
return { handled: true, response: this.success({ packages, total: packages.length }) };
|
|
5075
|
-
}
|
|
5076
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "packages" && m === "POST") {
|
|
5077
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5078
|
-
const { packageId, version, settings, enableOnInstall } = body ?? {};
|
|
5079
|
-
if (!packageId) return { handled: true, response: this.error("packageId is required", 400) };
|
|
5080
|
-
const qlSvc = await this.getObjectQLService();
|
|
5081
|
-
const pkgRegistry = qlSvc?.registry;
|
|
5082
|
-
const allPkgs = pkgRegistry?.getAllPackages?.() ?? [];
|
|
5083
|
-
const manifestEntry = allPkgs.find((p) => (p?.manifest?.id ?? p?.id) === packageId);
|
|
5084
|
-
const manifest = manifestEntry?.manifest ?? manifestEntry;
|
|
5085
|
-
if (!manifest) {
|
|
5086
|
-
return { handled: true, response: this.error(`Package '${packageId}' is not registered on this server`, 404) };
|
|
5087
|
-
}
|
|
5088
|
-
const CLOUD_SCOPES = /* @__PURE__ */ new Set(["cloud", "system", "platform"]);
|
|
5089
|
-
if (CLOUD_SCOPES.has(manifest?.scope)) {
|
|
5090
|
-
return { handled: true, response: this.error(`Package '${packageId}' has scope=${manifest.scope} and cannot be installed per-project`, 403) };
|
|
5091
|
-
}
|
|
5092
|
-
const projectRow = await findOne(ENV, { id: envId });
|
|
5093
|
-
if (!projectRow) {
|
|
5094
|
-
return { handled: true, response: this.error(`Project '${envId}' not found`, 404) };
|
|
5095
|
-
}
|
|
5096
|
-
const ownerOrgId = projectRow.organization_id ?? "system";
|
|
5097
|
-
let userId = "system";
|
|
5098
|
-
try {
|
|
5099
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
5100
|
-
const sessionData = await authService?.api?.getSession?.({
|
|
5101
|
-
headers: _context?.request?.headers
|
|
5102
|
-
});
|
|
5103
|
-
userId = sessionData?.user?.id ?? sessionData?.session?.userId ?? "system";
|
|
5104
|
-
} catch {
|
|
5105
|
-
}
|
|
5106
|
-
const resolvedVersion = version ?? manifest?.version ?? "1.0.0";
|
|
5107
|
-
const dup = await ql.findOne(PKG_INSTALL, {
|
|
5108
|
-
where: { environment_id: envId, package_id: packageId }
|
|
5109
|
-
});
|
|
5110
|
-
if (dup?.id) {
|
|
5111
|
-
return { handled: true, response: this.error(`Package '${packageId}' is already installed in this project`, 409) };
|
|
5112
|
-
}
|
|
5113
|
-
const sysPackageId = await ensureSysPackage(packageId, ownerOrgId, userId, manifest);
|
|
5114
|
-
const sysPackageVersionId = await ensureSysPackageVersion(sysPackageId, resolvedVersion, userId, manifest);
|
|
5115
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5116
|
-
const recordId = randomUUID();
|
|
5117
|
-
await ql.insert(PKG_INSTALL, {
|
|
5118
|
-
id: recordId,
|
|
5119
|
-
environment_id: envId,
|
|
5120
|
-
package_id: sysPackageId,
|
|
5121
|
-
package_version_id: sysPackageVersionId,
|
|
5122
|
-
status: "installed",
|
|
5123
|
-
enabled: enableOnInstall !== false,
|
|
5124
|
-
installed_at: nowIso,
|
|
5125
|
-
installed_by: userId,
|
|
5126
|
-
updated_at: nowIso,
|
|
5127
|
-
settings: settings ? JSON.stringify(settings) : null
|
|
5128
|
-
});
|
|
5129
|
-
const record = await ql.findOne(PKG_INSTALL, { where: { id: recordId } });
|
|
5130
|
-
try {
|
|
5131
|
-
await this.kernelManager?.evict(envId);
|
|
5132
|
-
} catch {
|
|
5133
|
-
}
|
|
5134
|
-
return { handled: true, response: this.success({ package: record }) };
|
|
5135
|
-
}
|
|
5136
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
5137
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5138
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5139
|
-
const record = await ql.findOne(PKG_INSTALL, { where: { environment_id: envId, package_id: pkgId } });
|
|
5140
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5141
|
-
return { handled: true, response: this.success({ package: record }) };
|
|
5142
|
-
}
|
|
5143
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "packages" && m === "DELETE") {
|
|
5144
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5145
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5146
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5147
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5148
|
-
const allPkgs0 = this.kernel.packages?.getAll?.() ?? [];
|
|
5149
|
-
const m0 = allPkgs0.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
5150
|
-
if (m0?.scope && ["cloud", "system", "platform"].includes(m0.scope)) {
|
|
5151
|
-
return { handled: true, response: this.error(`Package '${pkgId}' with scope=${m0.scope} cannot be uninstalled`, 403) };
|
|
5152
|
-
}
|
|
5153
|
-
await ql.delete(PKG_INSTALL, { where: { id: record.id } });
|
|
5154
|
-
try {
|
|
5155
|
-
await this.kernelManager?.evict(envId);
|
|
5156
|
-
} catch {
|
|
5157
|
-
}
|
|
5158
|
-
return { handled: true, response: this.success({ id: record.id, success: true }) };
|
|
5159
|
-
}
|
|
5160
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "enable" && m === "PATCH") {
|
|
5161
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5162
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5163
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5164
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5165
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5166
|
-
await ql.update(PKG_INSTALL, { enabled: true, status: "installed", updated_at: nowIso }, { where: { id: record.id } });
|
|
5167
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
5168
|
-
try {
|
|
5169
|
-
await this.kernelManager?.evict(envId);
|
|
5170
|
-
} catch {
|
|
5171
|
-
}
|
|
5172
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5173
|
-
}
|
|
5174
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "disable" && m === "PATCH") {
|
|
5175
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5176
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5177
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5178
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5179
|
-
const allPkgs1 = this.kernel.packages?.getAll?.() ?? [];
|
|
5180
|
-
const m1 = allPkgs1.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
5181
|
-
if (m1?.scope && ["cloud", "system", "platform"].includes(m1.scope)) {
|
|
5182
|
-
return { handled: true, response: this.error(`Package '${pkgId}' with scope=${m1.scope} cannot be disabled`, 403) };
|
|
5183
|
-
}
|
|
5184
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5185
|
-
await ql.update(PKG_INSTALL, { enabled: false, status: "disabled", updated_at: nowIso }, { where: { id: record.id } });
|
|
5186
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
5187
|
-
try {
|
|
5188
|
-
await this.kernelManager?.evict(envId);
|
|
5189
|
-
} catch {
|
|
5190
|
-
}
|
|
5191
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5192
|
-
}
|
|
5193
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "upgrade" && m === "POST") {
|
|
5194
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5195
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5196
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5197
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5198
|
-
const { targetVersion } = body ?? {};
|
|
5199
|
-
const allPkgs2 = this.kernel.packages?.getAll?.() ?? [];
|
|
5200
|
-
const manifest2 = allPkgs2.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
5201
|
-
const currentVer = await ql.findOne(PKG_VERSION, { where: { id: record.package_version_id } });
|
|
5202
|
-
const newVersion = targetVersion ?? manifest2?.version ?? currentVer?.version ?? "1.0.0";
|
|
5203
|
-
if (newVersion === currentVer?.version) {
|
|
5204
|
-
return { handled: true, response: this.success({ package: record, message: "Already at target version" }) };
|
|
5205
|
-
}
|
|
5206
|
-
let userId = "system";
|
|
5207
|
-
try {
|
|
5208
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
5209
|
-
const sessionData = await authService?.api?.getSession?.({
|
|
5210
|
-
headers: _context?.request?.headers
|
|
5211
|
-
});
|
|
5212
|
-
userId = sessionData?.user?.id ?? "system";
|
|
5213
|
-
} catch {
|
|
5214
|
-
}
|
|
5215
|
-
const newVersionId = await ensureSysPackageVersion(record.package_id, newVersion, userId, manifest2);
|
|
5216
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5217
|
-
await ql.update(PKG_INSTALL, {
|
|
5218
|
-
package_version_id: newVersionId,
|
|
5219
|
-
status: "installed",
|
|
5220
|
-
updated_at: nowIso
|
|
5221
|
-
}, { where: { id: record.id } });
|
|
5222
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
5223
|
-
try {
|
|
5224
|
-
await this.kernelManager?.evict(envId);
|
|
5225
|
-
} catch {
|
|
5226
|
-
}
|
|
5227
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5228
|
-
}
|
|
5229
|
-
} catch (e) {
|
|
5230
|
-
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
5231
|
-
}
|
|
5232
|
-
return { handled: false };
|
|
5233
|
-
}
|
|
5234
|
-
/**
|
|
5235
|
-
* Cascade-delete a project: cred / member / package_installation rows,
|
|
5236
|
-
* then the physical database via the provisioning adapter, then the
|
|
5237
|
-
* `sys_environment` row itself. Used by both `DELETE /cloud/environments/:id`
|
|
5238
|
-
* and the org-cascade in `DELETE /cloud/organizations/:id`.
|
|
5239
|
-
*
|
|
5240
|
-
* Idempotent and best-effort: missing rows / unreachable adapters
|
|
5241
|
-
* become warnings rather than hard failures, so a half-provisioned
|
|
5242
|
-
* project can still be cleaned out.
|
|
5243
|
-
*/
|
|
5244
|
-
async deleteProjectCascade(environmentId, deps) {
|
|
5245
|
-
const { ql, findOne, getRealAdapter, force } = deps;
|
|
5246
|
-
const ENV = "sys_environment";
|
|
5247
|
-
const warnings = [];
|
|
5248
|
-
const row = await findOne(ENV, { id: environmentId });
|
|
5249
|
-
if (!row) {
|
|
5250
|
-
return { ok: false, status: 404, error: `Project '${environmentId}' not found`, warnings };
|
|
5251
|
-
}
|
|
5252
|
-
if (row.is_system === true || row.is_system === 1) {
|
|
5253
|
-
return { ok: false, status: 409, error: `Project '${environmentId}' is a system project and cannot be deleted`, warnings };
|
|
5254
|
-
}
|
|
5255
|
-
if ((row.is_default === true || row.is_default === 1) && !force) {
|
|
5256
|
-
return {
|
|
5257
|
-
ok: false,
|
|
5258
|
-
status: 409,
|
|
5259
|
-
error: `Project '${environmentId}' is the default project for its organization. Pass ?force=1 to delete it.`,
|
|
5260
|
-
warnings
|
|
5261
|
-
};
|
|
5262
|
-
}
|
|
5263
|
-
const cascade = [
|
|
5264
|
-
{ object: "sys_environment_credential", field: "environment_id" },
|
|
5265
|
-
{ object: "sys_environment_member", field: "environment_id" },
|
|
5266
|
-
{ object: "sys_package_installation", field: "environment_id" }
|
|
5267
|
-
];
|
|
5268
|
-
for (const { object, field } of cascade) {
|
|
5269
|
-
try {
|
|
5270
|
-
let rows = await ql.find(object, { where: { [field]: environmentId } });
|
|
5271
|
-
if (rows && rows.value) rows = rows.value;
|
|
5272
|
-
if (Array.isArray(rows)) {
|
|
5273
|
-
for (const r of rows) {
|
|
5274
|
-
if (r?.id != null) {
|
|
5275
|
-
try {
|
|
5276
|
-
await ql.delete(object, { where: { id: r.id } });
|
|
5277
|
-
} catch (innerErr) {
|
|
5278
|
-
warnings.push(
|
|
5279
|
-
`Failed to delete ${object} ${r.id}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
5280
|
-
);
|
|
5281
|
-
}
|
|
5282
|
-
}
|
|
5283
|
-
}
|
|
5284
|
-
}
|
|
5285
|
-
} catch (err) {
|
|
5286
|
-
warnings.push(
|
|
5287
|
-
`Failed to enumerate ${object} for project ${environmentId}: ${err instanceof Error ? err.message : String(err)}`
|
|
5288
|
-
);
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
const driver = row.database_driver ?? "memory";
|
|
5292
|
-
const databaseUrl = row.database_url;
|
|
5293
|
-
const databaseName = `p-${String(environmentId).replace(/-/g, "").slice(0, 24)}`;
|
|
5294
|
-
try {
|
|
5295
|
-
const adapter = await getRealAdapter(driver);
|
|
5296
|
-
if (adapter?.deleteDatabase) {
|
|
5297
|
-
await adapter.deleteDatabase({ environmentId, databaseName, databaseUrl });
|
|
5298
|
-
} else {
|
|
5299
|
-
warnings.push(`No adapter for driver '${driver}'; physical DB for project ${environmentId} not released.`);
|
|
5300
|
-
}
|
|
5301
|
-
} catch (err) {
|
|
5302
|
-
warnings.push(
|
|
5303
|
-
`Failed to delete physical database for project ${environmentId}: ${err instanceof Error ? err.message : String(err)}`
|
|
5304
|
-
);
|
|
5305
|
-
}
|
|
5306
|
-
try {
|
|
5307
|
-
await ql.delete(ENV, { where: { id: environmentId } });
|
|
5308
|
-
} catch (err) {
|
|
5309
|
-
return {
|
|
5310
|
-
ok: false,
|
|
5311
|
-
status: 500,
|
|
5312
|
-
error: `Failed to delete sys_environment row: ${err instanceof Error ? err.message : String(err)}`,
|
|
5313
|
-
warnings
|
|
5314
|
-
};
|
|
5315
|
-
}
|
|
5316
|
-
if (this.envRegistry?.invalidate) {
|
|
5317
|
-
try {
|
|
5318
|
-
await this.envRegistry.invalidate(environmentId);
|
|
5319
|
-
} catch {
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5322
|
-
return { ok: true, warnings };
|
|
5323
|
-
}
|
|
5324
|
-
/**
|
|
5325
|
-
* Handles Storage requests
|
|
5326
|
-
* path: sub-path after /storage/
|
|
5327
|
-
*/
|
|
5328
|
-
async handleStorage(path, method, file, context) {
|
|
5329
|
-
const storageService = await this.getService(import_system.CoreServiceName.enum["file-storage"]) || this.kernel.services?.["file-storage"];
|
|
5330
|
-
if (!storageService) {
|
|
5331
|
-
return { handled: true, response: this.error("File storage not configured", 501) };
|
|
4083
|
+
/**
|
|
4084
|
+
* Handles Storage requests
|
|
4085
|
+
* path: sub-path after /storage/
|
|
4086
|
+
*/
|
|
4087
|
+
async handleStorage(path, method, file, context) {
|
|
4088
|
+
const storageService = await this.getService(import_system2.CoreServiceName.enum["file-storage"]) || this.kernel.services?.["file-storage"];
|
|
4089
|
+
if (!storageService) {
|
|
4090
|
+
return { handled: true, response: this.error("File storage not configured", 501) };
|
|
5332
4091
|
}
|
|
5333
4092
|
const m = method.toUpperCase();
|
|
5334
4093
|
const parts = path.replace(/^\/+/, "").split("/");
|
|
@@ -5391,6 +4150,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5391
4150
|
*
|
|
5392
4151
|
* Routes:
|
|
5393
4152
|
* GET / → listFlows
|
|
4153
|
+
* GET /actions → getActionDescriptors (ADR-0018; ?paradigm/?source/?category filters)
|
|
4154
|
+
* GET /connectors → getConnectorDescriptors (ADR-0022; ?type filter)
|
|
5394
4155
|
* GET /:name → getFlow
|
|
5395
4156
|
* POST / → createFlow (registerFlow)
|
|
5396
4157
|
* PUT /:name → updateFlow
|
|
@@ -5399,9 +4160,11 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5399
4160
|
* POST /:name/toggle → toggleFlow
|
|
5400
4161
|
* GET /:name/runs → listRuns
|
|
5401
4162
|
* GET /:name/runs/:runId → getRun
|
|
4163
|
+
* POST /:name/runs/:runId/resume → resume a paused run (screen input / ADR-0019)
|
|
4164
|
+
* GET /:name/runs/:runId/screen → the screen a paused run awaits
|
|
5402
4165
|
*/
|
|
5403
4166
|
async handleAutomation(path, method, body, context, query) {
|
|
5404
|
-
const automationService = await this.getService(
|
|
4167
|
+
const automationService = await this.getService(import_system2.CoreServiceName.enum.automation);
|
|
5405
4168
|
if (!automationService) return { handled: false };
|
|
5406
4169
|
const m = method.toUpperCase();
|
|
5407
4170
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -5428,6 +4191,32 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5428
4191
|
return { handled: true, response: this.success(body) };
|
|
5429
4192
|
}
|
|
5430
4193
|
}
|
|
4194
|
+
if (parts[0] === "actions" && parts.length === 1 && m === "GET") {
|
|
4195
|
+
if (typeof automationService.getActionDescriptors === "function") {
|
|
4196
|
+
let actions = automationService.getActionDescriptors() ?? [];
|
|
4197
|
+
if (query?.paradigm) {
|
|
4198
|
+
actions = actions.filter((a) => Array.isArray(a?.paradigms) && a.paradigms.includes(query.paradigm));
|
|
4199
|
+
}
|
|
4200
|
+
if (query?.source) {
|
|
4201
|
+
actions = actions.filter((a) => a?.source === query.source);
|
|
4202
|
+
}
|
|
4203
|
+
if (query?.category) {
|
|
4204
|
+
actions = actions.filter((a) => a?.category === query.category);
|
|
4205
|
+
}
|
|
4206
|
+
return { handled: true, response: this.success({ actions, total: actions.length }) };
|
|
4207
|
+
}
|
|
4208
|
+
return { handled: true, response: this.success({ actions: [], total: 0 }) };
|
|
4209
|
+
}
|
|
4210
|
+
if (parts[0] === "connectors" && parts.length === 1 && m === "GET") {
|
|
4211
|
+
if (typeof automationService.getConnectorDescriptors === "function") {
|
|
4212
|
+
let connectors = automationService.getConnectorDescriptors() ?? [];
|
|
4213
|
+
if (query?.type) {
|
|
4214
|
+
connectors = connectors.filter((c) => c?.type === query.type);
|
|
4215
|
+
}
|
|
4216
|
+
return { handled: true, response: this.success({ connectors, total: connectors.length }) };
|
|
4217
|
+
}
|
|
4218
|
+
return { handled: true, response: this.success({ connectors: [], total: 0 }) };
|
|
4219
|
+
}
|
|
5431
4220
|
if (parts.length >= 1) {
|
|
5432
4221
|
const name = parts[0];
|
|
5433
4222
|
if (parts[1] === "trigger" && m === "POST") {
|
|
@@ -5467,7 +4256,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5467
4256
|
return { handled: true, response: this.success({ name, enabled: body?.enabled ?? true }) };
|
|
5468
4257
|
}
|
|
5469
4258
|
}
|
|
5470
|
-
if (parts[1] === "runs" && parts[2] && m === "
|
|
4259
|
+
if (parts[1] === "runs" && parts[2] && parts[3] === "resume" && m === "POST") {
|
|
4260
|
+
if (typeof automationService.resume === "function") {
|
|
4261
|
+
const b = body && typeof body === "object" ? body : {};
|
|
4262
|
+
const inputs = b.inputs ?? b.variables;
|
|
4263
|
+
const signal = {};
|
|
4264
|
+
if (inputs && typeof inputs === "object") signal.variables = inputs;
|
|
4265
|
+
if (b.output && typeof b.output === "object") signal.output = b.output;
|
|
4266
|
+
if (typeof b.branchLabel === "string") signal.branchLabel = b.branchLabel;
|
|
4267
|
+
const result = await automationService.resume(parts[2], signal);
|
|
4268
|
+
return { handled: true, response: this.success(result) };
|
|
4269
|
+
}
|
|
4270
|
+
return { handled: true, response: this.error("Resume not supported", 501) };
|
|
4271
|
+
}
|
|
4272
|
+
if (parts[1] === "runs" && parts[2] && parts[3] === "screen" && m === "GET") {
|
|
4273
|
+
if (typeof automationService.getSuspendedScreen === "function") {
|
|
4274
|
+
const screen = automationService.getSuspendedScreen(parts[2]);
|
|
4275
|
+
if (!screen) return { handled: true, response: this.error("No pending screen for run", 404) };
|
|
4276
|
+
return { handled: true, response: this.success({ runId: parts[2], screen }) };
|
|
4277
|
+
}
|
|
4278
|
+
return { handled: true, response: this.error("Screen lookup not supported", 501) };
|
|
4279
|
+
}
|
|
4280
|
+
if (parts[1] === "runs" && parts[2] && !parts[3] && m === "GET") {
|
|
5471
4281
|
if (typeof automationService.getRun === "function") {
|
|
5472
4282
|
const run = await automationService.getRun(parts[2]);
|
|
5473
4283
|
if (!run) return { handled: true, response: this.error("Execution not found", 404) };
|
|
@@ -5793,11 +4603,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5793
4603
|
if (forbidden) {
|
|
5794
4604
|
return { handled: true, response: forbidden };
|
|
5795
4605
|
}
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
cleanPath = scopedMatch[1] ?? "";
|
|
5800
|
-
}
|
|
4606
|
+
const scopedMatch = cleanPath.match(/^\/projects\/[^/]+(\/.*)?$/);
|
|
4607
|
+
if (scopedMatch) {
|
|
4608
|
+
cleanPath = scopedMatch[1] ?? "";
|
|
5801
4609
|
}
|
|
5802
4610
|
try {
|
|
5803
4611
|
if ((cleanPath === "/discovery" || cleanPath === "") && method === "GET") {
|
|
@@ -5848,9 +4656,6 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5848
4656
|
if (cleanPath.startsWith("/packages")) {
|
|
5849
4657
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
5850
4658
|
}
|
|
5851
|
-
if (cleanPath.startsWith("/cloud")) {
|
|
5852
|
-
return this.handleCloud(cleanPath.substring(6), method, body, query, context);
|
|
5853
|
-
}
|
|
5854
4659
|
if (cleanPath.startsWith("/i18n")) {
|
|
5855
4660
|
return this.handleI18n(cleanPath.substring(5), method, query, context);
|
|
5856
4661
|
}
|
|
@@ -6466,344 +5271,136 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6466
5271
|
res.header(k, v);
|
|
6467
5272
|
}
|
|
6468
5273
|
}
|
|
6469
|
-
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
6470
|
-
});
|
|
6471
|
-
server.get(`${prefix}/discovery`, async (_req, res) => {
|
|
6472
|
-
if (securityHeaders) {
|
|
6473
|
-
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
6474
|
-
res.header(k, v);
|
|
6475
|
-
}
|
|
6476
|
-
}
|
|
6477
|
-
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
6478
|
-
});
|
|
6479
|
-
server.get(`${prefix}/health`, async (_req, res) => {
|
|
6480
|
-
try {
|
|
6481
|
-
const result = await dispatcher.dispatch("GET", "/health", void 0, {}, { request: _req });
|
|
6482
|
-
sendResult(result, res);
|
|
6483
|
-
} catch (err) {
|
|
6484
|
-
errorResponse(err, res);
|
|
6485
|
-
}
|
|
6486
|
-
});
|
|
6487
|
-
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
6488
|
-
try {
|
|
6489
|
-
const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
|
|
6490
|
-
sendResult(result, res);
|
|
6491
|
-
} catch (err) {
|
|
6492
|
-
errorResponse(err, res);
|
|
6493
|
-
}
|
|
6494
|
-
});
|
|
6495
|
-
server.post(`${prefix}/graphql`, async (req, res) => {
|
|
6496
|
-
try {
|
|
6497
|
-
const result = await dispatcher.handleGraphQL(req.body, { request: req });
|
|
6498
|
-
if (securityHeaders) {
|
|
6499
|
-
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
6500
|
-
res.header(k, v);
|
|
6501
|
-
}
|
|
6502
|
-
}
|
|
6503
|
-
res.json(result);
|
|
6504
|
-
} catch (err) {
|
|
6505
|
-
errorResponse(err, res);
|
|
6506
|
-
}
|
|
6507
|
-
});
|
|
6508
|
-
server.post(`${prefix}/analytics/query`, async (req, res) => {
|
|
6509
|
-
try {
|
|
6510
|
-
const result = await dispatcher.dispatch("POST", "/analytics/query", req.body, req.query, { request: req });
|
|
6511
|
-
sendResult(result, res);
|
|
6512
|
-
} catch (err) {
|
|
6513
|
-
errorResponse(err, res);
|
|
6514
|
-
}
|
|
6515
|
-
});
|
|
6516
|
-
server.get(`${prefix}/analytics/meta`, async (req, res) => {
|
|
6517
|
-
try {
|
|
6518
|
-
const result = await dispatcher.dispatch("GET", "/analytics/meta", void 0, req.query, { request: req });
|
|
6519
|
-
sendResult(result, res);
|
|
6520
|
-
} catch (err) {
|
|
6521
|
-
errorResponse(err, res);
|
|
6522
|
-
}
|
|
6523
|
-
});
|
|
6524
|
-
server.post(`${prefix}/analytics/sql`, async (req, res) => {
|
|
6525
|
-
try {
|
|
6526
|
-
const result = await dispatcher.dispatch("POST", "/analytics/sql", req.body, req.query, { request: req });
|
|
6527
|
-
sendResult(result, res);
|
|
6528
|
-
} catch (err) {
|
|
6529
|
-
errorResponse(err, res);
|
|
6530
|
-
}
|
|
6531
|
-
});
|
|
6532
|
-
server.get(`${prefix}/packages`, async (req, res) => {
|
|
6533
|
-
try {
|
|
6534
|
-
const result = await dispatcher.handlePackages("", "GET", {}, req.query, { request: req });
|
|
6535
|
-
sendResult(result, res);
|
|
6536
|
-
} catch (err) {
|
|
6537
|
-
errorResponse(err, res);
|
|
6538
|
-
}
|
|
6539
|
-
});
|
|
6540
|
-
server.post(`${prefix}/packages`, async (req, res) => {
|
|
6541
|
-
try {
|
|
6542
|
-
const result = await dispatcher.handlePackages("", "POST", req.body, {}, { request: req });
|
|
6543
|
-
sendResult(result, res);
|
|
6544
|
-
} catch (err) {
|
|
6545
|
-
errorResponse(err, res);
|
|
6546
|
-
}
|
|
6547
|
-
});
|
|
6548
|
-
server.get(`${prefix}/packages/:id/export`, async (req, res) => {
|
|
6549
|
-
try {
|
|
6550
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}/export`, "GET", {}, req.query, { request: req });
|
|
6551
|
-
sendResult(result, res);
|
|
6552
|
-
} catch (err) {
|
|
6553
|
-
errorResponse(err, res);
|
|
6554
|
-
}
|
|
6555
|
-
});
|
|
6556
|
-
server.get(`${prefix}/packages/:id`, async (req, res) => {
|
|
6557
|
-
try {
|
|
6558
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
6559
|
-
sendResult(result, res);
|
|
6560
|
-
} catch (err) {
|
|
6561
|
-
errorResponse(err, res);
|
|
6562
|
-
}
|
|
6563
|
-
});
|
|
6564
|
-
server.delete(`${prefix}/packages/:id`, async (req, res) => {
|
|
6565
|
-
try {
|
|
6566
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}`, "DELETE", {}, {}, { request: req });
|
|
6567
|
-
sendResult(result, res);
|
|
6568
|
-
} catch (err) {
|
|
6569
|
-
errorResponse(err, res);
|
|
6570
|
-
}
|
|
6571
|
-
});
|
|
6572
|
-
server.patch(`${prefix}/packages/:id/enable`, async (req, res) => {
|
|
6573
|
-
try {
|
|
6574
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}/enable`, "PATCH", {}, {}, { request: req });
|
|
6575
|
-
sendResult(result, res);
|
|
6576
|
-
} catch (err) {
|
|
6577
|
-
errorResponse(err, res);
|
|
6578
|
-
}
|
|
6579
|
-
});
|
|
6580
|
-
server.patch(`${prefix}/packages/:id/disable`, async (req, res) => {
|
|
6581
|
-
try {
|
|
6582
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}/disable`, "PATCH", {}, {}, { request: req });
|
|
6583
|
-
sendResult(result, res);
|
|
6584
|
-
} catch (err) {
|
|
6585
|
-
errorResponse(err, res);
|
|
6586
|
-
}
|
|
6587
|
-
});
|
|
6588
|
-
server.post(`${prefix}/packages/:id/publish`, async (req, res) => {
|
|
6589
|
-
try {
|
|
6590
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}/publish`, "POST", req.body, {}, { request: req });
|
|
6591
|
-
sendResult(result, res);
|
|
6592
|
-
} catch (err) {
|
|
6593
|
-
errorResponse(err, res);
|
|
6594
|
-
}
|
|
6595
|
-
});
|
|
6596
|
-
server.post(`${prefix}/packages/:id/revert`, async (req, res) => {
|
|
6597
|
-
try {
|
|
6598
|
-
const result = await dispatcher.handlePackages(`/${req.params.id}/revert`, "POST", req.body, {}, { request: req });
|
|
6599
|
-
sendResult(result, res);
|
|
6600
|
-
} catch (err) {
|
|
6601
|
-
errorResponse(err, res);
|
|
6602
|
-
}
|
|
6603
|
-
});
|
|
6604
|
-
server.get(`${prefix}/cloud/drivers`, async (req, res) => {
|
|
6605
|
-
try {
|
|
6606
|
-
const result = await dispatcher.handleCloud("/drivers", "GET", {}, req.query, { request: req });
|
|
6607
|
-
sendResult(result, res);
|
|
6608
|
-
} catch (err) {
|
|
6609
|
-
errorResponse(err, res);
|
|
6610
|
-
}
|
|
6611
|
-
});
|
|
6612
|
-
server.post(`${prefix}/cloud/admin/platform-sso/backfill`, async (req, res) => {
|
|
6613
|
-
try {
|
|
6614
|
-
const result = await dispatcher.handleCloud("/admin/platform-sso/backfill", "POST", req.body, req.query, { request: req });
|
|
6615
|
-
sendResult(result, res);
|
|
6616
|
-
} catch (err) {
|
|
6617
|
-
errorResponse(err, res);
|
|
6618
|
-
}
|
|
6619
|
-
});
|
|
6620
|
-
server.get(`${prefix}/cloud/templates`, async (req, res) => {
|
|
6621
|
-
try {
|
|
6622
|
-
const result = await dispatcher.handleCloud("/templates", "GET", {}, req.query, { request: req });
|
|
6623
|
-
sendResult(result, res);
|
|
6624
|
-
} catch (err) {
|
|
6625
|
-
errorResponse(err, res);
|
|
6626
|
-
}
|
|
6627
|
-
});
|
|
6628
|
-
server.get(`${prefix}/cloud/environments`, async (req, res) => {
|
|
6629
|
-
try {
|
|
6630
|
-
const result = await dispatcher.handleCloud("/projects", "GET", {}, req.query, { request: req });
|
|
6631
|
-
sendResult(result, res);
|
|
6632
|
-
} catch (err) {
|
|
6633
|
-
errorResponse(err, res);
|
|
6634
|
-
}
|
|
6635
|
-
});
|
|
6636
|
-
server.post(`${prefix}/cloud/environments`, async (req, res) => {
|
|
6637
|
-
try {
|
|
6638
|
-
const result = await dispatcher.handleCloud("/projects", "POST", req.body, {}, { request: req });
|
|
6639
|
-
sendResult(result, res);
|
|
6640
|
-
} catch (err) {
|
|
6641
|
-
errorResponse(err, res);
|
|
6642
|
-
}
|
|
6643
|
-
});
|
|
6644
|
-
server.get(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6645
|
-
try {
|
|
6646
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
6647
|
-
sendResult(result, res);
|
|
6648
|
-
} catch (err) {
|
|
6649
|
-
errorResponse(err, res);
|
|
6650
|
-
}
|
|
6651
|
-
});
|
|
6652
|
-
server.patch(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6653
|
-
try {
|
|
6654
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "PATCH", req.body, {}, { request: req });
|
|
6655
|
-
sendResult(result, res);
|
|
6656
|
-
} catch (err) {
|
|
6657
|
-
errorResponse(err, res);
|
|
6658
|
-
}
|
|
6659
|
-
});
|
|
6660
|
-
server.delete(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6661
|
-
try {
|
|
6662
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "DELETE", {}, req.query, { request: req });
|
|
6663
|
-
sendResult(result, res);
|
|
6664
|
-
} catch (err) {
|
|
6665
|
-
errorResponse(err, res);
|
|
6666
|
-
}
|
|
6667
|
-
});
|
|
6668
|
-
server.delete(`${prefix}/cloud/organizations/:id`, async (req, res) => {
|
|
6669
|
-
try {
|
|
6670
|
-
const result = await dispatcher.handleCloud(`/organizations/${req.params.id}`, "DELETE", {}, req.query, { request: req });
|
|
6671
|
-
sendResult(result, res);
|
|
6672
|
-
} catch (err) {
|
|
6673
|
-
errorResponse(err, res);
|
|
6674
|
-
}
|
|
6675
|
-
});
|
|
6676
|
-
server.post(`${prefix}/cloud/environments/:id/hostname`, async (req, res) => {
|
|
6677
|
-
try {
|
|
6678
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/hostname`, "POST", req.body, {}, { request: req });
|
|
6679
|
-
sendResult(result, res);
|
|
6680
|
-
} catch (err) {
|
|
6681
|
-
errorResponse(err, res);
|
|
6682
|
-
}
|
|
6683
|
-
});
|
|
6684
|
-
server.put(`${prefix}/cloud/environments/:id/hostname`, async (req, res) => {
|
|
6685
|
-
try {
|
|
6686
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/hostname`, "PUT", req.body, {}, { request: req });
|
|
6687
|
-
sendResult(result, res);
|
|
6688
|
-
} catch (err) {
|
|
6689
|
-
errorResponse(err, res);
|
|
6690
|
-
}
|
|
5274
|
+
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
5275
|
+
});
|
|
5276
|
+
server.get(`${prefix}/discovery`, async (_req, res) => {
|
|
5277
|
+
if (securityHeaders) {
|
|
5278
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
5279
|
+
res.header(k, v);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
6691
5283
|
});
|
|
6692
|
-
server.
|
|
5284
|
+
server.get(`${prefix}/health`, async (_req, res) => {
|
|
6693
5285
|
try {
|
|
6694
|
-
const result = await dispatcher.
|
|
5286
|
+
const result = await dispatcher.dispatch("GET", "/health", void 0, {}, { request: _req });
|
|
6695
5287
|
sendResult(result, res);
|
|
6696
5288
|
} catch (err) {
|
|
6697
5289
|
errorResponse(err, res);
|
|
6698
5290
|
}
|
|
6699
5291
|
});
|
|
6700
|
-
server.post(`${prefix}/
|
|
5292
|
+
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
6701
5293
|
try {
|
|
6702
|
-
const result = await dispatcher.
|
|
5294
|
+
const result = await dispatcher.handleAuth("login", "POST", req.body, { request: req });
|
|
6703
5295
|
sendResult(result, res);
|
|
6704
5296
|
} catch (err) {
|
|
6705
5297
|
errorResponse(err, res);
|
|
6706
5298
|
}
|
|
6707
5299
|
});
|
|
6708
|
-
server.post(`${prefix}/
|
|
5300
|
+
server.post(`${prefix}/graphql`, async (req, res) => {
|
|
6709
5301
|
try {
|
|
6710
|
-
const result = await dispatcher.
|
|
6711
|
-
|
|
5302
|
+
const result = await dispatcher.handleGraphQL(req.body, { request: req });
|
|
5303
|
+
if (securityHeaders) {
|
|
5304
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
5305
|
+
res.header(k, v);
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
res.json(result);
|
|
6712
5309
|
} catch (err) {
|
|
6713
5310
|
errorResponse(err, res);
|
|
6714
5311
|
}
|
|
6715
5312
|
});
|
|
6716
|
-
server.post(`${prefix}/
|
|
5313
|
+
server.post(`${prefix}/analytics/query`, async (req, res) => {
|
|
6717
5314
|
try {
|
|
6718
|
-
const result = await dispatcher.
|
|
5315
|
+
const result = await dispatcher.dispatch("POST", "/analytics/query", req.body, req.query, { request: req });
|
|
6719
5316
|
sendResult(result, res);
|
|
6720
5317
|
} catch (err) {
|
|
6721
5318
|
errorResponse(err, res);
|
|
6722
5319
|
}
|
|
6723
5320
|
});
|
|
6724
|
-
server.get(`${prefix}/
|
|
5321
|
+
server.get(`${prefix}/analytics/meta`, async (req, res) => {
|
|
6725
5322
|
try {
|
|
6726
|
-
const result = await dispatcher.
|
|
5323
|
+
const result = await dispatcher.dispatch("GET", "/analytics/meta", void 0, req.query, { request: req });
|
|
6727
5324
|
sendResult(result, res);
|
|
6728
5325
|
} catch (err) {
|
|
6729
5326
|
errorResponse(err, res);
|
|
6730
5327
|
}
|
|
6731
5328
|
});
|
|
6732
|
-
server.post(`${prefix}/
|
|
5329
|
+
server.post(`${prefix}/analytics/sql`, async (req, res) => {
|
|
6733
5330
|
try {
|
|
6734
|
-
const result = await dispatcher.
|
|
5331
|
+
const result = await dispatcher.dispatch("POST", "/analytics/sql", req.body, req.query, { request: req });
|
|
6735
5332
|
sendResult(result, res);
|
|
6736
5333
|
} catch (err) {
|
|
6737
5334
|
errorResponse(err, res);
|
|
6738
5335
|
}
|
|
6739
5336
|
});
|
|
6740
|
-
server.
|
|
5337
|
+
server.get(`${prefix}/packages`, async (req, res) => {
|
|
6741
5338
|
try {
|
|
6742
|
-
const result = await dispatcher.
|
|
5339
|
+
const result = await dispatcher.handlePackages("", "GET", {}, req.query, { request: req });
|
|
6743
5340
|
sendResult(result, res);
|
|
6744
5341
|
} catch (err) {
|
|
6745
5342
|
errorResponse(err, res);
|
|
6746
5343
|
}
|
|
6747
5344
|
});
|
|
6748
|
-
server.
|
|
5345
|
+
server.post(`${prefix}/packages`, async (req, res) => {
|
|
6749
5346
|
try {
|
|
6750
|
-
const result = await dispatcher.
|
|
5347
|
+
const result = await dispatcher.handlePackages("", "POST", req.body, {}, { request: req });
|
|
6751
5348
|
sendResult(result, res);
|
|
6752
5349
|
} catch (err) {
|
|
6753
5350
|
errorResponse(err, res);
|
|
6754
5351
|
}
|
|
6755
5352
|
});
|
|
6756
|
-
server.get(`${prefix}/
|
|
5353
|
+
server.get(`${prefix}/packages/:id/export`, async (req, res) => {
|
|
6757
5354
|
try {
|
|
6758
|
-
const result = await dispatcher.
|
|
5355
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/export`, "GET", {}, req.query, { request: req });
|
|
6759
5356
|
sendResult(result, res);
|
|
6760
5357
|
} catch (err) {
|
|
6761
5358
|
errorResponse(err, res);
|
|
6762
5359
|
}
|
|
6763
5360
|
});
|
|
6764
|
-
server.
|
|
5361
|
+
server.get(`${prefix}/packages/:id`, async (req, res) => {
|
|
6765
5362
|
try {
|
|
6766
|
-
const result = await dispatcher.
|
|
5363
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
6767
5364
|
sendResult(result, res);
|
|
6768
5365
|
} catch (err) {
|
|
6769
5366
|
errorResponse(err, res);
|
|
6770
5367
|
}
|
|
6771
5368
|
});
|
|
6772
|
-
server.
|
|
5369
|
+
server.delete(`${prefix}/packages/:id`, async (req, res) => {
|
|
6773
5370
|
try {
|
|
6774
|
-
const result = await dispatcher.
|
|
5371
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}`, "DELETE", {}, {}, { request: req });
|
|
6775
5372
|
sendResult(result, res);
|
|
6776
5373
|
} catch (err) {
|
|
6777
5374
|
errorResponse(err, res);
|
|
6778
5375
|
}
|
|
6779
5376
|
});
|
|
6780
|
-
server.
|
|
5377
|
+
server.patch(`${prefix}/packages/:id/enable`, async (req, res) => {
|
|
6781
5378
|
try {
|
|
6782
|
-
const result = await dispatcher.
|
|
5379
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/enable`, "PATCH", {}, {}, { request: req });
|
|
6783
5380
|
sendResult(result, res);
|
|
6784
5381
|
} catch (err) {
|
|
6785
5382
|
errorResponse(err, res);
|
|
6786
5383
|
}
|
|
6787
5384
|
});
|
|
6788
|
-
server.patch(`${prefix}/
|
|
5385
|
+
server.patch(`${prefix}/packages/:id/disable`, async (req, res) => {
|
|
6789
5386
|
try {
|
|
6790
|
-
const result = await dispatcher.
|
|
5387
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/disable`, "PATCH", {}, {}, { request: req });
|
|
6791
5388
|
sendResult(result, res);
|
|
6792
5389
|
} catch (err) {
|
|
6793
5390
|
errorResponse(err, res);
|
|
6794
5391
|
}
|
|
6795
5392
|
});
|
|
6796
|
-
server.
|
|
5393
|
+
server.post(`${prefix}/packages/:id/publish`, async (req, res) => {
|
|
6797
5394
|
try {
|
|
6798
|
-
const result = await dispatcher.
|
|
5395
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/publish`, "POST", req.body, {}, { request: req });
|
|
6799
5396
|
sendResult(result, res);
|
|
6800
5397
|
} catch (err) {
|
|
6801
5398
|
errorResponse(err, res);
|
|
6802
5399
|
}
|
|
6803
5400
|
});
|
|
6804
|
-
server.post(`${prefix}/
|
|
5401
|
+
server.post(`${prefix}/packages/:id/revert`, async (req, res) => {
|
|
6805
5402
|
try {
|
|
6806
|
-
const result = await dispatcher.
|
|
5403
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/revert`, "POST", req.body, {}, { request: req });
|
|
6807
5404
|
sendResult(result, res);
|
|
6808
5405
|
} catch (err) {
|
|
6809
5406
|
errorResponse(err, res);
|
|
@@ -6930,6 +5527,22 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6930
5527
|
errorResponse(err, res);
|
|
6931
5528
|
}
|
|
6932
5529
|
});
|
|
5530
|
+
server.post(`${base}/automation/:name/runs/:runId/resume`, async (req, res) => {
|
|
5531
|
+
try {
|
|
5532
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/runs/${req.params.runId}/resume`, req.body, req.query, { request: req });
|
|
5533
|
+
sendResult(result, res);
|
|
5534
|
+
} catch (err) {
|
|
5535
|
+
errorResponse(err, res);
|
|
5536
|
+
}
|
|
5537
|
+
});
|
|
5538
|
+
server.get(`${base}/automation/:name/runs/:runId/screen`, async (req, res) => {
|
|
5539
|
+
try {
|
|
5540
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs/${req.params.runId}/screen`, void 0, req.query, { request: req });
|
|
5541
|
+
sendResult(result, res);
|
|
5542
|
+
} catch (err) {
|
|
5543
|
+
errorResponse(err, res);
|
|
5544
|
+
}
|
|
5545
|
+
});
|
|
6933
5546
|
};
|
|
6934
5547
|
const registerAIRoutes = (base) => {
|
|
6935
5548
|
const wildcards = [
|
|
@@ -7870,21 +6483,17 @@ async function createDriver(driverType, databaseUrl, authToken) {
|
|
|
7870
6483
|
// src/cloud/artifact-kernel-factory.ts
|
|
7871
6484
|
var import_node_crypto2 = require("crypto");
|
|
7872
6485
|
var import_core3 = require("@objectstack/core");
|
|
7873
|
-
var
|
|
6486
|
+
var import_types3 = require("@objectstack/types");
|
|
7874
6487
|
init_driver_plugin();
|
|
7875
6488
|
init_app_plugin();
|
|
7876
6489
|
|
|
7877
6490
|
// src/cloud/capability-loader.ts
|
|
7878
6491
|
var CAPABILITY_PROVIDERS = {
|
|
7879
6492
|
automation: {
|
|
6493
|
+
// Self-contained: AutomationServicePlugin seeds all built-in node
|
|
6494
|
+
// executors itself (ADR-0018), so no companion node-pack plugins.
|
|
7880
6495
|
pkg: "@objectstack/service-automation",
|
|
7881
|
-
export: "AutomationServicePlugin"
|
|
7882
|
-
extras: [
|
|
7883
|
-
{ pkg: "@objectstack/service-automation", export: "CrudNodesPlugin" },
|
|
7884
|
-
{ pkg: "@objectstack/service-automation", export: "LogicNodesPlugin" },
|
|
7885
|
-
{ pkg: "@objectstack/service-automation", export: "HttpConnectorPlugin" },
|
|
7886
|
-
{ pkg: "@objectstack/service-automation", export: "ScreenNodesPlugin" }
|
|
7887
|
-
]
|
|
6496
|
+
export: "AutomationServicePlugin"
|
|
7888
6497
|
},
|
|
7889
6498
|
ai: {
|
|
7890
6499
|
pkg: "@objectstack/service-ai",
|
|
@@ -7915,6 +6524,19 @@ var CAPABILITY_PROVIDERS = {
|
|
|
7915
6524
|
pkg: "@objectstack/service-job",
|
|
7916
6525
|
export: "JobServicePlugin"
|
|
7917
6526
|
},
|
|
6527
|
+
messaging: {
|
|
6528
|
+
// Backs the `notify` flow node (ADR-0012): delivers to a user's
|
|
6529
|
+
// channels (inbox by default → `sys_inbox_message` rows).
|
|
6530
|
+
pkg: "@objectstack/service-messaging",
|
|
6531
|
+
export: "MessagingServicePlugin"
|
|
6532
|
+
},
|
|
6533
|
+
triggers: {
|
|
6534
|
+
// Concrete flow triggers — record-change (ObjectQL hooks) + schedule
|
|
6535
|
+
// (cron/interval via the job service; pair `triggers` with `job`).
|
|
6536
|
+
pkg: "@objectstack/plugin-trigger-record-change",
|
|
6537
|
+
export: "RecordChangeTriggerPlugin",
|
|
6538
|
+
extras: [{ pkg: "@objectstack/plugin-trigger-schedule", export: "ScheduleTriggerPlugin" }]
|
|
6539
|
+
},
|
|
7918
6540
|
realtime: {
|
|
7919
6541
|
pkg: "@objectstack/service-realtime",
|
|
7920
6542
|
export: "RealtimeServicePlugin"
|
|
@@ -7932,7 +6554,11 @@ async function loadCapabilities(opts) {
|
|
|
7932
6554
|
const { kernel, requires, bundle, environmentId } = opts;
|
|
7933
6555
|
const logger = opts.logger ?? console;
|
|
7934
6556
|
const installed = [];
|
|
7935
|
-
|
|
6557
|
+
const resolved = [...new Set(requires)];
|
|
6558
|
+
if (resolved.includes("audit") && !resolved.includes("messaging")) {
|
|
6559
|
+
resolved.push("messaging");
|
|
6560
|
+
}
|
|
6561
|
+
for (const cap of resolved) {
|
|
7936
6562
|
const spec = CAPABILITY_PROVIDERS[cap];
|
|
7937
6563
|
if (!spec) {
|
|
7938
6564
|
continue;
|
|
@@ -7999,8 +6625,197 @@ async function loadCapabilities(opts) {
|
|
|
7999
6625
|
return installed;
|
|
8000
6626
|
}
|
|
8001
6627
|
|
|
6628
|
+
// src/cloud/platform-sso.ts
|
|
6629
|
+
var import_node_crypto = require("crypto");
|
|
6630
|
+
var PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
6631
|
+
function derivePlatformSsoClientId(environmentId) {
|
|
6632
|
+
return `project_${environmentId}`;
|
|
6633
|
+
}
|
|
6634
|
+
function derivePlatformSsoClientSecret(baseSecret, environmentId) {
|
|
6635
|
+
return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
|
|
6636
|
+
}
|
|
6637
|
+
function hashPlatformSsoClientSecret(plaintext) {
|
|
6638
|
+
return (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
6639
|
+
}
|
|
6640
|
+
function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
|
|
6641
|
+
let host;
|
|
6642
|
+
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
|
|
6643
|
+
host = hostname;
|
|
6644
|
+
} else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
|
|
6645
|
+
const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
6646
|
+
const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
|
|
6647
|
+
host = `http://${hostWithPort}`;
|
|
6648
|
+
} else {
|
|
6649
|
+
host = `https://${hostname}`;
|
|
6650
|
+
}
|
|
6651
|
+
const trimmed = host.replace(/\/+$/, "");
|
|
6652
|
+
const path = basePath.replace(/\/+$/, "");
|
|
6653
|
+
return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
|
|
6654
|
+
}
|
|
6655
|
+
async function seedPlatformSsoClient(opts) {
|
|
6656
|
+
const { ql, environmentId, hostname, baseSecret, logger, throwOnError } = opts;
|
|
6657
|
+
if (!baseSecret) {
|
|
6658
|
+
logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { environmentId });
|
|
6659
|
+
return;
|
|
6660
|
+
}
|
|
6661
|
+
const clientId = derivePlatformSsoClientId(environmentId);
|
|
6662
|
+
const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, environmentId);
|
|
6663
|
+
const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
|
|
6664
|
+
const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
|
|
6665
|
+
let existing = null;
|
|
6666
|
+
try {
|
|
6667
|
+
const rows = await ql.find("sys_oauth_application", {
|
|
6668
|
+
where: { client_id: clientId },
|
|
6669
|
+
limit: 1
|
|
6670
|
+
}, { context: { isSystem: true } });
|
|
6671
|
+
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6672
|
+
existing = list[0] ?? null;
|
|
6673
|
+
} catch (err) {
|
|
6674
|
+
logger?.warn?.("[platform-sso] sys_oauth_application read failed \u2014 skipping seed", {
|
|
6675
|
+
environmentId,
|
|
6676
|
+
error: err?.message
|
|
6677
|
+
});
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6681
|
+
if (!existing) {
|
|
6682
|
+
const redirects = desiredRedirect ? [desiredRedirect] : [];
|
|
6683
|
+
try {
|
|
6684
|
+
await ql.insert("sys_oauth_application", {
|
|
6685
|
+
id: `oauthc_${environmentId}`,
|
|
6686
|
+
name: `Project ${environmentId}`,
|
|
6687
|
+
client_id: clientId,
|
|
6688
|
+
client_secret: clientSecretStored,
|
|
6689
|
+
type: "web",
|
|
6690
|
+
redirect_uris: JSON.stringify(redirects),
|
|
6691
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6692
|
+
response_types: JSON.stringify(["code"]),
|
|
6693
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6694
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
6695
|
+
require_pkce: false,
|
|
6696
|
+
skip_consent: true,
|
|
6697
|
+
disabled: false,
|
|
6698
|
+
subject_type: "public",
|
|
6699
|
+
created_at: nowIso,
|
|
6700
|
+
updated_at: nowIso
|
|
6701
|
+
}, { context: { isSystem: true } });
|
|
6702
|
+
logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
|
|
6703
|
+
} catch (err) {
|
|
6704
|
+
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
6705
|
+
environmentId,
|
|
6706
|
+
error: err?.message
|
|
6707
|
+
});
|
|
6708
|
+
if (throwOnError) throw err;
|
|
6709
|
+
}
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
6712
|
+
let currentRedirects = [];
|
|
6713
|
+
try {
|
|
6714
|
+
const raw = existing.redirect_uris;
|
|
6715
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
6716
|
+
if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
|
|
6717
|
+
} catch {
|
|
6718
|
+
}
|
|
6719
|
+
const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
|
|
6720
|
+
const repairPatch = {
|
|
6721
|
+
name: existing.name || `Project ${environmentId}`,
|
|
6722
|
+
client_secret: clientSecretStored,
|
|
6723
|
+
type: existing.type || "web",
|
|
6724
|
+
redirect_uris: JSON.stringify(mergedRedirects),
|
|
6725
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6726
|
+
response_types: JSON.stringify(["code"]),
|
|
6727
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6728
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
6729
|
+
require_pkce: false,
|
|
6730
|
+
skip_consent: true,
|
|
6731
|
+
disabled: false,
|
|
6732
|
+
subject_type: "public",
|
|
6733
|
+
updated_at: nowIso
|
|
6734
|
+
};
|
|
6735
|
+
try {
|
|
6736
|
+
await ql.update(
|
|
6737
|
+
"sys_oauth_application",
|
|
6738
|
+
repairPatch,
|
|
6739
|
+
{ where: { id: existing.id } },
|
|
6740
|
+
{ context: { isSystem: true } }
|
|
6741
|
+
);
|
|
6742
|
+
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
6743
|
+
environmentId,
|
|
6744
|
+
clientId,
|
|
6745
|
+
redirect_uris: mergedRedirects
|
|
6746
|
+
});
|
|
6747
|
+
} catch (err) {
|
|
6748
|
+
logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
|
|
6749
|
+
environmentId,
|
|
6750
|
+
error: err?.message
|
|
6751
|
+
});
|
|
6752
|
+
if (throwOnError) throw err;
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
async function backfillPlatformSsoClients(opts) {
|
|
6756
|
+
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
6757
|
+
if (!baseSecret) {
|
|
6758
|
+
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
6759
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
6760
|
+
}
|
|
6761
|
+
let projects = [];
|
|
6762
|
+
try {
|
|
6763
|
+
const rows = await ql.find("sys_environment", {
|
|
6764
|
+
limit,
|
|
6765
|
+
fields: ["id", "hostname", "status"]
|
|
6766
|
+
}, { context: { isSystem: true } });
|
|
6767
|
+
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6768
|
+
} catch (err) {
|
|
6769
|
+
logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
|
|
6770
|
+
error: err?.message
|
|
6771
|
+
});
|
|
6772
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
|
|
6773
|
+
}
|
|
6774
|
+
let seeded = 0;
|
|
6775
|
+
let alreadyExisted = 0;
|
|
6776
|
+
const failures = [];
|
|
6777
|
+
for (const p of projects) {
|
|
6778
|
+
if (!p?.id) continue;
|
|
6779
|
+
const before = await (async () => {
|
|
6780
|
+
try {
|
|
6781
|
+
const r = await ql.find("sys_oauth_application", {
|
|
6782
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6783
|
+
limit: 1
|
|
6784
|
+
}, { context: { isSystem: true } });
|
|
6785
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6786
|
+
return list[0] ?? null;
|
|
6787
|
+
} catch {
|
|
6788
|
+
return null;
|
|
6789
|
+
}
|
|
6790
|
+
})();
|
|
6791
|
+
try {
|
|
6792
|
+
await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
6793
|
+
if (before) alreadyExisted++;
|
|
6794
|
+
else {
|
|
6795
|
+
const after = await (async () => {
|
|
6796
|
+
try {
|
|
6797
|
+
const r = await ql.find("sys_oauth_application", {
|
|
6798
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6799
|
+
limit: 1
|
|
6800
|
+
}, { context: { isSystem: true } });
|
|
6801
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6802
|
+
return list[0] ?? null;
|
|
6803
|
+
} catch (err) {
|
|
6804
|
+
return { _readErr: err?.message };
|
|
6805
|
+
}
|
|
6806
|
+
})();
|
|
6807
|
+
if (after && !after._readErr) seeded++;
|
|
6808
|
+
else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
6809
|
+
}
|
|
6810
|
+
} catch (err) {
|
|
6811
|
+
failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6814
|
+
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
6815
|
+
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
6816
|
+
}
|
|
6817
|
+
|
|
8002
6818
|
// src/cloud/artifact-kernel-factory.ts
|
|
8003
|
-
init_platform_sso();
|
|
8004
6819
|
function deriveProjectAuthSecret(baseSecret, environmentId) {
|
|
8005
6820
|
return (0, import_node_crypto2.createHmac)("sha256", baseSecret).update(`project:${environmentId}`).digest("hex");
|
|
8006
6821
|
}
|
|
@@ -8010,7 +6825,7 @@ var ArtifactKernelFactory = class {
|
|
|
8010
6825
|
this.envRegistry = config.envRegistry;
|
|
8011
6826
|
this.logger = config.logger ?? console;
|
|
8012
6827
|
this.kernelConfig = config.kernelConfig;
|
|
8013
|
-
this.authBaseSecret = (config.authBaseSecret ?? (0,
|
|
6828
|
+
this.authBaseSecret = (config.authBaseSecret ?? (0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
8014
6829
|
}
|
|
8015
6830
|
async create(environmentId) {
|
|
8016
6831
|
let cached = this.envRegistry.peekById(environmentId);
|
|
@@ -8123,7 +6938,7 @@ var ArtifactKernelFactory = class {
|
|
|
8123
6938
|
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
|
|
8124
6939
|
}
|
|
8125
6940
|
try {
|
|
8126
|
-
const multiTenant = String((0,
|
|
6941
|
+
const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
8127
6942
|
if (multiTenant) {
|
|
8128
6943
|
try {
|
|
8129
6944
|
const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
|
|
@@ -9326,7 +8141,7 @@ async function createObjectOSStack(config) {
|
|
|
9326
8141
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
9327
8142
|
var import_node_fs4 = require("fs");
|
|
9328
8143
|
var import_node_path7 = require("path");
|
|
9329
|
-
var
|
|
8144
|
+
var import_types4 = require("@objectstack/types");
|
|
9330
8145
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
9331
8146
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
9332
8147
|
function safeFilename(manifestId) {
|
|
@@ -9870,7 +8685,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9870
8685
|
}
|
|
9871
8686
|
}
|
|
9872
8687
|
if (opts.seedNow && datasets.length > 0) {
|
|
9873
|
-
const multiTenant = String((0,
|
|
8688
|
+
const multiTenant = String((0, import_types4.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
9874
8689
|
try {
|
|
9875
8690
|
const ql = ctx.getService("objectql");
|
|
9876
8691
|
let metadata;
|
|
@@ -9988,9 +8803,6 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9988
8803
|
}
|
|
9989
8804
|
};
|
|
9990
8805
|
|
|
9991
|
-
// src/index.ts
|
|
9992
|
-
init_platform_sso();
|
|
9993
|
-
|
|
9994
8806
|
// src/sandbox/script-runner.ts
|
|
9995
8807
|
var UnimplementedScriptRunner = class {
|
|
9996
8808
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -10016,7 +8828,7 @@ init_body_runner();
|
|
|
10016
8828
|
// src/index.ts
|
|
10017
8829
|
var import_rest = require("@objectstack/rest");
|
|
10018
8830
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
10019
|
-
var
|
|
8831
|
+
var import_types5 = require("@objectstack/types");
|
|
10020
8832
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10021
8833
|
0 && (module.exports = {
|
|
10022
8834
|
AppPlugin,
|
|
@@ -10027,6 +8839,7 @@ var import_types6 = require("@objectstack/types");
|
|
|
10027
8839
|
DEFAULT_CLOUD_URL,
|
|
10028
8840
|
DEFAULT_RATE_LIMITS,
|
|
10029
8841
|
DriverPlugin,
|
|
8842
|
+
ExternalValidationPlugin,
|
|
10030
8843
|
FileArtifactApiClient,
|
|
10031
8844
|
HttpDispatcher,
|
|
10032
8845
|
HttpServer,
|
|
@@ -10065,6 +8878,7 @@ var import_types6 = require("@objectstack/types");
|
|
|
10065
8878
|
collectBundleHooks,
|
|
10066
8879
|
createDefaultHostConfig,
|
|
10067
8880
|
createDispatcherPlugin,
|
|
8881
|
+
createExternalValidationPlugin,
|
|
10068
8882
|
createObjectOSStack,
|
|
10069
8883
|
createRestApiPlugin,
|
|
10070
8884
|
createStandaloneStack,
|