@momentumcms/server-express 0.5.3 → 0.5.4

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/index.cjs +113 -9
  3. package/index.js +113 -9
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.5.4 (2026-03-07)
2
+
3
+ ### 🚀 Features
4
+
5
+ - Versioning & drafts with draft/publish workflow ([#50](https://github.com/DonaldMurillo/momentum-cms/pull/50))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Claude Haiku 4.5
10
+ - Claude Opus 4.6
11
+ - Donald Murillo @DonaldMurillo
12
+
1
13
  ## 0.5.0 (2026-02-23)
2
14
 
3
15
  This was a version bump only for server-express to align it with other projects, there were no code changes.
package/index.cjs CHANGED
@@ -2335,6 +2335,14 @@ function createSeedHelpers() {
2335
2335
  };
2336
2336
  }
2337
2337
 
2338
+ // libs/core/src/lib/versions/version.types.ts
2339
+ function hasVersionDrafts(collection) {
2340
+ const v = collection.versions;
2341
+ if (!v || typeof v === "boolean")
2342
+ return false;
2343
+ return !!v.drafts;
2344
+ }
2345
+
2338
2346
  // libs/server-core/src/lib/field-access.ts
2339
2347
  function hasFieldAccessControl(fields) {
2340
2348
  for (const field of fields) {
@@ -2921,13 +2929,18 @@ var VersionOperationsImpl = class {
2921
2929
  }
2922
2930
  return "draft";
2923
2931
  }
2924
- async compare(versionId1, versionId2) {
2932
+ async compare(versionId1, versionId2, parentId) {
2925
2933
  await this.checkAccess("readVersions");
2926
2934
  const version1 = await this.findVersionById(versionId1);
2927
2935
  const version2 = await this.findVersionById(versionId2);
2928
2936
  if (!version1 || !version2) {
2929
2937
  throw new Error("One or both versions not found");
2930
2938
  }
2939
+ if (parentId) {
2940
+ if (version1.parent !== parentId || version2.parent !== parentId) {
2941
+ throw new Error("Version does not belong to the specified document");
2942
+ }
2943
+ }
2931
2944
  const data1 = version1.version;
2932
2945
  const data2 = version2.version;
2933
2946
  if (!isRecord(data1) || !isRecord(data2)) {
@@ -2982,6 +2995,9 @@ var VersionOperationsImpl = class {
2982
2995
  // Private Helpers
2983
2996
  // ============================================
2984
2997
  async checkAccess(operation) {
2998
+ if (this.context.overrideAccess) {
2999
+ return;
3000
+ }
2985
3001
  const accessFn = this.collectionConfig.access?.[operation];
2986
3002
  if (!accessFn) {
2987
3003
  return;
@@ -3138,6 +3154,12 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3138
3154
  Object.assign(whereParams, constraints);
3139
3155
  }
3140
3156
  }
3157
+ if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
3158
+ const canSeeDrafts = await this.canReadDrafts();
3159
+ if (!canSeeDrafts) {
3160
+ whereParams["_status"] = "published";
3161
+ }
3162
+ }
3141
3163
  const query = {
3142
3164
  ...queryOptions,
3143
3165
  ...whereParams,
@@ -3200,6 +3222,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3200
3222
  doc[softDeleteField]) {
3201
3223
  return null;
3202
3224
  }
3225
+ if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
3226
+ const canSeeDrafts = await this.canReadDrafts();
3227
+ if (!canSeeDrafts) {
3228
+ const record = doc;
3229
+ if (record["_status"] !== "published") {
3230
+ return null;
3231
+ }
3232
+ }
3233
+ }
3203
3234
  if (!this.matchesDefaultWhereConstraints(doc)) {
3204
3235
  return null;
3205
3236
  }
@@ -3285,6 +3316,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3285
3316
  if (!this.matchesDefaultWhereConstraints(originalDoc)) {
3286
3317
  throw new DocumentNotFoundError(this.slug, id);
3287
3318
  }
3319
+ if (hasVersionDrafts(this.collectionConfig) && this.adapter.createVersion) {
3320
+ const status = originalDoc["_status"] ?? "draft";
3321
+ await this.adapter.createVersion(this.slug, id, originalDoc, { status });
3322
+ const versionsConfig = this.collectionConfig.versions;
3323
+ const maxPerDoc = typeof versionsConfig === "object" && versionsConfig !== null ? versionsConfig.maxPerDoc : void 0;
3324
+ if (maxPerDoc && this.adapter.deleteVersions) {
3325
+ await this.adapter.deleteVersions(this.slug, id, maxPerDoc);
3326
+ }
3327
+ }
3288
3328
  let processedData = data;
3289
3329
  const softDeleteField = getSoftDeleteField(this.collectionConfig);
3290
3330
  if (softDeleteField && softDeleteField in processedData) {
@@ -3616,6 +3656,30 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3616
3656
  throw new AccessDeniedError(operation, this.slug);
3617
3657
  }
3618
3658
  }
3659
+ /**
3660
+ * Check if the current user can see draft documents (non-throwing).
3661
+ * Uses `access.readDrafts` if configured, otherwise falls back to `access.update`.
3662
+ */
3663
+ async canReadDrafts() {
3664
+ if (!this.context.user)
3665
+ return false;
3666
+ const readDraftsFn = this.collectionConfig.access?.readDrafts;
3667
+ if (readDraftsFn) {
3668
+ try {
3669
+ return !!await Promise.resolve(readDraftsFn({ req: this.buildRequestContext() }));
3670
+ } catch {
3671
+ return false;
3672
+ }
3673
+ }
3674
+ const updateFn = this.collectionConfig.access?.update;
3675
+ if (!updateFn)
3676
+ return true;
3677
+ try {
3678
+ return !!await Promise.resolve(updateFn({ req: this.buildRequestContext() }));
3679
+ } catch {
3680
+ return false;
3681
+ }
3682
+ }
3619
3683
  buildRequestContext() {
3620
3684
  return {
3621
3685
  user: this.context.user
@@ -4752,7 +4816,7 @@ function startPublishScheduler(adapter, collections, options) {
4752
4816
  const scheduled = await adapter.findScheduledDocuments(collection.slug, now);
4753
4817
  for (const doc of scheduled) {
4754
4818
  try {
4755
- const api = getMomentumAPI();
4819
+ const api = getMomentumAPI().setContext({ overrideAccess: true });
4756
4820
  const versionOps = api.collection(collection.slug).versions();
4757
4821
  if (!versionOps)
4758
4822
  continue;
@@ -4859,8 +4923,9 @@ function buildGraphQLSchema(collections) {
4859
4923
  }
4860
4924
  function getOrCreateEnum(field, parentName) {
4861
4925
  const key = `${parentName}_${field.name}`;
4862
- if (enumCache.has(key)) {
4863
- return enumCache.get(key);
4926
+ const cached = enumCache.get(key);
4927
+ if (cached) {
4928
+ return cached;
4864
4929
  }
4865
4930
  const values = {};
4866
4931
  for (const opt of field.options) {
@@ -4971,8 +5036,9 @@ function buildGraphQLSchema(collections) {
4971
5036
  }
4972
5037
  }
4973
5038
  function getOrCreateObjectType(fields, typeName) {
4974
- if (typeCache.has(typeName)) {
4975
- return typeCache.get(typeName);
5039
+ const cachedType = typeCache.get(typeName);
5040
+ if (cachedType) {
5041
+ return cachedType;
4976
5042
  }
4977
5043
  const objType = new import_graphql2.GraphQLObjectType({
4978
5044
  name: typeName,
@@ -4993,8 +5059,9 @@ function buildGraphQLSchema(collections) {
4993
5059
  }
4994
5060
  function getOrCreateInputType(fields, typeName) {
4995
5061
  const inputName = `${typeName}Input`;
4996
- if (inputCache.has(inputName)) {
4997
- return inputCache.get(inputName);
5062
+ const cachedInput = inputCache.get(inputName);
5063
+ if (cachedInput) {
5064
+ return cachedInput;
4998
5065
  }
4999
5066
  const inputType = new import_graphql2.GraphQLInputObjectType({
5000
5067
  name: inputName,
@@ -6823,6 +6890,10 @@ function momentumApiMiddleware(config) {
6823
6890
  res.json(result);
6824
6891
  } catch (error) {
6825
6892
  const message = sanitizeErrorMessage(error, "Unknown error");
6893
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6894
+ res.status(403).json({ error: "Access denied" });
6895
+ return;
6896
+ }
6826
6897
  res.status(500).json({ error: "Failed to fetch versions", message });
6827
6898
  }
6828
6899
  });
@@ -6851,6 +6922,10 @@ function momentumApiMiddleware(config) {
6851
6922
  res.json(version);
6852
6923
  } catch (error) {
6853
6924
  const message = sanitizeErrorMessage(error, "Unknown error");
6925
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6926
+ res.status(403).json({ error: "Access denied" });
6927
+ return;
6928
+ }
6854
6929
  res.status(500).json({ error: "Failed to fetch version", message });
6855
6930
  }
6856
6931
  });
@@ -6889,6 +6964,10 @@ function momentumApiMiddleware(config) {
6889
6964
  res.status(400).json({ error: "Version parent mismatch", message });
6890
6965
  return;
6891
6966
  }
6967
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6968
+ res.status(403).json({ error: "Access denied" });
6969
+ return;
6970
+ }
6892
6971
  res.status(500).json({ error: "Failed to restore version", message });
6893
6972
  }
6894
6973
  });
@@ -6910,6 +6989,10 @@ function momentumApiMiddleware(config) {
6910
6989
  res.json({ doc: published, message: "Document published successfully" });
6911
6990
  } catch (error) {
6912
6991
  const message = sanitizeErrorMessage(error, "Unknown error");
6992
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6993
+ res.status(403).json({ error: "Access denied" });
6994
+ return;
6995
+ }
6913
6996
  res.status(500).json({ error: "Failed to publish document", message });
6914
6997
  }
6915
6998
  });
@@ -6939,6 +7022,10 @@ function momentumApiMiddleware(config) {
6939
7022
  res.json(result);
6940
7023
  } catch (error) {
6941
7024
  const message = sanitizeErrorMessage(error, "Unknown error");
7025
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7026
+ res.status(403).json({ error: "Access denied" });
7027
+ return;
7028
+ }
6942
7029
  res.status(500).json({ error: "Failed to schedule publish", message });
6943
7030
  }
6944
7031
  });
@@ -6960,6 +7047,10 @@ function momentumApiMiddleware(config) {
6960
7047
  res.json({ message: "Scheduled publish cancelled" });
6961
7048
  } catch (error) {
6962
7049
  const message = sanitizeErrorMessage(error, "Unknown error");
7050
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7051
+ res.status(403).json({ error: "Access denied" });
7052
+ return;
7053
+ }
6963
7054
  res.status(500).json({ error: "Failed to cancel scheduled publish", message });
6964
7055
  }
6965
7056
  });
@@ -6981,6 +7072,10 @@ function momentumApiMiddleware(config) {
6981
7072
  res.json({ doc: unpublished, message: "Document unpublished successfully" });
6982
7073
  } catch (error) {
6983
7074
  const message = sanitizeErrorMessage(error, "Unknown error");
7075
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7076
+ res.status(403).json({ error: "Access denied" });
7077
+ return;
7078
+ }
6984
7079
  res.status(500).json({ error: "Failed to unpublish document", message });
6985
7080
  }
6986
7081
  });
@@ -7003,6 +7098,10 @@ function momentumApiMiddleware(config) {
7003
7098
  res.json({ version: draft, message: "Draft saved successfully" });
7004
7099
  } catch (error) {
7005
7100
  const message = sanitizeErrorMessage(error, "Unknown error");
7101
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7102
+ res.status(403).json({ error: "Access denied" });
7103
+ return;
7104
+ }
7006
7105
  res.status(500).json({ error: "Failed to save draft", message });
7007
7106
  }
7008
7107
  });
@@ -7028,10 +7127,15 @@ function momentumApiMiddleware(config) {
7028
7127
  });
7029
7128
  return;
7030
7129
  }
7031
- const differences = await versionOps.compare(versionId1, versionId2);
7130
+ const parentId = req.params["id"];
7131
+ const differences = await versionOps.compare(versionId1, versionId2, parentId);
7032
7132
  res.json({ differences });
7033
7133
  } catch (error) {
7034
7134
  const message = sanitizeErrorMessage(error, "Unknown error");
7135
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7136
+ res.status(403).json({ error: "Access denied" });
7137
+ return;
7138
+ }
7035
7139
  res.status(500).json({ error: "Failed to compare versions", message });
7036
7140
  }
7037
7141
  });
package/index.js CHANGED
@@ -2291,6 +2291,14 @@ function createSeedHelpers() {
2291
2291
  };
2292
2292
  }
2293
2293
 
2294
+ // libs/core/src/lib/versions/version.types.ts
2295
+ function hasVersionDrafts(collection) {
2296
+ const v = collection.versions;
2297
+ if (!v || typeof v === "boolean")
2298
+ return false;
2299
+ return !!v.drafts;
2300
+ }
2301
+
2294
2302
  // libs/server-core/src/lib/field-access.ts
2295
2303
  function hasFieldAccessControl(fields) {
2296
2304
  for (const field of fields) {
@@ -2877,13 +2885,18 @@ var VersionOperationsImpl = class {
2877
2885
  }
2878
2886
  return "draft";
2879
2887
  }
2880
- async compare(versionId1, versionId2) {
2888
+ async compare(versionId1, versionId2, parentId) {
2881
2889
  await this.checkAccess("readVersions");
2882
2890
  const version1 = await this.findVersionById(versionId1);
2883
2891
  const version2 = await this.findVersionById(versionId2);
2884
2892
  if (!version1 || !version2) {
2885
2893
  throw new Error("One or both versions not found");
2886
2894
  }
2895
+ if (parentId) {
2896
+ if (version1.parent !== parentId || version2.parent !== parentId) {
2897
+ throw new Error("Version does not belong to the specified document");
2898
+ }
2899
+ }
2887
2900
  const data1 = version1.version;
2888
2901
  const data2 = version2.version;
2889
2902
  if (!isRecord(data1) || !isRecord(data2)) {
@@ -2938,6 +2951,9 @@ var VersionOperationsImpl = class {
2938
2951
  // Private Helpers
2939
2952
  // ============================================
2940
2953
  async checkAccess(operation) {
2954
+ if (this.context.overrideAccess) {
2955
+ return;
2956
+ }
2941
2957
  const accessFn = this.collectionConfig.access?.[operation];
2942
2958
  if (!accessFn) {
2943
2959
  return;
@@ -3094,6 +3110,12 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3094
3110
  Object.assign(whereParams, constraints);
3095
3111
  }
3096
3112
  }
3113
+ if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
3114
+ const canSeeDrafts = await this.canReadDrafts();
3115
+ if (!canSeeDrafts) {
3116
+ whereParams["_status"] = "published";
3117
+ }
3118
+ }
3097
3119
  const query = {
3098
3120
  ...queryOptions,
3099
3121
  ...whereParams,
@@ -3156,6 +3178,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3156
3178
  doc[softDeleteField]) {
3157
3179
  return null;
3158
3180
  }
3181
+ if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
3182
+ const canSeeDrafts = await this.canReadDrafts();
3183
+ if (!canSeeDrafts) {
3184
+ const record = doc;
3185
+ if (record["_status"] !== "published") {
3186
+ return null;
3187
+ }
3188
+ }
3189
+ }
3159
3190
  if (!this.matchesDefaultWhereConstraints(doc)) {
3160
3191
  return null;
3161
3192
  }
@@ -3241,6 +3272,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3241
3272
  if (!this.matchesDefaultWhereConstraints(originalDoc)) {
3242
3273
  throw new DocumentNotFoundError(this.slug, id);
3243
3274
  }
3275
+ if (hasVersionDrafts(this.collectionConfig) && this.adapter.createVersion) {
3276
+ const status = originalDoc["_status"] ?? "draft";
3277
+ await this.adapter.createVersion(this.slug, id, originalDoc, { status });
3278
+ const versionsConfig = this.collectionConfig.versions;
3279
+ const maxPerDoc = typeof versionsConfig === "object" && versionsConfig !== null ? versionsConfig.maxPerDoc : void 0;
3280
+ if (maxPerDoc && this.adapter.deleteVersions) {
3281
+ await this.adapter.deleteVersions(this.slug, id, maxPerDoc);
3282
+ }
3283
+ }
3244
3284
  let processedData = data;
3245
3285
  const softDeleteField = getSoftDeleteField(this.collectionConfig);
3246
3286
  if (softDeleteField && softDeleteField in processedData) {
@@ -3572,6 +3612,30 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
3572
3612
  throw new AccessDeniedError(operation, this.slug);
3573
3613
  }
3574
3614
  }
3615
+ /**
3616
+ * Check if the current user can see draft documents (non-throwing).
3617
+ * Uses `access.readDrafts` if configured, otherwise falls back to `access.update`.
3618
+ */
3619
+ async canReadDrafts() {
3620
+ if (!this.context.user)
3621
+ return false;
3622
+ const readDraftsFn = this.collectionConfig.access?.readDrafts;
3623
+ if (readDraftsFn) {
3624
+ try {
3625
+ return !!await Promise.resolve(readDraftsFn({ req: this.buildRequestContext() }));
3626
+ } catch {
3627
+ return false;
3628
+ }
3629
+ }
3630
+ const updateFn = this.collectionConfig.access?.update;
3631
+ if (!updateFn)
3632
+ return true;
3633
+ try {
3634
+ return !!await Promise.resolve(updateFn({ req: this.buildRequestContext() }));
3635
+ } catch {
3636
+ return false;
3637
+ }
3638
+ }
3575
3639
  buildRequestContext() {
3576
3640
  return {
3577
3641
  user: this.context.user
@@ -4708,7 +4772,7 @@ function startPublishScheduler(adapter, collections, options) {
4708
4772
  const scheduled = await adapter.findScheduledDocuments(collection.slug, now);
4709
4773
  for (const doc of scheduled) {
4710
4774
  try {
4711
- const api = getMomentumAPI();
4775
+ const api = getMomentumAPI().setContext({ overrideAccess: true });
4712
4776
  const versionOps = api.collection(collection.slug).versions();
4713
4777
  if (!versionOps)
4714
4778
  continue;
@@ -4828,8 +4892,9 @@ function buildGraphQLSchema(collections) {
4828
4892
  }
4829
4893
  function getOrCreateEnum(field, parentName) {
4830
4894
  const key = `${parentName}_${field.name}`;
4831
- if (enumCache.has(key)) {
4832
- return enumCache.get(key);
4895
+ const cached = enumCache.get(key);
4896
+ if (cached) {
4897
+ return cached;
4833
4898
  }
4834
4899
  const values = {};
4835
4900
  for (const opt of field.options) {
@@ -4940,8 +5005,9 @@ function buildGraphQLSchema(collections) {
4940
5005
  }
4941
5006
  }
4942
5007
  function getOrCreateObjectType(fields, typeName) {
4943
- if (typeCache.has(typeName)) {
4944
- return typeCache.get(typeName);
5008
+ const cachedType = typeCache.get(typeName);
5009
+ if (cachedType) {
5010
+ return cachedType;
4945
5011
  }
4946
5012
  const objType = new GraphQLObjectType({
4947
5013
  name: typeName,
@@ -4962,8 +5028,9 @@ function buildGraphQLSchema(collections) {
4962
5028
  }
4963
5029
  function getOrCreateInputType(fields, typeName) {
4964
5030
  const inputName = `${typeName}Input`;
4965
- if (inputCache.has(inputName)) {
4966
- return inputCache.get(inputName);
5031
+ const cachedInput = inputCache.get(inputName);
5032
+ if (cachedInput) {
5033
+ return cachedInput;
4967
5034
  }
4968
5035
  const inputType = new GraphQLInputObjectType({
4969
5036
  name: inputName,
@@ -6792,6 +6859,10 @@ function momentumApiMiddleware(config) {
6792
6859
  res.json(result);
6793
6860
  } catch (error) {
6794
6861
  const message = sanitizeErrorMessage(error, "Unknown error");
6862
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6863
+ res.status(403).json({ error: "Access denied" });
6864
+ return;
6865
+ }
6795
6866
  res.status(500).json({ error: "Failed to fetch versions", message });
6796
6867
  }
6797
6868
  });
@@ -6820,6 +6891,10 @@ function momentumApiMiddleware(config) {
6820
6891
  res.json(version);
6821
6892
  } catch (error) {
6822
6893
  const message = sanitizeErrorMessage(error, "Unknown error");
6894
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6895
+ res.status(403).json({ error: "Access denied" });
6896
+ return;
6897
+ }
6823
6898
  res.status(500).json({ error: "Failed to fetch version", message });
6824
6899
  }
6825
6900
  });
@@ -6858,6 +6933,10 @@ function momentumApiMiddleware(config) {
6858
6933
  res.status(400).json({ error: "Version parent mismatch", message });
6859
6934
  return;
6860
6935
  }
6936
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6937
+ res.status(403).json({ error: "Access denied" });
6938
+ return;
6939
+ }
6861
6940
  res.status(500).json({ error: "Failed to restore version", message });
6862
6941
  }
6863
6942
  });
@@ -6879,6 +6958,10 @@ function momentumApiMiddleware(config) {
6879
6958
  res.json({ doc: published, message: "Document published successfully" });
6880
6959
  } catch (error) {
6881
6960
  const message = sanitizeErrorMessage(error, "Unknown error");
6961
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6962
+ res.status(403).json({ error: "Access denied" });
6963
+ return;
6964
+ }
6882
6965
  res.status(500).json({ error: "Failed to publish document", message });
6883
6966
  }
6884
6967
  });
@@ -6908,6 +6991,10 @@ function momentumApiMiddleware(config) {
6908
6991
  res.json(result);
6909
6992
  } catch (error) {
6910
6993
  const message = sanitizeErrorMessage(error, "Unknown error");
6994
+ if (error instanceof Error && error.name === "AccessDeniedError") {
6995
+ res.status(403).json({ error: "Access denied" });
6996
+ return;
6997
+ }
6911
6998
  res.status(500).json({ error: "Failed to schedule publish", message });
6912
6999
  }
6913
7000
  });
@@ -6929,6 +7016,10 @@ function momentumApiMiddleware(config) {
6929
7016
  res.json({ message: "Scheduled publish cancelled" });
6930
7017
  } catch (error) {
6931
7018
  const message = sanitizeErrorMessage(error, "Unknown error");
7019
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7020
+ res.status(403).json({ error: "Access denied" });
7021
+ return;
7022
+ }
6932
7023
  res.status(500).json({ error: "Failed to cancel scheduled publish", message });
6933
7024
  }
6934
7025
  });
@@ -6950,6 +7041,10 @@ function momentumApiMiddleware(config) {
6950
7041
  res.json({ doc: unpublished, message: "Document unpublished successfully" });
6951
7042
  } catch (error) {
6952
7043
  const message = sanitizeErrorMessage(error, "Unknown error");
7044
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7045
+ res.status(403).json({ error: "Access denied" });
7046
+ return;
7047
+ }
6953
7048
  res.status(500).json({ error: "Failed to unpublish document", message });
6954
7049
  }
6955
7050
  });
@@ -6972,6 +7067,10 @@ function momentumApiMiddleware(config) {
6972
7067
  res.json({ version: draft, message: "Draft saved successfully" });
6973
7068
  } catch (error) {
6974
7069
  const message = sanitizeErrorMessage(error, "Unknown error");
7070
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7071
+ res.status(403).json({ error: "Access denied" });
7072
+ return;
7073
+ }
6975
7074
  res.status(500).json({ error: "Failed to save draft", message });
6976
7075
  }
6977
7076
  });
@@ -6997,10 +7096,15 @@ function momentumApiMiddleware(config) {
6997
7096
  });
6998
7097
  return;
6999
7098
  }
7000
- const differences = await versionOps.compare(versionId1, versionId2);
7099
+ const parentId = req.params["id"];
7100
+ const differences = await versionOps.compare(versionId1, versionId2, parentId);
7001
7101
  res.json({ differences });
7002
7102
  } catch (error) {
7003
7103
  const message = sanitizeErrorMessage(error, "Unknown error");
7104
+ if (error instanceof Error && error.name === "AccessDeniedError") {
7105
+ res.status(403).json({ error: "Access denied" });
7106
+ return;
7107
+ }
7004
7108
  res.status(500).json({ error: "Failed to compare versions", message });
7005
7109
  }
7006
7110
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentumcms/server-express",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Express adapter for Momentum CMS with Angular SSR support",
5
5
  "license": "MIT",
6
6
  "author": "Momentum CMS Contributors",