@objectstack/rest 9.0.0 → 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 CHANGED
@@ -546,37 +546,49 @@ var RestServer = class {
546
546
  this.hostnameCache.set(host, { value: result, expiresAt: now + this.hostnameCacheTtlMs });
547
547
  return result;
548
548
  }
549
- async resolveProtocol(environmentId, req) {
550
- if (environmentId === "platform") return this.protocol;
551
- if (!environmentId && req && this.envRegistry && this.kernelManager) {
549
+ /**
550
+ * Resolve the environment a request targets: explicit id → tenant hostname
551
+ * `X-Environment-Id` header single-project default. Returns undefined
552
+ * for control-plane requests. Shared by every per-environment service
553
+ * resolution (protocol, analytics, …) so they can never disagree about
554
+ * which kernel a request belongs to.
555
+ */
556
+ async resolveRequestEnvironmentId(environmentId, req) {
557
+ if (environmentId) return environmentId;
558
+ if (req && this.envRegistry && this.kernelManager) {
552
559
  const host = this.extractHostname(req);
553
560
  if (host) {
554
561
  try {
555
562
  const result = await this.resolveHostnameCached(host);
556
- if (result?.environmentId) environmentId = result.environmentId;
563
+ if (result?.environmentId) return result.environmentId;
557
564
  } catch {
558
565
  }
559
566
  }
560
- if (!environmentId && typeof this.envRegistry.resolveById === "function") {
567
+ if (typeof this.envRegistry.resolveById === "function") {
561
568
  const headerVal = this.extractProjectIdHeader(req);
562
569
  if (headerVal) {
563
570
  try {
564
571
  const driver = await this.envRegistry.resolveById(headerVal);
565
- if (driver) environmentId = headerVal;
572
+ if (driver) return headerVal;
566
573
  } catch {
567
574
  }
568
575
  }
569
576
  }
570
577
  }
571
- if (!environmentId && this.defaultEnvironmentIdProvider) {
578
+ if (this.defaultEnvironmentIdProvider) {
572
579
  try {
573
580
  const def = this.defaultEnvironmentIdProvider();
574
- if (def) environmentId = def;
581
+ if (def) return def;
575
582
  } catch {
576
583
  }
577
584
  }
578
- if (!environmentId || !this.kernelManager) return this.protocol;
579
- const kernel = await this.kernelManager.getOrCreate(environmentId);
585
+ return void 0;
586
+ }
587
+ async resolveProtocol(environmentId, req) {
588
+ if (environmentId === "platform") return this.protocol;
589
+ const envId = await this.resolveRequestEnvironmentId(environmentId, req);
590
+ if (!envId || !this.kernelManager) return this.protocol;
591
+ const kernel = await this.kernelManager.getOrCreate(envId);
580
592
  return kernel.getServiceAsync("protocol");
581
593
  }
582
594
  /**
@@ -2971,7 +2983,16 @@ var RestServer = class {
2971
2983
  */
2972
2984
  registerAnalyticsEndpoints(basePath) {
2973
2985
  const isScoped = basePath.includes("/environments/:environmentId");
2974
- const resolveService = async (environmentId) => {
2986
+ const resolveService = async (environmentId, req) => {
2987
+ try {
2988
+ const envId = await this.resolveRequestEnvironmentId(environmentId, req);
2989
+ if (envId && envId !== "platform" && this.kernelManager) {
2990
+ const kernel = await this.kernelManager.getOrCreate(envId);
2991
+ const svc = await kernel.getServiceAsync("analytics").catch(() => void 0);
2992
+ if (svc) return svc;
2993
+ }
2994
+ } catch {
2995
+ }
2975
2996
  if (!this.analyticsServiceProvider) return void 0;
2976
2997
  try {
2977
2998
  return await this.analyticsServiceProvider(environmentId);
@@ -2987,7 +3008,7 @@ var RestServer = class {
2987
3008
  const environmentId = isScoped ? req.params?.environmentId : void 0;
2988
3009
  const context = await this.resolveExecCtx(environmentId, req);
2989
3010
  if (this.enforceAuth(req, res, context)) return;
2990
- const svc = await resolveService(environmentId);
3011
+ const svc = await resolveService(environmentId, req);
2991
3012
  if (!svc || typeof svc.queryDataset !== "function") {
2992
3013
  return res.status(501).json({
2993
3014
  code: "NOT_IMPLEMENTED",
@@ -3002,10 +3023,11 @@ var RestServer = class {
3002
3023
  message: "body.selection.measures must be a non-empty array of measure names."
3003
3024
  });
3004
3025
  }
3026
+ const previewDrafts = body.previewDrafts === true || req.query?.preview === "draft";
3005
3027
  let dataset = body.dataset;
3006
3028
  if (!dataset && body.datasetName) {
3007
3029
  const p = await this.resolveProtocol(environmentId, req);
3008
- const items = await p.getMetaItems?.({ type: "dataset" }).catch(() => null);
3030
+ const items = await p.getMetaItems?.({ type: "dataset", previewDrafts }).catch(() => null);
3009
3031
  const list = Array.isArray(items?.items) ? items.items : Array.isArray(items) ? items : [];
3010
3032
  dataset = list.find((d) => d?.name === body.datasetName);
3011
3033
  if (!dataset) {
@@ -3025,7 +3047,12 @@ var RestServer = class {
3025
3047
  detail: String(verr?.message ?? verr).slice(0, 1e3)
3026
3048
  });
3027
3049
  }
3028
- const result = await svc.queryDataset(dataset, selection, context ?? void 0);
3050
+ const result = await svc.queryDataset(
3051
+ dataset,
3052
+ selection,
3053
+ context ?? void 0,
3054
+ previewDrafts ? { previewDrafts: true } : void 0
3055
+ );
3029
3056
  res.json(result);
3030
3057
  } catch (error) {
3031
3058
  const msg = String(error?.message ?? error ?? "");
@@ -3653,6 +3680,34 @@ var RestServer = class {
3653
3680
  };
3654
3681
  decisionRoute("approve");
3655
3682
  decisionRoute("reject");
3683
+ this.routeManager.register({
3684
+ method: "POST",
3685
+ path: `${dataPath}/approvals/requests/:id/recall`,
3686
+ handler: async (req, res) => {
3687
+ try {
3688
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3689
+ const context = await this.resolveExecCtx(environmentId, req);
3690
+ if (this.enforceAuth(req, res, context)) return;
3691
+ const svc = await resolveService(environmentId);
3692
+ if (!svc || typeof svc.recall !== "function") return respond501(res);
3693
+ const body = req.body ?? {};
3694
+ try {
3695
+ const out = await svc.recall(req.params.id, {
3696
+ actorId: body.actorId ?? body.actor_id ?? context?.userId,
3697
+ comment: body.comment
3698
+ }, context ?? {});
3699
+ res.json(out);
3700
+ } catch (err) {
3701
+ if (handleApprovalError(res, err)) return;
3702
+ throw err;
3703
+ }
3704
+ } catch (error) {
3705
+ logError("[REST] recall approval error:", error);
3706
+ res.status(500).json({ code: "APPROVAL_RECALL_FAILED", error: String(error?.message ?? error).slice(0, 500) });
3707
+ }
3708
+ },
3709
+ metadata: { summary: "Recall (withdraw) an approval request", tags: ["approvals"] }
3710
+ });
3656
3711
  this.routeManager.register({
3657
3712
  method: "GET",
3658
3713
  path: `${dataPath}/approvals/requests/:id/actions`,