@objectstack/rest 7.2.1 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +112 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -9
- package/dist/index.d.ts +7 -9
- package/dist/index.js +112 -152
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -250,6 +250,7 @@ var RouteGroupBuilder = class {
|
|
|
250
250
|
// src/rest-server.ts
|
|
251
251
|
var import_meta = {};
|
|
252
252
|
var logError = (...args) => globalThis.console?.error(...args);
|
|
253
|
+
var TRANSLATABLE_META_TYPES = /* @__PURE__ */ new Set(["view", "action", "object", "app", "dashboard"]);
|
|
253
254
|
function mapDataError(error, object) {
|
|
254
255
|
if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
|
|
255
256
|
return {
|
|
@@ -859,10 +860,10 @@ var RestServer = class {
|
|
|
859
860
|
* locale yields a match. Falls through unchanged for unsupported types
|
|
860
861
|
* or missing translations.
|
|
861
862
|
*/
|
|
862
|
-
async translateMetaItem(req, type, environmentId, item) {
|
|
863
|
+
async translateMetaItem(req, type, environmentId, item, i18nService) {
|
|
863
864
|
if (!item || typeof item !== "object") return item;
|
|
864
|
-
if (type
|
|
865
|
-
const i18n = await this.resolveI18nService(environmentId, req);
|
|
865
|
+
if (!TRANSLATABLE_META_TYPES.has(type)) return item;
|
|
866
|
+
const i18n = i18nService !== void 0 ? i18nService : await this.resolveI18nService(environmentId, req);
|
|
866
867
|
const bundle = this.buildTranslationBundle(i18n);
|
|
867
868
|
if (!bundle) return item;
|
|
868
869
|
const locale = this.extractLocale(req, i18n);
|
|
@@ -875,7 +876,7 @@ var RestServer = class {
|
|
|
875
876
|
*/
|
|
876
877
|
async translateMetaItems(req, type, environmentId, items) {
|
|
877
878
|
if (!Array.isArray(items)) return items;
|
|
878
|
-
if (type
|
|
879
|
+
if (!TRANSLATABLE_META_TYPES.has(type)) return items;
|
|
879
880
|
const i18n = await this.resolveI18nService(environmentId, req);
|
|
880
881
|
const bundle = this.buildTranslationBundle(i18n);
|
|
881
882
|
if (!bundle) return items;
|
|
@@ -1378,6 +1379,15 @@ var RestServer = class {
|
|
|
1378
1379
|
}
|
|
1379
1380
|
}
|
|
1380
1381
|
}
|
|
1382
|
+
if (req.params.type === "view" && req.query?.object) {
|
|
1383
|
+
const obj = String(req.query.object);
|
|
1384
|
+
const raw = visible;
|
|
1385
|
+
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.items) ? raw.items : null;
|
|
1386
|
+
if (list) {
|
|
1387
|
+
const filtered = list.filter((v) => v && typeof v === "object" && v.viewKind && v.object === obj).sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || String(a.name).localeCompare(String(b.name)));
|
|
1388
|
+
visible = Array.isArray(raw) ? filtered : { ...raw, items: filtered };
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1381
1391
|
const translated = await this.translateMetaItems(req, req.params.type, environmentId, visible);
|
|
1382
1392
|
res.header("Vary", "Accept-Language");
|
|
1383
1393
|
res.json(translated);
|
|
@@ -1444,10 +1454,13 @@ var RestServer = class {
|
|
|
1444
1454
|
ifNoneMatch: req.headers["if-none-match"],
|
|
1445
1455
|
ifModifiedSince: req.headers["if-modified-since"]
|
|
1446
1456
|
};
|
|
1457
|
+
const cacheI18n = await this.resolveI18nService(environmentId, req);
|
|
1458
|
+
const cacheLocale = this.extractLocale(req, cacheI18n);
|
|
1447
1459
|
const result = await p.getMetaItemCached({
|
|
1448
1460
|
type: req.params.type,
|
|
1449
1461
|
name: req.params.name,
|
|
1450
1462
|
cacheRequest,
|
|
1463
|
+
...cacheLocale ? { locale: cacheLocale } : {},
|
|
1451
1464
|
...environmentId ? { environmentId } : {}
|
|
1452
1465
|
});
|
|
1453
1466
|
if (result.notModified) {
|
|
@@ -1467,7 +1480,7 @@ var RestServer = class {
|
|
|
1467
1480
|
res.header("Cache-Control", directives + maxAge);
|
|
1468
1481
|
}
|
|
1469
1482
|
res.header("Vary", "Accept-Language");
|
|
1470
|
-
res.json(await this.translateMetaItem(req, req.params.type, environmentId, result.data));
|
|
1483
|
+
res.json(await this.translateMetaItem(req, req.params.type, environmentId, result.data, cacheI18n));
|
|
1471
1484
|
} else {
|
|
1472
1485
|
const packageId = req.query?.package || void 0;
|
|
1473
1486
|
const stateParam = typeof req.query?.state === "string" ? req.query.state.toLowerCase() : void 0;
|
|
@@ -1527,6 +1540,8 @@ var RestServer = class {
|
|
|
1527
1540
|
const actor = typeof actorHeader === "string" ? actorHeader : void 0;
|
|
1528
1541
|
const forceRaw = req.query?.force;
|
|
1529
1542
|
const force = typeof forceRaw === "string" ? ["true", "1", "yes", "on"].includes(forceRaw.toLowerCase()) : !!forceRaw;
|
|
1543
|
+
const packageRaw = req.query?.package;
|
|
1544
|
+
const packageId = typeof packageRaw === "string" && packageRaw && packageRaw !== "all" ? packageRaw : void 0;
|
|
1530
1545
|
const result = await p.saveMetaItem({
|
|
1531
1546
|
type: req.params.type,
|
|
1532
1547
|
name: req.params.name,
|
|
@@ -1535,6 +1550,7 @@ var RestServer = class {
|
|
|
1535
1550
|
...parentVersion !== void 0 ? { parentVersion } : {},
|
|
1536
1551
|
...actor ? { actor } : {},
|
|
1537
1552
|
...force ? { force: true } : {},
|
|
1553
|
+
...packageId ? { packageId } : {},
|
|
1538
1554
|
...typeof req.query?.mode === "string" && req.query.mode.toLowerCase() === "draft" ? { mode: "draft" } : {}
|
|
1539
1555
|
});
|
|
1540
1556
|
res.json(result);
|
|
@@ -1809,13 +1825,16 @@ var RestServer = class {
|
|
|
1809
1825
|
const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
|
|
1810
1826
|
const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
|
|
1811
1827
|
const actor = typeof actorHeader === "string" ? actorHeader : void 0;
|
|
1828
|
+
const packageRaw = req.query?.package;
|
|
1829
|
+
const packageId = typeof packageRaw === "string" && packageRaw && packageRaw !== "all" ? packageRaw : void 0;
|
|
1812
1830
|
const result = await p.saveMetaItem({
|
|
1813
1831
|
type: req.params.type,
|
|
1814
1832
|
name: compoundName,
|
|
1815
1833
|
item: req.body,
|
|
1816
1834
|
...environmentId ? { environmentId } : {},
|
|
1817
1835
|
...parentVersion !== void 0 ? { parentVersion } : {},
|
|
1818
|
-
...actor ? { actor } : {}
|
|
1836
|
+
...actor ? { actor } : {},
|
|
1837
|
+
...packageId ? { packageId } : {}
|
|
1819
1838
|
});
|
|
1820
1839
|
res.json(result);
|
|
1821
1840
|
} catch (error) {
|
|
@@ -3297,19 +3316,17 @@ var RestServer = class {
|
|
|
3297
3316
|
});
|
|
3298
3317
|
}
|
|
3299
3318
|
/**
|
|
3300
|
-
* Register approval
|
|
3319
|
+
* Register approval endpoints (ADR-0019: approval as a flow node).
|
|
3320
|
+
*
|
|
3321
|
+
* Approval is no longer a standalone process engine — a flow's Approval
|
|
3322
|
+
* node opens a request and suspends the run; a decision resumes it. There
|
|
3323
|
+
* are no process-authoring or submit routes anymore.
|
|
3301
3324
|
*
|
|
3302
3325
|
* Routes (all under {basePath}/approvals):
|
|
3303
|
-
* GET /processes — list approval processes
|
|
3304
|
-
* POST /processes — upsert (defineProcess)
|
|
3305
|
-
* GET /processes/:id — get by id or name
|
|
3306
|
-
* DELETE /processes/:id — delete process
|
|
3307
|
-
* POST /requests — submit
|
|
3308
3326
|
* GET /requests — list (filters: status, object, recordId, approverId, submitterId)
|
|
3309
3327
|
* GET /requests/:id — get request
|
|
3310
|
-
* POST /requests/:id/approve — approve
|
|
3311
|
-
* POST /requests/:id/reject — reject
|
|
3312
|
-
* POST /requests/:id/recall — recall (submitter only)
|
|
3328
|
+
* POST /requests/:id/approve — record an approve decision (resumes the flow)
|
|
3329
|
+
* POST /requests/:id/reject — record a reject decision (resumes the flow)
|
|
3313
3330
|
* GET /requests/:id/actions — audit trail
|
|
3314
3331
|
*
|
|
3315
3332
|
* Returns 501 when `approvalsServiceProvider` is unset so deployments
|
|
@@ -3337,8 +3354,6 @@ var RestServer = class {
|
|
|
3337
3354
|
[/^DUPLICATE_REQUEST/, 409, "DUPLICATE_REQUEST"],
|
|
3338
3355
|
[/^INVALID_STATE/, 409, "INVALID_STATE"],
|
|
3339
3356
|
[/^FORBIDDEN/, 403, "FORBIDDEN"],
|
|
3340
|
-
[/^NO_ACTIVE_PROCESS/, 404, "NO_ACTIVE_PROCESS"],
|
|
3341
|
-
[/^PROCESS_NOT_FOUND/, 404, "PROCESS_NOT_FOUND"],
|
|
3342
3357
|
[/^REQUEST_NOT_FOUND/, 404, "REQUEST_NOT_FOUND"]
|
|
3343
3358
|
];
|
|
3344
3359
|
for (const [re, status, code] of mapping) {
|
|
@@ -3349,127 +3364,6 @@ var RestServer = class {
|
|
|
3349
3364
|
}
|
|
3350
3365
|
return false;
|
|
3351
3366
|
};
|
|
3352
|
-
this.routeManager.register({
|
|
3353
|
-
method: "GET",
|
|
3354
|
-
path: `${dataPath}/approvals/processes`,
|
|
3355
|
-
handler: async (req, res) => {
|
|
3356
|
-
try {
|
|
3357
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3358
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3359
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3360
|
-
const svc = await resolveService(environmentId);
|
|
3361
|
-
if (!svc) return respond501(res);
|
|
3362
|
-
const q = req.query ?? {};
|
|
3363
|
-
const rows = await svc.listProcesses({
|
|
3364
|
-
object: q.object,
|
|
3365
|
-
activeOnly: q.activeOnly === "true" || q.activeOnly === true
|
|
3366
|
-
}, context ?? {});
|
|
3367
|
-
res.json({ data: rows });
|
|
3368
|
-
} catch (error) {
|
|
3369
|
-
logError("[REST] List approval processes error:", error);
|
|
3370
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_LIST_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3371
|
-
}
|
|
3372
|
-
},
|
|
3373
|
-
metadata: { summary: "List approval processes", tags: ["approvals"] }
|
|
3374
|
-
});
|
|
3375
|
-
this.routeManager.register({
|
|
3376
|
-
method: "POST",
|
|
3377
|
-
path: `${dataPath}/approvals/processes`,
|
|
3378
|
-
handler: async (req, res) => {
|
|
3379
|
-
try {
|
|
3380
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3381
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3382
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3383
|
-
const svc = await resolveService(environmentId);
|
|
3384
|
-
if (!svc) return respond501(res);
|
|
3385
|
-
try {
|
|
3386
|
-
const row = await svc.defineProcess(req.body ?? {}, context ?? {});
|
|
3387
|
-
res.status(201).json(row);
|
|
3388
|
-
} catch (err) {
|
|
3389
|
-
if (handleApprovalError(res, err)) return;
|
|
3390
|
-
throw err;
|
|
3391
|
-
}
|
|
3392
|
-
} catch (error) {
|
|
3393
|
-
logError("[REST] Define approval process error:", error);
|
|
3394
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DEFINE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3395
|
-
}
|
|
3396
|
-
},
|
|
3397
|
-
metadata: { summary: "Define (upsert) an approval process", tags: ["approvals"] }
|
|
3398
|
-
});
|
|
3399
|
-
this.routeManager.register({
|
|
3400
|
-
method: "GET",
|
|
3401
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3402
|
-
handler: async (req, res) => {
|
|
3403
|
-
try {
|
|
3404
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3405
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3406
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3407
|
-
const svc = await resolveService(environmentId);
|
|
3408
|
-
if (!svc) return respond501(res);
|
|
3409
|
-
const row = await svc.getProcess(req.params.id, context ?? {});
|
|
3410
|
-
if (!row) {
|
|
3411
|
-
res.status(404).json({ code: "PROCESS_NOT_FOUND", error: `Approval process '${req.params.id}' not found` });
|
|
3412
|
-
return;
|
|
3413
|
-
}
|
|
3414
|
-
res.json(row);
|
|
3415
|
-
} catch (error) {
|
|
3416
|
-
logError("[REST] Get approval process error:", error);
|
|
3417
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_GET_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3418
|
-
}
|
|
3419
|
-
},
|
|
3420
|
-
metadata: { summary: "Get an approval process by id or name", tags: ["approvals"] }
|
|
3421
|
-
});
|
|
3422
|
-
this.routeManager.register({
|
|
3423
|
-
method: "DELETE",
|
|
3424
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3425
|
-
handler: async (req, res) => {
|
|
3426
|
-
try {
|
|
3427
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3428
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3429
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3430
|
-
const svc = await resolveService(environmentId);
|
|
3431
|
-
if (!svc) return respond501(res);
|
|
3432
|
-
await svc.deleteProcess(req.params.id, context ?? {});
|
|
3433
|
-
res.status(204).end();
|
|
3434
|
-
} catch (error) {
|
|
3435
|
-
logError("[REST] Delete approval process error:", error);
|
|
3436
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DELETE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3437
|
-
}
|
|
3438
|
-
},
|
|
3439
|
-
metadata: { summary: "Delete an approval process", tags: ["approvals"] }
|
|
3440
|
-
});
|
|
3441
|
-
this.routeManager.register({
|
|
3442
|
-
method: "POST",
|
|
3443
|
-
path: `${dataPath}/approvals/requests`,
|
|
3444
|
-
handler: async (req, res) => {
|
|
3445
|
-
try {
|
|
3446
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3447
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3448
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3449
|
-
const svc = await resolveService(environmentId);
|
|
3450
|
-
if (!svc) return respond501(res);
|
|
3451
|
-
const body = req.body ?? {};
|
|
3452
|
-
try {
|
|
3453
|
-
const row = await svc.submit({
|
|
3454
|
-
object: body.object,
|
|
3455
|
-
recordId: body.recordId ?? body.record_id,
|
|
3456
|
-
processName: body.processName ?? body.process_name,
|
|
3457
|
-
submitterId: body.submitterId ?? body.submitter_id ?? context?.userId,
|
|
3458
|
-
comment: body.comment,
|
|
3459
|
-
payload: body.payload
|
|
3460
|
-
}, context ?? {});
|
|
3461
|
-
res.status(201).json(row);
|
|
3462
|
-
} catch (err) {
|
|
3463
|
-
if (handleApprovalError(res, err)) return;
|
|
3464
|
-
throw err;
|
|
3465
|
-
}
|
|
3466
|
-
} catch (error) {
|
|
3467
|
-
logError("[REST] Submit approval error:", error);
|
|
3468
|
-
res.status(500).json({ code: "APPROVAL_SUBMIT_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3469
|
-
}
|
|
3470
|
-
},
|
|
3471
|
-
metadata: { summary: "Submit a record for approval", tags: ["approvals"] }
|
|
3472
|
-
});
|
|
3473
3367
|
this.routeManager.register({
|
|
3474
3368
|
method: "GET",
|
|
3475
3369
|
path: `${dataPath}/approvals/requests`,
|
|
@@ -3522,10 +3416,10 @@ var RestServer = class {
|
|
|
3522
3416
|
},
|
|
3523
3417
|
metadata: { summary: "Get an approval request by id", tags: ["approvals"] }
|
|
3524
3418
|
});
|
|
3525
|
-
const decisionRoute = (
|
|
3419
|
+
const decisionRoute = (decision) => {
|
|
3526
3420
|
this.routeManager.register({
|
|
3527
3421
|
method: "POST",
|
|
3528
|
-
path: `${dataPath}/approvals/requests/:id/${
|
|
3422
|
+
path: `${dataPath}/approvals/requests/:id/${decision}`,
|
|
3529
3423
|
handler: async (req, res) => {
|
|
3530
3424
|
try {
|
|
3531
3425
|
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
@@ -3535,7 +3429,8 @@ var RestServer = class {
|
|
|
3535
3429
|
if (!svc) return respond501(res);
|
|
3536
3430
|
const body = req.body ?? {};
|
|
3537
3431
|
try {
|
|
3538
|
-
const out = await svc
|
|
3432
|
+
const out = await svc.decide(req.params.id, {
|
|
3433
|
+
decision,
|
|
3539
3434
|
actorId: body.actorId ?? body.actor_id ?? context?.userId,
|
|
3540
3435
|
comment: body.comment
|
|
3541
3436
|
}, context ?? {});
|
|
@@ -3545,16 +3440,15 @@ var RestServer = class {
|
|
|
3545
3440
|
throw err;
|
|
3546
3441
|
}
|
|
3547
3442
|
} catch (error) {
|
|
3548
|
-
logError(`[REST] ${
|
|
3549
|
-
res.status(500).json({ code: `APPROVAL_${
|
|
3443
|
+
logError(`[REST] ${decision} approval error:`, error);
|
|
3444
|
+
res.status(500).json({ code: `APPROVAL_${decision.toUpperCase()}_FAILED`, error: String(error?.message ?? error).slice(0, 500) });
|
|
3550
3445
|
}
|
|
3551
3446
|
},
|
|
3552
|
-
metadata: { summary: `${
|
|
3447
|
+
metadata: { summary: `${decision[0].toUpperCase()}${decision.slice(1)} an approval request`, tags: ["approvals"] }
|
|
3553
3448
|
});
|
|
3554
3449
|
};
|
|
3555
|
-
decisionRoute("approve"
|
|
3556
|
-
decisionRoute("reject"
|
|
3557
|
-
decisionRoute("recall", "recall");
|
|
3450
|
+
decisionRoute("approve");
|
|
3451
|
+
decisionRoute("reject");
|
|
3558
3452
|
this.routeManager.register({
|
|
3559
3453
|
method: "GET",
|
|
3560
3454
|
path: `${dataPath}/approvals/requests/:id/actions`,
|
|
@@ -3827,6 +3721,66 @@ function registerPackageRoutes(server, packageService, basePath = "/api/v1", opt
|
|
|
3827
3721
|
});
|
|
3828
3722
|
}
|
|
3829
3723
|
|
|
3724
|
+
// src/external-datasource-routes.ts
|
|
3725
|
+
function registerExternalDatasourceRoutes(server, ctx, basePath = "/api/v1") {
|
|
3726
|
+
const ext = `${basePath}/datasources/:name/external`;
|
|
3727
|
+
const externalService = () => {
|
|
3728
|
+
try {
|
|
3729
|
+
return ctx.getService("external-datasource");
|
|
3730
|
+
} catch {
|
|
3731
|
+
return void 0;
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
const unavailable = (res) => res.status(503).json({ error: "external_service_unavailable" });
|
|
3735
|
+
server.get(`${ext}/tables`, async (req, res) => {
|
|
3736
|
+
const svc = externalService();
|
|
3737
|
+
if (!svc?.listRemoteTables) return unavailable(res);
|
|
3738
|
+
const schema = typeof req.query?.schema === "string" ? req.query.schema : void 0;
|
|
3739
|
+
const tables = await svc.listRemoteTables(req.params.name, { schema });
|
|
3740
|
+
res.json({ tables });
|
|
3741
|
+
});
|
|
3742
|
+
server.post(`${ext}/tables/:remote/draft`, async (req, res) => {
|
|
3743
|
+
const svc = externalService();
|
|
3744
|
+
if (!svc?.generateObjectDraft) return unavailable(res);
|
|
3745
|
+
const draft = await svc.generateObjectDraft(
|
|
3746
|
+
req.params.name,
|
|
3747
|
+
req.params.remote,
|
|
3748
|
+
req.body ?? {}
|
|
3749
|
+
);
|
|
3750
|
+
res.json({ draft });
|
|
3751
|
+
});
|
|
3752
|
+
server.post(`${ext}/tables/:remote/import`, async (req, res) => {
|
|
3753
|
+
const svc = externalService();
|
|
3754
|
+
if (!svc?.importObject) return unavailable(res);
|
|
3755
|
+
try {
|
|
3756
|
+
const result = await svc.importObject(
|
|
3757
|
+
req.params.name,
|
|
3758
|
+
req.params.remote,
|
|
3759
|
+
req.body ?? {}
|
|
3760
|
+
);
|
|
3761
|
+
res.status(201).json({ object: result });
|
|
3762
|
+
} catch (err) {
|
|
3763
|
+
res.status(400).json({
|
|
3764
|
+
error: "external_import_error",
|
|
3765
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
server.post(`${ext}/refresh-catalog`, async (req, res) => {
|
|
3770
|
+
const svc = externalService();
|
|
3771
|
+
if (!svc?.refreshCatalog) return unavailable(res);
|
|
3772
|
+
const catalog = await svc.refreshCatalog(req.params.name);
|
|
3773
|
+
res.json({ catalog });
|
|
3774
|
+
});
|
|
3775
|
+
server.post(`${ext}/validate`, async (req, res) => {
|
|
3776
|
+
const svc = externalService();
|
|
3777
|
+
if (!svc?.validateAll) return unavailable(res);
|
|
3778
|
+
const report = await svc.validateAll();
|
|
3779
|
+
const results = (report.results ?? []).filter((r) => r.datasource === req.params.name);
|
|
3780
|
+
res.json({ ok: results.every((r) => r.ok), results });
|
|
3781
|
+
});
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3830
3784
|
// src/rest-api-plugin.ts
|
|
3831
3785
|
function createRestApiPlugin(config = {}) {
|
|
3832
3786
|
return {
|
|
@@ -3939,14 +3893,14 @@ function createRestApiPlugin(config = {}) {
|
|
|
3939
3893
|
ctx.logger.error("Failed to register REST API routes", { error: err.message });
|
|
3940
3894
|
throw err;
|
|
3941
3895
|
}
|
|
3896
|
+
const basePath = config.api?.api?.basePath || "/api";
|
|
3897
|
+
const version = config.api?.api?.version || "v1";
|
|
3898
|
+
const versionedBase = `${basePath}/${version}`;
|
|
3899
|
+
const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
|
|
3900
|
+
const projectResolution = config.api?.api?.projectResolution ?? "auto";
|
|
3942
3901
|
try {
|
|
3943
3902
|
const packageService = ctx.getService("package");
|
|
3944
3903
|
if (packageService) {
|
|
3945
|
-
const basePath = config.api?.api?.basePath || "/api";
|
|
3946
|
-
const version = config.api?.api?.version || "v1";
|
|
3947
|
-
const versionedBase = `${basePath}/${version}`;
|
|
3948
|
-
const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
|
|
3949
|
-
const projectResolution = config.api?.api?.projectResolution ?? "auto";
|
|
3950
3904
|
if (enableProjectScoping && projectResolution === "required") {
|
|
3951
3905
|
registerPackageRoutes(server, packageService, `${versionedBase}/environments/:environmentId`, {
|
|
3952
3906
|
protocol
|
|
@@ -3964,6 +3918,12 @@ function createRestApiPlugin(config = {}) {
|
|
|
3964
3918
|
} catch (e) {
|
|
3965
3919
|
ctx.logger.debug("Package service not available, package routes skipped");
|
|
3966
3920
|
}
|
|
3921
|
+
try {
|
|
3922
|
+
registerExternalDatasourceRoutes(server, ctx, versionedBase);
|
|
3923
|
+
ctx.logger.info("Datasource federation routes registered");
|
|
3924
|
+
} catch (e) {
|
|
3925
|
+
ctx.logger.warn("Datasource federation routes registration failed", { error: e?.message });
|
|
3926
|
+
}
|
|
3967
3927
|
}
|
|
3968
3928
|
};
|
|
3969
3929
|
}
|