Passed
Push — master ( 828af7...bf8c19 )
by Mark
01:41
created

Model.aSyncInsert   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
import HasManyThrough from './Model/Relation/HasManyThrough';
2
import HasMany from "./Model/Relation/HasMany";
3
import BelongsTo from "./Model/Relation/BelongTo";
4
import MorphOne from "./Model/Relation/MorphOne";
5
import HasOne from "./Model/Relation/HasOne";
6
import HasOneThrough from "./Model/Relation/HasOneThrough";
7
import MorphTo from "./Model/Field/MorphTo";
8
import Field from "./Model/Field";
9
import Relation from "./Model/Relation";
10
import ForeignKey from "./Model/Field/ForeignKey";
11
import Index from "./Table/Index";
12
import {ModelInterface, ModelStaticInterface} from "../JeloquentInterfaces";
13
import Collection from "./Collection";
14
import * as Str from "../Util/Str";
15
import * as Obj from "../Util/Obj";
16
import * as ModelSetup from "./Model/Util/Setup";
17
import Table from "./Table";
18
19
class Model implements ModelInterface {
20
21
    private static kebabCaseName: string;
22
23
    private static snakeCaseName: string;
24
25
    _tmpId: string;
26
27
    ['constructor']: ModelStaticInterface;
28
29
    private _originalFields: Map<string, Field>;
30
31
    private _primaryFields: Field[];
32
33
    private numberOfFields: number;
34
35
    constructor(fields: Field[] = []) {
36
        this.setFields(ModelSetup.addRelationFieldsToList(fields));
37
        this._tmpId = `_${++globalThis.Store.numberOfModelCreated}`;
38
39
        return ModelSetup.modelProxy(this);
40
    }
41
42
    static get className(): string {
43
        return this.name;
44
    }
45
46
    static get kebabCaseClassName(): string {
47
        return this.kebabCaseName ??= Str.KebabCase(this.className);
48
    }
49
50
    static get snakeCaseClassName(): string {
51
        return this.snakeCaseName ??= Str.SnakeCase(this.className);
52
    }
53
54
    get className(): string {
55
        return Object.getPrototypeOf(this).constructor.className;
56
    }
57
58
    get dirtyFieldNames(): string[] {
59
        return this.dirtyFields.map(field => field.name);
60
    }
61
62
    get dirtyFields(): Field[] {
63
        return this.originalFields.filter(field => field.isDirty);
64
    }
65
66
    get kebabCaseClassName(): string {
67
        return  Object.getPrototypeOf(this).constructor.kebabCaseClassName;
68
    }
69
70
    get originalFields(): Field[] {
71
        return [...this._originalFields.values()];
72
    }
73
74
    get originalPrimaryKey(): unknown {
75
        return this.primaryFields.reduce((toValue, field, i) => {
76
            if (i > 0) {
77
                return `${toValue}-${field.originalValue}`;
78
            }
79
            return field.originalValue;
80
        }, '') ?? this._tmpId ?? null;
81
    }
82
83
    get originalValues(): object {
84
        return this.originalFields.reduce((originalValues, field) => {
85
            if (field.originalValue !== undefined) {
86
                originalValues[field.name] = field.originalValue;
87
            }
88
            return originalValues;
89
        }, {});
90
    }
91
92
    get primaryFields(): Field[] {
93
        return this._primaryFields ??= this.originalFields.filter(field => field.isPrimary);
94
    }
95
96
    get primaryKey(): string {
97
        return (this.primaryFields.reduce((toValue:string, field:Field, i:number): string => {
98
            if (i > 0) {
99
                return `${toValue}-${field.value}`;
100
            }
101
102
            if (field.value === null) {
103
                return field.value;
104
            }
105
106
            return `${field.value}`;
107
        }, '') ?? this._tmpId ?? null);
108
    }
109
110
    primaryKeyByValues(values): string
111
    {
112
       return this.primaryFields.reduce((toValue, field:Field, i) => {
113
            if (i > 0) {
114
                return `${toValue}-${values[field.name]}`
115
            }
116
            return `${values[field.name]}`;
117
        }, '');
118
    }
119
120
121
    get primaryKeyName(): string[] {
122
        return this.originalFields.filter(field => field.isPrimary).map(field => field.name);
123
    }
124
125
    get snakeCaseClassName(): string {
126
        return Object.getPrototypeOf(this).constructor.snakeCaseClassName;
127
    }
128
129
    static aSyncInsert(data: object): Promise<Collection> {
130
        return new Promise((resolve) => {
131
            queueMicrotask(() => {
132
                resolve(this.insert(data));
133
            });
134
        });
135
    }
136
137
    static aSyncUpdate(data: object): Promise<Collection> {
138
        return new Promise((resolve) => {
139
            queueMicrotask(() => {
140
                resolve(this.update(data));
141
            });
142
        });
143
    }
144
145
    static all(): Collection {
146
        return globalThis.Store.database().all(this.className);
147
    }
148
149
    static delete(id): void {
150
        globalThis.Store.database().delete(this.className, id);
151
    }
152
153
    static find(id): unknown {
154
        return globalThis.Store.database().find(this.className, id);
155
    }
156
157
    static getIndexByKey(indexName) {
158
        return globalThis
159
            .Store
160
            .database()
161
            .getIndexByKey(this.className, indexName);
162
    }
163
164
    static getInstance(): ModelInterface {
165
        const original = globalThis.Store.classInstances[this.className] ?? (globalThis.Store.classInstances[this.className] = new this())
166
        const fieldsClone = original.originalFields.reduce((obj, field) => {
167
            obj.push(Object.assign(Object.create(Object.getPrototypeOf(field)), field));
168
            return obj;
169
        }, [])
170
171
        return ModelSetup.modelProxy(Object.create(Object.getPrototypeOf(original)).setFields(fieldsClone));
172
    }
173
174
    static ids() {
175
        return globalThis
176
            .Store
177
            .database()
178
            .ids(this.className);
179
    }
180
181
    static insert(data: object|object[]): Collection {
182
        const modelsData = Array.isArray(data) ? data : [data];
183
        const length = modelsData.length;
184
        const models = new Collection();
185
        for (let i = 0; i < length; i++) {
186
            const modelData = modelsData[i];
187
            const model = this.getInstance();
188
            model.fill(modelData);
189
            globalThis.Store.database().insert(this.className, model);
190
            model.fillRelations(modelData);
191
            models.push(model);
192
        }
193
        return models;
194
    }
195
196
    static registerIndex(name: string): void {
197
        Index.register(this.getInstance(), name);
198
    }
199
200
    static update(data: object|object[]): Collection {
201
        const modelsData = Array.isArray(data) ? data : [data];
202
        const length = modelsData.length;
203
        const models = new Collection();
204
        for (let i = 0; i < length; i++) {
205
            const model = this.find(this.getInstance().primaryKeyByValues(data));
206
            model.fill(data);
207
            globalThis.Store.database().update(this.className, model);
208
            model.fillRelations(data);
209
            models.push(model);
210
        }
211
        return models;
212
    }
213
214
    delete() {
215
        Object.getPrototypeOf(this).constructor.delete(this.primaryKey);
216
    }
217
218
    fill(data) {
219
        for (let i = 0; i < this.numberOfFields; i++) {
220
            if (!(this.originalFields[i] instanceof Relation)) {
221
                const fieldName = this.originalFields[i].name;
222
                if (data[fieldName] !== undefined) {
223
                    this[`_${fieldName}`] = data[fieldName];
224
                }
225
            }
226
        }
227
    }
228
229
    fillRelations(data: object): void {
230
        // insert through relations after model insert;
231
        for (let i = 0; i < this.numberOfFields; i++) {
232
            if ((this.originalFields[i] instanceof Relation)) {
233
                const fieldName = this.originalFields[i].name;
234
                if (data[fieldName] !== undefined) {
235
                    this[`_${fieldName}`] = data[fieldName];
236
                }
237
            }
238
        }
239
    }
240
241
    isDirty(fieldName:string = null) {
242
        if (fieldName) {
243
            return this.dirtyFieldNames.includes(fieldName);
244
        }
245
        return this.dirtyFields.length > 0;
246
    }
247
248
    registerIndex(name) {
249
        Index.register(this, name);
250
    }
251
252
    resetDirty() {
253
        this.originalFields.filter((field) => !(field instanceof Relation)).forEach(field => {
254
            field.resetDirty();
255
        })
256
    }
257
258
    save() {
259
        globalThis.Store.database().save(this.className, this);
260
    }
261
262
    setFields(fields: Field[]) {
263
        this._originalFields = new Map();
264
        ModelSetup.setFields(this, fields);
265
        return this;
266
    }
267
268
    tableSetup(table: Table) {
269
        ModelSetup.setupTable(this, table);
270
    }
271
272
    toJSON(): object {
273
        return this.toObject();
274
    }
275
276
    toObject(fromRelation = false): object {
277
        return Obj.fromModel(this, fromRelation);
278
    }
279
}
280
281
export {
282
    Model,
283
    Field,
284
    Relation,
285
    BelongsTo,
286
    HasOne,
287
    HasOneThrough,
288
    HasMany,
289
    HasManyThrough,
290
    MorphOne,
291
    MorphTo,
292
    ForeignKey,
293
};