@objectstack/rest 8.0.1 → 9.0.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 +81 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +81 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -268,6 +268,14 @@ declare class RestServer {
|
|
|
268
268
|
* next request.
|
|
269
269
|
*/
|
|
270
270
|
private resolveHostnameCached;
|
|
271
|
+
/**
|
|
272
|
+
* Resolve the environment a request targets: explicit id → tenant hostname
|
|
273
|
+
* → `X-Environment-Id` header → single-project default. Returns undefined
|
|
274
|
+
* for control-plane requests. Shared by every per-environment service
|
|
275
|
+
* resolution (protocol, analytics, …) so they can never disagree about
|
|
276
|
+
* which kernel a request belongs to.
|
|
277
|
+
*/
|
|
278
|
+
private resolveRequestEnvironmentId;
|
|
271
279
|
private resolveProtocol;
|
|
272
280
|
/**
|
|
273
281
|
* Resolve the i18n service for the request's project (or control plane
|
package/dist/index.d.ts
CHANGED
|
@@ -268,6 +268,14 @@ declare class RestServer {
|
|
|
268
268
|
* next request.
|
|
269
269
|
*/
|
|
270
270
|
private resolveHostnameCached;
|
|
271
|
+
/**
|
|
272
|
+
* Resolve the environment a request targets: explicit id → tenant hostname
|
|
273
|
+
* → `X-Environment-Id` header → single-project default. Returns undefined
|
|
274
|
+
* for control-plane requests. Shared by every per-environment service
|
|
275
|
+
* resolution (protocol, analytics, …) so they can never disagree about
|
|
276
|
+
* which kernel a request belongs to.
|
|
277
|
+
*/
|
|
278
|
+
private resolveRequestEnvironmentId;
|
|
271
279
|
private resolveProtocol;
|
|
272
280
|
/**
|
|
273
281
|
* Resolve the i18n service for the request's project (or control plane
|
package/dist/index.js
CHANGED
|
@@ -214,6 +214,9 @@ var RouteGroupBuilder = class {
|
|
|
214
214
|
// src/rest-server.ts
|
|
215
215
|
var logError = (...args) => globalThis.console?.error(...args);
|
|
216
216
|
var TRANSLATABLE_META_TYPES = /* @__PURE__ */ new Set(["view", "action", "object", "app", "dashboard"]);
|
|
217
|
+
function isMetaEnvelope(value) {
|
|
218
|
+
return !!value && typeof value === "object" && typeof value.type === "string" && typeof value.name === "string" && value.item != null && typeof value.item === "object" && !Array.isArray(value.item);
|
|
219
|
+
}
|
|
217
220
|
function mapDataError(error, object) {
|
|
218
221
|
if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
|
|
219
222
|
return {
|
|
@@ -503,37 +506,49 @@ var RestServer = class {
|
|
|
503
506
|
this.hostnameCache.set(host, { value: result, expiresAt: now + this.hostnameCacheTtlMs });
|
|
504
507
|
return result;
|
|
505
508
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Resolve the environment a request targets: explicit id → tenant hostname
|
|
511
|
+
* → `X-Environment-Id` header → single-project default. Returns undefined
|
|
512
|
+
* for control-plane requests. Shared by every per-environment service
|
|
513
|
+
* resolution (protocol, analytics, …) so they can never disagree about
|
|
514
|
+
* which kernel a request belongs to.
|
|
515
|
+
*/
|
|
516
|
+
async resolveRequestEnvironmentId(environmentId, req) {
|
|
517
|
+
if (environmentId) return environmentId;
|
|
518
|
+
if (req && this.envRegistry && this.kernelManager) {
|
|
509
519
|
const host = this.extractHostname(req);
|
|
510
520
|
if (host) {
|
|
511
521
|
try {
|
|
512
522
|
const result = await this.resolveHostnameCached(host);
|
|
513
|
-
if (result?.environmentId)
|
|
523
|
+
if (result?.environmentId) return result.environmentId;
|
|
514
524
|
} catch {
|
|
515
525
|
}
|
|
516
526
|
}
|
|
517
|
-
if (
|
|
527
|
+
if (typeof this.envRegistry.resolveById === "function") {
|
|
518
528
|
const headerVal = this.extractProjectIdHeader(req);
|
|
519
529
|
if (headerVal) {
|
|
520
530
|
try {
|
|
521
531
|
const driver = await this.envRegistry.resolveById(headerVal);
|
|
522
|
-
if (driver)
|
|
532
|
+
if (driver) return headerVal;
|
|
523
533
|
} catch {
|
|
524
534
|
}
|
|
525
535
|
}
|
|
526
536
|
}
|
|
527
537
|
}
|
|
528
|
-
if (
|
|
538
|
+
if (this.defaultEnvironmentIdProvider) {
|
|
529
539
|
try {
|
|
530
540
|
const def = this.defaultEnvironmentIdProvider();
|
|
531
|
-
if (def)
|
|
541
|
+
if (def) return def;
|
|
532
542
|
} catch {
|
|
533
543
|
}
|
|
534
544
|
}
|
|
535
|
-
|
|
536
|
-
|
|
545
|
+
return void 0;
|
|
546
|
+
}
|
|
547
|
+
async resolveProtocol(environmentId, req) {
|
|
548
|
+
if (environmentId === "platform") return this.protocol;
|
|
549
|
+
const envId = await this.resolveRequestEnvironmentId(environmentId, req);
|
|
550
|
+
if (!envId || !this.kernelManager) return this.protocol;
|
|
551
|
+
const kernel = await this.kernelManager.getOrCreate(envId);
|
|
537
552
|
return kernel.getServiceAsync("protocol");
|
|
538
553
|
}
|
|
539
554
|
/**
|
|
@@ -897,21 +912,28 @@ var RestServer = class {
|
|
|
897
912
|
const locale = this.extractLocale(req, i18n);
|
|
898
913
|
if (!locale) return item;
|
|
899
914
|
const { translateMetadataDocument } = await import("@objectstack/spec/system");
|
|
915
|
+
if (isMetaEnvelope(item)) {
|
|
916
|
+
return { ...item, item: translateMetadataDocument(type, item.item, bundle, { locale }) };
|
|
917
|
+
}
|
|
900
918
|
return translateMetadataDocument(type, item, bundle, { locale });
|
|
901
919
|
}
|
|
902
920
|
/**
|
|
903
921
|
* Translate a list of metadata documents using `translateMetaItem`.
|
|
904
922
|
*/
|
|
905
923
|
async translateMetaItems(req, type, environmentId, items) {
|
|
906
|
-
if (!Array.isArray(items)) return items;
|
|
907
924
|
if (!TRANSLATABLE_META_TYPES.has(type)) return items;
|
|
925
|
+
const arr = Array.isArray(items) ? items : items && typeof items === "object" && Array.isArray(items.items) ? items.items : null;
|
|
926
|
+
if (!arr) return items;
|
|
908
927
|
const i18n = await this.resolveI18nService(environmentId, req);
|
|
909
928
|
const bundle = this.buildTranslationBundle(i18n);
|
|
910
929
|
if (!bundle) return items;
|
|
911
930
|
const locale = this.extractLocale(req, i18n);
|
|
912
931
|
if (!locale) return items;
|
|
913
932
|
const { translateMetadataDocument } = await import("@objectstack/spec/system");
|
|
914
|
-
|
|
933
|
+
const translated = arr.map(
|
|
934
|
+
(item) => isMetaEnvelope(item) ? { ...item, item: translateMetadataDocument(type, item.item, bundle, { locale }) } : translateMetadataDocument(type, item, bundle, { locale })
|
|
935
|
+
);
|
|
936
|
+
return Array.isArray(items) ? translated : { ...items, items: translated };
|
|
915
937
|
}
|
|
916
938
|
/**
|
|
917
939
|
* Translate the `entries` payload returned by `getMetaTypes()` — applies
|
|
@@ -2921,7 +2943,16 @@ var RestServer = class {
|
|
|
2921
2943
|
*/
|
|
2922
2944
|
registerAnalyticsEndpoints(basePath) {
|
|
2923
2945
|
const isScoped = basePath.includes("/environments/:environmentId");
|
|
2924
|
-
const resolveService = async (environmentId) => {
|
|
2946
|
+
const resolveService = async (environmentId, req) => {
|
|
2947
|
+
try {
|
|
2948
|
+
const envId = await this.resolveRequestEnvironmentId(environmentId, req);
|
|
2949
|
+
if (envId && envId !== "platform" && this.kernelManager) {
|
|
2950
|
+
const kernel = await this.kernelManager.getOrCreate(envId);
|
|
2951
|
+
const svc = await kernel.getServiceAsync("analytics").catch(() => void 0);
|
|
2952
|
+
if (svc) return svc;
|
|
2953
|
+
}
|
|
2954
|
+
} catch {
|
|
2955
|
+
}
|
|
2925
2956
|
if (!this.analyticsServiceProvider) return void 0;
|
|
2926
2957
|
try {
|
|
2927
2958
|
return await this.analyticsServiceProvider(environmentId);
|
|
@@ -2937,7 +2968,7 @@ var RestServer = class {
|
|
|
2937
2968
|
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
2938
2969
|
const context = await this.resolveExecCtx(environmentId, req);
|
|
2939
2970
|
if (this.enforceAuth(req, res, context)) return;
|
|
2940
|
-
const svc = await resolveService(environmentId);
|
|
2971
|
+
const svc = await resolveService(environmentId, req);
|
|
2941
2972
|
if (!svc || typeof svc.queryDataset !== "function") {
|
|
2942
2973
|
return res.status(501).json({
|
|
2943
2974
|
code: "NOT_IMPLEMENTED",
|
|
@@ -2952,10 +2983,11 @@ var RestServer = class {
|
|
|
2952
2983
|
message: "body.selection.measures must be a non-empty array of measure names."
|
|
2953
2984
|
});
|
|
2954
2985
|
}
|
|
2986
|
+
const previewDrafts = body.previewDrafts === true || req.query?.preview === "draft";
|
|
2955
2987
|
let dataset = body.dataset;
|
|
2956
2988
|
if (!dataset && body.datasetName) {
|
|
2957
2989
|
const p = await this.resolveProtocol(environmentId, req);
|
|
2958
|
-
const items = await p.getMetaItems?.({ type: "dataset" }).catch(() => null);
|
|
2990
|
+
const items = await p.getMetaItems?.({ type: "dataset", previewDrafts }).catch(() => null);
|
|
2959
2991
|
const list = Array.isArray(items?.items) ? items.items : Array.isArray(items) ? items : [];
|
|
2960
2992
|
dataset = list.find((d) => d?.name === body.datasetName);
|
|
2961
2993
|
if (!dataset) {
|
|
@@ -2975,7 +3007,12 @@ var RestServer = class {
|
|
|
2975
3007
|
detail: String(verr?.message ?? verr).slice(0, 1e3)
|
|
2976
3008
|
});
|
|
2977
3009
|
}
|
|
2978
|
-
const result = await svc.queryDataset(
|
|
3010
|
+
const result = await svc.queryDataset(
|
|
3011
|
+
dataset,
|
|
3012
|
+
selection,
|
|
3013
|
+
context ?? void 0,
|
|
3014
|
+
previewDrafts ? { previewDrafts: true } : void 0
|
|
3015
|
+
);
|
|
2979
3016
|
res.json(result);
|
|
2980
3017
|
} catch (error) {
|
|
2981
3018
|
const msg = String(error?.message ?? error ?? "");
|
|
@@ -3603,6 +3640,34 @@ var RestServer = class {
|
|
|
3603
3640
|
};
|
|
3604
3641
|
decisionRoute("approve");
|
|
3605
3642
|
decisionRoute("reject");
|
|
3643
|
+
this.routeManager.register({
|
|
3644
|
+
method: "POST",
|
|
3645
|
+
path: `${dataPath}/approvals/requests/:id/recall`,
|
|
3646
|
+
handler: async (req, res) => {
|
|
3647
|
+
try {
|
|
3648
|
+
const environmentId = isScoped ? req.params?.environmentId : void 0;
|
|
3649
|
+
const context = await this.resolveExecCtx(environmentId, req);
|
|
3650
|
+
if (this.enforceAuth(req, res, context)) return;
|
|
3651
|
+
const svc = await resolveService(environmentId);
|
|
3652
|
+
if (!svc || typeof svc.recall !== "function") return respond501(res);
|
|
3653
|
+
const body = req.body ?? {};
|
|
3654
|
+
try {
|
|
3655
|
+
const out = await svc.recall(req.params.id, {
|
|
3656
|
+
actorId: body.actorId ?? body.actor_id ?? context?.userId,
|
|
3657
|
+
comment: body.comment
|
|
3658
|
+
}, context ?? {});
|
|
3659
|
+
res.json(out);
|
|
3660
|
+
} catch (err) {
|
|
3661
|
+
if (handleApprovalError(res, err)) return;
|
|
3662
|
+
throw err;
|
|
3663
|
+
}
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
logError("[REST] recall approval error:", error);
|
|
3666
|
+
res.status(500).json({ code: "APPROVAL_RECALL_FAILED", error: String(error?.message ?? error).slice(0, 500) });
|
|
3667
|
+
}
|
|
3668
|
+
},
|
|
3669
|
+
metadata: { summary: "Recall (withdraw) an approval request", tags: ["approvals"] }
|
|
3670
|
+
});
|
|
3606
3671
|
this.routeManager.register({
|
|
3607
3672
|
method: "GET",
|
|
3608
3673
|
path: `${dataPath}/approvals/requests/:id/actions`,
|