@momentumcms/server-express 0.5.9 → 0.5.11

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/index.cjs CHANGED
@@ -175,7 +175,7 @@ function s3StorageAdapter(options) {
175
175
  baseUrl,
176
176
  forcePathStyle = false,
177
177
  acl = "private",
178
- presignedUrlExpiry = 3600
178
+ presignedUrlExpiry = 900
179
179
  } = options;
180
180
  let client = null;
181
181
  async function getClient() {
@@ -569,14 +569,14 @@ var init_strip_artifacts = __esm({
569
569
 
570
570
  // libs/email/src/lib/render/render-types.ts
571
571
  function injectEmailData() {
572
- return (0, import_core12.inject)(EMAIL_DATA);
572
+ return (0, import_core13.inject)(EMAIL_DATA);
573
573
  }
574
- var import_core12, EMAIL_DATA;
574
+ var import_core13, EMAIL_DATA;
575
575
  var init_render_types = __esm({
576
576
  "libs/email/src/lib/render/render-types.ts"() {
577
577
  "use strict";
578
- import_core12 = require("@angular/core");
579
- EMAIL_DATA = new import_core12.InjectionToken("EMAIL_DATA");
578
+ import_core13 = require("@angular/core");
579
+ EMAIL_DATA = new import_core13.InjectionToken("EMAIL_DATA");
580
580
  }
581
581
  });
582
582
 
@@ -588,7 +588,7 @@ async function renderEmail(component, data, options) {
588
588
  const shouldInlineCss = options?.inlineCss ?? true;
589
589
  const shouldStripArtifacts = options?.stripArtifacts ?? true;
590
590
  const extraProviders = options?.providers ?? [];
591
- const mirror = (0, import_core13.reflectComponentType)(component);
591
+ const mirror = (0, import_core14.reflectComponentType)(component);
592
592
  if (!mirror) {
593
593
  throw new Error(
594
594
  `Cannot reflect component type: ${component.name}. Ensure it has a @Component decorator.`
@@ -610,12 +610,12 @@ async function renderEmail(component, data, options) {
610
610
  }
611
611
  return html;
612
612
  }
613
- var import_compiler, import_core13, import_platform_server, import_platform_browser;
613
+ var import_compiler, import_core14, import_platform_server, import_platform_browser;
614
614
  var init_render_email = __esm({
615
615
  "libs/email/src/lib/render/render-email.ts"() {
616
616
  "use strict";
617
617
  import_compiler = require("@angular/compiler");
618
- import_core13 = require("@angular/core");
618
+ import_core14 = require("@angular/core");
619
619
  import_platform_server = require("@angular/platform-server");
620
620
  import_platform_browser = require("@angular/platform-browser");
621
621
  init_css_inliner();
@@ -1048,26 +1048,26 @@ var init_default_templates = __esm({
1048
1048
  });
1049
1049
 
1050
1050
  // libs/email/src/lib/components/eml-body.component.ts
1051
- var import_core14, EmlBody;
1051
+ var import_core15, EmlBody;
1052
1052
  var init_eml_body_component = __esm({
1053
1053
  "libs/email/src/lib/components/eml-body.component.ts"() {
1054
1054
  "use strict";
1055
- import_core14 = require("@angular/core");
1055
+ import_core15 = require("@angular/core");
1056
1056
  EmlBody = class {
1057
1057
  constructor() {
1058
- this.backgroundColor = (0, import_core14.input)("#f4f4f5");
1059
- this.fontFamily = (0, import_core14.input)(
1058
+ this.backgroundColor = (0, import_core15.input)("#f4f4f5");
1059
+ this.fontFamily = (0, import_core15.input)(
1060
1060
  "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
1061
1061
  );
1062
- this.padding = (0, import_core14.input)("40px 20px");
1063
- this.tableStyle = (0, import_core14.computed)(
1062
+ this.padding = (0, import_core15.input)("40px 20px");
1063
+ this.tableStyle = (0, import_core15.computed)(
1064
1064
  () => `background-color: ${this.backgroundColor()}; font-family: ${this.fontFamily()}; line-height: 1.6; margin: 0; padding: 0;`
1065
1065
  );
1066
- this.cellStyle = (0, import_core14.computed)(() => `padding: ${this.padding()};`);
1066
+ this.cellStyle = (0, import_core15.computed)(() => `padding: ${this.padding()};`);
1067
1067
  }
1068
1068
  };
1069
1069
  EmlBody = __decorateClass([
1070
- (0, import_core14.Component)({
1070
+ (0, import_core15.Component)({
1071
1071
  selector: "eml-body",
1072
1072
  template: `
1073
1073
  <table
@@ -1084,33 +1084,33 @@ var init_eml_body_component = __esm({
1084
1084
  </tr>
1085
1085
  </table>
1086
1086
  `,
1087
- changeDetection: import_core14.ChangeDetectionStrategy.OnPush
1087
+ changeDetection: import_core15.ChangeDetectionStrategy.OnPush
1088
1088
  })
1089
1089
  ], EmlBody);
1090
1090
  }
1091
1091
  });
1092
1092
 
1093
1093
  // libs/email/src/lib/components/eml-container.component.ts
1094
- var import_core15, EmlContainer;
1094
+ var import_core16, EmlContainer;
1095
1095
  var init_eml_container_component = __esm({
1096
1096
  "libs/email/src/lib/components/eml-container.component.ts"() {
1097
1097
  "use strict";
1098
- import_core15 = require("@angular/core");
1098
+ import_core16 = require("@angular/core");
1099
1099
  EmlContainer = class {
1100
1100
  constructor() {
1101
- this.maxWidth = (0, import_core15.input)("480px");
1102
- this.backgroundColor = (0, import_core15.input)("#ffffff");
1103
- this.borderRadius = (0, import_core15.input)("8px");
1104
- this.padding = (0, import_core15.input)("40px");
1105
- this.shadow = (0, import_core15.input)("0 1px 3px rgba(0,0,0,0.1)");
1106
- this.tableStyle = (0, import_core15.computed)(
1101
+ this.maxWidth = (0, import_core16.input)("480px");
1102
+ this.backgroundColor = (0, import_core16.input)("#ffffff");
1103
+ this.borderRadius = (0, import_core16.input)("8px");
1104
+ this.padding = (0, import_core16.input)("40px");
1105
+ this.shadow = (0, import_core16.input)("0 1px 3px rgba(0,0,0,0.1)");
1106
+ this.tableStyle = (0, import_core16.computed)(
1107
1107
  () => `max-width: ${this.maxWidth()}; margin: 0 auto; background-color: ${this.backgroundColor()}; border-radius: ${this.borderRadius()}; box-shadow: ${this.shadow()};`
1108
1108
  );
1109
- this.cellStyle = (0, import_core15.computed)(() => `padding: ${this.padding()};`);
1109
+ this.cellStyle = (0, import_core16.computed)(() => `padding: ${this.padding()};`);
1110
1110
  }
1111
1111
  };
1112
1112
  EmlContainer = __decorateClass([
1113
- (0, import_core15.Component)({
1113
+ (0, import_core16.Component)({
1114
1114
  selector: "eml-container",
1115
1115
  template: `
1116
1116
  <table
@@ -1127,26 +1127,26 @@ var init_eml_container_component = __esm({
1127
1127
  </tr>
1128
1128
  </table>
1129
1129
  `,
1130
- changeDetection: import_core15.ChangeDetectionStrategy.OnPush
1130
+ changeDetection: import_core16.ChangeDetectionStrategy.OnPush
1131
1131
  })
1132
1132
  ], EmlContainer);
1133
1133
  }
1134
1134
  });
1135
1135
 
1136
1136
  // libs/email/src/lib/components/eml-section.component.ts
1137
- var import_core16, EmlSection;
1137
+ var import_core17, EmlSection;
1138
1138
  var init_eml_section_component = __esm({
1139
1139
  "libs/email/src/lib/components/eml-section.component.ts"() {
1140
1140
  "use strict";
1141
- import_core16 = require("@angular/core");
1141
+ import_core17 = require("@angular/core");
1142
1142
  EmlSection = class {
1143
1143
  constructor() {
1144
- this.padding = (0, import_core16.input)("0");
1145
- this.cellStyle = (0, import_core16.computed)(() => `padding: ${this.padding()};`);
1144
+ this.padding = (0, import_core17.input)("0");
1145
+ this.cellStyle = (0, import_core17.computed)(() => `padding: ${this.padding()};`);
1146
1146
  }
1147
1147
  };
1148
1148
  EmlSection = __decorateClass([
1149
- (0, import_core16.Component)({
1149
+ (0, import_core17.Component)({
1150
1150
  selector: "eml-section",
1151
1151
  template: `
1152
1152
  <table role="presentation" width="100%" cellspacing="0" cellpadding="0">
@@ -1157,22 +1157,22 @@ var init_eml_section_component = __esm({
1157
1157
  </tr>
1158
1158
  </table>
1159
1159
  `,
1160
- changeDetection: import_core16.ChangeDetectionStrategy.OnPush
1160
+ changeDetection: import_core17.ChangeDetectionStrategy.OnPush
1161
1161
  })
1162
1162
  ], EmlSection);
1163
1163
  }
1164
1164
  });
1165
1165
 
1166
1166
  // libs/email/src/lib/components/eml-row.component.ts
1167
- var import_core17, EmlRow;
1167
+ var import_core18, EmlRow;
1168
1168
  var init_eml_row_component = __esm({
1169
1169
  "libs/email/src/lib/components/eml-row.component.ts"() {
1170
1170
  "use strict";
1171
- import_core17 = require("@angular/core");
1171
+ import_core18 = require("@angular/core");
1172
1172
  EmlRow = class {
1173
1173
  };
1174
1174
  EmlRow = __decorateClass([
1175
- (0, import_core17.Component)({
1175
+ (0, import_core18.Component)({
1176
1176
  selector: "eml-row",
1177
1177
  template: `
1178
1178
  <table role="presentation" width="100%" cellspacing="0" cellpadding="0">
@@ -1181,130 +1181,130 @@ var init_eml_row_component = __esm({
1181
1181
  </tr>
1182
1182
  </table>
1183
1183
  `,
1184
- changeDetection: import_core17.ChangeDetectionStrategy.OnPush
1184
+ changeDetection: import_core18.ChangeDetectionStrategy.OnPush
1185
1185
  })
1186
1186
  ], EmlRow);
1187
1187
  }
1188
1188
  });
1189
1189
 
1190
1190
  // libs/email/src/lib/components/eml-column.component.ts
1191
- var import_core18, EmlColumn;
1191
+ var import_core19, EmlColumn;
1192
1192
  var init_eml_column_component = __esm({
1193
1193
  "libs/email/src/lib/components/eml-column.component.ts"() {
1194
1194
  "use strict";
1195
- import_core18 = require("@angular/core");
1195
+ import_core19 = require("@angular/core");
1196
1196
  EmlColumn = class {
1197
1197
  constructor() {
1198
- this.width = (0, import_core18.input)(void 0);
1199
- this.padding = (0, import_core18.input)("0");
1200
- this.verticalAlign = (0, import_core18.input)("top");
1201
- this.cellStyle = (0, import_core18.computed)(
1198
+ this.width = (0, import_core19.input)(void 0);
1199
+ this.padding = (0, import_core19.input)("0");
1200
+ this.verticalAlign = (0, import_core19.input)("top");
1201
+ this.cellStyle = (0, import_core19.computed)(
1202
1202
  () => `padding: ${this.padding()}; vertical-align: ${this.verticalAlign()};`
1203
1203
  );
1204
1204
  }
1205
1205
  };
1206
1206
  EmlColumn = __decorateClass([
1207
- (0, import_core18.Component)({
1207
+ (0, import_core19.Component)({
1208
1208
  selector: "eml-column",
1209
1209
  template: `
1210
1210
  <td [attr.style]="cellStyle()" [attr.width]="width()" valign="top">
1211
1211
  <ng-content />
1212
1212
  </td>
1213
1213
  `,
1214
- changeDetection: import_core18.ChangeDetectionStrategy.OnPush
1214
+ changeDetection: import_core19.ChangeDetectionStrategy.OnPush
1215
1215
  })
1216
1216
  ], EmlColumn);
1217
1217
  }
1218
1218
  });
1219
1219
 
1220
1220
  // libs/email/src/lib/components/eml-text.component.ts
1221
- var import_core19, EmlText;
1221
+ var import_core20, EmlText;
1222
1222
  var init_eml_text_component = __esm({
1223
1223
  "libs/email/src/lib/components/eml-text.component.ts"() {
1224
1224
  "use strict";
1225
- import_core19 = require("@angular/core");
1225
+ import_core20 = require("@angular/core");
1226
1226
  EmlText = class {
1227
1227
  constructor() {
1228
- this.color = (0, import_core19.input)("#3f3f46");
1229
- this.fontSize = (0, import_core19.input)("16px");
1230
- this.lineHeight = (0, import_core19.input)("1.6");
1231
- this.margin = (0, import_core19.input)("0 0 16px");
1232
- this.textAlign = (0, import_core19.input)("left");
1233
- this.pStyle = (0, import_core19.computed)(
1228
+ this.color = (0, import_core20.input)("#3f3f46");
1229
+ this.fontSize = (0, import_core20.input)("16px");
1230
+ this.lineHeight = (0, import_core20.input)("1.6");
1231
+ this.margin = (0, import_core20.input)("0 0 16px");
1232
+ this.textAlign = (0, import_core20.input)("left");
1233
+ this.pStyle = (0, import_core20.computed)(
1234
1234
  () => `margin: ${this.margin()}; color: ${this.color()}; font-size: ${this.fontSize()}; line-height: ${this.lineHeight()}; text-align: ${this.textAlign()};`
1235
1235
  );
1236
1236
  }
1237
1237
  };
1238
1238
  EmlText = __decorateClass([
1239
- (0, import_core19.Component)({
1239
+ (0, import_core20.Component)({
1240
1240
  selector: "eml-text",
1241
1241
  template: `
1242
1242
  <p [attr.style]="pStyle()">
1243
1243
  <ng-content />
1244
1244
  </p>
1245
1245
  `,
1246
- changeDetection: import_core19.ChangeDetectionStrategy.OnPush
1246
+ changeDetection: import_core20.ChangeDetectionStrategy.OnPush
1247
1247
  })
1248
1248
  ], EmlText);
1249
1249
  }
1250
1250
  });
1251
1251
 
1252
1252
  // libs/email/src/lib/components/eml-heading.component.ts
1253
- var import_core20, EmlHeading;
1253
+ var import_core21, EmlHeading;
1254
1254
  var init_eml_heading_component = __esm({
1255
1255
  "libs/email/src/lib/components/eml-heading.component.ts"() {
1256
1256
  "use strict";
1257
- import_core20 = require("@angular/core");
1257
+ import_core21 = require("@angular/core");
1258
1258
  EmlHeading = class {
1259
1259
  constructor() {
1260
- this.level = (0, import_core20.input)(1);
1261
- this.color = (0, import_core20.input)("#18181b");
1262
- this.margin = (0, import_core20.input)("0 0 24px");
1263
- this.textAlign = (0, import_core20.input)("left");
1260
+ this.level = (0, import_core21.input)(1);
1261
+ this.color = (0, import_core21.input)("#18181b");
1262
+ this.margin = (0, import_core21.input)("0 0 24px");
1263
+ this.textAlign = (0, import_core21.input)("left");
1264
1264
  this.fontSizeMap = {
1265
1265
  1: "24px",
1266
1266
  2: "20px",
1267
1267
  3: "16px"
1268
1268
  };
1269
- this.headingStyle = (0, import_core20.computed)(
1269
+ this.headingStyle = (0, import_core21.computed)(
1270
1270
  () => `margin: ${this.margin()}; font-size: ${this.fontSizeMap[this.level()] ?? "24px"}; font-weight: 600; color: ${this.color()}; text-align: ${this.textAlign()};`
1271
1271
  );
1272
1272
  }
1273
1273
  };
1274
1274
  EmlHeading = __decorateClass([
1275
- (0, import_core20.Component)({
1275
+ (0, import_core21.Component)({
1276
1276
  selector: "eml-heading",
1277
1277
  template: `<div [attr.style]="headingStyle()" [attr.role]="'heading'" [attr.aria-level]="level()">
1278
1278
  <ng-content />
1279
1279
  </div>`,
1280
- changeDetection: import_core20.ChangeDetectionStrategy.OnPush
1280
+ changeDetection: import_core21.ChangeDetectionStrategy.OnPush
1281
1281
  })
1282
1282
  ], EmlHeading);
1283
1283
  }
1284
1284
  });
1285
1285
 
1286
1286
  // libs/email/src/lib/components/eml-button.component.ts
1287
- var import_core21, EmlButton;
1287
+ var import_core22, EmlButton;
1288
1288
  var init_eml_button_component = __esm({
1289
1289
  "libs/email/src/lib/components/eml-button.component.ts"() {
1290
1290
  "use strict";
1291
- import_core21 = require("@angular/core");
1291
+ import_core22 = require("@angular/core");
1292
1292
  EmlButton = class {
1293
1293
  constructor() {
1294
- this.href = (0, import_core21.input)("");
1295
- this.backgroundColor = (0, import_core21.input)("#18181b");
1296
- this.color = (0, import_core21.input)("#ffffff");
1297
- this.borderRadius = (0, import_core21.input)("6px");
1298
- this.padding = (0, import_core21.input)("12px 24px");
1299
- this.textAlign = (0, import_core21.input)("left");
1300
- this.alignStyle = (0, import_core21.computed)(() => `padding: 0; text-align: ${this.textAlign()};`);
1301
- this.linkStyle = (0, import_core21.computed)(
1294
+ this.href = (0, import_core22.input)("");
1295
+ this.backgroundColor = (0, import_core22.input)("#18181b");
1296
+ this.color = (0, import_core22.input)("#ffffff");
1297
+ this.borderRadius = (0, import_core22.input)("6px");
1298
+ this.padding = (0, import_core22.input)("12px 24px");
1299
+ this.textAlign = (0, import_core22.input)("left");
1300
+ this.alignStyle = (0, import_core22.computed)(() => `padding: 0; text-align: ${this.textAlign()};`);
1301
+ this.linkStyle = (0, import_core22.computed)(
1302
1302
  () => `display: inline-block; padding: ${this.padding()}; background-color: ${this.backgroundColor()}; color: ${this.color()}; text-decoration: none; border-radius: ${this.borderRadius()}; font-weight: 500;`
1303
1303
  );
1304
1304
  }
1305
1305
  };
1306
1306
  EmlButton = __decorateClass([
1307
- (0, import_core21.Component)({
1307
+ (0, import_core22.Component)({
1308
1308
  selector: "eml-button",
1309
1309
  template: `
1310
1310
  <table role="presentation" width="100%" cellspacing="0" cellpadding="0">
@@ -1317,62 +1317,62 @@ var init_eml_button_component = __esm({
1317
1317
  </tr>
1318
1318
  </table>
1319
1319
  `,
1320
- changeDetection: import_core21.ChangeDetectionStrategy.OnPush
1320
+ changeDetection: import_core22.ChangeDetectionStrategy.OnPush
1321
1321
  })
1322
1322
  ], EmlButton);
1323
1323
  }
1324
1324
  });
1325
1325
 
1326
1326
  // libs/email/src/lib/components/eml-link.component.ts
1327
- var import_core22, EmlLink;
1327
+ var import_core23, EmlLink;
1328
1328
  var init_eml_link_component = __esm({
1329
1329
  "libs/email/src/lib/components/eml-link.component.ts"() {
1330
1330
  "use strict";
1331
- import_core22 = require("@angular/core");
1331
+ import_core23 = require("@angular/core");
1332
1332
  EmlLink = class {
1333
1333
  constructor() {
1334
- this.href = (0, import_core22.input)("");
1335
- this.color = (0, import_core22.input)("#18181b");
1336
- this.textDecoration = (0, import_core22.input)("underline");
1337
- this.linkStyle = (0, import_core22.computed)(
1334
+ this.href = (0, import_core23.input)("");
1335
+ this.color = (0, import_core23.input)("#18181b");
1336
+ this.textDecoration = (0, import_core23.input)("underline");
1337
+ this.linkStyle = (0, import_core23.computed)(
1338
1338
  () => `color: ${this.color()}; text-decoration: ${this.textDecoration()};`
1339
1339
  );
1340
1340
  }
1341
1341
  };
1342
1342
  EmlLink = __decorateClass([
1343
- (0, import_core22.Component)({
1343
+ (0, import_core23.Component)({
1344
1344
  selector: "eml-link",
1345
1345
  template: `
1346
1346
  <a [attr.href]="href()" [attr.style]="linkStyle()" target="_blank">
1347
1347
  <ng-content />
1348
1348
  </a>
1349
1349
  `,
1350
- changeDetection: import_core22.ChangeDetectionStrategy.OnPush
1350
+ changeDetection: import_core23.ChangeDetectionStrategy.OnPush
1351
1351
  })
1352
1352
  ], EmlLink);
1353
1353
  }
1354
1354
  });
1355
1355
 
1356
1356
  // libs/email/src/lib/components/eml-image.component.ts
1357
- var import_core23, EmlImage;
1357
+ var import_core24, EmlImage;
1358
1358
  var init_eml_image_component = __esm({
1359
1359
  "libs/email/src/lib/components/eml-image.component.ts"() {
1360
1360
  "use strict";
1361
- import_core23 = require("@angular/core");
1361
+ import_core24 = require("@angular/core");
1362
1362
  EmlImage = class {
1363
1363
  constructor() {
1364
- this.src = (0, import_core23.input)("");
1365
- this.alt = (0, import_core23.input)("");
1366
- this.width = (0, import_core23.input)(void 0);
1367
- this.height = (0, import_core23.input)(void 0);
1368
- this.borderRadius = (0, import_core23.input)("0");
1369
- this.imgStyle = (0, import_core23.computed)(
1364
+ this.src = (0, import_core24.input)("");
1365
+ this.alt = (0, import_core24.input)("");
1366
+ this.width = (0, import_core24.input)(void 0);
1367
+ this.height = (0, import_core24.input)(void 0);
1368
+ this.borderRadius = (0, import_core24.input)("0");
1369
+ this.imgStyle = (0, import_core24.computed)(
1370
1370
  () => `display: block; max-width: 100%; border: 0; outline: none; border-radius: ${this.borderRadius()};`
1371
1371
  );
1372
1372
  }
1373
1373
  };
1374
1374
  EmlImage = __decorateClass([
1375
- (0, import_core23.Component)({
1375
+ (0, import_core24.Component)({
1376
1376
  selector: "eml-image",
1377
1377
  template: `
1378
1378
  <img
@@ -1383,104 +1383,104 @@ var init_eml_image_component = __esm({
1383
1383
  [attr.style]="imgStyle()"
1384
1384
  />
1385
1385
  `,
1386
- changeDetection: import_core23.ChangeDetectionStrategy.OnPush
1386
+ changeDetection: import_core24.ChangeDetectionStrategy.OnPush
1387
1387
  })
1388
1388
  ], EmlImage);
1389
1389
  }
1390
1390
  });
1391
1391
 
1392
1392
  // libs/email/src/lib/components/eml-divider.component.ts
1393
- var import_core24, EmlDivider;
1393
+ var import_core25, EmlDivider;
1394
1394
  var init_eml_divider_component = __esm({
1395
1395
  "libs/email/src/lib/components/eml-divider.component.ts"() {
1396
1396
  "use strict";
1397
- import_core24 = require("@angular/core");
1397
+ import_core25 = require("@angular/core");
1398
1398
  EmlDivider = class {
1399
1399
  constructor() {
1400
- this.color = (0, import_core24.input)("#e4e4e7");
1401
- this.margin = (0, import_core24.input)("24px 0");
1402
- this.hrStyle = (0, import_core24.computed)(
1400
+ this.color = (0, import_core25.input)("#e4e4e7");
1401
+ this.margin = (0, import_core25.input)("24px 0");
1402
+ this.hrStyle = (0, import_core25.computed)(
1403
1403
  () => `border: none; border-top: 1px solid ${this.color()}; margin: ${this.margin()};`
1404
1404
  );
1405
1405
  }
1406
1406
  };
1407
1407
  EmlDivider = __decorateClass([
1408
- (0, import_core24.Component)({
1408
+ (0, import_core25.Component)({
1409
1409
  selector: "eml-divider",
1410
1410
  template: `<hr [attr.style]="hrStyle()" />`,
1411
- changeDetection: import_core24.ChangeDetectionStrategy.OnPush
1411
+ changeDetection: import_core25.ChangeDetectionStrategy.OnPush
1412
1412
  })
1413
1413
  ], EmlDivider);
1414
1414
  }
1415
1415
  });
1416
1416
 
1417
1417
  // libs/email/src/lib/components/eml-preview.component.ts
1418
- var import_core25, EmlPreview;
1418
+ var import_core26, EmlPreview;
1419
1419
  var init_eml_preview_component = __esm({
1420
1420
  "libs/email/src/lib/components/eml-preview.component.ts"() {
1421
1421
  "use strict";
1422
- import_core25 = require("@angular/core");
1422
+ import_core26 = require("@angular/core");
1423
1423
  EmlPreview = class {
1424
1424
  };
1425
1425
  EmlPreview = __decorateClass([
1426
- (0, import_core25.Component)({
1426
+ (0, import_core26.Component)({
1427
1427
  selector: "eml-preview",
1428
1428
  template: `
1429
1429
  <div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
1430
1430
  <ng-content />
1431
1431
  </div>
1432
1432
  `,
1433
- changeDetection: import_core25.ChangeDetectionStrategy.OnPush
1433
+ changeDetection: import_core26.ChangeDetectionStrategy.OnPush
1434
1434
  })
1435
1435
  ], EmlPreview);
1436
1436
  }
1437
1437
  });
1438
1438
 
1439
1439
  // libs/email/src/lib/components/eml-spacer.component.ts
1440
- var import_core26, EmlSpacer;
1440
+ var import_core27, EmlSpacer;
1441
1441
  var init_eml_spacer_component = __esm({
1442
1442
  "libs/email/src/lib/components/eml-spacer.component.ts"() {
1443
1443
  "use strict";
1444
- import_core26 = require("@angular/core");
1444
+ import_core27 = require("@angular/core");
1445
1445
  EmlSpacer = class {
1446
1446
  constructor() {
1447
- this.height = (0, import_core26.input)("24px");
1448
- this.spacerStyle = (0, import_core26.computed)(
1447
+ this.height = (0, import_core27.input)("24px");
1448
+ this.spacerStyle = (0, import_core27.computed)(
1449
1449
  () => `height: ${this.height()}; line-height: ${this.height()}; font-size: 1px;`
1450
1450
  );
1451
1451
  }
1452
1452
  };
1453
1453
  EmlSpacer = __decorateClass([
1454
- (0, import_core26.Component)({
1454
+ (0, import_core27.Component)({
1455
1455
  selector: "eml-spacer",
1456
1456
  template: `<div [attr.style]="spacerStyle()"></div>`,
1457
- changeDetection: import_core26.ChangeDetectionStrategy.OnPush
1457
+ changeDetection: import_core27.ChangeDetectionStrategy.OnPush
1458
1458
  })
1459
1459
  ], EmlSpacer);
1460
1460
  }
1461
1461
  });
1462
1462
 
1463
1463
  // libs/email/src/lib/components/eml-footer.component.ts
1464
- var import_core27, EmlFooter;
1464
+ var import_core28, EmlFooter;
1465
1465
  var init_eml_footer_component = __esm({
1466
1466
  "libs/email/src/lib/components/eml-footer.component.ts"() {
1467
1467
  "use strict";
1468
- import_core27 = require("@angular/core");
1468
+ import_core28 = require("@angular/core");
1469
1469
  EmlFooter = class {
1470
1470
  constructor() {
1471
- this.maxWidth = (0, import_core27.input)("480px");
1472
- this.color = (0, import_core27.input)("#71717a");
1473
- this.fontSize = (0, import_core27.input)("12px");
1474
- this.textAlign = (0, import_core27.input)("center");
1475
- this.padding = (0, import_core27.input)("20px 0 0");
1476
- this.tableStyle = (0, import_core27.computed)(() => `max-width: ${this.maxWidth()}; margin: 0 auto;`);
1477
- this.cellStyle = (0, import_core27.computed)(
1471
+ this.maxWidth = (0, import_core28.input)("480px");
1472
+ this.color = (0, import_core28.input)("#71717a");
1473
+ this.fontSize = (0, import_core28.input)("12px");
1474
+ this.textAlign = (0, import_core28.input)("center");
1475
+ this.padding = (0, import_core28.input)("20px 0 0");
1476
+ this.tableStyle = (0, import_core28.computed)(() => `max-width: ${this.maxWidth()}; margin: 0 auto;`);
1477
+ this.cellStyle = (0, import_core28.computed)(
1478
1478
  () => `text-align: ${this.textAlign()}; color: ${this.color()}; font-size: ${this.fontSize()}; padding: ${this.padding()};`
1479
1479
  );
1480
1480
  }
1481
1481
  };
1482
1482
  EmlFooter = __decorateClass([
1483
- (0, import_core27.Component)({
1483
+ (0, import_core28.Component)({
1484
1484
  selector: "eml-footer",
1485
1485
  template: `
1486
1486
  <table
@@ -1497,7 +1497,7 @@ var init_eml_footer_component = __esm({
1497
1497
  </tr>
1498
1498
  </table>
1499
1499
  `,
1500
- changeDetection: import_core27.ChangeDetectionStrategy.OnPush
1500
+ changeDetection: import_core28.ChangeDetectionStrategy.OnPush
1501
1501
  })
1502
1502
  ], EmlFooter);
1503
1503
  }
@@ -1658,6 +1658,7 @@ __export(src_exports3, {
1658
1658
  createMomentumServer: () => createMomentumServer,
1659
1659
  createOpenAPIMiddleware: () => createOpenAPIMiddleware,
1660
1660
  createProtectMiddleware: () => createProtectMiddleware,
1661
+ createRateLimitMiddleware: () => createRateLimitMiddleware,
1661
1662
  createSessionResolverMiddleware: () => createSessionResolverMiddleware,
1662
1663
  createSetupMiddleware: () => createSetupMiddleware,
1663
1664
  getPluginMiddleware: () => getPluginMiddleware,
@@ -2789,6 +2790,21 @@ var VersionOperationsImpl = class {
2789
2790
  });
2790
2791
  }
2791
2792
  }
2793
+ if (!this.context.overrideAccess && hasFieldAccessControl(this.collectionConfig.fields)) {
2794
+ for (let i = 0; i < docs.length; i++) {
2795
+ const version = docs[i].version;
2796
+ if (version && typeof version === "object") {
2797
+ const versionRecord = version;
2798
+ const filtered = await filterReadableFields(
2799
+ this.collectionConfig.fields,
2800
+ versionRecord,
2801
+ this.buildRequestContext()
2802
+ );
2803
+ const filteredAsT = filtered;
2804
+ docs[i] = { ...docs[i], version: filteredAsT };
2805
+ }
2806
+ }
2807
+ }
2792
2808
  const countOptions = {
2793
2809
  includeAutosave: options?.includeAutosave,
2794
2810
  status: options?.status
@@ -2818,9 +2834,21 @@ var VersionOperationsImpl = class {
2818
2834
  if (parsedVersion === null) {
2819
2835
  return null;
2820
2836
  }
2837
+ let filteredVersion = parsedVersion;
2838
+ if (!this.context.overrideAccess && hasFieldAccessControl(this.collectionConfig.fields)) {
2839
+ if (filteredVersion && typeof filteredVersion === "object") {
2840
+ const versionRecord = filteredVersion;
2841
+ const filtered = await filterReadableFields(
2842
+ this.collectionConfig.fields,
2843
+ versionRecord,
2844
+ this.buildRequestContext()
2845
+ );
2846
+ filteredVersion = filtered;
2847
+ }
2848
+ }
2821
2849
  return {
2822
2850
  ...version,
2823
- version: parsedVersion
2851
+ version: filteredVersion
2824
2852
  };
2825
2853
  }
2826
2854
  async restore(options) {
@@ -3042,6 +3070,415 @@ var VersionOperationsImpl = class {
3042
3070
  }
3043
3071
  };
3044
3072
 
3073
+ // libs/server-core/src/lib/where-clause.ts
3074
+ var OPERATOR_MAP = {
3075
+ equals: "$eq",
3076
+ gt: "$gt",
3077
+ gte: "$gte",
3078
+ lt: "$lt",
3079
+ lte: "$lte",
3080
+ not_equals: "$ne",
3081
+ like: "$like",
3082
+ contains: "$contains",
3083
+ in: "$in",
3084
+ not_in: "$nin",
3085
+ exists: "$exists"
3086
+ };
3087
+ var MAX_WHERE_CONDITIONS = 20;
3088
+ var MAX_JOINS = 5;
3089
+ var MAX_WHERE_NESTING_DEPTH = 5;
3090
+ var MAX_PAGE_LIMIT = 1e3;
3091
+ var MAX_PAGE = 1e6;
3092
+ var VALID_OPERATORS = new Set(Object.keys(OPERATOR_MAP));
3093
+ function countWhereConditions(where, depth = 0) {
3094
+ if (depth > MAX_WHERE_NESTING_DEPTH) {
3095
+ throw new ValidationError([
3096
+ {
3097
+ field: "where",
3098
+ message: `Where clause nesting depth exceeds maximum of ${MAX_WHERE_NESTING_DEPTH} levels.`
3099
+ }
3100
+ ]);
3101
+ }
3102
+ let count = 0;
3103
+ for (const [key, value] of Object.entries(where)) {
3104
+ if ((key === "and" || key === "or") && Array.isArray(value)) {
3105
+ for (const sub of value) {
3106
+ if (typeof sub === "object" && sub !== null) {
3107
+ count += countWhereConditions(sub, depth + 1);
3108
+ }
3109
+ }
3110
+ } else {
3111
+ count++;
3112
+ }
3113
+ }
3114
+ return count;
3115
+ }
3116
+ function flattenWhereClause(where) {
3117
+ if (!where)
3118
+ return {};
3119
+ const fieldCount = countWhereConditions(where);
3120
+ if (fieldCount > MAX_WHERE_CONDITIONS) {
3121
+ throw new ValidationError([
3122
+ {
3123
+ field: "where",
3124
+ message: `Where clause exceeds maximum of ${MAX_WHERE_CONDITIONS} conditions (got ${fieldCount}).`
3125
+ }
3126
+ ]);
3127
+ }
3128
+ return flattenWhereRecursive(where, 0);
3129
+ }
3130
+ function flattenWhereRecursive(where, depth) {
3131
+ if (depth > MAX_WHERE_NESTING_DEPTH) {
3132
+ throw new ValidationError([
3133
+ {
3134
+ field: "where",
3135
+ message: `Where clause nesting depth exceeds maximum of ${MAX_WHERE_NESTING_DEPTH} levels.`
3136
+ }
3137
+ ]);
3138
+ }
3139
+ const result = {};
3140
+ for (const [field, condition] of Object.entries(where)) {
3141
+ if (field === "and" || field === "or") {
3142
+ if (!Array.isArray(condition)) {
3143
+ throw new ValidationError([
3144
+ {
3145
+ field,
3146
+ message: `The "${field}" operator requires an array of conditions.`
3147
+ }
3148
+ ]);
3149
+ }
3150
+ const internalKey = field === "and" ? "$and" : "$or";
3151
+ result[internalKey] = condition.filter((sub) => typeof sub === "object" && sub !== null).map((sub) => {
3152
+ if ("$join" in sub)
3153
+ return sub;
3154
+ return flattenWhereRecursive(sub, depth + 1);
3155
+ });
3156
+ continue;
3157
+ }
3158
+ if (typeof condition !== "object" || condition === null) {
3159
+ result[field] = condition;
3160
+ continue;
3161
+ }
3162
+ const condObj = condition;
3163
+ const ops = {};
3164
+ let hasOp = false;
3165
+ for (const [userOp, internalOp] of Object.entries(OPERATOR_MAP)) {
3166
+ if (userOp in condObj) {
3167
+ let value = condObj[userOp];
3168
+ if (internalOp === "$exists" && typeof value === "string") {
3169
+ value = value === "true";
3170
+ }
3171
+ if (typeof value === "string") {
3172
+ value = value.replace(/\0/g, "");
3173
+ }
3174
+ if (Array.isArray(value)) {
3175
+ value = value.map(
3176
+ (item) => typeof item === "string" ? item.replace(/\0/g, "") : item
3177
+ );
3178
+ }
3179
+ ops[internalOp] = value;
3180
+ hasOp = true;
3181
+ }
3182
+ }
3183
+ for (const key of Object.keys(condObj)) {
3184
+ if (!VALID_OPERATORS.has(key)) {
3185
+ throw new ValidationError([
3186
+ {
3187
+ field,
3188
+ message: `Unknown operator "${key}". Valid operators: ${[...VALID_OPERATORS].sort().join(", ")}`
3189
+ }
3190
+ ]);
3191
+ }
3192
+ }
3193
+ if (hasOp) {
3194
+ result[field] = ops;
3195
+ } else {
3196
+ result[field] = condition;
3197
+ }
3198
+ }
3199
+ return result;
3200
+ }
3201
+ function extractRelationshipJoins(where, fields, allCollections) {
3202
+ if (!where)
3203
+ return { cleanedWhere: void 0, joins: [], allJoins: [] };
3204
+ const dataFields = flattenDataFields(fields);
3205
+ const fieldMap = new Map(dataFields.map((f) => [f.name, f]));
3206
+ const joins = [];
3207
+ const allJoins = [];
3208
+ const cleanedWhere = {};
3209
+ for (const [key, condition] of Object.entries(where)) {
3210
+ if (key === "and" || key === "or") {
3211
+ if (Array.isArray(condition)) {
3212
+ const cleanedArray = [];
3213
+ for (const sub of condition) {
3214
+ if (typeof sub === "object" && sub !== null) {
3215
+ const {
3216
+ cleanedWhere: subCleaned,
3217
+ joins: subTopJoins,
3218
+ allJoins: subAllJoins
3219
+ } = extractRelationshipJoins(
3220
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- WhereClause sub-object
3221
+ sub,
3222
+ fields,
3223
+ allCollections
3224
+ );
3225
+ if (subCleaned)
3226
+ cleanedArray.push(subCleaned);
3227
+ for (const join2 of subTopJoins) {
3228
+ cleanedArray.push({ ["$join"]: join2 });
3229
+ }
3230
+ allJoins.push(...subAllJoins);
3231
+ }
3232
+ }
3233
+ if (cleanedArray.length > 0) {
3234
+ cleanedWhere[key] = cleanedArray;
3235
+ }
3236
+ }
3237
+ continue;
3238
+ }
3239
+ let field = fieldMap.get(key);
3240
+ if (!field && key.includes(".")) {
3241
+ const [rootKey, ...subPath] = key.split(".");
3242
+ const rootField = fieldMap.get(rootKey);
3243
+ if (rootField && rootField.type === "relationship" && subPath.length > 0 && condition !== null) {
3244
+ let nested = condition;
3245
+ for (let i = subPath.length - 1; i >= 0; i--) {
3246
+ nested = { [subPath[i]]: nested };
3247
+ }
3248
+ field = rootField;
3249
+ const {
3250
+ cleanedWhere: subCleaned,
3251
+ joins: subJoins,
3252
+ allJoins: subAllJoins
3253
+ } = extractRelationshipJoins(
3254
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- reconstructed nested where
3255
+ { [rootKey]: nested },
3256
+ fields,
3257
+ allCollections
3258
+ );
3259
+ if (subCleaned)
3260
+ Object.assign(cleanedWhere, subCleaned);
3261
+ joins.push(...subJoins);
3262
+ allJoins.push(...subAllJoins);
3263
+ continue;
3264
+ }
3265
+ }
3266
+ if (!field || field.type !== "relationship" || typeof condition !== "object" || condition === null) {
3267
+ cleanedWhere[key] = condition;
3268
+ continue;
3269
+ }
3270
+ const condObj = condition;
3271
+ const condKeys = Object.keys(condObj);
3272
+ const hasOperatorKeys = condKeys.some((k) => VALID_OPERATORS.has(k));
3273
+ const hasNonOperatorKeys = condKeys.some((k) => !VALID_OPERATORS.has(k));
3274
+ if (hasOperatorKeys && hasNonOperatorKeys) {
3275
+ throw new ValidationError([
3276
+ {
3277
+ field: key,
3278
+ message: `Cannot mix operators and sub-field references on relationship field "${key}". Use either operators (e.g. { equals: 'id' }) or sub-field queries (e.g. { name: { equals: 'value' } }), not both.`
3279
+ }
3280
+ ]);
3281
+ }
3282
+ if (!hasNonOperatorKeys) {
3283
+ cleanedWhere[key] = condition;
3284
+ continue;
3285
+ }
3286
+ const relField = field;
3287
+ let targetSlug;
3288
+ try {
3289
+ const targetConfig = relField.collection();
3290
+ if (targetConfig && typeof targetConfig === "object" && "slug" in targetConfig) {
3291
+ targetSlug = targetConfig.slug;
3292
+ }
3293
+ } catch {
3294
+ }
3295
+ if (!targetSlug) {
3296
+ throw new ValidationError([
3297
+ {
3298
+ field: key,
3299
+ message: `Cannot resolve target collection for relationship field "${key}".`
3300
+ }
3301
+ ]);
3302
+ }
3303
+ const targetCollection = allCollections.find((c) => c.slug === targetSlug);
3304
+ if (!targetCollection) {
3305
+ throw new ValidationError([
3306
+ {
3307
+ field: key,
3308
+ message: `Target collection "${targetSlug}" not found for relationship field "${key}".`
3309
+ }
3310
+ ]);
3311
+ }
3312
+ const subWhere = condition;
3313
+ const flattenedConditions = flattenWhereClause(subWhere);
3314
+ const targetTable = targetCollection.dbName ?? targetCollection.slug;
3315
+ const joinSpec = {
3316
+ targetTable,
3317
+ localField: key,
3318
+ targetField: "id",
3319
+ conditions: flattenedConditions,
3320
+ rawWhere: subWhere
3321
+ };
3322
+ joins.push(joinSpec);
3323
+ allJoins.push(joinSpec);
3324
+ }
3325
+ return {
3326
+ cleanedWhere: Object.keys(cleanedWhere).length > 0 ? cleanedWhere : void 0,
3327
+ joins,
3328
+ allJoins
3329
+ };
3330
+ }
3331
+ var SYSTEM_QUERYABLE_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "_status"]);
3332
+ async function validateWhereFields(where, fields, req) {
3333
+ if (!where)
3334
+ return;
3335
+ const dataFields = flattenDataFields(fields);
3336
+ const fieldMap = new Map(dataFields.map((f) => [f.name, f]));
3337
+ for (const fieldName of Object.keys(where)) {
3338
+ if (fieldName === "and" || fieldName === "or") {
3339
+ const subs = where[fieldName];
3340
+ if (Array.isArray(subs)) {
3341
+ for (const sub of subs) {
3342
+ if (typeof sub === "object" && sub !== null) {
3343
+ await validateWhereFields(sub, fields, req);
3344
+ }
3345
+ }
3346
+ }
3347
+ continue;
3348
+ }
3349
+ const baseName = fieldName.includes(".") ? fieldName.split(".")[0] : fieldName;
3350
+ if (SYSTEM_QUERYABLE_FIELDS.has(baseName))
3351
+ continue;
3352
+ const field = fieldMap.get(baseName);
3353
+ if (!field) {
3354
+ throw new ValidationError([{ field: fieldName, message: `Unknown field: ${baseName}` }]);
3355
+ }
3356
+ if (!field.access?.read)
3357
+ continue;
3358
+ const allowed = await Promise.resolve(field.access.read({ req }));
3359
+ if (!allowed) {
3360
+ throw new AccessDeniedError("read", baseName);
3361
+ }
3362
+ }
3363
+ }
3364
+ async function validateSortField(sort, fields, req) {
3365
+ if (!sort)
3366
+ return;
3367
+ const fieldName = sort.startsWith("-") ? sort.slice(1) : sort;
3368
+ const baseName = fieldName.includes(".") ? fieldName.split(".")[0] : fieldName;
3369
+ const dataFields = flattenDataFields(fields);
3370
+ const field = dataFields.find((f) => f.name === baseName);
3371
+ if (!field?.access?.read)
3372
+ return;
3373
+ const allowed = await Promise.resolve(field.access.read({ req }));
3374
+ if (!allowed) {
3375
+ throw new AccessDeniedError("read", baseName);
3376
+ }
3377
+ }
3378
+
3379
+ // libs/server-core/src/lib/api-utils.ts
3380
+ function deepEqual(a, b) {
3381
+ if (a === b)
3382
+ return true;
3383
+ if (a == null || b == null)
3384
+ return false;
3385
+ if (typeof a !== "object" || typeof b !== "object")
3386
+ return false;
3387
+ if (Array.isArray(a)) {
3388
+ if (!Array.isArray(b) || a.length !== b.length)
3389
+ return false;
3390
+ return a.every((item, i) => deepEqual(item, b[i]));
3391
+ }
3392
+ if (Array.isArray(b))
3393
+ return false;
3394
+ const aKeys = Object.keys(a);
3395
+ const bKeys = Object.keys(b);
3396
+ if (aKeys.length !== bKeys.length)
3397
+ return false;
3398
+ const aRec = a;
3399
+ const bRec = b;
3400
+ return aKeys.every(
3401
+ (key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
3402
+ );
3403
+ }
3404
+ function stripTransientKeys(data) {
3405
+ const result = {};
3406
+ for (const [key, value] of Object.entries(data)) {
3407
+ if (!key.startsWith("_")) {
3408
+ result[key] = value;
3409
+ }
3410
+ }
3411
+ return result;
3412
+ }
3413
+
3414
+ // libs/server-core/src/lib/collection-access.ts
3415
+ async function checkSingleCollectionAdminAccess(collection, user) {
3416
+ const adminFn = collection.access?.admin;
3417
+ if (!adminFn) {
3418
+ return !!user;
3419
+ }
3420
+ const accessArgs = {
3421
+ req: { user }
3422
+ };
3423
+ return Promise.resolve(adminFn(accessArgs));
3424
+ }
3425
+ async function checkAccessFunction(accessFn, user, defaultIfUndefined) {
3426
+ if (!accessFn) {
3427
+ return defaultIfUndefined;
3428
+ }
3429
+ const accessArgs = {
3430
+ req: { user }
3431
+ };
3432
+ return Promise.resolve(accessFn(accessArgs));
3433
+ }
3434
+ async function getCollectionPermissions(config, user) {
3435
+ const results = await Promise.all(
3436
+ config.collections.map(async (collection) => {
3437
+ const isManaged = collection.managed === true;
3438
+ const [canAccess, canCreate, canRead, canUpdate, canDelete] = await Promise.all([
3439
+ checkSingleCollectionAdminAccess(collection, user),
3440
+ isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.create, user, !!user),
3441
+ checkAccessFunction(collection.access?.read, user, true),
3442
+ isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.update, user, !!user),
3443
+ isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.delete, user, !!user)
3444
+ ]);
3445
+ return {
3446
+ slug: collection.slug,
3447
+ canAccess,
3448
+ canCreate,
3449
+ canRead,
3450
+ canUpdate,
3451
+ canDelete,
3452
+ ...isManaged ? { managed: true } : {}
3453
+ };
3454
+ })
3455
+ );
3456
+ return results;
3457
+ }
3458
+ var ACCESS_OPS = ["read", "create", "update", "delete"];
3459
+ var ACCESS_DEFAULTS = {
3460
+ read: "public (anyone)",
3461
+ create: "any authenticated user",
3462
+ update: "any authenticated user",
3463
+ delete: "any authenticated user"
3464
+ };
3465
+ function warnInsecureDefaults(collections) {
3466
+ const logger = createLogger("Security");
3467
+ const warnings = [];
3468
+ for (const collection of collections) {
3469
+ if (collection.managed)
3470
+ continue;
3471
+ const missing = ACCESS_OPS.filter((op) => !collection.access?.[op]);
3472
+ if (missing.length > 0) {
3473
+ const details = missing.map((op) => `${op} (${ACCESS_DEFAULTS[op]})`).join(", ");
3474
+ const msg = `Collection "${collection.slug}" has no explicit access control for: ${details}. Define access functions to restrict.`;
3475
+ logger.warn(msg);
3476
+ warnings.push(msg);
3477
+ }
3478
+ }
3479
+ return warnings;
3480
+ }
3481
+
3045
3482
  // libs/server-core/src/lib/momentum-api.ts
3046
3483
  var momentumApiInstance = null;
3047
3484
  function initializeMomentumAPI(config) {
@@ -3049,6 +3486,7 @@ function initializeMomentumAPI(config) {
3049
3486
  createLogger("API").warn("Already initialized, returning existing instance");
3050
3487
  return momentumApiInstance;
3051
3488
  }
3489
+ warnInsecureDefaults(config.collections);
3052
3490
  momentumApiInstance = new MomentumAPIImpl(config);
3053
3491
  return momentumApiInstance;
3054
3492
  }
@@ -3106,70 +3544,6 @@ var MomentumAPIImpl = class _MomentumAPIImpl {
3106
3544
  return { ...this.context };
3107
3545
  }
3108
3546
  };
3109
- function deepEqual(a, b) {
3110
- if (a === b)
3111
- return true;
3112
- if (a == null || b == null)
3113
- return false;
3114
- if (typeof a !== "object" || typeof b !== "object")
3115
- return false;
3116
- if (Array.isArray(a)) {
3117
- if (!Array.isArray(b) || a.length !== b.length)
3118
- return false;
3119
- return a.every((item, i) => deepEqual(item, b[i]));
3120
- }
3121
- if (Array.isArray(b))
3122
- return false;
3123
- const aKeys = Object.keys(a);
3124
- const bKeys = Object.keys(b);
3125
- if (aKeys.length !== bKeys.length)
3126
- return false;
3127
- const aRec = a;
3128
- const bRec = b;
3129
- return aKeys.every(
3130
- (key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
3131
- );
3132
- }
3133
- function stripTransientKeys(data) {
3134
- const result = {};
3135
- for (const [key, value] of Object.entries(data)) {
3136
- if (!key.startsWith("_")) {
3137
- result[key] = value;
3138
- }
3139
- }
3140
- return result;
3141
- }
3142
- var COMPARISON_OPS = ["gt", "gte", "lt", "lte"];
3143
- function flattenWhereClause(where) {
3144
- if (!where)
3145
- return {};
3146
- const result = {};
3147
- for (const [field, condition] of Object.entries(where)) {
3148
- if (typeof condition !== "object" || condition === null) {
3149
- result[field] = condition;
3150
- continue;
3151
- }
3152
- const condObj = condition;
3153
- if ("equals" in condObj) {
3154
- result[field] = condObj["equals"];
3155
- continue;
3156
- }
3157
- const ops = {};
3158
- let hasComparisonOp = false;
3159
- for (const op of COMPARISON_OPS) {
3160
- if (op in condObj) {
3161
- ops[`$${op}`] = condObj[op];
3162
- hasComparisonOp = true;
3163
- }
3164
- }
3165
- if (hasComparisonOp) {
3166
- result[field] = ops;
3167
- } else {
3168
- result[field] = condition;
3169
- }
3170
- }
3171
- return result;
3172
- }
3173
3547
  var CollectionOperationsImpl = class _CollectionOperationsImpl {
3174
3548
  constructor(slug2, collectionConfig, adapter, context, allCollections = []) {
3175
3549
  this.slug = slug2;
@@ -3180,11 +3554,37 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3180
3554
  }
3181
3555
  async find(options = {}) {
3182
3556
  await this.checkAccess("read");
3557
+ if (!this.context.overrideAccess) {
3558
+ await validateWhereFields(
3559
+ options.where,
3560
+ this.collectionConfig.fields,
3561
+ this.buildRequestContext()
3562
+ );
3563
+ await validateSortField(
3564
+ options.sort,
3565
+ this.collectionConfig.fields,
3566
+ this.buildRequestContext()
3567
+ );
3568
+ }
3183
3569
  await this.runBeforeReadHooks();
3184
- const limit = options.limit ?? 10;
3185
- const page = options.page ?? 1;
3570
+ const rawLimit = options.limit ?? 10;
3571
+ const rawPage = options.page ?? 1;
3572
+ const limit = Math.max(
3573
+ 1,
3574
+ Math.min(Number.isFinite(rawLimit) ? Math.floor(rawLimit) : 10, MAX_PAGE_LIMIT)
3575
+ );
3576
+ const page = Math.max(
3577
+ 1,
3578
+ Math.min(Number.isFinite(rawPage) ? Math.floor(rawPage) : 1, MAX_PAGE)
3579
+ );
3186
3580
  const { depth: _depth, where, withDeleted: _wd, onlyDeleted: _od, ...queryOptions } = options;
3187
- const whereParams = flattenWhereClause(where);
3581
+ const { cleanedWhere, joins, allJoins } = extractRelationshipJoins(
3582
+ where,
3583
+ this.collectionConfig.fields,
3584
+ this.allCollections
3585
+ );
3586
+ await this.enforceWhereLimits(cleanedWhere, allJoins);
3587
+ const whereParams = flattenWhereClause(cleanedWhere);
3188
3588
  const softDeleteField = getSoftDeleteField(this.collectionConfig);
3189
3589
  if (softDeleteField && !options.withDeleted && !options.onlyDeleted) {
3190
3590
  whereParams[softDeleteField] = null;
@@ -3209,6 +3609,9 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3209
3609
  limit,
3210
3610
  page
3211
3611
  };
3612
+ if (joins.length > 0) {
3613
+ query["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
3614
+ }
3212
3615
  const docs = await this.adapter.find(this.slug, query);
3213
3616
  let afterHookDocs = await this.processAfterReadHooks(docs);
3214
3617
  const MAX_RELATIONSHIP_DEPTH = 10;
@@ -3232,14 +3635,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3232
3635
  );
3233
3636
  }
3234
3637
  const countQuery = { ...queryOptions, ...whereParams };
3638
+ if (joins.length > 0) {
3639
+ countQuery["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
3640
+ }
3235
3641
  delete countQuery["limit"];
3236
3642
  delete countQuery["page"];
3237
- const allDocs = await this.adapter.find(this.slug, {
3238
- ...countQuery,
3239
- limit: 0
3240
- // Signal to adapter: count-only (returns all if not supported)
3241
- });
3242
- const totalDocs = allDocs.length;
3643
+ const totalDocs = this.adapter.count ? await this.adapter.count(this.slug, countQuery) : (
3644
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Adapter returns Record<string, unknown>[], safe cast to T[]
3645
+ (await this.adapter.find(this.slug, { ...countQuery, limit: 0 })).length
3646
+ );
3243
3647
  const totalPages = Math.ceil(totalDocs / limit) || 1;
3244
3648
  return {
3245
3649
  docs: afterHookDocs,
@@ -3476,7 +3880,12 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3476
3880
  async restore(id) {
3477
3881
  const softDeleteField = getSoftDeleteField(this.collectionConfig);
3478
3882
  if (!softDeleteField) {
3479
- throw new Error(`Collection "${this.slug}" does not have soft delete enabled`);
3883
+ throw new ValidationError([
3884
+ {
3885
+ field: "_softDelete",
3886
+ message: `Collection "${this.slug}" does not have soft delete enabled`
3887
+ }
3888
+ ]);
3480
3889
  }
3481
3890
  const restoreAccessFn = this.collectionConfig.access?.restore;
3482
3891
  if (restoreAccessFn) {
@@ -3534,20 +3943,43 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3534
3943
  if (softDeleteField) {
3535
3944
  softDeleteFilter[softDeleteField] = null;
3536
3945
  }
3946
+ const defaultWhereFilter = {};
3947
+ if (this.collectionConfig.defaultWhere) {
3948
+ const constraints = this.collectionConfig.defaultWhere(this.buildRequestContext());
3949
+ if (constraints) {
3950
+ Object.assign(defaultWhereFilter, constraints);
3951
+ }
3952
+ }
3537
3953
  let docs;
3538
3954
  if (this.adapter.search) {
3539
3955
  docs = await this.adapter.search(this.slug, query, searchFields, { limit, page });
3540
3956
  if (softDeleteField) {
3541
3957
  docs = docs.filter((doc) => !doc[softDeleteField]);
3542
3958
  }
3959
+ if (Object.keys(defaultWhereFilter).length > 0) {
3960
+ docs = docs.filter((doc) => {
3961
+ return Object.entries(defaultWhereFilter).every(([key, value]) => {
3962
+ if (value && typeof value === "object" && !Array.isArray(value)) {
3963
+ return JSON.stringify(doc[key]) === JSON.stringify(value);
3964
+ }
3965
+ return doc[key] === value;
3966
+ });
3967
+ });
3968
+ }
3543
3969
  } else {
3544
- docs = await this.adapter.find(this.slug, { ...softDeleteFilter, limit, page });
3970
+ docs = await this.adapter.find(this.slug, {
3971
+ ...softDeleteFilter,
3972
+ ...defaultWhereFilter,
3973
+ limit,
3974
+ page
3975
+ });
3545
3976
  }
3546
3977
  const resolvedDocs = docs;
3547
- const totalDocs = resolvedDocs.length;
3978
+ const afterHookDocs = await this.processAfterReadHooks(resolvedDocs);
3979
+ const totalDocs = afterHookDocs.length;
3548
3980
  const totalPages = Math.max(1, Math.ceil(totalDocs / limit));
3549
3981
  return {
3550
- docs: resolvedDocs,
3982
+ docs: afterHookDocs,
3551
3983
  totalDocs,
3552
3984
  totalPages,
3553
3985
  page,
@@ -3560,13 +3992,40 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3560
3992
  }
3561
3993
  async count(where, options) {
3562
3994
  await this.checkAccess("read");
3563
- const whereParams = flattenWhereClause(where);
3995
+ if (!this.context.overrideAccess) {
3996
+ await validateWhereFields(where, this.collectionConfig.fields, this.buildRequestContext());
3997
+ }
3998
+ const { cleanedWhere, joins, allJoins } = extractRelationshipJoins(
3999
+ where,
4000
+ this.collectionConfig.fields,
4001
+ this.allCollections
4002
+ );
4003
+ await this.enforceWhereLimits(cleanedWhere, allJoins);
4004
+ const whereParams = flattenWhereClause(cleanedWhere);
3564
4005
  const softDeleteField = getSoftDeleteField(this.collectionConfig);
3565
4006
  if (softDeleteField && !options?.withDeleted) {
3566
4007
  whereParams[softDeleteField] = null;
3567
4008
  }
3568
- const query = { ...whereParams, limit: 0 };
3569
- const docs = await this.adapter.find(this.slug, query);
4009
+ if (this.collectionConfig.defaultWhere) {
4010
+ const constraints = this.collectionConfig.defaultWhere(this.buildRequestContext());
4011
+ if (constraints) {
4012
+ Object.assign(whereParams, constraints);
4013
+ }
4014
+ }
4015
+ if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
4016
+ const canSeeDrafts = await this.canReadDrafts();
4017
+ if (!canSeeDrafts) {
4018
+ whereParams["_status"] = "published";
4019
+ }
4020
+ }
4021
+ const query = { ...whereParams };
4022
+ if (joins.length > 0) {
4023
+ query["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
4024
+ }
4025
+ if (this.adapter.count) {
4026
+ return this.adapter.count(this.slug, query);
4027
+ }
4028
+ const docs = await this.adapter.find(this.slug, { ...query, limit: 0 });
3570
4029
  return docs.length;
3571
4030
  }
3572
4031
  async batchCreate(items) {
@@ -3726,6 +4185,46 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3726
4185
  return false;
3727
4186
  }
3728
4187
  }
4188
+ async enforceWhereLimits(cleanedWhere, joins) {
4189
+ if (joins.length > MAX_JOINS) {
4190
+ throw new ValidationError([
4191
+ {
4192
+ field: "where",
4193
+ message: `Number of relationship joins (${joins.length}) exceeds maximum of ${MAX_JOINS}.`
4194
+ }
4195
+ ]);
4196
+ }
4197
+ const mainCount = cleanedWhere ? countWhereConditions(cleanedWhere) : 0;
4198
+ const joinCount = joins.reduce((sum, j) => sum + countWhereConditions(j.rawWhere), 0);
4199
+ const totalConditions = mainCount + joinCount;
4200
+ if (totalConditions > MAX_WHERE_CONDITIONS) {
4201
+ throw new ValidationError([
4202
+ {
4203
+ field: "where",
4204
+ message: `Where clause exceeds maximum of ${MAX_WHERE_CONDITIONS} conditions (got ${totalConditions} across main query and ${joins.length} join(s)).`
4205
+ }
4206
+ ]);
4207
+ }
4208
+ if (!this.context.overrideAccess) {
4209
+ for (const join2 of joins) {
4210
+ const targetCol = this.allCollections.find(
4211
+ (c) => (c.dbName ?? c.slug) === join2.targetTable
4212
+ );
4213
+ if (targetCol) {
4214
+ const collectionAccessFn = targetCol.access?.read;
4215
+ if (collectionAccessFn) {
4216
+ const allowed = await Promise.resolve(
4217
+ collectionAccessFn({ req: this.buildRequestContext() })
4218
+ );
4219
+ if (!allowed) {
4220
+ throw new AccessDeniedError("read", targetCol.slug);
4221
+ }
4222
+ }
4223
+ await validateWhereFields(join2.rawWhere, targetCol.fields, this.buildRequestContext());
4224
+ }
4225
+ }
4226
+ }
4227
+ }
3729
4228
  buildRequestContext() {
3730
4229
  return {
3731
4230
  user: this.context.user
@@ -4134,7 +4633,14 @@ function createMomentumHandlers(config) {
4134
4633
  });
4135
4634
  return {
4136
4635
  docs: result.docs,
4137
- totalDocs: result.totalDocs
4636
+ totalDocs: result.totalDocs,
4637
+ totalPages: result.totalPages,
4638
+ page: result.page,
4639
+ limit: result.limit,
4640
+ hasNextPage: result.hasNextPage,
4641
+ hasPrevPage: result.hasPrevPage,
4642
+ nextPage: result.nextPage,
4643
+ prevPage: result.prevPage
4138
4644
  };
4139
4645
  } catch (error) {
4140
4646
  return handleError(error);
@@ -4222,7 +4728,14 @@ function createMomentumHandlers(config) {
4222
4728
  const result = await api.collection(request.collectionSlug).search(q, { fields, limit, page });
4223
4729
  return {
4224
4730
  docs: result.docs,
4225
- totalDocs: result.totalDocs
4731
+ totalDocs: result.totalDocs,
4732
+ totalPages: result.totalPages,
4733
+ page: result.page,
4734
+ limit: result.limit,
4735
+ hasNextPage: result.hasNextPage,
4736
+ hasPrevPage: result.hasPrevPage,
4737
+ nextPage: result.nextPage,
4738
+ prevPage: result.prevPage
4226
4739
  };
4227
4740
  } catch (error) {
4228
4741
  return handleError(error);
@@ -4287,51 +4800,6 @@ function handleError(error) {
4287
4800
  return { error: "Unknown error", status: 500 };
4288
4801
  }
4289
4802
 
4290
- // libs/server-core/src/lib/collection-access.ts
4291
- async function checkSingleCollectionAdminAccess(collection, user) {
4292
- const adminFn = collection.access?.admin;
4293
- if (!adminFn) {
4294
- return !!user;
4295
- }
4296
- const accessArgs = {
4297
- req: { user }
4298
- };
4299
- return Promise.resolve(adminFn(accessArgs));
4300
- }
4301
- async function checkAccessFunction(accessFn, user, defaultIfUndefined) {
4302
- if (!accessFn) {
4303
- return defaultIfUndefined;
4304
- }
4305
- const accessArgs = {
4306
- req: { user }
4307
- };
4308
- return Promise.resolve(accessFn(accessArgs));
4309
- }
4310
- async function getCollectionPermissions(config, user) {
4311
- const results = await Promise.all(
4312
- config.collections.map(async (collection) => {
4313
- const isManaged = collection.managed === true;
4314
- const [canAccess, canCreate, canRead, canUpdate, canDelete] = await Promise.all([
4315
- checkSingleCollectionAdminAccess(collection, user),
4316
- isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.create, user, !!user),
4317
- checkAccessFunction(collection.access?.read, user, true),
4318
- isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.update, user, !!user),
4319
- isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.delete, user, !!user)
4320
- ]);
4321
- return {
4322
- slug: collection.slug,
4323
- canAccess,
4324
- canCreate,
4325
- canRead,
4326
- canUpdate,
4327
- canDelete,
4328
- ...isManaged ? { managed: true } : {}
4329
- };
4330
- })
4331
- );
4332
- return results;
4333
- }
4334
-
4335
4803
  // libs/server-core/src/lib/seeding/seed-tracker.ts
4336
4804
  var import_node_crypto = require("node:crypto");
4337
4805
  function createSeedTracker(adapter) {
@@ -5364,7 +5832,7 @@ function checkDepth(node, currentDepth, maxDepth, context) {
5364
5832
  }
5365
5833
  }
5366
5834
  }
5367
- async function executeGraphQL(schema, requestBody, context) {
5835
+ async function executeGraphQL(schema, requestBody, context, options) {
5368
5836
  if (!requestBody.query) {
5369
5837
  return {
5370
5838
  status: 400,
@@ -5380,6 +5848,17 @@ async function executeGraphQL(schema, requestBody, context) {
5380
5848
  body: { errors: [{ message: "Query parsing failed" }] }
5381
5849
  };
5382
5850
  }
5851
+ if (options?.readOnly) {
5852
+ const hasMutation = document.definitions.some(
5853
+ (def) => def.kind === "OperationDefinition" && def.operation === "mutation"
5854
+ );
5855
+ if (hasMutation) {
5856
+ return {
5857
+ status: 405,
5858
+ body: { errors: [{ message: "Mutations are not allowed via GET requests" }] }
5859
+ };
5860
+ }
5861
+ }
5383
5862
  const depthErrors = (0, import_graphql3.validate)(schema, document, [depthLimitRule(MAX_QUERY_DEPTH)]);
5384
5863
  if (depthErrors.length > 0) {
5385
5864
  return {
@@ -6799,24 +7278,29 @@ function momentumApiMiddleware(config) {
6799
7278
  const router = (0, import_express.Router)();
6800
7279
  const handlers = createMomentumHandlers(config);
6801
7280
  router.use((0, import_express.json)());
7281
+ router.use((_req, res, next) => {
7282
+ res.setHeader("X-Content-Type-Options", "nosniff");
7283
+ res.setHeader("X-Frame-Options", "DENY");
7284
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
7285
+ res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
7286
+ next();
7287
+ });
6802
7288
  router.use((req, res, next) => {
6803
7289
  const corsConfig = config.server?.cors ?? {};
6804
7290
  const origins = Array.isArray(corsConfig.origin) ? corsConfig.origin : corsConfig.origin ? [corsConfig.origin] : [];
6805
- let allowOrigin;
6806
- if (origins.length === 0) {
6807
- allowOrigin = "*";
7291
+ if (origins.length === 0 || origins.includes("*")) {
7292
+ if (process.env["NODE_ENV"] === "production") {
7293
+ createLogger("CORS").warn(
7294
+ 'Origin is set to "*" in production. Configure explicit origins via config.server.cors.origin.'
7295
+ );
7296
+ }
7297
+ res.setHeader("Access-Control-Allow-Origin", "*");
6808
7298
  } else {
6809
7299
  const requestOrigin = req.headers["origin"] ?? "";
6810
- allowOrigin = origins.includes(requestOrigin) ? requestOrigin : origins[0];
6811
- }
6812
- if (allowOrigin === "*" && process.env["NODE_ENV"] === "production") {
6813
- createLogger("CORS").warn(
6814
- 'Origin is set to "*" in production. Configure explicit origins via config.server.cors.origin.'
6815
- );
6816
- }
6817
- res.setHeader("Access-Control-Allow-Origin", allowOrigin);
6818
- if (allowOrigin !== "*") {
6819
7300
  res.setHeader("Vary", "Origin");
7301
+ if (origins.includes(requestOrigin)) {
7302
+ res.setHeader("Access-Control-Allow-Origin", requestOrigin);
7303
+ }
6820
7304
  }
6821
7305
  res.setHeader(
6822
7306
  "Access-Control-Allow-Methods",
@@ -6899,7 +7383,12 @@ function momentumApiMiddleware(config) {
6899
7383
  res.status(400).json({ errors: [{ message: "Query parameter required" }] });
6900
7384
  return;
6901
7385
  }
6902
- const result = await executeGraphQL(graphqlSchema, { query: queryParam }, { user });
7386
+ const result = await executeGraphQL(
7387
+ graphqlSchema,
7388
+ { query: queryParam },
7389
+ { user },
7390
+ { readOnly: true }
7391
+ );
6903
7392
  res.status(result.status).json(result.body);
6904
7393
  });
6905
7394
  router.get("/globals/:slug", async (req, res) => {
@@ -7232,7 +7721,14 @@ function momentumApiMiddleware(config) {
7232
7721
  res.json({ status });
7233
7722
  } catch (error) {
7234
7723
  const message = sanitizeErrorMessage(error, "Unknown error");
7235
- res.status(500).json({ error: "Failed to get status", message });
7724
+ let status = 500;
7725
+ if (error instanceof Error) {
7726
+ if (error.name === "AccessDeniedError")
7727
+ status = 403;
7728
+ else if (error.name === "DocumentNotFoundError")
7729
+ status = 404;
7730
+ }
7731
+ res.status(status).json({ error: status === 403 ? "Access denied" : "Failed to get status", message });
7236
7732
  }
7237
7733
  });
7238
7734
  async function handlePreviewRequest(req, res) {
@@ -7421,8 +7917,11 @@ function momentumApiMiddleware(config) {
7421
7917
  return { docs, totalDocs: docs.length };
7422
7918
  },
7423
7919
  findById: (slug2, id) => txAdapter.findById(slug2, id),
7424
- count: async (slug2) => {
7425
- const docs = await txAdapter.find(slug2, {});
7920
+ count: async (slug2, where) => {
7921
+ if (txAdapter.count) {
7922
+ return txAdapter.count(slug2, where ?? {});
7923
+ }
7924
+ const docs = await txAdapter.find(slug2, where ?? {});
7426
7925
  return docs.length;
7427
7926
  },
7428
7927
  create: (slug2, data) => txAdapter.create(slug2, data),
@@ -7464,7 +7963,7 @@ function momentumApiMiddleware(config) {
7464
7963
  throw err;
7465
7964
  }
7466
7965
  },
7467
- count: (slug2) => ctxApi.collection(slug2).count(),
7966
+ count: (slug2, where) => ctxApi.collection(slug2).count(where),
7468
7967
  create: async (slug2, data) => {
7469
7968
  return await ctxApi.collection(slug2).create(data);
7470
7969
  },
@@ -7487,6 +7986,8 @@ function momentumApiMiddleware(config) {
7487
7986
  collection,
7488
7987
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Express body is parsed JSON
7489
7988
  body: req.body,
7989
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Express query params
7990
+ params: req.query,
7490
7991
  query: buildQueryHelper(contextApi)
7491
7992
  });
7492
7993
  res.status(result.status).json(result.body);
@@ -7554,7 +8055,15 @@ function momentumApiMiddleware(config) {
7554
8055
  }
7555
8056
  } catch (error) {
7556
8057
  const message = sanitizeErrorMessage(error, "Batch operation failed");
7557
- const status = error instanceof Error && error.name === "ValidationError" ? 400 : 500;
8058
+ let status = 500;
8059
+ if (error instanceof Error) {
8060
+ if (error.name === "ValidationError")
8061
+ status = 400;
8062
+ else if (error.name === "DocumentNotFoundError")
8063
+ status = 404;
8064
+ else if (error.name === "AccessDeniedError")
8065
+ status = 403;
8066
+ }
7558
8067
  res.status(status).json({ error: message });
7559
8068
  }
7560
8069
  });
@@ -7613,7 +8122,16 @@ function momentumApiMiddleware(config) {
7613
8122
  }
7614
8123
  } catch (error) {
7615
8124
  const message = sanitizeErrorMessage(error, "Export failed");
7616
- res.status(500).json({ error: message });
8125
+ let status = 500;
8126
+ if (error instanceof Error) {
8127
+ if (error.name === "AccessDeniedError")
8128
+ status = 403;
8129
+ else if (error.name === "ValidationError")
8130
+ status = 400;
8131
+ else if (error.name === "DocumentNotFoundError")
8132
+ status = 404;
8133
+ }
8134
+ res.status(status).json({ error: message });
7617
8135
  }
7618
8136
  });
7619
8137
  router.post("/:collection/import", async (req, res) => {
@@ -8411,6 +8929,7 @@ function createSetupMiddleware(config) {
8411
8929
  const dbConfig = isLegacyConfig(config) ? { type: "sqlite", database: config.database } : config.db;
8412
8930
  const { auth } = config;
8413
8931
  const router = (0, import_express3.Router)();
8932
+ let setupInProgress = false;
8414
8933
  router.get("/setup/status", async (_req, res) => {
8415
8934
  let hasUsers;
8416
8935
  if (dbConfig.type === "sqlite") {
@@ -8425,6 +8944,13 @@ function createSetupMiddleware(config) {
8425
8944
  res.json(status);
8426
8945
  });
8427
8946
  router.post("/setup/create-admin", async (req, res) => {
8947
+ if (setupInProgress) {
8948
+ res.status(409).json({
8949
+ error: { message: "Setup is already in progress." }
8950
+ });
8951
+ return;
8952
+ }
8953
+ setupInProgress = true;
8428
8954
  try {
8429
8955
  let hasUsers;
8430
8956
  if (dbConfig.type === "sqlite") {
@@ -8501,9 +9027,10 @@ function createSetupMiddleware(config) {
8501
9027
  updatedAt: userRow.updatedAt
8502
9028
  }
8503
9029
  });
8504
- } catch (error) {
8505
- const message = error instanceof Error ? error.message : "Failed to create admin user";
8506
- res.status(500).json({ error: { message } });
9030
+ } catch {
9031
+ res.status(500).json({ error: { message: "Failed to create admin user" } });
9032
+ } finally {
9033
+ setupInProgress = false;
8507
9034
  }
8508
9035
  });
8509
9036
  return router;
@@ -8579,9 +9106,8 @@ function createApiKeyRoutes(config) {
8579
9106
  return;
8580
9107
  }
8581
9108
  const role = body.role ?? "user";
8582
- const validRoles = ["admin", "editor", "user", "viewer"];
8583
- if (!validRoles.includes(role)) {
8584
- res.status(400).json({ error: `Invalid role. Must be one of: ${validRoles.join(", ")}` });
9109
+ if (!ROLE_HIERARCHY.includes(role)) {
9110
+ res.status(400).json({ error: `Invalid role. Must be one of: ${ROLE_HIERARCHY.join(", ")}` });
8585
9111
  return;
8586
9112
  }
8587
9113
  const userRoleIndex = ROLE_HIERARCHY.indexOf(user.role ?? "viewer");
@@ -8647,7 +9173,7 @@ function createApiKeyRoutes(config) {
8647
9173
  return;
8648
9174
  }
8649
9175
  if (existingKey.createdBy !== user.id) {
8650
- res.status(403).json({ error: "You can only delete your own API keys" });
9176
+ res.status(404).json({ error: "API key not found" });
8651
9177
  return;
8652
9178
  }
8653
9179
  }
@@ -8918,6 +9444,20 @@ function createHealthMiddleware(options = {}) {
8918
9444
  return router;
8919
9445
  }
8920
9446
 
9447
+ // libs/server-express/src/lib/rate-limit-middleware.ts
9448
+ function createRateLimitMiddleware(limiter) {
9449
+ return (req, res, next) => {
9450
+ const forwarded = req.headers["x-forwarded-for"];
9451
+ const key = (forwarded ? forwarded.toString().split(",")[0].trim() : req.ip) ?? "unknown";
9452
+ if (!limiter.isAllowed(key)) {
9453
+ res.setHeader("Retry-After", "60");
9454
+ res.status(429).json({ error: "Too many requests. Please try again later." });
9455
+ return;
9456
+ }
9457
+ next();
9458
+ };
9459
+ }
9460
+
8921
9461
  // libs/server-express/src/lib/create-momentum-server.ts
8922
9462
  var import_express6 = __toESM(require("express"));
8923
9463
  async function createMomentumServer(options) {
@@ -9004,6 +9544,7 @@ async function createMomentumServer(options) {
9004
9544
  createMomentumServer,
9005
9545
  createOpenAPIMiddleware,
9006
9546
  createProtectMiddleware,
9547
+ createRateLimitMiddleware,
9007
9548
  createSessionResolverMiddleware,
9008
9549
  createSetupMiddleware,
9009
9550
  getPluginMiddleware,