@objectstack/runtime 4.0.5 → 4.1.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/README.md +62 -0
- package/dist/index.cjs +2786 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +930 -53
- package/dist/index.d.ts +930 -53
- package/dist/index.js +2758 -121
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
package/dist/index.cjs
CHANGED
|
@@ -228,7 +228,7 @@ var init_seed_loader = __esm({
|
|
|
228
228
|
this.logger.info("[SeedLoader] Pass 2: resolving deferred references", {
|
|
229
229
|
count: deferredUpdates.length
|
|
230
230
|
});
|
|
231
|
-
await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors);
|
|
231
|
+
await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, config.organizationId);
|
|
232
232
|
}
|
|
233
233
|
const durationMs = Date.now() - startTime;
|
|
234
234
|
return this.buildResult(config, graph, allResults, allErrors, durationMs);
|
|
@@ -285,7 +285,11 @@ var init_seed_loader = __esm({
|
|
|
285
285
|
}
|
|
286
286
|
let existingRecords;
|
|
287
287
|
if ((mode === "upsert" || mode === "update" || mode === "ignore") && !config.dryRun) {
|
|
288
|
-
existingRecords = await this.loadExistingRecords(
|
|
288
|
+
existingRecords = await this.loadExistingRecords(
|
|
289
|
+
objectName,
|
|
290
|
+
externalId,
|
|
291
|
+
config.organizationId
|
|
292
|
+
);
|
|
289
293
|
}
|
|
290
294
|
const objectRefs = refMap.get(objectName) || [];
|
|
291
295
|
const seedNow = /* @__PURE__ */ new Date();
|
|
@@ -300,6 +304,9 @@ var init_seed_loader = __esm({
|
|
|
300
304
|
`[SeedLoader] Failed to resolve dynamic values for ${objectName} record #${i}: ${seedResult.error.message}`
|
|
301
305
|
);
|
|
302
306
|
}
|
|
307
|
+
if (config.organizationId && record["organization_id"] == null) {
|
|
308
|
+
record["organization_id"] = config.organizationId;
|
|
309
|
+
}
|
|
303
310
|
for (const ref of objectRefs) {
|
|
304
311
|
const fieldValue = record[ref.field];
|
|
305
312
|
if (fieldValue === void 0 || fieldValue === null) continue;
|
|
@@ -310,7 +317,7 @@ var init_seed_loader = __esm({
|
|
|
310
317
|
record[ref.field] = resolvedId;
|
|
311
318
|
referencesResolved++;
|
|
312
319
|
} else if (!config.dryRun) {
|
|
313
|
-
const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue);
|
|
320
|
+
const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue, config.organizationId);
|
|
314
321
|
if (dbId) {
|
|
315
322
|
record[ref.field] = dbId;
|
|
316
323
|
referencesResolved++;
|
|
@@ -404,10 +411,12 @@ var init_seed_loader = __esm({
|
|
|
404
411
|
// ==========================================================================
|
|
405
412
|
// Internal: Reference Resolution
|
|
406
413
|
// ==========================================================================
|
|
407
|
-
async resolveFromDatabase(targetObject, targetField, value) {
|
|
414
|
+
async resolveFromDatabase(targetObject, targetField, value, organizationId) {
|
|
408
415
|
try {
|
|
416
|
+
const where = { [targetField]: value };
|
|
417
|
+
if (organizationId) where.organization_id = organizationId;
|
|
409
418
|
const records = await this.engine.find(targetObject, {
|
|
410
|
-
where
|
|
419
|
+
where,
|
|
411
420
|
fields: ["id"],
|
|
412
421
|
limit: 1,
|
|
413
422
|
context: { isSystem: true }
|
|
@@ -419,7 +428,7 @@ var init_seed_loader = __esm({
|
|
|
419
428
|
}
|
|
420
429
|
return null;
|
|
421
430
|
}
|
|
422
|
-
async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors) {
|
|
431
|
+
async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, organizationId) {
|
|
423
432
|
for (const deferred of deferredUpdates) {
|
|
424
433
|
const targetMap = insertedRecords.get(deferred.targetObject);
|
|
425
434
|
let resolvedId = targetMap?.get(String(deferred.attemptedValue));
|
|
@@ -427,7 +436,8 @@ var init_seed_loader = __esm({
|
|
|
427
436
|
resolvedId = await this.resolveFromDatabase(
|
|
428
437
|
deferred.targetObject,
|
|
429
438
|
deferred.targetField,
|
|
430
|
-
deferred.attemptedValue
|
|
439
|
+
deferred.attemptedValue,
|
|
440
|
+
organizationId
|
|
431
441
|
) ?? void 0;
|
|
432
442
|
}
|
|
433
443
|
if (resolvedId) {
|
|
@@ -623,13 +633,15 @@ var init_seed_loader = __esm({
|
|
|
623
633
|
}
|
|
624
634
|
return map2;
|
|
625
635
|
}
|
|
626
|
-
async loadExistingRecords(objectName, externalId) {
|
|
636
|
+
async loadExistingRecords(objectName, externalId, organizationId) {
|
|
627
637
|
const map2 = /* @__PURE__ */ new Map();
|
|
628
638
|
try {
|
|
629
|
-
const
|
|
639
|
+
const findArgs = {
|
|
630
640
|
fields: ["id", externalId],
|
|
631
641
|
context: { isSystem: true }
|
|
632
|
-
}
|
|
642
|
+
};
|
|
643
|
+
if (organizationId) findArgs.where = { organization_id: organizationId };
|
|
644
|
+
const records = await this.engine.find(objectName, findArgs);
|
|
633
645
|
for (const record of records || []) {
|
|
634
646
|
const key = String(record[externalId] ?? "");
|
|
635
647
|
if (key) {
|
|
@@ -1155,9 +1167,12 @@ function buildSandboxApi(engineCtx, ql, errLabel) {
|
|
|
1155
1167
|
}
|
|
1156
1168
|
function buildSandboxContext(engineCtx, ql) {
|
|
1157
1169
|
const inputSnapshot = unwrapProxyToPlain(engineCtx?.input ?? engineCtx?.doc);
|
|
1170
|
+
const previousRaw = engineCtx?.previous ?? engineCtx?.previousDoc;
|
|
1158
1171
|
return {
|
|
1159
|
-
input: inputSnapshot,
|
|
1160
|
-
|
|
1172
|
+
input: inputSnapshot ?? {},
|
|
1173
|
+
// Preserve `undefined` for `previous` on insert events so hooks can
|
|
1174
|
+
// reliably distinguish create (`!ctx.previous`) from update/delete.
|
|
1175
|
+
previous: unwrapProxyToPlain(previousRaw),
|
|
1161
1176
|
user: engineCtx?.user ?? engineCtx?.session?.user,
|
|
1162
1177
|
session: engineCtx?.session,
|
|
1163
1178
|
event: typeof engineCtx?.event === "string" ? engineCtx.event : void 0,
|
|
@@ -1184,12 +1199,13 @@ function buildActionSandboxContext(actionCtx, ql) {
|
|
|
1184
1199
|
};
|
|
1185
1200
|
}
|
|
1186
1201
|
function unwrapProxyToPlain(v) {
|
|
1187
|
-
if (
|
|
1188
|
-
if (
|
|
1202
|
+
if (v === void 0 || v === null) return void 0;
|
|
1203
|
+
if (typeof v !== "object") return void 0;
|
|
1204
|
+
if (Array.isArray(v)) return void 0;
|
|
1189
1205
|
try {
|
|
1190
1206
|
return Object.fromEntries(Object.entries(v));
|
|
1191
1207
|
} catch {
|
|
1192
|
-
return
|
|
1208
|
+
return void 0;
|
|
1193
1209
|
}
|
|
1194
1210
|
}
|
|
1195
1211
|
var import_data2;
|
|
@@ -1408,8 +1424,51 @@ var init_app_plugin = __esm({
|
|
|
1408
1424
|
appId
|
|
1409
1425
|
});
|
|
1410
1426
|
}
|
|
1427
|
+
try {
|
|
1428
|
+
const approvals = Array.isArray(this.bundle.approvals) ? this.bundle.approvals : Array.isArray((this.bundle.manifest || {}).approvals) ? this.bundle.manifest.approvals : [];
|
|
1429
|
+
if (approvals.length > 0) {
|
|
1430
|
+
ctx.hook("kernel:ready", async () => {
|
|
1431
|
+
let svc;
|
|
1432
|
+
try {
|
|
1433
|
+
svc = ctx.getService("approvals");
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
1436
|
+
if (!svc || typeof svc.defineProcess !== "function") {
|
|
1437
|
+
ctx.logger.warn("[AppPlugin] approvals service not registered \u2014 skipping declarative processes", {
|
|
1438
|
+
appId,
|
|
1439
|
+
processCount: approvals.length
|
|
1440
|
+
});
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
const sysCtx = { isSystem: true, roles: [], permissions: [] };
|
|
1444
|
+
let ok = 0;
|
|
1445
|
+
for (const proc of approvals) {
|
|
1446
|
+
try {
|
|
1447
|
+
await svc.defineProcess({
|
|
1448
|
+
name: proc.name,
|
|
1449
|
+
label: proc.label,
|
|
1450
|
+
object: proc.object,
|
|
1451
|
+
description: proc.description,
|
|
1452
|
+
active: proc.active !== false,
|
|
1453
|
+
definition: proc
|
|
1454
|
+
}, sysCtx);
|
|
1455
|
+
ok++;
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
ctx.logger.warn("[AppPlugin] Failed to register approval process", {
|
|
1458
|
+
appId,
|
|
1459
|
+
process: proc?.name,
|
|
1460
|
+
error: err?.message ?? String(err)
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
ctx.logger.info("[AppPlugin] Registered approval processes", { appId, count: ok });
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
} catch (err) {
|
|
1468
|
+
ctx.logger.error("[AppPlugin] Failed to schedule approval-process registration", err, { appId });
|
|
1469
|
+
}
|
|
1411
1470
|
this.emitCatalogEvent(ctx, "app:registered", sys);
|
|
1412
|
-
this.loadTranslations(ctx, appId);
|
|
1471
|
+
await this.loadTranslations(ctx, appId);
|
|
1413
1472
|
const seedDatasets = [];
|
|
1414
1473
|
if (Array.isArray(this.bundle.data)) {
|
|
1415
1474
|
seedDatasets.push(...this.bundle.data);
|
|
@@ -1425,46 +1484,107 @@ var init_app_plugin = __esm({
|
|
|
1425
1484
|
object: d.object
|
|
1426
1485
|
}));
|
|
1427
1486
|
try {
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1487
|
+
const kernel = ctx.kernel;
|
|
1488
|
+
const existing = (() => {
|
|
1489
|
+
try {
|
|
1490
|
+
return kernel?.getService?.("seed-datasets");
|
|
1491
|
+
} catch {
|
|
1492
|
+
return void 0;
|
|
1493
|
+
}
|
|
1494
|
+
})();
|
|
1495
|
+
const merged = Array.isArray(existing) ? [...existing, ...normalizedDatasets] : normalizedDatasets;
|
|
1496
|
+
const registerSvc = (name, value) => {
|
|
1497
|
+
if (kernel?.registerService) kernel.registerService(name, value);
|
|
1498
|
+
else if (typeof ctx.registerService === "function") ctx.registerService(name, value);
|
|
1499
|
+
};
|
|
1500
|
+
registerSvc("seed-datasets", merged);
|
|
1501
|
+
const metadataNow = ctx.getService("metadata");
|
|
1502
|
+
const loggerRef = ctx.logger;
|
|
1503
|
+
const replayer = async (organizationId) => {
|
|
1504
|
+
if (!organizationId) return { inserted: 0, updated: 0, errors: [] };
|
|
1505
|
+
const md = metadataNow ?? ctx.getService("metadata");
|
|
1506
|
+
if (!md) {
|
|
1507
|
+
loggerRef.warn("[seed-replayer] metadata service unavailable");
|
|
1508
|
+
return { inserted: 0, updated: 0, errors: [] };
|
|
1509
|
+
}
|
|
1510
|
+
const datasetsNow = (() => {
|
|
1511
|
+
try {
|
|
1512
|
+
return kernel?.getService?.("seed-datasets");
|
|
1513
|
+
} catch {
|
|
1514
|
+
return merged;
|
|
1515
|
+
}
|
|
1516
|
+
})() ?? merged;
|
|
1517
|
+
if (!Array.isArray(datasetsNow) || datasetsNow.length === 0) {
|
|
1518
|
+
return { inserted: 0, updated: 0, errors: [] };
|
|
1519
|
+
}
|
|
1520
|
+
const seedLoader = new SeedLoaderService(ql, md, loggerRef);
|
|
1431
1521
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1432
1522
|
const request = SeedLoaderRequestSchema.parse({
|
|
1433
|
-
datasets:
|
|
1434
|
-
config: {
|
|
1523
|
+
datasets: datasetsNow,
|
|
1524
|
+
config: {
|
|
1525
|
+
defaultMode: "upsert",
|
|
1526
|
+
multiPass: true,
|
|
1527
|
+
organizationId
|
|
1528
|
+
}
|
|
1435
1529
|
});
|
|
1436
1530
|
const result = await seedLoader.load(request);
|
|
1437
|
-
|
|
1531
|
+
return {
|
|
1438
1532
|
inserted: result.summary.totalInserted,
|
|
1439
1533
|
updated: result.summary.totalUpdated,
|
|
1440
|
-
errors: result.errors
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1534
|
+
errors: result.errors
|
|
1535
|
+
};
|
|
1536
|
+
};
|
|
1537
|
+
registerSvc("seed-replayer", replayer);
|
|
1538
|
+
ctx.logger.info(`[Seeder] Registered ${normalizedDatasets.length} datasets + replayer on kernel (total datasets: ${merged.length})`);
|
|
1539
|
+
} catch (e) {
|
|
1540
|
+
ctx.logger.warn("[Seeder] Failed to register seed-datasets/seed-replayer service", { error: e?.message });
|
|
1541
|
+
}
|
|
1542
|
+
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "true").toLowerCase() !== "false";
|
|
1543
|
+
if (multiTenant) {
|
|
1544
|
+
ctx.logger.info("[Seeder] multi-tenant mode \u2014 skipping inline seed; per-org replay will run on sys_organization insert");
|
|
1545
|
+
} else {
|
|
1546
|
+
try {
|
|
1547
|
+
const metadata = ctx.getService("metadata");
|
|
1548
|
+
if (metadata) {
|
|
1549
|
+
const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
|
|
1550
|
+
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1551
|
+
const request = SeedLoaderRequestSchema.parse({
|
|
1552
|
+
datasets: normalizedDatasets,
|
|
1553
|
+
config: { defaultMode: "upsert", multiPass: true }
|
|
1554
|
+
});
|
|
1555
|
+
const result = await seedLoader.load(request);
|
|
1556
|
+
ctx.logger.info("[Seeder] Seed loading complete", {
|
|
1557
|
+
inserted: result.summary.totalInserted,
|
|
1558
|
+
updated: result.summary.totalUpdated,
|
|
1559
|
+
errors: result.errors.length
|
|
1560
|
+
});
|
|
1561
|
+
} else {
|
|
1562
|
+
ctx.logger.debug("[Seeder] No metadata service; using basic insert fallback");
|
|
1563
|
+
for (const dataset of normalizedDatasets) {
|
|
1564
|
+
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`);
|
|
1565
|
+
for (const record of dataset.records) {
|
|
1566
|
+
try {
|
|
1567
|
+
await ql.insert(dataset.object, record, { context: { isSystem: true } });
|
|
1568
|
+
} catch (err) {
|
|
1569
|
+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: err.message });
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
ctx.logger.info("[Seeder] Data seeding complete.");
|
|
1574
|
+
}
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
ctx.logger.warn("[Seeder] SeedLoaderService failed, falling back to basic insert", { error: err.message });
|
|
1444
1577
|
for (const dataset of normalizedDatasets) {
|
|
1445
|
-
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`);
|
|
1446
1578
|
for (const record of dataset.records) {
|
|
1447
1579
|
try {
|
|
1448
1580
|
await ql.insert(dataset.object, record, { context: { isSystem: true } });
|
|
1449
|
-
} catch (
|
|
1450
|
-
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error:
|
|
1581
|
+
} catch (insertErr) {
|
|
1582
|
+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: insertErr.message });
|
|
1451
1583
|
}
|
|
1452
1584
|
}
|
|
1453
1585
|
}
|
|
1454
|
-
ctx.logger.info("[Seeder] Data seeding complete.");
|
|
1586
|
+
ctx.logger.info("[Seeder] Data seeding complete (fallback).");
|
|
1455
1587
|
}
|
|
1456
|
-
} catch (err) {
|
|
1457
|
-
ctx.logger.warn("[Seeder] SeedLoaderService failed, falling back to basic insert", { error: err.message });
|
|
1458
|
-
for (const dataset of normalizedDatasets) {
|
|
1459
|
-
for (const record of dataset.records) {
|
|
1460
|
-
try {
|
|
1461
|
-
await ql.insert(dataset.object, record, { context: { isSystem: true } });
|
|
1462
|
-
} catch (insertErr) {
|
|
1463
|
-
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: insertErr.message });
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
ctx.logger.info("[Seeder] Data seeding complete (fallback).");
|
|
1468
1588
|
}
|
|
1469
1589
|
}
|
|
1470
1590
|
};
|
|
@@ -1523,7 +1643,7 @@ var init_app_plugin = __esm({
|
|
|
1523
1643
|
* Gracefully skips when the i18n service is not registered —
|
|
1524
1644
|
* this keeps AppPlugin resilient across server/dev/mock environments.
|
|
1525
1645
|
*/
|
|
1526
|
-
loadTranslations(ctx, appId) {
|
|
1646
|
+
async loadTranslations(ctx, appId) {
|
|
1527
1647
|
let i18nService;
|
|
1528
1648
|
try {
|
|
1529
1649
|
i18nService = ctx.getService("i18n");
|
|
@@ -1539,13 +1659,33 @@ var init_app_plugin = __esm({
|
|
|
1539
1659
|
}
|
|
1540
1660
|
if (!i18nService) {
|
|
1541
1661
|
if (bundles.length > 0) {
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1662
|
+
try {
|
|
1663
|
+
const mod = await import("@objectstack/core");
|
|
1664
|
+
const createMemoryI18n = mod.createMemoryI18n;
|
|
1665
|
+
if (typeof createMemoryI18n === "function") {
|
|
1666
|
+
const fallback = createMemoryI18n();
|
|
1667
|
+
ctx.registerService("i18n", fallback);
|
|
1668
|
+
i18nService = fallback;
|
|
1669
|
+
ctx.logger.info(
|
|
1670
|
+
`[i18n] Auto-registered in-memory i18n fallback for "${appId}" (${bundles.length} bundle(s) detected). Install I18nServicePlugin from @objectstack/service-i18n for file-based / production use.`
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
ctx.logger.warn(
|
|
1675
|
+
`[i18n] App "${appId}" has ${bundles.length} translation bundle(s) but auto-fallback failed: ${err?.message ?? err}.`
|
|
1676
|
+
);
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if (!i18nService) {
|
|
1680
|
+
ctx.logger.warn(
|
|
1681
|
+
`[i18n] App "${appId}" has ${bundles.length} translation bundle(s) but no i18n service is registered.`
|
|
1682
|
+
);
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1545
1685
|
} else {
|
|
1546
1686
|
ctx.logger.debug("[i18n] No i18n service registered; skipping translation loading", { appId });
|
|
1687
|
+
return;
|
|
1547
1688
|
}
|
|
1548
|
-
return;
|
|
1549
1689
|
}
|
|
1550
1690
|
const i18nConfig = this.bundle.i18n || (this.bundle.manifest || this.bundle)?.i18n;
|
|
1551
1691
|
if (i18nConfig?.defaultLocale && typeof i18nService.setDefaultLocale === "function") {
|
|
@@ -34364,16 +34504,382 @@ var init_dist = __esm({
|
|
|
34364
34504
|
}
|
|
34365
34505
|
});
|
|
34366
34506
|
|
|
34507
|
+
// src/cloud/platform-sso.ts
|
|
34508
|
+
var platform_sso_exports = {};
|
|
34509
|
+
__export(platform_sso_exports, {
|
|
34510
|
+
PLATFORM_SSO_PROVIDER_ID: () => PLATFORM_SSO_PROVIDER_ID,
|
|
34511
|
+
backfillPlatformSsoClients: () => backfillPlatformSsoClients,
|
|
34512
|
+
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
34513
|
+
derivePlatformSsoClientId: () => derivePlatformSsoClientId,
|
|
34514
|
+
derivePlatformSsoClientSecret: () => derivePlatformSsoClientSecret,
|
|
34515
|
+
hashPlatformSsoClientSecret: () => hashPlatformSsoClientSecret,
|
|
34516
|
+
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
34517
|
+
});
|
|
34518
|
+
function derivePlatformSsoClientId(projectId) {
|
|
34519
|
+
return `project_${projectId}`;
|
|
34520
|
+
}
|
|
34521
|
+
function derivePlatformSsoClientSecret(baseSecret, projectId) {
|
|
34522
|
+
return (0, import_node_crypto2.createHmac)("sha256", baseSecret).update(`oauth-client:${projectId}`).digest("hex");
|
|
34523
|
+
}
|
|
34524
|
+
function hashPlatformSsoClientSecret(plaintext) {
|
|
34525
|
+
return (0, import_node_crypto2.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
34526
|
+
}
|
|
34527
|
+
function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
|
|
34528
|
+
let host;
|
|
34529
|
+
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
|
|
34530
|
+
host = hostname;
|
|
34531
|
+
} else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
|
|
34532
|
+
const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
34533
|
+
const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
|
|
34534
|
+
host = `http://${hostWithPort}`;
|
|
34535
|
+
} else {
|
|
34536
|
+
host = `https://${hostname}`;
|
|
34537
|
+
}
|
|
34538
|
+
const trimmed = host.replace(/\/+$/, "");
|
|
34539
|
+
const path = basePath.replace(/\/+$/, "");
|
|
34540
|
+
return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
|
|
34541
|
+
}
|
|
34542
|
+
async function seedPlatformSsoClient(opts) {
|
|
34543
|
+
const { ql, projectId, hostname, baseSecret, logger, throwOnError } = opts;
|
|
34544
|
+
if (!baseSecret) {
|
|
34545
|
+
logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { projectId });
|
|
34546
|
+
return;
|
|
34547
|
+
}
|
|
34548
|
+
const clientId = derivePlatformSsoClientId(projectId);
|
|
34549
|
+
const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, projectId);
|
|
34550
|
+
const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
|
|
34551
|
+
const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
|
|
34552
|
+
let existing = null;
|
|
34553
|
+
try {
|
|
34554
|
+
const rows = await ql.find("sys_oauth_application", {
|
|
34555
|
+
where: { client_id: clientId },
|
|
34556
|
+
limit: 1
|
|
34557
|
+
}, { context: { isSystem: true } });
|
|
34558
|
+
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
34559
|
+
existing = list[0] ?? null;
|
|
34560
|
+
} catch (err) {
|
|
34561
|
+
logger?.warn?.("[platform-sso] sys_oauth_application read failed \u2014 skipping seed", {
|
|
34562
|
+
projectId,
|
|
34563
|
+
error: err?.message
|
|
34564
|
+
});
|
|
34565
|
+
return;
|
|
34566
|
+
}
|
|
34567
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
34568
|
+
if (!existing) {
|
|
34569
|
+
const redirects = desiredRedirect ? [desiredRedirect] : [];
|
|
34570
|
+
try {
|
|
34571
|
+
await ql.insert("sys_oauth_application", {
|
|
34572
|
+
id: `oauthc_${projectId}`,
|
|
34573
|
+
name: `Project ${projectId}`,
|
|
34574
|
+
client_id: clientId,
|
|
34575
|
+
client_secret: clientSecretStored,
|
|
34576
|
+
type: "web",
|
|
34577
|
+
redirect_uris: JSON.stringify(redirects),
|
|
34578
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
34579
|
+
response_types: JSON.stringify(["code"]),
|
|
34580
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
34581
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
34582
|
+
require_pkce: false,
|
|
34583
|
+
skip_consent: true,
|
|
34584
|
+
disabled: false,
|
|
34585
|
+
subject_type: "public",
|
|
34586
|
+
created_at: nowIso,
|
|
34587
|
+
updated_at: nowIso
|
|
34588
|
+
}, { context: { isSystem: true } });
|
|
34589
|
+
logger?.info?.("[platform-sso] sys_oauth_application row created", { projectId, clientId });
|
|
34590
|
+
} catch (err) {
|
|
34591
|
+
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
34592
|
+
projectId,
|
|
34593
|
+
error: err?.message
|
|
34594
|
+
});
|
|
34595
|
+
if (throwOnError) throw err;
|
|
34596
|
+
}
|
|
34597
|
+
return;
|
|
34598
|
+
}
|
|
34599
|
+
let currentRedirects = [];
|
|
34600
|
+
try {
|
|
34601
|
+
const raw = existing.redirect_uris;
|
|
34602
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
34603
|
+
if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
|
|
34604
|
+
} catch {
|
|
34605
|
+
}
|
|
34606
|
+
const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
|
|
34607
|
+
const repairPatch = {
|
|
34608
|
+
name: existing.name || `Project ${projectId}`,
|
|
34609
|
+
client_secret: clientSecretStored,
|
|
34610
|
+
type: existing.type || "web",
|
|
34611
|
+
redirect_uris: JSON.stringify(mergedRedirects),
|
|
34612
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
34613
|
+
response_types: JSON.stringify(["code"]),
|
|
34614
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
34615
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
34616
|
+
require_pkce: false,
|
|
34617
|
+
skip_consent: true,
|
|
34618
|
+
disabled: false,
|
|
34619
|
+
subject_type: "public",
|
|
34620
|
+
updated_at: nowIso
|
|
34621
|
+
};
|
|
34622
|
+
try {
|
|
34623
|
+
await ql.update(
|
|
34624
|
+
"sys_oauth_application",
|
|
34625
|
+
repairPatch,
|
|
34626
|
+
{ where: { id: existing.id } },
|
|
34627
|
+
{ context: { isSystem: true } }
|
|
34628
|
+
);
|
|
34629
|
+
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
34630
|
+
projectId,
|
|
34631
|
+
clientId,
|
|
34632
|
+
redirect_uris: mergedRedirects
|
|
34633
|
+
});
|
|
34634
|
+
} catch (err) {
|
|
34635
|
+
logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
|
|
34636
|
+
projectId,
|
|
34637
|
+
error: err?.message
|
|
34638
|
+
});
|
|
34639
|
+
if (throwOnError) throw err;
|
|
34640
|
+
}
|
|
34641
|
+
}
|
|
34642
|
+
async function backfillPlatformSsoClients(opts) {
|
|
34643
|
+
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
34644
|
+
if (!baseSecret) {
|
|
34645
|
+
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
34646
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
34647
|
+
}
|
|
34648
|
+
let projects = [];
|
|
34649
|
+
try {
|
|
34650
|
+
const rows = await ql.find("sys_environment", {
|
|
34651
|
+
limit,
|
|
34652
|
+
fields: ["id", "hostname", "status"]
|
|
34653
|
+
}, { context: { isSystem: true } });
|
|
34654
|
+
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
34655
|
+
} catch (err) {
|
|
34656
|
+
logger?.warn?.("[platform-sso] backfill: sys_project read failed", {
|
|
34657
|
+
error: err?.message
|
|
34658
|
+
});
|
|
34659
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ projectId: "<scan>", error: err?.message ?? String(err) }] };
|
|
34660
|
+
}
|
|
34661
|
+
let seeded = 0;
|
|
34662
|
+
let alreadyExisted = 0;
|
|
34663
|
+
const failures = [];
|
|
34664
|
+
for (const p of projects) {
|
|
34665
|
+
if (!p?.id) continue;
|
|
34666
|
+
const before = await (async () => {
|
|
34667
|
+
try {
|
|
34668
|
+
const r = await ql.find("sys_oauth_application", {
|
|
34669
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
34670
|
+
limit: 1
|
|
34671
|
+
}, { context: { isSystem: true } });
|
|
34672
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
34673
|
+
return list[0] ?? null;
|
|
34674
|
+
} catch {
|
|
34675
|
+
return null;
|
|
34676
|
+
}
|
|
34677
|
+
})();
|
|
34678
|
+
try {
|
|
34679
|
+
await seedPlatformSsoClient({ ql, projectId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
34680
|
+
if (before) alreadyExisted++;
|
|
34681
|
+
else {
|
|
34682
|
+
const after = await (async () => {
|
|
34683
|
+
try {
|
|
34684
|
+
const r = await ql.find("sys_oauth_application", {
|
|
34685
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
34686
|
+
limit: 1
|
|
34687
|
+
}, { context: { isSystem: true } });
|
|
34688
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
34689
|
+
return list[0] ?? null;
|
|
34690
|
+
} catch (err) {
|
|
34691
|
+
return { _readErr: err?.message };
|
|
34692
|
+
}
|
|
34693
|
+
})();
|
|
34694
|
+
if (after && !after._readErr) seeded++;
|
|
34695
|
+
else failures.push({ projectId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
34696
|
+
}
|
|
34697
|
+
} catch (err) {
|
|
34698
|
+
failures.push({ projectId: p.id, error: err?.message ?? String(err) });
|
|
34699
|
+
}
|
|
34700
|
+
}
|
|
34701
|
+
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
34702
|
+
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
34703
|
+
}
|
|
34704
|
+
var import_node_crypto2, PLATFORM_SSO_PROVIDER_ID;
|
|
34705
|
+
var init_platform_sso = __esm({
|
|
34706
|
+
"src/cloud/platform-sso.ts"() {
|
|
34707
|
+
"use strict";
|
|
34708
|
+
import_node_crypto2 = require("crypto");
|
|
34709
|
+
PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
34710
|
+
}
|
|
34711
|
+
});
|
|
34712
|
+
|
|
34713
|
+
// src/cloud/project-org-seed.ts
|
|
34714
|
+
var project_org_seed_exports = {};
|
|
34715
|
+
__export(project_org_seed_exports, {
|
|
34716
|
+
seedProjectMember: () => seedProjectMember,
|
|
34717
|
+
seedProjectOrganization: () => seedProjectOrganization
|
|
34718
|
+
});
|
|
34719
|
+
async function seedProjectOrganization(kernel, seed, logger) {
|
|
34720
|
+
if (!seed?.id || !seed?.name) return "skipped";
|
|
34721
|
+
try {
|
|
34722
|
+
const ql = kernel.getService("objectql");
|
|
34723
|
+
if (!ql?.insert || !ql?.find) {
|
|
34724
|
+
logger?.warn?.("[seedProjectOrganization] objectql service unavailable", { orgId: seed.id });
|
|
34725
|
+
return "skipped";
|
|
34726
|
+
}
|
|
34727
|
+
try {
|
|
34728
|
+
const existing = await ql.find(SYS_ORG, { where: { id: seed.id } });
|
|
34729
|
+
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
34730
|
+
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
34731
|
+
} catch {
|
|
34732
|
+
}
|
|
34733
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
34734
|
+
await ql.insert(SYS_ORG, {
|
|
34735
|
+
id: seed.id,
|
|
34736
|
+
name: seed.name,
|
|
34737
|
+
slug: seed.slug ?? null,
|
|
34738
|
+
logo: seed.logo ?? null,
|
|
34739
|
+
metadata: null,
|
|
34740
|
+
created_at: nowIso
|
|
34741
|
+
});
|
|
34742
|
+
logger?.info?.("[seedProjectOrganization] org seeded", {
|
|
34743
|
+
orgId: seed.id,
|
|
34744
|
+
name: seed.name
|
|
34745
|
+
});
|
|
34746
|
+
return "inserted";
|
|
34747
|
+
} catch (err) {
|
|
34748
|
+
logger?.warn?.("[seedProjectOrganization] failed (non-fatal)", {
|
|
34749
|
+
orgId: seed.id,
|
|
34750
|
+
error: err?.message
|
|
34751
|
+
});
|
|
34752
|
+
return "error";
|
|
34753
|
+
}
|
|
34754
|
+
}
|
|
34755
|
+
async function seedProjectMember(kernel, args, logger) {
|
|
34756
|
+
const { userId, organizationId } = args;
|
|
34757
|
+
const role = args.role ?? "member";
|
|
34758
|
+
if (!userId || !organizationId) return "skipped";
|
|
34759
|
+
try {
|
|
34760
|
+
const ql = kernel.getService("objectql");
|
|
34761
|
+
if (!ql?.insert || !ql?.find) {
|
|
34762
|
+
logger?.warn?.("[seedProjectMember] objectql service unavailable", { userId, organizationId });
|
|
34763
|
+
return "skipped";
|
|
34764
|
+
}
|
|
34765
|
+
try {
|
|
34766
|
+
const existing = await ql.find("sys_member", {
|
|
34767
|
+
where: { user_id: userId, organization_id: organizationId }
|
|
34768
|
+
});
|
|
34769
|
+
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
34770
|
+
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
34771
|
+
} catch {
|
|
34772
|
+
}
|
|
34773
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
34774
|
+
const memId = `mem_${Math.random().toString(36).slice(2, 14)}`;
|
|
34775
|
+
await ql.insert("sys_member", {
|
|
34776
|
+
id: memId,
|
|
34777
|
+
organization_id: organizationId,
|
|
34778
|
+
user_id: userId,
|
|
34779
|
+
role,
|
|
34780
|
+
created_at: nowIso
|
|
34781
|
+
});
|
|
34782
|
+
logger?.info?.("[seedProjectMember] member seeded", {
|
|
34783
|
+
userId,
|
|
34784
|
+
organizationId,
|
|
34785
|
+
role
|
|
34786
|
+
});
|
|
34787
|
+
return "inserted";
|
|
34788
|
+
} catch (err) {
|
|
34789
|
+
logger?.warn?.("[seedProjectMember] failed (non-fatal)", {
|
|
34790
|
+
userId,
|
|
34791
|
+
organizationId,
|
|
34792
|
+
error: err?.message
|
|
34793
|
+
});
|
|
34794
|
+
return "error";
|
|
34795
|
+
}
|
|
34796
|
+
}
|
|
34797
|
+
var SYS_ORG;
|
|
34798
|
+
var init_project_org_seed = __esm({
|
|
34799
|
+
"src/cloud/project-org-seed.ts"() {
|
|
34800
|
+
"use strict";
|
|
34801
|
+
SYS_ORG = "sys_organization";
|
|
34802
|
+
}
|
|
34803
|
+
});
|
|
34804
|
+
|
|
34805
|
+
// src/cloud/project-owner-seed.ts
|
|
34806
|
+
var project_owner_seed_exports = {};
|
|
34807
|
+
__export(project_owner_seed_exports, {
|
|
34808
|
+
seedProjectOwner: () => seedProjectOwner
|
|
34809
|
+
});
|
|
34810
|
+
async function seedProjectOwner(kernel, seed, logger) {
|
|
34811
|
+
if (!seed?.userId || !seed?.email) return "skipped";
|
|
34812
|
+
try {
|
|
34813
|
+
const ql = kernel.getService("objectql");
|
|
34814
|
+
if (!ql?.insert || !ql?.find) {
|
|
34815
|
+
logger?.warn?.("[seedProjectOwner] objectql service unavailable", { userId: seed.userId });
|
|
34816
|
+
return "skipped";
|
|
34817
|
+
}
|
|
34818
|
+
try {
|
|
34819
|
+
const existing = await ql.find(SYS_USER, { where: { id: seed.userId } });
|
|
34820
|
+
const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
|
|
34821
|
+
if (Array.isArray(rows) && rows.length > 0) return "exists";
|
|
34822
|
+
} catch {
|
|
34823
|
+
}
|
|
34824
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
34825
|
+
await ql.insert(SYS_USER, {
|
|
34826
|
+
id: seed.userId,
|
|
34827
|
+
email: seed.email,
|
|
34828
|
+
name: seed.name ?? seed.email.split("@")[0] ?? "Owner",
|
|
34829
|
+
image: seed.image ?? null,
|
|
34830
|
+
// Cloud already verified the upstream email. Marking it verified
|
|
34831
|
+
// here is what unblocks better-auth's accountLinking check on
|
|
34832
|
+
// the first SSO callback (alongside the trustedProviders config
|
|
34833
|
+
// in plugin-auth/auth-manager.ts).
|
|
34834
|
+
email_verified: true,
|
|
34835
|
+
created_at: nowIso,
|
|
34836
|
+
updated_at: nowIso
|
|
34837
|
+
});
|
|
34838
|
+
logger?.info?.("[seedProjectOwner] owner seeded", {
|
|
34839
|
+
userId: seed.userId,
|
|
34840
|
+
email: seed.email
|
|
34841
|
+
});
|
|
34842
|
+
return "inserted";
|
|
34843
|
+
} catch (err) {
|
|
34844
|
+
logger?.warn?.("[seedProjectOwner] failed (non-fatal)", {
|
|
34845
|
+
userId: seed.userId,
|
|
34846
|
+
error: err?.message
|
|
34847
|
+
});
|
|
34848
|
+
return "error";
|
|
34849
|
+
}
|
|
34850
|
+
}
|
|
34851
|
+
var SYS_USER;
|
|
34852
|
+
var init_project_owner_seed = __esm({
|
|
34853
|
+
"src/cloud/project-owner-seed.ts"() {
|
|
34854
|
+
"use strict";
|
|
34855
|
+
SYS_USER = "sys_user";
|
|
34856
|
+
}
|
|
34857
|
+
});
|
|
34858
|
+
|
|
34367
34859
|
// src/index.ts
|
|
34368
34860
|
var index_exports = {};
|
|
34369
34861
|
__export(index_exports, {
|
|
34370
34862
|
AppPlugin: () => AppPlugin,
|
|
34863
|
+
ArtifactApiClient: () => ArtifactApiClient,
|
|
34864
|
+
ArtifactEnvironmentRegistry: () => ArtifactEnvironmentRegistry,
|
|
34865
|
+
ArtifactKernelFactory: () => ArtifactKernelFactory,
|
|
34866
|
+
AuthProxyPlugin: () => AuthProxyPlugin,
|
|
34867
|
+
DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
|
|
34371
34868
|
DriverPlugin: () => DriverPlugin,
|
|
34869
|
+
FileArtifactApiClient: () => FileArtifactApiClient,
|
|
34372
34870
|
HttpDispatcher: () => HttpDispatcher,
|
|
34373
34871
|
HttpServer: () => HttpServer,
|
|
34872
|
+
InMemoryErrorReporter: () => InMemoryErrorReporter,
|
|
34873
|
+
InMemoryMetricsRegistry: () => InMemoryMetricsRegistry,
|
|
34874
|
+
KernelManager: () => KernelManager,
|
|
34374
34875
|
MiddlewareManager: () => MiddlewareManager,
|
|
34375
|
-
|
|
34876
|
+
NoopErrorReporter: () => NoopErrorReporter,
|
|
34877
|
+
NoopMetricsRegistry: () => NoopMetricsRegistry,
|
|
34878
|
+
ObjectKernel: () => import_core4.ObjectKernel,
|
|
34879
|
+
PLATFORM_SSO_PROVIDER_ID: () => PLATFORM_SSO_PROVIDER_ID,
|
|
34376
34880
|
QuickJSScriptRunner: () => QuickJSScriptRunner,
|
|
34881
|
+
RUNTIME_METRICS: () => RUNTIME_METRICS,
|
|
34882
|
+
RateLimiter: () => RateLimiter,
|
|
34377
34883
|
RestServer: () => import_rest.RestServer,
|
|
34378
34884
|
RouteGroupBuilder: () => import_rest.RouteGroupBuilder,
|
|
34379
34885
|
RouteManager: () => import_rest.RouteManager,
|
|
@@ -34383,21 +34889,35 @@ __export(index_exports, {
|
|
|
34383
34889
|
SeedLoaderService: () => SeedLoaderService,
|
|
34384
34890
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
34385
34891
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
34892
|
+
backfillPlatformSsoClients: () => backfillPlatformSsoClients,
|
|
34893
|
+
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
34894
|
+
buildSecurityHeaders: () => buildSecurityHeaders,
|
|
34386
34895
|
collectBundleActions: () => collectBundleActions,
|
|
34387
34896
|
collectBundleFunctions: () => collectBundleFunctions,
|
|
34388
34897
|
collectBundleHooks: () => collectBundleHooks,
|
|
34898
|
+
createDefaultHostConfig: () => createDefaultHostConfig,
|
|
34389
34899
|
createDispatcherPlugin: () => createDispatcherPlugin,
|
|
34900
|
+
createObjectOSStack: () => createObjectOSStack,
|
|
34390
34901
|
createRestApiPlugin: () => import_rest.createRestApiPlugin,
|
|
34391
34902
|
createStandaloneStack: () => createStandaloneStack,
|
|
34392
34903
|
createSystemProjectPlugin: () => createSystemProjectPlugin,
|
|
34904
|
+
derivePlatformSsoClientId: () => derivePlatformSsoClientId,
|
|
34905
|
+
derivePlatformSsoClientSecret: () => derivePlatformSsoClientSecret,
|
|
34906
|
+
extractRequestId: () => extractRequestId,
|
|
34907
|
+
formatTraceparent: () => formatTraceparent,
|
|
34908
|
+
generateRequestId: () => generateRequestId,
|
|
34393
34909
|
hookBodyRunnerFactory: () => hookBodyRunnerFactory,
|
|
34394
34910
|
isHttpUrl: () => isHttpUrl,
|
|
34395
34911
|
loadArtifactBundle: () => loadArtifactBundle,
|
|
34396
34912
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
34397
|
-
|
|
34913
|
+
parseTraceparent: () => parseTraceparent,
|
|
34914
|
+
readArtifactSource: () => readArtifactSource,
|
|
34915
|
+
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
34916
|
+
resolveRequestId: () => resolveRequestId,
|
|
34917
|
+
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
34398
34918
|
});
|
|
34399
34919
|
module.exports = __toCommonJS(index_exports);
|
|
34400
|
-
var
|
|
34920
|
+
var import_core4 = require("@objectstack/core");
|
|
34401
34921
|
|
|
34402
34922
|
// src/runtime.ts
|
|
34403
34923
|
var import_core = require("@objectstack/core");
|
|
@@ -34534,15 +35054,45 @@ async function createStandaloneStack(config) {
|
|
|
34534
35054
|
new ObjectQLPlugin({ projectId })
|
|
34535
35055
|
];
|
|
34536
35056
|
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
35057
|
+
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
35058
|
+
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
35059
|
+
const manifest = artifactBundle?.manifest;
|
|
34537
35060
|
return {
|
|
34538
35061
|
plugins,
|
|
34539
35062
|
api: {
|
|
34540
35063
|
enableProjectScoping: false,
|
|
34541
35064
|
projectResolution: "none"
|
|
34542
|
-
}
|
|
35065
|
+
},
|
|
35066
|
+
...requires ? { requires } : {},
|
|
35067
|
+
...objects ? { objects } : {},
|
|
35068
|
+
...manifest ? { manifest } : {}
|
|
34543
35069
|
};
|
|
34544
35070
|
}
|
|
34545
35071
|
|
|
35072
|
+
// src/default-host.ts
|
|
35073
|
+
var import_node_path3 = require("path");
|
|
35074
|
+
var import_node_fs2 = require("fs");
|
|
35075
|
+
init_load_artifact_bundle();
|
|
35076
|
+
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
35077
|
+
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path3.resolve)(cwd, "dist/objectstack.json");
|
|
35078
|
+
if (isHttpUrl(candidate)) return candidate;
|
|
35079
|
+
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
|
|
35080
|
+
return (0, import_node_fs2.existsSync)(candidate) ? candidate : void 0;
|
|
35081
|
+
}
|
|
35082
|
+
async function createDefaultHostConfig(options = {}) {
|
|
35083
|
+
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
35084
|
+
const resolvedArtifact = resolveDefaultArtifactPath(standaloneOpts.artifactPath);
|
|
35085
|
+
if (!resolvedArtifact && requireArtifact) {
|
|
35086
|
+
throw new Error(
|
|
35087
|
+
"[createDefaultHostConfig] No artifact source available. Set OS_ARTIFACT_PATH (file path or http(s):// URL), place the artifact at <cwd>/dist/objectstack.json, or pass `{ artifactPath: ... }` explicitly. To boot an empty kernel anyway, pass `{ requireArtifact: false }`."
|
|
35088
|
+
);
|
|
35089
|
+
}
|
|
35090
|
+
return createStandaloneStack({
|
|
35091
|
+
...standaloneOpts,
|
|
35092
|
+
artifactPath: resolvedArtifact
|
|
35093
|
+
});
|
|
35094
|
+
}
|
|
35095
|
+
|
|
34546
35096
|
// src/index.ts
|
|
34547
35097
|
init_driver_plugin();
|
|
34548
35098
|
init_app_plugin();
|
|
@@ -34897,7 +35447,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
34897
35447
|
* so project-scoped meta routes can resolve their project).
|
|
34898
35448
|
*/
|
|
34899
35449
|
async resolveEnvironmentContext(context, path) {
|
|
34900
|
-
const skipPaths = ["/
|
|
35450
|
+
const skipPaths = ["/cloud", "/health", "/discovery"];
|
|
34901
35451
|
if (skipPaths.some((p) => path.startsWith(p))) {
|
|
34902
35452
|
return;
|
|
34903
35453
|
}
|
|
@@ -34969,7 +35519,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
34969
35519
|
const qlService = await this.getObjectQLService();
|
|
34970
35520
|
const ql = qlService ?? await this.resolveService("objectql");
|
|
34971
35521
|
if (ql) {
|
|
34972
|
-
let rows = await ql.find("
|
|
35522
|
+
let rows = await ql.find("sys_environment", {
|
|
34973
35523
|
where: {
|
|
34974
35524
|
organization_id: activeOrganizationId,
|
|
34975
35525
|
is_default: true
|
|
@@ -35056,8 +35606,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35056
35606
|
const qlService = await this.getObjectQLService();
|
|
35057
35607
|
const ql = qlService ?? await this.resolveService("objectql");
|
|
35058
35608
|
if (!ql) return null;
|
|
35059
|
-
let rows = await ql.find("
|
|
35060
|
-
where: {
|
|
35609
|
+
let rows = await ql.find("sys_environment_member", {
|
|
35610
|
+
where: { environment_id: projectId, user_id: userId },
|
|
35061
35611
|
limit: 1
|
|
35062
35612
|
});
|
|
35063
35613
|
if (rows && rows.value) rows = rows.value;
|
|
@@ -35312,8 +35862,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35312
35862
|
}
|
|
35313
35863
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
35314
35864
|
}
|
|
35315
|
-
if (parts.length
|
|
35316
|
-
const
|
|
35865
|
+
if (parts.length >= 3 && parts[parts.length - 1] === "published" && (!method || method === "GET")) {
|
|
35866
|
+
const type = parts[0];
|
|
35867
|
+
const name = parts.slice(1, -1).join("/");
|
|
35317
35868
|
const metadataService = await this.getService(import_system.CoreServiceName.enum.metadata);
|
|
35318
35869
|
if (metadataService && typeof metadataService.getPublished === "function") {
|
|
35319
35870
|
const data = await metadataService.getPublished(type, name);
|
|
@@ -35330,14 +35881,16 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35330
35881
|
}
|
|
35331
35882
|
return { handled: true, response: this.error("Not found", 404) };
|
|
35332
35883
|
}
|
|
35333
|
-
if (parts.length
|
|
35334
|
-
const
|
|
35884
|
+
if (parts.length >= 2) {
|
|
35885
|
+
const type = parts[0];
|
|
35886
|
+
const name = parts.slice(1).join("/");
|
|
35335
35887
|
const packageId = query?.package || void 0;
|
|
35336
35888
|
if (method === "PUT" && body) {
|
|
35337
35889
|
const protocol = await this.resolveService("protocol");
|
|
35338
35890
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
35339
35891
|
try {
|
|
35340
|
-
const
|
|
35892
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
35893
|
+
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId });
|
|
35341
35894
|
return { handled: true, response: this.success(result) };
|
|
35342
35895
|
} catch (e) {
|
|
35343
35896
|
return { handled: true, response: this.error(e.message, 400) };
|
|
@@ -35361,7 +35914,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35361
35914
|
const scoped = scopedEnv !== void 0;
|
|
35362
35915
|
if (scoped && typeof protocol2.getMetaItem === "function") {
|
|
35363
35916
|
try {
|
|
35364
|
-
const
|
|
35917
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
35918
|
+
const data = await protocol2.getMetaItem({ type: "object", name, organizationId });
|
|
35365
35919
|
if (data && (data.item ?? data)) {
|
|
35366
35920
|
return { handled: true, response: this.success(data) };
|
|
35367
35921
|
}
|
|
@@ -35375,7 +35929,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35375
35929
|
}
|
|
35376
35930
|
if (!scoped && protocol2 && typeof protocol2.getMetaItem === "function") {
|
|
35377
35931
|
try {
|
|
35378
|
-
const
|
|
35932
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
35933
|
+
const data = await protocol2.getMetaItem({ type: "object", name, organizationId });
|
|
35379
35934
|
if (data && (data.item ?? data)) {
|
|
35380
35935
|
return { handled: true, response: this.success(data) };
|
|
35381
35936
|
}
|
|
@@ -35388,7 +35943,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35388
35943
|
const protocol = await this.resolveService("protocol");
|
|
35389
35944
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
35390
35945
|
try {
|
|
35391
|
-
const
|
|
35946
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
35947
|
+
const data = await protocol.getMetaItem({ type: singularType, name, packageId, organizationId });
|
|
35392
35948
|
return { handled: true, response: this.success(data) };
|
|
35393
35949
|
} catch (e) {
|
|
35394
35950
|
}
|
|
@@ -35412,7 +35968,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35412
35968
|
const protocol = await this.resolveService("protocol");
|
|
35413
35969
|
if (protocol && typeof protocol.getMetaItems === "function") {
|
|
35414
35970
|
try {
|
|
35415
|
-
const
|
|
35971
|
+
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
35972
|
+
const data = await protocol.getMetaItems({ type: typeOrName, packageId, organizationId });
|
|
35416
35973
|
if (data && (data.items !== void 0 || Array.isArray(data))) {
|
|
35417
35974
|
return { handled: true, response: this.success(data) };
|
|
35418
35975
|
}
|
|
@@ -35751,6 +36308,61 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35751
36308
|
* Physical database addressing (database_url, database_driver, etc.)
|
|
35752
36309
|
* is stored directly on the sys_project row.
|
|
35753
36310
|
*/
|
|
36311
|
+
/**
|
|
36312
|
+
* Resolve the calling user id from the request session, if any.
|
|
36313
|
+
* Returns `undefined` for anonymous calls or when auth is not wired up.
|
|
36314
|
+
*/
|
|
36315
|
+
async resolveActiveOrganizationId(context) {
|
|
36316
|
+
try {
|
|
36317
|
+
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
36318
|
+
const rawHeaders = context.request?.headers;
|
|
36319
|
+
let headers = rawHeaders;
|
|
36320
|
+
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
36321
|
+
try {
|
|
36322
|
+
const h = new Headers();
|
|
36323
|
+
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
36324
|
+
if (v == null) continue;
|
|
36325
|
+
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
36326
|
+
}
|
|
36327
|
+
headers = h;
|
|
36328
|
+
} catch {
|
|
36329
|
+
headers = rawHeaders;
|
|
36330
|
+
}
|
|
36331
|
+
}
|
|
36332
|
+
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
36333
|
+
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
36334
|
+
const oid = sessionData?.session?.activeOrganizationId;
|
|
36335
|
+
return typeof oid === "string" && oid.length > 0 ? oid : void 0;
|
|
36336
|
+
} catch {
|
|
36337
|
+
return void 0;
|
|
36338
|
+
}
|
|
36339
|
+
}
|
|
36340
|
+
async resolveCallerUserId(context) {
|
|
36341
|
+
try {
|
|
36342
|
+
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
36343
|
+
const rawHeaders = context.request?.headers;
|
|
36344
|
+
let headers = rawHeaders;
|
|
36345
|
+
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
36346
|
+
try {
|
|
36347
|
+
const h = new Headers();
|
|
36348
|
+
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
36349
|
+
if (v == null) continue;
|
|
36350
|
+
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
36351
|
+
}
|
|
36352
|
+
headers = h;
|
|
36353
|
+
} catch {
|
|
36354
|
+
headers = rawHeaders;
|
|
36355
|
+
}
|
|
36356
|
+
}
|
|
36357
|
+
const sessionData = await (authService?.auth?.api?.getSession ?? authService?.api?.getSession)?.call(
|
|
36358
|
+
authService?.auth?.api ?? authService?.api,
|
|
36359
|
+
{ headers }
|
|
36360
|
+
);
|
|
36361
|
+
return sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
36362
|
+
} catch (e) {
|
|
36363
|
+
return void 0;
|
|
36364
|
+
}
|
|
36365
|
+
}
|
|
35754
36366
|
async handleCloud(path, method, body, query, _context) {
|
|
35755
36367
|
const m = method.toUpperCase();
|
|
35756
36368
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -35759,9 +36371,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35759
36371
|
if (!ql) {
|
|
35760
36372
|
return { handled: true, response: this.error("Project service not available (ObjectQL missing)", 503) };
|
|
35761
36373
|
}
|
|
35762
|
-
const ENV = "
|
|
35763
|
-
const CRED = "
|
|
35764
|
-
const MEM = "
|
|
36374
|
+
const ENV = "sys_environment";
|
|
36375
|
+
const CRED = "sys_environment_credential";
|
|
36376
|
+
const MEM = "sys_environment_member";
|
|
35765
36377
|
const PKG_INSTALL = "sys_package_installation";
|
|
35766
36378
|
const PKG = "sys_package";
|
|
35767
36379
|
const PKG_VERSION = "sys_package_version";
|
|
@@ -35809,7 +36421,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35809
36421
|
const pkgRow = await ql.findOne(PKG, { where: { manifest_id: manifestId } });
|
|
35810
36422
|
if (!pkgRow?.id) return null;
|
|
35811
36423
|
return await ql.findOne(PKG_INSTALL, {
|
|
35812
|
-
where: {
|
|
36424
|
+
where: { environment_id: envId, package_id: pkgRow.id }
|
|
35813
36425
|
});
|
|
35814
36426
|
};
|
|
35815
36427
|
const toShortName = (driverId) => {
|
|
@@ -35908,6 +36520,44 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35908
36520
|
return { handled: true, response: this.success({ templates: [], total: 0 }) };
|
|
35909
36521
|
}
|
|
35910
36522
|
}
|
|
36523
|
+
if (parts.length === 3 && parts[0] === "admin" && parts[1] === "platform-sso" && parts[2] === "backfill" && m === "POST") {
|
|
36524
|
+
const baseSecret = (process.env.OS_AUTH_SECRET ?? process.env.AUTH_SECRET ?? "").trim();
|
|
36525
|
+
if (!baseSecret) {
|
|
36526
|
+
return { handled: true, response: this.error("OS_AUTH_SECRET not configured on this worker", 503) };
|
|
36527
|
+
}
|
|
36528
|
+
const rawHeaders = _context?.request?.headers;
|
|
36529
|
+
let authHeader;
|
|
36530
|
+
if (rawHeaders && typeof rawHeaders.get === "function") {
|
|
36531
|
+
authHeader = rawHeaders.get("authorization") ?? void 0;
|
|
36532
|
+
} else if (rawHeaders && typeof rawHeaders === "object") {
|
|
36533
|
+
authHeader = rawHeaders["authorization"] ?? rawHeaders["Authorization"];
|
|
36534
|
+
}
|
|
36535
|
+
const presented = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
|
|
36536
|
+
if (!presented || presented !== baseSecret) {
|
|
36537
|
+
return { handled: true, response: this.error("forbidden: Bearer token must match OS_AUTH_SECRET", 403) };
|
|
36538
|
+
}
|
|
36539
|
+
try {
|
|
36540
|
+
const { backfillPlatformSsoClients: backfillPlatformSsoClients2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
36541
|
+
const result = await backfillPlatformSsoClients2({
|
|
36542
|
+
ql,
|
|
36543
|
+
baseSecret,
|
|
36544
|
+
logger: console
|
|
36545
|
+
});
|
|
36546
|
+
let sample = [];
|
|
36547
|
+
let total = 0;
|
|
36548
|
+
try {
|
|
36549
|
+
const rows = await ql.find("sys_oauth_application", { limit: 5 }, { context: { isSystem: true } });
|
|
36550
|
+
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
36551
|
+
sample = list;
|
|
36552
|
+
total = typeof rows?.total === "number" ? rows.total : list.length;
|
|
36553
|
+
} catch (e) {
|
|
36554
|
+
sample = [{ _readErr: e?.message ?? String(e) }];
|
|
36555
|
+
}
|
|
36556
|
+
return { handled: true, response: this.success({ ...result, total, sample }) };
|
|
36557
|
+
} catch (err) {
|
|
36558
|
+
return { handled: true, response: this.error(`backfill failed: ${err?.message ?? String(err)}`, 500) };
|
|
36559
|
+
}
|
|
36560
|
+
}
|
|
35911
36561
|
if (parts.length === 1 && parts[0] === "projects" && m === "GET") {
|
|
35912
36562
|
const where = {};
|
|
35913
36563
|
if (query?.organizationId) where.organization_id = query.organizationId;
|
|
@@ -35921,16 +36571,26 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35921
36571
|
const req = body || {};
|
|
35922
36572
|
if (req.organization_id === "__session__" || req.created_by === "__session__") {
|
|
35923
36573
|
try {
|
|
35924
|
-
const
|
|
35925
|
-
|
|
35926
|
-
|
|
35927
|
-
}
|
|
36574
|
+
const userId = await this.resolveCallerUserId(_context);
|
|
36575
|
+
if (req.created_by === "__session__") {
|
|
36576
|
+
req.created_by = userId ?? "system";
|
|
36577
|
+
}
|
|
35928
36578
|
if (req.organization_id === "__session__") {
|
|
36579
|
+
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
36580
|
+
const rawHeaders = _context?.request?.headers;
|
|
36581
|
+
let headers = rawHeaders;
|
|
36582
|
+
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
36583
|
+
const h = new Headers();
|
|
36584
|
+
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
36585
|
+
if (v == null) continue;
|
|
36586
|
+
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
36587
|
+
}
|
|
36588
|
+
headers = h;
|
|
36589
|
+
}
|
|
36590
|
+
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
36591
|
+
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
35929
36592
|
req.organization_id = sessionData?.session?.activeOrganizationId ?? void 0;
|
|
35930
36593
|
}
|
|
35931
|
-
if (req.created_by === "__session__") {
|
|
35932
|
-
req.created_by = sessionData?.user?.id ?? "system";
|
|
35933
|
-
}
|
|
35934
36594
|
} catch {
|
|
35935
36595
|
}
|
|
35936
36596
|
}
|
|
@@ -35968,14 +36628,14 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35968
36628
|
try {
|
|
35969
36629
|
const orgRow = await findOne("sys_organization", { id: req.organization_id });
|
|
35970
36630
|
const orgSlug = orgRow?.slug || req.organization_id;
|
|
35971
|
-
const rootDomain = (0, import_core2.getEnv)("ROOT_DOMAIN", "objectstack.app");
|
|
36631
|
+
const rootDomain = (0, import_core2.getEnv)("OS_ROOT_DOMAIN") ?? (0, import_core2.getEnv)("ROOT_DOMAIN", "objectstack.app");
|
|
35972
36632
|
computedHostname = `${orgSlug}-${shortId}.${rootDomain}`;
|
|
35973
36633
|
} catch {
|
|
35974
36634
|
computedHostname = `${req.organization_id}-${shortId}.objectstack.app`;
|
|
35975
36635
|
}
|
|
35976
36636
|
}
|
|
35977
36637
|
try {
|
|
35978
|
-
const existing = await findOne("
|
|
36638
|
+
const existing = await findOne("sys_environment", {
|
|
35979
36639
|
hostname: computedHostname
|
|
35980
36640
|
});
|
|
35981
36641
|
if (existing && existing.id !== projectId) {
|
|
@@ -35993,6 +36653,40 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
35993
36653
|
const baseMetadata = { ...req.metadata ?? {} };
|
|
35994
36654
|
const simulateFailure = Boolean(baseMetadata.__simulateFailure);
|
|
35995
36655
|
const simulateDelayMs = Number(baseMetadata.__simulateDelayMs ?? 1500);
|
|
36656
|
+
try {
|
|
36657
|
+
let ownerUserId = req.created_by && req.created_by !== "system" ? String(req.created_by) : void 0;
|
|
36658
|
+
if (!ownerUserId) {
|
|
36659
|
+
ownerUserId = await this.resolveCallerUserId(_context);
|
|
36660
|
+
}
|
|
36661
|
+
if (ownerUserId) {
|
|
36662
|
+
const userRow = await ql.find("sys_user", { where: { id: ownerUserId } });
|
|
36663
|
+
const userRows = Array.isArray(userRow) ? userRow : userRow?.value ?? [];
|
|
36664
|
+
const u = Array.isArray(userRows) && userRows.length > 0 ? userRows[0] : null;
|
|
36665
|
+
if (u?.email) {
|
|
36666
|
+
baseMetadata.ownerSeed = {
|
|
36667
|
+
userId: String(ownerUserId),
|
|
36668
|
+
email: String(u.email),
|
|
36669
|
+
name: u.name ? String(u.name) : null,
|
|
36670
|
+
image: u.image ? String(u.image) : null
|
|
36671
|
+
};
|
|
36672
|
+
}
|
|
36673
|
+
}
|
|
36674
|
+
} catch {
|
|
36675
|
+
}
|
|
36676
|
+
try {
|
|
36677
|
+
const orgRow = await ql.find("sys_organization", { where: { id: req.organization_id } });
|
|
36678
|
+
const orgRows = Array.isArray(orgRow) ? orgRow : orgRow?.value ?? [];
|
|
36679
|
+
const org = Array.isArray(orgRows) && orgRows.length > 0 ? orgRows[0] : null;
|
|
36680
|
+
if (org?.id && org?.name) {
|
|
36681
|
+
baseMetadata.orgSeed = {
|
|
36682
|
+
id: String(org.id),
|
|
36683
|
+
name: String(org.name),
|
|
36684
|
+
slug: org.slug ? String(org.slug) : null,
|
|
36685
|
+
logo: org.logo ? String(org.logo) : null
|
|
36686
|
+
};
|
|
36687
|
+
}
|
|
36688
|
+
} catch {
|
|
36689
|
+
}
|
|
35996
36690
|
await ql.insert(ENV, {
|
|
35997
36691
|
id: projectId,
|
|
35998
36692
|
organization_id: req.organization_id,
|
|
@@ -36009,8 +36703,30 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36009
36703
|
database_driver: driver,
|
|
36010
36704
|
storage_limit_mb: req.storage_limit_mb ?? 1024,
|
|
36011
36705
|
provisioned_at: null,
|
|
36012
|
-
hostname: computedHostname
|
|
36706
|
+
hostname: computedHostname,
|
|
36707
|
+
visibility: (() => {
|
|
36708
|
+
const raw = String(req.visibility ?? "private");
|
|
36709
|
+
return raw === "unlisted" ? "private" : raw;
|
|
36710
|
+
})()
|
|
36013
36711
|
});
|
|
36712
|
+
try {
|
|
36713
|
+
const { seedPlatformSsoClient: seedPlatformSsoClient2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
36714
|
+
const baseSecret = (process.env.OS_AUTH_SECRET ?? process.env.AUTH_SECRET ?? "").trim();
|
|
36715
|
+
if (baseSecret) {
|
|
36716
|
+
await seedPlatformSsoClient2({
|
|
36717
|
+
ql,
|
|
36718
|
+
projectId,
|
|
36719
|
+
hostname: computedHostname,
|
|
36720
|
+
baseSecret,
|
|
36721
|
+
logger: console
|
|
36722
|
+
});
|
|
36723
|
+
}
|
|
36724
|
+
} catch (ssoErr) {
|
|
36725
|
+
console.warn?.("[http-dispatcher] platform SSO seed failed (non-fatal)", {
|
|
36726
|
+
projectId,
|
|
36727
|
+
error: ssoErr?.message
|
|
36728
|
+
});
|
|
36729
|
+
}
|
|
36014
36730
|
const runProvisioning = async () => {
|
|
36015
36731
|
try {
|
|
36016
36732
|
if (simulateDelayMs > 0) {
|
|
@@ -36048,7 +36764,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36048
36764
|
);
|
|
36049
36765
|
await ql.insert(CRED, {
|
|
36050
36766
|
id: credentialId,
|
|
36051
|
-
|
|
36767
|
+
environment_id: projectId,
|
|
36052
36768
|
secret_ciphertext: plaintextSecret,
|
|
36053
36769
|
encryption_key_id: "noop",
|
|
36054
36770
|
authorization: "full_access",
|
|
@@ -36163,8 +36879,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36163
36879
|
if (m === "GET") {
|
|
36164
36880
|
const envRow = await findOne(ENV, { id });
|
|
36165
36881
|
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
36166
|
-
const credRow = await findOne(CRED, {
|
|
36167
|
-
const
|
|
36882
|
+
const credRow = await findOne(CRED, { environment_id: id, status: "active" });
|
|
36883
|
+
const callerUserId = await this.resolveCallerUserId(_context);
|
|
36884
|
+
const membership = callerUserId ? await findOne(MEM, { environment_id: id, user_id: callerUserId }) : await findOne(MEM, { environment_id: id });
|
|
36168
36885
|
const credMeta = credRow ? {
|
|
36169
36886
|
id: credRow.id,
|
|
36170
36887
|
status: credRow.status,
|
|
@@ -36191,6 +36908,14 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36191
36908
|
if (body?.plan !== void 0) patch.plan = body.plan;
|
|
36192
36909
|
if (body?.status !== void 0) patch.status = body.status;
|
|
36193
36910
|
if (body?.is_default !== void 0) patch.is_default = body.is_default;
|
|
36911
|
+
if (body?.visibility !== void 0) {
|
|
36912
|
+
let v = String(body.visibility);
|
|
36913
|
+
if (v === "unlisted") v = "private";
|
|
36914
|
+
if (!["private", "public"].includes(v)) {
|
|
36915
|
+
return { handled: true, response: this.error(`Invalid visibility '${v}' (expected private | public)`, 400) };
|
|
36916
|
+
}
|
|
36917
|
+
patch.visibility = v;
|
|
36918
|
+
}
|
|
36194
36919
|
if (body?.metadata !== void 0) patch.metadata = JSON.stringify(body.metadata);
|
|
36195
36920
|
patch.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
36196
36921
|
await ql.update(ENV, patch, { where: { id } });
|
|
@@ -36397,11 +37122,11 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36397
37122
|
},
|
|
36398
37123
|
{ where: { id } }
|
|
36399
37124
|
);
|
|
36400
|
-
const existingCred = await findOne(CRED, {
|
|
37125
|
+
const existingCred = await findOne(CRED, { environment_id: id, status: "active" });
|
|
36401
37126
|
if (!existingCred) {
|
|
36402
37127
|
await ql.insert(CRED, {
|
|
36403
37128
|
id: randomUUID(),
|
|
36404
|
-
|
|
37129
|
+
environment_id: id,
|
|
36405
37130
|
secret_ciphertext: retrySecret,
|
|
36406
37131
|
encryption_key_id: "noop",
|
|
36407
37132
|
authorization: "full_access",
|
|
@@ -36448,7 +37173,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36448
37173
|
const envRow = await findOne(ENV, { id });
|
|
36449
37174
|
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
36450
37175
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
36451
|
-
let existing = await ql.find(CRED, { where: {
|
|
37176
|
+
let existing = await ql.find(CRED, { where: { environment_id: id, status: "active" } });
|
|
36452
37177
|
if (existing && existing.value) existing = existing.value;
|
|
36453
37178
|
for (const row of Array.isArray(existing) ? existing : []) {
|
|
36454
37179
|
await ql.update(CRED, {
|
|
@@ -36460,7 +37185,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36460
37185
|
const credentialId = randomUUID();
|
|
36461
37186
|
await ql.insert(CRED, {
|
|
36462
37187
|
id: credentialId,
|
|
36463
|
-
|
|
37188
|
+
environment_id: id,
|
|
36464
37189
|
secret_ciphertext: plaintext,
|
|
36465
37190
|
encryption_key_id: "noop",
|
|
36466
37191
|
authorization: "full_access",
|
|
@@ -36479,14 +37204,154 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36479
37204
|
}
|
|
36480
37205
|
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "GET") {
|
|
36481
37206
|
const id = decodeURIComponent(parts[1]);
|
|
36482
|
-
let rows = await ql.find(MEM, { where: {
|
|
37207
|
+
let rows = await ql.find(MEM, { where: { environment_id: id } });
|
|
36483
37208
|
if (rows && rows.value) rows = rows.value;
|
|
36484
37209
|
const members = Array.isArray(rows) ? rows : [];
|
|
36485
|
-
|
|
37210
|
+
const userIds = Array.from(new Set(members.map((mem) => mem.user_id).filter(Boolean)));
|
|
37211
|
+
const userMap = /* @__PURE__ */ new Map();
|
|
37212
|
+
for (const uid of userIds) {
|
|
37213
|
+
let row = null;
|
|
37214
|
+
for (const tableName of ["sys_user", "user"]) {
|
|
37215
|
+
try {
|
|
37216
|
+
const u = await ql.findOne(tableName, { where: { id: uid } });
|
|
37217
|
+
row = u?.value ?? u;
|
|
37218
|
+
if (row) break;
|
|
37219
|
+
} catch {
|
|
37220
|
+
}
|
|
37221
|
+
}
|
|
37222
|
+
if (row) userMap.set(String(uid), {
|
|
37223
|
+
id: row.id,
|
|
37224
|
+
name: row.name ?? row.display_name,
|
|
37225
|
+
email: row.email,
|
|
37226
|
+
image: row.image ?? row.avatar_url
|
|
37227
|
+
});
|
|
37228
|
+
}
|
|
37229
|
+
const enriched = members.map((mem) => ({
|
|
37230
|
+
...mem,
|
|
37231
|
+
user: userMap.get(String(mem.user_id)) ?? void 0
|
|
37232
|
+
}));
|
|
37233
|
+
return { handled: true, response: this.success({ members: enriched }) };
|
|
37234
|
+
}
|
|
37235
|
+
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "POST") {
|
|
37236
|
+
const id = decodeURIComponent(parts[1]);
|
|
37237
|
+
const project = await findOne(ENV, { id });
|
|
37238
|
+
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
37239
|
+
const callerId = await this.resolveCallerUserId(_context);
|
|
37240
|
+
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
37241
|
+
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
37242
|
+
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
37243
|
+
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
37244
|
+
}
|
|
37245
|
+
const email = typeof body?.email === "string" ? String(body.email).trim().toLowerCase() : null;
|
|
37246
|
+
let inviteUserId = typeof body?.user_id === "string" ? String(body.user_id).trim() : null;
|
|
37247
|
+
let role = String(body?.role ?? "member").trim().toLowerCase();
|
|
37248
|
+
if (!["owner", "admin", "member", "viewer"].includes(role)) {
|
|
37249
|
+
return { handled: true, response: this.error(`Invalid role '${role}' (expected owner | admin | member | viewer)`, 400) };
|
|
37250
|
+
}
|
|
37251
|
+
if (!email && !inviteUserId) {
|
|
37252
|
+
return { handled: true, response: this.error("email or user_id is required", 400) };
|
|
37253
|
+
}
|
|
37254
|
+
if (!inviteUserId && email) {
|
|
37255
|
+
let row = null;
|
|
37256
|
+
for (const tableName of ["sys_user", "user"]) {
|
|
37257
|
+
try {
|
|
37258
|
+
const u = await ql.findOne(tableName, { where: { email } });
|
|
37259
|
+
row = u?.value ?? u;
|
|
37260
|
+
if (row) break;
|
|
37261
|
+
} catch {
|
|
37262
|
+
}
|
|
37263
|
+
}
|
|
37264
|
+
if (!row?.id) {
|
|
37265
|
+
return { handled: true, response: this.error(`No user found with email '${email}'`, 404) };
|
|
37266
|
+
}
|
|
37267
|
+
inviteUserId = String(row.id);
|
|
37268
|
+
}
|
|
37269
|
+
const existing = await findOne(MEM, { environment_id: id, user_id: inviteUserId });
|
|
37270
|
+
if (existing) {
|
|
37271
|
+
return { handled: true, response: this.success({ member: existing, alreadyMember: true }) };
|
|
37272
|
+
}
|
|
37273
|
+
try {
|
|
37274
|
+
const memberId = randomUUID();
|
|
37275
|
+
await ql.insert(MEM, {
|
|
37276
|
+
id: memberId,
|
|
37277
|
+
environment_id: id,
|
|
37278
|
+
user_id: inviteUserId,
|
|
37279
|
+
role,
|
|
37280
|
+
invited_by: callerId,
|
|
37281
|
+
organization_id: project.organization_id ?? null
|
|
37282
|
+
});
|
|
37283
|
+
const created = await findOne(MEM, { id: memberId });
|
|
37284
|
+
return { handled: true, response: this.success({ member: created, alreadyMember: false }) };
|
|
37285
|
+
} catch (e) {
|
|
37286
|
+
return { handled: true, response: this.error(e?.message ?? "Failed to add member", 500) };
|
|
37287
|
+
}
|
|
37288
|
+
}
|
|
37289
|
+
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "PATCH") {
|
|
37290
|
+
const id = decodeURIComponent(parts[1]);
|
|
37291
|
+
const memberId = decodeURIComponent(parts[3]);
|
|
37292
|
+
const project = await findOne(ENV, { id });
|
|
37293
|
+
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
37294
|
+
const callerId = await this.resolveCallerUserId(_context);
|
|
37295
|
+
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
37296
|
+
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
37297
|
+
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
37298
|
+
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
37299
|
+
}
|
|
37300
|
+
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
37301
|
+
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
37302
|
+
const newRole = String(body?.role ?? "").trim().toLowerCase();
|
|
37303
|
+
if (!["owner", "admin", "member", "viewer"].includes(newRole)) {
|
|
37304
|
+
return { handled: true, response: this.error(`Invalid role '${newRole}'`, 400) };
|
|
37305
|
+
}
|
|
37306
|
+
if (target.role === "owner" && newRole !== "owner") {
|
|
37307
|
+
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
37308
|
+
if (owners && owners.value) owners = owners.value;
|
|
37309
|
+
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
37310
|
+
if (ownerCount <= 1) {
|
|
37311
|
+
return { handled: true, response: this.error("Cannot demote the last owner", 409) };
|
|
37312
|
+
}
|
|
37313
|
+
}
|
|
37314
|
+
try {
|
|
37315
|
+
await ql.update(MEM, { role: newRole, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, { where: { id: memberId } });
|
|
37316
|
+
const updated = await findOne(MEM, { id: memberId });
|
|
37317
|
+
return { handled: true, response: this.success({ member: updated }) };
|
|
37318
|
+
} catch (e) {
|
|
37319
|
+
return { handled: true, response: this.error(e?.message ?? "Failed to update role", 500) };
|
|
37320
|
+
}
|
|
37321
|
+
}
|
|
37322
|
+
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "DELETE") {
|
|
37323
|
+
const id = decodeURIComponent(parts[1]);
|
|
37324
|
+
const memberId = decodeURIComponent(parts[3]);
|
|
37325
|
+
const project = await findOne(ENV, { id });
|
|
37326
|
+
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
37327
|
+
const callerId = await this.resolveCallerUserId(_context);
|
|
37328
|
+
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
37329
|
+
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
37330
|
+
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
37331
|
+
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
37332
|
+
const isSelf = String(target.user_id) === String(callerId);
|
|
37333
|
+
const isPrivileged = callerMem && ["owner", "admin"].includes(String(callerMem.role));
|
|
37334
|
+
if (!isSelf && !isPrivileged) {
|
|
37335
|
+
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
37336
|
+
}
|
|
37337
|
+
if (target.role === "owner") {
|
|
37338
|
+
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
37339
|
+
if (owners && owners.value) owners = owners.value;
|
|
37340
|
+
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
37341
|
+
if (ownerCount <= 1) {
|
|
37342
|
+
return { handled: true, response: this.error("Cannot remove the last owner", 409) };
|
|
37343
|
+
}
|
|
37344
|
+
}
|
|
37345
|
+
try {
|
|
37346
|
+
await ql.delete(MEM, { where: { id: memberId } });
|
|
37347
|
+
return { handled: true, response: this.success({ removed: true, memberId }) };
|
|
37348
|
+
} catch (e) {
|
|
37349
|
+
return { handled: true, response: this.error(e?.message ?? "Failed to remove member", 500) };
|
|
37350
|
+
}
|
|
36486
37351
|
}
|
|
36487
37352
|
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
36488
37353
|
const envId = decodeURIComponent(parts[1]);
|
|
36489
|
-
let rows = await ql.find(PKG_INSTALL, { where: {
|
|
37354
|
+
let rows = await ql.find(PKG_INSTALL, { where: { environment_id: envId } });
|
|
36490
37355
|
if (rows && rows.value) rows = rows.value;
|
|
36491
37356
|
const installs = Array.isArray(rows) ? rows : [];
|
|
36492
37357
|
const packages = await Promise.all(
|
|
@@ -36547,7 +37412,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36547
37412
|
}
|
|
36548
37413
|
const resolvedVersion = version ?? manifest?.version ?? "1.0.0";
|
|
36549
37414
|
const dup = await ql.findOne(PKG_INSTALL, {
|
|
36550
|
-
where: {
|
|
37415
|
+
where: { environment_id: envId, package_id: packageId }
|
|
36551
37416
|
});
|
|
36552
37417
|
if (dup?.id) {
|
|
36553
37418
|
return { handled: true, response: this.error(`Package '${packageId}' is already installed in this project`, 409) };
|
|
@@ -36558,7 +37423,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36558
37423
|
const recordId = randomUUID();
|
|
36559
37424
|
await ql.insert(PKG_INSTALL, {
|
|
36560
37425
|
id: recordId,
|
|
36561
|
-
|
|
37426
|
+
environment_id: envId,
|
|
36562
37427
|
package_id: sysPackageId,
|
|
36563
37428
|
package_version_id: sysPackageVersionId,
|
|
36564
37429
|
status: "installed",
|
|
@@ -36578,7 +37443,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36578
37443
|
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
36579
37444
|
const envId = decodeURIComponent(parts[1]);
|
|
36580
37445
|
const pkgId = decodeURIComponent(parts[3]);
|
|
36581
|
-
const record = await ql.findOne(PKG_INSTALL, { where: {
|
|
37446
|
+
const record = await ql.findOne(PKG_INSTALL, { where: { environment_id: envId, package_id: pkgId } });
|
|
36582
37447
|
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
36583
37448
|
return { handled: true, response: this.success({ package: record }) };
|
|
36584
37449
|
}
|
|
@@ -36685,7 +37550,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36685
37550
|
*/
|
|
36686
37551
|
async deleteProjectCascade(projectId, deps) {
|
|
36687
37552
|
const { ql, findOne, getRealAdapter, force } = deps;
|
|
36688
|
-
const ENV = "
|
|
37553
|
+
const ENV = "sys_environment";
|
|
36689
37554
|
const warnings = [];
|
|
36690
37555
|
const row = await findOne(ENV, { id: projectId });
|
|
36691
37556
|
if (!row) {
|
|
@@ -36703,9 +37568,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
36703
37568
|
};
|
|
36704
37569
|
}
|
|
36705
37570
|
const cascade = [
|
|
36706
|
-
{ object: "
|
|
36707
|
-
{ object: "
|
|
36708
|
-
{ object: "sys_package_installation", field: "
|
|
37571
|
+
{ object: "sys_environment_credential", field: "environment_id" },
|
|
37572
|
+
{ object: "sys_environment_member", field: "environment_id" },
|
|
37573
|
+
{ object: "sys_package_installation", field: "environment_id" }
|
|
36709
37574
|
];
|
|
36710
37575
|
for (const { object, field } of cascade) {
|
|
36711
37576
|
try {
|
|
@@ -37381,8 +38246,316 @@ _HttpDispatcher.SYSTEM_PROJECT_ID = "00000000-0000-0000-0000-000000000001";
|
|
|
37381
38246
|
_HttpDispatcher.PLATFORM_ORG_ID = "00000000-0000-0000-0000-000000000000";
|
|
37382
38247
|
var HttpDispatcher = _HttpDispatcher;
|
|
37383
38248
|
|
|
38249
|
+
// src/security/security-headers.ts
|
|
38250
|
+
function buildSecurityHeaders(opts = {}) {
|
|
38251
|
+
const h = {};
|
|
38252
|
+
if (opts.contentSecurityPolicy !== false) {
|
|
38253
|
+
h["Content-Security-Policy"] = opts.contentSecurityPolicy ?? "default-src 'none'; frame-ancestors 'none'";
|
|
38254
|
+
}
|
|
38255
|
+
if (opts.hsts) {
|
|
38256
|
+
const cfg = typeof opts.hsts === "object" ? opts.hsts : {};
|
|
38257
|
+
const maxAge = cfg.maxAge ?? 15552e3;
|
|
38258
|
+
const parts = [`max-age=${maxAge}`];
|
|
38259
|
+
if (cfg.includeSubDomains ?? true) parts.push("includeSubDomains");
|
|
38260
|
+
if (cfg.preload) parts.push("preload");
|
|
38261
|
+
h["Strict-Transport-Security"] = parts.join("; ");
|
|
38262
|
+
}
|
|
38263
|
+
h["X-Content-Type-Options"] = "nosniff";
|
|
38264
|
+
if (opts.frameOptions !== false) {
|
|
38265
|
+
h["X-Frame-Options"] = opts.frameOptions ?? "DENY";
|
|
38266
|
+
}
|
|
38267
|
+
if (opts.referrerPolicy !== false) {
|
|
38268
|
+
h["Referrer-Policy"] = opts.referrerPolicy ?? "no-referrer";
|
|
38269
|
+
}
|
|
38270
|
+
if (opts.permissionsPolicy !== false) {
|
|
38271
|
+
h["Permissions-Policy"] = opts.permissionsPolicy ?? "geolocation=(), camera=(), microphone=(), payment=()";
|
|
38272
|
+
}
|
|
38273
|
+
if (opts.corp !== false) {
|
|
38274
|
+
h["Cross-Origin-Resource-Policy"] = opts.corp ?? "same-origin";
|
|
38275
|
+
}
|
|
38276
|
+
if (opts.extra) {
|
|
38277
|
+
Object.assign(h, opts.extra);
|
|
38278
|
+
}
|
|
38279
|
+
return h;
|
|
38280
|
+
}
|
|
38281
|
+
|
|
38282
|
+
// src/security/rate-limit.ts
|
|
38283
|
+
var MemoryStore = class {
|
|
38284
|
+
constructor(maxEntries = 1e5) {
|
|
38285
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
38286
|
+
this.maxEntries = maxEntries;
|
|
38287
|
+
}
|
|
38288
|
+
get(key) {
|
|
38289
|
+
return this.buckets.get(key);
|
|
38290
|
+
}
|
|
38291
|
+
set(key, state) {
|
|
38292
|
+
if (this.buckets.size >= this.maxEntries) {
|
|
38293
|
+
const dropCount = Math.max(1, Math.floor(this.maxEntries / 10));
|
|
38294
|
+
const iter = this.buckets.keys();
|
|
38295
|
+
for (let i = 0; i < dropCount; i++) {
|
|
38296
|
+
const k = iter.next().value;
|
|
38297
|
+
if (!k) break;
|
|
38298
|
+
this.buckets.delete(k);
|
|
38299
|
+
}
|
|
38300
|
+
}
|
|
38301
|
+
this.buckets.set(key, state);
|
|
38302
|
+
}
|
|
38303
|
+
prune(olderThanMs) {
|
|
38304
|
+
const cutoff = Date.now() - olderThanMs;
|
|
38305
|
+
for (const [k, v] of this.buckets) {
|
|
38306
|
+
if (v.lastRefill < cutoff) this.buckets.delete(k);
|
|
38307
|
+
}
|
|
38308
|
+
}
|
|
38309
|
+
};
|
|
38310
|
+
var RateLimiter = class {
|
|
38311
|
+
constructor(config, opts = {}) {
|
|
38312
|
+
if (config.capacity <= 0) throw new Error("RateLimiter: capacity must be > 0");
|
|
38313
|
+
if (config.refillPerSec <= 0) throw new Error("RateLimiter: refillPerSec must be > 0");
|
|
38314
|
+
this.config = config;
|
|
38315
|
+
this.store = opts.store ?? new MemoryStore();
|
|
38316
|
+
this.now = opts.now ?? Date.now;
|
|
38317
|
+
}
|
|
38318
|
+
/**
|
|
38319
|
+
* Attempt to consume `cost` tokens for `key`. Returns a decision
|
|
38320
|
+
* describing whether the request should proceed and, if not, how
|
|
38321
|
+
* long the caller should wait before retrying.
|
|
38322
|
+
*/
|
|
38323
|
+
consume(key, cost = this.config.defaultCost ?? 1) {
|
|
38324
|
+
const now = this.now();
|
|
38325
|
+
const { capacity, refillPerSec } = this.config;
|
|
38326
|
+
let state = this.store.get(key);
|
|
38327
|
+
if (!state) {
|
|
38328
|
+
state = { tokens: capacity, lastRefill: now };
|
|
38329
|
+
} else {
|
|
38330
|
+
const elapsedSec = (now - state.lastRefill) / 1e3;
|
|
38331
|
+
if (elapsedSec > 0) {
|
|
38332
|
+
state = {
|
|
38333
|
+
tokens: Math.min(capacity, state.tokens + elapsedSec * refillPerSec),
|
|
38334
|
+
lastRefill: now
|
|
38335
|
+
};
|
|
38336
|
+
}
|
|
38337
|
+
}
|
|
38338
|
+
if (state.tokens >= cost) {
|
|
38339
|
+
state.tokens -= cost;
|
|
38340
|
+
this.store.set(key, state);
|
|
38341
|
+
return {
|
|
38342
|
+
allowed: true,
|
|
38343
|
+
remaining: Math.floor(state.tokens),
|
|
38344
|
+
retryAfterMs: 0,
|
|
38345
|
+
resetAt: now + Math.ceil((capacity - state.tokens) / refillPerSec * 1e3)
|
|
38346
|
+
};
|
|
38347
|
+
}
|
|
38348
|
+
const tokensNeeded = cost - state.tokens;
|
|
38349
|
+
const retryAfterMs = Math.ceil(tokensNeeded / refillPerSec * 1e3);
|
|
38350
|
+
this.store.set(key, state);
|
|
38351
|
+
return {
|
|
38352
|
+
allowed: false,
|
|
38353
|
+
remaining: Math.floor(state.tokens),
|
|
38354
|
+
retryAfterMs,
|
|
38355
|
+
resetAt: now + retryAfterMs
|
|
38356
|
+
};
|
|
38357
|
+
}
|
|
38358
|
+
/** Force-reset a key (e.g. after a successful auth flow). */
|
|
38359
|
+
reset(key) {
|
|
38360
|
+
this.store.set(key, { tokens: this.config.capacity, lastRefill: this.now() });
|
|
38361
|
+
}
|
|
38362
|
+
};
|
|
38363
|
+
var DEFAULT_RATE_LIMITS = {
|
|
38364
|
+
auth: { capacity: 10, refillPerSec: 10 / 60 },
|
|
38365
|
+
write: { capacity: 60, refillPerSec: 60 / 60 },
|
|
38366
|
+
read: { capacity: 600, refillPerSec: 600 / 60 }
|
|
38367
|
+
};
|
|
38368
|
+
|
|
38369
|
+
// src/observability/request-context.ts
|
|
38370
|
+
var MAX_REQUEST_ID_LENGTH = 200;
|
|
38371
|
+
var REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
38372
|
+
function extractRequestId(headers) {
|
|
38373
|
+
if (!headers || typeof headers !== "object") return void 0;
|
|
38374
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
38375
|
+
if (k.toLowerCase() !== "x-request-id") continue;
|
|
38376
|
+
const raw = Array.isArray(v) ? v[0] : v;
|
|
38377
|
+
if (typeof raw !== "string") return void 0;
|
|
38378
|
+
const trimmed = raw.trim();
|
|
38379
|
+
if (!trimmed || trimmed.length > MAX_REQUEST_ID_LENGTH) return void 0;
|
|
38380
|
+
if (!REQUEST_ID_PATTERN.test(trimmed)) return void 0;
|
|
38381
|
+
return trimmed;
|
|
38382
|
+
}
|
|
38383
|
+
return void 0;
|
|
38384
|
+
}
|
|
38385
|
+
function generateRequestId() {
|
|
38386
|
+
const g = globalThis.crypto;
|
|
38387
|
+
if (g && typeof g.randomUUID === "function") {
|
|
38388
|
+
return `req_${g.randomUUID().replace(/-/g, "")}`;
|
|
38389
|
+
}
|
|
38390
|
+
const t = Date.now().toString(36);
|
|
38391
|
+
const r = Math.random().toString(36).slice(2, 12);
|
|
38392
|
+
return `req_${t}${r}`;
|
|
38393
|
+
}
|
|
38394
|
+
function resolveRequestId(headers, generate = generateRequestId) {
|
|
38395
|
+
return extractRequestId(headers) ?? generate();
|
|
38396
|
+
}
|
|
38397
|
+
var TRACEPARENT_PATTERN = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
38398
|
+
function parseTraceparent(value) {
|
|
38399
|
+
if (typeof value !== "string") return void 0;
|
|
38400
|
+
const m = TRACEPARENT_PATTERN.exec(value.trim().toLowerCase());
|
|
38401
|
+
if (!m) return void 0;
|
|
38402
|
+
const [, version, traceId, spanId, flags] = m;
|
|
38403
|
+
if (version !== "00") return void 0;
|
|
38404
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(spanId)) return void 0;
|
|
38405
|
+
const sampled = (parseInt(flags, 16) & 1) === 1;
|
|
38406
|
+
return { traceId, spanId, sampled };
|
|
38407
|
+
}
|
|
38408
|
+
function formatTraceparent(ctx) {
|
|
38409
|
+
const flag = ctx.sampled ? "01" : "00";
|
|
38410
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${flag}`;
|
|
38411
|
+
}
|
|
38412
|
+
|
|
38413
|
+
// src/observability/metrics.ts
|
|
38414
|
+
var NoopMetricsRegistry = class {
|
|
38415
|
+
counter() {
|
|
38416
|
+
}
|
|
38417
|
+
histogram() {
|
|
38418
|
+
}
|
|
38419
|
+
gauge() {
|
|
38420
|
+
}
|
|
38421
|
+
};
|
|
38422
|
+
var InMemoryMetricsRegistry = class {
|
|
38423
|
+
constructor() {
|
|
38424
|
+
this.samples = [];
|
|
38425
|
+
}
|
|
38426
|
+
counter(name, labels = {}, value = 1) {
|
|
38427
|
+
this.samples.push({ name, kind: "counter", value, labels, at: Date.now() });
|
|
38428
|
+
}
|
|
38429
|
+
histogram(name, value, labels = {}) {
|
|
38430
|
+
this.samples.push({ name, kind: "histogram", value, labels, at: Date.now() });
|
|
38431
|
+
}
|
|
38432
|
+
gauge(name, value, labels = {}) {
|
|
38433
|
+
this.samples.push({ name, kind: "gauge", value, labels, at: Date.now() });
|
|
38434
|
+
}
|
|
38435
|
+
/**
|
|
38436
|
+
* Sum of all counter increments matching `name` (and optionally a
|
|
38437
|
+
* label subset). Useful in tests: `metrics.totalCounter('http_requests_total', { status: '500' })`.
|
|
38438
|
+
*/
|
|
38439
|
+
totalCounter(name, labelMatch = {}) {
|
|
38440
|
+
return this.samples.filter(
|
|
38441
|
+
(s) => s.kind === "counter" && s.name === name && matchesLabels(s.labels, labelMatch)
|
|
38442
|
+
).reduce((acc, s) => acc + s.value, 0);
|
|
38443
|
+
}
|
|
38444
|
+
/**
|
|
38445
|
+
* All histogram observations matching `name` (and optionally a
|
|
38446
|
+
* label subset), as raw values.
|
|
38447
|
+
*/
|
|
38448
|
+
histogramValues(name, labelMatch = {}) {
|
|
38449
|
+
return this.samples.filter(
|
|
38450
|
+
(s) => s.kind === "histogram" && s.name === name && matchesLabels(s.labels, labelMatch)
|
|
38451
|
+
).map((s) => s.value);
|
|
38452
|
+
}
|
|
38453
|
+
/** Clear all recorded samples. */
|
|
38454
|
+
reset() {
|
|
38455
|
+
this.samples.length = 0;
|
|
38456
|
+
}
|
|
38457
|
+
};
|
|
38458
|
+
function matchesLabels(actual, expected) {
|
|
38459
|
+
for (const [k, v] of Object.entries(expected)) {
|
|
38460
|
+
if (actual[k] !== v) return false;
|
|
38461
|
+
}
|
|
38462
|
+
return true;
|
|
38463
|
+
}
|
|
38464
|
+
var RUNTIME_METRICS = {
|
|
38465
|
+
/** Counter, labels: method, route, status. */
|
|
38466
|
+
httpRequestsTotal: "http_requests_total",
|
|
38467
|
+
/** Histogram (ms), labels: method, route. */
|
|
38468
|
+
httpRequestDurationMs: "http_request_duration_ms",
|
|
38469
|
+
/** Counter, labels: method, route. Incremented when an in-flight handler throws (after the response is sent). */
|
|
38470
|
+
httpRequestErrorsTotal: "http_request_errors_total"
|
|
38471
|
+
};
|
|
38472
|
+
|
|
38473
|
+
// src/observability/error-reporter.ts
|
|
38474
|
+
var NoopErrorReporter = class {
|
|
38475
|
+
captureException() {
|
|
38476
|
+
}
|
|
38477
|
+
};
|
|
38478
|
+
var InMemoryErrorReporter = class {
|
|
38479
|
+
constructor() {
|
|
38480
|
+
this.captured = [];
|
|
38481
|
+
}
|
|
38482
|
+
captureException(error2, context = {}) {
|
|
38483
|
+
this.captured.push({ error: error2, context, at: Date.now() });
|
|
38484
|
+
}
|
|
38485
|
+
reset() {
|
|
38486
|
+
this.captured.length = 0;
|
|
38487
|
+
}
|
|
38488
|
+
};
|
|
38489
|
+
|
|
38490
|
+
// src/observability/instrument.ts
|
|
38491
|
+
function instrumentRouteHandler(method, route, handler, opts = {}) {
|
|
38492
|
+
const metrics = opts.metrics ?? new NoopMetricsRegistry();
|
|
38493
|
+
const errorReporter = opts.errorReporter ?? new NoopErrorReporter();
|
|
38494
|
+
const generateRequestId2 = opts.generateRequestId;
|
|
38495
|
+
const requestIdHeader = opts.requestIdHeader ?? "X-Request-Id";
|
|
38496
|
+
const now = opts.now ?? Date.now;
|
|
38497
|
+
return async (req, res) => {
|
|
38498
|
+
const requestId = resolveRequestId(req?.headers, generateRequestId2);
|
|
38499
|
+
try {
|
|
38500
|
+
req.requestId = requestId;
|
|
38501
|
+
} catch {
|
|
38502
|
+
}
|
|
38503
|
+
if (typeof res?.header === "function") {
|
|
38504
|
+
try {
|
|
38505
|
+
res.header(requestIdHeader, requestId);
|
|
38506
|
+
} catch {
|
|
38507
|
+
}
|
|
38508
|
+
}
|
|
38509
|
+
let status = 200;
|
|
38510
|
+
const origStatus = typeof res?.status === "function" ? res.status.bind(res) : void 0;
|
|
38511
|
+
if (origStatus) {
|
|
38512
|
+
res.status = (code) => {
|
|
38513
|
+
status = code;
|
|
38514
|
+
return origStatus(code);
|
|
38515
|
+
};
|
|
38516
|
+
}
|
|
38517
|
+
const startedAt = now();
|
|
38518
|
+
let threw = false;
|
|
38519
|
+
try {
|
|
38520
|
+
await handler(req, res);
|
|
38521
|
+
} catch (err) {
|
|
38522
|
+
threw = true;
|
|
38523
|
+
status = err?.statusCode ?? 500;
|
|
38524
|
+
metrics.counter(RUNTIME_METRICS.httpRequestErrorsTotal, { method, route });
|
|
38525
|
+
if (status >= 500) {
|
|
38526
|
+
safeReport(errorReporter, err, { requestId, method, route });
|
|
38527
|
+
}
|
|
38528
|
+
throw err;
|
|
38529
|
+
} finally {
|
|
38530
|
+
const elapsed = now() - startedAt;
|
|
38531
|
+
metrics.counter(RUNTIME_METRICS.httpRequestsTotal, {
|
|
38532
|
+
method,
|
|
38533
|
+
route,
|
|
38534
|
+
status: String(status)
|
|
38535
|
+
});
|
|
38536
|
+
metrics.histogram(
|
|
38537
|
+
RUNTIME_METRICS.httpRequestDurationMs,
|
|
38538
|
+
elapsed,
|
|
38539
|
+
{ method, route }
|
|
38540
|
+
);
|
|
38541
|
+
if (!threw && status >= 500) {
|
|
38542
|
+
const recorded = res?.__obsRecordedError;
|
|
38543
|
+
if (recorded !== void 0) {
|
|
38544
|
+
safeReport(errorReporter, recorded, { requestId, method, route });
|
|
38545
|
+
}
|
|
38546
|
+
}
|
|
38547
|
+
}
|
|
38548
|
+
};
|
|
38549
|
+
}
|
|
38550
|
+
function safeReport(reporter, err, ctx) {
|
|
38551
|
+
try {
|
|
38552
|
+
reporter.captureException(err, ctx);
|
|
38553
|
+
} catch {
|
|
38554
|
+
}
|
|
38555
|
+
}
|
|
38556
|
+
|
|
37384
38557
|
// src/dispatcher-plugin.ts
|
|
37385
|
-
function mountRouteOnServer(route, server, routePath) {
|
|
38558
|
+
function mountRouteOnServer(route, server, routePath, securityHeaders) {
|
|
37386
38559
|
const handler = async (req, res) => {
|
|
37387
38560
|
try {
|
|
37388
38561
|
const result = await route.handler({
|
|
@@ -37392,6 +38565,11 @@ function mountRouteOnServer(route, server, routePath) {
|
|
|
37392
38565
|
});
|
|
37393
38566
|
if (result.stream && result.events) {
|
|
37394
38567
|
res.status(result.status);
|
|
38568
|
+
if (securityHeaders) {
|
|
38569
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38570
|
+
res.header(k, v);
|
|
38571
|
+
}
|
|
38572
|
+
}
|
|
37395
38573
|
if (result.headers) {
|
|
37396
38574
|
for (const [k, v] of Object.entries(result.headers)) {
|
|
37397
38575
|
res.header(k, String(v));
|
|
@@ -37417,6 +38595,11 @@ function mountRouteOnServer(route, server, routePath) {
|
|
|
37417
38595
|
}
|
|
37418
38596
|
} else {
|
|
37419
38597
|
res.status(result.status);
|
|
38598
|
+
if (securityHeaders) {
|
|
38599
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38600
|
+
res.header(k, v);
|
|
38601
|
+
}
|
|
38602
|
+
}
|
|
37420
38603
|
if (result.body !== void 0) {
|
|
37421
38604
|
res.json(result.body);
|
|
37422
38605
|
} else {
|
|
@@ -37424,7 +38607,7 @@ function mountRouteOnServer(route, server, routePath) {
|
|
|
37424
38607
|
}
|
|
37425
38608
|
}
|
|
37426
38609
|
} catch (err) {
|
|
37427
|
-
|
|
38610
|
+
errorResponseBase(err, res, securityHeaders);
|
|
37428
38611
|
}
|
|
37429
38612
|
};
|
|
37430
38613
|
const m = route.method.toLowerCase();
|
|
@@ -37440,10 +38623,17 @@ function mountRouteOnServer(route, server, routePath) {
|
|
|
37440
38623
|
}
|
|
37441
38624
|
return false;
|
|
37442
38625
|
}
|
|
37443
|
-
function
|
|
38626
|
+
function sendResultBase(result, res, securityHeaders) {
|
|
38627
|
+
const applySecurityHeaders = () => {
|
|
38628
|
+
if (!securityHeaders) return;
|
|
38629
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38630
|
+
res.header(k, v);
|
|
38631
|
+
}
|
|
38632
|
+
};
|
|
37444
38633
|
if (result.handled) {
|
|
37445
38634
|
if (result.response) {
|
|
37446
38635
|
res.status(result.response.status);
|
|
38636
|
+
applySecurityHeaders();
|
|
37447
38637
|
if (result.response.headers) {
|
|
37448
38638
|
for (const [k, v] of Object.entries(result.response.headers)) {
|
|
37449
38639
|
res.header(k, v);
|
|
@@ -37453,11 +38643,15 @@ function sendResult(result, res) {
|
|
|
37453
38643
|
return;
|
|
37454
38644
|
}
|
|
37455
38645
|
if (result.result) {
|
|
37456
|
-
res.status(200)
|
|
38646
|
+
res.status(200);
|
|
38647
|
+
applySecurityHeaders();
|
|
38648
|
+
res.json(result.result);
|
|
37457
38649
|
return;
|
|
37458
38650
|
}
|
|
37459
38651
|
}
|
|
37460
|
-
res.status(404)
|
|
38652
|
+
res.status(404);
|
|
38653
|
+
applySecurityHeaders();
|
|
38654
|
+
res.json({
|
|
37461
38655
|
success: false,
|
|
37462
38656
|
error: {
|
|
37463
38657
|
message: "Not Found",
|
|
@@ -37467,9 +38661,21 @@ function sendResult(result, res) {
|
|
|
37467
38661
|
}
|
|
37468
38662
|
});
|
|
37469
38663
|
}
|
|
37470
|
-
function
|
|
38664
|
+
function errorResponseBase(err, res, securityHeaders) {
|
|
37471
38665
|
const code = err.statusCode || 500;
|
|
37472
|
-
res.status(code)
|
|
38666
|
+
res.status(code);
|
|
38667
|
+
if (securityHeaders) {
|
|
38668
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38669
|
+
res.header(k, v);
|
|
38670
|
+
}
|
|
38671
|
+
}
|
|
38672
|
+
if (code >= 500) {
|
|
38673
|
+
try {
|
|
38674
|
+
res.__obsRecordedError = err;
|
|
38675
|
+
} catch {
|
|
38676
|
+
}
|
|
38677
|
+
}
|
|
38678
|
+
res.json({
|
|
37473
38679
|
success: false,
|
|
37474
38680
|
error: { message: err.message || "Internal Server Error", code }
|
|
37475
38681
|
});
|
|
@@ -37494,10 +38700,52 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37494
38700
|
enforceProjectMembership: enforceMembership
|
|
37495
38701
|
});
|
|
37496
38702
|
const prefix = config.prefix || "/api/v1";
|
|
38703
|
+
const securityHeaders = config.securityHeaders === false ? void 0 : buildSecurityHeaders(
|
|
38704
|
+
typeof config.securityHeaders === "object" ? config.securityHeaders : {}
|
|
38705
|
+
);
|
|
38706
|
+
const sendResult = (result, res) => sendResultBase(result, res, securityHeaders);
|
|
38707
|
+
const errorResponse = (err, res) => errorResponseBase(err, res, securityHeaders);
|
|
38708
|
+
const metrics = config.observability?.metrics ?? new NoopMetricsRegistry();
|
|
38709
|
+
const errorReporter = config.observability?.errorReporter ?? new NoopErrorReporter();
|
|
38710
|
+
const generateRequestId2 = config.observability?.generateRequestId;
|
|
38711
|
+
const requestIdHeader = config.observability?.requestIdHeader ?? "X-Request-Id";
|
|
38712
|
+
const rawServer = server;
|
|
38713
|
+
server = new Proxy(rawServer, {
|
|
38714
|
+
get(target, prop, receiver) {
|
|
38715
|
+
if (prop === "get" || prop === "post" || prop === "delete") {
|
|
38716
|
+
const method = String(prop).toUpperCase();
|
|
38717
|
+
const original = target[prop];
|
|
38718
|
+
if (typeof original !== "function") return original;
|
|
38719
|
+
return (route, handler) => {
|
|
38720
|
+
return original.call(
|
|
38721
|
+
target,
|
|
38722
|
+
route,
|
|
38723
|
+
instrumentRouteHandler(method, route, handler, {
|
|
38724
|
+
metrics,
|
|
38725
|
+
errorReporter,
|
|
38726
|
+
generateRequestId: generateRequestId2,
|
|
38727
|
+
requestIdHeader
|
|
38728
|
+
})
|
|
38729
|
+
);
|
|
38730
|
+
};
|
|
38731
|
+
}
|
|
38732
|
+
return Reflect.get(target, prop, receiver);
|
|
38733
|
+
}
|
|
38734
|
+
});
|
|
37497
38735
|
server.get("/.well-known/objectstack", async (_req, res) => {
|
|
38736
|
+
if (securityHeaders) {
|
|
38737
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38738
|
+
res.header(k, v);
|
|
38739
|
+
}
|
|
38740
|
+
}
|
|
37498
38741
|
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
37499
38742
|
});
|
|
37500
38743
|
server.get(`${prefix}/discovery`, async (_req, res) => {
|
|
38744
|
+
if (securityHeaders) {
|
|
38745
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38746
|
+
res.header(k, v);
|
|
38747
|
+
}
|
|
38748
|
+
}
|
|
37501
38749
|
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
37502
38750
|
});
|
|
37503
38751
|
server.get(`${prefix}/health`, async (_req, res) => {
|
|
@@ -37519,6 +38767,11 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37519
38767
|
server.post(`${prefix}/graphql`, async (req, res) => {
|
|
37520
38768
|
try {
|
|
37521
38769
|
const result = await dispatcher.handleGraphQL(req.body, { request: req });
|
|
38770
|
+
if (securityHeaders) {
|
|
38771
|
+
for (const [k, v] of Object.entries(securityHeaders)) {
|
|
38772
|
+
res.header(k, v);
|
|
38773
|
+
}
|
|
38774
|
+
}
|
|
37522
38775
|
res.json(result);
|
|
37523
38776
|
} catch (err) {
|
|
37524
38777
|
errorResponse(err, res);
|
|
@@ -37526,7 +38779,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37526
38779
|
});
|
|
37527
38780
|
server.post(`${prefix}/analytics/query`, async (req, res) => {
|
|
37528
38781
|
try {
|
|
37529
|
-
const result = await dispatcher.
|
|
38782
|
+
const result = await dispatcher.dispatch("POST", "/analytics/query", req.body, req.query, { request: req });
|
|
37530
38783
|
sendResult(result, res);
|
|
37531
38784
|
} catch (err) {
|
|
37532
38785
|
errorResponse(err, res);
|
|
@@ -37534,7 +38787,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37534
38787
|
});
|
|
37535
38788
|
server.get(`${prefix}/analytics/meta`, async (req, res) => {
|
|
37536
38789
|
try {
|
|
37537
|
-
const result = await dispatcher.
|
|
38790
|
+
const result = await dispatcher.dispatch("GET", "/analytics/meta", void 0, req.query, { request: req });
|
|
37538
38791
|
sendResult(result, res);
|
|
37539
38792
|
} catch (err) {
|
|
37540
38793
|
errorResponse(err, res);
|
|
@@ -37542,7 +38795,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37542
38795
|
});
|
|
37543
38796
|
server.post(`${prefix}/analytics/sql`, async (req, res) => {
|
|
37544
38797
|
try {
|
|
37545
|
-
const result = await dispatcher.
|
|
38798
|
+
const result = await dispatcher.dispatch("POST", "/analytics/sql", req.body, req.query, { request: req });
|
|
37546
38799
|
sendResult(result, res);
|
|
37547
38800
|
} catch (err) {
|
|
37548
38801
|
errorResponse(err, res);
|
|
@@ -37620,6 +38873,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37620
38873
|
errorResponse(err, res);
|
|
37621
38874
|
}
|
|
37622
38875
|
});
|
|
38876
|
+
server.post(`${prefix}/cloud/admin/platform-sso/backfill`, async (req, res) => {
|
|
38877
|
+
try {
|
|
38878
|
+
const result = await dispatcher.handleCloud("/admin/platform-sso/backfill", "POST", req.body, req.query, { request: req });
|
|
38879
|
+
sendResult(result, res);
|
|
38880
|
+
} catch (err) {
|
|
38881
|
+
errorResponse(err, res);
|
|
38882
|
+
}
|
|
38883
|
+
});
|
|
37623
38884
|
server.get(`${prefix}/cloud/templates`, async (req, res) => {
|
|
37624
38885
|
try {
|
|
37625
38886
|
const result = await dispatcher.handleCloud("/templates", "GET", {}, req.query, { request: req });
|
|
@@ -37732,6 +38993,30 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37732
38993
|
errorResponse(err, res);
|
|
37733
38994
|
}
|
|
37734
38995
|
});
|
|
38996
|
+
server.post(`${prefix}/cloud/projects/:id/members`, async (req, res) => {
|
|
38997
|
+
try {
|
|
38998
|
+
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members`, "POST", req.body, {}, { request: req });
|
|
38999
|
+
sendResult(result, res);
|
|
39000
|
+
} catch (err) {
|
|
39001
|
+
errorResponse(err, res);
|
|
39002
|
+
}
|
|
39003
|
+
});
|
|
39004
|
+
server.patch(`${prefix}/cloud/projects/:id/members/:memberId`, async (req, res) => {
|
|
39005
|
+
try {
|
|
39006
|
+
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members/${req.params.memberId}`, "PATCH", req.body, {}, { request: req });
|
|
39007
|
+
sendResult(result, res);
|
|
39008
|
+
} catch (err) {
|
|
39009
|
+
errorResponse(err, res);
|
|
39010
|
+
}
|
|
39011
|
+
});
|
|
39012
|
+
server.delete(`${prefix}/cloud/projects/:id/members/:memberId`, async (req, res) => {
|
|
39013
|
+
try {
|
|
39014
|
+
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members/${req.params.memberId}`, "DELETE", req.body ?? {}, {}, { request: req });
|
|
39015
|
+
sendResult(result, res);
|
|
39016
|
+
} catch (err) {
|
|
39017
|
+
errorResponse(err, res);
|
|
39018
|
+
}
|
|
39019
|
+
});
|
|
37735
39020
|
server.get(`${prefix}/cloud/projects/:id/packages`, async (req, res) => {
|
|
37736
39021
|
try {
|
|
37737
39022
|
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages`, "GET", {}, req.query, { request: req });
|
|
@@ -37806,7 +39091,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37806
39091
|
});
|
|
37807
39092
|
server.get(`${prefix}/i18n/locales`, async (req, res) => {
|
|
37808
39093
|
try {
|
|
37809
|
-
const result = await dispatcher.
|
|
39094
|
+
const result = await dispatcher.dispatch("GET", "/i18n/locales", void 0, req.query, { request: req });
|
|
37810
39095
|
sendResult(result, res);
|
|
37811
39096
|
} catch (err) {
|
|
37812
39097
|
errorResponse(err, res);
|
|
@@ -37814,7 +39099,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37814
39099
|
});
|
|
37815
39100
|
server.get(`${prefix}/i18n/translations/:locale`, async (req, res) => {
|
|
37816
39101
|
try {
|
|
37817
|
-
const result = await dispatcher.
|
|
39102
|
+
const result = await dispatcher.dispatch("GET", `/i18n/translations/${req.params.locale}`, void 0, req.query, { request: req });
|
|
37818
39103
|
sendResult(result, res);
|
|
37819
39104
|
} catch (err) {
|
|
37820
39105
|
errorResponse(err, res);
|
|
@@ -37822,7 +39107,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37822
39107
|
});
|
|
37823
39108
|
server.get(`${prefix}/i18n/labels/:object/:locale`, async (req, res) => {
|
|
37824
39109
|
try {
|
|
37825
|
-
const result = await dispatcher.
|
|
39110
|
+
const result = await dispatcher.dispatch("GET", `/i18n/labels/${req.params.object}/${req.params.locale}`, void 0, req.query, { request: req });
|
|
37826
39111
|
sendResult(result, res);
|
|
37827
39112
|
} catch (err) {
|
|
37828
39113
|
errorResponse(err, res);
|
|
@@ -37831,7 +39116,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37831
39116
|
const registerAutomationRoutes = (base2) => {
|
|
37832
39117
|
server.get(`${base2}/automation`, async (req, res) => {
|
|
37833
39118
|
try {
|
|
37834
|
-
const result = await dispatcher.
|
|
39119
|
+
const result = await dispatcher.dispatch("GET", "/automation", void 0, req.query, { request: req });
|
|
37835
39120
|
sendResult(result, res);
|
|
37836
39121
|
} catch (err) {
|
|
37837
39122
|
errorResponse(err, res);
|
|
@@ -37839,7 +39124,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37839
39124
|
});
|
|
37840
39125
|
server.post(`${base2}/automation`, async (req, res) => {
|
|
37841
39126
|
try {
|
|
37842
|
-
const result = await dispatcher.
|
|
39127
|
+
const result = await dispatcher.dispatch("POST", "/automation", req.body, req.query, { request: req });
|
|
37843
39128
|
sendResult(result, res);
|
|
37844
39129
|
} catch (err) {
|
|
37845
39130
|
errorResponse(err, res);
|
|
@@ -37847,7 +39132,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37847
39132
|
});
|
|
37848
39133
|
server.get(`${base2}/automation/:name`, async (req, res) => {
|
|
37849
39134
|
try {
|
|
37850
|
-
const result = await dispatcher.
|
|
39135
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}`, void 0, req.query, { request: req });
|
|
37851
39136
|
sendResult(result, res);
|
|
37852
39137
|
} catch (err) {
|
|
37853
39138
|
errorResponse(err, res);
|
|
@@ -37855,7 +39140,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37855
39140
|
});
|
|
37856
39141
|
server.put(`${base2}/automation/:name`, async (req, res) => {
|
|
37857
39142
|
try {
|
|
37858
|
-
const result = await dispatcher.
|
|
39143
|
+
const result = await dispatcher.dispatch("PUT", `/automation/${req.params.name}`, req.body, req.query, { request: req });
|
|
37859
39144
|
sendResult(result, res);
|
|
37860
39145
|
} catch (err) {
|
|
37861
39146
|
errorResponse(err, res);
|
|
@@ -37863,7 +39148,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37863
39148
|
});
|
|
37864
39149
|
server.delete(`${base2}/automation/:name`, async (req, res) => {
|
|
37865
39150
|
try {
|
|
37866
|
-
const result = await dispatcher.
|
|
39151
|
+
const result = await dispatcher.dispatch("DELETE", `/automation/${req.params.name}`, void 0, req.query, { request: req });
|
|
37867
39152
|
sendResult(result, res);
|
|
37868
39153
|
} catch (err) {
|
|
37869
39154
|
errorResponse(err, res);
|
|
@@ -37871,7 +39156,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37871
39156
|
});
|
|
37872
39157
|
server.post(`${base2}/automation/trigger/:name`, async (req, res) => {
|
|
37873
39158
|
try {
|
|
37874
|
-
const result = await dispatcher.
|
|
39159
|
+
const result = await dispatcher.dispatch("POST", `/automation/trigger/${req.params.name}`, req.body, req.query, { request: req });
|
|
37875
39160
|
sendResult(result, res);
|
|
37876
39161
|
} catch (err) {
|
|
37877
39162
|
errorResponse(err, res);
|
|
@@ -37879,7 +39164,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37879
39164
|
});
|
|
37880
39165
|
server.post(`${base2}/automation/:name/trigger`, async (req, res) => {
|
|
37881
39166
|
try {
|
|
37882
|
-
const result = await dispatcher.
|
|
39167
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/trigger`, req.body, req.query, { request: req });
|
|
37883
39168
|
sendResult(result, res);
|
|
37884
39169
|
} catch (err) {
|
|
37885
39170
|
errorResponse(err, res);
|
|
@@ -37887,7 +39172,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37887
39172
|
});
|
|
37888
39173
|
server.post(`${base2}/automation/:name/toggle`, async (req, res) => {
|
|
37889
39174
|
try {
|
|
37890
|
-
const result = await dispatcher.
|
|
39175
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/toggle`, req.body, req.query, { request: req });
|
|
37891
39176
|
sendResult(result, res);
|
|
37892
39177
|
} catch (err) {
|
|
37893
39178
|
errorResponse(err, res);
|
|
@@ -37895,7 +39180,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37895
39180
|
});
|
|
37896
39181
|
server.get(`${base2}/automation/:name/runs`, async (req, res) => {
|
|
37897
39182
|
try {
|
|
37898
|
-
const result = await dispatcher.
|
|
39183
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs`, void 0, req.query, { request: req });
|
|
37899
39184
|
sendResult(result, res);
|
|
37900
39185
|
} catch (err) {
|
|
37901
39186
|
errorResponse(err, res);
|
|
@@ -37903,13 +39188,34 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37903
39188
|
});
|
|
37904
39189
|
server.get(`${base2}/automation/:name/runs/:runId`, async (req, res) => {
|
|
37905
39190
|
try {
|
|
37906
|
-
const result = await dispatcher.
|
|
39191
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs/${req.params.runId}`, void 0, req.query, { request: req });
|
|
37907
39192
|
sendResult(result, res);
|
|
37908
39193
|
} catch (err) {
|
|
37909
39194
|
errorResponse(err, res);
|
|
37910
39195
|
}
|
|
37911
39196
|
});
|
|
37912
39197
|
};
|
|
39198
|
+
const registerAIRoutes = (base2) => {
|
|
39199
|
+
const wildcards = [
|
|
39200
|
+
["get", `${base2}/ai/*`],
|
|
39201
|
+
["post", `${base2}/ai/*`],
|
|
39202
|
+
["delete", `${base2}/ai/*`],
|
|
39203
|
+
["put", `${base2}/ai/*`]
|
|
39204
|
+
];
|
|
39205
|
+
for (const [method, pattern] of wildcards) {
|
|
39206
|
+
server[method](pattern, async (req, res) => {
|
|
39207
|
+
try {
|
|
39208
|
+
const fullPath = req.path ?? "";
|
|
39209
|
+
const idx = fullPath.lastIndexOf("/ai");
|
|
39210
|
+
const aiSubPath = idx >= 0 ? fullPath.slice(idx) : "/ai";
|
|
39211
|
+
const result = await dispatcher.dispatch(method.toUpperCase(), aiSubPath, req.body, req.query, { request: req });
|
|
39212
|
+
sendResult(result, res);
|
|
39213
|
+
} catch (err) {
|
|
39214
|
+
errorResponse(err, res);
|
|
39215
|
+
}
|
|
39216
|
+
});
|
|
39217
|
+
}
|
|
39218
|
+
};
|
|
37913
39219
|
const registerActionRoutes = (base2) => {
|
|
37914
39220
|
server.post(`${base2}/actions/:object/:action`, async (req, res) => {
|
|
37915
39221
|
try {
|
|
@@ -37937,12 +39243,15 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37937
39243
|
if (enableProjectScoping && projectResolution === "required") {
|
|
37938
39244
|
registerAutomationRoutes(`${prefix}/projects/:projectId`);
|
|
37939
39245
|
registerActionRoutes(`${prefix}/projects/:projectId`);
|
|
39246
|
+
registerAIRoutes(`${prefix}/projects/:projectId`);
|
|
37940
39247
|
} else {
|
|
37941
39248
|
registerAutomationRoutes(prefix);
|
|
37942
39249
|
registerActionRoutes(prefix);
|
|
39250
|
+
registerAIRoutes(prefix);
|
|
37943
39251
|
if (enableProjectScoping) {
|
|
37944
39252
|
registerAutomationRoutes(`${prefix}/projects/:projectId`);
|
|
37945
39253
|
registerActionRoutes(`${prefix}/projects/:projectId`);
|
|
39254
|
+
registerAIRoutes(`${prefix}/projects/:projectId`);
|
|
37946
39255
|
}
|
|
37947
39256
|
}
|
|
37948
39257
|
ctx.logger.info("Dispatcher bridge routes registered", { prefix, enableProjectScoping, projectResolution });
|
|
@@ -37958,11 +39267,11 @@ function createDispatcherPlugin(config = {}) {
|
|
|
37958
39267
|
const routePath = route.path.startsWith("/api/v1") ? route.path : `${prefix}${route.path}`;
|
|
37959
39268
|
let count = 0;
|
|
37960
39269
|
if (enableProjectScoping && projectResolution === "required") {
|
|
37961
|
-
if (mountRouteOnServer(route, server, toScopedPath(routePath))) count++;
|
|
39270
|
+
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders)) count++;
|
|
37962
39271
|
} else {
|
|
37963
|
-
if (mountRouteOnServer(route, server, routePath)) count++;
|
|
39272
|
+
if (mountRouteOnServer(route, server, routePath, securityHeaders)) count++;
|
|
37964
39273
|
if (enableProjectScoping) {
|
|
37965
|
-
if (mountRouteOnServer(route, server, toScopedPath(routePath))) count++;
|
|
39274
|
+
if (mountRouteOnServer(route, server, toScopedPath(routePath), securityHeaders)) count++;
|
|
37966
39275
|
}
|
|
37967
39276
|
}
|
|
37968
39277
|
return count;
|
|
@@ -38287,6 +39596,1334 @@ var MiddlewareManager = class {
|
|
|
38287
39596
|
// src/index.ts
|
|
38288
39597
|
init_load_artifact_bundle();
|
|
38289
39598
|
|
|
39599
|
+
// src/cloud/kernel-manager.ts
|
|
39600
|
+
var KernelManager = class {
|
|
39601
|
+
constructor(config) {
|
|
39602
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
39603
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
39604
|
+
this.factory = config.factory;
|
|
39605
|
+
this.maxSize = config.maxSize ?? 32;
|
|
39606
|
+
this.ttlMs = config.ttlMs ?? 15 * 60 * 1e3;
|
|
39607
|
+
this.logger = config.logger ?? console;
|
|
39608
|
+
}
|
|
39609
|
+
/** Returns the currently cached projectIds (ordered by insertion). */
|
|
39610
|
+
keys() {
|
|
39611
|
+
return Array.from(this.cache.keys());
|
|
39612
|
+
}
|
|
39613
|
+
/** Cache size for diagnostics. */
|
|
39614
|
+
get size() {
|
|
39615
|
+
return this.cache.size;
|
|
39616
|
+
}
|
|
39617
|
+
/**
|
|
39618
|
+
* Resolve or construct the kernel for `projectId`.
|
|
39619
|
+
*
|
|
39620
|
+
* - Cache hit (fresh): bumps `lastAccess` and returns immediately.
|
|
39621
|
+
* - Cache hit (TTL expired): evicts then falls through to factory.
|
|
39622
|
+
* - Cache miss: dedupes concurrent callers through `pending`.
|
|
39623
|
+
*/
|
|
39624
|
+
async getOrCreate(projectId) {
|
|
39625
|
+
const existing = this.cache.get(projectId);
|
|
39626
|
+
if (existing) {
|
|
39627
|
+
if (this.ttlMs > 0 && Date.now() - existing.lastAccess > this.ttlMs) {
|
|
39628
|
+
await this.evict(projectId);
|
|
39629
|
+
} else {
|
|
39630
|
+
existing.lastAccess = Date.now();
|
|
39631
|
+
return existing.kernel;
|
|
39632
|
+
}
|
|
39633
|
+
}
|
|
39634
|
+
const inflight = this.pending.get(projectId);
|
|
39635
|
+
if (inflight) return inflight;
|
|
39636
|
+
const promise = (async () => {
|
|
39637
|
+
const kernel = await this.factory.create(projectId);
|
|
39638
|
+
const now = Date.now();
|
|
39639
|
+
this.cache.set(projectId, { kernel, createdAt: now, lastAccess: now });
|
|
39640
|
+
await this.enforceMaxSize();
|
|
39641
|
+
return kernel;
|
|
39642
|
+
})();
|
|
39643
|
+
this.pending.set(projectId, promise);
|
|
39644
|
+
try {
|
|
39645
|
+
return await promise;
|
|
39646
|
+
} finally {
|
|
39647
|
+
this.pending.delete(projectId);
|
|
39648
|
+
}
|
|
39649
|
+
}
|
|
39650
|
+
/**
|
|
39651
|
+
* Evict the kernel for `projectId` and invoke `kernel.shutdown()`.
|
|
39652
|
+
* No-op when the entry is absent.
|
|
39653
|
+
*/
|
|
39654
|
+
async evict(projectId) {
|
|
39655
|
+
const entry = this.cache.get(projectId);
|
|
39656
|
+
if (!entry) return;
|
|
39657
|
+
this.cache.delete(projectId);
|
|
39658
|
+
try {
|
|
39659
|
+
await entry.kernel.shutdown();
|
|
39660
|
+
} catch (err) {
|
|
39661
|
+
this.logger.error?.("[KernelManager] shutdown failed", { projectId, err });
|
|
39662
|
+
}
|
|
39663
|
+
}
|
|
39664
|
+
/** Evict all resident kernels. Used on runtime shutdown. */
|
|
39665
|
+
async evictAll() {
|
|
39666
|
+
const ids = Array.from(this.cache.keys());
|
|
39667
|
+
await Promise.all(ids.map((id) => this.evict(id)));
|
|
39668
|
+
}
|
|
39669
|
+
async enforceMaxSize() {
|
|
39670
|
+
while (this.cache.size > this.maxSize) {
|
|
39671
|
+
let oldestKey;
|
|
39672
|
+
let oldestAccess = Infinity;
|
|
39673
|
+
for (const [key, entry] of this.cache) {
|
|
39674
|
+
if (entry.lastAccess < oldestAccess) {
|
|
39675
|
+
oldestAccess = entry.lastAccess;
|
|
39676
|
+
oldestKey = key;
|
|
39677
|
+
}
|
|
39678
|
+
}
|
|
39679
|
+
if (!oldestKey) return;
|
|
39680
|
+
await this.evict(oldestKey);
|
|
39681
|
+
}
|
|
39682
|
+
}
|
|
39683
|
+
};
|
|
39684
|
+
|
|
39685
|
+
// src/cloud/artifact-api-client.ts
|
|
39686
|
+
var ArtifactApiClient = class {
|
|
39687
|
+
constructor(config) {
|
|
39688
|
+
this.hostnameCache = /* @__PURE__ */ new Map();
|
|
39689
|
+
this.artifactCache = /* @__PURE__ */ new Map();
|
|
39690
|
+
this.pendingHostname = /* @__PURE__ */ new Map();
|
|
39691
|
+
this.pendingArtifact = /* @__PURE__ */ new Map();
|
|
39692
|
+
if (!config.controlPlaneUrl) {
|
|
39693
|
+
throw new Error("[ArtifactApiClient] controlPlaneUrl is required");
|
|
39694
|
+
}
|
|
39695
|
+
this.base = config.controlPlaneUrl.replace(/\/+$/, "");
|
|
39696
|
+
this.apiKey = config.apiKey;
|
|
39697
|
+
this.cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1e3;
|
|
39698
|
+
this.requestTimeoutMs = config.requestTimeoutMs ?? 1e4;
|
|
39699
|
+
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
39700
|
+
this.logger = config.logger ?? console;
|
|
39701
|
+
if (typeof this.fetchImpl !== "function") {
|
|
39702
|
+
throw new Error("[ArtifactApiClient] global fetch is not available \u2014 provide config.fetch");
|
|
39703
|
+
}
|
|
39704
|
+
}
|
|
39705
|
+
/**
|
|
39706
|
+
* Resolve a hostname to its project. Returns `null` on 404 or
|
|
39707
|
+
* malformed responses. Errors (network / 5xx) are thrown so
|
|
39708
|
+
* upstream callers can retry.
|
|
39709
|
+
*/
|
|
39710
|
+
async resolveHostname(host) {
|
|
39711
|
+
const cached = this.hostnameCache.get(host);
|
|
39712
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
39713
|
+
const inflight = this.pendingHostname.get(host);
|
|
39714
|
+
if (inflight) return inflight;
|
|
39715
|
+
const promise = (async () => {
|
|
39716
|
+
try {
|
|
39717
|
+
const url = `${this.base}/api/v1/cloud/resolve-hostname?host=${encodeURIComponent(host)}`;
|
|
39718
|
+
const res = await this.request(url);
|
|
39719
|
+
if (res === null) return null;
|
|
39720
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
39721
|
+
if (!body || typeof body.projectId !== "string" || !body.projectId) return null;
|
|
39722
|
+
const value = {
|
|
39723
|
+
projectId: body.projectId,
|
|
39724
|
+
organizationId: body.organizationId,
|
|
39725
|
+
runtime: body.runtime
|
|
39726
|
+
};
|
|
39727
|
+
this.hostnameCache.set(host, { value, expiresAt: Date.now() + this.cacheTtlMs });
|
|
39728
|
+
return value;
|
|
39729
|
+
} finally {
|
|
39730
|
+
this.pendingHostname.delete(host);
|
|
39731
|
+
}
|
|
39732
|
+
})();
|
|
39733
|
+
this.pendingHostname.set(host, promise);
|
|
39734
|
+
return promise;
|
|
39735
|
+
}
|
|
39736
|
+
/**
|
|
39737
|
+
* Fetch the compiled artifact for a project.
|
|
39738
|
+
*
|
|
39739
|
+
* When `opts.commit` is set, requests that specific revision via the
|
|
39740
|
+
* existing `?commit=` query param. Different commits are cached
|
|
39741
|
+
* independently (the cache key includes the commit id) so the preview
|
|
39742
|
+
* runtime can hold multiple versions in memory simultaneously.
|
|
39743
|
+
*/
|
|
39744
|
+
async fetchArtifact(projectId, opts) {
|
|
39745
|
+
const commit = opts?.commit?.trim() || "";
|
|
39746
|
+
const cacheKey = commit ? `${projectId}@${commit}` : projectId;
|
|
39747
|
+
const cached = this.artifactCache.get(cacheKey);
|
|
39748
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
39749
|
+
const inflight = this.pendingArtifact.get(cacheKey);
|
|
39750
|
+
if (inflight) return inflight;
|
|
39751
|
+
const promise = (async () => {
|
|
39752
|
+
try {
|
|
39753
|
+
const qs = commit ? `?commit=${encodeURIComponent(commit)}` : "";
|
|
39754
|
+
const url = `${this.base}/api/v1/cloud/projects/${encodeURIComponent(projectId)}/artifact${qs}`;
|
|
39755
|
+
const res = await this.request(url);
|
|
39756
|
+
if (res === null) return null;
|
|
39757
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
39758
|
+
if (!body || typeof body !== "object") return null;
|
|
39759
|
+
if (!body.metadata) {
|
|
39760
|
+
this.logger.warn?.("[ArtifactApiClient] artifact response missing `metadata`", { projectId, commit });
|
|
39761
|
+
return null;
|
|
39762
|
+
}
|
|
39763
|
+
const value = body;
|
|
39764
|
+
this.artifactCache.set(cacheKey, { value, expiresAt: Date.now() + this.cacheTtlMs });
|
|
39765
|
+
return value;
|
|
39766
|
+
} finally {
|
|
39767
|
+
this.pendingArtifact.delete(cacheKey);
|
|
39768
|
+
}
|
|
39769
|
+
})();
|
|
39770
|
+
this.pendingArtifact.set(cacheKey, promise);
|
|
39771
|
+
return promise;
|
|
39772
|
+
}
|
|
39773
|
+
/**
|
|
39774
|
+
* Resolve an 8-hex project short id (first 8 hex chars of the UUID,
|
|
39775
|
+
* dashes stripped) to the full projectId. Used by the preview
|
|
39776
|
+
* runtime, which encodes project ids in subdomains.
|
|
39777
|
+
*
|
|
39778
|
+
* Returns `null` on 404 or ambiguity (the control plane returns 409
|
|
39779
|
+
* if the prefix matches more than one project).
|
|
39780
|
+
*/
|
|
39781
|
+
async lookupProjectByShortId(shortId) {
|
|
39782
|
+
const short = String(shortId ?? "").trim().toLowerCase();
|
|
39783
|
+
if (!/^[0-9a-f]{8,}$/.test(short)) return null;
|
|
39784
|
+
const url = `${this.base}/api/v1/cloud/projects-by-short-id/${encodeURIComponent(short)}`;
|
|
39785
|
+
const res = await this.request(url);
|
|
39786
|
+
if (res === null) return null;
|
|
39787
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
39788
|
+
if (!body || typeof body.projectId !== "string" || !body.projectId) return null;
|
|
39789
|
+
return { projectId: body.projectId, organizationId: body.organizationId };
|
|
39790
|
+
}
|
|
39791
|
+
/**
|
|
39792
|
+
* Fetch the head commit of a branch. Returns the commit id (and the
|
|
39793
|
+
* matching revision row's `published_at` for cache-validity checks).
|
|
39794
|
+
* Reuses the existing `GET /cloud/projects/:id/branches` endpoint.
|
|
39795
|
+
*/
|
|
39796
|
+
async fetchBranchHead(projectId, branchName) {
|
|
39797
|
+
const url = `${this.base}/api/v1/cloud/projects/${encodeURIComponent(projectId)}/branches`;
|
|
39798
|
+
const res = await this.request(url);
|
|
39799
|
+
if (res === null) return null;
|
|
39800
|
+
const body = res.success === false ? null : res.data ?? res;
|
|
39801
|
+
const branches = Array.isArray(body?.branches) ? body.branches : [];
|
|
39802
|
+
const target = String(branchName ?? "").trim().toLowerCase();
|
|
39803
|
+
const found = branches.find((b) => String(b?.branch ?? "").toLowerCase() === target);
|
|
39804
|
+
if (!found?.headCommitId) return null;
|
|
39805
|
+
return { commitId: String(found.headCommitId), publishedAt: found.headPublishedAt ?? null };
|
|
39806
|
+
}
|
|
39807
|
+
/** Drop cached entries for a project (and any matching hostname). */
|
|
39808
|
+
invalidate(projectId) {
|
|
39809
|
+
this.artifactCache.delete(projectId);
|
|
39810
|
+
const prefix = `${projectId}@`;
|
|
39811
|
+
for (const key of Array.from(this.artifactCache.keys())) {
|
|
39812
|
+
if (key.startsWith(prefix)) this.artifactCache.delete(key);
|
|
39813
|
+
}
|
|
39814
|
+
for (const [host, entry] of this.hostnameCache) {
|
|
39815
|
+
if (entry.value.projectId === projectId) this.hostnameCache.delete(host);
|
|
39816
|
+
}
|
|
39817
|
+
}
|
|
39818
|
+
/** Drop everything. Used on shutdown / hot-reload. */
|
|
39819
|
+
clear() {
|
|
39820
|
+
this.hostnameCache.clear();
|
|
39821
|
+
this.artifactCache.clear();
|
|
39822
|
+
}
|
|
39823
|
+
async request(url) {
|
|
39824
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
39825
|
+
const timer = controller ? setTimeout(() => controller.abort(), this.requestTimeoutMs) : null;
|
|
39826
|
+
try {
|
|
39827
|
+
const res = await this.fetchImpl(url, {
|
|
39828
|
+
method: "GET",
|
|
39829
|
+
headers: this.buildHeaders(),
|
|
39830
|
+
signal: controller?.signal
|
|
39831
|
+
});
|
|
39832
|
+
if (res.status === 404) return null;
|
|
39833
|
+
if (!res.ok) {
|
|
39834
|
+
throw new Error(`[ArtifactApiClient] ${url} \u2192 HTTP ${res.status}`);
|
|
39835
|
+
}
|
|
39836
|
+
return await res.json();
|
|
39837
|
+
} finally {
|
|
39838
|
+
if (timer) clearTimeout(timer);
|
|
39839
|
+
}
|
|
39840
|
+
}
|
|
39841
|
+
buildHeaders() {
|
|
39842
|
+
const headers = {
|
|
39843
|
+
"accept": "application/json",
|
|
39844
|
+
"user-agent": "objectos-runtime"
|
|
39845
|
+
};
|
|
39846
|
+
if (this.apiKey) headers["authorization"] = `Bearer ${this.apiKey}`;
|
|
39847
|
+
return headers;
|
|
39848
|
+
}
|
|
39849
|
+
};
|
|
39850
|
+
|
|
39851
|
+
// src/cloud/artifact-environment-registry.ts
|
|
39852
|
+
var import_node_path4 = require("path");
|
|
39853
|
+
var ArtifactEnvironmentRegistry = class {
|
|
39854
|
+
constructor(config) {
|
|
39855
|
+
this.hostnameCache = /* @__PURE__ */ new Map();
|
|
39856
|
+
this.idCache = /* @__PURE__ */ new Map();
|
|
39857
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
39858
|
+
this.client = config.client;
|
|
39859
|
+
this.cacheTTL = config.cacheTtlMs ?? 5 * 60 * 1e3;
|
|
39860
|
+
this.logger = config.logger ?? console;
|
|
39861
|
+
}
|
|
39862
|
+
async resolveByHostname(host) {
|
|
39863
|
+
const cached = this.hostnameCache.get(host);
|
|
39864
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
39865
|
+
return { projectId: cached.projectId, driver: cached.driver };
|
|
39866
|
+
}
|
|
39867
|
+
const key = `host:${host}`;
|
|
39868
|
+
const inflight = this.pending.get(key);
|
|
39869
|
+
if (inflight) {
|
|
39870
|
+
const result = await inflight;
|
|
39871
|
+
return result ? { projectId: result.projectId, driver: result.driver } : null;
|
|
39872
|
+
}
|
|
39873
|
+
const promise = (async () => {
|
|
39874
|
+
try {
|
|
39875
|
+
const resolved = await this.client.resolveHostname(host);
|
|
39876
|
+
if (!resolved) return null;
|
|
39877
|
+
const entry2 = await this.buildCacheEntry(resolved.projectId, resolved.runtime, resolved.organizationId, host);
|
|
39878
|
+
if (!entry2) return null;
|
|
39879
|
+
this.hostnameCache.set(host, entry2);
|
|
39880
|
+
this.idCache.set(entry2.projectId, entry2);
|
|
39881
|
+
return entry2;
|
|
39882
|
+
} catch (err) {
|
|
39883
|
+
this.logger.error?.("[ArtifactEnvironmentRegistry] resolveByHostname failed", {
|
|
39884
|
+
host,
|
|
39885
|
+
error: err?.message ?? err
|
|
39886
|
+
});
|
|
39887
|
+
return null;
|
|
39888
|
+
} finally {
|
|
39889
|
+
this.pending.delete(key);
|
|
39890
|
+
}
|
|
39891
|
+
})();
|
|
39892
|
+
this.pending.set(key, promise);
|
|
39893
|
+
const entry = await promise;
|
|
39894
|
+
return entry ? { projectId: entry.projectId, driver: entry.driver } : null;
|
|
39895
|
+
}
|
|
39896
|
+
async resolveById(projectId) {
|
|
39897
|
+
const cached = this.idCache.get(projectId);
|
|
39898
|
+
if (cached && cached.expiresAt > Date.now()) return cached.driver;
|
|
39899
|
+
const key = `id:${projectId}`;
|
|
39900
|
+
const inflight = this.pending.get(key);
|
|
39901
|
+
if (inflight) {
|
|
39902
|
+
const result = await inflight;
|
|
39903
|
+
return result?.driver ?? null;
|
|
39904
|
+
}
|
|
39905
|
+
const promise = (async () => {
|
|
39906
|
+
try {
|
|
39907
|
+
const entry2 = await this.buildCacheEntry(projectId, void 0, void 0, void 0);
|
|
39908
|
+
if (!entry2) return null;
|
|
39909
|
+
this.idCache.set(projectId, entry2);
|
|
39910
|
+
if (entry2.project?.hostname) this.hostnameCache.set(entry2.project.hostname, entry2);
|
|
39911
|
+
return entry2;
|
|
39912
|
+
} catch (err) {
|
|
39913
|
+
this.logger.error?.("[ArtifactEnvironmentRegistry] resolveById failed", {
|
|
39914
|
+
projectId,
|
|
39915
|
+
error: err?.message ?? err
|
|
39916
|
+
});
|
|
39917
|
+
return null;
|
|
39918
|
+
} finally {
|
|
39919
|
+
this.pending.delete(key);
|
|
39920
|
+
}
|
|
39921
|
+
})();
|
|
39922
|
+
this.pending.set(key, promise);
|
|
39923
|
+
const entry = await promise;
|
|
39924
|
+
return entry?.driver ?? null;
|
|
39925
|
+
}
|
|
39926
|
+
peekById(projectId) {
|
|
39927
|
+
const cached = this.idCache.get(projectId);
|
|
39928
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
39929
|
+
return { projectId: cached.projectId, driver: cached.driver, project: cached.project };
|
|
39930
|
+
}
|
|
39931
|
+
return null;
|
|
39932
|
+
}
|
|
39933
|
+
invalidate(projectId) {
|
|
39934
|
+
this.idCache.delete(projectId);
|
|
39935
|
+
for (const [host, entry] of this.hostnameCache) {
|
|
39936
|
+
if (entry.projectId === projectId) this.hostnameCache.delete(host);
|
|
39937
|
+
}
|
|
39938
|
+
this.client.invalidate(projectId);
|
|
39939
|
+
}
|
|
39940
|
+
async buildCacheEntry(projectId, runtimeFromHostname, orgIdFromHostname, hostname) {
|
|
39941
|
+
let runtime = runtimeFromHostname;
|
|
39942
|
+
let organizationId = orgIdFromHostname;
|
|
39943
|
+
let host = hostname;
|
|
39944
|
+
let artifactProjectId = projectId;
|
|
39945
|
+
if (!runtime || !organizationId) {
|
|
39946
|
+
const artifact = await this.client.fetchArtifact(projectId);
|
|
39947
|
+
if (!artifact) {
|
|
39948
|
+
this.logger.warn?.("[ArtifactEnvironmentRegistry] artifact not found", { projectId });
|
|
39949
|
+
return null;
|
|
39950
|
+
}
|
|
39951
|
+
artifactProjectId = artifact.projectId ?? projectId;
|
|
39952
|
+
if (!runtime) runtime = artifact.runtime ?? extractRuntimeFromMetadata(artifact.metadata);
|
|
39953
|
+
if (!organizationId) organizationId = artifact.runtime?.organizationId;
|
|
39954
|
+
if (!host) host = artifact.runtime?.hostname;
|
|
39955
|
+
}
|
|
39956
|
+
if (!runtime || !runtime.databaseUrl || !runtime.databaseDriver) {
|
|
39957
|
+
this.logger.warn?.("[ArtifactEnvironmentRegistry] no runtime config for project", { projectId });
|
|
39958
|
+
return null;
|
|
39959
|
+
}
|
|
39960
|
+
const driver = await createDriver(runtime.databaseDriver, runtime.databaseUrl, runtime.databaseAuthToken ?? "");
|
|
39961
|
+
const projectRow = {
|
|
39962
|
+
id: artifactProjectId,
|
|
39963
|
+
organization_id: organizationId,
|
|
39964
|
+
hostname: host,
|
|
39965
|
+
database_url: runtime.databaseUrl,
|
|
39966
|
+
database_driver: runtime.databaseDriver,
|
|
39967
|
+
metadata: runtime.metadata
|
|
39968
|
+
};
|
|
39969
|
+
return {
|
|
39970
|
+
projectId: artifactProjectId,
|
|
39971
|
+
driver,
|
|
39972
|
+
project: projectRow,
|
|
39973
|
+
expiresAt: Date.now() + this.cacheTTL
|
|
39974
|
+
};
|
|
39975
|
+
}
|
|
39976
|
+
};
|
|
39977
|
+
function extractRuntimeFromMetadata(metadata) {
|
|
39978
|
+
const datasources = metadata?.datasources;
|
|
39979
|
+
if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
|
|
39980
|
+
const mapping = metadata?.datasourceMapping;
|
|
39981
|
+
let preferredName;
|
|
39982
|
+
if (mapping) {
|
|
39983
|
+
const def = mapping.find((m) => m?.default === true);
|
|
39984
|
+
if (def?.datasource) preferredName = def.datasource;
|
|
39985
|
+
}
|
|
39986
|
+
const ds = preferredName ? datasources.find((d) => d?.name === preferredName) : datasources[0];
|
|
39987
|
+
if (!ds || typeof ds !== "object") return void 0;
|
|
39988
|
+
const config = ds.config ?? {};
|
|
39989
|
+
const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
|
|
39990
|
+
const driver = ds.driver;
|
|
39991
|
+
if (typeof driver !== "string" || typeof url !== "string") return void 0;
|
|
39992
|
+
return {
|
|
39993
|
+
databaseDriver: driver,
|
|
39994
|
+
databaseUrl: url,
|
|
39995
|
+
databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
|
|
39996
|
+
};
|
|
39997
|
+
}
|
|
39998
|
+
async function createDriver(driverType, databaseUrl, authToken) {
|
|
39999
|
+
switch (driverType) {
|
|
40000
|
+
case "memory": {
|
|
40001
|
+
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
40002
|
+
const dbName = databaseUrl.replace(/^memory:\/\//, "").trim();
|
|
40003
|
+
const filePath = dbName ? (0, import_node_path4.resolve)(process.cwd(), ".objectstack/data/projects", `${dbName}.json`) : void 0;
|
|
40004
|
+
return new InMemoryDriver({
|
|
40005
|
+
persistence: filePath ? { type: "file", path: filePath } : "file"
|
|
40006
|
+
});
|
|
40007
|
+
}
|
|
40008
|
+
case "sqlite":
|
|
40009
|
+
case "sql": {
|
|
40010
|
+
const filePath = databaseUrl.replace(/^file:/, "").replace(/^sql:\/\//, "");
|
|
40011
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
40012
|
+
return new SqlDriver({
|
|
40013
|
+
client: "better-sqlite3",
|
|
40014
|
+
connection: { filename: filePath },
|
|
40015
|
+
useNullAsDefault: true
|
|
40016
|
+
});
|
|
40017
|
+
}
|
|
40018
|
+
case "libsql":
|
|
40019
|
+
case "turso": {
|
|
40020
|
+
const { TursoDriver } = await import("@objectstack/driver-turso");
|
|
40021
|
+
return new TursoDriver({ url: databaseUrl, authToken });
|
|
40022
|
+
}
|
|
40023
|
+
case "postgres":
|
|
40024
|
+
case "postgresql":
|
|
40025
|
+
case "pg": {
|
|
40026
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
40027
|
+
return new SqlDriver({
|
|
40028
|
+
client: "pg",
|
|
40029
|
+
connection: databaseUrl,
|
|
40030
|
+
pool: { min: 0, max: 5 }
|
|
40031
|
+
});
|
|
40032
|
+
}
|
|
40033
|
+
case "mongodb":
|
|
40034
|
+
case "mongo": {
|
|
40035
|
+
const { MongoDBDriver: MongoDBDriver2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
40036
|
+
return new MongoDBDriver2({ url: databaseUrl });
|
|
40037
|
+
}
|
|
40038
|
+
default:
|
|
40039
|
+
throw new Error(`[ArtifactEnvironmentRegistry] Unsupported driver type: ${driverType}`);
|
|
40040
|
+
}
|
|
40041
|
+
}
|
|
40042
|
+
|
|
40043
|
+
// src/cloud/artifact-kernel-factory.ts
|
|
40044
|
+
var import_node_crypto3 = require("crypto");
|
|
40045
|
+
var import_core3 = require("@objectstack/core");
|
|
40046
|
+
init_driver_plugin();
|
|
40047
|
+
init_app_plugin();
|
|
40048
|
+
|
|
40049
|
+
// src/cloud/capability-loader.ts
|
|
40050
|
+
var CAPABILITY_PROVIDERS = {
|
|
40051
|
+
automation: {
|
|
40052
|
+
pkg: "@objectstack/service-automation",
|
|
40053
|
+
export: "AutomationServicePlugin",
|
|
40054
|
+
extras: [
|
|
40055
|
+
{ pkg: "@objectstack/service-automation", export: "CrudNodesPlugin" },
|
|
40056
|
+
{ pkg: "@objectstack/service-automation", export: "LogicNodesPlugin" },
|
|
40057
|
+
{ pkg: "@objectstack/service-automation", export: "HttpConnectorPlugin" },
|
|
40058
|
+
{ pkg: "@objectstack/service-automation", export: "ScreenNodesPlugin" }
|
|
40059
|
+
]
|
|
40060
|
+
},
|
|
40061
|
+
ai: {
|
|
40062
|
+
pkg: "@objectstack/service-ai",
|
|
40063
|
+
export: "AIServicePlugin"
|
|
40064
|
+
},
|
|
40065
|
+
analytics: {
|
|
40066
|
+
pkg: "@objectstack/service-analytics",
|
|
40067
|
+
export: "AnalyticsServicePlugin",
|
|
40068
|
+
configKey: "analyticsCubes"
|
|
40069
|
+
},
|
|
40070
|
+
audit: {
|
|
40071
|
+
pkg: "@objectstack/plugin-audit",
|
|
40072
|
+
export: "AuditPlugin"
|
|
40073
|
+
},
|
|
40074
|
+
cache: {
|
|
40075
|
+
pkg: "@objectstack/service-cache",
|
|
40076
|
+
export: "CacheServicePlugin"
|
|
40077
|
+
},
|
|
40078
|
+
storage: {
|
|
40079
|
+
pkg: "@objectstack/service-storage",
|
|
40080
|
+
export: "StorageServicePlugin"
|
|
40081
|
+
},
|
|
40082
|
+
queue: {
|
|
40083
|
+
pkg: "@objectstack/service-queue",
|
|
40084
|
+
export: "QueueServicePlugin"
|
|
40085
|
+
},
|
|
40086
|
+
job: {
|
|
40087
|
+
pkg: "@objectstack/service-job",
|
|
40088
|
+
export: "JobServicePlugin"
|
|
40089
|
+
},
|
|
40090
|
+
realtime: {
|
|
40091
|
+
pkg: "@objectstack/service-realtime",
|
|
40092
|
+
export: "RealtimeServicePlugin"
|
|
40093
|
+
},
|
|
40094
|
+
feed: {
|
|
40095
|
+
pkg: "@objectstack/service-feed",
|
|
40096
|
+
export: "FeedServicePlugin"
|
|
40097
|
+
},
|
|
40098
|
+
settings: {
|
|
40099
|
+
pkg: "@objectstack/service-settings",
|
|
40100
|
+
export: "SettingsServicePlugin"
|
|
40101
|
+
}
|
|
40102
|
+
};
|
|
40103
|
+
async function loadCapabilities(opts) {
|
|
40104
|
+
const { kernel, requires, bundle, projectId } = opts;
|
|
40105
|
+
const logger = opts.logger ?? console;
|
|
40106
|
+
const installed = [];
|
|
40107
|
+
for (const cap of requires) {
|
|
40108
|
+
const spec = CAPABILITY_PROVIDERS[cap];
|
|
40109
|
+
if (!spec) {
|
|
40110
|
+
continue;
|
|
40111
|
+
}
|
|
40112
|
+
try {
|
|
40113
|
+
const mod = await import(
|
|
40114
|
+
/* webpackIgnore: true */
|
|
40115
|
+
spec.pkg
|
|
40116
|
+
);
|
|
40117
|
+
const Ctor = mod[spec.export];
|
|
40118
|
+
if (!Ctor) {
|
|
40119
|
+
logger.warn?.(
|
|
40120
|
+
`[CapabilityLoader] '${cap}': package '${spec.pkg}' did not export '${spec.export}'`,
|
|
40121
|
+
{ projectId }
|
|
40122
|
+
);
|
|
40123
|
+
continue;
|
|
40124
|
+
}
|
|
40125
|
+
let arg;
|
|
40126
|
+
if (spec.configKey) {
|
|
40127
|
+
const v = bundle[spec.configKey];
|
|
40128
|
+
if (spec.configKey === "analyticsCubes") {
|
|
40129
|
+
arg = { cubes: Array.isArray(v) ? v : [] };
|
|
40130
|
+
} else if (v !== void 0) {
|
|
40131
|
+
arg = v;
|
|
40132
|
+
}
|
|
40133
|
+
}
|
|
40134
|
+
await kernel.use(arg !== void 0 ? new Ctor(arg) : new Ctor());
|
|
40135
|
+
installed.push(spec.export);
|
|
40136
|
+
if (spec.extras) {
|
|
40137
|
+
for (const ex of spec.extras) {
|
|
40138
|
+
try {
|
|
40139
|
+
const exMod = await import(
|
|
40140
|
+
/* webpackIgnore: true */
|
|
40141
|
+
ex.pkg
|
|
40142
|
+
);
|
|
40143
|
+
const ExCtor = exMod[ex.export];
|
|
40144
|
+
if (ExCtor) {
|
|
40145
|
+
await kernel.use(new ExCtor());
|
|
40146
|
+
installed.push(ex.export);
|
|
40147
|
+
}
|
|
40148
|
+
} catch {
|
|
40149
|
+
}
|
|
40150
|
+
}
|
|
40151
|
+
}
|
|
40152
|
+
logger.info?.(
|
|
40153
|
+
`[CapabilityLoader] '${cap}' installed (${spec.export}${spec.extras ? " + " + spec.extras.length + " extras" : ""})`,
|
|
40154
|
+
{ projectId }
|
|
40155
|
+
);
|
|
40156
|
+
} catch (err) {
|
|
40157
|
+
const msg = err?.message ?? String(err);
|
|
40158
|
+
if (msg.includes("Cannot find module") || msg.includes("ERR_MODULE_NOT_FOUND")) {
|
|
40159
|
+
logger.warn?.(
|
|
40160
|
+
`[CapabilityLoader] '${cap}' requested but '${spec.pkg}' not installed in host \u2014 skipped`,
|
|
40161
|
+
{ projectId }
|
|
40162
|
+
);
|
|
40163
|
+
} else {
|
|
40164
|
+
logger.error?.(
|
|
40165
|
+
`[CapabilityLoader] '${cap}' load failed: ${msg}`,
|
|
40166
|
+
{ projectId }
|
|
40167
|
+
);
|
|
40168
|
+
}
|
|
40169
|
+
}
|
|
40170
|
+
}
|
|
40171
|
+
return installed;
|
|
40172
|
+
}
|
|
40173
|
+
|
|
40174
|
+
// src/cloud/artifact-kernel-factory.ts
|
|
40175
|
+
init_platform_sso();
|
|
40176
|
+
function deriveProjectAuthSecret(baseSecret, projectId) {
|
|
40177
|
+
return (0, import_node_crypto3.createHmac)("sha256", baseSecret).update(`project:${projectId}`).digest("hex");
|
|
40178
|
+
}
|
|
40179
|
+
var ArtifactKernelFactory = class {
|
|
40180
|
+
constructor(config) {
|
|
40181
|
+
this.client = config.client;
|
|
40182
|
+
this.envRegistry = config.envRegistry;
|
|
40183
|
+
this.logger = config.logger ?? console;
|
|
40184
|
+
this.kernelConfig = config.kernelConfig;
|
|
40185
|
+
this.authBaseSecret = (config.authBaseSecret ?? process.env.OS_AUTH_SECRET ?? process.env.AUTH_SECRET ?? "").trim();
|
|
40186
|
+
}
|
|
40187
|
+
async create(projectId) {
|
|
40188
|
+
let cached = this.envRegistry.peekById(projectId);
|
|
40189
|
+
if (!cached) {
|
|
40190
|
+
const driver2 = await this.envRegistry.resolveById(projectId);
|
|
40191
|
+
if (!driver2) {
|
|
40192
|
+
throw new Error(`[ArtifactKernelFactory] Could not resolve driver for project '${projectId}'`);
|
|
40193
|
+
}
|
|
40194
|
+
cached = this.envRegistry.peekById(projectId);
|
|
40195
|
+
if (!cached) {
|
|
40196
|
+
throw new Error(`[ArtifactKernelFactory] envRegistry returned a driver but no cached entry for '${projectId}'`);
|
|
40197
|
+
}
|
|
40198
|
+
}
|
|
40199
|
+
const driver = cached.driver;
|
|
40200
|
+
const project = cached.project;
|
|
40201
|
+
const artifact = await this.client.fetchArtifact(projectId);
|
|
40202
|
+
if (!artifact) {
|
|
40203
|
+
throw new Error(`[ArtifactKernelFactory] Artifact not available for project '${projectId}'`);
|
|
40204
|
+
}
|
|
40205
|
+
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
40206
|
+
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
40207
|
+
const kernel = new import_core3.ObjectKernel(this.kernelConfig);
|
|
40208
|
+
await kernel.use(new DriverPlugin(driver, { datasourceName: "cloud" }));
|
|
40209
|
+
await kernel.use(new ObjectQLPlugin({ projectId, skipSchemaSync: false }));
|
|
40210
|
+
await kernel.use(new MetadataPlugin({
|
|
40211
|
+
watch: false,
|
|
40212
|
+
projectId,
|
|
40213
|
+
organizationId: project.organization_id,
|
|
40214
|
+
// ADR-0005: customization overlays (user-created views, dashboards,
|
|
40215
|
+
// edited objects, ...) are persisted by
|
|
40216
|
+
// ObjectStackProtocolImplementation.saveMetaItem on whichever
|
|
40217
|
+
// engine the protocol is attached to. For per-project kernels that
|
|
40218
|
+
// means the project's own DB, so the sys_metadata + history tables
|
|
40219
|
+
// MUST be provisioned here. The previous `false` setting caused
|
|
40220
|
+
// "no such table: sys_metadata" errors on any PUT /api/v1/meta/*
|
|
40221
|
+
// call (e.g. Studio "Create View") against a project deployment.
|
|
40222
|
+
registerSystemObjects: true
|
|
40223
|
+
}));
|
|
40224
|
+
if (this.authBaseSecret) {
|
|
40225
|
+
try {
|
|
40226
|
+
const { AuthPlugin } = await import("@objectstack/plugin-auth");
|
|
40227
|
+
const projectSecret = deriveProjectAuthSecret(this.authBaseSecret, projectId);
|
|
40228
|
+
const baseUrl = project.hostname ? project.hostname.startsWith("http") ? project.hostname : /(\.|^)localhost(:\d+)?$/i.test(project.hostname) ? (() => {
|
|
40229
|
+
const runtimePort = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
40230
|
+
const hasPort = /:\d+$/.test(project.hostname);
|
|
40231
|
+
const hostWithPort = hasPort || !runtimePort ? project.hostname : `${project.hostname}:${runtimePort}`;
|
|
40232
|
+
return `http://${hostWithPort}`;
|
|
40233
|
+
})() : `https://${project.hostname}` : void 0;
|
|
40234
|
+
const trustedOriginsList = [];
|
|
40235
|
+
if (baseUrl) trustedOriginsList.push(baseUrl);
|
|
40236
|
+
const platformOrigins = (process.env.OS_TRUSTED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
40237
|
+
for (const o of platformOrigins) {
|
|
40238
|
+
if (!trustedOriginsList.includes(o)) trustedOriginsList.push(o);
|
|
40239
|
+
}
|
|
40240
|
+
const rootDomain = (process.env.OS_ROOT_DOMAIN ?? "").trim().replace(/^https?:\/\//, "");
|
|
40241
|
+
if (rootDomain) {
|
|
40242
|
+
const wildcard = `https://*.${rootDomain}`;
|
|
40243
|
+
if (!trustedOriginsList.includes(wildcard)) trustedOriginsList.push(wildcard);
|
|
40244
|
+
}
|
|
40245
|
+
if (project.hostname) {
|
|
40246
|
+
const bareHost = project.hostname.replace(/^https?:\/\//, "");
|
|
40247
|
+
if (bareHost.endsWith(".localhost") || bareHost === "localhost") {
|
|
40248
|
+
trustedOriginsList.push(`http://${bareHost}`);
|
|
40249
|
+
trustedOriginsList.push(`http://${bareHost}:*`);
|
|
40250
|
+
trustedOriginsList.push(`https://${bareHost}:*`);
|
|
40251
|
+
}
|
|
40252
|
+
}
|
|
40253
|
+
const platformSsoEnabled = String(
|
|
40254
|
+
process.env.OS_PLATFORM_SSO ?? "true"
|
|
40255
|
+
).toLowerCase() !== "false";
|
|
40256
|
+
const cloudBaseUrl = (process.env.OS_CLOUD_URL ?? "").trim().replace(/\/+$/, "");
|
|
40257
|
+
const oidcProviders = platformSsoEnabled && cloudBaseUrl && /^https?:\/\//.test(cloudBaseUrl) ? [{
|
|
40258
|
+
providerId: PLATFORM_SSO_PROVIDER_ID,
|
|
40259
|
+
name: "ObjectStack",
|
|
40260
|
+
discoveryUrl: `${cloudBaseUrl}/.well-known/openid-configuration`,
|
|
40261
|
+
clientId: derivePlatformSsoClientId(projectId),
|
|
40262
|
+
clientSecret: derivePlatformSsoClientSecret(this.authBaseSecret, projectId),
|
|
40263
|
+
scopes: ["openid", "email", "profile"]
|
|
40264
|
+
}] : void 0;
|
|
40265
|
+
await kernel.use(new AuthPlugin({
|
|
40266
|
+
secret: projectSecret,
|
|
40267
|
+
baseUrl,
|
|
40268
|
+
// Project kernel has no http-server (host owns it). The
|
|
40269
|
+
// dispatcher's handleAuth path resolves `auth` via
|
|
40270
|
+
// getService and invokes the handler directly — route
|
|
40271
|
+
// registration is unnecessary and would warn.
|
|
40272
|
+
registerRoutes: false,
|
|
40273
|
+
// Identity tables live in the project's own DB — keep
|
|
40274
|
+
// sys_user/sys_session local to this kernel.
|
|
40275
|
+
manifestDatasource: "default",
|
|
40276
|
+
// Cookie scope: default to the project's own host. We
|
|
40277
|
+
// intentionally do NOT pass crossSubDomainCookies here
|
|
40278
|
+
// so cookies stay isolated per project subdomain.
|
|
40279
|
+
trustedOrigins: trustedOriginsList.length ? trustedOriginsList : void 0,
|
|
40280
|
+
...oidcProviders ? { oidcProviders } : {}
|
|
40281
|
+
}));
|
|
40282
|
+
if (oidcProviders) {
|
|
40283
|
+
this.logger.info?.("[ArtifactKernelFactory] platform SSO wired", {
|
|
40284
|
+
projectId,
|
|
40285
|
+
cloudBaseUrl
|
|
40286
|
+
});
|
|
40287
|
+
}
|
|
40288
|
+
} catch (err) {
|
|
40289
|
+
this.logger.warn?.("[ArtifactKernelFactory] AuthPlugin not registered", {
|
|
40290
|
+
projectId,
|
|
40291
|
+
error: err?.message
|
|
40292
|
+
});
|
|
40293
|
+
}
|
|
40294
|
+
} else {
|
|
40295
|
+
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { projectId });
|
|
40296
|
+
}
|
|
40297
|
+
try {
|
|
40298
|
+
const { SecurityPlugin } = await import("@objectstack/plugin-security");
|
|
40299
|
+
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "true").toLowerCase() !== "false";
|
|
40300
|
+
await kernel.use(new SecurityPlugin({ multiTenant }));
|
|
40301
|
+
} catch (err) {
|
|
40302
|
+
this.logger.warn?.("[ArtifactKernelFactory] SecurityPlugin not registered", {
|
|
40303
|
+
projectId,
|
|
40304
|
+
error: err?.message
|
|
40305
|
+
});
|
|
40306
|
+
}
|
|
40307
|
+
const projectName = project.hostname ?? projectId;
|
|
40308
|
+
const bundle = artifact.metadata;
|
|
40309
|
+
const sys = bundle?.manifest ?? bundle;
|
|
40310
|
+
const packageId = sys?.packageId ?? sys?.package_id ?? bundle?.packageId;
|
|
40311
|
+
const i18nCfg = bundle?.i18n ?? sys?.i18n ?? {};
|
|
40312
|
+
const trArr = Array.isArray(bundle?.translations) ? bundle.translations : Array.isArray(sys?.translations) ? sys.translations : [];
|
|
40313
|
+
try {
|
|
40314
|
+
const { I18nServicePlugin } = await import("@objectstack/service-i18n");
|
|
40315
|
+
await kernel.use(new I18nServicePlugin({
|
|
40316
|
+
defaultLocale: i18nCfg.defaultLocale,
|
|
40317
|
+
fallbackLocale: i18nCfg.fallbackLocale ?? i18nCfg.defaultLocale ?? "en",
|
|
40318
|
+
// Routes are dispatched by HttpDispatcher.handleI18n via
|
|
40319
|
+
// kernel.getService('i18n'); the host worker owns the
|
|
40320
|
+
// HTTP server. Skip self-registration to avoid warnings.
|
|
40321
|
+
registerRoutes: false
|
|
40322
|
+
}));
|
|
40323
|
+
console.warn(
|
|
40324
|
+
`[ArtifactKernelFactory] I18nServicePlugin registered (project=${projectId}, translations=${trArr.length}, defaultLocale=${i18nCfg.defaultLocale ?? "en"})`
|
|
40325
|
+
);
|
|
40326
|
+
} catch (err) {
|
|
40327
|
+
this.logger.warn?.("[ArtifactKernelFactory] I18nServicePlugin not registered", {
|
|
40328
|
+
projectId,
|
|
40329
|
+
error: err?.message
|
|
40330
|
+
});
|
|
40331
|
+
}
|
|
40332
|
+
const requiresRaw = (Array.isArray(bundle?.requires) ? bundle.requires : null) ?? (Array.isArray(sys?.requires) ? sys.requires : null) ?? [];
|
|
40333
|
+
const requires = requiresRaw.filter((x) => typeof x === "string" && x.length > 0);
|
|
40334
|
+
if (requires.length > 0) {
|
|
40335
|
+
const installed = await loadCapabilities({
|
|
40336
|
+
kernel,
|
|
40337
|
+
requires,
|
|
40338
|
+
bundle: { ...bundle ?? {}, ...sys ?? {} },
|
|
40339
|
+
logger: this.logger,
|
|
40340
|
+
projectId
|
|
40341
|
+
});
|
|
40342
|
+
this.logger.info?.("[ArtifactKernelFactory] capabilities loaded", {
|
|
40343
|
+
projectId,
|
|
40344
|
+
requires,
|
|
40345
|
+
installed
|
|
40346
|
+
});
|
|
40347
|
+
}
|
|
40348
|
+
await kernel.use(new AppPlugin(bundle, {
|
|
40349
|
+
projectId,
|
|
40350
|
+
organizationId: project.organization_id ?? "",
|
|
40351
|
+
projectName,
|
|
40352
|
+
packageId,
|
|
40353
|
+
source: packageId ? "package" : "user"
|
|
40354
|
+
}));
|
|
40355
|
+
await kernel.bootstrap();
|
|
40356
|
+
try {
|
|
40357
|
+
const projMeta = typeof project?.metadata === "string" ? JSON.parse(project.metadata) : project?.metadata ?? {};
|
|
40358
|
+
const ownerSeed = projMeta?.ownerSeed;
|
|
40359
|
+
const orgSeed = projMeta?.orgSeed;
|
|
40360
|
+
if (orgSeed?.id && orgSeed?.name) {
|
|
40361
|
+
try {
|
|
40362
|
+
const { seedProjectOrganization: seedProjectOrganization2 } = await Promise.resolve().then(() => (init_project_org_seed(), project_org_seed_exports));
|
|
40363
|
+
await seedProjectOrganization2(kernel, orgSeed, this.logger);
|
|
40364
|
+
} catch (e) {
|
|
40365
|
+
this.logger.warn?.("[ArtifactKernelFactory] orgSeed threw", {
|
|
40366
|
+
projectId,
|
|
40367
|
+
error: e?.message
|
|
40368
|
+
});
|
|
40369
|
+
}
|
|
40370
|
+
}
|
|
40371
|
+
if (ownerSeed?.userId && ownerSeed?.email) {
|
|
40372
|
+
try {
|
|
40373
|
+
const { seedProjectOwner: seedProjectOwner2 } = await Promise.resolve().then(() => (init_project_owner_seed(), project_owner_seed_exports));
|
|
40374
|
+
await seedProjectOwner2(kernel, ownerSeed, this.logger);
|
|
40375
|
+
} catch (e) {
|
|
40376
|
+
this.logger.warn?.("[ArtifactKernelFactory] ownerSeed threw", {
|
|
40377
|
+
projectId,
|
|
40378
|
+
error: e?.message
|
|
40379
|
+
});
|
|
40380
|
+
}
|
|
40381
|
+
if (orgSeed?.id) {
|
|
40382
|
+
try {
|
|
40383
|
+
const { seedProjectMember: seedProjectMember2 } = await Promise.resolve().then(() => (init_project_org_seed(), project_org_seed_exports));
|
|
40384
|
+
await seedProjectMember2(
|
|
40385
|
+
kernel,
|
|
40386
|
+
{ userId: ownerSeed.userId, organizationId: orgSeed.id, role: "owner" },
|
|
40387
|
+
this.logger
|
|
40388
|
+
);
|
|
40389
|
+
} catch (e) {
|
|
40390
|
+
this.logger.warn?.("[ArtifactKernelFactory] memberSeed threw", {
|
|
40391
|
+
projectId,
|
|
40392
|
+
error: e?.message
|
|
40393
|
+
});
|
|
40394
|
+
}
|
|
40395
|
+
}
|
|
40396
|
+
}
|
|
40397
|
+
} catch (err) {
|
|
40398
|
+
this.logger.warn?.("[ArtifactKernelFactory] owner/org seed skipped", {
|
|
40399
|
+
projectId,
|
|
40400
|
+
error: err?.message
|
|
40401
|
+
});
|
|
40402
|
+
}
|
|
40403
|
+
try {
|
|
40404
|
+
const datasetsNow = (() => {
|
|
40405
|
+
try {
|
|
40406
|
+
return kernel.getService?.("seed-datasets");
|
|
40407
|
+
} catch {
|
|
40408
|
+
return void 0;
|
|
40409
|
+
}
|
|
40410
|
+
})();
|
|
40411
|
+
const replayer = (() => {
|
|
40412
|
+
try {
|
|
40413
|
+
return kernel.getService?.("seed-replayer");
|
|
40414
|
+
} catch {
|
|
40415
|
+
return void 0;
|
|
40416
|
+
}
|
|
40417
|
+
})();
|
|
40418
|
+
if (Array.isArray(datasetsNow) && datasetsNow.length > 0 && typeof replayer === "function") {
|
|
40419
|
+
const projMetaRaw = project?.metadata;
|
|
40420
|
+
const projMeta = typeof projMetaRaw === "string" ? (() => {
|
|
40421
|
+
try {
|
|
40422
|
+
return JSON.parse(projMetaRaw);
|
|
40423
|
+
} catch {
|
|
40424
|
+
return {};
|
|
40425
|
+
}
|
|
40426
|
+
})() : projMetaRaw ?? {};
|
|
40427
|
+
let primaryOrgId = projMeta?.orgSeed?.id;
|
|
40428
|
+
if (!primaryOrgId) {
|
|
40429
|
+
try {
|
|
40430
|
+
const ql = kernel.getService?.("objectql");
|
|
40431
|
+
if (ql?.find) {
|
|
40432
|
+
const rows = await ql.find("sys_organization", { limit: 5, orderBy: [{ field: "created_at", direction: "asc" }] });
|
|
40433
|
+
const list = Array.isArray(rows) ? rows : rows?.value ?? rows?.records ?? [];
|
|
40434
|
+
if (Array.isArray(list) && list.length > 0 && list[0]?.id) {
|
|
40435
|
+
primaryOrgId = String(list[0].id);
|
|
40436
|
+
}
|
|
40437
|
+
}
|
|
40438
|
+
} catch {
|
|
40439
|
+
}
|
|
40440
|
+
}
|
|
40441
|
+
if (primaryOrgId) {
|
|
40442
|
+
try {
|
|
40443
|
+
const summary = await replayer(primaryOrgId);
|
|
40444
|
+
const inserted = summary?.inserted ?? 0;
|
|
40445
|
+
const updated = summary?.updated ?? 0;
|
|
40446
|
+
const errs = summary?.errors?.length ?? 0;
|
|
40447
|
+
if (inserted > 0 || updated > 0 || errs > 0) {
|
|
40448
|
+
this.logger.info?.("[ArtifactKernelFactory] post-bootstrap seed replay", {
|
|
40449
|
+
projectId,
|
|
40450
|
+
organizationId: primaryOrgId,
|
|
40451
|
+
datasets: datasetsNow.length,
|
|
40452
|
+
inserted,
|
|
40453
|
+
updated,
|
|
40454
|
+
errors: errs
|
|
40455
|
+
});
|
|
40456
|
+
}
|
|
40457
|
+
} catch (e) {
|
|
40458
|
+
this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed replay failed", {
|
|
40459
|
+
projectId,
|
|
40460
|
+
organizationId: primaryOrgId,
|
|
40461
|
+
error: e?.message
|
|
40462
|
+
});
|
|
40463
|
+
}
|
|
40464
|
+
}
|
|
40465
|
+
}
|
|
40466
|
+
} catch (err) {
|
|
40467
|
+
this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed step threw", {
|
|
40468
|
+
projectId,
|
|
40469
|
+
error: err?.message
|
|
40470
|
+
});
|
|
40471
|
+
}
|
|
40472
|
+
let i18nSvc = null;
|
|
40473
|
+
try {
|
|
40474
|
+
i18nSvc = kernel.getService?.("i18n");
|
|
40475
|
+
} catch {
|
|
40476
|
+
i18nSvc = null;
|
|
40477
|
+
}
|
|
40478
|
+
try {
|
|
40479
|
+
if (i18nSvc && typeof i18nSvc.loadTranslations === "function") {
|
|
40480
|
+
if (i18nCfg.defaultLocale && typeof i18nSvc.setDefaultLocale === "function") {
|
|
40481
|
+
i18nSvc.setDefaultLocale(i18nCfg.defaultLocale);
|
|
40482
|
+
}
|
|
40483
|
+
let loaded = 0;
|
|
40484
|
+
for (const tbundle of trArr) {
|
|
40485
|
+
if (!tbundle || typeof tbundle !== "object") continue;
|
|
40486
|
+
for (const [locale, data] of Object.entries(tbundle)) {
|
|
40487
|
+
if (data && typeof data === "object") {
|
|
40488
|
+
try {
|
|
40489
|
+
i18nSvc.loadTranslations(locale, data);
|
|
40490
|
+
loaded++;
|
|
40491
|
+
} catch (err) {
|
|
40492
|
+
this.logger.warn?.("[ArtifactKernelFactory] i18n loadTranslations failed", {
|
|
40493
|
+
projectId,
|
|
40494
|
+
locale,
|
|
40495
|
+
error: err?.message
|
|
40496
|
+
});
|
|
40497
|
+
}
|
|
40498
|
+
}
|
|
40499
|
+
}
|
|
40500
|
+
}
|
|
40501
|
+
if (loaded > 0) {
|
|
40502
|
+
this.logger.info?.("[ArtifactKernelFactory] i18n direct-load complete", {
|
|
40503
|
+
projectId,
|
|
40504
|
+
locales: loaded,
|
|
40505
|
+
bundles: trArr.length
|
|
40506
|
+
});
|
|
40507
|
+
}
|
|
40508
|
+
}
|
|
40509
|
+
} catch (err) {
|
|
40510
|
+
this.logger.warn?.("[ArtifactKernelFactory] i18n direct-load failed", {
|
|
40511
|
+
projectId,
|
|
40512
|
+
error: err?.message
|
|
40513
|
+
});
|
|
40514
|
+
}
|
|
40515
|
+
this.logger.info?.("[ArtifactKernelFactory] kernel ready", {
|
|
40516
|
+
projectId,
|
|
40517
|
+
commitId: artifact.commitId,
|
|
40518
|
+
checksum: artifact.checksum,
|
|
40519
|
+
authEnabled: Boolean(this.authBaseSecret)
|
|
40520
|
+
});
|
|
40521
|
+
return kernel;
|
|
40522
|
+
}
|
|
40523
|
+
};
|
|
40524
|
+
|
|
40525
|
+
// src/cloud/auth-proxy-plugin.ts
|
|
40526
|
+
var AUTH_PREFIX = "/api/v1/auth";
|
|
40527
|
+
function pickHandler(svc) {
|
|
40528
|
+
if (!svc) return void 0;
|
|
40529
|
+
if (typeof svc.handleRequest === "function") return svc.handleRequest.bind(svc);
|
|
40530
|
+
if (typeof svc.handler === "function") return svc.handler.bind(svc);
|
|
40531
|
+
if (svc.api && typeof svc.api.handler === "function") return svc.api.handler.bind(svc.api);
|
|
40532
|
+
if (svc.auth && typeof svc.auth.handler === "function") return svc.auth.handler.bind(svc.auth);
|
|
40533
|
+
return void 0;
|
|
40534
|
+
}
|
|
40535
|
+
async function resolveAuthHandler(svc) {
|
|
40536
|
+
const direct = pickHandler(svc);
|
|
40537
|
+
if (direct) return direct;
|
|
40538
|
+
if (typeof svc?.getApi === "function") {
|
|
40539
|
+
try {
|
|
40540
|
+
const api = await svc.getApi();
|
|
40541
|
+
return pickHandler(api) ?? pickHandler({ api });
|
|
40542
|
+
} catch {
|
|
40543
|
+
return void 0;
|
|
40544
|
+
}
|
|
40545
|
+
}
|
|
40546
|
+
return void 0;
|
|
40547
|
+
}
|
|
40548
|
+
var AuthProxyPlugin = class {
|
|
40549
|
+
constructor() {
|
|
40550
|
+
this.name = "com.objectstack.runtime.auth-proxy";
|
|
40551
|
+
this.version = "1.0.0";
|
|
40552
|
+
this.init = async (_ctx) => {
|
|
40553
|
+
};
|
|
40554
|
+
this.start = async (ctx) => {
|
|
40555
|
+
ctx.hook("kernel:ready", async () => {
|
|
40556
|
+
let httpServer;
|
|
40557
|
+
try {
|
|
40558
|
+
httpServer = ctx.getService("http-server");
|
|
40559
|
+
} catch {
|
|
40560
|
+
ctx.logger?.warn?.("[AuthProxyPlugin] http-server not available \u2014 auth routes not mounted");
|
|
40561
|
+
return;
|
|
40562
|
+
}
|
|
40563
|
+
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
40564
|
+
ctx.logger?.warn?.("[AuthProxyPlugin] http-server missing getRawApp() \u2014 auth routes not mounted");
|
|
40565
|
+
return;
|
|
40566
|
+
}
|
|
40567
|
+
const rawApp = httpServer.getRawApp();
|
|
40568
|
+
const kernelManager = ctx.getService("kernel-manager");
|
|
40569
|
+
const envRegistry = ctx.getService("env-registry");
|
|
40570
|
+
const handler = async (c) => {
|
|
40571
|
+
try {
|
|
40572
|
+
const url = new URL(c.req.url);
|
|
40573
|
+
const host = url.hostname;
|
|
40574
|
+
let projectId;
|
|
40575
|
+
try {
|
|
40576
|
+
const env = await envRegistry.resolveByHostname(host);
|
|
40577
|
+
projectId = env?.projectId;
|
|
40578
|
+
} catch {
|
|
40579
|
+
}
|
|
40580
|
+
if (!projectId) {
|
|
40581
|
+
return c.json({ error: "project_not_found", host }, 404);
|
|
40582
|
+
}
|
|
40583
|
+
const projectKernel = await kernelManager.getOrCreate(projectId);
|
|
40584
|
+
let authSvc;
|
|
40585
|
+
try {
|
|
40586
|
+
authSvc = await projectKernel.getServiceAsync?.("auth");
|
|
40587
|
+
} catch {
|
|
40588
|
+
authSvc = void 0;
|
|
40589
|
+
}
|
|
40590
|
+
if (!authSvc) {
|
|
40591
|
+
try {
|
|
40592
|
+
authSvc = projectKernel.getService?.("auth");
|
|
40593
|
+
} catch {
|
|
40594
|
+
}
|
|
40595
|
+
}
|
|
40596
|
+
const subPath = url.pathname.startsWith(AUTH_PREFIX + "/") ? url.pathname.substring(AUTH_PREFIX.length + 1) : "";
|
|
40597
|
+
if (c.req.method === "GET" && (subPath === "config" || subPath === "bootstrap-status")) {
|
|
40598
|
+
if (subPath === "config") {
|
|
40599
|
+
try {
|
|
40600
|
+
const config = typeof authSvc?.getPublicConfig === "function" ? authSvc.getPublicConfig() : null;
|
|
40601
|
+
if (config) {
|
|
40602
|
+
return c.json({ success: true, data: config });
|
|
40603
|
+
}
|
|
40604
|
+
return c.json({ success: false, error: { code: "auth_config_unavailable", message: "AuthManager has no getPublicConfig()" } }, 503);
|
|
40605
|
+
} catch (e) {
|
|
40606
|
+
return c.json({ success: false, error: { code: "auth_config_error", message: String(e?.message ?? e) } }, 500);
|
|
40607
|
+
}
|
|
40608
|
+
}
|
|
40609
|
+
try {
|
|
40610
|
+
try {
|
|
40611
|
+
const pubCfg = typeof authSvc?.getPublicConfig === "function" ? authSvc.getPublicConfig() : null;
|
|
40612
|
+
const ssoProviders = Array.isArray(pubCfg?.socialProviders) ? pubCfg.socialProviders : [];
|
|
40613
|
+
const ssoWired = ssoProviders.some(
|
|
40614
|
+
(p) => p?.enabled !== false && p?.id === "objectstack-cloud"
|
|
40615
|
+
);
|
|
40616
|
+
if (ssoWired) {
|
|
40617
|
+
return c.json({ hasOwner: true });
|
|
40618
|
+
}
|
|
40619
|
+
} catch {
|
|
40620
|
+
}
|
|
40621
|
+
const dataEngine = typeof authSvc?.getDataEngine === "function" ? authSvc.getDataEngine() : null;
|
|
40622
|
+
if (!dataEngine || typeof dataEngine.count !== "function") {
|
|
40623
|
+
return c.json({ hasOwner: true });
|
|
40624
|
+
}
|
|
40625
|
+
const count = await dataEngine.count("sys_user", {});
|
|
40626
|
+
return c.json({ hasOwner: (count ?? 0) > 0 });
|
|
40627
|
+
} catch {
|
|
40628
|
+
return c.json({ hasOwner: true });
|
|
40629
|
+
}
|
|
40630
|
+
}
|
|
40631
|
+
const fn = await resolveAuthHandler(authSvc);
|
|
40632
|
+
if (!fn) {
|
|
40633
|
+
return c.json({ error: "auth_service_unavailable", projectId }, 503);
|
|
40634
|
+
}
|
|
40635
|
+
const resp = await fn(c.req.raw);
|
|
40636
|
+
const rootDomain = process.env.OS_ROOT_DOMAIN || "";
|
|
40637
|
+
if (rootDomain) {
|
|
40638
|
+
const leakyDomain = rootDomain.startsWith(".") ? rootDomain : `.${rootDomain}`;
|
|
40639
|
+
const leakyNames = [
|
|
40640
|
+
"__Secure-better-auth.session_token",
|
|
40641
|
+
"better-auth.session_token",
|
|
40642
|
+
"__Secure-better-auth.state",
|
|
40643
|
+
"better-auth.state",
|
|
40644
|
+
"__Secure-better-auth.csrf_token",
|
|
40645
|
+
"better-auth.csrf_token"
|
|
40646
|
+
];
|
|
40647
|
+
try {
|
|
40648
|
+
for (const n of leakyNames) {
|
|
40649
|
+
const isSecure = n.startsWith("__Secure-");
|
|
40650
|
+
const attrs = `Max-Age=0; Path=/; Domain=${leakyDomain}; SameSite=Lax${isSecure ? "; Secure" : ""}`;
|
|
40651
|
+
resp.headers?.append?.("Set-Cookie", `${n}=; ${attrs}`);
|
|
40652
|
+
}
|
|
40653
|
+
} catch {
|
|
40654
|
+
}
|
|
40655
|
+
}
|
|
40656
|
+
return resp;
|
|
40657
|
+
} catch (err) {
|
|
40658
|
+
ctx.logger?.error?.("[AuthProxyPlugin] auth dispatch failed", {
|
|
40659
|
+
error: err?.message,
|
|
40660
|
+
stack: err?.stack
|
|
40661
|
+
});
|
|
40662
|
+
return c.json({
|
|
40663
|
+
error: "auth_dispatch_failed",
|
|
40664
|
+
message: err?.message ?? String(err)
|
|
40665
|
+
}, 500);
|
|
40666
|
+
}
|
|
40667
|
+
};
|
|
40668
|
+
if (typeof rawApp.all === "function") {
|
|
40669
|
+
rawApp.all(`${AUTH_PREFIX}/*`, handler);
|
|
40670
|
+
} else {
|
|
40671
|
+
for (const m of ["get", "post", "put", "delete", "patch", "options"]) {
|
|
40672
|
+
try {
|
|
40673
|
+
rawApp[m]?.(`${AUTH_PREFIX}/*`, handler);
|
|
40674
|
+
} catch {
|
|
40675
|
+
}
|
|
40676
|
+
}
|
|
40677
|
+
}
|
|
40678
|
+
ctx.logger?.info?.(`[AuthProxyPlugin] auth proxy mounted at ${AUTH_PREFIX}/*`);
|
|
40679
|
+
});
|
|
40680
|
+
};
|
|
40681
|
+
}
|
|
40682
|
+
};
|
|
40683
|
+
|
|
40684
|
+
// src/cloud/file-artifact-api-client.ts
|
|
40685
|
+
var import_promises2 = require("fs/promises");
|
|
40686
|
+
var import_node_path5 = require("path");
|
|
40687
|
+
var FileArtifactApiClient = class {
|
|
40688
|
+
constructor(config = {}) {
|
|
40689
|
+
const cwd = process.cwd();
|
|
40690
|
+
this.artifactPath = (0, import_node_path5.resolve)(
|
|
40691
|
+
cwd,
|
|
40692
|
+
config.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? "dist/objectstack.json"
|
|
40693
|
+
);
|
|
40694
|
+
this.projectId = config.projectId ?? process.env.OS_PROJECT_ID ?? "proj_local";
|
|
40695
|
+
this.organizationId = config.organizationId ?? process.env.OS_ORGANIZATION_ID ?? "org_local";
|
|
40696
|
+
this.overrideRuntime = config.runtime;
|
|
40697
|
+
this.watch = config.watch ?? true;
|
|
40698
|
+
this.logger = config.logger ?? console;
|
|
40699
|
+
}
|
|
40700
|
+
async resolveHostname(_host) {
|
|
40701
|
+
const runtime = this.overrideRuntime ?? await this.readRuntimeFromArtifact();
|
|
40702
|
+
return {
|
|
40703
|
+
projectId: this.projectId,
|
|
40704
|
+
organizationId: this.organizationId,
|
|
40705
|
+
...runtime ? { runtime } : {}
|
|
40706
|
+
};
|
|
40707
|
+
}
|
|
40708
|
+
async fetchArtifact(_projectId, _opts) {
|
|
40709
|
+
return this.loadArtifact();
|
|
40710
|
+
}
|
|
40711
|
+
async lookupProjectByShortId(_shortId) {
|
|
40712
|
+
return { projectId: this.projectId, organizationId: this.organizationId };
|
|
40713
|
+
}
|
|
40714
|
+
async fetchBranchHead(_projectId, _branchName) {
|
|
40715
|
+
const artifact = await this.loadArtifact();
|
|
40716
|
+
return artifact ? { commitId: artifact.commitId ?? "local", publishedAt: null } : null;
|
|
40717
|
+
}
|
|
40718
|
+
invalidate(_projectId) {
|
|
40719
|
+
this.cached = void 0;
|
|
40720
|
+
}
|
|
40721
|
+
clear() {
|
|
40722
|
+
this.cached = void 0;
|
|
40723
|
+
}
|
|
40724
|
+
async loadArtifact() {
|
|
40725
|
+
try {
|
|
40726
|
+
const stats = await (0, import_promises2.stat)(this.artifactPath);
|
|
40727
|
+
const mtimeMs = stats.mtimeMs;
|
|
40728
|
+
if (!this.watch && this.cached) return this.cached.response;
|
|
40729
|
+
if (this.cached && this.cached.mtimeMs === mtimeMs) return this.cached.response;
|
|
40730
|
+
const raw = await (0, import_promises2.readFile)(this.artifactPath, "utf8");
|
|
40731
|
+
const parsed = JSON.parse(raw);
|
|
40732
|
+
const isEnvelope = parsed && typeof parsed === "object" && typeof parsed.metadata === "object" && parsed.metadata !== null;
|
|
40733
|
+
const metadata = isEnvelope ? parsed.metadata : parsed;
|
|
40734
|
+
const runtime = this.overrideRuntime ?? (isEnvelope ? parsed.runtime : void 0) ?? this.deriveRuntimeFromMetadata(metadata) ?? this.defaultLocalSqliteRuntime();
|
|
40735
|
+
const response = {
|
|
40736
|
+
schemaVersion: parsed.schemaVersion ?? "1",
|
|
40737
|
+
projectId: parsed.projectId ?? this.projectId,
|
|
40738
|
+
commitId: parsed.commitId ?? "local",
|
|
40739
|
+
checksum: parsed.checksum ?? "",
|
|
40740
|
+
publishedAt: parsed.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
40741
|
+
metadata,
|
|
40742
|
+
functions: parsed.functions,
|
|
40743
|
+
manifest: parsed.manifest,
|
|
40744
|
+
runtime: {
|
|
40745
|
+
organizationId: this.organizationId,
|
|
40746
|
+
...runtime
|
|
40747
|
+
}
|
|
40748
|
+
};
|
|
40749
|
+
this.cached = { mtimeMs, response };
|
|
40750
|
+
return response;
|
|
40751
|
+
} catch (err) {
|
|
40752
|
+
this.logger.error?.("[FileArtifactApiClient] failed to load artifact", {
|
|
40753
|
+
artifactPath: this.artifactPath,
|
|
40754
|
+
error: err?.message ?? err
|
|
40755
|
+
});
|
|
40756
|
+
return null;
|
|
40757
|
+
}
|
|
40758
|
+
}
|
|
40759
|
+
async readRuntimeFromArtifact() {
|
|
40760
|
+
const artifact = await this.loadArtifact();
|
|
40761
|
+
return artifact?.runtime;
|
|
40762
|
+
}
|
|
40763
|
+
deriveRuntimeFromMetadata(metadata) {
|
|
40764
|
+
const datasources = metadata?.datasources;
|
|
40765
|
+
if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
|
|
40766
|
+
const mapping = metadata?.datasourceMapping;
|
|
40767
|
+
let preferredName;
|
|
40768
|
+
if (mapping) {
|
|
40769
|
+
const def = mapping.find((m) => m?.default === true);
|
|
40770
|
+
if (def?.datasource) preferredName = def.datasource;
|
|
40771
|
+
}
|
|
40772
|
+
const ds = preferredName ? datasources.find((d) => d?.name === preferredName) ?? datasources[0] : datasources[0];
|
|
40773
|
+
if (!ds || typeof ds !== "object") return void 0;
|
|
40774
|
+
const config = ds.config ?? {};
|
|
40775
|
+
const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
|
|
40776
|
+
const driver = ds.driver;
|
|
40777
|
+
if (typeof driver !== "string" || typeof url !== "string") return void 0;
|
|
40778
|
+
return {
|
|
40779
|
+
databaseDriver: driver,
|
|
40780
|
+
databaseUrl: url,
|
|
40781
|
+
databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
|
|
40782
|
+
};
|
|
40783
|
+
}
|
|
40784
|
+
defaultLocalSqliteRuntime() {
|
|
40785
|
+
const cwd = process.cwd();
|
|
40786
|
+
const dbPath = (0, import_node_path5.resolve)(cwd, ".objectstack/data", `${this.projectId}.db`);
|
|
40787
|
+
return {
|
|
40788
|
+
databaseDriver: "sqlite",
|
|
40789
|
+
databaseUrl: `file:${dbPath}`
|
|
40790
|
+
};
|
|
40791
|
+
}
|
|
40792
|
+
};
|
|
40793
|
+
|
|
40794
|
+
// src/cloud/objectos-stack.ts
|
|
40795
|
+
async function createHostEnginePlugins() {
|
|
40796
|
+
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
40797
|
+
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
40798
|
+
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
40799
|
+
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
40800
|
+
const driver = new InMemoryDriver();
|
|
40801
|
+
const driverName = "memory";
|
|
40802
|
+
const oqlRef = { ql: null };
|
|
40803
|
+
const objectql = {
|
|
40804
|
+
name: "com.objectstack.engine.objectql",
|
|
40805
|
+
version: "0.0.0",
|
|
40806
|
+
async init(ctx) {
|
|
40807
|
+
const plugin = new ObjectQLPlugin();
|
|
40808
|
+
this._inner = plugin;
|
|
40809
|
+
if (plugin.init) await plugin.init(ctx);
|
|
40810
|
+
oqlRef.ql = plugin.ql ?? plugin;
|
|
40811
|
+
},
|
|
40812
|
+
async start(ctx) {
|
|
40813
|
+
const plugin = this._inner;
|
|
40814
|
+
if (plugin?.start) await plugin.start(ctx);
|
|
40815
|
+
},
|
|
40816
|
+
async destroy() {
|
|
40817
|
+
const plugin = this._inner;
|
|
40818
|
+
if (plugin?.destroy) await plugin.destroy();
|
|
40819
|
+
else if (plugin?.stop) await plugin.stop();
|
|
40820
|
+
}
|
|
40821
|
+
};
|
|
40822
|
+
const datasourceMapping = {
|
|
40823
|
+
name: "objectos-host-datasource-mapping",
|
|
40824
|
+
version: "0.0.0",
|
|
40825
|
+
dependencies: ["com.objectstack.engine.objectql"],
|
|
40826
|
+
async init() {
|
|
40827
|
+
const ql = oqlRef.ql;
|
|
40828
|
+
if (ql?.setDatasourceMapping) {
|
|
40829
|
+
ql.setDatasourceMapping([
|
|
40830
|
+
{ default: true, datasource: `com.objectstack.driver.${driverName}` }
|
|
40831
|
+
]);
|
|
40832
|
+
}
|
|
40833
|
+
}
|
|
40834
|
+
};
|
|
40835
|
+
const driverPlugin = new DriverPlugin2(driver, driverName);
|
|
40836
|
+
const metadata = new MetadataPlugin({
|
|
40837
|
+
watch: false,
|
|
40838
|
+
// The host kernel is a routing shell. It doesn't own metadata —
|
|
40839
|
+
// every per-project kernel registers its own.
|
|
40840
|
+
registerSystemObjects: false
|
|
40841
|
+
});
|
|
40842
|
+
return [objectql, datasourceMapping, driverPlugin, metadata];
|
|
40843
|
+
}
|
|
40844
|
+
var ObjectOSProjectPlugin = class {
|
|
40845
|
+
constructor(config) {
|
|
40846
|
+
this.name = "com.objectstack.runtime.objectos-project";
|
|
40847
|
+
this.version = "1.0.0";
|
|
40848
|
+
this.init = async (ctx) => {
|
|
40849
|
+
const client = this.config.client ?? (this.config.controlPlaneUrl === "file" ? new FileArtifactApiClient({
|
|
40850
|
+
...this.config.fileConfig ?? {},
|
|
40851
|
+
logger: ctx.logger
|
|
40852
|
+
}) : new ArtifactApiClient({
|
|
40853
|
+
controlPlaneUrl: this.config.controlPlaneUrl,
|
|
40854
|
+
apiKey: this.config.controlPlaneApiKey,
|
|
40855
|
+
cacheTtlMs: this.config.artifactCacheTtlMs,
|
|
40856
|
+
logger: ctx.logger
|
|
40857
|
+
}));
|
|
40858
|
+
this.client = client;
|
|
40859
|
+
const envRegistry = new ArtifactEnvironmentRegistry({
|
|
40860
|
+
client,
|
|
40861
|
+
cacheTtlMs: this.config.envCacheTtlMs,
|
|
40862
|
+
logger: ctx.logger
|
|
40863
|
+
});
|
|
40864
|
+
const factory = new ArtifactKernelFactory({
|
|
40865
|
+
client,
|
|
40866
|
+
envRegistry,
|
|
40867
|
+
logger: ctx.logger
|
|
40868
|
+
});
|
|
40869
|
+
const kernelManager = new KernelManager({
|
|
40870
|
+
factory,
|
|
40871
|
+
maxSize: this.config.kernelCacheSize,
|
|
40872
|
+
ttlMs: this.config.kernelTtlMs,
|
|
40873
|
+
logger: ctx.logger
|
|
40874
|
+
});
|
|
40875
|
+
this.kernelManager = kernelManager;
|
|
40876
|
+
ctx.registerService("env-registry", envRegistry);
|
|
40877
|
+
ctx.registerService("kernel-manager", kernelManager);
|
|
40878
|
+
ctx.registerService("artifact-api-client", client);
|
|
40879
|
+
ctx.logger.info?.("ObjectOSProjectPlugin: registered env-registry + kernel-manager", {
|
|
40880
|
+
mode: this.config.controlPlaneUrl === "file" ? "file" : "http",
|
|
40881
|
+
controlPlaneUrl: this.config.controlPlaneUrl
|
|
40882
|
+
});
|
|
40883
|
+
};
|
|
40884
|
+
this.destroy = async () => {
|
|
40885
|
+
try {
|
|
40886
|
+
await this.kernelManager?.evictAll();
|
|
40887
|
+
} catch {
|
|
40888
|
+
}
|
|
40889
|
+
try {
|
|
40890
|
+
this.client?.clear();
|
|
40891
|
+
} catch {
|
|
40892
|
+
}
|
|
40893
|
+
};
|
|
40894
|
+
this.config = config;
|
|
40895
|
+
}
|
|
40896
|
+
};
|
|
40897
|
+
async function createObjectOSStack(config) {
|
|
40898
|
+
if (!config.controlPlaneUrl && !config.client) {
|
|
40899
|
+
throw new Error("[createObjectOSStack] either controlPlaneUrl or client is required");
|
|
40900
|
+
}
|
|
40901
|
+
const merged = {
|
|
40902
|
+
...config,
|
|
40903
|
+
kernelCacheSize: Number(process.env.OS_KERNEL_CACHE_SIZE ?? config.kernelCacheSize ?? 32),
|
|
40904
|
+
kernelTtlMs: Number(process.env.OS_KERNEL_TTL_MS ?? config.kernelTtlMs ?? 15 * 60 * 1e3),
|
|
40905
|
+
envCacheTtlMs: Number(process.env.OS_ENV_CACHE_TTL_MS ?? config.envCacheTtlMs ?? 5 * 60 * 1e3),
|
|
40906
|
+
artifactCacheTtlMs: Number(process.env.OS_ARTIFACT_CACHE_TTL_MS ?? config.artifactCacheTtlMs ?? 5 * 60 * 1e3)
|
|
40907
|
+
};
|
|
40908
|
+
const enginePlugins = await createHostEnginePlugins();
|
|
40909
|
+
return {
|
|
40910
|
+
plugins: [...enginePlugins, new ObjectOSProjectPlugin(merged), new AuthProxyPlugin()],
|
|
40911
|
+
api: {
|
|
40912
|
+
enableProjectScoping: true,
|
|
40913
|
+
projectResolution: "auto",
|
|
40914
|
+
// ObjectOS is multi-tenant: anonymous /api/v1/data/* must never
|
|
40915
|
+
// leak per-project data across organisations. AuthProxyPlugin
|
|
40916
|
+
// verifies upstream tokens and populates ctx.userId; requireAuth
|
|
40917
|
+
// turns missing userId into 401 at the REST layer before the
|
|
40918
|
+
// request reaches the per-project kernel.
|
|
40919
|
+
requireAuth: true
|
|
40920
|
+
}
|
|
40921
|
+
};
|
|
40922
|
+
}
|
|
40923
|
+
|
|
40924
|
+
// src/index.ts
|
|
40925
|
+
init_platform_sso();
|
|
40926
|
+
|
|
38290
40927
|
// src/sandbox/script-runner.ts
|
|
38291
40928
|
var UnimplementedScriptRunner = class {
|
|
38292
40929
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -38315,12 +40952,26 @@ __reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
|
38315
40952
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38316
40953
|
0 && (module.exports = {
|
|
38317
40954
|
AppPlugin,
|
|
40955
|
+
ArtifactApiClient,
|
|
40956
|
+
ArtifactEnvironmentRegistry,
|
|
40957
|
+
ArtifactKernelFactory,
|
|
40958
|
+
AuthProxyPlugin,
|
|
40959
|
+
DEFAULT_RATE_LIMITS,
|
|
38318
40960
|
DriverPlugin,
|
|
40961
|
+
FileArtifactApiClient,
|
|
38319
40962
|
HttpDispatcher,
|
|
38320
40963
|
HttpServer,
|
|
40964
|
+
InMemoryErrorReporter,
|
|
40965
|
+
InMemoryMetricsRegistry,
|
|
40966
|
+
KernelManager,
|
|
38321
40967
|
MiddlewareManager,
|
|
40968
|
+
NoopErrorReporter,
|
|
40969
|
+
NoopMetricsRegistry,
|
|
38322
40970
|
ObjectKernel,
|
|
40971
|
+
PLATFORM_SSO_PROVIDER_ID,
|
|
38323
40972
|
QuickJSScriptRunner,
|
|
40973
|
+
RUNTIME_METRICS,
|
|
40974
|
+
RateLimiter,
|
|
38324
40975
|
RestServer,
|
|
38325
40976
|
RouteGroupBuilder,
|
|
38326
40977
|
RouteManager,
|
|
@@ -38330,18 +40981,32 @@ __reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
|
38330
40981
|
SeedLoaderService,
|
|
38331
40982
|
UnimplementedScriptRunner,
|
|
38332
40983
|
actionBodyRunnerFactory,
|
|
40984
|
+
backfillPlatformSsoClients,
|
|
40985
|
+
buildPlatformSsoRedirectUri,
|
|
40986
|
+
buildSecurityHeaders,
|
|
38333
40987
|
collectBundleActions,
|
|
38334
40988
|
collectBundleFunctions,
|
|
38335
40989
|
collectBundleHooks,
|
|
40990
|
+
createDefaultHostConfig,
|
|
38336
40991
|
createDispatcherPlugin,
|
|
40992
|
+
createObjectOSStack,
|
|
38337
40993
|
createRestApiPlugin,
|
|
38338
40994
|
createStandaloneStack,
|
|
38339
40995
|
createSystemProjectPlugin,
|
|
40996
|
+
derivePlatformSsoClientId,
|
|
40997
|
+
derivePlatformSsoClientSecret,
|
|
40998
|
+
extractRequestId,
|
|
40999
|
+
formatTraceparent,
|
|
41000
|
+
generateRequestId,
|
|
38340
41001
|
hookBodyRunnerFactory,
|
|
38341
41002
|
isHttpUrl,
|
|
38342
41003
|
loadArtifactBundle,
|
|
38343
41004
|
mergeRuntimeModule,
|
|
41005
|
+
parseTraceparent,
|
|
38344
41006
|
readArtifactSource,
|
|
41007
|
+
resolveDefaultArtifactPath,
|
|
41008
|
+
resolveRequestId,
|
|
41009
|
+
seedPlatformSsoClient,
|
|
38345
41010
|
...require("@objectstack/core")
|
|
38346
41011
|
});
|
|
38347
41012
|
//# sourceMappingURL=index.cjs.map
|