@objectstack/rest 12.6.0 → 14.3.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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/rest-server.ts
2
2
  import { resolveAuthzContext, resolveLocalizationContext, isAuthGateAllowlisted } from "@objectstack/core";
3
+ import { isMcpServerEnabled } from "@objectstack/types";
3
4
 
4
5
  // src/route-manager.ts
5
6
  var RouteManager = class {
@@ -287,6 +288,22 @@ function optionLabel(value, options) {
287
288
  const hit = options.find((o) => o && o.value === value);
288
289
  return hit?.label ?? value;
289
290
  }
291
+ function toArgb(color) {
292
+ if (typeof color !== "string") return void 0;
293
+ const hex = color.trim().replace(/^#/, "");
294
+ if (/^[0-9a-fA-F]{3}$/.test(hex)) {
295
+ const [r, g, b] = hex;
296
+ return `FF${r}${r}${g}${g}${b}${b}`.toUpperCase();
297
+ }
298
+ if (/^[0-9a-fA-F]{6}$/.test(hex)) return `FF${hex}`.toUpperCase();
299
+ return void 0;
300
+ }
301
+ function cellFontColor(value, meta) {
302
+ if (value === null || value === void 0) return void 0;
303
+ if (!meta || !meta.type || !OPTION_TYPES.has(meta.type) || !meta.options) return void 0;
304
+ const hit = meta.options.find((o) => o && o.value === value);
305
+ return toArgb(hit?.color);
306
+ }
290
307
  function displayFromRecord(rec, displayField) {
291
308
  if (displayField && rec[displayField] != null) return String(rec[displayField]);
292
309
  for (const k of NAME_KEY_FALLBACKS) {
@@ -873,154 +890,10 @@ function applyMappingToRows(rows, artifact) {
873
890
  return { ok: true, rows: out };
874
891
  }
875
892
 
876
- // src/rest-server.ts
877
- var logError = (...args) => globalThis.console?.error(...args);
878
- var TRANSLATABLE_META_TYPES = /* @__PURE__ */ new Set(["view", "action", "object", "app", "dashboard"]);
893
+ // src/import-prepare.ts
879
894
  function isMetaEnvelope(value) {
880
895
  return !!value && typeof value === "object" && typeof value.type === "string" && typeof value.name === "string" && value.item != null && typeof value.item === "object" && !Array.isArray(value.item);
881
896
  }
882
- function mapDataError(error, object) {
883
- if (error?.code === "DELETE_RESTRICTED") {
884
- return {
885
- status: 409,
886
- body: {
887
- error: error?.message ?? "Cannot delete: dependent records exist",
888
- code: "DELETE_RESTRICTED",
889
- ...error?.dependentObject ? { dependentObject: error.dependentObject } : {},
890
- ...typeof error?.dependentCount === "number" ? { dependentCount: error.dependentCount } : {},
891
- ...object ? { object } : {}
892
- }
893
- };
894
- }
895
- if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
896
- return {
897
- status: 409,
898
- body: {
899
- error: error?.message ?? "Record was modified by another user",
900
- code: "CONCURRENT_UPDATE",
901
- ...error?.currentVersion ? { currentVersion: error.currentVersion } : {},
902
- ...error?.currentRecord ? { currentRecord: error.currentRecord } : {},
903
- ...object ? { object } : {}
904
- }
905
- };
906
- }
907
- if (error?.code === "VALIDATION_FAILED" || error?.name === "ValidationError") {
908
- return {
909
- status: 400,
910
- body: {
911
- error: error?.message ?? "Validation failed",
912
- code: "VALIDATION_FAILED",
913
- fields: Array.isArray(error?.fields) ? error.fields : [],
914
- ...object ? { object } : {}
915
- }
916
- };
917
- }
918
- if (error?.code === "PERMISSION_DENIED" || error?.name === "PermissionDeniedError" || typeof error?.message === "string" && error.message.startsWith("[Security] Access denied")) {
919
- return {
920
- status: 403,
921
- body: {
922
- error: error?.message ?? "Permission denied",
923
- code: "PERMISSION_DENIED",
924
- ...object ? { object } : {}
925
- }
926
- };
927
- }
928
- const raw = String(error?.message ?? error ?? "");
929
- const lower = raw.toLowerCase();
930
- if (raw.includes("[EnvironmentKernelFactory]") && (lower.includes("missing database_url") || lower.includes("not found"))) {
931
- const isProvisioning = lower.includes("status='provisioning'") || lower.includes("status='pending'");
932
- const isFailed = lower.includes("status='failed'");
933
- return {
934
- status: isProvisioning ? 503 : isFailed ? 502 : 404,
935
- body: {
936
- error: raw,
937
- code: isProvisioning ? "PROJECT_PROVISIONING" : isFailed ? "PROJECT_PROVISIONING_FAILED" : "PROJECT_NOT_FOUND"
938
- }
939
- };
940
- }
941
- if (error?.code === "RECORD_NOT_FOUND" || /^Record\s+\S+\s+not found in\s+\S+/i.test(raw)) {
942
- return {
943
- status: 404,
944
- body: {
945
- error: raw,
946
- code: "RECORD_NOT_FOUND",
947
- ...object ? { object } : {}
948
- }
949
- };
950
- }
951
- const unknownColumn = /has no column named\s+["'`]?([a-z0-9_]+)/i.exec(raw) || /no such column:\s*["'`]?([a-z0-9_.]+)/i.exec(raw) || /unknown column\s+["'`]([a-z0-9_]+)["'`]/i.exec(raw) || /column\s+["'`]([a-z0-9_]+)["'`]\s+of relation\s+\S+\s+does not exist/i.exec(raw);
952
- if (unknownColumn) {
953
- const field = unknownColumn[1]?.split(".").pop();
954
- return {
955
- status: 400,
956
- body: {
957
- error: field ? `Unknown field '${field}'${object ? ` on object '${object}'` : ""}` : "Request references a field that does not exist",
958
- code: "INVALID_FIELD",
959
- ...field ? { field } : {},
960
- ...object ? { object } : {}
961
- }
962
- };
963
- }
964
- const notNull = /not null constraint failed:\s*\S*?\.([a-z0-9_]+)/i.exec(raw) || /null value in column\s+["'`]([a-z0-9_]+)["'`]/i.exec(raw) || /column\s+["'`]([a-z0-9_]+)["'`]\s+cannot be null/i.exec(raw);
965
- if (notNull) {
966
- const field = notNull[1];
967
- return {
968
- status: 400,
969
- body: {
970
- error: `${field} is required`,
971
- code: "VALIDATION_FAILED",
972
- fields: [{ field, code: "required", message: `${field} is required` }],
973
- hint: `If '${field}' is optional in your object metadata, the database column is still NOT NULL \u2014 the physical schema has drifted from metadata. Run 'os migrate' to reconcile (or reset the dev database).`,
974
- ...object ? { object } : {}
975
- }
976
- };
977
- }
978
- const looksLikeUnknownObject = lower.includes("no such table") || lower.includes("relation") && lower.includes("does not exist") || lower.includes("table not found") || lower.includes("unknown object") || lower.includes("object not found") || lower.includes("no driver available") || object !== void 0 && lower.includes(`'${object.toLowerCase()}'`) && lower.includes("not");
979
- if (looksLikeUnknownObject) {
980
- return {
981
- status: 404,
982
- body: {
983
- error: object ? `Object '${object}' is not registered` : "Object not found",
984
- code: "object_not_found",
985
- object
986
- }
987
- };
988
- }
989
- const looksLikeSqlLeak = lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key");
990
- if (looksLikeSqlLeak) {
991
- if (lower.includes("unique constraint") || lower.includes("unique violation")) {
992
- return {
993
- status: 409,
994
- body: {
995
- error: "A record with this value already exists",
996
- code: "UNIQUE_VIOLATION",
997
- ...object ? { object } : {}
998
- }
999
- };
1000
- }
1001
- return {
1002
- status: 500,
1003
- body: { error: "Internal data error", code: "DATABASE_ERROR" }
1004
- };
1005
- }
1006
- return { status: 400, body: { error: raw || "Bad request" } };
1007
- }
1008
- function sendError(res, error, object) {
1009
- if (typeof error?.status === "number" && error.status >= 400 && error.status < 600) {
1010
- const safeMsg = typeof error.message === "string" && error.message.length < 500 ? error.message : "Request failed";
1011
- res.status(error.status).json({
1012
- error: safeMsg,
1013
- ...error.code ? { code: error.code } : {},
1014
- ...Array.isArray(error.issues) ? { issues: error.issues } : {}
1015
- });
1016
- return;
1017
- }
1018
- const mapped = mapDataError(error, object);
1019
- res.status(mapped.status).json(mapped.body);
1020
- }
1021
- function isExpectedDataStatus(status) {
1022
- return status === 403 || status === 404 || status === 409 || status === 502 || status === 503;
1023
- }
1024
897
  function parseCsvToRows(csv, mapping = {}) {
1025
898
  const text = csv.replace(/^\uFEFF/, "");
1026
899
  const cells = [];
@@ -1239,6 +1112,162 @@ async function prepareImportRequest(body, opts) {
1239
1112
  }
1240
1113
  };
1241
1114
  }
1115
+
1116
+ // src/rest-server.ts
1117
+ var logError = (...args) => globalThis.console?.error(...args);
1118
+ var TRANSLATABLE_META_TYPES = /* @__PURE__ */ new Set(["view", "action", "object", "app", "dashboard"]);
1119
+ function mapDataError(error, object) {
1120
+ if (error?.code === "DELETE_RESTRICTED") {
1121
+ return {
1122
+ status: 409,
1123
+ body: {
1124
+ error: error?.message ?? "Cannot delete: dependent records exist",
1125
+ code: "DELETE_RESTRICTED",
1126
+ ...error?.dependentObject ? { dependentObject: error.dependentObject } : {},
1127
+ ...typeof error?.dependentCount === "number" ? { dependentCount: error.dependentCount } : {},
1128
+ ...object ? { object } : {}
1129
+ }
1130
+ };
1131
+ }
1132
+ if (error?.code === "CONCURRENT_UPDATE" || error?.name === "ConcurrentUpdateError") {
1133
+ return {
1134
+ status: 409,
1135
+ body: {
1136
+ error: error?.message ?? "Record was modified by another user",
1137
+ code: "CONCURRENT_UPDATE",
1138
+ ...error?.currentVersion ? { currentVersion: error.currentVersion } : {},
1139
+ ...error?.currentRecord ? { currentRecord: error.currentRecord } : {},
1140
+ ...object ? { object } : {}
1141
+ }
1142
+ };
1143
+ }
1144
+ if (error?.code === "VALIDATION_FAILED" || error?.name === "ValidationError") {
1145
+ return {
1146
+ status: 400,
1147
+ body: {
1148
+ error: error?.message ?? "Validation failed",
1149
+ code: "VALIDATION_FAILED",
1150
+ fields: Array.isArray(error?.fields) ? error.fields : [],
1151
+ ...object ? { object } : {}
1152
+ }
1153
+ };
1154
+ }
1155
+ if (error?.code === "FEEDS_DISABLED" || error?.code === "FILES_DISABLED") {
1156
+ return {
1157
+ status: 403,
1158
+ body: {
1159
+ error: error?.message ?? "This capability is disabled for the target object",
1160
+ code: error.code,
1161
+ ...error?.object || object ? { object: error?.object ?? object } : {}
1162
+ }
1163
+ };
1164
+ }
1165
+ if (error?.code === "PERMISSION_DENIED" || error?.name === "PermissionDeniedError" || typeof error?.message === "string" && error.message.startsWith("[Security] Access denied")) {
1166
+ return {
1167
+ status: 403,
1168
+ body: {
1169
+ error: error?.message ?? "Permission denied",
1170
+ code: "PERMISSION_DENIED",
1171
+ ...object ? { object } : {}
1172
+ }
1173
+ };
1174
+ }
1175
+ const raw = String(error?.message ?? error ?? "");
1176
+ const lower = raw.toLowerCase();
1177
+ if (raw.includes("[EnvironmentKernelFactory]") && (lower.includes("missing database_url") || lower.includes("not found"))) {
1178
+ const isProvisioning = lower.includes("status='provisioning'") || lower.includes("status='pending'");
1179
+ const isFailed = lower.includes("status='failed'");
1180
+ return {
1181
+ status: isProvisioning ? 503 : isFailed ? 502 : 404,
1182
+ body: {
1183
+ error: raw,
1184
+ code: isProvisioning ? "PROJECT_PROVISIONING" : isFailed ? "PROJECT_PROVISIONING_FAILED" : "PROJECT_NOT_FOUND"
1185
+ }
1186
+ };
1187
+ }
1188
+ if (error?.code === "RECORD_NOT_FOUND" || /^Record\s+\S+\s+not found in\s+\S+/i.test(raw)) {
1189
+ return {
1190
+ status: 404,
1191
+ body: {
1192
+ error: raw,
1193
+ code: "RECORD_NOT_FOUND",
1194
+ ...object ? { object } : {}
1195
+ }
1196
+ };
1197
+ }
1198
+ const unknownColumn = /has no column named\s+["'`]?([a-z0-9_]+)/i.exec(raw) || /no such column:\s*["'`]?([a-z0-9_.]+)/i.exec(raw) || /unknown column\s+["'`]([a-z0-9_]+)["'`]/i.exec(raw) || /column\s+["'`]([a-z0-9_]+)["'`]\s+of relation\s+\S+\s+does not exist/i.exec(raw);
1199
+ if (unknownColumn) {
1200
+ const field = unknownColumn[1]?.split(".").pop();
1201
+ return {
1202
+ status: 400,
1203
+ body: {
1204
+ error: field ? `Unknown field '${field}'${object ? ` on object '${object}'` : ""}` : "Request references a field that does not exist",
1205
+ code: "INVALID_FIELD",
1206
+ ...field ? { field } : {},
1207
+ ...object ? { object } : {}
1208
+ }
1209
+ };
1210
+ }
1211
+ const notNull = /not null constraint failed:\s*\S*?\.([a-z0-9_]+)/i.exec(raw) || /null value in column\s+["'`]([a-z0-9_]+)["'`]/i.exec(raw) || /column\s+["'`]([a-z0-9_]+)["'`]\s+cannot be null/i.exec(raw);
1212
+ if (notNull) {
1213
+ const field = notNull[1];
1214
+ return {
1215
+ status: 400,
1216
+ body: {
1217
+ error: `${field} is required`,
1218
+ code: "VALIDATION_FAILED",
1219
+ fields: [{ field, code: "required", message: `${field} is required` }],
1220
+ hint: `If '${field}' is optional in your object metadata, the database column is still NOT NULL \u2014 the physical schema has drifted from metadata. Run 'os migrate' to reconcile (or reset the dev database).`,
1221
+ ...object ? { object } : {}
1222
+ }
1223
+ };
1224
+ }
1225
+ const looksLikeUnknownObject = lower.includes("no such table") || lower.includes("relation") && lower.includes("does not exist") || lower.includes("table not found") || lower.includes("unknown object") || lower.includes("object not found") || lower.includes("no driver available") || object !== void 0 && lower.includes(`'${object.toLowerCase()}'`) && lower.includes("not");
1226
+ if (looksLikeUnknownObject) {
1227
+ return {
1228
+ status: 404,
1229
+ body: {
1230
+ error: object ? `Object '${object}' is not registered` : "Object not found",
1231
+ code: "object_not_found",
1232
+ object
1233
+ }
1234
+ };
1235
+ }
1236
+ const looksLikeSqlLeak = lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key");
1237
+ if (looksLikeSqlLeak) {
1238
+ if (lower.includes("unique constraint") || lower.includes("unique violation")) {
1239
+ return {
1240
+ status: 409,
1241
+ body: {
1242
+ error: "A record with this value already exists",
1243
+ code: "UNIQUE_VIOLATION",
1244
+ ...object ? { object } : {}
1245
+ }
1246
+ };
1247
+ }
1248
+ return {
1249
+ status: 500,
1250
+ body: { error: "Internal data error", code: "DATABASE_ERROR" }
1251
+ };
1252
+ }
1253
+ return { status: 400, body: { error: raw || "Bad request" } };
1254
+ }
1255
+ function sendError(res, error, object) {
1256
+ if (typeof error?.status === "number" && error.status >= 400 && error.status < 600) {
1257
+ const safeMsg = typeof error.message === "string" && error.message.length < 500 ? error.message : "Request failed";
1258
+ res.status(error.status).json({
1259
+ error: safeMsg,
1260
+ ...error.code ? { code: error.code } : {},
1261
+ ...Array.isArray(error.issues) ? { issues: error.issues } : {}
1262
+ });
1263
+ return;
1264
+ }
1265
+ const mapped = mapDataError(error, object);
1266
+ res.status(mapped.status).json(mapped.body);
1267
+ }
1268
+ function isExpectedDataStatus(status) {
1269
+ return status === 403 || status === 404 || status === 409 || status === 502 || status === 503;
1270
+ }
1242
1271
  var IMPORT_JOB_OBJECT = "sys_import_job";
1243
1272
  var IMPORT_JOB_MAX_ROWS = 5e4;
1244
1273
  var IMPORT_JOB_RESULTS_CAP = 500;
@@ -1343,7 +1372,7 @@ function rowsToCsv(fields, rows, includeHeader, metaMap) {
1343
1372
  }
1344
1373
  return lines.join("\r\n") + (lines.length > 0 ? "\r\n" : "");
1345
1374
  }
1346
- async function createXlsxStream(res) {
1375
+ async function createXlsxStream(res, useStyles = false) {
1347
1376
  const { PassThrough } = await import("stream");
1348
1377
  const ExcelJS = (await import("exceljs")).default ?? await import("exceljs");
1349
1378
  const passthrough = new PassThrough();
@@ -1360,7 +1389,7 @@ async function createXlsxStream(res) {
1360
1389
  });
1361
1390
  passthrough.on("error", reject);
1362
1391
  });
1363
- const wb = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: passthrough, useStyles: false });
1392
+ const wb = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: passthrough, useStyles });
1364
1393
  const ws = wb.addWorksheet("Export");
1365
1394
  return {
1366
1395
  ws,
@@ -1371,8 +1400,8 @@ async function createXlsxStream(res) {
1371
1400
  }
1372
1401
  };
1373
1402
  }
1374
- var RestServer = class {
1375
- constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider, serviceExistsProvider) {
1403
+ var RestServer = class _RestServer {
1404
+ constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider, serviceExistsProvider, securityServiceProvider) {
1376
1405
  /**
1377
1406
  * Short-TTL cache for `hostname → environmentId` (P1-4). `resolveByHostname`
1378
1407
  * is a control-plane lookup (typically a DB query) that otherwise runs on
@@ -1425,6 +1454,7 @@ var RestServer = class {
1425
1454
  this.analyticsServiceProvider = analyticsServiceProvider;
1426
1455
  this.settingsServiceProvider = settingsServiceProvider;
1427
1456
  this.serviceExistsProvider = serviceExistsProvider;
1457
+ this.securityServiceProvider = securityServiceProvider;
1428
1458
  }
1429
1459
  /**
1430
1460
  * Resolve the protocol for a given request. When `environmentId` is present
@@ -1663,6 +1693,54 @@ var RestServer = class {
1663
1693
  perReq.set(key, pending);
1664
1694
  return pending;
1665
1695
  }
1696
+ /**
1697
+ * [ADR-0046 §6.7] The audience-evaluation view of the caller for book/doc
1698
+ * gating. `permissionSets` resolves through the security service's
1699
+ * `resolvePermissionSetNames` — the SAME resolution as data-plane
1700
+ * enforcement (positions expanded, additive baseline), so the docs gate
1701
+ * can never drift from it. `permissionSets` stays undefined when the
1702
+ * service is absent or resolution fails; `audienceAllows` then DENIES
1703
+ * permission-set-gated audiences (fail closed, ADR-0049). Resolution is
1704
+ * skipped unless `needPermissionSets` — callers pass true only when a
1705
+ * `{ permissionSet }` audience is actually in play.
1706
+ */
1707
+ async resolveAudienceCaller(environmentId, req, opts) {
1708
+ const ctx = await this.resolveExecCtx(environmentId, req).catch(() => void 0);
1709
+ const authenticated = !!ctx?.userId;
1710
+ if (!authenticated || !opts.needPermissionSets || !this.securityServiceProvider) {
1711
+ return { authenticated };
1712
+ }
1713
+ try {
1714
+ const svc = await this.securityServiceProvider(environmentId);
1715
+ if (!svc || typeof svc.resolvePermissionSetNames !== "function") return { authenticated };
1716
+ const names = await svc.resolvePermissionSetNames(ctx);
1717
+ return { authenticated, permissionSets: Array.isArray(names) ? names : [] };
1718
+ } catch {
1719
+ return { authenticated };
1720
+ }
1721
+ }
1722
+ /** Whether any of these books carries a `{ permissionSet }` audience. */
1723
+ static anyPermissionSetAudience(books) {
1724
+ return books.some(
1725
+ (b) => b && typeof b === "object" && b.audience && typeof b.audience === "object" && typeof b.audience.permissionSet === "string"
1726
+ );
1727
+ }
1728
+ /** Coerce a getMetaItems result (array | {items}) into an array. */
1729
+ static metaItemsArray(raw) {
1730
+ if (Array.isArray(raw)) return raw;
1731
+ if (raw && typeof raw === "object" && Array.isArray(raw.items)) return raw.items;
1732
+ return [];
1733
+ }
1734
+ /** Fetch every book of the environment, shaped for the audience resolver. */
1735
+ async fetchAudienceBooks(p, environmentId) {
1736
+ const raw = await p.getMetaItems({
1737
+ type: "book",
1738
+ ...environmentId ? { environmentId } : {}
1739
+ }).catch(() => []);
1740
+ return _RestServer.metaItemsArray(raw).map(
1741
+ (b) => b && typeof b === "object" ? { ...b, packageId: b._packageId } : b
1742
+ );
1743
+ }
1666
1744
  /** Heavy path behind `resolveExecCtx` — resolve identity + RBAC/RLS + localization. */
1667
1745
  async computeExecCtx(environmentId, req) {
1668
1746
  try {
@@ -1753,7 +1831,7 @@ var RestServer = class {
1753
1831
  userId: authz.userId,
1754
1832
  tenantId: authz.tenantId,
1755
1833
  email: authz.email,
1756
- roles: authz.roles,
1834
+ positions: authz.positions,
1757
1835
  permissions: authz.permissions,
1758
1836
  systemPermissions: authz.systemPermissions,
1759
1837
  ...authz.tabPermissions ? { tabPermissions: authz.tabPermissions } : {},
@@ -2171,6 +2249,8 @@ var RestServer = class {
2171
2249
  this.registerReportsEndpoints(bp);
2172
2250
  this.registerApprovalsEndpoints(bp);
2173
2251
  this.registerAnalyticsEndpoints(bp);
2252
+ this.registerSecurityEndpoints(bp);
2253
+ this.registerSecurityExplainEndpoints(bp);
2174
2254
  this.registerDataActionEndpoints(bp);
2175
2255
  if (this.config.api.enableCrud) {
2176
2256
  this.registerCrudEndpoints(bp);
@@ -2211,8 +2291,7 @@ var RestServer = class {
2211
2291
  if (this.config.api.enableUi) {
2212
2292
  discovery.routes.ui = `${realBase}/ui`;
2213
2293
  }
2214
- const mcpEnabled = globalThis?.process?.env?.OS_MCP_SERVER_ENABLED === "true";
2215
- if (mcpEnabled) {
2294
+ if (isMcpServerEnabled()) {
2216
2295
  const unscopedBase = isScoped ? basePath.replace(/\/(environments|projects)\/:environmentId$/, "") : basePath;
2217
2296
  discovery.routes.mcp = `${unscopedBase}/mcp`;
2218
2297
  } else {
@@ -2533,6 +2612,48 @@ var RestServer = class {
2533
2612
  visible = Array.isArray(raw) ? filtered : { ...raw, items: filtered };
2534
2613
  }
2535
2614
  }
2615
+ if (req.params.type === "book") {
2616
+ const raw = visible;
2617
+ const list = _RestServer.metaItemsArray(raw);
2618
+ if (list.length > 0) {
2619
+ const { audienceAllows } = await import("@objectstack/spec/system");
2620
+ const caller = await this.resolveAudienceCaller(environmentId, req, {
2621
+ needPermissionSets: _RestServer.anyPermissionSetAudience(list)
2622
+ });
2623
+ const filtered = list.filter((b) => b && typeof b === "object" && audienceAllows(b.audience, caller));
2624
+ visible = Array.isArray(raw) ? filtered : { ...raw, items: filtered };
2625
+ }
2626
+ }
2627
+ if (req.params.type === "doc") {
2628
+ const raw = visible;
2629
+ const list = _RestServer.metaItemsArray(raw);
2630
+ if (list.length > 0) {
2631
+ const { audienceAllows, docAudienceAllows, resolveDocAudiences } = await import("@objectstack/spec/system");
2632
+ const books = await this.fetchAudienceBooks(p, environmentId);
2633
+ const caller = await this.resolveAudienceCaller(environmentId, req, {
2634
+ needPermissionSets: _RestServer.anyPermissionSetAudience(books)
2635
+ });
2636
+ let filtered;
2637
+ if (caller.authenticated && !_RestServer.anyPermissionSetAudience(books)) {
2638
+ filtered = list;
2639
+ } else {
2640
+ const corpus = list.filter((d) => d && typeof d === "object").map((d) => ({
2641
+ name: d.name,
2642
+ group: d.group,
2643
+ tags: d.tags,
2644
+ order: d.order,
2645
+ packageId: d._packageId
2646
+ }));
2647
+ const audiences = resolveDocAudiences(books, corpus);
2648
+ filtered = list.filter((d) => {
2649
+ if (!d || typeof d !== "object") return false;
2650
+ const eff = audiences.get(d.name);
2651
+ return eff ? docAudienceAllows(eff, caller) : audienceAllows("org", caller);
2652
+ });
2653
+ }
2654
+ visible = Array.isArray(raw) ? filtered : { ...raw, items: filtered };
2655
+ }
2656
+ }
2536
2657
  if (req.params.type === "doc") {
2537
2658
  const locale = this.extractLocale(req);
2538
2659
  const { resolveDocLocale } = await import("@objectstack/spec/system");
@@ -2606,7 +2727,7 @@ var RestServer = class {
2606
2727
  const prot = await this.resolveProtocol(environmentId, req);
2607
2728
  const locale = this.extractLocale(req);
2608
2729
  const packageId = req.query?.package || void 0;
2609
- const { resolveBookTree, deriveImplicitPackageBook, isPublicAudience, resolveDocLocale } = await import("@objectstack/spec/system");
2730
+ const { resolveBookTree, deriveImplicitPackageBook, audienceAllows, resolveDocAudiences, docAudienceAllows, resolveDocLocale } = await import("@objectstack/spec/system");
2610
2731
  const norm = (raw) => Array.isArray(raw) ? raw : raw && Array.isArray(raw.items) ? raw.items : [];
2611
2732
  const books = norm(await prot.getMetaItems({
2612
2733
  type: "book",
@@ -2617,9 +2738,16 @@ var RestServer = class {
2617
2738
  if (!book) {
2618
2739
  book = deriveImplicitPackageBook(req.params.name, req.params.name);
2619
2740
  }
2620
- const ctx = await this.resolveExecCtx(environmentId, req).catch(() => void 0);
2621
- if (!ctx?.userId && !isPublicAudience(book.audience)) {
2622
- sendError(res, { code: "unauthorized", message: "This documentation requires sign-in", status: 401 });
2741
+ const audienceBooks = books.map((b) => b && typeof b === "object" ? { ...b, packageId: b._packageId } : b);
2742
+ const caller = await this.resolveAudienceCaller(environmentId, req, {
2743
+ needPermissionSets: _RestServer.anyPermissionSetAudience([book, ...audienceBooks])
2744
+ });
2745
+ if (!audienceAllows(book.audience, caller)) {
2746
+ if (!caller.authenticated) {
2747
+ sendError(res, { code: "unauthorized", message: "This documentation requires sign-in", status: 401 });
2748
+ } else {
2749
+ sendError(res, { code: "PERMISSION_DENIED", message: "This documentation is limited to holders of a permission set you do not have", status: 403 });
2750
+ }
2623
2751
  return;
2624
2752
  }
2625
2753
  const docs = norm(await prot.getMetaItems({
@@ -2636,6 +2764,14 @@ var RestServer = class {
2636
2764
  packageId: d._packageId
2637
2765
  }));
2638
2766
  const tree = resolveBookTree(book, docs, book._packageId);
2767
+ const gatedTreePossible = !caller.authenticated || _RestServer.anyPermissionSetAudience(audienceBooks);
2768
+ if (gatedTreePossible) {
2769
+ const audiences = resolveDocAudiences(audienceBooks, docs);
2770
+ tree.groups = tree.groups.map((g) => ({
2771
+ ...g,
2772
+ entries: g.entries.filter((e) => !e.doc || docAudienceAllows(audiences.get(e.doc), caller))
2773
+ })).filter((g) => g.entries.some((e) => e.doc || e.href));
2774
+ }
2639
2775
  res.json(tree);
2640
2776
  } catch (error) {
2641
2777
  logError("[REST] Unhandled error:", error);
@@ -2670,7 +2806,7 @@ var RestServer = class {
2670
2806
  const isDraftRead = typeof req.query?.state === "string" && req.query.state.toLowerCase() === "draft";
2671
2807
  const previewDrafts = typeof req.query?.preview === "string" && req.query.preview.toLowerCase() === "draft";
2672
2808
  const packageScoped = typeof req.query?.package === "string" && req.query.package.length > 0;
2673
- if (metadata.enableCache && p.getMetaItemCached && !isAppType && !isDraftRead && !previewDrafts && !packageScoped && req.params.type !== "doc") {
2809
+ if (metadata.enableCache && p.getMetaItemCached && !isAppType && !isDraftRead && !previewDrafts && !packageScoped && req.params.type !== "doc" && req.params.type !== "book") {
2674
2810
  const cacheRequest = {
2675
2811
  ifNoneMatch: req.headers["if-none-match"],
2676
2812
  ifModifiedSince: req.headers["if-modified-since"]
@@ -2739,6 +2875,47 @@ var RestServer = class {
2739
2875
  const serviceGate = registered ? (n) => registered.has(n) : void 0;
2740
2876
  if (serviceGate) visible = this.filterDashboardForUser(visible, serviceGate);
2741
2877
  }
2878
+ if ((req.params.type === "book" || req.params.type === "doc") && visible) {
2879
+ const { audienceAllows, docAudienceAllows, resolveDocAudiences } = await import("@objectstack/spec/system");
2880
+ const target = isMetaEnvelope(visible) ? visible.item : visible;
2881
+ let caller;
2882
+ let allowed;
2883
+ if (req.params.type === "book") {
2884
+ caller = await this.resolveAudienceCaller(environmentId, req, {
2885
+ needPermissionSets: _RestServer.anyPermissionSetAudience([target])
2886
+ });
2887
+ allowed = audienceAllows(target?.audience, caller);
2888
+ } else {
2889
+ const books = await this.fetchAudienceBooks(p, environmentId);
2890
+ caller = await this.resolveAudienceCaller(environmentId, req, {
2891
+ needPermissionSets: _RestServer.anyPermissionSetAudience(books)
2892
+ });
2893
+ if (caller.authenticated && !_RestServer.anyPermissionSetAudience(books)) {
2894
+ allowed = true;
2895
+ } else {
2896
+ const corpus = _RestServer.metaItemsArray(await p.getMetaItems({
2897
+ type: "doc",
2898
+ ...environmentId ? { environmentId } : {}
2899
+ }).catch(() => [])).filter((d) => d && typeof d === "object").map((d) => ({
2900
+ name: d.name,
2901
+ group: d.group,
2902
+ tags: d.tags,
2903
+ order: d.order,
2904
+ packageId: d._packageId
2905
+ }));
2906
+ const audiences = resolveDocAudiences(books, corpus);
2907
+ allowed = docAudienceAllows(audiences.get(target?.name), caller);
2908
+ }
2909
+ }
2910
+ if (!allowed) {
2911
+ if (!caller.authenticated) {
2912
+ sendError(res, { code: "unauthorized", message: "This documentation requires sign-in", status: 401 });
2913
+ } else {
2914
+ sendError(res, { code: "PERMISSION_DENIED", message: "This documentation is limited to holders of a permission set you do not have", status: 403 });
2915
+ }
2916
+ return;
2917
+ }
2918
+ }
2742
2919
  if (req.params.type === "doc" && visible) {
2743
2920
  const locale = this.extractLocale(req);
2744
2921
  const { resolveDocLocale } = await import("@objectstack/spec/system");
@@ -3739,9 +3916,11 @@ var RestServer = class {
3739
3916
  const includeHeader = String(q.header ?? "true").toLowerCase() !== "false";
3740
3917
  const HARD_CAP = 5e4;
3741
3918
  const MAX_CHUNK = 5e3;
3919
+ const STYLE_ROW_CAP = 1e4;
3742
3920
  const requestedLimit = q.limit != null ? Math.max(1, Number(q.limit) || 0) : 1e4;
3743
3921
  const limit = Math.min(requestedLimit, HARD_CAP);
3744
3922
  const chunkSize = Math.min(MAX_CHUNK, Math.max(50, q.page != null ? Number(q.page) || 500 : 500));
3923
+ const styled = format === "xlsx" && limit <= STYLE_ROW_CAP;
3745
3924
  let filter = void 0;
3746
3925
  if (typeof q.filter === "string" && q.filter.length > 0) {
3747
3926
  try {
@@ -3808,12 +3987,13 @@ var RestServer = class {
3808
3987
  }
3809
3988
  res.header("X-Export-Format", format);
3810
3989
  res.header("X-Export-Limit", String(limit));
3990
+ if (format === "xlsx") res.header("X-Export-Styles", styled ? "applied" : "dropped");
3811
3991
  res.header("Cache-Control", "no-store");
3812
3992
  let exported = 0;
3813
3993
  let firstChunk = true;
3814
3994
  let skip = 0;
3815
3995
  if (format === "json") res.write("[");
3816
- const xlsx = format === "xlsx" ? await createXlsxStream(res) : null;
3996
+ const xlsx = format === "xlsx" ? await createXlsxStream(res, styled) : null;
3817
3997
  while (exported < limit) {
3818
3998
  const take = Math.min(chunkSize, limit - exported);
3819
3999
  const findArgs = {
@@ -3841,8 +4021,16 @@ var RestServer = class {
3841
4021
  if (firstChunk && includeHeader) {
3842
4022
  xlsx.ws.addRow((fields ?? []).map((f) => headerLabel(f, metaMap))).commit();
3843
4023
  }
4024
+ const cols = fields ?? [];
3844
4025
  for (const row of rows) {
3845
- xlsx.ws.addRow(formatRowCells(row, fields ?? [], metaMap)).commit();
4026
+ const r = xlsx.ws.addRow(formatRowCells(row, cols, metaMap));
4027
+ if (styled) {
4028
+ cols.forEach((f, i) => {
4029
+ const argb = cellFontColor(row?.[f], metaMap.get(f));
4030
+ if (argb) r.getCell(i + 1).font = { color: { argb } };
4031
+ });
4032
+ }
4033
+ r.commit();
3846
4034
  }
3847
4035
  } else {
3848
4036
  for (let i = 0; i < rows.length; i++) {
@@ -4471,6 +4659,100 @@ var RestServer = class {
4471
4659
  metadata: { summary: "Run a semantic-layer dataset (preview/query)", tags: ["analytics"] }
4472
4660
  });
4473
4661
  }
4662
+ /**
4663
+ * [ADR-0090 D6] Access-explanation endpoint — the REST face of the
4664
+ * explain engine (framework#2696).
4665
+ *
4666
+ * GET {basePath}/security/explain?object=…&operation=…&userId=…
4667
+ * POST {basePath}/security/explain body: { object, operation, userId? }
4668
+ *
4669
+ * Delegates to the security service's `explain(request, callerContext)`
4670
+ * (`SecurityPlugin.explainAccessForCaller`) — the same code paths the
4671
+ * enforcement middleware runs, so the report is explained by
4672
+ * construction. Caller authorization lives in the SERVICE, not here:
4673
+ * explaining ANOTHER user requires `manage_users` or a delegated
4674
+ * `adminScope` covering that user (D12); the service's
4675
+ * `PermissionDeniedError` maps to 403. The route itself only insists on
4676
+ * an authenticated caller (an access report is sensitive even about
4677
+ * oneself, and the anonymous `guest` posture is not this endpoint's
4678
+ * business) and returns 501 when no security service exposing `explain`
4679
+ * is mounted (a deployment without `@objectstack/plugin-security`).
4680
+ */
4681
+ registerSecurityExplainEndpoints(basePath) {
4682
+ const isScoped = basePath.includes("/environments/:environmentId");
4683
+ const resolveService = async (environmentId, req) => {
4684
+ try {
4685
+ const envId = await this.resolveRequestEnvironmentId(environmentId, req);
4686
+ if (envId && envId !== "platform" && this.kernelManager) {
4687
+ const kernel = await this.kernelManager.getOrCreate(envId);
4688
+ const svc = await kernel.getServiceAsync("security").catch(() => void 0);
4689
+ if (svc) return svc;
4690
+ }
4691
+ } catch {
4692
+ }
4693
+ if (!this.securityServiceProvider) return void 0;
4694
+ try {
4695
+ return await this.securityServiceProvider(environmentId);
4696
+ } catch {
4697
+ return void 0;
4698
+ }
4699
+ };
4700
+ const handler = async (req, res) => {
4701
+ try {
4702
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
4703
+ const context = await this.resolveExecCtx(environmentId, req);
4704
+ if (this.enforceAuth(req, res, context)) return;
4705
+ if (!context?.userId) {
4706
+ return res.status(401).json({
4707
+ code: "UNAUTHORIZED",
4708
+ message: "The access-explanation endpoint requires an authenticated caller."
4709
+ });
4710
+ }
4711
+ const svc = await resolveService(environmentId, req);
4712
+ if (!svc || typeof svc.explain !== "function") {
4713
+ return res.status(501).json({
4714
+ code: "NOT_IMPLEMENTED",
4715
+ message: "Access explanation is not available on this deployment (no security service with explain)."
4716
+ });
4717
+ }
4718
+ const src = req.method === "GET" ? req.query ?? {} : req.body ?? {};
4719
+ const { ExplainRequestSchema } = await import("@objectstack/spec/security");
4720
+ const parsed = ExplainRequestSchema.safeParse({
4721
+ object: src.object,
4722
+ operation: src.operation ?? "read",
4723
+ ...src.userId != null && src.userId !== "" ? { userId: src.userId } : {}
4724
+ });
4725
+ if (!parsed.success) {
4726
+ return res.status(400).json({
4727
+ code: "VALIDATION_FAILED",
4728
+ message: "Invalid explain request \u2014 expected { object: string, operation: read|create|update|delete|transfer|restore|purge, userId?: string }.",
4729
+ detail: String(parsed.error?.message ?? "").slice(0, 1e3)
4730
+ });
4731
+ }
4732
+ const decision = await svc.explain(parsed.data, context);
4733
+ res.json(decision);
4734
+ } catch (error) {
4735
+ const msg = String(error?.message ?? error ?? "");
4736
+ if (error?.code === "PERMISSION_DENIED" || error?.name === "PermissionDeniedError" || msg.startsWith("[Security] Access denied")) {
4737
+ return res.status(403).json({ code: "PERMISSION_DENIED", message: msg.slice(0, 1e3) });
4738
+ }
4739
+ logError("[REST] Security explain error:", error);
4740
+ res.status(500).json({ code: "EXPLAIN_FAILED", error: msg.slice(0, 500) });
4741
+ }
4742
+ };
4743
+ this.routeManager.register({
4744
+ method: "GET",
4745
+ path: `${basePath}/security/explain`,
4746
+ handler,
4747
+ metadata: { summary: "Explain why a principal can (or cannot) perform an operation on an object (ADR-0090 D6)", tags: ["security"] }
4748
+ });
4749
+ this.routeManager.register({
4750
+ method: "POST",
4751
+ path: `${basePath}/security/explain`,
4752
+ handler,
4753
+ metadata: { summary: "Explain why a principal can (or cannot) perform an operation on an object (ADR-0090 D6)", tags: ["security"] }
4754
+ });
4755
+ }
4474
4756
  registerSharingEndpoints(basePath) {
4475
4757
  const { crud } = this.config;
4476
4758
  const dataPath = `${basePath}${crud.dataPrefix}`;
@@ -4713,6 +4995,106 @@ var RestServer = class {
4713
4995
  metadata: { summary: "Re-evaluate a sharing rule and reconcile grants", tags: ["sharing"] }
4714
4996
  });
4715
4997
  }
4998
+ /**
4999
+ * Register the security admin endpoints (ADR-0090 D5/D9) — suggested
5000
+ * audience bindings. A package permission set declaring `isDefault: true`
5001
+ * is an install-time SUGGESTION to bind it to the `everyone` position;
5002
+ * these routes surface pending suggestions and let a tenant admin resolve
5003
+ * them. The `security` service (plugin-security) does the real gating:
5004
+ * tenant-admin pre-check on all three, and confirm writes the binding
5005
+ * with the caller's execution context so the audience-anchor and
5006
+ * delegated-admin gates enforce it — never auto-bound, never system.
5007
+ *
5008
+ * GET {basePath}/security/suggested-bindings?status=&packageId=
5009
+ * POST {basePath}/security/suggested-bindings/:id/confirm
5010
+ * POST {basePath}/security/suggested-bindings/:id/dismiss
5011
+ *
5012
+ * Routes return 501 when the `security` service is not registered
5013
+ * (deployment without plugin-security). Typed service errors carry their
5014
+ * HTTP status (403 permission / 404 not found / 409 state).
5015
+ */
5016
+ registerSecurityEndpoints(basePath) {
5017
+ const dataPath = basePath;
5018
+ const isScoped = basePath.includes("/environments/:environmentId");
5019
+ const resolveService = async (environmentId) => {
5020
+ if (!this.securityServiceProvider) return void 0;
5021
+ try {
5022
+ const svc = await this.securityServiceProvider(environmentId);
5023
+ return svc && typeof svc.listAudienceBindingSuggestions === "function" ? svc : void 0;
5024
+ } catch {
5025
+ return void 0;
5026
+ }
5027
+ };
5028
+ const respond501 = (res) => res.status(501).json({
5029
+ code: "NOT_IMPLEMENTED",
5030
+ message: "Security service is not configured on this deployment"
5031
+ });
5032
+ const handleError = (err, res, defaultCode) => {
5033
+ const status = typeof err?.statusCode === "number" ? err.statusCode : 500;
5034
+ if (status !== 500) {
5035
+ return res.status(status).json({ code: err?.code ?? defaultCode, error: String(err?.message ?? err) });
5036
+ }
5037
+ logError(`[REST] suggested-bindings ${defaultCode}:`, err);
5038
+ return res.status(500).json({ code: defaultCode, error: String(err?.message ?? err).slice(0, 500) });
5039
+ };
5040
+ this.routeManager.register({
5041
+ method: "GET",
5042
+ path: `${dataPath}/security/suggested-bindings`,
5043
+ handler: async (req, res) => {
5044
+ try {
5045
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
5046
+ const context = await this.resolveExecCtx(environmentId, req);
5047
+ if (this.enforceAuth(req, res, context)) return;
5048
+ const svc = await resolveService(environmentId);
5049
+ if (!svc) return respond501(res);
5050
+ const result = await svc.listAudienceBindingSuggestions(context ?? {}, {
5051
+ status: req.query?.status ? String(req.query.status) : void 0,
5052
+ packageId: req.query?.packageId ? String(req.query.packageId) : void 0
5053
+ });
5054
+ res.json({ data: result });
5055
+ } catch (err) {
5056
+ handleError(err, res, "SUGGESTION_LIST_FAILED");
5057
+ }
5058
+ },
5059
+ metadata: { summary: "List suggested audience bindings (ADR-0090 D5/D9)", tags: ["security"] }
5060
+ });
5061
+ this.routeManager.register({
5062
+ method: "POST",
5063
+ path: `${dataPath}/security/suggested-bindings/:id/confirm`,
5064
+ handler: async (req, res) => {
5065
+ try {
5066
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
5067
+ const context = await this.resolveExecCtx(environmentId, req);
5068
+ if (this.enforceAuth(req, res, context)) return;
5069
+ const svc = await resolveService(environmentId);
5070
+ if (!svc) return respond501(res);
5071
+ const result = await svc.confirmAudienceBindingSuggestion(context ?? {}, String(req.params.id));
5072
+ res.json({ data: result });
5073
+ } catch (err) {
5074
+ handleError(err, res, "SUGGESTION_CONFIRM_FAILED");
5075
+ }
5076
+ },
5077
+ metadata: { summary: "Confirm a suggested audience binding (creates the everyone/guest binding)", tags: ["security"] }
5078
+ });
5079
+ this.routeManager.register({
5080
+ method: "POST",
5081
+ path: `${dataPath}/security/suggested-bindings/:id/dismiss`,
5082
+ handler: async (req, res) => {
5083
+ try {
5084
+ const environmentId = isScoped ? req.params?.environmentId : void 0;
5085
+ const context = await this.resolveExecCtx(environmentId, req);
5086
+ if (this.enforceAuth(req, res, context)) return;
5087
+ const svc = await resolveService(environmentId);
5088
+ if (!svc) return respond501(res);
5089
+ const result = await svc.dismissAudienceBindingSuggestion(context ?? {}, String(req.params.id));
5090
+ res.json({ data: result });
5091
+ } catch (err) {
5092
+ handleError(err, res, "SUGGESTION_DISMISS_FAILED");
5093
+ }
5094
+ },
5095
+ metadata: { summary: "Dismiss a suggested audience binding", tags: ["security"] }
5096
+ });
5097
+ }
4716
5098
  /**
4717
5099
  * Register saved-report + scheduled-digest endpoints (M11.C16).
4718
5100
  *
@@ -5510,6 +5892,20 @@ function registerPackageRoutes(server, packageService, basePath = "/api/v1", opt
5510
5892
  try {
5511
5893
  const packageId = req.params.id;
5512
5894
  const version = req.query?.version;
5895
+ if (!version && typeof options.protocol?.deletePackage === "function") {
5896
+ const result2 = await options.protocol.deletePackage({ packageId });
5897
+ if (result2.failedCount === 0) {
5898
+ res.json({
5899
+ success: true,
5900
+ message: `Deleted ${packageId}`,
5901
+ deletedCount: result2.deletedCount,
5902
+ cleanups: result2.cleanups
5903
+ });
5904
+ return;
5905
+ }
5906
+ res.status(400).json({ success: false, failed: result2.failed, cleanups: result2.cleanups });
5907
+ return;
5908
+ }
5513
5909
  const result = await packageService.delete(packageId, version);
5514
5910
  if (result.success) {
5515
5911
  res.json({
@@ -5709,6 +6105,13 @@ function createRestApiPlugin(config = {}) {
5709
6105
  return void 0;
5710
6106
  }
5711
6107
  };
6108
+ const securityServiceProvider = async (_environmentId) => {
6109
+ try {
6110
+ return ctx.getService("security");
6111
+ } catch {
6112
+ return void 0;
6113
+ }
6114
+ };
5712
6115
  if (!server) {
5713
6116
  ctx.logger.warn(`RestApiPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);
5714
6117
  return;
@@ -5726,7 +6129,7 @@ function createRestApiPlugin(config = {}) {
5726
6129
  }
5727
6130
  };
5728
6131
  try {
5729
- const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider, serviceExistsProvider);
6132
+ const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider, serviceExistsProvider, securityServiceProvider);
5730
6133
  restServer.registerRoutes();
5731
6134
  ctx.logger.info("REST API successfully registered");
5732
6135
  if (config.api?.api?.requireAuth === false) {
@@ -5781,6 +6184,13 @@ export {
5781
6184
  RestServer,
5782
6185
  RouteGroupBuilder,
5783
6186
  RouteManager,
5784
- createRestApiPlugin
6187
+ buildFieldMetaMap,
6188
+ coerceRow,
6189
+ createRestApiPlugin,
6190
+ isMetaEnvelope,
6191
+ parseCsvToRows,
6192
+ parseXlsxToRows,
6193
+ prepareImportRequest,
6194
+ runImport
5785
6195
  };
5786
6196
  //# sourceMappingURL=index.js.map