@zakodium/adonis-mongodb 0.10.4 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Migration.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  ClientSession,
5
5
  Db,
6
6
  IndexSpecification,
7
+ DropIndexesOptions,
7
8
  } from 'mongodb';
8
9
 
9
10
  import type {
@@ -13,18 +14,26 @@ import type {
13
14
 
14
15
  enum MigrationType {
15
16
  CreateCollection = 'CreateCollection',
17
+ DropIndex = 'DropIndex',
16
18
  CreateIndex = 'CreateIndex',
17
19
  Custom = 'Custom',
18
20
  }
19
21
 
20
22
  interface CreateCollectionOperation {
21
23
  type: MigrationType.CreateCollection;
22
- name: string;
24
+ collectionName: string;
25
+ }
26
+
27
+ interface DropIndexOperation {
28
+ type: MigrationType.DropIndex;
29
+ collectionName: string;
30
+ indexName: string;
31
+ options?: DropIndexesOptions;
23
32
  }
24
33
 
25
34
  interface CreateIndexOperation {
26
35
  type: MigrationType.CreateIndex;
27
- name: string;
36
+ collectionName: string;
28
37
  index: IndexSpecification;
29
38
  options?: CreateIndexesOptions;
30
39
  }
@@ -36,6 +45,7 @@ interface CustomOperation {
36
45
 
37
46
  type MigrationOperation =
38
47
  | CreateCollectionOperation
48
+ | DropIndexOperation
39
49
  | CreateIndexOperation
40
50
  | CustomOperation;
41
51
 
@@ -67,7 +77,20 @@ export default function createMigration(Database: DatabaseContract): any {
67
77
  public createCollection(collectionName: string): void {
68
78
  this.$operations.push({
69
79
  type: MigrationType.CreateCollection,
70
- name: collectionName,
80
+ collectionName,
81
+ });
82
+ }
83
+
84
+ public dropIndex(
85
+ collectionName: string,
86
+ indexName: string,
87
+ options?: DropIndexesOptions,
88
+ ): void {
89
+ this.$operations.push({
90
+ type: MigrationType.DropIndex,
91
+ collectionName,
92
+ indexName,
93
+ options,
71
94
  });
72
95
  }
73
96
 
@@ -78,7 +101,7 @@ export default function createMigration(Database: DatabaseContract): any {
78
101
  ): void {
79
102
  this.$operations.push({
80
103
  type: MigrationType.CreateIndex,
81
- name: collectionName,
104
+ collectionName,
82
105
  index,
83
106
  options,
84
107
  });
@@ -94,6 +117,7 @@ export default function createMigration(Database: DatabaseContract): any {
94
117
  public async execUp(): Promise<void> {
95
118
  this.up();
96
119
  await this._createCollections();
120
+ await this._dropIndexes();
97
121
  await this._createIndexes();
98
122
  await this._executeDeferred();
99
123
  }
@@ -113,8 +137,10 @@ export default function createMigration(Database: DatabaseContract): any {
113
137
  private async _createCollections(): Promise<void> {
114
138
  const db = await this.$connection.database();
115
139
  for (const op of this.$operations.filter(isCreateCollection)) {
116
- this.$logger.info(`Creating collection ${op.name}`);
117
- await db.createCollection(op.name, { session: this.$session });
140
+ this.$logger.info(`Creating collection ${op.collectionName}`);
141
+ await db.createCollection(op.collectionName, {
142
+ session: this.$session,
143
+ });
118
144
  }
119
145
  }
120
146
 
@@ -125,15 +151,29 @@ export default function createMigration(Database: DatabaseContract): any {
125
151
  }
126
152
  }
127
153
 
154
+ private async _dropIndexes(): Promise<void> {
155
+ const db = await this.$connection.database();
156
+ for (const op of this.$operations.filter(isDropIndex)) {
157
+ this.$logger.info(
158
+ `Dropping index ${op.indexName} on ${op.collectionName}`,
159
+ );
160
+ const collection = db.collection(op.collectionName);
161
+ // Index deletion cannot be done in a transaction.
162
+ await collection.dropIndex(op.indexName, { ...op.options });
163
+ }
164
+ }
165
+
128
166
  private async _createIndexes(): Promise<void> {
129
167
  const db = await this.$connection.database();
130
168
  const collections = await this._listCollections();
131
169
  for (const op of this.$operations.filter(isCreateIndex)) {
132
- this.$logger.info(`Creating index on ${op.name}`);
133
- await db.createIndex(op.name, op.index, {
170
+ this.$logger.info(`Creating index on ${op.collectionName}`);
171
+ await db.createIndex(op.collectionName, op.index, {
134
172
  ...op.options,
135
- // index creation will fail if collection pre-exists the transaction
136
- session: collections.includes(op.name) ? undefined : this.$session,
173
+ // Index creation will fail if collection pre-exists the transaction.
174
+ session: collections.includes(op.collectionName)
175
+ ? undefined
176
+ : this.$session,
137
177
  });
138
178
  }
139
179
  }
@@ -154,6 +194,10 @@ function isCreateIndex(op: MigrationOperation): op is CreateIndexOperation {
154
194
  return op.type === MigrationType.CreateIndex;
155
195
  }
156
196
 
197
+ function isDropIndex(op: MigrationOperation): op is DropIndexOperation {
198
+ return op.type === MigrationType.DropIndex;
199
+ }
200
+
157
201
  function isCustom(op: MigrationOperation): op is CustomOperation {
158
202
  return op.type === MigrationType.Custom;
159
203
  }
@@ -1,3 +1,5 @@
1
+ import assert from 'assert';
2
+
1
3
  import { defineStaticProperty, Exception } from '@poppinss/utils';
2
4
  import { cloneDeep, isEqual, pickBy, snakeCase } from 'lodash';
3
5
  import {
@@ -8,9 +10,13 @@ import {
8
10
  CountOptions,
9
11
  DeleteOptions,
10
12
  DistinctOptions,
13
+ Document,
14
+ ExplainVerbosityLike,
11
15
  Filter,
12
16
  FindOptions,
13
17
  InsertOneOptions,
18
+ SortDirection,
19
+ WithId,
14
20
  } from 'mongodb';
15
21
  import pluralize from 'pluralize';
16
22
 
@@ -23,6 +29,8 @@ import {
23
29
  ModelAdapterOptions,
24
30
  ModelDocumentOptions,
25
31
  FieldOptions,
32
+ QuerySortObject,
33
+ ForbiddenQueryOptions,
26
34
  } from '@ioc:Zakodium/Mongodb/Odm';
27
35
 
28
36
  import { proxyHandler } from './proxyHandler';
@@ -37,32 +45,97 @@ function mergeDriverOptions<
37
45
  } as DriverOptionType;
38
46
  }
39
47
 
40
- function ensureSort(options?: FindOptions): void {
41
- if (!options || options.sort) return;
42
- options.sort = {
43
- _id: -1,
44
- };
48
+ interface QueryLocalOptions {
49
+ sort: QuerySortObject;
50
+ skip?: number;
51
+ limit?: number;
45
52
  }
46
53
 
54
+ const forbiddenQueryOptions: ForbiddenQueryOptions[] = [
55
+ 'sort',
56
+ 'skip',
57
+ 'limit',
58
+ 'explain',
59
+ ];
60
+
47
61
  class Query<ModelType extends typeof BaseModel>
48
62
  implements QueryContract<InstanceType<ModelType>>
49
63
  {
64
+ private localCustomSort = false;
65
+ private localOptions: QueryLocalOptions = {
66
+ sort: {
67
+ _id: 'descending',
68
+ },
69
+ };
70
+
71
+ private getDriverOptions(): FindOptions<
72
+ ModelAttributes<InstanceType<ModelType>>
73
+ > {
74
+ return { ...mergeDriverOptions(this.options), ...this.localOptions };
75
+ }
76
+
50
77
  public constructor(
51
78
  private filter: Filter<ModelAttributes<InstanceType<ModelType>>>,
52
79
  private options:
53
80
  | ModelAdapterOptions<
54
- FindOptions<ModelAttributes<InstanceType<ModelType>>>
81
+ Omit<
82
+ FindOptions<ModelAttributes<InstanceType<ModelType>>>,
83
+ ForbiddenQueryOptions
84
+ >
55
85
  >
56
86
  | undefined,
57
87
  private modelConstructor: ModelType,
58
- ) {}
88
+ ) {
89
+ if (options?.driverOptions) {
90
+ for (const key of forbiddenQueryOptions) {
91
+ if (key in options.driverOptions) {
92
+ throw new TypeError(`${key} is not allowed in query's driverOptions`);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ public sort(sort: QuerySortObject): this {
99
+ if (!this.localCustomSort) {
100
+ this.localCustomSort = true;
101
+ this.localOptions.sort = sort;
102
+ } else {
103
+ Object.assign(this.localOptions.sort, sort);
104
+ }
105
+ return this;
106
+ }
107
+
108
+ public sortBy(field: string, direction: SortDirection = 'ascending'): this {
109
+ return this.sort({ [field]: direction });
110
+ }
111
+
112
+ public skip(skip: number): this {
113
+ if (!Number.isInteger(skip)) {
114
+ throw new TypeError(`skip must be an integer`);
115
+ }
116
+ if (skip < 0) {
117
+ throw new TypeError(`skip must be at least zero`);
118
+ }
119
+ this.localOptions.skip = skip;
120
+ return this;
121
+ }
122
+
123
+ public limit(limit: number): this {
124
+ if (!Number.isInteger(limit)) {
125
+ throw new TypeError(`limit must be an integer`);
126
+ }
127
+ if (limit < 1) {
128
+ throw new TypeError(`limit must be at least one`);
129
+ }
130
+ this.localOptions.limit = limit;
131
+ return this;
132
+ }
59
133
 
60
134
  public async first(): Promise<InstanceType<ModelType> | null> {
61
135
  const collection = await this.modelConstructor.getCollection();
62
- const driverOptions = mergeDriverOptions(this.options);
63
- ensureSort(driverOptions);
136
+ const driverOptions = this.getDriverOptions();
64
137
  const result = await collection.findOne(this.filter, driverOptions);
65
- if (result === undefined) {
138
+ if (result === null) {
66
139
  return null;
67
140
  }
68
141
  const instance = new this.modelConstructor(
@@ -87,9 +160,13 @@ class Query<ModelType extends typeof BaseModel>
87
160
 
88
161
  public async all(): Promise<Array<InstanceType<ModelType>>> {
89
162
  const collection = await this.modelConstructor.getCollection();
90
- const driverOptions = mergeDriverOptions(this.options);
91
- ensureSort(driverOptions);
92
- const result = await collection.find(this.filter, driverOptions).toArray();
163
+ const driverOptions = this.getDriverOptions();
164
+ const result = await collection
165
+ .find(
166
+ this.filter as Filter<WithId<ModelAttributes<InstanceType<ModelType>>>>,
167
+ driverOptions,
168
+ )
169
+ .toArray();
93
170
  return result.map(
94
171
  (value) =>
95
172
  new this.modelConstructor(
@@ -106,7 +183,7 @@ class Query<ModelType extends typeof BaseModel>
106
183
 
107
184
  public async count(): Promise<number> {
108
185
  const collection = await this.modelConstructor.getCollection();
109
- const driverOptions = mergeDriverOptions(this.options);
186
+ const driverOptions = this.getDriverOptions();
110
187
  return collection.countDocuments(
111
188
  this.filter,
112
189
  driverOptions as CountOptions,
@@ -115,7 +192,7 @@ class Query<ModelType extends typeof BaseModel>
115
192
 
116
193
  public async distinct<T = unknown>(key: string): Promise<T[]> {
117
194
  const collection = await this.modelConstructor.getCollection();
118
- const driverOptions = mergeDriverOptions(this.options);
195
+ const driverOptions = this.getDriverOptions();
119
196
  return collection.distinct(
120
197
  key,
121
198
  this.filter,
@@ -123,13 +200,26 @@ class Query<ModelType extends typeof BaseModel>
123
200
  );
124
201
  }
125
202
 
203
+ public async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
204
+ const collection = await this.modelConstructor.getCollection();
205
+ const driverOptions = this.getDriverOptions();
206
+ return collection
207
+ .find(
208
+ this.filter as Filter<WithId<ModelAttributes<InstanceType<ModelType>>>>,
209
+ driverOptions,
210
+ )
211
+ .explain(verbosity);
212
+ }
213
+
126
214
  public async *[Symbol.asyncIterator](): AsyncIterableIterator<
127
215
  InstanceType<ModelType>
128
216
  > {
129
217
  const collection = await this.modelConstructor.getCollection();
130
- const driverOptions = mergeDriverOptions(this.options);
131
- ensureSort(driverOptions);
132
- for await (const value of collection.find(this.filter, driverOptions)) {
218
+ const driverOptions = this.getDriverOptions();
219
+ for await (const value of collection.find(
220
+ this.filter as Filter<WithId<ModelAttributes<InstanceType<ModelType>>>>,
221
+ driverOptions,
222
+ )) {
133
223
  if (value === null) continue;
134
224
  yield new this.modelConstructor(
135
225
  value,
@@ -153,6 +243,13 @@ interface InternalModelConstructorOptions {
153
243
  session?: ClientSession;
154
244
  }
155
245
 
246
+ function ensureSort(options?: FindOptions): void {
247
+ if (!options || options.sort) return;
248
+ options.sort = {
249
+ _id: -1,
250
+ };
251
+ }
252
+
156
253
  function hasOwn(object: unknown, key: string): boolean {
157
254
  return Object.prototype.hasOwnProperty.call(object, key);
158
255
  }
@@ -173,15 +270,17 @@ export class BaseModel {
173
270
  public readonly createdAt: Date;
174
271
  public readonly updatedAt: Date;
175
272
 
176
- public $isDeleted: boolean;
273
+ public $original: Record<string, unknown>;
274
+ public $attributes: Record<string, unknown>;
275
+
276
+ public $isPersisted = false;
277
+ public $isLocal = true;
278
+ public $isDeleted = false;
177
279
 
178
280
  protected $collection: Collection<
179
281
  ModelAttributes<MongodbDocument<unknown>>
180
282
  > | null = null;
181
- protected $originalData: Record<string, unknown>;
182
- protected $currentData: Record<string, unknown>;
183
283
  protected $options: InternalModelConstructorOptions;
184
- protected $alreadySaved: boolean;
185
284
 
186
285
  public constructor(
187
286
  dbObj?: Record<string, unknown>,
@@ -189,11 +288,11 @@ export class BaseModel {
189
288
  alreadyExists = false,
190
289
  ) {
191
290
  if (dbObj) {
192
- this.$originalData = alreadyExists === true ? cloneDeep(dbObj) : {};
193
- this.$currentData = dbObj;
291
+ this.$original = alreadyExists === true ? cloneDeep(dbObj) : {};
292
+ this.$attributes = dbObj;
194
293
  } else {
195
- this.$originalData = {};
196
- this.$currentData = {};
294
+ this.$original = {};
295
+ this.$attributes = {};
197
296
  }
198
297
 
199
298
  if (options !== undefined) {
@@ -201,8 +300,11 @@ export class BaseModel {
201
300
  this.$collection = options.collection;
202
301
  }
203
302
 
204
- this.$alreadySaved = alreadyExists;
205
- this.$isDeleted = false;
303
+ if (alreadyExists) {
304
+ this.$isPersisted = true;
305
+ this.$isLocal = false;
306
+ }
307
+
206
308
  // eslint-disable-next-line no-constructor-return
207
309
  return new Proxy(this, proxyHandler);
208
310
  }
@@ -319,7 +421,7 @@ export class BaseModel {
319
421
  ModelAttributes<InstanceType<ModelType>>
320
422
  >;
321
423
  const result = await collection.findOne(filter, driverOptions);
322
- if (result === undefined) return null;
424
+ if (result === null) return null;
323
425
  const instance = new this(
324
426
  result,
325
427
  // @ts-expect-error Unavoidable error.
@@ -357,7 +459,7 @@ export class BaseModel {
357
459
  ModelAttributes<InstanceType<ModelType>>
358
460
  >;
359
461
  const result = await collection.findOne(filter, driverOptions);
360
- if (result === undefined) return null;
462
+ if (result === null) return null;
361
463
  const instance = new this(
362
464
  result,
363
465
  // @ts-expect-error Unavoidable error.
@@ -431,7 +533,10 @@ export class BaseModel {
431
533
  this: ModelType,
432
534
  filter: Filter<ModelAttributes<InstanceType<ModelType>>> = {},
433
535
  options?: ModelAdapterOptions<
434
- FindOptions<ModelAttributes<InstanceType<ModelType>>>
536
+ Omit<
537
+ FindOptions<ModelAttributes<InstanceType<ModelType>>>,
538
+ ForbiddenQueryOptions
539
+ >
435
540
  >,
436
541
  ): Query<ModelType> {
437
542
  return new Query(filter, options, this);
@@ -441,27 +546,34 @@ export class BaseModel {
441
546
  this: ModelType,
442
547
  connection = this.connection,
443
548
  ): Promise<Collection<ModelAttributes<InstanceType<ModelType>>>> {
444
- if (!this.$database) {
445
- throw new Error('Model should only be accessed from IoC container');
446
- }
549
+ assert(this.$database, 'Model should only be accessed from IoC container');
447
550
  const connectionInstance = this.$database.connection(connection);
448
551
  return connectionInstance.collection(this.collectionName);
449
552
  }
450
553
 
451
554
  public [Symbol.for('nodejs.util.inspect.custom')](): unknown {
452
555
  return {
453
- model: this.constructor.name,
454
- originalData: this.$originalData,
455
- currentData: this.$currentData,
456
- isDirty: this.$isDirty,
556
+ Model: this.constructor.name,
557
+ $original: this.$original,
558
+ $attributes: this.$attributes,
559
+ $isPersisted: this.$isPersisted,
560
+ $isNew: this.$isNew,
561
+ $isLocal: this.$isLocal,
562
+ $isDeleted: this.$isDeleted,
563
+ $dirty: this.$dirty,
564
+ $isDirty: this.$isDirty,
457
565
  };
458
566
  }
459
567
 
568
+ public get $isNew(): boolean {
569
+ return !this.$isPersisted;
570
+ }
571
+
460
572
  public get $dirty(): Partial<ModelAttributes<this>> {
461
- return pickBy(this.$currentData, (value, key) => {
573
+ return pickBy(this.$attributes, (value, key) => {
462
574
  return (
463
- this.$originalData[key] === undefined ||
464
- !isEqual(this.$originalData[key], value)
575
+ this.$original[key] === undefined ||
576
+ !isEqual(this.$original[key], value)
465
577
  );
466
578
  }) as Partial<ModelAttributes<this>>;
467
579
  }
@@ -490,7 +602,7 @@ export class BaseModel {
490
602
  public $prepareToSet() {
491
603
  const dirty = this.$dirty;
492
604
  const dirtyEntries = Object.entries(dirty);
493
- if (dirtyEntries.length === 0) {
605
+ if (dirtyEntries.length === 0 && this.$isPersisted) {
494
606
  return null;
495
607
  }
496
608
 
@@ -499,11 +611,11 @@ export class BaseModel {
499
611
  // which shouldn't reset the createdAt field.
500
612
  const toSet = {} as DataToSet;
501
613
  const now = new Date();
502
- if (this.$currentData.createdAt === undefined) {
503
- this.$currentData.createdAt = now;
614
+ if (this.$attributes.createdAt === undefined) {
615
+ this.$attributes.createdAt = now;
504
616
  toSet.createdAt = now;
505
617
  }
506
- this.$currentData.updatedAt = now;
618
+ this.$attributes.updatedAt = now;
507
619
  toSet.updatedAt = now;
508
620
 
509
621
  for (const [dirtyKey, dirtyValue] of dirtyEntries) {
@@ -513,7 +625,7 @@ export class BaseModel {
513
625
  }
514
626
 
515
627
  public get id() {
516
- return this.$currentData._id;
628
+ return this.$attributes._id;
517
629
  }
518
630
 
519
631
  public get $isDirty(): boolean {
@@ -521,7 +633,7 @@ export class BaseModel {
521
633
  }
522
634
 
523
635
  public toJSON(): unknown {
524
- return this.$currentData;
636
+ return this.$attributes;
525
637
  }
526
638
 
527
639
  public async save(
@@ -536,18 +648,18 @@ export class BaseModel {
536
648
  ...options?.driverOptions,
537
649
  session: this.$options?.session,
538
650
  };
539
- if (this.$alreadySaved === false) {
651
+ if (!this.$isPersisted) {
540
652
  const result = await collection.insertOne(toSet, driverOptions);
541
- this.$currentData._id = result.insertedId;
653
+ this.$attributes._id = result.insertedId;
654
+ this.$isPersisted = true;
542
655
  } else {
543
656
  await collection.updateOne(
544
- { _id: this.$currentData._id },
657
+ { _id: this.$attributes._id },
545
658
  { $set: toSet },
546
659
  driverOptions,
547
660
  );
548
661
  }
549
- this.$originalData = cloneDeep(this.$currentData);
550
- this.$alreadySaved = true;
662
+ this.$original = cloneDeep(this.$attributes);
551
663
  return true;
552
664
  }
553
665
 
@@ -562,7 +674,7 @@ export class BaseModel {
562
674
  };
563
675
  const result = await collection.deleteOne(
564
676
  {
565
- _id: this.$currentData._id,
677
+ _id: this.$attributes._id,
566
678
  },
567
679
  driverOptions,
568
680
  );
@@ -574,7 +686,7 @@ export class BaseModel {
574
686
  values: NoExtraProperties<Partial<Omit<ModelAttributes<this>, '_id'>>, T>,
575
687
  ): this {
576
688
  Object.entries(values).forEach(([key, value]) => {
577
- this.$currentData[key] = value;
689
+ this.$attributes[key] = value;
578
690
  });
579
691
  return this;
580
692
  }
@@ -582,11 +694,11 @@ export class BaseModel {
582
694
  public fill<T extends Partial<Omit<ModelAttributes<this>, '_id'>>>(
583
695
  values: NoExtraProperties<Partial<Omit<ModelAttributes<this>, '_id'>>, T>,
584
696
  ) {
585
- const createdAt = this.$currentData.createdAt;
586
- this.$currentData = {
697
+ const createdAt = this.$attributes.createdAt;
698
+ this.$attributes = {
587
699
  _id: this.id,
588
700
  };
589
- if (createdAt) this.$currentData.createdAt = createdAt;
701
+ if (createdAt) this.$attributes.createdAt = createdAt;
590
702
  return this.merge(values);
591
703
  }
592
704
  }
@@ -602,7 +714,6 @@ export class BaseAutoIncrementModel extends BaseModel {
602
714
 
603
715
  const toSet = this.$prepareToSet();
604
716
  if (toSet === null) return false;
605
-
606
717
  const driverOptions = {
607
718
  ...options?.driverOptions,
608
719
  session: this.$options?.session,
@@ -615,22 +726,23 @@ export class BaseAutoIncrementModel extends BaseModel {
615
726
  );
616
727
 
617
728
  const doc = await counterCollection.findOneAndUpdate(
618
- { _id: computeCollectionName(this.constructor.name) },
729
+ { _id: (this.constructor as typeof BaseModel).collectionName },
619
730
  { $inc: { count: 1 } },
620
- { ...driverOptions, upsert: true },
731
+ { ...driverOptions, upsert: true, returnDocument: 'after' },
621
732
  );
622
- const newCount = doc.value ? doc.value.count + 1 : 1;
623
- toSet._id = newCount;
733
+ assert(doc.value, 'upsert should always create a document');
734
+ toSet._id = doc.value.count;
624
735
  await collection.insertOne(toSet, driverOptions);
625
- this.$currentData._id = newCount;
736
+ this.$attributes._id = doc.value.count;
737
+ this.$isPersisted = true;
626
738
  } else {
627
739
  await collection.updateOne(
628
- { _id: this.$currentData._id },
740
+ { _id: this.$attributes._id },
629
741
  { $set: toSet },
630
742
  driverOptions,
631
743
  );
632
744
  }
633
- this.$originalData = cloneDeep(this.$currentData);
745
+ this.$original = cloneDeep(this.$attributes);
634
746
  return true;
635
747
  }
636
748
  }
@@ -1,16 +1,21 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
- export const proxyHandler = {
4
- get(target: any, prop: string) {
3
+ export const proxyHandler: ProxyHandler<any> = {
4
+ get(target: any, prop: string | symbol) {
5
5
  if (typeof target[prop] !== 'undefined') {
6
6
  return Reflect.get(target, prop);
7
7
  }
8
- return Reflect.get(target.$currentData, prop);
8
+ return Reflect.get(target.$attributes, prop);
9
9
  },
10
- set(target: any, prop: string, value: any) {
10
+ set(target: any, prop: string | symbol, value: any) {
11
11
  if (typeof target[prop] !== 'undefined') {
12
12
  return Reflect.set(target, prop, value);
13
13
  }
14
- return Reflect.set(target.$currentData, prop, value);
14
+ return Reflect.set(target.$attributes, prop, value);
15
+ },
16
+ ownKeys() {
17
+ throw new Error(
18
+ 'Getting model keys is disallowed. If you want to use object spread on the current data, do { ...model.$attributes }',
19
+ );
15
20
  },
16
21
  };