@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.d.cts
CHANGED
|
@@ -521,19 +521,17 @@ declare class RestServer {
|
|
|
521
521
|
*/
|
|
522
522
|
private registerReportsEndpoints;
|
|
523
523
|
/**
|
|
524
|
-
* Register approval
|
|
524
|
+
* Register approval endpoints (ADR-0019: approval as a flow node).
|
|
525
|
+
*
|
|
526
|
+
* Approval is no longer a standalone process engine — a flow's Approval
|
|
527
|
+
* node opens a request and suspends the run; a decision resumes it. There
|
|
528
|
+
* are no process-authoring or submit routes anymore.
|
|
525
529
|
*
|
|
526
530
|
* Routes (all under {basePath}/approvals):
|
|
527
|
-
* GET /processes — list approval processes
|
|
528
|
-
* POST /processes — upsert (defineProcess)
|
|
529
|
-
* GET /processes/:id — get by id or name
|
|
530
|
-
* DELETE /processes/:id — delete process
|
|
531
|
-
* POST /requests — submit
|
|
532
531
|
* GET /requests — list (filters: status, object, recordId, approverId, submitterId)
|
|
533
532
|
* GET /requests/:id — get request
|
|
534
|
-
* POST /requests/:id/approve — approve
|
|
535
|
-
* POST /requests/:id/reject — reject
|
|
536
|
-
* POST /requests/:id/recall — recall (submitter only)
|
|
533
|
+
* POST /requests/:id/approve — record an approve decision (resumes the flow)
|
|
534
|
+
* POST /requests/:id/reject — record a reject decision (resumes the flow)
|
|
537
535
|
* GET /requests/:id/actions — audit trail
|
|
538
536
|
*
|
|
539
537
|
* Returns 501 when `approvalsServiceProvider` is unset so deployments
|
package/dist/index.d.ts
CHANGED
|
@@ -521,19 +521,17 @@ declare class RestServer {
|
|
|
521
521
|
*/
|
|
522
522
|
private registerReportsEndpoints;
|
|
523
523
|
/**
|
|
524
|
-
* Register approval
|
|
524
|
+
* Register approval endpoints (ADR-0019: approval as a flow node).
|
|
525
|
+
*
|
|
526
|
+
* Approval is no longer a standalone process engine — a flow's Approval
|
|
527
|
+
* node opens a request and suspends the run; a decision resumes it. There
|
|
528
|
+
* are no process-authoring or submit routes anymore.
|
|
525
529
|
*
|
|
526
530
|
* Routes (all under {basePath}/approvals):
|
|
527
|
-
* GET /processes — list approval processes
|
|
528
|
-
* POST /processes — upsert (defineProcess)
|
|
529
|
-
* GET /processes/:id — get by id or name
|
|
530
|
-
* DELETE /processes/:id — delete process
|
|
531
|
-
* POST /requests — submit
|
|
532
531
|
* GET /requests — list (filters: status, object, recordId, approverId, submitterId)
|
|
533
532
|
* GET /requests/:id — get request
|
|
534
|
-
* POST /requests/:id/approve — approve
|
|
535
|
-
* POST /requests/:id/reject — reject
|
|
536
|
-
* POST /requests/:id/recall — recall (submitter only)
|
|
533
|
+
* POST /requests/:id/approve — record an approve decision (resumes the flow)
|
|
534
|
+
* POST /requests/:id/reject — record a reject decision (resumes the flow)
|
|
537
535
|
* GET /requests/:id/actions — audit trail
|
|
538
536
|
*
|
|
539
537
|
* Returns 501 when `approvalsServiceProvider` is unset so deployments
|
package/dist/index.js
CHANGED
|
@@ -210,6 +210,7 @@ var RouteGroupBuilder = class {
|
|
|
210
210
|
|
|
211
211
|
// src/rest-server.ts
|
|
212
212
|
var logError = (...args) => globalThis.console?.error(...args);
|
|
213
|
+
var TRANSLATABLE_META_TYPES = /* @__PURE__ */ new Set(["view", "action", "object", "app", "dashboard"]);
|
|
213
214
|
function mapDataError(error, object) {
|
|
214
215
|
if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
|
|
215
216
|
return {
|
|
@@ -819,10 +820,10 @@ var RestServer = class {
|
|
|
819
820
|
* locale yields a match. Falls through unchanged for unsupported types
|
|
820
821
|
* or missing translations.
|
|
821
822
|
*/
|
|
822
|
-
async translateMetaItem(req, type, environmentId, item) {
|
|
823
|
+
async translateMetaItem(req, type, environmentId, item, i18nService) {
|
|
823
824
|
if (!item || typeof item !== "object") return item;
|
|
824
|
-
if (type
|
|
825
|
-
const i18n = await this.resolveI18nService(environmentId, req);
|
|
825
|
+
if (!TRANSLATABLE_META_TYPES.has(type)) return item;
|
|
826
|
+
const i18n = i18nService !== void 0 ? i18nService : await this.resolveI18nService(environmentId, req);
|
|
826
827
|
const bundle = this.buildTranslationBundle(i18n);
|
|
827
828
|
if (!bundle) return item;
|
|
828
829
|
const locale = this.extractLocale(req, i18n);
|
|
@@ -835,7 +836,7 @@ var RestServer = class {
|
|
|
835
836
|
*/
|
|
836
837
|
async translateMetaItems(req, type, environmentId, items) {
|
|
837
838
|
if (!Array.isArray(items)) return items;
|
|
838
|
-
if (type
|
|
839
|
+
if (!TRANSLATABLE_META_TYPES.has(type)) return items;
|
|
839
840
|
const i18n = await this.resolveI18nService(environmentId, req);
|
|
840
841
|
const bundle = this.buildTranslationBundle(i18n);
|
|
841
842
|
if (!bundle) return items;
|
|
@@ -1338,6 +1339,15 @@ var RestServer = class {
|
|
|
1338
1339
|
}
|
|
1339
1340
|
}
|
|
1340
1341
|
}
|
|
1342
|
+
if (req.params.type === "view" && req.query?.object) {
|
|
1343
|
+
const obj = String(req.query.object);
|
|
1344
|
+
const raw = visible;
|
|
1345
|
+
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.items) ? raw.items : null;
|
|
1346
|
+
if (list) {
|
|
1347
|
+
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)));
|
|
1348
|
+
visible = Array.isArray(raw) ? filtered : { ...raw, items: filtered };
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1341
1351
|
const translated = await this.translateMetaItems(req, req.params.type, environmentId, visible);
|
|
1342
1352
|
res.header("Vary", "Accept-Language");
|
|
1343
1353
|
res.json(translated);
|
|
@@ -1404,10 +1414,13 @@ var RestServer = class {
|
|
|
1404
1414
|
ifNoneMatch: req.headers["if-none-match"],
|
|
1405
1415
|
ifModifiedSince: req.headers["if-modified-since"]
|
|
1406
1416
|
};
|
|
1417
|
+
const cacheI18n = await this.resolveI18nService(environmentId, req);
|
|
1418
|
+
const cacheLocale = this.extractLocale(req, cacheI18n);
|
|
1407
1419
|
const result = await p.getMetaItemCached({
|
|
1408
1420
|
type: req.params.type,
|
|
1409
1421
|
name: req.params.name,
|
|
1410
1422
|
cacheRequest,
|
|
1423
|
+
...cacheLocale ? { locale: cacheLocale } : {},
|
|
1411
1424
|
...environmentId ? { environmentId } : {}
|
|
1412
1425
|
});
|
|
1413
1426
|
if (result.notModified) {
|
|
@@ -1427,7 +1440,7 @@ var RestServer = class {
|
|
|
1427
1440
|
res.header("Cache-Control", directives + maxAge);
|
|
1428
1441
|
}
|
|
1429
1442
|
res.header("Vary", "Accept-Language");
|
|
1430
|
-
res.json(await this.translateMetaItem(req, req.params.type, environmentId, result.data));
|
|
1443
|
+
res.json(await this.translateMetaItem(req, req.params.type, environmentId, result.data, cacheI18n));
|
|
1431
1444
|
} else {
|
|
1432
1445
|
const packageId = req.query?.package || void 0;
|
|
1433
1446
|
const stateParam = typeof req.query?.state === "string" ? req.query.state.toLowerCase() : void 0;
|
|
@@ -1487,6 +1500,8 @@ var RestServer = class {
|
|
|
1487
1500
|
const actor = typeof actorHeader === "string" ? actorHeader : void 0;
|
|
1488
1501
|
const forceRaw = req.query?.force;
|
|
1489
1502
|
const force = typeof forceRaw === "string" ? ["true", "1", "yes", "on"].includes(forceRaw.toLowerCase()) : !!forceRaw;
|
|
1503
|
+
const packageRaw = req.query?.package;
|
|
1504
|
+
const packageId = typeof packageRaw === "string" && packageRaw && packageRaw !== "all" ? packageRaw : void 0;
|
|
1490
1505
|
const result = await p.saveMetaItem({
|
|
1491
1506
|
type: req.params.type,
|
|
1492
1507
|
name: req.params.name,
|
|
@@ -1495,6 +1510,7 @@ var RestServer = class {
|
|
|
1495
1510
|
...parentVersion !== void 0 ? { parentVersion } : {},
|
|
1496
1511
|
...actor ? { actor } : {},
|
|
1497
1512
|
...force ? { force: true } : {},
|
|
1513
|
+
...packageId ? { packageId } : {},
|
|
1498
1514
|
...typeof req.query?.mode === "string" && req.query.mode.toLowerCase() === "draft" ? { mode: "draft" } : {}
|
|
1499
1515
|
});
|
|
1500
1516
|
res.json(result);
|
|
@@ -1769,13 +1785,16 @@ var RestServer = class {
|
|
|
1769
1785
|
const parentVersion = typeof ifMatchHeader === "string" ? ifMatchHeader.replace(/^"|"$/g, "") : void 0;
|
|
1770
1786
|
const actorHeader = req.headers?.["x-actor"] ?? req.headers?.["X-Actor"] ?? req.user?.id ?? req.userId;
|
|
1771
1787
|
const actor = typeof actorHeader === "string" ? actorHeader : void 0;
|
|
1788
|
+
const packageRaw = req.query?.package;
|
|
1789
|
+
const packageId = typeof packageRaw === "string" && packageRaw && packageRaw !== "all" ? packageRaw : void 0;
|
|
1772
1790
|
const result = await p.saveMetaItem({
|
|
1773
1791
|
type: req.params.type,
|
|
1774
1792
|
name: compoundName,
|
|
1775
1793
|
item: req.body,
|
|
1776
1794
|
...environmentId ? { environmentId } : {},
|
|
1777
1795
|
...parentVersion !== void 0 ? { parentVersion } : {},
|
|
1778
|
-
...actor ? { actor } : {}
|
|
1796
|
+
...actor ? { actor } : {},
|
|
1797
|
+
...packageId ? { packageId } : {}
|
|
1779
1798
|
});
|
|
1780
1799
|
res.json(result);
|
|
1781
1800
|
} catch (error) {
|
|
@@ -3257,19 +3276,17 @@ var RestServer = class {
|
|
|
3257
3276
|
});
|
|
3258
3277
|
}
|
|
3259
3278
|
/**
|
|
3260
|
-
* Register approval
|
|
3279
|
+
* Register approval endpoints (ADR-0019: approval as a flow node).
|
|
3280
|
+
*
|
|
3281
|
+
* Approval is no longer a standalone process engine — a flow's Approval
|
|
3282
|
+
* node opens a request and suspends the run; a decision resumes it. There
|
|
3283
|
+
* are no process-authoring or submit routes anymore.
|
|
3261
3284
|
*
|
|
3262
3285
|
* Routes (all under {basePath}/approvals):
|
|
3263
|
-
* GET /processes — list approval processes
|
|
3264
|
-
* POST /processes — upsert (defineProcess)
|
|
3265
|
-
* GET /processes/:id — get by id or name
|
|
3266
|
-
* DELETE /processes/:id — delete process
|
|
3267
|
-
* POST /requests — submit
|
|
3268
3286
|
* GET /requests — list (filters: status, object, recordId, approverId, submitterId)
|
|
3269
3287
|
* GET /requests/:id — get request
|
|
3270
|
-
* POST /requests/:id/approve — approve
|
|
3271
|
-
* POST /requests/:id/reject — reject
|
|
3272
|
-
* POST /requests/:id/recall — recall (submitter only)
|
|
3288
|
+
* POST /requests/:id/approve — record an approve decision (resumes the flow)
|
|
3289
|
+
* POST /requests/:id/reject — record a reject decision (resumes the flow)
|
|
3273
3290
|
* GET /requests/:id/actions — audit trail
|
|
3274
3291
|
*
|
|
3275
3292
|
* Returns 501 when `approvalsServiceProvider` is unset so deployments
|
|
@@ -3297,8 +3314,6 @@ var RestServer = class {
|
|
|
3297
3314
|
[/^DUPLICATE_REQUEST/, 409, "DUPLICATE_REQUEST"],
|
|
3298
3315
|
[/^INVALID_STATE/, 409, "INVALID_STATE"],
|
|
3299
3316
|
[/^FORBIDDEN/, 403, "FORBIDDEN"],
|
|
3300
|
-
[/^NO_ACTIVE_PROCESS/, 404, "NO_ACTIVE_PROCESS"],
|
|
3301
|
-
[/^PROCESS_NOT_FOUND/, 404, "PROCESS_NOT_FOUND"],
|
|
3302
3317
|
[/^REQUEST_NOT_FOUND/, 404, "REQUEST_NOT_FOUND"]
|
|
3303
3318
|
];
|
|
3304
3319
|
for (const [re, status, code] of mapping) {
|
|
@@ -3309,127 +3324,6 @@ var RestServer = class {
|
|
|
3309
3324
|
}
|
|
3310
3325
|
return false;
|
|
3311
3326
|
};
|
|
3312
|
-
this.routeManager.register({
|
|
3313
|
-
method: "GET",
|
|
3314
|
-
path: `${dataPath}/approvals/processes`,
|
|
3315
|
-
handler: async (req, res) => {
|
|
3316
|
-
try {
|
|
3317
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3318
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3319
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3320
|
-
const svc = await resolveService(environmentId);
|
|
3321
|
-
if (!svc) return respond501(res);
|
|
3322
|
-
const q = req.query ?? {};
|
|
3323
|
-
const rows = await svc.listProcesses({
|
|
3324
|
-
object: q.object,
|
|
3325
|
-
activeOnly: q.activeOnly === "true" || q.activeOnly === true
|
|
3326
|
-
}, context ?? {});
|
|
3327
|
-
res.json({ data: rows });
|
|
3328
|
-
} catch (error) {
|
|
3329
|
-
logError("[REST] List approval processes error:", error);
|
|
3330
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_LIST_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3331
|
-
}
|
|
3332
|
-
},
|
|
3333
|
-
metadata: { summary: "List approval processes", tags: ["approvals"] }
|
|
3334
|
-
});
|
|
3335
|
-
this.routeManager.register({
|
|
3336
|
-
method: "POST",
|
|
3337
|
-
path: `${dataPath}/approvals/processes`,
|
|
3338
|
-
handler: async (req, res) => {
|
|
3339
|
-
try {
|
|
3340
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3341
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3342
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3343
|
-
const svc = await resolveService(environmentId);
|
|
3344
|
-
if (!svc) return respond501(res);
|
|
3345
|
-
try {
|
|
3346
|
-
const row = await svc.defineProcess(req.body ?? {}, context ?? {});
|
|
3347
|
-
res.status(201).json(row);
|
|
3348
|
-
} catch (err) {
|
|
3349
|
-
if (handleApprovalError(res, err)) return;
|
|
3350
|
-
throw err;
|
|
3351
|
-
}
|
|
3352
|
-
} catch (error) {
|
|
3353
|
-
logError("[REST] Define approval process error:", error);
|
|
3354
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DEFINE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3355
|
-
}
|
|
3356
|
-
},
|
|
3357
|
-
metadata: { summary: "Define (upsert) an approval process", tags: ["approvals"] }
|
|
3358
|
-
});
|
|
3359
|
-
this.routeManager.register({
|
|
3360
|
-
method: "GET",
|
|
3361
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3362
|
-
handler: async (req, res) => {
|
|
3363
|
-
try {
|
|
3364
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3365
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3366
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3367
|
-
const svc = await resolveService(environmentId);
|
|
3368
|
-
if (!svc) return respond501(res);
|
|
3369
|
-
const row = await svc.getProcess(req.params.id, context ?? {});
|
|
3370
|
-
if (!row) {
|
|
3371
|
-
res.status(404).json({ code: "PROCESS_NOT_FOUND", error: `Approval process '${req.params.id}' not found` });
|
|
3372
|
-
return;
|
|
3373
|
-
}
|
|
3374
|
-
res.json(row);
|
|
3375
|
-
} catch (error) {
|
|
3376
|
-
logError("[REST] Get approval process error:", error);
|
|
3377
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_GET_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3378
|
-
}
|
|
3379
|
-
},
|
|
3380
|
-
metadata: { summary: "Get an approval process by id or name", tags: ["approvals"] }
|
|
3381
|
-
});
|
|
3382
|
-
this.routeManager.register({
|
|
3383
|
-
method: "DELETE",
|
|
3384
|
-
path: `${dataPath}/approvals/processes/:id`,
|
|
3385
|
-
handler: async (req, res) => {
|
|
3386
|
-
try {
|
|
3387
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3388
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3389
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3390
|
-
const svc = await resolveService(environmentId);
|
|
3391
|
-
if (!svc) return respond501(res);
|
|
3392
|
-
await svc.deleteProcess(req.params.id, context ?? {});
|
|
3393
|
-
res.status(204).end();
|
|
3394
|
-
} catch (error) {
|
|
3395
|
-
logError("[REST] Delete approval process error:", error);
|
|
3396
|
-
res.status(500).json({ code: "APPROVAL_PROCESS_DELETE_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3397
|
-
}
|
|
3398
|
-
},
|
|
3399
|
-
metadata: { summary: "Delete an approval process", tags: ["approvals"] }
|
|
3400
|
-
});
|
|
3401
|
-
this.routeManager.register({
|
|
3402
|
-
method: "POST",
|
|
3403
|
-
path: `${dataPath}/approvals/requests`,
|
|
3404
|
-
handler: async (req, res) => {
|
|
3405
|
-
try {
|
|
3406
|
-
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3407
|
-
const context = await this.resolveExecCtx(environmentId, req);
|
|
3408
|
-
if (this.enforceAuth(req, res, context)) return;
|
|
3409
|
-
const svc = await resolveService(environmentId);
|
|
3410
|
-
if (!svc) return respond501(res);
|
|
3411
|
-
const body = req.body ?? {};
|
|
3412
|
-
try {
|
|
3413
|
-
const row = await svc.submit({
|
|
3414
|
-
object: body.object,
|
|
3415
|
-
recordId: body.recordId ?? body.record_id,
|
|
3416
|
-
processName: body.processName ?? body.process_name,
|
|
3417
|
-
submitterId: body.submitterId ?? body.submitter_id ?? context?.userId,
|
|
3418
|
-
comment: body.comment,
|
|
3419
|
-
payload: body.payload
|
|
3420
|
-
}, context ?? {});
|
|
3421
|
-
res.status(201).json(row);
|
|
3422
|
-
} catch (err) {
|
|
3423
|
-
if (handleApprovalError(res, err)) return;
|
|
3424
|
-
throw err;
|
|
3425
|
-
}
|
|
3426
|
-
} catch (error) {
|
|
3427
|
-
logError("[REST] Submit approval error:", error);
|
|
3428
|
-
res.status(500).json({ code: "APPROVAL_SUBMIT_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3429
|
-
}
|
|
3430
|
-
},
|
|
3431
|
-
metadata: { summary: "Submit a record for approval", tags: ["approvals"] }
|
|
3432
|
-
});
|
|
3433
3327
|
this.routeManager.register({
|
|
3434
3328
|
method: "GET",
|
|
3435
3329
|
path: `${dataPath}/approvals/requests`,
|
|
@@ -3482,10 +3376,10 @@ var RestServer = class {
|
|
|
3482
3376
|
},
|
|
3483
3377
|
metadata: { summary: "Get an approval request by id", tags: ["approvals"] }
|
|
3484
3378
|
});
|
|
3485
|
-
const decisionRoute = (
|
|
3379
|
+
const decisionRoute = (decision) => {
|
|
3486
3380
|
this.routeManager.register({
|
|
3487
3381
|
method: "POST",
|
|
3488
|
-
path: `${dataPath}/approvals/requests/:id/${
|
|
3382
|
+
path: `${dataPath}/approvals/requests/:id/${decision}`,
|
|
3489
3383
|
handler: async (req, res) => {
|
|
3490
3384
|
try {
|
|
3491
3385
|
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
@@ -3495,7 +3389,8 @@ var RestServer = class {
|
|
|
3495
3389
|
if (!svc) return respond501(res);
|
|
3496
3390
|
const body = req.body ?? {};
|
|
3497
3391
|
try {
|
|
3498
|
-
const out = await svc
|
|
3392
|
+
const out = await svc.decide(req.params.id, {
|
|
3393
|
+
decision,
|
|
3499
3394
|
actorId: body.actorId ?? body.actor_id ?? context?.userId,
|
|
3500
3395
|
comment: body.comment
|
|
3501
3396
|
}, context ?? {});
|
|
@@ -3505,16 +3400,15 @@ var RestServer = class {
|
|
|
3505
3400
|
throw err;
|
|
3506
3401
|
}
|
|
3507
3402
|
} catch (error) {
|
|
3508
|
-
logError(`[REST] ${
|
|
3509
|
-
res.status(500).json({ code: `APPROVAL_${
|
|
3403
|
+
logError(`[REST] ${decision} approval error:`, error);
|
|
3404
|
+
res.status(500).json({ code: `APPROVAL_${decision.toUpperCase()}_FAILED`, error: String(error?.message ?? error).slice(0, 500) });
|
|
3510
3405
|
}
|
|
3511
3406
|
},
|
|
3512
|
-
metadata: { summary: `${
|
|
3407
|
+
metadata: { summary: `${decision[0].toUpperCase()}${decision.slice(1)} an approval request`, tags: ["approvals"] }
|
|
3513
3408
|
});
|
|
3514
3409
|
};
|
|
3515
|
-
decisionRoute("approve"
|
|
3516
|
-
decisionRoute("reject"
|
|
3517
|
-
decisionRoute("recall", "recall");
|
|
3410
|
+
decisionRoute("approve");
|
|
3411
|
+
decisionRoute("reject");
|
|
3518
3412
|
this.routeManager.register({
|
|
3519
3413
|
method: "GET",
|
|
3520
3414
|
path: `${dataPath}/approvals/requests/:id/actions`,
|
|
@@ -3787,6 +3681,66 @@ function registerPackageRoutes(server, packageService, basePath = "/api/v1", opt
|
|
|
3787
3681
|
});
|
|
3788
3682
|
}
|
|
3789
3683
|
|
|
3684
|
+
// src/external-datasource-routes.ts
|
|
3685
|
+
function registerExternalDatasourceRoutes(server, ctx, basePath = "/api/v1") {
|
|
3686
|
+
const ext = `${basePath}/datasources/:name/external`;
|
|
3687
|
+
const externalService = () => {
|
|
3688
|
+
try {
|
|
3689
|
+
return ctx.getService("external-datasource");
|
|
3690
|
+
} catch {
|
|
3691
|
+
return void 0;
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
const unavailable = (res) => res.status(503).json({ error: "external_service_unavailable" });
|
|
3695
|
+
server.get(`${ext}/tables`, async (req, res) => {
|
|
3696
|
+
const svc = externalService();
|
|
3697
|
+
if (!svc?.listRemoteTables) return unavailable(res);
|
|
3698
|
+
const schema = typeof req.query?.schema === "string" ? req.query.schema : void 0;
|
|
3699
|
+
const tables = await svc.listRemoteTables(req.params.name, { schema });
|
|
3700
|
+
res.json({ tables });
|
|
3701
|
+
});
|
|
3702
|
+
server.post(`${ext}/tables/:remote/draft`, async (req, res) => {
|
|
3703
|
+
const svc = externalService();
|
|
3704
|
+
if (!svc?.generateObjectDraft) return unavailable(res);
|
|
3705
|
+
const draft = await svc.generateObjectDraft(
|
|
3706
|
+
req.params.name,
|
|
3707
|
+
req.params.remote,
|
|
3708
|
+
req.body ?? {}
|
|
3709
|
+
);
|
|
3710
|
+
res.json({ draft });
|
|
3711
|
+
});
|
|
3712
|
+
server.post(`${ext}/tables/:remote/import`, async (req, res) => {
|
|
3713
|
+
const svc = externalService();
|
|
3714
|
+
if (!svc?.importObject) return unavailable(res);
|
|
3715
|
+
try {
|
|
3716
|
+
const result = await svc.importObject(
|
|
3717
|
+
req.params.name,
|
|
3718
|
+
req.params.remote,
|
|
3719
|
+
req.body ?? {}
|
|
3720
|
+
);
|
|
3721
|
+
res.status(201).json({ object: result });
|
|
3722
|
+
} catch (err) {
|
|
3723
|
+
res.status(400).json({
|
|
3724
|
+
error: "external_import_error",
|
|
3725
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
server.post(`${ext}/refresh-catalog`, async (req, res) => {
|
|
3730
|
+
const svc = externalService();
|
|
3731
|
+
if (!svc?.refreshCatalog) return unavailable(res);
|
|
3732
|
+
const catalog = await svc.refreshCatalog(req.params.name);
|
|
3733
|
+
res.json({ catalog });
|
|
3734
|
+
});
|
|
3735
|
+
server.post(`${ext}/validate`, async (req, res) => {
|
|
3736
|
+
const svc = externalService();
|
|
3737
|
+
if (!svc?.validateAll) return unavailable(res);
|
|
3738
|
+
const report = await svc.validateAll();
|
|
3739
|
+
const results = (report.results ?? []).filter((r) => r.datasource === req.params.name);
|
|
3740
|
+
res.json({ ok: results.every((r) => r.ok), results });
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3790
3744
|
// src/rest-api-plugin.ts
|
|
3791
3745
|
function createRestApiPlugin(config = {}) {
|
|
3792
3746
|
return {
|
|
@@ -3899,14 +3853,14 @@ function createRestApiPlugin(config = {}) {
|
|
|
3899
3853
|
ctx.logger.error("Failed to register REST API routes", { error: err.message });
|
|
3900
3854
|
throw err;
|
|
3901
3855
|
}
|
|
3856
|
+
const basePath = config.api?.api?.basePath || "/api";
|
|
3857
|
+
const version = config.api?.api?.version || "v1";
|
|
3858
|
+
const versionedBase = `${basePath}/${version}`;
|
|
3859
|
+
const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
|
|
3860
|
+
const projectResolution = config.api?.api?.projectResolution ?? "auto";
|
|
3902
3861
|
try {
|
|
3903
3862
|
const packageService = ctx.getService("package");
|
|
3904
3863
|
if (packageService) {
|
|
3905
|
-
const basePath = config.api?.api?.basePath || "/api";
|
|
3906
|
-
const version = config.api?.api?.version || "v1";
|
|
3907
|
-
const versionedBase = `${basePath}/${version}`;
|
|
3908
|
-
const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
|
|
3909
|
-
const projectResolution = config.api?.api?.projectResolution ?? "auto";
|
|
3910
3864
|
if (enableProjectScoping && projectResolution === "required") {
|
|
3911
3865
|
registerPackageRoutes(server, packageService, `${versionedBase}/environments/:environmentId`, {
|
|
3912
3866
|
protocol
|
|
@@ -3924,6 +3878,12 @@ function createRestApiPlugin(config = {}) {
|
|
|
3924
3878
|
} catch (e) {
|
|
3925
3879
|
ctx.logger.debug("Package service not available, package routes skipped");
|
|
3926
3880
|
}
|
|
3881
|
+
try {
|
|
3882
|
+
registerExternalDatasourceRoutes(server, ctx, versionedBase);
|
|
3883
|
+
ctx.logger.info("Datasource federation routes registered");
|
|
3884
|
+
} catch (e) {
|
|
3885
|
+
ctx.logger.warn("Datasource federation routes registration failed", { error: e?.message });
|
|
3886
|
+
}
|
|
3927
3887
|
}
|
|
3928
3888
|
};
|
|
3929
3889
|
}
|