tantan-typeorm-gs 1.0.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/bun.lock +300 -0
  4. package/docs/authentication.md +118 -0
  5. package/docs/configuration.md +680 -0
  6. package/docs/contributing.md +177 -0
  7. package/docs/crud.md +351 -0
  8. package/docs/custom-client.md +165 -0
  9. package/docs/development.md +243 -0
  10. package/docs/entities.md +502 -0
  11. package/docs/find-options.md +296 -0
  12. package/docs/google-cloud.md +101 -0
  13. package/docs/installation.md +51 -0
  14. package/docs/integration-testing.md +178 -0
  15. package/docs/limitations.md +223 -0
  16. package/docs/pagination.md +384 -0
  17. package/docs/performance.md +171 -0
  18. package/docs/security.md +153 -0
  19. package/docs/sorting.md +0 -0
  20. package/docs/supported-features.md +310 -0
  21. package/docs/synchronization.md +203 -0
  22. package/package.json +43 -0
  23. package/src/client/index.ts +589 -0
  24. package/src/core/data-source.ts +47 -0
  25. package/src/core/driver.ts +290 -0
  26. package/src/core/error.ts +144 -0
  27. package/src/core/memory.ts +152 -0
  28. package/src/core/query/interpreter.ts +949 -0
  29. package/src/core/query/runner.ts +1297 -0
  30. package/src/core/query/types.ts +155 -0
  31. package/src/core/schema-builder.ts +64 -0
  32. package/src/core/types.ts +113 -0
  33. package/src/core/utils.ts +13 -0
  34. package/src/index.ts +3 -0
  35. package/tantan-typeorm-gs.code-workspace +8 -0
  36. package/test/base/0001-data-source.test.ts +252 -0
  37. package/test/base/0002-operator.test.ts +616 -0
  38. package/test/base/0003-select.test.ts +281 -0
  39. package/test/base/0004-aggregate.test.ts +213 -0
  40. package/test/base/0005-transcation.test.ts +1566 -0
  41. package/test/base/0006-relation.test.ts +1611 -0
  42. package/test/base/0007-logging.test.ts +182 -0
  43. package/test/base/0008-soft-delete.test.ts +649 -0
  44. package/test/google-sheets/0001-client.test.ts +1705 -0
  45. package/test/google-sheets/0002-worksheet-management.test.ts +408 -0
  46. package/test/google-sheets/0003-data-source.test.ts +1935 -0
  47. package/test/google-sheets/0004-transactions.test.ts +952 -0
  48. package/test/google-sheets/0005-operator.test.ts +1124 -0
  49. package/test/google-sheets/0006-object-criteria.test.ts +283 -0
  50. package/test/google-sheets/0007-performance.test.ts +532 -0
  51. package/test/public-api.test.ts +34 -0
  52. package/tsconfig.build.json +21 -0
  53. package/tsconfig.json +34 -0
@@ -0,0 +1,1566 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
3
+ import { Memory } from '../../src/core/memory';
4
+ import { createGoogleSheetsDataSource } from '../../src/core/data-source';
5
+
6
+ describe('GoogleSheetsDataSource', () =>
7
+ {
8
+ @Entity()
9
+ class User
10
+ {
11
+ @PrimaryGeneratedColumn()
12
+ id!: number;
13
+
14
+ @Column()
15
+ name!: string;
16
+
17
+ @ManyToMany(
18
+ () => Role,
19
+ (role) => role.users,
20
+ )
21
+ @JoinTable({
22
+ name: 'user_role',
23
+ joinColumn: {
24
+ name: 'userId',
25
+ referencedColumnName: 'id',
26
+ },
27
+ inverseJoinColumn: {
28
+ name: 'roleId',
29
+ referencedColumnName: 'id',
30
+ },
31
+ })
32
+ roles?: Role[];
33
+ }
34
+
35
+ @Entity()
36
+ class Category
37
+ {
38
+ @PrimaryGeneratedColumn()
39
+ id!: number;
40
+
41
+ @Column()
42
+ name!: string;
43
+ }
44
+
45
+ @Entity()
46
+ class Post
47
+ {
48
+ @PrimaryGeneratedColumn()
49
+ id!: number;
50
+
51
+ @Column()
52
+ title!: string;
53
+
54
+ @Column()
55
+ userId!: number;
56
+
57
+ @Column({ nullable: true })
58
+ categoryId!: number;
59
+
60
+ @ManyToOne(
61
+ () => User,
62
+ )
63
+ @JoinColumn({
64
+ name: 'userId',
65
+ })
66
+ user!: User;
67
+
68
+ @ManyToOne(
69
+ () => Category,
70
+ )
71
+ @JoinColumn({
72
+ name: 'categoryId',
73
+ })
74
+ category!: Category;
75
+ }
76
+
77
+ @Entity()
78
+ class Role
79
+ {
80
+ @PrimaryGeneratedColumn()
81
+ id!: number;
82
+
83
+ @Column()
84
+ name!: string;
85
+
86
+ @ManyToMany(
87
+ () => User,
88
+ (user) => user.roles,
89
+ )
90
+ users!: User[];
91
+ }
92
+
93
+ @Entity()
94
+ class UuidUser
95
+ {
96
+ @PrimaryGeneratedColumn('uuid')
97
+ id!: string;
98
+
99
+ @Column()
100
+ name!: string;
101
+ }
102
+
103
+ @Entity()
104
+ class IdentityUser
105
+ {
106
+ @PrimaryGeneratedColumn('identity')
107
+ id!: number;
108
+
109
+ @Column()
110
+ name!: string;
111
+ }
112
+
113
+ @Entity()
114
+ class RowidUser
115
+ {
116
+ @PrimaryGeneratedColumn('rowid')
117
+ id!: string;
118
+
119
+ @Column()
120
+ name!: string;
121
+ }
122
+
123
+ const entities = [User, Post, Category, Role, UuidUser, IdentityUser, RowidUser]
124
+
125
+ test('inserts a new entity', async () =>
126
+ {
127
+ const client = new Memory({
128
+ user: [
129
+ { id: 1, name: 'Basuni' },
130
+ { id: 2, name: 'Tan Tan' },
131
+ ],
132
+ });
133
+
134
+ const dataSource = createGoogleSheetsDataSource({
135
+ type: 'google-sheets',
136
+ client,
137
+ entities
138
+ });
139
+
140
+ await dataSource.initialize();
141
+
142
+ const repository = dataSource.getRepository(User);
143
+
144
+ const user = repository.create({
145
+ id: 3,
146
+ name: 'Budi',
147
+ });
148
+
149
+ await repository.save(user);
150
+
151
+ expect(user.id).toBe(3);
152
+
153
+ const users = await repository.find();
154
+
155
+ expect(users).toHaveLength(3);
156
+ expect(users[2]).toMatchObject({
157
+ id: 3,
158
+ name: 'Budi',
159
+ });
160
+
161
+ await dataSource.destroy();
162
+ });
163
+
164
+ test('updates an existing entity', async () =>
165
+ {
166
+ const client = new Memory({
167
+ user: [
168
+ { id: 1, name: 'Basuni' },
169
+ { id: 2, name: 'Tan Tan' },
170
+ ],
171
+ });
172
+
173
+ const dataSource = createGoogleSheetsDataSource({
174
+ type: 'google-sheets',
175
+ client,
176
+ entities
177
+ });
178
+
179
+ await dataSource.initialize();
180
+
181
+ const repository = dataSource.getRepository(User);
182
+
183
+ const user = await repository.findOne({
184
+ where: {
185
+ id: 1,
186
+ },
187
+ });
188
+
189
+ expect(user).not.toBeNull();
190
+
191
+ if (!user)
192
+ {
193
+ throw new Error('User not found.');
194
+ }
195
+
196
+ user.name = 'Basuni Updated';
197
+
198
+ await repository.save(user);
199
+
200
+ const updatedUser = await repository.findOne({
201
+ where: {
202
+ id: 1,
203
+ },
204
+ });
205
+
206
+ expect(updatedUser).toMatchObject({
207
+ id: 1,
208
+ name: 'Basuni Updated',
209
+ });
210
+
211
+ await dataSource.destroy();
212
+ });
213
+
214
+ test('deletes an existing entity', async () =>
215
+ {
216
+ const client = new Memory({
217
+ user: [
218
+ { id: 1, name: 'Basuni' },
219
+ { id: 2, name: 'Tan Tan' },
220
+ ],
221
+ });
222
+
223
+ const dataSource = createGoogleSheetsDataSource({
224
+ type: 'google-sheets',
225
+ client,
226
+ entities
227
+ });
228
+
229
+ await dataSource.initialize();
230
+
231
+ const repository = dataSource.getRepository(User);
232
+
233
+ const user = await repository.findOne({
234
+ where: {
235
+ id: 1,
236
+ },
237
+ });
238
+
239
+ expect(user).not.toBeNull();
240
+
241
+ if (!user)
242
+ {
243
+ throw new Error('User not found.');
244
+ }
245
+
246
+ await repository.remove(user);
247
+
248
+ const deletedUser = await repository.findOne({
249
+ where: {
250
+ id: 1,
251
+ },
252
+ });
253
+
254
+ expect(deletedUser).toBeNull();
255
+
256
+ const users = await repository.find();
257
+
258
+ expect(users).toHaveLength(1);
259
+ expect(users[0]).toMatchObject({
260
+ id: 2,
261
+ name: 'Tan Tan',
262
+ });
263
+
264
+ await dataSource.destroy();
265
+ });
266
+
267
+ test('bulk inserts multiple entities', async () =>
268
+ {
269
+ const client = new Memory({
270
+ user: [
271
+ { id: 1, name: 'Basuni' },
272
+ ],
273
+ });
274
+
275
+ const dataSource = createGoogleSheetsDataSource({
276
+ type: 'google-sheets',
277
+ client,
278
+ entities
279
+ });
280
+
281
+ await dataSource.initialize();
282
+
283
+ const repository = dataSource.getRepository(User);
284
+
285
+ await repository.insert([
286
+ { name: 'Budi' },
287
+ { name: 'Andi' },
288
+ ]);
289
+
290
+ const users = await repository.find();
291
+
292
+ expect(users).toHaveLength(3);
293
+
294
+ expect(users).toMatchObject([
295
+ { id: 1, name: 'Basuni' },
296
+ { id: 2, name: 'Budi' },
297
+ { id: 3, name: 'Andi' },
298
+ ]);
299
+
300
+ await dataSource.destroy();
301
+ });
302
+
303
+ test('updates multiple entities', async () =>
304
+ {
305
+ const client = new Memory({
306
+ user: [
307
+ { id: 1, name: 'Basuni' },
308
+ { id: 2, name: 'Basuni' },
309
+ { id: 3, name: 'Tan Tan' },
310
+ ],
311
+ });
312
+
313
+ const dataSource = createGoogleSheetsDataSource({
314
+ type: 'google-sheets',
315
+ client,
316
+ entities
317
+ });
318
+
319
+ await dataSource.initialize();
320
+
321
+ const repository = dataSource.getRepository(User);
322
+
323
+ await repository.update(
324
+ { name: 'Basuni' },
325
+ { name: 'Basuni Updated' },
326
+ );
327
+
328
+ const users = await repository.find();
329
+
330
+ expect(users).toMatchObject([
331
+ { id: 1, name: 'Basuni Updated' },
332
+ { id: 2, name: 'Basuni Updated' },
333
+ { id: 3, name: 'Tan Tan' },
334
+ ]);
335
+
336
+ await dataSource.destroy();
337
+ });
338
+
339
+ test('deletes multiple entities', async () =>
340
+ {
341
+ const client = new Memory({
342
+ user: [
343
+ { id: 1, name: 'Basuni' },
344
+ { id: 2, name: 'Basuni' },
345
+ { id: 3, name: 'Tan Tan' },
346
+ ],
347
+ });
348
+
349
+ const dataSource = createGoogleSheetsDataSource({
350
+ type: 'google-sheets',
351
+ client,
352
+ entities
353
+ });
354
+
355
+ await dataSource.initialize();
356
+
357
+ const repository = dataSource.getRepository(User);
358
+
359
+ await repository.delete({
360
+ name: 'Basuni',
361
+ });
362
+
363
+ const users = await repository.find();
364
+
365
+ expect(users).toMatchObject([
366
+ { id: 3, name: 'Tan Tan' },
367
+ ]);
368
+
369
+ await dataSource.destroy();
370
+ });
371
+
372
+ test('saves an existing entity', async () =>
373
+ {
374
+ const client = new Memory({
375
+ user: [
376
+ { id: 1, name: 'Tan Tan' },
377
+ { id: 2, name: 'Basuni' },
378
+ ],
379
+ });
380
+
381
+ const dataSource = createGoogleSheetsDataSource({
382
+ type: 'google-sheets',
383
+ client,
384
+ entities
385
+ });
386
+
387
+ await dataSource.initialize();
388
+
389
+ const repository = dataSource.getRepository(User);
390
+
391
+ const user = await repository.findOneBy({
392
+ id: 1,
393
+ });
394
+
395
+ if (!user)
396
+ {
397
+ throw new Error('User not found.');
398
+ }
399
+
400
+ user.name = 'Tan Tan Updated';
401
+
402
+ await repository.save(user);
403
+
404
+ const updated = await repository.findOneBy({
405
+ id: 1,
406
+ });
407
+
408
+ expect(updated).toEqual({
409
+ id: 1,
410
+ name: 'Tan Tan Updated',
411
+ });
412
+
413
+ await dataSource.destroy();
414
+ });
415
+
416
+ test('removes an existing entity', async () =>
417
+ {
418
+ const client = new Memory({
419
+ user: [
420
+ { id: 1, name: 'Tan Tan' },
421
+ { id: 2, name: 'Basuni' },
422
+ ],
423
+ });
424
+
425
+ const dataSource = createGoogleSheetsDataSource({
426
+ type: 'google-sheets',
427
+ client,
428
+ entities
429
+ });
430
+
431
+ await dataSource.initialize();
432
+
433
+ const repository = dataSource.getRepository(User);
434
+
435
+ const user = await repository.findOneBy({
436
+ id: 1,
437
+ });
438
+
439
+ if (!user)
440
+ {
441
+ throw new Error('User not found.');
442
+ }
443
+
444
+ await repository.remove(user);
445
+
446
+ const removed = await repository.findOneBy({
447
+ id: 1,
448
+ });
449
+
450
+ expect(removed).toBeNull();
451
+
452
+ const remaining = await repository.find();
453
+
454
+ expect(remaining).toEqual([
455
+ {
456
+ id: 2,
457
+ name: 'Basuni',
458
+ },
459
+ ]);
460
+
461
+ await dataSource.destroy();
462
+ });
463
+
464
+ test('preloads an existing entity', async () =>
465
+ {
466
+ const client = new Memory({
467
+ user: [
468
+ { id: 1, name: 'Tan Tan' },
469
+ { id: 2, name: 'Basuni' },
470
+ ],
471
+ });
472
+
473
+ const dataSource = createGoogleSheetsDataSource({
474
+ type: 'google-sheets',
475
+ client,
476
+ entities
477
+ });
478
+
479
+ await dataSource.initialize();
480
+
481
+ const repository = dataSource.getRepository(User);
482
+
483
+ const user = await repository.preload({
484
+ id: 1,
485
+ name: 'Tan Tan Updated',
486
+ });
487
+
488
+ expect(user).toEqual({
489
+ id: 1,
490
+ name: 'Tan Tan Updated',
491
+ });
492
+
493
+ await dataSource.destroy();
494
+ });
495
+
496
+ test('returns undefined when preloading a non-existing entity', async () =>
497
+ {
498
+ const client = new Memory({
499
+ user: [
500
+ { id: 1, name: 'Tan Tan' },
501
+ { id: 2, name: 'Basuni' },
502
+ ],
503
+ });
504
+
505
+ const dataSource = createGoogleSheetsDataSource({
506
+ type: 'google-sheets',
507
+ client,
508
+ entities
509
+ });
510
+
511
+ await dataSource.initialize();
512
+
513
+ const repository = dataSource.getRepository(User);
514
+
515
+ const user = await repository.preload({
516
+ id: 999,
517
+ name: 'Budi',
518
+ });
519
+
520
+ expect(user).toBeUndefined();
521
+
522
+ await dataSource.destroy();
523
+ });
524
+
525
+ test('assigns generated id when saving a new entity', async () =>
526
+ {
527
+ const client = new Memory({
528
+ user: [
529
+ { id: 1, name: 'Tan Tan' },
530
+ { id: 2, name: 'Basuni' },
531
+ ],
532
+ });
533
+
534
+ const dataSource = createGoogleSheetsDataSource({
535
+ type: 'google-sheets',
536
+ client,
537
+ entities
538
+ });
539
+
540
+ await dataSource.initialize();
541
+
542
+ const repository = dataSource.getRepository(User);
543
+
544
+ const user = repository.create({
545
+ name: 'Andi',
546
+ });
547
+
548
+ await repository.save(user);
549
+
550
+ expect(user.id).toBe(3);
551
+
552
+ const saved = await repository.findOneBy({
553
+ id: 3,
554
+ });
555
+
556
+ expect(saved).toEqual({
557
+ id: 3,
558
+ name: 'Andi',
559
+ });
560
+
561
+ await dataSource.destroy();
562
+ });
563
+
564
+ test('assigns generated ids when saving multiple new entities', async () =>
565
+ {
566
+ const client = new Memory({
567
+ user: [
568
+ { id: 1, name: 'Tan Tan' },
569
+ { id: 2, name: 'Basuni' },
570
+ ],
571
+ });
572
+
573
+ const dataSource = createGoogleSheetsDataSource({
574
+ type: 'google-sheets',
575
+ client,
576
+ entities
577
+ });
578
+
579
+ await dataSource.initialize();
580
+
581
+ const repository = dataSource.getRepository(User);
582
+
583
+ const users = repository.create([
584
+ {
585
+ name: 'Andi',
586
+ },
587
+ {
588
+ name: 'Budi',
589
+ },
590
+ {
591
+ name: 'Citra',
592
+ },
593
+ ]);
594
+
595
+ await repository.save(users);
596
+
597
+ expect(users[0]!.id).toBe(3);
598
+ expect(users[1]!.id).toBe(4);
599
+ expect(users[2]!.id).toBe(5);
600
+
601
+ const saved = await repository.find({
602
+ order: {
603
+ id: 'ASC',
604
+ },
605
+ });
606
+
607
+ expect(saved).toEqual([
608
+ {
609
+ id: 1,
610
+ name: 'Tan Tan',
611
+ },
612
+ {
613
+ id: 2,
614
+ name: 'Basuni',
615
+ },
616
+ {
617
+ id: 3,
618
+ name: 'Andi',
619
+ },
620
+ {
621
+ id: 4,
622
+ name: 'Budi',
623
+ },
624
+ {
625
+ id: 5,
626
+ name: 'Citra',
627
+ },
628
+ ]);
629
+
630
+ await dataSource.destroy();
631
+ });
632
+
633
+ test('assigns generated ids when saving multiple new entities to an empty sheet', async () =>
634
+ {
635
+ const client = new Memory({
636
+ user: [],
637
+ });
638
+
639
+ const dataSource = createGoogleSheetsDataSource({
640
+ type: 'google-sheets',
641
+ client,
642
+ entities
643
+ });
644
+
645
+ await dataSource.initialize();
646
+
647
+ const repository = dataSource.getRepository(User);
648
+
649
+ const users = repository.create([
650
+ {
651
+ name: 'Andi',
652
+ },
653
+ {
654
+ name: 'Budi',
655
+ },
656
+ {
657
+ name: 'Citra',
658
+ },
659
+ ]);
660
+
661
+ await repository.save(users);
662
+
663
+ expect(users[0]!.id).toBe(1);
664
+ expect(users[1]!.id).toBe(2);
665
+ expect(users[2]!.id).toBe(3);
666
+
667
+ const saved = await repository.find({
668
+ order: {
669
+ id: 'ASC',
670
+ },
671
+ });
672
+
673
+ expect(saved).toEqual([
674
+ {
675
+ id: 1,
676
+ name: 'Andi',
677
+ },
678
+ {
679
+ id: 2,
680
+ name: 'Budi',
681
+ },
682
+ {
683
+ id: 3,
684
+ name: 'Citra',
685
+ },
686
+ ]);
687
+
688
+ await dataSource.destroy();
689
+ });
690
+
691
+ test('assigns generated id based on the maximum existing id', async () =>
692
+ {
693
+ const client = new Memory({
694
+ user: [
695
+ { id: 1, name: 'Tan Tan' },
696
+ { id: 10, name: 'Basuni' },
697
+ { id: 5, name: 'Citra' },
698
+ ],
699
+ });
700
+
701
+ const dataSource = createGoogleSheetsDataSource({
702
+ type: 'google-sheets',
703
+ client,
704
+ entities
705
+ });
706
+
707
+ await dataSource.initialize();
708
+
709
+ const repository = dataSource.getRepository(User);
710
+
711
+ const user = repository.create({
712
+ name: 'Andi',
713
+ });
714
+
715
+ await repository.save(user);
716
+
717
+ expect(user.id).toBe(11);
718
+
719
+ const saved = await repository.findOneBy({
720
+ id: 11,
721
+ });
722
+
723
+ expect(saved).toEqual({
724
+ id: 11,
725
+ name: 'Andi',
726
+ });
727
+
728
+ await dataSource.destroy();
729
+ });
730
+
731
+ test('returns affected rows when updating entities with query builder', async () =>
732
+ {
733
+ const client = new Memory({
734
+ user: [
735
+ { id: 1, name: 'Tan Tan' },
736
+ { id: 2, name: 'Basuni' },
737
+ { id: 3, name: 'Tan Tan' },
738
+ ],
739
+ });
740
+
741
+ const dataSource = createGoogleSheetsDataSource({
742
+ type: 'google-sheets',
743
+ client,
744
+ entities
745
+ });
746
+
747
+ await dataSource.initialize();
748
+
749
+ const result = await dataSource
750
+ .createQueryBuilder()
751
+ .update(User)
752
+ .set({
753
+ name: 'Updated',
754
+ })
755
+ .where('id = :id', {
756
+ id: 1,
757
+ })
758
+ .execute();
759
+
760
+ expect(result.affected).toBe(1);
761
+
762
+ await dataSource.destroy();
763
+ });
764
+
765
+ test('returns zero affected rows when updating a non-existing entity', async () =>
766
+ {
767
+ const client = new Memory({
768
+ user: [
769
+ { id: 1, name: 'Tan Tan' },
770
+ ],
771
+ });
772
+
773
+ const dataSource = createGoogleSheetsDataSource({
774
+ type: 'google-sheets',
775
+ client,
776
+ entities
777
+ });
778
+
779
+ await dataSource.initialize();
780
+
781
+ const result = await dataSource
782
+ .createQueryBuilder()
783
+ .update(User)
784
+ .set({
785
+ name: 'Updated',
786
+ })
787
+ .where('id = :id', {
788
+ id: 999,
789
+ })
790
+ .execute();
791
+
792
+ expect(result.affected).toBe(0);
793
+
794
+ await dataSource.destroy();
795
+ });
796
+
797
+ test('returns affected rows when deleting entities with query builder', async () =>
798
+ {
799
+ const client = new Memory({
800
+ user: [
801
+ { id: 1, name: 'Tan Tan' },
802
+ { id: 2, name: 'Basuni' },
803
+ { id: 3, name: 'Citra' },
804
+ ],
805
+ });
806
+
807
+ const dataSource = createGoogleSheetsDataSource({
808
+ type: 'google-sheets',
809
+ client,
810
+ entities
811
+ });
812
+
813
+ await dataSource.initialize();
814
+
815
+ const result = await dataSource
816
+ .createQueryBuilder()
817
+ .delete()
818
+ .from(User)
819
+ .where('id = :id', {
820
+ id: 2,
821
+ })
822
+ .execute();
823
+
824
+ expect(result.affected).toBe(1);
825
+
826
+ await dataSource.destroy();
827
+ });
828
+
829
+ test('returns zero affected rows when deleting a non-existing entity', async () =>
830
+ {
831
+ const client = new Memory({
832
+ user: [
833
+ { id: 1, name: 'Tan Tan' },
834
+ ],
835
+ });
836
+
837
+ const dataSource = createGoogleSheetsDataSource({
838
+ type: 'google-sheets',
839
+ client,
840
+ entities
841
+ });
842
+
843
+ await dataSource.initialize();
844
+
845
+ const result = await dataSource
846
+ .createQueryBuilder()
847
+ .delete()
848
+ .from(User)
849
+ .where('id = :id', {
850
+ id: 999,
851
+ })
852
+ .execute();
853
+
854
+ expect(result.affected).toBe(0);
855
+
856
+ await dataSource.destroy();
857
+ });
858
+
859
+ test('assigns generated uuid when saving a new entity', async () =>
860
+ {
861
+ const client =
862
+ new Memory();
863
+
864
+ const dataSource =
865
+ createGoogleSheetsDataSource({
866
+ type: 'google-sheets',
867
+ client,
868
+ entities
869
+ });
870
+
871
+ await dataSource.initialize();
872
+
873
+ const user =
874
+ dataSource
875
+ .getRepository(UuidUser)
876
+ .create({
877
+ name: 'John',
878
+ });
879
+
880
+ await dataSource
881
+ .getRepository(UuidUser)
882
+ .save(user);
883
+
884
+ expect(user.id)
885
+ .toBeString();
886
+
887
+ expect(user.id.length)
888
+ .toBe(36);
889
+
890
+ expect(
891
+ user.id,
892
+ ).toMatch(
893
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
894
+ );
895
+ });
896
+
897
+ test('rejects generated identity when saving a new entity', async () =>
898
+ {
899
+ const client =
900
+ new Memory();
901
+
902
+ const dataSource =
903
+ createGoogleSheetsDataSource({
904
+ type: 'google-sheets',
905
+ client,
906
+ entities
907
+ });
908
+
909
+ await dataSource.initialize();
910
+
911
+ const user =
912
+ dataSource
913
+ .getRepository(IdentityUser)
914
+ .create({
915
+ name: 'John',
916
+ });
917
+
918
+ await expect(
919
+ dataSource
920
+ .getRepository(IdentityUser)
921
+ .save(user),
922
+ ).rejects.toThrow(
923
+ 'PrimaryGeneratedColumn("identity") is not supported by Google Sheets driver.',
924
+ );
925
+ });
926
+
927
+ test('rejects generated rowid when saving a new entity', async () =>
928
+ {
929
+ const client =
930
+ new Memory();
931
+
932
+ const dataSource =
933
+ createGoogleSheetsDataSource({
934
+ type: 'google-sheets',
935
+ client,
936
+ entities
937
+ });
938
+
939
+ await dataSource.initialize();
940
+
941
+ const user =
942
+ dataSource
943
+ .getRepository(RowidUser)
944
+ .create({
945
+ name: 'John',
946
+ });
947
+
948
+ await expect(
949
+ dataSource
950
+ .getRepository(RowidUser)
951
+ .save(user),
952
+ ).rejects.toThrow(
953
+ 'PrimaryGeneratedColumn("rowid") is not supported by Google Sheets driver.',
954
+ );
955
+ });
956
+
957
+ test('saves ManyToOne relation', async () =>
958
+ {
959
+ const client =
960
+ new Memory({
961
+ user: [
962
+ {
963
+ id: 1,
964
+ name: 'Basuni',
965
+ },
966
+ ],
967
+ post: [],
968
+ });
969
+
970
+ const dataSource =
971
+ createGoogleSheetsDataSource({
972
+ type: 'google-sheets',
973
+ client,
974
+ entities
975
+ });
976
+
977
+ await dataSource.initialize();
978
+
979
+ const userRepository =
980
+ dataSource.getRepository(User);
981
+
982
+ const postRepository =
983
+ dataSource.getRepository(Post);
984
+
985
+ const user =
986
+ await userRepository.findOneBy({
987
+ id: 1,
988
+ });
989
+
990
+ expect(user).not.toBeNull();
991
+
992
+ if (!user)
993
+ {
994
+ throw new Error(
995
+ 'User not found.',
996
+ );
997
+ }
998
+
999
+ const post =
1000
+ postRepository.create({
1001
+ title: 'New Post',
1002
+ user,
1003
+ });
1004
+
1005
+ await postRepository.save(post);
1006
+
1007
+ expect(post.id).toBeDefined();
1008
+ expect(post.title).toBe('New Post');
1009
+ expect(post.userId).toBe(1);
1010
+
1011
+ const saved =
1012
+ await postRepository.findOneBy({
1013
+ id: post.id,
1014
+ });
1015
+
1016
+ expect(saved).toMatchObject({
1017
+ id: post.id,
1018
+ title: 'New Post',
1019
+ userId: 1,
1020
+ });
1021
+
1022
+ await dataSource.destroy();
1023
+ });
1024
+
1025
+ test('saves multiple ManyToOne relations', async () =>
1026
+ {
1027
+ const client =
1028
+ new Memory({
1029
+ user: [
1030
+ {
1031
+ id: 1,
1032
+ name: 'Basuni',
1033
+ },
1034
+ ],
1035
+ category: [
1036
+ {
1037
+ id: 1,
1038
+ name: 'Technology',
1039
+ },
1040
+ ],
1041
+ post: [],
1042
+ });
1043
+
1044
+ const dataSource =
1045
+ createGoogleSheetsDataSource({
1046
+ type: 'google-sheets',
1047
+ client,
1048
+ entities,
1049
+ });
1050
+
1051
+ await dataSource.initialize();
1052
+
1053
+ const userRepository =
1054
+ dataSource.getRepository(User);
1055
+
1056
+ const categoryRepository =
1057
+ dataSource.getRepository(Category);
1058
+
1059
+ const postRepository =
1060
+ dataSource.getRepository(Post);
1061
+
1062
+ const user =
1063
+ await userRepository.findOneBy({
1064
+ id: 1,
1065
+ });
1066
+
1067
+ const category =
1068
+ await categoryRepository.findOneBy({
1069
+ id: 1,
1070
+ });
1071
+
1072
+ expect(user).not.toBeNull();
1073
+ expect(category).not.toBeNull();
1074
+
1075
+ if (!user || !category)
1076
+ {
1077
+ throw new Error(
1078
+ 'Relation entity not found.',
1079
+ );
1080
+ }
1081
+
1082
+ const post =
1083
+ postRepository.create({
1084
+ title: 'New Post',
1085
+ user,
1086
+ category,
1087
+ });
1088
+
1089
+ await postRepository.save(post);
1090
+
1091
+ expect(post.id).toBeDefined();
1092
+ expect(post.title).toBe('New Post');
1093
+ expect(post.userId).toBe(1);
1094
+ expect(post.categoryId).toBe(1);
1095
+
1096
+ const saved =
1097
+ await postRepository.findOneBy({
1098
+ id: post.id,
1099
+ });
1100
+
1101
+ expect(saved).toMatchObject({
1102
+ id: post.id,
1103
+ title: 'New Post',
1104
+ userId: 1,
1105
+ categoryId: 1,
1106
+ });
1107
+
1108
+ await dataSource.destroy();
1109
+ });
1110
+
1111
+ test('saves ManyToMany relation', async () =>
1112
+ {
1113
+ const client =
1114
+ new Memory({
1115
+ user: [],
1116
+ role: [
1117
+ {
1118
+ id: 1,
1119
+ name: 'Admin',
1120
+ },
1121
+ {
1122
+ id: 2,
1123
+ name: 'Developer',
1124
+ },
1125
+ ],
1126
+ user_role: [],
1127
+ });
1128
+
1129
+ const dataSource =
1130
+ createGoogleSheetsDataSource({
1131
+ type: 'google-sheets',
1132
+ client,
1133
+ entities: [
1134
+ User,
1135
+ Role,
1136
+ ],
1137
+ });
1138
+
1139
+ await dataSource.initialize();
1140
+
1141
+ const userRepository =
1142
+ dataSource.getRepository(User);
1143
+
1144
+ const roleRepository =
1145
+ dataSource.getRepository(Role);
1146
+
1147
+ const admin =
1148
+ await roleRepository.findOneBy({
1149
+ id: 1,
1150
+ });
1151
+
1152
+ const developer =
1153
+ await roleRepository.findOneBy({
1154
+ id: 2,
1155
+ });
1156
+
1157
+ expect(admin).not.toBeNull();
1158
+ expect(developer).not.toBeNull();
1159
+
1160
+ if (!admin || !developer)
1161
+ {
1162
+ throw new Error(
1163
+ 'Role not found.',
1164
+ );
1165
+ }
1166
+
1167
+ const user =
1168
+ userRepository.create({
1169
+ name: 'Basuni',
1170
+ roles: [
1171
+ admin,
1172
+ developer,
1173
+ ],
1174
+ });
1175
+
1176
+ await userRepository.save(user);
1177
+
1178
+ expect(user.id).toBeDefined();
1179
+ expect(user.name).toBe('Basuni');
1180
+
1181
+ const saved =
1182
+ await userRepository
1183
+ .createQueryBuilder('user')
1184
+ .leftJoinAndSelect(
1185
+ 'user.roles',
1186
+ 'role',
1187
+ )
1188
+ .where(
1189
+ 'user.id = :id',
1190
+ {
1191
+ id: user.id,
1192
+ },
1193
+ )
1194
+ .getOne();
1195
+
1196
+ expect(saved).toMatchObject({
1197
+ id: user.id,
1198
+ name: 'Basuni',
1199
+ roles: [
1200
+ {
1201
+ id: 1,
1202
+ name: 'Admin',
1203
+ },
1204
+ {
1205
+ id: 2,
1206
+ name: 'Developer',
1207
+ },
1208
+ ],
1209
+ });
1210
+
1211
+ await dataSource.destroy();
1212
+ });
1213
+
1214
+ test('updates ManyToMany relation', async () =>
1215
+ {
1216
+ const client =
1217
+ new Memory({
1218
+ user: [
1219
+ {
1220
+ id: 1,
1221
+ name: 'Basuni',
1222
+ },
1223
+ ],
1224
+ role: [
1225
+ {
1226
+ id: 1,
1227
+ name: 'Admin',
1228
+ },
1229
+ {
1230
+ id: 2,
1231
+ name: 'Developer',
1232
+ },
1233
+ ],
1234
+ user_role: [
1235
+ {
1236
+ userId: 1,
1237
+ roleId: 1,
1238
+ },
1239
+ {
1240
+ userId: 1,
1241
+ roleId: 2,
1242
+ },
1243
+ ],
1244
+ });
1245
+
1246
+ const dataSource =
1247
+ createGoogleSheetsDataSource({
1248
+ type: 'google-sheets',
1249
+ client,
1250
+ entities: [
1251
+ User,
1252
+ Role,
1253
+ ],
1254
+ });
1255
+
1256
+ await dataSource.initialize();
1257
+
1258
+ const userRepository =
1259
+ dataSource.getRepository(User);
1260
+
1261
+ const roleRepository =
1262
+ dataSource.getRepository(Role);
1263
+
1264
+ const user =
1265
+ await userRepository.findOne({
1266
+ where: {
1267
+ id: 1,
1268
+ },
1269
+ relations: {
1270
+ roles: true,
1271
+ },
1272
+ });
1273
+
1274
+ const developer =
1275
+ await roleRepository.findOneBy({
1276
+ id: 2,
1277
+ });
1278
+
1279
+ expect(user).not.toBeNull();
1280
+ expect(developer).not.toBeNull();
1281
+
1282
+ if (!user || !developer)
1283
+ {
1284
+ throw new Error(
1285
+ 'User or role not found.',
1286
+ );
1287
+ }
1288
+
1289
+ user.roles = [
1290
+ developer,
1291
+ ];
1292
+
1293
+ await userRepository.save(user);
1294
+
1295
+ const saved =
1296
+ await userRepository.findOne({
1297
+ where: {
1298
+ id: 1,
1299
+ },
1300
+ relations: {
1301
+ roles: true,
1302
+ },
1303
+ });
1304
+
1305
+ expect(saved).toMatchObject({
1306
+ id: 1,
1307
+ name: 'Basuni',
1308
+ roles: [
1309
+ {
1310
+ id: 2,
1311
+ name: 'Developer',
1312
+ },
1313
+ ],
1314
+ });
1315
+
1316
+ const junctionRows =
1317
+ await client.getRows(
1318
+ 'user_role',
1319
+ );
1320
+
1321
+ expect(junctionRows).toEqual([
1322
+ {
1323
+ userId: 1,
1324
+ roleId: 2,
1325
+ },
1326
+ ]);
1327
+
1328
+ await dataSource.destroy();
1329
+ });
1330
+
1331
+ test('removes ManyToMany relations', async () =>
1332
+ {
1333
+ const client =
1334
+ new Memory({
1335
+ user: [
1336
+ {
1337
+ id: 1,
1338
+ name: 'Basuni',
1339
+ },
1340
+ ],
1341
+ role: [
1342
+ {
1343
+ id: 1,
1344
+ name: 'Admin',
1345
+ },
1346
+ {
1347
+ id: 2,
1348
+ name: 'Developer',
1349
+ },
1350
+ ],
1351
+ user_role: [
1352
+ {
1353
+ userId: 1,
1354
+ roleId: 1,
1355
+ },
1356
+ {
1357
+ userId: 1,
1358
+ roleId: 2,
1359
+ },
1360
+ ],
1361
+ });
1362
+
1363
+ const dataSource =
1364
+ createGoogleSheetsDataSource({
1365
+ type: 'google-sheets',
1366
+ client,
1367
+ entities: [
1368
+ User,
1369
+ Role,
1370
+ ],
1371
+ });
1372
+
1373
+ await dataSource.initialize();
1374
+
1375
+ const userRepository =
1376
+ dataSource.getRepository(User);
1377
+
1378
+ const user =
1379
+ await userRepository.findOne({
1380
+ where: {
1381
+ id: 1,
1382
+ },
1383
+ relations: {
1384
+ roles: true,
1385
+ },
1386
+ });
1387
+
1388
+ expect(user).not.toBeNull();
1389
+
1390
+ if (!user)
1391
+ {
1392
+ throw new Error(
1393
+ 'User not found.',
1394
+ );
1395
+ }
1396
+
1397
+ expect(user.roles).toHaveLength(2);
1398
+
1399
+ user.roles = [];
1400
+
1401
+ await userRepository.save(user);
1402
+
1403
+ const saved =
1404
+ await userRepository.findOne({
1405
+ where: {
1406
+ id: 1,
1407
+ },
1408
+ relations: {
1409
+ roles: true,
1410
+ },
1411
+ });
1412
+
1413
+ expect(saved).toMatchObject({
1414
+ id: 1,
1415
+ name: 'Basuni',
1416
+ roles: [],
1417
+ });
1418
+
1419
+ const junctionRows =
1420
+ await client.getRows(
1421
+ 'user_role',
1422
+ );
1423
+
1424
+ expect(junctionRows).toEqual([]);
1425
+
1426
+ await dataSource.destroy();
1427
+ });
1428
+
1429
+ test('replaces ManyToMany relations', async () =>
1430
+ {
1431
+ const client =
1432
+ new Memory({
1433
+ user: [
1434
+ {
1435
+ id: 1,
1436
+ name: 'Basuni',
1437
+ },
1438
+ ],
1439
+ role: [
1440
+ {
1441
+ id: 1,
1442
+ name: 'Admin',
1443
+ },
1444
+ {
1445
+ id: 2,
1446
+ name: 'Developer',
1447
+ },
1448
+ {
1449
+ id: 3,
1450
+ name: 'Manager',
1451
+ },
1452
+ ],
1453
+ user_role: [
1454
+ {
1455
+ userId: 1,
1456
+ roleId: 1,
1457
+ },
1458
+ {
1459
+ userId: 1,
1460
+ roleId: 2,
1461
+ },
1462
+ ],
1463
+ });
1464
+
1465
+ const dataSource =
1466
+ createGoogleSheetsDataSource({
1467
+ type: 'google-sheets',
1468
+ client,
1469
+ entities: [
1470
+ User,
1471
+ Role,
1472
+ ],
1473
+ });
1474
+
1475
+ await dataSource.initialize();
1476
+
1477
+ const userRepository =
1478
+ dataSource.getRepository(User);
1479
+
1480
+ const roleRepository =
1481
+ dataSource.getRepository(Role);
1482
+
1483
+ const user =
1484
+ await userRepository.findOne({
1485
+ where: {
1486
+ id: 1,
1487
+ },
1488
+ relations: {
1489
+ roles: true,
1490
+ },
1491
+ });
1492
+
1493
+ const developer =
1494
+ await roleRepository.findOneBy({
1495
+ id: 2,
1496
+ });
1497
+
1498
+ const manager =
1499
+ await roleRepository.findOneBy({
1500
+ id: 3,
1501
+ });
1502
+
1503
+ expect(user).not.toBeNull();
1504
+ expect(developer).not.toBeNull();
1505
+ expect(manager).not.toBeNull();
1506
+
1507
+ if (!user || !developer || !manager)
1508
+ {
1509
+ throw new Error(
1510
+ 'User or role not found.',
1511
+ );
1512
+ }
1513
+
1514
+ expect(user.roles).toHaveLength(2);
1515
+
1516
+ user.roles = [
1517
+ developer,
1518
+ manager,
1519
+ ];
1520
+
1521
+ await userRepository.save(user);
1522
+
1523
+ const saved =
1524
+ await userRepository.findOne({
1525
+ where: {
1526
+ id: 1,
1527
+ },
1528
+ relations: {
1529
+ roles: true,
1530
+ },
1531
+ });
1532
+
1533
+ expect(saved).toMatchObject({
1534
+ id: 1,
1535
+ name: 'Basuni',
1536
+ roles: [
1537
+ {
1538
+ id: 2,
1539
+ name: 'Developer',
1540
+ },
1541
+ {
1542
+ id: 3,
1543
+ name: 'Manager',
1544
+ },
1545
+ ],
1546
+ });
1547
+
1548
+ const junctionRows =
1549
+ await client.getRows(
1550
+ 'user_role',
1551
+ );
1552
+
1553
+ expect(junctionRows).toEqual([
1554
+ {
1555
+ userId: 1,
1556
+ roleId: 2,
1557
+ },
1558
+ {
1559
+ userId: 1,
1560
+ roleId: 3,
1561
+ },
1562
+ ]);
1563
+
1564
+ await dataSource.destroy();
1565
+ });
1566
+ });