vasuzex 2.3.6 → 2.3.8

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.
@@ -34,7 +34,10 @@ export class Model extends GuruORMModel {
34
34
 
35
35
  // Boot tracking
36
36
  static booted = false;
37
- static globalScopes = {};
37
+ // NOTE: Do NOT re-declare globalScopes here.
38
+ // GuruORM already initialises it as `new Map()` on the base Model class.
39
+ // Overriding with `{}` would break applyScopes() → getGlobalScopes() which
40
+ // calls map.get() — crashing any model that registers a global scope.
38
41
 
39
42
  // Event dispatcher
40
43
  static dispatcher = null;
@@ -140,15 +143,15 @@ export class Model extends GuruORMModel {
140
143
  }
141
144
 
142
145
  /**
143
- * Set attribute with mutator support
146
+ * Set attribute with mutator support.
147
+ * Delegates accessor lookup to GuruORM's cached getMutator().
144
148
  */
145
149
  setAttribute(key, value) {
146
150
  // Skip mutators when hydrating from database
147
151
  if (!this.isHydrating) {
148
- // Check if mutator exists (setXxxAttribute method)
149
- const mutator = `set${this.studly(key)}Attribute`;
150
- if (typeof this[mutator] === 'function') {
151
- const result = this[mutator](value);
152
+ const mutator = this.getMutator(key); // cached in GuruORM core
153
+ if (mutator !== null) {
154
+ const result = mutator.call(this, value);
152
155
  // If mutator returns a promise, store it for later resolution
153
156
  if (result instanceof Promise) {
154
157
  this.pendingMutators.push(result);
@@ -169,25 +172,21 @@ export class Model extends GuruORMModel {
169
172
  }
170
173
 
171
174
  /**
172
- * Get attribute with accessor support and lazy loading
173
- * Calls GuruORM's getAttribute to get lazy loading support
175
+ * Get attribute with accessor support and lazy loading.
176
+ * Delegates accessor lookup to GuruORM's cached getAccessor().
174
177
  */
175
178
  getAttribute(key) {
176
- // Check if accessor exists (getXxxAttribute method)
177
- const accessor = `get${this.studly(key)}Attribute`;
178
- if (typeof this[accessor] === 'function') {
179
- return this[accessor](this.attributes[key]);
179
+ const accessor = this.getAccessor(key); // cached in GuruORM core
180
+ if (accessor !== null) {
181
+ return accessor.call(this, this.attributes[key]);
180
182
  }
181
183
 
182
- // Check if appended (computed attributes)
184
+ // Safety: appended (computed) attributes without an accessor return null
183
185
  if (this.constructor.appends.includes(key)) {
184
- return this[accessor] ? this[accessor]() : null;
186
+ return null;
185
187
  }
186
188
 
187
- // Delegate to GuruORM's getAttribute for:
188
- // - Lazy loading of relationships
189
- // - Loaded relations
190
- // - Attributes with casting
189
+ // Delegate to GuruORM's getAttribute for lazy loading, relations, and casts
191
190
  return super.getAttribute(key);
192
191
  }
193
192
 
@@ -755,20 +754,10 @@ export class Model extends GuruORMModel {
755
754
  static async find(id) {
756
755
  try {
757
756
  const pk = this.primaryKey || 'id';
758
- let query = this.where(pk, id);
759
-
760
- // Apply soft deletes
761
- if (this.softDeletes) {
762
- query = query.whereNull(this.deletedAt);
763
- }
764
-
765
- const result = await query.first();
766
-
767
- if (!result) {
768
- return null;
769
- }
770
-
771
- return this.newFromBuilder(result);
757
+ // Note: this.where() calls this.query() which already applies the soft-delete
758
+ // scope via whereNull(qualifiedColumn). Do NOT add whereNull again here.
759
+ // GuruORM Builder.first() returns a hydrated model instance (or null) directly.
760
+ return await this.where(pk, id).first();
772
761
  } catch (error) {
773
762
  // Log database error if logger is available
774
763
  if (this.logger) {
@@ -797,35 +786,19 @@ export class Model extends GuruORMModel {
797
786
  * Get all records (override GuruORM's all)
798
787
  */
799
788
  static async all() {
800
- let query = this.query();
801
-
802
- // Apply soft deletes
803
- if (this.softDeletes) {
804
- query = query.whereNull(this.deletedAt);
805
- }
806
-
807
- const results = await query.get();
808
- return results.map(result => this.newFromBuilder(result));
789
+ // query() already applies the soft-delete scope - no duplicate whereNull needed.
790
+ // GuruORM Builder.get() returns a Collection of hydrated model instances.
791
+ // Spread into a plain Array to maintain the same return type as before.
792
+ return [...await this.query().get()];
809
793
  }
810
794
 
811
795
  /**
812
796
  * Get first record
813
797
  */
814
798
  static async first() {
815
- let query = this.query();
816
-
817
- // Apply soft deletes
818
- if (this.softDeletes) {
819
- query = query.whereNull(this.deletedAt);
820
- }
821
-
822
- const result = await query.first();
823
-
824
- if (!result) {
825
- return null;
826
- }
827
-
828
- return this.newFromBuilder(result);
799
+ // query() already applies the soft-delete scope.
800
+ // GuruORM Builder.first() returns a hydrated model instance or null directly.
801
+ return await this.query().first();
829
802
  }
830
803
 
831
804
  /**
@@ -833,31 +806,24 @@ export class Model extends GuruORMModel {
833
806
  */
834
807
  static async paginate(perPage = null, page = 1) {
835
808
  perPage = perPage || this.perPage;
836
-
837
809
  const offset = (page - 1) * perPage;
838
-
839
- let query = this.query();
840
-
841
- // Apply soft deletes
842
- if (this.softDeletes) {
843
- query = query.whereNull(this.deletedAt);
844
- }
845
-
846
- const countResult = await query.count('* as count');
847
- const totalCount = countResult[0]?.count || 0;
848
-
849
- const results = await query.limit(perPage).offset(offset).get();
850
810
 
851
- const items = results.map(result => this.newFromBuilder(result));
811
+ // Use two separate queries:
812
+ // 1. count query — GuruORM's count() returns a plain number, not an array.
813
+ // 2. data query — limit/offset applied on its own fresh builder.
814
+ // Both go through this.query() which already applies the soft-delete scope.
815
+ // Do NOT add whereNull again here — that would produce a duplicate SQL condition.
816
+ const totalCount = await this.query().count();
817
+ const items = [...await this.query().limit(perPage).offset(offset).get()];
852
818
 
853
819
  return {
854
820
  data: items,
855
821
  total: totalCount,
856
822
  perPage,
857
823
  currentPage: page,
858
- lastPage: Math.ceil(totalCount / perPage),
824
+ lastPage: Math.ceil(totalCount / perPage) || 1,
859
825
  from: offset + 1,
860
- to: offset + items.length
826
+ to: offset + items.length,
861
827
  };
862
828
  }
863
829
 
@@ -887,17 +853,24 @@ export class Model extends GuruORMModel {
887
853
  query = query.whereNull(qualifiedColumn);
888
854
  }
889
855
 
890
- // Monkey-patch the update method to add timestamps
891
- const modelClass = this;
892
- const originalUpdate = query.update;
893
-
894
- query.update = async function(data) {
895
- // Add updated_at if timestamps are enabled
896
- if (modelClass.timestamps && modelClass.updatedAt && data[modelClass.updatedAt] === undefined) {
897
- data[modelClass.updatedAt] = new Date();
856
+ // Inject updated_at on bulk Builder-level updates (e.g. User.where(...).update({}))
857
+ // Cache the wrapper closure per model class so it is created only ONCE, not on
858
+ // every query() call (which would allocate a new closure per DB operation).
859
+ if (this.timestamps && this.updatedAt) {
860
+ if (!Object.prototype.hasOwnProperty.call(this, '_cachedUpdateFn')) {
861
+ const modelClass = this;
862
+ // query.update is EloquentBuilder.prototype.update a stable reference
863
+ // captured once and reused for the lifetime of this model class.
864
+ const protoUpdate = query.update;
865
+ this._cachedUpdateFn = async function timestampedUpdate(data) {
866
+ if (data[modelClass.updatedAt] === undefined) {
867
+ data[modelClass.updatedAt] = new Date();
868
+ }
869
+ return protoUpdate.call(this, data);
870
+ };
898
871
  }
899
- return await originalUpdate.call(this, data);
900
- };
872
+ query.update = this._cachedUpdateFn;
873
+ }
901
874
 
902
875
  return query;
903
876
  }
@@ -77,6 +77,7 @@ export function EnhancedPhotoManager({
77
77
  deletePhoto,
78
78
  deleteMultiplePhotos,
79
79
  reorderPhotos,
80
+ setPrimaryPhoto,
80
81
  } = photosHook;
81
82
 
82
83
  const [selectedPhotos, setSelectedPhotos] = useState([]);
@@ -255,6 +256,7 @@ export function EnhancedPhotoManager({
255
256
  isSelected={selectedPhotos.includes(photo.id)}
256
257
  onSelect={handlePhotoSelect}
257
258
  onDelete={handlePhotoDelete}
259
+ onSetPrimary={setPrimaryPhoto || undefined}
258
260
  disabled={loading}
259
261
  />
260
262
  ))}
@@ -294,6 +296,7 @@ EnhancedPhotoManager.propTypes = {
294
296
  deletePhoto: PropTypes.func,
295
297
  deleteMultiplePhotos: PropTypes.func,
296
298
  reorderPhotos: PropTypes.func,
299
+ setPrimaryPhoto: PropTypes.func,
297
300
  }).isRequired,
298
301
  /** Section title */
299
302
  title: PropTypes.string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vasuzex",
3
- "version": "2.3.6",
3
+ "version": "2.3.8",
4
4
  "description": "Laravel-inspired framework for Node.js monorepos - V2 with optimized dependencies",
5
5
  "type": "module",
6
6
  "main": "./framework/index.js",
@@ -108,7 +108,7 @@
108
108
  "express-rate-limit": "^8.2.1",
109
109
  "firebase-admin": "^13.6.0",
110
110
  "fs-extra": "^11.3.2",
111
- "guruorm": "^2.0.20",
111
+ "guruorm": "^2.1.20",
112
112
  "helmet": "^8.1.0",
113
113
  "inquirer": "^9.3.8",
114
114
  "ip2location-nodejs": "^9.7.0",