@objectstack/rest 5.1.0 → 6.0.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 CHANGED
@@ -248,6 +248,7 @@ var RouteGroupBuilder = class {
248
248
  };
249
249
 
250
250
  // src/rest-server.ts
251
+ var import_meta = {};
251
252
  var logError = (...args) => globalThis.console?.error(...args);
252
253
  function mapDataError(error, object) {
253
254
  if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
@@ -285,7 +286,7 @@ function mapDataError(error, object) {
285
286
  }
286
287
  const raw = String(error?.message ?? error ?? "");
287
288
  const lower = raw.toLowerCase();
288
- if (raw.includes("[ProjectKernelFactory]") && (lower.includes("missing database_url") || lower.includes("not found"))) {
289
+ if (raw.includes("[EnvironmentKernelFactory]") && (lower.includes("missing database_url") || lower.includes("not found"))) {
289
290
  const isProvisioning = lower.includes("status='provisioning'") || lower.includes("status='pending'");
290
291
  const isFailed = lower.includes("status='failed'");
291
292
  return {
@@ -442,13 +443,19 @@ function rowsToCsv(fields, rows, includeHeader) {
442
443
  return lines.join("\r\n") + (lines.length > 0 ? "\r\n" : "");
443
444
  }
444
445
  var RestServer = class {
445
- constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultProjectIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider) {
446
+ constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider) {
447
+ /**
448
+ * Lazily load the OpenAPI spec JSON shipped by @objectstack/spec.
449
+ * Cached after first read. Resilient to missing files / parse errors
450
+ * so a degraded environment still boots.
451
+ */
452
+ this._openApiSpecCache = void 0;
446
453
  this.protocol = protocol;
447
454
  this.config = this.normalizeConfig(config);
448
455
  this.routeManager = new RouteManager(server);
449
456
  this.kernelManager = kernelManager;
450
457
  this.envRegistry = envRegistry;
451
- this.defaultProjectIdProvider = defaultProjectIdProvider;
458
+ this.defaultEnvironmentIdProvider = defaultEnvironmentIdProvider;
452
459
  this.authServiceProvider = authServiceProvider;
453
460
  this.objectQLProvider = objectQLProvider;
454
461
  this.emailServiceProvider = emailServiceProvider;
@@ -459,20 +466,20 @@ var RestServer = class {
459
466
  this.i18nServiceProvider = i18nServiceProvider;
460
467
  }
461
468
  /**
462
- * Resolve the protocol for a given request. When `projectId` is present
469
+ * Resolve the protocol for a given request. When `environmentId` is present
463
470
  * and a KernelManager is wired, fetch the per-project kernel's
464
471
  * `protocol` service so metadata / data / UI reads hit the project's
465
472
  * own registry and datastore.
466
473
  *
467
- * When `projectId` is absent on an unscoped route and an `envRegistry`
474
+ * When `environmentId` is absent on an unscoped route and an `envRegistry`
468
475
  * is wired (runtime mode), the resolution chain is:
469
- * 1. Hostname → projectId (`envRegistry.resolveByHostname`)
470
- * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)
471
- * 3. Default-project fallback (`defaultProjectIdProvider`, set by
472
- * `createSingleProjectPlugin`)
476
+ * 1. Hostname → environmentId (`envRegistry.resolveByHostname`)
477
+ * 2. `X-Environment-Id` header → environmentId (`envRegistry.resolveById`)
478
+ * 3. Default-project fallback (`defaultEnvironmentIdProvider`, set by
479
+ * `createSingleEnvironmentPlugin`)
473
480
  * 4. Control-plane protocol captured at boot.
474
481
  *
475
- * Special case: `projectId === 'platform'` is a reserved virtual id used
482
+ * Special case: `environmentId === 'platform'` is a reserved virtual id used
476
483
  * by Studio to address the control plane through the regular project
477
484
  * URL shape (`/projects/platform/...`). It is NOT a row in the projects
478
485
  * table, so we must never call `KernelManager.getOrCreate('platform')`.
@@ -480,37 +487,37 @@ var RestServer = class {
480
487
  * (and any other client) speak a single, uniform URL family without
481
488
  * duplicating route logic for the platform surface.
482
489
  */
483
- async resolveProtocol(projectId, req) {
484
- if (projectId === "platform") return this.protocol;
485
- if (!projectId && req && this.envRegistry && this.kernelManager) {
490
+ async resolveProtocol(environmentId, req) {
491
+ if (environmentId === "platform") return this.protocol;
492
+ if (!environmentId && req && this.envRegistry && this.kernelManager) {
486
493
  const host = this.extractHostname(req);
487
494
  if (host) {
488
495
  try {
489
496
  const result = await this.envRegistry.resolveByHostname(host);
490
- if (result?.projectId) projectId = result.projectId;
497
+ if (result?.environmentId) environmentId = result.environmentId;
491
498
  } catch {
492
499
  }
493
500
  }
494
- if (!projectId && typeof this.envRegistry.resolveById === "function") {
501
+ if (!environmentId && typeof this.envRegistry.resolveById === "function") {
495
502
  const headerVal = this.extractProjectIdHeader(req);
496
503
  if (headerVal) {
497
504
  try {
498
505
  const driver = await this.envRegistry.resolveById(headerVal);
499
- if (driver) projectId = headerVal;
506
+ if (driver) environmentId = headerVal;
500
507
  } catch {
501
508
  }
502
509
  }
503
510
  }
504
511
  }
505
- if (!projectId && this.defaultProjectIdProvider) {
512
+ if (!environmentId && this.defaultEnvironmentIdProvider) {
506
513
  try {
507
- const def = this.defaultProjectIdProvider();
508
- if (def) projectId = def;
514
+ const def = this.defaultEnvironmentIdProvider();
515
+ if (def) environmentId = def;
509
516
  } catch {
510
517
  }
511
518
  }
512
- if (!projectId || !this.kernelManager) return this.protocol;
513
- const kernel = await this.kernelManager.getOrCreate(projectId);
519
+ if (!environmentId || !this.kernelManager) return this.protocol;
520
+ const kernel = await this.kernelManager.getOrCreate(environmentId);
514
521
  return kernel.getServiceAsync("protocol");
515
522
  }
516
523
  /**
@@ -519,43 +526,43 @@ var RestServer = class {
519
526
  * registered, so callers can short-circuit and skip translation rather
520
527
  * than failing.
521
528
  *
522
- * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the
529
+ * Mirrors `resolveProtocol`'s lookup chain: explicit `environmentId` from the
523
530
  * route → kernel-managed `i18n` service. Control-plane / unscoped
524
531
  * requests intentionally return `undefined` because the platform kernel
525
532
  * does not own per-app translation bundles.
526
533
  */
527
- async resolveI18nService(projectId, req) {
528
- if (projectId === "platform") return void 0;
529
- if (!projectId && req && this.envRegistry && this.kernelManager) {
534
+ async resolveI18nService(environmentId, req) {
535
+ if (environmentId === "platform") return void 0;
536
+ if (!environmentId && req && this.envRegistry && this.kernelManager) {
530
537
  const host = this.extractHostname(req);
531
538
  if (host) {
532
539
  try {
533
540
  const result = await this.envRegistry.resolveByHostname(host);
534
- if (result?.projectId) projectId = result.projectId;
541
+ if (result?.environmentId) environmentId = result.environmentId;
535
542
  } catch {
536
543
  }
537
544
  }
538
- if (!projectId && typeof this.envRegistry.resolveById === "function") {
545
+ if (!environmentId && typeof this.envRegistry.resolveById === "function") {
539
546
  const headerVal = this.extractProjectIdHeader(req);
540
547
  if (headerVal) {
541
548
  try {
542
549
  const driver = await this.envRegistry.resolveById(headerVal);
543
- if (driver) projectId = headerVal;
550
+ if (driver) environmentId = headerVal;
544
551
  } catch {
545
552
  }
546
553
  }
547
554
  }
548
555
  }
549
- if (!projectId && this.defaultProjectIdProvider) {
556
+ if (!environmentId && this.defaultEnvironmentIdProvider) {
550
557
  try {
551
- const def = this.defaultProjectIdProvider();
552
- if (def) projectId = def;
558
+ const def = this.defaultEnvironmentIdProvider();
559
+ if (def) environmentId = def;
553
560
  } catch {
554
561
  }
555
562
  }
556
- if (projectId && this.kernelManager) {
563
+ if (environmentId && this.kernelManager) {
557
564
  try {
558
- const kernel = await this.kernelManager.getOrCreate(projectId);
565
+ const kernel = await this.kernelManager.getOrCreate(environmentId);
559
566
  const svc = await kernel.getServiceAsync("i18n");
560
567
  if (svc) return svc;
561
568
  } catch {
@@ -563,7 +570,7 @@ var RestServer = class {
563
570
  }
564
571
  if (this.i18nServiceProvider) {
565
572
  try {
566
- return await this.i18nServiceProvider(projectId);
573
+ return await this.i18nServiceProvider(environmentId);
567
574
  } catch {
568
575
  return void 0;
569
576
  }
@@ -595,23 +602,23 @@ var RestServer = class {
595
602
  * `undefined` for anonymous requests so callers can pass `context` as-is
596
603
  * to the protocol layer (the SecurityPlugin treats undefined as anon).
597
604
  */
598
- async resolveExecCtx(projectId, req) {
605
+ async resolveExecCtx(environmentId, req) {
599
606
  try {
600
- if (!projectId && req && this.envRegistry && this.kernelManager) {
607
+ if (!environmentId && req && this.envRegistry && this.kernelManager) {
601
608
  const host = this.extractHostname(req);
602
609
  if (host) {
603
610
  try {
604
611
  const result = await this.envRegistry.resolveByHostname(host);
605
- if (result?.projectId) projectId = result.projectId;
612
+ if (result?.environmentId) environmentId = result.environmentId;
606
613
  } catch {
607
614
  }
608
615
  }
609
- if (!projectId && typeof this.envRegistry.resolveById === "function") {
616
+ if (!environmentId && typeof this.envRegistry.resolveById === "function") {
610
617
  const headerVal = this.extractProjectIdHeader(req);
611
618
  if (headerVal) {
612
619
  try {
613
620
  const driver = await this.envRegistry.resolveById(headerVal);
614
- if (driver) projectId = headerVal;
621
+ if (driver) environmentId = headerVal;
615
622
  } catch {
616
623
  }
617
624
  }
@@ -619,13 +626,13 @@ var RestServer = class {
619
626
  }
620
627
  let authService;
621
628
  let kernel;
622
- if (projectId && projectId !== "platform" && this.kernelManager) {
623
- kernel = await this.kernelManager.getOrCreate(projectId);
629
+ if (environmentId && environmentId !== "platform" && this.kernelManager) {
630
+ kernel = await this.kernelManager.getOrCreate(environmentId);
624
631
  authService = await kernel.getServiceAsync("auth").catch(() => void 0);
625
632
  }
626
- if (!authService && this.defaultProjectIdProvider && this.kernelManager) {
633
+ if (!authService && this.defaultEnvironmentIdProvider && this.kernelManager) {
627
634
  try {
628
- const def = this.defaultProjectIdProvider();
635
+ const def = this.defaultEnvironmentIdProvider();
629
636
  if (def) {
630
637
  kernel = await this.kernelManager.getOrCreate(def);
631
638
  authService = await kernel.getServiceAsync("auth").catch(() => void 0);
@@ -634,7 +641,7 @@ var RestServer = class {
634
641
  }
635
642
  }
636
643
  if (!authService && this.authServiceProvider) {
637
- authService = await this.authServiceProvider(projectId).catch(() => void 0);
644
+ authService = await this.authServiceProvider(environmentId).catch(() => void 0);
638
645
  }
639
646
  if (!authService) return void 0;
640
647
  let api = authService.api;
@@ -667,7 +674,7 @@ var RestServer = class {
667
674
  ql = await kernel.getServiceAsync("objectql").catch(() => void 0);
668
675
  }
669
676
  if (!ql && this.objectQLProvider) {
670
- ql = await this.objectQLProvider(projectId).catch(() => void 0);
677
+ ql = await this.objectQLProvider(environmentId).catch(() => void 0);
671
678
  }
672
679
  if (ql && typeof ql.find === "function") {
673
680
  const sysOpts = { context: { isSystem: true } };
@@ -709,12 +716,40 @@ var RestServer = class {
709
716
  }
710
717
  } catch {
711
718
  }
719
+ let org_user_ids = [userId];
720
+ if (tenantId) {
721
+ try {
722
+ let ql;
723
+ if (kernel) {
724
+ ql = await kernel.getServiceAsync("objectql").catch(() => void 0);
725
+ }
726
+ if (!ql && this.objectQLProvider) {
727
+ ql = await this.objectQLProvider(environmentId).catch(() => void 0);
728
+ }
729
+ if (ql && typeof ql.find === "function") {
730
+ const sysOpts = { context: { isSystem: true } };
731
+ const memberRows = await ql.find("sys_member", {
732
+ where: { organization_id: tenantId },
733
+ limit: 1e3,
734
+ ...sysOpts
735
+ }).catch(() => []);
736
+ const ids = /* @__PURE__ */ new Set([userId]);
737
+ for (const m of memberRows ?? []) {
738
+ const uid = m.user_id ?? m.userId;
739
+ if (typeof uid === "string" && uid.length > 0) ids.add(uid);
740
+ }
741
+ org_user_ids = Array.from(ids);
742
+ }
743
+ } catch {
744
+ }
745
+ }
712
746
  return {
713
747
  userId,
714
748
  tenantId,
715
749
  roles,
716
750
  permissions,
717
- isSystem: false
751
+ isSystem: false,
752
+ org_user_ids
718
753
  };
719
754
  } catch {
720
755
  return void 0;
@@ -768,10 +803,10 @@ var RestServer = class {
768
803
  * locale yields a match. Falls through unchanged for unsupported types
769
804
  * or missing translations.
770
805
  */
771
- async translateMetaItem(req, type, projectId, item) {
806
+ async translateMetaItem(req, type, environmentId, item) {
772
807
  if (!item || typeof item !== "object") return item;
773
808
  if (type !== "view" && type !== "action" && type !== "object") return item;
774
- const i18n = await this.resolveI18nService(projectId, req);
809
+ const i18n = await this.resolveI18nService(environmentId, req);
775
810
  const bundle = this.buildTranslationBundle(i18n);
776
811
  if (!bundle) return item;
777
812
  const locale = this.extractLocale(req, i18n);
@@ -782,10 +817,10 @@ var RestServer = class {
782
817
  /**
783
818
  * Translate a list of metadata documents using `translateMetaItem`.
784
819
  */
785
- async translateMetaItems(req, type, projectId, items) {
820
+ async translateMetaItems(req, type, environmentId, items) {
786
821
  if (!Array.isArray(items)) return items;
787
822
  if (type !== "view" && type !== "action" && type !== "object") return items;
788
- const i18n = await this.resolveI18nService(projectId, req);
823
+ const i18n = await this.resolveI18nService(environmentId, req);
789
824
  const bundle = this.buildTranslationBundle(i18n);
790
825
  if (!bundle) return items;
791
826
  const locale = this.extractLocale(req, i18n);
@@ -819,7 +854,7 @@ var RestServer = class {
819
854
  return String(host).split(":")[0].toLowerCase();
820
855
  }
821
856
  /**
822
- * Pull the `X-Project-Id` header from a Node- or Fetch-style request.
857
+ * Pull the `X-Environment-Id` header from a Node- or Fetch-style request.
823
858
  * Header names are case-insensitive; we probe both casings to cover
824
859
  * adapters that don't normalize headers (e.g. raw Node http).
825
860
  */
@@ -828,9 +863,9 @@ var RestServer = class {
828
863
  if (!headers) return void 0;
829
864
  let val;
830
865
  if (typeof headers.get === "function") {
831
- val = headers.get("x-project-id") ?? headers.get("X-Project-Id");
866
+ val = headers.get("x-environment-id") ?? headers.get("X-Environment-Id");
832
867
  } else {
833
- val = headers["x-project-id"] ?? headers["X-Project-Id"];
868
+ val = headers["x-environment-id"] ?? headers["X-Environment-Id"];
834
869
  }
835
870
  if (Array.isArray(val)) val = val[0];
836
871
  if (typeof val !== "string") return void 0;
@@ -856,6 +891,7 @@ var RestServer = class {
856
891
  enableUi: api.enableUi ?? true,
857
892
  enableBatch: api.enableBatch ?? true,
858
893
  enableDiscovery: api.enableDiscovery ?? true,
894
+ enableOpenApi: api.enableOpenApi ?? true,
859
895
  enableSearch: api.enableSearch ?? true,
860
896
  enableProjectScoping: api.enableProjectScoping ?? false,
861
897
  projectResolution: api.projectResolution ?? "auto",
@@ -914,16 +950,16 @@ var RestServer = class {
914
950
  }
915
951
  /**
916
952
  * Get the project-scoped base path for a given unscoped base.
917
- * Example: `/api/v1` → `/api/v1/projects/:projectId`.
953
+ * Example: `/api/v1` → `/api/v1/environments/:environmentId`.
918
954
  */
919
955
  getScopedBasePath(basePath) {
920
- return `${basePath}/projects/:projectId`;
956
+ return `${basePath}/environments/:environmentId`;
921
957
  }
922
958
  /**
923
959
  * Register all REST API routes
924
960
  *
925
961
  * When `enableProjectScoping` is true, routes are registered under
926
- * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy
962
+ * `/api/v1/environments/:environmentId/...`. The `projectResolution` strategy
927
963
  * controls whether unscoped legacy routes remain available:
928
964
  * - `required` → only scoped routes registered.
929
965
  * - `optional` / `auto` → both scoped and unscoped routes registered.
@@ -935,6 +971,9 @@ var RestServer = class {
935
971
  if (this.config.api.enableDiscovery) {
936
972
  this.registerDiscoveryEndpoints(bp);
937
973
  }
974
+ if (this.config.api.enableOpenApi ?? true) {
975
+ this.registerOpenApiEndpoints(bp);
976
+ }
938
977
  if (this.config.api.enableMetadata) {
939
978
  this.registerMetadataEndpoints(bp);
940
979
  }
@@ -974,12 +1013,12 @@ var RestServer = class {
974
1013
  * Register discovery endpoints
975
1014
  */
976
1015
  registerDiscoveryEndpoints(basePath) {
977
- const isScoped = basePath.includes("/projects/:projectId");
1016
+ const isScoped = basePath.includes("/environments/:environmentId");
978
1017
  const discoveryHandler = async (req, res) => {
979
1018
  try {
980
1019
  const discovery = await this.protocol.getDiscovery();
981
1020
  discovery.version = this.config.api.version;
982
- const realBase = isScoped ? basePath.replace(":projectId", req.params?.projectId ?? ":projectId") : basePath;
1021
+ const realBase = isScoped ? basePath.replace(":environmentId", req.params?.environmentId ?? ":environmentId") : basePath;
983
1022
  if (discovery.routes) {
984
1023
  if (this.config.api.enableCrud) {
985
1024
  discovery.routes.data = `${realBase}${this.config.crud.dataPrefix}`;
@@ -991,7 +1030,7 @@ var RestServer = class {
991
1030
  discovery.routes.ui = `${realBase}/ui`;
992
1031
  }
993
1032
  if (discovery.routes.auth) {
994
- const unscopedBase = isScoped ? basePath.replace(/\/projects\/:projectId$/, "") : basePath;
1033
+ const unscopedBase = isScoped ? basePath.replace(/\/projects\/:environmentId$/, "") : basePath;
995
1034
  discovery.routes.auth = `${unscopedBase}/auth`;
996
1035
  }
997
1036
  }
@@ -999,7 +1038,7 @@ var RestServer = class {
999
1038
  enabled: this.config.api.enableProjectScoping,
1000
1039
  resolution: this.config.api.projectResolution,
1001
1040
  scoped: isScoped,
1002
- projectId: isScoped ? req.params?.projectId : void 0
1041
+ environmentId: isScoped ? req.params?.environmentId : void 0
1003
1042
  };
1004
1043
  res.json(discovery);
1005
1044
  } catch (error) {
@@ -1026,21 +1065,150 @@ var RestServer = class {
1026
1065
  }
1027
1066
  });
1028
1067
  }
1068
+ /**
1069
+ * Register OpenAPI 3.1 spec + interactive docs viewer.
1070
+ *
1071
+ * GET <basePath>/openapi.json → enriched OpenAPI document
1072
+ * GET <basePath>/docs → Scalar-rendered HTML (CDN, no dep)
1073
+ *
1074
+ * Enrichment at request time:
1075
+ * - servers[0].url — derived from the request's Host header
1076
+ * - paths — `{object}` placeholders expanded into
1077
+ * one concrete path per registered object
1078
+ * from the protocol's discovery metadata
1079
+ *
1080
+ * The base spec is loaded lazily from @objectstack/spec/openapi.json
1081
+ * (shipped pre-generated by spec's build pipeline) so we don't pay
1082
+ * the cost of regenerating on every request, and a missing or
1083
+ * malformed file degrades to a stub instead of crashing.
1084
+ */
1085
+ registerOpenApiEndpoints(basePath) {
1086
+ const isScoped = basePath.includes("/environments/:environmentId");
1087
+ const openApiHandler = async (req, res) => {
1088
+ try {
1089
+ const spec = await this.loadOpenApiSpec();
1090
+ if (!spec) {
1091
+ res.status?.(503);
1092
+ res.json({
1093
+ error: "openapi_unavailable",
1094
+ message: "OpenAPI spec is not bundled with this runtime."
1095
+ });
1096
+ return;
1097
+ }
1098
+ const enriched = { ...spec, servers: [...spec.servers ?? []] };
1099
+ const host = req.headers?.host ?? req.headers?.["host"];
1100
+ const proto = req.headers?.["x-forwarded-proto"] || req.protocol || "http";
1101
+ if (host) {
1102
+ enriched.servers = [
1103
+ { url: `${proto}://${host}`, description: "Current server" },
1104
+ ...spec.servers ?? []
1105
+ ];
1106
+ }
1107
+ try {
1108
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1109
+ const protocol = await this.resolveProtocol(environmentId, req);
1110
+ const items = await protocol?.getMetaItems?.({ type: "object" }).catch(() => null);
1111
+ const objects = Array.isArray(items?.items) ? items.items.map((i) => i?.name).filter(Boolean) : Array.isArray(items) ? items.map((i) => i?.name).filter(Boolean) : [];
1112
+ if (objects.length > 0 && enriched.paths) {
1113
+ const expanded = {};
1114
+ for (const [p, def] of Object.entries(enriched.paths)) {
1115
+ if (p.includes("{object}")) {
1116
+ expanded[p] = { ...def, "x-template": true };
1117
+ for (const obj of objects) {
1118
+ expanded[p.replace("{object}", obj)] = def;
1119
+ }
1120
+ } else {
1121
+ expanded[p] = def;
1122
+ }
1123
+ }
1124
+ enriched.paths = expanded;
1125
+ }
1126
+ } catch {
1127
+ }
1128
+ if (enriched.info) {
1129
+ enriched.info = {
1130
+ ...enriched.info,
1131
+ version: this.config.api.version || enriched.info.version
1132
+ };
1133
+ }
1134
+ res.json(enriched);
1135
+ } catch (error) {
1136
+ logError("[REST] openapi.json error:", error);
1137
+ sendError(res, error);
1138
+ }
1139
+ };
1140
+ this.routeManager.register({
1141
+ method: "GET",
1142
+ path: `${basePath}/openapi.json`,
1143
+ handler: openApiHandler,
1144
+ metadata: {
1145
+ summary: "OpenAPI 3.1 specification (machine-readable)",
1146
+ tags: ["openapi"]
1147
+ }
1148
+ });
1149
+ this.routeManager.register({
1150
+ method: "GET",
1151
+ path: `${basePath}/docs`,
1152
+ handler: async (req, res) => {
1153
+ const reqPath = req.path || req.url || `${basePath}/docs`;
1154
+ const apiBase = reqPath.replace(/\/docs\/?$/, "");
1155
+ const specUrl = `${apiBase}/openapi.json`;
1156
+ const html = `<!doctype html>
1157
+ <html>
1158
+ <head>
1159
+ <meta charset="utf-8" />
1160
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1161
+ <title>ObjectStack API Docs</title>
1162
+ </head>
1163
+ <body>
1164
+ <script id="api-reference" data-url="${specUrl}"></script>
1165
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
1166
+ </body>
1167
+ </html>`;
1168
+ if (res.setHeader) res.setHeader("content-type", "text/html; charset=utf-8");
1169
+ if (res.send) res.send(html);
1170
+ else if (res.body) res.body = html;
1171
+ else res.json?.(html);
1172
+ },
1173
+ metadata: {
1174
+ summary: "Interactive API docs (Scalar viewer)",
1175
+ tags: ["openapi"]
1176
+ }
1177
+ });
1178
+ }
1179
+ async loadOpenApiSpec() {
1180
+ if (this._openApiSpecCache !== void 0) return this._openApiSpecCache;
1181
+ try {
1182
+ const mod = await import("module");
1183
+ const requireFn = mod.createRequire(import_meta.url);
1184
+ const pkgJsonPath = requireFn.resolve("@objectstack/spec/package.json");
1185
+ const pathMod = await import("path");
1186
+ const fsMod = await import("fs");
1187
+ const specPath = pathMod.join(pathMod.dirname(pkgJsonPath), "json-schema", "openapi.json");
1188
+ const raw = await fsMod.promises.readFile(specPath, "utf-8");
1189
+ this._openApiSpecCache = JSON.parse(raw);
1190
+ return this._openApiSpecCache;
1191
+ } catch (err) {
1192
+ logError("[REST] Failed to load OpenAPI spec:", err?.message ?? err);
1193
+ this._openApiSpecCache = null;
1194
+ return null;
1195
+ }
1196
+ }
1029
1197
  /**
1030
1198
  * Register metadata endpoints
1031
1199
  */
1032
1200
  registerMetadataEndpoints(basePath) {
1033
1201
  const { metadata } = this.config;
1034
1202
  const metaPath = `${basePath}${metadata.prefix}`;
1035
- const isScoped = basePath.includes("/projects/:projectId");
1203
+ const isScoped = basePath.includes("/environments/:environmentId");
1036
1204
  if (metadata.endpoints.types !== false) {
1037
1205
  this.routeManager.register({
1038
1206
  method: "GET",
1039
1207
  path: metaPath,
1040
1208
  handler: async (req, res) => {
1041
1209
  try {
1042
- const projectId = isScoped ? req.params?.projectId : void 0;
1043
- const p = await this.resolveProtocol(projectId, req);
1210
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1211
+ const p = await this.resolveProtocol(environmentId, req);
1044
1212
  const types = await p.getMetaTypes();
1045
1213
  res.json(types);
1046
1214
  } catch (error) {
@@ -1061,14 +1229,14 @@ var RestServer = class {
1061
1229
  handler: async (req, res) => {
1062
1230
  try {
1063
1231
  const packageId = req.query?.package || void 0;
1064
- const projectId = isScoped ? req.params?.projectId : void 0;
1065
- const p = await this.resolveProtocol(projectId, req);
1232
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1233
+ const p = await this.resolveProtocol(environmentId, req);
1066
1234
  const items = await p.getMetaItems({
1067
1235
  type: req.params.type,
1068
1236
  packageId,
1069
- ...projectId ? { projectId } : {}
1237
+ ...environmentId ? { environmentId } : {}
1070
1238
  });
1071
- const translated = await this.translateMetaItems(req, req.params.type, projectId, items);
1239
+ const translated = await this.translateMetaItems(req, req.params.type, environmentId, items);
1072
1240
  res.header("Vary", "Accept-Language");
1073
1241
  res.json(translated);
1074
1242
  } catch (error) {
@@ -1088,8 +1256,8 @@ var RestServer = class {
1088
1256
  path: `${metaPath}/:type/:name`,
1089
1257
  handler: async (req, res) => {
1090
1258
  try {
1091
- const projectId = isScoped ? req.params?.projectId : void 0;
1092
- const p = await this.resolveProtocol(projectId, req);
1259
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1260
+ const p = await this.resolveProtocol(environmentId, req);
1093
1261
  if (metadata.enableCache && p.getMetaItemCached) {
1094
1262
  const cacheRequest = {
1095
1263
  ifNoneMatch: req.headers["if-none-match"],
@@ -1099,7 +1267,7 @@ var RestServer = class {
1099
1267
  type: req.params.type,
1100
1268
  name: req.params.name,
1101
1269
  cacheRequest,
1102
- ...projectId ? { projectId } : {}
1270
+ ...environmentId ? { environmentId } : {}
1103
1271
  });
1104
1272
  if (result.notModified) {
1105
1273
  res.status(304).send();
@@ -1118,7 +1286,7 @@ var RestServer = class {
1118
1286
  res.header("Cache-Control", directives + maxAge);
1119
1287
  }
1120
1288
  res.header("Vary", "Accept-Language");
1121
- res.json(await this.translateMetaItem(req, req.params.type, projectId, result.data));
1289
+ res.json(await this.translateMetaItem(req, req.params.type, environmentId, result.data));
1122
1290
  } else {
1123
1291
  const packageId = req.query?.package || void 0;
1124
1292
  const item = await p.getMetaItem({
@@ -1127,7 +1295,7 @@ var RestServer = class {
1127
1295
  packageId
1128
1296
  });
1129
1297
  res.header("Vary", "Accept-Language");
1130
- res.json(await this.translateMetaItem(req, req.params.type, projectId, item));
1298
+ res.json(await this.translateMetaItem(req, req.params.type, environmentId, item));
1131
1299
  }
1132
1300
  } catch (error) {
1133
1301
  logError("[REST] Unhandled error:", error);
@@ -1145,8 +1313,8 @@ var RestServer = class {
1145
1313
  path: `${metaPath}/:type/:name`,
1146
1314
  handler: async (req, res) => {
1147
1315
  try {
1148
- const projectId = isScoped ? req.params?.projectId : void 0;
1149
- const p = await this.resolveProtocol(projectId, req);
1316
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1317
+ const p = await this.resolveProtocol(environmentId, req);
1150
1318
  if (!p.saveMetaItem) {
1151
1319
  res.status(501).json({ error: "Save operation not supported by protocol implementation" });
1152
1320
  return;
@@ -1161,7 +1329,7 @@ var RestServer = class {
1161
1329
  type: req.params.type,
1162
1330
  name: req.params.name,
1163
1331
  item,
1164
- ...projectId ? { projectId } : {},
1332
+ ...environmentId ? { environmentId } : {},
1165
1333
  ...parentVersion !== void 0 ? { parentVersion } : {},
1166
1334
  ...actor ? { actor } : {}
1167
1335
  });
@@ -1181,8 +1349,8 @@ var RestServer = class {
1181
1349
  path: `${metaPath}/:type/:name`,
1182
1350
  handler: async (req, res) => {
1183
1351
  try {
1184
- const projectId = isScoped ? req.params?.projectId : void 0;
1185
- const p = await this.resolveProtocol(projectId, req);
1352
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1353
+ const p = await this.resolveProtocol(environmentId, req);
1186
1354
  if (!p.deleteMetaItem) {
1187
1355
  res.status(501).json({
1188
1356
  error: "Reset operation not supported by protocol implementation"
@@ -1196,7 +1364,7 @@ var RestServer = class {
1196
1364
  const result = await p.deleteMetaItem({
1197
1365
  type: req.params.type,
1198
1366
  name: req.params.name,
1199
- ...projectId ? { projectId } : {},
1367
+ ...environmentId ? { environmentId } : {},
1200
1368
  ...parentVersion !== void 0 ? { parentVersion } : {},
1201
1369
  ...actor ? { actor } : {}
1202
1370
  });
@@ -1216,8 +1384,8 @@ var RestServer = class {
1216
1384
  path: `${metaPath}/:type/:name/history`,
1217
1385
  handler: async (req, res) => {
1218
1386
  try {
1219
- const projectId = isScoped ? req.params?.projectId : void 0;
1220
- const p = await this.resolveProtocol(projectId, req);
1387
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1388
+ const p = await this.resolveProtocol(environmentId, req);
1221
1389
  if (!p.historyMetaItem) {
1222
1390
  res.status(501).json({
1223
1391
  error: "History query not supported by protocol implementation"
@@ -1229,7 +1397,7 @@ var RestServer = class {
1229
1397
  const result = await p.historyMetaItem({
1230
1398
  type: req.params.type,
1231
1399
  name: req.params.name,
1232
- ...projectId ? { projectId } : {},
1400
+ ...environmentId ? { environmentId } : {},
1233
1401
  ...sinceSeq !== void 0 && Number.isFinite(sinceSeq) ? { sinceSeq } : {},
1234
1402
  ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
1235
1403
  });
@@ -1250,8 +1418,8 @@ var RestServer = class {
1250
1418
  path: `${metaPath}/:type/:section/:name`,
1251
1419
  handler: async (req, res) => {
1252
1420
  try {
1253
- const projectId = isScoped ? req.params?.projectId : void 0;
1254
- const p = await this.resolveProtocol(projectId, req);
1421
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1422
+ const p = await this.resolveProtocol(environmentId, req);
1255
1423
  const compoundName = `${req.params.section}/${req.params.name}`;
1256
1424
  const packageId = req.query?.package || void 0;
1257
1425
  const item = await p.getMetaItem({
@@ -1260,7 +1428,7 @@ var RestServer = class {
1260
1428
  packageId
1261
1429
  });
1262
1430
  res.header("Vary", "Accept-Language");
1263
- res.json(await this.translateMetaItem(req, req.params.type, projectId, item));
1431
+ res.json(await this.translateMetaItem(req, req.params.type, environmentId, item));
1264
1432
  } catch (error) {
1265
1433
  logError("[REST] Unhandled error:", error);
1266
1434
  sendError(res, error);
@@ -1277,8 +1445,8 @@ var RestServer = class {
1277
1445
  path: `${metaPath}/:type/:section/:name`,
1278
1446
  handler: async (req, res) => {
1279
1447
  try {
1280
- const projectId = isScoped ? req.params?.projectId : void 0;
1281
- const p = await this.resolveProtocol(projectId, req);
1448
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1449
+ const p = await this.resolveProtocol(environmentId, req);
1282
1450
  if (!p.saveMetaItem) {
1283
1451
  res.status(501).json({ error: "Save operation not supported by protocol implementation" });
1284
1452
  return;
@@ -1292,7 +1460,7 @@ var RestServer = class {
1292
1460
  type: req.params.type,
1293
1461
  name: compoundName,
1294
1462
  item: req.body,
1295
- ...projectId ? { projectId } : {},
1463
+ ...environmentId ? { environmentId } : {},
1296
1464
  ...parentVersion !== void 0 ? { parentVersion } : {},
1297
1465
  ...actor ? { actor } : {}
1298
1466
  });
@@ -1313,19 +1481,19 @@ var RestServer = class {
1313
1481
  */
1314
1482
  registerUiEndpoints(basePath) {
1315
1483
  const uiPath = `${basePath}/ui`;
1316
- const isScoped = basePath.includes("/projects/:projectId");
1484
+ const isScoped = basePath.includes("/environments/:environmentId");
1317
1485
  this.routeManager.register({
1318
1486
  method: "GET",
1319
1487
  path: `${uiPath}/view/:object/:type`,
1320
1488
  handler: async (req, res) => {
1321
1489
  try {
1322
- const projectId = isScoped ? req.params?.projectId : void 0;
1323
- const p = await this.resolveProtocol(projectId, req);
1490
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1491
+ const p = await this.resolveProtocol(environmentId, req);
1324
1492
  if (p.getUiView) {
1325
1493
  const view = await p.getUiView({
1326
1494
  object: req.params.object,
1327
1495
  type: req.params.type,
1328
- ...projectId ? { projectId } : {}
1496
+ ...environmentId ? { environmentId } : {}
1329
1497
  });
1330
1498
  res.json(view);
1331
1499
  } else {
@@ -1348,7 +1516,7 @@ var RestServer = class {
1348
1516
  registerCrudEndpoints(basePath) {
1349
1517
  const { crud } = this.config;
1350
1518
  const dataPath = `${basePath}${crud.dataPrefix}`;
1351
- const isScoped = basePath.includes("/projects/:projectId");
1519
+ const isScoped = basePath.includes("/environments/:environmentId");
1352
1520
  const operations = crud.operations;
1353
1521
  if (operations.list) {
1354
1522
  this.routeManager.register({
@@ -1356,14 +1524,14 @@ var RestServer = class {
1356
1524
  path: `${dataPath}/:object`,
1357
1525
  handler: async (req, res) => {
1358
1526
  try {
1359
- const projectId = isScoped ? req.params?.projectId : void 0;
1360
- const p = await this.resolveProtocol(projectId, req);
1361
- const context = await this.resolveExecCtx(projectId, req);
1527
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1528
+ const p = await this.resolveProtocol(environmentId, req);
1529
+ const context = await this.resolveExecCtx(environmentId, req);
1362
1530
  if (this.enforceAuth(req, res, context)) return;
1363
1531
  const result = await p.findData({
1364
1532
  object: req.params.object,
1365
1533
  query: req.query,
1366
- ...projectId ? { projectId } : {},
1534
+ ...environmentId ? { environmentId } : {},
1367
1535
  ...context ? { context } : {}
1368
1536
  });
1369
1537
  res.json(result);
@@ -1389,17 +1557,17 @@ var RestServer = class {
1389
1557
  path: `${dataPath}/:object/:id`,
1390
1558
  handler: async (req, res) => {
1391
1559
  try {
1392
- const projectId = isScoped ? req.params?.projectId : void 0;
1393
- const p = await this.resolveProtocol(projectId, req);
1560
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1561
+ const p = await this.resolveProtocol(environmentId, req);
1394
1562
  const { select, expand } = req.query || {};
1395
- const context = await this.resolveExecCtx(projectId, req);
1563
+ const context = await this.resolveExecCtx(environmentId, req);
1396
1564
  if (this.enforceAuth(req, res, context)) return;
1397
1565
  const result = await p.getData({
1398
1566
  object: req.params.object,
1399
1567
  id: req.params.id,
1400
1568
  ...select != null ? { select } : {},
1401
1569
  ...expand != null ? { expand } : {},
1402
- ...projectId ? { projectId } : {},
1570
+ ...environmentId ? { environmentId } : {},
1403
1571
  ...context ? { context } : {}
1404
1572
  });
1405
1573
  res.json(result);
@@ -1421,14 +1589,14 @@ var RestServer = class {
1421
1589
  path: `${dataPath}/:object`,
1422
1590
  handler: async (req, res) => {
1423
1591
  try {
1424
- const projectId = isScoped ? req.params?.projectId : void 0;
1425
- const p = await this.resolveProtocol(projectId, req);
1426
- const context = await this.resolveExecCtx(projectId, req);
1592
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1593
+ const p = await this.resolveProtocol(environmentId, req);
1594
+ const context = await this.resolveExecCtx(environmentId, req);
1427
1595
  if (this.enforceAuth(req, res, context)) return;
1428
1596
  const result = await p.createData({
1429
1597
  object: req.params.object,
1430
1598
  data: req.body,
1431
- ...projectId ? { projectId } : {},
1599
+ ...environmentId ? { environmentId } : {},
1432
1600
  ...context ? { context } : {}
1433
1601
  });
1434
1602
  res.status(201).json(result);
@@ -1450,14 +1618,14 @@ var RestServer = class {
1450
1618
  path: `${dataPath}/:object/query`,
1451
1619
  handler: async (req, res) => {
1452
1620
  try {
1453
- const projectId = isScoped ? req.params?.projectId : void 0;
1454
- const p = await this.resolveProtocol(projectId, req);
1455
- const context = await this.resolveExecCtx(projectId, req);
1621
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1622
+ const p = await this.resolveProtocol(environmentId, req);
1623
+ const context = await this.resolveExecCtx(environmentId, req);
1456
1624
  if (this.enforceAuth(req, res, context)) return;
1457
1625
  const result = await p.findData({
1458
1626
  object: req.params.object,
1459
1627
  query: req.body || {},
1460
- ...projectId ? { projectId } : {},
1628
+ ...environmentId ? { environmentId } : {},
1461
1629
  ...context ? { context } : {}
1462
1630
  });
1463
1631
  res.json(result);
@@ -1479,9 +1647,9 @@ var RestServer = class {
1479
1647
  path: `${dataPath}/:object/:id`,
1480
1648
  handler: async (req, res) => {
1481
1649
  try {
1482
- const projectId = isScoped ? req.params?.projectId : void 0;
1483
- const p = await this.resolveProtocol(projectId, req);
1484
- const context = await this.resolveExecCtx(projectId, req);
1650
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1651
+ const p = await this.resolveProtocol(environmentId, req);
1652
+ const context = await this.resolveExecCtx(environmentId, req);
1485
1653
  if (this.enforceAuth(req, res, context)) return;
1486
1654
  const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1487
1655
  const bodyVersion = req.body && typeof req.body === "object" ? req.body.expectedVersion : void 0;
@@ -1496,7 +1664,7 @@ var RestServer = class {
1496
1664
  id: req.params.id,
1497
1665
  data,
1498
1666
  ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1499
- ...projectId ? { projectId } : {},
1667
+ ...environmentId ? { environmentId } : {},
1500
1668
  ...context ? { context } : {}
1501
1669
  });
1502
1670
  res.json(result);
@@ -1518,9 +1686,9 @@ var RestServer = class {
1518
1686
  path: `${dataPath}/:object/:id`,
1519
1687
  handler: async (req, res) => {
1520
1688
  try {
1521
- const projectId = isScoped ? req.params?.projectId : void 0;
1522
- const p = await this.resolveProtocol(projectId, req);
1523
- const context = await this.resolveExecCtx(projectId, req);
1689
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1690
+ const p = await this.resolveProtocol(environmentId, req);
1691
+ const context = await this.resolveExecCtx(environmentId, req);
1524
1692
  if (this.enforceAuth(req, res, context)) return;
1525
1693
  const ifMatchHeader = req.headers?.["if-match"] ?? req.headers?.["If-Match"];
1526
1694
  const queryVersion = req.query && typeof req.query === "object" ? req.query.expectedVersion : void 0;
@@ -1529,7 +1697,7 @@ var RestServer = class {
1529
1697
  object: req.params.object,
1530
1698
  id: req.params.id,
1531
1699
  ...expectedVersion ? { expectedVersion: String(expectedVersion) } : {},
1532
- ...projectId ? { projectId } : {},
1700
+ ...environmentId ? { environmentId } : {},
1533
1701
  ...context ? { context } : {}
1534
1702
  });
1535
1703
  res.json(result);
@@ -1555,7 +1723,7 @@ var RestServer = class {
1555
1723
  * POST {basePath}/data/lead/:id/convert — M10.6 lead conversion.
1556
1724
  */
1557
1725
  registerDataActionEndpoints(basePath) {
1558
- const isScoped = basePath.includes("/projects/:projectId");
1726
+ const isScoped = basePath.includes("/environments/:environmentId");
1559
1727
  const { crud } = this.config;
1560
1728
  const dataPath = `${basePath}${crud.dataPrefix}`;
1561
1729
  this.routeManager.register({
@@ -1563,9 +1731,9 @@ var RestServer = class {
1563
1731
  path: `${dataPath}/lead/:id/convert`,
1564
1732
  handler: async (req, res) => {
1565
1733
  try {
1566
- const projectId = isScoped ? req.params?.projectId : void 0;
1567
- const p = await this.resolveProtocol(projectId, req);
1568
- const context = await this.resolveExecCtx(projectId, req);
1734
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1735
+ const p = await this.resolveProtocol(environmentId, req);
1736
+ const context = await this.resolveExecCtx(environmentId, req);
1569
1737
  if (this.enforceAuth(req, res, context)) return;
1570
1738
  const convertLead = p.convertLead;
1571
1739
  if (typeof convertLead !== "function") {
@@ -1598,9 +1766,9 @@ var RestServer = class {
1598
1766
  path: `${dataPath}/:object/import`,
1599
1767
  handler: async (req, res) => {
1600
1768
  try {
1601
- const projectId = isScoped ? req.params?.projectId : void 0;
1602
- const p = await this.resolveProtocol(projectId, req);
1603
- const context = await this.resolveExecCtx(projectId, req);
1769
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1770
+ const p = await this.resolveProtocol(environmentId, req);
1771
+ const context = await this.resolveExecCtx(environmentId, req);
1604
1772
  if (this.enforceAuth(req, res, context)) return;
1605
1773
  const objectName = String(req.params.object || "");
1606
1774
  if (!objectName) {
@@ -1681,9 +1849,9 @@ var RestServer = class {
1681
1849
  path: `${dataPath}/:object/export`,
1682
1850
  handler: async (req, res) => {
1683
1851
  try {
1684
- const projectId = isScoped ? req.params?.projectId : void 0;
1685
- const p = await this.resolveProtocol(projectId, req);
1686
- const context = await this.resolveExecCtx(projectId, req);
1852
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1853
+ const p = await this.resolveProtocol(environmentId, req);
1854
+ const context = await this.resolveExecCtx(environmentId, req);
1687
1855
  if (this.enforceAuth(req, res, context)) return;
1688
1856
  const objectName = String(req.params.object || "");
1689
1857
  if (!objectName) {
@@ -1732,7 +1900,7 @@ var RestServer = class {
1732
1900
  }
1733
1901
  if (!fields || fields.length === 0) {
1734
1902
  try {
1735
- const schema = await p.getObjectSchema?.(objectName, projectId);
1903
+ const schema = await p.getObjectSchema?.(objectName, environmentId);
1736
1904
  const schemaFields = schema?.fields;
1737
1905
  if (Array.isArray(schemaFields)) {
1738
1906
  fields = schemaFields.map((f) => f.name).filter((n) => typeof n === "string");
@@ -1766,7 +1934,7 @@ var RestServer = class {
1766
1934
  $top: take,
1767
1935
  $skip: skip
1768
1936
  },
1769
- ...projectId ? { projectId } : {},
1937
+ ...environmentId ? { environmentId } : {},
1770
1938
  ...context ? { context } : {}
1771
1939
  };
1772
1940
  const result = await p.findData(findArgs);
@@ -1814,15 +1982,15 @@ var RestServer = class {
1814
1982
  * GET {basePath}/search?q=acme&objects=lead,account&limit=20&perObject=5
1815
1983
  */
1816
1984
  registerSearchEndpoints(basePath) {
1817
- const isScoped = basePath.includes("/projects/:projectId");
1985
+ const isScoped = basePath.includes("/environments/:environmentId");
1818
1986
  this.routeManager.register({
1819
1987
  method: "GET",
1820
1988
  path: `${basePath}/search`,
1821
1989
  handler: async (req, res) => {
1822
1990
  try {
1823
- const projectId = isScoped ? req.params?.projectId : void 0;
1824
- const p = await this.resolveProtocol(projectId, req);
1825
- const context = await this.resolveExecCtx(projectId, req);
1991
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
1992
+ const p = await this.resolveProtocol(environmentId, req);
1993
+ const context = await this.resolveExecCtx(environmentId, req);
1826
1994
  if (this.enforceAuth(req, res, context)) return;
1827
1995
  const searchAll = p.searchAll;
1828
1996
  if (typeof searchAll !== "function") {
@@ -1874,14 +2042,14 @@ var RestServer = class {
1874
2042
  * }
1875
2043
  */
1876
2044
  registerEmailEndpoints(basePath) {
1877
- const isScoped = basePath.includes("/projects/:projectId");
2045
+ const isScoped = basePath.includes("/environments/:environmentId");
1878
2046
  this.routeManager.register({
1879
2047
  method: "POST",
1880
2048
  path: `${basePath}/email/send`,
1881
2049
  handler: async (req, res) => {
1882
2050
  try {
1883
- const projectId = isScoped ? req.params?.projectId : void 0;
1884
- const context = await this.resolveExecCtx(projectId, req);
2051
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2052
+ const context = await this.resolveExecCtx(environmentId, req);
1885
2053
  if (this.enforceAuth(req, res, context)) return;
1886
2054
  if (!this.emailServiceProvider) {
1887
2055
  res.status(501).json({
@@ -1890,7 +2058,7 @@ var RestServer = class {
1890
2058
  });
1891
2059
  return;
1892
2060
  }
1893
- const emailService = await this.emailServiceProvider(projectId).catch(() => void 0);
2061
+ const emailService = await this.emailServiceProvider(environmentId).catch(() => void 0);
1894
2062
  if (!emailService || typeof emailService.send !== "function") {
1895
2063
  res.status(501).json({
1896
2064
  code: "NOT_IMPLEMENTED",
@@ -1969,7 +2137,7 @@ var RestServer = class {
1969
2137
  * `mapViewSpecToEmbeddableConfig` expects.
1970
2138
  */
1971
2139
  registerFormEndpoints(basePath) {
1972
- const isScoped = basePath.includes("/projects/:projectId");
2140
+ const isScoped = basePath.includes("/environments/:environmentId");
1973
2141
  const slugMatchesPublicLink = (publicLink, slug) => {
1974
2142
  if (!publicLink || typeof publicLink !== "string") return false;
1975
2143
  const normalized = publicLink.replace(/^\/+/, "").replace(/^forms\//, "");
@@ -1999,12 +2167,12 @@ var RestServer = class {
1999
2167
  }
2000
2168
  return null;
2001
2169
  };
2002
- const resolveFormBySlug = async (projectId, req, slug) => {
2003
- const p = await this.resolveProtocol(projectId, req);
2170
+ const resolveFormBySlug = async (environmentId, req, slug) => {
2171
+ const p = await this.resolveProtocol(environmentId, req);
2004
2172
  if (typeof p.getMetaItems !== "function") return null;
2005
2173
  const result = await p.getMetaItems({
2006
2174
  type: "view",
2007
- ...projectId ? { projectId } : {}
2175
+ ...environmentId ? { environmentId } : {}
2008
2176
  });
2009
2177
  const items = Array.isArray(result?.items) ? result.items : Array.isArray(result) ? result : [];
2010
2178
  return findPublicFormView(items, slug);
@@ -2014,13 +2182,13 @@ var RestServer = class {
2014
2182
  path: `${basePath}/forms/:slug`,
2015
2183
  handler: async (req, res) => {
2016
2184
  try {
2017
- const projectId = isScoped ? req.params?.projectId : void 0;
2185
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2018
2186
  const slug = String(req.params?.slug ?? "").trim();
2019
2187
  if (!slug) {
2020
2188
  res.status(400).json({ code: "INVALID_REQUEST", error: "slug is required" });
2021
2189
  return;
2022
2190
  }
2023
- const match = await resolveFormBySlug(projectId, req, slug);
2191
+ const match = await resolveFormBySlug(environmentId, req, slug);
2024
2192
  if (!match) {
2025
2193
  res.status(404).json({
2026
2194
  code: "FORM_NOT_FOUND",
@@ -2030,11 +2198,11 @@ var RestServer = class {
2030
2198
  }
2031
2199
  let objectSchema = null;
2032
2200
  try {
2033
- const p = await this.resolveProtocol(projectId, req);
2201
+ const p = await this.resolveProtocol(environmentId, req);
2034
2202
  if (typeof p.getMetaItems === "function") {
2035
2203
  const r = await p.getMetaItems({
2036
2204
  type: "object",
2037
- ...projectId ? { projectId } : {}
2205
+ ...environmentId ? { environmentId } : {}
2038
2206
  });
2039
2207
  const items = Array.isArray(r?.items) ? r.items : Array.isArray(r) ? r : [];
2040
2208
  const obj = items.find((o) => o?.name === match.object);
@@ -2054,7 +2222,7 @@ var RestServer = class {
2054
2222
  }
2055
2223
  objectSchema = { name: obj.name, label: obj.label, fields };
2056
2224
  try {
2057
- const i18n = await this.resolveI18nService(projectId, req);
2225
+ const i18n = await this.resolveI18nService(environmentId, req);
2058
2226
  const bundle = this.buildTranslationBundle(i18n);
2059
2227
  const locale = this.extractLocale(req, i18n);
2060
2228
  if (bundle && locale) {
@@ -2114,13 +2282,13 @@ var RestServer = class {
2114
2282
  path: `${basePath}/forms/:slug/submit`,
2115
2283
  handler: async (req, res) => {
2116
2284
  try {
2117
- const projectId = isScoped ? req.params?.projectId : void 0;
2285
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2118
2286
  const slug = String(req.params?.slug ?? "").trim();
2119
2287
  if (!slug) {
2120
2288
  res.status(400).json({ code: "INVALID_REQUEST", error: "slug is required" });
2121
2289
  return;
2122
2290
  }
2123
- const match = await resolveFormBySlug(projectId, req, slug);
2291
+ const match = await resolveFormBySlug(environmentId, req, slug);
2124
2292
  if (!match) {
2125
2293
  res.status(404).json({
2126
2294
  code: "FORM_NOT_FOUND",
@@ -2148,11 +2316,11 @@ var RestServer = class {
2148
2316
  permissions: ["guest_portal"],
2149
2317
  anonymous: true
2150
2318
  };
2151
- const p = await this.resolveProtocol(projectId, req);
2319
+ const p = await this.resolveProtocol(environmentId, req);
2152
2320
  const result = await p.createData({
2153
2321
  object: match.object,
2154
2322
  data: filteredData,
2155
- ...projectId ? { projectId } : {},
2323
+ ...environmentId ? { environmentId } : {},
2156
2324
  context
2157
2325
  });
2158
2326
  res.status(201).json(result);
@@ -2174,14 +2342,14 @@ var RestServer = class {
2174
2342
  path: `${basePath}/forms/:slug/lookup/:field`,
2175
2343
  handler: async (req, res) => {
2176
2344
  try {
2177
- const projectId = isScoped ? req.params?.projectId : void 0;
2345
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2178
2346
  const slug = String(req.params?.slug ?? "").trim();
2179
2347
  const fieldName = String(req.params?.field ?? "").trim();
2180
2348
  if (!slug || !fieldName) {
2181
2349
  res.status(400).json({ code: "INVALID_REQUEST", error: "slug and field are required" });
2182
2350
  return;
2183
2351
  }
2184
- const match = await resolveFormBySlug(projectId, req, slug);
2352
+ const match = await resolveFormBySlug(environmentId, req, slug);
2185
2353
  if (!match) {
2186
2354
  res.status(404).json({
2187
2355
  code: "FORM_NOT_FOUND",
@@ -2208,13 +2376,13 @@ var RestServer = class {
2208
2376
  });
2209
2377
  return;
2210
2378
  }
2211
- const p = await this.resolveProtocol(projectId, req);
2379
+ const p = await this.resolveProtocol(environmentId, req);
2212
2380
  let referenceTo = picker.object;
2213
2381
  if (!referenceTo && typeof p.getMetaItems === "function") {
2214
2382
  try {
2215
2383
  const r = await p.getMetaItems({
2216
2384
  type: "object",
2217
- ...projectId ? { projectId } : {}
2385
+ ...environmentId ? { environmentId } : {}
2218
2386
  });
2219
2387
  const items = Array.isArray(r?.items) ? r.items : Array.isArray(r) ? r : [];
2220
2388
  const obj = items.find((o) => o?.name === match.object);
@@ -2250,7 +2418,7 @@ var RestServer = class {
2250
2418
  select: ["id", ...displayFields],
2251
2419
  sort: picker.sort ?? [{ field: displayFields[0], order: "asc" }]
2252
2420
  },
2253
- ...projectId ? { projectId } : {},
2421
+ ...environmentId ? { environmentId } : {},
2254
2422
  context
2255
2423
  });
2256
2424
  const rows = Array.isArray(result?.data) ? result.data : Array.isArray(result?.items) ? result.items : [];
@@ -2300,11 +2468,11 @@ var RestServer = class {
2300
2468
  registerSharingEndpoints(basePath) {
2301
2469
  const { crud } = this.config;
2302
2470
  const dataPath = `${basePath}${crud.dataPrefix}`;
2303
- const isScoped = basePath.includes("/projects/:projectId");
2304
- const resolveService = async (projectId) => {
2471
+ const isScoped = basePath.includes("/environments/:environmentId");
2472
+ const resolveService = async (environmentId) => {
2305
2473
  if (!this.sharingServiceProvider) return void 0;
2306
2474
  try {
2307
- return await this.sharingServiceProvider(projectId);
2475
+ return await this.sharingServiceProvider(environmentId);
2308
2476
  } catch {
2309
2477
  return void 0;
2310
2478
  }
@@ -2318,10 +2486,10 @@ var RestServer = class {
2318
2486
  path: `${dataPath}/:object/:id/shares`,
2319
2487
  handler: async (req, res) => {
2320
2488
  try {
2321
- const projectId = isScoped ? req.params?.projectId : void 0;
2322
- const context = await this.resolveExecCtx(projectId, req);
2489
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2490
+ const context = await this.resolveExecCtx(environmentId, req);
2323
2491
  if (this.enforceAuth(req, res, context)) return;
2324
- const svc = await resolveService(projectId);
2492
+ const svc = await resolveService(environmentId);
2325
2493
  if (!svc) return respond501(res);
2326
2494
  const rows = await svc.listShares(req.params.object, req.params.id, context ?? {});
2327
2495
  res.json({ data: rows });
@@ -2337,10 +2505,10 @@ var RestServer = class {
2337
2505
  path: `${dataPath}/:object/:id/shares`,
2338
2506
  handler: async (req, res) => {
2339
2507
  try {
2340
- const projectId = isScoped ? req.params?.projectId : void 0;
2341
- const context = await this.resolveExecCtx(projectId, req);
2508
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2509
+ const context = await this.resolveExecCtx(environmentId, req);
2342
2510
  if (this.enforceAuth(req, res, context)) return;
2343
- const svc = await resolveService(projectId);
2511
+ const svc = await resolveService(environmentId);
2344
2512
  if (!svc) return respond501(res);
2345
2513
  const body = req.body ?? {};
2346
2514
  const input = {
@@ -2379,10 +2547,10 @@ var RestServer = class {
2379
2547
  path: `${dataPath}/:object/:id/shares/:shareId`,
2380
2548
  handler: async (req, res) => {
2381
2549
  try {
2382
- const projectId = isScoped ? req.params?.projectId : void 0;
2383
- const context = await this.resolveExecCtx(projectId, req);
2550
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2551
+ const context = await this.resolveExecCtx(environmentId, req);
2384
2552
  if (this.enforceAuth(req, res, context)) return;
2385
- const svc = await resolveService(projectId);
2553
+ const svc = await resolveService(environmentId);
2386
2554
  if (!svc) return respond501(res);
2387
2555
  await svc.revoke(req.params.shareId, context ?? {});
2388
2556
  res.status(204).end();
@@ -2408,11 +2576,11 @@ var RestServer = class {
2408
2576
  */
2409
2577
  registerSharingRuleEndpoints(basePath) {
2410
2578
  const dataPath = basePath;
2411
- const isScoped = basePath.includes("/projects/:projectId");
2412
- const resolveService = async (projectId) => {
2579
+ const isScoped = basePath.includes("/environments/:environmentId");
2580
+ const resolveService = async (environmentId) => {
2413
2581
  if (!this.sharingRulesServiceProvider) return void 0;
2414
2582
  try {
2415
- return await this.sharingRulesServiceProvider(projectId);
2583
+ return await this.sharingRulesServiceProvider(environmentId);
2416
2584
  } catch {
2417
2585
  return void 0;
2418
2586
  }
@@ -2437,10 +2605,10 @@ var RestServer = class {
2437
2605
  path: `${dataPath}/sharing/rules`,
2438
2606
  handler: async (req, res) => {
2439
2607
  try {
2440
- const projectId = isScoped ? req.params?.projectId : void 0;
2441
- const context = await this.resolveExecCtx(projectId, req);
2608
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2609
+ const context = await this.resolveExecCtx(environmentId, req);
2442
2610
  if (this.enforceAuth(req, res, context)) return;
2443
- const svc = await resolveService(projectId);
2611
+ const svc = await resolveService(environmentId);
2444
2612
  if (!svc) return respond501(res);
2445
2613
  const rows = await svc.listRules({
2446
2614
  object: req.query?.object,
@@ -2458,10 +2626,10 @@ var RestServer = class {
2458
2626
  path: `${dataPath}/sharing/rules`,
2459
2627
  handler: async (req, res) => {
2460
2628
  try {
2461
- const projectId = isScoped ? req.params?.projectId : void 0;
2462
- const context = await this.resolveExecCtx(projectId, req);
2629
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2630
+ const context = await this.resolveExecCtx(environmentId, req);
2463
2631
  if (this.enforceAuth(req, res, context)) return;
2464
- const svc = await resolveService(projectId);
2632
+ const svc = await resolveService(environmentId);
2465
2633
  if (!svc) return respond501(res);
2466
2634
  const body = req.body ?? {};
2467
2635
  const input = {
@@ -2488,10 +2656,10 @@ var RestServer = class {
2488
2656
  path: `${dataPath}/sharing/rules/:idOrName`,
2489
2657
  handler: async (req, res) => {
2490
2658
  try {
2491
- const projectId = isScoped ? req.params?.projectId : void 0;
2492
- const context = await this.resolveExecCtx(projectId, req);
2659
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2660
+ const context = await this.resolveExecCtx(environmentId, req);
2493
2661
  if (this.enforceAuth(req, res, context)) return;
2494
- const svc = await resolveService(projectId);
2662
+ const svc = await resolveService(environmentId);
2495
2663
  if (!svc) return respond501(res);
2496
2664
  const row = await svc.getRule(req.params.idOrName, context ?? {});
2497
2665
  if (!row) return res.status(404).json({ code: "RULE_NOT_FOUND" });
@@ -2507,10 +2675,10 @@ var RestServer = class {
2507
2675
  path: `${dataPath}/sharing/rules/:idOrName`,
2508
2676
  handler: async (req, res) => {
2509
2677
  try {
2510
- const projectId = isScoped ? req.params?.projectId : void 0;
2511
- const context = await this.resolveExecCtx(projectId, req);
2678
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2679
+ const context = await this.resolveExecCtx(environmentId, req);
2512
2680
  if (this.enforceAuth(req, res, context)) return;
2513
- const svc = await resolveService(projectId);
2681
+ const svc = await resolveService(environmentId);
2514
2682
  if (!svc) return respond501(res);
2515
2683
  await svc.deleteRule(req.params.idOrName, context ?? {});
2516
2684
  res.status(204).end();
@@ -2525,10 +2693,10 @@ var RestServer = class {
2525
2693
  path: `${dataPath}/sharing/rules/:idOrName/evaluate`,
2526
2694
  handler: async (req, res) => {
2527
2695
  try {
2528
- const projectId = isScoped ? req.params?.projectId : void 0;
2529
- const context = await this.resolveExecCtx(projectId, req);
2696
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2697
+ const context = await this.resolveExecCtx(environmentId, req);
2530
2698
  if (this.enforceAuth(req, res, context)) return;
2531
- const svc = await resolveService(projectId);
2699
+ const svc = await resolveService(environmentId);
2532
2700
  if (!svc) return respond501(res);
2533
2701
  const result = await svc.evaluateRule(req.params.idOrName, context ?? {});
2534
2702
  res.json(result);
@@ -2562,11 +2730,11 @@ var RestServer = class {
2562
2730
  */
2563
2731
  registerReportsEndpoints(basePath) {
2564
2732
  const dataPath = basePath;
2565
- const isScoped = basePath.includes("/projects/:projectId");
2566
- const resolveService = async (projectId) => {
2733
+ const isScoped = basePath.includes("/environments/:environmentId");
2734
+ const resolveService = async (environmentId) => {
2567
2735
  if (!this.reportsServiceProvider) return void 0;
2568
2736
  try {
2569
- return await this.reportsServiceProvider(projectId);
2737
+ return await this.reportsServiceProvider(environmentId);
2570
2738
  } catch {
2571
2739
  return void 0;
2572
2740
  }
@@ -2595,10 +2763,10 @@ var RestServer = class {
2595
2763
  path: `${dataPath}/reports`,
2596
2764
  handler: async (req, res) => {
2597
2765
  try {
2598
- const projectId = isScoped ? req.params?.projectId : void 0;
2599
- const context = await this.resolveExecCtx(projectId, req);
2766
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2767
+ const context = await this.resolveExecCtx(environmentId, req);
2600
2768
  if (this.enforceAuth(req, res, context)) return;
2601
- const svc = await resolveService(projectId);
2769
+ const svc = await resolveService(environmentId);
2602
2770
  if (!svc) return respond501(res);
2603
2771
  const q = req.query ?? {};
2604
2772
  const rows = await svc.listReports({ object: q.object, ownerId: q.ownerId }, context ?? {});
@@ -2615,10 +2783,10 @@ var RestServer = class {
2615
2783
  path: `${dataPath}/reports`,
2616
2784
  handler: async (req, res) => {
2617
2785
  try {
2618
- const projectId = isScoped ? req.params?.projectId : void 0;
2619
- const context = await this.resolveExecCtx(projectId, req);
2786
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2787
+ const context = await this.resolveExecCtx(environmentId, req);
2620
2788
  if (this.enforceAuth(req, res, context)) return;
2621
- const svc = await resolveService(projectId);
2789
+ const svc = await resolveService(environmentId);
2622
2790
  if (!svc) return respond501(res);
2623
2791
  try {
2624
2792
  const row = await svc.saveReport(req.body ?? {}, context ?? {});
@@ -2639,10 +2807,10 @@ var RestServer = class {
2639
2807
  path: `${dataPath}/reports/:id`,
2640
2808
  handler: async (req, res) => {
2641
2809
  try {
2642
- const projectId = isScoped ? req.params?.projectId : void 0;
2643
- const context = await this.resolveExecCtx(projectId, req);
2810
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2811
+ const context = await this.resolveExecCtx(environmentId, req);
2644
2812
  if (this.enforceAuth(req, res, context)) return;
2645
- const svc = await resolveService(projectId);
2813
+ const svc = await resolveService(environmentId);
2646
2814
  if (!svc) return respond501(res);
2647
2815
  const row = await svc.getReport(req.params.id, context ?? {});
2648
2816
  if (!row) {
@@ -2662,10 +2830,10 @@ var RestServer = class {
2662
2830
  path: `${dataPath}/reports/:id`,
2663
2831
  handler: async (req, res) => {
2664
2832
  try {
2665
- const projectId = isScoped ? req.params?.projectId : void 0;
2666
- const context = await this.resolveExecCtx(projectId, req);
2833
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2834
+ const context = await this.resolveExecCtx(environmentId, req);
2667
2835
  if (this.enforceAuth(req, res, context)) return;
2668
- const svc = await resolveService(projectId);
2836
+ const svc = await resolveService(environmentId);
2669
2837
  if (!svc) return respond501(res);
2670
2838
  await svc.deleteReport(req.params.id, context ?? {});
2671
2839
  res.status(204).end();
@@ -2681,10 +2849,10 @@ var RestServer = class {
2681
2849
  path: `${dataPath}/reports/:id/run`,
2682
2850
  handler: async (req, res) => {
2683
2851
  try {
2684
- const projectId = isScoped ? req.params?.projectId : void 0;
2685
- const context = await this.resolveExecCtx(projectId, req);
2852
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2853
+ const context = await this.resolveExecCtx(environmentId, req);
2686
2854
  if (this.enforceAuth(req, res, context)) return;
2687
- const svc = await resolveService(projectId);
2855
+ const svc = await resolveService(environmentId);
2688
2856
  if (!svc) return respond501(res);
2689
2857
  try {
2690
2858
  const result = await svc.run(req.params.id, context ?? {});
@@ -2705,10 +2873,10 @@ var RestServer = class {
2705
2873
  path: `${dataPath}/reports/:id/schedule`,
2706
2874
  handler: async (req, res) => {
2707
2875
  try {
2708
- const projectId = isScoped ? req.params?.projectId : void 0;
2709
- const context = await this.resolveExecCtx(projectId, req);
2876
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2877
+ const context = await this.resolveExecCtx(environmentId, req);
2710
2878
  if (this.enforceAuth(req, res, context)) return;
2711
- const svc = await resolveService(projectId);
2879
+ const svc = await resolveService(environmentId);
2712
2880
  if (!svc) return respond501(res);
2713
2881
  const body = req.body ?? {};
2714
2882
  try {
@@ -2741,10 +2909,10 @@ var RestServer = class {
2741
2909
  path: `${dataPath}/reports/:id/schedules`,
2742
2910
  handler: async (req, res) => {
2743
2911
  try {
2744
- const projectId = isScoped ? req.params?.projectId : void 0;
2745
- const context = await this.resolveExecCtx(projectId, req);
2912
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2913
+ const context = await this.resolveExecCtx(environmentId, req);
2746
2914
  if (this.enforceAuth(req, res, context)) return;
2747
- const svc = await resolveService(projectId);
2915
+ const svc = await resolveService(environmentId);
2748
2916
  if (!svc) return respond501(res);
2749
2917
  const rows = await svc.listSchedules({ reportId: req.params.id }, context ?? {});
2750
2918
  res.json({ data: rows });
@@ -2760,10 +2928,10 @@ var RestServer = class {
2760
2928
  path: `${dataPath}/reports/schedules/:scheduleId`,
2761
2929
  handler: async (req, res) => {
2762
2930
  try {
2763
- const projectId = isScoped ? req.params?.projectId : void 0;
2764
- const context = await this.resolveExecCtx(projectId, req);
2931
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
2932
+ const context = await this.resolveExecCtx(environmentId, req);
2765
2933
  if (this.enforceAuth(req, res, context)) return;
2766
- const svc = await resolveService(projectId);
2934
+ const svc = await resolveService(environmentId);
2767
2935
  if (!svc) return respond501(res);
2768
2936
  await svc.unscheduleReport(req.params.scheduleId, context ?? {});
2769
2937
  res.status(204).end();
@@ -2796,11 +2964,11 @@ var RestServer = class {
2796
2964
  */
2797
2965
  registerApprovalsEndpoints(basePath) {
2798
2966
  const dataPath = basePath;
2799
- const isScoped = basePath.includes("/projects/:projectId");
2800
- const resolveService = async (projectId) => {
2967
+ const isScoped = basePath.includes("/environments/:environmentId");
2968
+ const resolveService = async (environmentId) => {
2801
2969
  if (!this.approvalsServiceProvider) return void 0;
2802
2970
  try {
2803
- return await this.approvalsServiceProvider(projectId);
2971
+ return await this.approvalsServiceProvider(environmentId);
2804
2972
  } catch {
2805
2973
  return void 0;
2806
2974
  }
@@ -2833,10 +3001,10 @@ var RestServer = class {
2833
3001
  path: `${dataPath}/approvals/processes`,
2834
3002
  handler: async (req, res) => {
2835
3003
  try {
2836
- const projectId = isScoped ? req.params?.projectId : void 0;
2837
- const context = await this.resolveExecCtx(projectId, req);
3004
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3005
+ const context = await this.resolveExecCtx(environmentId, req);
2838
3006
  if (this.enforceAuth(req, res, context)) return;
2839
- const svc = await resolveService(projectId);
3007
+ const svc = await resolveService(environmentId);
2840
3008
  if (!svc) return respond501(res);
2841
3009
  const q = req.query ?? {};
2842
3010
  const rows = await svc.listProcesses({
@@ -2856,10 +3024,10 @@ var RestServer = class {
2856
3024
  path: `${dataPath}/approvals/processes`,
2857
3025
  handler: async (req, res) => {
2858
3026
  try {
2859
- const projectId = isScoped ? req.params?.projectId : void 0;
2860
- const context = await this.resolveExecCtx(projectId, req);
3027
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3028
+ const context = await this.resolveExecCtx(environmentId, req);
2861
3029
  if (this.enforceAuth(req, res, context)) return;
2862
- const svc = await resolveService(projectId);
3030
+ const svc = await resolveService(environmentId);
2863
3031
  if (!svc) return respond501(res);
2864
3032
  try {
2865
3033
  const row = await svc.defineProcess(req.body ?? {}, context ?? {});
@@ -2880,10 +3048,10 @@ var RestServer = class {
2880
3048
  path: `${dataPath}/approvals/processes/:id`,
2881
3049
  handler: async (req, res) => {
2882
3050
  try {
2883
- const projectId = isScoped ? req.params?.projectId : void 0;
2884
- const context = await this.resolveExecCtx(projectId, req);
3051
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3052
+ const context = await this.resolveExecCtx(environmentId, req);
2885
3053
  if (this.enforceAuth(req, res, context)) return;
2886
- const svc = await resolveService(projectId);
3054
+ const svc = await resolveService(environmentId);
2887
3055
  if (!svc) return respond501(res);
2888
3056
  const row = await svc.getProcess(req.params.id, context ?? {});
2889
3057
  if (!row) {
@@ -2903,10 +3071,10 @@ var RestServer = class {
2903
3071
  path: `${dataPath}/approvals/processes/:id`,
2904
3072
  handler: async (req, res) => {
2905
3073
  try {
2906
- const projectId = isScoped ? req.params?.projectId : void 0;
2907
- const context = await this.resolveExecCtx(projectId, req);
3074
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3075
+ const context = await this.resolveExecCtx(environmentId, req);
2908
3076
  if (this.enforceAuth(req, res, context)) return;
2909
- const svc = await resolveService(projectId);
3077
+ const svc = await resolveService(environmentId);
2910
3078
  if (!svc) return respond501(res);
2911
3079
  await svc.deleteProcess(req.params.id, context ?? {});
2912
3080
  res.status(204).end();
@@ -2922,10 +3090,10 @@ var RestServer = class {
2922
3090
  path: `${dataPath}/approvals/requests`,
2923
3091
  handler: async (req, res) => {
2924
3092
  try {
2925
- const projectId = isScoped ? req.params?.projectId : void 0;
2926
- const context = await this.resolveExecCtx(projectId, req);
3093
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3094
+ const context = await this.resolveExecCtx(environmentId, req);
2927
3095
  if (this.enforceAuth(req, res, context)) return;
2928
- const svc = await resolveService(projectId);
3096
+ const svc = await resolveService(environmentId);
2929
3097
  if (!svc) return respond501(res);
2930
3098
  const body = req.body ?? {};
2931
3099
  try {
@@ -2954,10 +3122,10 @@ var RestServer = class {
2954
3122
  path: `${dataPath}/approvals/requests`,
2955
3123
  handler: async (req, res) => {
2956
3124
  try {
2957
- const projectId = isScoped ? req.params?.projectId : void 0;
2958
- const context = await this.resolveExecCtx(projectId, req);
3125
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3126
+ const context = await this.resolveExecCtx(environmentId, req);
2959
3127
  if (this.enforceAuth(req, res, context)) return;
2960
- const svc = await resolveService(projectId);
3128
+ const svc = await resolveService(environmentId);
2961
3129
  if (!svc) {
2962
3130
  res.json({ data: [] });
2963
3131
  return;
@@ -2983,10 +3151,10 @@ var RestServer = class {
2983
3151
  path: `${dataPath}/approvals/requests/:id`,
2984
3152
  handler: async (req, res) => {
2985
3153
  try {
2986
- const projectId = isScoped ? req.params?.projectId : void 0;
2987
- const context = await this.resolveExecCtx(projectId, req);
3154
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3155
+ const context = await this.resolveExecCtx(environmentId, req);
2988
3156
  if (this.enforceAuth(req, res, context)) return;
2989
- const svc = await resolveService(projectId);
3157
+ const svc = await resolveService(environmentId);
2990
3158
  if (!svc) return respond501(res);
2991
3159
  const row = await svc.getRequest(req.params.id, context ?? {});
2992
3160
  if (!row) {
@@ -3007,10 +3175,10 @@ var RestServer = class {
3007
3175
  path: `${dataPath}/approvals/requests/:id/${suffix}`,
3008
3176
  handler: async (req, res) => {
3009
3177
  try {
3010
- const projectId = isScoped ? req.params?.projectId : void 0;
3011
- const context = await this.resolveExecCtx(projectId, req);
3178
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3179
+ const context = await this.resolveExecCtx(environmentId, req);
3012
3180
  if (this.enforceAuth(req, res, context)) return;
3013
- const svc = await resolveService(projectId);
3181
+ const svc = await resolveService(environmentId);
3014
3182
  if (!svc) return respond501(res);
3015
3183
  const body = req.body ?? {};
3016
3184
  try {
@@ -3039,10 +3207,10 @@ var RestServer = class {
3039
3207
  path: `${dataPath}/approvals/requests/:id/actions`,
3040
3208
  handler: async (req, res) => {
3041
3209
  try {
3042
- const projectId = isScoped ? req.params?.projectId : void 0;
3043
- const context = await this.resolveExecCtx(projectId, req);
3210
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3211
+ const context = await this.resolveExecCtx(environmentId, req);
3044
3212
  if (this.enforceAuth(req, res, context)) return;
3045
- const svc = await resolveService(projectId);
3213
+ const svc = await resolveService(environmentId);
3046
3214
  if (!svc) return respond501(res);
3047
3215
  const rows = await svc.listActions(req.params.id, context ?? {});
3048
3216
  res.json({ data: rows });
@@ -3060,7 +3228,7 @@ var RestServer = class {
3060
3228
  registerBatchEndpoints(basePath) {
3061
3229
  const { crud, batch } = this.config;
3062
3230
  const dataPath = `${basePath}${crud.dataPrefix}`;
3063
- const isScoped = basePath.includes("/projects/:projectId");
3231
+ const isScoped = basePath.includes("/environments/:environmentId");
3064
3232
  const operations = batch.operations;
3065
3233
  if (batch.enableBatchEndpoint && this.protocol.batchData) {
3066
3234
  this.routeManager.register({
@@ -3068,14 +3236,14 @@ var RestServer = class {
3068
3236
  path: `${dataPath}/:object/batch`,
3069
3237
  handler: async (req, res) => {
3070
3238
  try {
3071
- const projectId = isScoped ? req.params?.projectId : void 0;
3072
- const p = await this.resolveProtocol(projectId, req);
3073
- const context = await this.resolveExecCtx(projectId, req);
3239
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3240
+ const p = await this.resolveProtocol(environmentId, req);
3241
+ const context = await this.resolveExecCtx(environmentId, req);
3074
3242
  if (this.enforceAuth(req, res, context)) return;
3075
3243
  const result = await p.batchData({
3076
3244
  object: req.params.object,
3077
3245
  request: req.body,
3078
- ...projectId ? { projectId } : {},
3246
+ ...environmentId ? { environmentId } : {},
3079
3247
  ...context ? { context } : {}
3080
3248
  });
3081
3249
  res.json(result);
@@ -3096,14 +3264,14 @@ var RestServer = class {
3096
3264
  path: `${dataPath}/:object/createMany`,
3097
3265
  handler: async (req, res) => {
3098
3266
  try {
3099
- const projectId = isScoped ? req.params?.projectId : void 0;
3100
- const p = await this.resolveProtocol(projectId, req);
3101
- const context = await this.resolveExecCtx(projectId, req);
3267
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3268
+ const p = await this.resolveProtocol(environmentId, req);
3269
+ const context = await this.resolveExecCtx(environmentId, req);
3102
3270
  if (this.enforceAuth(req, res, context)) return;
3103
3271
  const result = await p.createManyData({
3104
3272
  object: req.params.object,
3105
3273
  records: req.body || [],
3106
- ...projectId ? { projectId } : {},
3274
+ ...environmentId ? { environmentId } : {},
3107
3275
  ...context ? { context } : {}
3108
3276
  });
3109
3277
  res.status(201).json(result);
@@ -3124,14 +3292,14 @@ var RestServer = class {
3124
3292
  path: `${dataPath}/:object/updateMany`,
3125
3293
  handler: async (req, res) => {
3126
3294
  try {
3127
- const projectId = isScoped ? req.params?.projectId : void 0;
3128
- const p = await this.resolveProtocol(projectId, req);
3129
- const context = await this.resolveExecCtx(projectId, req);
3295
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3296
+ const p = await this.resolveProtocol(environmentId, req);
3297
+ const context = await this.resolveExecCtx(environmentId, req);
3130
3298
  if (this.enforceAuth(req, res, context)) return;
3131
3299
  const result = await p.updateManyData({
3132
3300
  object: req.params.object,
3133
3301
  ...req.body,
3134
- ...projectId ? { projectId } : {},
3302
+ ...environmentId ? { environmentId } : {},
3135
3303
  ...context ? { context } : {}
3136
3304
  });
3137
3305
  res.json(result);
@@ -3152,14 +3320,14 @@ var RestServer = class {
3152
3320
  path: `${dataPath}/:object/deleteMany`,
3153
3321
  handler: async (req, res) => {
3154
3322
  try {
3155
- const projectId = isScoped ? req.params?.projectId : void 0;
3156
- const p = await this.resolveProtocol(projectId, req);
3157
- const context = await this.resolveExecCtx(projectId, req);
3323
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
3324
+ const p = await this.resolveProtocol(environmentId, req);
3325
+ const context = await this.resolveExecCtx(environmentId, req);
3158
3326
  if (this.enforceAuth(req, res, context)) return;
3159
3327
  const result = await p.deleteManyData({
3160
3328
  object: req.params.object,
3161
3329
  ...req.body,
3162
- ...projectId ? { projectId } : {},
3330
+ ...environmentId ? { environmentId } : {},
3163
3331
  ...context ? { context } : {}
3164
3332
  });
3165
3333
  res.json(result);
@@ -3337,64 +3505,64 @@ function createRestApiPlugin(config = {}) {
3337
3505
  envRegistry = ctx.getService("env-registry");
3338
3506
  } catch (e) {
3339
3507
  }
3340
- const defaultProjectIdProvider = () => {
3508
+ const defaultEnvironmentIdProvider = () => {
3341
3509
  try {
3342
3510
  const dp = ctx.getService("default-project");
3343
- return dp?.projectId;
3511
+ return dp?.environmentId;
3344
3512
  } catch {
3345
3513
  return void 0;
3346
3514
  }
3347
3515
  };
3348
- const authServiceProvider = async (_projectId) => {
3516
+ const authServiceProvider = async (_environmentId) => {
3349
3517
  try {
3350
3518
  return ctx.getService("auth");
3351
3519
  } catch {
3352
3520
  return void 0;
3353
3521
  }
3354
3522
  };
3355
- const objectQLProvider = async (_projectId) => {
3523
+ const objectQLProvider = async (_environmentId) => {
3356
3524
  try {
3357
3525
  return ctx.getService("objectql");
3358
3526
  } catch {
3359
3527
  return void 0;
3360
3528
  }
3361
3529
  };
3362
- const emailServiceProvider = async (_projectId) => {
3530
+ const emailServiceProvider = async (_environmentId) => {
3363
3531
  try {
3364
3532
  return ctx.getService("email");
3365
3533
  } catch {
3366
3534
  return void 0;
3367
3535
  }
3368
3536
  };
3369
- const sharingServiceProvider = async (_projectId) => {
3537
+ const sharingServiceProvider = async (_environmentId) => {
3370
3538
  try {
3371
3539
  return ctx.getService("sharing");
3372
3540
  } catch {
3373
3541
  return void 0;
3374
3542
  }
3375
3543
  };
3376
- const reportsServiceProvider = async (_projectId) => {
3544
+ const reportsServiceProvider = async (_environmentId) => {
3377
3545
  try {
3378
3546
  return ctx.getService("reports");
3379
3547
  } catch {
3380
3548
  return void 0;
3381
3549
  }
3382
3550
  };
3383
- const approvalsServiceProvider = async (_projectId) => {
3551
+ const approvalsServiceProvider = async (_environmentId) => {
3384
3552
  try {
3385
3553
  return ctx.getService("approvals");
3386
3554
  } catch {
3387
3555
  return void 0;
3388
3556
  }
3389
3557
  };
3390
- const sharingRulesServiceProvider = async (_projectId) => {
3558
+ const sharingRulesServiceProvider = async (_environmentId) => {
3391
3559
  try {
3392
3560
  return ctx.getService("sharingRules");
3393
3561
  } catch {
3394
3562
  return void 0;
3395
3563
  }
3396
3564
  };
3397
- const i18nServiceProvider = async (_projectId) => {
3565
+ const i18nServiceProvider = async (_environmentId) => {
3398
3566
  try {
3399
3567
  return ctx.getService("i18n");
3400
3568
  } catch {
@@ -3411,7 +3579,7 @@ function createRestApiPlugin(config = {}) {
3411
3579
  }
3412
3580
  ctx.logger.info("Hydrating REST API from Protocol...");
3413
3581
  try {
3414
- const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultProjectIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider);
3582
+ const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider);
3415
3583
  restServer.registerRoutes();
3416
3584
  ctx.logger.info("REST API successfully registered");
3417
3585
  } catch (err) {
@@ -3427,13 +3595,13 @@ function createRestApiPlugin(config = {}) {
3427
3595
  const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;
3428
3596
  const projectResolution = config.api?.api?.projectResolution ?? "auto";
3429
3597
  if (enableProjectScoping && projectResolution === "required") {
3430
- registerPackageRoutes(server, packageService, `${versionedBase}/projects/:projectId`, {
3598
+ registerPackageRoutes(server, packageService, `${versionedBase}/environments/:environmentId`, {
3431
3599
  protocol
3432
3600
  });
3433
3601
  } else {
3434
3602
  registerPackageRoutes(server, packageService, versionedBase, { protocol });
3435
3603
  if (enableProjectScoping) {
3436
- registerPackageRoutes(server, packageService, `${versionedBase}/projects/:projectId`, {
3604
+ registerPackageRoutes(server, packageService, `${versionedBase}/environments/:environmentId`, {
3437
3605
  protocol
3438
3606
  });
3439
3607
  }