@objectstack/rest 7.3.0 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +105 -151
- 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 +105 -151
- 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;
|
|
@@ -3303,19 +3316,17 @@ var RestServer = class {
|
|
|
3303
3316
|
});
|
|
3304
3317
|
}
|
|
3305
3318
|
/**
|
|
3306
|
-
* 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.
|
|
3307
3324
|
*
|
|
3308
3325
|
* Routes (all under {basePath}/approvals):
|
|
3309
|
-
* GET /processes — list approval processes
|
|
3310
|
-
* POST /processes — upsert (defineProcess)
|
|
3311
|
-
* GET /processes/:id — get by id or name
|
|
3312
|
-
* DELETE /processes/:id — delete process
|
|
3313
|
-
* POST /requests — submit
|
|
3314
3326
|
* GET /requests — list (filters: status, object, recordId, approverId, submitterId)
|
|
3315
3327
|
* GET /requests/:id — get request
|
|
3316
|
-
* POST /requests/:id/approve — approve
|
|
3317
|
-
* POST /requests/:id/reject — reject
|
|
3318
|
-
* 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)
|
|
3319
3330
|
* GET /requests/:id/actions — audit trail
|
|
3320
3331
|
*
|
|
3321
3332
|
* Returns 501 when `approvalsServiceProvider` is unset so deployments
|
|
@@ -3343,8 +3354,6 @@ var RestServer = class {
|
|
|
3343
3354
|
[/^DUPLICATE_REQUEST/, 409, "DUPLICATE_REQUEST"],
|
|
3344
3355
|
[/^INVALID_STATE/, 409, "INVALID_STATE"],
|
|
3345
3356
|
[/^FORBIDDEN/, 403, "FORBIDDEN"],
|
|
3346
|
-
[/^NO_ACTIVE_PROCESS/, 404, "NO_ACTIVE_PROCESS"],
|
|
3347
|
-
[/^PROCESS_NOT_FOUND/, 404, "PROCESS_NOT_FOUND"],
|
|
3348
3357
|
[/^REQUEST_NOT_FOUND/, 404, "REQUEST_NOT_FOUND"]
|
|
3349
3358
|
];
|
|
3350
3359
|
for (const [re, status, code] of mapping) {
|
|
@@ -3355,127 +3364,6 @@ var RestServer = class {
|
|
|
3355
3364
|
}
|
|
3356
3365
|
return false;
|
|
3357
3366
|
};
|
|
3358
|
-
this.routeManager.register({
|
|
3359
|
-
method: "GET",
|
|
3360
|
-
path: `${dataPath}/approvals/processes`,
|
|
3361
|
-
handler: async (req, res) => {
|
|
3362
|
-
try {
|
|
3363
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3364
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3365
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3366
|
-
const svc = await resolveService(environmentId);
|
|
3367
|
-
if (!svc) return respond501(res);
|
|
3368
|
-
const q = req.query ?? {};
|
|
3369
|
-
const rows = await svc.listProcesses({
|
|
3370
|
-
object: q.object,
|
|
3371
|
-
activeOnly: q.activeOnly === "true" || q.activeOnly === true
|
|
3372
|
-
}, context ?? {});
|
|
3373
|
-
res.json({ data: rows });
|
|
3374
|
-
} catch (error) {
|
|
3375
|
-
logError("[REST] List approval processes error:", error);
|
|
3376
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_LIST_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3377
|
-
}
|
|
3378
|
-
},
|
|
3379
|
-
metadata: { summary: "List approval processes", tags: ["approvals"] }
|
|
3380
|
-
});
|
|
3381
|
-
this.routeManager.register({
|
|
3382
|
-
method: "POST",
|
|
3383
|
-
path: `${dataPath}/approvals/processes`,
|
|
3384
|
-
handler: async (req, res) => {
|
|
3385
|
-
try {
|
|
3386
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3387
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3388
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3389
|
-
const svc = await resolveService(environmentId);
|
|
3390
|
-
if (!svc) return respond501(res);
|
|
3391
|
-
try {
|
|
3392
|
-
const row = await svc.defineProcess(req.body ?? {}, context ?? {});
|
|
3393
|
-
res.status(201).json(row);
|
|
3394
|
-
} catch (err) {
|
|
3395
|
-
if (handleApprovalError(res, err)) return;
|
|
3396
|
-
throw err;
|
|
3397
|
-
}
|
|
3398
|
-
} catch (error) {
|
|
3399
|
-
logError("[REST] Define approval process error:", error);
|
|
3400
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DEFINE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3401
|
-
}
|
|
3402
|
-
},
|
|
3403
|
-
metadata: { summary: "Define (upsert) an approval process", tags: ["approvals"] }
|
|
3404
|
-
});
|
|
3405
|
-
this.routeManager.register({
|
|
3406
|
-
method: "GET",
|
|
3407
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3408
|
-
handler: async (req, res) => {
|
|
3409
|
-
try {
|
|
3410
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3411
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3412
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3413
|
-
const svc = await resolveService(environmentId);
|
|
3414
|
-
if (!svc) return respond501(res);
|
|
3415
|
-
const row = await svc.getProcess(req.params.id, context ?? {});
|
|
3416
|
-
if (!row) {
|
|
3417
|
-
res.status(404).json({ code: "PROCESS_NOT_FOUND", error: `Approval process '${req.params.id}' not found` });
|
|
3418
|
-
return;
|
|
3419
|
-
}
|
|
3420
|
-
res.json(row);
|
|
3421
|
-
} catch (error) {
|
|
3422
|
-
logError("[REST] Get approval process error:", error);
|
|
3423
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_GET_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3424
|
-
}
|
|
3425
|
-
},
|
|
3426
|
-
metadata: { summary: "Get an approval process by id or name", tags: ["approvals"] }
|
|
3427
|
-
});
|
|
3428
|
-
this.routeManager.register({
|
|
3429
|
-
method: "DELETE",
|
|
3430
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3431
|
-
handler: async (req, res) => {
|
|
3432
|
-
try {
|
|
3433
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3434
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3435
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3436
|
-
const svc = await resolveService(environmentId);
|
|
3437
|
-
if (!svc) return respond501(res);
|
|
3438
|
-
await svc.deleteProcess(req.params.id, context ?? {});
|
|
3439
|
-
res.status(204).end();
|
|
3440
|
-
} catch (error) {
|
|
3441
|
-
logError("[REST] Delete approval process error:", error);
|
|
3442
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DELETE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3443
|
-
}
|
|
3444
|
-
},
|
|
3445
|
-
metadata: { summary: "Delete an approval process", tags: ["approvals"] }
|
|
3446
|
-
});
|
|
3447
|
-
this.routeManager.register({
|
|
3448
|
-
method: "POST",
|
|
3449
|
-
path: `${dataPath}/approvals/requests`,
|
|
3450
|
-
handler: async (req, res) => {
|
|
3451
|
-
try {
|
|
3452
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3453
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3454
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3455
|
-
const svc = await resolveService(environmentId);
|
|
3456
|
-
if (!svc) return respond501(res);
|
|
3457
|
-
const body = req.body ?? {};
|
|
3458
|
-
try {
|
|
3459
|
-
const row = await svc.submit({
|
|
3460
|
-
object: body.object,
|
|
3461
|
-
recordId: body.recordId ?? body.record_id,
|
|
3462
|
-
processName: body.processName ?? body.process_name,
|
|
3463
|
-
submitterId: body.submitterId ?? body.submitter_id ?? context?.userId,
|
|
3464
|
-
comment: body.comment,
|
|
3465
|
-
payload: body.payload
|
|
3466
|
-
}, context ?? {});
|
|
3467
|
-
res.status(201).json(row);
|
|
3468
|
-
} catch (err) {
|
|
3469
|
-
if (handleApprovalError(res, err)) return;
|
|
3470
|
-
throw err;
|
|
3471
|
-
}
|
|
3472
|
-
} catch (error) {
|
|
3473
|
-
logError("[REST] Submit approval error:", error);
|
|
3474
|
-
res.status(500).json({ code: "APPROVAL_SUBMIT_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3475
|
-
}
|
|
3476
|
-
},
|
|
3477
|
-
metadata: { summary: "Submit a record for approval", tags: ["approvals"] }
|
|
3478
|
-
});
|
|
3479
3367
|
this.routeManager.register({
|
|
3480
3368
|
method: "GET",
|
|
3481
3369
|
path: `${dataPath}/approvals/requests`,
|
|
@@ -3528,10 +3416,10 @@ var RestServer = class {
|
|
|
3528
3416
|
},
|
|
3529
3417
|
metadata: { summary: "Get an approval request by id", tags: ["approvals"] }
|
|
3530
3418
|
});
|
|
3531
|
-
const decisionRoute = (
|
|
3419
|
+
const decisionRoute = (decision) => {
|
|
3532
3420
|
this.routeManager.register({
|
|
3533
3421
|
method: "POST",
|
|
3534
|
-
path: `${dataPath}/approvals/requests/:id/${
|
|
3422
|
+
path: `${dataPath}/approvals/requests/:id/${decision}`,
|
|
3535
3423
|
handler: async (req, res) => {
|
|
3536
3424
|
try {
|
|
3537
3425
|
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
@@ -3541,7 +3429,8 @@ var RestServer = class {
|
|
|
3541
3429
|
if (!svc) return respond501(res);
|
|
3542
3430
|
const body = req.body ?? {};
|
|
3543
3431
|
try {
|
|
3544
|
-
const out = await svc
|
|
3432
|
+
const out = await svc.decide(req.params.id, {
|
|
3433
|
+
decision,
|
|
3545
3434
|
actorId: body.actorId ?? body.actor_id ?? context?.userId,
|
|
3546
3435
|
comment: body.comment
|
|
3547
3436
|
}, context ?? {});
|
|
@@ -3551,16 +3440,15 @@ var RestServer = class {
|
|
|
3551
3440
|
throw err;
|
|
3552
3441
|
}
|
|
3553
3442
|
} catch (error) {
|
|
3554
|
-
logError(`[REST] ${
|
|
3555
|
-
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) });
|
|
3556
3445
|
}
|
|
3557
3446
|
},
|
|
3558
|
-
metadata: { summary: `${
|
|
3447
|
+
metadata: { summary: `${decision[0].toUpperCase()}${decision.slice(1)} an approval request`, tags: ["approvals"] }
|
|
3559
3448
|
});
|
|
3560
3449
|
};
|
|
3561
|
-
decisionRoute("approve"
|
|
3562
|
-
decisionRoute("reject"
|
|
3563
|
-
decisionRoute("recall", "recall");
|
|
3450
|
+
decisionRoute("approve");
|
|
3451
|
+
decisionRoute("reject");
|
|
3564
3452
|
this.routeManager.register({
|
|
3565
3453
|
method: "GET",
|
|
3566
3454
|
path: `${dataPath}/approvals/requests/:id/actions`,
|
|
@@ -3833,6 +3721,66 @@ function registerPackageRoutes(server, packageService, basePath = "/api/v1", opt
|
|
|
3833
3721
|
});
|
|
3834
3722
|
}
|
|
3835
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
|
+
|
|
3836
3784
|
// src/rest-api-plugin.ts
|
|
3837
3785
|
function createRestApiPlugin(config = {}) {
|
|
3838
3786
|
return {
|
|
@@ -3945,14 +3893,14 @@ function createRestApiPlugin(config = {}) {
|
|
|
3945
3893
|
ctx.logger.error("Failed to register REST API routes", { error: err.message });
|
|
3946
3894
|
throw err;
|
|
3947
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";
|
|
3948
3901
|
try {
|
|
3949
3902
|
const packageService = ctx.getService("package");
|
|
3950
3903
|
if (packageService) {
|
|
3951
|
-
const basePath = config.api?.api?.basePath || "/api";
|
|
3952
|
-
const version = config.api?.api?.version || "v1";
|
|
3953
|
-
const versionedBase = `${basePath}/${version}`;
|
|
3954
|
-
const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
|
|
3955
|
-
const projectResolution = config.api?.api?.projectResolution ?? "auto";
|
|
3956
3904
|
if (enableProjectScoping && projectResolution === "required") {
|
|
3957
3905
|
registerPackageRoutes(server, packageService, `${versionedBase}/environments/:environmentId`, {
|
|
3958
3906
|
protocol
|
|
@@ -3970,6 +3918,12 @@ function createRestApiPlugin(config = {}) {
|
|
|
3970
3918
|
} catch (e) {
|
|
3971
3919
|
ctx.logger.debug("Package service not available, package routes skipped");
|
|
3972
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
|
+
}
|
|
3973
3927
|
}
|
|
3974
3928
|
};
|
|
3975
3929
|
}
|