@objectstack/rest 9.0.0 → 9.1.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.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
@@ -506,37 +506,49 @@ var RestServer = class {
506
506
  this.hostnameCache.set(host, { value: result, expiresAt: now + this.hostnameCacheTtlMs });
507
507
  return result;
508
508
  }
509
- async resolveProtocol(environmentId, req) {
510
- if (environmentId === "platform") return this.protocol;
511
- if (!environmentId && req && this.envRegistry && this.kernelManager) {
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) {
512
519
  const host = this.extractHostname(req);
513
520
  if (host) {
514
521
  try {
515
522
  const result = await this.resolveHostnameCached(host);
516
- if (result?.environmentId) environmentId = result.environmentId;
523
+ if (result?.environmentId) return result.environmentId;
517
524
  } catch {
518
525
  }
519
526
  }
520
- if (!environmentId && typeof this.envRegistry.resolveById === "function") {
527
+ if (typeof this.envRegistry.resolveById === "function") {
521
528
  const headerVal = this.extractProjectIdHeader(req);
522
529
  if (headerVal) {
523
530
  try {
524
531
  const driver = await this.envRegistry.resolveById(headerVal);
525
- if (driver) environmentId = headerVal;
532
+ if (driver) return headerVal;
526
533
  } catch {
527
534
  }
528
535
  }
529
536
  }
530
537
  }
531
- if (!environmentId && this.defaultEnvironmentIdProvider) {
538
+ if (this.defaultEnvironmentIdProvider) {
532
539
  try {
533
540
  const def = this.defaultEnvironmentIdProvider();
534
- if (def) environmentId = def;
541
+ if (def) return def;
535
542
  } catch {
536
543
  }
537
544
  }
538
- if (!environmentId || !this.kernelManager) return this.protocol;
539
- const kernel = await this.kernelManager.getOrCreate(environmentId);
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);
540
552
  return kernel.getServiceAsync("protocol");
541
553
  }
542
554
  /**
@@ -2931,7 +2943,16 @@ var RestServer = class {
2931
2943
  */
2932
2944
  registerAnalyticsEndpoints(basePath) {
2933
2945
  const isScoped = basePath.includes("/environments/:environmentId");
2934
- 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
+ }
2935
2956
  if (!this.analyticsServiceProvider) return void 0;
2936
2957
  try {
2937
2958
  return await this.analyticsServiceProvider(environmentId);
@@ -2947,7 +2968,7 @@ var RestServer = class {
2947
2968
  const environmentId = isScoped ? req.params?.environmentId : void 0;
2948
2969
  const context = await this.resolveExecCtx(environmentId, req);
2949
2970
  if (this.enforceAuth(req, res, context)) return;
2950
- const svc = await resolveService(environmentId);
2971
+ const svc = await resolveService(environmentId, req);
2951
2972
  if (!svc || typeof svc.queryDataset !== "function") {
2952
2973
  return res.status(501).json({
2953
2974
  code: "NOT_IMPLEMENTED",
@@ -2962,10 +2983,11 @@ var RestServer = class {
2962
2983
  message: "body.selection.measures must be a non-empty array of measure names."
2963
2984
  });
2964
2985
  }
2986
+ const previewDrafts = body.previewDrafts === true || req.query?.preview === "draft";
2965
2987
  let dataset = body.dataset;
2966
2988
  if (!dataset && body.datasetName) {
2967
2989
  const p = await this.resolveProtocol(environmentId, req);
2968
- const items = await p.getMetaItems?.({ type: "dataset" }).catch(() => null);
2990
+ const items = await p.getMetaItems?.({ type: "dataset", previewDrafts }).catch(() => null);
2969
2991
  const list = Array.isArray(items?.items) ? items.items : Array.isArray(items) ? items : [];
2970
2992
  dataset = list.find((d) => d?.name === body.datasetName);
2971
2993
  if (!dataset) {
@@ -2985,7 +3007,12 @@ var RestServer = class {
2985
3007
  detail: String(verr?.message ?? verr).slice(0, 1e3)
2986
3008
  });
2987
3009
  }
2988
- const result = await svc.queryDataset(dataset, selection, context ?? void 0);
3010
+ const result = await svc.queryDataset(
3011
+ dataset,
3012
+ selection,
3013
+ context ?? void 0,
3014
+ previewDrafts ? { previewDrafts: true } : void 0
3015
+ );
2989
3016
  res.json(result);
2990
3017
  } catch (error) {
2991
3018
  const msg = String(error?.message ?? error ?? "");
@@ -3613,6 +3640,34 @@ var RestServer = class {
3613
3640
  };
3614
3641
  decisionRoute("approve");
3615
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
+ });
3616
3671
  this.routeManager.register({
3617
3672
  method: "GET",
3618
3673
  path: `${dataPath}/approvals/requests/:id/actions`,